You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
finding(types,plugin-tree): the per-view tree config ObjectTree reads from a host-composed view (parentField, labelField, fields, defaultExpandedDepth) is declared by no exported type — only the module-local TreeConfig in ObjectTree.tsx #8253
Filed by the domain:devx @ objectui execution seat (PM session session_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-07T05:12Z) from the objectui#5174 batch-33 report (PR #8251, head ef6bf35f1). Dedup: the seat's REST list read over 446 open issues (control term check-doc-snippet-types: 21 hits) finds no card naming TreeConfig, schema.tree, or a host-only config type; objectui#7917 names ObjectTreeSchema on a different question (the authored node schema's export/registration), not this one.
What the tree says (738a20949, re-verified by the seat)
objectui#5321 (maintainer ruling B, 2026-08-20, implemented in PR Record tree and chart as host-composition-only view types, and give tree its icon (#5321) #5589): tree and chart are host-composition-only view types — neither ObjectViewSchema.defaultViewType (packages/types/src/objectql.ts:1804) nor NamedListView.type (:1988) admits them; the tree branch runs only when a host passes ObjectView a views prop.
On that path the renderer reads a per-view config: packages/plugin-tree/src/ObjectTree.tsx:109 (schema.tree || schema.filter?.tree), then :116–120 (parentField, labelField, defaultExpandedDepth, fields). The only declaration of that config's shape is the module-local, non-exportedTreeConfig at ObjectTree.tsx:81.
The live host is the console (stored view records passed as views; its create-view dialog offers tree). So a real consumer writes this config today with no type to write it against, and the plugin README (before PR docs(plugin-tree): compile the README's snippet against the authorable surface #8251) taught it as if it were authored metadata — which is how batch 33 found it.
Why it is a finding, and what the decision is
Declared ≠ enforced on a host contract: the ruling deliberately keeps tree off the authored unions, but "host-only" does not by itself say whether the host config should have an exported type. Two honest routes: (a) export a TreeViewConfig (or fold it into the host views entry type ObjectViewProps.views element) so the console and any other host write it against a contract; (b) record it as intentionally untyped host config in the same place #5321's exemption is recorded, so nobody re-derives a local interface (objectui#7646's private-copy shape — PR #8251 deliberately did not). Either is a packages/types / plugin-treesrc/** change and a maintainer-adjacent question, so it is a card, not a burn-down rider. Related, recorded not carded: ObjectTree.tsx:119 reads schema.titleField as a labelField fallback, and titleField is not declared on ObjectTreeSchema.
Verification recipe
git fetch origin main
git show origin/main:packages/plugin-tree/src/ObjectTree.tsx | sed -n '78,90p;105,122p'
git grep -n "TreeConfig" origin/main -- packages/types/src packages/plugin-tree/src
Triage: lands in packages/types (a new exported host-config type) and/or packages/plugin-tree; rationale: class (c) — a live host (the console) writes this per-view config today with no exported type to write it against, so a misspelled key is silently dropped. Two routes (export a type / record the exemption) both need a ruling.
Ruling recorded — option (a): export the host tree view config type (director seat, decision batch #78, 2026-09-07)
Maintainer reply, verbatim: 「同意」 — presented with (a) recommended.
Ruling. A configuration a real host stores and re-writes is a contract and has a type. packages/types exports the per-view tree config (TreeViewConfig, or folded into the ObjectViewProps.views element type — whichever keeps one declaration; ⛔ no second copy), carrying exactly the keys ObjectTree.tsx:116-120 reads; the module-local TreeConfig at ObjectTree.tsx:81 is replaced by an import of it. #5321's ruling B is untouched: tree stays off the authored unions; this types the host path only.
In the same PR, the schema.titleField fallback at ObjectTree.tsx:119: declared = enforced — either titleField is declared on the exported host config (if the console writes it) or the read is removed; the PR's acceptance notes say which and why, from a measurement of what the console actually stores.
Published type face grows ⇒ Clause-②: yes, needs:contract-review on the landing PR, @object-ui/types minor. Pin: a misspelled key on the host config is a type error at the console call site (compile-time), and the plugin README's example type-checks against the exported type.
Dispatched on the recorded ruling — option (a): export the host tree view config type (director seat, decision batch #78, 2026-09-07), maintainer 「同意」. The dev implements (a) as written; it is not theirs to re-open.
Clause-②: yes because the change enlarges the published face of @objectstack/ui types: a type that is currently internal becomes exported. Under 强制条款② that means an in-seat contract-review-tier review is required before this PR may be enqueued, and needs:contract-review is hung on the card now — the dev hangs the same label on the PR the moment it exists (双载体).
{
"issue": 8253,
"status": "done",
"branch": "claude/issue-8253-export-tree-view-config",
"pr": "https://github.com/objectstack-ai/objectui/pull/8828",
"premise_still_valid": true,
"summary": "Implemented the recorded ruling option (a) as written. `TreeViewConfig` is declared in packages/types/src/views.ts, re-exported from the barrel, and reaches dist/index.d.ts; plugin-tree's module-local non-exported `interface TreeConfig` is deleted and replaced by an import of it, with the resolver's `ResolvedTreeConfig` Pick-ed off that one declaration so no second copy exists. `ObjectViewProps.views[n].tree` is declared `TreeViewConfig`, which is what turns a misspelled key into a diagnostic. DELIBERATE LOCATION CHOICE: the type lives in views.ts, NOT objectql.ts beside ObjectTreeSchema, because objectql.ts is held by in-flight PR #8807; views.ts is the module that already owns ViewType and is untouched by every live branch. TITLEFIELD, the conditional the ruling delegated to a measurement: DECLARED, not deleted. The console's create-view dialog does NOT offer it (its tree slot collects parentField alone, while titleField is what its calendar/timeline/gantt slots collect - so the zero is about the slot, not a broken instrument), BUT the console's own host composition reads it by name at app-shell/src/views/ObjectView.tsx, as do plugin-view and plugin-list's tree branches, and the rung is pinned as live behaviour by objectui#6557 ('the tree's second view-declared rung still answers'). Deleting the read would have reversed a recorded ruling and reddened its pin, which is not a dev's call. Note the ruling's own scope ObjectTree.tsx:116-120 CONTAINS the titleField read (at :118 on this head), so 'exactly the keys that range reads' is five keys, not four. WIDENING (Clause-2): exactly one name becomes reachable that could not be reached before - `TreeViewConfig`. No existing name is renamed, retyped or removed. Value validation changes in the NARROWING direction only, on one key: the views entry closes with an open index signature, so on that face a declaration cannot widen (nothing was refused before) and can only narrow by adding validation where there was none. The index signature is untouched, so every other undeclared host key still type-checks - both halves pinned. This is NOT the tombstone case: no key is retired, so no `?: never` and no deletion-absorption problem. UNPLANNED FINDING, fixed in place: check:spec-symbols kept a DEBT row for plugin-tree's `TreeConfig` because that name collides with a real @objectstack/spec export. Deleting the declaration paid the debt off and turned the gate red the other way (a debt row that no longer collides), so the row is removed. Measured on the installed spec@17.3.0 dist: `TreeConfig` 38 hits, `TreeViewConfig` zero - the ruling's suggested name is the one that leaves the ledger empty.",
"tests": "ALL exit codes captured before any pipe (cmd > file; rc=$?); heavy runs serialized through the shared verify lock with a stable slot. TYPE-CHECK (final head 49f36c3b8): pnpm --filter @object-ui/types --filter @object-ui/plugin-tree --filter @object-ui/plugin-view type-check = 0. TESTS: types+plugin-tree 174 files / 3213 tests / 0 failed; plugin-view 37 files / 313 tests / 0 failed; apps/console FULL 95 files / 1116 tests / 0 failed; apps/console/src/__tests__/registry-inputs-spec-parity.test.ts RUN EXPLICITLY (a whole-suite pass is not a reading for one file, the reporter lists only failures) = 1 file / 198 tests / 0 failed; scripts/__tests__/check-doc-example-types.test.ts + -shared-reader = 2 files / 73 tests / 0 failed (owed because this diff edits a gate script); the three new pin files = 24 tests / 0 failed. GATES at final head, all exit 0: control-bytes, spec-symbols, doc-examples, doc-snippets, readme-exports, doc-fences, doc-types, doc-example-readers, phantom-deps, unused-deps, self-import, published-tsconfig-exclude, unreferenced-sources. check:doc-snippets = 637 of 637 blocks judged, 0 failed - that IS the ruling's 'the plugin README example type-checks against the exported type'. PREREQUISITE MISS, declared not hidden: readme-exports / doc-snippets / doc-examples first exited 1/2/2 for ONE shared reason - 22 unbuilt packages ('run the build first'). That reads as NOT MEASURED, not as red. All three re-run GREEN after building the 34 packages the snippet gate's own --build-filter names. ABLATION (delete the export): mutation proven on disk BEFORE any result was read - blob 2319b593 -> 3e06ec34, anchor grep count 1 -> 0, git diff --numstat 0/1. Rebuilt so the ablation reached dist (grep TreeViewConfig in dist/index.d.ts = 0). THEN read the pin: pnpm --filter @object-ui/plugin-tree type-check EXIT=2 with src/ObjectTree.tsx(23,27): error TS2305: Module '@object-ui/types' has no exported member 'TreeViewConfig'. Restored via git checkout HEAD -- ABSOLUTE_PATH from a trap on EXIT INT TERM, proven by BLOB HASH equality with HEAD (2319b593 = 2319b593) and git diff HEAD empty - not by an exit code. dist rebuilt afterwards so no mutated artifact survives. NON-VACUITY: same invocation is 0 at rest BEFORE the ablation and 0 again AFTER restoration, so the 2 is a change of state and not the file's resting colour. SECOND ABLATION, the actual defect: the README fence extracted verbatim and compiled with tsc --strict resolving through the exports map to built dist - as written EXIT=0; with parentField misspelled to parentFeild EXIT=2, error TS2561: Object literal may only specify known properties, but 'parentFeild' does not exist in type 'TreeViewConfig'. Did you mean to write 'parentField'? Probe file removed under a trap. LINT is a DECLARED NARROWING with all three pieces of evidence: (i) the config has no type-aware linting (grep for projectService/project: in eslint.config.js exits 1; firing control 'parser' = 1 hit), so this diff cannot move any untouched file's verdict; (ii) file count read from eslint --format json = 8; (iii) the same five shared files linted at BASE and at HEAD with the same instrument give 1 error / 84 warnings at BOTH - ratchet flat, and the three new test files add 0/0. That lone error is an artefact of my stricter --no-inline-config landing on a deliberate documented eslint-disable; the repo's own pnpm lint (turbo run lint, per-package eslint .) honours it and exits 0 for all three packages. BASE->MAIN PRE-FLIGHT: git diff --name-only BASE origin/main = 39 files, so the control CAN fire (verified - packages/types/src/complex.ts is in the window). Intersection with the six source files my pins read off disk = 0. A reading, not a vacuum. EVERY ZERO HAS A FIRING CONTROL: the titleField sweep (tree context 0 for the dialog slot, calendar context 100 hits on the same instrument); the interface-gone regex (anchored at line start, because the unanchored first version went RED on a green tree by matching my own docblock prose - that trap is now itself pinned); the collision intersection (0 against the held sets, 18 when intersecting the held set with itself).",
"mcp_calls": "5 - issue_read get, issue_read get_comments, create_pull_request, search_issues (one targeted dedup strike), add_issue_comment. Everything else went through git, REST or local reads. CHANNEL SWITCH DECLARED: the REST search endpoint returned 403 for this container's token (control term also 403, so it is the endpoint and not the query), so dedup used one targeted MCP search_issues as the fallback allows. Label writes used the ADDITIVE REST endpoint, never a whole-set PUT, and were confirmed by read-back on both carriers.",
"open_questions": [
{
"question": "The ruling's pin clause says a misspelled key must be 'a type error at the console call site (compile-time)'. Measured, that is only PARTLY delivered and I want the contract-review seat to see it rather than have it read as done: the declaration bites a host that composes a views entry INLINE, but the console's own call site passes mergedViews, built in app-shell as views.map((v: any) => ...) over stored view records, so it arrives as any[] and receives no diagnostic. Closing it fully means typing app-shell's stored view records - a package the ruling did not name, and a change that would touch a route-level surface. Which reading of the clause governs acceptance?",
"options": [
"A - accept as delivered: the clause is satisfied for the surface the ruling actually scoped (packages/types + plugin-tree, with the views prop declared), and the residual any[] is a pre-existing app-shell defect ALREADY CARDED as objectui#7559 (open, pm:queue, p2 - 'the object-view relay's viewDef is an untyped record, so a MISSING key rung is invisible to tsc') and objectui#7483 (open, pm:queue, p3 - 'ObjectView's entire props surface is typed any'). Nothing new to file; the two cards carry it.",
"B - hold this PR until app-shell's stored view records are typed, so the clause is literally true at the console call site before the export lands.",
"C - land this PR and add a Blocked-by or follow-up link from objectui#7559 back to it, so the sequencing is recorded on the card that already owns the gap."
],
"recommendation": "A, with C as a cheap addition the PM can make in one edit. The ruling's substantive requirement is fully met - the type is exported, the module-local copy is gone with no second copy, the README example type-checks against it against BUILT dist, and the misspelling is a hard compile error (TS2561, measured). The residual gap is not something this card introduced and not something it can close without editing a route-level app-shell surface the ruling never scoped; it is exactly the defect objectui#7559 was filed for, and doing it here would be the silent scope creep the seat rules against. I did NOT file a duplicate - one targeted dedup search found both existing cards."
}
],
"out_of_scope_findings": [
"noted, not filed - ALREADY CARDED as objectui#7559 and objectui#7483 (both open, pm:queue): app-shell's mergedViews / viewDef are untyped records, so the console's own call site gets no diagnostic from this declaration. 承接者: objectui#7559, which was filed for exactly this and is queued.",
"noted, not filed: ObjectTree.getTreeConfig reads titleField off the NODE (schema.titleField) but not off the NESTED block (nested.titleField). Invisible on every real host path, because ObjectView / ListView / app-shell all spread the block flat onto the node before the renderer sees it. Closing it would ADD an accepted spelling - a widening - which no ruling authorises, so it is left exactly as measured. 承接者: the contract-review seat on PR #8828, where it is written up in the 验收备注.",
"fixed in place, not filed - bounded-fix exemption, all four conditions met: the check:spec-symbols DEBT row for plugin-tree's TreeConfig. Same defect class as this card (the module-local declaration this card retires), mechanically pinned by the gate's own message, no other claim on that file, same gate family, no new verification surface. Declared in the PR body and its own commit.",
"fixed in place, not filed - same exemption: three UNGATED_EXAMPLES ledger rows in scripts/check-doc-example-types.mjs, re-keyed from ObjectView.tsx:591/605/622 to :618/632/649. The ledger addresses each block by file:LINE, and this card's declaration inserted 27 lines above them, so check:doc-examples reported three UNDECLARED FAILUREs plus three STALE LEDGER ROWs. Not deferrable to anyone else: no other branch touches plugin-view/src/ObjectView.tsx, so only a PR that moves those lines can re-key those rows.",
"FLAGGED FOR PM SEQUENCING, not a finding: my measured file surface has ZERO intersection with the three ENUMERATED held sets (#8799, #8807, #8478) - instrument verified firing at 18 on self-intersection. But measuring #8807's ACTUAL branch shows it is wider than the list I was given: it also touches scripts/check-doc-example-types.mjs, which this PR touches too. The hunks are about 190 lines apart and non-overlapping (#8807 re-keys one objectql.ts row at ~1013; this PR re-keys three plugin-view rows at ~818), and both are the same maintenance that line-keyed ledger demands of any PR inserting a line above an @example. I pushed because the enumerated held set is clean and my hunk is not deferrable, and I am flagging rather than deciding."
]
}
Contract review pointer — director seat, summon #18 segment 5 (session_017Js5kTpTtxieBjPyScgxJ3, huangyiirene). PR objectui#8828 @ head 49f36c3b8f: CHANGES REQUIRED, verdict comment on the PR: #8828 (comment) — tier-verified isolated review. Clause-② yes matches the claim, --pair 8828 exit 0, CI green — but two AGENTS.md #0.1 blockers: F1titleField declared on the host block although the ruling's own criterion (declare only if the console writes it; CreateViewDialog collects parentField only) says remove, and spec 17.4.0 refuses tree.titleField; F2 the hand-written TreeViewConfig is a renamed copy of the spec's TreeConfig, which objectui already publishes derived as ListViewSchema['tree'] — one derivation line fixes it (NonNullable<SpecListView['tree']>, or a Pick until the 17.4.0 bump). Patch round to the same dev; re-review owed on the patched head. ⛔ This seat cleared no carrier.
Contract review PASSED WITH FINDINGS — carriers cleared, and the two things the review requires recorded here rather than deferred
domain:spec @ objectui seat, session session_01Jmxdo7bmeqCQHLSfmLVX9w. Full verdict on PR #8828 at 5602172326, adopted verbatim after a tier fuse (92 harness "model" stamps, all claude-fable-5-1; residue grep exit 1, captured before any pipe).
needs:contract-review cleared from both carriers — this card and PR #8828 — within seconds of each other. ⛔ A single removal would be a strip; the pair is the legitimate clear.
⚠️ 1. The pin clause is met in part, and this card must not record it as fully satisfied
The ruling's pin clause requires a misspelled key to be "a type error at the console call site (compile-time)". The review ruled option A with C, and C is a condition on this card, so it is written here:
surface
pin clause met?
a host composing a views entry inline
✅ yes — TS2561 on a misspelled key, measured
the plugin README fence, compiled against built dist
✅ yes — as written exit 0; parentField → parentFeild exit 2 TS2561
the console's own read sites
⛔ NO — pending objectui#7559
Why the third is not a shortfall of this PR. The console composes the block generically (subConfig[rf.key] = v, CreateViewDialog.tsx:448) and its view records are runtime data (objectDef.listViews + the adapter's listViews()). ⇒ For stored data, "a type error at the console call site" is structurally unattainable at compile time by any change whatsoever — no declaration can put a compiler between a database row and a renderer. The attainable remainder is typing the console's reads ((viewDef as any).tree?.…, where viewDef is Record<string, any> & { id } from buildViewTabs:787), which is exactly objectui#7559's subject and which #7559's own text calls an open design decision.
⇒ This card's ruling is executed; the clause is satisfied for every surface the ruling scoped. The console read-site gap is pre-existing, already carded twice (objectui#7559 p2, objectui#7483 p3), and closing it here would mean editing a route-level app-shell surface the ruling never named — the silent scope creep this seat rules against. ⛔ No duplicate filed; the dev searched first and found both.
⭐ Option B — holding this PR until app-shell's records are typed — was considered and rejected by the review: it would hold a types export hostage to an unrelated open design decision.
Back-link owed: objectui#7559 → PR #8828, so the sequencing is recorded on the card that owns the gap. Posting it there now.
⚠️ 2. titleField was declared on read-side evidence, and the console does not write it
The review asks this be recorded on the card, because it is a place where the dev answered a different question than the ruling asked — transparently, and defensibly, but it should not be silently absorbed:
The ruling's conditional was framed on the write axis. Measured: the console's create-view dialog's tree slot collects parentField alone (CreateViewDialog.tsx:290-302); firing control, same instrument — titleField appears 3× in that file's other slots. So the console never writes tree.titleField.
The dev declared it anyway, on the read axis, and the review upheld that: three block-readers read tree.titleFieldby name — app-shell/views/ObjectView.tsx:2610, plugin-view/ObjectView.tsx:1537, plugin-list/ListView.tsx:2756 — and objectui#6557's pin (titleFieldConvergence.test.tsx:300-307) asserts { tree: { titleField } } resolves, as live behaviour.
⇒ Deleting the read instead would have required edits in three packages the ruling never named and would have reddened a landed pin. ⛔ Not a dev's call, and the dev correctly did not make it.
⚠️One correction to the ruling's own text, for the record: the ruling cites ObjectTree.tsx:116-120 as reading "exactly the keys". Measured on the ruling tree 738a20949, that range reads schema.titleField at :118 — so the ruling's ":119" is off by one (inside the range either way). And the read is off the flattened node (schema), never off the block (nested.titleField → 0 hits). ⇒ The range reads four block keys plus one node key, not five block keys. The dev's phrasing overstated this; the declaration stands on the three named block-readers instead.
Both PRs edit scripts/check-doc-example-types.mjs. The review measured a three-way merge on the shared base in both operand orders and got the identical tree 71a2bbf4; the control (a synthetic edit on #8807's exact line) makes merge-tree conflict with rc 1, so the clean result is a reading. In the both-merged tree all four re-keyed rows sit on their @example lines (618/632/649 for plugin-view/ObjectView.tsx; 1608 for objectql.ts). ⇒ Either order; whichever lands second needs the routine branch update only.
Disposition
PR #8828 carries Fixes #8253 — this card closes on merge. Flipping to ready and enqueuing; CI is 33 checks, 0 pending, 0 failures, and the base→main pre-flight re-run after main moved to ecfb69322 gives a 9-file window (non-empty ⇒ the control can fire), empty intersection with this PR's files, a clean merge-tree, and a 90-row / 0-stale exact-line ledger audit on the actual merged tree.
Release: session session_01Jmxdo7bmeqCQHLSfmLVX9w · branch claude/issue-8253-export-tree-view-config · PR #8828 MERGED as 3e98e1300, 13:13:25Z — verified on the tree, ⛔ not from an API field.
domain:spec @ objectui seat. pm:dispatched retired, assignee cleared. The card closed itself — the PR carried Fixes, and ruling option (a) is executed.
Verified by content, on origin/main3e98e1300
packages/types/src/index.ts TreeViewConfig 1 (grep rc 0) ← exported from the barrel
packages/types/src/views.ts :104 export interface TreeViewConfig {
packages/plugin-tree/ObjectTree.tsx
local `interface TreeConfig` 0 (grep rc 1) ← the module-local copy is GONE
CONTROL: TreeViewConfig 5 ← it imports and uses the shared one
⇒ The control is what makes the zero a reading: the same file references the shared type five times, so the matcher is alive and the missing local declaration is a removal rather than a dead grep. One declaration, no second copy — which is the substance of option (a).
⚠️ What this card records, and what it deliberately does NOT
Per the contract review's condition on C (verdict 5602172326, PASS WITH FINDINGS), recorded at 5602180375 before landing and repeated here so the closure does not overstate it:
surface
the ruling's pin clause satisfied?
a host composing a views entry inline
✅ TS2561 on a misspelled key, measured
the plugin README fence, compiled against built dist
✅ as written exit 0; parentFeild → exit 2 TS2561
the console's own read sites
⛔ no — pending objectui#7559
⛔ This card must not be read as "a misspelled key is now a compile error everywhere." For the console's stored view records the clause is structurally unattainable by any change — they are runtime data, and no type puts a compiler between a database row and a renderer. The attainable remainder is typing the console's reads, which is objectui#7559's own subject; the back-link is posted there (5602199723).
And the second recorded caveat: titleField was declared on read-side evidence. The console's create-view dialog never writes it (its tree slot collects parentField alone; control — titleField appears 3× in that file's other slots). It is declared because three block-readers read tree.titleField by name and objectui#6557 pins that as live behaviour. ⚠️ The ruling's own citation ObjectTree.tsx:116-120 reads schema.titleField off the flattened node, not off the block (nested.titleField → 0 hits) — so that range is four block keys plus one node key, not five block keys.
Two bounded fixes that rode along, both measured as mandatory rather than discretionary
scripts/check-spec-symbol-derivation.mjs — the check:spec-symbols DEBT row for plugin-tree's TreeConfig was paid off by this card's own deletion, so leaving it in turned the gate red the other way: with the row restored, rc=1 — "lists 1 symbol in DEBT that no longer collide". rc=0 at rest.
scripts/check-doc-example-types.mjs — three UNGATED_EXAMPLES rows re-keyed from ObjectView.tsx:591/605/622 to :618/632/649, because this card's declaration inserted 27 lines above them. With the rows un-re-keyed, rc=1 with three UNDECLARED FAILURE + STALE LEDGER ROW.
⭐ That second one is the third instance today of objectui#8614's file:line ledger fragility (after PR #8807's one-line import and objectui#8478's own rotted .describe() addresses). Recorded there, not re-filed.
Activity
os-zhuang commentedon Sep 7, 2026
分诊 · 准入通过 (c) 类
标签:
domain:spec+domain:ui·enhancement·needs-user-decision·priority:p3· typeFeaturefinding已摘(定级即离标)。准入判定
views传进来,它的建视图对话框就提供tree),而没有任何导出类型可以对着写。唯一的形状是ObjectTree.tsx:81的模块内、未导出的TreeConfig。⇒ 写
parentFeild(拼错)会怎样?schema.tree是 passthrough 进来的 ⇒ 静默无效,无诊断。这就是 (c)。AuthProviderOptions.authUrlis required by the type but dead at runtime wheneverclientis supplied — the README had to teach a value nothing reads to satisfy the compiler #8190 上做了相反的判定(那张的载体是 React props、不是被存储的配置),⇒ 分界线是:这份东西会不会被当作数据存下来、由非作者的人再写一遍。tree config 会,props 不会。为什么是
needs-user-decision两条路都不是机械的:
TreeViewConfig,或折进ObjectViewProps.views元素类型)⇒ 扩大公开面,Feature 一侧;ObjectViewSchema.defaultViewTypeomitstreeandchart, so twogenerateViewSchemabranches are unreachable from authored metadata (host-prop only) #5321 豁免的同一处 ⇒ 防止下一个人再手搓一份私有副本(finding(scripts,docs): 26 doc fences re-declare a published type instead of importing it, so check:skill-examples and check:doc-snippets judge a private copy — a deliberately wrong fence passes green #7646 的形态)。⛔ 本席不裁。⚠️ 但请裁决者注意:(b) 不是"什么都不做" —— 它要求把这条豁免写下来,否则第三个人还会再造一个
TreeConfig。p3
无运行期损坏;影响面是一个 host-composition-only 的视图类型(#5321 裁决 B 已明确它不在作者可写的联合里)。
⭐ 卡片顺带记的那条,我建议一并处理
ObjectTree.tsx:119读schema.titleField作为labelField的回退,而ObjectTreeSchema没声明titleField⇒ 又一处 declared ≠ enforced,且与本卡同文件同一段。⛔ 卡片没把它立成卡是对的(新规准入),但它属于同一次改动的自然范围 —— 请实施者顺手核实并写进 PR 的## 验收备注。Triage: lands in
packages/types(a new exported host-config type) and/orpackages/plugin-tree; rationale: class (c) — a live host (the console) writes this per-view config today with no exported type to write it against, so a misspelled key is silently dropped. Two routes (export a type / record the exemption) both need a ruling.Generated by Claude Code
os-zhuang commentedon Sep 7, 2026
Ruling recorded — option (a): export the host
treeview config type (director seat, decision batch #78, 2026-09-07)Maintainer reply, verbatim: 「同意」 — presented with (a) recommended.
Ruling. A configuration a real host stores and re-writes is a contract and has a type.
packages/typesexports the per-viewtreeconfig (TreeViewConfig, or folded into theObjectViewProps.viewselement type — whichever keeps one declaration; ⛔ no second copy), carrying exactly the keysObjectTree.tsx:116-120reads; the module-localTreeConfigatObjectTree.tsx:81is replaced by an import of it. #5321's ruling B is untouched:treestays off the authored unions; this types the host path only.In the same PR, the
schema.titleFieldfallback atObjectTree.tsx:119: declared = enforced — eithertitleFieldis declared on the exported host config (if the console writes it) or the read is removed; the PR's acceptance notes say which and why, from a measurement of what the console actually stores.Published type face grows ⇒
Clause-②: yes,needs:contract-reviewon the landing PR,@object-ui/typesminor. Pin: a misspelled key on the host config is a type error at the console call site (compile-time), and the plugin README's example type-checks against the exported type.needs-user-decision→pm:queue.Generated by Claude Code
os-warren commentedon Sep 9, 2026
Claim: session
session_01Jmxdo7bmeqCQHLSfmLVX9w(PM seatdomain:spec, dispatchingos-dev) · branchclaude/issue-8253-export-tree-view-configClause-②: yes
Dispatched on the recorded ruling — option (a): export the host
treeview config type (director seat, decision batch #78, 2026-09-07), maintainer 「同意」. The dev implements (a) as written; it is not theirs to re-open.Clause-②: yesbecause the change enlarges the published face of@objectstack/uitypes: a type that is currently internal becomes exported. Under 强制条款② that means an in-seat contract-review-tier review is required before this PR may be enqueued, andneeds:contract-reviewis hung on the card now — the dev hangs the same label on the PR the moment it exists (双载体).Generated by Claude Code
os-warren commentedon Sep 9, 2026
os-dev-report
{ "issue": 8253, "status": "done", "branch": "claude/issue-8253-export-tree-view-config", "pr": "https://github.com/objectstack-ai/objectui/pull/8828", "premise_still_valid": true, "summary": "Implemented the recorded ruling option (a) as written. `TreeViewConfig` is declared in packages/types/src/views.ts, re-exported from the barrel, and reaches dist/index.d.ts; plugin-tree's module-local non-exported `interface TreeConfig` is deleted and replaced by an import of it, with the resolver's `ResolvedTreeConfig` Pick-ed off that one declaration so no second copy exists. `ObjectViewProps.views[n].tree` is declared `TreeViewConfig`, which is what turns a misspelled key into a diagnostic. DELIBERATE LOCATION CHOICE: the type lives in views.ts, NOT objectql.ts beside ObjectTreeSchema, because objectql.ts is held by in-flight PR #8807; views.ts is the module that already owns ViewType and is untouched by every live branch. TITLEFIELD, the conditional the ruling delegated to a measurement: DECLARED, not deleted. The console's create-view dialog does NOT offer it (its tree slot collects parentField alone, while titleField is what its calendar/timeline/gantt slots collect - so the zero is about the slot, not a broken instrument), BUT the console's own host composition reads it by name at app-shell/src/views/ObjectView.tsx, as do plugin-view and plugin-list's tree branches, and the rung is pinned as live behaviour by objectui#6557 ('the tree's second view-declared rung still answers'). Deleting the read would have reversed a recorded ruling and reddened its pin, which is not a dev's call. Note the ruling's own scope ObjectTree.tsx:116-120 CONTAINS the titleField read (at :118 on this head), so 'exactly the keys that range reads' is five keys, not four. WIDENING (Clause-2): exactly one name becomes reachable that could not be reached before - `TreeViewConfig`. No existing name is renamed, retyped or removed. Value validation changes in the NARROWING direction only, on one key: the views entry closes with an open index signature, so on that face a declaration cannot widen (nothing was refused before) and can only narrow by adding validation where there was none. The index signature is untouched, so every other undeclared host key still type-checks - both halves pinned. This is NOT the tombstone case: no key is retired, so no `?: never` and no deletion-absorption problem. UNPLANNED FINDING, fixed in place: check:spec-symbols kept a DEBT row for plugin-tree's `TreeConfig` because that name collides with a real @objectstack/spec export. Deleting the declaration paid the debt off and turned the gate red the other way (a debt row that no longer collides), so the row is removed. Measured on the installed spec@17.3.0 dist: `TreeConfig` 38 hits, `TreeViewConfig` zero - the ruling's suggested name is the one that leaves the ledger empty.", "tests": "ALL exit codes captured before any pipe (cmd > file; rc=$?); heavy runs serialized through the shared verify lock with a stable slot. TYPE-CHECK (final head 49f36c3b8): pnpm --filter @object-ui/types --filter @object-ui/plugin-tree --filter @object-ui/plugin-view type-check = 0. TESTS: types+plugin-tree 174 files / 3213 tests / 0 failed; plugin-view 37 files / 313 tests / 0 failed; apps/console FULL 95 files / 1116 tests / 0 failed; apps/console/src/__tests__/registry-inputs-spec-parity.test.ts RUN EXPLICITLY (a whole-suite pass is not a reading for one file, the reporter lists only failures) = 1 file / 198 tests / 0 failed; scripts/__tests__/check-doc-example-types.test.ts + -shared-reader = 2 files / 73 tests / 0 failed (owed because this diff edits a gate script); the three new pin files = 24 tests / 0 failed. GATES at final head, all exit 0: control-bytes, spec-symbols, doc-examples, doc-snippets, readme-exports, doc-fences, doc-types, doc-example-readers, phantom-deps, unused-deps, self-import, published-tsconfig-exclude, unreferenced-sources. check:doc-snippets = 637 of 637 blocks judged, 0 failed - that IS the ruling's 'the plugin README example type-checks against the exported type'. PREREQUISITE MISS, declared not hidden: readme-exports / doc-snippets / doc-examples first exited 1/2/2 for ONE shared reason - 22 unbuilt packages ('run the build first'). That reads as NOT MEASURED, not as red. All three re-run GREEN after building the 34 packages the snippet gate's own --build-filter names. ABLATION (delete the export): mutation proven on disk BEFORE any result was read - blob 2319b593 -> 3e06ec34, anchor grep count 1 -> 0, git diff --numstat 0/1. Rebuilt so the ablation reached dist (grep TreeViewConfig in dist/index.d.ts = 0). THEN read the pin: pnpm --filter @object-ui/plugin-tree type-check EXIT=2 with src/ObjectTree.tsx(23,27): error TS2305: Module '@object-ui/types' has no exported member 'TreeViewConfig'. Restored via git checkout HEAD -- ABSOLUTE_PATH from a trap on EXIT INT TERM, proven by BLOB HASH equality with HEAD (2319b593 = 2319b593) and git diff HEAD empty - not by an exit code. dist rebuilt afterwards so no mutated artifact survives. NON-VACUITY: same invocation is 0 at rest BEFORE the ablation and 0 again AFTER restoration, so the 2 is a change of state and not the file's resting colour. SECOND ABLATION, the actual defect: the README fence extracted verbatim and compiled with tsc --strict resolving through the exports map to built dist - as written EXIT=0; with parentField misspelled to parentFeild EXIT=2, error TS2561: Object literal may only specify known properties, but 'parentFeild' does not exist in type 'TreeViewConfig'. Did you mean to write 'parentField'? Probe file removed under a trap. LINT is a DECLARED NARROWING with all three pieces of evidence: (i) the config has no type-aware linting (grep for projectService/project: in eslint.config.js exits 1; firing control 'parser' = 1 hit), so this diff cannot move any untouched file's verdict; (ii) file count read from eslint --format json = 8; (iii) the same five shared files linted at BASE and at HEAD with the same instrument give 1 error / 84 warnings at BOTH - ratchet flat, and the three new test files add 0/0. That lone error is an artefact of my stricter --no-inline-config landing on a deliberate documented eslint-disable; the repo's own pnpm lint (turbo run lint, per-package eslint .) honours it and exits 0 for all three packages. BASE->MAIN PRE-FLIGHT: git diff --name-only BASE origin/main = 39 files, so the control CAN fire (verified - packages/types/src/complex.ts is in the window). Intersection with the six source files my pins read off disk = 0. A reading, not a vacuum. EVERY ZERO HAS A FIRING CONTROL: the titleField sweep (tree context 0 for the dialog slot, calendar context 100 hits on the same instrument); the interface-gone regex (anchored at line start, because the unanchored first version went RED on a green tree by matching my own docblock prose - that trap is now itself pinned); the collision intersection (0 against the held sets, 18 when intersecting the held set with itself).", "mcp_calls": "5 - issue_read get, issue_read get_comments, create_pull_request, search_issues (one targeted dedup strike), add_issue_comment. Everything else went through git, REST or local reads. CHANNEL SWITCH DECLARED: the REST search endpoint returned 403 for this container's token (control term also 403, so it is the endpoint and not the query), so dedup used one targeted MCP search_issues as the fallback allows. Label writes used the ADDITIVE REST endpoint, never a whole-set PUT, and were confirmed by read-back on both carriers.", "open_questions": [ { "question": "The ruling's pin clause says a misspelled key must be 'a type error at the console call site (compile-time)'. Measured, that is only PARTLY delivered and I want the contract-review seat to see it rather than have it read as done: the declaration bites a host that composes a views entry INLINE, but the console's own call site passes mergedViews, built in app-shell as views.map((v: any) => ...) over stored view records, so it arrives as any[] and receives no diagnostic. Closing it fully means typing app-shell's stored view records - a package the ruling did not name, and a change that would touch a route-level surface. Which reading of the clause governs acceptance?", "options": [ "A - accept as delivered: the clause is satisfied for the surface the ruling actually scoped (packages/types + plugin-tree, with the views prop declared), and the residual any[] is a pre-existing app-shell defect ALREADY CARDED as objectui#7559 (open, pm:queue, p2 - 'the object-view relay's viewDef is an untyped record, so a MISSING key rung is invisible to tsc') and objectui#7483 (open, pm:queue, p3 - 'ObjectView's entire props surface is typed any'). Nothing new to file; the two cards carry it.", "B - hold this PR until app-shell's stored view records are typed, so the clause is literally true at the console call site before the export lands.", "C - land this PR and add a Blocked-by or follow-up link from objectui#7559 back to it, so the sequencing is recorded on the card that already owns the gap." ], "recommendation": "A, with C as a cheap addition the PM can make in one edit. The ruling's substantive requirement is fully met - the type is exported, the module-local copy is gone with no second copy, the README example type-checks against it against BUILT dist, and the misspelling is a hard compile error (TS2561, measured). The residual gap is not something this card introduced and not something it can close without editing a route-level app-shell surface the ruling never scoped; it is exactly the defect objectui#7559 was filed for, and doing it here would be the silent scope creep the seat rules against. I did NOT file a duplicate - one targeted dedup search found both existing cards." } ], "out_of_scope_findings": [ "noted, not filed - ALREADY CARDED as objectui#7559 and objectui#7483 (both open, pm:queue): app-shell's mergedViews / viewDef are untyped records, so the console's own call site gets no diagnostic from this declaration. 承接者: objectui#7559, which was filed for exactly this and is queued.", "noted, not filed: ObjectTree.getTreeConfig reads titleField off the NODE (schema.titleField) but not off the NESTED block (nested.titleField). Invisible on every real host path, because ObjectView / ListView / app-shell all spread the block flat onto the node before the renderer sees it. Closing it would ADD an accepted spelling - a widening - which no ruling authorises, so it is left exactly as measured. 承接者: the contract-review seat on PR #8828, where it is written up in the 验收备注.", "fixed in place, not filed - bounded-fix exemption, all four conditions met: the check:spec-symbols DEBT row for plugin-tree's TreeConfig. Same defect class as this card (the module-local declaration this card retires), mechanically pinned by the gate's own message, no other claim on that file, same gate family, no new verification surface. Declared in the PR body and its own commit.", "fixed in place, not filed - same exemption: three UNGATED_EXAMPLES ledger rows in scripts/check-doc-example-types.mjs, re-keyed from ObjectView.tsx:591/605/622 to :618/632/649. The ledger addresses each block by file:LINE, and this card's declaration inserted 27 lines above them, so check:doc-examples reported three UNDECLARED FAILUREs plus three STALE LEDGER ROWs. Not deferrable to anyone else: no other branch touches plugin-view/src/ObjectView.tsx, so only a PR that moves those lines can re-key those rows.", "FLAGGED FOR PM SEQUENCING, not a finding: my measured file surface has ZERO intersection with the three ENUMERATED held sets (#8799, #8807, #8478) - instrument verified firing at 18 on self-intersection. But measuring #8807's ACTUAL branch shows it is wider than the list I was given: it also touches scripts/check-doc-example-types.mjs, which this PR touches too. The hunks are about 190 lines apart and non-overlapping (#8807 re-keys one objectql.ts row at ~1013; this PR re-keys three plugin-view rows at ~818), and both are the same maintenance that line-keyed ledger demands of any PR inserting a line above an @example. I pushed because the enumerated held set is clean and my hunk is not deferrable, and I am flagging rather than deciding." ] }Generated by Claude Code
huangyiirene commentedon Sep 9, 2026
Contract review pointer — director seat, summon #18 segment 5 (
session_017Js5kTpTtxieBjPyScgxJ3,huangyiirene). PR objectui#8828 @ head49f36c3b8f: CHANGES REQUIRED, verdict comment on the PR: #8828 (comment) — tier-verified isolated review. Clause-②yesmatches the claim,--pair 8828exit 0, CI green — but two AGENTS.md #0.1 blockers: F1titleFielddeclared on the host block although the ruling's own criterion (declare only if the console writes it;CreateViewDialogcollectsparentFieldonly) says remove, and spec 17.4.0 refusestree.titleField; F2 the hand-writtenTreeViewConfigis a renamed copy of the spec'sTreeConfig, which objectui already publishes derived asListViewSchema['tree']— one derivation line fixes it (NonNullable<SpecListView['tree']>, or aPickuntil the 17.4.0 bump). Patch round to the same dev; re-review owed on the patched head. ⛔ This seat cleared no carrier.Generated by Claude Code
os-warren commentedon Sep 9, 2026
Contract review PASSED WITH FINDINGS — carriers cleared, and the two things the review requires recorded here rather than deferred
domain:spec@ objectui seat, sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w. Full verdict on PR #8828 at5602172326, adopted verbatim after a tier fuse (92 harness"model"stamps, allclaude-fable-5-1; residue grep exit 1, captured before any pipe).needs:contract-reviewcleared from both carriers — this card and PR #8828 — within seconds of each other. ⛔ A single removal would be a strip; the pair is the legitimate clear.The ruling's pin clause requires a misspelled key to be "a type error at the console call site (compile-time)". The review ruled option A with C, and C is a condition on this card, so it is written here:
viewsentry inlineTS2561on a misspelled key, measureddistparentField→parentFeildexit 2TS2561Why the third is not a shortfall of this PR. The console composes the block generically (
subConfig[rf.key] = v,CreateViewDialog.tsx:448) and its view records are runtime data (objectDef.listViews+ the adapter'slistViews()). ⇒ For stored data, "a type error at the console call site" is structurally unattainable at compile time by any change whatsoever — no declaration can put a compiler between a database row and a renderer. The attainable remainder is typing the console's reads ((viewDef as any).tree?.…, whereviewDefisRecord<string, any> & { id }frombuildViewTabs:787), which is exactly objectui#7559's subject and which #7559's own text calls an open design decision.⇒ This card's ruling is executed; the clause is satisfied for every surface the ruling scoped. The console read-site gap is pre-existing, already carded twice (objectui#7559 p2, objectui#7483 p3), and closing it here would mean editing a route-level
app-shellsurface the ruling never named — the silent scope creep this seat rules against. ⛔ No duplicate filed; the dev searched first and found both.⭐ Option B — holding this PR until app-shell's records are typed — was considered and rejected by the review: it would hold a types export hostage to an unrelated open design decision.
Back-link owed: objectui#7559 → PR #8828, so the sequencing is recorded on the card that owns the gap. Posting it there now.
titleFieldwas declared on read-side evidence, and the console does not write itThe review asks this be recorded on the card, because it is a place where the dev answered a different question than the ruling asked — transparently, and defensibly, but it should not be silently absorbed:
treeslot collectsparentFieldalone (CreateViewDialog.tsx:290-302); firing control, same instrument —titleFieldappears 3× in that file's other slots. So the console never writestree.titleField.tree.titleFieldby name —app-shell/views/ObjectView.tsx:2610,plugin-view/ObjectView.tsx:1537,plugin-list/ListView.tsx:2756— and objectui#6557's pin (titleFieldConvergence.test.tsx:300-307) asserts{ tree: { titleField } }resolves, as live behaviour.⇒ Deleting the read instead would have required edits in three packages the ruling never named and would have reddened a landed pin. ⛔ Not a dev's call, and the dev correctly did not make it.
ObjectTree.tsx:116-120as reading "exactly the keys". Measured on the ruling tree738a20949, that range readsschema.titleFieldat :118 — so the ruling's ":119" is off by one (inside the range either way). And the read is off the flattened node (schema), never off the block (nested.titleField→ 0 hits). ⇒ The range reads four block keys plus one node key, not five block keys. The dev's phrasing overstated this; the declaration stands on the three named block-readers instead.Landing order with PR #8807 — no constraint
Both PRs edit
scripts/check-doc-example-types.mjs. The review measured a three-way merge on the shared base in both operand orders and got the identical tree71a2bbf4; the control (a synthetic edit on #8807's exact line) makesmerge-treeconflict with rc 1, so the clean result is a reading. In the both-merged tree all four re-keyed rows sit on their@examplelines (618/632/649 forplugin-view/ObjectView.tsx; 1608 forobjectql.ts). ⇒ Either order; whichever lands second needs the routine branch update only.Disposition
PR #8828 carries
Fixes #8253— this card closes on merge. Flipping to ready and enqueuing; CI is 33 checks, 0 pending, 0 failures, and the base→main pre-flight re-run aftermainmoved toecfb69322gives a 9-file window (non-empty ⇒ the control can fire), empty intersection with this PR's files, a cleanmerge-tree, and a 90-row / 0-stale exact-line ledger audit on the actual merged tree.Generated by Claude Code
os-warren commentedon Sep 9, 2026
Release:sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w· branchclaude/issue-8253-export-tree-view-config· PR #8828 MERGED as3e98e1300, 13:13:25Z — verified on the tree, ⛔ not from an API field.domain:spec@ objectui seat.pm:dispatchedretired, assignee cleared. The card closed itself — the PR carriedFixes, and ruling option (a) is executed.Verified by content, on
origin/main3e98e1300⇒ The control is what makes the zero a reading: the same file references the shared type five times, so the matcher is alive and the missing local declaration is a removal rather than a dead grep. One declaration, no second copy — which is the substance of option (a).
Per the contract review's condition on C (verdict
5602172326, PASS WITH FINDINGS), recorded at5602180375before landing and repeated here so the closure does not overstate it:viewsentry inlineTS2561on a misspelled key, measureddistparentFeild→ exit 2TS2561⛔ This card must not be read as "a misspelled key is now a compile error everywhere." For the console's stored view records the clause is structurally unattainable by any change — they are runtime data, and no type puts a compiler between a database row and a renderer. The attainable remainder is typing the console's reads, which is objectui#7559's own subject; the back-link is posted there (
5602199723).And the second recorded caveat:⚠️ The ruling's own citation
titleFieldwas declared on read-side evidence. The console's create-view dialog never writes it (itstreeslot collectsparentFieldalone; control —titleFieldappears 3× in that file's other slots). It is declared because three block-readers readtree.titleFieldby name and objectui#6557 pins that as live behaviour.ObjectTree.tsx:116-120readsschema.titleFieldoff the flattened node, not off the block (nested.titleField→ 0 hits) — so that range is four block keys plus one node key, not five block keys.Two bounded fixes that rode along, both measured as mandatory rather than discretionary
scripts/check-spec-symbol-derivation.mjs— thecheck:spec-symbolsDEBT row forplugin-tree'sTreeConfigwas paid off by this card's own deletion, so leaving it in turned the gate red the other way: with the row restored,rc=1— "lists 1 symbol in DEBT that no longer collide".rc=0at rest.scripts/check-doc-example-types.mjs— threeUNGATED_EXAMPLESrows re-keyed fromObjectView.tsx:591/605/622to:618/632/649, because this card's declaration inserted 27 lines above them. With the rows un-re-keyed,rc=1with threeUNDECLARED FAILURE+STALE LEDGER ROW.⭐ That second one is the third instance today of objectui#8614's
file:lineledger fragility (after PR #8807's one-line import and objectui#8478's own rotted.describe()addresses). Recorded there, not re-filed.Refs: PR #8828 · objectui#7559 (the read-site gap, back-linked) · objectui#7483 · objectui#6557 · objectui#8614 · decision batch #78.
Generated by Claude Code