You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat: render PR guides on campdotdev.github.io/guides - #1
Agents write most of the PRs in shaders, and a GitHub diff shows what changed in file order, with none of the reasoning behind it. A guide pairs each step of a change with its real diff hunks and a plain-English explanation. This PR builds the site that renders guides at campdotdev.github.io/guides. The pr-guide skill that writes them is SHA-288.
What changes
The guide format and its renderer
A guide is a Markdown file on the content branch at <repo>/<pr>.md. It holds prose and pointers to line ranges, never pasted code. The Astro site validates each guide against a schema and resolves every pointer to the real hunk through GitHub's compare API, reading the base side at the merge base. A malformed guide fails the build with an error that names the field. docs/guide-format.md documents the format, and the site serves it at /guides/format/.
The guide page
The left pane holds the steps and their hunks, with expandable context and labels for new, deleted, and renamed files. Image pointers show the image before and after the change. "Also changed" lists every changed file that no step shows. The right pane holds the overview or the selected step's explanation, and its inline pointers highlight the lines they name. Readers can link to #step-<n>, move with j and k, and mark files as reviewed. A banner warns when the PR has moved past the guide's head. On narrow screens, the panes stack.
The index
The home page lists each repo that has guides. A repo page lists its guides newest first, with a scope filter. Pagefind searches the prose of every guide.
Deploys and tests
deploy.yml on main builds the renderer with a checkout of content, caches fetched files by SHA, and deploys to Pages. A push to content runs only the workflows on content, so a trigger workflow there dispatches the deploy on main. ci.yml runs the Playwright specs against recorded snapshots of shaders PRs, with no network access.
Known limitations
The shaders#264 sample guide and the trigger workflow reach content by a direct push, not through this PR. The site first deploys when this PR merges. Any push to content can change its trigger workflow. A ruleset on content that covers .github/** would prevent that.
Closes SHA-280
Closes SHA-281
Closes SHA-282
Closes SHA-283
Closes SHA-284
Closes SHA-285
Closes SHA-286
Closes SHA-287
Part of SHA-279
The renderer reads each pull request's changed files and file contents
through one interface, `RepoFileSource`. `REPO_FILE_SOURCE` picks the
adapter: `github` reads the REST API with `GITHUB_TOKEN`, and `fixture`
(the default) reads a recorded snapshot.
The GitHub adapter lists files with `compare/{base}...{head}`, which
never changes for fixed SHAs. The PR files endpoint follows the PR's
current head instead.
`scripts/record-fixture.ts` records a snapshot through the GitHub
adapter, so the snapshot matches what production sees, and checks each
head file against its blob SHA.
This commit also scaffolds the Astro 7 project: pnpm 11, Node 24, and
`base: '/guides'`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixture guide `tests/fixtures/guides/shaders/264.md` has an
overview and two steps. The snapshot holds the compare file list for
#264 and every changed file at its base and head SHAs.
`pnpm record-fixture` regenerates it.
The tests own this guide, so the sample on `content` can change
without breaking the specs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`/guides/<repo>/<pr>/` shows the guide's overview in the right pane and
one collapsed block per step in the left pane. Clicking a block opens
its hunks and shows the step's explanation in the right pane. Clicking
it again brings back the overview.
The guide model in `src/guide/` does the work, apart from the
components. It parses the body, checks the structure, and resolves each
block pointer to diff rows. A pointer shows its range with three lines
of context. The rows are cut from GitHub's `patch` hunks, not computed
with a diff library, so they match the pull request on GitHub. Prose
goes through `rehype-sanitize`, because agents push guides without
review.
A malformed guide fails the build with an error that names the file and
the field, such as `steps.2.pointers.1`. Frontmatter errors come from
the collection schema.
Browser behavior lives in `src/client/`, one module per feature.
Step selection exports `selectStep` and fires `guide:step-selected`, so
later features can select steps the same way a click does.
The Playwright specs build the site from the fixtures, serve it under
`/guides`, and block every request outside the preview server.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`docs/guide-format.md` documents the frontmatter, the overview, steps,
block pointers, and the build errors. The `pr-guide` skill reads it as
raw Markdown, and the site renders the same file at `/guides/format/`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`deploy.yml` runs on pushes to `main` and on `workflow_dispatch`. It
checks out `content` beside the renderer, builds with the `github`
source, and deploys to Pages. Only `main` may deploy to the
`github-pages` environment, so `content` dispatches this workflow
instead of deploying itself.
`ci.yml` type-checks and runs the Playwright specs on pull requests to
`main`. The specs use the fixture source, so CI needs no token.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening a guide at `#step-<n>` selects that step. Selecting a step any
other way writes its anchor to the URL with `history.replaceState`, so
the address bar links to the current step and a click adds no history
entry. A change to the anchor, such as Back after following a step
link, selects the step it names. An anchor to a step that the guide
doesn't have leaves the overview showing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`j` selects the next step and `k` the previous one, and both stop at
the first and last step. A key leaves focus on the selected step's
header, the way a click does, so the header scrolls into view and Tab
continues into that step.
The keys do nothing while focus is in a form field or when Control,
Meta, or Alt is held. Buttons don't count as form fields, because a
click leaves focus on a step's header and `j` must still work there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An inline pointer is a link in a step's prose to a repo-relative path
with a line range. It still links to the lines on GitHub, and now also
carries its file and lines, which the sanitize schema keeps. The schema
keeps GitHub's URL protocols, so a javascript: link still loses its href.
Hovering or focusing the phrase highlights the matching rows in the
step's code. A plain click opens the step if needed, keeps focus on the
phrase so the highlight stays, and scrolls the rows into view. A
modified click still opens GitHub.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… not show
Every line an inline pointer names must be a head-side row of its own
step's hunks, context lines included. Otherwise the phrase would
highlight nothing. The error names the step and the pointer, and lists
the lines the step does show:
steps.2.inlinePointers.1: [exports the primitive](...engine.ts#L70-L80)
points at lines that step 2 does not show. Step 2 shows lines 67-78 of
packages/shaders/src/engine.ts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the "Inline pointers and links" stub with an "Inline pointers"
section covering syntax, the rule that a step must show every line its
pointers name, and page behavior. The remaining link rules move to
"Other links". The build-errors table gains steps.<n>.inlinePointers.<m>.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The index specs need a second scope in one repo and a second repo.
shaders#241 has the scope `docs`, and bones#23 has no scope. Both
snapshots come from `pnpm record-fixture`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The home page at `/guides/` lists each repo that has guides. Each repo's
page lists its guides by pull request number, newest first, with the
title, number, and scope. A Scope select hides the guides with other
scopes. The page renders the select hidden, and the script shows it,
because the filter does nothing without the script.
`src/guide/catalog.ts` groups the guides by repo and builds the links
under the `/guides` base.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`pnpm build` now runs `pagefind --site dist` after `astro build`, so the
deploy and CI workflows build the index with no changes. The guide
page marks its explanation pane with `data-pagefind-body`, so the index
holds the overview and the steps' prose but not the diffs. Pages
without the marker, such as the index pages, stay out of the index.
The home page's search box loads the Pagefind bundle from under the
`/guides` base on first focus and lists the matching guides with an
excerpt. The search specs query the real index from the built site.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README says that search needs a built site and that the search
specs use the real index. The format reference says what search
indexes and that a repo's page filters by `scope`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After the last step, the left pane lists every file that the pull
request changes and no block pointer names, with its status and its
added and removed line counts. The renderer builds the list from the
compare file list, so a guide cannot skip a file without the page
showing it.
An image file in the list shows a thumbnail. The build writes each
changed image, at the base and head commits where it exists, under its
guide's page at `images/<commit>/<path>`. The page never loads an image
from GitHub.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#264 only adds images. #235 regenerates Aurora's visual-regression
baselines, so its images change, and #243 deletes the Performant card's
image. Each snapshot holds the pull request's compare file list and
every changed file at its base and head SHAs, about 1.9 MB in all.
The fixture guides for both pull requests arrive with image pointers,
because the renderer cannot parse them before then. To regenerate a
snapshot, run `pnpm record-fixture` on its guide.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Below 800px, the panes stack. The overview sits above the steps, and a
selected step's explanation sits in its block, between the header and
the code.
CSS stacks the panes, and `narrow-layout.ts` moves the explanation
elements into place. Reordering with CSS alone would put every
explanation after all the code in the tab order and for screen
readers. The elements move back when the window widens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A block pointer to an image file has no line range, such as
`[](apps/docs/public/posters/aurora.jpg)`. The step shows the image at
the base commit under "Before" and at the head commit under "After". A
new image shows only After, and a deleted image shows only Before. The
image file's extension decides the pointer's kind. A line range on an
image pointer fails the build and names the pointer.
`parseBlockPointer` keeps its signature. `parseImagePointer` sits beside
it, and `ParsedStep` holds image pointers in a separate `images` list,
so the hunk resolver in `resolve.ts` is unchanged. A step shows its
images after its hunks.
The #264 fixture guide gains a third step that points at the Linux
baseline. New fixture guides for #235 and #243 cover a changed image
and a deleted one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The format reference gains an "Image pointers" section, with a table of
what the page shows for a changed, new, deleted, and renamed image, and
an "Also changed" section. "Block pointers" links to the new section,
because its line-range rule does not apply to images.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each file header has a Reviewed checkbox, and each step header counts
its reviewed files, such as "1/2 reviewed". Local storage keeps one key
per mark:
guides:reviewed:<owner>/<repo>#<pr>:<path>@<blob SHA>
A refreshed guide that shows a file at a new blob SHA looks up a
different key, so the file shows unchecked again, the way GitHub's
Viewed checkbox works.
The refresh spec needs one file at two blob SHAs. The fixture guide in
tests/fixtures/previous-head pins #264 to its previous head, f09b489,
and `pnpm record-fixture` recorded its snapshot. The last commit,
84cc417, changed only flute-surface.ts. The spec serves the f09b489
build at the preview server's origin, marks files, and reloads into the
84cc417 build, so local storage carries over the way it does across a
real deploy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On load, the page asks the GitHub API for the pull request's head SHA
with one unauthenticated fetch. If the head differs from `headSha`, a
banner in the header names the guide's commit and the current head,
and links to the pull request. A failed request, such as a 403 from
GitHub's limit of 60 requests per hour, shows no banner and no error.
The fetch sends no custom headers, so the browser skips the CORS
preflight. The banner fills an empty status region, so screen readers
announce it. The specs stub the request with `page.route`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#264 adds and modifies files but deletes and renames none. These pull
requests fill the gap: #193 deletes two files, #169 renames a file and
edits it, and #232 renames a file without edits. `pnpm record-fixture`
recorded each one through the `github` source.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An inline pointer's click opens its step, and opening a step now writes
the step's anchor to the URL. The spec still checks that the click
stays on the guide page, and now expects `#step-2` at the end of the
URL.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The `content/` rule matched every directory named `content`, so the
recorded snapshots for shaders#264 left out `apps/docs/src/content/*.ts`.
Anchor the rule to the root and commit the missing files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A separator sits above, between, and below the rows a step shows for a
file, and says how many diff lines it hides. Clicking it shows the real
lines, and clicking it again hides them. A pointer range that spans two
of GitHub's hunks shows both, with a separator between them.
The guide model fills GitHub's hunks out to the whole file with the
unchanged lines from the head commit. `StepFile.gaps` holds the rows
that the visible hunks leave out, and `StepFile.hunks` does not change.
The build renders every hidden row, so expanding needs no request.
A pointer's cut can leave changed rows out of a hunk, such as the rest
of a new file. Only a separator that hides unchanged rows says
"unchanged". The others say "more added lines", "more removed lines",
or how many of the hidden lines changed.
The fixture guide for #169 points across two hunks of `dot-field.tsx`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FileDiff and ImageDiff each carried the file header, the Reviewed
checkbox, and its CSS, and StepBlock and AlsoChanged each carried the
line-count markup. FileCard and LineCounts now hold them once. The
narrow layout hides a step explanation's heading with the global
.visually-hidden class instead of a copy of its rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… reads
catalog.ts and load.ts each built a guide's URL, and three modules
trimmed BASE_URL. sitePath now joins the base path once, and guideUrl
and guideParams live in catalog.ts. Four client modules looked up the
element a control's aria-controls names, now controlledElement. The
cache read its entries with a copy of the fixture source's reader,
now readSnapshotFile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
stepImages and alsoChanged took the changed files, the pull request,
and the merge base out of PullRequestFiles and worked on them outside
it. They are now its methods, and the class looks up a changed file
in one place for text pointers and image pointers alike.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
repo meant owner/name in the frontmatter and the catalog, but only the
name in RepoRef, which now calls it name. pullRequestName builds
"owner/name#pr" in place of nine copies, shortSha replaces four
slice(0, 7) calls, and pullRequestOf is the one place that splits
owner/name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One idea had four names: separator on the page and in the format
reference, gaps in StepFile, hiddenRuns in hunks.ts, and diff-context
in the client. The control is a separator and the rows it hides are
hidden rows, so StepFile.gaps is StepFile.hidden, the client module is
separators.ts with data-separator, and the spec is separators.spec.ts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ChangedFile.previousPath and StepFile.basePath held the same path, and
the file wrapper's data-file and the checkbox's data-path held the same
path. ChangedFile now calls it basePath too, with the snapshots'
key renamed to match, and a Reviewed checkbox reads its path from the
data-file around it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cutHunks took a context size, githubRepoFileSource a fetch, the cache a
logger, repoFileSource an environment, and loadGuide a source, and no
caller passed any of them. Guide.changedFiles had no reader, and
languageOf, reviewedKey, and CONTEXT_LINES had no importer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The image allowlist checks only the new path, allowing cross-format renames to publish SVG base versions that can execute scripts on the GitHub Pages origin.
Reference-style Markdown links are not rewritten here: [ADR][decision] with [decision]: docs/adr/0001.md stores its URL in a definition node, not a link node. Even when the definition is in the same prose section, the rendered href remains relative to the guide page and leads to a nonexistent Pages URL instead of the repository at headSha. Rewrite definition URLs alongside inline link URLs.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The no-JavaScript fallback leaves step code hidden, while symlink dereferencing can display incorrect diffs and cause fixture blob-SHA checks to fail.
No open issues 🎉
2 previously missed issues
In code that hasn't changed since last review
Unscoped noscript CSS needed for nested step code
src/components/GuidePage.astro:20
This is a scoped Astro stylesheet, but .step-code is rendered inside StepBlock.astro, so the fallback selector cannot match those nested elements. With JavaScript disabled, their hidden attributes are never removed and none of the steps' code is readable. Make this an inline, unscoped stylesheet so the override reaches the child components and stays conditional on <noscript>.
Read symlink blobs instead of dereferenced target contents
src/repo-files/github.ts:57
The Contents API dereferences a symlink when its target is a normal file in the same repository, including with the raw media type. For a changed symlink, this therefore returns the target's contents rather than the symlink blob described by the comparison: FileDiff can display target source code in place of the link's diff, and record-fixture fails its blob-SHA check. Read the actual Git blob at the pinned commit, or explicitly detect and reject symlinks, rather than treating raw Contents responses as exact blob bytes.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human assessment is needed because unpublished findings identify CI and deployment installation failures and accessibility risks from document-wide single-character shortcuts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Agents write most of the PRs in shaders, and a GitHub diff shows what changed in file order, with none of the reasoning behind it. A guide pairs each step of a change with its real diff hunks and a plain-English explanation. This PR builds the site that renders guides at
campdotdev.github.io/guides. Thepr-guideskill that writes them is SHA-288.What changes
The guide format and its renderer
A guide is a Markdown file on the
contentbranch at<repo>/<pr>.md. It holds prose and pointers to line ranges, never pasted code. The Astro site validates each guide against a schema and resolves every pointer to the real hunk through GitHub's compare API, reading the base side at the merge base. A malformed guide fails the build with an error that names the field.docs/guide-format.mddocuments the format, and the site serves it at/guides/format/.The guide page
The left pane holds the steps and their hunks, with expandable context and labels for new, deleted, and renamed files. Image pointers show the image before and after the change. "Also changed" lists every changed file that no step shows. The right pane holds the overview or the selected step's explanation, and its inline pointers highlight the lines they name. Readers can link to
#step-<n>, move withjandk, and mark files as reviewed. A banner warns when the PR has moved past the guide's head. On narrow screens, the panes stack.The index
The home page lists each repo that has guides. A repo page lists its guides newest first, with a scope filter. Pagefind searches the prose of every guide.
Deploys and tests
deploy.ymlonmainbuilds the renderer with a checkout ofcontent, caches fetched files by SHA, and deploys to Pages. A push tocontentruns only the workflows oncontent, so a trigger workflow there dispatches the deploy onmain.ci.ymlruns the Playwright specs against recorded snapshots of shaders PRs, with no network access.Known limitations
The shaders#264 sample guide and the trigger workflow reach
contentby a direct push, not through this PR. The site first deploys when this PR merges. Any push tocontentcan change its trigger workflow. A ruleset oncontentthat covers.github/**would prevent that.Closes SHA-280
Closes SHA-281
Closes SHA-282
Closes SHA-283
Closes SHA-284
Closes SHA-285
Closes SHA-286
Closes SHA-287
Part of SHA-279
🤖 Generated with Claude Code