Skip to content

refactor: www/ splits into src/pages/ and public/ - #457

Merged
oddharsh merged 1 commit into
mainfrom
refactor/pages-and-public
Aug 18, 2026
Merged

oddharsh merged 1 commit into
mainfrom
refactor/pages-and-public

Conversation

@oddharsh

Copy link
Copy Markdown
Owner

The last of the www/ split, and the seventh slice of lifting the parked bun branch's renames onto main.

36 HTML documents become src/pages/. The other 804 files, which are bytes a browser fetches unchanged, become public/. The staged root renames with them: .build/www is .build/public, and wrangler.jsonc's assets.directory follows.

The mapping needed no judgement. main's www/ held exactly 840 files; the branch's two destinations hold exactly those 840; every one lands in exactly one of them. Every HTML is a page, nothing else is.

Every served artifact is byte-identical

1497 compared with the root name normalised. The four remaining differences are path references inside cal, serendipity and the Worker: three comments and one JSON import specifier.

The build composes the served tree now

public/ + src/pages/ + src/content/ + src/client/ + src/styles/ all stage into .build/public, so the URL layout is untouched while the source layout says what each file is. A new servedFiles() walks the three authored roots in one place, because a tripwire that walks only some of them reports a clean tree it never read.

Six things the gates caught

The pattern is the point: a blanket path sweep cannot tell a source read from a staged path, an asset from a document, or a path from a regex.

what broke found by
staticShellPages() read www/ for the freshness check build invariant
the Worker imported ../../www/images/hashes.json build
gen-md-twins looked for page HTML under www/, then read the generated tier from the staged root build's own twin counters
the page generators in pipelines/ wrote into www/ pages:check
the .src.html twin read its authored source from the wrong root the byte control
assert.match(lens, /… 401 && www/) — a regex delimiter read as a path separator the test suite

Two deserve more than a row. The twin bug fell into a catch and shipped the staged bytes instead, so every readable twin carried rewritten /a/ hashes — the entire purpose of a twin, lost with no error. The build's own log line is what exposed it: 47 from staged where main says 12. And the regex case rewrote an assertion out from under a real variable named www, which no amount of path-checking would have caught.

Gates

305 tests, build, lint, typecheck, routes:check, pages:check, tools:check, perf-budget, photos:check, check-wrangler, the cal suite, and a wrangler deploy --dry-run.

After this

www/ is gone. The tree reads: public/ is what a browser fetches, src/ is what people author, tools/ is what developers run, config/ is declared state. That is the whole restructure, and the parked migration (#443) is now a toolchain swap against a settled layout rather than a 1229-file branch.

The last of the www/ split. 36 HTML documents become src/pages/; the other 804
files, which are bytes a browser fetches unchanged, become public/. The staged
root renames with them: .build/www is .build/public, and wrangler.jsonc's
assets.directory follows.

Seventh and final slice of lifting the parked bun branch's renames onto main
(#450, #451, #452, #453, #455, #456). The mapping needed no judgement: main's
www/ held exactly 840 files, the branch's two destinations hold exactly those
840, and every one lands in exactly one of them. Every HTML is a page; nothing
else is.

EVERY SERVED ARTIFACT IS BYTE-IDENTICAL, 1497 compared with the root name
normalised. The four remaining differences are path references inside cal,
serendipity and the Worker: three comments and one JSON import specifier.

THE BUILD COMPOSES THE SERVED TREE NOW rather than copying one directory.
public/ + src/pages/ + src/content/ + src/client/ + src/styles/ all stage into
.build/public, so the URL layout is untouched while the source layout says what
each file IS. servedFiles() walks the three authored roots in one place, because
a tripwire that walks only some of them reports a clean tree it never read.

SIX THINGS THE GATES CAUGHT, and the pattern is worth stating: a blanket path
sweep cannot tell a source read from a staged path, an asset from a document, or
a path from a regex.

  * staticShellPages() read www/ for the freshness check
  * the Worker imported ../../www/images/hashes.json
  * gen-md-twins looked for page HTML under www/, then read the generated tier
    from the staged www/ root
  * the page generators in pipelines/ wrote into www/
  * the .src.html twin read its AUTHORED source from the wrong root, so it fell
    into a catch and shipped the STAGED bytes: readable twins carrying rewritten
    /a/ hashes, which is the whole point of the twin lost silently. The build's
    own counter is what showed it, 47 from staged where main says 12.
  * and one false positive: `assert.match(lens, /… 401 && www/)` is a regex whose
    DELIMITER the sweep read as a path separator, rewriting the assertion out
    from under a real variable named www.

Gates: 305 tests, build, lint, typecheck, routes:check, pages:check,
tools:check, perf-budget, photos:check, check-wrangler, cal, wrangler dry-run.
@github-actions

Copy link
Copy Markdown
Contributor

Wire-size diff vs merge base

1ead759 → a312aae

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: 0.00 KiB → 75.10 KiB (+75.10 KiB, n/a) across 21 files, 21 changed.

file base head Δ
lens.js — 22.02 KiB new
luna.css — 7.60 KiB new
nav.js — 5.73 KiB new
nav-run.js — 5.09 KiB new
lens-tools.js — 4.99 KiB new
lens-browser.js — 4.09 KiB new
quiz.js — 2.95 KiB new
lens-wire.js — 2.86 KiB new
tooltip.js — 2.78 KiB new
icons.svg — 2.67 KiB new
lens-reader.js — 2.48 KiB new
notepad.js — 2.08 KiB new
infotip.js — 1.83 KiB new
nav-run.css — 1.75 KiB new
nav-tray.js — 1.60 KiB new
lwe-base.css — 1.31 KiB new
hoist.js — 1.08 KiB new
nav-tray.css — 0.78 KiB new
infotip.css — 0.52 KiB new
lens-boot.js — 0.49 KiB new
… 1 more

Pages

Total Brotli: 0.00 KiB → 502.10 KiB (+502.10 KiB, n/a) across 48 files, 48 changed.

file base head Δ
garage/horizon.html — 65.30 KiB new
access/index.html — 38.76 KiB new
lwe/vigenere.html — 18.94 KiB new
lwe/lean.html — 16.97 KiB new
lens.html — 16.05 KiB new
lwe/dac.html — 15.79 KiB new
garage/wire.html — 14.07 KiB new
garage/encoding.html — 13.97 KiB new
garage/tooltips.html — 13.80 KiB new
pixel-peeper/index.html — 11.95 KiB new
garage/blueprint.html — 11.74 KiB new
garage/pqc.html — 11.07 KiB new
garage/index.html — 10.49 KiB new
garage/pretext.html — 10.30 KiB new
garage/teardown.html — 10.30 KiB new
garage/chunks.html — 10.10 KiB new
lwe/encoding.html — 10.10 KiB new
garage/cloudflare.html — 9.89 KiB new
lwe/fhe.html — 9.74 KiB new
garage/compression.html — 9.71 KiB new
… 28 more

Worker bundle

No change. 230.10 KiB gzip.

Dictionary deltas (pd/)

0 → 144 deltas, 0.00 KiB → 456.84 KiB (+456.84 KiB).

@oddharsh
oddharsh merged commit 1b621da into main Aug 18, 2026
6 of 7 checks passed
@oddharsh
oddharsh deleted the refactor/pages-and-public branch August 18, 2026 16:01
oddharsh added a commit that referenced this pull request Aug 18, 2026
The bullet under the header still called public/ the live site including the
_worker.js/ dispatcher, which is two moves out of date: the Worker left the
served tree in #450 and the documents left it in #457. It now names what each
root is for, which is the same question the table below answers per entry.
oddharsh added a commit that referenced this pull request Aug 18, 2026
* docs: the layout table describes the tree that now exists

Seven slices moved almost every path in this repository (#450, #451, #452,
#453, #455, #456, #457), and the automated sweeps that repointed code also
walked CLAUDE.md. That left the map wrong in two different ways.

STALE: the table still said public/ holds "pages, client scripts, CSS, photos,
dictionaries". It holds none of the first four now. Rows added for src/pages,
src/content and src/dict; scripts/ folded into the tools/ row; every row's
description checked against the tree rather than against its own history.

FALSE: the sweep rewrote HISTORICAL names, so the file claimed the Worker lived
at `public/_worker.js/` and the photo pipeline at `public/scripts/`. Neither
ever existed. A stale note is a note somebody distrusts; a false one is a note
somebody follows.

The naming note now records the full sequence, holding -> www -> the split, and
says explicitly that the rewrites deliberately left old mentions alone, so
reading `www/` in an older note is correct rather than a typo. It also drops the
"duplicate path" explanation, since scripts/ no longer exists to be confused
with tools/photos/.

And it states the test for where a new file goes, which the table never did: if
a build step transforms it, it is src/; if it ships byte-for-byte, it is public/.

* docs: the intro names the split tree too

The bullet under the header still called public/ the live site including the
_worker.js/ dispatcher, which is two moves out of date: the Worker left the
served tree in #450 and the documents left it in #457. It now names what each
root is for, which is the same question the table below answers per entry.

---------

Co-authored-by: Aadharsh Pannirselvam <19518661+oddharsh@users.noreply.github.com>
oddharsh added a commit that referenced this pull request Aug 19, 2026
…eated (#467)

#452 moved the photo pipeline to tools/photos/ and #451/#457 moved the served
tree to public/, and four ignore rules kept naming the old locations:

  www/scripts/zenc/target/  ->  tools/photos/zenc/target/
  www/scripts/.venv/        ->  tools/photos/.venv/
  www/ad/                   ->  public/ad/
  www/images/meta/          ->  public/images/meta/

None of the new paths existed yet, which is why nobody hit it. The first person to
run photos:env or build zenc would have had a 13M venv and 218M of Rust build
output appear as untracked, in the one place a git add -A would sweep them in.

Verified with git check-ignore: all four new paths are ignored, and none of them
is tracked today, so no committed file changes status.

Co-authored-by: Aadharsh Pannirselvam <19518661+oddharsh@users.noreply.github.com>
oddharsh added a commit that referenced this pull request Aug 24, 2026
…no card

`bun run og-cards` has been writing to a directory nothing serves since the
2026-08-18 split (#457) deleted www/. Both halves of the pipeline held
`const HOLDING = path.join(ROOT, "www")`, which broke three things at once:
the cards went to a www/og that mkdir happily created, the wallpaper read
missed luna.css, and the section walk found no documents to enumerate.

IT WAS SILENT FOR SIX DAYS because nobody adds a page most weeks. The tell was
sitting in the tree: 18 garage cards against 22 garage pages, and no card
committed since #457 itself. /garage/useragent shipped on 2026-08-21 with no
card and nothing said so. This is gotcha 40's family with a hardcoded name
instead of a relative expression, and the same repair applies: name the tree
each use actually wants, so the next rename breaks at the constant rather than
at the output.

One HOLDING becomes four constants, because the served tree is three
directories now and no single name is honest about it:

  PUBLIC  public/          bytes that ship unchanged; the cards land here
  PAGES   src/pages/       the documents the section walk enumerates
  STYLES  src/styles/      luna.css, for the Bliss wallpaper read
  SERVE   .build/public/   the merged URL root, which only the build assembles

The local-server path now fails by name when .build/public is missing, rather
than serving an empty tree, which would read as every page having lost its demo.

FOUR CARDS BAKED against production: garage-typed-config, garage-hidden-flags,
garage-useragent and lwe-fuse. Three first came out through the window
fallback, which the HERO table's own comment records as no longer acceptable
since the Explorer chrome landed in #275: a whole-window card reads as a
screenshot of a browser rather than one demo panel floated on the desktop. They
have hero selectors now and were re-captured.

THE FAVICON STAMP TOOK TWO TRIES AND THE FIRST ONE MADE IT WORSE. Generated
pages ship no `rel="icon"`, so their cards fell back to a blank blue square.
Passing `/section-icons/<section>.svg` as the src looked like the fix and
rendered an EMPTY WHITE BOX: the card is built with `setContent`, so the
document has no base URL and a root-relative path resolves against nothing.
Every page whose icon works declares it as `data:image/svg+xml,...` for exactly
that reason. The fallback inlines the file the same way now, and all four cards
carry their section mark. Both attempts were rendered and looked at, which is
the only reason the first one did not ship.

Green: build, 354 contract tests, typecheck, oxlint type-aware, tools:check.
oddharsh added a commit that referenced this pull request Aug 24, 2026
…no card (#550)

* garage: two pages on wrangler config, from the work that produced them

/garage/typed-config and /garage/hidden-flags, both written out of the
measurements taken while converting cf-garage to wrangler's experimental
TypeScript config.

TYPED-CONFIG is the conversion, measured on both sides. The centrepiece is a
three-pane explorer: toggle bindings and watch the config you write, the
resolved config the platform receives, and the inferred Env interface move
together. Every row in it is measured rather than modelled. The resolved JSON
is what `wrangler build --x-cf-build-output` wrote for that binding, and each
type is what tsc reported for that key, probed one key at a time.

Two findings the page exists for. bindings.text("default") types the variable
as the literal "default" and bindings.json({dcz: true, tier: 2}) comes back as
{ dcz: true; tier: number }, so a plain variable's value reaches the type,
which a JSON config structurally cannot do. And the Durable Object type goes
BACKWARDS: DurableObjectNamespace<undefined> under inference against a real
class threaded through by `wrangler types` on the JSON config. That was
measured under both entrypoint forms, the path string and the documented
{ type: "cf-worker" } module import, because one form failing reads as holding
it wrong. So the scoreboard is split rather than a win.

HIDDEN-FLAGS is the survey. All 19 flags carrying `hidden: true` in the 4.125.0
bundle, filterable, complete rather than curated, with the five that touch this
site marked. The argument is that acceptance and effect are separate questions
and the first is usually free, carried by four cases from this repo: the closed
Browser Run schema, the zstd dictionary option three runtimes disagree about in
silence, --max-versions proving a cap lives on the server through its own source
comment, and --old-asset-ttl reading like a fix while being wired to the legacy
Workers Sites path this site does not run. Ends on a control picker: name the
shape of the doubt, get the experiment and its decision table.

A BUILD INVARIANT WAS NARROWER THAN THE IDS IT SCANS FOR. Gallery link
discovery matched /garage/[a-z0-9]+ while the generator has always allowed
hyphens, and no page had ever carried one. The first hyphenated id generated
fine, registered fine, got a hand-written shelf card, and then failed the
invariant claiming the card was missing from the file it was sitting in. Widened
to match the generator's own id class, with the reasoning at the line.

The 38 touched static pages are one regenerated field: the garage taskbar pin
goes from data-count="20" to "22". `bun run gen:shell` owns that, and the
pipeline README's step list does not mention it, which is worth knowing before
the next page.

Verified in a real browser on a port confirmed to belong to this worktree:
both demos drive, the filters and tabs re-render, the quiz renders 7 questions,
and at 375px neither page overflows horizontally while the wide tables and code
panes scroll inside their own containers.

Green: build (52 documents, 3015 internal refs resolve, 33 quiz payloads
byte-equal), pages:check, 354 contract + 353 node tests, routes:check,
perf-budget, typecheck, oxlint type-aware.

* fix(og): the card pipeline still pointed at www/, and four pages had no card

`bun run og-cards` has been writing to a directory nothing serves since the
2026-08-18 split (#457) deleted www/. Both halves of the pipeline held
`const HOLDING = path.join(ROOT, "www")`, which broke three things at once:
the cards went to a www/og that mkdir happily created, the wallpaper read
missed luna.css, and the section walk found no documents to enumerate.

IT WAS SILENT FOR SIX DAYS because nobody adds a page most weeks. The tell was
sitting in the tree: 18 garage cards against 22 garage pages, and no card
committed since #457 itself. /garage/useragent shipped on 2026-08-21 with no
card and nothing said so. This is gotcha 40's family with a hardcoded name
instead of a relative expression, and the same repair applies: name the tree
each use actually wants, so the next rename breaks at the constant rather than
at the output.

One HOLDING becomes four constants, because the served tree is three
directories now and no single name is honest about it:

  PUBLIC  public/          bytes that ship unchanged; the cards land here
  PAGES   src/pages/       the documents the section walk enumerates
  STYLES  src/styles/      luna.css, for the Bliss wallpaper read
  SERVE   .build/public/   the merged URL root, which only the build assembles

The local-server path now fails by name when .build/public is missing, rather
than serving an empty tree, which would read as every page having lost its demo.

FOUR CARDS BAKED against production: garage-typed-config, garage-hidden-flags,
garage-useragent and lwe-fuse. Three first came out through the window
fallback, which the HERO table's own comment records as no longer acceptable
since the Explorer chrome landed in #275: a whole-window card reads as a
screenshot of a browser rather than one demo panel floated on the desktop. They
have hero selectors now and were re-captured.

THE FAVICON STAMP TOOK TWO TRIES AND THE FIRST ONE MADE IT WORSE. Generated
pages ship no `rel="icon"`, so their cards fell back to a blank blue square.
Passing `/section-icons/<section>.svg` as the src looked like the fix and
rendered an EMPTY WHITE BOX: the card is built with `setContent`, so the
document has no base URL and a root-relative path resolves against nothing.
Every page whose icon works declares it as `data:image/svg+xml,...` for exactly
that reason. The fallback inlines the file the same way now, and all four cards
carry their section mark. Both attempts were rendered and looked at, which is
the only reason the first one did not ship.

Green: build, 354 contract tests, typecheck, oxlint type-aware, tools:check.

---------

Co-authored-by: Aadharsh Pannirselvam <19518661+oddharsh@users.noreply.github.com>
oddharsh added a commit that referenced this pull request Oct 17, 2026
8c checks that every garage/lwe page on disk is registered in
site-manifest.json. The www/ split (#457, 2026-08-18) pointed its walk at
public/garage and public/lwe, which still exist for av2/, enc/ and lean/
assets but hold no .html. readdir succeeded, so the check inspected 0 of
the 39 pages in src/pages for 60 days and passed every build.

The walk now reads src/pages, counts what it inspects, fails under 30
pages, and prints the count in the invariants summary.

Invariant 10 (conflict markers) had the same shape: it read serendipity
as /\.js$/ after #523 (2026-08-23) made the module TypeScript, so it
scanned 0 of 3 files. It reads .ts now, and every root it walks must
yield at least one file.

Gotcha 75 records the class: a walk over a tree that exists but no
longer holds what it checks passes on zero files.

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