Files
pad/internal/server/testdata
xarmian 95046c9b3b feat(server): gzip what the client accepts, except streams, upgrades, ranges and secrets (TASK-2225) (#1934)
* feat(server): gzip what the client accepts, except streams, upgrades, ranges and credential responses (TASK-2225)

Audit C18: the Go server ignored Accept-Encoding entirely, so the items
index (2.1 MB on a large workspace, 6.8x smaller gzipped), every JS chunk
and every API poll went out uncompressed. Invisible on a LAN; most of a
page's load time on a self-hosted WAN link. Pad Cloud already gets Brotli
from Cloudflare, so this is the self-host win.

CompressResponses wraps chi's Compress(5) (negotiation, Vary:
Accept-Encoding, and compressible types only: text, JSON, JS, CSS, SVG;
never images, archives or the export bundle) and skips:
- long-lived streams: /api/v1/events* (SSE) and /mcp (streamable HTTP);
- the collab WebSocket and any Upgrade request;
- Range requests (a 206's Content-Range names identity offsets);
- credential responses (/api/v1/auth/*, /oauth/*, */tokens): the BREACH
  shape, and small enough that skipping costs nothing.

Measured on a throwaway instance: the largest JS chunk 1,456,216 ->
452,518 bytes (3.2x), index.html 6,444 -> 1,354.

Tests: gzip for an accepting client (Vary set, body decodes) and identity
otherwise; every skip; images untouched; an SSE stream still delivers a
flushed event while open; wired into the real router (/api/v1/health).
Mutants: a skip list that never skips, the middleware unwired: each
killed. e2e on the built binary: the SSE and collab specs (14 pass).

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* fix(server): compress reads only, so no minted secret is ever compressed (TASK-2225, codex r1)

Every secret the API mints comes back from a write; write responses are small.
The account export's flushes still deliver (chi's writer flushes gzip then the
connection), now pinned by a test.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* fix(server): a workspace claim code is never compressed (TASK-2225, codex r2)

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* fix(server): a HEAD keeps its Content-Length: compress GETs only (TASK-2225, codex r3)

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* fix(server): the members list is never compressed: it can carry a legacy invitation code (TASK-2225, codex r4)

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* fix(server): a share view's signed attachment references are never compressed (TASK-2225, codex r5 sweep)

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* test(server): a census of every GET route, each classified compress or skip (TASK-2225, lead condition)

The secret-bearing GETs compressSkips leaves alone are a deny list, and a
deny list rots: the next GET route that returns a secret would be
compressed silently. testdata/compress_get_routes.txt holds every GET
route the router serves (149, 18 skipped) with its classification;
TestCompressRouteCensus walks the router with chi.Walk and fails when a
route is missing from the census, when the census names a route the router
no longer serves, or when compressSkips disagrees with it. A new GET route
therefore fails CI until someone decides whether its response can carry a
secret and regenerates the file (PAD_UPDATE_COMPRESS_ROUTES=1), making the
decision a reviewable diff. Checked: a dropped census line fails naming the
route; dropping the claim-code skip fails naming the disagreement.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* fix(server): leave attachment bytes uncompressed (TASK-2225)

CI on #1934 failed TestTask3401c_AStalledClientDoesNotOutliveTheDeadline
in both Go jobs: the client got EOF before any headers. chi's compressor
holds the headers until its first flush, and a 24 MiB file of repeated
text compresses ~1000:1, so on a slower runner the 300ms route write
deadline expired before a byte reached the wire. Attachment downloads are
users' files streamed under that deadline; they now go out as written.
The attachment list at /attachments still compresses.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* docs(server): state the deadline rule beside the attachment skip (TASK-2225)

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
2026-10-09 03:16:26 -04:00
..