component varians and improved generalization
add margin note support
tangled repos support
tab complete mentions
Fix final-review findings on comment mentions/links feature Four fixes from the whole-branch final review before merge: 1. richtext.ts: allowlist URI schemes (http/https/mailto/at) on facet-derived hrefs before interpolating into <a href>. Comment facets now come from arbitrary DIDs via Constellation backlinks, so a javascript: URI in a #link/#webMention facet was a live XSS vector. 2. Comments.svelte: carry a composer's confirmed mentions through the OAuth scope-upgrade redirect draft (sessionStorage doesn't preserve a Map, so serialize/deserialize as [handle, did][] entries) — previously every mention was silently dropped on a visitor's first comment in a lexicon, the common case since the nav bar only grants base scope. 3. commentFacets.ts: dedup overlapping facets produced by prefix-colliding confirmed handles (e.g. "bob.com" / "bob.com.br"), keeping the first match in any overlapping run — overlapping facets are invalid per the AT Protocol facet spec. 4. commentFacets.ts: require a real host after the URL scheme before emitting a link facet, so a bare "https://" typo no longer produces a facet with a meaningless URI. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Wire MentionTextarea and RichText into Comments.svelte Threads per-composer mention state through attemptPost, fixing the postComment call-site mismatch Task 5 introduced (now `postComment(session, ctx, text, mentions, replyTo?)`), swaps the composer's raw w1c-textarea for MentionTextarea (Task 6), and renders comment bodies through RichText so facets (mentions/links) actually render instead of plain text. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Add MentionTextarea.svelte for comment @mention typeahead Wraps w1c-textarea with an @mention autocomplete dropdown, anchored at the "@" character's pixel position via the mirror-div caret-measurement technique. Consumes actorSearch.ts's searchActors (Task 1) and mentionTrigger.ts's pure detectTrigger/applyMentionSelection (Task 2). No caller yet — Task 7 wires this into Comments.svelte. CSS lives in global.css rather than a scoped <style> block, since this component is used inside Comments.svelte, shared between the SSR'd and live-fetch mount paths that scoped Astro styles don't reach. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Task 5: Read and write facets in comments.ts Add facets field to Comment interface and read facets back from all three fetch functions (fetchLeafletComments, fetchPcktComments, fetchBlueskyComments). Thread mentions parameter through postComment function signature and compute real facets for all three lexicons (leaflet, pckt, bluesky) using the adapter functions from commentFacets.ts (Task 3). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add support for rendering blog.pckt.mini.post mention/link facets Extends renderRichText to handle blog.pckt.mini.post#mention and blog.pckt.mini.post#link facet types, enabling comment composer preview to properly render @mentions and URLs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Fix commentFacets.ts: replace as-any-as casts with l.$Typed casts The three toXFacets() functions each ended their .map() with a blanket `as any as X[]` cast, bypassing type-checking entirely. The real error without the cast: the generated `features` array element type is a union of `l.$Typed<X>` (X & { $type: <required literal> }), not the bare defs type X (whose $type is optional) — so a hand-constructed literal cast `as LeafletDidMention` didn't structurally satisfy the stricter required-$type member type. Casting each constructed feature object as `l.$Typed<X>` instead of the bare type fixes this, so the .map() return typechecks against Main[]/Facet[] with no cast needed on the array itself. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Implement commentFacets.ts — lexicon-agnostic facet computation. Compute UTF-8-byte-offset facets from plain text with confirmed @mentions and auto-detected URLs, then convert to each of the three comment lexicon shapes (pub.leaflet.comment, blog.pckt.mini.post, app.bsky.feed.post). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Implement pure mention-trigger logic for @mention detection Add detectTrigger() to identify when cursor is inside an "@mention" being typed, and applyMentionSelection() to compute text/cursor after picking a suggestion. Both functions are DOM-free and easily testable. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extract shared actor search to actorSearch.ts Refactor the TypeaheadActor type, ENDPOINT constant, and searchActors function from handleTypeahead.ts into a new shared actorSearch.ts module. Make the limit parameter optional with a default value of 8 to maintain backward compatibility with the existing call in handleTypeahead.ts. This extraction enables the mention-in-comments typeahead feature (Task 6) to reuse the same actor search implementation without duplication. - Create src/lib/actorSearch.ts with TypeaheadActor interface and searchActors function - Update src/lib/handleTypeahead.ts to import from the new module - Verified: astro check (110 errors, no change), npm run build (164 pages), API endpoint, page load Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Fix off-by-one byte/cursor offsets in plan verification scripts Pre-flight scan caught 5 hand-arithmetic errors in Task 2/3/4's throwaway test expectations (cursor and byte-range literals), verified correct via real node -e runs before dispatching Task 1. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add spec and implementation plan for comment mentions/links Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
blog engagement improvements
blog engagement updates
avatar favicon
add /contact
sitemap cleanup
/about page
leaflet doc support
new address bar prefix
oops more currents.is support
currents.is support
generalized collection routes
add blog post recommends page
flicker fix
scrobbles page
updated teal to prod lexicon
add top8.sites component
Wire up top.8sites.picks as a real lexicon/ component Real end-to-end test of the unified dispatch mechanism for a brand new NSID: lexicon/top/8sites/picks.svelte (auto-discovered by lexiconRegistry.ts's glob) + a new, trivial live-fetch loader (top8sitesPicks.ts — no blob refs, no further async work needed, unlike bskyPost.ts/cosmikCard.ts) registered in loaders/index.ts's `loaders` map. No content.config.ts collection exists for this NSID (never statically built), so `components` picking this up automatically (derived from lexiconRegistry since Phase 6) is what makes this whole addition just two new files plus one loaders-map entry — no manual component-table wiring needed anywhere. Real record confirmed on the account's PDS (rkey always "self", per the lexicon's own l.record('literal:self', ...) constraint): 8 ranked domain picks. Renders as a simple ranked list, same plain-list styling convention as .background-list/.card-notes. Verified with a real headless-browser check against http://localhost:4321/top.8sites.picks/self: HTTP 404 (genuinely live-fetch, no static page), real title "Top 8 Sites — tynanpurdy.com", real h1, all 8 real domains rendered in correct rank order, no raw-JSON GenericRecord fallback. astro check/build baseline unchanged (109 errors, 156 pages — this NSID never becomes a static page). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Fold pub.leaflet/app.offprint's own content dispatchers into lexicon/ The last real exception to "one set of lexicon components for everything" — pub/leaflet/Content.astro + ContentLive.svelte and app/offprint/Content.astro + ContentLive.svelte still lived at their original pre-migration paths. Relocated to lexicon/pub/leaflet/ content.{astro,svelte} and lexicon/app/offprint/content.{astro,svelte} — the same "two files, one NSID, one directory" shape document.astro/ document.svelte already established, registry.ts's contentRenderers playing the same explicit-import role islands.ts plays for document.astro (neither is glob-discovered; both are physically co-located with their NSID's live-fetch half regardless). This surfaced a real, previously-latent precedence bug: the top-level Content.astro checked lexiconRegistry before registry.ts's contentRenderers — harmless while contentRenderers' two NSIDs had no lexiconRegistry counterpart to conflict with, wrong the instant content.svelte became glob-discoverable under the same NSID (would silently swap the full-fidelity Astro renderer for the narrower live-fetch one on the static path). Fixed by flipping the precedence (contentRenderers first, same order [collection]/[rkey].astro already uses for islands over lexiconRegistry) and verified as a true no-op before relocating anything (byte-identical output for the real leaflet document with an embedded post). document.svelte's own local contentRenderersLive table is deleted outright, not given the same precedence-flip treatment — once both content.svelte files live under lexicon/, that table pointed at the exact same files lexiconRegistry already resolves to, pure redundant duplication with no fidelity difference to arbitrate on the live-fetch path. Verified thoroughly: astro check/build baseline unchanged (109 errors, 156 pages); byte-identical rendered output for the real leaflet and offprint documents on the static path both before and after the relocation; a real live-fetch browser check on the leaflet document with an embedded post confirms all 15 real bskyPost blocks are accounted for (12 rendered live, 3 legitimate "post no longer available" fallback links, zero page errors) — the narrower live-fetch component still works correctly after losing its local dispatch table. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Delete the unimplemented top.8sites.picks stub src/components/top/8sites/Picks.astro was a 0-byte placeholder with zero references anywhere in the dispatch system (not even the old registry.ts tables) — genuinely unbuilt, not a relocation candidate. Removed per explicit direction rather than left as dead weight; falls through to the generic JSON viewer like any other unwired NSID until someone actually designs and builds it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Update CLAUDE.md + spec for Phase 6's full lexicon/ unification CLAUDE.md still named the old file paths (DocumentPage.astro, DocumentPageContent.svelte, bsky/PostCard.svelte) and described the network.cosmik/app.bsky.feed.post fold-in as "deliberately left open." Spec: records Phase 6 as executed further than item 6's literal "retire src/lib/render/loaders/" wording suggested — the real goal ("one set of lexicon components for everything") only required retiring the components half of that file, never the loaders half (genuinely different, live-only data-fetching logic with no static equivalent). Also corrects a stale "Decisions from spec review" bullet that predicted lexiconRegistry.ts would eventually merge into registry.ts — it doesn't, since registry.ts's contentRenderers table is now understood to be permanent (Phase 2's correction), not something a later phase empties out. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Fold remaining live-fetch-only NSIDs into src/components/lexicon/ Every NSID-dispatched component now physically lives under src/components/lexicon/, mirroring its NSID, exactly like content blocks and the whole-record NSIDs from Phase 5 — "one set of lexicon components for everything," not some living under src/components/pages/, src/components/bsky/, src/components/network/ cosmik/ as ad hoc exceptions: - app.bsky.feed.post: bsky/PostCard.svelte -> lexicon/app/bsky/feed/ post.svelte - network.cosmik.card: network/cosmik/Card.svelte -> lexicon/network/ cosmik/card.svelte - network.cosmik.collection: network/cosmik/CollectionView.svelte -> lexicon/network/cosmik/collection.svelte - site.standard.document (live-fetch-only counterpart): DocumentPageContent.svelte -> lexicon/site/standard/document.svelte - site.standard.document (static, Astro-only): pages/DocumentPage.astro -> lexicon/site/standard/document.astro, still registered in islands.ts (not auto-discovered — lexiconRegistry's glob only picks up *.svelte — but now physically co-located with document.svelte under the same NSID's directory, same pattern every hydrating block's *.svelte + *.island.astro pair already uses) src/lib/render/loaders/index.ts's `components` map is no longer a hand-maintained table of imports duplicating what lexiconRegistry.ts already knows — it's now derived directly from lexiconRegistry for every NSID in `loaders`. `loaders` itself (the real, live-only data-fetching logic) is unchanged and stays separate, genuinely different work from component dispatch. Applied the standing $derived conversion to each relocated component's top-level consts reading `data`/destructures of it, matching every other file this migration has touched. Verified against real content, both paths: the static cosmik.card list page (44 real w1c-panel renders), a real site.standard.document page, the one real leaflet document with an embedded Bluesky post (island still resolves correctly), and the sifa CV page all render correctly. Real headless-browser checks forcing the live-fetch path confirm network.cosmik.card and network.cosmik.collection (including its nested Card renders) both work end to end through the new lexiconRegistry- derived dispatch, zero page errors. astro check/build baseline unchanged (109 errors, 156 pages). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Update CLAUDE.md for Phase 5's whole-record dispatch + blockRenderers retirement Component conventions / permanent-Astro-boundary / Live-fetch layer sections still described blockRenderers as "empty" and pageRenderers as "pending" (both fully retired now), and didn't mention id.sifa.profile. self/site.standard.document having moved onto the unified dispatch pipeline (Phase 5) or DocumentPage.astro's new islands.ts registration. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Retire registry.ts's dead blockRenderers table Empty since Phase 4 (both families that ever used it, pub.leaflet and app.offprint, fully migrated to lexiconRegistry.ts + LexiconBlock.astro in Phases 2-3), and confirmed via grep to have zero real consumers left anywhere in the codebase — genuinely dead, not just currently unused. registry.ts now holds exactly one table, contentRenderers, with its two permanent entries. Fixes four stale comment references to the old contentRenderers/ blockRenderers/pageRenderers triple (GenericRecord.svelte, recordViewer.ts, lexiconRegistry.ts, loaders/index.ts) — both blockRenderers and pageRenderers are gone now, not just "empty"/ "pending". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Port whole-record NSIDs onto the unified dispatch pipeline (Phase 5) id.sifa.profile.self becomes a flat lexicon/ entry (src/components/lexicon/id/sifa/profile/self.svelte) — no Astro dependency, no hydration, exactly like every other clean-case NSID in this migration. site.standard.document registers in islands.ts instead: DocumentPage.astro renders Content.astro, itself permanently Astro for 2 of 4 content families, so it can never be a flat lexicon/ entry — but islands.ts was always meant to generalize beyond client-hydration cases ("consulted... by whole-record dispatch in a later phase", its own Phase 2 header comment) to cover exactly this: an NSID that needs a real .astro file for any reason. src/pages/[collection]/[rkey].astro now consults the SAME unified lookup (islands ?? lexiconRegistry?.component ?? GenericPage) every other dispatch site in this migration already uses, instead of a separate pageRenderers table — retired from registry.ts entirely, both former entries now resolve through one pipeline as intended. Corrects an initial mis-scoping of this phase (recorded inline in the spec): the first draft concluded site.standard.document must live forever in a separate legacy table, which overlooked islands.ts's own already-documented intent to cover this case. Verified against real content: id.sifa.profile.self's CV page renders all 10 real sections; a real site.standard.document page renders correctly with its RecommendButton; the one real leaflet document with an embedded Bluesky post (rkey 3lwgzk2ovnk2c) still shows its island working identically to before (Comments/RecommendButton/bskyPost component-urls unchanged), confirming islands.ts's new entry didn't disturb the existing per-block mechanism. astro check/build baseline unchanged (109 errors, 156 pages). network.cosmik.card/collection and app.bsky.feed.post are deliberately NOT part of this phase — none has a static page today (live-fetch only), so there's no pageRenderers-vs-lexicon duplication to resolve for them; left for the live-fetch layer's own future phase (retiring src/lib/render/loaders/), per the spec's Phase 5 correction. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Check off completed steps in Phase 3/4 plan docs Both plans finished execution (Phase 3 app.offprint, Phase 4 blog.pckt) with all tasks reviewed clean, but the checkbox tracking was never updated to reflect it. Cosmetic only. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Add fail-loud hydrate guard to blog.pckt's content.svelte Every other dispatch surface on this branch (LexiconBlock.astro, Content.astro) throws if a resolved lexiconRegistry entry declares hydrate but has no island support, rather than silently rendering it un-hydrated. content.svelte (blog.pckt's flat content dispatcher) was the one exception — not because blog.pckt is exempt from the risk, but because it's never needed hydration before. Its own lexicon declares an open block union ("closed": false in lexicons/blog/pckt/content.json), so a future block type could add one. Adds the same guard, verified against real synced content (build/typecheck baseline unchanged, since none of the 4 currently-wired blocks declare hydrate). Final-review finding from the blog.pckt migration. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
non-claude agents docs and skill
Update docs for completed blog.pckt migration
Retire the old blog.pckt dispatch code Removes blog.pckt.content's entry + import from registry.ts's contentRenderers (now holds exactly the two permanent residents, pub.leaflet.content and app.offprint.content — every family that could ever fully leave this table has now done so) and from DocumentPageContent.svelte's own local contentRenderersLive table. Deletes the 5 old blog/pckt files, now fully superseded by src/components/lexicon/blog/pckt/. Full regression check re-confirms both real blog.pckt documents in this account still render correctly on both the static and live-fetch paths after the cleanup. This closes Phase 4 of docs/superpowers/specs/2026-09-02-registry-driven-dispatch-design.md. Every content family (at.unthread, pub.leaflet, app.offprint, blog.pckt) is now on the shared lexiconRegistry. Remaining phases in the spec's original rough shape: whole-record NSIDs (site.standard.document, id.sifa.profile.self, network.cosmik.card/collection, app.bsky.feed.post) and the live-fetch loaders/ retirement. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Port blog.pckt (content dispatcher + all 4 blocks) to the registry heading, text, image, website relocated to src/components/lexicon/ blog/pckt/block/, one path segment deeper (+1 relative import level each, the same rule every prior phase has used). Any top-level const reading the data prop converted to $derived per the standing instruction, including the old file's `const { attrs } = data` destructure. content.svelte replaces the old Content.svelte's local pcktBlockRenderers table with a direct lexiconRegistry read — the same simplification already applied to pub.leaflet's unorderedList and app.offprint's blockquote, here from the start since blog.pckt has no async block to complicate it. Unlike pub.leaflet/app.offprint, blog.pckt has no per-family Content.astro/ContentLive.svelte to rewrite — Content.astro and DocumentPageContent.svelte already check lexiconRegistry first (built in Phase 1), so creating this one file is the entire wiring step for both the static and live-fetch paths. Verified against both real blog.pckt documents in this account (rkeys 3mrtg5yhusb3j, 3mrter4fqkipk, covering all 4 wired block types combined) on both paths: real headings/images/website-embed markup present, zero GenericRecord raw-JSON fallback, real headless-browser check confirms the live-fetch path too. Note: content.svelte's GenericRecord/lexiconRegistry import paths use ../../../ (3 levels up), not the ../../ shown in the task brief's code sample — content.svelte sits at src/components/lexicon/blog/pckt/ (3 levels under src/components/, vs. the old Content.svelte's 2), so it needs the same "one extra ../" adjustment the brief's own relocation rule calls for. Caught by a real build failure (UNRESOLVED_IMPORT) and fixed before verification. Part of docs/superpowers/specs/2026-09-02-registry-driven-dispatch-design.md Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Add Phase 4 implementation plan: blog.pckt migration Smallest of the four content-family phases — blog.pckt has no async block type, so this phase needs none of islands.ts/LexiconBlock.astro/ a separate ContentLive.svelte file. Content.astro and DocumentPageContent.svelte already check lexiconRegistry first (Phase 1), so creating src/components/lexicon/blog/pckt/content.svelte is the entire wiring step for both dispatch paths. content.svelte's own nested dispatch reads lexiconRegistry directly from the start, matching the simplification already applied to pub.leaflet's unorderedList and app.offprint's blockquote. Part of docs/superpowers/specs/2026-09-02-registry-driven-dispatch-design.md Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Style app.offprint imageGrid/imageCarousel/imageDiff These components were structurally complete but entirely unstyled (no CSS anywhere targeted .image-grid/.image-carousel/.image-diff) — images just stacked as bare <img> tags. Adds real, CSS-only layout to global.css (shared, unscoped, since these lexicon/ components render on both the static SSR'd path and the live-fetch imperatively-mounted path): - .image-grid: a responsive CSS grid (auto-fit/minmax), with the column basis varying by the existing data.aspectRatio mode (landscape/portrait/square/mosaic). - .image-carousel: a horizontal scroll-snap strip, zero JS, native swipe/scroll/arrow-key behavior — matches this site's minimal- hydration convention rather than adding a new interactive island. - .image-diff: a simple two-column Before/After layout using the existing figcaption labels. Verified against real synced content (rkey 3mih3ccd6nj23, real imageGrid + imageCarousel blocks) with a real headless-browser check: computed styles confirm display:grid/flex + scroll-snap actually applied, real screenshots show a clean grid and a working scrollable strip, no JS errors. astro check/build baseline unchanged (109 errors, 156 pages). No real imageDiff record exists in this account's synced content to verify the same way — its CSS follows the identical pattern as the other two but is unverified against real data. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Fix offprint imageGrid/imageCarousel/imageDiff images not rendering The published app.offprint lexicons define their shared GridImage sub-type's field as `blob`, but real Offprint clients send `image` instead — a genuine upstream schema/data mismatch (CLAUDE.md previously documented this as a known, deliberately-unfixed rough edge). Confirmed with real synced content (rkey 3mih3ccd6nj23): every real imageGrid/ imageCarousel item has an `image` field, no `blob` key at all. Reads `image` first, falling back to `blob` so any hypothetical spec-conformant data still works. Verified against real content: the same document's rendered <img> tag count goes from 1 (only the singular image block) to 14 (grid + both carousel blocks now included), with no change to the astro check/build baseline (109 errors, 156 pages). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Update stale docs/comments after the app.offprint migration CLAUDE.md's Component conventions and "one permanent Astro/Svelte boundary" sections still described app.offprint as being on the old dispatch convention, named files this phase deleted (the old app/offprint/blocks/Heading.svelte, BlueskyPost.astro, bsky/Post.astro), and asserted app.offprint's live-fetch Bluesky-embed gap was still a permanent scope cut — all stale as of this phase. Rewrites both sections to reflect the real state: blog.pckt is now the one remaining family on the old convention, both pub.leaflet and app.offprint are symmetric on the new registry-driven mechanism, and the live-fetch gap is closed for both. Also fixes the img.blob/img.image known-bug note's now-wrong file paths. registry.ts's own comment above contentRenderers previously implied pub.leaflet.content/app.offprint.content should be "absent" from that table once migrated — false and actively misleading (they're permanent residents there by design, per the spec's corrected architecture; only blockRenderers/pageRenderers are meant to empty out). Corrected. Three global.css comments still named the deleted bsky/Post.astro as a build-time consumer of shared styles; updated to name its real successor, bsky/PostEmbed.svelte. Final-review findings from docs/superpowers/plans/2026-09-02-registry-dispatch-phase3-offprint.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Retire the old app.offprint block dispatch code Empties registry.ts's blockRenderers (both families that ever populated it — pub.leaflet in Phase 2, app.offprint here — are now fully migrated to lexiconRegistry.ts + LexiconBlock.astro; the table itself is left in place until a later phase formally retires registry.ts's old tables). Deletes the 18 old app.offprint block files, now fully superseded by src/components/lexicon/app/offprint/block/. Also deletes bsky/Post.astro (its only remaining caller, the old BlueskyPost.astro, is deleted in this same commit) — PostCard.svelte/PostEmbed.svelte, its real successors, are unaffected. Fixes one stale comment reference in LinkCard.svelte. Full regression check re-confirms both the static and live-fetch paths still correctly render the real offprint documents (rkeys 3mih3ccd6nj23, 3mcsvjjceei23) after the cleanup. This closes Phase 3 of docs/superpowers/specs/2026-09-02-registry-driven-dispatch-design.md. app.offprint was the second and last content family with a real async block type — the remaining families (blog.pckt) and whole-record NSIDs have no hydration concerns, so no future phase needs islands.ts changes of this shape again. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Wire the live-fetch offprint dispatcher to the shared registry ContentLive.svelte now reads lexiconRegistry directly instead of its own local table, matching Phase 2's identical change to pub/leaflet's ContentLive.svelte. This closes CLAUDE.md's documented "permanent, accepted scope cut" for app.offprint's live-fetch bskyPost rendering. No real offprint record with that block type exists to route an actual live-fetch request through end to end, but the two hard parts are both independently proven real: PostEmbed.svelte genuinely fetches and renders a real post (Task 2's scratch-page browser check), and this exact ContentLive.svelte pattern genuinely renders a real embedded post on the live-fetch path (Phase 2's proof for leaflet). Real headless-browser check against rkey 3mih3ccd6nj23, forced through the live-fetch path, confirms ordinary offprint block rendering still works correctly on this path (HTTP 404, 9 real headings rendered, no errors). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Wire the static app.offprint dispatcher to the shared registry Content.astro keeps its file path and export shape (registry.ts's contentRenderers entry is untouched) but now calls the same LexiconBlock.astro helper Phase 2 built for pub.leaflet, instead of the old blockRenderers table lookup. Verified byte-identical rendered output for both real app.offprint.content documents in this account (rkeys 3mih3ccd6nj23, 3mcsvjjceei23) against a pre-migration snapshot, covering 9 of the 17 static block types including the documented img.blob bug (confirmed still broken exactly the same way, not fixed or duplicated). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Wire up the async Bluesky-embed block for app.offprint blueskyPost.svelte + its island wrapper follow the exact same shape Phase 2 established for pub.leaflet.blocks.bskyPost, reusing PostEmbed.svelte unchanged — the only real difference is the field name (data.post.uri, a plain strong ref, vs leaflet's data.postRef.uri). Verified end to end against a real post by the site owner (at://did:plc:6ayddqghxhciedbaofoxkcbs/app.bsky.feed.post/3mukljf4uf22d) via a temporary, uncommitted test page exercising the real dispatch chain (LexiconBlock -> islands/lexiconRegistry -> the island wrapper -> blueskyPost.svelte -> PostEmbed.svelte -> a real getPost() fetch) — no real synced app.offprint.block.blueskyPost record was needed, since the fetch is keyed by literal AT-URI regardless of which document (if any) references it. Real headless-browser check confirmed the real post text rendered with no errors. islands.ts now maps both families' async blocks. Not yet wired into any dispatch path — that's next. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S825WPbtgawQaBERQwL1oH
Port 17 non-async app.offprint blocks to the registry text, heading, callout, bulletList, orderedList, taskList, codeBlock, horizontalRule, image, imageGrid, imageCarousel, imageDiff, webBookmark, webEmbed, button, mathBlock, blockquote relocated to src/components/lexicon/app/offprint/block/, one path segment deeper (+1 relative import level each, uniform rule established in Phase 2). Any top-level const reading the data prop converted to $derived per the standing instruction from Phase 2. blockquote dispatches nested block content by reading lexiconRegistry directly (no local table, no per-sibling imports, no blueskyPost exclusion to write down) — the simpler pattern pub.leaflet's unorderedList was refactored to use ahead of this phase, after its original local table proved to be exactly the kind of hand-maintained duplicate that drifts out of sync. The documented img.blob-vs-real-img.image bug in imageGrid/imageCarousel/ imageDiff is preserved exactly, not fixed. Not yet wired into any dispatch path — that's the next task. Part of docs/superpowers/specs/2026-09-02-registry-driven-dispatch-design.md Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add Phase 3 implementation plan: app.offprint migration Mirrors Phase 2's already-proven pub.leaflet pattern for the second and last content family with a real async block type (18 block types vs leaflet's 11). Incorporates two decisions made while drafting this plan, not present in the original spec/Phase 2 precedent: - blockquote.svelte (offprint's arbitrary-nested-block dispatcher, the counterpart to leaflet's unorderedList) reads lexiconRegistry directly for nested block dispatch instead of hand-maintaining a local duplicate table — the same simplification just applied to leaflet's unorderedList.svelte in the previous commit, once its original local table proved to be exactly the kind of hand-maintained duplicate this architecture exists to eliminate. - blueskyPost verification uses a real post by the site owner (at://did:plc:6ayddqghxhciedbaofoxkcbs/app.bsky.feed.post/3mukljf4uf22d) run through the real dispatch chain via a temporary scratch page, rather than settling for code-parity-only verification — no real synced app.offprint.content record with this block type exists yet, but the fetch is keyed by literal AT-URI regardless of which document references it, so real end-to-end proof doesn't require one. Part of docs/superpowers/specs/2026-09-02-registry-driven-dispatch-design.md Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Simplify unorderedList.svelte to read lexiconRegistry directly Drops the hand-maintained local leafletBlockRenderers table (which the file's own comment already admitted had drifted out of sync with the real block set twice before) in favor of the same shared lexiconRegistry every other dispatch site already reads. Confirmed safe despite the circular import (lexiconRegistry.ts's glob imports this file, which now imports lexiconRegistry.ts back) by real build + byte-identical diff against every real leaflet document in this account that exercises unorderedList (rkeys 3mjagcnwocs2g, 3mtk6unfa3k22, 3m2p274bfqk2i, 3lzways66yk25, 3lx3ylippms23) plus the one with a real embedded Bluesky post (3lwgzk2ovnk2c) — zero rendered-output change. Side effect, not the goal: a nested bskyPost block (none exist in real content today) now falls through to PostEmbed's own real fallback link via the registry instead of GenericRecord's raw-JSON dump on the static path, and renders/hydrates for real on the live-fetch path — strictly better than the old table's unconditional exclusion, not a regression. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix Phase 2 (pub.leaflet) final-review findings Addresses the 6 findings from the whole-branch code review of the pub.leaflet registry-dispatch migration ("Ready to merge: With fixes"): - LexiconBlock.astro: fail loudly (mirroring Content.astro's existing guard) when a lexiconRegistry entry declares `hydrate` but has no matching islands.ts wrapper, instead of silently rendering un-hydrated. - PostEmbed.svelte: pre-hydration SSR output now always shows the real bsky.app fallback link instead of a bare, dead-end "Loading post…" message — matches this site's established graceful-degradation convention. Dropped the unused loading state and its CSS rule. - CLAUDE.md + add-lexicon skill: corrected multiple claims left stale by this phase's deletions (Header.svelte/Code.svelte/BskyPost.astro no longer exist; pub.leaflet is now fully migrated to the lexicon/ registry + islands.ts hydration mechanism; app.offprint is still on the old registry.ts tables). Updated the skill's instructions and worked examples to match, and reworded a client-hydration claim that the same migration had also made stale. - Content.astro: corrected its hydrate-guard comment/error text, which claimed islands.ts didn't exist yet (it does now, consulted by LexiconBlock.astro for per-block dispatch — Content.astro itself just doesn't use it yet, unchanged in this fix). - unorderedList.svelte: documented that its local dispatch table is a hand-maintained duplicate prone to drift (per CLAUDE.md's own history), and that its bskyPost exclusion now affects both the static and live-fetch paths, not just static. - LexiconBlock.astro: widened `nsid` to `string | undefined` to match the real call site (`entry.block.$type`). Verified: npx astro check unchanged at 109 errors / 0 warnings; npm run build still produces 156 pages; the real document with embedded posts (site.standard.document/3lwgzk2ovnk2c) no longer contains "Loading post" anywhere in its built HTML and shows real bsky.app fallback links pre-hydration. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Be4p7x9rp7jSBQBW86FuJh
Retire the old pub.leaflet block dispatch code Removes the 11 now-dead pub.leaflet.blocks.* entries + imports from registry.ts's blockRenderers (app.offprint's 17 entries in the same table are untouched — not migrated yet). Deletes the 11 old block files, now fully superseded by src/components/lexicon/pub/leaflet/ blocks/. bsky/Post.astro stays — still app.offprint's own renderer until that family migrates. Full regression check re-confirms both the static and live-fetch paths still correctly render the real embedded Bluesky post at rkey 3lwgzk2ovnk2c after the cleanup. This closes Phase 2 of docs/superpowers/specs/2026-09-02-registry-driven-dispatch-design.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wire the live-fetch leaflet dispatcher to the shared registry ContentLive.svelte now reads lexiconRegistry directly instead of its own local table, and — closing a gap CLAUDE.md has documented as a permanent limitation since §48 — a live-fetched leaflet document with an embedded Bluesky post now renders it correctly. Real headless-browser check against rkey 3lwgzk2ovnk2c, forced through the live-fetch path, confirms it: HTTP 404 (genuinely live-fetched, not a static leftover) with the real post rendered. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix LexiconBlock.astro typecheck regression from Task 3 review The Task 3 review found LexiconBlock.astro's GenericRecord fallback call (`<GenericRecord data={data} />`) was a real, newly-introduced astro check regression (109 -> 110 errors): data is typed unknown, which TypeScript won't assign to GenericRecordData's object shape without a cast, even with its index signature. Cast at the call site instead of loosening GenericRecord.svelte's prop type, matching this codebase's established convention (cast to a looser/specific type at the point of use, e.g. documentSchema.content is z.unknown() by design and every block component casts to Main). astro check is back to 109 errors / 0 warnings; build still produces 156 pages. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Be4p7x9rp7jSBQBW86FuJh
Build shared per-block dispatch helper, rewire pub/leaflet/Content.astro LexiconBlock.astro is the family-agnostic islands-aware per-block dispatcher every content family's own walking loop can now reuse instead of reimplementing island-lookup logic. pub/leaflet/Content.astro keeps its file path and export shape (registry.ts's contentRenderers entry is untouched) but now calls LexiconBlock instead of the old blockRenderers table. First real, in-this-codebase proof that selective per-block hydration works: the real document at rkey 3lwgzk2ovnk2c renders its 15 real embedded Bluesky posts as genuine hydration islands, while a document with no such block ships zero client JS for content. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Build the shared async Bluesky-embed mechanism + islands.ts PostEmbed.svelte does its own onMount fetch (getPost(), unchanged) and renders the already-pure-presentational PostCard.svelte, unchanged. pub.leaflet.blocks.bskyPost.svelte wraps it, declares hydrate: "visible". islands.ts is new — first real entry, proving the island-wrapper mechanism (client:visible needs a literal import, confirmed by hitting NoMatchingImport in the Astro spike) against this codebase's real build for the first time. Not yet wired into any dispatch path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Port 10 non-async pub.leaflet blocks to the registry text, header, blockquote, code, image, horizontalRule, website, iframe, button, unorderedList relocated to src/components/lexicon/pub/leaflet/ blocks/, one path segment deeper (+1 relative import level each, uniform rule). Any top-level const reading the data prop converted to $derived per this phase's standing instruction (a plain const only captures data's initial value — Svelte 5's real state_referenced_locally warning). Not yet wired into any dispatch path — that's the next task. Part of docs/superpowers/specs/2026-09-02-registry-driven-dispatch-design.md Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add Phase 2 implementation plan: pub.leaflet migration Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Be4p7x9rp7jSBQBW86FuJh
Correct spec: content-family dispatchers stay Astro, not Svelte Phase 1's own island-wrapper finding (client:* needs a literal import in an .astro file) also rules out a pure-Svelte content-family dispatcher for any family with an async block type — it could never consult islands.ts, so nested async blocks would silently render un-hydrated. pub.leaflet.content/app.offprint.content keep their existing Content.astro walking loop; blog.pckt.content/at.unthread.content are unaffected since neither family's lexicon defines an async block type at all. New shared helper: src/components/LexiconBlock.astro, family-agnostic per-block islands-aware dispatch, used by every family's own walking loop instead of each reimplementing it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Be4p7x9rp7jSBQBW86FuJh
Fix state_referenced_locally warning in at.unthread.content const html read the data prop directly, only capturing its initial value. Use $derived so it stays reactive if the component is ever reused with new props. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Be4p7x9rp7jSBQBW86FuJh
Final-review fix wave: doc drift + defensive guards for registry dispatch Addresses all 7 findings from the whole-branch code review of Phase 1 (registry-driven lexicon dispatch, verdict: ready to merge). None of these are blockers — documentation now accurately describes the new lexiconRegistry.ts dispatch surface and its coexistence with registry.ts's old tables, and two cheap runtime guards catch likely mistakes in later migration phases before they fail silently: - CLAUDE.md + add-lexicon skill: document the new lexicon/ registry convention and its "checked first, falls back to old tables" coexistence with registry.ts, since only at.unthread.content has migrated so far. - Content.astro: throws a clear error if a migrated entry ever declares a truthy `hydrate` before the islands mechanism exists (a later phase's responsibility) instead of silently rendering un-hydrated. - lexiconRegistry.ts: warns on a derived NSID that doesn't look like a real NSID shape (catches the wrong-casing mistake this skill's own opening paragraph warns about elsewhere). - Content.astro / DocumentPageContent.svelte: warn if an NSID resolves via the new registry but still has a stale, now-dead entry in the corresponding old table (a half-finished-migration signal). - registry.ts / global.css: stale comments/cross-references corrected. - Design spec: Phase 1's "Migration approach" bullet now matches what was actually built (wired into real dispatch sites, islands deferred on purpose); added an explicit note that lexiconRegistry.ts is a transitional name that becomes registry.ts once the migration completes. Verified: npx astro check stays at the 109-error/0-warning baseline, npm run build stays at 156 pages, and the real at.unthread.content target document (site.standard.document/3mqfdhlwwr623) still renders identically. Full hand-traced verification of the new guard logic against real current state (no false positives) is in .superpowers/sdd/2026-09-02-registry-dispatch-phase1-mechanism-and-unthread/final-fix-report.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wire the live-fetch content dispatcher to the same lexicon registry DocumentPageContent.svelte now checks lexiconRegistry before falling back to its own contentRenderersLive table, and no longer imports at.unthread's Content.svelte directly — closing the loop on Task 1's migration: at.unthread.content now renders identically through the same component on both the static and live-fetch paths. Also deletes src/components/at/unthread/Content.svelte, the thin delegating wrapper Task 1 left in place specifically because this task's own DocumentPageContent.svelte edit still imported from that path — confirmed via grep that nothing else references it before removing it (and its now-empty parent directory). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Be4p7x9rp7jSBQBW86FuJh
Add auto-discovered lexicon registry, migrate at.unthread.content (static path) New src/components/lexiconRegistry.ts globs src/components/lexicon/**/*.svelte and derives each entry's NSID from its path (mirroring src/lexicons/'s own nesting). Content.astro checks it before falling back to registry.ts's old contentRenderers table. at.unthread.content is the first family moved over (no async/hydration need, so this proves the glob-and-dispatch mechanism without yet needing the separate islands mechanism a later phase adds). Part of the registry-driven dispatch migration: docs/superpowers/specs/2026-09-02-registry-driven-dispatch-design.md Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add Phase 1 implementation plan: registry mechanism + at.unthread.content Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Be4p7x9rp7jSBQBW86FuJh
Resolve open spec questions: nested lexicon/ layout, no page-level registry - src/components/lexicon/ mirrors src/lexicons/'s directory nesting (path segments -> dot-joined NSID), not a flat dotted filename. - pageRenderers as a distinct concept is retired: every NSID is one registry entry regardless of how much it renders. Composed pages (/now, /sitemap, etc.) stay ordinary Astro pages outside this system entirely, unaffected by the migration. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Be4p7x9rp7jSBQBW86FuJh
Add design spec for registry-driven lexicon dispatch Ports the architecture validated in this session's Astro spike (~/Documents/tynanpurdy-astro-spike) onto the real site: one auto-discovered registry instead of two hand-maintained tables, and per-component hydration declarations that close the permanent Astro-only exception documented in CLAUDE.md §54. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Be4p7x9rp7jSBQBW86FuJh
simplified claude.md
nav fixes
input type url on login field
dynamically import atproto client, fixing silently failing interactive components
last updated and commit in footer
deduping some types
Fix residual review findings: consolidate duplicated loaders/index.ts comment, remove incorrect Map/client:* claim
Fix Phase 8 final review findings: SifaSelfPage.svelte whitespace regression + doc/comment corrections
Document Phase 8 (SifaSelfPage.svelte + live loader) and the loaders/index.ts permanence correction in CLAUDE.md
Add live-fetch loader for id.sifa.profile.self, correct loaders/index.ts's retirement comment Also fixes an already-stale claim in the same header comment block (registry.ts "still imports SifaSelfPage.astro") left over from Task 1's SifaSelfPage.svelte port, since it directly contradicts this file now importing SifaSelfPage.svelte itself.
Extract Sifa sort/group helpers into src/lib/sifaSort.ts
Port SifaSelfPage.astro to SifaSelfPage.svelte, move its scoped styles to global.css
Add Phase 8 plan: SifaSelfPage.svelte + live loader (final phase)
Fix Phase 7 review findings: restore Comments content-serialization guard, add CLAUDE.md §53, fix stale references F1: DocumentPage.astro's <Comments> call site was passing content unconditionally, regressing the payload-size guard the original Comments.astro had (only pub.leaflet.content documents need it for quote-excerpt resolution). Restored the conditional — measured directly, a real app.offprint document's Comments island props dropped from 15,053 bytes to 299 bytes, while a real Leaflet document stayed at 7,048 bytes unchanged. F2: Added CLAUDE.md §53 documenting the Comments.svelte migration. F3: Added "Superseded — see §53" pointers in §15 (data-content attribute, commentsWidget.ts's grantedLexicon tracker) and §22 (#comments-login) for claims now describing deleted files/behavior. F4: Fixed two stale comments in navAuth.ts and recordViewer.ts still naming the deleted commentsWidget.ts as a live file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HYAWtGaTHBPpcjS8NBT89D
Expand DocumentPageContentData/loadDocument() with rkey/bskyPostRef/contentType, wire Comments into DocumentPageContent.svelte
Wire Comments.svelte into DocumentPage.astro with client:load, delete the old .astro/commentsWidget.ts
Create Comments.svelte, the second client-hydrated Svelte component in this codebase
Add Phase 7 plan: Comments.svelte (second client-hydrated component, largest rewrite)
Add Superseded pointer to the last remaining §14 RecommendButton.astro reference
Fix Phase 6 final review findings: CLAUDE.md §52, stale comments, element order - Add CLAUDE.md §52 documenting RecommendButton.svelte (the first client-hydrated Svelte component in this codebase), including the verified BroadcastChannel/crypto.subtle build-time dependency this introduces via @atproto/oauth-client-browser, and mark the now-stale recommendWidget.ts (§14) / RecommendButton.astro (§22) descriptions as superseded. - Fix src/lib/render/loaders/document.ts's header comment, which claimed this component doesn't render RecommendButton — it does now. - Fix src/lib/navAuth.ts's comment referencing the now-deleted recommendWidget.ts. - Swap RecommendButton/WaypointPicker order in DocumentPageContent.svelte to match DocumentPage.astro's real (unchanged) order. Verified: npm run build (156 pages), npx astro check (110 errors, 0 warnings) — unchanged baseline, no logic change in this pass.
Add RecommendButton to DocumentPageContent.svelte, closing the live-fetch scope cut from Phase 5 RecommendButton.svelte (Task 1) is now nested directly in the live-fetch document renderer, right after WaypointPicker in the header, matching the static DocumentPage.astro/Content.astro layout. No client:* directive is used or needed — DocumentPageContent.svelte is only ever mounted imperatively via mountSvelteComponent() from client-side JS, so RecommendButton's own onMount() fires normally as part of that mount. Also corrected the file's header comment, which previously stated RecommendButton was excluded.
Wire RecommendButton.svelte into DocumentPage.astro with client:load, delete the old .astro/recommendWidget.ts
Create RecommendButton.svelte, the first client-hydrated Svelte component in this codebase
Add Phase 6 plan: RecommendButton.svelte (first client-hydrated component)
Fix Phase 5 final review findings: CLAUDE.md §51, skill doc, minor polish - Add CLAUDE.md §51 documenting the site.standard.document live-fetch loader/renderer phase, including the master spec's wrong assumption that DocumentPageContent.svelte could nest each family's real Content.svelte (false — pub.leaflet/app.offprint each keep one Astro-only Bluesky-post block, and a .svelte file can't render an .astro component) and the two ContentLive.svelte counterparts built to resolve it. - Correct §25's now-stale "4 rich collection NSIDs" to 3, matching recordViewer.ts's actual RICH_COLLECTIONS set now that site.standard.document has its own live loader. - Update add-lexicon skill's Step 4 with the two new dispatch-table locations (ContentLive.svelte per family, contentRenderersLive for a brand-new family) a new leaflet/offprint block type needs. - document.ts: correct header comment ("the same shape" -> "a subset of the same shape") and validate tags array elements are strings instead of casting. - DocumentPageContent.svelte: make Renderer a $derived binding. npm run build: 156 pages (unchanged). npx astro check: 110 errors, 0 warnings (unchanged). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HYAWtGaTHBPpcjS8NBT89D
Wire site.standard.document into the live-fetch layer via a new document loader
Create DocumentPageContent.svelte, a live-fetch-only renderer for site.standard.document
Create live-fetch-only ContentLive.svelte for pub.leaflet and app.offprint