Repository navigation
[ai][go][js][make][cfg][docs] embed all of web/build, serve /favicon.ico from it, stop embedding static/ - #704
Merged
Merged
Conversation
WebBuildFiles was `//go:embed web/build/*`. The glob names web/build/_app, so that directory is kept, but Go then skips every file below it whose name starts with "_" or ".". SvelteKit names shared chunks _app/immutable/chunks/<hash>.js, and the hash alphabet includes "_" (D_9rjNUa, start.BQyT_uoz.js). A chunk whose hash started with one would be missing from the binary and 404 under /app in production, while the --debug server, which reads web/build from disk, served it. Today's single-chunk build has one hash-named file per build, so the odds were small but never zero. The SvelteKit 3 split strategy would make dozens. `all:web/build` keeps everything, as DocsSiteFiles already does. With a stub build plus fake chunks _Ab12Cd3.js and Bx9_k2Lm.js, `go list` showed the old directive embedding only the second; the new one embeds both. StaticFiles (`static/*`) keeps its pattern: the server routes only /favicon.ico to it. TestWebBuildEmbedKeepsUnderscoreFiles pins the directive itself, since no committed build contains a "_" file to prove the point at run time. TestWebBuildEmbedMatchesDisk checks that every file under web/build is readable from the embed, which CI's integration job runs against a real build. With the old directive and the fake chunk on disk, both fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tic/ StaticFiles (`//go:embed static/*`) existed for one route, /favicon.ico, and in production that route had been a 404: the handler was http.FileServer(http.FS(radar.StaticFiles)), which looks for favicon.ico at the root of a tree whose files sit under static/. Dev mode read ./static from disk and worked, which is why it went unnoticed. The SvelteKit app links /app/favicon.ico itself, but /docs/ declares no icon, so browsers fell back to the broken /favicon.ico. The long-running local server answers 404 there today. The favicon now comes from the web build, web/static/favicon.ico by way of web/build: fs.Sub of WebBuildFiles in production and ./web/build in dev, the same tree /app is served from. This is items 1.1 and 1.3 of the binary size reduction plan. The two icons are byte-identical, so nothing visible changes except that the route works. Dropping StaticFiles also stops the binary embedding whatever sits in static/, which web/scripts/copy-build.js filled with a copy of the web build. Identical files are stored once by the compiler, so in a local build the binary shrinks by only 16,752 bytes. The cost was stale pages that no longer matched the build (31 KB of them in a long-lived checkout), and once the 172 MB of accumulated builds the plan measured. TestStart_ProductionModeListenAndShutdown already requested /favicon.ico but checked nothing, and it fails against the old handler with a 404. It now requires 200 and web/build/favicon.ico's bytes, or a 404 when the build is the index.html-only stub. TestStart_DevModeWithBuildDir checks that dev mode serves the build's favicon. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The web build's postbuild hook, web/scripts/copy-build.js, copied web/build/* into the repo-root static/ so the server could embed it. #49 added the copy when /app was already served from web/build, and the server never served anything from static/ but /favicon.ico. With the favicon now taken from the web build, nothing reads static/, so the hook and the script go, and so does the tracked static/favicon.ico, which is byte-identical to web/static/favicon.ico. - make clean-web removed three static/_app subdirectories; it now removes static/ outright, clearing what older builds left in existing checkouts. - .gitignore keeps static/ ignored so those leftovers stay out of git status, and says why. - The static-build Docker context no longer re-includes static/. `make build-web` now leaves no static/ behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ticks the plan's first three checklist items: the static/ embed and StaticFiles are gone, and the favicon comes from the web build in production and dev. A note at the top records what was measured on the way. static/ held a byte-identical copy of the web build, which the compiler's content-addressed storage kept almost free (16,752 bytes), plus stale pages. The favicon it was kept for had been a 404 in production. The two Markdown links to ../../static become code spans, since a clean checkout no longer has that directory. The canonical hub's "currently ~211 MB" becomes "when the plan was written", and the stale static/ row reads 0 MB. ARCHITECTURE.md says where /favicon.ico comes from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#677 left "serialization" in the plan's effort table, which fails the British-spelling step of `make lint-docs` on main. Same length, so the table stays aligned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
ddol
added a commit
that referenced
this pull request
Oct 7, 2026
…tatic 4 (#705) * [ai][js][cfg] web: import src/lib through #lib subpath imports SvelteKit 3 removes the $lib alias in favour of a Node subpath import declared in package.json "imports". Vite, TypeScript and Jest already resolve that on SvelteKit 2, so the rename lands first, on its own, and the kit 3 migration that follows carries only the changes that need judgement. The 34 files follow the rule `sv migrate sveltekit-3` applies: `$lib/foo` becomes `#lib/foo.js` when foo is a .ts module (TypeScript's convention for naming a .ts source), and a .svelte import keeps its extension. The extension is not optional: TypeScript rejects an extensionless `#lib/foo` with TS2307. Jest maps `#lib/foo.js` back to src/lib/foo and lets moduleFileExtensions find the .ts. On SvelteKit 2.70.3: svelte-check 0 errors, 906 tests pass, lint clean, build passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * [ai][js][cfg] web: migrate to SvelteKit 3 and adapter-static 4 Dependabot's kit 3.0.0 bump (#681) failed the web build and #693 held kit at 2.x. This is the migration that bump needed. Config. Kit 3 refuses svelte.config.js, so its options move into sveltekit({...}) in vite.config.ts, flat rather than under `kit:`; the note on why $scene is a Vite alias and not a kit alias moves with them. tsconfig.json extends $app/tsconfig, which svelte-kit sync writes into node_modules and which already sets allowJs, checkJs, bundler resolution and the rest, so only strict and sourceMap remain. That parent has no include list, and kit's suggested "*" would put jest.config.js and the .cjs transformer under checkJs, so the include is src plus vite.config.ts, which kit 2's generated config also checked. Bundling. Kit 3 sets codeSplitting itself and ignores the manualChunks: () => 'everything.js' that #25 added ("bundle chunks, improve go server log-lines"; #25 served web/build from disk, and the embed came in #30). output.bundleStrategy: 'single' is kit's option for the same result: one bundle.<hash>.js and one bundle.<hash>.css, the CSS byte-identical to kit 2's everything.<hash>.css. It keeps the shipped shape of the kit 2 build. Kit's split default would also serve correctly now: since #704 the server embeds all of web/build, so a chunk whose hash begins with "_" is no longer dropped from the binary. Switching to split is a separate decision about load behaviour on the Pi. emptyOutDir goes: kit overrides it, and adapter-static clears build/ itself. Build provenance. Kit 3 fills %sveltekit.env.NAME% in app.html only for variables declared in src/env.ts. Without one, the app-git-sha, app-build-time and app-build-version meta tags render empty, and `make dev-go` would rebuild web on every run, since its staleness check greps app-build-version. src/env.ts declares the three as public and static, and required: set-build-env.js sets all three for every build and dev run, and a bare `vite build` now fails naming the missing ones. Runtime APIs. $app/environment is now $app/env. $app/stores is gone, and the two pages that used it read $app/state through svelte/store's toStore: they use legacy `$:` statements, which never see $app/state change. Read directly, a saved new scene would leave the reused /scene/[id] component on 'new', so a second Save would create a second scene, and the tracks page would ignore a changed sensor_id. page.url is now a ReadonlyURL, so the layout gives svelte-ux's NavItem (typed for URL, reads only the pathname) a copy. resolve() treats a leading "/" as a route ID, so dynamic links pass params: resolve('/scene/[id]', { id }). engines.node is >=22.17, kit 3's own floor. CI's setup-node "22" and the ubuntu-24.04 runner image (Node 22.23.3) both satisfy it. Verified: svelte-check 0 errors, the web tests, lint, make build-web, and a smoke test of the embedded build under /app on a scratch port covering every route, client-side navigation, scene create then update (one POST, then a PUT), and a sensor_id change on the tracks page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * [ai][ci] dependabot: let SvelteKit and adapter-static majors through again #693 ignored semver-major updates of @sveltejs/kit and @sveltejs/adapter-static until the web app was migrated to kit 3. It now is, so the hold goes and Dependabot will raise kit 3.x and adapter-static 4.x bumps as usual. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * [ai][make][sh] web tooling after SvelteKit 3: watch vite.config.ts, ask for Node 22.17 ensure-dev-web-build did not treat a newer vite.config.ts as making the web build stale. The SvelteKit config now lives there (svelte.config.js, which it did not watch either, is gone), so it does. dev-setup.sh asked for Node.js 18 and offered the NodeSource 20.x installer; the web app needs 22.17, which kit 3 requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * [ai][cfg] web: drop the minimumReleaseAgeExclude entry for kit 2.70.2 #542 exempted @sveltejs/kit@2.70.2 from pnpm's minimum release age so that bump could install the day it shipped. The lock has since moved to 2.70.3 and now to 3.0.0, so the exemption names a version nothing resolves to. The lockfile does not record the setting, and `pnpm install --frozen-lockfile` is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * [ai][js][cfg] web: mark the two dynamic routes client-only so prerendering is clean Every build printed prerender_unseen_routes for /scene/[id] and /site/[id]. Kit 3 leads with the error code, so it now reads like a failure, but kit 2 printed the same thing; handleUnseenRoutes: 'warn' (from #49) kept it from failing the build. The root +layout.js marks every route prerenderable, and kit cannot prerender these two: their ids are database rows, and with ssr off the prerendered HTML is a bare shell with no links for the crawler to follow. They work at run time because the Go server answers any unknown /app path with index.html, the app shell, and the client router renders the page. Each route now says so with `export const prerender = false`. With two routes not prerendered and no adapter fallback page, adapter-static's strict check would refuse the build, so it gets `strict: false` and a comment naming the Go server's fallback as the reason that is safe. handleUnseenRoutes goes back to kit's default, 'fail': with the marker on site/[id] moved aside, the build now fails naming /site/[id] instead of passing with a warning. The build output is unchanged (the same 17 files, the version stamp in index.html intact). /app/site/1 and /app/scene/<id>, loaded directly from the embedded build, render their pages from the fallback shell with no console errors. svelte-check 0 errors, 909 tests, lint clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
Summary
Fixes two things about what the Go binary embeds from the web build. It also finishes items 1.1 and 1.3 of the binary size reduction plan.
//go:embed all:web/build. The oldweb/build/*keptweb/build/_appbecause the glob names it, but silently skipped every file below it whose name starts with_or.. SvelteKit names shared chunks<hash>.js, and the hash alphabet includes_(D_9rjNUa,start.BQyT_uoz.js). A chunk whose hash began with one would be missing from the binary and 404 in production, while--debug(which serves from disk) worked.go listwith a fake_Ab12Cd3.jsconfirmed it: the old directive dropped the file, the new one keeps it./favicon.icowas a 404 in production.static/is no longer embedded or written.StaticFiles(static/*) existed only for/favicon.ico, but the handler servedhttp.FS(StaticFiles)withoutfs.Sub, so it looked forfavicon.icoat the root of a tree whose files sit understatic/. A long-running local server answers 404 there today. The app links/app/favicon.icoitself, but/docs/declares no icon, so browsers hit the broken route. The favicon now comes from the web build: embedded in production,./web/buildin dev.web/scripts/copy-build.jsand itspostbuildhook only existed to feed that embed, so they go, along with the trackedstatic/favicon.ico(byte-identical toweb/static/favicon.ico).Size
Smaller than the plan's history suggests. The compiler stores embedded file contents content-addressed, so
static/'s byte-identical copy of the web build (35 files, 1,820,699 bytes) was stored once. Removing the embed shrank a local darwin/arm64make build-radar-localbinary by 16,752 bytes. Whatstatic/still cost was content that no longer matched the build: 3 stale pages (31 KB) in one long-lived checkout, and at worst the 172 MB of accumulated builds the plan originally measured. That can't recur now, because nothing reads or writesstatic/.Commits
[ai][go] embed all of web/build, including _-prefixed files:TestWebBuildEmbedKeepsUnderscoreFilespins the directive. No committed build contains a_file to prove the fix at run time.TestWebBuildEmbedMatchesDiskchecks every on-disk build file is embedded; CI's integration job runs it against a real build. Both fail with the old directive.[ai][go] serve /favicon.ico from the web build and stop embedding static/:TestStart_ProductionModeListenAndShutdownalready requested/favicon.icowithout checking the response. It now requires a 200 withweb/build/favicon.ico's bytes, or a 404 for the index-only stub build, and it fails against the old handler.TestStart_DevModeWithBuildDirchecks the dev path.[ai][js][make][cfg] stop copying the web build into static/: removescopy-build.js,postbuildand the tracked favicon.make clean-webnow removesstatic/outright..gitignorekeepsstatic/ignored, with a note, so old checkouts' leftovers stay quiet. The static-build Docker context drops!static/.[ai][docs] binary size plan: phase 1.1 and 1.3 landed: ticks the checklist, records the measurements, and turns two links to the now-absentstatic/into code spans. It also updates the canonical hub and ARCHITECTURE.md.[ai][docs] physical pose plan: British spelling of serialisation: unrelated one-word fix, somake lint-docspasses again.Verification
All of the following were re-run after rebasing onto
mainat #685, on Go 1.27.1.make format-go,make lint-go,make test-go(84 packages) pass. The new and changed tests pass under-tags=pcap.make lint-web,make test-web(909) pass.make build-webpasses and leaves nostatic/.make build-radar-localbuilds a stamped binary.web/build.check-backtick-paths.py,check-relative-links.pyandcheck-plan-canonical-links.pypass withstatic/absent.make lint-docspasses. Onmainit failed on one British-spelling hit,serializationindocs/plans/lidar-swift-physical-pose-plan.md(from [ai][docs] devlog register format and PR numbers; 0.5.2 backlog cleanup #677), which commit 5 fixes.After merging, run
make clean-webonce in existing checkouts to delete the leftoverstatic/.🤖 Generated with Claude Code