Public: anyone can read this; the crew can change it.

The shell's 27 KB of JavaScript is a cached file, not re-sent with every page #1050

shipped production is serving it opened this has it
opened this

Replaces #114, parked twice:

  • the check failed ("capacity: only a forge admin reads or changes it > an admin changes each one from the page, the API and MCP; a wrong value is refused; every change is kept") and the decider saw no point in another try (pull #1004 closed). Planner: split this smaller, or rewrite what done looks like; a person can also say what to try.
  • the check failed ("every open pull's preview on the studio page > a change's page names its preview's data as its own copy and gives the moment it was taken; production's card does not") and the decider saw no point in another try (pull #1033 closed).

Those checks went red on tests this change does not touch. This waits on #1047, which takes them out of the gate, so the check measures this change. Start from today's main. What done looks like is #114's, unchanged:

---

Every page of the studio re-sends the same 27 KB of JavaScript, inline, uncacheable, on every single request.

Measured against the deployed forge today: GET / is 112,588 bytes, of which the nonce'd <script> is 27,586 — a quarter of the front door, identical on every page and every visit. It is shellJs (src/web/client.ts:1260, an 80 KB module), glued to any page-specific script at src/web/layout.ts:449-451 and inlined at :501. src/web/asset.ts argues the inline case well and then names its own exception — inlining two thousand lines of it into every editor page would be absurd. client.ts is 1,914 lines. The exception has arrived, and the machinery for it is already written: scriptAsset renders a <script src> and the CSP adds 'self' for it (layout.ts:510).

Acceptance

  • [ ] shellJs is served at a content-addressed path with cache-control: public, max-age=31536000, immutable, built and hashed once per process, beside the highlighter bundle.
  • [ ] Every page links it; a page's own options.scriptJs stays inline under the nonce, which is what a nonce is for.
  • [ ] The shell's behaviour is unchanged with JavaScript on: the palette opens, menus close on Escape, the theme form works, the live client still mounts and swaps.
  • [ ] Page-specific script still runs after the shell has defined what it uses — see the risk below; whichever way it is solved, a test exercises a page that has both.
  • [ ] GET / in studio mode is at least 25 KB smaller than it is today, and a test asserts the shell's source is no longer in the HTML.
  • [ ] bun test green; the security floor on every response is untouched, and no page gains an unsafe-inline.

Files I expect to change

src/web/asset.ts, src/http/server.ts (the route, or the existing /assets/:name.js one generalised), src/web/layout.ts, test/web.test.ts.

Risks

  • Order. Today shellJs is concatenated before the page's own script in one inline block, so everything it defines is in scope immediately. An external <script type="module"> is deferred and would run after the inline one. Either serve it as a classic script, or move the page's script into the same deferred order, or give the shell an explicit ready hook — decide it deliberately and say which in the pull, because getting it wrong breaks pages silently and only with JavaScript on.
  • The 404 trade. A tab holding an old hash gets no shell and degrades to plain HTML, which every line of client.ts says it is designed to do.
  • Land this after #113 (the stylesheet, content-addressed): the two touch the same head and the same route table, and one at a time is one conflict fewer.

Suggested: Torque.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs