docs(plugin-tree): compile the README's snippet against the authorable surface - #8251
Conversation
…e surface
The page's only collected fence was a bare `object-view` literal carrying
`views: [{ type: 'tree', tree: { ... } }]`. It never parsed (TS1005 x2 +
TS1109), so the ledger row was the whole of what this gate knew about the
page, and a genuine type error in that literal left the row byte-identical.
Two facts made the obvious repair wrong. `ObjectViewSchema` declares no
`views` member at all, so annotating the literal with it compiles green
while checking nothing that matters -- measured: `views: 'not-even-an-array'`
is accepted, only `objectName` turns red. And `tree` is a host-composition
view type, not an authorable one: objectui#5321 ruled it recorded rather
than declared, and ObjectView.tsx states verbatim that its config surface is
"not authoring surface to teach in the docs". The example taught, as
authored metadata, the one shape that ruling forbids teaching.
So the example now writes the `object-tree` node an author can actually
select, bound through the exported `ObjectQLComponentSchema` union, which
narrows on `type` and checks all five declared members. The host-only `tree`
alias keeps its paragraph, as prose rather than as a copyable example.
The four documented defaults were each checked against ObjectTree.tsx and
were already correct; they are unchanged.
Part of #5174 (batch 33)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
✅ 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
|
|
Armed —
Landing stroke follows the merge: content probe on re-fetched Generated by Claude Code |
|
LANDED — Merged at 2026-09-07T05:36:49Z as Batch 34 ( Generated by Claude Code |
Part of #5174 (batch 33:
packages/plugin-tree/README.md)Burns down the page's
UNGATED_DOCSentry so the gate compiles its snippet instead of ledgering it. Base738a20949(carries PR #8249, batch 32). Headef6bf35f1.The short version: the obvious repair was the wrong one, and measuring said so. The page's example taught, as authored metadata, the one shape a maintainer ruling says must not be taught in docs — and the annotation that would have made it compile checks none of it.
Census reading 1 — the ledger-literal reading (base
738a20949)Method: the gate's own exported
scanFences/analyze/compileSnippets, with this page removed fromUNGATED_DOCS, against the built--build-filterclosure (35/35 turbo tasks successful). No hand-written regex.tsparse=1 bound=0 semanticFailures=0. Thesemantic=0is not measured — the block never reached the semantic phase.Ledger row accuracy, checked term by term: the count (
3 parse diagnostic(s)) is right, the language (fenced \ts`) is right, andbare object literalsis the right shape. One wording correction only: the row saysblocks` plural where there is exactly one block.Negative control
A genuine type error injected into the base page's bare literal, compiled in memory through the same
compileSnippets; nothing written to disk. The mutation is length-preserving on purpose (objectName: 'business_unit'toobjectName: 424242424242424, 15 characters for 15) — a shorter replacement shifts every downstream diagnostic offset by its own length delta and breaks byte-identity for a reason that has nothing to do with the finding.parse=1 bound=0 semantic=0/ TS1005@71 TS1005@97 TS1109@455parse=1 bound=0 semantic=0/ TS1005@71 TS1005@97 TS1109@455Byte-identical: true. The ledger row was the same with and without a real type error on a declared member. The blind spot is measured, not argued.
Census reading 2 — after the minimum parse repair, and why it decided the batch
The minimum change that makes the block parseable is a typed binding around the bare literal. Bound to
ObjectViewSchema, as the shape of the example suggests:parse=0 bound=0 semantic=0— zero diagnostics.That green is the finding.
ObjectViewSchema(packages/types/src/objectql.ts:1773) declares noviewsmember at all — it carrieslistViews?: Record<string, NamedListView>anddefaultViewType, and nothing else in that neighbourhood.viewstherefore lands onBaseSchema's index signature (packages/types/src/base.ts:467,[key: string]: any) and is checked by nothing. Measured directly against that annotation:views[0].type= a nonsense stringviews[0].type=42views='not-even-an-array'objectName= a number (a declared member)Binding to
ObjectViewSchemawould have produced a green fence, deleted the ledger row, and verified nothing about the substance of the example. That is the tolerant shape this burn-down exists to remove, so it was rejected.The binding decision
Two facts settled it.
1.
viewsis not authored metadata; it is a host prop. Theviewsthe example writes isObjectViewProps.views(packages/plugin-view/src/ObjectView.tsx:303), a React prop whose entries requireidandlabel— the example has neither — and whosetypeisViewType.2.
treeis a host-composition view type, and the docs are named in the ruling.packages/plugin-view/src/ObjectView.tsx:523-583records the maintainer's 2026-08-20 verdict on objectui#5321 (verbatim in-tree: 「其他接受你的建议。」):treeandchartare recorded as host-composition-only surfaces rather than added to the authored unions. NeitherObjectViewSchema.defaultViewTypenorNamedListView.typespellstree— both stop atmap. The comment states the docs consequence twice, verbatim::527-529— "not to be taught as authorabledefaultViewType/NamedListView.typevalues anywhere in the docs (objectui#5321)":1494-1496, on thetreebranch ofgenerateViewSchema— "TheviewOptions.tree.*surface below is therefore HOST config — maintained, read, and not authoring surface to teach in the docs."The page's Usage example was exactly that forbidden shape, written as a copyable authored document.
What replaced it. The authorable node is
object-tree, declared byObjectTreeSchema(packages/types/src/objectql.ts:2358) with its four config keys flat on the node — the same shapegenerateViewSchema's tree branch itself emits (:1497-1508) and the flat limbgetTreeConfigreads. It is legal at the parse door too: the zod mirror carries it (packages/types/src/zod/objectql.zod.ts:747-754,type: z.literal('object-tree'), in the union at:1115), and both faces declare the identical five members.The one obstacle, and a finding.
ObjectTreeSchemais not exported from the package barrel — annotating with it givesTS2724: '"@object-ui/types"' has no exported member named 'ObjectTreeSchema'. It is absent frompackages/types/src/index.tsand from the builtdist/index.d.ts, while its siblingsObjectMapSchema,ObjectGanttSchema,ObjectGridSchemaandObjectViewSchemaare all exported (index.ts:421,423,434,438), and its own zod twin is exported. Adding that export is apackages/types/src/**change and out of this batch's surface, so it is a proposal below, not an edit here.So the example binds through
ObjectQLComponentSchema— exported (index.ts:442, present indist/index.d.ts), and a discriminated union that narrows ontype: 'object-tree'. That is a real check, not a phantom one:ObjectQLComponentSchemaobjectName= numberparentField=42labelField=42fields= a stringdefaultExpandedDepth='deep'No marker, no widened type, no invented local interface, no
src/**edit.Per-key table (key-surface bound, objectui#7927)
ObjectTreeSchema extends BaseSchemaandBaseSchemacarries[key: string]: any, so the annotation checks member types and never keys. Classified by reading the declaration and the renderer's read sites.Keys the repaired example writes:
typeobjectql.ts:2359'object-tree'plugin-tree/src/index.tsx:34objectNameobjectql.ts:2361stringObjectTree.tsxrecord fetchparentFieldobjectql.ts:2366string?ObjectTree.tsx:117labelFieldobjectql.ts:2368string?ObjectTree.tsx:118-119fieldsobjectql.ts:2370string[]?ObjectTree.tsx:110-115defaultExpandedDepthobjectql.ts:2374number?ObjectTree.tsx:120,:621Keys the old example wrote, and why each left:
viewsObjectViewSchema— no such member. OnlyObjectViewProps.views(ObjectView.tsx:303), a host prop requiringid+labelViewSwitcher.tsx:164+viaViewSwitcherSchema.views;ObjectView.tsx:638via the proptype: 'tree'ViewTypeonly; in neither authored unionviewsproptreeblockTreeConfig(ObjectTree.tsx:81)ObjectTree.tsx:109,schema.tree || schema.filter?.treeOne undeclared-but-read key found outside the example, reported not acted on:
ObjectTree.tsx:119readsschema.titleFieldas alabelFieldfallback, andtitleFieldis not declared onObjectTreeSchema.Defaults check
Authority:
packages/plugin-tree/src/ObjectTree.tsx. All four documented defaults were already correct; none needed repair, and the table is unchanged.parentFieldtreefield or a lookup/master_detail referencing the same objectdetectParentField:154-183— atreefield whosereferenceis absent or names this object, else the first self-referencinglookup/master_detaillabelFieldname:118-119,... ?? 'name'fields[]:110-115,[]when neither source is an arraydefaultExpandedDepth0= roots onlyinitialExpanded:258-271—depth == nullexpands every node with children;0makesn.depth < 0false at the rootsAssumption A5 confirmed on both halves.
Ledger decision
The page reads zero after the repair, so its
UNGATED_DOCSentry is deleted outright (ruling 6, first branch). No marker, no rewritten remainder. Ledger 5 rows to 4.Census reading 3, on the real ledger at head: 1 collected fence at line 21,
parse=0 bound=0 semanticFailures=0, blind-spot count 0 of 1. The gate's ownsemanticallyJudgedcounter moved 617 to 618 — that increment is the proof the fence reached the semantic phase rather than merely being green.Strictness region
Region from the
Fence scanningbanner to EOF, both measured here:738a209492749d53ae3a8df033a53b8d7a354fa7e22ee2d1a17f6ad0c3d61122e904e084bef6bf35f12749d53ae3a8df033a53b8d7a354fa7e22ee2d1a17f6ad0c3d61122e904e084bIdentical. The only gate-script change is the two-line ledger row deletion.
Positive control
Run against the committed tree, under a
trap ... EXIT INT TERMwith absolute paths. HEAD blob for the page:d71be52b386bb7bb465ec99603d8d83aa8778f04.Leg 1 — a DECLARED member.
objectName: 'business_unit'toobjectName: 42. On-disk mutation proven by marker count 0 to 1; mutated blob25a65befe3b7021d316d862225c6a25a7831e7d3.pnpm check:doc-snippetsEXIT=1, naming the page and the line:Leg 2 — an UNDECLARED key.
invented_key_nobody_reads: trueadded to the same literal. On-disk mutation proven by marker count 0 to 1; mutated blob7be323eeb32373625ef7d5f2fda9223eeaf6d112.pnpm check:doc-snippetsEXIT=0,618 of 618 judged, 0 failed. The gate stays green, and that is the measured statement of the seam rather than a defect: the objectui#7927 key-surface bound means an annotation checks member types and never keys, so this example is protected against a wrong-typed declared member and not against an invented one.Restore proven by STATE, not by exit code:
git hash-objectback tod71be52b386bb7bb465ec99603d8d83aa8778f04, equal to the HEAD blob, andgit diff HEADempty.Gates (exit codes, pinned to head
ef6bf35f1)pnpm check:doc-snippetspnpm check:doc-typespnpm check:readme-exportspnpm check:doc-fencespnpm check:control-bytespnpm check:entry-guardpnpm exec vitest run scripts/__tests__/pnpm exec vitest run packages/plugin-tree/pnpm type-check:scriptspnpm lint:roote2e/live/**andscripts/__tests__/**; 0 in either changed filenode scripts/check-changeset-presence.mjsnode scripts/check-governed-queue-guard.mjs --teston both pathsgrep -naPcontrol-byte self-scan of both changed pathsAssumption A4 confirmed:
check:readme-exportsjudges only self-imports (528 judged) and counts this page's@object-ui/typesimport among its "186 to other packages", so a README importing a type from another package stays green.scripts/__tests__/check-doc-snippet-types.test.tsneeded no edit — as batches 28-32 found, its ledger pin enumerates only the root README row.Proposals for their own cards (not filed here, not fixed here)
Searched first: cards updated since 2026-09-07T04:39Z are only #8250, #6892 (the vi-mock slice-12 series, excluded) and #5174 itself. A targeted issue search for the export gap returned 0 hits, with a control query in the same session returning 30 — so the empty reading is real, not a broken channel.
ObjectTreeSchemais declared but not exported from@object-ui/types. The interface exists (objectql.ts:2358), sits in the exportedObjectQLComponentSchemaunion, and has an exported zod twin — but the barrel never re-exports it, so it is absent fromdist/index.d.tswhile every sibling object-node schema is present. A reader documenting or authoring anobject-treenode cannot name its type; this PR had to route around it through the union.packages/types/src/**, out of this batch's surface.The nested
treeconfig is declared by no exported type.ObjectTree.tsx:109readsschema.tree || schema.filter?.treeagainst the module-local, non-exportedTreeConfig(:81). It is deliberate host config under objectui#5321, so the contract question is whether a host-only surface should carry an exported type at all — worth ruling rather than assuming. Deliberately not repaired with a private local interface here (the objectui#7646 shape).Premise
Valid, with one correction to the brief. The ledger row and its 3-diagnostic count reproduced exactly. Assumption A1 confirmed (bare literal, TS1005 x2 + TS1109, cleared by a typed binding). Assumption A2 falsified:
ObjectViewSchema.viewsdoes not exist as a member, so there was no view-schema union element to follow — that falsification is what redirected the batch. Assumption A3 confirmed (no competing branch on either path). The dispatch's warning against binding toTreeViewSchemawas right, and for a second reason beyond the type mismatch: the claim comment's guess that the nested view isTreeViewSchemais also falsified —tree-viewis an inline-nodes component, unrelated to this path.Generated by Claude Code