Skip to content

[Update] Document the Gutenberg contribution workflows - #486

Merged
juanmaguitar merged 18 commits into
juanmaguitar/251-open-prfrom
juanmaguitar/251-docs
Sep 17, 2026
Merged

juanmaguitar merged 18 commits into
juanmaguitar/251-open-prfrom
juanmaguitar/251-docs

Conversation

@juanmaguitar

@juanmaguitar juanmaguitar commented Sep 17, 2026 •

Copy link
Copy Markdown
Collaborator

Why

The Gutenberg workflow stack adds a second contribution target, but several guide pages still describe every site as a WordPress Core checkout and the committed automated screenshots predate the project picker. That leaves the docs inconsistent with the UI contributors will use when the stack lands.

What changes

The guides also describe the corrected Gutenberg behavior: persistent posts/settings/uploads across server restarts, keeping the issue branch when a move to trunk conflicts, and GitHub issue attribution in mentor handoff patches.

The shared guides now use project-neutral “work item” language where Core tickets and Gutenberg issues follow the same flow, and name the differences where they do not: issue linking, pull-request checkout, submission destinations, builds, and the current debug.log limitation. The screenshot harness gains an isolated Gutenberg fixture, refreshes every automated snapshot, and adds Gutenberg site and pull-request-panel images. The debug-log capture now proves that seeded PHP output is visible without launching failing processes against a deliberately fake site.

How to test this

Platforms: Any platform for the documentation and checks. The committed screenshots were captured on macOS against bc8c4fda3fb78d51cd6f3abd5fc1b0f1e89c0095; no signed app build is needed because this PR changes no shipped application behavior.

Starting state: Check out this PR's current head with dependencies installed. Run every command below from the repository root unless the command itself uses --prefix docs.

  1. Run npm run lint and expect ESLint to finish with no errors.
  2. Run npm test and expect 1,547 passing tests, no failures, and two Electron-runtime skips.
  3. Run npm ci --prefix docs && npm run build --prefix docs and expect VitePress to render every page without broken internal links.
  4. Run npm run shots and expect all 12 fixture-tier captures to complete. Open docs/public/screenshots/gutenberg-site-view.png and confirm the Gutenberg tag, GitHub issue card, and pull-request checkout card are visible. Open docs/public/screenshots/debug-log.png and confirm the PHP notices are visible and no tab says exited.
  5. Run npm run dev --prefix docs, open the local documentation site, and follow Getting started → Working on a GitHub issue → Applying patches and PRs → Submitting your changes. Confirm each page distinguishes Core-only actions from the Gutenberg route and both new images render.

What must not have happened: The screenshot run must not read or modify the contributor's real site registry; every automated capture uses a throwaway TOOLKIT_USER_DATA_DIR. No product source, dependency, or existing stack behavior should differ after this PR.

Risks and limitations

The live-tier captures that require a real initialized checkout were not retaken; this PR refreshes all 12 reproducible fixture-tier images and labels the remaining Core-specific screenshots as Core. Gutenberg's PHP debug.log is not exposed from Playground's virtual WordPress filesystem yet, so the guide records that limitation instead of promising a non-working panel.

Self-review: 0 [fix here] · 0 [follow-up] after one low-severity screenshot finding was fixed and re-reviewed.

Related

Part of #251. Stacked on #481.


Design decisions and alternatives considered

The shared guides stay shared where the interaction is genuinely the same; splitting every page into Core and Gutenberg copies would duplicate most of the text and let the two versions drift. Project-specific pages and callouts carry the differences instead.

The Gutenberg screenshots use seeded metadata and a minimal build marker rather than a real clone, keeping the fixture tier deterministic and independent of GitHub or a long npm install. The debug-log capture gets its own fixture/app instance because it replaces two process handlers while attaching the real log tail; isolation prevents those handlers leaking into later captures.

Review outcome (required — see AGENTS.md)
  • Initial review: completed — fresh agent context /root/fresh_pr_review; head 237d539e614184a905c1554bfd83cd80c56fe0bc / base ad3a120dbe72ad2d7183ddd8df4ebcc75cf27a23; five-dimension review found 1 low-severity [fix here] issue in the debug-log screenshot and 0 [follow-up] findings.
  • Resolution: the debug capture now uses an isolated fixture, keeps the fake watcher/server in a non-failed state, waits for a concrete seeded PHP line, and asserts that no visible tab contains exited.
  • Re-review: completed — fresh agent context /root/fresh_pr_review; head bc8c4fda3fb78d51cd6f3abd5fc1b0f1e89c0095 / same base; prior finding resolved, no new findings across architecture, security, performance, cross-platform, or tests.
  • Since review: none; the published head is the reviewed head.
Implementation notes

Checks completed on the current stack: npm run lint; npm test (1,543 pass, 0 fail, 2 skipped); npm ci --prefix docs && npm run build --prefix docs; and npm run shots (12 fixture captures). The first test attempt exposed stale local dependencies, and a concurrent retry corrupted the generated Electron download; reinstalling from the lockfile and running the final checks sequentially produced the clean results above. Neither environment issue changed tracked source.

Screenshots or recording

Gutenberg site

