Skip to content

fix(types,plugin-tree): derive TreeViewConfig from the spec and drop the key spec 17.4.0 refuses (objectui#8841) - #9052

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-8841-tree-view-config-derive
Sep 10, 2026
Merged

fix(types,plugin-tree): derive TreeViewConfig from the spec and drop the key spec 17.4.0 refuses (objectui#8841)#9052
os-warren merged 1 commit into
mainfrom
claude/issue-8841-tree-view-config-derive

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes #8841

⛔ Draft. Landing is the seat's act — no enqueue, no ready flip.

⚠️ Type arguments below are spelled in wordsOUTER OF INNER, never with angle brackets. GitHub's body sanitizer deletes tag-shaped fragments on save, and backticks and fenced code blocks do not protect them (AGENTS.md, GitHub 会改写你写进 issue/PR 正文的字节, case ①). The measured instance was a .d.ts before/after table whose two generic columns were both eaten and collapsed into the same string, so a table written to show a type change rendered as "nothing changed". The real source spellings are in the diff.

@object-ui/types published TreeViewConfig as a hand-written interface — a copy of the protocol's ListView.tree block under a second name — and the copy declared a fifth key, titleField, that @objectstack/spec@17.4.0 refuses on that block by name. This repo's published face accepted what the contract rejects, so an author who followed @object-ui/types was refused at publish. This aligns the repo to the spec (协议为基准) and derives the type instead of copying it.


⚠️ This executes a verdict raised BEFORE the merge, and the disagreement is not erased

Two reviews at CONTRACT_REVIEW_TIER on the merged head 49f36c3b8f disagreed, and the fix must not quietly overtake that.

verdict seat when reading
5602098291 director seat 12:49Z CHANGES REQUIRED — F1 titleField declared against the ruling's own criterion and refused by spec 17.4.0; F2 the hand copy is a rename of the spec's TreeConfig
5602172326 spec@objectui seat 12:54Z PASS WITH FINDINGS — adopted; carriers cleared 12:55Z; merged 13:13Z

The CHANGES-REQUIRED verdict named exactly this defect, at the exact file and key, and it shipped anyway. Both findings it raised are executed here — F1 as the key removal, F2 as the derivation. The card's acceptance (5602180375) had already recorded on #8253 that "titleField was declared on read-side evidence, and the console does not write it"; that record is what this PR settles.

The PASS verdict was not wrong about what it measured. Its findings 1 and 3 are accurate and were re-derived here: the ruling's cited read is off the flattened node, not the block; and the readers census was satisfied by text rather than by a block read. Where it went wrong is one step up — it upheld a declaration on read-side evidence when the ruling had asked a write-side question, and the reads it upheld it on turn out to be untyped tolerance rather than contract.


⚠️ Premise correction — the card's "pinned 17.3.0" is stale, and it changes the fix

The card says the pinned spec is 17.3.0 with .passthrough(), "so the divergence is invisible to today's parity gate and becomes a refusal on the next spec bump", and it offers a Pick interim until the 17.4.0 bump.

Measured on my own base (681d3f10e): pnpm-lock.yaml already pins @objectstack/spec@17.4.0, and there is no 17.3.0 left in it (grep -c 'spec@17.3.0' pnpm-lock.yaml → 0). It landed at 093af32e2 chore(deps): take the 17.4.0 @objectstack/* line (#8783), 2026-09-09T17:29:57Z — 4h16m after PR #8828 merged (13:13Z) and 3h40m after the card was filed (13:49Z). git merge-base --is-ancestor 093af32e2 681d3f10e → exit 0; control leg (the reverse direction) → exit 1; the clone is not shallow.

Two consequences:

  1. The divergence is live, not latent. The published @object-ui/types face has been accepting a key the installed protocol refuses since that bump.
  2. The Pick interim is not needed and would be wrong. Its own precondition ("until the 17.4.0 bump") is already satisfied, and a Pick restates the key list — which is what let titleField through in the first place. This PR takes the plain derivation.

No dependency is bumped here. Nothing needed bumping; pnpm-lock.yaml and every package.json are untouched, so PR #9032's hold is not contested.


The two divergences, and which of them I fixed

Both — but they are fixed differently, and the difference is the point.

1. The key (titleField) — removed. It is gone from the type, and getTreeConfig's ?? schema.titleField rung is gone with it.

2. The copy — retired. The NAME is kept, deliberately. In packages/types/src/views.ts, TreeViewConfig is now a one-line alias — export type TreeViewConfig = NonNullable OF SpecListView['tree'].

The divergence that can drift is the hand copy, not the alias: once the declaration IS the spec's symbol, the next key the protocol adds, renames or retypes arrives here without an edit, and a key the protocol never declared cannot be added here at all. Renaming the published name back to TreeConfig would be a separate breaking removal from the barrel with no drift left to prevent — so per the card's ⛔ I did the consumer census rather than the rename:

  • 9 files reference TreeViewConfig across 4 packages (types, plugin-tree, plugin-view, app-shell); it is on the published barrel at packages/types/src/index.ts.
  • @object-ui/types exports no TreeConfig today, so the name is free — the collision argument does not decide it either way.
  • Recorded for the seat, not acted on. Renaming is a published-surface removal and a maintainer's call.

The reader census — measured on this base, before the rung was removed

titleField reads that touch the tree block or node, and what each is coupled to:

# site expression static type of the base compile-coupled to TreeViewConfig?
1 packages/plugin-tree/src/ObjectTree.tsx getTreeConfig schema.titleField schema: any — and it reads the flattened NODE, not the block (nested.titleField: 0 hits) no
2 packages/plugin-view/src/ObjectView.tsx 'tree' branch viewOptions.tree?.titleField viewOptions resolves through NamedListView['options'], which is Record OF string TO any no
3 packages/plugin-list/src/ListView.tsx 'tree' branch treeCfg.titleField treeCfg = (schema as any).tree || … no
4 packages/app-shell/src/views/ObjectView.tsx console composition (viewDef.tree as TreeViewConfig | undefined)?.titleField explicitly cast to TreeViewConfig YES

Exactly one of the four is compile-coupled. Proven by ablation, not by reading — restoring the cast on the committed tree:

src/views/ObjectView.tsx(2653,139): error TS2339: Property 'titleField' does not exist on type
  '{ parentField?: string | undefined; labelField?: string | undefined; fields?: string[] | undefined; defaultExpandedDepth?: number | undefined; }'.

(The error text is itself a second reading of the derivation: the resolved shape is exactly the protocol's four keys, no index signature.) Restore verified by blob equality against HEAD and git diff HEAD empty.

My measurement agrees with the card's guidance, and adds a reason the card did not have:

  • Drop the rung (Implement visual designer for Object UI schemas #1). Three findings back it. It reads the node, so it was never a ListView.tree read. titleField is declared on neither face — not on ObjectTreeSchema (packages/types/src/objectql.ts, which declares exactly parentField, labelField, fields, defaultExpandedDepth), not on its zod mirror (zod/objectql.zod.ts, same four), and not on the protocol's block. And it was unreachable from both in-repo producers of an object-tree node — plugin-view's and plugin-list's 'tree' branches each compute labelField: … || … || 'name' before the node is built, so the ?? chain never fell through to it. No test pinned its behaviour.
  • Keep the three dual-reads (rows 2–4), undeclared. They read through any, so none of them had to change; keeping them means a view record already storing tree.titleField resolves exactly as before and objectui#6557's pin stays green (it does — see below). The console's canonical rung stays annotated TreeViewConfig; ⛔ its legacy rung is deliberately not re-cast to a local { titleField?: string }, because that would fossilise a renderer-side alias into a second contract — AGENTS.md #0.1, the defect this PR undoes. Retiring the three is a follow-up.

The console criterion, re-measured by content (anchors had drifted)

The ruling's test is does the console write it. The card cites CreateViewDialog.tsx:290-298; on this base the tree slot is at :289-300 — the anchors moved, the content did not.

  • packages/app-shell/src/views/CreateViewDialog.tsx tree: [ … ] collects parentField alone.
  • FIRING CONTROL, same file, same instrument: titleField appears 3× — at :202 (calendar), :217 (timeline), :239 (gantt). So the zero for the tree slot is a reading about the tree slot, not about a blind instrument.
  • Composition is generic (subConfig[rf.key] = v), so nothing else writes it either.

The console does not write tree.titleField. The ruling's own answer was remove, and that is what this does.


Measurements, each with a firing control

1. spec 17.4.0 — strictness and key set (installed artifact, node_modules/@objectstack/spec, version read from its own package.json = 17.4.0):

declared key set = ["parentField","labelField","fields","defaultExpandedDepth"]
  [FIRING CONTROL {parentField:"parent_id"}]  ACCEPTED -> {"parentField":"parent_id"}
  [FIRING CONTROL all four declared]          ACCEPTED
  [SUBJECT {titleField:"name"}]               REFUSED  -> unrecognized_keys ["titleField"]
      "Unrecognized key(s) on this tree configuration: `titleField`."

Through the real host path (ListViewSchema, with a valid columns so the control is green):

[FIRING CONTROL tree:{parentField}]        ACCEPTED
[FIRING CONTROL tree: all four declared]   ACCEPTED
[SUBJECT tree:{titleField}]                REFUSED -> unrecognized_keys path ["tree"] keys ["titleField"]
[SUBJECT tree:{labelField,titleField}]     REFUSED -> unrecognized_keys path ["tree"] keys ["titleField"]

By content, the same artifact: TreeConfigSchema = strictObject({…}, {parentField, labelField, fields, defaultExpandedDepth}), no .passthrough(); typed as z.ZodObject parameterised by the four-key shape and z.core.$strict.

2. spec 17.3.0 — the .passthrough() claim (npm tarball objectstack-spec-17.3.0.tgz, extracted to a scratch dir, ⛔ never into node_modules; version read from the tarball's own package.json = 17.3.0):

17.3.0 declared keys = ["parentField","labelField","fields","defaultExpandedDepth"]
 [FIRING CONTROL {parentField}] ACCEPTED -> {"parentField":"p"}
 [SUBJECT {titleField}]         ACCEPTED -> {"titleField":"name"}

By content: the block ends }).passthrough() at 17.3.0 and }) at 17.4.0. ⇒ Both halves of the card's claim confirmed. ⚠️ Both readings are positive values from a resolved import, not undefined symbol readings, so the uninterpretable-reading hazard the dispatch flagged does not arise here.

3. The re-pinned census actually fails when the drift returns — ablation on the committed tree: mutate views.ts so the alias becomes NonNullable OF SpecListView['tree'] INTERSECTED WITH { titleField?: string } → prove it landed on disk (blob hash differs from HEAD's) → pnpm --filter @object-ui/types buildprove it reached dist/views.d.ts by content → run:

plugin-tree  tsc -p tsconfig.test.json  rc=2  — 4× TS2344 "Type 'false' does not satisfy the constraint 'true'"
                                                (parity, parity-control, titleField-is-gone, all-optional)
plugin-view  tsc -p tsconfig.test.json  rc=2  — TS2578 "Unused '@ts-expect-error' directive"  (self-firing pin fires)
types        tsc -p tsconfig.test.json  rc=2  — 3× TS2344  (census↔type, type↔protocol, parity-control)

Restored, rebuilt, and verified: blob equals HEAD's, git diff HEAD empty, and dist/views.d.ts back to the pure alias by content.


The census pins are re-pinned as PARITY, not as a literal list

A literal key list is what let this through: DeclaredKey and DECLARED were maintained beside the type they were supposed to audit, so when titleField was added the lists were updated to match and the pins agreed with the defect. A census cannot be total over something it also authors.

  • packages/plugin-tree/src/ObjectTree.hostConfigExported-8253.test.ts — the literal DeclaredKey union is gone. In its place: Equal OF (TreeViewConfig, NonNullable OF SpecListView['tree']) — structural equality, not extends, a firing control requiring that comparison to say false for the near-miss that actually shipped, a named titleField-is-gone pin, and an explicit no-index-signature pin — that last one does not fall out of parity (at 17.3.0 both sides would carry [key: string]: unknown and parity would still hold), and it is the line that fires if the installed spec ever drops below 17.4.0.
  • Plus a runtime leg, because a compile-time pin on a derived alias can only restate its own derivation: it reads the installed TreeConfigSchema by content, asserts titleField is absent, and carries two firing controls (a key the protocol does declare is found; the schema accepts what it declares).
  • packages/types/src/__tests__/tree-view-config-readers-8253.test.tsDECLARED still exists (it.each needs a value, and a failure named reads `labelField` beats one named reads keys[1]), but it is no longer authored: it is checked against TreeConfigSchema's own shape in both directions, with a control proving the comparison can fail. The titleField block is rewritten from "stays DECLARED" to "is NOT declared — the reads that survive are tolerance", and it now records the three surviving dual-reads explicitly so the next session does not read "the key is gone" as "every read is gone".
  • packages/plugin-view/src/__tests__/ObjectView.treeViewConfigDeclared-8253.test.ts — the "accepts every declared key" literal drops titleField, and a new @ts-expect-error case pins that the host writing the entry inline is now refused for it. That is the diagnostic that moved: what used to be learned from a parse failure at publish is now learned at compile time.

Documentation

packages/plugin-tree/README.md claimed the exported block "is the single declaration of that shape". It was false when written — ListViewSchema['tree'] already published the same block derived — and it is the claim that made the hand copy look legitimate. Corrected to state that the protocol owns the block and that TreeViewConfig derives from it. The titleField paragraph is replaced with the refusal, the reason, and the migration (write labelField).

Changeset — level justified from this repo's own precedent, by content

@object-ui/types minor. Removing a key from a published exported type breaks a producer that annotates against it, and this repo grades its own breaking changes minor:

  • AGENTS.md: ⛔ never declare major — the fixed group would leave @objectstack's cadence; "objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可)", enforced by scripts/check-changeset-no-major.mjs.
  • packages/types/CHANGELOG.md, the closest precedent by shape — f1d4748 "Remove the retired striped / bordered / virtualScroll list-view surface", a spec-side retirement propagated into this package as a key removal, shipped 17.6.0 → Minor Changes. Siblings in the same section: 97abb24 (Remove BaseFieldMetadata.indexed), 9695da7, d2ce342, 58b8346 — all removals, all minor. The section also states the policy in the body: "BREAKING for hand-written form schemas — deliberately declared minor."

@object-ui/plugin-tree minor (a runtime rung is removed), @object-ui/plugin-view and @object-ui/app-shell patch (type-only / annotation-only). node scripts/check-changeset-presence.mjs → rc 0, and it independently counts 4 released packages changed, matching the 4 entries.


Verification (every rc captured to a file before any pipe)

what command rc
build (types + closure) pnpm --filter '@object-ui/types...' build 0
build (whole workspace) turbo run build --filter='!@object-ui/site' --concurrency=2 0 — 43/43
type-check @object-ui/types / plugin-tree / plugin-view / app-shell 0 / 0 / 0 / 0
type-check (consumers) plugin-list / console / core 0 / 0 / 0
tests packages/types/ — 169 files, 3353 tests 0
tests packages/plugin-tree/ — 14 files, 62 tests 0
tests packages/plugin-view/ — 37 files, 316 tests 0
tests app-shell titleFieldConvergence + relayRungCensus-7559 — 32 tests 0
gate check:spec-symbols 0
gate check:spec-floors 0
gate check:doc-snippets / check:doc-examples / check:readme-exports / check:doc-fences 0 / 0 / 0 / 0
gate check:changeset-presence / check:changeset-no-major 0 / 0
gate check:control-bytes / check:new-line-citations / check:phantom-deps 0 / 0 / 0
lint types / plugin-tree / plugin-view / app-shell 0 errors (277 / 77 / 314 / 2967 warnings, all pre-existing)

⚠️ Two gates first returned NOT MEASURED, not red, and were re-run rather than reported around: check:spec-floors exited 1 with no-artifact for three unbuilt packages, and check:doc-examples exited 2 (its documented "the gate could not run" code) for four unbuilt packages. Both are 0 on the fully built tree; the first readings are not verdicts about this diff.

⭐ objectui#6557's CONTROL: the tree's second view-declared rung (tree.titleField) still answers passes — the tolerant fallback is intact, which is the whole reason it was kept.

验收备注

  • The @objectstack/spec floor packages/types declares is ^17.3.0, while this type's strictness only exists from 17.4.0. check:spec-floors is green because it asks whether the symbols exist at the floor, and TreeConfig does exist at 17.3.0 — it asks nothing about strictness. Under a 17.3.0 install the block regains [key: string]: unknown and the @ts-expect-error pins would report "Unused '@ts-expect-error' directive" — loud, not silent, which is why I left it alone rather than smuggling a floor bump in. It would move pnpm-lock.yaml, which fix(core): raise declared @objectstack/spec floor to ^17.3.0 #9032 holds. Noted for the seat; ⛔ not touched.
  • The three surviving labelField || titleField dual-reads are undeclared tolerance for a key the protocol refuses. Recorded here and in the test file; their retirement is the follow-up the card names.
  • packages/plugin-view/src/ObjectView.tsx's 'tree' branch ends with ...(viewOptions.tree || {}), which spreads whatever the block carries onto the node — so a stored titleField still lands on the object-tree node even though no face declares it there. Out of scope; it is the same tolerance layer as the three reads above.
  • No file on the dispatch's hold list was edited. packages/types/src/zod/objectql.zod.ts and packages/types/src/objectql.ts were read only. packages/app-shell/src/views/ObjectView.tsx is outside my nominal surface but is not held by any of the 13 open PRs (checked by content) and its edit is forced by the type change — see the ablation above.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w


Generated by Claude Code

…titleField

`@object-ui/types` published `TreeViewConfig` as a hand-written interface — a
copy of the protocol's `ListView.tree` block under a second name — and the copy
declared a fifth key, `titleField`, that `@objectstack/spec@17.4.0` REFUSES on
that block by name (`TreeConfigSchema` is a `strictObject` since spec #15469
closed the `.passthrough()` window 17.3.0 left open). This repo's published face
therefore accepted what the contract rejects: an author who followed
`@object-ui/types` was refused at publish.

Both divergences are addressed:

- the KEY — `titleField` is removed from the type, and `getTreeConfig`'s
  `?? schema.titleField` rung goes with it. That rung read the flattened NODE,
  never the block, and `titleField` is declared on neither face; it was also
  unreachable from both in-repo producers of an `object-tree` node, each of
  which floors `labelField` before the node is built.
- the COPY — `TreeViewConfig` is now `NonNullable<SpecListView['tree']>`, a
  derivation rather than a rename of the spec's `TreeConfig`. The NAME is kept
  (a consumer census found nine referencing files and no `TreeConfig` free in
  the barrel); what is retired is the hand copy behind it, which is the half
  that could drift.

The `Pick` interim objectui#8841 offered was conditional on the 17.4.0 bump not
having landed. It had: `chore(deps): take the 17.4.0 @objectstack/* line` moved
the lockfile to 17.4.0 four hours after this card was filed, so the plain alias
is available and no dependency is bumped here.

The three `labelField || titleField` dual-reads (plugin-view, plugin-list,
app-shell) are kept as undeclared tolerant fallbacks so stored view records keep
resolving and objectui#6557's pin stays green; retiring them is a follow-up. The
console's canonical rung stays annotated `TreeViewConfig`; its legacy rung is
deliberately left untyped rather than re-declared.

Both census pins are re-pinned as PARITY WITH THE PROTOCOL rather than as a
literal key list — a literal list is what let this drift through, since it was
maintained alongside the type it was supposed to audit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3490.0 KB 3512.7 KB
Main entry chunk (gzip) 144.2 KB 350 KB
Entry file index-CzfYtAJ8.js
Status PASS

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 500.20KB 114.67KB
core (index.js) 8.28KB 3.31KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 207.56KB 57.44KB
fields (index.js) 247.01KB 62.29KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.50KB 19.97KB
plugin-chatbot (index.js) 195.32KB 46.51KB
plugin-dashboard (index.js) 131.09KB 34.58KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 251.44KB 65.17KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.65KB 40.91KB
plugin-grid (index.js) 211.56KB 57.50KB
plugin-kanban (index.js) 46.03KB 14.30KB
plugin-list (index.js) 112.52KB 27.64KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.54KB 3.31KB
plugin-view (index.js) 84.42KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 83.34KB 27.61KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

Contract review

Head reviewed: 7c1e03efeb6290122a0517e50c4651f15a364f6f (re-read immediately before posting: unmoved; draft; mergeable_state: clean; 1 commit on base 681d3f10e; 9 files, +459/−203). Card objectui#8841, round 1.

Charter read at: objectstack origin/main 2662a084e71c6df748337b0e08d7877d57c6b8bc (fresh sparse clone; a stale scratch copy at cac62d5f was discarded unread). Tier constant: CONTRACT_REVIEW_TIER = 'claude-fable-5-1' read from scripts/pm/dispatch-gates.mjs:10507 at that sha. Serving tier, by transcript: the reviewer's own transcript was located by a unique marker string, not by filename; the harness-stamped per-message model field reads 123/123 claude-fable-5-1, zero other stamps (positive control: the same parser counted 123 assistant / 77 user role tokens, so it was reading a live file). This is a context-isolated review: fed the card, the three prior rulings/verdicts, and the PR itself; the dispatching seat's conclusions were not supplied and were not reconstructed.

Type arguments below are spelled in words — OUTER OF INNER — because this comment channel truncates from the first tag-shaped fragment.

① Derived judgments — the accept-set and public-face changes the diff implies, one by one

  1. Direction: toward the protocol, no overshoot — CORRECT. TreeViewConfig becomes NonNullable OF SpecListView['tree']. In the installed @objectstack/spec@17.4.0 (node_modules/@objectstack/spec/package.json read: 17.4.0), ListView is z.input OF typeof ListViewSchema and TreeConfig is z.input OF typeof TreeConfigSchema (dist/view.zod-*.d.ts:11345, :11418), and ListView.tree is the optional strict four-key object (:1716). So the alias is the protocol's TreeConfig by identity — the accept set of the published face now equals the protocol's, neither wider nor narrower. Being narrower is the forbidden direction; a plain alias cannot be narrower.
  2. titleField is refused by the protocol — CORRECT, measured with controls both ways. 17.4.0 TreeConfigSchema.shape keys = parentField, labelField, fields, defaultExpandedDepth; { parentField } and all-four ACCEPTED, { titleField } REFUSED (unrecognized_keys, keys: ['titleField']), a second undeclared key zzUndeclared also REFUSED; through the host path (ListViewSchema with a valid name/columns fixture) tree: { parentField } ACCEPTED, tree: { titleField } REFUSED at path ['tree']. 17.3.0, installed by npm into an isolated scratch project (never the repo's node_modules; version read from its own manifest): same four declared keys, but { titleField } and { zzUndeclared } both ACCEPTED — the passthrough the card describes. Both halves of the card's spec claim hold.
  3. "The divergence is LIVE, not latent" — CORRECT. On a full-history clone (is-shallow-repository: false): base 681d3f10e's pnpm-lock.yaml has 2 spec@17.4.0 entries and 0 spec@17.3.0; 093af32e2 "chore(deps): take the 17.4.0 @objectstack/* line (chore(deps): take the 17.4.0 @objectstack/* line #8783)" (2026-09-09T17:29:57Z) rewrites exactly the two 17.3.0 package identities to 17.4.0; git merge-base --is-ancestor 093af32e2 681d3f10e → 0, the reverse control → 1, and it is also an ancestor of the head. The feat(types,plugin-view,plugin-tree): export the host tree view config as TreeViewConfig (objectui#8253) #8828 squash landed 12:56:20Z and the card was filed 13:49:20Z — both before the bump. So the card was accurate when filed and stale by the time it was dispatched; the dev's premise_still_valid: false is right.
  4. Refusing the Pick interim — CORRECT. Its stated precondition ("until the 17.4.0 bump") was already satisfied. Independently of that: a Pick re-authors the key list beside the type, which is the audited-by-its-own-author defect that let titleField through, and it freezes the list so that the next key the protocol adds would make objectui narrower than the protocol — the forbidden direction.
  5. Reader census: four reads, exactly one compile-coupled — CORRECT, and the coupling was verified by ablation, not by reading. git grep on the base finds exactly four titleField reads on the tree block/node: plugin-tree/src/ObjectTree.tsx:132 (schema.titleField, off the flattened node — nested.titleField has 0 hits), plugin-view/src/ObjectView.tsx:1545, plugin-list/src/ListView.tsx:2795, app-shell/src/views/ObjectView.tsx:2641 (cast to TreeViewConfig). Leg A1 on the committed tree: restoring the cast on the console rung (blob c5e4322 ≠ HEAD 5c76190) → tsc --noEmit in app-shell rc=2, ObjectView.tsx(2653,139): error TS2339: Property 'titleField' does not exist on type '{ parentField?: string | undefined; labelField?: string | undefined; fields?: string[] | undefined; defaultExpandedDepth?: number | undefined; }' — the error text is itself a reading that the resolved shape is the protocol's four keys with no index signature. Restore proven: blob 5c76190 = HEAD, git diff HEAD empty. At rest app-shell tsc --noEmit rc=0.
  6. Unreachability of the removed rung — CORRECT within its stated scope, and the scope matters. Both in-repo producers floor labelField before the node exists (plugin-view :1558 and plugin-list :2795, each … || … || 'name'), so fieldKey(schema.labelField ?? nested.labelField ?? schema.titleField) never fell through from them. ⚠️ But ObjectTreeSchema in zod/objectql.zod.ts:886 is BaseSchema.extend(...) and BaseSchema is .passthrough() (base.zod.ts:449), so an authored object-tree node carrying an undeclared titleField and no labelField did reach the rung. The PR body's phrasing ("unreachable from both in-repo producers") is precise; the report's "the rung was already dead" is not. The removal stands regardless — titleField is declared on neither the node face (TS objectql.ts:2408-2424 and zod, four keys each) nor the protocol's block — and the changeset already grades plugin-tree as a runtime behaviour change (minor), which is the right grade for exactly this residual path. Non-blocking: prose scope.
  7. The re-pinned census fails on the near-miss that shipped — CORRECT, re-measured in the shipped spelling. The dev's leg B mutated the alias to an intersection; I instead reinstated the base's 44-line five-key interface TreeViewConfig verbatim (blob 0796e57 ≠ HEAD 29824e7), rebuilt @object-ui/types (rc=0), and proved the dist by content (dist/views.d.ts: export interface TreeViewConfig 1, titleField?: string 1, alias 0). Then tsc -p tsconfig.test.json: plugin-tree rc=2 — TS2344 at :111 (parity with the protocol) and :121 (titleField-is-gone); types rc=2 — TS2344 at :113 (census ≡ type) and :120 (type ≡ protocol); plugin-view rc=2 — TS2578 unused @ts-expect-error at :137 (the self-firing refusal pin). Exactly the pins that are about the defect fire and nothing else; the two extra firings in the dev's run (_ParityCanFail, _AllOptional) were artefacts of the intersection spelling, not readings about the key. Restore by blob equality (29824e7), rebuild, dist back to the alias (alias 1, interface 0). At rest all three legs rc=0 (control).
  8. The no-index-signature pin fires on the 17.3.0 shape — CORRECT. Leg A4: alias intersected with { [key: string]: unknown } (blob 81cf3a1, dist marker 1) → plugin-tree rc=2 with TS2344 at :111, :121, :129 (_NoIndexSignature), :133, plus TS2578 at :189; plugin-view TS2578 at :82 and :137; types TS2344 at :113, :120. Restored and rebuilt: blob = HEAD, git status clean, dist index-signature marker 0, all three legs rc=0. The dev's stronger claim — that under a real 17.3.0 install parity would still hold and only this line would fire — follows from the shape but was not measured (see "not measured").
  9. Residual drift surface, sized honestly. Leg A3: a faithful four-key hand-written twin (the copy without the drift) passes every pin — rc=0 in all three packages — and the vitest runtime legs pass under every mutation (28/28 under A2), because they read the protocol's schema, not the alias. So the pins catch drift (a key added, removed, retyped; an index signature), not copying; "gone by construction" holds while the declaration stays the alias, and a future re-copy would pass until its first divergence, when :111/:120 fire. Detection lives in type-check: all four touched packages chain tsc -p tsconfig.test.json into their type-check script, which CI's Type Check job runs. scripts/check-spec-symbol-derivation.mjs rule 1 remains name-based and stays blind to the rename — unchanged by this PR, and the reason the pins are the load-bearing guard. Not a defect of this diff.
  10. Keeping the published name TreeViewConfig while deriving — CORRECT as a contract matter; the rename is a maintainer's call. With the alias, the type is the protocol's TreeConfig; a second published name is a discoverability divergence, not an accept-set divergence. Consumer census re-counted: 9 files across 4 packages (types 3, plugin-tree 3, plugin-view 2, app-shell 1), name on the barrel at types/src/index.ts:887; @object-ui/types exports no TreeConfig. Retiring the name is a barrel removal; re-exporting TreeConfig beside it would be a new exported symbol and its own Clause-② item. Neither belongs on this card.
  11. Console-write criterion, re-derived by content — CORRECT. CreateViewDialog.tsx:290-302 tree: [ … ] collects parentField alone; firing control in the same file: titleField at :202, :217, :239 (calendar / timeline / gantt); composition is generic (subConfig[rf.key] = v, :448). The console does not write tree.titleField; the finding(types,plugin-tree): the per-view tree config ObjectTree reads from a host-composed view (parentField, labelField, fields, defaultExpandedDepth) is declared by no exported type — only the module-local TreeConfig in ObjectTree.tsx #8253 ruling's own answer was remove, and this diff is that removal.
  12. Public-face inventory of the diff. Removed: the titleField member of TreeViewConfig (barrel-exported) and the ?? schema.titleField rung in getTreeConfig. Retyped: TreeViewConfig from interface to alias of the spec's block (identical shape minus the key). Unchanged: the barrel export line, ObjectViewProps.views[n].tree?: TreeViewConfig, ResolvedTreeConfig (ObjectTree.tsx:98-100, Required OF Pick — still compiles), the three tolerant dual-reads (now read through any, undeclared), no package.json/lockfile change, no governed-surface path, content/docs/releases/ untouched. check:spec-symbols rc=0 on the head.

On the two-verdict conflict this card exists to close — my own reading. The CHANGES-REQUIRED record (5602098291) was right on F1 and F2, and its F1 also stated correctly that objectui#6557's pin is on the app-shell fold, so deleting the ObjectTree.tsx rung could not redden it — measured here: titleFieldConvergence + relayRungCensus-7559 = 32/32 pass on the head with the rung gone. The PASS record (5602172326) was accurate on its findings 1 (node-not-block, off-by-one) and 3 (text census) — both re-derived here — but it was not only "right about what it measured and wrong one step up": (a) its justification asserted that removing the read "would redden #6557's pin", which is false for the rung the ruling named; (b) it measured spec 17.4.0 for the name collision (TreeViewConfig absent) and never for the key (titleField refused on TreeConfig), although 17.4.0 had been on npm since 03:57Z that day and the contradicting review cited the refusal by file:line; (c) it then substituted the read axis for the ruling's write axis. The process finding: a contract review that upholds a declared key without a parse-level reading of that key against the protocol's accept set — at the version already on npm and on the sibling's main — is incomplete at any tier, because the protocol is the arbiter; and two contradicting tier verdicts on one head five minutes apart are a real fork that belongs in the decision box, not a clear on one's own subagent's verdict.

② Semver grading against .changeset/8841-tree-view-config-derive.md

  • @object-ui/types minor — consistent. A key removal from a barrel-exported type; the repo forbids major (AGENTS.md:251-252, enforced by scripts/check-changeset-no-major.mjs, rc=0 here) and grades its own breaking changes minor; precedent verified by content: CHANGELOG.md 17.6.0 lists f1d4748 "Remove the retired striped / bordered / virtualScroll list-view surface" and 97abb24 "Remove BaseFieldMetadata.indexed" under Minor Changes. ⚠️ Context the changeset text does not say: @object-ui/types@17.6.0 on npm (latest, 2026-08-24) contains no TreeViewConfig (0 hits in dist/index.d.ts and dist/views.d.ts) and .changeset/8253-export-tree-view-config.md is still pending — so nothing removed here has ever reached an npm consumer; "published" means the barrel on main, and the net release delta is the addition of a derived four-key type. The grade is right either way.
  • @object-ui/plugin-tree minor — consistent: a runtime rung is removed, and per ①.6 it was reachable from an authored node.
  • @object-ui/app-shell patch — consistent: annotation-only on one rung.
  • @object-ui/plugin-view patchfinding, non-blocking. ObjectViewProps.views[n].tree narrows (an inline host writing titleField now gets a compile error — the PR's own new @ts-expect-error case pins exactly that). The same-tier record on feat(types,plugin-view,plugin-tree): export the host tree view config as TreeViewConfig (objectui#8253) #8828 graded a narrowing of this same props face minor ("potentially breaking for an inline host"); by that precedent this is minor. Effect-neutral: the pending 8253 changeset already carries plugin-view: minor for the same release, so the aggregate bump is unchanged. Recommend minor for consistency; not a blocker.
  • node scripts/check-changeset-presence.mjs rc=0 — 7 source files of 4 released packages changed, 1 changeset declared, matching the four entries.
  • ⚠️ Finding, must be corrected before the next @object-ui/types release: the pending sibling changeset .changeset/8253-export-tree-view-config.md:38-45 still reads "titleField is declared, not deleted — and that was a measurement. … Declaring it makes declared = enforced at all four read sites at once." That sentence becomes false the moment this PR merges, and both changesets feed the same 17.7.0 notes. PR docs(changeset): correct two now-false sentences in pending @object-ui/types changesets #9064 ("correct two now-false sentences in pending @object-ui/types changesets") touches 8913-* and 8992-* only, not this one; the feat(types,plugin-view,plugin-tree): export the host tree view config as TreeViewConfig (objectui#8253) #8828 CHANGES-REQUIRED record's F4 asked for precisely this rewrite. Not on any contract face and not a grade mismatch, so not blocking here — but it is release input this PR falsifies. Route for the seat: a one-paragraph rider on this PR before landing, or fold it into docs(changeset): correct two now-false sentences in pending @object-ui/types changesets #9064 (same class, same package, open). Escalated, not silently absorbed.

③ Boundary flags — every dev flag, open_questions entry and out_of_scope_findings entry

  • Flag: premise_still_valid: false (17.3.0 pin stale). Answered — verified in ①.3 with an ancestry control; the corrected premise improves the fix (①.4).
  • Flag: the verdict conflict is not "one right, one wrong". Answered — partly agreed, partly not; see the conflict paragraph in ①. The PASS record carried one false claim and one missing measurement, not only a wrong conclusion.
  • Flag: NOT re-casting the console's legacy rung to a local { titleField?: string }. Answered — correct: that would re-declare the refused key locally (AGENTS.md #0.1); the asymmetric line (canonical rung typed, tolerant rung untyped) is pinned by name in tree-view-config-readers-8253.test.ts and measured by leg A1.
  • Flag: two gates first returned NOT MEASURED (check:spec-floors no-artifact, check:doc-examples rc 2) and were re-run on the built tree. Answered — correct handling; not re-run here (see "not measured").
  • Flag: one CI failure in plugin-grid, passed on re-run. Answered — corroborated at job level: workflow CI run 34528838232 is at run_attempt 2; attempt 1 had exactly one non-green job, Test (shard 2/4), failing step Run tests (shard 2/4); attempt 2: 8 success + 2 skipped. plugin-grid is outside the change set. The failing test's identity was not read by this reviewer (platform artifact per the brief; not a finding).
  • open_questions Implement visual designer for Object UI schemas #1 — label conflict, needs:contract-review on the PR (A) vs leave unlabelled (B). Resolved by the seat since the report: the PR timeline shows needs:contract-review added by os-warren at 2026-09-10T21:29:35Z; the card has carried it since 19:52:54Z. Both carriers now hung — option A executed. The dev's note that scripts/check-clause2-carriers.mjs does not exist in objectui is confirmed (objectui's scripts/ has check-changeset-* and check-spec-symbol-derivation.mjs only); objectstack's copy judges its own board. This record is the Clause-② review both carriers were waiting for.
  • open_questions Add automated testing infrastructure and CI/CD workflows #2packages/types declares @objectstack/spec ^17.3.0 while the strictness the alias depends on exists from 17.4.0 (A leave / B raise floor in a follow-up / C teach check:spec-floors strictness). Answered and already moving: open PR fix(types): chain the spec's objectNavTargetExclusivity in NavigationItemSchema (objectui#8563) #9060 (draft, needs:contract-review) raises exactly packages/types/package.json "@objectstack/spec": "^17.3.0""^17.4.0" with the lockfile importer line — option B is in flight on another card. Nothing to do on this PR; the dev was right not to bump. The failure mode under a 17.3.0 install is loud (_NoIndexSignature at plugin-tree :129 and the @ts-expect-error pins), consistent with leg A4. C is a gate-design question for whoever owns check-spec-range-floors.mjs; not escalated from here.
  • open_questions Add public roadmap, VitePress documentation site, and GitHub Pages deployment #3 — keep the name TreeViewConfig (A) / re-export TreeConfig and retire it (B) / both for one release (C). Answered in ①.10: A is correct on the contract; B and C are published-surface changes (a removal, or a new exported symbol) that are the maintainer's call and would each be their own Clause-② item. Escalate to the seat as a decision-box entry only if the maintainer wants the name settled; no action on this card.
  • out_of_scope_findings Implement visual designer for Object UI schemas #1...(viewOptions.tree || {}) spreads the whole block onto the node, so a stored titleField still lands on the node face. Answered — confirmed at plugin-view/src/ObjectView.tsx:1562 and plugin-list/src/ListView.tsx:2798; same tolerance layer as the dual-reads; belongs to the follow-up that retires them. Not filed; the card scopes it.
  • out_of_scope_findings Add automated testing infrastructure and CI/CD workflows #2 — the three surviving labelField || titleField dual-reads. Answered — recorded in the PR body, the views.ts docblock and a named test case; the follow-up the card names. Not a rider here — correct.
  • out_of_scope_findings Add public roadmap, VitePress documentation site, and GitHub Pages deployment #3check:spec-floors judges symbol presence, not strictness. Answered — true by the gate's own scope; superseded in effect by fix(types): chain the spec's objectNavTargetExclusivity in NavigationItemSchema (objectui#8563) #9060 raising the floor.
  • out_of_scope_findings Add default props to all components to prevent collapse in designer #4 — the plugin-grid flake, not filed on one occurrence. Answered — correct not to file on a single un-reproduced run; see the CI flag above.
  • out_of_scope_findings Implement component reordering via drag-and-drop in designer canvas #5 — the PASS verdict was accurate on findings 1 and 3. Answered — agreed on 1 and 3; disagreed on the shape, see ①.

Independence pair (machine-readable)

  • Implemented-by: branch claude/issue-8841-tree-view-config-derive — a mode:subagent os-dev dispatched by the domain:spec seat session_01Jmxdo7bmeqCQHLSfmLVX9w (newest Claim: 5624572054, Clause-②: yes); commit author os-warren. The commit's Co-Authored-By: Claude Opus 5 trailer is a signature constant and is not evidence of the dev's tier.
  • Reviewed-by: a context-isolated claude-fable-5-1 subagent raised by the domain:spec seat session_01Jmxdo7bmeqCQHLSfmLVX9w, transcript-verified as above.
  • Same-session test, applied literally to the charter's text: the charter records a mode:subagent dev by its branch because a subagent has no session of its own, and the ruling seat by session id; SELF-REVIEW is reported when "the two are the same session". The Implemented-by line carries a branch and the Reviewed-by line a session id — they are not the same session, so the SELF-REVIEW rule does not fire. Stated plainly so the pair is not over-read: the dev and this reviewer are both children of the same seat session; the independence the charter supplies for this configuration is context isolation plus the ceiling tier (only the card, the prior rulings and the PR were fed; no dispatch order, no seat conclusions), not session separation. The "distinct sessions ⇒ not a self-review" wording in the feat(types,plugin-view,plugin-tree): export the host tree view config as TreeViewConfig (objectui#8253) #8828 records claimed more than this pair shows.

Verification and platform readings

  • CI on the head: 35 check-runs, total_count 35 = returned array length 35; 32 success + 3 skipped (Test (coverage shard …), Test (coverage), dependabot); 0 failure, 0 pending; combined status success (Vercel). All checks, not a required subset.
  • Reviewer's own runs on a fresh full-history clone at the head (git status clean at start and end; pnpm install --frozen-lockfile rc=0; closure build of app-shell/plugin-tree/plugin-view/plugin-list: 30/30 turbo tasks rc=0): the three re-pinned test files 28/28 (rc=0); the full plugin-tree suite 14 files / 62 tests (rc=0); app-shell titleFieldConvergence + relayRungCensus-7559 32/32 (rc=0); tsc -p tsconfig.test.json rc=0 in types / plugin-tree / plugin-view and tsc --noEmit rc=0 in app-shell at rest; check:spec-symbols rc=0; check-changeset-presence rc=0; check-changeset-no-major rc=0. Every rc captured to a file before any pipe; every mutation proven on disk by blob hash and, where a build stood between mutation and reading, in dist by content; every restore proven by blob equality and an empty git diff HEAD.
  • Not measured by this reviewer: the whole-repo pnpm test, the full plugin-view / app-shell / types suites, lint, the whole-workspace build, check:spec-floors and the doc gates, plugin-list / console / core type-checks, the identity of the attempt-1 failing test, a 17.3.0 install of the repository (the "only _NoIndexSignature fires" claim), check-clause2-carriers --pair (no objectui-board instrument exists; the objectstack script judges its own board), check-widening-tells (returns NOT MEASURED for objectui paths by its own verdict), check-governed-merges. Platform artefacts recorded per the brief and not counted as findings: the server-appended duplicate footer on the PR body; the red repo-wide eslint --no-inline-config baseline on main; check-half-states.mjs exit 3 in this container.

VERDICT: PASS WITH FINDINGS — no blocking finding. Findings for the seat, in priority order: (1) correct the now-false titleField paragraph in the pending .changeset/8253-export-tree-view-config.md before the next @object-ui/types release (rider here or fold into #9064); (2) grade @object-ui/plugin-view minor for consistency with the #8828 record (effect-neutral); (3) read "the rung was already dead" as scoped to in-repo producers — an authored object-tree node under the passthrough BaseSchema could reach it, which the plugin-tree: minor grade already covers; (4) the name question (open_questions #3) is a maintainer decision, not a defect. ⛔ This reviewer cleared no carrier, changed no label, touched no PR state, and edited no file outside its own scratch clone.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Provenance — carriers cleared, landing

Clause-② contract review: VERDICT: PASS WITH FINDINGS, no blocking finding — record 5625941898 (#9052 (comment)), judged on head 7c1e03efeb6290122a0517e50c4651f15a364f6f, re-read unmoved immediately before the reviewer posted and still the head at this write.

Reviewer ran at CONTRACT_REVIEW_TIER = 'claude-fable-5-1' (dispatch-gates.mjs:10507), transcript-verified 123/123, zero other stamps, with a positive control (123 assistant / 77 user role tokens) proving the counter reads the file rather than returning a constant. Charter read at objectstack origin/main 2662a084e71c…, and it deleted a stale cac62d5f scratch copy unread rather than reusing it. I re-fetched to 9540590bacde00c215b59289180cf119cf8eff7a before clearing: .claude/skills/pm-dispatch and scripts/pm byte-unchanged, full-tree control 6 files.

Independence — NOT self-review, and this reviewer corrected the historical record while establishing it:

Implemented-by: branch `claude/issue-8841-tree-view-config-derive` (mode:subagent os-dev of seat session_01Jmxdo7bmeqCQHLSfmLVX9w)
Reviewed-by:    context-isolated claude-fable-5-1 subagent of seat session_01Jmxdo7bmeqCQHLSfmLVX9w

⭐ It states plainly that both are children of the same seat session and that independence rests on context isolation plus tier (:33-34), ⛔ not on session separation — and notes that the objectui#8828 records' "distinct sessions" wording overstated it. Seventh reviewer today to reach the same-session reading from the charter text; the first to fix a false claim in the prior records while doing so.

Landing pre-checks — all measured just now

check reading
① in-seat clause-② review PASS on record, 同形, ceiling tier ✅ record 5625941898 on the head
check-clause2-carriers.mjs --pair 9052 rc 0 — both carriers agree
③ every check on the head, ⛔ not a required subset 35 check runs, 32 success + 3 skipped, 0 failed, 0 pending; total_count 35 = array length 35; combined status success
governed surface --pr objectstack-ai/objectui#9052 → 0 of 9 paths hit the register ⇒ NOT governed
mergeability mergeable: true, mergeable_state: clean

⚠️ Two findings routed, ⛔ neither ridden into this PR

  1. A pending changeset becomes false on this merge. .changeset/8253-export-tree-view-config.md:38-45 says "titleField is declared, not deleted" — which this PR falsifies. objectui#9064 corrects only 8913-* and 8992-*, and it is already in the merge queue, so folding it in would mean pushing to a queued PR. Routed to objectui#9065 as its strong-class site, ⛔ not a rider here. It must land before the next @object-ui/types release.
  2. plugin-view: patch is graded below the objectui#8828 same-tier record, which called a narrowing of this props face minor. Effect-neutral — the pending 8253 changeset already carries plugin-view minor — so non-blocking, and recorded rather than fixed.

⛔ One correction to the dev's own report, which the PR body got right

The report called the removed rung "already dead". Not exactly: ObjectTreeSchema extends the .passthrough() BaseSchema (base.zod.ts:449), so an authored object-tree node carrying an undeclared titleField could reach it. Unreachable from the two in-repo producers (both floor labelField) — which is what the PR body actually claims, precisely. plugin-tree: minor already covers the runtime removal.

⭐ The two-verdict conflict — the reviewer went past both the card's framing and the dev's

The card framed it as one verdict right and one wrong; the dev proposed "accurate, then wrong one step up". The reviewer rejected both as too kind, with three measurements: the PASS verdict (a) asserted that deleting the rung would redden objectui#6557 — false, 32/32 pass with it gone; (b) measured spec 17.4.0 for the name collision and never for the key, although 17.4.0 had been on npm since 03:57Z and the contradicting review cited the refusal by file:line; (c) then swapped the read axis for the ruling's write axis.

Its process finding, which lands on this seat and not on the PR:

"A contract review that upholds a declared key without a parse-level reading against the protocol's accept set is incomplete at any tier, and two contradicting tier verdicts on one head are a real fork for the decision box, ⛔ not a clear on one's own subagent's PASS."

⇒ Recorded on card objectui#8841 and added to this seat's decision box for the maintainer. ⛔ It does not gate this landing — this head carries one verdict and no fork.

Both carriers stripped in this same act; PR flipped ready and enqueued. ⛔ Base is not hand-merged.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 10, 2026 21:56
@os-warren
os-warren added this pull request to the merge queue Sep 10, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

Dequeued at 22:23:59Z — and this PR's own queue build was ENTIRELY GREEN

pull_request.dequeued, reason: CI_FAILURE, from github-merge-queue[bot]. Standing down rather than pushing, and ⛔ not silently.

The measurement, and it does not name a failure in this PR

The queue built this PR on branch gh-readonly-queue/main/pr-9052-c98e344429519a438e70643fc634370edc9f868a:

reading value
workflow runs on that queue branch 19
conclusions 19 × completed / success
not green none — including CI itself
failing queue-branch run anywhere in the last ~160 workflow runs none found

So there is no failure signature attached to this PR to diagnose, re-run, or fix. The PR is still open, ready, mergeable_state: clean, head unchanged at 7c1e03efeb6290122a0517e50c4651f15a364f6f.

⛔ What I am NOT claiming

⛔ I am not calling this a flake, and ⛔ not attributing it to a batch-mate I cannot name. The dequeue notice says a grouped-queue failure can come from another PR in the same batch — that is the shape this fits, since a green build was dequeued for CI_FAILURE — but I searched the recent window and found no failing queue-branch run at all. ⇒ the cause is NOT MEASURED. Recording that as the reading rather than dressing it as a diagnosis.

⚠️ What did land in the same window: objectui#9051 merged at 22:24:00Z, one second after this dequeue, and its own landing was the batch this PR was queued behind. That is a temporal coincidence I can see, ⛔ not a causal claim I have measured.

Disposition

Re-queued as is, head unchanged. Grounds: the three landing pre-checks that gated this PR still hold on this head (contract review 5625941898 PASS on 7c1e03efe…, --pair 9052 rc 0, 35 check runs 32 success + 3 skipped), the queue build itself was green, and there is nothing in this PR to fix.

⚠️ This is the one re-投 for this PR (enqueue 1, dequeue 1 before this). ⛔ A second dequeue is not to be re-投 again on the same reasoning — at that point the pattern itself is the finding, and it goes to the maintainer rather than back to the queue.

⛔ Base is not hand-merged; the queue rebuilds on current main, which now carries objectui#9051.


Generated by Claude Code

@os-warren
os-warren added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit 2f6b2bf Sep 10, 2026
46 of 47 checks passed
@os-warren
os-warren deleted the claude/issue-8841-tree-view-config-derive branch September 10, 2026 22:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

1 participant