Skip to content

perf: put a section's favicon in its own head, and stop the size table double-counting - #346

Merged
oddharsh merged 3 commits into
mainfrom
fix/favicon-cache-and-snapshot-names
Aug 12, 2026
Merged

oddharsh merged 3 commits into
mainfrom
fix/favicon-cache-and-snapshot-names

Conversation

@oddharsh

@oddharsh oddharsh commented Aug 12, 2026 •

Copy link
Copy Markdown
Owner

Two follow-ups to #341 (now on main) and #345.

Base note: #341 has merged, so this now carries only #345 plus the fixes. It still contains #345 in full, because the fixes edit code #345 introduces. Merge #345 first and this collapses to scripts/build.mjs, www/_headers, the shell files and the regenerated pages; or merge this and close #345 as carried.

Both were found the same way: by reading each PR's own wire-size report against the tree.

1. The /a/ names now dehash to their own filenames

#341 gave the three first-interaction sheets a -style suffix in /a/, because nav-run was already taken by nav-run.js in the same STRING_ASSETS map. perf-snapshot merges the top-level copy and the /a/ copy on the dehashed filename, so a base that does not match its file never merges, and every byte is counted twice.

That is why #341's table reads +4.82 KiB across 22 files for a change that takes 1.28 KiB off the critical path. The code was right; the instrument was reading a name.

base tracks the file now, and the js/css pairs sharing a stem are told apart by extension inside hashedFor.

2. A section's favicon is in its own <head>

#345 compiled the section tiles into real SVG assets, then had nav.js find them through a data-favicon attribute on each taskbar pin. Two costs come with that: the attribute rides all 46 pages so that 11 of them can read one, and setFavicon() still applied it after boot, so a section page painted one icon and swapped to another.

The document declares it now. SECTION_FAVICONS is projected into lib/desktop.js beside DESKTOP_CHROME, lunaPage reads its own route, and the four section routes outside lunaPage (/writing, /serendipity, /coffee, /terminal) state it themselves. /pixel-peeper had no head icon at all and relied entirely on the script, which is the fragility this removes. Anything that is not a section keeps /favicon.ico.

setFavicon() and every data-favicon are gone.

