What the panel shows
Three states are rendered, depending on the reader and the composer’s progress:- Ready — a cover-card thumbnail, the brief’s greeting, the day’s thread count, and a Read brief → button that opens the signed magazine URL in a new tab.
- Composing — a soft empty state when the composer has not yet produced today’s brief. The panel auto-refreshes on the next user-visible interaction; no manual retry is needed.
- Locked — a premium-upgrade overlay drawn by the base panel framework when the reader does not meet the PRO gate.
latest-brief; canonical component is src/components/LatestBriefPanel.ts.
How you reach it
- Cmd+K — type brief (matches “Panel: Latest Brief” in the command palette).
- Availability by variant: registered and enabled by default in the full/geopolitical, tech, finance, and commodity variants (all of them as
premium: 'locked'). Not present in the happy variant. Source:FULL_PANELS,TECH_PANELS,FINANCE_PANELS,COMMODITY_PANELSinsrc/config/panels.ts.
Data sources
The panel callsGET /api/latest-brief (Clerk JWT required, PRO gated). The response is either the ready payload (issueDate, dateLong, greeting, threadCount, magazineUrl) or a { status: "composing", issueDate } stub. The signed magazineUrl is generated server-side — the HMAC signing secret never lives in the client bundle.
The brief itself is composed by a Railway job from:
- Curated news aggregates and GDELT signals
- The intelligence, conflict, and markets caches
- Per-reader personalisation if the user has configured preferences
- The composer writes the envelope to
brief:{userId}:{issueSlot}— whereissueSlotis computed byissueSlotInTz(nowMs, userTz), so two same-day compose runs in different time-of-day slots produce distinct keys. - A latest pointer is written atomically alongside:
brief:latest:{userId}→{ "issueSlot": "<slot>" }. Readers (the dashboard panel, the share-URL endpoint) resolve the current brief by reading the pointer first, then fetching the slot-keyed envelope. - TTL is 7 days on both keys; if the pointer is absent, the reader treats the user as having no recent brief.
scripts/seed-digest-notifications.mjs (writer) and api/latest-brief.ts (reader).
Refresh cadence
- Composer: one run per eligible user per day (Railway cron).
- Panel — ready state: reads on panel load; subsequent reads happen only on user-visible refresh triggers. No polling timer while the brief is already displayed.
- Panel — composing state: schedules a 60-second re-poll (
COMPOSING_POLL_MS = 60_000insrc/components/LatestBriefPanel.ts) so the panel transitions fromcomposingtoreadyon the next composer tick without requiring a full page reload. The poll timer is cleared when the panel leaves the composing state.
composing rather than yesterday’s content, and auto-promotes to ready once the composer writes today’s payload.
Tier & gating
Latest Brief is PRO. The panel is registered withpremium: 'locked' in src/config/panels.ts, so free-tier readers see the upgrade overlay and never see the brief body. Verification runs server-side in api/latest-brief.ts — a valid Clerk JWT and an active PRO entitlement are both required; otherwise the endpoint returns 403 pro_required. No content is exposed over the wire for non-PRO readers.
API reference
- AI Brief Endpoints — covers
/api/latest-brief,/api/brief/share-url, the public/api/brief/public/{hash}read, the per-user per-date read, and the social-ready PNG carousel route.