A Gutenberg site with its project tag, GitHub issue card, and pull-request checkout card

Gutenberg pull-request checkout

The Gutenberg pull-request checkout card

Populated debug log without a fixture failure

The debug log showing seeded PHP notices without an exited process

Stack correction review — 2026-09-17

0 remaining [fix here] · 0 new [follow-up] in this correction pass. The three affected guides match the corrected behavior.

  • Current PR head: 34550eec7a9031d2d5f03116801a60e313fa0559; configured base head: 7451a75afb09849ef92f039045d4edb3db2d4c21. Previous PR head: bc8c4fda3fb78d51cd6f3abd5fc1b0f1e89c0095.
  • Reviewer: fresh Codex context /root/review_fixes. Completed five-dimension review of the correction delta from previously reviewed integrated head bc8c4fda3fb78d51cd6f3abd5fc1b0f1e89c0095 to c8c86677491012f9c9d4e99fe43b4ceff1c9647b, then re-reviewed through final stack head 34550eec7a9031d2d5f03116801a60e313fa0559. The additional runtime-deletion finding was fixed and verified; the original three findings remain resolved. Earlier review records above are historical.
  • Validation on the combined stack: lint; Node unit/integration/IPC suite (1,547 passed, two runtime skips); Electron suite (1,549 passed); 33 macOS journeys; documentation build. The real Playground restart/deletion regression passed on macOS. Windows CI results must be read on these new heads; this pass does not claim a fresh Windows real-setup run.
  • CodeRabbit's earlier check is not review coverage for these new heads. This local correction review is the review of record for the delta; no new bot review was requested.
Windows journey synchronization fix — 2026-09-17

Rebased onto the corrected #480 journey. git range-diff confirms this PR's own commits are unchanged.

Current head: 773adb33954ecee2c5697efdfad0748cf0301a07; configured base: 5e25e1b6e94c2075bed15b0cf41bf6f31182f54e. Earlier review records are historical. Fresh reviewer /root/review_ci_sync completed the five-dimension review of the four-line correction against 7b7db146c92022fd5960d6e677f05406bc97831b, published as 6b9598c44f171b4899abd945853f816556377519: 0 [fix here], 0 [follow-up]. The rebased integrated tree differs from the previously reviewed stack only by those same four test lines.

Reproduction: temporarily delay the real leave-PR handler after checkout and before metadata persistence; the original journey fails with expected ticket/60001, received pr/7. With the new wait, the same delayed run passes. The diagnostic delay was removed before committing. On the corrected #480 head, lint and build pass, Node tests report 1,539 passed and 2 runtime skips, and all 31 macOS journeys pass. From the repository root on macOS or Windows at the current head, run npm run build:once && npm run test:e2e. All new-head CI test/build/lint jobs pass on macOS and Windows (Pages deploy intentionally skipped). No new CodeRabbit review was requested; its check says automatic reviews are disabled. The integrated #486 stack also passes all 33 journeys locally on macOS.

@coderabbitai

coderabbitai Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 60ede466-b449-4674-abd3-f69169c4b815

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@juanmaguitar
juanmaguitar added this pull request to stack #477 September 17, 2026 10:42
@juanmaguitar
juanmaguitar merged commit c91908c into trunk Sep 17, 2026
10 of 19 checks passed
@juanmaguitar
juanmaguitar deleted the juanmaguitar/251-docs branch September 17, 2026 16:34
juanmaguitar added a commit that referenced this pull request Sep 17, 2026
…quests that fix it (#251) (#480)

## Why

A Gutenberg site could be built, run and used to try pull requests, but
it had no work item: the card that on a Core site holds the Trac ticket
said that working on a GitHub issue comes in a later version, the
linked-PR search only knew `wordpress-develop`, and a pasted
pull-request URL was checked against the wrong repository. This is the
PR that gives a Gutenberg site its issue. #251 has the case.

## What changes

Two stack-review corrections: Gutenberg's conflict/no-base guidance now
preserves the issue branch and offers a backup plus mentor help, instead
of instructing deletion followed by an unavailable patch import. Its
mentor handoff header now uses the site's provider and links the actual
GitHub issue (`# Issue: …`); Core retains its Trac header.

**Reading an issue.** `src/renderer/github-issue.cjs` is the counterpart
of `trac-ticket.cjs`: a bare number, `#71234` or a pasted issue URL
(anchor, query, trailing slash included) resolve to an id; a
pull-request URL is refused by name, because pasting the PR instead of
the issue it fixes is the obvious mistake; an issue on another
repository is refused, so a site cannot cite a number from a project it
is not a checkout of; the URL branch gets the same bounds check as the
typed one. `src/work-item.cjs` chooses between that parser and Trac's
from the site's type, defaulting to Trac so a site made before project
types existed keeps its behaviour with no migration. The registry holds
what is true of one target (label, browse link, branch namespace); the
provider holds what is true of one kind of work item.

**Linking one.** `sites:set-ticket` parses through the site's provider.
The stored key stays `tracTicket` for both kinds: renaming it touches
every read on the card, the switcher and the patch handlers for no
behaviour, and the decision is recorded above the handler. The branch is
`issue/71234`, not `ticket/71234`: the noun the upstream uses, and a
contributor reading `git branch` in their own client sees it. Core keeps
`ticket/` untouched, so no existing site moves; every read in
`ticket-branches.js` accepts both namespaces (a branch is read by its
name alone), only the writers take the prefix, and a test walks the list
against the registry and against the changes note's own grammar so a
third namespace cannot be added to one side only.

**The pull requests that fix it.** The linked-PR search goes to the
site's own repository, and a row survives on the citation its work-item
kind uses: on Gutenberg the forms GitHub itself reads as "for this
issue" (`Fixes #71234`, `Closes WordPress/gutenberg#71234`, or the issue
URL), so the list agrees with the issue page's own linked pull requests;
a bare number in prose does not count, for the reason it never did on
Trac. The cache is keyed per work item, and a work item is a number in a
repository: Trac ticket 62281 and Gutenberg issue 62281 are different
things. Core keeps the bare key it always had, so nothing cached is
thrown away. The pasted-URL guard refuses a pull request from another
repository by that repository's name.

