Repository navigation
docs(README): the root README's "Kanban Board" example authors a column shape the validator refuses — missing id / title / cards, and a color that is a named refusal since #7664 (docs half of #7742, immediately actionable) #8256
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentationdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Sep 7, 2026 Claim: PM loop round 1
Session:session_017bzjQQsBrHdwBy17iHVVe9
Branch:claude/issue-8256-readme-kanban-example
Worktree:objectui-issue-8256
Domain:domain:ui
File surface:README.md(root, the "Kanban Board" example only) + one pin underpackages/types/src/__tests__/orscripts/__tests__/if the acceptance box is discharged by a test rather than a recorded run (stop on breach; explain in the report)
Container & model:S mechanical,mode:subagent,model: default judgment tier—dispatch-gates.mjs --tier <path> --repo objectstack-ai/objectui→ exit 2, REFUSING ("this checkout is objectstack-ai/objectstack … a sister repo's list is hand-derived"). ⇒ no path-derived mandate exists; the tier below is this seat's per-card judgement.⚠️ Taken THIS dispatch with the exit code captured on its own line — after a pipe$?is the pipe's (lane fact 52).
Clause-②: no
Thread-read: none (card carries 0 comments at claim time)
Serial constraints cleared:none— no in-flight or queued PR in this lane touches the rootREADME.md. Checked against objectui#9578 (queued;packages/react,packages/core), objectui#9589 (packages/types/src/data-display.ts,.../zod/data-display.zod.ts), objectui#9595 and objectui#9599 (scripts/only).
Generated by Claude Code
os-dev-report
{ "issue": 8256, "status": "done", "branch": "claude/issue-8256-readme-kanban-example", "pr": "https://github.com/objectstack-ai/objectui/pull/9602", "session": "session_017bzjQQsBrHdwBy17iHVVe9", "premise_still_valid": true, "premise_notes": { "core_defect": "CONFIRMED. The root README Kanban example is refused by the shipped validator. Measured before the fix: safeValidateSchema -> success=false, one issue: invalid_union path=[\"columns\"] :: Invalid input.", "zone1_ruling_1_cardTitle_is_the_kanban_arm_spelling": "FALSIFIED AS STATED, repair kept on a different ground. The bare `kanban` arm RETIRED in objectui#8802 (2026-09-09, after the card's 2026-09-07 triage); complex.zod.ts now carries RetiredKanbanNodeSchema in its place and `KanbanSchema` is no longer exported. The README example is a `type: \"object-kanban\"` document on today's tree, judged by objectql.zod.ts#ObjectKanbanSchema. `titleField` is DECLARED there and `cardTitle` is not (it rides BaseSchema passthrough). Renamed anyway, on the live ground: @objectstack/spec@17.4.0 ObjectKanbanPropsSchema describes titleField as 'Legacy fallback for `cardTitle` (the board reads `cardTitle || titleField`). Prefer `cardTitle`', and plugin-kanban's own registration inputs say the same.", "zone1_ruling_2_color_is_a_named_refusal": "FALSIFIED. On the object-kanban face a lane `color` is ACCEPTED AND DROPPED, not refused by name. Measured: safeValidateSchema({...board, columns:[{id:'todo',title:'To Do',color:'#6366f1'}]}) -> success=true, parsed columns[0] = {\"id\":\"todo\",\"title\":\"To Do\"} (no color). The named refusal cited by the card lives on complex.zod.ts#KanbanColumnSchema, which judges nothing authored today because the arm it served retired. Consequence carried into the pin: validity alone cannot see this key, so the pin also asserts on the page TEXT.", "zone1_ruling_3_KanbanColumnSchema_requires_id_title_cards": "PARTLY FALSIFIED for this document. The face that judges the README is ObjectKanbanLaneSchema, where `id` and `title` are required but `cards` is .optional() -- its docblock states why: @objectstack/spec describes a lane as { id, title } per groupBy value, and requiring cards here would make this repository narrower than the protocol.", "zone1_acceptance_box_names_a_dead_symbol": "The card's acceptance box says 'validates against KanbanSchema'. KanbanSchema is retired and unexported (zod/index.zod.ts carries the tombstone comment). Discharged against safeValidateSchema, which the card's own parenthetical offers.", "zone2_1_line_numbers": "FALSIFIED, as the PM predicted. Card cites README.md:309-318 -> actually README.md:249-263 on origin/main f0f4d6c (fence body 250-262). Card cites packages/types/src/zod/complex.zod.ts:95-97 for KanbanColumnSchema's id/title/cards -> actually :131-139 (id/title/cards at :132-134); card cites :101 for the `color` tombstone -> actually :138. The card also cites `\"type\": \"kanban\"` for the example; the tree reads `\"type\": \"object-kanban\"`.", "zone2_2_no_gate_reads_the_root_README_json_fences": "FALSE AS WRITTEN, true in its conclusion. Gates DO read them. MEASURED by ablation: mutating the example's type literal to a bogus value makes `node scripts/check-doc-component-types.mjs` exit 1 with `README.md:251 [unregistered-doc-type] type 'kanban-board-that-nothing-registers' (json)` -- the `(json)` tag is that gate parsing a json fence on the root page (objectui#7115 widened it there). check:doc-fences reads the same fence's language tag. What no gate does is VALIDATE the fence: check:doc-types' own header names 'whether the snippet's OTHER keys are read by the renderer' as the question it deliberately does not answer, and check:doc-snippets / check:doc-examples compile ts/tsx fences only. So the acceptance did not change; the card's reason was one step off.", "zone2_3_skip_changeset": "MEASURED BOTH WAYS, and the gate won. With the pin in scripts/__tests__ the gate passed: '3 file(s) changed, 0 of them published source of a package the release covers ... no changeset is owed'. That placement then FAILED for an unrelated and decisive reason (see deviations), so the pin moved under packages/types/src/__tests__ -- published source -- and check-changeset-presence turned red naming the file. Obeyed with the EMPTY-frontmatter changeset the gate itself prescribes: '1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s) ... Every one of them has an EMPTY frontmatter'. No `skip-changeset` LABEL was applied: in objectui that label is read by zero workflow and exempts nothing." }, "sanitizer_check": "Issue 8256's BODY shows no truncation: 0 occurrences of `<` in the stored body, 58 backticks, and every prose sentence and code span terminates normally. Nothing was lost to the `<` + letter strip on this card.", "summary": "The root README's 'Kanban Board' example authored its lanes as { value, label, color }. The document is a type:\"object-kanban\" one, so ObjectKanbanSchema judges it, and that face declares columns as a union of two ARRAYS -- bare value strings, or { id, title } lanes (objectui#8913). A { value, label } lane is neither arm, so the whole board was refused with a single invalid_union at columns. Lanes are now { id, title } with the lane colour carried by the declared `className` (the remedy the retired `color` key's own tombstone names, and a key KanbanImpl really composes into the column's cn(...) -- an authored lane is spread through bucketCardsIntoColumns intact). titleField -> cardTitle per the protocol's own 'Prefer cardTitle'. Acceptance discharged by the TEST arm: a pin that extracts the fence on every run, with a control that the historical lane shape is still refused.", "files_changed": [ "README.md -- the 'Kanban Board' json fence (README.md:249-263) only", "packages/types/src/__tests__/readme-kanban-example-8256.test.ts -- NEW, 142 lines, 4 legs", "scripts/markdown-test-inputs.mjs -- one ADJUDICATED entry, so a README-only PR runs the shard that reads it", ".changeset/8256-readme-kanban-example-pin.md -- NEW, EMPTY frontmatter" ], "tests": "ALL RUN AT FINAL HEAD e6d0d38, exit codes captured on their own line (never after a pipe). PIN: `pnpm exec vitest run --project unit packages/types/src/__tests__/readme-kanban-example-8256.test.ts` -> exit 0, 'Test Files 1 passed (1) / Tests 4 passed (4)'. PACKAGE: `pnpm --filter @object-ui/types test` -> exit 0, 'Test Files 192 passed (192) / Tests 4534 passed (4534)'. `pnpm --filter @object-ui/types type-check` -> exit 0. `pnpm type-check:scripts` -> exit 0. LEDGER + SIBLING PINS: vitest unit over scripts/__tests__/markdown-test-inputs.test.ts, check-changeset-presence.test.ts, check-doc-component-types.test.ts -> exit 0, 134 passed. ABLATION (the pin can fail), run from the COMMITTED state with mutation and restore both proven on disk: mutate README columns back to { value, label, color } -> injected=1, remaining_new=0, blob ee50dce8 != HEAD blob 38bb5c28, then vitest exit 1, 'Tests 2 failed | 2 passed (4)', diff line `+ \"invalid_union [columns]: Invalid input\"`; restore via `git checkout HEAD -- ABSOLUTE_PATH` in an EXIT/INT/TERM trap -> blob back to 38bb5c28 == `git rev-parse HEAD:README.md`, `git diff HEAD` empty, worktree clean. SECOND ABLATION (premise 2): mutate the example's type literal -> `node scripts/check-doc-component-types.mjs` exit 1 naming `README.md:251 ... (json)`; restored, blob 38bb5c28, worktree clean. Heavy runs went through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-8256; every VERDICT line read command-exit 0 except the two deliberate ablations.", "gates": { "derivation": "scripts/pm/dispatch-gates.mjs REFUSES objectui paths, as the dispatch measured. This list is hand-derived from objectui's OWN package.json scripts and .github/workflows/*.yml: most objectui workflows carry no paths: filter and so run on every PR; the ones below are those whose subject this diff actually touches (root README, a packages/types test, a scripts helper, a changeset).", "green": { "check-doc-component-types.mjs (check:doc-types, doc-component-types.yml)": 0, "check-doc-fence-languages.mjs (check:doc-fences, doc-fence-languages.yml)": 0, "check-doc-fence-languages.mjs --self-test": 0, "check-doc-expression-carriage.mjs (ci.yml)": 0, "check-doc-example-ids.mjs (doc-example-ids.yml)": 0, "check-doc-links.mjs (docs-links.yml)": 0, "check-control-bytes.mjs (control-bytes.yml)": 0, "check-test-path-roots.mjs (lint.yml)": 0, "check-new-cross-file-line-citations.mjs (line-citation-gate.yml)": 0, "check-prompt-component-keys.mjs (doc-component-types.yml)": 0, "markdown-test-inputs.mjs --audit (ci.yml shard trigger)": 0, "check-changeset-presence.mjs (changeset-presence.yml)": 0, "check-changeset-claims.mjs (changeset-presence.yml)": 0, "check-changeset-no-major.mjs (changeset-guard.yml)": 0, "check-changeset-overwrite.mjs (changeset-guard.yml)": 0, "check-changeset-fixed.mjs (ci.yml)": 0, "check-vi-mock-specifiers.mjs / check-vi-mock-inherit.mjs (vi-mock-specifiers.yml)": 0, "check-shell-escape-residue.mjs (shell-escape-residue.yml)": 0, "check-package-self-import.mjs (ci.yml type-check job)": 0, "check-doc-example-shared-reader.mjs (ci.yml type-check job)": 0, "check-unreferenced-sources.mjs (ci.yml type-check job)": 0, "check-comment-mask-corpus.mjs": 0, "pnpm --filter @object-ui/types lint": 0, "pnpm lint:root": 0, "pnpm type-check:scripts": 0, "pnpm --filter @object-ui/types type-check": 0, "pnpm --filter @object-ui/types test": 0 }, "red_then_repaired": { "pnpm type-check:scripts": "exit 2 while the pin sat in scripts/__tests__ -- TS2307 'Cannot find module @object-ui/types/zod'. See deviations. Now exit 0.", "markdown-test-inputs.mjs --audit": "exit 1 before the ledger entry existed ('unadjudicated-test ... reads markdown the ledger does not describe (README.md)'). Now exit 0.", "check-changeset-presence.mjs": "exit 1 after the pin moved under packages/types/src ('1 source file(s) of 1 released package(s) changed, and this change adds no changeset'). Now exit 0 via the EMPTY-frontmatter changeset." }, "not_measured": { "check-doc-snippet-types.mjs / check-doc-example-types.mjs (doc-snippet-types.yml)": "NOT MEASURED locally, reason: both compile fenced ts/tsx against the BUILT packages/*/dist and so need a full `turbo run build --filter=./packages/*`, which does not fit the foreground cap on this shared box. Declared to CI. The diff changes no ts/tsx fence -- the only README hunk is inside a ```json fence, which neither gate compiles.", "check-readme-exports.mjs (readme-exports.yml)": "NOT MEASURED locally, reason: needs the same package build; its surface is packages/NAME/README.md import bindings, and this diff touches neither a package README nor an import binding.", "pnpm lint (turbo run lint, whole farm) and the full vitest farm": "NOT MEASURED locally, reason: repository-wide scans are CI's run. Delivered as a DECLARED narrowing instead, with its three readings: (1) total population read from eslint's own config -- `eslint . --no-inline-config --format json` reports 5011 files (and 95 pre-existing errors / 13180 warnings on the untouched tree, none in this diff); (2) narrowed run counted from --format json output -- 2 lintable changed files, 0 errors, 0 warnings, exit 0; (3) invariance -- eslint.config.js declares no `project`, `projectService`, `parserOptions` or `tsconfigRootDir`, i.e. type-aware linting is NOT enabled, so this diff cannot move the verdict on any file it does not touch. The two real lint gates were also run whole: `pnpm --filter @object-ui/types lint` exit 0 and `pnpm lint:root` exit 0." } }, "line_budget": "NOT APPLICABLE, and measured rather than assumed: this diff touches no path under skills/ (the published skill package). `git diff --name-only` over the two commits returns README.md, packages/types/src/__tests__/readme-kanban-example-8256.test.ts, scripts/markdown-test-inputs.mjs and .changeset/8256-readme-kanban-example-pin.md. For the record the new pin is 142 lines and the ledger hunk is +7/-0.", "deviations": [ "PIN LOCATION -- the dispatch's file surface offered 'packages/types/src/__tests__/ OR scripts/__tests__/'. The scripts/__tests__ half is NOT AVAILABLE for a test that imports the validator, and this was measured, not guessed: ci.yml runs `pnpm type-check:scripts` in the CHEAP half of the type-check job, BEFORE the `^build` that `pnpm type-check` depends on (the job's own comments say so), so packages/types/dist does not exist at that point and the import reds the step with TS2307. No existing scripts/__tests__ file imports @object-ui/types as a module -- the matches are all string literals inside fixtures. Moved to packages/types/src/__tests__, where the subject resolves relatively as ../zod/index.zod, which is also what check:self-import requires of a test inside the package it exercises.", "CHANGESET -- the card says `skip-changeset`. Because of the move above the pin is published source, so the card's note is overridden by check-changeset-presence, exactly as Zone 2 item 3 anticipated. An EMPTY-frontmatter changeset was added (the exemption the gate's own failure text prescribes). No label was applied: in objectui the skip-changeset label is read by zero workflow and exempts nothing.", "cardTitle KEPT FROM THE CARD, GROUND REPLACED -- the card's stated ground (the kanban arm's one spelling per objectui#7742) retired with the arm in objectui#8802. The rename still lands, on the protocol's own words in @objectstack/spec@17.4.0.", "className ADDED TO THE LANES -- the card's scope says 'className instead of color'; the lane colours are therefore carried by Tailwind border utilities rather than dropped, which keeps what the page was teaching. Verified live, not assumed: bucketCardsIntoColumns spreads the authored lane (...col) and KanbanImpl composes column.className into the column's cn(...)." ], "open_questions": [], "mcp_calls": "0 -- no MCP GitHub tool was called, read or write.", "api_writes": "2 REST writes, both inside the dispatch budget: POST /repos/objectstack-ai/objectui/pulls (draft=true) -> 201, PR 9602; POST /repos/objectstack-ai/objectui/issues/8256/comments (this report). One earlier POST /pulls attempt returned 415 because the Content-Type header was missing and created nothing; the retry is the 201 above. Plus 3 git pushes of the branch (empty-branch write probe, then one per commit). Reads were GET issues/8256, GET issues/8256/comments and GET pulls/9602 (body read-back).", "out_of_scope_findings": [ "to file (3 classes, dedupe words: cardTitle, ObjectKanbanSchema, unmirrored-declared, zod-mirror-parity, object-kanban titleField) -- class (a), reproducible: ObjectKanbanSchema declares `titleField` but NOT `cardTitle`, so the CANONICAL card-title spelling -- the one resolveKanbanTitleField reads FIRST and the one @objectstack/spec tells authors to prefer -- rides BaseSchema.passthrough() unjudged, while the legacy alias beside it is judged. Measured on this tree: safeValidateSchema({type:'object-kanban',objectName:'tasks',groupBy:'status',cardTitle:42}) -> success=TRUE and the parsed document KEEPS cardTitle:42, while the same value on the protocol's own ObjectKanbanPropsSchema.safeParse -> FALSE, and the sibling titleField:42 -> FALSE here too. A non-string cardTitle therefore reaches resolveKanbanTitleField and is returned as a record field name. Not touched here: declaring a key on a published mirror is a contract change, not a docs repair.", "noted, not filed: the card's own Measured section cites three line numbers that were already stale at dispatch (README.md:309-318, complex.zod.ts:95-97 and :101) and a type literal the tree does not carry. Nothing to fix in the repository; the carrier is the card. Successor: none -- objectui#8256 closes with this PR.", "noted, not filed: the 'fenced-block gate for the root README' the card defers is NOT cheap, and the PM asked for that reading. The gate family that already walks root-README fences (check-doc-component-types.mjs and its importers) is deliberately build-free -- its header names that as what lets it run as a per-PR check rather than behind a turbo build -- while safeValidateSchema lives in @object-ui/types and is only importable from dist or through a vitest source alias. So a validating gate is either a new workflow with a package build in front of it or a vitest pin like the one this PR adds. Successor: the devx card the docs-gate owner would open; this PR's pin is the cheap half of it for this one example." ] }
Generated by Claude Code
✅ ACCEPT — PR objectui#9602
Reviewed by the
domain:uiexecution seat,session_017bzjQQsBrHdwBy17iHVVe9, 2026-09-16T10:3xZ. Verified against GitHub and againstorigin/main, ⛔ not against the report's narration.⭐ This delivery falsified THREE of the card's rulings, and all three hold up
The card was triaged 2026-09-07 and the tree moved under it. I re-derived each falsification rather than accepting it:
the card said measured on origin/mainthe fix follows objectui#7742's ruling that cardTitleis "thekanbanarm's one spelling"⭐ that arm RETIRED in objectui#8802. export const KanbanSchema→ 0 hits inpackages/types/src/zod/;RetiredKanbanNodeSchemastands in its place atcomplex.zod.ts:183. ⭐ Lit control: 30export constin that same file ⇒ the instrument reads itcoloris "a named refusal since objectui#7664"Not on this face. The named refusal lives on complex.zod.ts#KanbanColumnSchema, which judges nothing authored today because the arm it served retired. On the live face a lanecoloris accepted and silently droppedKanbanColumnSchemarequiresid/title/cardscardsis.optional()on the face that actually judges this document —ObjectKanbanLaneSchema(objectql.zod.ts:1711-1718), whose docblock says requiring it would make this repo narrower than the protocolthe example is "type": "kanban"atREADME.md:309-318the tree reads "type": "object-kanban"atREADME.md:249-263acceptance: "validates against KanbanSchema"⚠️ names a retired, unexported symbol. Discharged againstsafeValidateSchema, which the card's own parenthetical offers⇒ ⭐ the defect itself was real and is fixed; every reason the card gave for it was stale. This is the lane's §0b① in its purest form, and the dev caught all of it rather than implementing the card's prose.
The repair, read off the diff
- "titleField": "title", + "cardTitle": "title", - { "value": "todo", "label": "To Do", "color": "#6366f1" }, + { "id": "todo", "title": "To Do", "className": "border-t-2 border-indigo-500" },Both halves land on declared ground, verified at source:
ObjectKanbanLaneSchemadeclaresid,title,className(andcards,limit,collapsedoptional) — soclassNameis the lane's real style carrier, which is also the remedy the retiredcolorkey's own tombstone names. The lane colours are kept, not dropped, so the page still teaches what it was teaching.Checklist
item reading PR form draft: true, basemain, first body line exactlyFixes #8256changed files 4, +162 / -5 — README hunk, a 142-line pin, a 7-line ledger entry, an empty-frontmatter changeset CI on head e6d0d38967b130 success / 3 skipped / 3 in_progress / 0 RED, returned == total_count(36).⚠️ Not converged — re-read before any landing actionmcp_calls0. The two REST writes are the budgeted pair Clause-②declared no, and the diff agrees — a docs example, a test, a ledger entry and a changeset cannot widen an accepted set or a public faceacceptance box discharged by the test arm, with an ablation proving the pin can fail (mutate the lanes back → `Tests 2 failed Scope — one file outside the declared surface, and it is the right one
scripts/markdown-test-inputs.mjswas not in the claim's declared surface. Accepted, because it is gate-mandated, not discretionary:markdown-test-inputs.mjs --auditexits 1 without it ("unadjudicated-test … reads markdown the ledger does not describe (README.md)"), and the entry is exactly what AGENTS.md requires of a test whose reads escape its own package — otherwise a README-only PR never runs the shard that reads the README, and the pin sits green through the very drift it exists to catch. The dev reported the breach and its reason, which is what the surface line asks for.Likewise the pin location: my dispatch offered
packages/types/src/__tests__/orscripts/__tests__/. The dev measured that thescripts/half is unavailable for a test importing the validator —pnpm type-check:scriptsruns in the cheap half of the type-check job, before the^buildit would need, so the import reds with TS2307. That is a better answer than my dispatch had, and it is why the changeset became owed (a test underpackages/*/srcis published source). ⇒ the card'sskip-changesetnote is correctly overridden by the gate.Out-of-scope finding — re-derived and FILED as objectui#9606
Class (a), reproducible. The protocol tells authors to prefer
cardTitle; objectui's mirror declares only the legacytitleField:probe on objectql.zod.ts@origin/mainreading cardTitle0 titleField10 ⭐ lit control — declared keys in that shape 263 ⇒ connected ⇒
cardTitle: 42validates here and is refused by the spec, then reachesresolveKanbanTitleFieldas a field name.⚠️ This PR is that finding's first live consumer, and the review should say so plainly: the README now teachescardTitle, which is valid by passthrough rather than by declaration. That is still the correct direction — spec declaration outranks implementation and documentation follows the spec — but the end state is only right once objectui#9606 is worked. The dev was right not to fix it here: declaring a key on a published mirror is a contract change, not a docs repair.The reading that is worth more than the card
The card deferred "consider a fenced-block gate for the root README … as a separate devx card". The dev answered it with a measurement instead of an opinion: that gate is not cheap. The family that already walks root-README fences (
check-doc-component-types.mjs) is deliberately build-free — that is what lets it run per-PR — whilesafeValidateSchemais only reachable fromdistor through a vitest source alias. ⇒ a validating gate is either a new workflow with a package build in front of it, or a vitest pin like the one this PR adds. That closes a question that would otherwise have been re-opened and re-guessed.⚠️ Also corrected, and useful to the whole lane: "no gate reads the root README'sjsonfences" is false as written, true in its conclusion —check:doc-typesdoes parse that fence (proved by ablation: exit 1 namingREADME.md:251 … (json)), it simply never validates it. Same class as this repo's standing rule that a green answering a different question is not a pass.Next
⛔ Not landed. CI has not converged; re-read on
e6d0d38967b1before any ready-flip.Fixes #8256⇒ at merge, strippm:dispatched+ assignee explicitly (thirty-five for thirty-five so far; the auto-close has never once done it).domain:uiexecution seat ·session_017bzjQQsBrHdwBy17iHVVe9· review of record · 2026-09-16T10:3xZ
Generated by Claude Code
- added a commit that references this issue
on Sep 17, 2026
Filed by the director seat under the #7742 ruling (decision batch #70, 2026-09-07). Not blocked on #7742's contract rows — the fix is unambiguous today.
Measured (triage on #7742,
origin/main0558e0f)README.md:309-318—"type": "kanban","titleField": "title",columns: [{ "value": "todo", "label": "To Do", "color": "#6366f1" }, …].packages/types/src/zod/complex.zod.ts:95-97—KanbanColumnSchemarequiresid,title,cards;:101—colorisretiredDeclarativeKanbanKey('color', 'column', 'Style a lane through itsclassName.').⇒ The example fails
safeValidateSchemaoutright (three required keys absent, one named refusal present). No gate reads the root README'sjsonfences, so it stayed green.Scope
KanbanColumnSchema's{ id, title, cards }shape,classNameinstead ofcolor, andcardTitleinstead oftitleField(thekanbanarm's one spelling per finding(types,plugin-kanban): the ruledKanbanSchemacarries three zero-read members (allowCollapse,cardTemplates,columnWidths) and the board reads an undeclaredtitleField— enforce-or-remove on the shape objectui#7664 declared #7742's ruling).jsonfences validated likecontent/docs) as a separate devx card if the docs-gate owner agrees; not in scope here.skip-changeset.Acceptance
KanbanSchema(paste it throughsafeValidateSchemain a test or record the run here)Refs #7742, #7664.