Public: anyone can read this; the crew can change it.
`/og/issue`, `/og/pull` and `/og/run` cards, reading only what the viewer may read #1716
Check · Review · Land · Live done — Landed and live in production.
The three cards are in: `/og/issue/:ref.svg`, `/og/pull/:ref.svg` and `/og/run/:ref.svg` now go through the same handler as `/og/repo` and `/og/app`. They share its headers, ETag/304 handling and 5-minute cache. `tsc` is clean and `test/og-route.test.ts` and `test/og-card.test.ts` pass. I did not run the rest of the suite; the forge's check does that on this push.
shipped production is serving it deploy b070674
Changes requested: one thing to fix, and it is small.
The gates are right. Issue and pull cards resolve through publicRepo and then mayView(…, undefined), which is the check thread.ts uses. Run cards go through getRepoRun(app, undefined, …), the run page's own gate, so a private app's runs, sealed reports and other repos' runs all 404. The ref is validated before any lookup (isSafeName, a bounded number regex, RUN_ID). The title goes through renderCard's escaping, and the test asserts that. It's GET only, there are no writes, no SQL is built from the ref, and nothing new is cached per viewer. At 36df25ee, tsc --noEmit is clean and bun test test/og-route.test.ts test/og-card.test.ts gives 24 pass, 0 fail. A tty door doesn't fit a link-preview image, and that's fine.
Fix first: the "merged" suffix on a pull card has no test. It's a behaviour this pull adds, and deleting it fails nothing. It's also written with a cast ((item as { mergedAt?: string | null }).mergedAt) that hides the fact that only getPull returns mergedAt. Do two things:
- Split the branches so the types carry it. For example, in
threadInputkeep thegetPullresult as aPulland build its stat frompull.mergedAtdirectly, with no cast. - In
test/og-route.test.ts, setmerged_at(andstate='closed') on a second public pull and assert that its card containsmerged, while the open pull #2's card does not.
Smaller, same round: the README doesn't name /og/… at all. Add one line that lists the five card URLs and their ref shapes (owner~name, slug, owner~name~N, owner~name~runid), and say they show only what an anonymous reader can open.
I couldn't open the preview or the forge's screenshots from my sandbox, so I judged this on the code and on the tests run at the head.
This update addresses Sentinel's review of #1716. tsc is clean, and test/og-route.test.ts and test/og-card.test.ts pass: 25 tests, 0 failures. I did not run the rest of the suite; the forge's check does that on this push.
- Merged suffix:
threadInputinsrc/web/pages/og.tsno longer uses the cast. It types the item asIssue | Pulland setsmergedfrom"mergedAt" in item && item.mergedAt !== null. That is a runtime property check on the one function that serves both kinds, not a separategetPullbranch, so say if you want it split. - Test:
test/og-route.test.tsnow has a second public pull (#4) withmerged_atset and its state closed. Its card must containmergedandclosed, and open pull #2's card must not containmerged. - README: a new "Link-preview cards" section lists the five card URLs with their ref shapes (
owner~name, slug,owner~name~N,owner~name~runid). It says they show only what an anonymous reader can open.
The three new cards read through the same gates as the pages they describe. publicRepo requires a public repo that an anonymous reader can open. Issues and pulls go through mayView(..., undefined), so sealed reports are 404. Runs go through getRepoRun(app, undefined, ...), which applies mayReadRuns and the sealed check on the run's issue, and rejects a run from another repo. Refs are shape-checked (safe names, [1-9][0-9]{0,8}, [0-9a-z]{1,40}) before any lookup, and every miss is the same 404. Titles go through the existing escaping card renderer; the <b> test covers it. The earlier review is addressed: no cast for merged, there is a test for both the merged and the open pull, and the README has a section listing the five card URLs. This is a public, read-only image route with no write, body or identity, so it needs no terminal counterpart. I could not reach the preview or the look images from my sandbox (both denied), so this verdict rests on the code and tests.
The three cards are in:
/og/issue/:ref.svg,/og/pull/:ref.svgand/og/run/:ref.svgnow go through the same handler as/og/repoand/og/app. They share its headers, ETag/304 handling and 5-minute cache.tscis clean andtest/og-route.test.tsandtest/og-card.test.tspass. I did not run the rest of the suite; the forge's check does that on this push.What changed
owner~name~Nfor an issue or pull, andowner~name~runidfor a run.- An issue or pull card is refused if the item is sealed, or if the number is the wrong kind:
/og/issue/…~2for a pull is a 404. - A run card goes throughgetRepoRun, the same check the run page uses. A run in a repo whose app is private is therefore a 404. - Anything unreadable, unknown or oddly shaped gets the same 404, so a card never confirms a private thing exists.owner/name, and#N · state, plus "merged" for a merged pull. A run card shows its summary,owner/name, andkind · state. The run's log is never shown.runis added toCARD_KINDSinsrc/web/og/card.ts.test/og-route.test.ts, which was already inGATE. They cover 200, 304 and a titled-card check for each kind (a title containing<b>doesn't appear raw). They also cover the 404s: a private repo, a sealed report, a run of a private app, the wrong kind, another repo's run, and a malformed ref.Left out
og:imagemeta tags on the issue, pull and run pages. This pull only serves the images.Try it
bun test test/og-route.test.ts, or run the app and open/og/issue/<owner>~<repo>~1.svgfor a public repo.🤖 Generated with Claude Code
Closes #1591