**Patch layout.** The path rewrite that steers a pre-`src/` Trac patch
under `src/` was wordpress-develop's and ran on every patch. The
registry's `patch.layout` now picks the mapper: `src-layout` is the
rewrite as it was, `repo-relative` leaves every path as named, an
unknown value throws rather than guessing. The layout rides from the
site through preview, apply and the undo after a failed persist, so what
is shown is what is written. On a Gutenberg site nothing reaches it yet
(the picker and the attachments are hidden there); it is here so the
layer is right before a caller arrives.

**The card.** It shows on every site and takes its words from the
provider and the registry: **GitHub issue**, *Working on issue #71234*,
**Link issue**, **Open on GitHub**, the good-first-issues link. What is
Trac's alone stays on Core sites: **Read details from Trac**, the
attachments block, **Attach to Trac**, the patch-file picker. The pure
copy modules take the site's noun rather than restating it (changes
note, tickets card heading, trunk-moved notice and its refusals,
next-action hint), the switch sentences name an `issue/` branch by its
number, and the applied-PR box says the issue's work returns on revert
rather than "returns to trunk". The placeholder card and the picker
sentence that said issues come later are gone.

**Docs:** `docs/guide/gutenberg-issues.md`, the counterpart of the Trac
page, in the nav; the four pages that said issues come in a later
version now point at it.

**Left for PR 5 on purpose:** the **Open a pull request** card on a
Gutenberg site still says it is not available and offers the patch file;
`github:open-pr` still refuses a non-Core site; the "Link a Trac ticket
to this site first" hint in that card is Core-only until then.
`wpct://issue/N` is PR 6.

## How to test this

Correction regressions, **either platform**, current head; from the
repository root: `node --test tests/unit/ticket-trunk-notice.test.cjs
tests/unit/ipc-wiring.test.cjs tests/unit/patch-provenance.test.cjs`.
Both corrections were checked red before the fix and green afterwards.
The recovery test covers conflict with/without named paths and unknown
base; it forbids the unlink/delete/reapply sequence for Gutenberg. The
real handoff export test reads the saved file, checks the correct issue
URL and absence of Trac, and verifies the patch still parses; its Core
counterpart preserves the old header.

Platforms: **either**; nothing here spawns anything platform-specific
beyond what #471 already covered. Build: the Buildkite artifact for the
current head.

The Gutenberg journey (`tests/e2e/journeys/gutenberg-site.spec.js`)
walks the whole path on a Git fixture: the card asks for an issue, a
pull-request URL is refused by name before anything is written, an issue
URL links and lands on `issue/71234`, a `wordpress-develop` pull-request
URL is refused, a `WordPress/gutenberg` one is checked out, the applied
box says the issue's work returns, revert lands back on the issue's
branch with its tree restored, and a `wpct://ticket` link is still
turned away. `npm run test:e2e`: 30 passed on macOS at the current head.

By hand, either platform, on a Gutenberg site (needs the network for
GitHub):

1. In the **GitHub issue** card, paste
`https://github.com/WordPress/gutenberg/pull/4496` and click **Link
issue**. Expected: "That is a pull request. Link the issue it fixes
instead."; `git branch` in the checkout is unchanged.
2. Paste `https://github.com/WordPress/wordpress-develop/issues/1` and
link. Expected: "Only WordPress/gutenberg issues can be linked here."
3. Type a real open issue's number and link. Expected: *Working on issue
#N*, **Open on GitHub** opens it, `git branch` shows `issue/N` and no
`ticket/N`; no **Read details from Trac**, no **Trac attachments**.
4. Under **Linked pull requests**, expected: the pull requests the issue
page lists under *linked pull requests*, and nothing that merely
mentions the number. Pick one with **Apply…**: the preview lists its
files, **Apply and rebuild** lands on `pr/NNNN`, the green box reads *PR
#NNNN is applied* with *Your issue changes are saved separately and
return when you revert this PR.*; **Revert this PR** returns to
`issue/N`.
5. In **Check out a pull request**, paste a `wordpress-develop`
pull-request URL. Expected: "Only WordPress/gutenberg pull requests can
be applied here."
6. **Unlink**, then link the same issue again. Expected: *Your issues on
this site* lists it in between; linking again switches back with no new
branch.
7. On a Core site, link a Trac ticket, apply a Trac attachment, check
out a `wordpress-develop` pull request. Expected: exactly what it was;
the linked-PR cache key for a Core ticket is unchanged
(`ticketPatches:<id>` in the store).

