fix(server): tag the poll answers by their bytes and answer 304 to a matching If-None-Match #365
No reviewers
Labels
No labels
bug
enhancement
needs-triage
ready-for-agent
ready-for-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle!365
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/361"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #361
What
GET /api/tree,/api/badges,/api/publish/pendingand/api/lockssend a strongETagandCache-Control: no-storeon every200. The tag is the xxhash64 of the body's bytes, not the derived stamp. A derived body's tag is computed once, when the entry is stored (Handler.derivednow returnsbody, tag, err). The lock list and the badges/pending "unavailable" answers are hashed per request.writePollBody(internal/server/etag.go) answers a matchingIf-None-Matchwith304and no body, following RFC 9110 §13.1.2 (comma-separated list, weak comparison,*). The304keepsETag,Cache-Controland, in branch mode,X-Deckle-Draft-Tip, and dropsContent-Type. Error answers carry no tag and are never304.requestTextConditionalreplacesrequestTextWithHeader. The tree, badges and locks stores keep the tag beside the body they hold and send it. A304is a successful poll that changes nothing:connectedstays true, the badges still go out, andonForeignTipstill fires when the tip moved. A language change drops the tree and badges tags; a page change drops the locks tag. The pending list is still fetched without a condition.docs/api.md, plus a pointer line in each of the four route sections. A sentence indocs/architecture.md, the new ADR 0031, and a pointer to it from ADR 0030. Skills, AGENTS.md, the README and the styleguide are untouched, as the brief says.Proof
go vet ./...andgo test ./...pass. New tests ininternal/server/etag_test.gocover:304on all four routes, with the draft tip in branch mode;200, an element-only save gives304(this pins the body-tag decision), and a hand edit gives200;200;*, a list andW/give304, and another language's tag gives200;BenchmarkPollTreeuncached / cached / conditional at 148 / 23 / 25 ms, writing 567,354 / 567,354 / 0 body bytes.BenchmarkPollCycleConditionalis new.npm testpasses (140 files, 3477 tests) andtsc --noEmitpasses. The newweb/src/test/conditionalPolls.test.tsand theclient.test.tscases cover every vitest acceptance criterion.One note: a page title changes through
POST /api/page/settings/save.POST /api/page/saveedits elements only, so the tests use each one for the case it actually covers.🤖 Generated with Claude Code
[autoland] verdict: pass
PASS. Signal: labctl pr checks 365 = success (ci / build-and-test ran npm run build, npm test, go vet ./..., go test ./...; release skipped by design). Conventions: Conventional Commits title, Closes #361 present, merges cleanly with current main (git merge-tree, no conflicts; main's #364 touches disjoint files). Scope matches the brief: one owner for the conditional answer (internal/server/etag.go writePollBody/ifNoneMatch), Handler.derived returns body+tag with the tag computed once per stored entry, all four poll handlers go through it, error answers untagged; client requestTextConditional replaces requestTextWithHeader, tree/badges/locks stores keep tag beside body and drop it on lang/page change (plus the badges tag when another language's tree lands, which a 304 would otherwise leave stale). Tests cover every acceptance criterion (internal/server/etag_test.go incl. the element-save 304 / title-save 200 / hand-edit 200 pin; web/src/test/conditionalPolls.test.ts, client.test.ts). Docs: api.md convention bullet + four route pointers, architecture.md sentence, ADR 0031, ADR 0030 pointer; skills/AGENTS.md/README/styleguide untouched as the brief states. gofmt clean.