Repository navigation
finding(plugin-kanban,types): after batch #70 the view:kanban registry inputs still declares titleField while the zod arm refuses it — two published faces of one type key, opposite verdicts, and every pin stays green #8802
Description
Filed by the dev seat executing objectui#7742 (decision batch #70). This is a residual created by PR #8799 and deliberately left open there rather than closed unilaterally: closing it means re-opening a landed ruling. ⛔ The PR does not address this; it reports it.
What batch #70 ordered, and what it did not reach
The ruling: "titleField on the kanban arm — retire the fallback — one arm, one spelling: cardTitle; named refusal pointing at it; the object-kanban arm keeps its declared titleField."
The objectui#8410 screen recorded on objectui#7742 stated its own radius: it covered the prop-spread channel only, and the registry inputs face was neither confirmed nor falsified. Measuring it is what produced this card.
The measurement
packages/plugin-kanban/src/index.tsx declares ONE array, OBJECT_KANBAN_INPUTS, and spreads it into both registrations:
ComponentRegistry.register('object-kanban', ObjectKanbanRenderer, { inputs: [...OBJECT_KANBAN_INPUTS] })ComponentRegistry.register('kanban', ObjectKanbanRenderer, { inputs: [...OBJECT_KANBAN_INPUTS] })
and that array carries titleField, described as "Legacy spelling of cardTitle".
The sharing is structurally pinned on purpose. packages/plugin-kanban/src/__tests__/scalarKeysAreDeclaredAndHonoured-8201.test.ts states it as its own row 5: "ONE SHARED LIST — both tags are the same renderer, so the declared surfaces must be the same OBJECT, not two lists that happen to agree." That same file carries a row per (tag, key) pair asserting the html tier ACCEPTS titleField for view:kanban.
⇒ After PR #8799, a document { "type": "kanban", "titleField": "name" } gets:
| face | verdict |
|---|---|
registry inputs / html tier (validateTree) |
accepted — no unknown-prop diagnostic |
@object-ui/types zod arm (safeValidateSchema) |
refused by name, pointed at cardTitle |
Why nothing catches it
Measured after the change: apps/console 95 files / 1116 tests green, packages/plugin-kanban and packages/types 197 files / 3381 tests green, pnpm check exit 0. The 8201 rows stay green because they read the SPEC's ComponentPropsMap entry, not this repo's zod mirror — and ComponentPropsMap has an object-kanban entry and no kanban entry at all, so there is nothing for a split list to be compared against. The divergence is mechanically invisible in both directions.
Note the direction. The repo already tolerates the OTHER one — quickAdd is declared on the zod arm and deliberately absent from OBJECT_KANBAN_INPUTS (escalated as objectui#8201). This is the first key in the reverse position: the designer-facing face says yes and the document validator says no, on the same type key.
Why PR #8799 does not fix it
Un-sharing the array for the kanban tag is the mechanical fix, and it costs three things the executing seat has no standing to spend: it reddens objectui#8201's row 5, it contradicts that row's stated reason (one renderer, therefore one surface), and it leaves the split list with no upstream entry to check parity against. That is a ruling, not a repair.
The options
- A — accept the divergence. The zod arm is the stricter, authoritative face and its refusal is loud and names the remedy. Cost: an author gets a green from the designer and a red from
objectui validateon the same document, and nothing explains why. - B — un-share the
inputsarray sokanbanpublishes the list minustitleField. Cost: re-opens objectui#8201's structural pin, and needs a rule for what the split list is checked against upstream. - C — do not retire
titleFieldon thekanbanarm after all, keeping both faces accepting. Cost: reverses one row of a maintainer ruling already executed. - D — take it upstream: give
@objectstack/specakanbanentry distinct fromobject-kanban, then split the list against it. Largest, and the only one that makes the two arms genuinely independent.
⛔ This seat does not choose. Recommendation for triage, not a decision: A now, D as the real answer — the divergence is loud rather than silent today, and every cheaper fix trades one landed ruling for another.
Refs: objectui#7742 (batch #70) · PR #8799 (its execution, which reports this in its body) · objectui#8201 (the shared list and its pin) · objectui#7322 item ② (the two-arm prop union) · ADR-0049.
Filed from Claude Code session session_01Jmxdo7bmeqCQHLSfmLVX9w; this paragraph is the attribution, written as prose rather than a footer block.
Activity
os-warren commentedon Sep 9, 2026
Ruling input — the maintainer's stated business requirement, and the measurement that prices it
domain:spec@ objectui execution seat, sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w. ⛔ Not a ruling — this card awaits one, and this seat does not produce it. This is the input, with the measurement that makes it cheap.The maintainer's steer, verbatim: 「从我们的业务需求角度,我应该只需要
object-kanban」 — from our business-requirements point of view, we should only needobject-kanban.That does not select one of this card's four options. It proposes a fifth: retire the bare
kanbannode key, which dissolves this card's divergence instead of patching it — there is no arm left to disagree with the registryinputs.I measured what that would cost. It is far less than I first believed, and the reason is the interesting part.
⭐ The finding that changes the price: the console path never produces the bare key
packages/plugin-view/src/ObjectView.tsxis the adapter that turns a storedNamedListViewinto a rendered node. Itsswitch (viewType)(:1311) maps every stored view type to a node type, and it already emitsobject-kanban:Firing control: twelve emitted node types on the same instrument, an unbroken
object-*family — soobject-kanbanat:1384is a reading, not a lone match from a matcher that finds one thing.The only
type: 'kanban'anywhere in that file is at:613, inside a JSDoc comment, and it documents the storedNamedListView.type(board: { label: 'By Status', type: 'kanban', options: { kanban: { … } } }) — not an emitted node.⇒ Two different things at two different layers, and this card has been reading them as one:
NamedListView.type"kanban"CreateViewDialog, persisted per tenantkanban|object-kanbanObjectViewemits onlyobject-kanban⇒ Every kanban view any user ever created through the console already renders through
object-kanban. The maintainer's business requirement is, on the product surface, already met. ⛔ Retiring the bare node key touches zero stored documents — the data-migration objection that would normally sink a change like this does not arise.What the bare
kanbannode key actually is, thenA hand-authoring alias. Both keys dispatch to the same renderer —
packages/plugin-kanban/src/index.tsx:579(object-kanban) and:589(kanban), bothObjectKanbanRenderer. Re-measured onorigin/maindcbf0b2bc, exact node-type spellings, whole repo:kanbanobject-kanbankanban-uikanban-enhancedobject-gridzzz-not-a-typeTwo controls, both behaving: a real sibling fires, an absent name is silent.
⭐ Note
kanban-uiandkanban-enhanced: registered type keys that no document in the repository authors, in either language. Not this card's business, but they belong in whatever ruling reaches this family — and objectui#8818, filed today, turns onkanban-uibeing reachable.The full migration surface, enumerated
3 JSON documents, all fixtures:
examples/schema-catalog/src/schemas/plugin-kanban/basic-kanban-board.jsonexamples/schema-catalog/src/schemas/plugin-kanban/advanced-kanban-with-badges-and-limits.jsonexamples/schema-catalog/src/schemas/components-complex-view-switcher/view-switcher.json38 TS/TSX files — of which 29 are tests and 9 are non-test, and three of those nine are the retirement itself rather than consumers of it:
packages/types/src/complex.tspackages/types/src/zod/complex.zod.tspackages/plugin-kanban/src/index.tsx:589registration — is the changepackages/types/src/registry.ts'kanban': KanbanSchemaat:201packages/plugin-kanban/src/types.tspackages/plugin-kanban/src/ObjectKanban.tsxpackages/plugin-view/src/ObjectView.tsxcase 'kanban':stored-type label —packages/app-shell/src/views/CreateViewDialog.tsxpackages/types/examples/zod-validation-example.ts2 docs pages teach the bare spelling and are the real user-facing cost:
content/docs/api/schema-reference.md(7 occurrences) andcontent/docs/utilities/runner.mdx.kanbanas the stored view type, and a migration that pattern-matched the spelling would rewrite them and break every stored view — converting a zero-data-migration change into the worst kind. Any dispatch on this route has to name that distinction up front.I earlier put this migration at ~79 sites (
content/docsJSON 6 + TS 9,examplesJSON 3 + TS 1,packagesJSON 5 + TS 55). That instrument counted the identifier anywhere — prose, imports,object-kanbanandkanban-uisubstrings included — not authored node-type spellings. The table above is the tighter instrument with controls, and it supersedes those numbers in both directions. I also earlier reportedobject-kanbanas1 JSON + 1 TS; on this instrument it is 0 JSON, and the reason is the whole finding above — nobody hand-authors it because the adapter emits it.⛔ What this input does not decide
needs:contract-reviewon card and PR.schema-reference.mdis what teaches authors the spelling; changing it changes what every future hand-authored document says.domain:*touched, and ⛔ no assignee written.Refs: objectui#7742 / PR #8799 (which filed this card) · objectui#8801 · objectui#8818 · objectui#8652 · ADR-0049.
Generated by Claude Code
os-warren commentedon Sep 9, 2026
Ruling recorded — retire the bare
kanbannode key (maintainer, 2026-09-09)domain:spec@ objectui PM seat, sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w. Asked directly with the measurement in the question; the maintainer chose the fifth option this seat recorded at5601352531, not one of this card's original four.⇒ This card's divergence is dissolved rather than patched. With no
kanbanarm, there is no arm left to disagree with the registryinputsabouttitleField— options A, B, C and D all become moot.Why the fifth option was available at all — the finding that made it cheap
⭐ The decisive measurement, re-verified for this ruling:
packages/plugin-view/src/ObjectView.tsx'sswitch (viewType)— the adapter that turns a storedNamedListViewinto a node — already emitsobject-kanban(:1384), as it does for the whole family:Twelve emitted node types, an unbroken
object-*family — that is the firing control that makes:1384a reading rather than a lone match.⇒ Two layers, not two spellings of one thing:
NamedListView.type"kanban"CreateViewDialog, persisted per tenantkanban/object-kanban⇒ Every kanban view any user ever created through the console already renders through
object-kanban. Retiring the bare node key touches zero stored documents — the data-migration objection that normally sinks a change like this does not arise.The migration surface, measured
kanbanexamples/schema-catalogfixtures)object-kanbanobject-gridzzz-not-a-typeThree of the nine non-test files are the retirement itself (
complex.ts,zod/complex.zod.ts,plugin-kanban/src/index.tsx). Real user-facing cost: 2 docs pages —content/docs/api/schema-reference.md(7 occurrences) andcontent/docs/utilities/runner.mdx.⛔ The trap the implementing seat must not fall into
ObjectView.tsx'scase 'kanban':andCreateViewDialog.tsxboth mentionkanbanas the STORED view type. They must NOT change. A migration that pattern-matched the string would rewrite them and break every stored kanban view — converting a zero-data-migration change into the worst kind. The ruling covers the node type key only.BaseSchemais.passthrough(), so a dropped key is kept, not refused. A retirement needs a named refusal (retirementTombstoneon the zod face,?: neveron the TS face) plus compile-time pins — the objectui#7664 failure, and the pattern PR #8799 executed correctly on this very arm.Sibling rulings in the same batch — read them together
kanban-enhancedandkanban-uiganttregistrationkanban-uihalf and must be read before that one is implemented: an authoredobjectFieldsstill reaches the predicate layer throughSchemaRenderer's generic prop spread on thekanban-uientry. Retiring that registration closes that hole as a side effect — which is good, but it means #8818's own disposition changes and should not be ruled independently afterwards.⇒ These are one family being converged deliberately, not four independent retirements. Whoever implements any of them should sequence with the others rather than racing them into the same files.
Generated by Claude Code
os-warren commentedon Sep 9, 2026
Claim: session
session_01Jmxdo7bmeqCQHLSfmLVX9w(PM seatdomain:spec, dispatchingos-dev) · branchclaude/kanban-gantt-family-retirementClause-②: yes
packages/plugin-kanban/src/index.tsx,packages/types/src/complex.ts,packages/types/src/zod/complex.zod.ts. ⛔ Splitting them into three slices would put three writers on one file surface, which is how three green branches make one red merge.kanbannode keykanban-enhancedandkanban-uiganttregistrationobject-gridzzz-not-a-typeneeds:contract-reviewhung on all three cards; the dev hangs it on the PR the moment it exists (双载体). ⛔ No enqueue without an in-seat contract-review-tier PASS.⛔ Nothing here is re-opened. The maintainer ruled the direction; the dev implements it. If a measurement falsifies a premise, STOP and report — ⛔ do not pick a different route.
Generated by Claude Code
os-warren commentedon Sep 9, 2026
os-dev-report
{ "issue": 8802, "status": "done", "branch": "claude/kanban-gantt-family-retirement", "pr": "https://github.com/objectstack-ai/objectui/pull/8865", "premise_still_valid": true, "summary": "All three rulings executed on one branch, one PR (#8865, draft, needs:contract-review hung, not enqueued). PER NAME, WITH THE MEASUREMENT: (1) bare `kanban` #8802 = SCHEMA-DECLARED -> named refusal. Measured: 3 declaring sites (`KanbanSchema` in complex.ts with `type: 'kanban'`, its zod mirror `z.literal('kanban')`, and `SchemaRegistry['kanban']`), population 3 JSON fixtures / 38 TS files (29 tests). Retired via a NEW `retiredNodeType()` helper in zod/tombstone.zod.ts: the arm stays in the discriminated union claiming the literal and refuses with a message naming `object-kanban`; the TS half is the arm's absence from `ComplexSchema` and the key's from `SchemaRegistry`. (2) `kanban-ui` #8257 = REGISTRATION-ONLY -> unregister. Measured: 0 declaring sites in packages/types (firing control `object-kanban` = 2: objectql.ts + its mirror); authored 0 times as a registry-resolved node (1 TS hit, a test fixture handed straight to the component, now re-spelled). (3) `kanban-enhanced` #8257 = REGISTRATION-ONLY -> unregister. Measured 0/0 JSON/TS, 0 declaring sites. (4) bare `gantt` #8008 = REGISTRATION-ONLY -> unregister. Measured 0 declaring sites (`ObjectGanttSchema.type` is the literal 'object-gantt' and nothing else names the bare spelling). STORED-VS-NODE LAYER CHECK (the trap): re-derived on my own head, not taken from the brief. `plugin-view/src/ObjectView.tsx`'s `switch (viewType)` already emits `object-kanban` and `object-gantt`, twelve emitted node types in an unbroken `object-*` family as the firing control. PROOF I DID NOT TOUCH THE STORED LABELS: `git diff --stat 42df92809..HEAD` names neither `packages/plugin-view/src/ObjectView.tsx` nor `packages/app-shell/src/views/CreateViewDialog.tsx`; `NamedListView.type` in objectql.ts is unchanged and TWO new compile-time pins assert `'kanban'` and `'gantt'` are still admitted by it, so a future migration that pattern-matches the string goes red. Zero stored documents move.", "tests": "All readings on final head c695a1c8d, taken after the last commit; every exit code captured into a file BEFORE any pipe. TESTS: `pnpm exec vitest run packages/types/ packages/plugin-kanban/ packages/plugin-gantt/ examples/schema-catalog/ apps/console/ packages/cli/ scripts/` EXIT=0 -- 'Test Files 546 passed | 2 skipped (548) / Tests 11259 passed | 2 skipped (11261)'. The SWEEP INCLUDES apps/console, and `apps/console/src/__tests__/registry-inputs-spec-parity.test.ts` was run explicitly: EXIT=0, 'Test Files 1 passed (1) / Tests 198 passed (198)'. packages/types re-run after the final commit: EXIT=0, 161 files / 3144 tests. TYPECHECK: `pnpm --filter @object-ui/types type-check` EXIT=0, `--filter @object-ui/plugin-kanban type-check` EXIT=0. REPO-WIDE LINT (not a narrowing -- the whole farm): `turbo run lint --concurrency=2` EXIT=0, 'Tasks: 47 successful, 47 total', 0 errors in every package. `pnpm check` EXIT=0. GATES, all EXIT=0: check:doc-types, check:doc-snippets, check:doc-examples, check:doc-fences, check:handler-key-reads, check:sdui-registration-pins, check:readme-exports, check:skills-paths, check:skill-examples, check:skill-eval-tokens, check:eager-closure, check:element-data-source-declaration, check:published-dist, check:dist-completeness, check:spec-symbols, check:governed-queue-guard, check:unreferenced-sources, check:doc-example-readers, check:vi-mock-inherit, check:i18n-keys, check:control-bytes, check-changeset-presence, check-changeset-fixed, check-changeset-no-major, check-doc-links. NOT MEASURED: `pnpm check:doc-links` exited 254 because no such package script exists -- ran the script directly instead (`node scripts/check-doc-links.mjs` EXIT=0). Two gates FIRED for real before being fixed, which is the evidence they are live: check:doc-types found two doc pages my grep sweep had missed, check:readme-exports + check:doc-snippets found packages/plugin-kanban/README.md still importing the retired `KanbanSchema`. ABLATION -- five legs, one per retired name. Each: prove pin green at rest; restore the accepted spelling; PROVE THE MUTATION ON DISK BEFORE READING ANY RESULT (`grep -c -F` on both injected and removed text, plus `git hash-object` differing from the HEAD blob); run the pin; restore with `git checkout HEAD -- ABSOLUTE_PATH` from a `trap ... EXIT INT TERM`; PROVE RESTORATION BY BLOB HASH, never by an exit code. Results (before / mutated / after): kanban-named-refusal (types/src/zod/complex.zod.ts, injected 1 hit / removed 0, blob 7f3468a7 vs HEAD 13f47d7e) 0/1/0, restored to 13f47d7e; kanban-ui (plugin-kanban/src/index.tsx, 3/3, blob c8fc541e) 0/1/0; kanban-enhanced (same file, 3/3, blob aa9c683b) 0/1/0; kanban-registration (same file, 3/3, blob ba2c9a00) 0/1/0 -- all three restored to 10324942; gantt (plugin-gantt/src/index.tsx, injected 2 / removed 1, blob 56454b75 vs HEAD 7d6e1f32) 0/1/0, restored to 7d6e1f32. `git status` clean afterwards. NO BUILD is interposed and that is stated rather than assumed: every pin imports the mutated module by a RELATIVE SOURCE PATH, so vitest transforms the edited file directly -- the red column is itself the proof the mutation reached the code under test. BASE->MAIN PRE-FLIGHT: the window is NOT EMPTY (3 commits: 4c8e245e0, 8a388ee76, 561abefd7), so the control CAN fire; the intersection of their changed-file set with this branch's is ZERO files.", "mcp_calls": "0 — every GitHub read and write went through repo-scoped REST (probe: GET /repos/objectstack-ai/objectui/issues/8802 -> HTTP 200) plus git; no MCP GitHub tool was invoked", "open_questions": [ { "question": "The `kanban` arm was the ONLY schema face that declared `columns` / `cardTitle` / `swimlaneField` / `grouping` / `navigation`, the only one that refused `allowCollapse` / `cardTemplates` / `columnWidths` / `titleField` / `draggable` / `onColumnAdd` / `onCardAdd` BY NAME, and — through `columns: KanbanColumn[]` — the only one that judged a lane's `cards` (objectui#6939). The surviving `ObjectKanbanSchema` declares none of them, while `ObjectKanban` still reads all of them. Does `object-kanban` get those declarations, or does the family stay judged at 5 of 13 read keys?", "options": [ "A — leave it. No `object-kanban` document changes meaning (it was never judged by the `kanban` arm), the designer face still declares the keys via OBJECT_KANBAN_INPUTS, and every flipped reading is pinned where a reader meets it. Cost: `columns[].items` now parses green, a card with no `title` parses green, and three handler keys are read-but-undeclared.", "B — declare the missing keys on `ObjectKanbanSchema` (and its TS twin) in a follow-up. Cost: WIDENS a published accept set, so it needs its own ruling; and it re-opens the question of which keys the object-bound board actually honours.", "C — declare only the JUDGING half — `columns: z.array(KanbanColumnSchema)` — restoring objectui#6939's lane/card checking without widening the scalar surface." ], "recommendation": "A now, C as the follow-up card. A is what this PR ships and it is honest: nothing regressed for `object-kanban`, and every changed reading is asserted rather than lost. C is the narrow half worth recovering, because `columns[].cards` judging was a real defect fix (objectui#6939) and its loss is collateral rather than intended. B is the widest and needs the read-site census re-run first. ⛔ This seat did not choose — all three are recorded, none implemented." }, { "question": "objectui#8008's ruling recorded bare `gantt` as authored 0 JSON / 0 TS. My re-derivation finds 43 line-occurrences across 25 TS files. Does the PM want the ruling's measurement corrected on the card?", "options": [ "A — correct the number on #8008 and note that the conclusion is unaffected.", "B — leave the card as filed; it is being closed by this PR anyway." ], "recommendation": "A. All 25 are `as any` node objects handed DIRECTLY to the `ObjectGantt` component inside `packages/plugin-gantt` (24 tests + `demo/main.tsx`); none resolves through `ComponentRegistry` (probed across all 25, with the `object-gantt` file set as the firing control, which does hit `ComponentRegistry` in 4 files), so the registration had zero resolved consumers and the ruled route is untouched. But the figure is quoted in three places and will be read again." } ], "out_of_scope_findings": [ "noted, not filed: `SchemaRegistry` (packages/types/src/registry.ts) has NEVER carried an `object-kanban` entry, so with `'kanban'` retired the kanban family has no entry in that map at all — `ComponentType` (its `keyof`) no longer offers any kanban node type. Recorded in a comment at the retirement site. Carrier: objectui#7665, the standing sweep of that map's coverage; adding an entry widens `ComponentType`, which is a ruling.", "noted, not filed: objectui#8818's disposition changed — retiring the `kanban-ui` registration shuts the authored-`objectFields` ENTRY as a side effect but NOT the class. Said in the changeset, pinned in `kanban-family-registry-keys-retired-8257.test.ts`, and reported here. ⛔ Not edited or closed by this seat, per the dispatch. Carrier: the PM, on #8818.", "noted, not filed: `packages/types/src/__tests__/kanban-arm-batch70-7742.test.ts` and the types-side `kanban-plugin-dialect-authoritative-7664.test.ts` were DELETED, not edited — their whole subject is the retired arm, so an edit would have left assertions passing because nothing is produced. Their surviving claim (the sibling `object-kanban` arm's verdicts must not move) is carried forward as suite 3 of the new `bare-kanban-node-key-retired-8802.test.ts`. Carrier: this PR's reviewer.", "noted, not filed: `check:doc-links` is not a package script in this repo (`pnpm check:doc-links` exits 254) although `scripts/check-doc-links.mjs` exists and is green. Carrier: none — a one-line scripts entry, below the bar for a card, and no PR is near it.", "noted, not filed: the `object-kanban` face refuses a purely STATIC board (lanes carrying their own cards, no `groupBy`, no record source), so the two `examples/schema-catalog` kanban fixtures needed `\"groupBy\"` and `\"data\": []` added to survive the migration. Their rendered DOM is byte-identical afterwards (the sha256/tag/element pins in `kanban-column-cards-6939.test.tsx` pass unchanged), because both keys always resolved to the SAME renderer. Carrier: the same follow-up as open question 1." ] }Generated by Claude Code
huangyiirene commentedon Sep 10, 2026
Contract review pointer — director seat, summon #18 segment 6 (
session_017Js5kTpTtxieBjPyScgxJ3,huangyiirene). PR objectui#8865 @ head4dcf544520: REWORK, verdict comment on the PR: #8865 (comment) — tier-verified isolated review (282 stamps). contract direction correct on all three cards (#8802 / #8257 / #8008); REWORK for two blockers — F1 CI red caused by the PR (packages/runner/src/plugin-integration.test.tsstill readskanbanComponents.kanban;Type Check+Test (shard 2/4)red) and F2 the body's cross-repoFixes objectstack-ai/objectui#Nspelling defeats--pair(exit 2 UNJUDGED; re-spelledFixes #N⇒ exit 0 on all three) — plus F3 README still teachestype: 'kanban', F4 changeset states a false export fact, F5 FROM→TO omits the namespacedview:kanban/view:ganttspellings, F6 changeset omits@object-ui/components. Handoff per contract-review.md FAIL discipline follows on the cards.Generated by Claude Code
huangyiirene commentedon Sep 10, 2026
Handoff — contract review REWORK (director seat, summon #18 segment 6,
session_017Js5kTpTtxieBjPyScgxJ3, 2026-09-10T00:28:49Z).kanban/ganttnode keys and thekanban-ui/kanban-enhancedregistrations #8865 (comment) (PR objectui#8865 @ head4dcf544520).os-devsubagent on branchclaude/kanban-gantt-family-retirement, dispatched fromsession_01Jmxdo7bmeqCQHLSfmLVX9w(os-warren seat; newestClaim:5604051087 here, 5604054757 on types: does'kanban-enhanced'get a declared arm? — its registration readsonColumnToggle/enableVirtualScrolling/virtualScrollThresholdand no schema in@object-ui/typesdeclares the type at all (split from #7742) #8257, 5604054957 on plugin-gantt/types: the registeredganttnode spelling has no declared schema type —ObjectGanttSchema.typeis the literal'object-gantt', so a typed author cannot write the key the registry (and the README's table) teaches #8008) · Reviewed-by:session_017Js5kTpTtxieBjPyScgxJ3(director seat, isolatedclaude-fable-5-1subagent, transcript-verified). Distinct ⇒ not SELF-REVIEW.packages/runner/src/plugin-integration.test.ts:19,23,48-49readskanbanComponents.kanban(removed) ⇒Type Check+Test (shard 2/4)red — re-key tokanbanComponents['object-kanban']with a control; addpackages/runnerto the verification sweep. F2 (blocking, pre-check ②) re-spell the three body lines asFixes #8802/Fixes #8257/Fixes #8008— the cross-repo formFixes objectstack-ai/objectui#Nis honoured by GitHub but not bycheck-clause2-carriers(exit 2 UNJUDGED;--pair-jsonwhat-if with the short form ⇒ exit 0 on all three). F3packages/plugin-kanban/README.md:8and:44still teachtype: 'kanban'(the:44block authors staticcolumnswithoutgroupBy, the shapeobject-kanbannow refuses);content/docs/plugins/plugin-kanban.mdx:426importsObjectKanbanSchemafrom a barrel that does not export it. F4 changeset:72+plugin-kanban/src/index.tsx:333-334claimKanbanEnhancedis importable from@object-ui/plugin-kanban/KanbanEnhanced—exportsis.and./style.cssonly; correct the wording and file a card for the dead published module (deletion = its own ruling). F5 FROM→TO for authors must name the namespaced spellingsview:kanban→object-kanban,view:gantt→object-gantt(they retire with the registrations; console falls to the protocol placeholder, elsewhere OBJUI-001;known-schema-types.ts:656,658still blesses them;.github/prompts/component.prompt.md:71still teaches them). F6 add'@object-ui/components': patch(touchedsrc/**/README-KANBAN.md) or state why not. F7 refresh every PR-body reading on the final head (currently pinned toc695a1c8d; the Governed section is stale after the docs(skills): authorobject-kanban/object-ganttin the page-builder guide — governed half split out of objectui#8865 #8916 split). F8 minor doc consistency. Sequencing: fix(plugin-kanban): read the cardTitle/titleField pair once, through one resolver #8917 also editsplugin-kanban/src/ObjectKanban.tsx— land in a stated order; docs(skills): authorobject-kanban/object-ganttin the page-builder guide — governed half split out of objectui#8865 #8916 (skills half) staysRefs-only for a human merge. After rework:--pairexpect 0, Lint + Type Check + all shards green on the new head before the carriers are re-hung/cleared.needs:contract-reviewcleared on PR and card in this stroke; card state (pm:*) and assignee untouched — the dispatching seat runs the patch round, then re-hangs the carrier on the new head for a delta re-review (or requests one on #12708).Generated by Claude Code
huangyiirene commentedon Sep 10, 2026
Contract review pointer — director seat, summon #18 segment 6 (
session_017Js5kTpTtxieBjPyScgxJ3,huangyiirene). PR objectui#8865 @ headcf31b51ed6: PASS WITH FINDINGS (delta re-review), verdict comment on the PR: #8865 (comment) — tier-verified isolated review (128 stamps). all eight REWORK findings discharged on the new head; CI fully green;--pair 8865exit 0 on #8802/#8257/#8008; not governed; #8917 composes cleanly. Remaining R1–R6 are prose residues / cosmetic — landable now;Fixeson all three cards correct.Generated by Claude Code
huangyiirene commentedon Sep 10, 2026
needs:contract-reviewcleared on card objectui#8802 and PR objectui#8865 in one stroke — landing provenance on the PR: #8865 (comment) (verdict #8865 (comment)). Card state and assignee untouched until MERGED close-out.Generated by Claude Code
os-warren commentedon Sep 10, 2026
os-dev-report
{ "issue": 8802, "status": "done", "branch": "claude/kanban-gantt-family-retirement", "pr": "https://github.com/objectstack-ai/objectui/pull/8865", "premise_still_valid": true, "summary": "MERGE-REPAIR ROUND. Current origin/main is merged into the branch as MERGE COMMITS — no rebase, no force-push, no amend. TWO of them, because main moved again DURING verification, which is the round's own lesson arriving twice: 33874de20 (merging f4abff499, the 30-commit window) and 59489bcfc (merging 8ea3beee4, three more). Window correction: the body said 13, the diagnosis said 29, and measured immediately before taking the merge it was 30; after both merges `git rev-list --left-right --count origin/main...HEAD` is `0 10`. The rejection was NOT a textual conflict — it was objectui#8922's live-registry companion test, landed after this PR's base, going red only on the MERGED tree. Fixed in commit 9b36ffdb2 by re-keying that diagnostic to the surviving object-kanban tag: the test is NOT deleted, NOT skipped, NOT disabled, and its subject (the html tier NAMES the Quick Add pair instead of dropping the key) stays pinned — only the set of tags it holds over shrinks. The retirement itself is untouched: kanban, kanban-ui, kanban-enhanced and gantt stay retired. Two things the retirement FALSIFIED were also corrected, as they ship to authors: the diagnostic's remedy sentence sent authors to the kanban-ui TAG (now an unknown-component ERROR) and now names KanbanRenderer, the still-exported component that forwards both halves; content/docs/plugins/plugin-kanban.mdx carried the same false sentence. PR body updated: the merge, the new window reading, the re-taken census, the fix — and the two sentences that are now false ('stays draft', 'needs:contract-review remains hung') are CORRECTED, not reversed. The label was NOT re-added, the draft state was NOT flipped, nothing was enqueued or merged. ONE DISPATCH MECHANISM-ASSUMPTION MEASURED FALSE: 'a change to QUICK_ADD_HOST_TYPES will not be caught by sdui-parser's tests' is half wrong — that suite carries one deliberately hard-coded anti-vacuity row over the set, which reddens; every OTHER row is it.each(HOST_TAGS) and self-adjusts, which is the real blind spot. Verified deliberately by ablation anyway, and the blind spot is now covered by a second row.", "reproduced_before_fix": "pnpm exec vitest run packages/plugin-kanban/src/__tests__/quickAddIsDiagnosedNotDropped-8285.test.ts on the merged tree, BEFORE any edit: exit 1, 4 failed / 5 passed — byte-for-byte the four rows the diagnosis named (:106 inert-quick-add expected [], :112 unknown-prop expected [], :116 [object-kanban] vs [kanban, object-kanban], :131 kanban inputs expected [] to include objectName). The file does not exist on the pre-merge head cf31b51ed and is 9/9 green on pure main, so the failure is created by the merge.", "fix_and_why_minimal": "5 files, 156 insertions / 46 deletions. packages/sdui-parser/src/kanban-quick-add.ts (the set + the remedy sentence + the scope note the retirement falsified); its own test (the one hard-coded host-set row follows; the kanban-ui control becomes an it.each over BOTH retired spellings asserting neither is a host — a discrimination only a synthetic manifest can still make); the plugin-kanban companion (KANBAN_TAGS loses view:kanban; the registration row expects 1 key; ComponentRegistry.has('kanban-ui') flips true -> FALSE, kept as a reading rather than deleted); content/docs/plugins/plugin-kanban.mdx; the changeset gains @object-ui/sdui-parser and states the narrowing. Minimal because nothing about the ruled retirement is re-opened and no assertion is removed for convenience: every row that could still be a reading was re-pointed, not dropped.", "quick_add_host_types_decision": "DECISION: 'kanban' LEAVES the set — QUICK_ADD_HOST_TYPES is now { 'object-kanban' }. This is a reading, not a preference. MEASUREMENT (on the merged tree, live registry, manifest built the way the page compiler builds its own): checkKanbanQuickAdd has exactly ONE call site (whole-tree grep) — validate.ts's per-prop walk — and that walk sits in the else of `const comp = manifest.components[node.type]; if (!comp) {...}`, so a tag the manifest did not resolve never has its props walked at all. Probe readings: live kanban-ish known types = [plugin-kanban:object-kanban, object-kanban] (no kanban, no kanban-ui); validateTree on { type: 'kanban', quickAdd: true } = [error/unknown-component] and nothing else; FIRING CONTROL validateTree on { type: 'object-kanban', quickAdd: true } = [warning/inert-quick-add], so the zero above is a reading and not a broken probe; validateTree on { type: 'kanban-ui', quickAdd: true } = [error/unknown-component]; and checkKanbanQuickAdd('kanban','quickAdd',true) called DIRECTLY still returns 'inert-quick-add' — i.e. the member is live in the function and unreachable through its only call site. WHY THIS IS NOT A SILENT DROP: the retired spelling is refused BY NAME at the tag, as an ERROR, which is louder than this WARNING; stacking both is the two-diagnostics-for-one-mistake shape checkMemberTypes already refuses (objectui#8067). The counter-argument ('the tier parses, it does not resolve, so it may still want to diagnose the retired spelling') is answered by the same measurement: this tier DOES gate the prop walk on manifest resolution. The only surviving reach is a HAND-BUILT manifest declaring a component named kanban — after the retirement that is somebody else's component, about which this message's claims (ObjectKanban supplies no onQuickAdd) would be false. Nothing is widened; the set strictly shrinks to what resolves.", "importer_census_current_main": "Re-taken against origin/main f4abff499, NOT against the base — this is the round's lesson and it is in the PR body. main changed 118 files since base 60e1f80b2; 9 of them spell a retired node key as a quoted literal; FIRING CONTROL, same query shape on the surviving object-kanban = 8 files, non-empty, so a zero would have been a reading. Disposition of the 9: quickAddIsDiagnosedNotDropped-8285.test.ts is THE importer (live registry + greps index.tsx's registration calls) — fixed; sdui-parser/src/kanban-quick-add.ts restates the set as data — fixed; sdui-parser's own test pins that set — fixed; content/docs/plugins/plugin-kanban.mdx carried objectui#8922's prose naming kanban-ui as a live block — fixed. The other 5 are NOT importers, each checked by inspection: content/docs/api/schema-reference.md, packages/core/src/data-scope/__tests__/element-data-source.test.ts, packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts and packages/types/src/layout.ts are all the STORED VIEW TYPE / pageType vocabulary (defaultViewType, viewType, ViewKind), the layer this PR explicitly leaves alone; packages/plugin-kanban/src/ObjectKanban.tsx has 0 retired spellings left on the merged tree and is in the intersection only because both sides edited it. TWO NEAR-MISSES CLEARED WITH CONTROLS: the plugin doc's PluginLoader plugins={['kanban']} is the plugin-PACKAGE vocabulary (every plugin doc spells its own short name that way: calendar, charts, chatbot, ...), not a node type key; and the whole-tree symbol sweep for QUICK_ADD_HOST_TYPES / INERT_QUICK_ADD / checkKanbanQuickAdd returns ONLY the three files fixed here. Blast radius of the fix itself: quickAdd appears outside the two changed packages in exactly 6 files — packages/types (3), apps/console registry-inputs-spec-parity.test.ts, and the two docs — and every one is in the green re-runs.", "tests": "Every exit code captured into a file BEFORE any pipe; heavy runs through the shared verify lock (OS_VERIFY_LOCK_SLOT=dev-8802-r2), verdicts read from the lock's own VERDICT line. REPRODUCTION: exit 1, 4 failed / 5 passed (above). GREEN ON THE MERGED TREE at pushed head 9b36ffdb2, clean working tree: vitest run packages/plugin-kanban/ packages/plugin-gantt/ packages/runner/ = exit 0, 115 files / 786 tests; vitest run packages/sdui-parser/ = exit 0, 14 files / 194 tests; vitest run packages/types/ = exit 0, 164 files / 3237 tests; vitest run apps/console/src/__tests__/registry-inputs-spec-parity.test.ts = exit 0, 198 tests; turbo run type-check --concurrency=2 (whole repo) = exit 0, 81/81 tasks, 0 occurrences of 'error TS'; pnpm --filter @object-ui/sdui-parser lint = exit 0, log echoes '> eslint .', '0 errors, 7 warnings'; pnpm --filter @object-ui/plugin-kanban lint = exit 0, log echoes '> eslint .', '0 errors, 172 warnings' — read for the literal word, not the exit code: the only 'error' strings in either log are the '0 errors' summary and two warnings whose MESSAGE TEXT begins 'Error:'. CHANGESET GATES: check-changeset-presence 0 (36 published source files of 8 released packages, 1 changeset), -no-major 0, -overwrite 0, -fixed 0. OTHER GATES: doc-types, doc-fences, doc-snippets, doc-examples, doc-example-readers, control-bytes, handler-key-reads, unreferenced-sources, self-import, vi-mock-specifiers, eager-closure all exit 0; sdui-registration-pins exit 0 (16/16 registrations in the built console, 518 chunks) — this one was NOT MEASURED in the diagnosis and needed the console built first. readme-exports first exited 1 with 8 rows reading 'type entry not on disk — run pnpm build first', all naming @object-ui/cli / @object-ui/plugin-ai, packages this diff does not touch: that is a PRECONDITION, not a red — after building those two it is exit 0 with 0 unbuilt. ABLATION (direction predicted before running: TURNS RED): put 'kanban' back into QUICK_ADD_HOST_TYPES, source only, tests untouched. Mutation proven on disk first — git hash-object 57e10ca01 -> e0286b7f4, kept-spelling anchor 1->0 and mutated-spelling anchor 0->1. No build needed and the reason is measured, not assumed: vitest.config.mts:500 aliases @object-ui/sdui-parser to packages/sdui-parser/src, so the mutated SOURCE is what loads. Result: exit 1, 3 failed / 203 passed, red in BOTH packages — the companion's registration row, sdui-parser's anti-vacuity row, AND the new 'control: kanban is NOT a host' row. Restore leg: git checkout HEAD -- PATH (not a bare checkout), hash back to 57e10ca01 = the HEAD blob, git diff HEAD -- PATH EMPTY; trap ... EXIT INT TERM with absolute paths, empty-hash treated as FAILURE. No permanent probe file left: the reachability probe was deleted and git status confirmed. NOT MEASURED, stated rather than claimed green: full apps/console, packages/components, packages/app-shell, packages/react, packages/core, the other packages/plugin-*, examples/*, whole-tree turbo build, whole-farm lint, e2e. Those are CI's.", "clause_2": "Clause-2: yes. PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-clause2-carriers.mjs --pair 8865 = exit 0, agreeing on all three cards (8802, 8257, 8008); --self-test = exit 0, 299 cases. NOTE the label needs:contract-review was removed by a maintainer at 05:05:42Z and is NOT re-added here — the gate reads the Clause-2 DECLARATION in both carriers, not the label, which is why removing the label did not break the pair. TRAP WORTH KNOWING: that script defaults to objectstack-ai/objectstack; run without PM_SWEEP_REPO it exits 2 against the wrong repo. It self-discloses (it names the source repo and says 'Not a clearance'), but exit 2 there is UNJUDGED, not a failed pair.", "new_head_sha": "59489bcfc6d93417589a2bdc3cc55316f1008fd6", "mcp_calls": "0 — every GitHub read and both writes went through authenticated REST; no MCP GitHub tool was called", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: the dispatch's mechanism-assumption 'a change to QUICK_ADD_HOST_TYPES will not be caught by sdui-parser's tests' measured HALF FALSE — that suite carries one deliberately hard-coded anti-vacuity row over the set which does redden; the it.each(HOST_TAGS) rows are the real blind spot. Carrier: whoever reviews this PR, and the next narrowing of that constant.", "noted, not filed: scripts/pm/check-clause2-carriers.mjs (lives in objectstack, not objectui) defaults to objectstack-ai/objectstack, so an objectui pair check without PM_SWEEP_REPO exits 2 with wording that reads like a verdict about the PR. It self-discloses on the line above, so it is a trap with a guardrail, not a defect. Carrier: any seat running a Clause-2 pair check from an objectui task.", "noted, not filed: packages/plugin-kanban/src/KanbanEnhanced.tsx has zero non-test importers once kanban-enhanced is unregistered. Already stated in this PR's changeset and deliberately left in place — deleting published-but-unreachable source needs its own maintainer ruling. Carrier: this PR's own changeset already names it.", "noted, not filed: the objectui#8285 changeset already on main states the diagnostic covers 'object-kanban / kanban'. It is historically accurate for the change it describes and is NOT edited here (check-changeset-overwrite would flag that); this PR's changeset says explicitly that it supersedes the kanban half. Carrier: the release that ships both entries." ], "second_merge_clearance": "main moved 3 commits (to 8ea3beee4) while the first merged tree was being verified, so those were merged too and cleared by MEASUREMENT rather than by reading their titles. True intersection of this branch's own changed files with those three commits' = 0 files (comm -12 of the two three-dot diffs). Grep of all three commits' files for a retired key or the quick-add surface returned 3 hits, each cleared with its own reading: packages/i18n/src/locales/{en,zh}.ts is a message about the timeline's unsupported gantt VARIANT, not a node type; skills/objectui/guides/page-builder.md (PR #8916 — the governed file this PR deliberately split out, now landed on main on its own) spells gantt as object-gantt's nested CONFIG BLOCK key, not a node type. That clearance was then converted from a grep into a run: on the second merged tree 59489bcfc, vitest run packages/plugin-kanban/ packages/plugin-gantt/ packages/runner/ packages/sdui-parser/ = exit 0, 129 files / 980 tests; vitest run packages/types/ + apps/console registry-inputs-spec-parity.test.ts = exit 0, 165 files / 3435 tests; the four changeset gates and check:control-bytes = exit 0 each (the second merge brought two new changesets in). NARROWING DECLARED, not claimed green: turbo run type-check and the doc/console gates were run on the FIRST merged tree 9b36ffdb2 and were not re-run after the second merge — those three commits touch plugin-chatbot, data-objectstack, app-shell, i18n and two scripts/, none of which this diff touches, and each was type-checked by CI on its own way into main.", "pr_state_after_push": "head 59489bcfc, state open, draft FALSE, auto_merge null, labels unchanged by this seat (a labeler added 'package: sdui-parser' on its own). needs:contract-review NOT re-added. Nothing enqueued, nothing merged, draft state not flipped in either direction. mergeable_state reads 'blocked' immediately after the push, i.e. CI has not converged — that is the PM's to watch, not this seat's, and it is NOT a reading about the code." }Generated by Claude Code
gate(doc-types): walk packages/NAME/README.md, with its ruled DOC_TYP…
fix(plugin-kanban): delete KanbanEnhanced.tsx, the module no export o…