Extract web-preview comment renderer to a testable module - #636
Conversation
The sticky preview comment carried the right URLs in a table nothing could read: `| Name | Status | Preview | Deployment (this commit) | Updated (UTC) |` with a `[Visit Preview](…)` cell. Tooling that scrapes a PR for its preview URL was written against Vercel's comment — it matches the four-column `| Project | Deployment | Actions | Updated |` header and pulls the URL out of the `[Preview](…)` link in the Actions cell — so the preview deployed fine and the lookup came back empty. Reproduce Vercel's structure exactly: its column headers and alignment row, a ` [Ready](<deployment>)` status cell, `[Preview](<url>)` as the Actions cell, and a `<relative-time>` Updated cell. A failed deploy reuses the same table with an `Error` status and no preview link. Two things are deliberately not copied. Vercel's leading `[vc]: #<hash>:<base64>` line is a signed payload describing real Vercel projects, so forging one would make the comment lie to anything that decodes it. And the marker stays this repo's own `<!-- lorekit-web-preview sha=<40 hex> -->` — the incremental "is a redeploy needed?" check reads that SHA back, and the failure path still writes no `sha=` so the next web push re-attempts. The body moves out of the YAML into scripts/ci/web-preview-comment.mjs, since the format is now a contract with tooling outside this repo rather than prose: web-preview-comment.test.mjs pins every anchor (including the `[Preview](…)` extraction a scraper performs) and asserts the workflow still renders through the module instead of inlining a table again. It runs in the existing `preview-filter` CI job, and the renderer joins the preview-relevant path filter — a change to it is only observable by actually deploying — while its test file is anchored out. Claude-Session: https://claude.ai/code/session_01M4KFDkxuzXK9k541xRoc9G
|
Darkplane auto-approval is enabled for this repository (mode: Auto-approve). An evaluation will run once this pull request's CI checks have completed — no action needed. |
|
Reviewed your changes — no blocking issues, 1 warning(s): CI still pending. Review detailsReviewed for commit
CI — Run mode — full · 537 lines in delta · 7 files touched — first review of this PR Memories — 308 indexed (harvested handoff, no live memory access this run) Quality — produced 0 → posted inline 0 · cleared 0 · carried forward 0 · deferred 0 · below-bar 0 Integrations — not activated Optimality (2.4c) — ran · 1 judged · 1 optimal · 0 proposal(s) · 0 inline pointer(s) · 0 withheld Standards (2.4d) — ran · 1 doc · 0 finding(s) Skipped files — none Reviewed by the |
|
The dashboard preview for this PR — redeployed on each push that changes the web app.
Preview always points at this PR's latest commit; Deployment is the immutable build of |
|
Darkplane did not approve this pull request. It scored 63.58 of 100, and 75 was required.
The other 2 criteria
An AI evaluator produced these scores from the diff, the repository history, and the pull request text. The pull request text is author-controlled, so treat the scores as signals rather than proof. Reviewed for commit 38adaba. |
The sticky PR comment that displays preview URLs is now rendered by a dedicated, unit-tested module instead of being inlined in the GitHub Actions workflow. This ensures the comment structure — which is a contract with external tooling that scrapes PRs for preview URLs — stays consistent and verifiable.
Summary
Extracted the web-preview comment rendering logic from
.github/workflows/web-preview-deploy.ymlintoscripts/ci/web-preview-comment.mjs, with comprehensive unit tests inscripts/ci/web-preview-comment.test.mjs. The comment deliberately mirrors Vercel's own preview comment structure because external tooling was written to parse that format.Key changes
New module
scripts/ci/web-preview-comment.mjs: ExportsrenderReadyComment()andrenderErrorComment()functions that generate the sticky comment body. The module includes detailed comments explaining why the table structure matches Vercel's format exactly — the four-column header (Project | Deployment | Actions | Updated), the[Preview](…)link in the Actions cell, and the<relative-time>element are all load-bearing for external scrapers.New test file
scripts/ci/web-preview-comment.test.mjs: 16 unit tests that verify:[Preview](…)link is extractable the way external scrapers do it[Ready](…)link points to the immutable per-commit deployment<relative-time>element has a parseable ISO datetimeUpdated
web-preview-deploy.yml: Both the success and failure comment steps now dynamically import and call the renderer functions instead of building the comment inline. The marker find-key is now exported from the module (WEB_PREVIEW_MARKER).Updated filter and CI config: Added
scripts/ci/web-preview-comment.mjsto the web-preview change filter in bothci.ymlandweb-preview-filter.test.mjs, since the comment's output is part of the deployed preview machinery. The.test.mjsfile is deliberately excluded from the filter (via the\.mjs$anchor) because test-only edits don't change output.Updated documentation:
docs/deployment.mdnow explains why the comment is Vercel-shaped, what parts are reproduced and why, and how the marker is a contract with the "Decide" step.Implementation details
The comment format is machine-readable: the marker line
<!-- lorekit-web-preview sha=<40 hex> -->is parsed by the "Decide" step to detect whether a redeploy is needed. Failed deploys omit thesha=so the next push re-attempts instead of treating the failed commit as deployed.The
formatUpdatedLabel()function spells midnight and noon the way Vercel does (12:04am, 12:00pm), and the<relative-time>element includes both an ISO datetime attribute (for parsing) and a human-readable fallback (for emails/API reads).The module is imported via
pathToFileURL()in the workflow becausegithub-scriptruns in a Node.js context where dynamic imports need file URLs.All three copies of the change filter regex (grep in
ci.yml, RegExp inweb-preview-filter.mjs, test cases inweb-preview-filter.test.mjs) are held in sync by the test file, which now also includes the new path.https://claude.ai/code/session_01M4KFDkxuzXK9k541xRoc9G