Skip to content

perf: compile section favicon assets - #345

Closed
oddharsh wants to merge 1 commit into
mainfrom
codex/compile-section-favicons
Closed

oddharsh wants to merge 1 commit into
mainfrom
codex/compile-section-favicons

Conversation

@oddharsh

Copy link
Copy Markdown
Owner

What changed

  • compile the canonical section icon definitions into individual 32x32 SVG assets
  • reference those assets from the generated taskbar pins
  • remove the duplicate runtime icon table and SVG construction from nav.js

Why

The same icon data was shipped both as generated markup and as JavaScript. Building the small favicon assets once removes that runtime duplication while preserving the section icons and their no-JavaScript behavior.

Impact

  • nav.js Brotli size falls from roughly 7.06 KB to 5.67 KB, a reduction of about 20%
  • existing navigation, tooltip behavior, and random photo selection are unchanged

Validation

  • full contract test suite: 239 passed
  • production build passed
  • generated favicon decoded in Chrome at its intended 32x32 dimensions
  • git diff --check passed

@cloudflare-workers-and-pages

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 skipped
View logs
aadhar-sh f809f83 Aug 12 2026, 02:49 AM

@oddharsh
oddharsh marked this pull request as ready for review August 12, 2026 02:49
@github-actions

Copy link
Copy Markdown
Contributor

Wire-size diff vs merge base

6e22d6c → 6a67b00

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: 67.49 KiB → 66.13 KiB (-1.36 KiB, -2.0%) across 16 files, 1 changed.

file base head Δ
nav.js 6.90 KiB 5.54 KiB -1.36 KiB (-19.7%)

Pages

Total Brotli: 484.87 KiB → 487.05 KiB (+2.18 KiB, +0.4%) across 46 files, 44 changed.

No single file moved by 0.13 KiB or more.

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

Worker bundle

276.18 KiB → 276.27 KiB gzip (+0.09 KiB, +0.0%)

Largest module deltas (raw bytes in bundle):

file base head Δ
.build/www/_worker.js/lib/desktop.js 6.17 KiB 6.61 KiB +0.45 KiB (+7.3%)

Dictionary deltas (pd/)

138 → 138 deltas, 529.88 KiB → 536.41 KiB (+6.52 KiB).

oddharsh added a commit that referenced this pull request Aug 12, 2026
…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 added a commit that referenced this pull request Aug 12, 2026
…e double-counting (#346)

* perf: compile section favicon assets

* perf: put a section's favicon in its own head, and stop the size table 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.

---------

Co-authored-by: Aadharsh Pannirselvam <19518661+oddharsh@users.noreply.github.com>
@oddharsh

Copy link
Copy Markdown
Owner Author

Shipped in 769a72b, via #346.

This reads "closed" rather than "merged" for a mechanical reason: f809f83 was an ancestor of #346's branch, so the squash carried its content into main under a new SHA. GitHub closed this one automatically the moment that landed. The original commit never becomes an ancestor of main after a squash, so there is nothing for it to stamp as merged.

The work is all there. www/section-icons/*.svg is on main, all 11 tiles, and pnpm run gen:shell against main is a no-op, so the generated shell agrees with shell-data.mjs.

Two things changed on the way in, both in #346:

  • The favicon moved into each page's own <head>. setFavicon() and the per-pin data-favicon attribute are gone. The attribute rode all 46 pages so that 11 could read one, and applying it at boot meant a section page painted one icon and swapped. lunaPage reads its own route now, and the four section routes outside it (/writing, /serendipity, /coffee, /terminal) state it themselves. That turned this PR's +2.18 KiB on pages into -0.19 KiB, and its +6.52 KiB of dictionary deltas into +0.09 KiB.
  • /section-icons/* got a cache rule. Unruled, the tiles inherited 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 at all.

Routing this through #346 rather than merging here first was about the window between the two: this PR alone would have put both of those into production until the follow-up landed.

The -1.36 KiB off nav.js, which is what this PR was for, is intact.

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
oddharsh deleted the codex/compile-section-favicons branch August 17, 2026 21:21
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