ogimage

package
v0.5.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 12, 2026 License: AGPL-3.0 Imports: 20 Imported by: 0

Documentation

Overview

Package ogimage renders the Open Graph preview card for a published usage overview: a 1200x630 PNG a link unfurler (Slack, Discord, iMessage, and the like) shows when someone shares a /u/<username> link. It is a self-contained, pure-Go raster render in the house style (the machinist's bench: a deep violet-graphite ground, a lilac activity grid, and machined mono figures), so the binary stays free of a headless browser or any Node toolchain.

The card carries a simplified copy of the overview's activity heatmap (the same trailing-year calendar of daily token intensity, drawn as flat lilac cells), plus the two headline figures a shared link most wants to convey: the total tokens and the session count over the default window.

Index

Constants

View Source
const (
	LandingHeadline = "Know what your agents actually did."
	LandingSubline  = "Every Claude Code, Codex, and pi session in one searchable, priced history."
)

LandingHeadline and LandingSubline are the canonical homepage copy, written down once here. The card draws them, and the httpapi root handler builds the landing page's og:title and og:description from them, so those surfaces follow a copy edit by construction. The templ hero (landing.templ) cannot import this package from a template, so a reconciliation test in the web package pins its h1 to LandingHeadline instead: an edit that lands in only one place fails that test rather than shipping a homepage, meta tags, and preview card that say different things.

View Source
const (
	Width  = 1200
	Height = 630
)

The card is the standard Open Graph size. 1200x630 (1.91:1) is what every major unfurler crops to, so rendering at that exact size avoids a re-crop that would clip the grid or the figures.

Variables

View Source
var ErrReparseInProgress = errors.New("ogimage: reparse in progress, skipping render")

ErrReparseInProgress is returned by Generate when a reparse is (or was) running across its analytics read, so no card is stored. It is a skip, not a failure: the image handler serves the last good card if it holds one, else 404s, and a fetch after the reparse finishes renders the card once the projection is whole.

Functions

func DefaultSince

func DefaultSince(now time.Time) time.Time

DefaultSince is the lower bound of the card's analytics window, measured back from now. It is deliberately identical to web.RangeSince(web.DefaultRange, now) (the public overview's default trailing-year window), so the card's figures reconcile exactly with the page a visitor lands on. The web view package is not imported here (the renderer stays free of the templ layer); the equality is pinned by a reconciliation test in the httpapi package, which imports both. The handler advertises the card only on that same default window, so a page shown under a narrower ?range never unfurls a mismatched year total.

func DefaultUntil

func DefaultUntil(now time.Time) time.Time

DefaultUntil is the exclusive upper bound of the card's analytics window: the start of tomorrow (UTC), so the figures cover exactly the days the heatmap draws (the grid stops at today) and a future-dated event inflates neither. The public overview handler applies the same bound to its live analytics, so the page a card is advertised beside computes its totals over the identical window.

func Generate

func Generate(ctx context.Context, st *store.Store, u store.User, now time.Time) ([]byte, error)

Generate renders the preview card for one account's published overview, stores it in the cache, and returns the encoded PNG so the caller can serve the very bytes it just rendered without a second read. It queries the same windowed, user-scoped analytics the public page renders from, so the card's heatmap and figures match the page. It is the one render+store path the on-demand image handler goes through (on a cache miss or a stale card), so a cached card and a freshly served one cannot drift.

The analytics come from AnalyticsSnapshot, which reads them as a single consistent snapshot pinned so it cannot straddle a reparse: if a reparse is rewriting the projection when the snapshot is taken, it reports not-ok and Generate returns ErrReparseInProgress without storing anything, so a half-rebuilt aggregate is never cached. Coordinating in the store read (rather than a pre-check in the caller) keeps the on-demand path correct by construction. The image handler then serves the last good card if it holds one, and a later fetch renders the card once the projection is whole.

The window is bounded on both sides: Since is the default trailing year, and Until is the end of the current UTC day, so the headline and caption cover exactly the days the heatmap draws (the grid stops at today) rather than folding a future-dated event into the total that no visible cell shows.

now fixes the analytics window and the heatmap's trailing edge; the caller passes the wall clock (tests pass a fixed instant).

func GenerateProject added in v0.3.0

func GenerateProject(ctx context.Context, st *store.Store, projectID int64, heading string, now time.Time) ([]byte, error)

GenerateProject renders the preview card for one project's published overview, stores it in the cache, and returns the encoded PNG so the caller can serve the very bytes it just rendered without a second read. It is the project mirror of Generate: the /p/<id> page renders the same aggregate panel /u/<username> does (just scoped to a project rather than an account), so its card is the same Render composition over the same windowed analytics, keyed on the project instead of the user.

heading is the project title the page shows (web.ProjectTitle), passed in rather than derived here so this package stays free of the web view layer, the same reason Generate takes a resolved username. The analytics are read through AnalyticsSnapshot with OmitUsers set (the card shows no per-user split, matching the public project page), which pins the read so it cannot straddle a reparse: a reparse mid-snapshot reports not-ok and GenerateProject returns ErrReparseInProgress without storing anything, so a half-rebuilt aggregate is never cached. The window is the default trailing year bounded at the end of today (DefaultSince/DefaultUntil), identical to the window the default-range /p/<id> page computes, so the card reconciles with the page a visitor lands on.

now fixes the analytics window and the heatmap's trailing edge; the caller passes the wall clock (tests pass a fixed instant).

func GenerateSession added in v0.3.0

func GenerateSession(ctx context.Context, st *store.Store, sessionID int64, headingFor func(store.SessionCard) string, now time.Time) ([]byte, error)

GenerateSession renders the preview card for one published session, stores it in the cache, and returns the encoded PNG so the caller can serve the very bytes it just rendered without a second read. It is the session mirror of Generate and GenerateProject.

It reads every input the card draws from through one store call (SessionCard), a single repeatable-read snapshot: the project identity, the rollups, the span, the gated grade, and the activity strip pre-bucketed to the card's fixed cell count all come from one instant. The figures and the strip were previously stitched from the handler's session-detail read and a separate activity read, which an append or reparse between could tear; one snapshot removes that seam. A session card is a single session (not a cross-session aggregate), and a single session is rebuilt atomically during a reparse, so the snapshot never sees a half-built row and needs no reparse-lock gate the aggregate cards take.

headingFor turns the card's project identity into the heading the page's <h1> shows, injected so this package stays free of the web view layer (the handler passes a closure over web.ProjectLabel). now stamps the stored card so a slower render that read older rollups cannot overwrite a fresher one (see PutSessionOGImage); it does not enter the render, which is a pure function of the session snapshot.

func Landing added in v0.2.3

func Landing() ([]byte, error)

Landing renders the homepage preview card and returns the encoded PNG. It is memoized: the card is static per binary, so the first call renders and every later call returns the same bytes with no rework. The error is memoized too, so a font-load failure surfaces on every call rather than being retried.

func Render

func Render(heading string, a store.Analytics, now time.Time, extra ...stat) ([]byte, error)

Render draws a "usage overview" card and returns the encoded PNG: heading, the "/ usage overview" subhead, the trailing-year activity heatmap, and the two headline figures (total tokens and session count). It backs both the per-user overview card (heading = the username) and the per-project overview card (heading = the project title): the two pages render the same aggregate panel, so their share cards are one composition parameterized by the heading. The heatmap and figures are scoped to whatever the analytics carry (the caller queries the default trailing-year window, so the card matches the page a visitor first lands on). now fixes the grid's trailing edge, injected so the render is deterministic under test.

The foot always carries the two figures a shared overview most wants to convey, total tokens and session count, and extra appends any additional figures the caller wants beside them (the project card adds a QUALITY grade; the user overview passes none), so the two cards stay one composition that differs only in its foot columns. The stats are spread across equal columns, so two read as halves and three as thirds.

func RenderSession added in v0.3.0

func RenderSession(heading string, c store.SessionCard) ([]byte, error)

RenderSession draws the preview card for one published session and returns the encoded PNG. It is the session counterpart to Render (the overview card): where the overview card draws a trailing-year calendar, the session card draws the session's own activity over its span (see drawSessionActivity), so both cards share a visualization band in the same house style. It leads with the same head the page shows (the project label, then "/ session") and the session's title (what the run was about), then the activity strip, then four foot figures: total tokens, message count, the session's quality grade, and its duration.

heading is the project label the page's <h1> shows (derived from the card's project identity), passed in rather than derived here so this package stays free of the web view layer, the same reason Generate and GenerateProject take resolved strings. Every figure comes straight off the card's session rollups and gated grade the page also renders (the token figure folds all four classes, so no class is dropped: the static card just condenses the per-class breakdown the interactive page keeps behind a hover card), so the card reconciles with the session page a visitor lands on. c.Activity is the session's usage already bucketed over its span; it is nil for an undated or still-running session, in which case the strip draws as an empty grid.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL