test(app-shell): serve batch 4's four _drafts probes from doubles (objectui#7307) - #8032
Merged
Merged
Conversation
…objectui#7307)
The four `console/home` HomePage tests each mounted `PendingDraftsBanner`, whose
`usePendingDrafts({})` fetches `GET /api/v1/meta/_drafts` with the global `fetch`
and no `apiFetch` seam. Under happy-dom that resolved to a real socket on
localhost:3000, once per case; the hook's best-effort `catch` is why the suites
stayed green while every request failed.
Each file now installs a recording router at module scope that answers
`/api/v1/meta/_drafts` with an empty draft ledger in the `{ drafts: [...] }`
envelope, 404s anything else, and asserts in `afterEach` that no URL outside the
served set was requested. `PendingDraftsBanner` renders `null` for both `count
=== null` (the failed read) and `count === 0` (an empty ledger), so no assertion
moves. `cleanup()` runs before `vi.unstubAllGlobals()` (objectui#7439).
Their four lines leave `KNOWN_ESCAPES` and `PINNED_LEDGER` in this same commit:
8 entries to 4 in both lists, verified by diffing the quoted paths of the two
literals against each other.
Part of #7307
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contributor
Author
|
Standing-down note — Generated by Claude Code |
baozhoutao
marked this pull request as ready for review
September 6, 2026 11:59
This was referenced Sep 6, 2026
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Part of #7307 — batch 4 of the network-escape burn-down (batch 1 was #7999, batch 2 #8013, batch 3 #8019).
The four
app-shellconsole/homerows now serve their/api/v1/meta/_draftsprobe from a recording double, and their lines leave
KNOWN_ESCAPESandPINNED_LEDGERin the same commit.Per file
Every one of the four takes the same single route through the same single call
site — measured, not assumed (see below). No file in this batch needs a second
route, so all four routers are byte-identical.
src/console/home/__tests__/HomePage.approvalsTarget.test.tsxGET /api/v1/meta/_draftsusePendingDrafts.ts:48, a bare globalfetchwith noapiFetchseaminstallDraftsDouble()in a module-scopebeforeEachsrc/console/home/__tests__/HomePage.authoringCapabilityGate.test.tsxGET /api/v1/meta/_draftsusePendingDrafts.ts:48installDraftsDouble()in a module-scopebeforeEachsrc/console/home/__tests__/HomePage.inboxLinksTarget.test.tsxGET /api/v1/meta/_draftsusePendingDrafts.ts:48installDraftsDouble()in a module-scopebeforeEachsrc/console/home/__tests__/HomePage.notificationDeepLink.test.tsxGET /api/v1/meta/_draftsusePendingDrafts.ts:48installDraftsDouble()in a module-scopebeforeEachMechanism, measured rather than assumed. A trap-guarded stack probe injected
at the guard's own attribution point, restored by blob hash afterwards, attributed
every escape in all four files. Each render of
HomePagemountsPendingDraftsBanner, which reads the env-wide count throughusePendingDrafts({}); the hook's mount effect (usePendingDrafts.ts:116viarefreshat:94) callsfetchPendingDrafts, which reaches the globalfetchatusePendingDrafts.ts:48. There is noapiFetchseam anywhere on that path — unlikethe
apiFetch ?? fetchfallbacks batches 1 to 3 met, this hook only ever had theglobal. Under happy-dom that global is a real HTTP client whose document URL is
http://localhost:3000, so the relative path resolved to a live socket, exactlyonce per case (5 / 9 / 9 / 6 escapes against 5 / 9 / 9 / 6 tests). The read is
best-effort — its
catchleavescountatnull— which is why these files stayedgreen while the request always failed.
Every escaped URL in all four files was
http://localhost:3000/api/v1/meta/_drafts,with no second endpoint anywhere. The probe was reverted with
git checkout HEAD -- path; the guard is byte-identical toHEADafterwards(blob back to
347ed78b,git diff HEADempty,git status --porcelainempty).The doubles are batch 1/2/3's shape verbatim:
vi.stubGlobal('fetch', router)in
beforeEach,cleanup()beforevi.unstubAllGlobals()inafterEach(the#7439 ordering). Each is a router, not a sink — it records every URL it is handed
and
afterEachfails on any URL outside the set it serves, so an escape elsewherereds here instead of vanishing into the hook's best-effort
catch. Routes arematched on the PATHNAME, because
fetchPendingDraftsappends a?packageId=scopefor package-bound callers; the full URL is what gets recorded.
What it answers, and why no assertion moves:
/api/v1/meta/_drafts— a known-EMPTY draft ledger, in the{ drafts: [...] }envelope
fetchPendingDraftsreads (the shapeMetadataClient.listDraftspinsfor this endpoint; the bare-array and
{ data: { drafts } }shapes parse to thesame rows).
warning about batches 1 to 3 was right not to transfer: the explain verdict those
batches served was fail-open and unread, but a
_draftsanswer feeds visible UIstate.
PendingDraftsBannerrendersnullwhen(count ?? 0) is at most 0, andthe failing request produced
count === null, so an empty ledger yieldsbyte-identical output to what these cases have always rendered. A seeded ledger
would have added a banner paragraph and a
pending-drafts-publishbutton to everycase's tree — that would move what the files prove, not just what they fetch.
depends on the request FAILING: each stubs
useMetadataClientandusePublishAllDraftsat the module boundary already, and asserts only navigationtargets, CTA enablement, or gate copy.
Nothing is skipped, quarantined or silenced.
Hook placement. All four install at MODULE scope rather than inside the existing
describe-levelbeforeEach. Two of the four (authoringCapabilityGate,inboxLinksTarget) carry nesteddescribeblocks with their ownbeforeEach, anda module-scope pair is the one placement that provably covers every block in every
file; the ablation below runs against the file with the most nesting for exactly
that reason. The router's shape and the #7439 teardown ordering are unchanged either
way.
Ledger arithmetic
8 to 4, in both lists. The four names are deleted from
KNOWN_ESCAPESinvitest.setup.network-escape-guard.ts(each with its endpoint comment) and fromPINNED_LEDGERinscripts/__tests__/network-escape-ledger.test.ts, in one commit.Checked in lockstep by diffing the quoted paths of the two literals against each
other on the final head: empty diff, both at 4, zero
console/homerows left ineither. The pin's non-vacuity floor is
lengthgreater than 0, so 4 clears it.Remaining 4, all app-shell, in three endpoint families:
/api/v1/meta/object(
FlowNodeInspector.inactiveRetained,FlowNodeInspector.specKeys),/api/v1/automation/_status(StudioDesignSurface.designerRegistryMissing),/api/v1/ai/conversations(studioSurfaceContext).No prose count moved. All five "21" references across the two ledger files are
provenance claims about the original sweep on
67dadd6, not live counts of thecurrent list — re-checked line by line, the same reading batch 3 recorded.
Evidence
Per file, before then after (attribution lines /
ECONNREFUSEDlines, then thepost-fix run on the final head):
HomePage.approvalsTargetTests 5 passedHomePage.authoringCapabilityGateTests 9 passedHomePage.inboxLinksTargetTests 9 passedHomePage.notificationDeepLinkTests 6 passedTest counts are unchanged in every file — the doubles add no cases and remove none.
pnpm exec vitest run packages/app-shell/src/console/home/ scripts/__tests__/network-escape-ledger.test.tson the final head
6a0c8d8dd: exit 0,Test Files 13 passed (13),Tests 72 passed (72), and zero lines matchingnetwork-escapeorECONNREFUSEDin the whole run.The whole
app-shellpackage plus the pin also ran green (Test Files 633 passed (633),Tests 6080 passed | 1 skipped (6081), exit 0, 918s). That run is a superset,not a zero-escape claim: its 18 remaining attribution lines all name the four rows
still on the ledger (
FlowNodeInspector.specKeys3,studioSurfaceContext4,FlowNodeInspector.inactiveRetained1,StudioDesignSurface.designerRegistryMissingconsole/homefile. It was taken on the pre-mergecommit
d4558f65f; the merge that followed touched none ofapp-shell's sources,and the 13-file scope above was re-run on the merged head.
Ablation (on the committed tree, one script with
trap ... EXIT INT TERMandabsolute paths throughout).
HomePage.authoringCapabilityGate.test.tsx— the filewith the most
describenesting, so the one that most needs the module-scopeplacement to be real — had its double reverted to its pre-batch-4 blob while its
line stayed deleted from BOTH ledgers. Mutation proven on disk, not by exit code:
blob
8c6b9dc7toddee802f, asserted equal to the83a9d22abase blob andunequal to
HEAD's; anchorsinstallDraftsDouble2 to 0 andvi.stubGlobal1 to 0;that path counted 0 times in
KNOWN_ESCAPESand 0 times inPINNED_LEDGERduringthe run. Predicted direction stated before running: red naming the file.
OBSERVED red — exit 1,
Test Files 1 failed (1),Tests 9 failed (9), withNetwork escape: this test reached a REAL socket at http://localhost:3000/api/v1/meta/_draftsand
file: packages/app-shell/src/console/home/__tests__/HomePage.authoringCapabilityGate.test.tsx.All nine cases failed, across all five
describeblocks — which is the half thatshows the module-scope hooks really are what covers every block. Restore proven by
observation: blob back to
8c6b9dc7,git diff HEADon that path empty,git status --porcelainempty.No dist preflight leg: these are the vitest projects' own test files and the root
config aliases every package specifier to
src— measured, in that all four suitesran, before and after, on a fully unbuilt tree (fresh install, no build had happened
yet).
Gates, exit codes captured by redirect-then-capture, never through a pipe, all
re-taken on the final head
6a0c8d8dd:node scripts/check-changeset-presence.mjsexit 0 — "4 source file(s) of 1released package(s) changed, and this change declares 1 changeset(s)"; the
changeset has an EMPTY frontmatter, the explicit exemption for a test-only change
under a released package's
src/, and not a label.pnpm check:control-bytesexit 0 (6449 tracked text files), plus agrep -naPself-scan of the control range over every changed path, no hits.
node scripts/check-governed-queue-guard.mjs --testover all changed paths: exit0, "NOT GOVERNED — 15 path(s) checked against 5 governed surface(s)".
pnpm type-check:scriptsexit 0 ·pnpm check:vi-mock-inheritexit 0 (229 callsites judged on the widened specifier set test(console,app-shell,scripts): three 1-site specifiers join
COVERED_SPECIFIERS(objectui#6892 slice 1) #8020 landed, 229 inherit, 0auto-mocked) ·
pnpm check:vi-mock-specifiersexit 0.pnpm type-check:vitest-setupexit 0 — owed because this diff editsvitest.setup.network-escape-guard.ts, which lives in that program and no other.It is build-dependent by construction (
vitest.setup.dom.tsxside-effect-importsfour packages through their
exports.types), so its first reading on the unbuilttree was TS2882 on those four imports — a missing prerequisite, not a red gate, and
it is green on the built tree. CI documents the same ordering at
.github/workflows/ci.yml:623.type-checkandlintforapp-shellviaturbo --concurrency=2 --force: exit 0,Tasks: 32 successful, 32 total,0 eslint errors. Proven non-vacuous rather than assumed:
tsc -p tsconfig.test.json --listFilesnames each of the four edited files exactlyonce (in a 4421-file program), and
eslint --format jsonlists each among the 1087linted files. Warning totals are unchanged (2894) and no warning is mine: each
touched file's warnings sit on pre-existing lines strictly BEFORE this diff's
insertion point, checked hunk range by hunk range (approvalsTarget warning at line
74 vs hunks at 43, 45 and 122+; authoringCapabilityGate 63/83 vs 45, 47, 130+;
inboxLinksTarget 73/85/86 vs 41, 43, 124+; notificationDeepLink 71/82 vs 41, 43,
120+), and the inserted block contributes none.
The repo-wide
eslint . --no-inline-configrun belongs to CI, and CI is not awaitedhere: the report is delivered at draft-PR time per the dispatch contract.
Live E2E (informational)is red on every branch today for the upstream reasontracked in #7990 — not this PR's.
Concurrency
origin/mainmoved from83a9d22ato68912772while this branch was beingverified (#8022, #8020, #8023 landed). It was merged in before this PR was opened —
read with
git ls-remoteand fetched into a private ref rather than moving theshared remote-tracking name — and every reading above was re-taken on the merged
head
6a0c8d8dd, including the one gate whose script the merge changed(
scripts/check-vi-mock-inherit.mjs, widened by #8020 from 226 to 229 judged callsites; the four edited files pass under the widened rule). None of the three merged
commits touches
console/homeor either ledger.Draft PR #7685 touches 17
app-shellpaths, none underconsole/home/, andFlowNodeInspector.specKeys— a batch 5 row deliberately left on the ledger here.The
domain:uiseat is active inpackages/app-shell; this diff touches TEST filesunder
console/home/__tests__/only, and no product source at all.#7996 and #8014 are untouched and stay open: neither concerns this endpoint, and
neither double's shape was copied — this router answers the key the reader actually
reads.
Generated by Claude Code