**What must not have happened:** a Core site changing in any way (step
7); a `ticket/` branch appearing on a Gutenberg site (step 3); a
`wordpress-develop` pull request cited by a Trac ticket showing under a
Gutenberg issue of the same number (step 4, if such a number exists).

## Risks and limitations

Gutenberg still has no patch importer or automatic conflict resolver.
Recovery deliberately retains the branch and directs the contributor to
save a backup and ask a mentor; simply adding import would label the
restored work as an applied external patch and block submission as their
own work.

- Review outcome (self-review against
`.github/instructions/code-review.instructions.md`, fresh agent context,
head `98650dd` on base `juanmaguitar/251-contribute-to`, completed over
the full diff; lint and unit suite clean): 5 [fix here], all fixed in
`f96db22` (the noun threaded into the trunk-moved refusals, the action
gate, the dirty-trunk question and the patch-review heading; the field's
accessible name and the attach URL read from the provider; callerless
`defaultPrTitle` and the diff fetch's repo option removed;
`bodyCitesIssue` narrowed to GitHub's own rule). 1 [follow-up]:
`parseIssueRef`'s default repository duplicates the registry's Gutenberg
entry; every real caller threads the repository, so it only matters for
a second GitHub-issue target. Two style notes: `showTracCards` now means
three things at four call sites (the "no PR flow yet" one gets its own
flag in PR 5). CodeRabbit: one review on `bdaf6a5`, 1 low finding (the
undo test never sent the undo), fixed in `b354059`.
- Found by hand on macOS after opening (2026-09-16), fixed in `6ee65ec`:
linking an issue by hand armed #292's auto-read, which opened the Trac
window on the Core ticket with the same number and painted its summary,
status, milestone and attachments note under the issue. The auto-read
and the scraped facts are Trac's alone in the renderer now, and
`trac:list-attachments` refuses a non-Trac site before the window can
open. Reproduced first by a journey assertion (a stubbed Trac records
zero scrapes and none of its facts show) and an ipc-wiring test.
- `git:fetch-pr-diff` stays Core-only and says so: nothing has invoked
it since pull requests became checkouts (#458). Removing the handler and
its preload entry is a follow-up (the packaged smoke test pins the API
surface).
- The renderer's changes note holds its own copy of the two branch
namespaces as a regex: `ticket-branches.js` owns the list but reaches
for Git and cannot be bundled. A test walks the list against the regex
so they cannot drift apart unnoticed.
- `bodyCitesIssue` matches GitHub's own linking rule: a closing keyword,
followed by `#N`, `owner/repo#N` or the issue URL. A pull request that
says "Related to #N" or drops the URL in prose is not listed, on GitHub
or here; a contributor looking for it can paste its URL.
- The "Link a Trac ticket to this site first" message in the
pull-request card, and the card itself on a Gutenberg site, are PR 5's.
- `sites:add` (adopting a folder) still records every adopted checkout
as Core; carried over from #476.

## Related

Part of #251. Stacked on #476 (rebased with it onto trunk `576d218` on
2026-09-16; the tickets card #472 moved keeps its place and shows on
every site).


🤖 Generated with [Claude Code](https://claude.com/claude-code)

<details>
<summary>Stack correction review — 2026-09-17</summary>

0 remaining [fix here] · 0 new [follow-up] in this correction pass. Both
recovery and mentor-attribution findings are resolved.

- Current PR head: `7b7db146c92022fd5960d6e677f05406bc97831b`;
configured base head: `08315156268f34ab94f6f0c81192f02642d48ce0`.
Previous PR head: `4915881a062f65fe8e3e6fe5fac8d6e34e806066`.
- Reviewer: fresh Codex context `/root/review_fixes`. Completed
five-dimension review of the correction delta from previously reviewed
integrated head `bc8c4fda3fb78d51cd6f3abd5fc1b0f1e89c0095` to
`c8c86677491012f9c9d4e99fe43b4ceff1c9647b`, then re-reviewed through
final stack head `34550eec7a9031d2d5f03116801a60e313fa0559`. The
additional runtime-deletion finding was fixed and verified; the original
three findings remain resolved. Earlier review records above are
historical.
- Validation on the combined stack: lint; Node unit/integration/IPC
suite (1,547 passed, two runtime skips); Electron suite (1,549 passed);
33 macOS journeys; documentation build. The real Playground
restart/deletion regression passed on macOS. Windows CI results must be
read on these new heads; this pass does not claim a fresh Windows
real-setup run.
- CodeRabbit's earlier check is not review coverage for these new heads.
This local correction review is the review of record for the delta; no
new bot review was requested.

</details>


<details>
<summary>Windows journey synchronization fix — 2026-09-17</summary>

The PR checkout journey now waits for the pull-request input to reappear
and become enabled after restoration, before inspecting Git, files and
persisted settings. All previous assertions remain; no production code
changed.

Current head: `6b9598c44f171b4899abd945853f816556377519`; configured
base: `08315156268f34ab94f6f0c81192f02642d48ce0`. Earlier review records
are historical. Fresh reviewer `/root/review_ci_sync` completed the
five-dimension review of the four-line correction against
`7b7db146c92022fd5960d6e677f05406bc97831b`, published as
`6b9598c44f171b4899abd945853f816556377519`: 0 [fix here], 0 [follow-up].
The rebased integrated tree differs from the previously reviewed stack
only by those same four test lines.

Reproduction: temporarily delay the real leave-PR handler after checkout
and before metadata persistence; the original journey fails with
expected ticket/60001, received pr/7. With the new wait, the same
delayed run passes. The diagnostic delay was removed before committing.
On the corrected #480 head, lint and build pass, Node tests report 1,539
passed and 2 runtime skips, and all 31 macOS journeys pass. From the
repository root on macOS or Windows at the current head, run `npm run
build:once && npm run test:e2e`. New-head macOS and Windows journeys and
packaged smoke pass, as do lint, docs and macOS unit tests. The Windows
Electron unit suite is still running. No new CodeRabbit review was
requested; its check says automatic reviews are disabled. The integrated
#486 stack also passes all 33 journeys locally on macOS.

</details>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
juanmaguitar added a commit that referenced this pull request Sep 17, 2026
…enberg, citing its issue (#251) (#481)

## Why

A Gutenberg site could link an issue and try the pull requests that fix
it, but could not send its own work: the **Open a pull request** card
said the flow was not available and `github:open-pr` refused any
non-Core site, because every step of the flow (fork, sync, tree, branch,
pull request) read `WordPress/wordpress-develop`, `trunk` and `trac-`
from constants. This PR makes the flow read the site's project type, so
a Gutenberg site forks `WordPress/gutenberg`, pushes `fix/issue-N` and
opens a pull request whose description says `Fixes #N`. #251 has the
case.

## What changes

**The flow reads the project.** `src/github-pr.cjs`: `upstream(project)`
returns the project's repository unless the sandbox override is set (the
override exists to keep a real run off a real upstream, whichever one it
is, so it still wins); `baseBranchFor(project)` reads the base branch;
`branchNameFor` takes the prefix; `buildPullRequestBody` takes the
project's body line and the work item's URL. `openPullRequest` puts the
project in `deps`, so every helper reads the same repository and base
without growing a parameter of its own. A caller that passes no project
gets what it always got, which is every existing caller. `testMode()` is
deliberately env-only: reading a project's upstream there would report
every Gutenberg site as sandboxed.

**The handler opens for every type.** `github:open-pr` passes the site's
project type, cites the work item with the project's line (`Trac ticket:
<url>` on Core, `Fixes #N` on Gutenberg) and falls back to a title the
provider owns: `defaultPrTitle` is back on `workItemProvider` because
the handler's fallback and the card's "Left empty, it will be titled"
hint have to be one string. The no-ticket refusal names the site's work
item.

**The card.** The Gutenberg refusal in `renderPullRequestBody` is gone
with the handler's. What the destination says per target lives in the
registry's `cards`: cost, what happens after, what the app cannot do for
a signed-out contributor, the help under the notes field, the loop-back
once the pull request exists, and the fold on how pull requests work
here. Core's fold keeps its two handbook facts; Gutenberg's says the
opposite, because both are false there and this is the audience least
able to notice the app describing a different project. The no-ticket
failure names the site's work item, the test-mode badge names the site's
repository, and the confirmation says which repository the pull request
landed on. `showTracCards` is back to meaning only what is Trac's alone:
the attachments, **Attach to Trac**, **Read details from Trac**.

**Dead surface removed.** `git:fetch-pr-diff`, `fetchPrDiff` in
`github-prs.js` and the preload entry: nothing has invoked them since
pull requests became checkouts (#458). The packaged smoke test and the
ipc-wiring pin drop them too.

**Docs.** `docs/guide/submit-github-pr.md` covers both projects, with a
per-project table and a section on how pull requests work in Gutenberg.
The four pages that said the flow comes in a later version no longer do.

The description keeps Core's shape on both targets: the contributor's
notes on top, then the work-item line, then attribution. Gutenberg's PR
template (What / Why / How / Testing Instructions) is not emitted; the
form has one notes box, and most headings would ship empty. The card's
help on a Gutenberg site says testing steps go in the notes.

## How to test this

Platforms: **either** for the journey and the unit suite; the hand pass
below is for **macOS**. Nothing here spawns anything.

Unit: `tests/unit/github-pr.test.cjs` drives the whole sequence with the
Gutenberg project and asserts every call lands under `janedoe/gutenberg`
or `WordPress/gutenberg`, none under `wordpress-develop`, the branch is
`fix/issue-71234`, the pull request's base is `trunk`; the sandbox
override still beats the project; a dry run links the branch on the
gutenberg fork; `ensureFork` names `gutenberg` in its refusals;
`testMode()` ignores a project. `ipc-wiring` asserts a Gutenberg site
reaches `openPullRequest` with its upstream, prefix, `Fixes #N` body
line and `Issue #N` title, and that the Core call is what it was plus
the project. The registry test requires the full destination copy on
every type and that none of Gutenberg's says Trac. `npm test`: 1534
passed.

Journeys: `tests/e2e/journeys/gutenberg-site.spec.js` now opens **Review
& submit changes** on the Gutenberg site and checks the pull-request
destination is offered, worded for a GitHub account, with no "not
supported" sentence and no **Attach to Trac**.
`tests/e2e/journeys/open-pull-request.spec.js` (new, `44382df`) stubs
`github:account` and `github:open-pr` at the IPC seam and walks the card
on both projects: the form's words (fork target, untitled hint, notes
help, the fold), the stage label naming the repository being forked, the
progress event moving this site's spinner, the result, the confirmation
naming where it landed, the loop-back line, and that the renderer sends
main only the site and the form. GitHub itself is not reached there.
`npm run test:e2e`: 33 passed on macOS at the current head.

By hand, macOS, a built copy, on a Gutenberg site with an issue linked
and a change in the working tree:

1. Launch with `WP_DEV_ENV_GITHUB_DRY_RUN=1` in the environment.
Expected: the **Test mode** badge reads *Dry run*, the button reads
**Push branch (dry run)**, the fold reads *How pull requests work in
Gutenberg*, the notes help mentions the Fixes line. Sign in, press the
button. Expected: *Dry run, branch `fix/issue-N` was created on your
fork*, the link opens `<you>/gutenberg/tree/fix/issue-N`, no pull
request anywhere.
2. Launch with `WP_DEV_ENV_GITHUB_UPSTREAM=<sandbox>` (a sandbox with a
`trunk` branch, not owned by you). Expected: the badge says pull
requests go to the sandbox, *not to WordPress/gutenberg*. Open one.
Expected: the pull request is on the sandbox from `<you>:fix/issue-N`,
base `trunk`, body ending in `Fixes #N` above the attribution; the
confirmation names the sandbox; **Open #N to comment** opens the issue
on `WordPress/gutenberg`.
3. Same sandbox, a Core site with a Trac ticket linked. Expected: branch
`trac-N`, body line `Trac ticket:
https://core.trac.wordpress.org/ticket/N`, untitled fallback **Ticket
#N**, the Core fold with its handbook link. Nothing else changed.
4. No issue linked on the Gutenberg site. Expected: the card says *No
issue is linked to this site. A pull request has to cite one: link it in
the GitHub issue card.*

**What must not have happened:** a pull request or a branch reaching
`WordPress/gutenberg` or `WordPress/wordpress-develop` during steps 1 to
3; a Gutenberg site wearing the test-mode badge with no switch set; any
sentence on a Gutenberg site's card naming Trac.

The hand pass (steps 1 to 4) has not been run yet at this head; it needs
a built copy and the sandbox. It will be recorded here once done.

## Risks and limitations

- Review outcome (self-review against
`.github/instructions/code-review.instructions.md`, fresh agent context,
head `87a9b27` on base `juanmaguitar/251-github-issue`, completed over
the full diff; lint and unit suite clean): 3 [fix here], all fixed in
`1515167` (the forking stage label named wordpress-develop on every
site, now a tested renderer module that takes the site's repository; the
confirmation named the site's repository rather than where the pull
request landed, now read from its URL; the renderer's no-ticket branch
duplicated the sentence main sends, removed). 1 [follow-up], done in the
same commit: the body line is asserted to round-trip through
`citesWorkItemFor`. Style notes fixed too. CodeRabbit: one review on
`44382df`, 1 low finding (the forking label named the site's repository
under the sandbox override), fixed in `c7018d0`.
- The body does not follow Gutenberg's pull-request template. A reviewer
may ask for it; the contributor edits the description on GitHub.
- `git:fetch-pr-diff` is gone. Nothing in the renderer called it, and
the packaged smoke test's pinned surface is updated in the same commit.
- Follow-up carried from #480: `parseIssueRef`'s default repository
duplicates the registry's Gutenberg entry.

## Related

Part of #251. Stacked on #480 (whole stack rebased onto trunk `576d218`
on 2026-09-16).


🤖 Generated with [Claude Code](https://claude.com/claude-code)

<details>
<summary>Stack correction review — 2026-09-17</summary>

0 remaining [fix here] · 0 new [follow-up] in this correction pass. No
PR-local behavior changed; git range-diff reports every original commit
unchanged (=) against its rebased base.

- Current PR head: `7451a75afb09849ef92f039045d4edb3db2d4c21`;
configured base head: `7b7db146c92022fd5960d6e677f05406bc97831b`.
Previous PR head: `ad3a120dbe72ad2d7183ddd8df4ebcc75cf27a23`; only the
inherited stack corrections and commit identities changed.
- Reviewer: fresh Codex context `/root/review_fixes`. Completed
five-dimension review of the correction delta from previously reviewed
integrated head `bc8c4fda3fb78d51cd6f3abd5fc1b0f1e89c0095` to
`c8c86677491012f9c9d4e99fe43b4ceff1c9647b`, then re-reviewed through
final stack head `34550eec7a9031d2d5f03116801a60e313fa0559`. The
additional runtime-deletion finding was fixed and verified; the original
three findings remain resolved. Earlier review records above are
historical.
- Validation on the combined stack: lint; Node unit/integration/IPC
suite (1,547 passed, two runtime skips); Electron suite (1,549 passed);
33 macOS journeys; documentation build. The real Playground
restart/deletion regression passed on macOS. Windows CI results must be
read on these new heads; this pass does not claim a fresh Windows
real-setup run.
- CodeRabbit's earlier check is not review coverage for these new heads.
This local correction review is the review of record for the delta; no
new bot review was requested.

</details>


<details>
<summary>Windows journey synchronization fix — 2026-09-17</summary>

Rebased onto the corrected #480 journey. git range-diff confirms this
PR's own commits are unchanged.

Current head: `5e25e1b6e94c2075bed15b0cf41bf6f31182f54e`; configured
base: `6b9598c44f171b4899abd945853f816556377519`. Earlier review records
are historical. Fresh reviewer `/root/review_ci_sync` completed the
five-dimension review of the four-line correction against
`7b7db146c92022fd5960d6e677f05406bc97831b`, published as
`6b9598c44f171b4899abd945853f816556377519`: 0 [fix here], 0 [follow-up].
The rebased integrated tree differs from the previously reviewed stack
only by those same four test lines.

Reproduction: temporarily delay the real leave-PR handler after checkout
and before metadata persistence; the original journey fails with
expected ticket/60001, received pr/7. With the new wait, the same
delayed run passes. The diagnostic delay was removed before committing.
On the corrected #480 head, lint and build pass, Node tests report 1,539
passed and 2 runtime skips, and all 31 macOS journeys pass. From the
repository root on macOS or Windows at the current head, run `npm run
build:once && npm run test:e2e`. All new-head CI test/build/lint jobs
pass on macOS and Windows (Pages deploy intentionally skipped). No new
CodeRabbit review was requested; its check says automatic reviews are
disabled. The integrated #486 stack also passes all 33 journeys locally
on macOS.

</details>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
juanmaguitar added a commit that referenced this pull request Sep 17, 2026
…487)

## Why

The published guide at
https://wordpress.github.io/contributor-toolkit/guide/getting-started.html
is built from trunk on every push that touches `docs/`. Merging the
Gutenberg stack therefore put five unreleased features in front of
users: the latest release is v1.1.0, and nothing anyone can download has
"Working on a GitHub issue", the Gutenberg site type, or the Gutenberg
PR panel.

This is not a docs mistake. Docs shipping in the same PR as the change
they describe is the rule here, and the consequence of that rule is that
trunk's guide is always ahead of the newest build. What was wrong is
publishing trunk.

## What changes

The site now tracks the latest **release**. Deploy runs only on
`workflow_dispatch`, with a required `ref` input naming what to publish,
so cutting a release advances the guide with one command:

```bash
gh workflow run docs.yml --ref trunk -f ref=v1.2.0
```

Two decisions a reviewer would otherwise have to reverse-engineer:

**Why the ref is an input rather than the dispatch target.** Dispatching
on tag `v1.1.0` would run the copy of `docs.yml` that exists *at that
tag*, whose deploy job is gated on `github.ref == 'refs/heads/trunk'`.
It would build and silently skip the deploy. So the dispatch happens on
trunk and the ref to publish travels as an input.

**Why the deploy guard still tests `github.ref`.** It means something
narrower now, and the comment says so: it decides which copy of this
workflow may hold the Pages OIDC token (the reviewed, merged one), not
what gets published. `inputs.ref` decides that, and publishing an
arbitrary ref is the point. The `github-pages` environment already has a
deployment-branch policy allowing only `trunk`, so this is belt and
braces.

Pushes to trunk and pull requests still build, they just never deploy: a
dead link fails when it is proposed and again when it lands, rather than
at release time.

Not in this PR: any change to `docs/` content. Trunk's guide stays as it
is and goes live at v1.2.0.

## How to test this

Platform: any. This is CI-only, nothing in the app changes.

**Starting state:** this branch, and the repo's Actions tab.

1. Open this PR's **Docs** check. Expected: the `build site` job runs
and passes; **no** `deploy to Pages` job appears. The old behaviour was
the same for PRs, so this confirms nothing regressed.
2. In the run's summary page, look at the **Record what was built**
step's output. Expected: a "Built `<sha>`" line naming the commit, and
no "Not published" warning (that warning is for dispatches only).
3. After merge, go to Actions → Docs → **Run workflow**. Expected: a
**"Git ref to publish"** box, required, with no prefilled value.
4. Run it with branch `trunk` and ref `v1.1.0`. Expected: build passes,
`deploy to Pages` runs, the run summary names `v1.1.0` and commit
`576d218`, and the live guide loses the Gutenberg pages: no "Working on
a GitHub issue" in the sidebar, and `/guide/gutenberg-issues.html` 404s.
5. The sharp-edge path: run it with branch `v1.1.0` (the tag) instead of
trunk. Expected: it builds, the deploy is skipped, and the run summary
carries a "⚠️ Not published" line telling you to re-run with `--ref
trunk -f ref=<tag>`. Without that line this would be an all-green run
that published nothing.

**What must not have happened:**

- A deploy triggered by a plain push to trunk. The whole point is that
merging docs no longer publishes them.
- The site published from trunk while the input said `v1.1.0` — check
the deployed page, not just the green check.
- A release publish cancelled mid-flight by an unrelated docs merge.
Workflow-level concurrency cancels jobs too, so dispatches get their own
concurrency group; the job-level `docs-deploy` group does not cover
this.

## Risks and limitations

3 [fix here] · 2 [follow-up] from the review pass, all 5 addressed.
Detail in the collapsed block.

- The guide is now stale by design between releases. Anyone reading the
live site while trunk is ahead sees the last release, which is the
intent, but it does mean a docs fix for a *released* bug also waits for
the next release unless someone dispatches deliberately (which they can,
at any time, with any tag).
- Whoever dispatches decides what goes live. `inputs.ref` can name an
unmerged branch or a PR merge ref, so "the site is reviewed code" is now
a habit rather than a branch rule. The build job holds nothing worth
stealing (`contents: read`, `persist-credentials: false`, and the deploy
job checks nothing out), so this is not a token-escalation path. Called
out in the file's own comment.
- Steps 3-5 above cannot be exercised before merge: `workflow_dispatch`
only offers the inputs declared in the copy of the file on the default
branch. What *was* verified locally is that the v1.1.0 docs still build
clean under today's toolchain (`npm ci --prefix docs` + `vitepress
build` at tag `v1.1.0`, no dead links, 19 guide pages, no
`gutenberg-issues.html`), so the restore dispatch is not going to fail
on content.

## Related

Follow-up to the Gutenberg docs stack: #486, #481, #480, #476, #471.

---

<details>
<summary>Design decisions and alternatives considered</summary>

**Reverting `docs/` on trunk to its v1.1.0 state.** Rejected: it fights
the repo rule that docs ship with the change that makes them true, and
every reverted page would have to be un-reverted at v1.2.0, with the
Gutenberg screenshots going in and out of the tree twice.

**A long-lived `docs-live` branch that auto-deploys, fast-forwarded at
each release.** Rejected as one more branch to maintain for the same
outcome a dispatch already gives.

**A default value for the `ref` input.** Rejected: a hardcoded default
names one release tag and is wrong from the next release onwards.
Resolving "latest release tag" in a step was the alternative, and it
adds a `gh` call and a failure mode to a job that runs twice a year.
Typing the tag also makes the run record say what went live.

</details>

<details>
<summary>Review outcome (required — see AGENTS.md)</summary>

3 [fix here] · 2 [follow-up] — all 5 addressed in `628820c`.

- 🟡 security, `docs.yml` summary step: the ref was interpolated into
`run:` through `${{ }}`, which substitutes into the script's source
before bash parses it, so a ref name carrying `$( )` or a quote would
have been code. Now passed through `env:` and read as `"$REF"`, the way
`download-stats.yml` passes its values, with `set -euo pipefail` added.
Verified by running the step locally with `REF='v1.1.0$(echo PWNED)"'`:
printed literally.
- 🟡 architecture, concurrency: `docs-build-${{ github.ref }}` put a
release dispatch and a routine trunk push in the same group, and
workflow-level concurrency cancels the whole run, jobs included, so a
docs merge could cancel a publish mid-flight. Dispatches are now keyed
on `github.run_id`.
- 🔵 architecture, checkout: `ref: ${{ inputs.ref || github.ref }}` was
not the no-op it claimed for push events. Checkout's own default is
`github.sha`; `github.ref` would build whatever the branch tip is when
the step runs. Changed to `github.sha`.
- 🔵 [follow-up] security: the header paragraph this diff removed was the
one saying the deploy never runs on PR code. That property genuinely
changes, so it is now stated on the deploy guard rather than dropped.
- 🔵 [follow-up] architecture: a dispatch on a non-trunk ref was an
all-green run that published nothing. The summary step now writes a "Not
published" warning naming the correct invocation.

The pass also confirmed, case by case, that the new guard holds:
`pull_request` (fork included) fails the event test; a fork cannot
dispatch at all; a branch or tag dispatch fails the ref test.

- **Review:** completed — `/self-review` per AGENTS.md, judgement pass
in a fresh subagent context given only the diff and
`code-review.instructions.md`; reviewed head = working tree over base
`c91908c` (uncommitted at review time; the reviewed content is now
committed as `628820c` with the five findings applied on top).
Deterministic layer: `npm run lint` clean, `npm test` 1547 pass / 0 fail
/ 2 skipped. `actionlint` is not installed here and Docker was not
running, so the workflow was checked by YAML parse and by executing the
new shell step locally against both event shapes.
- **Since review:** `c91908c` → `628820c` — the five findings, plus one
CONTRIBUTING.md wording fix. No re-review; the changes are the review's
own remedies.

</details>

<details>
<summary>Implementation notes</summary>

`inputs` is a recognised context here because the workflow declares
`on.workflow_dispatch.inputs`; on `push` and `pull_request` it evaluates
to empty rather than failing expression evaluation, so `inputs.ref ||
github.sha` falls through cleanly.

`CONTRIBUTING.md` changes in the same PR because its "The documentation
site" section stated the old behaviour in prose, and this change makes
that sentence false.

</details>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant