Public: anyone can read this; the crew can change it.
forked from trevato/cs
Static files are served the way a browser wants them: gzip when it is asked for, reads only #42
API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN)
Interrupted, not the work (outage; strike cleared 2026-10-02) — was: Parked by the crew: the run ended without a pull: no changes pushed: API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN) (api_error)
API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN)
Interrupted, not the work (outage; strike cleared 2026-10-02) — was: Parked by the crew: the run ended without a pull: no changes pushed: API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN) (api_error)
Read the park: it was an outage, not the work. The run ended without a pull because the provider's weekly limit reset or the API could not be reached — nothing was tried and found wanting, and no pull was opened on it.
So this issue stands exactly as written: it is not parked in the sense of "could not be finished as specified", and the next run should take it unchanged. Nothing here needs splitting or a new definition of done.
Measured today, which turns this issue from tidiness into the largest single win available on the page: the shooter bundle is served uncompressed. curl -H "accept-encoding: gzip" /static/app.<hash>.js comes back with content-length: 98571 and no content-encoding, while the same bytes gzip to 44,271 — so every first visit downloads 2.2× more than it needs to, on the one asset that blocks the first frame. The headers that are right are right (cache-control: public, max-age=31536000, immutable, nosniff, the CSP); it is only the encoding that is missing. Worth pairing in a reader's mind with #214, which makes those bytes fewer, and #220, which makes sure they are the right bytes at all.
Measured on 0577870 and turned into #229, which I have put in front of this one. Nothing this server sends is compressed — not the assets and not the pages: / is 16,625 bytes where gzip is 6,174, the bundle 89,037 where 35,268 would do, the weights 11,120 where 8,251 would, and curl --compressed / comes back with no content-encoding and no vary. A first visit transfers 116.8 kB against 49.7 kB. #229 asks for that in one place in src/server.ts for every response, so what is left here afterwards is the half this issue is really about — reads only, and the path safety around them. Whoever takes it then should close it if nothing remains.
Re-read on : most of this has landed, and what is left is one status code. Parked on Sep 20 by a DNS failure; in the meantime #229 and #247 rebuilt this path and took the complaint with them.
What the code does today, verified by reading and : every response meets in exactly one place, a body with a gzip beside it carries and is sent compressed only to a request that asked, the bundles are gzipped once at start-up and never per request, a is weighed like its and then loses its body, and is guarded by , looked up by file name so a is not a path, and still for a year. The original complaint — answering 200 with the whole file — is gone.
What is left, and all this issue now asks for:
- [ ] A , or to a path under that names a real file answers 405 with and the security floor, rather than falling through to the 404 every wrong path gets. A method that is not a read on a missing file stays a 404.
- [ ] covers it, and covers the negotiation that is already there so it cannot regress: gunzipping the compressed static response gives the bytes on disk, the identity response is unchanged, is on both, and has no body.
- [ ] The pull says whether the forge's edge compresses the preview already — , with and without — as a fact for the reviewer.
The module the body above asks for is not wanted any more: that concern lives in and now, and moving it again would be churn.
(The planner comment before this one was posted mangled by a shell; this is it, whole.)
Re-read on d2c980a: most of this has landed, and what is left is one status code. Parked on Sep 20 by a DNS failure; #229 and #247 rebuilt this path in the meantime and took the complaint with them.
Verified by reading src/server.ts:625-660 and src/compress.ts: every response meets accept-encoding in exactly one place, a body with a gzip beside it carries vary: accept-encoding and is compressed only for a request that asked, the bundles are gzipped once at start-up, a HEAD is weighed like its GET and then loses its body, and /static/ is guarded by isRead, looked up by file name so a .. is not a path, and still immutable for a year. The original complaint — a POST to a bundle answering 200 with the whole file — is gone.
What is left, and all this issue now asks for:
- [ ] A write method to a path under
/static/that names a real file answers 405 withallow: GET, HEADand the security floor, instead of falling through to the 404 every wrong path gets. A write to a missing file stays a 404. - [ ]
src/server.test.tscovers the 405 and pins the negotiation that is already there: gunzipping the compressed static response gives the bytes on disk, the identity response is unchanged,varyis on both,HEADhas no body. - [ ] The pull says whether the forge's edge already compresses the preview, with and without an
accept-encoding: gzipheader, as a fact for the reviewer.
The src/assets.ts module the body above asks for is not wanted any more: that concern lives in src/compress.ts and src/bundles.ts now, and moving it again would be churn.
A phone downloads 68,457 bytes of
static/app.<hash>.jstoday when 31,661 would do, andPOST /static/app.<hash>.jsanswers 200 with the whole file: the static branch ofsrc/server.tsshould negotiate its encoding and answer reads only.Done looks like
src/assets.ts— whatstatic/serves and in which encoding — owns this, andsrc/server.tskeeps one branch that calls it. Each entry's bundle is gzipped once at start-up atGZIP_LEVELfromscripts/build.tsand held in memory by file name; nothing is compressed per request, and nothing else instatic/is held in memory.accept-encodingoffers gzip gets the compressed bytes withcontent-encoding: gzip, the matchingcontent-length, andvary: accept-encoding; one that does not gets the bytes as today. Both keepcache-control: public, max-age=31536000, immutableand the whole security floor.HEADon a static file answers likeGETwith no body, in either encoding.GETandHEADreach static: anything else is 405 withallow: GET, HEADand the floor. A path that is not a file is still the same 404, and..is still refused.src/server.test.tscovers it: gunzipping the compressed response gives byte-for-byte the file on disk, the identity response is unchanged,varyis present on both,HEADhas no body, the 405, and a non-bundle file understatic/is still served.curl -sI -H 'accept-encoding: gzip' <preview>/static/<bundle>with and without. If the edge already does it, say so plainly: the saving is then only for a direct hit, and that is a fact for the reviewer, not a reason to hide the result.Files I expect to change
src/assets.ts(new),src/server.ts,src/server.test.ts,README.md(a line, if anything).Risks
content-lengthmust match the encoding actually sent, or a browser hangs waiting for bytes that never come.varyis what keeps a shared cache from feeding gzip to a client that cannot read it. Compressing the HTML is deliberately not in this issue:/changes with the leaderboard, so it would be per-request CPU on the path that has to stay in single-digit milliseconds — say that in the pull rather than quietly doing it. Memory is bounded by the entry table (~100 kB today); do not cache arbitrary files a request names. #38 is editingsrc/server.tsaround/api/match, so rebase rather than merge and keep this to the static branch.Suggested: Torque — one boundary, measurable in bytes.