Repository navigation
feat(serendipity): build the dashboard at deploy, fetch its events as an island - #929
Merged
Merged
Conversation
… an island The /serendipity dashboard rendered per request behind a 60 s edge cache. It shipped at the edge's q4 brotli with no dcz delta and no ETag, and with script-src 'unsafe-inline', since it composes its own policy and #925's lunaPage hashing never reached it. It was the largest page left in that state: 15,258 B on the wire, 12 KB of it CSS. build.ts step 5b now bakes the shell: the CSS, the toolbar and the filter script. The pool's count line and cards arrive from /serendipity/events.html through lib/island.ts. The island is cookie-free and edge-cached for the same 60 s, and every mutation evicts it the way it evicted the cached dashboard. A flash ?msg= view still renders live and whole for the person who just acted. The built page keeps serendipity's own CSP, since its img-src admits the cover proxy's https fallback, with the build's three hashes in place of 'unsafe-inline'. Its headers go in through serveStaticPage: withSerendipitySecurityHeaders rebuilds the Response from an object init, which would drop encodeBody on the precompressed body (gotcha 13). Measured against production: shell 5,260 B at q11 plus ~7,350 B of cards, so a first visit is about 12.6 KB (-17%) and a return visit pays only the cards. Also: - The dashboard no longer mints a uid cookie on a plain view. Only /contribute and the POSTs use it, and they still mint it. - serendipity's CSS ended with a stray top-level "}". Browsers skip it, and the build's Lightning CSS pass refused it on the page's first bake. - The filter script re-reads the cards on every apply and re-runs when the island lands; the toolbar stays in the shell, so a search typed before the swap survives it. - The layout-shift lab's control went unseen here, because .window > .body is a flex row on this shell. It now tries the content column first, and the control is seen on both shells.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
aadhar-sh | a96b5b3 | Sep 25 2026, 06:54 PM |
Contributor
Wire-size diff vs merge base
Brotli q11, the quality the edge serves. Every number here is deterministic: Client assetsNo change. 27 files, 91.60 KiB Brotli total. PagesTotal Brotli: 608.75 KiB → 613.89 KiB (+5.14 KiB, +0.8%) across 61 files, 1 changed.
Worker bundle257.53 KiB → 258.02 KiB gzip (+0.49 KiB, +0.2%) Largest module deltas (raw bytes in bundle):
Dictionary transportFamily dictionary acquisition: no change, 14.41 KiB q11.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
The
/serendipitydashboard stops rendering per request. build.ts step 5b bakes the shell: 12 KB of CSS, the toolbar and the filter script. The pool's count line and cards arrive from/serendipity/events.htmlthroughlib/island.ts, the same move #924, #926 and #928 made.script-src'self' 'unsafe-inline'It was the largest page still on the loose script policy, because it composes its own CSP and #925's
lunaPagehashing never reached it.How it fits serendipity's own rules
?msg=view still renders live and whole, since it belongs to the person who just acted, and so does every other/serendipity/*path. Only the plain dashboard GET is built.serendipityCsp(scriptSrc)is now the one builder, so the built page keepsimg-src 'self' data: https:for the cover proxy's fallback, which the site-wide hashed policy does not allow. It takes'self'plus three hashes: the filter script, the island loader, and the shared speculation rules.serveStaticPage, never throughwithSerendipitySecurityHeaders. That function rebuilds the Response from an object init, which dropsencodeBodyon a precompressed body and double-encodes it (gotcha 13). Checked on the local built Worker: the body decodes once to HTML. The encodeBody walker's record for serendipity said this day would come; it now says how it was handled./contributeand the POSTs use it, and they still mint it on first use.The placeholder
A count line and 8 inert placeholder cards, which are
<div>s rather than links to an event that doesn't exist. The live count differs, and it doesn't need to match: nothing follows the island except the hidden no-match note and the cover popover. The toolbar stays in the shell, so a search typed before the swap survives it, and the filter re-applies when the island lands. Over an empty pool, a:has()rule hides the toolbar in the built page, the same way the live render omits it.Also
}. Browsers skip it silently. The build's Lightning CSS pass refused it the first time the page was baked..window > .body, which on serendipity's shell is a flex row, so the block is a zero-width column that moves nothing. The lab now tries.window > .body > .contentfirst, and the control is seen here and still on/garage/dyno.Verification
bun run build,bun run lint,bun run typecheck: clean.bun run test: 922 pass.bun run test:node: 912 pass, 10 skipped (HTMLRewriter-only), 0 fail. The newcontract-serendipity-dashboard-is-a-built-documentcovers the shell, the island's caching and eviction, the live flash view, the empty pool and the CSP builder.bun run routes:check: 182 routes, 0 failures. The harness's local D1 carries no serendipity schema, so its fragment row pins only the shape (200 or 503).link); search, the no-match note and the date chips work across the swap; no cookie is set; no console errors.bun run clsreads the dashboard clean on desktop and phone, with the control seen.Not in this PR
/serendipity/mcp-infois deterministic and could be baked the same way. It's low traffic, so it was left out.tools/build.tsstep 5b and the CLAUDE.md "built documents" sentence right where feat(ledger, around): build both pages at deploy, fetch their live rows as islands #928 does. Whichever merges second needs a two-line conflict resolved.