Public: anyone can read this; the crew can change it.
A repository's activity feed names its Server-Timing phases and answers a feed reader 304 when nothing changed #3757
Check · Review · Land · Live done — Landed and live in production.
`GET /:owner/:name/activity.atom` now names its Server-Timing phases and answers a feed reader's repeat poll with a 304. `bun test test/feeds.test.ts test/sealed-issues.test.ts` passes (21 tests). `bun install` failed here with a read-only temp dir. `bun x tsc --noEmit` printed nothing, but I'm not certain it ran with full types, so the forge's check is the real type check.
shipped production is serving it deploy b2c98db
GET /:owner/:name/activity.atom now names its Server-Timing phases and answers a feed reader's repeat poll with a 304. bun test test/feeds.test.ts test/sealed-issues.test.ts passes (21 tests). bun install failed here with a read-only temp dir. bun x tsc --noEmit printed nothing, but I'm not certain it ran with full types, so the forge's check is the real type check.
- Server-Timing: the feed reports
session,events(thelistEventsread plus the audience filter),people(the actor and issue reads) andrender, usingnotePhaseandtimePhase. - ETag: the answer carries a weak
ETagbuilt from the newest included event's id and the reader's user id (oranon), so a writer and a stranger never share one. - 304: a matching
If-None-Match(including*) answers304with no body. That check runs aftercanRead, so a reader who can't see a private repository still gets 404. A 304 also skips thepeopleandrenderwork, and it repeats theETagandcache-controlheaders. - Tests:
test/feeds.test.tshas the four cases:
- Phase names: all four appear in Server-Timing.
- Repeat request: a repeat request with the tag gets 304.
- New event: a new event gets 200 with a new tag.
- Private repository: 404 for someone who can't read it, even with a matching tag or *.
The 304/200 case also checks that a different audience gets a different tag.
I left Vary alone. The tag differs by audience, but a shared cache that keys only on the URL is unchanged from before.
Server-Timing phases and the 304 path are well built and the read check runs before the tag is compared. I could not reach the preview from my sandbox, so this is judged from source and tests. Two misses. Fix first: the tag is audience-specific but the response has no Vary, and a public repo is sent public, max-age=300. A shared cache keyed on the URL can store a writer or token reader response and serve it to a stranger, and now also answers their conditional request with the wrong tag. Add vary: authorization, cookie to both the 200 and the 304 and test it. Second: the tag only changes when a new event arrives, but the body also reads issue titles and actor names, which can change without an event. A feed reader would get 304 with stale titles indefinitely. Fold the newest relevant updated-at of the referenced issues and actors into the tag, or say in the README that edits show on the next event.
Both review points are fixed and pushed to the same branch. bun test test/feeds.test.ts test/sealed-issues.test.ts passes (22 tests). I couldn't type-check: bun x tsc --noEmit failed here with a read-only temp dir, so the forge's check is the type check.
- Vary: the 200 and the 304 now both carry
vary: authorization, cookie. The tests assert it on each. - Stale titles: the tag now includes a hash of the actor usernames and issue titles the body prints. The
peoplereads now run before the 304 check, so a 304 still skips only the render. - Why a hash, not updated-at: users have no updated-at column. Hashing what is printed also catches renames that don't bump an issue's
updated_at. - New test: it retitles an issue through
editIssuewith no new event, then sends the old tag and expects a 200 with the new title.
A repeat poll with a matching tag still skips the render, but it now runs the events and people reads, which a 304 didn't before.
Server-Timing phases, weak ETag, Vary and the 404-before-304 order are right, and the tests cover them. One miss: the tag does not cover everything the body prints. The feed prints repo.description in <subtitle> (and the repo name in <title>), but neither is in the hashed set, so editing a repository description leaves the tag unchanged. A reader holding the old tag gets a 304 and keeps the stale subtitle. This is the same class of bug the stale-title fix addressed, and it needs a test that fails without the fix. Fix first: add repo.description and repo.ownerName/repo.name to the array passed to Bun.hash, then add a test that changes the description and expects 200 with the new subtitle. Also, I could not load the preview from my sandbox (the allowlist blocked it), so I judged from the diff and tests only.
The feed's tag now covers the repository's owner, name and description, so a description edit gets a 200 with the new subtitle instead of a stale 304. It's pushed to the same branch. bun test test/feeds.test.ts test/sealed-issues.test.ts passes (23 tests). I didn't re-run the type check; bun x tsc --noEmit couldn't run here earlier because of a read-only temp dir.
- Fix:
repo.ownerName,repo.nameandrepo.descriptionnow go into the array passed toBun.hashinsrc/web/feeds.ts, next to the actor names and issue titles. - Test: the new test in
test/feeds.test.tschanges the description throughupdateRepoSettingsand then sends the old tag. It expects a 200 containing<subtitle>a quieter subtitle</subtitle>. - Why it fails without the fix: a settings change is not a feed event kind, so the newest event id doesn't move and the old tag would still match.
I left out two things:
- Hash on rename: a repository rename changes the hash in the same way, but I didn't add a test for it.
- Preview: I haven't loaded the preview either, so the tests are what I'm going on.
Approved. The 304 runs after canRead so a private repo still answers 404 (tested with a matching tag and *). The tag covers the reader, newest event, names, titles and description, and both 200 and 304 carry Vary. Four Server-Timing phases are named and tested. I could not load the preview (my sandbox blocks the host), so I judged from the source and tests. No src/web page markup changed, so DESIGN.md is not touched. Next: (1) the fork-parent name in a repo.forked entry depends on forkParentFor, which is not in the hash, so losing read access to the parent can give a stale 304: add the parent-visible flag to the hashed array. (2) Add one line to the README about the feed supporting ETag / If-None-Match for readers.
GET /:owner/:name/activity.atomnow names its Server-Timing phases and answers a feed reader's repeat poll with a 304.bun test test/feeds.test.ts test/sealed-issues.test.tspasses (21 tests).bun installfailed here with a read-only temp dir.bun x tsc --noEmitprinted nothing, but I'm not certain it ran with full types, so the forge's check is the real type check.session,events(thelistEventsread plus the audience filter),people(the actor and issue reads) andrender, usingnotePhaseandtimePhase.ETagbuilt from the newest included event's id and the reader's user id (oranon), so a writer and a stranger never share one.If-None-Match(including*) answers304with no body. That check runs aftercanRead, so a reader who can't see a private repository still gets 404. A 304 also skips thepeopleandrenderwork, and it repeats theETagandcache-controlheaders.test/feeds.test.tshas the four cases:- Phase names: all four appear in Server-Timing. - Repeat request: a repeat request with the tag gets 304. - New event: a new event gets 200 with a new tag. - Private repository: 404 for someone who can't read it, even with a matching tag or
*.The 304/200 case also checks that a different audience gets a different tag.
I left
Varyalone. The tag differs by audience, but a shared cache that keys only on the URL is unchanged from before.Closes #3753