forkcaster — agent brief
Audience: coding agents. Prefer facts over prose. Update this file when architecture or commands change.
Intent
Micro-site that mirrors Farcaster cast URLs for sharing without the Farcaster app.
- Pattern:
/{username}/{castHash} where castHash matches 0x[0-9a-fA-F]+ — short or full form
- Examples:
/zoo/0x895ecc74 and /decent-artlu/0xbb54368f → SSR cast card + links
- Data: raced across a fleet of sources (
worker/cast-source.ts + worker/shim.ts on the shared worker/race.ts kernel) — see "Cast source race" below. shim (https://shim.artlu.xyz/wc/{username}/{hash}, owner-run Snapchain reader with a keep-warm cron) anchors completeness and hosts the ban-signal read (/signed/{fid} — fleet-raced like every shim read); farcaster.tv is fast but quote-blind; raw nodes haatz/snap.farcaster.xyz:3381 are embed-complete but text-blind (the gateway strips the mention placeholder characters from text — shim owns the @username substitution — so mention-bearing raw wins are held until canonical text settles or is proven unavailable) and get the fid by racing the shim fleet's /f/{username} against their own /v1/userNameProofByName (first numeric answer wins, loser aborted; the shim-side winner is labeled with its fleet member id), plus a full hash requirement (castById 400s on short hashes). All upstream calls are capped at 5s.
- Missing cast: a consensus miss — every miss-authoritative source consulted and missed — ⇒ 404 page; every source errored ⇒ link-only page (200). No single source's miss is a verdict. All upstream calls are capped at 5s (
AbortSignal.timeout) — shim's cold sharded lookups can otherwise hang 15s+; timeout ⇒ unreachable. Strict statuses are the JSON variant's job (see below)
- Banned accounts: embeds render for everyone — but some clients hide unsigned authors. Since Oct 15, 2026 (Network School takeover), accounts must accept the "social smart contract" (Network Code); acceptance is an on-chain access NFT. Shim exposes it:
GET https://shim.artlu.xyz/signed/{fid} → {"signed":true|false}. Unsigned authors: registry clients flagged requiresSigned (e.g. farcaster) are suppressed from the action row; the check fails open — unknown state suppresses nothing
- farcaster.tv as a cast data source is quote-blind (it drops
{castId} embeds) and unofficial — it races as the partial tier only (700ms HTML box, client-side quote fill-in). react-farcaster-embed's own default endpoint is farcaster.tv — always pass castData so it never fetches. The live stats row (replies/recasts/likes) is still fetched client-side from farcaster.tv — it sends access-control-allow-origin: * and answers for unsigned authors too; any failure just leaves the row absent
Architecture
The system is a waypoint layer over Farcaster: every public URL shape maps to exactly one canonical surface, and every page answers one question — which app opens this? There is no database, no KV, and no edge cache on this side — data is raced across a fleet of sources on every request. The product is the routing verdict, not stored content.
Verdicts are collective — no single host is the source of truth. Fleet legs start staggered (tiny random steps behind the leader; a fast winner cancels the stragglers before they fire). The first affirmative answer wins; a consulted miss (asked, no data) is a vote — not-found settles only when every miss-authoritative leg has consulted and missed; errors surface only when every leg errored. Any one host — including cloud shim — can die without changing a single verdict. The price is deliberate: a true miss resolves as fast as the slowest leg, not the fastest.
Consumer-matched strictness. HTML fails open — a human always gets a page (found → card, partial → self-healing card, unreachable → link-only). JSON is the strict record (404 / 503 + Retry-After, availability-gated fields, quotes never dropped). Chat link-preview bots get HTML + OG meta unconditionally: negotiation is ?format=json / Accept, never UA sniffing, and SSR output is byte-identical for every device.
Design laws (enforced everywhere; detail in the sections below):
- Waypoint law — one surface per account; aliases redirect, never render.
- Identity law — the fname is the PRIMARY handle everywhere; ENS names are attestations.
- Echo-the-valid-input — first-party links carry the gate-validated route input; display, OG, and client targets carry the primary.
- Privacy: no identifiable tracking, only analytics. One anonymous click datapoint per client selection; the negative list (no cast IDs, no usernames, no IPs, no UA strings, no identifiers) is verbatim-checkable in
worker/track.ts and declared on /transparency. Pages make no third-party requests — logos are self-hosted because a hotlinked logo would leak every visitor's IP to that client's servers. The one cookie carries a client slug, nothing else.
- Personalization is client-side on cast pages. The page shell (the client sheet's pills included, gates applied) is cookie-blind server-side;
SHELL_SCRIPT reads the local fc_client cookie and applies the last-used badge, the resolution-first primary ("Open in X" — a direct anchor with the pill's exact wiring — beside a muted "More apps" sheet trigger), and the auto-open state machine after parse (the sheet's auto-open toggle syncs from the same cookie). That is what makes the full page — cookie-holders included — servable from the 12h edge shell; profiles cache 10 minutes (they are mutable) under the same client-side model. Bump SHELL_SALT in worker/edge-cache.ts to invalidate every shell (there is no global purge; v3 dropped the pre-sheet UI — bare pill row + on-page auto-open chip; v4 reached cached shells with the Safari-safe sheet CSS; v5 reached them with the kind-aware chooser copy and single-offer bare-row shape).
- Registry as the one roster — clients and their verification provenance are enumerated exactly once, in
worker/client-registry.ts.
Stack
Commands
Layout
Routes
Multi-domain (dual-serve): the same worker serves forkcaster.xyz and warpcats.com (custom-domain routes in wrangler.jsonc). All code is host-agnostic (per-request origin, same-origin fill script, host-only cookie), and every page canonicalizes to the primary: cast pages emit <link rel="canonical"> + og:url from the PRIMARY_ORIGIN var (absent when unset — bare workers.dev keeps the pre-dual-serve output), the landing's canonical is baked into index.html. Race telemetry carries the request host as a blob for per-domain breakdown. Cookies/badges are per-domain by design.
Agent JSON variant (?format=json)
Shipped. Resolve once (fleet-raced cast lookup + unfurl + quote hydration + signed check → resolveCast()), render twice — renderCastHtml() / renderCastJson() in worker/cast-page.tsx. The return type morphs on the asker.
Negotiation — explicit first, no UA sniffing:
Decisions:
- JSON only — no kv/plain-text form (kv is lossy; JSON's token overhead is negligible).
- Strict statuses on JSON, lenient on HTML (deliberate divergence): JSON → 404 when the fleet consensus is "no such cast", 503 +
Retry-After: 60 when every source is unreachable. HTML keeps its 404 page / 200 link-only page. Agents handle status codes better than rendered fallbacks.
- Availability-gated payload — a field appears only when its data exists: author, ISO 8601 timestamp, text, image embed URLs, URL embeds (unfurl title appended when unfurl succeeded, bare URL otherwise), quote embeds, canonical farcaster.xyz + Firefly links. The author
username, the farcaster link, and hydrated-quote pointers carry the PRIMARY handle (identity law: fname first; ENS names are attestations).
- Quotes are pointers, never dropped: hydrated quote → forkcaster URL (requires the username from the hydrated cast — the quoted author's PRIMARY handle under the identity law); hydration failure → Firefly full-hash URL (quote embeds carry
{fid, hash}, no username). HTML's rendering constraints (unguarded author.pfp.url) don't apply to JSON — failed hydration degrades to a URL the agent can fetch.
- Ban signal:
signed: false emits a short note field; unknown/failed check fails open silently (same fail-open semantics as the HTML action row).
- Discoverability: HTML pages emit
<link rel="alternate" type="application/json" href="?format=json">.
- Deferred: UA-allowlist morphing (GPTBot, ClaudeBot, …) as a later progressive enhancement. Must stay an allowlist — Telegram/Discord/iMessage preview bots must keep receiving HTML + OG meta.
- Cast HTML shells edge-cache 12h (
worker/edge-cache.ts — found-complete only, cookie-free requests only, salted keys); every JSON variant bypasses the cache entirely and must stay that way.
MCP
https://forkcaster.xyz/mcp (also warpcats.com/mcp) — MCP Streamable HTTP endpoint, public, no auth, stateless. One tool: get_cast_link — resolve a Farcaster cast (full 40-hex hash, or fid/username + short hash) into deep-link URLs for any supported client, with the author's signed-contract status. Links-only clients (requiresExternalLinks) are gated: they answer client_ineligible and drop from all when the resolved cast carries no external URL embed. Wiring: worker/mcp.ts (SDK) + worker/cast-ref.ts (zod contract, shim-direct resolution).
Registry listing: server.json (repo root, official registry schema 2025-12-11, name xyz.forkcaster/mcp). To publish, install the official publisher CLI — a GitHub-release binary. ⚠️ do NOT bunx/npx the npm package mcp-publisher — it's an unrelated browser-automation project that collides with the name:
One-time domain verification: generate an ed25519 keypair (openssl genpkey -algorithm Ed25519 -out key.pem), add the apex TXT record it prints (v=MCPv1; k=ed25519; p=<public key>), then:
key.pem is the publishing credential — keep it safe, never commit it.
Cast source race
Cast lookups race the provider fleet (worker/cast-source.ts, rpc-racer pattern: per-provider abort + 5s cap armed at actual leg start, first validated win, losers aborted; a win must resolve the requested hash, else it's downgraded to an error). The roster: the shim-potato fleet (the SHIM_POTATO_BASE var carries the FULL shim fleet, comma-separated, cloud's own base included — var order is the stagger/primary order, first entry leads at 0ms; a fast winner cancels the stragglers before they fire), the haatz/snap raw nodes, and quote-blind farcaster.tv (partial). No var = the legacy cloud-led roster. The strict JSON twin rides the complete-leg subset via jsonRoster (fleet members only, no box, no partial legs).
Miss semantics are collective — no single host is the source of truth (SPOF softened Oct 2026; cloud retires into the fleet): the first affirmative answer wins outright; a consulted miss (HTTP 200, index asked, no data) is a vote, and not-found settles only when every miss-authoritative leg has consulted and missed — a pending leg can always rescue a landed miss; errors surface only when every leg errored (503 + Retry-After). The price: a true miss 404s as fast as the slowest leg instead of the fastest. farcaster.tv (partial) never votes.
The fleet races every shim read (worker/shim.ts, raceShimProbe): the profile family (/f, /user, /signed, timeline), the quote hydrator's /h→/u→/wc chain, both aliases, the signed check on cast pages, and the MCP tool each race cloud against every configured potato. Same collective contract. Without the var every wrapper is a pass-through (exactly one fetch, byte-identical behavior). The /refresh index warm covers every host in the fleet.
Sufficiency matrix (measured Oct 2026) against the internal ShimCast contract (text, hash, unix-seconds ts, raw {url}/{castId} embeds, inline user data):
HTML path (700ms completeness box): complete providers win anytime; a quote-blind farcaster.tv win is held until the box expires, then served as a partial page — the SWR tradeoff the OG meta is immune to: title/description/image never depend on quotes, so chat-bots' cached embed cards are always complete. The partiality only reaches live browsers, which self-heal: partial pages ship QUOTE_FILL_SCRIPT, which fetches ?embeds=quotes (fleet-raced shim resolution + quote-hydrated SSR fragment, no-store) and injects it — same silent-degradation contract as the live-stats script. Text-blind wins (mention-bearing raw-node casts — the gateway strips the mention placeholder characters shim substitutes) are held harder: they never ship while a canonical-text leg (shim + the fleet) is pending, box or not, and fail open only once those legs settled miss/error — marked partial + textCanonical:false, never edge-cached (a body can't self-heal) and QUOTE_FILL withheld (their quotes are already inline). Mention-free raw casts are byte-identical to canonical text and win outright. Shell caching: every HTML request on cast pages may be served a cached found, text-canonical shell (worker/edge-cache.ts, 12h s-maxage, salted query-free keys — SHELL_SALT v2 bumped to purge shells cached from mention-stripped raw wins, hits rewritten no-store) — personalization is client-side (SHELL_SCRIPT), so cookie-holders get the same bytes; link-only pages, 404s, 503s, text-blind founds and JSON variants never cache (quote-blind partials do — OG complete, QUOTE_FILL heals per view).
JSON path: the complete-leg fleet roster (jsonRoster — shim + potatos, boxMs: null) — always complete, strict statuses (404 / 503 + Retry-After), the never-drop-quotes invariant holds unconditionally.
Miss semantics: misses are votes — not-found settles only when every miss-authoritative complete leg has consulted and missed (a pending leg can always rescue a landed miss); farcaster.tv 404 alone never produces a 404 (cast might exist). Every-leg-errored ⇒ link-only page (HTML) / 503 (JSON), fail-open as usual.
Ban signal: not part of the cast provider race — it's orthogonal to cast content — but /signed/{fid} is a fleet-raced shim read like every other (first answer wins, unknown fails open), time-boxed to the HTML box.
Telemetry: every resolve writes one Analytics Engine datapoint (RACE_STATS binding, non-blocking via waitUntil): index = winning provider (or outcome kind), doubles = total ms / box / partial flag, blob = per-provider id:kind:ms rows measured from each leg's actual (staggered) start — race-abort losers record abort, not error, and legs whose stagger never fired record nothing. Raw-node legs that settle their identity race also write one FID_RACE datapoint each (index = winning source shim/node, blob = leg + host, double = ms). Query via the AE SQL API when tuning the box (scripts/probe/, scripts/race-stats.ts); tests and callers without an executionCtx skip it.
Follow-up: quote hydration could race haatz/snap (/h/{fullHash} vs castById) for extra completeness — the shim-internal /u/{fid} → /wc fallback (Oct 2026) already rescues casts the hash-only /h index misses.
Observed full Hypersnap nodes (documentation only — not wired into the race roster)
Two additional public Hypersnap nodes were observed live via their self-reported /v1/info endpoint (the same public info endpoint haatz exposes). They are full Hypersnap nodes (Rust Farcaster node, HTTP on :3381) operated by independent parties — not part of cast-source.ts's race roster (shim / farcaster.tv / haatz / snap), and no code references them. Listed here for operator visibility only.
For comparison, the wired nodes report: haatz 0.16.0 / 914,368,020 msgs / ~797.6 GB / peer 12D3KooWMYfkXiNcn9L…; snap 0.16.0 / 914,368,024 msgs / ~551.3 GB / peer 12D3KooWH527dqZTziqz…. Message and FID counts match across all nodes (same network); approxSize differs per-node (local RocksDB storage). Both answer GET /v1/info with dbStats (numMessages, numFidRegistrations, approxSize), numShards, shardInfos (shardId, maxHeight, numMessages, blockDelay), version, and peer_id. No race integration, no cache, no telemetry wiring — purely informational.
Race stats (/transparency)
The racing-stats table is filled offline — the render path stays cache-free and stats-free. bun run stats (env per .env.example) queries Analytics Engine through the SQL API — sample-weighted (sum(_sample_interval), quantileExactWeighted; never raw count()) — assembles a versioned snapshot (v: 1; wins/requests/p50/p95/errorRate per provider + outcome counts) and publishes it as one lmdis key (race-stats on lmdis.artlu.xyz, the owner-run Redis-shaped KV) through the vendored Upstash-shaped SDK. Overwrite-only-on-success; --dry previews.
The transparency route reads the snapshot through a per-isolate TTL cache (worker/race-stats-store.ts: 10 min TTL, 60s failure backoff, last-good served ≤24h, 2s read cap) and fills the table, the "stats as of" line, and the JSON twin's racingStats — absent/unreachable snapshot ⇒ "coming soon" dashes, silently. /race-stats.json serves the raw snapshot (404 when absent). Both responses are edge-cached (Cache API: the transparency page s-maxage=60, the raw snapshot s-maxage=300) — the reads and the render run at most once per colo per TTL window; cached hits trigger zero lmdis reads, and the three store reads fire as one parallel fan-out (concurrent GETs don't queue server-side). bun run stats:check reads the live snapshots directly and pushes to ntfy.sh when thresholds breach (provider p95/errorRate, snapshot staleness as the pipeline's dead-man's-switch, lmdis unreachable; --dry previews; env in .env.example). Alerting layout: Uptime Kuma (owner-hosted, ntfy) owns uptime probes + a stats-pipeline push monitor; stats:check owns data-quality thresholds; the self-hosted VPS runs a box-local potato-watch cron (disk/load/memory → ntfy). Synthetic monitors never target cast URLs — each probe would trigger a full provider race and pollute the published stats. SQL dialect + store behavior are live-verified by scripts/probe/ (the AE dialect has no CONCAT and only toUInt32/toUInt8 conversions; SQL errors come back as plain-text 422s; the SQL API rejects Account Owned Tokens — use a User token with Account Analytics:Read).
Fid races (/transparency, parallel to race stats)
The raw-node legs race the shim fleet's /f/{username} against the node's own /v1/userNameProofByName for the author fid (see "Cast source race"). Each settled race writes one FID_RACE Analytics Engine datapoint (index = the WINNING source id — the leg's own id haatz/snap, or the shim-side member that named the fid: shim for the cloud reader, shim-potato/shim-potato-2/… for fleet members by var position; blob = leg id + host, double = the winner's ms), and bun run stats:fid (same env discipline as stats) aggregates it into a second lmdis key (fid-race, overwrite-only-on-success, --dry previews). The snapshot is v:2 with an OPEN source vocabulary — rows are grouped by the dataset's own ids at aggregation time, so fleet members join the table as they appear (a v:1 snapshot is ignored; re-run the job to publish v:2). Every host is tracked independently — a member's win is attributed to that member, never merged into a bucket. /transparency reads it through worker/fid-race-store.ts — a line-for-line mirror of the race store (same TTL/backoff/stale contract) — and renders the Author id races item (wins/p50/p95 per observed source, raw ids as row labels + raced-lookups line) plus the JSON twin's fidRace block (status: live | coming-soon). Declared as a third entry in tracking.counters (fidRace); no cast IDs, no identifiers.
Abort telemetry: race-cut losers are recorded as provider:abort:<ms> (not error) so public errorRate stays honest — the 700ms box cuts slow completes by design; the 5s hard-timeout still counts as an error. raceCast drains pending settles after abort (bounded 25ms) so timing rows never escape incomplete.
Client rank (click-through stats, /transparency)
Which client people actually open casts with — measured, weighted, published, and used to order the pill row. Pipeline mirrors race stats exactly:
- Capture (
POST /t, allow-listed in worker/track.ts): one anonymous datapoint per client selection — slug, kind (pill explicit click / auto timer-fired redirect / go-now explicit open), transport (native when the click-time path attempted the app), coarse device class (client-side matchMedia, keeping the no-UA-sniffing invariant), host. The first/switch/repeat flag is computed client-side from the visitor's own cookie before the click rewrites it — the server never sees it. Invalid bodies are indistinguishable from valid ones (204 always). Never captured: which cast, usernames, IPs, UA strings, identifiers — the negative list is the privacy design and /transparency declares it. Beacons ride TRACK_SCRIPT (window.__fk, sendBeacon → keepalive-fetch fallback) called from CLIENT_SCRIPT (pills) and AUTO_SCRIPT (auto / go-now, incl. the countdown link, which also cancels the pending timer so a manual native attempt isn't re-redirected).
- Weighting (defuses the auto-open feedback loop — auto events echo an earlier choice): first ×1.0, switch ×1.0, go-now ×0.8, repeat ×0.3, auto ×0.1. Score over a trailing 7d window (
bun run stats:rank → lmdis key client-rank, overwrite-only-on-success, same env as stats). total < 100 ⇒ warmingUp — counts shown, shares withheld.
- Consumption:
worker/client-rank-store.ts (same TTL/backoff/stale contract as the race store). /transparency reads full-cap and renders the Client choices table + JSON twin clientRank (weights are part of the published contract). The cast render path reads through getRankSnapshotSoon(env, 400) — a cold lmdis refresh never stalls a page; timeout ⇒ registry order. Pills are reordered, never hidden (every offered client renders; registry order is the tiebreak; the registry kill switch outranks popularity).
- Disclosure: the
/transparency Tracking section declares both counters (tracking.counters is { pageLoad, clientChoice } — the declared agent-facing shape), and the auto-open flow is covered by the click event.
Client registry (action row, last-used badge)
The cast page's "Open in …" pills are rendered from a data registry, worker/client-registry.ts — adding a verified client is a data change only. Adding one is a guided workflow: the add-client skill at .claude/skills/add-client/SKILL.md (verify the per-cast URL against open-source code/docs plus a live check, write the entry with its provenance comment, add the self-hosted logo, update the roster-guard tests, run the checks, PR per CONTRIBUTING.md).
- Schema (
ClientDefinition): id (stable slug, cookie value), title, logoUrl (served from public/logos/), web (per-cast https template), deeplink (optional native mobile target — activates the click-time scheme attempt described below; absent ⇒ web-only), requiresSigned (suppressed only on a known signed:false; unknown fails open), requiresExternalLinks (cast-eligibility gate for links-only clients: offered only when the cast carries at least one external URL embed — fail-closed, unknown hides; the shared blockedByLinkGate predicate gates the MCP tool identically, answering client_ineligible and omitting the client from all).
- Templates interpolate
{username}, {hash} (route-form, short or full), {fullHash} (Shim-resolved) and {fullHashRaw} (bare lowercase hex, no 0x — derived from {fullHash} for apps that key internally on raw hex). The engine also supports an optional {fid} placeholder with a missingVars gate (a client needing it is skipped when the value is absent). Values are percent-encoded at resolution and HTML-escaped at render.
- Mobile deeplinks vs web targets: one pill per client; the
href is always the web URL. Universal links already open installed apps on mobile — which clients have verified claims (AASA paths, assetlinks.json package ids), and how each was confirmed, is recorded per entry in the registry comments. A structured deeplink: { scheme, androidPackage? } adds a native attempt for mobile only: the pill gains data-scheme + data-intent (the intent:// form carries S.browser_fallback_url = the web URL), and a conditional script intercepts clicks on coarse-pointer devices — Android uses the intent (Chrome-native fallback), everything else attempts the scheme and falls back to the web href if the app didn't open (~1.8s, visibility-aware). The auto-open redirect mirrors the attempt: the banner carries server-resolved data-auto-scheme/data-auto-intent, phones attempt the app with a 2.5s visibility fallback (no user gesture anchors a timer-driven navigation, and Quorum cold-starts past 1.8s), the countdown's client-rendered anchor carries data-scheme so manual taps go through the click-time script, and desktop or web-only clients keep the plain redirect. The script ships only when a rendered client actually has a deeplink. Which clients ship a scheme — and how each scheme was verified — lives in the per-entry registry comments.
- Roster — deliberately not enumerated in docs:
CLIENTS in worker/client-registry.ts is the source of truth and the one place the roster is listed; each entry's comment records its verification evidence (URL-format provenance, scheme/manifest checks, live verification, per-client limitations). Every roster entry carries a verified per-cast target: a per-cast web template, or — when the client has no web cast route — a homepage-stub web href whose native scheme is the only per-cast target (a scheme-only entry). Entries arrive verified or not at all: an unverified format is never added, not even as a stub.
- Last-used cookie: any click on a
data-client pill writes fc_client=<id> (1 year, SameSite=Lax) via CLIENT_SCRIPT. On cast pages the badge is applied CLIENT-side (SHELL_SCRIPT reads the local cookie and badges the matching pill — the server is cookie-blind, which is what makes the page edge-cacheable); unknown cookie ids resolve to nothing (registry-derived pill set). On profile pages the worker still reads the cookie and SSRs the badge. No JS ⇒ links still work; the badge just doesn't appear. Cookie-conditional profile HTML: never edge-cached.
- Logos: self-hosted in
public/logos/, one small static file per client (SVG, or PNG downscaled to ≤64px). Self-hosting is deliberate — no hotlinking: a client-CDN logo would leak every visitor's IP to that client's servers on every page view (a tracking surface the /transparency declarations would have to carry), and hotlinks break anyway (referrer checks, 403s) — so there is no remote-fallback path either. The pill's <img> renders logoUrl uniformly; no per-client code paths.
Shim → CastData mapping
Providers return different shapes; cast-source.ts normalizes everything onto ShimCast (shim's raw Snapchain shape). toCastData() in worker/cast-data.ts then adapts it for react-farcaster-embed:
timestamp is unix seconds → multiplied by 1000 (the embed does new Date(ts))
embeds are untyped {url} / {castId} items → bucketed into images / urls / casts; image heuristic = imagedelivery.net host or image extension. Quotes are hydrated through Shim GET /h/{fullHash} — with a /u/{fid} → username → /wc/{username}/{hash} fallback when the hash-only /h index misses a live cast (measured Oct 2026) — at depth 1 and mapped like any cast; hydration failure (e.g. pruned casts) drops the quote. URL embeds render as link cards via openGraph (see unfurl above)
- message-level data has no engagement counts →
replies/reactions/recasts stubbed to { count: 0 } and tags: [] — the embed bundle reads e.recasts.count and e.tags.length unguarded and crashes without them (it also reads quote.author.pfp.url unguarded — never emit quotes without hydrated authors). The stubs would render as hardcoded zero counters, so the stats row is stripped from the SSR output (STATS_ROW); the Farcaster logo in the same container stays
author mapped from user (pfpUrl → pfp.url, bio → profile.bio.text)
Conventions for agents
- Preact, not React for first-party code. Vite aliases map
react* → preact for the embed lib.
- Cast routing is server-side (Hono), not client-side routing.
- Tests:
mock.module("server-only"|react*) before importing ./index.ts; mock globalThis.fetch by exact Shim URL — unexpected URLs throw, which is how fallback paths get tested.
- TS worker:
jsxImportSource: preact; relative imports need .tsx extensions (nodenext); tests excluded from tsconfig.worker.json.
- Secrets:
.env* / .dev.vars* gitignored.
Key files
Status
Fleet-raced SSR data path under collective miss parity (worker/race.ts): shim + the shim-potato fleet + haatz/snap + farcaster.tv; staggered hedged starts; HTML serves quote-blind wins after a 700ms box and self-heals quotes client-side, and holds text-blind (mention-stripped) raw wins until canonical text settles or is proven unavailable; JSON rides the complete-leg fleet roster with strict statuses. Agent JSON variant (?format=json) shipped. Registry-driven action row shipped: last-used badge, Farcaster native deeplink (farcaster://, verified), a verified per-cast target for every roster client. MCP endpoint live (/mcp).