fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939) - #7533

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-6939-group2-treeview-mirror
Sep 3, 2026
Merged

fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939)#7533
os-project-manager merged 3 commits into
mainfrom
claude/issue-6939-group2-treeview-mirror

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Part of #6939group 2 of 8 (tree-view). Deliberately worded to leave the card open: four
of that card's eight groups are still unimplemented, so it must stay open.

⚠️Draft + needs:contract-review on purpose. This moves an accept surface;
the dispatching seat reviews before it lands. Please do not mark ready or enable
auto-merge.

The repair

TreeViewSchema REQUIRED data — the limb the renderer reads third:

const rawNodes = boundData || schema.nodes || schema.data || [];
// packages/components/src/renderers/data-display/tree-view.tsx:105

The registration's own inputs and defaultProps spell it nodes, and the four
components-data-display-tree-view/* catalog entries are those defaultProps.
data is now optional on the zod mirror and on the TypeScript twin, in the same
stroke.

What I re-measured, and where the card is stale

Card readings re-derived on origin/main at fe4e7a9e8, through a bare
SchemaRenderer harness:

card saysre-measuredverdict
4 files4 (deep-nesting, file-tree, org-chart, sidebar-navigation), all type,title,nodes✅ confirmed
zod demands data (required)nodes-only parse false, data-only parse true, bare {type} parse false✅ confirmed
renderer reads boundData or schema.nodes or schema.dataread site intact at :105; title at :115 / :117✅ confirmed
render after "correcting" the fixture: identical — 14 elementsidentical: ✅ (element count, tag census and a SHA-256 of textContent, all four tiles). 14: not reproduced — this harness measures 28 / 28 / 12 / 34⚠️ partly stale

Element counts are harness-bound (the docs-gallery harness gives different absolutes
for the same tile), so the pin records identity within one harness and carries the
numbers I measured rather than the card's.

⚠️The card's stated disposition is also half-landed already. It asks to "declare
nodes and title, and stop requiring data". objectui#6150 landed the declaration
half after the card was filed — both keys are declared on both faces today, and #6150
wrote in as many words that relaxing data was a separate accept-set change. This PR
is only that remaining half; nodes and title are untouched.

data stays declared rather than being deleted

The intuitive read of "the renderer prefers nodes" is to drop data. That is wrong,
and measurably: BaseSchema already declares data (z.any().optional(), data?: any
on the TS face), so removing the member would not reject the key — it would admit it
unvalidated while the renderer went on reading it. The pin
control: BaseSchema alone would have admitted both of those makes the difference
visible: the base object accepts the two values the member refuses.

No refinement was added

Unlike this card's object-map / object-gantt group, which added an "at least one of"
refinement. A tree-view carrying no data source at all becomes legal here, and that
admits no new rendering outcome: { data: [] } was already legal and already drew
the same empty tree. A refinement would forbid a spelling of an empty state the contract
already permits rather than buy a guarantee.

Verification

All runs at 6cad675a6; heavy runs serialised through the shared verify lock.

  • pnpm exec vitest run over the two new pins plus undeclared-but-consumed-keys-6150,
    zod-mirror-parity, safe-validate-corpus-6318, handler-keys-json-refusal-6124
    6 files, 374 tests, all passing. The parity ledger needed no edit: tree-view's
    only entry in any of the three ledgers is RuntimeOnlyDeclaredonNodeClick, and
    this change moves optionality, not key membership.
  • pnpm --filter @object-ui/types type-check — green (covers tsconfig.test.json,
    so both compile-time pins are checked).
  • pnpm --filter @object-ui/components type-check — green, after building the
    9-package closure. That is the only TS-face consumer of TreeViewSchema outside
    packages/types, by exhaustive grep.
  • eslint on the four touched files: exit 0, 0 errors, 31 warnings — all pre-existing
    no-explicit-any on lines outside every hunk.
  • check:control-bytes, check:doc-types, check:doc-fences,
    check-changeset-no-major — green.

Reversible verification (both faces restored to fe4e7a9e8, then restored back)

Mutation proven on disk by blob hash and by anchor counts in both directions
(optional-anchor 1 to 0, required-anchor 0 to 1, on both files). Restore proven by
state: on-disk blobs equal the HEAD blobs, git diff HEAD empty, git status
empty. Absolute paths, trap on EXIT INT TERM.

Predicted red set and measured red set agreed:

  • vitest, mutated: exit 1, 8 red — the three "nodes-only is legal" pins, the four
    validates under safeValidateSchema cases, and both spellings validate.
  • vitest, mutated: still green — all four render-identity cases, all four
    anti-vacuity cases, all four "correcting it to data changes no pixel" cases, and the
    whole "data is still validated" group. That asymmetry is the point: the pin is on
    the validator, and the renderer did not move.
  • type-check, mutated: exit 2 on exactly the two compile-time pins vitest cannot
    see — TS2344 on the optionality equality and TS2741 (Property 'data' is missing)
    on the nodes-only document literal.
  • restored: vitest exit 0 (5 files), type-check exit 0.

A first ablation round found two of my own controls reddening for the wrong reason
(their carrier documents were nodes-only, so the pre-repair mirror refused the carrier
itself). Both were rewritten onto data-bearing carriers that are legal in either state,
and the second round shows them staying green while the load-bearing pins still redden.

Declared narrowing

pnpm --filter @object-ui/example-schema-catalog type-check was not measured here.
It needs a 30-package dist closure, and the shared verify lock refused twice at its
9-minute budget with another seat holding it for 550s. Its local output is a precondition
failure, not a verdict: 67 errors, 63 of them TS2307/TS2882 "cannot find module",
the other 4 consequential implicit-any in files this PR does not touch — and the two
lines naming this PR's own file are that same "cannot find module @object-ui/components
/ @object-ui/react" class that every sibling test file reports. Nothing in it is about
this change. That file's behaviour is proven by executing it (green, and red under
ablation); CI type-checks the farm regardless.

Narrowing evidence for the lint side: the linted population is each package's own
eslint .; this diff touches files in two packages and all four were linted (count read
from --format json); and eslint.config.js configures no type-aware linting
(projectService 0, parserOptions 0, against a lit control of languageOptions 1), so
this diff cannot move the verdict on a file it does not touch.

Changeset

@object-ui/types: patch. The accept set only widens — no document that validated
before validates less, including the data-spelled tree-view in
packages/types/examples/data-display-examples.json — and this is the same shape as the
two sibling groups of this card that already landed on patch. The TS twin's
required-to-optional move is the honest half of the same fact: the renderer never
guaranteed data was present, its own read is || []-guarded, and the single consumer
type-checks clean.

⚠️ Interaction with #6951 — the reviewer needs to see this

Found while de-duplicating an unrelated observation, not supplied by the dispatch.
#6951 is open and carries needs-user-decision, and it explicitly folds in this
exact question:

data is required on TreeViewSchema while nodes is the spelling the docs name
as canonical.
[...] Whichever way the pair ruling goes, the required-ness has to move
with it. That is why it is one card.

#6951's own ruling is an ADR-0049 enforce-or-remove choice: retire one spelling of the
nodes / data pair. That choice has not been made. This PR does not make it — it
retires neither spelling, and both keep parsing. What it does do is presume the direction
that every in-repo artefact already names: the doc comments name nodes the winner,
#6951 itself says "nodes is the winner named in the doc comment", the registration's
inputs and defaultProps spell it nodes, the docs page lists both as optional, and
the four catalog entries author nodes.

So the two possible rulings land differently on this PR:

  • nodes wins (what every artefact points to) — this PR is a strictly smaller step in
    the same direction and stays correct as-is.
  • data wins — this PR would have to be reverted along with the rest.

⇒ Flagging rather than choosing. If the reviewing seat would rather this waited on #6951,
the branch is a clean single commit and nothing else depends on it. I have also left a
cross-reference on #6951 so this does not become the invisible interim that card was
filed to prevent.

Out of scope

⛔ Only group 2. Group 5 (chart) lives in the same file and is untouched; group 3
(kanban) is a genuine fork awaiting a ruling; groups 4 and 7 are unassigned. No catalog
fixture is edited — the card is explicit that the fixtures are the side that is right.

Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`TreeViewSchema` required `data`, which the renderer reads THIRD:
const rawNodes = boundData || schema.nodes || schema.data || [];
// packages/components/src/renderers/data-display/tree-view.tsx:105
The registration's own `inputs` and `defaultProps` spell it `nodes`, and the
four `components-data-display-tree-view/*` catalog entries ARE those
`defaultProps`, so `safeValidateSchema` refused every one of them while the
renderer drew them correctly. Re-measured on origin/main at fe4e7a9: four
refusals, and renders byte-identical under either spelling (28 / 28 / 12 / 34
elements, same tag census, same textContent SHA-256) -- objectui#6318's own
triage test for "the schema was the wrong side".
`data` becomes optional on the zod mirror and on the TypeScript twin in the
same stroke. It stays DECLARED rather than deleted: BaseSchema already declares
`data` as `z.any().optional()`, so removing the member would not reject the key
-- it would admit it unvalidated while the renderer went on reading it.
No refinement is added, unlike this card's object-map / object-gantt group: a
tree-view with no data source now validates, and that admits no new rendering
outcome, since `{ data: [] }` was already legal and already drew the same empty
tree.
`nodes` and `title` are objectui#6150's declarations and are untouched; that
card declared the reads and said in as many words that relaxing `data` was a
separate accept-set change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

合约审查裁断:FAIL —— 逐字采信

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。本席是 opus,不在 CONTRACT_REVIEW_TIER,故本 Clause-② yes 的 PR 由隔离审查者出裁。简报只含卡片、裁决与本 PR,⛔ 不含派发指令、⛔ 不含本席结论。

转录档位核验(先决条件):对审查者转录只提取 harness 生成的 model 字段(⛔ 不对自由文本搜关键词——一句「未检出 X」自身含 X,会污染下一轮检测)。248 行、0 行不可解析、119 个 assistant 轮次,全部 claude-fable-5-1,无 fallback 证据 ⇒ 裁断在档,逐字采信。⛔ 未改写、未删节、未润色。

⚠️ 三条 FAIL 全部是 PR 关于改动的陈述,不是改动本身;审查者对改动本身逐轴测过并通过(NEWLY_FAIL=0、消融红集吻合、两面同步、parity ledger、以及本 PR 声明未测的那个 type-check)。


VERDICT: FAIL

The code change itself checks out on every axis I could measure — accept-set direction (NEWLY_FAIL=0), ablation red set, both faces moving together, parity ledger, both heavy type-checks including the one the PR declared unmeasured, and a clean merge onto the current origin/main. The FAIL is on three items the PR says about the change that are wrong or undisclosed, each with a one-to-two-line fix. None requires re-measuring the change.

All measurements were mine, on a detached worktree at 6cad675a6 (git worktree add --detach /home/user/objectui-cr7533 6cad675a6, pnpm install --frozen-lockfile exit 0), base faces taken from 78a3cc23 (blob-identical to merge-base fe4e7a9e8 for both touched source files: zod d1d427db…, ts 58850203…). Worktree removed after review.


Finding 1 — a compile-time pin bites for a different mechanism than its comment names

File:packages/types/src/__tests__/tree-view-data-optional-6939.test.ts:60-67

 * - member DELETED -> falls through `BaseSchema`'s `[key: string]: any`
* to `any`, and `Equal< any, … >` is false -> red

That is not what happens. BaseSchemadeclaresdata?: any (packages/types/src/base.ts:183), so a twin without its own data member resolves ['data'] to the inherited declared member, not to the index signature. The pin still reddens (inherited member is any), but the comment records the wrong mechanism — and this file's own header paragraph, the TS doc comment on data, and the zod .describe() all state the correct one, so the pin's comment contradicts the rest of the PR.

Measurement (tsc --ignoreConfig --noEmit --strict … --types react src/__cr_probe__/mechanism.probe.ts run inside packages/types):

typeWithoutIndexSignature<D>={[KinkeyofDasstringextendsK ? never : numberextendsK ? never : K]: D[K]};// 1. `data` IS a declared member of BaseSchema (survives index-signature removal)exporttype_DataIsDeclaredOnBase=Expect<Equal<'data'extendskeyofWithoutIndexSignature<BaseSchema> ? true : false,true>>;exporttype_SentinelIsNotDeclared=Expect<Equal<'undeclaredTreeKey6939'extendskeyofWithoutIndexSignature<BaseSchema> ? true : false,false>>;// 2. twin without its own `data`: still a DECLARED member, and `any`interfaceTwinWithoutDataextendsBaseSchema{type: 'tree-view';nodes?: TreeNode[]}exporttype_InheritedDataIsAny=Expect<Equal<TwinWithoutData['data'],any>>;exporttype_InheritedDataStillDeclared=Expect<Equal<'data'extendskeyofWithoutIndexSignature<TwinWithoutData> ? true : false,true>>;// 3. same on a base with NO index signature at all: still `any`, from the inherited member aloneinterfaceTwinOnBaseNoIndexextendsWithoutIndexSignature<BaseSchema>{type: 'tree-view';nodes?: TreeNode[]}exporttype_AnyWithoutAnyIndexSignature=Expect<Equal<TwinOnBaseNoIndex['data'],any>>;// @ts-expect-error — an UNDECLARED key on that base is an error, not `any`exporttype_UndeclaredIsErrorWithoutIndex=TwinOnBaseNoIndex['undeclaredTreeKey6939'];
PROBE_CLEAN_EXIT=0
--- control: expectation 2 flipped to `false` ---
src/__cr_probe__/mechanism.flipped.ts(21,50): error TS2344: Type 'false' does not satisfy the constraint 'true'.
FLIPPED_CLEAN_EXIT=2

Line 3 is decisive: with the index signature stripped entirely, ['data'] is still any. The index signature is not on the path.

Minimal fix: replace the two comment lines with
member DELETED -> resolves to the INHERITED \BaseSchema.data?: any` (base.ts:183) — `any`, and `Equal< any, … >` is false -> red. (The identical wrong sentence does *not* appear in undeclared-but-consumed-keys-6150.test.ts:91-94; there the keys are genuinely undeclared on BaseSchema, so the index-signature account is correct for them. It is wrong only for data`.)


Finding 2 — a published doc now states a contract fact this PR made false

File:content/docs/api/schema-reference.md:527 (unchanged by the PR; the PR touches 5 files, none under content/):

| `data` | `TreeNode[]` | **Required.** Nested tree data. Each node has `id`, `label`, optional `icon` and `children`. |

Before this PR that row was true; after it, the published API reference says data is required while the published type says data?: TreeNode[] and the published validator accepts documents without it. No gate covers the row: scripts/check-doc-component-types.mjs reads this file only for its type vocabulary (its exemption block at :410), and check:doc-snippets compiles ts/tsx fences, not property tables (check:doc-types exit 0, check:doc-fences exit 0 at head — both green with the stale row). This card's two landed siblings corrected the docs page that published the old required-ness (tooltip.mdx in #7456; plugin-map.mdx and the gantt page in #7471); this PR leaves its equivalent.

Measurement:

$ grep -n "| \`data\` | \`TreeNode\[\]\`" content/docs/api/schema-reference.md # at 6cad675a6
527:| `data` | `TreeNode[]` | **Required.** Nested tree data. …
$ git diff --stat fe4e7a9e8 6cad675a6 -- content/
(empty)

(content/docs/components/data-display/tree-view.mdx:28-31 already reads data?: TreeNode[] / nodes?: TreeNode[], so the component page is consistent; the API reference is the one that is not.)

Minimal fix: rewrite the row, e.g.
| \data` | `TreeNode[]` | Optional. Nested tree data, read only when `nodes` is absent (the renderer reads `nodes` first — objectui#6939). |— and, since the table lists neithernodesnortitle, a nodes` row would make the JSON example above it honest, but that omission predates this PR (#6150) and is not part of this finding.


Finding 3 — the changeset's "WIDENING, on both faces" is wrong on the reader side of the TS face, and the changeset is where it needs to be right

File:.changeset/6939-tree-view-nodes-mirror.md:24-29

**This is a WIDENING, on both faces.** `data` goes from required to optional on
the mirror and on the TypeScript twin in the same stroke; nothing that validated
before validates less.

For the validator and for an author of the TS type, true. For a reader of the published TS twin it is a narrowing: TreeViewSchema['data'] goes from TreeNode[] to TreeNode[] | undefined, and code that relied on presence stops compiling. The PR body says this ("the honest half of the same fact … its own read is || []-guarded") — the changeset, which is the artefact that becomes the CHANGELOG, says the opposite. AGENTS.md §版本号策略 is explicit that breaking semantics are stated in the changeset body.

Measurement (consumer probe against the published entry, tsc --ignoreConfig --noEmit --strict …):

importtype{TreeViewSchema,TreeNode}from'…/packages/types/src/index';exportfunctioncount(schema: TreeViewSchema): number{constnodes: TreeNode[]=schema.data;returnnodes.length+schema.data.length;}
--- at HEAD (data?: TreeNode[]) ---
consumer.probe.ts(4,9): error TS2322: Type 'TreeNode[] | undefined' is not assignable to type 'TreeNode[]'.
consumer.probe.ts(5,25): error TS18048: 'schema.data' is possibly 'undefined'.
HEAD CONSUMER_PROBE_EXIT=2
--- at BASE faces (data: TreeNode[]) blobs: 588502039b4df4ca47c150317474e4e61882a3b5 ---
BASE CONSUMER_PROBE_EXIT=0

On the bump:patch stands. scripts/check-changeset-no-major.mjs forbids only major (exit 0 at head: "No changeset declares a major bump."); the maintainer ruling on #6939 (comment 5510084784) grades this exact class "patch where the accept set only widens toward what already renders"; and both landed siblings moved a required TS member to optional under patch (objectName in .changeset/6939-objectql-record-source-refinement.md, children in .changeset/6939-overlay-trigger-mirror.md). The #6318 reviewer's patch→minor condition predates that ruling and concerned union membership, not optionality. So the grade is what the repo's rules call for; the description is not.

Minimal fix: replace the bolded sentence with something like: "The accept set widens on both faces for authors. For a reader of the TypeScript twin this is a narrowing — data is now TreeNode[] | undefined, so an unguarded schema.data read needs a guard; the only in-repo reader (tree-view.tsx:105) already has one (|| [])." Grade unchanged.


What I measured that checks out (so the adopter can see the FAIL is not about the change)

Accept set, both directions — NEWLY_FAIL = 0. Own census over every tracked *.json/*.yaml/*.yml (664 files, 664 parsed, 479 with a root type string), each root run through safeValidateSchema and every tree-view node (root or nested) through TreeViewSchema.safeParse, via tsx on source (packages/types/src/zod/index.zod.ts), at head and with both faces restored to 78a3cc23:

head: root-union ok=392 fail=91 base: ok=385 fail=98
NEWLY_PASS (base refused, head accepts): 4 tracked files at root —
components-data-display-tree-view/{deep-nesting,file-tree,org-chart,sidebar-navigation}.json
base issue on all four: ': Invalid input' (root) / 'data: Invalid input: expected array, received undefined' (member)
NEWLY_FAIL (base accepted, head refuses): 0

So the PR's "four" is exact for the objectui check predicate. The complete member-level count is larger, and the PR should be read as having counted at root level: a fifth tracked node flips — the nested tree-view in components-complex-resizable/editor-interface.json (/panels/0/content/0/children/1, nodes-only) — while its enclosing resizable document stays refused at both revs for an unrelated reason (panels.0.id, panels.1.id missing). Two inline TS literals also flip: { type: 'tree-view', bind: 'treeNodes' } (shadowed-renderer-behaviour.test.tsx:119) and the registration's own defaultProps; the data-spelled packages/types/examples/data-display-examples.json entry and TREE_CONTROL = { type, data: [] } stay accepted (True→True). The implementer's own #6951 comment names the nested one, so this is a counting-level difference, not an unknown.

Ablation reproduces the PR's prediction exactly. Base faces on disk (blobs d1d427db…/58850203…, anchors optional-zod 0 / required-zod 1 / optional-ts 0 / required-ts 1), vitest run on the two new files:

Tests 8 failed | 25 passed (33) VITEST_BASE_EXIT=1
× a tree-view with NO data source at all is legal… × the spelling the renderer reads FIRST now parses on its own
× …and the value SURVIVES the parse × both spellings validate — the accept set widened…
× {deep-nesting,file-tree,org-chart,sidebar-navigation} validates under safeValidateSchema

The four render-identity, four anti-vacuity, four "changes no pixel", "keys are DECLARED" (carrier {type,title,data:[],nodes}), "UNDECLARED key admitted" (carrier {type,data:[],[SENTINEL]}), and "BaseSchema alone would have admitted both" controls all stayed green — the rewritten controls from assertion 7 do redden only for their own reason. pnpm --filter @object-ui/types type-check under base faces: exit 2 with exactly TS2344 at :72 and TS2741 Property 'data' is missing at :85. Restore proven by state: blobs back to a0f1499d…/e4f16b12…, anchors 1/1, git status --porcelain empty.

Head is green everywhere I ran it.vitest run on the two new pins + undeclared-but-consumed-keys-6150 + safe-validate-corpus-6318: 4 files / 118 tests; zod-mirror-parity + handler-keys-json-refusal-6124: 2 files / 256 tests; the whole packages/types/ + examples/schema-catalog/ population plus shadowed-renderer-behaviour: 121 files / 3644 tests, exit 0. pnpm --filter @object-ui/types type-check exit 0 (clean tree, 0 error TS), with --listFiles showing both new test files in their tsconfig.test.json programs. Gates: check:control-bytes, check:doc-types, check:doc-fences, check-changeset-no-major, check-changeset-presence ("3 source file(s) of 1 released package… 1 changeset(s)") all exit 0. eslint on the four touched files: 0 errors; both new files 0 warnings; 30 + 1 warnings on the two source files (count matches the PR's 31).

Assertion 8 — measured, and it could not have caught anything.turbo run build --filter=@object-ui/example-schema-catalog...: 29 successful / 29 total; then pnpm --filter @object-ui/example-schema-catalog type-check (tsc --noEmit && tsc -p tsconfig.test.json): exit 0, 0 errors, and pnpm --filter @object-ui/components type-check: exit 0. The narrowing is closed.

Compile-time pins — what each catches (probe pins.probe.ts, exit 0):_TreeDataIsOptionalTreeNodes catches data re-required (TS2344, measured), data deleted (inherited any — Finding 1), and an element-type change. _TreeNodesStillTreeNodes / _TreeTitleStillString are optionality-blind by construction (NonNullable<Req['nodes']> equals NonNullable<Opt['nodes']> — measured true): they catch deletion and element-type change, not nodes/title becoming required; they are inherited from #6150 unchanged, so this is a limitation to know, not a defect of this PR. NODES_ONLY_DOCUMENT catches any key re-required (TS2741, measured) but cannot catch nodes/title deleted or renamed — { …, nodez: […] } and { …, undeclaredTreeKey6939: 1 } both compile through BaseSchema's index signature (measured); a wrong-typed declared key still errors (measured). The runtime pins cover the deletion case (data: 'not-an-array' refused AT data; BaseSchema alone accepts it — both measured green at head).

Other comment mechanisms:.passthrough()base.zod.ts:212; BaseSchema.data: z.any().optional()base.zod.ts:121; data?: anybase.ts:183; [key: string]: anybase.ts:409; renderer read at tree-view.tsx:104-106, inputs/defaultProps spelled nodes. All as stated. Parity ledger: TreeViewSchema's only entry is RuntimeOnlyDeclared: 'onNodeClick' (zod-mirror-parity.test.ts:1239); the compile-time NarrowerThanDeclared comparison does see optionality ([D[K]] extends [InputOf<Shape[K]>]), which is why both faces had to move together — and did (type-check exit 0 covers it).

Assertion 4 (no refinement) — sound.{ type: 'tree-view' } is accepted at head and { type: 'tree-view', data: [] } was accepted at base (census, both measured); the renderer's || [] guard means neither draws anything the other does not, so "at least one of" never guaranteed a non-empty tree. The census also shows why a two-limb refinement would be wrong: { type: 'tree-view', bind: 'treeNodes' } — a real, rendering document — flips refused→accepted here, and a nodes/data-only rule would refuse it. The #6939 maintainer ruling names a refinement for object-map/object-gantt only; #6951's comment 5527723261 recommending one is a triage recommendation on a needs-user-decision card, not a ruling.

Assertion 5 — confirmed stale half. Base data-display.ts:1220-1236 already declares nodes?/title? with #6150's comments; #6150's pins pass at head.

Merge target:git merge-tree --write-tree origin/main 6cad675a6 exit 0, no conflicts; origin/main's packages/react/src/index.ts still export * from './schema-input.js' (the new test's toRenderableSchema/SchemaRenderer imports resolve).

NARROWINGS

  • The card's "14 elements" — not reproduced or attempted; it comes from the docs-gallery harness. I confirmed only that the PR's 28 / 28 / 12 / 34 reproduce in this environment (all four render-identity cases pass at head).
  • eslint warnings on touched lines — I read the counts (30 + 1, both new files 0), not the line positions; whether all 31 sit outside the hunks is the PR's claim, not mine.
  • The PR's own reported runs (pnpm exec vitest run over its six files, its own ablation and blob hashes, its lock timings) — not re-read; superseded by my own runs above.
  • objectui check (node packages/cli/dist/cli.js check) — not run; my census reimplements its validation arm without the structural arm, so my "4 tracked files flip at root" is on that predicate, not on the CLI's printed count.
  • Whole-repo eslint, check:doc-snippets, check:readme-exports, Bundle Analysis, CI convergence — not run; CI-owned. At review time CI showed 27 completed checks all success/skipped with Type Check and the four Test shards still in progress.
  • The Two alias pairs on TextSchema and TreeViewSchema are now DECLARED in both spellings — and TreeViewSchema.data is required while nodes is the documented one (deferred rulings carried out of #6150) #6951 pair ruling — unmade at review time; the PR's revert-if-data-wins caveat is accurate but not something I can measure.

派发席处置

补丁轮,三条全修,⛔ 不放行。⚠️ Finding 2 是本席在席内核验时漏掉的真缺陷:本席查了 JSON 文档与 TS 消费者,没查声明合约事实的已发布散文,而本卡两个已落兄弟组(#7456#7471)恰恰都改了各自的文档页——本 PR 偏离了它自己这张卡的既定做法。

⛔ 注意 content/docs/api/schema-reference.md不是content/docs/releases/,改它不触犯「代码 PR 不得改发布说明」那条。

⭐ 三条 FAIL 都不要求重测改动本身:审查者已把 accept set 双向(NEWLY_FAIL=0)、消融红集、两面同步、以及本 PR 自己申报为未测的 example-schema-catalog type-checkexit 0,收窄已闭合)全部测过。patch 档位经三条独立支撑维持不变

本 PR 的 CI 在 6cad675a6 上已 32/32 全绿(其中 Lint run 33785853068、Docs Links run 33785852959 是按工作流正查确认的——get_check_runs 只返回了 30 条)。


Generated by Claude Code

Contract review of PR 7533 passed the change on every axis and failed three
statements ABOUT it. None of the code under repair moves here.
1. The compile-time pin's comment named the wrong mechanism. Deleting the
member does not fall through BaseSchema's `[key: string]: any`; it resolves
to the INHERITED declared `BaseSchema.data?: any` (base.ts:183), because a
declared member outranks an index signature. Verified independently before
accepting the finding: strip the index signature with the homomorphic
keyof-remap and `['data']` is still `any`, while an index-signature-only key
stops resolving at all -- both controls lit. The pin's verdict is unchanged;
only the reason it gives was wrong, and it contradicted the file header, the
TS-face doc comment and the mirror's describe().
2. `content/docs/api/schema-reference.md` still called `data` **Required**,
which this branch made false, and no gate covers that row. Corrected, and a
`nodes` row added beside it so the corrected sentence does not reference a
key the table never lists. The two sibling groups of this card each updated
their own doc page; this one had missed it.
3. The changeset called the change a widening "on both faces" without saying
that a READER of the TypeScript twin sees a narrowing: `data` is now
`TreeNode[] | undefined`, so an unguarded `schema.data` read stops compiling.
The PR body already said this; the changeset is the artefact that becomes the
CHANGELOG, so it is the one that had to. Level is unchanged and still patch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@claude

claudeBot commented Sep 3, 2026

Copy link
Copy Markdown
ContributorAuthor

Patch round — all three FAIL findings addressed at 794aa2ad4. Nothing under repair moved.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. One commit
(5d7e47fa7) plus a merge of origin/main at d04e79a80 (merge 794aa2ad4, no
conflicts). ⛔ Changeset level untouched, still patch. ⛔ nodes / title
declarations untouched. ⛔ No re-测 of the change itself.

Finding 1 — I checked it before accepting it, and the review is right

I did not take the mechanism on trust, because the instruction was to say so if I measured
otherwise. A throwaway type-level probe, run through pnpm --filter @object-ui/types type-check and then deleted, with both controls lit:

proberesult
['data'] with the index signature presentany
['data'] with the index signature STRIPPED (the homomorphic keyof-remap zod-mirror-parity.test.ts uses)still any
data survives the strippingyes, so it is a declared member
CONTROL: an index-signature-only key survives the strippingno — so the stripping really is doing something
CONTROL: that same key reads as any while the index signature is presentyes

All five compiled, exit 0. BaseSchema carries bothdata?: any (base.ts:183) and
[key: string]: any (base.ts:409), and the declared member — inherited or not — outranks
the index signature. So the comment was wrong and the review's replacement is right;
the pin's verdict never depended on it. Replacement text taken as given, plus a short
paragraph recording the measurement so the next reader does not have to redo it.

undeclared-but-consumed-keys-6150.test.ts:91-94 deliberately NOT touched — the same
sentence is true there, because those 13 keys genuinely are undeclared on BaseSchema.

Finding 2 — content/docs/api/schema-reference.md:527

Corrected to the suggested text. I also added onenodes row directly above it, and
that is a judgment call worth flagging rather than burying: the corrected sentence says
"read only when nodes is absent", and nodes was not in that table, so the correction
would otherwise have created a dangling reference. One row, not a documentation sweep —
title is still absent and I left it alone as the #6150 leftover the review says it is.
The sentence about node shape (id, label, optional icon and children) moved onto
the nodes row rather than being dropped.

content/docs/releases/ not touched.

ℹ️ Two more census data points, recorded not acted on: the JSON example immediately above
that table also authors data, as does the tree-view entry in
packages/types/examples/data-display-examples.json. Both are still legal after this
branch, so neither is a false statement — but if the #6951 retirement lands on nodes,
they are two of the documents that have to move.

Finding 3 — .changeset/6939-tree-view-nodes-mirror.md

Split into the two halves that are separately true: authors get a widening on both faces;
a reader of the TypeScript twin gets a narrowing, TreeNode[] | undefined, with the
TS2322 + TS18048 consumer-probe result named, the one in-repo reader shown to be
already guarded, and out-of-repo unguarded consumers named as the population the paragraph
exists for. Level unchanged, with the three reasons it stays patch stated in place.

Convergence verification, at 794aa2ad4 (post-merge head)

Heavy runs serialised through the shared verify lock; exit codes captured to files before
any pipe.

checkexit
pnpm exec vitest run over both new test files0 — "Test Files 2 passed (2)"
pnpm --filter @object-ui/types type-check0
pnpm check:doc-types0
pnpm check:doc-fences0
node scripts/check-control-bytes.mjs0 (6206 tracked files)
node scripts/check-changeset-no-major.mjs0

⚠️ Worth recording about check:doc-types: its counters are byte-identical before and
after my documentation edit — same 188 documents, same 4 key tables, same 24 rows, same 45
table keys. That is the review's own point measured from the other side: the gate never
looked at this table, so the stale row could not have been caught by it.

Thank you for the census — the root-level 4 is exactly what I claimed and the
member-level flips (the nested editor-interface.json node, and the real bind-only
document at shadowed-renderer-behaviour.test.tsx:119) are a level distinction I had not
made. I have left the counts alone as instructed.

Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

在席处置:补丁轮已核,放行 —— 剥标签 + ready + auto-merge SQUASH

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。链条:隔离审查 FAIL 三条 → 补丁轮 → 在席按内容核实 → 放行。

决定性的一读:被审查过的改动没有动

补丁轮之后我没有重跑合约审查,依据是这个:

 6cad675a6 (被审的 head) → 794aa2ad4 (补丁后)
data-display.zod.ts a0f1499dff920cfa1de27573396e1c2734674d7d IDENTICAL
data-display.ts e4f16b1261cfd621cac6d83a061041967768a5ec IDENTICAL

⇒ 两个被审源文件逐字节相同 ⇒ 审查者对改动本身的裁断(accept set 双向 NEWLY_FAIL=0、消融红集吻合、两面同步、parity ledger、干净 merge)原样成立,无需重审。补丁提交 5d7e47fa7 只碰了三个文件——正是三条 finding 各自的落点(changeset、content/docs/api/schema-reference.md、测试注释);diff stat 看着大是因为它同时 merge 了 origin/main

三条修正逐一核过

⭐ 一条从反面得来的读数,值得留档

实现者报告:check:doc-types 在它改动文档前后计数器逐字节相同(同样 188 文档 / 4 个 key table / 24 行 / 45 个 table key)。它把这条报成「一个对我修的那行什么也没说的绿」,而不是报成覆盖率——这正是审查者「no gate covers this row」从另一侧的验证。

放行状态

CI 在当前 head 794aa2ad4completed / success⚠️ 不是旧 head 6cad675a6 那个绿——按 status=completed 过滤会返回旧 head 的绿,那对当前 head 什么也没说)。已剥 needs:contract-review、ready、auto-merge SQUASH,并按 base-sha 链正向确认入队(队首,base = f0f774b0d)。

⚠️ 放行动作一度因 GitHub 配额半完成(标签已剥、undraft 失败),已排定时提醒跟踪并在配额恢复后补完 —— ⛔ 没有静默停在半完成状态。

⛔ 未随本 PR 解决的

#6951needs-user-decisionnodes / data 的 ADR-0049 退役选择未裁。本 PR 不做那个选择(两种拼法都仍解析)。若最终 data 胜出,本分支需连同其余一并回退。相关记录已留在 #6951(含本席对自己一处口径错误的订正)。


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939) - #7533

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-6939-group2-treeview-mirror
Sep 3, 2026
Merged

fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939)#7533
os-project-manager merged 3 commits into
mainfrom
claude/issue-6939-group2-treeview-mirror

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Part of #6939group 2 of 8 (tree-view). Deliberately worded to leave the card open: four
of that card's eight groups are still unimplemented, so it must stay open.

⚠️Draft + needs:contract-review on purpose. This moves an accept surface;
the dispatching seat reviews before it lands. Please do not mark ready or enable
auto-merge.

The repair

TreeViewSchema REQUIRED data — the limb the renderer reads third:

const rawNodes = boundData || schema.nodes || schema.data || [];
// packages/components/src/renderers/data-display/tree-view.tsx:105

The registration's own inputs and defaultProps spell it nodes, and the four
components-data-display-tree-view/* catalog entries are those defaultProps.
data is now optional on the zod mirror and on the TypeScript twin, in the same
stroke.

What I re-measured, and where the card is stale

Card readings re-derived on origin/main at fe4e7a9e8, through a bare
SchemaRenderer harness:

card saysre-measuredverdict
4 files4 (deep-nesting, file-tree, org-chart, sidebar-navigation), all type,title,nodes✅ confirmed
zod demands data (required)nodes-only parse false, data-only parse true, bare {type} parse false✅ confirmed
renderer reads boundData or schema.nodes or schema.dataread site intact at :105; title at :115 / :117✅ confirmed
render after "correcting" the fixture: identical — 14 elementsidentical: ✅ (element count, tag census and a SHA-256 of textContent, all four tiles). 14: not reproduced — this harness measures 28 / 28 / 12 / 34⚠️ partly stale

Element counts are harness-bound (the docs-gallery harness gives different absolutes
for the same tile), so the pin records identity within one harness and carries the
numbers I measured rather than the card's.

⚠️The card's stated disposition is also half-landed already. It asks to "declare
nodes and title, and stop requiring data". objectui#6150 landed the declaration
half after the card was filed — both keys are declared on both faces today, and #6150
wrote in as many words that relaxing data was a separate accept-set change. This PR
is only that remaining half; nodes and title are untouched.

data stays declared rather than being deleted

The intuitive read of "the renderer prefers nodes" is to drop data. That is wrong,
and measurably: BaseSchema already declares data (z.any().optional(), data?: any
on the TS face), so removing the member would not reject the key — it would admit it
unvalidated while the renderer went on reading it. The pin
control: BaseSchema alone would have admitted both of those makes the difference
visible: the base object accepts the two values the member refuses.

No refinement was added

Unlike this card's object-map / object-gantt group, which added an "at least one of"
refinement. A tree-view carrying no data source at all becomes legal here, and that
admits no new rendering outcome: { data: [] } was already legal and already drew
the same empty tree. A refinement would forbid a spelling of an empty state the contract
already permits rather than buy a guarantee.

Verification

All runs at 6cad675a6; heavy runs serialised through the shared verify lock.

  • pnpm exec vitest run over the two new pins plus undeclared-but-consumed-keys-6150,
    zod-mirror-parity, safe-validate-corpus-6318, handler-keys-json-refusal-6124
    6 files, 374 tests, all passing. The parity ledger needed no edit: tree-view's
    only entry in any of the three ledgers is RuntimeOnlyDeclaredonNodeClick, and
    this change moves optionality, not key membership.
  • pnpm --filter @object-ui/types type-check — green (covers tsconfig.test.json,
    so both compile-time pins are checked).
  • pnpm --filter @object-ui/components type-check — green, after building the
    9-package closure. That is the only TS-face consumer of TreeViewSchema outside
    packages/types, by exhaustive grep.
  • eslint on the four touched files: exit 0, 0 errors, 31 warnings — all pre-existing
    no-explicit-any on lines outside every hunk.
  • check:control-bytes, check:doc-types, check:doc-fences,
    check-changeset-no-major — green.

Reversible verification (both faces restored to fe4e7a9e8, then restored back)

Mutation proven on disk by blob hash and by anchor counts in both directions
(optional-anchor 1 to 0, required-anchor 0 to 1, on both files). Restore proven by
state: on-disk blobs equal the HEAD blobs, git diff HEAD empty, git status
empty. Absolute paths, trap on EXIT INT TERM.

Predicted red set and measured red set agreed:

  • vitest, mutated: exit 1, 8 red — the three "nodes-only is legal" pins, the four
    validates under safeValidateSchema cases, and both spellings validate.
  • vitest, mutated: still green — all four render-identity cases, all four
    anti-vacuity cases, all four "correcting it to data changes no pixel" cases, and the
    whole "data is still validated" group. That asymmetry is the point: the pin is on
    the validator, and the renderer did not move.
  • type-check, mutated: exit 2 on exactly the two compile-time pins vitest cannot
    see — TS2344 on the optionality equality and TS2741 (Property 'data' is missing)
    on the nodes-only document literal.
  • restored: vitest exit 0 (5 files), type-check exit 0.

A first ablation round found two of my own controls reddening for the wrong reason
(their carrier documents were nodes-only, so the pre-repair mirror refused the carrier
itself). Both were rewritten onto data-bearing carriers that are legal in either state,
and the second round shows them staying green while the load-bearing pins still redden.

Declared narrowing

pnpm --filter @object-ui/example-schema-catalog type-check was not measured here.
It needs a 30-package dist closure, and the shared verify lock refused twice at its
9-minute budget with another seat holding it for 550s. Its local output is a precondition
failure, not a verdict: 67 errors, 63 of them TS2307/TS2882 "cannot find module",
the other 4 consequential implicit-any in files this PR does not touch — and the two
lines naming this PR's own file are that same "cannot find module @object-ui/components
/ @object-ui/react" class that every sibling test file reports. Nothing in it is about
this change. That file's behaviour is proven by executing it (green, and red under
ablation); CI type-checks the farm regardless.

Narrowing evidence for the lint side: the linted population is each package's own
eslint .; this diff touches files in two packages and all four were linted (count read
from --format json); and eslint.config.js configures no type-aware linting
(projectService 0, parserOptions 0, against a lit control of languageOptions 1), so
this diff cannot move the verdict on a file it does not touch.

Changeset

@object-ui/types: patch. The accept set only widens — no document that validated
before validates less, including the data-spelled tree-view in
packages/types/examples/data-display-examples.json — and this is the same shape as the
two sibling groups of this card that already landed on patch. The TS twin's
required-to-optional move is the honest half of the same fact: the renderer never
guaranteed data was present, its own read is || []-guarded, and the single consumer
type-checks clean.

⚠️ Interaction with #6951 — the reviewer needs to see this

Found while de-duplicating an unrelated observation, not supplied by the dispatch.
#6951 is open and carries needs-user-decision, and it explicitly folds in this
exact question:

data is required on TreeViewSchema while nodes is the spelling the docs name
as canonical.
[...] Whichever way the pair ruling goes, the required-ness has to move
with it. That is why it is one card.

#6951's own ruling is an ADR-0049 enforce-or-remove choice: retire one spelling of the
nodes / data pair. That choice has not been made. This PR does not make it — it
retires neither spelling, and both keep parsing. What it does do is presume the direction
that every in-repo artefact already names: the doc comments name nodes the winner,
#6951 itself says "nodes is the winner named in the doc comment", the registration's
inputs and defaultProps spell it nodes, the docs page lists both as optional, and
the four catalog entries author nodes.

So the two possible rulings land differently on this PR:

  • nodes wins (what every artefact points to) — this PR is a strictly smaller step in
    the same direction and stays correct as-is.
  • data wins — this PR would have to be reverted along with the rest.

⇒ Flagging rather than choosing. If the reviewing seat would rather this waited on #6951,
the branch is a clean single commit and nothing else depends on it. I have also left a
cross-reference on #6951 so this does not become the invisible interim that card was
filed to prevent.

Out of scope

⛔ Only group 2. Group 5 (chart) lives in the same file and is untouched; group 3
(kanban) is a genuine fork awaiting a ruling; groups 4 and 7 are unassigned. No catalog
fixture is edited — the card is explicit that the fixtures are the side that is right.

Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`TreeViewSchema` required `data`, which the renderer reads THIRD:
const rawNodes = boundData || schema.nodes || schema.data || [];
// packages/components/src/renderers/data-display/tree-view.tsx:105
The registration's own `inputs` and `defaultProps` spell it `nodes`, and the
four `components-data-display-tree-view/*` catalog entries ARE those
`defaultProps`, so `safeValidateSchema` refused every one of them while the
renderer drew them correctly. Re-measured on origin/main at fe4e7a9: four
refusals, and renders byte-identical under either spelling (28 / 28 / 12 / 34
elements, same tag census, same textContent SHA-256) -- objectui#6318's own
triage test for "the schema was the wrong side".
`data` becomes optional on the zod mirror and on the TypeScript twin in the
same stroke. It stays DECLARED rather than deleted: BaseSchema already declares
`data` as `z.any().optional()`, so removing the member would not reject the key
-- it would admit it unvalidated while the renderer went on reading it.
No refinement is added, unlike this card's object-map / object-gantt group: a
tree-view with no data source now validates, and that admits no new rendering
outcome, since `{ data: [] }` was already legal and already drew the same empty
tree.
`nodes` and `title` are objectui#6150's declarations and are untouched; that
card declared the reads and said in as many words that relaxing `data` was a
separate accept-set change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

合约审查裁断:FAIL —— 逐字采信

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。本席是 opus,不在 CONTRACT_REVIEW_TIER,故本 Clause-② yes 的 PR 由隔离审查者出裁。简报只含卡片、裁决与本 PR,⛔ 不含派发指令、⛔ 不含本席结论。

转录档位核验(先决条件):对审查者转录只提取 harness 生成的 model 字段(⛔ 不对自由文本搜关键词——一句「未检出 X」自身含 X,会污染下一轮检测)。248 行、0 行不可解析、119 个 assistant 轮次,全部 claude-fable-5-1,无 fallback 证据 ⇒ 裁断在档,逐字采信。⛔ 未改写、未删节、未润色。

⚠️ 三条 FAIL 全部是 PR 关于改动的陈述,不是改动本身;审查者对改动本身逐轴测过并通过(NEWLY_FAIL=0、消融红集吻合、两面同步、parity ledger、以及本 PR 声明未测的那个 type-check)。


VERDICT: FAIL

The code change itself checks out on every axis I could measure — accept-set direction (NEWLY_FAIL=0), ablation red set, both faces moving together, parity ledger, both heavy type-checks including the one the PR declared unmeasured, and a clean merge onto the current origin/main. The FAIL is on three items the PR says about the change that are wrong or undisclosed, each with a one-to-two-line fix. None requires re-measuring the change.

All measurements were mine, on a detached worktree at 6cad675a6 (git worktree add --detach /home/user/objectui-cr7533 6cad675a6, pnpm install --frozen-lockfile exit 0), base faces taken from 78a3cc23 (blob-identical to merge-base fe4e7a9e8 for both touched source files: zod d1d427db…, ts 58850203…). Worktree removed after review.


Finding 1 — a compile-time pin bites for a different mechanism than its comment names

File:packages/types/src/__tests__/tree-view-data-optional-6939.test.ts:60-67

 * - member DELETED -> falls through `BaseSchema`'s `[key: string]: any`
* to `any`, and `Equal< any, … >` is false -> red

That is not what happens. BaseSchemadeclaresdata?: any (packages/types/src/base.ts:183), so a twin without its own data member resolves ['data'] to the inherited declared member, not to the index signature. The pin still reddens (inherited member is any), but the comment records the wrong mechanism — and this file's own header paragraph, the TS doc comment on data, and the zod .describe() all state the correct one, so the pin's comment contradicts the rest of the PR.

Measurement (tsc --ignoreConfig --noEmit --strict … --types react src/__cr_probe__/mechanism.probe.ts run inside packages/types):

typeWithoutIndexSignature<D>={[KinkeyofDasstringextendsK ? never : numberextendsK ? never : K]: D[K]};// 1. `data` IS a declared member of BaseSchema (survives index-signature removal)exporttype_DataIsDeclaredOnBase=Expect<Equal<'data'extendskeyofWithoutIndexSignature<BaseSchema> ? true : false,true>>;exporttype_SentinelIsNotDeclared=Expect<Equal<'undeclaredTreeKey6939'extendskeyofWithoutIndexSignature<BaseSchema> ? true : false,false>>;// 2. twin without its own `data`: still a DECLARED member, and `any`interfaceTwinWithoutDataextendsBaseSchema{type: 'tree-view';nodes?: TreeNode[]}exporttype_InheritedDataIsAny=Expect<Equal<TwinWithoutData['data'],any>>;exporttype_InheritedDataStillDeclared=Expect<Equal<'data'extendskeyofWithoutIndexSignature<TwinWithoutData> ? true : false,true>>;// 3. same on a base with NO index signature at all: still `any`, from the inherited member aloneinterfaceTwinOnBaseNoIndexextendsWithoutIndexSignature<BaseSchema>{type: 'tree-view';nodes?: TreeNode[]}exporttype_AnyWithoutAnyIndexSignature=Expect<Equal<TwinOnBaseNoIndex['data'],any>>;// @ts-expect-error — an UNDECLARED key on that base is an error, not `any`exporttype_UndeclaredIsErrorWithoutIndex=TwinOnBaseNoIndex['undeclaredTreeKey6939'];
PROBE_CLEAN_EXIT=0
--- control: expectation 2 flipped to `false` ---
src/__cr_probe__/mechanism.flipped.ts(21,50): error TS2344: Type 'false' does not satisfy the constraint 'true'.
FLIPPED_CLEAN_EXIT=2

Line 3 is decisive: with the index signature stripped entirely, ['data'] is still any. The index signature is not on the path.

Minimal fix: replace the two comment lines with
member DELETED -> resolves to the INHERITED \BaseSchema.data?: any` (base.ts:183) — `any`, and `Equal< any, … >` is false -> red. (The identical wrong sentence does *not* appear in undeclared-but-consumed-keys-6150.test.ts:91-94; there the keys are genuinely undeclared on BaseSchema, so the index-signature account is correct for them. It is wrong only for data`.)


Finding 2 — a published doc now states a contract fact this PR made false

File:content/docs/api/schema-reference.md:527 (unchanged by the PR; the PR touches 5 files, none under content/):

| `data` | `TreeNode[]` | **Required.** Nested tree data. Each node has `id`, `label`, optional `icon` and `children`. |

Before this PR that row was true; after it, the published API reference says data is required while the published type says data?: TreeNode[] and the published validator accepts documents without it. No gate covers the row: scripts/check-doc-component-types.mjs reads this file only for its type vocabulary (its exemption block at :410), and check:doc-snippets compiles ts/tsx fences, not property tables (check:doc-types exit 0, check:doc-fences exit 0 at head — both green with the stale row). This card's two landed siblings corrected the docs page that published the old required-ness (tooltip.mdx in #7456; plugin-map.mdx and the gantt page in #7471); this PR leaves its equivalent.

Measurement:

$ grep -n "| \`data\` | \`TreeNode\[\]\`" content/docs/api/schema-reference.md # at 6cad675a6
527:| `data` | `TreeNode[]` | **Required.** Nested tree data. …
$ git diff --stat fe4e7a9e8 6cad675a6 -- content/
(empty)

(content/docs/components/data-display/tree-view.mdx:28-31 already reads data?: TreeNode[] / nodes?: TreeNode[], so the component page is consistent; the API reference is the one that is not.)

Minimal fix: rewrite the row, e.g.
| \data` | `TreeNode[]` | Optional. Nested tree data, read only when `nodes` is absent (the renderer reads `nodes` first — objectui#6939). |— and, since the table lists neithernodesnortitle, a nodes` row would make the JSON example above it honest, but that omission predates this PR (#6150) and is not part of this finding.


Finding 3 — the changeset's "WIDENING, on both faces" is wrong on the reader side of the TS face, and the changeset is where it needs to be right

File:.changeset/6939-tree-view-nodes-mirror.md:24-29

**This is a WIDENING, on both faces.** `data` goes from required to optional on
the mirror and on the TypeScript twin in the same stroke; nothing that validated
before validates less.

For the validator and for an author of the TS type, true. For a reader of the published TS twin it is a narrowing: TreeViewSchema['data'] goes from TreeNode[] to TreeNode[] | undefined, and code that relied on presence stops compiling. The PR body says this ("the honest half of the same fact … its own read is || []-guarded") — the changeset, which is the artefact that becomes the CHANGELOG, says the opposite. AGENTS.md §版本号策略 is explicit that breaking semantics are stated in the changeset body.

Measurement (consumer probe against the published entry, tsc --ignoreConfig --noEmit --strict …):

importtype{TreeViewSchema,TreeNode}from'…/packages/types/src/index';exportfunctioncount(schema: TreeViewSchema): number{constnodes: TreeNode[]=schema.data;returnnodes.length+schema.data.length;}
--- at HEAD (data?: TreeNode[]) ---
consumer.probe.ts(4,9): error TS2322: Type 'TreeNode[] | undefined' is not assignable to type 'TreeNode[]'.
consumer.probe.ts(5,25): error TS18048: 'schema.data' is possibly 'undefined'.
HEAD CONSUMER_PROBE_EXIT=2
--- at BASE faces (data: TreeNode[]) blobs: 588502039b4df4ca47c150317474e4e61882a3b5 ---
BASE CONSUMER_PROBE_EXIT=0

On the bump:patch stands. scripts/check-changeset-no-major.mjs forbids only major (exit 0 at head: "No changeset declares a major bump."); the maintainer ruling on #6939 (comment 5510084784) grades this exact class "patch where the accept set only widens toward what already renders"; and both landed siblings moved a required TS member to optional under patch (objectName in .changeset/6939-objectql-record-source-refinement.md, children in .changeset/6939-overlay-trigger-mirror.md). The #6318 reviewer's patch→minor condition predates that ruling and concerned union membership, not optionality. So the grade is what the repo's rules call for; the description is not.

Minimal fix: replace the bolded sentence with something like: "The accept set widens on both faces for authors. For a reader of the TypeScript twin this is a narrowing — data is now TreeNode[] | undefined, so an unguarded schema.data read needs a guard; the only in-repo reader (tree-view.tsx:105) already has one (|| [])." Grade unchanged.


What I measured that checks out (so the adopter can see the FAIL is not about the change)

Accept set, both directions — NEWLY_FAIL = 0. Own census over every tracked *.json/*.yaml/*.yml (664 files, 664 parsed, 479 with a root type string), each root run through safeValidateSchema and every tree-view node (root or nested) through TreeViewSchema.safeParse, via tsx on source (packages/types/src/zod/index.zod.ts), at head and with both faces restored to 78a3cc23:

head: root-union ok=392 fail=91 base: ok=385 fail=98
NEWLY_PASS (base refused, head accepts): 4 tracked files at root —
components-data-display-tree-view/{deep-nesting,file-tree,org-chart,sidebar-navigation}.json
base issue on all four: ': Invalid input' (root) / 'data: Invalid input: expected array, received undefined' (member)
NEWLY_FAIL (base accepted, head refuses): 0

So the PR's "four" is exact for the objectui check predicate. The complete member-level count is larger, and the PR should be read as having counted at root level: a fifth tracked node flips — the nested tree-view in components-complex-resizable/editor-interface.json (/panels/0/content/0/children/1, nodes-only) — while its enclosing resizable document stays refused at both revs for an unrelated reason (panels.0.id, panels.1.id missing). Two inline TS literals also flip: { type: 'tree-view', bind: 'treeNodes' } (shadowed-renderer-behaviour.test.tsx:119) and the registration's own defaultProps; the data-spelled packages/types/examples/data-display-examples.json entry and TREE_CONTROL = { type, data: [] } stay accepted (True→True). The implementer's own #6951 comment names the nested one, so this is a counting-level difference, not an unknown.

Ablation reproduces the PR's prediction exactly. Base faces on disk (blobs d1d427db…/58850203…, anchors optional-zod 0 / required-zod 1 / optional-ts 0 / required-ts 1), vitest run on the two new files:

Tests 8 failed | 25 passed (33) VITEST_BASE_EXIT=1
× a tree-view with NO data source at all is legal… × the spelling the renderer reads FIRST now parses on its own
× …and the value SURVIVES the parse × both spellings validate — the accept set widened…
× {deep-nesting,file-tree,org-chart,sidebar-navigation} validates under safeValidateSchema

The four render-identity, four anti-vacuity, four "changes no pixel", "keys are DECLARED" (carrier {type,title,data:[],nodes}), "UNDECLARED key admitted" (carrier {type,data:[],[SENTINEL]}), and "BaseSchema alone would have admitted both" controls all stayed green — the rewritten controls from assertion 7 do redden only for their own reason. pnpm --filter @object-ui/types type-check under base faces: exit 2 with exactly TS2344 at :72 and TS2741 Property 'data' is missing at :85. Restore proven by state: blobs back to a0f1499d…/e4f16b12…, anchors 1/1, git status --porcelain empty.

Head is green everywhere I ran it.vitest run on the two new pins + undeclared-but-consumed-keys-6150 + safe-validate-corpus-6318: 4 files / 118 tests; zod-mirror-parity + handler-keys-json-refusal-6124: 2 files / 256 tests; the whole packages/types/ + examples/schema-catalog/ population plus shadowed-renderer-behaviour: 121 files / 3644 tests, exit 0. pnpm --filter @object-ui/types type-check exit 0 (clean tree, 0 error TS), with --listFiles showing both new test files in their tsconfig.test.json programs. Gates: check:control-bytes, check:doc-types, check:doc-fences, check-changeset-no-major, check-changeset-presence ("3 source file(s) of 1 released package… 1 changeset(s)") all exit 0. eslint on the four touched files: 0 errors; both new files 0 warnings; 30 + 1 warnings on the two source files (count matches the PR's 31).

Assertion 8 — measured, and it could not have caught anything.turbo run build --filter=@object-ui/example-schema-catalog...: 29 successful / 29 total; then pnpm --filter @object-ui/example-schema-catalog type-check (tsc --noEmit && tsc -p tsconfig.test.json): exit 0, 0 errors, and pnpm --filter @object-ui/components type-check: exit 0. The narrowing is closed.

Compile-time pins — what each catches (probe pins.probe.ts, exit 0):_TreeDataIsOptionalTreeNodes catches data re-required (TS2344, measured), data deleted (inherited any — Finding 1), and an element-type change. _TreeNodesStillTreeNodes / _TreeTitleStillString are optionality-blind by construction (NonNullable<Req['nodes']> equals NonNullable<Opt['nodes']> — measured true): they catch deletion and element-type change, not nodes/title becoming required; they are inherited from #6150 unchanged, so this is a limitation to know, not a defect of this PR. NODES_ONLY_DOCUMENT catches any key re-required (TS2741, measured) but cannot catch nodes/title deleted or renamed — { …, nodez: […] } and { …, undeclaredTreeKey6939: 1 } both compile through BaseSchema's index signature (measured); a wrong-typed declared key still errors (measured). The runtime pins cover the deletion case (data: 'not-an-array' refused AT data; BaseSchema alone accepts it — both measured green at head).

Other comment mechanisms:.passthrough()base.zod.ts:212; BaseSchema.data: z.any().optional()base.zod.ts:121; data?: anybase.ts:183; [key: string]: anybase.ts:409; renderer read at tree-view.tsx:104-106, inputs/defaultProps spelled nodes. All as stated. Parity ledger: TreeViewSchema's only entry is RuntimeOnlyDeclared: 'onNodeClick' (zod-mirror-parity.test.ts:1239); the compile-time NarrowerThanDeclared comparison does see optionality ([D[K]] extends [InputOf<Shape[K]>]), which is why both faces had to move together — and did (type-check exit 0 covers it).

Assertion 4 (no refinement) — sound.{ type: 'tree-view' } is accepted at head and { type: 'tree-view', data: [] } was accepted at base (census, both measured); the renderer's || [] guard means neither draws anything the other does not, so "at least one of" never guaranteed a non-empty tree. The census also shows why a two-limb refinement would be wrong: { type: 'tree-view', bind: 'treeNodes' } — a real, rendering document — flips refused→accepted here, and a nodes/data-only rule would refuse it. The #6939 maintainer ruling names a refinement for object-map/object-gantt only; #6951's comment 5527723261 recommending one is a triage recommendation on a needs-user-decision card, not a ruling.

Assertion 5 — confirmed stale half. Base data-display.ts:1220-1236 already declares nodes?/title? with #6150's comments; #6150's pins pass at head.

Merge target:git merge-tree --write-tree origin/main 6cad675a6 exit 0, no conflicts; origin/main's packages/react/src/index.ts still export * from './schema-input.js' (the new test's toRenderableSchema/SchemaRenderer imports resolve).

NARROWINGS

  • The card's "14 elements" — not reproduced or attempted; it comes from the docs-gallery harness. I confirmed only that the PR's 28 / 28 / 12 / 34 reproduce in this environment (all four render-identity cases pass at head).
  • eslint warnings on touched lines — I read the counts (30 + 1, both new files 0), not the line positions; whether all 31 sit outside the hunks is the PR's claim, not mine.
  • The PR's own reported runs (pnpm exec vitest run over its six files, its own ablation and blob hashes, its lock timings) — not re-read; superseded by my own runs above.
  • objectui check (node packages/cli/dist/cli.js check) — not run; my census reimplements its validation arm without the structural arm, so my "4 tracked files flip at root" is on that predicate, not on the CLI's printed count.
  • Whole-repo eslint, check:doc-snippets, check:readme-exports, Bundle Analysis, CI convergence — not run; CI-owned. At review time CI showed 27 completed checks all success/skipped with Type Check and the four Test shards still in progress.
  • The Two alias pairs on TextSchema and TreeViewSchema are now DECLARED in both spellings — and TreeViewSchema.data is required while nodes is the documented one (deferred rulings carried out of #6150) #6951 pair ruling — unmade at review time; the PR's revert-if-data-wins caveat is accurate but not something I can measure.

派发席处置

补丁轮,三条全修,⛔ 不放行。⚠️ Finding 2 是本席在席内核验时漏掉的真缺陷:本席查了 JSON 文档与 TS 消费者,没查声明合约事实的已发布散文,而本卡两个已落兄弟组(#7456#7471)恰恰都改了各自的文档页——本 PR 偏离了它自己这张卡的既定做法。

⛔ 注意 content/docs/api/schema-reference.md不是content/docs/releases/,改它不触犯「代码 PR 不得改发布说明」那条。

⭐ 三条 FAIL 都不要求重测改动本身:审查者已把 accept set 双向(NEWLY_FAIL=0)、消融红集、两面同步、以及本 PR 自己申报为未测的 example-schema-catalog type-checkexit 0,收窄已闭合)全部测过。patch 档位经三条独立支撑维持不变

本 PR 的 CI 在 6cad675a6 上已 32/32 全绿(其中 Lint run 33785853068、Docs Links run 33785852959 是按工作流正查确认的——get_check_runs 只返回了 30 条)。


Generated by Claude Code

Contract review of PR 7533 passed the change on every axis and failed three
statements ABOUT it. None of the code under repair moves here.
1. The compile-time pin's comment named the wrong mechanism. Deleting the
member does not fall through BaseSchema's `[key: string]: any`; it resolves
to the INHERITED declared `BaseSchema.data?: any` (base.ts:183), because a
declared member outranks an index signature. Verified independently before
accepting the finding: strip the index signature with the homomorphic
keyof-remap and `['data']` is still `any`, while an index-signature-only key
stops resolving at all -- both controls lit. The pin's verdict is unchanged;
only the reason it gives was wrong, and it contradicted the file header, the
TS-face doc comment and the mirror's describe().
2. `content/docs/api/schema-reference.md` still called `data` **Required**,
which this branch made false, and no gate covers that row. Corrected, and a
`nodes` row added beside it so the corrected sentence does not reference a
key the table never lists. The two sibling groups of this card each updated
their own doc page; this one had missed it.
3. The changeset called the change a widening "on both faces" without saying
that a READER of the TypeScript twin sees a narrowing: `data` is now
`TreeNode[] | undefined`, so an unguarded `schema.data` read stops compiling.
The PR body already said this; the changeset is the artefact that becomes the
CHANGELOG, so it is the one that had to. Level is unchanged and still patch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@claude

claudeBot commented Sep 3, 2026

Copy link
Copy Markdown
ContributorAuthor

Patch round — all three FAIL findings addressed at 794aa2ad4. Nothing under repair moved.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. One commit
(5d7e47fa7) plus a merge of origin/main at d04e79a80 (merge 794aa2ad4, no
conflicts). ⛔ Changeset level untouched, still patch. ⛔ nodes / title
declarations untouched. ⛔ No re-测 of the change itself.

Finding 1 — I checked it before accepting it, and the review is right

I did not take the mechanism on trust, because the instruction was to say so if I measured
otherwise. A throwaway type-level probe, run through pnpm --filter @object-ui/types type-check and then deleted, with both controls lit:

proberesult
['data'] with the index signature presentany
['data'] with the index signature STRIPPED (the homomorphic keyof-remap zod-mirror-parity.test.ts uses)still any
data survives the strippingyes, so it is a declared member
CONTROL: an index-signature-only key survives the strippingno — so the stripping really is doing something
CONTROL: that same key reads as any while the index signature is presentyes

All five compiled, exit 0. BaseSchema carries bothdata?: any (base.ts:183) and
[key: string]: any (base.ts:409), and the declared member — inherited or not — outranks
the index signature. So the comment was wrong and the review's replacement is right;
the pin's verdict never depended on it. Replacement text taken as given, plus a short
paragraph recording the measurement so the next reader does not have to redo it.

undeclared-but-consumed-keys-6150.test.ts:91-94 deliberately NOT touched — the same
sentence is true there, because those 13 keys genuinely are undeclared on BaseSchema.

Finding 2 — content/docs/api/schema-reference.md:527

Corrected to the suggested text. I also added onenodes row directly above it, and
that is a judgment call worth flagging rather than burying: the corrected sentence says
"read only when nodes is absent", and nodes was not in that table, so the correction
would otherwise have created a dangling reference. One row, not a documentation sweep —
title is still absent and I left it alone as the #6150 leftover the review says it is.
The sentence about node shape (id, label, optional icon and children) moved onto
the nodes row rather than being dropped.

content/docs/releases/ not touched.

ℹ️ Two more census data points, recorded not acted on: the JSON example immediately above
that table also authors data, as does the tree-view entry in
packages/types/examples/data-display-examples.json. Both are still legal after this
branch, so neither is a false statement — but if the #6951 retirement lands on nodes,
they are two of the documents that have to move.

Finding 3 — .changeset/6939-tree-view-nodes-mirror.md

Split into the two halves that are separately true: authors get a widening on both faces;
a reader of the TypeScript twin gets a narrowing, TreeNode[] | undefined, with the
TS2322 + TS18048 consumer-probe result named, the one in-repo reader shown to be
already guarded, and out-of-repo unguarded consumers named as the population the paragraph
exists for. Level unchanged, with the three reasons it stays patch stated in place.

Convergence verification, at 794aa2ad4 (post-merge head)

Heavy runs serialised through the shared verify lock; exit codes captured to files before
any pipe.

checkexit
pnpm exec vitest run over both new test files0 — "Test Files 2 passed (2)"
pnpm --filter @object-ui/types type-check0
pnpm check:doc-types0
pnpm check:doc-fences0
node scripts/check-control-bytes.mjs0 (6206 tracked files)
node scripts/check-changeset-no-major.mjs0

⚠️ Worth recording about check:doc-types: its counters are byte-identical before and
after my documentation edit — same 188 documents, same 4 key tables, same 24 rows, same 45
table keys. That is the review's own point measured from the other side: the gate never
looked at this table, so the stale row could not have been caught by it.

Thank you for the census — the root-level 4 is exactly what I claimed and the
member-level flips (the nested editor-interface.json node, and the real bind-only
document at shadowed-renderer-behaviour.test.tsx:119) are a level distinction I had not
made. I have left the counts alone as instructed.

Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

在席处置:补丁轮已核,放行 —— 剥标签 + ready + auto-merge SQUASH

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。链条:隔离审查 FAIL 三条 → 补丁轮 → 在席按内容核实 → 放行。

决定性的一读:被审查过的改动没有动

补丁轮之后我没有重跑合约审查,依据是这个:

 6cad675a6 (被审的 head) → 794aa2ad4 (补丁后)
data-display.zod.ts a0f1499dff920cfa1de27573396e1c2734674d7d IDENTICAL
data-display.ts e4f16b1261cfd621cac6d83a061041967768a5ec IDENTICAL

⇒ 两个被审源文件逐字节相同 ⇒ 审查者对改动本身的裁断(accept set 双向 NEWLY_FAIL=0、消融红集吻合、两面同步、parity ledger、干净 merge)原样成立,无需重审。补丁提交 5d7e47fa7 只碰了三个文件——正是三条 finding 各自的落点(changeset、content/docs/api/schema-reference.md、测试注释);diff stat 看着大是因为它同时 merge 了 origin/main

三条修正逐一核过

⭐ 一条从反面得来的读数,值得留档

实现者报告:check:doc-types 在它改动文档前后计数器逐字节相同(同样 188 文档 / 4 个 key table / 24 行 / 45 个 table key)。它把这条报成「一个对我修的那行什么也没说的绿」,而不是报成覆盖率——这正是审查者「no gate covers this row」从另一侧的验证。

放行状态

CI 在当前 head 794aa2ad4completed / success⚠️ 不是旧 head 6cad675a6 那个绿——按 status=completed 过滤会返回旧 head 的绿,那对当前 head 什么也没说)。已剥 needs:contract-review、ready、auto-merge SQUASH,并按 base-sha 链正向确认入队(队首,base = f0f774b0d)。

⚠️ 放行动作一度因 GitHub 配额半完成(标签已剥、undraft 失败),已排定时提醒跟踪并在配额恢复后补完 —— ⛔ 没有静默停在半完成状态。

⛔ 未随本 PR 解决的

#6951needs-user-decisionnodes / data 的 ADR-0049 退役选择未裁。本 PR 不做那个选择(两种拼法都仍解析)。若最终 data 胜出,本分支需连同其余一并回退。相关记录已留在 #6951(含本席对自己一处口径错误的订正)。


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939) - #7533

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-6939-group2-treeview-mirror
Sep 3, 2026
Merged

fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939)#7533
os-project-manager merged 3 commits into
mainfrom
claude/issue-6939-group2-treeview-mirror

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Part of #6939group 2 of 8 (tree-view). Deliberately worded to leave the card open: four
of that card's eight groups are still unimplemented, so it must stay open.

⚠️Draft + needs:contract-review on purpose. This moves an accept surface;
the dispatching seat reviews before it lands. Please do not mark ready or enable
auto-merge.

The repair

TreeViewSchema REQUIRED data — the limb the renderer reads third:

const rawNodes = boundData || schema.nodes || schema.data || [];
// packages/components/src/renderers/data-display/tree-view.tsx:105

The registration's own inputs and defaultProps spell it nodes, and the four
components-data-display-tree-view/* catalog entries are those defaultProps.
data is now optional on the zod mirror and on the TypeScript twin, in the same
stroke.

What I re-measured, and where the card is stale

Card readings re-derived on origin/main at fe4e7a9e8, through a bare
SchemaRenderer harness:

card saysre-measuredverdict
4 files4 (deep-nesting, file-tree, org-chart, sidebar-navigation), all type,title,nodes✅ confirmed
zod demands data (required)nodes-only parse false, data-only parse true, bare {type} parse false✅ confirmed
renderer reads boundData or schema.nodes or schema.dataread site intact at :105; title at :115 / :117✅ confirmed
render after "correcting" the fixture: identical — 14 elementsidentical: ✅ (element count, tag census and a SHA-256 of textContent, all four tiles). 14: not reproduced — this harness measures 28 / 28 / 12 / 34⚠️ partly stale

Element counts are harness-bound (the docs-gallery harness gives different absolutes
for the same tile), so the pin records identity within one harness and carries the
numbers I measured rather than the card's.

⚠️The card's stated disposition is also half-landed already. It asks to "declare
nodes and title, and stop requiring data". objectui#6150 landed the declaration
half after the card was filed — both keys are declared on both faces today, and #6150
wrote in as many words that relaxing data was a separate accept-set change. This PR
is only that remaining half; nodes and title are untouched.

data stays declared rather than being deleted

The intuitive read of "the renderer prefers nodes" is to drop data. That is wrong,
and measurably: BaseSchema already declares data (z.any().optional(), data?: any
on the TS face), so removing the member would not reject the key — it would admit it
unvalidated while the renderer went on reading it. The pin
control: BaseSchema alone would have admitted both of those makes the difference
visible: the base object accepts the two values the member refuses.

No refinement was added

Unlike this card's object-map / object-gantt group, which added an "at least one of"
refinement. A tree-view carrying no data source at all becomes legal here, and that
admits no new rendering outcome: { data: [] } was already legal and already drew
the same empty tree. A refinement would forbid a spelling of an empty state the contract
already permits rather than buy a guarantee.

Verification

All runs at 6cad675a6; heavy runs serialised through the shared verify lock.

  • pnpm exec vitest run over the two new pins plus undeclared-but-consumed-keys-6150,
    zod-mirror-parity, safe-validate-corpus-6318, handler-keys-json-refusal-6124
    6 files, 374 tests, all passing. The parity ledger needed no edit: tree-view's
    only entry in any of the three ledgers is RuntimeOnlyDeclaredonNodeClick, and
    this change moves optionality, not key membership.
  • pnpm --filter @object-ui/types type-check — green (covers tsconfig.test.json,
    so both compile-time pins are checked).
  • pnpm --filter @object-ui/components type-check — green, after building the
    9-package closure. That is the only TS-face consumer of TreeViewSchema outside
    packages/types, by exhaustive grep.
  • eslint on the four touched files: exit 0, 0 errors, 31 warnings — all pre-existing
    no-explicit-any on lines outside every hunk.
  • check:control-bytes, check:doc-types, check:doc-fences,
    check-changeset-no-major — green.

Reversible verification (both faces restored to fe4e7a9e8, then restored back)

Mutation proven on disk by blob hash and by anchor counts in both directions
(optional-anchor 1 to 0, required-anchor 0 to 1, on both files). Restore proven by
state: on-disk blobs equal the HEAD blobs, git diff HEAD empty, git status
empty. Absolute paths, trap on EXIT INT TERM.

Predicted red set and measured red set agreed:

  • vitest, mutated: exit 1, 8 red — the three "nodes-only is legal" pins, the four
    validates under safeValidateSchema cases, and both spellings validate.
  • vitest, mutated: still green — all four render-identity cases, all four
    anti-vacuity cases, all four "correcting it to data changes no pixel" cases, and the
    whole "data is still validated" group. That asymmetry is the point: the pin is on
    the validator, and the renderer did not move.
  • type-check, mutated: exit 2 on exactly the two compile-time pins vitest cannot
    see — TS2344 on the optionality equality and TS2741 (Property 'data' is missing)
    on the nodes-only document literal.
  • restored: vitest exit 0 (5 files), type-check exit 0.

A first ablation round found two of my own controls reddening for the wrong reason
(their carrier documents were nodes-only, so the pre-repair mirror refused the carrier
itself). Both were rewritten onto data-bearing carriers that are legal in either state,
and the second round shows them staying green while the load-bearing pins still redden.

Declared narrowing

pnpm --filter @object-ui/example-schema-catalog type-check was not measured here.
It needs a 30-package dist closure, and the shared verify lock refused twice at its
9-minute budget with another seat holding it for 550s. Its local output is a precondition
failure, not a verdict: 67 errors, 63 of them TS2307/TS2882 "cannot find module",
the other 4 consequential implicit-any in files this PR does not touch — and the two
lines naming this PR's own file are that same "cannot find module @object-ui/components
/ @object-ui/react" class that every sibling test file reports. Nothing in it is about
this change. That file's behaviour is proven by executing it (green, and red under
ablation); CI type-checks the farm regardless.

Narrowing evidence for the lint side: the linted population is each package's own
eslint .; this diff touches files in two packages and all four were linted (count read
from --format json); and eslint.config.js configures no type-aware linting
(projectService 0, parserOptions 0, against a lit control of languageOptions 1), so
this diff cannot move the verdict on a file it does not touch.

Changeset

@object-ui/types: patch. The accept set only widens — no document that validated
before validates less, including the data-spelled tree-view in
packages/types/examples/data-display-examples.json — and this is the same shape as the
two sibling groups of this card that already landed on patch. The TS twin's
required-to-optional move is the honest half of the same fact: the renderer never
guaranteed data was present, its own read is || []-guarded, and the single consumer
type-checks clean.

⚠️ Interaction with #6951 — the reviewer needs to see this

Found while de-duplicating an unrelated observation, not supplied by the dispatch.
#6951 is open and carries needs-user-decision, and it explicitly folds in this
exact question:

data is required on TreeViewSchema while nodes is the spelling the docs name
as canonical.
[...] Whichever way the pair ruling goes, the required-ness has to move
with it. That is why it is one card.

#6951's own ruling is an ADR-0049 enforce-or-remove choice: retire one spelling of the
nodes / data pair. That choice has not been made. This PR does not make it — it
retires neither spelling, and both keep parsing. What it does do is presume the direction
that every in-repo artefact already names: the doc comments name nodes the winner,
#6951 itself says "nodes is the winner named in the doc comment", the registration's
inputs and defaultProps spell it nodes, the docs page lists both as optional, and
the four catalog entries author nodes.

So the two possible rulings land differently on this PR:

  • nodes wins (what every artefact points to) — this PR is a strictly smaller step in
    the same direction and stays correct as-is.
  • data wins — this PR would have to be reverted along with the rest.

⇒ Flagging rather than choosing. If the reviewing seat would rather this waited on #6951,
the branch is a clean single commit and nothing else depends on it. I have also left a
cross-reference on #6951 so this does not become the invisible interim that card was
filed to prevent.

Out of scope

⛔ Only group 2. Group 5 (chart) lives in the same file and is untouched; group 3
(kanban) is a genuine fork awaiting a ruling; groups 4 and 7 are unassigned. No catalog
fixture is edited — the card is explicit that the fixtures are the side that is right.

Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`TreeViewSchema` required `data`, which the renderer reads THIRD:
const rawNodes = boundData || schema.nodes || schema.data || [];
// packages/components/src/renderers/data-display/tree-view.tsx:105
The registration's own `inputs` and `defaultProps` spell it `nodes`, and the
four `components-data-display-tree-view/*` catalog entries ARE those
`defaultProps`, so `safeValidateSchema` refused every one of them while the
renderer drew them correctly. Re-measured on origin/main at fe4e7a9: four
refusals, and renders byte-identical under either spelling (28 / 28 / 12 / 34
elements, same tag census, same textContent SHA-256) -- objectui#6318's own
triage test for "the schema was the wrong side".
`data` becomes optional on the zod mirror and on the TypeScript twin in the
same stroke. It stays DECLARED rather than deleted: BaseSchema already declares
`data` as `z.any().optional()`, so removing the member would not reject the key
-- it would admit it unvalidated while the renderer went on reading it.
No refinement is added, unlike this card's object-map / object-gantt group: a
tree-view with no data source now validates, and that admits no new rendering
outcome, since `{ data: [] }` was already legal and already drew the same empty
tree.
`nodes` and `title` are objectui#6150's declarations and are untouched; that
card declared the reads and said in as many words that relaxing `data` was a
separate accept-set change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

合约审查裁断:FAIL —— 逐字采信

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。本席是 opus,不在 CONTRACT_REVIEW_TIER,故本 Clause-② yes 的 PR 由隔离审查者出裁。简报只含卡片、裁决与本 PR,⛔ 不含派发指令、⛔ 不含本席结论。

转录档位核验(先决条件):对审查者转录只提取 harness 生成的 model 字段(⛔ 不对自由文本搜关键词——一句「未检出 X」自身含 X,会污染下一轮检测)。248 行、0 行不可解析、119 个 assistant 轮次,全部 claude-fable-5-1,无 fallback 证据 ⇒ 裁断在档,逐字采信。⛔ 未改写、未删节、未润色。

⚠️ 三条 FAIL 全部是 PR 关于改动的陈述,不是改动本身;审查者对改动本身逐轴测过并通过(NEWLY_FAIL=0、消融红集吻合、两面同步、parity ledger、以及本 PR 声明未测的那个 type-check)。


VERDICT: FAIL

The code change itself checks out on every axis I could measure — accept-set direction (NEWLY_FAIL=0), ablation red set, both faces moving together, parity ledger, both heavy type-checks including the one the PR declared unmeasured, and a clean merge onto the current origin/main. The FAIL is on three items the PR says about the change that are wrong or undisclosed, each with a one-to-two-line fix. None requires re-measuring the change.

All measurements were mine, on a detached worktree at 6cad675a6 (git worktree add --detach /home/user/objectui-cr7533 6cad675a6, pnpm install --frozen-lockfile exit 0), base faces taken from 78a3cc23 (blob-identical to merge-base fe4e7a9e8 for both touched source files: zod d1d427db…, ts 58850203…). Worktree removed after review.


Finding 1 — a compile-time pin bites for a different mechanism than its comment names

File:packages/types/src/__tests__/tree-view-data-optional-6939.test.ts:60-67

 * - member DELETED -> falls through `BaseSchema`'s `[key: string]: any`
* to `any`, and `Equal< any, … >` is false -> red

That is not what happens. BaseSchemadeclaresdata?: any (packages/types/src/base.ts:183), so a twin without its own data member resolves ['data'] to the inherited declared member, not to the index signature. The pin still reddens (inherited member is any), but the comment records the wrong mechanism — and this file's own header paragraph, the TS doc comment on data, and the zod .describe() all state the correct one, so the pin's comment contradicts the rest of the PR.

Measurement (tsc --ignoreConfig --noEmit --strict … --types react src/__cr_probe__/mechanism.probe.ts run inside packages/types):

typeWithoutIndexSignature<D>={[KinkeyofDasstringextendsK ? never : numberextendsK ? never : K]: D[K]};// 1. `data` IS a declared member of BaseSchema (survives index-signature removal)exporttype_DataIsDeclaredOnBase=Expect<Equal<'data'extendskeyofWithoutIndexSignature<BaseSchema> ? true : false,true>>;exporttype_SentinelIsNotDeclared=Expect<Equal<'undeclaredTreeKey6939'extendskeyofWithoutIndexSignature<BaseSchema> ? true : false,false>>;// 2. twin without its own `data`: still a DECLARED member, and `any`interfaceTwinWithoutDataextendsBaseSchema{type: 'tree-view';nodes?: TreeNode[]}exporttype_InheritedDataIsAny=Expect<Equal<TwinWithoutData['data'],any>>;exporttype_InheritedDataStillDeclared=Expect<Equal<'data'extendskeyofWithoutIndexSignature<TwinWithoutData> ? true : false,true>>;// 3. same on a base with NO index signature at all: still `any`, from the inherited member aloneinterfaceTwinOnBaseNoIndexextendsWithoutIndexSignature<BaseSchema>{type: 'tree-view';nodes?: TreeNode[]}exporttype_AnyWithoutAnyIndexSignature=Expect<Equal<TwinOnBaseNoIndex['data'],any>>;// @ts-expect-error — an UNDECLARED key on that base is an error, not `any`exporttype_UndeclaredIsErrorWithoutIndex=TwinOnBaseNoIndex['undeclaredTreeKey6939'];
PROBE_CLEAN_EXIT=0
--- control: expectation 2 flipped to `false` ---
src/__cr_probe__/mechanism.flipped.ts(21,50): error TS2344: Type 'false' does not satisfy the constraint 'true'.
FLIPPED_CLEAN_EXIT=2

Line 3 is decisive: with the index signature stripped entirely, ['data'] is still any. The index signature is not on the path.

Minimal fix: replace the two comment lines with
member DELETED -> resolves to the INHERITED \BaseSchema.data?: any` (base.ts:183) — `any`, and `Equal< any, … >` is false -> red. (The identical wrong sentence does *not* appear in undeclared-but-consumed-keys-6150.test.ts:91-94; there the keys are genuinely undeclared on BaseSchema, so the index-signature account is correct for them. It is wrong only for data`.)


Finding 2 — a published doc now states a contract fact this PR made false

File:content/docs/api/schema-reference.md:527 (unchanged by the PR; the PR touches 5 files, none under content/):

| `data` | `TreeNode[]` | **Required.** Nested tree data. Each node has `id`, `label`, optional `icon` and `children`. |

Before this PR that row was true; after it, the published API reference says data is required while the published type says data?: TreeNode[] and the published validator accepts documents without it. No gate covers the row: scripts/check-doc-component-types.mjs reads this file only for its type vocabulary (its exemption block at :410), and check:doc-snippets compiles ts/tsx fences, not property tables (check:doc-types exit 0, check:doc-fences exit 0 at head — both green with the stale row). This card's two landed siblings corrected the docs page that published the old required-ness (tooltip.mdx in #7456; plugin-map.mdx and the gantt page in #7471); this PR leaves its equivalent.

Measurement:

$ grep -n "| \`data\` | \`TreeNode\[\]\`" content/docs/api/schema-reference.md # at 6cad675a6
527:| `data` | `TreeNode[]` | **Required.** Nested tree data. …
$ git diff --stat fe4e7a9e8 6cad675a6 -- content/
(empty)

(content/docs/components/data-display/tree-view.mdx:28-31 already reads data?: TreeNode[] / nodes?: TreeNode[], so the component page is consistent; the API reference is the one that is not.)

Minimal fix: rewrite the row, e.g.
| \data` | `TreeNode[]` | Optional. Nested tree data, read only when `nodes` is absent (the renderer reads `nodes` first — objectui#6939). |— and, since the table lists neithernodesnortitle, a nodes` row would make the JSON example above it honest, but that omission predates this PR (#6150) and is not part of this finding.


Finding 3 — the changeset's "WIDENING, on both faces" is wrong on the reader side of the TS face, and the changeset is where it needs to be right

File:.changeset/6939-tree-view-nodes-mirror.md:24-29

**This is a WIDENING, on both faces.** `data` goes from required to optional on
the mirror and on the TypeScript twin in the same stroke; nothing that validated
before validates less.

For the validator and for an author of the TS type, true. For a reader of the published TS twin it is a narrowing: TreeViewSchema['data'] goes from TreeNode[] to TreeNode[] | undefined, and code that relied on presence stops compiling. The PR body says this ("the honest half of the same fact … its own read is || []-guarded") — the changeset, which is the artefact that becomes the CHANGELOG, says the opposite. AGENTS.md §版本号策略 is explicit that breaking semantics are stated in the changeset body.

Measurement (consumer probe against the published entry, tsc --ignoreConfig --noEmit --strict …):

importtype{TreeViewSchema,TreeNode}from'…/packages/types/src/index';exportfunctioncount(schema: TreeViewSchema): number{constnodes: TreeNode[]=schema.data;returnnodes.length+schema.data.length;}
--- at HEAD (data?: TreeNode[]) ---
consumer.probe.ts(4,9): error TS2322: Type 'TreeNode[] | undefined' is not assignable to type 'TreeNode[]'.
consumer.probe.ts(5,25): error TS18048: 'schema.data' is possibly 'undefined'.
HEAD CONSUMER_PROBE_EXIT=2
--- at BASE faces (data: TreeNode[]) blobs: 588502039b4df4ca47c150317474e4e61882a3b5 ---
BASE CONSUMER_PROBE_EXIT=0

On the bump:patch stands. scripts/check-changeset-no-major.mjs forbids only major (exit 0 at head: "No changeset declares a major bump."); the maintainer ruling on #6939 (comment 5510084784) grades this exact class "patch where the accept set only widens toward what already renders"; and both landed siblings moved a required TS member to optional under patch (objectName in .changeset/6939-objectql-record-source-refinement.md, children in .changeset/6939-overlay-trigger-mirror.md). The #6318 reviewer's patch→minor condition predates that ruling and concerned union membership, not optionality. So the grade is what the repo's rules call for; the description is not.

Minimal fix: replace the bolded sentence with something like: "The accept set widens on both faces for authors. For a reader of the TypeScript twin this is a narrowing — data is now TreeNode[] | undefined, so an unguarded schema.data read needs a guard; the only in-repo reader (tree-view.tsx:105) already has one (|| [])." Grade unchanged.


What I measured that checks out (so the adopter can see the FAIL is not about the change)

Accept set, both directions — NEWLY_FAIL = 0. Own census over every tracked *.json/*.yaml/*.yml (664 files, 664 parsed, 479 with a root type string), each root run through safeValidateSchema and every tree-view node (root or nested) through TreeViewSchema.safeParse, via tsx on source (packages/types/src/zod/index.zod.ts), at head and with both faces restored to 78a3cc23:

head: root-union ok=392 fail=91 base: ok=385 fail=98
NEWLY_PASS (base refused, head accepts): 4 tracked files at root —
components-data-display-tree-view/{deep-nesting,file-tree,org-chart,sidebar-navigation}.json
base issue on all four: ': Invalid input' (root) / 'data: Invalid input: expected array, received undefined' (member)
NEWLY_FAIL (base accepted, head refuses): 0

So the PR's "four" is exact for the objectui check predicate. The complete member-level count is larger, and the PR should be read as having counted at root level: a fifth tracked node flips — the nested tree-view in components-complex-resizable/editor-interface.json (/panels/0/content/0/children/1, nodes-only) — while its enclosing resizable document stays refused at both revs for an unrelated reason (panels.0.id, panels.1.id missing). Two inline TS literals also flip: { type: 'tree-view', bind: 'treeNodes' } (shadowed-renderer-behaviour.test.tsx:119) and the registration's own defaultProps; the data-spelled packages/types/examples/data-display-examples.json entry and TREE_CONTROL = { type, data: [] } stay accepted (True→True). The implementer's own #6951 comment names the nested one, so this is a counting-level difference, not an unknown.

Ablation reproduces the PR's prediction exactly. Base faces on disk (blobs d1d427db…/58850203…, anchors optional-zod 0 / required-zod 1 / optional-ts 0 / required-ts 1), vitest run on the two new files:

Tests 8 failed | 25 passed (33) VITEST_BASE_EXIT=1
× a tree-view with NO data source at all is legal… × the spelling the renderer reads FIRST now parses on its own
× …and the value SURVIVES the parse × both spellings validate — the accept set widened…
× {deep-nesting,file-tree,org-chart,sidebar-navigation} validates under safeValidateSchema

The four render-identity, four anti-vacuity, four "changes no pixel", "keys are DECLARED" (carrier {type,title,data:[],nodes}), "UNDECLARED key admitted" (carrier {type,data:[],[SENTINEL]}), and "BaseSchema alone would have admitted both" controls all stayed green — the rewritten controls from assertion 7 do redden only for their own reason. pnpm --filter @object-ui/types type-check under base faces: exit 2 with exactly TS2344 at :72 and TS2741 Property 'data' is missing at :85. Restore proven by state: blobs back to a0f1499d…/e4f16b12…, anchors 1/1, git status --porcelain empty.

Head is green everywhere I ran it.vitest run on the two new pins + undeclared-but-consumed-keys-6150 + safe-validate-corpus-6318: 4 files / 118 tests; zod-mirror-parity + handler-keys-json-refusal-6124: 2 files / 256 tests; the whole packages/types/ + examples/schema-catalog/ population plus shadowed-renderer-behaviour: 121 files / 3644 tests, exit 0. pnpm --filter @object-ui/types type-check exit 0 (clean tree, 0 error TS), with --listFiles showing both new test files in their tsconfig.test.json programs. Gates: check:control-bytes, check:doc-types, check:doc-fences, check-changeset-no-major, check-changeset-presence ("3 source file(s) of 1 released package… 1 changeset(s)") all exit 0. eslint on the four touched files: 0 errors; both new files 0 warnings; 30 + 1 warnings on the two source files (count matches the PR's 31).

Assertion 8 — measured, and it could not have caught anything.turbo run build --filter=@object-ui/example-schema-catalog...: 29 successful / 29 total; then pnpm --filter @object-ui/example-schema-catalog type-check (tsc --noEmit && tsc -p tsconfig.test.json): exit 0, 0 errors, and pnpm --filter @object-ui/components type-check: exit 0. The narrowing is closed.

Compile-time pins — what each catches (probe pins.probe.ts, exit 0):_TreeDataIsOptionalTreeNodes catches data re-required (TS2344, measured), data deleted (inherited any — Finding 1), and an element-type change. _TreeNodesStillTreeNodes / _TreeTitleStillString are optionality-blind by construction (NonNullable<Req['nodes']> equals NonNullable<Opt['nodes']> — measured true): they catch deletion and element-type change, not nodes/title becoming required; they are inherited from #6150 unchanged, so this is a limitation to know, not a defect of this PR. NODES_ONLY_DOCUMENT catches any key re-required (TS2741, measured) but cannot catch nodes/title deleted or renamed — { …, nodez: […] } and { …, undeclaredTreeKey6939: 1 } both compile through BaseSchema's index signature (measured); a wrong-typed declared key still errors (measured). The runtime pins cover the deletion case (data: 'not-an-array' refused AT data; BaseSchema alone accepts it — both measured green at head).

Other comment mechanisms:.passthrough()base.zod.ts:212; BaseSchema.data: z.any().optional()base.zod.ts:121; data?: anybase.ts:183; [key: string]: anybase.ts:409; renderer read at tree-view.tsx:104-106, inputs/defaultProps spelled nodes. All as stated. Parity ledger: TreeViewSchema's only entry is RuntimeOnlyDeclared: 'onNodeClick' (zod-mirror-parity.test.ts:1239); the compile-time NarrowerThanDeclared comparison does see optionality ([D[K]] extends [InputOf<Shape[K]>]), which is why both faces had to move together — and did (type-check exit 0 covers it).

Assertion 4 (no refinement) — sound.{ type: 'tree-view' } is accepted at head and { type: 'tree-view', data: [] } was accepted at base (census, both measured); the renderer's || [] guard means neither draws anything the other does not, so "at least one of" never guaranteed a non-empty tree. The census also shows why a two-limb refinement would be wrong: { type: 'tree-view', bind: 'treeNodes' } — a real, rendering document — flips refused→accepted here, and a nodes/data-only rule would refuse it. The #6939 maintainer ruling names a refinement for object-map/object-gantt only; #6951's comment 5527723261 recommending one is a triage recommendation on a needs-user-decision card, not a ruling.

Assertion 5 — confirmed stale half. Base data-display.ts:1220-1236 already declares nodes?/title? with #6150's comments; #6150's pins pass at head.

Merge target:git merge-tree --write-tree origin/main 6cad675a6 exit 0, no conflicts; origin/main's packages/react/src/index.ts still export * from './schema-input.js' (the new test's toRenderableSchema/SchemaRenderer imports resolve).

NARROWINGS

  • The card's "14 elements" — not reproduced or attempted; it comes from the docs-gallery harness. I confirmed only that the PR's 28 / 28 / 12 / 34 reproduce in this environment (all four render-identity cases pass at head).
  • eslint warnings on touched lines — I read the counts (30 + 1, both new files 0), not the line positions; whether all 31 sit outside the hunks is the PR's claim, not mine.
  • The PR's own reported runs (pnpm exec vitest run over its six files, its own ablation and blob hashes, its lock timings) — not re-read; superseded by my own runs above.
  • objectui check (node packages/cli/dist/cli.js check) — not run; my census reimplements its validation arm without the structural arm, so my "4 tracked files flip at root" is on that predicate, not on the CLI's printed count.
  • Whole-repo eslint, check:doc-snippets, check:readme-exports, Bundle Analysis, CI convergence — not run; CI-owned. At review time CI showed 27 completed checks all success/skipped with Type Check and the four Test shards still in progress.
  • The Two alias pairs on TextSchema and TreeViewSchema are now DECLARED in both spellings — and TreeViewSchema.data is required while nodes is the documented one (deferred rulings carried out of #6150) #6951 pair ruling — unmade at review time; the PR's revert-if-data-wins caveat is accurate but not something I can measure.

派发席处置

补丁轮,三条全修,⛔ 不放行。⚠️ Finding 2 是本席在席内核验时漏掉的真缺陷:本席查了 JSON 文档与 TS 消费者,没查声明合约事实的已发布散文,而本卡两个已落兄弟组(#7456#7471)恰恰都改了各自的文档页——本 PR 偏离了它自己这张卡的既定做法。

⛔ 注意 content/docs/api/schema-reference.md不是content/docs/releases/,改它不触犯「代码 PR 不得改发布说明」那条。

⭐ 三条 FAIL 都不要求重测改动本身:审查者已把 accept set 双向(NEWLY_FAIL=0)、消融红集、两面同步、以及本 PR 自己申报为未测的 example-schema-catalog type-checkexit 0,收窄已闭合)全部测过。patch 档位经三条独立支撑维持不变

本 PR 的 CI 在 6cad675a6 上已 32/32 全绿(其中 Lint run 33785853068、Docs Links run 33785852959 是按工作流正查确认的——get_check_runs 只返回了 30 条)。


Generated by Claude Code

Contract review of PR 7533 passed the change on every axis and failed three
statements ABOUT it. None of the code under repair moves here.
1. The compile-time pin's comment named the wrong mechanism. Deleting the
member does not fall through BaseSchema's `[key: string]: any`; it resolves
to the INHERITED declared `BaseSchema.data?: any` (base.ts:183), because a
declared member outranks an index signature. Verified independently before
accepting the finding: strip the index signature with the homomorphic
keyof-remap and `['data']` is still `any`, while an index-signature-only key
stops resolving at all -- both controls lit. The pin's verdict is unchanged;
only the reason it gives was wrong, and it contradicted the file header, the
TS-face doc comment and the mirror's describe().
2. `content/docs/api/schema-reference.md` still called `data` **Required**,
which this branch made false, and no gate covers that row. Corrected, and a
`nodes` row added beside it so the corrected sentence does not reference a
key the table never lists. The two sibling groups of this card each updated
their own doc page; this one had missed it.
3. The changeset called the change a widening "on both faces" without saying
that a READER of the TypeScript twin sees a narrowing: `data` is now
`TreeNode[] | undefined`, so an unguarded `schema.data` read stops compiling.
The PR body already said this; the changeset is the artefact that becomes the
CHANGELOG, so it is the one that had to. Level is unchanged and still patch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@claude

claudeBot commented Sep 3, 2026

Copy link
Copy Markdown
ContributorAuthor

Patch round — all three FAIL findings addressed at 794aa2ad4. Nothing under repair moved.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. One commit
(5d7e47fa7) plus a merge of origin/main at d04e79a80 (merge 794aa2ad4, no
conflicts). ⛔ Changeset level untouched, still patch. ⛔ nodes / title
declarations untouched. ⛔ No re-测 of the change itself.

Finding 1 — I checked it before accepting it, and the review is right

I did not take the mechanism on trust, because the instruction was to say so if I measured
otherwise. A throwaway type-level probe, run through pnpm --filter @object-ui/types type-check and then deleted, with both controls lit:

proberesult
['data'] with the index signature presentany
['data'] with the index signature STRIPPED (the homomorphic keyof-remap zod-mirror-parity.test.ts uses)still any
data survives the strippingyes, so it is a declared member
CONTROL: an index-signature-only key survives the strippingno — so the stripping really is doing something
CONTROL: that same key reads as any while the index signature is presentyes

All five compiled, exit 0. BaseSchema carries bothdata?: any (base.ts:183) and
[key: string]: any (base.ts:409), and the declared member — inherited or not — outranks
the index signature. So the comment was wrong and the review's replacement is right;
the pin's verdict never depended on it. Replacement text taken as given, plus a short
paragraph recording the measurement so the next reader does not have to redo it.

undeclared-but-consumed-keys-6150.test.ts:91-94 deliberately NOT touched — the same
sentence is true there, because those 13 keys genuinely are undeclared on BaseSchema.

Finding 2 — content/docs/api/schema-reference.md:527

Corrected to the suggested text. I also added onenodes row directly above it, and
that is a judgment call worth flagging rather than burying: the corrected sentence says
"read only when nodes is absent", and nodes was not in that table, so the correction
would otherwise have created a dangling reference. One row, not a documentation sweep —
title is still absent and I left it alone as the #6150 leftover the review says it is.
The sentence about node shape (id, label, optional icon and children) moved onto
the nodes row rather than being dropped.

content/docs/releases/ not touched.

ℹ️ Two more census data points, recorded not acted on: the JSON example immediately above
that table also authors data, as does the tree-view entry in
packages/types/examples/data-display-examples.json. Both are still legal after this
branch, so neither is a false statement — but if the #6951 retirement lands on nodes,
they are two of the documents that have to move.

Finding 3 — .changeset/6939-tree-view-nodes-mirror.md

Split into the two halves that are separately true: authors get a widening on both faces;
a reader of the TypeScript twin gets a narrowing, TreeNode[] | undefined, with the
TS2322 + TS18048 consumer-probe result named, the one in-repo reader shown to be
already guarded, and out-of-repo unguarded consumers named as the population the paragraph
exists for. Level unchanged, with the three reasons it stays patch stated in place.

Convergence verification, at 794aa2ad4 (post-merge head)

Heavy runs serialised through the shared verify lock; exit codes captured to files before
any pipe.

checkexit
pnpm exec vitest run over both new test files0 — "Test Files 2 passed (2)"
pnpm --filter @object-ui/types type-check0
pnpm check:doc-types0
pnpm check:doc-fences0
node scripts/check-control-bytes.mjs0 (6206 tracked files)
node scripts/check-changeset-no-major.mjs0

⚠️ Worth recording about check:doc-types: its counters are byte-identical before and
after my documentation edit — same 188 documents, same 4 key tables, same 24 rows, same 45
table keys. That is the review's own point measured from the other side: the gate never
looked at this table, so the stale row could not have been caught by it.

Thank you for the census — the root-level 4 is exactly what I claimed and the
member-level flips (the nested editor-interface.json node, and the real bind-only
document at shadowed-renderer-behaviour.test.tsx:119) are a level distinction I had not
made. I have left the counts alone as instructed.

Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

在席处置:补丁轮已核,放行 —— 剥标签 + ready + auto-merge SQUASH

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。链条:隔离审查 FAIL 三条 → 补丁轮 → 在席按内容核实 → 放行。

决定性的一读:被审查过的改动没有动

补丁轮之后我没有重跑合约审查,依据是这个:

 6cad675a6 (被审的 head) → 794aa2ad4 (补丁后)
data-display.zod.ts a0f1499dff920cfa1de27573396e1c2734674d7d IDENTICAL
data-display.ts e4f16b1261cfd621cac6d83a061041967768a5ec IDENTICAL

⇒ 两个被审源文件逐字节相同 ⇒ 审查者对改动本身的裁断(accept set 双向 NEWLY_FAIL=0、消融红集吻合、两面同步、parity ledger、干净 merge)原样成立,无需重审。补丁提交 5d7e47fa7 只碰了三个文件——正是三条 finding 各自的落点(changeset、content/docs/api/schema-reference.md、测试注释);diff stat 看着大是因为它同时 merge 了 origin/main

三条修正逐一核过

⭐ 一条从反面得来的读数,值得留档

实现者报告:check:doc-types 在它改动文档前后计数器逐字节相同(同样 188 文档 / 4 个 key table / 24 行 / 45 个 table key)。它把这条报成「一个对我修的那行什么也没说的绿」,而不是报成覆盖率——这正是审查者「no gate covers this row」从另一侧的验证。

放行状态

CI 在当前 head 794aa2ad4completed / success⚠️ 不是旧 head 6cad675a6 那个绿——按 status=completed 过滤会返回旧 head 的绿,那对当前 head 什么也没说)。已剥 needs:contract-review、ready、auto-merge SQUASH,并按 base-sha 链正向确认入队(队首,base = f0f774b0d)。

⚠️ 放行动作一度因 GitHub 配额半完成(标签已剥、undraft 失败),已排定时提醒跟踪并在配额恢复后补完 —— ⛔ 没有静默停在半完成状态。

⛔ 未随本 PR 解决的

#6951needs-user-decisionnodes / data 的 ADR-0049 退役选择未裁。本 PR 不做那个选择(两种拼法都仍解析)。若最终 data 胜出,本分支需连同其余一并回退。相关记录已留在 #6951(含本席对自己一处口径错误的订正)。


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939) - #7533

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-6939-group2-treeview-mirror
Sep 3, 2026
Merged

fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939)#7533
os-project-manager merged 3 commits into
mainfrom
claude/issue-6939-group2-treeview-mirror

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Part of #6939group 2 of 8 (tree-view). Deliberately worded to leave the card open: four
of that card's eight groups are still unimplemented, so it must stay open.

⚠️Draft + needs:contract-review on purpose. This moves an accept surface;
the dispatching seat reviews before it lands. Please do not mark ready or enable
auto-merge.

The repair

TreeViewSchema REQUIRED data — the limb the renderer reads third:

const rawNodes = boundData || schema.nodes || schema.data || [];
// packages/components/src/renderers/data-display/tree-view.tsx:105

The registration's own inputs and defaultProps spell it nodes, and the four
components-data-display-tree-view/* catalog entries are those defaultProps.
data is now optional on the zod mirror and on the TypeScript twin, in the same
stroke.

What I re-measured, and where the card is stale

Card readings re-derived on origin/main at fe4e7a9e8, through a bare
SchemaRenderer harness:

card saysre-measuredverdict
4 files4 (deep-nesting, file-tree, org-chart, sidebar-navigation), all type,title,nodes✅ confirmed
zod demands data (required)nodes-only parse false, data-only parse true, bare {type} parse false✅ confirmed
renderer reads boundData or schema.nodes or schema.dataread site intact at :105; title at :115 / :117✅ confirmed
render after "correcting" the fixture: identical — 14 elementsidentical: ✅ (element count, tag census and a SHA-256 of textContent, all four tiles). 14: not reproduced — this harness measures 28 / 28 / 12 / 34⚠️ partly stale

Element counts are harness-bound (the docs-gallery harness gives different absolutes
for the same tile), so the pin records identity within one harness and carries the
numbers I measured rather than the card's.

⚠️The card's stated disposition is also half-landed already. It asks to "declare
nodes and title, and stop requiring data". objectui#6150 landed the declaration
half after the card was filed — both keys are declared on both faces today, and #6150
wrote in as many words that relaxing data was a separate accept-set change. This PR
is only that remaining half; nodes and title are untouched.

data stays declared rather than being deleted

The intuitive read of "the renderer prefers nodes" is to drop data. That is wrong,
and measurably: BaseSchema already declares data (z.any().optional(), data?: any
on the TS face), so removing the member would not reject the key — it would admit it
unvalidated while the renderer went on reading it. The pin
control: BaseSchema alone would have admitted both of those makes the difference
visible: the base object accepts the two values the member refuses.

No refinement was added

Unlike this card's object-map / object-gantt group, which added an "at least one of"
refinement. A tree-view carrying no data source at all becomes legal here, and that
admits no new rendering outcome: { data: [] } was already legal and already drew
the same empty tree. A refinement would forbid a spelling of an empty state the contract
already permits rather than buy a guarantee.

Verification

All runs at 6cad675a6; heavy runs serialised through the shared verify lock.

  • pnpm exec vitest run over the two new pins plus undeclared-but-consumed-keys-6150,
    zod-mirror-parity, safe-validate-corpus-6318, handler-keys-json-refusal-6124
    6 files, 374 tests, all passing. The parity ledger needed no edit: tree-view's
    only entry in any of the three ledgers is RuntimeOnlyDeclaredonNodeClick, and
    this change moves optionality, not key membership.
  • pnpm --filter @object-ui/types type-check — green (covers tsconfig.test.json,
    so both compile-time pins are checked).
  • pnpm --filter @object-ui/components type-check — green, after building the
    9-package closure. That is the only TS-face consumer of TreeViewSchema outside
    packages/types, by exhaustive grep.
  • eslint on the four touched files: exit 0, 0 errors, 31 warnings — all pre-existing
    no-explicit-any on lines outside every hunk.
  • check:control-bytes, check:doc-types, check:doc-fences,
    check-changeset-no-major — green.

Reversible verification (both faces restored to fe4e7a9e8, then restored back)

Mutation proven on disk by blob hash and by anchor counts in both directions
(optional-anchor 1 to 0, required-anchor 0 to 1, on both files). Restore proven by
state: on-disk blobs equal the HEAD blobs, git diff HEAD empty, git status
empty. Absolute paths, trap on EXIT INT TERM.

Predicted red set and measured red set agreed:

  • vitest, mutated: exit 1, 8 red — the three "nodes-only is legal" pins, the four
    validates under safeValidateSchema cases, and both spellings validate.
  • vitest, mutated: still green — all four render-identity cases, all four
    anti-vacuity cases, all four "correcting it to data changes no pixel" cases, and the
    whole "data is still validated" group. That asymmetry is the point: the pin is on
    the validator, and the renderer did not move.
  • type-check, mutated: exit 2 on exactly the two compile-time pins vitest cannot
    see — TS2344 on the optionality equality and TS2741 (Property 'data' is missing)
    on the nodes-only document literal.
  • restored: vitest exit 0 (5 files), type-check exit 0.

A first ablation round found two of my own controls reddening for the wrong reason
(their carrier documents were nodes-only, so the pre-repair mirror refused the carrier
itself). Both were rewritten onto data-bearing carriers that are legal in either state,
and the second round shows them staying green while the load-bearing pins still redden.

Declared narrowing

pnpm --filter @object-ui/example-schema-catalog type-check was not measured here.
It needs a 30-package dist closure, and the shared verify lock refused twice at its
9-minute budget with another seat holding it for 550s. Its local output is a precondition
failure, not a verdict: 67 errors, 63 of them TS2307/TS2882 "cannot find module",
the other 4 consequential implicit-any in files this PR does not touch — and the two
lines naming this PR's own file are that same "cannot find module @object-ui/components
/ @object-ui/react" class that every sibling test file reports. Nothing in it is about
this change. That file's behaviour is proven by executing it (green, and red under
ablation); CI type-checks the farm regardless.

Narrowing evidence for the lint side: the linted population is each package's own
eslint .; this diff touches files in two packages and all four were linted (count read
from --format json); and eslint.config.js configures no type-aware linting
(projectService 0, parserOptions 0, against a lit control of languageOptions 1), so
this diff cannot move the verdict on a file it does not touch.

Changeset

@object-ui/types: patch. The accept set only widens — no document that validated
before validates less, including the data-spelled tree-view in
packages/types/examples/data-display-examples.json — and this is the same shape as the
two sibling groups of this card that already landed on patch. The TS twin's
required-to-optional move is the honest half of the same fact: the renderer never
guaranteed data was present, its own read is || []-guarded, and the single consumer
type-checks clean.

⚠️ Interaction with #6951 — the reviewer needs to see this

Found while de-duplicating an unrelated observation, not supplied by the dispatch.
#6951 is open and carries needs-user-decision, and it explicitly folds in this
exact question:

data is required on TreeViewSchema while nodes is the spelling the docs name
as canonical.
[...] Whichever way the pair ruling goes, the required-ness has to move
with it. That is why it is one card.

#6951's own ruling is an ADR-0049 enforce-or-remove choice: retire one spelling of the
nodes / data pair. That choice has not been made. This PR does not make it — it
retires neither spelling, and both keep parsing. What it does do is presume the direction
that every in-repo artefact already names: the doc comments name nodes the winner,
#6951 itself says "nodes is the winner named in the doc comment", the registration's
inputs and defaultProps spell it nodes, the docs page lists both as optional, and
the four catalog entries author nodes.

So the two possible rulings land differently on this PR:

  • nodes wins (what every artefact points to) — this PR is a strictly smaller step in
    the same direction and stays correct as-is.
  • data wins — this PR would have to be reverted along with the rest.

⇒ Flagging rather than choosing. If the reviewing seat would rather this waited on #6951,
the branch is a clean single commit and nothing else depends on it. I have also left a
cross-reference on #6951 so this does not become the invisible interim that card was
filed to prevent.

Out of scope

⛔ Only group 2. Group 5 (chart) lives in the same file and is untouched; group 3
(kanban) is a genuine fork awaiting a ruling; groups 4 and 7 are unassigned. No catalog
fixture is edited — the card is explicit that the fixtures are the side that is right.

Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`TreeViewSchema` required `data`, which the renderer reads THIRD:
const rawNodes = boundData || schema.nodes || schema.data || [];
// packages/components/src/renderers/data-display/tree-view.tsx:105
The registration's own `inputs` and `defaultProps` spell it `nodes`, and the
four `components-data-display-tree-view/*` catalog entries ARE those
`defaultProps`, so `safeValidateSchema` refused every one of them while the
renderer drew them correctly. Re-measured on origin/main at fe4e7a9: four
refusals, and renders byte-identical under either spelling (28 / 28 / 12 / 34
elements, same tag census, same textContent SHA-256) -- objectui#6318's own
triage test for "the schema was the wrong side".
`data` becomes optional on the zod mirror and on the TypeScript twin in the
same stroke. It stays DECLARED rather than deleted: BaseSchema already declares
`data` as `z.any().optional()`, so removing the member would not reject the key
-- it would admit it unvalidated while the renderer went on reading it.
No refinement is added, unlike this card's object-map / object-gantt group: a
tree-view with no data source now validates, and that admits no new rendering
outcome, since `{ data: [] }` was already legal and already drew the same empty
tree.
`nodes` and `title` are objectui#6150's declarations and are untouched; that
card declared the reads and said in as many words that relaxing `data` was a
separate accept-set change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

合约审查裁断:FAIL —— 逐字采信

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。本席是 opus,不在 CONTRACT_REVIEW_TIER,故本 Clause-② yes 的 PR 由隔离审查者出裁。简报只含卡片、裁决与本 PR,⛔ 不含派发指令、⛔ 不含本席结论。

转录档位核验(先决条件):对审查者转录只提取 harness 生成的 model 字段(⛔ 不对自由文本搜关键词——一句「未检出 X」自身含 X,会污染下一轮检测)。248 行、0 行不可解析、119 个 assistant 轮次,全部 claude-fable-5-1,无 fallback 证据 ⇒ 裁断在档,逐字采信。⛔ 未改写、未删节、未润色。

⚠️ 三条 FAIL 全部是 PR 关于改动的陈述,不是改动本身;审查者对改动本身逐轴测过并通过(NEWLY_FAIL=0、消融红集吻合、两面同步、parity ledger、以及本 PR 声明未测的那个 type-check)。


VERDICT: FAIL

The code change itself checks out on every axis I could measure — accept-set direction (NEWLY_FAIL=0), ablation red set, both faces moving together, parity ledger, both heavy type-checks including the one the PR declared unmeasured, and a clean merge onto the current origin/main. The FAIL is on three items the PR says about the change that are wrong or undisclosed, each with a one-to-two-line fix. None requires re-measuring the change.

All measurements were mine, on a detached worktree at 6cad675a6 (git worktree add --detach /home/user/objectui-cr7533 6cad675a6, pnpm install --frozen-lockfile exit 0), base faces taken from 78a3cc23 (blob-identical to merge-base fe4e7a9e8 for both touched source files: zod d1d427db…, ts 58850203…). Worktree removed after review.


Finding 1 — a compile-time pin bites for a different mechanism than its comment names

File:packages/types/src/__tests__/tree-view-data-optional-6939.test.ts:60-67

 * - member DELETED -> falls through `BaseSchema`'s `[key: string]: any`
* to `any`, and `Equal< any, … >` is false -> red

That is not what happens. BaseSchemadeclaresdata?: any (packages/types/src/base.ts:183), so a twin without its own data member resolves ['data'] to the inherited declared member, not to the index signature. The pin still reddens (inherited member is any), but the comment records the wrong mechanism — and this file's own header paragraph, the TS doc comment on data, and the zod .describe() all state the correct one, so the pin's comment contradicts the rest of the PR.

Measurement (tsc --ignoreConfig --noEmit --strict … --types react src/__cr_probe__/mechanism.probe.ts run inside packages/types):

typeWithoutIndexSignature<D>={[KinkeyofDasstringextendsK ? never : numberextendsK ? never : K]: D[K]};// 1. `data` IS a declared member of BaseSchema (survives index-signature removal)exporttype_DataIsDeclaredOnBase=Expect<Equal<'data'extendskeyofWithoutIndexSignature<BaseSchema> ? true : false,true>>;exporttype_SentinelIsNotDeclared=Expect<Equal<'undeclaredTreeKey6939'extendskeyofWithoutIndexSignature<BaseSchema> ? true : false,false>>;// 2. twin without its own `data`: still a DECLARED member, and `any`interfaceTwinWithoutDataextendsBaseSchema{type: 'tree-view';nodes?: TreeNode[]}exporttype_InheritedDataIsAny=Expect<Equal<TwinWithoutData['data'],any>>;exporttype_InheritedDataStillDeclared=Expect<Equal<'data'extendskeyofWithoutIndexSignature<TwinWithoutData> ? true : false,true>>;// 3. same on a base with NO index signature at all: still `any`, from the inherited member aloneinterfaceTwinOnBaseNoIndexextendsWithoutIndexSignature<BaseSchema>{type: 'tree-view';nodes?: TreeNode[]}exporttype_AnyWithoutAnyIndexSignature=Expect<Equal<TwinOnBaseNoIndex['data'],any>>;// @ts-expect-error — an UNDECLARED key on that base is an error, not `any`exporttype_UndeclaredIsErrorWithoutIndex=TwinOnBaseNoIndex['undeclaredTreeKey6939'];
PROBE_CLEAN_EXIT=0
--- control: expectation 2 flipped to `false` ---
src/__cr_probe__/mechanism.flipped.ts(21,50): error TS2344: Type 'false' does not satisfy the constraint 'true'.
FLIPPED_CLEAN_EXIT=2

Line 3 is decisive: with the index signature stripped entirely, ['data'] is still any. The index signature is not on the path.

Minimal fix: replace the two comment lines with
member DELETED -> resolves to the INHERITED \BaseSchema.data?: any` (base.ts:183) — `any`, and `Equal< any, … >` is false -> red. (The identical wrong sentence does *not* appear in undeclared-but-consumed-keys-6150.test.ts:91-94; there the keys are genuinely undeclared on BaseSchema, so the index-signature account is correct for them. It is wrong only for data`.)


Finding 2 — a published doc now states a contract fact this PR made false

File:content/docs/api/schema-reference.md:527 (unchanged by the PR; the PR touches 5 files, none under content/):

| `data` | `TreeNode[]` | **Required.** Nested tree data. Each node has `id`, `label`, optional `icon` and `children`. |

Before this PR that row was true; after it, the published API reference says data is required while the published type says data?: TreeNode[] and the published validator accepts documents without it. No gate covers the row: scripts/check-doc-component-types.mjs reads this file only for its type vocabulary (its exemption block at :410), and check:doc-snippets compiles ts/tsx fences, not property tables (check:doc-types exit 0, check:doc-fences exit 0 at head — both green with the stale row). This card's two landed siblings corrected the docs page that published the old required-ness (tooltip.mdx in #7456; plugin-map.mdx and the gantt page in #7471); this PR leaves its equivalent.

Measurement:

$ grep -n "| \`data\` | \`TreeNode\[\]\`" content/docs/api/schema-reference.md # at 6cad675a6
527:| `data` | `TreeNode[]` | **Required.** Nested tree data. …
$ git diff --stat fe4e7a9e8 6cad675a6 -- content/
(empty)

(content/docs/components/data-display/tree-view.mdx:28-31 already reads data?: TreeNode[] / nodes?: TreeNode[], so the component page is consistent; the API reference is the one that is not.)

Minimal fix: rewrite the row, e.g.
| \data` | `TreeNode[]` | Optional. Nested tree data, read only when `nodes` is absent (the renderer reads `nodes` first — objectui#6939). |— and, since the table lists neithernodesnortitle, a nodes` row would make the JSON example above it honest, but that omission predates this PR (#6150) and is not part of this finding.


Finding 3 — the changeset's "WIDENING, on both faces" is wrong on the reader side of the TS face, and the changeset is where it needs to be right

File:.changeset/6939-tree-view-nodes-mirror.md:24-29

**This is a WIDENING, on both faces.** `data` goes from required to optional on
the mirror and on the TypeScript twin in the same stroke; nothing that validated
before validates less.

For the validator and for an author of the TS type, true. For a reader of the published TS twin it is a narrowing: TreeViewSchema['data'] goes from TreeNode[] to TreeNode[] | undefined, and code that relied on presence stops compiling. The PR body says this ("the honest half of the same fact … its own read is || []-guarded") — the changeset, which is the artefact that becomes the CHANGELOG, says the opposite. AGENTS.md §版本号策略 is explicit that breaking semantics are stated in the changeset body.

Measurement (consumer probe against the published entry, tsc --ignoreConfig --noEmit --strict …):

importtype{TreeViewSchema,TreeNode}from'…/packages/types/src/index';exportfunctioncount(schema: TreeViewSchema): number{constnodes: TreeNode[]=schema.data;returnnodes.length+schema.data.length;}
--- at HEAD (data?: TreeNode[]) ---
consumer.probe.ts(4,9): error TS2322: Type 'TreeNode[] | undefined' is not assignable to type 'TreeNode[]'.
consumer.probe.ts(5,25): error TS18048: 'schema.data' is possibly 'undefined'.
HEAD CONSUMER_PROBE_EXIT=2
--- at BASE faces (data: TreeNode[]) blobs: 588502039b4df4ca47c150317474e4e61882a3b5 ---
BASE CONSUMER_PROBE_EXIT=0

On the bump:patch stands. scripts/check-changeset-no-major.mjs forbids only major (exit 0 at head: "No changeset declares a major bump."); the maintainer ruling on #6939 (comment 5510084784) grades this exact class "patch where the accept set only widens toward what already renders"; and both landed siblings moved a required TS member to optional under patch (objectName in .changeset/6939-objectql-record-source-refinement.md, children in .changeset/6939-overlay-trigger-mirror.md). The #6318 reviewer's patch→minor condition predates that ruling and concerned union membership, not optionality. So the grade is what the repo's rules call for; the description is not.

Minimal fix: replace the bolded sentence with something like: "The accept set widens on both faces for authors. For a reader of the TypeScript twin this is a narrowing — data is now TreeNode[] | undefined, so an unguarded schema.data read needs a guard; the only in-repo reader (tree-view.tsx:105) already has one (|| [])." Grade unchanged.


What I measured that checks out (so the adopter can see the FAIL is not about the change)

Accept set, both directions — NEWLY_FAIL = 0. Own census over every tracked *.json/*.yaml/*.yml (664 files, 664 parsed, 479 with a root type string), each root run through safeValidateSchema and every tree-view node (root or nested) through TreeViewSchema.safeParse, via tsx on source (packages/types/src/zod/index.zod.ts), at head and with both faces restored to 78a3cc23:

head: root-union ok=392 fail=91 base: ok=385 fail=98
NEWLY_PASS (base refused, head accepts): 4 tracked files at root —
components-data-display-tree-view/{deep-nesting,file-tree,org-chart,sidebar-navigation}.json
base issue on all four: ': Invalid input' (root) / 'data: Invalid input: expected array, received undefined' (member)
NEWLY_FAIL (base accepted, head refuses): 0

So the PR's "four" is exact for the objectui check predicate. The complete member-level count is larger, and the PR should be read as having counted at root level: a fifth tracked node flips — the nested tree-view in components-complex-resizable/editor-interface.json (/panels/0/content/0/children/1, nodes-only) — while its enclosing resizable document stays refused at both revs for an unrelated reason (panels.0.id, panels.1.id missing). Two inline TS literals also flip: { type: 'tree-view', bind: 'treeNodes' } (shadowed-renderer-behaviour.test.tsx:119) and the registration's own defaultProps; the data-spelled packages/types/examples/data-display-examples.json entry and TREE_CONTROL = { type, data: [] } stay accepted (True→True). The implementer's own #6951 comment names the nested one, so this is a counting-level difference, not an unknown.

Ablation reproduces the PR's prediction exactly. Base faces on disk (blobs d1d427db…/58850203…, anchors optional-zod 0 / required-zod 1 / optional-ts 0 / required-ts 1), vitest run on the two new files:

Tests 8 failed | 25 passed (33) VITEST_BASE_EXIT=1
× a tree-view with NO data source at all is legal… × the spelling the renderer reads FIRST now parses on its own
× …and the value SURVIVES the parse × both spellings validate — the accept set widened…
× {deep-nesting,file-tree,org-chart,sidebar-navigation} validates under safeValidateSchema

The four render-identity, four anti-vacuity, four "changes no pixel", "keys are DECLARED" (carrier {type,title,data:[],nodes}), "UNDECLARED key admitted" (carrier {type,data:[],[SENTINEL]}), and "BaseSchema alone would have admitted both" controls all stayed green — the rewritten controls from assertion 7 do redden only for their own reason. pnpm --filter @object-ui/types type-check under base faces: exit 2 with exactly TS2344 at :72 and TS2741 Property 'data' is missing at :85. Restore proven by state: blobs back to a0f1499d…/e4f16b12…, anchors 1/1, git status --porcelain empty.

Head is green everywhere I ran it.vitest run on the two new pins + undeclared-but-consumed-keys-6150 + safe-validate-corpus-6318: 4 files / 118 tests; zod-mirror-parity + handler-keys-json-refusal-6124: 2 files / 256 tests; the whole packages/types/ + examples/schema-catalog/ population plus shadowed-renderer-behaviour: 121 files / 3644 tests, exit 0. pnpm --filter @object-ui/types type-check exit 0 (clean tree, 0 error TS), with --listFiles showing both new test files in their tsconfig.test.json programs. Gates: check:control-bytes, check:doc-types, check:doc-fences, check-changeset-no-major, check-changeset-presence ("3 source file(s) of 1 released package… 1 changeset(s)") all exit 0. eslint on the four touched files: 0 errors; both new files 0 warnings; 30 + 1 warnings on the two source files (count matches the PR's 31).

Assertion 8 — measured, and it could not have caught anything.turbo run build --filter=@object-ui/example-schema-catalog...: 29 successful / 29 total; then pnpm --filter @object-ui/example-schema-catalog type-check (tsc --noEmit && tsc -p tsconfig.test.json): exit 0, 0 errors, and pnpm --filter @object-ui/components type-check: exit 0. The narrowing is closed.

Compile-time pins — what each catches (probe pins.probe.ts, exit 0):_TreeDataIsOptionalTreeNodes catches data re-required (TS2344, measured), data deleted (inherited any — Finding 1), and an element-type change. _TreeNodesStillTreeNodes / _TreeTitleStillString are optionality-blind by construction (NonNullable<Req['nodes']> equals NonNullable<Opt['nodes']> — measured true): they catch deletion and element-type change, not nodes/title becoming required; they are inherited from #6150 unchanged, so this is a limitation to know, not a defect of this PR. NODES_ONLY_DOCUMENT catches any key re-required (TS2741, measured) but cannot catch nodes/title deleted or renamed — { …, nodez: […] } and { …, undeclaredTreeKey6939: 1 } both compile through BaseSchema's index signature (measured); a wrong-typed declared key still errors (measured). The runtime pins cover the deletion case (data: 'not-an-array' refused AT data; BaseSchema alone accepts it — both measured green at head).

Other comment mechanisms:.passthrough()base.zod.ts:212; BaseSchema.data: z.any().optional()base.zod.ts:121; data?: anybase.ts:183; [key: string]: anybase.ts:409; renderer read at tree-view.tsx:104-106, inputs/defaultProps spelled nodes. All as stated. Parity ledger: TreeViewSchema's only entry is RuntimeOnlyDeclared: 'onNodeClick' (zod-mirror-parity.test.ts:1239); the compile-time NarrowerThanDeclared comparison does see optionality ([D[K]] extends [InputOf<Shape[K]>]), which is why both faces had to move together — and did (type-check exit 0 covers it).

Assertion 4 (no refinement) — sound.{ type: 'tree-view' } is accepted at head and { type: 'tree-view', data: [] } was accepted at base (census, both measured); the renderer's || [] guard means neither draws anything the other does not, so "at least one of" never guaranteed a non-empty tree. The census also shows why a two-limb refinement would be wrong: { type: 'tree-view', bind: 'treeNodes' } — a real, rendering document — flips refused→accepted here, and a nodes/data-only rule would refuse it. The #6939 maintainer ruling names a refinement for object-map/object-gantt only; #6951's comment 5527723261 recommending one is a triage recommendation on a needs-user-decision card, not a ruling.

Assertion 5 — confirmed stale half. Base data-display.ts:1220-1236 already declares nodes?/title? with #6150's comments; #6150's pins pass at head.

Merge target:git merge-tree --write-tree origin/main 6cad675a6 exit 0, no conflicts; origin/main's packages/react/src/index.ts still export * from './schema-input.js' (the new test's toRenderableSchema/SchemaRenderer imports resolve).

NARROWINGS

  • The card's "14 elements" — not reproduced or attempted; it comes from the docs-gallery harness. I confirmed only that the PR's 28 / 28 / 12 / 34 reproduce in this environment (all four render-identity cases pass at head).
  • eslint warnings on touched lines — I read the counts (30 + 1, both new files 0), not the line positions; whether all 31 sit outside the hunks is the PR's claim, not mine.
  • The PR's own reported runs (pnpm exec vitest run over its six files, its own ablation and blob hashes, its lock timings) — not re-read; superseded by my own runs above.
  • objectui check (node packages/cli/dist/cli.js check) — not run; my census reimplements its validation arm without the structural arm, so my "4 tracked files flip at root" is on that predicate, not on the CLI's printed count.
  • Whole-repo eslint, check:doc-snippets, check:readme-exports, Bundle Analysis, CI convergence — not run; CI-owned. At review time CI showed 27 completed checks all success/skipped with Type Check and the four Test shards still in progress.
  • The Two alias pairs on TextSchema and TreeViewSchema are now DECLARED in both spellings — and TreeViewSchema.data is required while nodes is the documented one (deferred rulings carried out of #6150) #6951 pair ruling — unmade at review time; the PR's revert-if-data-wins caveat is accurate but not something I can measure.

派发席处置

补丁轮,三条全修,⛔ 不放行。⚠️ Finding 2 是本席在席内核验时漏掉的真缺陷:本席查了 JSON 文档与 TS 消费者,没查声明合约事实的已发布散文,而本卡两个已落兄弟组(#7456#7471)恰恰都改了各自的文档页——本 PR 偏离了它自己这张卡的既定做法。

⛔ 注意 content/docs/api/schema-reference.md不是content/docs/releases/,改它不触犯「代码 PR 不得改发布说明」那条。

⭐ 三条 FAIL 都不要求重测改动本身:审查者已把 accept set 双向(NEWLY_FAIL=0)、消融红集、两面同步、以及本 PR 自己申报为未测的 example-schema-catalog type-checkexit 0,收窄已闭合)全部测过。patch 档位经三条独立支撑维持不变

本 PR 的 CI 在 6cad675a6 上已 32/32 全绿(其中 Lint run 33785853068、Docs Links run 33785852959 是按工作流正查确认的——get_check_runs 只返回了 30 条)。


Generated by Claude Code

Contract review of PR 7533 passed the change on every axis and failed three
statements ABOUT it. None of the code under repair moves here.
1. The compile-time pin's comment named the wrong mechanism. Deleting the
member does not fall through BaseSchema's `[key: string]: any`; it resolves
to the INHERITED declared `BaseSchema.data?: any` (base.ts:183), because a
declared member outranks an index signature. Verified independently before
accepting the finding: strip the index signature with the homomorphic
keyof-remap and `['data']` is still `any`, while an index-signature-only key
stops resolving at all -- both controls lit. The pin's verdict is unchanged;
only the reason it gives was wrong, and it contradicted the file header, the
TS-face doc comment and the mirror's describe().
2. `content/docs/api/schema-reference.md` still called `data` **Required**,
which this branch made false, and no gate covers that row. Corrected, and a
`nodes` row added beside it so the corrected sentence does not reference a
key the table never lists. The two sibling groups of this card each updated
their own doc page; this one had missed it.
3. The changeset called the change a widening "on both faces" without saying
that a READER of the TypeScript twin sees a narrowing: `data` is now
`TreeNode[] | undefined`, so an unguarded `schema.data` read stops compiling.
The PR body already said this; the changeset is the artefact that becomes the
CHANGELOG, so it is the one that had to. Level is unchanged and still patch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@claude

claudeBot commented Sep 3, 2026

Copy link
Copy Markdown
ContributorAuthor

Patch round — all three FAIL findings addressed at 794aa2ad4. Nothing under repair moved.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. One commit
(5d7e47fa7) plus a merge of origin/main at d04e79a80 (merge 794aa2ad4, no
conflicts). ⛔ Changeset level untouched, still patch. ⛔ nodes / title
declarations untouched. ⛔ No re-测 of the change itself.

Finding 1 — I checked it before accepting it, and the review is right

I did not take the mechanism on trust, because the instruction was to say so if I measured
otherwise. A throwaway type-level probe, run through pnpm --filter @object-ui/types type-check and then deleted, with both controls lit:

proberesult
['data'] with the index signature presentany
['data'] with the index signature STRIPPED (the homomorphic keyof-remap zod-mirror-parity.test.ts uses)still any
data survives the strippingyes, so it is a declared member
CONTROL: an index-signature-only key survives the strippingno — so the stripping really is doing something
CONTROL: that same key reads as any while the index signature is presentyes

All five compiled, exit 0. BaseSchema carries bothdata?: any (base.ts:183) and
[key: string]: any (base.ts:409), and the declared member — inherited or not — outranks
the index signature. So the comment was wrong and the review's replacement is right;
the pin's verdict never depended on it. Replacement text taken as given, plus a short
paragraph recording the measurement so the next reader does not have to redo it.

undeclared-but-consumed-keys-6150.test.ts:91-94 deliberately NOT touched — the same
sentence is true there, because those 13 keys genuinely are undeclared on BaseSchema.

Finding 2 — content/docs/api/schema-reference.md:527

Corrected to the suggested text. I also added onenodes row directly above it, and
that is a judgment call worth flagging rather than burying: the corrected sentence says
"read only when nodes is absent", and nodes was not in that table, so the correction
would otherwise have created a dangling reference. One row, not a documentation sweep —
title is still absent and I left it alone as the #6150 leftover the review says it is.
The sentence about node shape (id, label, optional icon and children) moved onto
the nodes row rather than being dropped.

content/docs/releases/ not touched.

ℹ️ Two more census data points, recorded not acted on: the JSON example immediately above
that table also authors data, as does the tree-view entry in
packages/types/examples/data-display-examples.json. Both are still legal after this
branch, so neither is a false statement — but if the #6951 retirement lands on nodes,
they are two of the documents that have to move.

Finding 3 — .changeset/6939-tree-view-nodes-mirror.md

Split into the two halves that are separately true: authors get a widening on both faces;
a reader of the TypeScript twin gets a narrowing, TreeNode[] | undefined, with the
TS2322 + TS18048 consumer-probe result named, the one in-repo reader shown to be
already guarded, and out-of-repo unguarded consumers named as the population the paragraph
exists for. Level unchanged, with the three reasons it stays patch stated in place.

Convergence verification, at 794aa2ad4 (post-merge head)

Heavy runs serialised through the shared verify lock; exit codes captured to files before
any pipe.

checkexit
pnpm exec vitest run over both new test files0 — "Test Files 2 passed (2)"
pnpm --filter @object-ui/types type-check0
pnpm check:doc-types0
pnpm check:doc-fences0
node scripts/check-control-bytes.mjs0 (6206 tracked files)
node scripts/check-changeset-no-major.mjs0

⚠️ Worth recording about check:doc-types: its counters are byte-identical before and
after my documentation edit — same 188 documents, same 4 key tables, same 24 rows, same 45
table keys. That is the review's own point measured from the other side: the gate never
looked at this table, so the stale row could not have been caught by it.

Thank you for the census — the root-level 4 is exactly what I claimed and the
member-level flips (the nested editor-interface.json node, and the real bind-only
document at shadowed-renderer-behaviour.test.tsx:119) are a level distinction I had not
made. I have left the counts alone as instructed.

Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

在席处置:补丁轮已核,放行 —— 剥标签 + ready + auto-merge SQUASH

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。链条:隔离审查 FAIL 三条 → 补丁轮 → 在席按内容核实 → 放行。

决定性的一读:被审查过的改动没有动

补丁轮之后我没有重跑合约审查,依据是这个:

 6cad675a6 (被审的 head) → 794aa2ad4 (补丁后)
data-display.zod.ts a0f1499dff920cfa1de27573396e1c2734674d7d IDENTICAL
data-display.ts e4f16b1261cfd621cac6d83a061041967768a5ec IDENTICAL

⇒ 两个被审源文件逐字节相同 ⇒ 审查者对改动本身的裁断(accept set 双向 NEWLY_FAIL=0、消融红集吻合、两面同步、parity ledger、干净 merge)原样成立,无需重审。补丁提交 5d7e47fa7 只碰了三个文件——正是三条 finding 各自的落点(changeset、content/docs/api/schema-reference.md、测试注释);diff stat 看着大是因为它同时 merge 了 origin/main

三条修正逐一核过

⭐ 一条从反面得来的读数,值得留档

实现者报告:check:doc-types 在它改动文档前后计数器逐字节相同(同样 188 文档 / 4 个 key table / 24 行 / 45 个 table key)。它把这条报成「一个对我修的那行什么也没说的绿」,而不是报成覆盖率——这正是审查者「no gate covers this row」从另一侧的验证。

放行状态

CI 在当前 head 794aa2ad4completed / success⚠️ 不是旧 head 6cad675a6 那个绿——按 status=completed 过滤会返回旧 head 的绿,那对当前 head 什么也没说)。已剥 needs:contract-review、ready、auto-merge SQUASH,并按 base-sha 链正向确认入队(队首,base = f0f774b0d)。

⚠️ 放行动作一度因 GitHub 配额半完成(标签已剥、undraft 失败),已排定时提醒跟踪并在配额恢复后补完 —— ⛔ 没有静默停在半完成状态。

⛔ 未随本 PR 解决的

#6951needs-user-decisionnodes / data 的 ADR-0049 退役选择未裁。本 PR 不做那个选择(两种拼法都仍解析)。若最终 data 胜出,本分支需连同其余一并回退。相关记录已留在 #6951(含本席对自己一处口径错误的订正)。


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939) - #7533

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-6939-group2-treeview-mirror
Sep 3, 2026
Merged

fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939)#7533
os-project-manager merged 3 commits into
mainfrom
claude/issue-6939-group2-treeview-mirror

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Part of #6939group 2 of 8 (tree-view). Deliberately worded to leave the card open: four
of that card's eight groups are still unimplemented, so it must stay open.

⚠️Draft + needs:contract-review on purpose. This moves an accept surface;
the dispatching seat reviews before it lands. Please do not mark ready or enable
auto-merge.

The repair

TreeViewSchema REQUIRED data — the limb the renderer reads third:

const rawNodes = boundData || schema.nodes || schema.data || [];
// packages/components/src/renderers/data-display/tree-view.tsx:105

The registration's own inputs and defaultProps spell it nodes, and the four
components-data-display-tree-view/* catalog entries are those defaultProps.
data is now optional on the zod mirror and on the TypeScript twin, in the same
stroke.

What I re-measured, and where the card is stale

Card readings re-derived on origin/main at fe4e7a9e8, through a bare
SchemaRenderer harness:

card saysre-measuredverdict
4 files4 (deep-nesting, file-tree, org-chart, sidebar-navigation), all type,title,nodes✅ confirmed
zod demands data (required)nodes-only parse false, data-only parse true, bare {type} parse false✅ confirmed
renderer reads boundData or schema.nodes or schema.dataread site intact at :105; title at :115 / :117✅ confirmed
render after "correcting" the fixture: identical — 14 elementsidentical: ✅ (element count, tag census and a SHA-256 of textContent, all four tiles). 14: not reproduced — this harness measures 28 / 28 / 12 / 34⚠️ partly stale

Element counts are harness-bound (the docs-gallery harness gives different absolutes
for the same tile), so the pin records identity within one harness and carries the
numbers I measured rather than the card's.

⚠️The card's stated disposition is also half-landed already. It asks to "declare
nodes and title, and stop requiring data". objectui#6150 landed the declaration
half after the card was filed — both keys are declared on both faces today, and #6150
wrote in as many words that relaxing data was a separate accept-set change. This PR
is only that remaining half; nodes and title are untouched.

data stays declared rather than being deleted

The intuitive read of "the renderer prefers nodes" is to drop data. That is wrong,
and measurably: BaseSchema already declares data (z.any().optional(), data?: any
on the TS face), so removing the member would not reject the key — it would admit it
unvalidated while the renderer went on reading it. The pin
control: BaseSchema alone would have admitted both of those makes the difference
visible: the base object accepts the two values the member refuses.

No refinement was added

Unlike this card's object-map / object-gantt group, which added an "at least one of"
refinement. A tree-view carrying no data source at all becomes legal here, and that
admits no new rendering outcome: { data: [] } was already legal and already drew
the same empty tree. A refinement would forbid a spelling of an empty state the contract
already permits rather than buy a guarantee.

Verification

All runs at 6cad675a6; heavy runs serialised through the shared verify lock.

  • pnpm exec vitest run over the two new pins plus undeclared-but-consumed-keys-6150,
    zod-mirror-parity, safe-validate-corpus-6318, handler-keys-json-refusal-6124
    6 files, 374 tests, all passing. The parity ledger needed no edit: tree-view's
    only entry in any of the three ledgers is RuntimeOnlyDeclaredonNodeClick, and
    this change moves optionality, not key membership.
  • pnpm --filter @object-ui/types type-check — green (covers tsconfig.test.json,
    so both compile-time pins are checked).
  • pnpm --filter @object-ui/components type-check — green, after building the
    9-package closure. That is the only TS-face consumer of TreeViewSchema outside
    packages/types, by exhaustive grep.
  • eslint on the four touched files: exit 0, 0 errors, 31 warnings — all pre-existing
    no-explicit-any on lines outside every hunk.
  • check:control-bytes, check:doc-types, check:doc-fences,
    check-changeset-no-major — green.

Reversible verification (both faces restored to fe4e7a9e8, then restored back)

Mutation proven on disk by blob hash and by anchor counts in both directions
(optional-anchor 1 to 0, required-anchor 0 to 1, on both files). Restore proven by
state: on-disk blobs equal the HEAD blobs, git diff HEAD empty, git status
empty. Absolute paths, trap on EXIT INT TERM.

Predicted red set and measured red set agreed:

  • vitest, mutated: exit 1, 8 red — the three "nodes-only is legal" pins, the four
    validates under safeValidateSchema cases, and both spellings validate.
  • vitest, mutated: still green — all four render-identity cases, all four
    anti-vacuity cases, all four "correcting it to data changes no pixel" cases, and the
    whole "data is still validated" group. That asymmetry is the point: the pin is on
    the validator, and the renderer did not move.
  • type-check, mutated: exit 2 on exactly the two compile-time pins vitest cannot
    see — TS2344 on the optionality equality and TS2741 (Property 'data' is missing)
    on the nodes-only document literal.
  • restored: vitest exit 0 (5 files), type-check exit 0.

A first ablation round found two of my own controls reddening for the wrong reason
(their carrier documents were nodes-only, so the pre-repair mirror refused the carrier
itself). Both were rewritten onto data-bearing carriers that are legal in either state,
and the second round shows them staying green while the load-bearing pins still redden.

Declared narrowing

pnpm --filter @object-ui/example-schema-catalog type-check was not measured here.
It needs a 30-package dist closure, and the shared verify lock refused twice at its
9-minute budget with another seat holding it for 550s. Its local output is a precondition
failure, not a verdict: 67 errors, 63 of them TS2307/TS2882 "cannot find module",
the other 4 consequential implicit-any in files this PR does not touch — and the two
lines naming this PR's own file are that same "cannot find module @object-ui/components
/ @object-ui/react" class that every sibling test file reports. Nothing in it is about
this change. That file's behaviour is proven by executing it (green, and red under
ablation); CI type-checks the farm regardless.

Narrowing evidence for the lint side: the linted population is each package's own
eslint .; this diff touches files in two packages and all four were linted (count read
from --format json); and eslint.config.js configures no type-aware linting
(projectService 0, parserOptions 0, against a lit control of languageOptions 1), so
this diff cannot move the verdict on a file it does not touch.

Changeset

@object-ui/types: patch. The accept set only widens — no document that validated
before validates less, including the data-spelled tree-view in
packages/types/examples/data-display-examples.json — and this is the same shape as the
two sibling groups of this card that already landed on patch. The TS twin's
required-to-optional move is the honest half of the same fact: the renderer never
guaranteed data was present, its own read is || []-guarded, and the single consumer
type-checks clean.

⚠️ Interaction with #6951 — the reviewer needs to see this

Found while de-duplicating an unrelated observation, not supplied by the dispatch.
#6951 is open and carries needs-user-decision, and it explicitly folds in this
exact question:

data is required on TreeViewSchema while nodes is the spelling the docs name
as canonical.
[...] Whichever way the pair ruling goes, the required-ness has to move
with it. That is why it is one card.

#6951's own ruling is an ADR-0049 enforce-or-remove choice: retire one spelling of the
nodes / data pair. That choice has not been made. This PR does not make it — it
retires neither spelling, and both keep parsing. What it does do is presume the direction
that every in-repo artefact already names: the doc comments name nodes the winner,
#6951 itself says "nodes is the winner named in the doc comment", the registration's
inputs and defaultProps spell it nodes, the docs page lists both as optional, and
the four catalog entries author nodes.

So the two possible rulings land differently on this PR:

  • nodes wins (what every artefact points to) — this PR is a strictly smaller step in
    the same direction and stays correct as-is.
  • data wins — this PR would have to be reverted along with the rest.

⇒ Flagging rather than choosing. If the reviewing seat would rather this waited on #6951,
the branch is a clean single commit and nothing else depends on it. I have also left a
cross-reference on #6951 so this does not become the invisible interim that card was
filed to prevent.

Out of scope

⛔ Only group 2. Group 5 (chart) lives in the same file and is untouched; group 3
(kanban) is a genuine fork awaiting a ruling; groups 4 and 7 are unassigned. No catalog
fixture is edited — the card is explicit that the fixtures are the side that is right.

Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`TreeViewSchema` required `data`, which the renderer reads THIRD:
const rawNodes = boundData || schema.nodes || schema.data || [];
// packages/components/src/renderers/data-display/tree-view.tsx:105
The registration's own `inputs` and `defaultProps` spell it `nodes`, and the
four `components-data-display-tree-view/*` catalog entries ARE those
`defaultProps`, so `safeValidateSchema` refused every one of them while the
renderer drew them correctly. Re-measured on origin/main at fe4e7a9: four
refusals, and renders byte-identical under either spelling (28 / 28 / 12 / 34
elements, same tag census, same textContent SHA-256) -- objectui#6318's own
triage test for "the schema was the wrong side".
`data` becomes optional on the zod mirror and on the TypeScript twin in the
same stroke. It stays DECLARED rather than deleted: BaseSchema already declares
`data` as `z.any().optional()`, so removing the member would not reject the key
-- it would admit it unvalidated while the renderer went on reading it.
No refinement is added, unlike this card's object-map / object-gantt group: a
tree-view with no data source now validates, and that admits no new rendering
outcome, since `{ data: [] }` was already legal and already drew the same empty
tree.
`nodes` and `title` are objectui#6150's declarations and are untouched; that
card declared the reads and said in as many words that relaxing `data` was a
separate accept-set change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

合约审查裁断:FAIL —— 逐字采信

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。本席是 opus,不在 CONTRACT_REVIEW_TIER,故本 Clause-② yes 的 PR 由隔离审查者出裁。简报只含卡片、裁决与本 PR,⛔ 不含派发指令、⛔ 不含本席结论。

转录档位核验(先决条件):对审查者转录只提取 harness 生成的 model 字段(⛔ 不对自由文本搜关键词——一句「未检出 X」自身含 X,会污染下一轮检测)。248 行、0 行不可解析、119 个 assistant 轮次,全部 claude-fable-5-1,无 fallback 证据 ⇒ 裁断在档,逐字采信。⛔ 未改写、未删节、未润色。

⚠️ 三条 FAIL 全部是 PR 关于改动的陈述,不是改动本身;审查者对改动本身逐轴测过并通过(NEWLY_FAIL=0、消融红集吻合、两面同步、parity ledger、以及本 PR 声明未测的那个 type-check)。


VERDICT: FAIL

The code change itself checks out on every axis I could measure — accept-set direction (NEWLY_FAIL=0), ablation red set, both faces moving together, parity ledger, both heavy type-checks including the one the PR declared unmeasured, and a clean merge onto the current origin/main. The FAIL is on three items the PR says about the change that are wrong or undisclosed, each with a one-to-two-line fix. None requires re-measuring the change.

All measurements were mine, on a detached worktree at 6cad675a6 (git worktree add --detach /home/user/objectui-cr7533 6cad675a6, pnpm install --frozen-lockfile exit 0), base faces taken from 78a3cc23 (blob-identical to merge-base fe4e7a9e8 for both touched source files: zod d1d427db…, ts 58850203…). Worktree removed after review.


Finding 1 — a compile-time pin bites for a different mechanism than its comment names

File:packages/types/src/__tests__/tree-view-data-optional-6939.test.ts:60-67

 * - member DELETED -> falls through `BaseSchema`'s `[key: string]: any`
* to `any`, and `Equal< any, … >` is false -> red

That is not what happens. BaseSchemadeclaresdata?: any (packages/types/src/base.ts:183), so a twin without its own data member resolves ['data'] to the inherited declared member, not to the index signature. The pin still reddens (inherited member is any), but the comment records the wrong mechanism — and this file's own header paragraph, the TS doc comment on data, and the zod .describe() all state the correct one, so the pin's comment contradicts the rest of the PR.

Measurement (tsc --ignoreConfig --noEmit --strict … --types react src/__cr_probe__/mechanism.probe.ts run inside packages/types):

typeWithoutIndexSignature<D>={[KinkeyofDasstringextendsK ? never : numberextendsK ? never : K]: D[K]};// 1. `data` IS a declared member of BaseSchema (survives index-signature removal)exporttype_DataIsDeclaredOnBase=Expect<Equal<'data'extendskeyofWithoutIndexSignature<BaseSchema> ? true : false,true>>;exporttype_SentinelIsNotDeclared=Expect<Equal<'undeclaredTreeKey6939'extendskeyofWithoutIndexSignature<BaseSchema> ? true : false,false>>;// 2. twin without its own `data`: still a DECLARED member, and `any`interfaceTwinWithoutDataextendsBaseSchema{type: 'tree-view';nodes?: TreeNode[]}exporttype_InheritedDataIsAny=Expect<Equal<TwinWithoutData['data'],any>>;exporttype_InheritedDataStillDeclared=Expect<Equal<'data'extendskeyofWithoutIndexSignature<TwinWithoutData> ? true : false,true>>;// 3. same on a base with NO index signature at all: still `any`, from the inherited member aloneinterfaceTwinOnBaseNoIndexextendsWithoutIndexSignature<BaseSchema>{type: 'tree-view';nodes?: TreeNode[]}exporttype_AnyWithoutAnyIndexSignature=Expect<Equal<TwinOnBaseNoIndex['data'],any>>;// @ts-expect-error — an UNDECLARED key on that base is an error, not `any`exporttype_UndeclaredIsErrorWithoutIndex=TwinOnBaseNoIndex['undeclaredTreeKey6939'];
PROBE_CLEAN_EXIT=0
--- control: expectation 2 flipped to `false` ---
src/__cr_probe__/mechanism.flipped.ts(21,50): error TS2344: Type 'false' does not satisfy the constraint 'true'.
FLIPPED_CLEAN_EXIT=2

Line 3 is decisive: with the index signature stripped entirely, ['data'] is still any. The index signature is not on the path.

Minimal fix: replace the two comment lines with
member DELETED -> resolves to the INHERITED \BaseSchema.data?: any` (base.ts:183) — `any`, and `Equal< any, … >` is false -> red. (The identical wrong sentence does *not* appear in undeclared-but-consumed-keys-6150.test.ts:91-94; there the keys are genuinely undeclared on BaseSchema, so the index-signature account is correct for them. It is wrong only for data`.)


Finding 2 — a published doc now states a contract fact this PR made false

File:content/docs/api/schema-reference.md:527 (unchanged by the PR; the PR touches 5 files, none under content/):

| `data` | `TreeNode[]` | **Required.** Nested tree data. Each node has `id`, `label`, optional `icon` and `children`. |

Before this PR that row was true; after it, the published API reference says data is required while the published type says data?: TreeNode[] and the published validator accepts documents without it. No gate covers the row: scripts/check-doc-component-types.mjs reads this file only for its type vocabulary (its exemption block at :410), and check:doc-snippets compiles ts/tsx fences, not property tables (check:doc-types exit 0, check:doc-fences exit 0 at head — both green with the stale row). This card's two landed siblings corrected the docs page that published the old required-ness (tooltip.mdx in #7456; plugin-map.mdx and the gantt page in #7471); this PR leaves its equivalent.

Measurement:

$ grep -n "| \`data\` | \`TreeNode\[\]\`" content/docs/api/schema-reference.md # at 6cad675a6
527:| `data` | `TreeNode[]` | **Required.** Nested tree data. …
$ git diff --stat fe4e7a9e8 6cad675a6 -- content/
(empty)

(content/docs/components/data-display/tree-view.mdx:28-31 already reads data?: TreeNode[] / nodes?: TreeNode[], so the component page is consistent; the API reference is the one that is not.)

Minimal fix: rewrite the row, e.g.
| \data` | `TreeNode[]` | Optional. Nested tree data, read only when `nodes` is absent (the renderer reads `nodes` first — objectui#6939). |— and, since the table lists neithernodesnortitle, a nodes` row would make the JSON example above it honest, but that omission predates this PR (#6150) and is not part of this finding.


Finding 3 — the changeset's "WIDENING, on both faces" is wrong on the reader side of the TS face, and the changeset is where it needs to be right

File:.changeset/6939-tree-view-nodes-mirror.md:24-29

**This is a WIDENING, on both faces.** `data` goes from required to optional on
the mirror and on the TypeScript twin in the same stroke; nothing that validated
before validates less.

For the validator and for an author of the TS type, true. For a reader of the published TS twin it is a narrowing: TreeViewSchema['data'] goes from TreeNode[] to TreeNode[] | undefined, and code that relied on presence stops compiling. The PR body says this ("the honest half of the same fact … its own read is || []-guarded") — the changeset, which is the artefact that becomes the CHANGELOG, says the opposite. AGENTS.md §版本号策略 is explicit that breaking semantics are stated in the changeset body.

Measurement (consumer probe against the published entry, tsc --ignoreConfig --noEmit --strict …):

importtype{TreeViewSchema,TreeNode}from'…/packages/types/src/index';exportfunctioncount(schema: TreeViewSchema): number{constnodes: TreeNode[]=schema.data;returnnodes.length+schema.data.length;}
--- at HEAD (data?: TreeNode[]) ---
consumer.probe.ts(4,9): error TS2322: Type 'TreeNode[] | undefined' is not assignable to type 'TreeNode[]'.
consumer.probe.ts(5,25): error TS18048: 'schema.data' is possibly 'undefined'.
HEAD CONSUMER_PROBE_EXIT=2
--- at BASE faces (data: TreeNode[]) blobs: 588502039b4df4ca47c150317474e4e61882a3b5 ---
BASE CONSUMER_PROBE_EXIT=0

On the bump:patch stands. scripts/check-changeset-no-major.mjs forbids only major (exit 0 at head: "No changeset declares a major bump."); the maintainer ruling on #6939 (comment 5510084784) grades this exact class "patch where the accept set only widens toward what already renders"; and both landed siblings moved a required TS member to optional under patch (objectName in .changeset/6939-objectql-record-source-refinement.md, children in .changeset/6939-overlay-trigger-mirror.md). The #6318 reviewer's patch→minor condition predates that ruling and concerned union membership, not optionality. So the grade is what the repo's rules call for; the description is not.

Minimal fix: replace the bolded sentence with something like: "The accept set widens on both faces for authors. For a reader of the TypeScript twin this is a narrowing — data is now TreeNode[] | undefined, so an unguarded schema.data read needs a guard; the only in-repo reader (tree-view.tsx:105) already has one (|| [])." Grade unchanged.


What I measured that checks out (so the adopter can see the FAIL is not about the change)

Accept set, both directions — NEWLY_FAIL = 0. Own census over every tracked *.json/*.yaml/*.yml (664 files, 664 parsed, 479 with a root type string), each root run through safeValidateSchema and every tree-view node (root or nested) through TreeViewSchema.safeParse, via tsx on source (packages/types/src/zod/index.zod.ts), at head and with both faces restored to 78a3cc23:

head: root-union ok=392 fail=91 base: ok=385 fail=98
NEWLY_PASS (base refused, head accepts): 4 tracked files at root —
components-data-display-tree-view/{deep-nesting,file-tree,org-chart,sidebar-navigation}.json
base issue on all four: ': Invalid input' (root) / 'data: Invalid input: expected array, received undefined' (member)
NEWLY_FAIL (base accepted, head refuses): 0

So the PR's "four" is exact for the objectui check predicate. The complete member-level count is larger, and the PR should be read as having counted at root level: a fifth tracked node flips — the nested tree-view in components-complex-resizable/editor-interface.json (/panels/0/content/0/children/1, nodes-only) — while its enclosing resizable document stays refused at both revs for an unrelated reason (panels.0.id, panels.1.id missing). Two inline TS literals also flip: { type: 'tree-view', bind: 'treeNodes' } (shadowed-renderer-behaviour.test.tsx:119) and the registration's own defaultProps; the data-spelled packages/types/examples/data-display-examples.json entry and TREE_CONTROL = { type, data: [] } stay accepted (True→True). The implementer's own #6951 comment names the nested one, so this is a counting-level difference, not an unknown.

Ablation reproduces the PR's prediction exactly. Base faces on disk (blobs d1d427db…/58850203…, anchors optional-zod 0 / required-zod 1 / optional-ts 0 / required-ts 1), vitest run on the two new files:

Tests 8 failed | 25 passed (33) VITEST_BASE_EXIT=1
× a tree-view with NO data source at all is legal… × the spelling the renderer reads FIRST now parses on its own
× …and the value SURVIVES the parse × both spellings validate — the accept set widened…
× {deep-nesting,file-tree,org-chart,sidebar-navigation} validates under safeValidateSchema

The four render-identity, four anti-vacuity, four "changes no pixel", "keys are DECLARED" (carrier {type,title,data:[],nodes}), "UNDECLARED key admitted" (carrier {type,data:[],[SENTINEL]}), and "BaseSchema alone would have admitted both" controls all stayed green — the rewritten controls from assertion 7 do redden only for their own reason. pnpm --filter @object-ui/types type-check under base faces: exit 2 with exactly TS2344 at :72 and TS2741 Property 'data' is missing at :85. Restore proven by state: blobs back to a0f1499d…/e4f16b12…, anchors 1/1, git status --porcelain empty.

Head is green everywhere I ran it.vitest run on the two new pins + undeclared-but-consumed-keys-6150 + safe-validate-corpus-6318: 4 files / 118 tests; zod-mirror-parity + handler-keys-json-refusal-6124: 2 files / 256 tests; the whole packages/types/ + examples/schema-catalog/ population plus shadowed-renderer-behaviour: 121 files / 3644 tests, exit 0. pnpm --filter @object-ui/types type-check exit 0 (clean tree, 0 error TS), with --listFiles showing both new test files in their tsconfig.test.json programs. Gates: check:control-bytes, check:doc-types, check:doc-fences, check-changeset-no-major, check-changeset-presence ("3 source file(s) of 1 released package… 1 changeset(s)") all exit 0. eslint on the four touched files: 0 errors; both new files 0 warnings; 30 + 1 warnings on the two source files (count matches the PR's 31).

Assertion 8 — measured, and it could not have caught anything.turbo run build --filter=@object-ui/example-schema-catalog...: 29 successful / 29 total; then pnpm --filter @object-ui/example-schema-catalog type-check (tsc --noEmit && tsc -p tsconfig.test.json): exit 0, 0 errors, and pnpm --filter @object-ui/components type-check: exit 0. The narrowing is closed.

Compile-time pins — what each catches (probe pins.probe.ts, exit 0):_TreeDataIsOptionalTreeNodes catches data re-required (TS2344, measured), data deleted (inherited any — Finding 1), and an element-type change. _TreeNodesStillTreeNodes / _TreeTitleStillString are optionality-blind by construction (NonNullable<Req['nodes']> equals NonNullable<Opt['nodes']> — measured true): they catch deletion and element-type change, not nodes/title becoming required; they are inherited from #6150 unchanged, so this is a limitation to know, not a defect of this PR. NODES_ONLY_DOCUMENT catches any key re-required (TS2741, measured) but cannot catch nodes/title deleted or renamed — { …, nodez: […] } and { …, undeclaredTreeKey6939: 1 } both compile through BaseSchema's index signature (measured); a wrong-typed declared key still errors (measured). The runtime pins cover the deletion case (data: 'not-an-array' refused AT data; BaseSchema alone accepts it — both measured green at head).

Other comment mechanisms:.passthrough()base.zod.ts:212; BaseSchema.data: z.any().optional()base.zod.ts:121; data?: anybase.ts:183; [key: string]: anybase.ts:409; renderer read at tree-view.tsx:104-106, inputs/defaultProps spelled nodes. All as stated. Parity ledger: TreeViewSchema's only entry is RuntimeOnlyDeclared: 'onNodeClick' (zod-mirror-parity.test.ts:1239); the compile-time NarrowerThanDeclared comparison does see optionality ([D[K]] extends [InputOf<Shape[K]>]), which is why both faces had to move together — and did (type-check exit 0 covers it).

Assertion 4 (no refinement) — sound.{ type: 'tree-view' } is accepted at head and { type: 'tree-view', data: [] } was accepted at base (census, both measured); the renderer's || [] guard means neither draws anything the other does not, so "at least one of" never guaranteed a non-empty tree. The census also shows why a two-limb refinement would be wrong: { type: 'tree-view', bind: 'treeNodes' } — a real, rendering document — flips refused→accepted here, and a nodes/data-only rule would refuse it. The #6939 maintainer ruling names a refinement for object-map/object-gantt only; #6951's comment 5527723261 recommending one is a triage recommendation on a needs-user-decision card, not a ruling.

Assertion 5 — confirmed stale half. Base data-display.ts:1220-1236 already declares nodes?/title? with #6150's comments; #6150's pins pass at head.

Merge target:git merge-tree --write-tree origin/main 6cad675a6 exit 0, no conflicts; origin/main's packages/react/src/index.ts still export * from './schema-input.js' (the new test's toRenderableSchema/SchemaRenderer imports resolve).

NARROWINGS

  • The card's "14 elements" — not reproduced or attempted; it comes from the docs-gallery harness. I confirmed only that the PR's 28 / 28 / 12 / 34 reproduce in this environment (all four render-identity cases pass at head).
  • eslint warnings on touched lines — I read the counts (30 + 1, both new files 0), not the line positions; whether all 31 sit outside the hunks is the PR's claim, not mine.
  • The PR's own reported runs (pnpm exec vitest run over its six files, its own ablation and blob hashes, its lock timings) — not re-read; superseded by my own runs above.
  • objectui check (node packages/cli/dist/cli.js check) — not run; my census reimplements its validation arm without the structural arm, so my "4 tracked files flip at root" is on that predicate, not on the CLI's printed count.
  • Whole-repo eslint, check:doc-snippets, check:readme-exports, Bundle Analysis, CI convergence — not run; CI-owned. At review time CI showed 27 completed checks all success/skipped with Type Check and the four Test shards still in progress.
  • The Two alias pairs on TextSchema and TreeViewSchema are now DECLARED in both spellings — and TreeViewSchema.data is required while nodes is the documented one (deferred rulings carried out of #6150) #6951 pair ruling — unmade at review time; the PR's revert-if-data-wins caveat is accurate but not something I can measure.

派发席处置

补丁轮,三条全修,⛔ 不放行。⚠️ Finding 2 是本席在席内核验时漏掉的真缺陷:本席查了 JSON 文档与 TS 消费者,没查声明合约事实的已发布散文,而本卡两个已落兄弟组(#7456#7471)恰恰都改了各自的文档页——本 PR 偏离了它自己这张卡的既定做法。

⛔ 注意 content/docs/api/schema-reference.md不是content/docs/releases/,改它不触犯「代码 PR 不得改发布说明」那条。

⭐ 三条 FAIL 都不要求重测改动本身:审查者已把 accept set 双向(NEWLY_FAIL=0)、消融红集、两面同步、以及本 PR 自己申报为未测的 example-schema-catalog type-checkexit 0,收窄已闭合)全部测过。patch 档位经三条独立支撑维持不变

本 PR 的 CI 在 6cad675a6 上已 32/32 全绿(其中 Lint run 33785853068、Docs Links run 33785852959 是按工作流正查确认的——get_check_runs 只返回了 30 条)。


Generated by Claude Code

Contract review of PR 7533 passed the change on every axis and failed three
statements ABOUT it. None of the code under repair moves here.
1. The compile-time pin's comment named the wrong mechanism. Deleting the
member does not fall through BaseSchema's `[key: string]: any`; it resolves
to the INHERITED declared `BaseSchema.data?: any` (base.ts:183), because a
declared member outranks an index signature. Verified independently before
accepting the finding: strip the index signature with the homomorphic
keyof-remap and `['data']` is still `any`, while an index-signature-only key
stops resolving at all -- both controls lit. The pin's verdict is unchanged;
only the reason it gives was wrong, and it contradicted the file header, the
TS-face doc comment and the mirror's describe().
2. `content/docs/api/schema-reference.md` still called `data` **Required**,
which this branch made false, and no gate covers that row. Corrected, and a
`nodes` row added beside it so the corrected sentence does not reference a
key the table never lists. The two sibling groups of this card each updated
their own doc page; this one had missed it.
3. The changeset called the change a widening "on both faces" without saying
that a READER of the TypeScript twin sees a narrowing: `data` is now
`TreeNode[] | undefined`, so an unguarded `schema.data` read stops compiling.
The PR body already said this; the changeset is the artefact that becomes the
CHANGELOG, so it is the one that had to. Level is unchanged and still patch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@claude

claudeBot commented Sep 3, 2026

Copy link
Copy Markdown
ContributorAuthor

Patch round — all three FAIL findings addressed at 794aa2ad4. Nothing under repair moved.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. One commit
(5d7e47fa7) plus a merge of origin/main at d04e79a80 (merge 794aa2ad4, no
conflicts). ⛔ Changeset level untouched, still patch. ⛔ nodes / title
declarations untouched. ⛔ No re-测 of the change itself.

Finding 1 — I checked it before accepting it, and the review is right

I did not take the mechanism on trust, because the instruction was to say so if I measured
otherwise. A throwaway type-level probe, run through pnpm --filter @object-ui/types type-check and then deleted, with both controls lit:

proberesult
['data'] with the index signature presentany
['data'] with the index signature STRIPPED (the homomorphic keyof-remap zod-mirror-parity.test.ts uses)still any
data survives the strippingyes, so it is a declared member
CONTROL: an index-signature-only key survives the strippingno — so the stripping really is doing something
CONTROL: that same key reads as any while the index signature is presentyes

All five compiled, exit 0. BaseSchema carries bothdata?: any (base.ts:183) and
[key: string]: any (base.ts:409), and the declared member — inherited or not — outranks
the index signature. So the comment was wrong and the review's replacement is right;
the pin's verdict never depended on it. Replacement text taken as given, plus a short
paragraph recording the measurement so the next reader does not have to redo it.

undeclared-but-consumed-keys-6150.test.ts:91-94 deliberately NOT touched — the same
sentence is true there, because those 13 keys genuinely are undeclared on BaseSchema.

Finding 2 — content/docs/api/schema-reference.md:527

Corrected to the suggested text. I also added onenodes row directly above it, and
that is a judgment call worth flagging rather than burying: the corrected sentence says
"read only when nodes is absent", and nodes was not in that table, so the correction
would otherwise have created a dangling reference. One row, not a documentation sweep —
title is still absent and I left it alone as the #6150 leftover the review says it is.
The sentence about node shape (id, label, optional icon and children) moved onto
the nodes row rather than being dropped.

content/docs/releases/ not touched.

ℹ️ Two more census data points, recorded not acted on: the JSON example immediately above
that table also authors data, as does the tree-view entry in
packages/types/examples/data-display-examples.json. Both are still legal after this
branch, so neither is a false statement — but if the #6951 retirement lands on nodes,
they are two of the documents that have to move.

Finding 3 — .changeset/6939-tree-view-nodes-mirror.md

Split into the two halves that are separately true: authors get a widening on both faces;
a reader of the TypeScript twin gets a narrowing, TreeNode[] | undefined, with the
TS2322 + TS18048 consumer-probe result named, the one in-repo reader shown to be
already guarded, and out-of-repo unguarded consumers named as the population the paragraph
exists for. Level unchanged, with the three reasons it stays patch stated in place.

Convergence verification, at 794aa2ad4 (post-merge head)

Heavy runs serialised through the shared verify lock; exit codes captured to files before
any pipe.

checkexit
pnpm exec vitest run over both new test files0 — "Test Files 2 passed (2)"
pnpm --filter @object-ui/types type-check0
pnpm check:doc-types0
pnpm check:doc-fences0
node scripts/check-control-bytes.mjs0 (6206 tracked files)
node scripts/check-changeset-no-major.mjs0

⚠️ Worth recording about check:doc-types: its counters are byte-identical before and
after my documentation edit — same 188 documents, same 4 key tables, same 24 rows, same 45
table keys. That is the review's own point measured from the other side: the gate never
looked at this table, so the stale row could not have been caught by it.

Thank you for the census — the root-level 4 is exactly what I claimed and the
member-level flips (the nested editor-interface.json node, and the real bind-only
document at shadowed-renderer-behaviour.test.tsx:119) are a level distinction I had not
made. I have left the counts alone as instructed.

Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

在席处置:补丁轮已核,放行 —— 剥标签 + ready + auto-merge SQUASH

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。链条:隔离审查 FAIL 三条 → 补丁轮 → 在席按内容核实 → 放行。

决定性的一读:被审查过的改动没有动

补丁轮之后我没有重跑合约审查,依据是这个:

 6cad675a6 (被审的 head) → 794aa2ad4 (补丁后)
data-display.zod.ts a0f1499dff920cfa1de27573396e1c2734674d7d IDENTICAL
data-display.ts e4f16b1261cfd621cac6d83a061041967768a5ec IDENTICAL

⇒ 两个被审源文件逐字节相同 ⇒ 审查者对改动本身的裁断(accept set 双向 NEWLY_FAIL=0、消融红集吻合、两面同步、parity ledger、干净 merge)原样成立,无需重审。补丁提交 5d7e47fa7 只碰了三个文件——正是三条 finding 各自的落点(changeset、content/docs/api/schema-reference.md、测试注释);diff stat 看着大是因为它同时 merge 了 origin/main

三条修正逐一核过

⭐ 一条从反面得来的读数,值得留档

实现者报告:check:doc-types 在它改动文档前后计数器逐字节相同(同样 188 文档 / 4 个 key table / 24 行 / 45 个 table key)。它把这条报成「一个对我修的那行什么也没说的绿」,而不是报成覆盖率——这正是审查者「no gate covers this row」从另一侧的验证。

放行状态

CI 在当前 head 794aa2ad4completed / success⚠️ 不是旧 head 6cad675a6 那个绿——按 status=completed 过滤会返回旧 head 的绿,那对当前 head 什么也没说)。已剥 needs:contract-review、ready、auto-merge SQUASH,并按 base-sha 链正向确认入队(队首,base = f0f774b0d)。

⚠️ 放行动作一度因 GitHub 配额半完成(标签已剥、undraft 失败),已排定时提醒跟踪并在配额恢复后补完 —— ⛔ 没有静默停在半完成状态。

⛔ 未随本 PR 解决的

#6951needs-user-decisionnodes / data 的 ADR-0049 退役选择未裁。本 PR 不做那个选择(两种拼法都仍解析)。若最终 data 胜出,本分支需连同其余一并回退。相关记录已留在 #6951(含本席对自己一处口径错误的订正)。


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939) - #7533

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-6939-group2-treeview-mirror
Sep 3, 2026
Merged

fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939)#7533
os-project-manager merged 3 commits into
mainfrom
claude/issue-6939-group2-treeview-mirror

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Part of #6939group 2 of 8 (tree-view). Deliberately worded to leave the card open: four
of that card's eight groups are still unimplemented, so it must stay open.

⚠️Draft + needs:contract-review on purpose. This moves an accept surface;
the dispatching seat reviews before it lands. Please do not mark ready or enable
auto-merge.

The repair

TreeViewSchema REQUIRED data — the limb the renderer reads third:

const rawNodes = boundData || schema.nodes || schema.data || [];
// packages/components/src/renderers/data-display/tree-view.tsx:105

The registration's own inputs and defaultProps spell it nodes, and the four
components-data-display-tree-view/* catalog entries are those defaultProps.
data is now optional on the zod mirror and on the TypeScript twin, in the same
stroke.

What I re-measured, and where the card is stale

Card readings re-derived on origin/main at fe4e7a9e8, through a bare
SchemaRenderer harness:

card saysre-measuredverdict
4 files4 (deep-nesting, file-tree, org-chart, sidebar-navigation), all type,title,nodes✅ confirmed
zod demands data (required)nodes-only parse false, data-only parse true, bare {type} parse false✅ confirmed
renderer reads boundData or schema.nodes or schema.dataread site intact at :105; title at :115 / :117✅ confirmed
render after "correcting" the fixture: identical — 14 elementsidentical: ✅ (element count, tag census and a SHA-256 of textContent, all four tiles). 14: not reproduced — this harness measures 28 / 28 / 12 / 34⚠️ partly stale

Element counts are harness-bound (the docs-gallery harness gives different absolutes
for the same tile), so the pin records identity within one harness and carries the
numbers I measured rather than the card's.

⚠️The card's stated disposition is also half-landed already. It asks to "declare
nodes and title, and stop requiring data". objectui#6150 landed the declaration
half after the card was filed — both keys are declared on both faces today, and #6150
wrote in as many words that relaxing data was a separate accept-set change. This PR
is only that remaining half; nodes and title are untouched.

data stays declared rather than being deleted

The intuitive read of "the renderer prefers nodes" is to drop data. That is wrong,
and measurably: BaseSchema already declares data (z.any().optional(), data?: any
on the TS face), so removing the member would not reject the key — it would admit it
unvalidated while the renderer went on reading it. The pin
control: BaseSchema alone would have admitted both of those makes the difference
visible: the base object accepts the two values the member refuses.

No refinement was added

Unlike this card's object-map / object-gantt group, which added an "at least one of"
refinement. A tree-view carrying no data source at all becomes legal here, and that
admits no new rendering outcome: { data: [] } was already legal and already drew
the same empty tree. A refinement would forbid a spelling of an empty state the contract
already permits rather than buy a guarantee.

Verification

All runs at 6cad675a6; heavy runs serialised through the shared verify lock.

  • pnpm exec vitest run over the two new pins plus undeclared-but-consumed-keys-6150,
    zod-mirror-parity, safe-validate-corpus-6318, handler-keys-json-refusal-6124
    6 files, 374 tests, all passing. The parity ledger needed no edit: tree-view's
    only entry in any of the three ledgers is RuntimeOnlyDeclaredonNodeClick, and
    this change moves optionality, not key membership.
  • pnpm --filter @object-ui/types type-check — green (covers tsconfig.test.json,
    so both compile-time pins are checked).
  • pnpm --filter @object-ui/components type-check — green, after building the
    9-package closure. That is the only TS-face consumer of TreeViewSchema outside
    packages/types, by exhaustive grep.
  • eslint on the four touched files: exit 0, 0 errors, 31 warnings — all pre-existing
    no-explicit-any on lines outside every hunk.
  • check:control-bytes, check:doc-types, check:doc-fences,
    check-changeset-no-major — green.

Reversible verification (both faces restored to fe4e7a9e8, then restored back)

Mutation proven on disk by blob hash and by anchor counts in both directions
(optional-anchor 1 to 0, required-anchor 0 to 1, on both files). Restore proven by
state: on-disk blobs equal the HEAD blobs, git diff HEAD empty, git status
empty. Absolute paths, trap on EXIT INT TERM.

Predicted red set and measured red set agreed:

  • vitest, mutated: exit 1, 8 red — the three "nodes-only is legal" pins, the four
    validates under safeValidateSchema cases, and both spellings validate.
  • vitest, mutated: still green — all four render-identity cases, all four
    anti-vacuity cases, all four "correcting it to data changes no pixel" cases, and the
    whole "data is still validated" group. That asymmetry is the point: the pin is on
    the validator, and the renderer did not move.
  • type-check, mutated: exit 2 on exactly the two compile-time pins vitest cannot
    see — TS2344 on the optionality equality and TS2741 (Property 'data' is missing)
    on the nodes-only document literal.
  • restored: vitest exit 0 (5 files), type-check exit 0.

A first ablation round found two of my own controls reddening for the wrong reason
(their carrier documents were nodes-only, so the pre-repair mirror refused the carrier
itself). Both were rewritten onto data-bearing carriers that are legal in either state,
and the second round shows them staying green while the load-bearing pins still redden.

Declared narrowing

pnpm --filter @object-ui/example-schema-catalog type-check was not measured here.
It needs a 30-package dist closure, and the shared verify lock refused twice at its
9-minute budget with another seat holding it for 550s. Its local output is a precondition
failure, not a verdict: 67 errors, 63 of them TS2307/TS2882 "cannot find module",
the other 4 consequential implicit-any in files this PR does not touch — and the two
lines naming this PR's own file are that same "cannot find module @object-ui/components
/ @object-ui/react" class that every sibling test file reports. Nothing in it is about
this change. That file's behaviour is proven by executing it (green, and red under
ablation); CI type-checks the farm regardless.

Narrowing evidence for the lint side: the linted population is each package's own
eslint .; this diff touches files in two packages and all four were linted (count read
from --format json); and eslint.config.js configures no type-aware linting
(projectService 0, parserOptions 0, against a lit control of languageOptions 1), so
this diff cannot move the verdict on a file it does not touch.

Changeset

@object-ui/types: patch. The accept set only widens — no document that validated
before validates less, including the data-spelled tree-view in
packages/types/examples/data-display-examples.json — and this is the same shape as the
two sibling groups of this card that already landed on patch. The TS twin's
required-to-optional move is the honest half of the same fact: the renderer never
guaranteed data was present, its own read is || []-guarded, and the single consumer
type-checks clean.

⚠️ Interaction with #6951 — the reviewer needs to see this

Found while de-duplicating an unrelated observation, not supplied by the dispatch.
#6951 is open and carries needs-user-decision, and it explicitly folds in this
exact question:

data is required on TreeViewSchema while nodes is the spelling the docs name
as canonical.
[...] Whichever way the pair ruling goes, the required-ness has to move
with it. That is why it is one card.

#6951's own ruling is an ADR-0049 enforce-or-remove choice: retire one spelling of the
nodes / data pair. That choice has not been made. This PR does not make it — it
retires neither spelling, and both keep parsing. What it does do is presume the direction
that every in-repo artefact already names: the doc comments name nodes the winner,
#6951 itself says "nodes is the winner named in the doc comment", the registration's
inputs and defaultProps spell it nodes, the docs page lists both as optional, and
the four catalog entries author nodes.

So the two possible rulings land differently on this PR:

  • nodes wins (what every artefact points to) — this PR is a strictly smaller step in
    the same direction and stays correct as-is.
  • data wins — this PR would have to be reverted along with the rest.

⇒ Flagging rather than choosing. If the reviewing seat would rather this waited on #6951,
the branch is a clean single commit and nothing else depends on it. I have also left a
cross-reference on #6951 so this does not become the invisible interim that card was
filed to prevent.

Out of scope

⛔ Only group 2. Group 5 (chart) lives in the same file and is untouched; group 3
(kanban) is a genuine fork awaiting a ruling; groups 4 and 7 are unassigned. No catalog
fixture is edited — the card is explicit that the fixtures are the side that is right.

Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`TreeViewSchema` required `data`, which the renderer reads THIRD:
const rawNodes = boundData || schema.nodes || schema.data || [];
// packages/components/src/renderers/data-display/tree-view.tsx:105
The registration's own `inputs` and `defaultProps` spell it `nodes`, and the
four `components-data-display-tree-view/*` catalog entries ARE those
`defaultProps`, so `safeValidateSchema` refused every one of them while the
renderer drew them correctly. Re-measured on origin/main at fe4e7a9: four
refusals, and renders byte-identical under either spelling (28 / 28 / 12 / 34
elements, same tag census, same textContent SHA-256) -- objectui#6318's own
triage test for "the schema was the wrong side".
`data` becomes optional on the zod mirror and on the TypeScript twin in the
same stroke. It stays DECLARED rather than deleted: BaseSchema already declares
`data` as `z.any().optional()`, so removing the member would not reject the key
-- it would admit it unvalidated while the renderer went on reading it.
No refinement is added, unlike this card's object-map / object-gantt group: a
tree-view with no data source now validates, and that admits no new rendering
outcome, since `{ data: [] }` was already legal and already drew the same empty
tree.
`nodes` and `title` are objectui#6150's declarations and are untouched; that
card declared the reads and said in as many words that relaxing `data` was a
separate accept-set change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

合约审查裁断:FAIL —— 逐字采信

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。本席是 opus,不在 CONTRACT_REVIEW_TIER,故本 Clause-② yes 的 PR 由隔离审查者出裁。简报只含卡片、裁决与本 PR,⛔ 不含派发指令、⛔ 不含本席结论。

转录档位核验(先决条件):对审查者转录只提取 harness 生成的 model 字段(⛔ 不对自由文本搜关键词——一句「未检出 X」自身含 X,会污染下一轮检测)。248 行、0 行不可解析、119 个 assistant 轮次,全部 claude-fable-5-1,无 fallback 证据 ⇒ 裁断在档,逐字采信。⛔ 未改写、未删节、未润色。

⚠️ 三条 FAIL 全部是 PR 关于改动的陈述,不是改动本身;审查者对改动本身逐轴测过并通过(NEWLY_FAIL=0、消融红集吻合、两面同步、parity ledger、以及本 PR 声明未测的那个 type-check)。


VERDICT: FAIL

The code change itself checks out on every axis I could measure — accept-set direction (NEWLY_FAIL=0), ablation red set, both faces moving together, parity ledger, both heavy type-checks including the one the PR declared unmeasured, and a clean merge onto the current origin/main. The FAIL is on three items the PR says about the change that are wrong or undisclosed, each with a one-to-two-line fix. None requires re-measuring the change.

All measurements were mine, on a detached worktree at 6cad675a6 (git worktree add --detach /home/user/objectui-cr7533 6cad675a6, pnpm install --frozen-lockfile exit 0), base faces taken from 78a3cc23 (blob-identical to merge-base fe4e7a9e8 for both touched source files: zod d1d427db…, ts 58850203…). Worktree removed after review.


Finding 1 — a compile-time pin bites for a different mechanism than its comment names

File:packages/types/src/__tests__/tree-view-data-optional-6939.test.ts:60-67

 * - member DELETED -> falls through `BaseSchema`'s `[key: string]: any`
* to `any`, and `Equal< any, … >` is false -> red

That is not what happens. BaseSchemadeclaresdata?: any (packages/types/src/base.ts:183), so a twin without its own data member resolves ['data'] to the inherited declared member, not to the index signature. The pin still reddens (inherited member is any), but the comment records the wrong mechanism — and this file's own header paragraph, the TS doc comment on data, and the zod .describe() all state the correct one, so the pin's comment contradicts the rest of the PR.

Measurement (tsc --ignoreConfig --noEmit --strict … --types react src/__cr_probe__/mechanism.probe.ts run inside packages/types):

typeWithoutIndexSignature<D>={[KinkeyofDasstringextendsK ? never : numberextendsK ? never : K]: D[K]};// 1. `data` IS a declared member of BaseSchema (survives index-signature removal)exporttype_DataIsDeclaredOnBase=Expect<Equal<'data'extendskeyofWithoutIndexSignature<BaseSchema> ? true : false,true>>;exporttype_SentinelIsNotDeclared=Expect<Equal<'undeclaredTreeKey6939'extendskeyofWithoutIndexSignature<BaseSchema> ? true : false,false>>;// 2. twin without its own `data`: still a DECLARED member, and `any`interfaceTwinWithoutDataextendsBaseSchema{type: 'tree-view';nodes?: TreeNode[]}exporttype_InheritedDataIsAny=Expect<Equal<TwinWithoutData['data'],any>>;exporttype_InheritedDataStillDeclared=Expect<Equal<'data'extendskeyofWithoutIndexSignature<TwinWithoutData> ? true : false,true>>;// 3. same on a base with NO index signature at all: still `any`, from the inherited member aloneinterfaceTwinOnBaseNoIndexextendsWithoutIndexSignature<BaseSchema>{type: 'tree-view';nodes?: TreeNode[]}exporttype_AnyWithoutAnyIndexSignature=Expect<Equal<TwinOnBaseNoIndex['data'],any>>;// @ts-expect-error — an UNDECLARED key on that base is an error, not `any`exporttype_UndeclaredIsErrorWithoutIndex=TwinOnBaseNoIndex['undeclaredTreeKey6939'];
PROBE_CLEAN_EXIT=0
--- control: expectation 2 flipped to `false` ---
src/__cr_probe__/mechanism.flipped.ts(21,50): error TS2344: Type 'false' does not satisfy the constraint 'true'.
FLIPPED_CLEAN_EXIT=2

Line 3 is decisive: with the index signature stripped entirely, ['data'] is still any. The index signature is not on the path.

Minimal fix: replace the two comment lines with
member DELETED -> resolves to the INHERITED \BaseSchema.data?: any` (base.ts:183) — `any`, and `Equal< any, … >` is false -> red. (The identical wrong sentence does *not* appear in undeclared-but-consumed-keys-6150.test.ts:91-94; there the keys are genuinely undeclared on BaseSchema, so the index-signature account is correct for them. It is wrong only for data`.)


Finding 2 — a published doc now states a contract fact this PR made false

File:content/docs/api/schema-reference.md:527 (unchanged by the PR; the PR touches 5 files, none under content/):

| `data` | `TreeNode[]` | **Required.** Nested tree data. Each node has `id`, `label`, optional `icon` and `children`. |

Before this PR that row was true; after it, the published API reference says data is required while the published type says data?: TreeNode[] and the published validator accepts documents without it. No gate covers the row: scripts/check-doc-component-types.mjs reads this file only for its type vocabulary (its exemption block at :410), and check:doc-snippets compiles ts/tsx fences, not property tables (check:doc-types exit 0, check:doc-fences exit 0 at head — both green with the stale row). This card's two landed siblings corrected the docs page that published the old required-ness (tooltip.mdx in #7456; plugin-map.mdx and the gantt page in #7471); this PR leaves its equivalent.

Measurement:

$ grep -n "| \`data\` | \`TreeNode\[\]\`" content/docs/api/schema-reference.md # at 6cad675a6
527:| `data` | `TreeNode[]` | **Required.** Nested tree data. …
$ git diff --stat fe4e7a9e8 6cad675a6 -- content/
(empty)

(content/docs/components/data-display/tree-view.mdx:28-31 already reads data?: TreeNode[] / nodes?: TreeNode[], so the component page is consistent; the API reference is the one that is not.)

Minimal fix: rewrite the row, e.g.
| \data` | `TreeNode[]` | Optional. Nested tree data, read only when `nodes` is absent (the renderer reads `nodes` first — objectui#6939). |— and, since the table lists neithernodesnortitle, a nodes` row would make the JSON example above it honest, but that omission predates this PR (#6150) and is not part of this finding.


Finding 3 — the changeset's "WIDENING, on both faces" is wrong on the reader side of the TS face, and the changeset is where it needs to be right

File:.changeset/6939-tree-view-nodes-mirror.md:24-29

**This is a WIDENING, on both faces.** `data` goes from required to optional on
the mirror and on the TypeScript twin in the same stroke; nothing that validated
before validates less.

For the validator and for an author of the TS type, true. For a reader of the published TS twin it is a narrowing: TreeViewSchema['data'] goes from TreeNode[] to TreeNode[] | undefined, and code that relied on presence stops compiling. The PR body says this ("the honest half of the same fact … its own read is || []-guarded") — the changeset, which is the artefact that becomes the CHANGELOG, says the opposite. AGENTS.md §版本号策略 is explicit that breaking semantics are stated in the changeset body.

Measurement (consumer probe against the published entry, tsc --ignoreConfig --noEmit --strict …):

importtype{TreeViewSchema,TreeNode}from'…/packages/types/src/index';exportfunctioncount(schema: TreeViewSchema): number{constnodes: TreeNode[]=schema.data;returnnodes.length+schema.data.length;}
--- at HEAD (data?: TreeNode[]) ---
consumer.probe.ts(4,9): error TS2322: Type 'TreeNode[] | undefined' is not assignable to type 'TreeNode[]'.
consumer.probe.ts(5,25): error TS18048: 'schema.data' is possibly 'undefined'.
HEAD CONSUMER_PROBE_EXIT=2
--- at BASE faces (data: TreeNode[]) blobs: 588502039b4df4ca47c150317474e4e61882a3b5 ---
BASE CONSUMER_PROBE_EXIT=0

On the bump:patch stands. scripts/check-changeset-no-major.mjs forbids only major (exit 0 at head: "No changeset declares a major bump."); the maintainer ruling on #6939 (comment 5510084784) grades this exact class "patch where the accept set only widens toward what already renders"; and both landed siblings moved a required TS member to optional under patch (objectName in .changeset/6939-objectql-record-source-refinement.md, children in .changeset/6939-overlay-trigger-mirror.md). The #6318 reviewer's patch→minor condition predates that ruling and concerned union membership, not optionality. So the grade is what the repo's rules call for; the description is not.

Minimal fix: replace the bolded sentence with something like: "The accept set widens on both faces for authors. For a reader of the TypeScript twin this is a narrowing — data is now TreeNode[] | undefined, so an unguarded schema.data read needs a guard; the only in-repo reader (tree-view.tsx:105) already has one (|| [])." Grade unchanged.


What I measured that checks out (so the adopter can see the FAIL is not about the change)

Accept set, both directions — NEWLY_FAIL = 0. Own census over every tracked *.json/*.yaml/*.yml (664 files, 664 parsed, 479 with a root type string), each root run through safeValidateSchema and every tree-view node (root or nested) through TreeViewSchema.safeParse, via tsx on source (packages/types/src/zod/index.zod.ts), at head and with both faces restored to 78a3cc23:

head: root-union ok=392 fail=91 base: ok=385 fail=98
NEWLY_PASS (base refused, head accepts): 4 tracked files at root —
components-data-display-tree-view/{deep-nesting,file-tree,org-chart,sidebar-navigation}.json
base issue on all four: ': Invalid input' (root) / 'data: Invalid input: expected array, received undefined' (member)
NEWLY_FAIL (base accepted, head refuses): 0

So the PR's "four" is exact for the objectui check predicate. The complete member-level count is larger, and the PR should be read as having counted at root level: a fifth tracked node flips — the nested tree-view in components-complex-resizable/editor-interface.json (/panels/0/content/0/children/1, nodes-only) — while its enclosing resizable document stays refused at both revs for an unrelated reason (panels.0.id, panels.1.id missing). Two inline TS literals also flip: { type: 'tree-view', bind: 'treeNodes' } (shadowed-renderer-behaviour.test.tsx:119) and the registration's own defaultProps; the data-spelled packages/types/examples/data-display-examples.json entry and TREE_CONTROL = { type, data: [] } stay accepted (True→True). The implementer's own #6951 comment names the nested one, so this is a counting-level difference, not an unknown.

Ablation reproduces the PR's prediction exactly. Base faces on disk (blobs d1d427db…/58850203…, anchors optional-zod 0 / required-zod 1 / optional-ts 0 / required-ts 1), vitest run on the two new files:

Tests 8 failed | 25 passed (33) VITEST_BASE_EXIT=1
× a tree-view with NO data source at all is legal… × the spelling the renderer reads FIRST now parses on its own
× …and the value SURVIVES the parse × both spellings validate — the accept set widened…
× {deep-nesting,file-tree,org-chart,sidebar-navigation} validates under safeValidateSchema

The four render-identity, four anti-vacuity, four "changes no pixel", "keys are DECLARED" (carrier {type,title,data:[],nodes}), "UNDECLARED key admitted" (carrier {type,data:[],[SENTINEL]}), and "BaseSchema alone would have admitted both" controls all stayed green — the rewritten controls from assertion 7 do redden only for their own reason. pnpm --filter @object-ui/types type-check under base faces: exit 2 with exactly TS2344 at :72 and TS2741 Property 'data' is missing at :85. Restore proven by state: blobs back to a0f1499d…/e4f16b12…, anchors 1/1, git status --porcelain empty.

Head is green everywhere I ran it.vitest run on the two new pins + undeclared-but-consumed-keys-6150 + safe-validate-corpus-6318: 4 files / 118 tests; zod-mirror-parity + handler-keys-json-refusal-6124: 2 files / 256 tests; the whole packages/types/ + examples/schema-catalog/ population plus shadowed-renderer-behaviour: 121 files / 3644 tests, exit 0. pnpm --filter @object-ui/types type-check exit 0 (clean tree, 0 error TS), with --listFiles showing both new test files in their tsconfig.test.json programs. Gates: check:control-bytes, check:doc-types, check:doc-fences, check-changeset-no-major, check-changeset-presence ("3 source file(s) of 1 released package… 1 changeset(s)") all exit 0. eslint on the four touched files: 0 errors; both new files 0 warnings; 30 + 1 warnings on the two source files (count matches the PR's 31).

Assertion 8 — measured, and it could not have caught anything.turbo run build --filter=@object-ui/example-schema-catalog...: 29 successful / 29 total; then pnpm --filter @object-ui/example-schema-catalog type-check (tsc --noEmit && tsc -p tsconfig.test.json): exit 0, 0 errors, and pnpm --filter @object-ui/components type-check: exit 0. The narrowing is closed.

Compile-time pins — what each catches (probe pins.probe.ts, exit 0):_TreeDataIsOptionalTreeNodes catches data re-required (TS2344, measured), data deleted (inherited any — Finding 1), and an element-type change. _TreeNodesStillTreeNodes / _TreeTitleStillString are optionality-blind by construction (NonNullable<Req['nodes']> equals NonNullable<Opt['nodes']> — measured true): they catch deletion and element-type change, not nodes/title becoming required; they are inherited from #6150 unchanged, so this is a limitation to know, not a defect of this PR. NODES_ONLY_DOCUMENT catches any key re-required (TS2741, measured) but cannot catch nodes/title deleted or renamed — { …, nodez: […] } and { …, undeclaredTreeKey6939: 1 } both compile through BaseSchema's index signature (measured); a wrong-typed declared key still errors (measured). The runtime pins cover the deletion case (data: 'not-an-array' refused AT data; BaseSchema alone accepts it — both measured green at head).

Other comment mechanisms:.passthrough()base.zod.ts:212; BaseSchema.data: z.any().optional()base.zod.ts:121; data?: anybase.ts:183; [key: string]: anybase.ts:409; renderer read at tree-view.tsx:104-106, inputs/defaultProps spelled nodes. All as stated. Parity ledger: TreeViewSchema's only entry is RuntimeOnlyDeclared: 'onNodeClick' (zod-mirror-parity.test.ts:1239); the compile-time NarrowerThanDeclared comparison does see optionality ([D[K]] extends [InputOf<Shape[K]>]), which is why both faces had to move together — and did (type-check exit 0 covers it).

Assertion 4 (no refinement) — sound.{ type: 'tree-view' } is accepted at head and { type: 'tree-view', data: [] } was accepted at base (census, both measured); the renderer's || [] guard means neither draws anything the other does not, so "at least one of" never guaranteed a non-empty tree. The census also shows why a two-limb refinement would be wrong: { type: 'tree-view', bind: 'treeNodes' } — a real, rendering document — flips refused→accepted here, and a nodes/data-only rule would refuse it. The #6939 maintainer ruling names a refinement for object-map/object-gantt only; #6951's comment 5527723261 recommending one is a triage recommendation on a needs-user-decision card, not a ruling.

Assertion 5 — confirmed stale half. Base data-display.ts:1220-1236 already declares nodes?/title? with #6150's comments; #6150's pins pass at head.

Merge target:git merge-tree --write-tree origin/main 6cad675a6 exit 0, no conflicts; origin/main's packages/react/src/index.ts still export * from './schema-input.js' (the new test's toRenderableSchema/SchemaRenderer imports resolve).

NARROWINGS

  • The card's "14 elements" — not reproduced or attempted; it comes from the docs-gallery harness. I confirmed only that the PR's 28 / 28 / 12 / 34 reproduce in this environment (all four render-identity cases pass at head).
  • eslint warnings on touched lines — I read the counts (30 + 1, both new files 0), not the line positions; whether all 31 sit outside the hunks is the PR's claim, not mine.
  • The PR's own reported runs (pnpm exec vitest run over its six files, its own ablation and blob hashes, its lock timings) — not re-read; superseded by my own runs above.
  • objectui check (node packages/cli/dist/cli.js check) — not run; my census reimplements its validation arm without the structural arm, so my "4 tracked files flip at root" is on that predicate, not on the CLI's printed count.
  • Whole-repo eslint, check:doc-snippets, check:readme-exports, Bundle Analysis, CI convergence — not run; CI-owned. At review time CI showed 27 completed checks all success/skipped with Type Check and the four Test shards still in progress.
  • The Two alias pairs on TextSchema and TreeViewSchema are now DECLARED in both spellings — and TreeViewSchema.data is required while nodes is the documented one (deferred rulings carried out of #6150) #6951 pair ruling — unmade at review time; the PR's revert-if-data-wins caveat is accurate but not something I can measure.

派发席处置

补丁轮,三条全修,⛔ 不放行。⚠️ Finding 2 是本席在席内核验时漏掉的真缺陷:本席查了 JSON 文档与 TS 消费者,没查声明合约事实的已发布散文,而本卡两个已落兄弟组(#7456#7471)恰恰都改了各自的文档页——本 PR 偏离了它自己这张卡的既定做法。

⛔ 注意 content/docs/api/schema-reference.md不是content/docs/releases/,改它不触犯「代码 PR 不得改发布说明」那条。

⭐ 三条 FAIL 都不要求重测改动本身:审查者已把 accept set 双向(NEWLY_FAIL=0)、消融红集、两面同步、以及本 PR 自己申报为未测的 example-schema-catalog type-checkexit 0,收窄已闭合)全部测过。patch 档位经三条独立支撑维持不变

本 PR 的 CI 在 6cad675a6 上已 32/32 全绿(其中 Lint run 33785853068、Docs Links run 33785852959 是按工作流正查确认的——get_check_runs 只返回了 30 条)。


Generated by Claude Code

Contract review of PR 7533 passed the change on every axis and failed three
statements ABOUT it. None of the code under repair moves here.
1. The compile-time pin's comment named the wrong mechanism. Deleting the
member does not fall through BaseSchema's `[key: string]: any`; it resolves
to the INHERITED declared `BaseSchema.data?: any` (base.ts:183), because a
declared member outranks an index signature. Verified independently before
accepting the finding: strip the index signature with the homomorphic
keyof-remap and `['data']` is still `any`, while an index-signature-only key
stops resolving at all -- both controls lit. The pin's verdict is unchanged;
only the reason it gives was wrong, and it contradicted the file header, the
TS-face doc comment and the mirror's describe().
2. `content/docs/api/schema-reference.md` still called `data` **Required**,
which this branch made false, and no gate covers that row. Corrected, and a
`nodes` row added beside it so the corrected sentence does not reference a
key the table never lists. The two sibling groups of this card each updated
their own doc page; this one had missed it.
3. The changeset called the change a widening "on both faces" without saying
that a READER of the TypeScript twin sees a narrowing: `data` is now
`TreeNode[] | undefined`, so an unguarded `schema.data` read stops compiling.
The PR body already said this; the changeset is the artefact that becomes the
CHANGELOG, so it is the one that had to. Level is unchanged and still patch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@claude

claudeBot commented Sep 3, 2026

Copy link
Copy Markdown
ContributorAuthor

Patch round — all three FAIL findings addressed at 794aa2ad4. Nothing under repair moved.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. One commit
(5d7e47fa7) plus a merge of origin/main at d04e79a80 (merge 794aa2ad4, no
conflicts). ⛔ Changeset level untouched, still patch. ⛔ nodes / title
declarations untouched. ⛔ No re-测 of the change itself.

Finding 1 — I checked it before accepting it, and the review is right

I did not take the mechanism on trust, because the instruction was to say so if I measured
otherwise. A throwaway type-level probe, run through pnpm --filter @object-ui/types type-check and then deleted, with both controls lit:

proberesult
['data'] with the index signature presentany
['data'] with the index signature STRIPPED (the homomorphic keyof-remap zod-mirror-parity.test.ts uses)still any
data survives the strippingyes, so it is a declared member
CONTROL: an index-signature-only key survives the strippingno — so the stripping really is doing something
CONTROL: that same key reads as any while the index signature is presentyes

All five compiled, exit 0. BaseSchema carries bothdata?: any (base.ts:183) and
[key: string]: any (base.ts:409), and the declared member — inherited or not — outranks
the index signature. So the comment was wrong and the review's replacement is right;
the pin's verdict never depended on it. Replacement text taken as given, plus a short
paragraph recording the measurement so the next reader does not have to redo it.

undeclared-but-consumed-keys-6150.test.ts:91-94 deliberately NOT touched — the same
sentence is true there, because those 13 keys genuinely are undeclared on BaseSchema.

Finding 2 — content/docs/api/schema-reference.md:527

Corrected to the suggested text. I also added onenodes row directly above it, and
that is a judgment call worth flagging rather than burying: the corrected sentence says
"read only when nodes is absent", and nodes was not in that table, so the correction
would otherwise have created a dangling reference. One row, not a documentation sweep —
title is still absent and I left it alone as the #6150 leftover the review says it is.
The sentence about node shape (id, label, optional icon and children) moved onto
the nodes row rather than being dropped.

content/docs/releases/ not touched.

ℹ️ Two more census data points, recorded not acted on: the JSON example immediately above
that table also authors data, as does the tree-view entry in
packages/types/examples/data-display-examples.json. Both are still legal after this
branch, so neither is a false statement — but if the #6951 retirement lands on nodes,
they are two of the documents that have to move.

Finding 3 — .changeset/6939-tree-view-nodes-mirror.md

Split into the two halves that are separately true: authors get a widening on both faces;
a reader of the TypeScript twin gets a narrowing, TreeNode[] | undefined, with the
TS2322 + TS18048 consumer-probe result named, the one in-repo reader shown to be
already guarded, and out-of-repo unguarded consumers named as the population the paragraph
exists for. Level unchanged, with the three reasons it stays patch stated in place.

Convergence verification, at 794aa2ad4 (post-merge head)

Heavy runs serialised through the shared verify lock; exit codes captured to files before
any pipe.

checkexit
pnpm exec vitest run over both new test files0 — "Test Files 2 passed (2)"
pnpm --filter @object-ui/types type-check0
pnpm check:doc-types0
pnpm check:doc-fences0
node scripts/check-control-bytes.mjs0 (6206 tracked files)
node scripts/check-changeset-no-major.mjs0

⚠️ Worth recording about check:doc-types: its counters are byte-identical before and
after my documentation edit — same 188 documents, same 4 key tables, same 24 rows, same 45
table keys. That is the review's own point measured from the other side: the gate never
looked at this table, so the stale row could not have been caught by it.

Thank you for the census — the root-level 4 is exactly what I claimed and the
member-level flips (the nested editor-interface.json node, and the real bind-only
document at shadowed-renderer-behaviour.test.tsx:119) are a level distinction I had not
made. I have left the counts alone as instructed.

Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

在席处置:补丁轮已核,放行 —— 剥标签 + ready + auto-merge SQUASH

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。链条:隔离审查 FAIL 三条 → 补丁轮 → 在席按内容核实 → 放行。

决定性的一读:被审查过的改动没有动

补丁轮之后我没有重跑合约审查,依据是这个:

 6cad675a6 (被审的 head) → 794aa2ad4 (补丁后)
data-display.zod.ts a0f1499dff920cfa1de27573396e1c2734674d7d IDENTICAL
data-display.ts e4f16b1261cfd621cac6d83a061041967768a5ec IDENTICAL

⇒ 两个被审源文件逐字节相同 ⇒ 审查者对改动本身的裁断(accept set 双向 NEWLY_FAIL=0、消融红集吻合、两面同步、parity ledger、干净 merge)原样成立,无需重审。补丁提交 5d7e47fa7 只碰了三个文件——正是三条 finding 各自的落点(changeset、content/docs/api/schema-reference.md、测试注释);diff stat 看着大是因为它同时 merge 了 origin/main

三条修正逐一核过

⭐ 一条从反面得来的读数,值得留档

实现者报告:check:doc-types 在它改动文档前后计数器逐字节相同(同样 188 文档 / 4 个 key table / 24 行 / 45 个 table key)。它把这条报成「一个对我修的那行什么也没说的绿」,而不是报成覆盖率——这正是审查者「no gate covers this row」从另一侧的验证。

放行状态

CI 在当前 head 794aa2ad4completed / success⚠️ 不是旧 head 6cad675a6 那个绿——按 status=completed 过滤会返回旧 head 的绿,那对当前 head 什么也没说)。已剥 needs:contract-review、ready、auto-merge SQUASH,并按 base-sha 链正向确认入队(队首,base = f0f774b0d)。

⚠️ 放行动作一度因 GitHub 配额半完成(标签已剥、undraft 失败),已排定时提醒跟踪并在配额恢复后补完 —— ⛔ 没有静默停在半完成状态。

⛔ 未随本 PR 解决的

#6951needs-user-decisionnodes / data 的 ADR-0049 退役选择未裁。本 PR 不做那个选择(两种拼法都仍解析)。若最终 data 胜出,本分支需连同其余一并回退。相关记录已留在 #6951(含本席对自己一处口径错误的订正)。


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939) - #7533

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-6939-group2-treeview-mirror
Sep 3, 2026
Merged

fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939)#7533
os-project-manager merged 3 commits into
mainfrom
claude/issue-6939-group2-treeview-mirror

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Part of #6939group 2 of 8 (tree-view). Deliberately worded to leave the card open: four
of that card's eight groups are still unimplemented, so it must stay open.

⚠️Draft + needs:contract-review on purpose. This moves an accept surface;
the dispatching seat reviews before it lands. Please do not mark ready or enable
auto-merge.

The repair

TreeViewSchema REQUIRED data — the limb the renderer reads third:

const rawNodes = boundData || schema.nodes || schema.data || [];
// packages/components/src/renderers/data-display/tree-view.tsx:105

The registration's own inputs and defaultProps spell it nodes, and the four
components-data-display-tree-view/* catalog entries are those defaultProps.
data is now optional on the zod mirror and on the TypeScript twin, in the same
stroke.

What I re-measured, and where the card is stale

Card readings re-derived on origin/main at fe4e7a9e8, through a bare
SchemaRenderer harness:

card saysre-measuredverdict
4 files4 (deep-nesting, file-tree, org-chart, sidebar-navigation), all type,title,nodes✅ confirmed
zod demands data (required)nodes-only parse false, data-only parse true, bare {type} parse false✅ confirmed
renderer reads boundData or schema.nodes or schema.dataread site intact at :105; title at :115 / :117✅ confirmed
render after "correcting" the fixture: identical — 14 elementsidentical: ✅ (element count, tag census and a SHA-256 of textContent, all four tiles). 14: not reproduced — this harness measures 28 / 28 / 12 / 34⚠️ partly stale

Element counts are harness-bound (the docs-gallery harness gives different absolutes
for the same tile), so the pin records identity within one harness and carries the
numbers I measured rather than the card's.

⚠️The card's stated disposition is also half-landed already. It asks to "declare
nodes and title, and stop requiring data". objectui#6150 landed the declaration
half after the card was filed — both keys are declared on both faces today, and #6150
wrote in as many words that relaxing data was a separate accept-set change. This PR
is only that remaining half; nodes and title are untouched.

data stays declared rather than being deleted

The intuitive read of "the renderer prefers nodes" is to drop data. That is wrong,
and measurably: BaseSchema already declares data (z.any().optional(), data?: any
on the TS face), so removing the member would not reject the key — it would admit it
unvalidated while the renderer went on reading it. The pin
control: BaseSchema alone would have admitted both of those makes the difference
visible: the base object accepts the two values the member refuses.

No refinement was added

Unlike this card's object-map / object-gantt group, which added an "at least one of"
refinement. A tree-view carrying no data source at all becomes legal here, and that
admits no new rendering outcome: { data: [] } was already legal and already drew
the same empty tree. A refinement would forbid a spelling of an empty state the contract
already permits rather than buy a guarantee.

Verification

All runs at 6cad675a6; heavy runs serialised through the shared verify lock.

  • pnpm exec vitest run over the two new pins plus undeclared-but-consumed-keys-6150,
    zod-mirror-parity, safe-validate-corpus-6318, handler-keys-json-refusal-6124
    6 files, 374 tests, all passing. The parity ledger needed no edit: tree-view's
    only entry in any of the three ledgers is RuntimeOnlyDeclaredonNodeClick, and
    this change moves optionality, not key membership.
  • pnpm --filter @object-ui/types type-check — green (covers tsconfig.test.json,
    so both compile-time pins are checked).
  • pnpm --filter @object-ui/components type-check — green, after building the
    9-package closure. That is the only TS-face consumer of TreeViewSchema outside
    packages/types, by exhaustive grep.
  • eslint on the four touched files: exit 0, 0 errors, 31 warnings — all pre-existing
    no-explicit-any on lines outside every hunk.
  • check:control-bytes, check:doc-types, check:doc-fences,
    check-changeset-no-major — green.

Reversible verification (both faces restored to fe4e7a9e8, then restored back)

Mutation proven on disk by blob hash and by anchor counts in both directions
(optional-anchor 1 to 0, required-anchor 0 to 1, on both files). Restore proven by
state: on-disk blobs equal the HEAD blobs, git diff HEAD empty, git status
empty. Absolute paths, trap on EXIT INT TERM.

Predicted red set and measured red set agreed:

  • vitest, mutated: exit 1, 8 red — the three "nodes-only is legal" pins, the four
    validates under safeValidateSchema cases, and both spellings validate.
  • vitest, mutated: still green — all four render-identity cases, all four
    anti-vacuity cases, all four "correcting it to data changes no pixel" cases, and the
    whole "data is still validated" group. That asymmetry is the point: the pin is on
    the validator, and the renderer did not move.
  • type-check, mutated: exit 2 on exactly the two compile-time pins vitest cannot
    see — TS2344 on the optionality equality and TS2741 (Property 'data' is missing)
    on the nodes-only document literal.
  • restored: vitest exit 0 (5 files), type-check exit 0.

A first ablation round found two of my own controls reddening for the wrong reason
(their carrier documents were nodes-only, so the pre-repair mirror refused the carrier
itself). Both were rewritten onto data-bearing carriers that are legal in either state,
and the second round shows them staying green while the load-bearing pins still redden.

Declared narrowing

pnpm --filter @object-ui/example-schema-catalog type-check was not measured here.
It needs a 30-package dist closure, and the shared verify lock refused twice at its
9-minute budget with another seat holding it for 550s. Its local output is a precondition
failure, not a verdict: 67 errors, 63 of them TS2307/TS2882 "cannot find module",
the other 4 consequential implicit-any in files this PR does not touch — and the two
lines naming this PR's own file are that same "cannot find module @object-ui/components
/ @object-ui/react" class that every sibling test file reports. Nothing in it is about
this change. That file's behaviour is proven by executing it (green, and red under
ablation); CI type-checks the farm regardless.

Narrowing evidence for the lint side: the linted population is each package's own
eslint .; this diff touches files in two packages and all four were linted (count read
from --format json); and eslint.config.js configures no type-aware linting
(projectService 0, parserOptions 0, against a lit control of languageOptions 1), so
this diff cannot move the verdict on a file it does not touch.

Changeset

@object-ui/types: patch. The accept set only widens — no document that validated
before validates less, including the data-spelled tree-view in
packages/types/examples/data-display-examples.json — and this is the same shape as the
two sibling groups of this card that already landed on patch. The TS twin's
required-to-optional move is the honest half of the same fact: the renderer never
guaranteed data was present, its own read is || []-guarded, and the single consumer
type-checks clean.

⚠️ Interaction with #6951 — the reviewer needs to see this

Found while de-duplicating an unrelated observation, not supplied by the dispatch.
#6951 is open and carries needs-user-decision, and it explicitly folds in this
exact question:

data is required on TreeViewSchema while nodes is the spelling the docs name
as canonical.
[...] Whichever way the pair ruling goes, the required-ness has to move
with it. That is why it is one card.

#6951's own ruling is an ADR-0049 enforce-or-remove choice: retire one spelling of the
nodes / data pair. That choice has not been made. This PR does not make it — it
retires neither spelling, and both keep parsing. What it does do is presume the direction
that every in-repo artefact already names: the doc comments name nodes the winner,
#6951 itself says "nodes is the winner named in the doc comment", the registration's
inputs and defaultProps spell it nodes, the docs page lists both as optional, and
the four catalog entries author nodes.

So the two possible rulings land differently on this PR:

  • nodes wins (what every artefact points to) — this PR is a strictly smaller step in
    the same direction and stays correct as-is.
  • data wins — this PR would have to be reverted along with the rest.

⇒ Flagging rather than choosing. If the reviewing seat would rather this waited on #6951,
the branch is a clean single commit and nothing else depends on it. I have also left a
cross-reference on #6951 so this does not become the invisible interim that card was
filed to prevent.

Out of scope

⛔ Only group 2. Group 5 (chart) lives in the same file and is untouched; group 3
(kanban) is a genuine fork awaiting a ruling; groups 4 and 7 are unassigned. No catalog
fixture is edited — the card is explicit that the fixtures are the side that is right.

Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`TreeViewSchema` required `data`, which the renderer reads THIRD:
const rawNodes = boundData || schema.nodes || schema.data || [];
// packages/components/src/renderers/data-display/tree-view.tsx:105
The registration's own `inputs` and `defaultProps` spell it `nodes`, and the
four `components-data-display-tree-view/*` catalog entries ARE those
`defaultProps`, so `safeValidateSchema` refused every one of them while the
renderer drew them correctly. Re-measured on origin/main at fe4e7a9: four
refusals, and renders byte-identical under either spelling (28 / 28 / 12 / 34
elements, same tag census, same textContent SHA-256) -- objectui#6318's own
triage test for "the schema was the wrong side".
`data` becomes optional on the zod mirror and on the TypeScript twin in the
same stroke. It stays DECLARED rather than deleted: BaseSchema already declares
`data` as `z.any().optional()`, so removing the member would not reject the key
-- it would admit it unvalidated while the renderer went on reading it.
No refinement is added, unlike this card's object-map / object-gantt group: a
tree-view with no data source now validates, and that admits no new rendering
outcome, since `{ data: [] }` was already legal and already drew the same empty
tree.
`nodes` and `title` are objectui#6150's declarations and are untouched; that
card declared the reads and said in as many words that relaxing `data` was a
separate accept-set change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

合约审查裁断:FAIL —— 逐字采信

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。本席是 opus,不在 CONTRACT_REVIEW_TIER,故本 Clause-② yes 的 PR 由隔离审查者出裁。简报只含卡片、裁决与本 PR,⛔ 不含派发指令、⛔ 不含本席结论。

转录档位核验(先决条件):对审查者转录只提取 harness 生成的 model 字段(⛔ 不对自由文本搜关键词——一句「未检出 X」自身含 X,会污染下一轮检测)。248 行、0 行不可解析、119 个 assistant 轮次,全部 claude-fable-5-1,无 fallback 证据 ⇒ 裁断在档,逐字采信。⛔ 未改写、未删节、未润色。

⚠️ 三条 FAIL 全部是 PR 关于改动的陈述,不是改动本身;审查者对改动本身逐轴测过并通过(NEWLY_FAIL=0、消融红集吻合、两面同步、parity ledger、以及本 PR 声明未测的那个 type-check)。


VERDICT: FAIL

The code change itself checks out on every axis I could measure — accept-set direction (NEWLY_FAIL=0), ablation red set, both faces moving together, parity ledger, both heavy type-checks including the one the PR declared unmeasured, and a clean merge onto the current origin/main. The FAIL is on three items the PR says about the change that are wrong or undisclosed, each with a one-to-two-line fix. None requires re-measuring the change.

All measurements were mine, on a detached worktree at 6cad675a6 (git worktree add --detach /home/user/objectui-cr7533 6cad675a6, pnpm install --frozen-lockfile exit 0), base faces taken from 78a3cc23 (blob-identical to merge-base fe4e7a9e8 for both touched source files: zod d1d427db…, ts 58850203…). Worktree removed after review.


Finding 1 — a compile-time pin bites for a different mechanism than its comment names

File:packages/types/src/__tests__/tree-view-data-optional-6939.test.ts:60-67

 * - member DELETED -> falls through `BaseSchema`'s `[key: string]: any`
* to `any`, and `Equal< any, … >` is false -> red

That is not what happens. BaseSchemadeclaresdata?: any (packages/types/src/base.ts:183), so a twin without its own data member resolves ['data'] to the inherited declared member, not to the index signature. The pin still reddens (inherited member is any), but the comment records the wrong mechanism — and this file's own header paragraph, the TS doc comment on data, and the zod .describe() all state the correct one, so the pin's comment contradicts the rest of the PR.

Measurement (tsc --ignoreConfig --noEmit --strict … --types react src/__cr_probe__/mechanism.probe.ts run inside packages/types):

typeWithoutIndexSignature<D>={[KinkeyofDasstringextendsK ? never : numberextendsK ? never : K]: D[K]};// 1. `data` IS a declared member of BaseSchema (survives index-signature removal)exporttype_DataIsDeclaredOnBase=Expect<Equal<'data'extendskeyofWithoutIndexSignature<BaseSchema> ? true : false,true>>;exporttype_SentinelIsNotDeclared=Expect<Equal<'undeclaredTreeKey6939'extendskeyofWithoutIndexSignature<BaseSchema> ? true : false,false>>;// 2. twin without its own `data`: still a DECLARED member, and `any`interfaceTwinWithoutDataextendsBaseSchema{type: 'tree-view';nodes?: TreeNode[]}exporttype_InheritedDataIsAny=Expect<Equal<TwinWithoutData['data'],any>>;exporttype_InheritedDataStillDeclared=Expect<Equal<'data'extendskeyofWithoutIndexSignature<TwinWithoutData> ? true : false,true>>;// 3. same on a base with NO index signature at all: still `any`, from the inherited member aloneinterfaceTwinOnBaseNoIndexextendsWithoutIndexSignature<BaseSchema>{type: 'tree-view';nodes?: TreeNode[]}exporttype_AnyWithoutAnyIndexSignature=Expect<Equal<TwinOnBaseNoIndex['data'],any>>;// @ts-expect-error — an UNDECLARED key on that base is an error, not `any`exporttype_UndeclaredIsErrorWithoutIndex=TwinOnBaseNoIndex['undeclaredTreeKey6939'];
PROBE_CLEAN_EXIT=0
--- control: expectation 2 flipped to `false` ---
src/__cr_probe__/mechanism.flipped.ts(21,50): error TS2344: Type 'false' does not satisfy the constraint 'true'.
FLIPPED_CLEAN_EXIT=2

Line 3 is decisive: with the index signature stripped entirely, ['data'] is still any. The index signature is not on the path.

Minimal fix: replace the two comment lines with
member DELETED -> resolves to the INHERITED \BaseSchema.data?: any` (base.ts:183) — `any`, and `Equal< any, … >` is false -> red. (The identical wrong sentence does *not* appear in undeclared-but-consumed-keys-6150.test.ts:91-94; there the keys are genuinely undeclared on BaseSchema, so the index-signature account is correct for them. It is wrong only for data`.)


Finding 2 — a published doc now states a contract fact this PR made false

File:content/docs/api/schema-reference.md:527 (unchanged by the PR; the PR touches 5 files, none under content/):

| `data` | `TreeNode[]` | **Required.** Nested tree data. Each node has `id`, `label`, optional `icon` and `children`. |

Before this PR that row was true; after it, the published API reference says data is required while the published type says data?: TreeNode[] and the published validator accepts documents without it. No gate covers the row: scripts/check-doc-component-types.mjs reads this file only for its type vocabulary (its exemption block at :410), and check:doc-snippets compiles ts/tsx fences, not property tables (check:doc-types exit 0, check:doc-fences exit 0 at head — both green with the stale row). This card's two landed siblings corrected the docs page that published the old required-ness (tooltip.mdx in #7456; plugin-map.mdx and the gantt page in #7471); this PR leaves its equivalent.

Measurement:

$ grep -n "| \`data\` | \`TreeNode\[\]\`" content/docs/api/schema-reference.md # at 6cad675a6
527:| `data` | `TreeNode[]` | **Required.** Nested tree data. …
$ git diff --stat fe4e7a9e8 6cad675a6 -- content/
(empty)

(content/docs/components/data-display/tree-view.mdx:28-31 already reads data?: TreeNode[] / nodes?: TreeNode[], so the component page is consistent; the API reference is the one that is not.)

Minimal fix: rewrite the row, e.g.
| \data` | `TreeNode[]` | Optional. Nested tree data, read only when `nodes` is absent (the renderer reads `nodes` first — objectui#6939). |— and, since the table lists neithernodesnortitle, a nodes` row would make the JSON example above it honest, but that omission predates this PR (#6150) and is not part of this finding.


Finding 3 — the changeset's "WIDENING, on both faces" is wrong on the reader side of the TS face, and the changeset is where it needs to be right

File:.changeset/6939-tree-view-nodes-mirror.md:24-29

**This is a WIDENING, on both faces.** `data` goes from required to optional on
the mirror and on the TypeScript twin in the same stroke; nothing that validated
before validates less.

For the validator and for an author of the TS type, true. For a reader of the published TS twin it is a narrowing: TreeViewSchema['data'] goes from TreeNode[] to TreeNode[] | undefined, and code that relied on presence stops compiling. The PR body says this ("the honest half of the same fact … its own read is || []-guarded") — the changeset, which is the artefact that becomes the CHANGELOG, says the opposite. AGENTS.md §版本号策略 is explicit that breaking semantics are stated in the changeset body.

Measurement (consumer probe against the published entry, tsc --ignoreConfig --noEmit --strict …):

importtype{TreeViewSchema,TreeNode}from'…/packages/types/src/index';exportfunctioncount(schema: TreeViewSchema): number{constnodes: TreeNode[]=schema.data;returnnodes.length+schema.data.length;}
--- at HEAD (data?: TreeNode[]) ---
consumer.probe.ts(4,9): error TS2322: Type 'TreeNode[] | undefined' is not assignable to type 'TreeNode[]'.
consumer.probe.ts(5,25): error TS18048: 'schema.data' is possibly 'undefined'.
HEAD CONSUMER_PROBE_EXIT=2
--- at BASE faces (data: TreeNode[]) blobs: 588502039b4df4ca47c150317474e4e61882a3b5 ---
BASE CONSUMER_PROBE_EXIT=0

On the bump:patch stands. scripts/check-changeset-no-major.mjs forbids only major (exit 0 at head: "No changeset declares a major bump."); the maintainer ruling on #6939 (comment 5510084784) grades this exact class "patch where the accept set only widens toward what already renders"; and both landed siblings moved a required TS member to optional under patch (objectName in .changeset/6939-objectql-record-source-refinement.md, children in .changeset/6939-overlay-trigger-mirror.md). The #6318 reviewer's patch→minor condition predates that ruling and concerned union membership, not optionality. So the grade is what the repo's rules call for; the description is not.

Minimal fix: replace the bolded sentence with something like: "The accept set widens on both faces for authors. For a reader of the TypeScript twin this is a narrowing — data is now TreeNode[] | undefined, so an unguarded schema.data read needs a guard; the only in-repo reader (tree-view.tsx:105) already has one (|| [])." Grade unchanged.


What I measured that checks out (so the adopter can see the FAIL is not about the change)

Accept set, both directions — NEWLY_FAIL = 0. Own census over every tracked *.json/*.yaml/*.yml (664 files, 664 parsed, 479 with a root type string), each root run through safeValidateSchema and every tree-view node (root or nested) through TreeViewSchema.safeParse, via tsx on source (packages/types/src/zod/index.zod.ts), at head and with both faces restored to 78a3cc23:

head: root-union ok=392 fail=91 base: ok=385 fail=98
NEWLY_PASS (base refused, head accepts): 4 tracked files at root —
components-data-display-tree-view/{deep-nesting,file-tree,org-chart,sidebar-navigation}.json
base issue on all four: ': Invalid input' (root) / 'data: Invalid input: expected array, received undefined' (member)
NEWLY_FAIL (base accepted, head refuses): 0

So the PR's "four" is exact for the objectui check predicate. The complete member-level count is larger, and the PR should be read as having counted at root level: a fifth tracked node flips — the nested tree-view in components-complex-resizable/editor-interface.json (/panels/0/content/0/children/1, nodes-only) — while its enclosing resizable document stays refused at both revs for an unrelated reason (panels.0.id, panels.1.id missing). Two inline TS literals also flip: { type: 'tree-view', bind: 'treeNodes' } (shadowed-renderer-behaviour.test.tsx:119) and the registration's own defaultProps; the data-spelled packages/types/examples/data-display-examples.json entry and TREE_CONTROL = { type, data: [] } stay accepted (True→True). The implementer's own #6951 comment names the nested one, so this is a counting-level difference, not an unknown.

Ablation reproduces the PR's prediction exactly. Base faces on disk (blobs d1d427db…/58850203…, anchors optional-zod 0 / required-zod 1 / optional-ts 0 / required-ts 1), vitest run on the two new files:

Tests 8 failed | 25 passed (33) VITEST_BASE_EXIT=1
× a tree-view with NO data source at all is legal… × the spelling the renderer reads FIRST now parses on its own
× …and the value SURVIVES the parse × both spellings validate — the accept set widened…
× {deep-nesting,file-tree,org-chart,sidebar-navigation} validates under safeValidateSchema

The four render-identity, four anti-vacuity, four "changes no pixel", "keys are DECLARED" (carrier {type,title,data:[],nodes}), "UNDECLARED key admitted" (carrier {type,data:[],[SENTINEL]}), and "BaseSchema alone would have admitted both" controls all stayed green — the rewritten controls from assertion 7 do redden only for their own reason. pnpm --filter @object-ui/types type-check under base faces: exit 2 with exactly TS2344 at :72 and TS2741 Property 'data' is missing at :85. Restore proven by state: blobs back to a0f1499d…/e4f16b12…, anchors 1/1, git status --porcelain empty.

Head is green everywhere I ran it.vitest run on the two new pins + undeclared-but-consumed-keys-6150 + safe-validate-corpus-6318: 4 files / 118 tests; zod-mirror-parity + handler-keys-json-refusal-6124: 2 files / 256 tests; the whole packages/types/ + examples/schema-catalog/ population plus shadowed-renderer-behaviour: 121 files / 3644 tests, exit 0. pnpm --filter @object-ui/types type-check exit 0 (clean tree, 0 error TS), with --listFiles showing both new test files in their tsconfig.test.json programs. Gates: check:control-bytes, check:doc-types, check:doc-fences, check-changeset-no-major, check-changeset-presence ("3 source file(s) of 1 released package… 1 changeset(s)") all exit 0. eslint on the four touched files: 0 errors; both new files 0 warnings; 30 + 1 warnings on the two source files (count matches the PR's 31).

Assertion 8 — measured, and it could not have caught anything.turbo run build --filter=@object-ui/example-schema-catalog...: 29 successful / 29 total; then pnpm --filter @object-ui/example-schema-catalog type-check (tsc --noEmit && tsc -p tsconfig.test.json): exit 0, 0 errors, and pnpm --filter @object-ui/components type-check: exit 0. The narrowing is closed.

Compile-time pins — what each catches (probe pins.probe.ts, exit 0):_TreeDataIsOptionalTreeNodes catches data re-required (TS2344, measured), data deleted (inherited any — Finding 1), and an element-type change. _TreeNodesStillTreeNodes / _TreeTitleStillString are optionality-blind by construction (NonNullable<Req['nodes']> equals NonNullable<Opt['nodes']> — measured true): they catch deletion and element-type change, not nodes/title becoming required; they are inherited from #6150 unchanged, so this is a limitation to know, not a defect of this PR. NODES_ONLY_DOCUMENT catches any key re-required (TS2741, measured) but cannot catch nodes/title deleted or renamed — { …, nodez: […] } and { …, undeclaredTreeKey6939: 1 } both compile through BaseSchema's index signature (measured); a wrong-typed declared key still errors (measured). The runtime pins cover the deletion case (data: 'not-an-array' refused AT data; BaseSchema alone accepts it — both measured green at head).

Other comment mechanisms:.passthrough()base.zod.ts:212; BaseSchema.data: z.any().optional()base.zod.ts:121; data?: anybase.ts:183; [key: string]: anybase.ts:409; renderer read at tree-view.tsx:104-106, inputs/defaultProps spelled nodes. All as stated. Parity ledger: TreeViewSchema's only entry is RuntimeOnlyDeclared: 'onNodeClick' (zod-mirror-parity.test.ts:1239); the compile-time NarrowerThanDeclared comparison does see optionality ([D[K]] extends [InputOf<Shape[K]>]), which is why both faces had to move together — and did (type-check exit 0 covers it).

Assertion 4 (no refinement) — sound.{ type: 'tree-view' } is accepted at head and { type: 'tree-view', data: [] } was accepted at base (census, both measured); the renderer's || [] guard means neither draws anything the other does not, so "at least one of" never guaranteed a non-empty tree. The census also shows why a two-limb refinement would be wrong: { type: 'tree-view', bind: 'treeNodes' } — a real, rendering document — flips refused→accepted here, and a nodes/data-only rule would refuse it. The #6939 maintainer ruling names a refinement for object-map/object-gantt only; #6951's comment 5527723261 recommending one is a triage recommendation on a needs-user-decision card, not a ruling.

Assertion 5 — confirmed stale half. Base data-display.ts:1220-1236 already declares nodes?/title? with #6150's comments; #6150's pins pass at head.

Merge target:git merge-tree --write-tree origin/main 6cad675a6 exit 0, no conflicts; origin/main's packages/react/src/index.ts still export * from './schema-input.js' (the new test's toRenderableSchema/SchemaRenderer imports resolve).

NARROWINGS

  • The card's "14 elements" — not reproduced or attempted; it comes from the docs-gallery harness. I confirmed only that the PR's 28 / 28 / 12 / 34 reproduce in this environment (all four render-identity cases pass at head).
  • eslint warnings on touched lines — I read the counts (30 + 1, both new files 0), not the line positions; whether all 31 sit outside the hunks is the PR's claim, not mine.
  • The PR's own reported runs (pnpm exec vitest run over its six files, its own ablation and blob hashes, its lock timings) — not re-read; superseded by my own runs above.
  • objectui check (node packages/cli/dist/cli.js check) — not run; my census reimplements its validation arm without the structural arm, so my "4 tracked files flip at root" is on that predicate, not on the CLI's printed count.
  • Whole-repo eslint, check:doc-snippets, check:readme-exports, Bundle Analysis, CI convergence — not run; CI-owned. At review time CI showed 27 completed checks all success/skipped with Type Check and the four Test shards still in progress.
  • The Two alias pairs on TextSchema and TreeViewSchema are now DECLARED in both spellings — and TreeViewSchema.data is required while nodes is the documented one (deferred rulings carried out of #6150) #6951 pair ruling — unmade at review time; the PR's revert-if-data-wins caveat is accurate but not something I can measure.

派发席处置

补丁轮,三条全修,⛔ 不放行。⚠️ Finding 2 是本席在席内核验时漏掉的真缺陷:本席查了 JSON 文档与 TS 消费者,没查声明合约事实的已发布散文,而本卡两个已落兄弟组(#7456#7471)恰恰都改了各自的文档页——本 PR 偏离了它自己这张卡的既定做法。

⛔ 注意 content/docs/api/schema-reference.md不是content/docs/releases/,改它不触犯「代码 PR 不得改发布说明」那条。

⭐ 三条 FAIL 都不要求重测改动本身:审查者已把 accept set 双向(NEWLY_FAIL=0)、消融红集、两面同步、以及本 PR 自己申报为未测的 example-schema-catalog type-checkexit 0,收窄已闭合)全部测过。patch 档位经三条独立支撑维持不变

本 PR 的 CI 在 6cad675a6 上已 32/32 全绿(其中 Lint run 33785853068、Docs Links run 33785852959 是按工作流正查确认的——get_check_runs 只返回了 30 条)。


Generated by Claude Code

Contract review of PR 7533 passed the change on every axis and failed three
statements ABOUT it. None of the code under repair moves here.
1. The compile-time pin's comment named the wrong mechanism. Deleting the
member does not fall through BaseSchema's `[key: string]: any`; it resolves
to the INHERITED declared `BaseSchema.data?: any` (base.ts:183), because a
declared member outranks an index signature. Verified independently before
accepting the finding: strip the index signature with the homomorphic
keyof-remap and `['data']` is still `any`, while an index-signature-only key
stops resolving at all -- both controls lit. The pin's verdict is unchanged;
only the reason it gives was wrong, and it contradicted the file header, the
TS-face doc comment and the mirror's describe().
2. `content/docs/api/schema-reference.md` still called `data` **Required**,
which this branch made false, and no gate covers that row. Corrected, and a
`nodes` row added beside it so the corrected sentence does not reference a
key the table never lists. The two sibling groups of this card each updated
their own doc page; this one had missed it.
3. The changeset called the change a widening "on both faces" without saying
that a READER of the TypeScript twin sees a narrowing: `data` is now
`TreeNode[] | undefined`, so an unguarded `schema.data` read stops compiling.
The PR body already said this; the changeset is the artefact that becomes the
CHANGELOG, so it is the one that had to. Level is unchanged and still patch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@claude

claudeBot commented Sep 3, 2026

Copy link
Copy Markdown
ContributorAuthor

Patch round — all three FAIL findings addressed at 794aa2ad4. Nothing under repair moved.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. One commit
(5d7e47fa7) plus a merge of origin/main at d04e79a80 (merge 794aa2ad4, no
conflicts). ⛔ Changeset level untouched, still patch. ⛔ nodes / title
declarations untouched. ⛔ No re-测 of the change itself.

Finding 1 — I checked it before accepting it, and the review is right

I did not take the mechanism on trust, because the instruction was to say so if I measured
otherwise. A throwaway type-level probe, run through pnpm --filter @object-ui/types type-check and then deleted, with both controls lit:

proberesult
['data'] with the index signature presentany
['data'] with the index signature STRIPPED (the homomorphic keyof-remap zod-mirror-parity.test.ts uses)still any
data survives the strippingyes, so it is a declared member
CONTROL: an index-signature-only key survives the strippingno — so the stripping really is doing something
CONTROL: that same key reads as any while the index signature is presentyes

All five compiled, exit 0. BaseSchema carries bothdata?: any (base.ts:183) and
[key: string]: any (base.ts:409), and the declared member — inherited or not — outranks
the index signature. So the comment was wrong and the review's replacement is right;
the pin's verdict never depended on it. Replacement text taken as given, plus a short
paragraph recording the measurement so the next reader does not have to redo it.

undeclared-but-consumed-keys-6150.test.ts:91-94 deliberately NOT touched — the same
sentence is true there, because those 13 keys genuinely are undeclared on BaseSchema.

Finding 2 — content/docs/api/schema-reference.md:527

Corrected to the suggested text. I also added onenodes row directly above it, and
that is a judgment call worth flagging rather than burying: the corrected sentence says
"read only when nodes is absent", and nodes was not in that table, so the correction
would otherwise have created a dangling reference. One row, not a documentation sweep —
title is still absent and I left it alone as the #6150 leftover the review says it is.
The sentence about node shape (id, label, optional icon and children) moved onto
the nodes row rather than being dropped.

content/docs/releases/ not touched.

ℹ️ Two more census data points, recorded not acted on: the JSON example immediately above
that table also authors data, as does the tree-view entry in
packages/types/examples/data-display-examples.json. Both are still legal after this
branch, so neither is a false statement — but if the #6951 retirement lands on nodes,
they are two of the documents that have to move.

Finding 3 — .changeset/6939-tree-view-nodes-mirror.md

Split into the two halves that are separately true: authors get a widening on both faces;
a reader of the TypeScript twin gets a narrowing, TreeNode[] | undefined, with the
TS2322 + TS18048 consumer-probe result named, the one in-repo reader shown to be
already guarded, and out-of-repo unguarded consumers named as the population the paragraph
exists for. Level unchanged, with the three reasons it stays patch stated in place.

Convergence verification, at 794aa2ad4 (post-merge head)

Heavy runs serialised through the shared verify lock; exit codes captured to files before
any pipe.

checkexit
pnpm exec vitest run over both new test files0 — "Test Files 2 passed (2)"
pnpm --filter @object-ui/types type-check0
pnpm check:doc-types0
pnpm check:doc-fences0
node scripts/check-control-bytes.mjs0 (6206 tracked files)
node scripts/check-changeset-no-major.mjs0

⚠️ Worth recording about check:doc-types: its counters are byte-identical before and
after my documentation edit — same 188 documents, same 4 key tables, same 24 rows, same 45
table keys. That is the review's own point measured from the other side: the gate never
looked at this table, so the stale row could not have been caught by it.

Thank you for the census — the root-level 4 is exactly what I claimed and the
member-level flips (the nested editor-interface.json node, and the real bind-only
document at shadowed-renderer-behaviour.test.tsx:119) are a level distinction I had not
made. I have left the counts alone as instructed.

Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

在席处置:补丁轮已核,放行 —— 剥标签 + ready + auto-merge SQUASH

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。链条:隔离审查 FAIL 三条 → 补丁轮 → 在席按内容核实 → 放行。

决定性的一读:被审查过的改动没有动

补丁轮之后我没有重跑合约审查,依据是这个:

 6cad675a6 (被审的 head) → 794aa2ad4 (补丁后)
data-display.zod.ts a0f1499dff920cfa1de27573396e1c2734674d7d IDENTICAL
data-display.ts e4f16b1261cfd621cac6d83a061041967768a5ec IDENTICAL

⇒ 两个被审源文件逐字节相同 ⇒ 审查者对改动本身的裁断(accept set 双向 NEWLY_FAIL=0、消融红集吻合、两面同步、parity ledger、干净 merge)原样成立,无需重审。补丁提交 5d7e47fa7 只碰了三个文件——正是三条 finding 各自的落点(changeset、content/docs/api/schema-reference.md、测试注释);diff stat 看着大是因为它同时 merge 了 origin/main

三条修正逐一核过

⭐ 一条从反面得来的读数,值得留档

实现者报告:check:doc-types 在它改动文档前后计数器逐字节相同(同样 188 文档 / 4 个 key table / 24 行 / 45 个 table key)。它把这条报成「一个对我修的那行什么也没说的绿」,而不是报成覆盖率——这正是审查者「no gate covers this row」从另一侧的验证。

放行状态

CI 在当前 head 794aa2ad4completed / success⚠️ 不是旧 head 6cad675a6 那个绿——按 status=completed 过滤会返回旧 head 的绿,那对当前 head 什么也没说)。已剥 needs:contract-review、ready、auto-merge SQUASH,并按 base-sha 链正向确认入队(队首,base = f0f774b0d)。

⚠️ 放行动作一度因 GitHub 配额半完成(标签已剥、undraft 失败),已排定时提醒跟踪并在配额恢复后补完 —— ⛔ 没有静默停在半完成状态。

⛔ 未随本 PR 解决的

#6951needs-user-decisionnodes / data 的 ADR-0049 退役选择未裁。本 PR 不做那个选择(两种拼法都仍解析)。若最终 data 胜出,本分支需连同其余一并回退。相关记录已留在 #6951(含本席对自己一处口径错误的订正)。


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939) - #7533

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-6939-group2-treeview-mirror
Sep 3, 2026
Merged

fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939)#7533
os-project-manager merged 3 commits into
mainfrom
claude/issue-6939-group2-treeview-mirror

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Part of #6939group 2 of 8 (tree-view). Deliberately worded to leave the card open: four
of that card's eight groups are still unimplemented, so it must stay open.

⚠️Draft + needs:contract-review on purpose. This moves an accept surface;
the dispatching seat reviews before it lands. Please do not mark ready or enable
auto-merge.

The repair

TreeViewSchema REQUIRED data — the limb the renderer reads third:

const rawNodes = boundData || schema.nodes || schema.data || [];
// packages/components/src/renderers/data-display/tree-view.tsx:105

The registration's own inputs and defaultProps spell it nodes, and the four
components-data-display-tree-view/* catalog entries are those defaultProps.
data is now optional on the zod mirror and on the TypeScript twin, in the same
stroke.

What I re-measured, and where the card is stale

Card readings re-derived on origin/main at fe4e7a9e8, through a bare
SchemaRenderer harness:

card saysre-measuredverdict
4 files4 (deep-nesting, file-tree, org-chart, sidebar-navigation), all type,title,nodes✅ confirmed
zod demands data (required)nodes-only parse false, data-only parse true, bare {type} parse false✅ confirmed
renderer reads boundData or schema.nodes or schema.dataread site intact at :105; title at :115 / :117✅ confirmed
render after "correcting" the fixture: identical — 14 elementsidentical: ✅ (element count, tag census and a SHA-256 of textContent, all four tiles). 14: not reproduced — this harness measures 28 / 28 / 12 / 34⚠️ partly stale

Element counts are harness-bound (the docs-gallery harness gives different absolutes
for the same tile), so the pin records identity within one harness and carries the
numbers I measured rather than the card's.

⚠️The card's stated disposition is also half-landed already. It asks to "declare
nodes and title, and stop requiring data". objectui#6150 landed the declaration
half after the card was filed — both keys are declared on both faces today, and #6150
wrote in as many words that relaxing data was a separate accept-set change. This PR
is only that remaining half; nodes and title are untouched.

data stays declared rather than being deleted

The intuitive read of "the renderer prefers nodes" is to drop data. That is wrong,
and measurably: BaseSchema already declares data (z.any().optional(), data?: any
on the TS face), so removing the member would not reject the key — it would admit it
unvalidated while the renderer went on reading it. The pin
control: BaseSchema alone would have admitted both of those makes the difference
visible: the base object accepts the two values the member refuses.

No refinement was added

Unlike this card's object-map / object-gantt group, which added an "at least one of"
refinement. A tree-view carrying no data source at all becomes legal here, and that
admits no new rendering outcome: { data: [] } was already legal and already drew
the same empty tree. A refinement would forbid a spelling of an empty state the contract
already permits rather than buy a guarantee.

Verification

All runs at 6cad675a6; heavy runs serialised through the shared verify lock.

  • pnpm exec vitest run over the two new pins plus undeclared-but-consumed-keys-6150,
    zod-mirror-parity, safe-validate-corpus-6318, handler-keys-json-refusal-6124
    6 files, 374 tests, all passing. The parity ledger needed no edit: tree-view's
    only entry in any of the three ledgers is RuntimeOnlyDeclaredonNodeClick, and
    this change moves optionality, not key membership.
  • pnpm --filter @object-ui/types type-check — green (covers tsconfig.test.json,
    so both compile-time pins are checked).
  • pnpm --filter @object-ui/components type-check — green, after building the
    9-package closure. That is the only TS-face consumer of TreeViewSchema outside
    packages/types, by exhaustive grep.
  • eslint on the four touched files: exit 0, 0 errors, 31 warnings — all pre-existing
    no-explicit-any on lines outside every hunk.
  • check:control-bytes, check:doc-types, check:doc-fences,
    check-changeset-no-major — green.

Reversible verification (both faces restored to fe4e7a9e8, then restored back)

Mutation proven on disk by blob hash and by anchor counts in both directions
(optional-anchor 1 to 0, required-anchor 0 to 1, on both files). Restore proven by
state: on-disk blobs equal the HEAD blobs, git diff HEAD empty, git status
empty. Absolute paths, trap on EXIT INT TERM.

Predicted red set and measured red set agreed:

  • vitest, mutated: exit 1, 8 red — the three "nodes-only is legal" pins, the four
    validates under safeValidateSchema cases, and both spellings validate.
  • vitest, mutated: still green — all four render-identity cases, all four
    anti-vacuity cases, all four "correcting it to data changes no pixel" cases, and the
    whole "data is still validated" group. That asymmetry is the point: the pin is on
    the validator, and the renderer did not move.
  • type-check, mutated: exit 2 on exactly the two compile-time pins vitest cannot
    see — TS2344 on the optionality equality and TS2741 (Property 'data' is missing)
    on the nodes-only document literal.
  • restored: vitest exit 0 (5 files), type-check exit 0.

A first ablation round found two of my own controls reddening for the wrong reason
(their carrier documents were nodes-only, so the pre-repair mirror refused the carrier
itself). Both were rewritten onto data-bearing carriers that are legal in either state,
and the second round shows them staying green while the load-bearing pins still redden.

Declared narrowing

pnpm --filter @object-ui/example-schema-catalog type-check was not measured here.
It needs a 30-package dist closure, and the shared verify lock refused twice at its
9-minute budget with another seat holding it for 550s. Its local output is a precondition
failure, not a verdict: 67 errors, 63 of them TS2307/TS2882 "cannot find module",
the other 4 consequential implicit-any in files this PR does not touch — and the two
lines naming this PR's own file are that same "cannot find module @object-ui/components
/ @object-ui/react" class that every sibling test file reports. Nothing in it is about
this change. That file's behaviour is proven by executing it (green, and red under
ablation); CI type-checks the farm regardless.

Narrowing evidence for the lint side: the linted population is each package's own
eslint .; this diff touches files in two packages and all four were linted (count read
from --format json); and eslint.config.js configures no type-aware linting
(projectService 0, parserOptions 0, against a lit control of languageOptions 1), so
this diff cannot move the verdict on a file it does not touch.

Changeset

@object-ui/types: patch. The accept set only widens — no document that validated
before validates less, including the data-spelled tree-view in
packages/types/examples/data-display-examples.json — and this is the same shape as the
two sibling groups of this card that already landed on patch. The TS twin's
required-to-optional move is the honest half of the same fact: the renderer never
guaranteed data was present, its own read is || []-guarded, and the single consumer
type-checks clean.

⚠️ Interaction with #6951 — the reviewer needs to see this

Found while de-duplicating an unrelated observation, not supplied by the dispatch.
#6951 is open and carries needs-user-decision, and it explicitly folds in this
exact question:

data is required on TreeViewSchema while nodes is the spelling the docs name
as canonical.
[...] Whichever way the pair ruling goes, the required-ness has to move
with it. That is why it is one card.

#6951's own ruling is an ADR-0049 enforce-or-remove choice: retire one spelling of the
nodes / data pair. That choice has not been made. This PR does not make it — it
retires neither spelling, and both keep parsing. What it does do is presume the direction
that every in-repo artefact already names: the doc comments name nodes the winner,
#6951 itself says "nodes is the winner named in the doc comment", the registration's
inputs and defaultProps spell it nodes, the docs page lists both as optional, and
the four catalog entries author nodes.

So the two possible rulings land differently on this PR:

  • nodes wins (what every artefact points to) — this PR is a strictly smaller step in
    the same direction and stays correct as-is.
  • data wins — this PR would have to be reverted along with the rest.

⇒ Flagging rather than choosing. If the reviewing seat would rather this waited on #6951,
the branch is a clean single commit and nothing else depends on it. I have also left a
cross-reference on #6951 so this does not become the invisible interim that card was
filed to prevent.

Out of scope

⛔ Only group 2. Group 5 (chart) lives in the same file and is untouched; group 3
(kanban) is a genuine fork awaiting a ruling; groups 4 and 7 are unassigned. No catalog
fixture is edited — the card is explicit that the fixtures are the side that is right.

Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`TreeViewSchema` required `data`, which the renderer reads THIRD:
const rawNodes = boundData || schema.nodes || schema.data || [];
// packages/components/src/renderers/data-display/tree-view.tsx:105
The registration's own `inputs` and `defaultProps` spell it `nodes`, and the
four `components-data-display-tree-view/*` catalog entries ARE those
`defaultProps`, so `safeValidateSchema` refused every one of them while the
renderer drew them correctly. Re-measured on origin/main at fe4e7a9: four
refusals, and renders byte-identical under either spelling (28 / 28 / 12 / 34
elements, same tag census, same textContent SHA-256) -- objectui#6318's own
triage test for "the schema was the wrong side".
`data` becomes optional on the zod mirror and on the TypeScript twin in the
same stroke. It stays DECLARED rather than deleted: BaseSchema already declares
`data` as `z.any().optional()`, so removing the member would not reject the key
-- it would admit it unvalidated while the renderer went on reading it.
No refinement is added, unlike this card's object-map / object-gantt group: a
tree-view with no data source now validates, and that admits no new rendering
outcome, since `{ data: [] }` was already legal and already drew the same empty
tree.
`nodes` and `title` are objectui#6150's declarations and are untouched; that
card declared the reads and said in as many words that relaxing `data` was a
separate accept-set change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

合约审查裁断:FAIL —— 逐字采信

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。本席是 opus,不在 CONTRACT_REVIEW_TIER,故本 Clause-② yes 的 PR 由隔离审查者出裁。简报只含卡片、裁决与本 PR,⛔ 不含派发指令、⛔ 不含本席结论。

转录档位核验(先决条件):对审查者转录只提取 harness 生成的 model 字段(⛔ 不对自由文本搜关键词——一句「未检出 X」自身含 X,会污染下一轮检测)。248 行、0 行不可解析、119 个 assistant 轮次,全部 claude-fable-5-1,无 fallback 证据 ⇒ 裁断在档,逐字采信。⛔ 未改写、未删节、未润色。

⚠️ 三条 FAIL 全部是 PR 关于改动的陈述,不是改动本身;审查者对改动本身逐轴测过并通过(NEWLY_FAIL=0、消融红集吻合、两面同步、parity ledger、以及本 PR 声明未测的那个 type-check)。


VERDICT: FAIL

The code change itself checks out on every axis I could measure — accept-set direction (NEWLY_FAIL=0), ablation red set, both faces moving together, parity ledger, both heavy type-checks including the one the PR declared unmeasured, and a clean merge onto the current origin/main. The FAIL is on three items the PR says about the change that are wrong or undisclosed, each with a one-to-two-line fix. None requires re-measuring the change.

All measurements were mine, on a detached worktree at 6cad675a6 (git worktree add --detach /home/user/objectui-cr7533 6cad675a6, pnpm install --frozen-lockfile exit 0), base faces taken from 78a3cc23 (blob-identical to merge-base fe4e7a9e8 for both touched source files: zod d1d427db…, ts 58850203…). Worktree removed after review.


Finding 1 — a compile-time pin bites for a different mechanism than its comment names

File:packages/types/src/__tests__/tree-view-data-optional-6939.test.ts:60-67

 * - member DELETED -> falls through `BaseSchema`'s `[key: string]: any`
* to `any`, and `Equal< any, … >` is false -> red

That is not what happens. BaseSchemadeclaresdata?: any (packages/types/src/base.ts:183), so a twin without its own data member resolves ['data'] to the inherited declared member, not to the index signature. The pin still reddens (inherited member is any), but the comment records the wrong mechanism — and this file's own header paragraph, the TS doc comment on data, and the zod .describe() all state the correct one, so the pin's comment contradicts the rest of the PR.

Measurement (tsc --ignoreConfig --noEmit --strict … --types react src/__cr_probe__/mechanism.probe.ts run inside packages/types):

typeWithoutIndexSignature<D>={[KinkeyofDasstringextendsK ? never : numberextendsK ? never : K]: D[K]};// 1. `data` IS a declared member of BaseSchema (survives index-signature removal)exporttype_DataIsDeclaredOnBase=Expect<Equal<'data'extendskeyofWithoutIndexSignature<BaseSchema> ? true : false,true>>;exporttype_SentinelIsNotDeclared=Expect<Equal<'undeclaredTreeKey6939'extendskeyofWithoutIndexSignature<BaseSchema> ? true : false,false>>;// 2. twin without its own `data`: still a DECLARED member, and `any`interfaceTwinWithoutDataextendsBaseSchema{type: 'tree-view';nodes?: TreeNode[]}exporttype_InheritedDataIsAny=Expect<Equal<TwinWithoutData['data'],any>>;exporttype_InheritedDataStillDeclared=Expect<Equal<'data'extendskeyofWithoutIndexSignature<TwinWithoutData> ? true : false,true>>;// 3. same on a base with NO index signature at all: still `any`, from the inherited member aloneinterfaceTwinOnBaseNoIndexextendsWithoutIndexSignature<BaseSchema>{type: 'tree-view';nodes?: TreeNode[]}exporttype_AnyWithoutAnyIndexSignature=Expect<Equal<TwinOnBaseNoIndex['data'],any>>;// @ts-expect-error — an UNDECLARED key on that base is an error, not `any`exporttype_UndeclaredIsErrorWithoutIndex=TwinOnBaseNoIndex['undeclaredTreeKey6939'];
PROBE_CLEAN_EXIT=0
--- control: expectation 2 flipped to `false` ---
src/__cr_probe__/mechanism.flipped.ts(21,50): error TS2344: Type 'false' does not satisfy the constraint 'true'.
FLIPPED_CLEAN_EXIT=2

Line 3 is decisive: with the index signature stripped entirely, ['data'] is still any. The index signature is not on the path.

Minimal fix: replace the two comment lines with
member DELETED -> resolves to the INHERITED \BaseSchema.data?: any` (base.ts:183) — `any`, and `Equal< any, … >` is false -> red. (The identical wrong sentence does *not* appear in undeclared-but-consumed-keys-6150.test.ts:91-94; there the keys are genuinely undeclared on BaseSchema, so the index-signature account is correct for them. It is wrong only for data`.)


Finding 2 — a published doc now states a contract fact this PR made false

File:content/docs/api/schema-reference.md:527 (unchanged by the PR; the PR touches 5 files, none under content/):

| `data` | `TreeNode[]` | **Required.** Nested tree data. Each node has `id`, `label`, optional `icon` and `children`. |

Before this PR that row was true; after it, the published API reference says data is required while the published type says data?: TreeNode[] and the published validator accepts documents without it. No gate covers the row: scripts/check-doc-component-types.mjs reads this file only for its type vocabulary (its exemption block at :410), and check:doc-snippets compiles ts/tsx fences, not property tables (check:doc-types exit 0, check:doc-fences exit 0 at head — both green with the stale row). This card's two landed siblings corrected the docs page that published the old required-ness (tooltip.mdx in #7456; plugin-map.mdx and the gantt page in #7471); this PR leaves its equivalent.

Measurement:

$ grep -n "| \`data\` | \`TreeNode\[\]\`" content/docs/api/schema-reference.md # at 6cad675a6
527:| `data` | `TreeNode[]` | **Required.** Nested tree data. …
$ git diff --stat fe4e7a9e8 6cad675a6 -- content/
(empty)

(content/docs/components/data-display/tree-view.mdx:28-31 already reads data?: TreeNode[] / nodes?: TreeNode[], so the component page is consistent; the API reference is the one that is not.)

Minimal fix: rewrite the row, e.g.
| \data` | `TreeNode[]` | Optional. Nested tree data, read only when `nodes` is absent (the renderer reads `nodes` first — objectui#6939). |— and, since the table lists neithernodesnortitle, a nodes` row would make the JSON example above it honest, but that omission predates this PR (#6150) and is not part of this finding.


Finding 3 — the changeset's "WIDENING, on both faces" is wrong on the reader side of the TS face, and the changeset is where it needs to be right

File:.changeset/6939-tree-view-nodes-mirror.md:24-29

**This is a WIDENING, on both faces.** `data` goes from required to optional on
the mirror and on the TypeScript twin in the same stroke; nothing that validated
before validates less.

For the validator and for an author of the TS type, true. For a reader of the published TS twin it is a narrowing: TreeViewSchema['data'] goes from TreeNode[] to TreeNode[] | undefined, and code that relied on presence stops compiling. The PR body says this ("the honest half of the same fact … its own read is || []-guarded") — the changeset, which is the artefact that becomes the CHANGELOG, says the opposite. AGENTS.md §版本号策略 is explicit that breaking semantics are stated in the changeset body.

Measurement (consumer probe against the published entry, tsc --ignoreConfig --noEmit --strict …):

importtype{TreeViewSchema,TreeNode}from'…/packages/types/src/index';exportfunctioncount(schema: TreeViewSchema): number{constnodes: TreeNode[]=schema.data;returnnodes.length+schema.data.length;}
--- at HEAD (data?: TreeNode[]) ---
consumer.probe.ts(4,9): error TS2322: Type 'TreeNode[] | undefined' is not assignable to type 'TreeNode[]'.
consumer.probe.ts(5,25): error TS18048: 'schema.data' is possibly 'undefined'.
HEAD CONSUMER_PROBE_EXIT=2
--- at BASE faces (data: TreeNode[]) blobs: 588502039b4df4ca47c150317474e4e61882a3b5 ---
BASE CONSUMER_PROBE_EXIT=0

On the bump:patch stands. scripts/check-changeset-no-major.mjs forbids only major (exit 0 at head: "No changeset declares a major bump."); the maintainer ruling on #6939 (comment 5510084784) grades this exact class "patch where the accept set only widens toward what already renders"; and both landed siblings moved a required TS member to optional under patch (objectName in .changeset/6939-objectql-record-source-refinement.md, children in .changeset/6939-overlay-trigger-mirror.md). The #6318 reviewer's patch→minor condition predates that ruling and concerned union membership, not optionality. So the grade is what the repo's rules call for; the description is not.

Minimal fix: replace the bolded sentence with something like: "The accept set widens on both faces for authors. For a reader of the TypeScript twin this is a narrowing — data is now TreeNode[] | undefined, so an unguarded schema.data read needs a guard; the only in-repo reader (tree-view.tsx:105) already has one (|| [])." Grade unchanged.


What I measured that checks out (so the adopter can see the FAIL is not about the change)

Accept set, both directions — NEWLY_FAIL = 0. Own census over every tracked *.json/*.yaml/*.yml (664 files, 664 parsed, 479 with a root type string), each root run through safeValidateSchema and every tree-view node (root or nested) through TreeViewSchema.safeParse, via tsx on source (packages/types/src/zod/index.zod.ts), at head and with both faces restored to 78a3cc23:

head: root-union ok=392 fail=91 base: ok=385 fail=98
NEWLY_PASS (base refused, head accepts): 4 tracked files at root —
components-data-display-tree-view/{deep-nesting,file-tree,org-chart,sidebar-navigation}.json
base issue on all four: ': Invalid input' (root) / 'data: Invalid input: expected array, received undefined' (member)
NEWLY_FAIL (base accepted, head refuses): 0

So the PR's "four" is exact for the objectui check predicate. The complete member-level count is larger, and the PR should be read as having counted at root level: a fifth tracked node flips — the nested tree-view in components-complex-resizable/editor-interface.json (/panels/0/content/0/children/1, nodes-only) — while its enclosing resizable document stays refused at both revs for an unrelated reason (panels.0.id, panels.1.id missing). Two inline TS literals also flip: { type: 'tree-view', bind: 'treeNodes' } (shadowed-renderer-behaviour.test.tsx:119) and the registration's own defaultProps; the data-spelled packages/types/examples/data-display-examples.json entry and TREE_CONTROL = { type, data: [] } stay accepted (True→True). The implementer's own #6951 comment names the nested one, so this is a counting-level difference, not an unknown.

Ablation reproduces the PR's prediction exactly. Base faces on disk (blobs d1d427db…/58850203…, anchors optional-zod 0 / required-zod 1 / optional-ts 0 / required-ts 1), vitest run on the two new files:

Tests 8 failed | 25 passed (33) VITEST_BASE_EXIT=1
× a tree-view with NO data source at all is legal… × the spelling the renderer reads FIRST now parses on its own
× …and the value SURVIVES the parse × both spellings validate — the accept set widened…
× {deep-nesting,file-tree,org-chart,sidebar-navigation} validates under safeValidateSchema

The four render-identity, four anti-vacuity, four "changes no pixel", "keys are DECLARED" (carrier {type,title,data:[],nodes}), "UNDECLARED key admitted" (carrier {type,data:[],[SENTINEL]}), and "BaseSchema alone would have admitted both" controls all stayed green — the rewritten controls from assertion 7 do redden only for their own reason. pnpm --filter @object-ui/types type-check under base faces: exit 2 with exactly TS2344 at :72 and TS2741 Property 'data' is missing at :85. Restore proven by state: blobs back to a0f1499d…/e4f16b12…, anchors 1/1, git status --porcelain empty.

Head is green everywhere I ran it.vitest run on the two new pins + undeclared-but-consumed-keys-6150 + safe-validate-corpus-6318: 4 files / 118 tests; zod-mirror-parity + handler-keys-json-refusal-6124: 2 files / 256 tests; the whole packages/types/ + examples/schema-catalog/ population plus shadowed-renderer-behaviour: 121 files / 3644 tests, exit 0. pnpm --filter @object-ui/types type-check exit 0 (clean tree, 0 error TS), with --listFiles showing both new test files in their tsconfig.test.json programs. Gates: check:control-bytes, check:doc-types, check:doc-fences, check-changeset-no-major, check-changeset-presence ("3 source file(s) of 1 released package… 1 changeset(s)") all exit 0. eslint on the four touched files: 0 errors; both new files 0 warnings; 30 + 1 warnings on the two source files (count matches the PR's 31).

Assertion 8 — measured, and it could not have caught anything.turbo run build --filter=@object-ui/example-schema-catalog...: 29 successful / 29 total; then pnpm --filter @object-ui/example-schema-catalog type-check (tsc --noEmit && tsc -p tsconfig.test.json): exit 0, 0 errors, and pnpm --filter @object-ui/components type-check: exit 0. The narrowing is closed.

Compile-time pins — what each catches (probe pins.probe.ts, exit 0):_TreeDataIsOptionalTreeNodes catches data re-required (TS2344, measured), data deleted (inherited any — Finding 1), and an element-type change. _TreeNodesStillTreeNodes / _TreeTitleStillString are optionality-blind by construction (NonNullable<Req['nodes']> equals NonNullable<Opt['nodes']> — measured true): they catch deletion and element-type change, not nodes/title becoming required; they are inherited from #6150 unchanged, so this is a limitation to know, not a defect of this PR. NODES_ONLY_DOCUMENT catches any key re-required (TS2741, measured) but cannot catch nodes/title deleted or renamed — { …, nodez: […] } and { …, undeclaredTreeKey6939: 1 } both compile through BaseSchema's index signature (measured); a wrong-typed declared key still errors (measured). The runtime pins cover the deletion case (data: 'not-an-array' refused AT data; BaseSchema alone accepts it — both measured green at head).

Other comment mechanisms:.passthrough()base.zod.ts:212; BaseSchema.data: z.any().optional()base.zod.ts:121; data?: anybase.ts:183; [key: string]: anybase.ts:409; renderer read at tree-view.tsx:104-106, inputs/defaultProps spelled nodes. All as stated. Parity ledger: TreeViewSchema's only entry is RuntimeOnlyDeclared: 'onNodeClick' (zod-mirror-parity.test.ts:1239); the compile-time NarrowerThanDeclared comparison does see optionality ([D[K]] extends [InputOf<Shape[K]>]), which is why both faces had to move together — and did (type-check exit 0 covers it).

Assertion 4 (no refinement) — sound.{ type: 'tree-view' } is accepted at head and { type: 'tree-view', data: [] } was accepted at base (census, both measured); the renderer's || [] guard means neither draws anything the other does not, so "at least one of" never guaranteed a non-empty tree. The census also shows why a two-limb refinement would be wrong: { type: 'tree-view', bind: 'treeNodes' } — a real, rendering document — flips refused→accepted here, and a nodes/data-only rule would refuse it. The #6939 maintainer ruling names a refinement for object-map/object-gantt only; #6951's comment 5527723261 recommending one is a triage recommendation on a needs-user-decision card, not a ruling.

Assertion 5 — confirmed stale half. Base data-display.ts:1220-1236 already declares nodes?/title? with #6150's comments; #6150's pins pass at head.

Merge target:git merge-tree --write-tree origin/main 6cad675a6 exit 0, no conflicts; origin/main's packages/react/src/index.ts still export * from './schema-input.js' (the new test's toRenderableSchema/SchemaRenderer imports resolve).

NARROWINGS

  • The card's "14 elements" — not reproduced or attempted; it comes from the docs-gallery harness. I confirmed only that the PR's 28 / 28 / 12 / 34 reproduce in this environment (all four render-identity cases pass at head).
  • eslint warnings on touched lines — I read the counts (30 + 1, both new files 0), not the line positions; whether all 31 sit outside the hunks is the PR's claim, not mine.
  • The PR's own reported runs (pnpm exec vitest run over its six files, its own ablation and blob hashes, its lock timings) — not re-read; superseded by my own runs above.
  • objectui check (node packages/cli/dist/cli.js check) — not run; my census reimplements its validation arm without the structural arm, so my "4 tracked files flip at root" is on that predicate, not on the CLI's printed count.
  • Whole-repo eslint, check:doc-snippets, check:readme-exports, Bundle Analysis, CI convergence — not run; CI-owned. At review time CI showed 27 completed checks all success/skipped with Type Check and the four Test shards still in progress.
  • The Two alias pairs on TextSchema and TreeViewSchema are now DECLARED in both spellings — and TreeViewSchema.data is required while nodes is the documented one (deferred rulings carried out of #6150) #6951 pair ruling — unmade at review time; the PR's revert-if-data-wins caveat is accurate but not something I can measure.

派发席处置

补丁轮,三条全修,⛔ 不放行。⚠️ Finding 2 是本席在席内核验时漏掉的真缺陷:本席查了 JSON 文档与 TS 消费者,没查声明合约事实的已发布散文,而本卡两个已落兄弟组(#7456#7471)恰恰都改了各自的文档页——本 PR 偏离了它自己这张卡的既定做法。

⛔ 注意 content/docs/api/schema-reference.md不是content/docs/releases/,改它不触犯「代码 PR 不得改发布说明」那条。

⭐ 三条 FAIL 都不要求重测改动本身:审查者已把 accept set 双向(NEWLY_FAIL=0)、消融红集、两面同步、以及本 PR 自己申报为未测的 example-schema-catalog type-checkexit 0,收窄已闭合)全部测过。patch 档位经三条独立支撑维持不变

本 PR 的 CI 在 6cad675a6 上已 32/32 全绿(其中 Lint run 33785853068、Docs Links run 33785852959 是按工作流正查确认的——get_check_runs 只返回了 30 条)。


Generated by Claude Code

Contract review of PR 7533 passed the change on every axis and failed three
statements ABOUT it. None of the code under repair moves here.
1. The compile-time pin's comment named the wrong mechanism. Deleting the
member does not fall through BaseSchema's `[key: string]: any`; it resolves
to the INHERITED declared `BaseSchema.data?: any` (base.ts:183), because a
declared member outranks an index signature. Verified independently before
accepting the finding: strip the index signature with the homomorphic
keyof-remap and `['data']` is still `any`, while an index-signature-only key
stops resolving at all -- both controls lit. The pin's verdict is unchanged;
only the reason it gives was wrong, and it contradicted the file header, the
TS-face doc comment and the mirror's describe().
2. `content/docs/api/schema-reference.md` still called `data` **Required**,
which this branch made false, and no gate covers that row. Corrected, and a
`nodes` row added beside it so the corrected sentence does not reference a
key the table never lists. The two sibling groups of this card each updated
their own doc page; this one had missed it.
3. The changeset called the change a widening "on both faces" without saying
that a READER of the TypeScript twin sees a narrowing: `data` is now
`TreeNode[] | undefined`, so an unguarded `schema.data` read stops compiling.
The PR body already said this; the changeset is the artefact that becomes the
CHANGELOG, so it is the one that had to. Level is unchanged and still patch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@claude

claudeBot commented Sep 3, 2026

Copy link
Copy Markdown
ContributorAuthor

Patch round — all three FAIL findings addressed at 794aa2ad4. Nothing under repair moved.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. One commit
(5d7e47fa7) plus a merge of origin/main at d04e79a80 (merge 794aa2ad4, no
conflicts). ⛔ Changeset level untouched, still patch. ⛔ nodes / title
declarations untouched. ⛔ No re-测 of the change itself.

Finding 1 — I checked it before accepting it, and the review is right

I did not take the mechanism on trust, because the instruction was to say so if I measured
otherwise. A throwaway type-level probe, run through pnpm --filter @object-ui/types type-check and then deleted, with both controls lit:

proberesult
['data'] with the index signature presentany
['data'] with the index signature STRIPPED (the homomorphic keyof-remap zod-mirror-parity.test.ts uses)still any
data survives the strippingyes, so it is a declared member
CONTROL: an index-signature-only key survives the strippingno — so the stripping really is doing something
CONTROL: that same key reads as any while the index signature is presentyes

All five compiled, exit 0. BaseSchema carries bothdata?: any (base.ts:183) and
[key: string]: any (base.ts:409), and the declared member — inherited or not — outranks
the index signature. So the comment was wrong and the review's replacement is right;
the pin's verdict never depended on it. Replacement text taken as given, plus a short
paragraph recording the measurement so the next reader does not have to redo it.

undeclared-but-consumed-keys-6150.test.ts:91-94 deliberately NOT touched — the same
sentence is true there, because those 13 keys genuinely are undeclared on BaseSchema.

Finding 2 — content/docs/api/schema-reference.md:527

Corrected to the suggested text. I also added onenodes row directly above it, and
that is a judgment call worth flagging rather than burying: the corrected sentence says
"read only when nodes is absent", and nodes was not in that table, so the correction
would otherwise have created a dangling reference. One row, not a documentation sweep —
title is still absent and I left it alone as the #6150 leftover the review says it is.
The sentence about node shape (id, label, optional icon and children) moved onto
the nodes row rather than being dropped.

content/docs/releases/ not touched.

ℹ️ Two more census data points, recorded not acted on: the JSON example immediately above
that table also authors data, as does the tree-view entry in
packages/types/examples/data-display-examples.json. Both are still legal after this
branch, so neither is a false statement — but if the #6951 retirement lands on nodes,
they are two of the documents that have to move.

Finding 3 — .changeset/6939-tree-view-nodes-mirror.md

Split into the two halves that are separately true: authors get a widening on both faces;
a reader of the TypeScript twin gets a narrowing, TreeNode[] | undefined, with the
TS2322 + TS18048 consumer-probe result named, the one in-repo reader shown to be
already guarded, and out-of-repo unguarded consumers named as the population the paragraph
exists for. Level unchanged, with the three reasons it stays patch stated in place.

Convergence verification, at 794aa2ad4 (post-merge head)

Heavy runs serialised through the shared verify lock; exit codes captured to files before
any pipe.

checkexit
pnpm exec vitest run over both new test files0 — "Test Files 2 passed (2)"
pnpm --filter @object-ui/types type-check0
pnpm check:doc-types0
pnpm check:doc-fences0
node scripts/check-control-bytes.mjs0 (6206 tracked files)
node scripts/check-changeset-no-major.mjs0

⚠️ Worth recording about check:doc-types: its counters are byte-identical before and
after my documentation edit — same 188 documents, same 4 key tables, same 24 rows, same 45
table keys. That is the review's own point measured from the other side: the gate never
looked at this table, so the stale row could not have been caught by it.

Thank you for the census — the root-level 4 is exactly what I claimed and the
member-level flips (the nested editor-interface.json node, and the real bind-only
document at shadowed-renderer-behaviour.test.tsx:119) are a level distinction I had not
made. I have left the counts alone as instructed.

Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

在席处置:补丁轮已核,放行 —— 剥标签 + ready + auto-merge SQUASH

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。链条:隔离审查 FAIL 三条 → 补丁轮 → 在席按内容核实 → 放行。

决定性的一读:被审查过的改动没有动

补丁轮之后我没有重跑合约审查,依据是这个:

 6cad675a6 (被审的 head) → 794aa2ad4 (补丁后)
data-display.zod.ts a0f1499dff920cfa1de27573396e1c2734674d7d IDENTICAL
data-display.ts e4f16b1261cfd621cac6d83a061041967768a5ec IDENTICAL

⇒ 两个被审源文件逐字节相同 ⇒ 审查者对改动本身的裁断(accept set 双向 NEWLY_FAIL=0、消融红集吻合、两面同步、parity ledger、干净 merge)原样成立,无需重审。补丁提交 5d7e47fa7 只碰了三个文件——正是三条 finding 各自的落点(changeset、content/docs/api/schema-reference.md、测试注释);diff stat 看着大是因为它同时 merge 了 origin/main

三条修正逐一核过

⭐ 一条从反面得来的读数,值得留档

实现者报告:check:doc-types 在它改动文档前后计数器逐字节相同(同样 188 文档 / 4 个 key table / 24 行 / 45 个 table key)。它把这条报成「一个对我修的那行什么也没说的绿」,而不是报成覆盖率——这正是审查者「no gate covers this row」从另一侧的验证。

放行状态

CI 在当前 head 794aa2ad4completed / success⚠️ 不是旧 head 6cad675a6 那个绿——按 status=completed 过滤会返回旧 head 的绿,那对当前 head 什么也没说)。已剥 needs:contract-review、ready、auto-merge SQUASH,并按 base-sha 链正向确认入队(队首,base = f0f774b0d)。

⚠️ 放行动作一度因 GitHub 配额半完成(标签已剥、undraft 失败),已排定时提醒跟踪并在配额恢复后补完 —— ⛔ 没有静默停在半完成状态。

⛔ 未随本 PR 解决的

#6951needs-user-decisionnodes / data 的 ADR-0049 退役选择未裁。本 PR 不做那个选择(两种拼法都仍解析)。若最终 data 胜出,本分支需连同其余一并回退。相关记录已留在 #6951(含本席对自己一处口径错误的订正)。


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-project-manager@claude