3. /section-icons/* is cached

Unruled, the tiles inherited the static-assets default of max-age=0, must-revalidate, verified against the comparable /icons.svg in production:

$ curl -sI https://aadhar.sh/icons.svg
cache-control: public, max-age=0, must-revalidate

So every section page view spent a 304 on an icon that changes about never, where the old data: URI needed no request. 30 days, matching /og/, the other generated-but-unhashed image tier. Deliberately not immutable: these are not content-addressed, so a year would be a lie. Checked against gotcha 31 as well, since a _headers rule combines rather than overrides. Nothing else carrying a Cache-Control matches this path, so it stands alone.

Measured, against origin/main before these merges

#341 + #345 with these fixes
client assets +4.82 KiB / 22 files +0.30 KiB / 19 files
pages +2.18 KiB -0.19 KiB
dictionary deltas (pd/) +6.52 KiB +0.09 KiB

luna.css -1.45 KiB and nav.js -1.29 KiB off the render-blocking path, with the three sheets (3.05 KiB) moved behind first interaction.

Validation

Re-run after rebuilding on the post-merge main (so #342, #343 and #344 are in the base):

  • contract tests: 241 pass, 0 fail
  • worker typecheck and browser typecheck (test: typecheck browser interaction modules #343's new project): both clean
  • production build: clean, and pnpm run gen:shell is a no-op against the committed tree
  • lunaPage rendered for /lens, /terminal, /around, /reading returns the section tile; /security and a routeless page return /favicon.ico
  • the new contract test asserts the three regressions: a pin carrying favicon data again, nav.js setting an icon again, and a TASKBAR route whose tile is missing from disk. Both negative controls were run and fail, so it is not agreeing with itself.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
aadhar-sh 836d40f Aug 12 2026, 02:03 PM

@github-actions

github-actions Bot commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Wire-size diff vs merge base

8e32771 → fa1df21

Brotli q11, the quality the edge serves. Every number here is deterministic:
an unchanged file produces no row. Advisory — this check fails on nothing.

Client assets

Total Brotli: 72.60 KiB → 68.07 KiB (-4.53 KiB, -6.2%) across 19 files, 4 changed.

file base head Δ
nav-run-style.css 1.75 KiB — removed
nav.js 7.20 KiB 5.72 KiB -1.49 KiB (-20.6%)
nav-tray-style.css 0.78 KiB — removed
infotip-style.css 0.52 KiB — removed

Pages

Total Brotli: 490.58 KiB → 490.31 KiB (-0.27 KiB, -0.1%) across 48 files, 44 changed.

file base head Δ
lwe/index.html 4.65 KiB 4.43 KiB -0.22 KiB (-4.7%)
garage/index.html 10.21 KiB 10.06 KiB -0.15 KiB (-1.5%)

Plus 42 files under the 0.13 KiB floor, net +0.09 KiB — usually content-hash churn in /a/ references, counted in the total above.

Worker bundle

276.28 KiB → 276.27 KiB gzip (-0.01 KiB, -0.0%)

Largest module deltas (raw bytes in bundle):

file base head Δ
.build/www/_worker.js/lib/desktop.js 6.17 KiB 6.66 KiB +0.49 KiB (+8.0%)
.build/cal/src/templates.js 18.70 KiB 18.28 KiB -0.42 KiB (-2.2%)
.build/www/_worker.js/lib/chrome.js 6.56 KiB 6.76 KiB +0.21 KiB (+3.1%)
.build/serendipity/serendipity.js 114.17 KiB 114.25 KiB +0.08 KiB (+0.1%)
.build/www/_worker.js/writing.js 15.66 KiB 15.73 KiB +0.07 KiB (+0.4%)
.build/www/_worker.js/terminal.js 46.08 KiB 46.11 KiB +0.02 KiB (+0.1%)

Dictionary deltas (pd/)

140 → 140 deltas, 533.08 KiB → 532.98 KiB (-0.10 KiB).

…e double-counting

Two follow-ups to #341 and #345, both found by reading their own wire-size
reports against the tree.

`nav-run` was already taken by `nav-run.js` in the same STRING_ASSETS map.
perf-snapshot merges the top-level copy and the `/a/` copy on the DEHASHED
filename, so a base that does not match its file never merges and every byte is
counted twice. #341's table read **+4.82 KiB across 22 files** for a change that
takes 1.28 KiB off the critical path.

base tracks the file now, and the js/css pairs sharing a stem are told apart by
extension inside `hashedFor`. 19 files, and each sheet appears once.

through a `data-favicon` attribute on each taskbar pin. The attribute rides all
46 pages so that 11 of them can read one, and `setFavicon()` still applied it
after boot, so a section page painted one icon and swapped to another.

The document declares it instead. `SECTION_FAVICONS` is projected into
lib/desktop.js beside DESKTOP_CHROME, lunaPage reads its own route, and the four
section routes outside lunaPage (/writing, /serendipity, /coffee, /terminal)
state it themselves. /pixel-peeper had no head icon at all and relied entirely
on the script, which is the fragility this removes. Anything that is not a
section keeps /favicon.ico.

`setFavicon()` and every `data-favicon` are gone.

Unruled, the tiles inherited the static-assets default of
`max-age=0, must-revalidate` (verified against the comparable /icons.svg in
production), so every section page view spent a 304 on an icon that changes
about never. 30 days, matching /og/, the other generated-but-unhashed image
tier. Not `immutable`, because these are not content-addressed.

| | #341 + #345 | with these fixes |
|---|--:|--:|
| client assets | +4.82 KiB / 22 files | **+0.30 KiB / 19 files** |
| pages | +2.18 KiB | **-0.19 KiB** |
| dictionary deltas (`pd/`) | +6.52 KiB | **+0.09 KiB** |

luna.css -1.45 KiB and nav.js -1.29 KiB off the render-blocking path; the three
sheets (3.05 KiB) move behind first interaction.

A contract test asserts the three ways this regresses: a pin carrying favicon
data again, nav.js setting an icon again, and a TASKBAR route whose tile is
missing from disk. Both negative controls were run and fail.
@oddharsh
oddharsh force-pushed the fix/favicon-cache-and-snapshot-names branch from 7e29429 to 836d40f Compare August 12, 2026 14:02
@oddharsh
oddharsh merged commit 769a72b into main Aug 12, 2026
6 of 7 checks passed
@oddharsh
oddharsh deleted the fix/favicon-cache-and-snapshot-names branch August 12, 2026 14:12
oddharsh added a commit that referenced this pull request Aug 12, 2026
Six PRs shipped today and none of them carried a changelog entry, so the ramp
correctly reported "nothing staged" and /updates still ends at v184. These are
the two visitor-facing threads in that work.

v185 lighter-shell covers #341, #342, #344 and #346 (which carried #345): the
first-interaction stylesheets moved off the render-blocking path, the section
favicon moved into each page's own head, and the blank Run and Search shells
became prebuilt static documents.

v186 dictionary-roll covers #348 and #349: the shared-compression dictionary set
had been stale since 2026-07-30, and now rolls itself nightly.

#343 is deliberately absent. It adds a typecheck project and changes nothing a
visitor can observe, and /updates is a changelog rather than a commit log.

Co-authored-by: Aadharsh Pannirselvam <19518661+oddharsh@users.noreply.github.com>
oddharsh added a commit that referenced this pull request Sep 30, 2026
The /lens card was last captured in #173 (2026-07-30), and #841 only
re-encoded it from PNG to JPEG. So it still showed the pre-#205 toolbar:
the Compare mode tab first, and the machine lenses in a strip above all
three panes. Production has the mode row as Human, Machine, Browser,
Delta, Compare, with the lens tabs inside the Machine View pane.

Recaptured from production with the MAINTENANCE.md recipe: both
Browser-Rendering caches prewarmed, then OG_ONLY=lens bun run og-cards.

The first recapture stamped an empty white box where the favicon goes.
#346 moved /lens's icon to a root-relative /section-icons/lens.svg, and
the card is built with setContent, so the href resolves against nothing.
gen-og-cards.ts now inlines a root-relative .svg icon from public/, the
same bytes the page serves. data: icons pass through untouched, and no
other carded page declares a root-relative icon.

Co-authored-by: Aadharsh Pannirselvam <19518661+oddharsh@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant