test(app-shell): serve batch 5's three app-shell probes from doubles (objectui#7307) - #8053
Merged
Merged
Conversation
…(objectui#7307)
Three of the four remaining network-escape rows now answer from recording
doubles instead of a real socket, and their lines leave both KNOWN_ESCAPES
and PINNED_LEDGER in lockstep (4 -> 1 on each side).
Traced at the guard's attribution point rather than guessed:
- FlowNodeInspector.inactiveRetained -> FlowReferenceField.tsx:389 ->
MetadataClient.list -> GET /api/v1/meta/object, answered as an empty
`{ type, items: [] }` registry;
- StudioDesignSurface.designerRegistryMissing -> StudioDesignSurface.tsx:3797
-> GET /api/v1/automation/_status, answered as an empty
`{ data: { flows: [] } }` roster;
- studioSurfaceContext -> useChatConversation.ts:609 ->
POST /api/v1/ai/conversations, answered as one empty conversation, plus
the GET resume route the hook's localStorage cache makes cases 2-4 take.
Each double is a router, not a sink: it records every URL and its afterEach
fails on any URL outside the routes it serves; cleanup() runs before
vi.unstubAllGlobals() (objectui#7439 ordering).
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 on Generated by Claude Code |
This was referenced Sep 6, 2026
baozhoutao
marked this pull request as ready for review
September 6, 2026 13:28
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 5 of the network-escape burn-down (batch 1 was #7999, batch 2 #8013, batch 3 #8019, batch 4 #8032).
The three takeable
app-shellrows now serve their probes from recordingdoubles, and their lines leave
KNOWN_ESCAPESandPINNED_LEDGERin the samecommit. Unlike batch 4, these three are three different endpoint families with
three different readers, so each gets its own route table and its own reasoning
about what answer keeps its assertions' meaning.
Session for this run, as a code span so it survives a body edit:
https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr.Per file
src/views/metadata-admin/inspectors/FlowNodeInspector.inactiveRetained.test.tsxGET /api/v1/meta/objectFlowReferenceField.tsx:389→MetadataClient.list(metadata-client.ts:764) → the authenticated wrapper's bareawait fetch(...)(packages/auth/src/createAuthenticatedFetch.ts:144)installMetaObjectDouble()in a module-scopebeforeEach{ type, items: [] }src/views/studio-design/StudioDesignSurface.designerRegistryMissing.test.tsxGET /api/v1/automation/_statusStudioDesignSurface.tsx:3797, a bare globalfetchin a mount effect, noapiFetchseaminstallStatusDouble()in a module-scopebeforeEach{ data: { flows: [] } }src/views/studio-design/__tests__/studioSurfaceContext.test.tsxPOST /api/v1/ai/conversationsandGET /api/v1/ai/conversations/THE_MINTED_IDuseChatConversation.ts:609(createConversation) from the resolve effect at:828/:864; the resume leg isfetchConversationat:599installConversationsDouble()in a module-scopebeforeEachServerConversation({ id, messages: [] }), minted once and resumed thereafterMechanism, measured rather than assumed. A trap-guarded stack probe injected
at the guard's own attribution point (a
new Error('probe').stackbesidepending.push(escape)), restored by blob hash afterwards, attributed every escapein all three files. Its readings, against the dispatch's un-traced guesses:
/api/v1/meta/object— the dispatch offeredAiChatPage.tsx:1528,ExcelImportBar.tsx:79,AppSidebar.tsx:253orMetadataService.tsascandidates. None of them. The inspector renders
FlowReferenceFieldforevery reference-kind key on the selected node, and that field resolves combobox
options through
useMetadataListOptions→MetadataClient.list(type). Thefile's own
vi.mockofuseObjectFieldsdoes not cover it./api/v1/automation/_status— the dispatch had located no reader at all andsuggested it might live in another package. It is in
app-shellafter all,inside
StudioDesignSurface.tsxitself: the effect that turns per-flow runtimestate into the rail's status dots.
/api/v1/ai/conversations— the dispatch namedhooks/useConversationList.ts. Falsified. The reader here ishooks/useChatConversation.ts, and it is not a list read at all: it is theresolve effect MINTING a thread with a
POST. That difference is what producedthe second route below.
Escaped URLs were exactly one per family, with no second endpoint before the
doubles went in. The probe was reverted with
git checkout HEAD -- path; theguard is byte-identical to
HEADafterwards (blob back to4205b9ff,git diff HEADempty).The doubles are batch 1/2/3/4's shape verbatim:
vi.stubGlobal('fetch', router)in a module-scopebeforeEach,cleanup()beforevi.unstubAllGlobals()inafterEach(the #7439 ordering). Each is a router,not a sink — it records every URL it is handed and its
afterEachfails on anyURL outside the routes it serves. All three carry a
headers: new Headers()onthe answer, which is part of the answer rather than decoration for file 1: the
authenticated wrapper reads
response.headers.get('set-auth-token')on every APIcall before the caller ever sees the body.
In files 1 and 2 the new
afterEachreplaces the bareafterEach(cleanup)that used to stand there —
cleanup()is still called, from inside it, ahead ofthe unstub. That is stated in the comment at each site so no reader reads it as a
dropped teardown.
Why each answer keeps every assertion's meaning:
meta/object— empty. The failing request landed inuseMetadataListOptions's.catch, which sets{ options: [], loading: false }.An empty registry produces the identical state through
.then. A seeded onewould put options into every reference combobox in the tree. Nothing in the file
asserts anything about reference options: the 11 cases assert the
inactive-retainednotice, its wording, itsdata-inactive-retainedattribute,the clear button, and that
onPatchis not called on render.automation/_status— empty. The effect turns each returned row into astatus DOT on the flow rail; the failing request left
flowStatusat{}withno dots. A seeded roster would have added a dot for
nightlyto the veryAutomations tableau the file pins. The four cases assert registry emptiness with
a must-hit control, plus the "no designers are registered" copy and the absence
of the retired "click a node" instructions.
ai/conversations— one empty thread, and TWO routes. This is the batch'sone real surprise, and it was measured, not reasoned: serving only the mint made
this file's OWN router assertion go red naming
/api/v1/ai/conversations/conv_studio_copilot. A mint that SUCCEEDS isresumable,
useChatConversationcaches the minted id inlocalStorage, andhappy-dom keeps that store for the whole file — so case 1 mints and cases 2 to 4
resume. Both routes answer the same empty
ServerConversation, so the fakeserver is self-consistent: one thread, minted once, resumed thereafter. Empty
rather than seeded because a seeded thread would hydrate
initialMessagesintothe capture stub for no assertion's benefit. The file asserts exactly one thing
per case — the
surfaceContextprop the pane receives, derived from the URLalone —
ChatPaneis a capture stub, and the resolve settles in a microtaskAFTER each synchronous case body has already read
capturedProps.No file's assertions depend on the request FAILING, so there is no fork to report.
Nothing is skipped, quarantined or silenced; the real request stayed the evidence
until the double replaced it.
Ledger arithmetic
4 to 1, in both lists. The three 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 onecommit. Checked in lockstep by diffing the quoted paths of the two literals
against each other on the final head: empty diff, both at 1. The pin's
non-vacuity floor is
lengthgreater than 0 andKNOWN_ESCAPES.sizegreater than0, so 1 clears both.
The one remaining row is
packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.specKeys.test.tsx(
/api/v1/meta/object). It is deliberately untouched: draft PR #7685 holds thatfile and is still open (re-checked on this run —
open,draft: true, untouchedsince 05:26Z). It is a one-file batch 6.
No prose count moved. Every "21" in the two ledger files is a provenance claim
about the original sweep on
67dadd6, not a live count — re-checked line by line,the same reading batches 3 and 4 recorded.
Evidence
Per file, before then after. "Before" for files 2 and 3 is read off the
stack-probe run, which only ADDS stderr lines and cannot change the escape or
socket counts; file 1 also has a clean pre-probe baseline that agrees (1 / 3).
ECONNREFUSEDlines)FlowNodeInspector.inactiveRetainedTests 11 passed (11)StudioDesignSurface.designerRegistryMissingTests 4 passed (4)studioSurfaceContextTests 4 passed (4)Test counts are unchanged in every file — the doubles add no cases and remove
none.
The dispatch-named scope on the final head
e56f739d3:pnpm exec vitest run packages/app-shell/src/views/metadata-admin/inspectors/ packages/app-shell/src/views/studio-design/ scripts/__tests__/network-escape-ledger.test.ts— exit 0,
Test Files 120 passed (120),Tests 1083 passed | 1 skipped (1084),316s. It is not zero-escape, and that is expected and stated in the dispatch:
its 1 remaining attribution line names
FlowNodeInspector.specKeys.test.tsx, therow still on the ledger, and its 1
ECONNREFUSEDline is that same escape. Zerolines in the run name any of this batch's three files.
The whole-package
app-shellsuite is declared as CI's rather than run here: ittook 918s for batch 4 and would exceed this container's foreground cap under
concurrent load. The scope above is the dispatch-named one and covers every file
this diff touches plus the pin.
Ablation
Run on the final head against
FlowNodeInspector.inactiveRetained(3describeblocks, 11 cases — the most of the three), with its
KNOWN_ESCAPESandPINNED_LEDGERlines still deleted:installMetaObjectDoubleoccurrences2 to 0, bare
afterEach(cleanup);0 to 1, blob86b5c8f8toad794f14(a hash comparison, not an editor exit code);
Tests 1 failed | 10 passed (11), and the guard's red namesthe file, the socket and the case:
Network escape: this test reached a REAL socket at http://localhost:3000/api/v1/meta/object,file: packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.inactiveRetained.test.tsx,test: the affordance appears exactly when a value is hidden-but-stored > does NOT appear on a node with no gated fields at all;git checkout HEAD -- pathfrom inside the script'strap ... EXIT INT TERM: blob back to86b5c8f8,git diff HEADempty,git status --porcelainempty.The direction is the plain one — red — and it is the direction that matters here:
it proves the three deletions are backed by real doubles rather than by luck.
No dist preflight leg was needed, re-confirmed on the first run as the
dispatch asked: the ablation edits the test file itself, which Vite transforms
directly, so there is no
dist/between the mutation and the assertion.Gates
All on the final head
e56f739d3, exit codes captured by redirect-then-capture.vitestscopenode scripts/check-changeset-presence.mjspnpm check:control-bytesgrep -naPcontrol-byte self-scan over all 6 changed pathsnode scripts/check-governed-queue-guard.mjs --test(all 6 paths)pnpm type-check:scriptspnpm type-check:vitest-setupvitest.setup.dom.tsx, none about this diffpnpm check:vi-mock-inheritpnpm check:vi-mock-specifiersturbo run type-check lint --concurrency=2 --force --filter @object-ui/app-shellNon-vacuity, both proven rather than asserted:
tsc -p packages/app-shell/tsconfig.test.json --listFiles— 4421 files in theprogram, and each of the three edited test files appears exactly once.
eslint . --format jsoninpackages/app-shell— 1087 files reported, each ofthe three edited files present, 0 errors repo-package-wide. The two warnings
on
FlowNodeInspector.inactiveRetainedare pre-existing@typescript-eslint/no-explicit-anyhits on the file's ownas anycasts, at lines 104 and 144 before this change and 177 and 217 after —the same two, shifted by the 73 lines added above them.
Live E2E (informational)is red on every branch today for an upstream reason(#7990 / objectstack#16186) — not this PR's.
Concurrency
Branched from
8d40c18a7.mainmoved to58cd01a38mid-run (#8034, #8036,#8038, #8040), so
origin/mainwas merged in and every gate above was re-run onthe merged head
e56f739d3— the numbers quoted are all post-merge. Theincoming diff is disjoint from this one: it touches
plugin-charts,packages/types/README.md, twocontent/docspages,scripts/check-doc-snippet-types.mjsand
packages/app-shell/src/__tests__/widget-dom-leak-sweep.test.tsx, none ofwhich this batch reads or writes. Draft PR #7685 still holds
FlowNodeInspector.specKeys.test.tsx, which is why that row is excluded here.No labels, assignee or PR state were written by this run; the identity is the
branch named in the batch-5 claim comment.
Generated by Claude Code