Skip to content

feat(types,plugin-kanban,plugin-gantt)!: retire the bare kanban / gantt node keys and the kanban-ui / kanban-enhanced registrations - #8865

Merged
os-warren merged 10 commits into
mainfrom
claude/kanban-gantt-family-retirement
Sep 10, 2026
Merged

feat(types,plugin-kanban,plugin-gantt)!: retire the bare kanban / gantt node keys and the kanban-ui / kanban-enhanced registrations#8865
os-warren merged 10 commits into
mainfrom
claude/kanban-gantt-family-retirement

Conversation

@os-warren

@os-warren os-warren commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Fixes #8802
Fixes #8257
Fixes #8008

⚠️ Those three lines are spelled as bare Fixes #N on purpose. The cross-repo
form (Fixes owner/repo#N) is honoured by GitHub but NOT by this seat's landing
pre-check, whose closingKeywordRe accepts only the bare form — on the previous
head check-clause2-carriers.mjs --pair 8865 therefore exited 2 =
UNJUDGED
, no pair formed, and the gate could not answer at all. ⛔ Do not
"normalise" them back.

Executes three maintainer rulings taken in one batch on 2026-09-09. ⛔ Nothing about the direction is re-opened here.

Clause-②: yes — this narrows published accept sets. An in-seat contract-review-tier PASS is the PM's to run, and landing is the PM's and the maintainer's call.

CORRECTION, 2026-09-10 — this paragraph previously said the PR "stays draft" and that "needs:contract-review remains hung". Both are now FALSE and are corrected rather than reversed. At 05:05:42Z a maintainer flipped this PR out of draft and removed the needs:contract-review label. Neither act was this seat's; ⛔ neither is reversed here — the label is not re-added and the draft state is not flipped back. What the removal did not change is the declaration itself: PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-clause2-carriers.mjs --pair 8865 exits 0 on the current head, agreeing on all three cards (#8802, #8257, #8008), with --self-test0 (299 cases) as its control. That gate reads the Clause-② declaration in both carriers, ⛔ not the label, which is why removing the label did not break the pair.

🔁 Merge-repair round, 2026-09-10 — current main merged in, and the semantic collision that exposed

A maintainer's merge attempt was rejected. ⛔ Not a textual conflictgit merge-tree --write-tree exits 0 and a real git merge --no-ff origin/main auto-merges with 0 unmerged paths. The failure is semantic and appears only on the MERGED tree, which is what the queue builds and what a mergeable_state computed against a stale base cannot see.

main is now merged in, as merge commits — ⛔ no rebase, ⛔ no force-push, ⛔ no amend, because other agents and the reviewer's records reference this branch's existing commits. Two of them, because main moved again during verification, which is the round's own lesson arriving twice: 33874de20 (merging origin/main at f4abff499, the 30-commit window) and 59489bcfc (merging 8ea3beee4, three more).

The window reading, corrected. This body said 13; the diagnosis comment said 29; measured immediately before taking the merge it was 30main moved between the last two readings, which is the whole point of this round. After both merges: git rev-list --left-right --count origin/main...HEAD = 0 10 — zero behind.

⚠️ The three commits of the second merge were cleared by MEASUREMENT, ⛔ not 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, every one 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: the whole affected surface is re-run green on the second merged tree, below.

The failure, reproduced before anything was changed

packages/plugin-kanban/src/__tests__/quickAddIsDiagnosedNotDropped-8285.test.ts — exit 1, 4 failed / 5 passed on the merged tree:

'view:kanban' — an authored `quickAdd: true` draws the inert-pair warning
  expected [] to deeply equal [ 'inert-quick-add' ]                          (:106)
'view:kanban' — control: a genuinely unknown prop is still reported
  expected [] to deeply equal [ 'unknown-prop' ]                             (:112)
the diagnostic covers exactly the tags `index.tsx` registers this renderer under
  expected [ 'object-kanban' ] to deeply equal [ 'kanban', 'object-kanban' ] (:116)
'view:kanban' — this DIAGNOSES; it does not declare `quickAdd`
  kanban inputs: expected [] to include 'objectName'                         (:131)

⭐ That file does not exist on this branch's pre-merge head and is 9/9 green on pure main. It landed with objectui#8922 (card objectui#8285) after this PR's base — and objectui#8922's own commit message anticipated the day: "the contract-side refusal retires object-kanban.quickAdd from the spec's ComponentPropsMap, and this module goes with it. The live pin's spec row reddens on the day that lands."

The fix, and why it is minimal

The retirement is ruled and ⛔ is not re-opened: kanban, kanban-ui, kanban-enhanced and gantt stay retired. ⛔ The test is not deleted, not skipped, not disabled. Its subjectthe html tier NAMES the Quick Add pair instead of dropping the key — is still true and stays pinned; only the set of tags it holds over was wrong, and it shrinks to the one key that resolves.

file what changed
packages/sdui-parser/src/kanban-quick-add.ts QUICK_ADD_HOST_TYPES{ 'object-kanban' }; the remedy sentence names KanbanRenderer instead of the retired kanban-ui tag
packages/sdui-parser/src/__tests__/kanban-quick-add-8285.test.ts the hard-coded host-set row follows; the kanban-ui control becomes an it.each over both retired spellings, asserting neither is a host
packages/plugin-kanban/src/__tests__/quickAddIsDiagnosedNotDropped-8285.test.ts KANBAN_TAGS loses view:kanban; the registration row expects 1 key, and ComponentRegistry.has('kanban-ui') flips truefalse — kept as a reading, ⛔ not deleted
content/docs/plugins/plugin-kanban.mdx the section that told authors the pair lives on the kanban-ui block
.changeset/8802-…-retirement.md adds @object-ui/sdui-parser, and states the narrowing and the message move

⭐ The QUICK_ADD_HOST_TYPES decision, and the measurement behind it

Decision: kanban leaves the set. Not a judgement call in the end — a reading.

checkKanbanQuickAdd has exactly one call site (whole-tree grep): validate.ts's per-prop walk. That walk lives in the else of const comp = manifest.components[node.type]; if (!comp) { … } — so for a tag the manifest did not resolve, the props are never walked at all. Measured on the merged tree against a manifest built from the live registry, the way the page compiler builds its own:

probe reading
live registry, 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
validateTree on { type: 'object-kanban', quickAdd: true } ["warning/inert-quick-add"]FIRING CONTROL, so the row above is a reading and not a broken probe
validateTree on { type: 'kanban-ui', quickAdd: true } ["error/unknown-component"]
checkKanbanQuickAdd('kanban', 'quickAdd', true) called directly 'inert-quick-add' — the member is live in the function and unreachable through its only call site

⇒ Keeping kanban would be dead data. And ⛔ nothing is silently dropped by removing it: the retired spelling is refused by name at the tag, as an error, which is a louder answer than this warning; stacking both would be the two-diagnostics-for-one-mistake shape checkMemberTypes already refuses (objectui#8067). The counter-argument — "the sdui 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, and the only way a kanban entry can still be reached is a hand-built manifest declaring a component of that name, which after the retirement 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.

Verifying the constant deliberately — the ablation

⚠️ The stated hazard was that packages/sdui-parser's suite builds a synthetic manifest and would therefore not catch a change to QUICK_ADD_HOST_TYPES. That premise is half false, and the half that is true is now covered. The suite carries one row that hard-codes the set (the host set is …, and is not empty), deliberately, as anti-vacuity — every other row is it.each(HOST_TAGS) and re-derives itself from the constant, which is exactly the blind spot named.

Predicted direction before running: turns red. Ablation — put 'kanban' back into the constant, source only, tests untouched:

leg evidence
mutation reached disk git hash-object 57e10ca01e0286b7f4; kept-spelling anchor 1→0, mutated-spelling anchor 0→1
no build needed, and why vitest.config.mts:500 aliases @object-ui/sdui-parserpackages/sdui-parser/src, so the mutated source is what loads
result exit 13 failed / 203 passed, in both packages: the companion's registration row, the sdui-parser anti-vacuity row, and the new control: kanban is NOT a host row
restore git checkout HEAD -- PATH, hash back to 57e10ca01 = the HEAD blob, git diff HEAD -- PATH empty. trap … EXIT INT TERM with absolute paths

⇒ the constant is now read by two rows inside packages/sdui-parser itself, not one, so the next narrowing cannot pass silently there either.

⭐ The importer census, re-taken against CURRENT main

This is the round's real lesson, and it is the same class as F1"a retirement's blast radius is its IMPORTERS, not its own package" — with one difference that F1 could not have taught: the importer did not exist when that census was taken. ⇒ a retirement that sits open across other merges must have its census re-taken against current main, never against its own base.

Re-taken on origin/main f4abff499. Query: every file main has changed or added since this PR's base 60e1f80b2 that spells a retired node key as a quoted literal.

reading value
files main changed since the base 118
…of those, spelling a retired key (kanban / kanban-ui / kanban-enhanced / gantt) 9
FIRING CONTROL, same query shape on the surviving object-kanban 8 — non-empty, so a zero above would have been a reading

The 9, each disposed of by inspection rather than by count:

file verdict
packages/plugin-kanban/src/__tests__/quickAddIsDiagnosedNotDropped-8285.test.ts THE IMPORTER — reads the live registry and greps index.tsx's registration calls. Fixed here
packages/sdui-parser/src/kanban-quick-add.ts restates the tag set as data. Fixed here
packages/sdui-parser/src/__tests__/kanban-quick-add-8285.test.ts pins that set. Fixed here
content/docs/plugins/plugin-kanban.mdx carried objectui#8922's new prose naming kanban-ui as a live block. Fixed here
content/docs/api/schema-reference.md ⛔ not an importer — every hit is the stored view type (defaultViewType: "kanban"), the layer this PR explicitly leaves alone, and this file already says so in its own note
packages/core/src/data-scope/__tests__/element-data-source.test.ts ⛔ not an importer — type: 'kanban' is a saved view, whose output field is viewType: 'kanban'; packages/core registers no plugin
packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts ⛔ not an importer — the ViewKind enum ('grid' | 'kanban' | … | 'gantt'), same stored-view layer
packages/types/src/layout.ts ⛔ not an importer — pageType: 'kanban', same layer
packages/plugin-kanban/src/ObjectKanban.tsx ⛔ no retired spelling left on the merged tree (0 hits); it is in the intersection only because both sides edited it, and it auto-merged

Two near-misses cleared with their own controls, because a name-match is not a reading: the plugin doc's PluginLoader plugins={['kanban']} is the plugin package vocabulary (calendar, charts, chatbot, … — every plugin doc spells its own short name that way), ⛔ 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, bounded the same way: quickAdd appears outside the two changed packages in exactly 6 files — packages/types (3), apps/console/src/__tests__/registry-inputs-spec-parity.test.ts, and the two docs — and every one of them is in the green re-runs below.

Green on the MERGED tree — every exit code captured into a file BEFORE any pipe

command reading
vitest run packages/plugin-kanban/ packages/plugin-gantt/ packages/runner/ exit 0115 files / 786 tests
vitest run packages/sdui-parser/ exit 014 files / 194 tests
vitest run packages/types/ exit 0164 files / 3237 tests
vitest run apps/console/src/__tests__/registry-inputs-spec-parity.test.ts exit 0198 tests
turbo run type-check --concurrency=2 (whole repo) exit 081/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 merely the exit code: the only error strings in either log are the 0 errors summary and two warnings whose message text begins "Error:"
check-changeset-presence / -no-major / -overwrite / -fixed exit 0, 0, 0, 0 — 36 published source files of 8 released packages, 1 changeset, no major, no pre-existing changeset modified
check:doc-types · check:doc-fences · check:doc-snippets · check:doc-examples · check:doc-example-readers exit 0 each
check:sdui-registration-pins exit 016/16 registrations present in the built console, 518 chunks weighed. ⭐ This was NOT MEASURED in the diagnosis; it needed pnpm --filter @object-ui/console build first
check:readme-exports exit 0⚠️ first run 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 0 with 0 unbuilt
check:control-bytes · check:handler-key-reads · check:unreferenced-sources · check:self-import · check:vi-mock-specifiers · check:eager-closure exit 0 each

Re-run on the second merged tree (59489bcfc), after main moved three more commits:

command reading
vitest run packages/plugin-kanban/ packages/plugin-gantt/ packages/runner/ packages/sdui-parser/ exit 0129 files / 980 tests
vitest run packages/types/ apps/console/src/__tests__/registry-inputs-spec-parity.test.ts exit 0165 files / 3435 tests
check-changeset-presence / -no-major / -overwrite / -fixed · check:control-bytes exit 0 each — the second merge brought two new changesets in

⚠️ turbo run type-check and the doc/console gates were run on the first merged tree (9b36ffdb2) and ⛔ 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. Stated as a NARROWING, ⛔ not claimed green.

⚠️ NOT MEASURED locally, stated rather than claimed green: the full apps/console, packages/components, packages/app-shell, packages/react, packages/core and the other packages/plugin-* suites; examples/*; turbo run build whole-tree; turbo run lint whole-farm; e2e. Those are CI's, and the narrowing above is what bounds the risk: the changed public face is one exported Set and one message string, and the whole-tree sweep for readers of either returns only files that ARE in the runs above.

🔁 Rework round — what the REWORK verdict asked for, and what changed

CORRECTION, 2026-09-10. This section previously said the verdict "returned
six findings" and tabulated F1–F6. That was wrong, and it was wrong because of
a PM error, not a reviewer one.
The
contract-review-tier verdict (REWORK, PR comment 5610751162) returned nine
numbered items: eight findings, F1–F8, plus F9 "verified, no action"
— of
which F1 and F2 were the blockers. The PM truncated the verdict at F6 when
dispatching the repair round, so F7 and F8 were never seen by that round and
this body inherited the truncated count. F7 was closed by the same round
incidentally (it asked for exactly the refresh that round performed); F8 needed
a round of its own
, which is the commit cf31b51ed this body now stands on.

The verdict reopened nothing about the retirement itself. ⛔ The reasoning,
the measurements and the ablation record below are unchanged; what follows
was added, and the sections that carried stale numbers were corrected in place.

# what it was what changed
F1 blocking — CI red, and this PR caused it. packages/runner was outside the previous round's sweep and outside both --filter type-checks, so packages/runner/src/plugin-integration.test.ts still read kanbanComponents.kanban after this PR removed that key re-keyed to kanbanComponents['object-kanban'] with a two-directional control; reproduce→green pair below
F2 blocking — the closing keywords defeated the landing pre-check three lines re-spelled Fixes #N (see the note at the top)
F3 the package README still taught type: 'kanban' in two places the gates structurally cannot see both migrated; the same class found and fixed in plugin-gantt/README.md — see below
F4 the changeset and the barrel both stated a FALSE published fact about KanbanEnhanced corrected in both places; ⛔ the file itself is deliberately not deleted
F5 the FROM→TO table omitted the namespaced spellings new section below, and a matching block in the changeset
F6 the changeset omitted a touched published package @object-ui/components added, and @object-ui/runner with it
F7 not seen by the truncated dispatch. The body's readings were all pinned to c695a1c8d while head was 4dcf5445; the "Governed surface" section still claimed GOVERNED/1 path, false after the split; and "3 JSON fixtures" was stated where only 2 changed closed incidentally by the same round, which re-took every reading on 616973214, rewrote the governed-surface section to its post-split reading, and added the third-fixture paragraph naming the view-type vocabulary collision
F8 not seen by the truncated dispatch, and needed its own round. content/docs/api/schema-reference.md's ObjectKanbanSchema table omitted data / bind although the migration path this PR teaches depends on "data": []; and content/docs/plugins/plugin-kanban.mdx still authored onCardMove inside typed ObjectKanbanSchema document literals, contradicting the README this PR had just corrected closed on cf31b51ed — see the F8 section below
F9 "verified, no action" — the stored layer, the four gantt files the PM owed the seat, the remaining in-tree type:'kanban' read sites, and the retiredNodeType() mechanism deviation, all checked and all correct nothing to change

⇒ The lesson this round is paying for, stated so it is not re-learned: a
retirement's blast radius is its IMPORTERS, not its own package.
The importer
census that now backs the sweep is in the Verification section.

⇒ And the second lesson, which is the PM's: a verdict is read to its last
finding before any of it is dispatched.
Truncating at F6 cost this PR an extra
round and left this body contradicting the verdict it was reporting.

F3, widened by the same reasoning — plugin-gantt/README.md

The verdict named plugin-kanban/README.md. Reading the other retired
family's README on the same instrument found the identical defect in a file
this PR already edits, so it is fixed here rather than filed:

  • Four untyped const … = { type: 'gantt', … } literals (README lines 233, 260,
    445, 582 on the pre-fix head) still authored the retired key. ⚠️ They sit
    inside ```typescript fences and the gates are green over them — an
    untyped object literal compiles either way, which is the same blindness
    that let the kanban README's "Automatic Registration" block through. ⛔ A gate
    cannot find the rest of this class; only reading the file can.
  • One sentence asserted that type: 'gantt' "is a registered type, so the
    node mounts and simply finds nothing to draw". This PR makes that false.
    Re-pointed at object-gantt, which is what the sentence is now true about.

content/docs/plugins/plugin-kanban.mdx imported ObjectKanbanSchema from
@object-ui/plugin-kanban, which has never exported it (@object-ui/types
does, @object-ui/types/zod carries the mirror). Split into two imports.
⚠️ The explanatory note went in the prose above the fence rather than inside
it: a plaintext fence that opens with a comment is deliberately not counted by
check:doc-fences, so writing the note inside would have driven that file's
KNOWN_UNHIGHLIGHTED_TS_FENCES row to 0 and demanded a ledger edit — by hiding
the block from the detector rather than by highlighting it. Measured, not
reasoned: the first attempt did exactly that and the gate went exit 1 with
baselined at 1, now carries 0; moving the note out returned it to exit 0
with the ledger reading 80 files / 89 blocks, unmoved. That red is also this
gate's firing control.

F4 — what was false, and what is true

Both the changeset and plugin-kanban/src/index.tsx said KanbanEnhanced "is
still exported and still importable from @object-ui/plugin-kanban/KanbanEnhanced".

That subpath has never resolved. This package's exports map has exactly
two entries — . and ./style.css — and the barrel never re-exported the
component. After this PR KanbanEnhanced.tsx has zero non-test importers
(cardPredicateScope.test.tsx reaches it by relative path; one vi.mock names
it). KanbanRenderer, by contrast, is exported from the package entry, and
only its registry key is gone — the two were wrongly stated as one case.

KanbanEnhanced.tsx is deliberately left in place. Deleting published-
but-unreachable source is a FURTHER narrowing and needs its own maintainer
ruling, which this PR does not carry. The reading is reported; it is not acted
on here. The verdict asked for a card for the dead module: it is
objectui#8932.

F6 — the changeset now names every released package whose source moved

check-changeset-presence.mjs counts 33 source files of 7 released packages
in this diff. The changeset named 6. @object-ui/components was missing
(src/renderers/complex/README-KANBAN.md — the presence rule counts .md
under src/), and this round's own edit adds a seventh, @object-ui/runner.
Both at patch: neither package's published, executable bytes move, and the
fixed group bumps them regardless. 7 named = 7 changed.

F8 — the two doc surfaces the truncated dispatch never reached

Closed on cf31b51ed. ⛔ No schema face, behaviour or test changed anywhere
git diff --cached confirms the commit touches exactly two files under
content/docs/** and nothing under packages/**.

  • content/docs/api/schema-reference.md. The ObjectKanbanSchema section
    (re-derived at :913:949 on this head) omitted data and bind while the
    retirement's own migration note requires "data": [] for a purely static
    board. One prose blockquote added after the existing columns collateral-class
    note, in the same style, stating that both ride BaseSchema rather than being
    narrowed on ObjectKanbanSchema, and naming the renderer's own record-source
    ladder.
    ⚠️ A correction to the verdict's own framing, reported rather than acted
    around:
    on this head bind (base.ts:248) and data (base.ts:184) are
    both named, JSDoc'd BaseSchema membersbind's own doc already gives an
    object-kanban example — rather than bare index-signature riders. objectui#6357
    landed that before this PR opened. The verdict's remedy (document in prose,
    touch no schema face) is unaffected by the nuance and is what was done.
  • content/docs/plugins/plugin-kanban.mdx. onCardMove was authored inside
    typed ObjectKanbanSchema object literals in five places (Basic Usage
    ~:44; the Schema API shape ~:79:92 and its Properties table ~:109:115;
    Project Task Board ~:196; Event Handling's useBoard hook ~:396) — all
    re-derived on this head, and the dispatch's README anchor had moved too, from
    :99:104 to :130. The real wiring was traced (ObjectKanban.tsx /
    index.tsx's KanbanRenderer) to confirm that an authored onCardMove is
    never read on the registered object-kanban path — it is always overwritten
    internally — which is what packages/plugin-kanban/README.md's corrected
    sentence already says. Every occurrence now splits onCardMove into its own
    binding with the README's disclaimer; the Properties-table row was removed
    rather than kept-and-reworded, because a runtime prop does not belong in a
    table of document properties.
  • ⚠️ Noted, not filed (below this round's filing bar): that same file's
    "### Properties" table (~:115:120) omits type, groupBy, data and
    objectName, all present in the Schema API shape immediately above it. That is
    an incomplete listing, not a copy-and-it-fails defect. Whoever next edits that
    table is its natural owner.

⛔ The trap this change had to avoid, and the measurement that says it did

The strings kanban and gantt name two different things at two different layers. Only one is retiring.

layer value who writes it retired?
stored NamedListView.type "kanban", "gantt" CreateViewDialog, persisted in every tenant's database NO
node type key kanban, gantt hand-authored JSON only ✅ yes

Re-derived on this head rather than taken from the brief: packages/plugin-view/src/ObjectView.tsx's switch (viewType) is the seam, and it already emits object-kanban and object-gantt — twelve emitted node types, an unbroken object-* family, which is the firing control that makes those two readings rather than lone matches.

⇒ Every kanban and gantt view any user ever created through the console already renders through the surviving spelling, so this retirement moves zero stored documents. ObjectView.tsx's case 'kanban': / case 'gantt': and CreateViewDialog.tsx's type values are unchangedgit diff --stat names neither file. Two compile-time pins guard the layer against a future migration that pattern-matches the string:

_StoredViewTypeKeepsKanban   'kanban' extends NonNullable of NamedListView.type  =>  true
_StoredViewTypeKeepsGantt    'gantt'  extends NonNullable of NamedListView.type  =>  true

Which case each of the four names was, with the measurement

Measured whole-repo on this head, PCRE with a lookbehind so viewType: and friends cannot be counted as node keys. Controls on the same instrument: object-grid (2 JSON / 128 line-occurrences in TS) fires, zzz-not-a-type (0) is silent.

name schema face declaring it disposition migration surface
kanban yesKanbanSchema in complex.ts, its Zod mirror, SchemaRegistry['kanban'] named refusal + unregister 2 JSON fixtures migrated (of 3 carrying the spelling — see below), 38 TS files, 6 doc pages
kanban-ui no — 0 declarations unregister 0 authored documents
kanban-enhanced no — 0 declarations unregister 0 authored documents
gantt no — 0 declarations unregister 0 registry-resolved authorings

⚠️ One brief figure did not survive re-derivation, and it is reported rather than acted around. The #8008 ruling recorded bare gantt as 0 JSON / 0 TS. On this head the node-key spelling appears in 43 line-occurrences across 25 TS files — 24 test files inside packages/plugin-gantt plus its demo/main.tsx. Every one of them hands the node object directly to the ObjectGantt component with an as any cast; none resolves through ComponentRegistry (probed for SchemaRenderer / ComponentRegistry / validateTree / resolveComponent across all 25, with the object-gantt file set as the firing control, which does hit ComponentRegistry in 4 files). ⇒ The ruled route is untouched — the registration had zero resolved consumers — and the ruling's own follow-up paragraph anticipated exactly this by telling the implementing seat to re-derive the write/store surfaces. The count is wrong; the conclusion it priced is not.

⚠️ The third JSON fixture is deliberately untouched, and the count is corrected
rather than left reading as three migrations.
examples/schema-catalog/src/schemas/components-complex-view-switcher/view-switcher.json:16
carries "type": "kanban" as a ViewSwitcher views[].type — the view-type
vocabulary
(ViewType, packages/types/src/views.ts), which is what a
switcher tab names, not a node type; the nested schema in the same entry is
its own document. git diff --stat over that path is empty. The same
vocabulary collision is now declared, with its reason, in
scripts/check-doc-component-types.mjs's DOC_TYPE_EXEMPTIONS — until this
card the value passed by coincidence, the two vocabularies sharing one spelling.

⭐ The namespaced spellings retire too — view:kanban and view:gantt

ComponentRegistry.register(type, C, { namespace }) stores both the
namespaced key and a bare-type fallback (packages/core/src/registry/Registry.ts),
so every retired key had a twin that goes with it. Neither the changeset nor
this body named them before this round:

retired spelling namespaced twin author instead
kanban view:kanban object-kanban
gantt view:gantt object-gantt
kanban-ui plugin-kanban:kanban-ui object-kanban
kanban-enhanced plugin-kanban:kanban-enhanced object-kanban

Both spellings are pinned as gone — each against a firing control on the
surviving key — in kanban-family-registry-keys-retired-8257.test.ts and
bare-gantt-node-key-retired-8008.test.ts, measured through getConfig(key, ns)
so that a retirement removing only one spelling would fail.

What an unmigrated view:kanban / view:gantt node now renders depends on the
host.
In apps/console it renders the protocol placeholder panel, not the
OBJUI-001 "Unknown component type" error: the console calls the opt-in
registerPlaceholders() (@object-ui/components, renderers/placeholders.tsx)
after its plugin registrations, both keys are in that file's
PROTOCOL_COMPONENTS list, and registerPlaceholder only claims a key nothing
else has taken — which, until this change, the two plugins had. In every other
host, which does not call that bootstrap, the same node renders OBJUI-001.

⚠️ And that is a real author-facing trap: objectui check will not flag either
namespaced spelling.
The CLI's known-schema-types.ts is GENERATED from the
repository's real registration calls, and the placeholder registration is a real
one — so view:kanban and view:gantt are still on that list and still
validate green, while the node renders a placeholder rather than a board.
The bare kanban / gantt entries DID leave the generated list; only the
namespaced pair survives, and only because of the placeholder. ⇒ Grep documents
for the namespaced spellings directly; do not rely on objectui check to find
them. The migration note in the changeset says so.

The mechanism — why kanban is a refusal and the other three are deletions

BaseSchema closes with [key: string]: any and BaseSchemaCore ends .passthrough(), so a dropped member key is KEPT, not refused. ⚠️ That rule does not govern a type literal: AnyComponentSchema is a discriminated union (objectui#8498), so it selects one arm from the authored literal and an unclaimed literal matches nothing — deleting the arm would already refuse. What deleting would not do is name a remedy: the union answers a missed discriminator with its own Invalid input.

So a new retiredNodeType() joins the three refusal helpers in zod/tombstone.zod.ts, and the arm survives to carry the message. Measured on zod 4.4.3, through the real safeValidateSchema:

{ type: 'kanban', objectName: 'tasks', groupBy: 'status' }
  => code 'custom' at path ['type']
  => "`kanban` is a RETIRED node type (ADR-0049). Author `object-kanban` instead …
      ⚠️ The STORED `NamedListView.type` value "kanban" is a DIFFERENT layer and is
      unaffected — do not rewrite it."

{ type: 'zzz-not-a-type' }   => the union's generic path, no remedy text  (the separator control)
{ type: 'object-kanban', objectName: 'tasks', groupBy: 'status' }  => accepted  (the firing control)

⭐ objectui#8818 — the ENTRY is shut, ⛔ the CLASS is not

SchemaRenderer strips a fixed enumerated metadata list and spreads the rest as React props; objectFields is not on that list, and KanbanRenderer — the component kanban-ui resolved to — declares objectFields as a real prop. Retiring the registration means no authored node can reach that component through the registry, which shuts that path. ⚠️ The class is untouched: the hole returns the moment another registered renderer declares an objectFields prop. That card's option (a), stripping at the SchemaRenderer boundary, is what would shut the class. Said in the changeset and pinned in kanban-family-registry-keys-retired-8257.test.ts. ⛔ That card itself is not edited or touched by this PR; the PM updates it — done at objectui#8818 comment 5611940366.

⚠️ What the kanban arm took with it — the cost of this change, stated

That arm was the only schema face that ever declared columns, cardTitle, swimlaneField, grouping and 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.

No object-kanban document changes meaning: it was never judged by the kanban arm, so every one of those keys has always ridden BaseSchema's index signature there. What is gone is the kanban document that had them. Declaring them on ObjectKanbanSchema would WIDEN a published accept set, which is a ruling and not a repair — so each flipped reading is pinned where it can be seen instead of quietly disappearing:

  • examples/schema-catalog/test/kanban-column-cards-6939.test.tsxcolumns[].items now parses green; four probes measured with a firing control (the surviving arm's own groupField tombstone still refuses).
  • packages/plugin-kanban/src/__tests__/kanban-handler-slots-7664.test.tsxonCardMove / onCardClick / onQuickAdd are still forwarded and are now declared on no face; firing control groupBy reads true on the same shape lookup.
  • packages/types/src/__tests__/handler-keys-json-refusal-6124.test.ts — the ledger shrinks 67 → 62, with the arm-vs-key distinction written where a future reader meets it.
  • zod-mirror-parity.test.ts — KnownDrift 41 → 40 entries, 64 → 61 keys.
  • packages/types/src/registry.ts — measured and reported: SchemaRegistry has never carried an object-kanban entry, so the kanban family now has no entry in that map at all (objectui#7665's territory, not this card's).

⇒ The restore-the-judging follow-up is filed as objectui#8913 and is pm:blocked on this PR.

Verification

⚠️ Amended three times — by the merge round, then the rework round, then the F8
round. The measurements below are NOT rewritten.
origin/main (60e1f80b2)
was merged in as 6ed28c04f; the rework round landed 616973214 on top; the F8
round landed cf31b51ed, which is this PR's head. All are append-only —
c695a1c8d, 4dcf54452, 6ed28c04f and 616973214 are all still ancestors —
the merge resolved zero conflicts, and the push 616973214..cf31b51ed was a
plain fast-forward with ⛔ no force-push.

Which head each reading stands on, stated rather than left to be inferred:

  • The per-gate readings in the Ablation and Tests subsections were taken on
    the pre-merge heads and stand as taken.
  • The Rework round subsections were taken on 616973214.
  • cf31b51ed adds two content/docs/** files and nothing else — no
    packages/** path moved — so every packages/** reading above carries over
    unchanged. The readings that could move with a docs edit were re-taken on
    cf31b51ed
    and are listed in their own subsection below.

Every exit code was captured into a file BEFORE any pipe (cmd > file 2>&1; EXIT=$?), never off a cmd | tail pipeline.

Ablation — one leg per retired name, five legs

Each leg: prove the pin green at rest, restore the accepted spelling, prove the mutation on disk before reading any result (grep -c -F on both the injected and the removed text, plus a git hash-object that must differ from the HEAD blob), run the pin, restore with git checkout HEAD -- ABSOLUTE_PATH from a trap … EXIT INT TERM, and prove restoration by blob hash — never by an exit code. Non-vacuity is the before/after pair.

leg file mutated on-disk proof before mutated after restore proven
kanban (named refusal) types/src/zod/complex.zod.ts injected 1 hit, removed 0; blob 7f3468a7 vs HEAD 13f47d7e 0 1 0 blob back to 13f47d7e
kanban-ui (registration) plugin-kanban/src/index.tsx injected 3, removed 3; blob c8fc541e vs HEAD 10324942 0 1 0 blob back to 10324942
kanban-enhanced (registration) plugin-kanban/src/index.tsx injected 3, removed 3; blob aa9c683b 0 1 0 blob back to 10324942
kanban (registration) plugin-kanban/src/index.tsx injected 3, removed 3; blob ba2c9a00 0 1 0 blob back to 10324942
gantt (registration) plugin-gantt/src/index.tsx injected 2, removed 1; blob 56454b75 vs HEAD 7d6e1f32 0 1 0 blob back to 7d6e1f32

git status is clean after the run, and the harness's trap restores on any exit — including the container's ten-minute foreground SIGTERM landing mid-mutation.

⚠️ No build is interposed: 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 that the mutation reached the code under test.

Tests

run result
vitest run packages/types/ packages/plugin-kanban/ packages/plugin-gantt/ examples/schema-catalog/ apps/console/ packages/cli/ scripts/ exit 0 — 546 files, 11,259 passed, 2 skipped
the same sweep re-run for packages/types/ after the final commit exit 0 — 161 files, 3,144 passed
apps/console/src/__tests__/registry-inputs-spec-parity.test.ts, run explicitly exit 0198 tests, 1 file
pnpm --filter @object-ui/types type-check, --filter @object-ui/plugin-kanban type-check exit 0 each

The console sweep is in the first row; registry-inputs-spec-parity is named separately because retiring registrations moves exactly what it checks.

Gates

check:doc-types 0 · check:doc-snippets 0 · check:doc-examples 0 · check:doc-fences 0 · check:handler-key-reads 0 · check:sdui-registration-pins 0 · check:readme-exports 0 · check:skills-paths 0 · check:skill-examples 0 · check:skill-eval-tokens 0 · check:eager-closure 0 · check:element-data-source-declaration 0 · check:published-dist 0 · check:dist-completeness 0 · check:spec-symbols 0 · check:governed-queue-guard 0 · check:unreferenced-sources 0 · check:doc-example-readers 0 · check:vi-mock-inherit 0 · check:i18n-keys 0 · check:control-bytes 0 · check-changeset-presence 0 · check-changeset-fixed 0 · check-changeset-no-major 0 · check-doc-links 0 · pnpm check 0 · turbo run lint --concurrency=2 0 — 47/47 tasks, 0 errors across the whole farm.

⚠️ Two gates fired for real and were fixed rather than argued with, which is the evidence they are live: check:doc-types found two doc pages the grep sweep had missed, and check:readme-exports / check:doc-snippets found packages/plugin-kanban/README.md still importing the retired KanbanSchema.

Governed surface

node scripts/check-governed-queue-guard.mjs --test over all 50 changed paths (49 + the runner test the rework round re-keys), re-run on 616973214: NOT GOVERNED — exit 0, 50 paths checked against 5 governed surfaces, none matched. Firing control on the same probe: the split-out skills/objectui/guides/page-builder.md alone still reads GOVERNED, exit 3.

⚠️ Unchanged by cf31b51ed: that commit edits two files already inside the 50-path set (content/docs/api/schema-reference.md, content/docs/plugins/plugin-kanban.mdx), so the changed-path set neither grew nor gained a skills/** entry.

⇒ The governed-surface reason for parking is gone — that file left with PR #8916 and this PR carries no skills/** path.

CORRECTION, 2026-09-10. The rest of this paragraph said the PR "stays draft" and that "needs:contract-review remains hung on this PR and on all three cards". Both were true when written and are false now: a maintainer flipped the PR out of draft and removed the label at 05:05:42Z. ⛔ Neither is reversed here. What is unchanged: this seat does ⛔ not enqueue, ⛔ not enable auto-merge, ⛔ not merge and ⛔ not flip the draft state in either direction. Landing is the PM's and the maintainer's.

objectui#8614 ledger hazard

scripts/check-doc-example-types.mjs keys UNGATED_EXAMPLES by file:line, and this diff shifts lines in packages/types/src/complex.ts and — in the rework round — in packages/plugin-kanban/src/index.tsx. Re-derived on 616973214 by importing the ledger rather than eyeballing it: 90 rows across 74 distinct files; the intersection with this diff's 50-file changed set is empty, so no row needs re-keying. Two controls on the same membership test: a file that IS in the ledger (packages/auth/src/AuthGuard.tsx) reads true, and a file that cannot be (zzz/not-a-file.ts) reads false — so the empty intersection is a reading and not a parser that found nothing. check:doc-examples exits 0.

Re-derived again on cf31b51ed after the F8 edit, by the same import-the-ledger method and the same two controls: 90 rows / 74 distinct files, changed set still 50 files (both edited files were already in it), intersection still empty.

⚠️ A second, unrelated line-keyed ledger was checked for and does not exist: a scan of scripts/*.mjs for path:line-shaped keys returns exactly one file, the one above.

Base to main pre-flight

Re-taken on 616973214, and the earlier reading is superseded, not amended: main has moved again. The window is NOT empty13 commits, so the control can fire — and this time the intersection is NOT empty either:

SUPERSEDED, 2026-09-10 — every window figure in this subsection is a reading on an OLD head. main has moved 30 commits since 60e1f80b2, and current main is now merged into this branch; the authoritative window reading is in 🔁 Merge-repair round above. Kept as history, ⛔ not as a current reading.

Re-derived on cf31b51ed, with the instrument itself corrected: merge-base(HEAD, origin/main) = 60e1f80b2 (unchanged), window 13 commits (main has not moved further), and the true intersection computed via two ..-scoped diffs from the real merge-base⚠️not a raw two-dot HEAD..origin/main diff, which conflates unrelated same-file edits and is what an earlier round reached for. Result: exactly 1 file, packages/plugin-kanban/src/ObjectKanban.tsx, already known and already accepted. Self-comparison control (the diff set against itself) = the full 50, confirming the comm methodology. git merge-tree --write-tree HEAD origin/main exit 0, same clean tree 33eb6e6d4 — no new conflict introduced.

🔁 Rework round — F1's reproduce-then-green pair

⛔ A fix with no reproduced failure is a guess that compiled, so both legs were
run on this branch before and after the re-key. Every exit code captured into a
file BEFORE any pipe.

leg command before (6ed28c04f) after (616973214)
Type Check turbo run type-check --filter=@object-ui/runner exit 2 — 4× TS2339: Property 'kanban' does not exist on type '{ 'object-kanban': FC of ObjectKanbanComponentProps; }' at src/plugin-integration.test.ts(19,31), (23,42), (48,29), (49,76) exit 0
Tests vitest run packages/runner/src/plugin-integration.test.ts exit 1 — 3 failed / 3 passed; expected 'undefined' to be 'function' at :24, expected undefined to be defined at :19 and :53 exit 0 — 7 passed

⚠️ The two error shapes above spell the generic parameter as a word on purpose;
GitHub's body sanitiser eats angle-bracket-shaped fragments, backticks and code
fences included.

Shard membership is reported as NOT MEASURED, not as green. The verdict
placed the failing file in Test (shard 2/4); vitest list --shard=2/4 did not
finish inside this container's foreground window (killed at 480s, exit 124), so
that placement is the reviewer's reading and not one this round re-took. What
was re-taken is the failing file itself, red then green.

Ablation — the new control can fail, and only for its own reason. The
control asserts Object.keys(kanbanComponents) is exactly ['object-kanban']
and that the three retired spellings resolve to nothing; asserting the survivor
alone would pass just as well against a map that had been renamed wholesale.
Mutation: re-introduce a bare kanban: ObjectKanban entry in
plugin-kanban/src/index.tsx. Proven on disk before any result was read —
injected line count 1, blob 6947d63a vs HEAD blob 4e8b86c8 — and restored
from a trap … EXIT INT TERM with git checkout HEAD -- ABSOLUTE_PATH, with
restoration proven by blob hash back to 4e8b86c8 and an empty git diff HEAD,
never by an exit code. Result: exactly 1 test failed (the new control, on
[ 'object-kanban', 'kanban' ] to deeply equal [ 'object-kanban' ]), 6 passed.
No build is interposed: the root Vitest config aliases @object-ui/plugin-kanban
to packages/plugin-kanban/src, so the mutated source is what ran.

🔁 Rework round — importer census

The census, not the diff, defines the sweep. Every package that imports from
@object-ui/plugin-kanban or @object-ui/plugin-gantt, derived two ways —
package.json dependency declarations, and a tracked-file grep for the import
specifiers — then confirmed present in the sweep:

importer how it imports in the sweep
packages/runner static import { kanbanComponents } — the only consumer of that map anywhere ✅ tests + type-check
apps/console registerLazy(…, () => import(…)) for both plugins ✅ tests + type-check + build
apps/site dynamic import() per plugin name in PluginLoader ✅ type-check (0 test files)
examples/console-starter side-effect import in main.tsx ✅ tests + type-check
examples/schema-catalog test-side imports ✅ tests + type-check
packages/app-shell declared dep + a test that reads plugin-kanban source off disk by path ✅ tests (all 4 shards) + type-check
packages/cli emits the specifier into a generated app template ✅ tests + type-check
packages/sdui-parser names it in scripts/gen-manifest.ts ✅ tests + type-check

Two readings worth keeping: kanbanComponents has exactly one consumer in the
whole tree
— the runner test F1 fixes — and ganttComponents does not
exist
(plugin-gantt publishes no components map; its README says so). Every
'kanban' / 'gantt' string literal in the importer packages was re-checked
and is stored-view-type layer, allowedVisualizations vocabulary, or —
in apps/site — that loader's own local union of package names, none of them
a registry node type key.

🔁 Rework round — verification on 616973214

Tests, run from the repo root with paths relative to it. packages/app-shell
does not fit this container's foreground window as one run (669 test files), so
it was run as four complete shards rather than narrowed: 168 + 167 + 167 +
167 = 669, which is exactly its tracked test-file count, so nothing was skipped.

run result
vitest run packages/plugin-kanban/ packages/plugin-gantt/ packages/runner/ exit 0 — 113 files, 769 tests
vitest run packages/app-shell/ --shard=N/4, N = 1…4 exit 0 each — 669 files total, 1 skipped test
vitest run packages/sdui-parser/ exit 0 — 13 files, 182 tests
vitest run apps/console/ packages/cli/ examples/schema-catalog/ examples/console-starter/ exit 0 — 146 files, 3,544 tests
vitest run packages/types/ scripts/ exit 0 — 297 files, 7,033 tests
turbo run type-check --concurrency=2whole repo, the CI Type Check job's own command exit 081/81 tasks
check-type-check-coverage, tsc -p tsconfig.scripts.json, tsc -p tsconfig.vitest-setup.json exit 0 each
turbo run build --filter=!@object-ui/site --concurrency=2 exit 0 — 43/43
turbo run lint over the three packages whose source changed exit 0 — 0 errors

Gates, all on 616973214, each exit code captured before any pipe:

check:doc-types 0 · check:doc-snippets 0 · check:doc-examples 0 ·
check:doc-fences 0 · check:readme-exports 0 · check-doc-links 0 ·
check:handler-key-reads 0 · check:sdui-registration-pins 0 ·
check:unreferenced-sources 0 · check:element-data-source-declaration 0 ·
check:side-effects-array 0 · check:doc-example-readers 0 ·
check:spec-symbols 0 · check:dist-completeness 0 · check:published-dist 0 ·
check:phantom-deps 0 · check:self-import 0 · check:vi-mock-specifiers 0 ·
check:vi-mock-inherit 0 · check:i18n-keys 0 · check:control-bytes 0 ·
check-changeset-presence 0 · check-changeset-fixed 0 ·
check-changeset-no-major 0 · check-governed-queue-guard --test 0.

⚠️ Three gates were exit 2 = PRECONDITION NOT MET on first run and are
reported as such rather than as passes**: check:doc-examples,
check:readme-exports (which additionally printed "the population COLLAPSED —
this run proves nothing"
, 15 of 40 packages readable) and
check:sdui-registration-pins (no console bundle to weigh). All three were
re-run after the builds they name and are the zeros listed above; the
readme-exports census then read 36 of 40 packages, 535 self-imports judged
real, 0 fabricated.

⚠️ check:doc-fences fired for real this round — see the F3 section. A gate
that goes red on your own first attempt and green on the fix is the evidence it
is live.

🔁 F8 round — verification on cf31b51ed

The F8 commit touches two content/docs/** files and nothing under packages/**,
so the doc gates are what can move. Build precondition satisfied first
(turbo run build --filter='!@object-ui/site' --concurrency=2 exit 0, 43/43,
cache hit) so that none of these could return exit 2 = PRECONDITION NOT MET.
Every exit code captured into a file BEFORE any pipe.

gate reading
check:doc-fences exit 0 both before and after the edit — ledger unmoved at 80 files / 89 blocks each time, both logs kept. The rework round's trap was avoided by design: both explanatory notes sit in prose above their fences, never as a fence-opening comment inside one — verified against the diff, which touches no fence's first line
check:doc-types exit 0 — 895 type literals judged, 776 registered + 119 exempted
check:doc-snippets exit 0638 of 638 covered ts/tsx blocks compiled strict against built dist, 0 failed (this is what confirms the four edited tsx blocks still type-check under BaseSchema's index signature)
check:doc-examples exit 0 — 90 of 125 examples fail exactly as the ledger declares (90 rows), 35 compile
check:readme-exports exit 037 of 40 tracked packages readable, so the population is healthy and ⛔ not collapsed; 535 self-imports judged real
check-doc-links exit 0 — links valid across 17 scan roots, including the new #baseschema anchor. ⚠️ Run as node scripts/check-doc-links.mjs: the pnpm wrapper's exit 254 is a known quirk of that script's runner, not a failure
check:control-bytes exit 0 (not on the requested list; run as a low-cost sanity check)

Fixes vs Refs, per card

All three are Fixes, each confirmed on this head rather than assumed:

node scripts/check-clause2-carriers.mjs --pair 8865 exits 0 on this head, with --pair 89162 and --self-test0 (319 cases) as its controls.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w


Generated by Claude Code

…gantt` node keys and the `kanban-ui` / `kanban-enhanced` registrations

Executes three maintainer rulings taken in one batch on 2026-09-09:

  - objectui#8802 — retire the bare `kanban` node type key
  - objectui#8257 — retire `kanban-enhanced` and `kanban-ui`
  - objectui#8008 — retire the bare `gantt` registration

Three of the four are registration-only: no schema face ever declared
`kanban-ui`, `kanban-enhanced` or `gantt` as a component node type, so
unregistering is the whole retirement. The bare `kanban` key had a declared arm
on both faces, so it retires as a NAMED REFUSAL — a new `retiredNodeType()`
helper keeps an arm claiming the literal in `AnyComponentSchema`'s discriminated
union and answers a `{ "type": "kanban" }` document with a message naming
`object-kanban`, while the TypeScript half is the absence of the arm from
`ComplexSchema` and of the key from `SchemaRegistry`.

The STORED `NamedListView.type` values `"kanban"` and `"gantt"` are a different
layer and are untouched: `ObjectView` already maps a stored view type onto the
`object-*` node type it renders, so zero stored documents move. A compile-time
pin guards that layer against a future pattern-matching migration.

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

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

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

Size Limits

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

os-warren and others added 2 commits September 9, 2026 16:26
…ly retirement pins

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

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3479.3 KB 3512.7 KB
Main entry chunk (gzip) 144.0 KB 350 KB
Entry file index-ZPp-MVpx.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

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

Size Limits

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

Copy link
Copy Markdown
Collaborator Author

Landing plan — ⛔ I cannot merge this PR as it stands, and the maintainer ruled to split it

domain:spec @ objectui PM seat, session session_01Jmxdo7bmeqCQHLSfmLVX9w.

The blocker

This PR touches one governed-surface file: skills/objectui/guides/page-builder.md (1 of 50).

⛔ This seat never merges a governed-surface PR and never submits an approving review on one, under any account. That is a hard constraint, not a judgement call — so as a single 50-file PR this cannot be landed by me at all, and it would sit as a draft waiting for a human while packages/types/src/complex.ts, zod/complex.zod.ts and packages/plugin-kanban/** stay held against every other slice.

⚠️ Two instruments disagree about the governed reading, and I am recording that rather than picking the convenient one: CI's Governed Surface Queue Guard reports success on this head, while the dev's local node scripts/check-governed-queue-guard.mjs --test over all 50 paths reports GOVERNED. They are not measuring the same thing — the CI check and the --test probe take different inputs. ⛔ I am not resolving that here; the split makes it moot for the 49, and the 1 goes to a human either way. Worth a card if it recurs.

Maintainer ruling, 2026-09-09: split the skills file out; the rest goes through the queue.

part disposition
skills/objectui/guides/page-builder.md its own PR, ⛔ untouched by me beyond being separated — a human merges it
the other 49 files stay on this PR → contract-review-tier review (Clause-② yes) → queue

⇒ The 49 land on the normal path and release the held files; the governed one waits for a human without blocking them.

⛔ What the split must not do

  • Do not change the skills file's content while separating it. It moves branches; its diff stays byte-identical.
  • ⚠️ Verify the 49 are green WITHOUT it. If the skills page documents a spelling this PR retires, removing its edit from the 49-file PR may redden check:doc-types / check:readme-exports / check:doc-snippets — the dev reported those three fired for real during this round, which is the evidence they are live. If they do redden, ⛔ STOP and report: that means the two halves are not separable and the whole thing is a human merge.
  • ⛔ Do not enqueue either PR. This one still needs its contract review; the skills one is not mine to land.

needs:contract-review stays hung on this PR and on all three cards.

Also recorded from this round

  • objectui#8008's population figure was wrong in the ruling request — I said bare gantt was authored 0 times; it is 43 across 25 files. The ruling stands (the registration had zero resolved consumers) but the correction is on 5610343710, and it names four files the contract-review seat should independently re-check, because "none resolves through ComponentRegistry" is now the load-bearing claim.
  • The judging regression is carded, not absorbed: objectui#8913 (option C — restore columns: KanbanColumn[] on ObjectKanbanSchema), per the maintainer's "A now, C as the follow-up".
  • objectui#8818's disposition changed by this PR and is being updated there; ⛔ the dev correctly did not touch that card.

Generated by Claude Code

`skills/objectui/guides/page-builder.md` is a governed surface
(`GOVERNED_SURFACES` -> `skills-catalog`), so a PR containing it can only
be merged by a human. Carrying it here held `packages/types/src/complex.ts`,
`zod/complex.zod.ts` and `packages/plugin-kanban/**` against every other
slice.

Maintainer ruling, 2026-09-09: split the skills file out. This commit
reverts that one file to its merge-base blob (6df1359), leaving this PR
at 49 files on the normal queue path. The edit itself moves, unchanged, to
its own PR for a human to merge.

No content of the guide is changed here and nothing else on this branch is
touched: the revert restores blob 6df1359 exactly, and the same two
one-word type-literal corrections are reproduced byte-identically on the
new branch (blob ac30a6e).

History is NOT rewritten -- this is an added commit, no rebase, no amend,
no force-push, because other seats read this branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w
os-warren pushed a commit that referenced this pull request Sep 9, 2026
…lder guide

Split out of objectui#8865 (branch `claude/kanban-gantt-family-retirement`)
under the maintainer's 2026-09-09 ruling, byte-identical.

`skills/**` is a governed surface (`GOVERNED_SURFACES` -> `skills-catalog`),
so a PR carrying this file can only be merged by a human. On #8865 it was 1
file of 50 and held the other 49 -- `packages/types/src/complex.ts`,
`zod/complex.zod.ts`, `packages/plugin-kanban/**` -- against every other
slice. Splitting it lets those 49 take the normal queue path while this one
waits for a human.

The change is the same two one-word type-literal corrections: the guide's
two `<!-- os:check -->` JSON fences authored the bare `kanban` and `gantt`
node keys, which #8865 retires (`kanban` becomes a named refusal, `gantt`
loses its registration). They now author the surviving `object-kanban` and
`object-gantt` spellings.

⚠️ This is the NODE TYPE KEY layer only. The stored `NamedListView.type`
value "kanban" is a different layer, is NOT retired, and is untouched here.

Byte-identity with #8865: blob ac30a6e,
equal to the blob this path carried on #8865's head c695a1c. Whole-file
line count 331 before and after -- no ratchet spent.

Refs objectui#8802
Refs objectui#8257
Refs objectui#8008

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3480.4 KB 3512.7 KB
Main entry chunk (gzip) 144.0 KB 350 KB
Entry file index-3eNMe7KV.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

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

Size Limits

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

Copy link
Copy Markdown
Collaborator Author

os-dev-report

{
  "issue": 8865,
  "status": "done",
  "branch": "49-file half: claude/kanban-gantt-family-retirement (head c695a1c8d -> 4dcf54452) | governed half: claude/split-8865-skills-page-builder (head 33c9234b2, based on origin/main 348725a7c)",
  "pr": "49-file half: https://github.com/objectstack-ai/objectui/pull/8865 (draft, unchanged) | governed half: https://github.com/objectstack-ai/objectui/pull/8916 (new, draft)",
  "premise_still_valid": true,
  "summary": "The split is DONE and the two halves ARE separable: no gate reddened without the skills edit. objectui#8865 went 50 -> 49 files by ADDING commit 4dcf54452 that reverts skills/objectui/guides/page-builder.md to its merge-base blob; history was NOT rewritten (push was a fast-forward c695a1c8d..4dcf54452, and c695a1c8d is still an ancestor of the new head, exit 0). The same edit now rides objectui#8916 alone off origin/main, byte-identical. The change is two one-word type-literal corrections inside two os:check-marked JSON fences, bare kanban/gantt -> object-kanban/object-gantt. The gate that actually reads those fences is check:skill-examples -- NOT one of the three the brief named; it parses marked JSON fences and, per its own docblock, deliberately does NOT validate them against @object-ui/types schemas, which is the mechanism that makes the two halves separable at all. Neither PR was enqueued, flipped ready, or had needs:contract-review touched; objectui#8865 still carries that label and is still draft.",
  "tests": "PRECONDITION: turbo run build --filter='!@object-ui/site' --concurrency=2 -> exit 0, 43/43 tasks. Run FIRST, because several of these gates exit 2 = precondition-not-met without dist. No gate below returned 2, so none is a NOT MEASURED.\nAll gate exit codes captured into a variable BEFORE any pipe (cmd > file 2>&1; e=$?), on 49-file head 4dcf54452.\nGATES ON THE 49-FILE HEAD, ALL GREEN, none reddened by removing the skills edit:\n  check:doc-types = node scripts/check-doc-component-types.mjs -> exit 0 (brief named scripts/check-doc-types.mjs; that file does not exist). 188 doc files, 1107 code blocks, 892 type literals vs 650 registered keys.\n  node scripts/check-readme-exports.mjs -> exit 0. 43 READMEs, 412 fenced blocks, 3324 export symbols from 37 of 40 packages, 0 fabricated, 0 stale omissions.\n  check:doc-snippets = node scripts/check-doc-snippet-types.mjs -> exit 0. 245 documents, 637 of 637 blocks judged, 0 failed. Gate prints its OWN sentinel control: importing 'ThisNameIsDefinitelyNotExported' -> 1 diagnostic TS2305, positive 'BaseSchema' -> 0.\n  node scripts/check-doc-links.mjs -> exit 0, links valid across 17 scan roots. CONFIRMED: pnpm check:doc-links -> exit 254, because no such package script exists; it is wired as docs:check-links. Ran the .mjs directly, as instructed.\n  check:skill-examples -> exit 0. THE decisive gate: it is the only one that reads skills/** fences. 20 guides, 70 json fences parsed, 0 failed; marked floor 70 json / 13 ts, so the population did not shrink.\n  check:doc-examples -> exit 0; check:skills-paths -> exit 0 (88/89 paths, 1 baselined); check:skill-eval-tokens -> exit 0; check:doc-fences -> exit 0.\nFIRING CONTROL for check:skill-examples -- three points on ONE instrument against the BUILT tree, so the zeros are measurements of THESE fences and not a skipped file: (1) bare kanban/gantt, this PR's pre-image -> exit 0, 70 parsed 0 failed. (2) object-kanban/object-gantt, the post-image blob ac30a6e6c -> exit 0, 70 parsed 0 failed. (3) deliberately invalid JSON at the same fence -> exit 1, naming 'skills/objectui/guides/page-builder.md:255', JSON phase 70 parsed 1 FAILED.\nABLATION DISCIPLINE: mutation proven ON DISK before any result was read (poison token count 0 -> 1; blob 6df13594d -> f98e4541d, differing from HEAD); restored via git checkout HEAD -- ABSOLUTE_PATH from a trap on EXIT INT TERM; restoration proven BY BLOB HASH (back to 6df13594d) and by 'git diff HEAD' being empty, never by an exit code. Worktree clean afterwards.\nCOVERAGE PROOF that the gate reaches the reverted lines: check-skill-examples.mjs --list shows 10 marked fences in page-builder.md, including :255 and :266 -- the exact two fences reverted -- each 'marked pass', out of 212 listed rows.\napps/console FULL SWEEP -> exit 0, 98 test files, 1132 tests passed.\napps/console/src/__tests__/registry-inputs-spec-parity.test.ts run EXPLICITLY and alone -> exit 0, 1 file, 198 TESTS (matches the 198 objectui#8865 reported).\nPATH CHECK (the sibling-worktree hazard): every vitest RUN line and every /home/user/objectui* path in both logs resolves to /home/user/objectui-pr8865-49. Zero paths from any sibling worktree.\nBRANCH B build-free gates: check:skills-paths 0, check:skill-eval-tokens 0, check:doc-fences 0, check-changeset-presence 0 ('no changeset is owed'). check:skill-examples on branch B's own tree is NOT MEASURED -- it exits 1 with ERR_MODULE_NOT_FOUND because that worktree has no node_modules, which is a harness miss, not a red gate; its content is covered by control point (2) above and CI measures it.",
  "mcp_calls": "0 — every GitHub read and write went through the repo-scoped REST channel (probed first: GET repos/objectstack-ai/objectui -> 200). No MCP GitHub tool was invoked.",
  "open_questions": [
    {
      "question": "objectui#8865 is 20 commits behind origin/main and 5 of its 49 files ALSO changed in that window. Who merges main in before it is enqueued? I did not, because the brief said change nothing else on either branch.",
      "options": [
        "A — the contract-review seat merges origin/main into the branch as part of its pass, re-running the gates on the merged tree",
        "B — the PM merges main in now, before dispatching the contract review, so the review reads the tree that will actually land",
        "C — leave it to the merge queue and let it reject if it conflicts"
      ],
      "recommendation": "B, because the overlapping 5 files are exactly the load-bearing ones — packages/types/src/complex.ts, packages/types/src/zod/complex.zod.ts, packages/plugin-kanban/src/index.tsx, content/docs/api/schema-reference.md, content/docs/plugins/plugin-kanban.mdx — and objectui#8870 (refuse `actions` by name on the `page` node) landed in complex.ts and complex.zod.ts inside that window. A contract review of the accept set that reads the pre-merge tree is reviewing a tree that will not land. My gate readings above are likewise on the unmerged 49-file head, so they do not speak for the merged result."
    },
    {
      "question": "The PM recorded on comment 5610353950 that CI's Governed Surface Queue Guard reported success on the 50-file head while the local --test probe reported GOVERNED. The split makes it moot for these two PRs, but the instrument disagreement is unresolved.",
      "options": [
        "A — file a card to reconcile the CI check's input with the --test probe's input",
        "B — leave it; the split made it moot and it may not recur"
      ],
      "recommendation": "A, but it is the PM's to file, not mine — the brief scoped me to the split and told me to touch nothing else. Recording it here so it is not lost. Note the guard's pull_request leg is documented as deliberately exit 0, so a CI green there is NOT evidence a PR is ungoverned; that alone may explain the disagreement without any defect."
    }
  ],
  "out_of_scope_findings": [
    "noted, not filed: the brief's gate list named `scripts/check-doc-types.mjs`, which does not exist — the script is `scripts/check-doc-component-types.mjs` (package script `check:doc-types`). Successor who hits this: the contract-review seat on objectui#8865, and any seat copying the brief's gate list. Ran the real script; exit 0.",
    "noted, not filed: `pnpm check:doc-links` exits 254 because there is no `check:doc-links` package script — it is wired as `docs:check-links`. The brief predicted this and it reproduced exactly. Successor: the same seats as above. Filing declined: it is a naming inconsistency, not a reproducible defect, a declared-contract violation, or a metadata trap.",
    "noted, not filed: the three doc gates the brief flagged as the risk (check:doc-types, check:readme-exports, check:doc-snippets) structurally CANNOT be reddened by a skills/** edit — each excludes that surface by construction, as check-skill-examples.mjs's own header table records verbatim (DOCS_ROOT = 'content/docs', 'not skills/**'). The gate that does read skills/** fences is check:skill-examples, which the brief did not name. Successor: whoever writes the next split brief. Reported rather than silently relied on, because it is the reason the split is separable.",
    "noted, not filed: objectui#8865's PR body still describes a 50-file change, a 3-commit base-to-main window and a skills line-budget table for a file the PR no longer carries. Successor: the contract-review seat, who reads that body next. I did NOT edit the body — the brief said change nothing else, and rewriting a governed-adjacent body to tidy it risks more than it fixes."
  ]
}

Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Split done — 50 → 49 files. ⛔ And my risk analysis named three gates that CANNOT see the surface in question

domain:spec @ objectui PM seat, session session_01Jmxdo7bmeqCQHLSfmLVX9w.

⛔ My error, first

I wrote that removing the skills/** edit might redden check:doc-types, check:readme-exports, check:doc-snippets, and told the dev to stop if it did.

All three structurally exclude skills/** by constructioncheck-skill-examples.mjs's own header table records it verbatim (DOCS_ROOT = 'content/docs', "not skills/**"). ⇒ my named risk could not have materialised, and a dev that trusted my list would have run three gates blind to the thing it was checking.

The gate that does read skills/** fences is check:skill-examples — which I did not name. The dev found it, ran it, and it is the reason the split is separable at all: per its own docblock it parses marked JSON fences and deliberately does not validate them against @object-ui/types schemas.

⚠️ Also: scripts/check-doc-types.mjs does not exist — the script is scripts/check-doc-component-types.mjs. I quoted a path I had not verified. Same failure I corrected on objectui#8008 an hour ago: a name I carried instead of a name I measured.

The split, verified

#8865 50 → 49 files, by adding commit 4dcf54452 that reverts the skills file. ⛔ No history rewrite — the push was a fast-forward and c695a1c8d is still an ancestor
#8916 (new) the skills edit alone, off origin/main, draft

Byte-identity proven more strongly than I asked. I asked for the end-state blob to match. The dev proved both ends: post-image ac30a6e6c… equal at #8865's old head and #8916's head, and pre-image 6df13594d… equal at the merge-base and origin/main. ⇒ the diff is identical, not merely the result — which is the claim that actually matters when a file moves branches.

Firing control on check:skill-examples, three points on one instrument against the built tree: pre-image → exit 0 (70 parsed, 0 failed); post-image → exit 0 (70/0); deliberately invalid JSON at the same fence → exit 1, naming skills/objectui/guides/page-builder.md:255. ⇒ the zeros measure these fences, not a skipped file. Coverage confirmed by --list: the two reverted fences at :255 and :266 are among the 10 marked ones, each "marked pass".

Ledger (objectui#8614): 0 rows cite skills/**; control fires (AuthGuard.tsx:36). And the revert shifts no lines at all — 331 before, 331 after, two in-place single-line replacements — so no file:line key could move even if one had cited the file.

Governed reading, paired controls on one probe: the 49 paths → NOT GOVERNED (exit 0); the 1 skills path → GOVERNED (exit 3).

⚠️ The instrument disagreement is explained, not a defect

I recorded that CI's Governed Surface Queue Guard said success while the local --test probe said GOVERNED. The dev found the explanation: the guard's pull_request leg is documented as deliberately exit 0.

A green Governed Surface Queue Guard on a pull_request run is NOT evidence that a PR is ungoverned. ⛔ Any seat that reads it that way will merge a governed PR believing CI cleared it. Recording it here rather than filing a card, because the behaviour is documented and intended — the trap is in reading it, and this comment is where the next reader of this PR will look.

⚠️ Before the contract review — this PR is 20 commits behind and 5 of its 49 files moved in that window

packages/types/src/complex.ts · packages/types/src/zod/complex.zod.ts · packages/plugin-kanban/src/index.tsx · content/docs/api/schema-reference.md · content/docs/plugins/plugin-kanban.mdx.

⚠️ objectui#8870 (refuse actions by name on the page node) landed in complex.ts and complex.zod.ts inside that window — the same two files this PR rewrites the kanban arm in.

A contract review of the accept set that reads the pre-merge tree reviews a tree that will not land, and the dev's gate readings are likewise on the unmerged head. Taking the dev's recommendation (B): origin/main gets merged in before the review is dispatched, and the gates re-run on the merged tree. Dispatched.

⛔ Nothing enqueued, nothing flipped ready, needs:contract-review untouched on this PR and on all three cards.

⚠️ This PR's body still describes a 50-file change with a skills line-budget table for a file it no longer carries. The dev correctly declined to rewrite it under a "change nothing else" brief; it will be corrected in the merge round.


Generated by Claude Code

Brings the branch up to date so the contract review reads the tree that
will actually land. Zero conflicts; both sides' intent verified present.

Five files changed on both sides:
  content/docs/api/schema-reference.md
  content/docs/plugins/plugin-kanban.mdx
  packages/plugin-kanban/src/index.tsx
  packages/types/src/complex.ts
  packages/types/src/zod/complex.zod.ts

Proven as a union rather than asserted: diff(old head -> merge) restricted
to those five equals diff(merge-base -> main) restricted to them (53 lines),
and diff(main -> merge) equals diff(merge-base -> old head) (936 lines).
Control: the two expected diffs are not equal to each other, so the
instrument discriminates.

Correction to the dispatch premise, recorded rather than acted around:
objectui#8870 (refuse `actions` by name on the `page` node) did NOT land in
complex.ts / complex.zod.ts. It landed in packages/types/src/layout.ts and
packages/types/src/zod/layout.zod.ts, which this branch does not touch; both
are byte-identical to main in the merge result. What main actually
contributed to the two contested schema files is the `refreshInterval` ->
`refreshIntervalSeconds` rename on the Dashboard node (objectui#7783,
@objectstack/spec 17.4.0), a different subject from the kanban arm.

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

Copy link
Copy Markdown
Collaborator

Contract review at CONTRACT_REVIEW_TIERVerdict: REWORK (audit reading; director seat, summon #18 segment 6, session_017Js5kTpTtxieBjPyScgxJ3, 2026-09-10T00xxZ)

PR objectui#8865 · head 4dcf5445203c329127b69743c2b6c202e54bfa6f (re-read at posting 00:28:45Z; unchanged since 00:13Z) · card objectui#8802.


Verdict: REWORK (contract direction correct on all three cards; two blockers + doc/claim defects before a PASS can be recorded)

Head reviewed

4dcf5445203c329127b69743c2b6c202e54bfa6f (matches brief), branch claude/kanban-gantt-family-retirement, draft, mergeable_state: behind (21 commits; the 5 overlapping files — complex.ts, complex.zod.ts, plugin-kanban/src/index.tsx, the two docs — changed on main only by #8783 refreshIntervalSeconds / kanban limit input; no semantic conflict). Head did not move during review. Top commit is the split that removed skills/objectui/guides/page-builder.md (now PR #8916, Refs only, human merge).

Clause-② reading · claim · --pair

  • Reading: yes — four registry keys retire (kanban, kanban-ui, kanban-enhanced, gantt, plus their namespaced twins view:kanban, plugin-kanban:kanban-ui, plugin-kanban:kanban-enhanced, view:gantt, since Registry.register stores both keys — packages/core/src/registry/Registry.ts:457-492); KanbanSchema leaves the TS barrel (packages/types/src/index.ts:312), the zod barrel (zod/index.zod.ts:254-258) and SchemaRegistry (registry.ts:186); kanbanComponents shrinks to one key (plugin-kanban/src/index.tsx:305). Claim Clause-②: yes on PR and all three cards — matches.
  • check-clause2-carriers.mjs --pair 8865: EXIT 2 (UNJUDGED) on both the local copy and origin/main's copy (run from an extracted copy; the primary objectstack checkout is behind main). Cause: the body spells Fixes objectstack-ai/objectui#8802 (cross-repo form); closingKeywordRe (check-half-states.mjs:1526) accepts only Fixes #N, and the branch carries no issue-N, so no pair forms. What-if with the same body re-spelled Fixes #8802/#8257/#8008 via --pair-json: EXIT 0, all three cards, both carriers agree. Every card carries needs:contract-review + a Clause-②: yes claim. See F2.

Governed surface

node scripts/check-governed-queue-guard.mjs --test <49 paths>exit 0, NOT GOVERNED; control (skills/objectui/guides/page-builder.md) → exit 3 GOVERNED. CI "Governed Surface Queue Guard" agrees. Normal queue route once fixed.

CI on head (4dcf544)

28 success · 3 skipped · 2 failure, both PR-caused: Type Check (runner pnpm run type-check → 4× "Property 'kanban' does not exist on type '{ 'object-kanban': … }'") and Test (shard 2/4) (packages/runner/src/plugin-integration.test.ts:19,24,53). See F1.

Findings

F1 — CI red, caused by the PR (blocking). packages/runner/src/plugin-integration.test.ts:19,23,48-49 reads kanbanComponents.kanban; the PR removed that key (plugin-kanban/src/index.tsx:305-307) and packages/runner was outside the dev's sweep (vitest … types/ plugin-kanban/ plugin-gantt/ schema-catalog/ console/ cli/ scripts/) and outside the two --filter type-checks. Re-key to kanbanComponents['object-kanban'] with a control; add packages/runner to the verification sweep.

F2 — Landing pre-check ② cannot pass on this body (blocking). Re-spell the three lines as Fixes #8802, Fixes #8257, Fixes #8008 (see --pair above). GitHub honours the cross-repo form, the seat's machine floor does not.

F3 — Package README still teaches the retired spelling. packages/plugin-kanban/README.md:8 ("use type: 'kanban' in your schema") and :44 (untyped "Automatic Registration" block authoring type: 'kanban' with static columns, no groupBy/source — the exact shape object-kanban now refuses). The gates only compile annotated fences, which is why the diff fixed lines 76-231 and missed these. Also content/docs/plugins/plugin-kanban.mdx:426 imports ObjectKanbanSchema from @object-ui/plugin-kanban, which does not export it (barrel index.tsx:112-135); plaintext fence, so ungated.

F4 — Changeset states a false published fact. .changeset/…-retirement.md:72 and plugin-kanban/src/index.tsx:333-334 say KanbanEnhanced "is still exported and still importable from @object-ui/plugin-kanban/KanbanEnhanced". package.json exports is . and ./style.css only; the barrel never exports it; after this PR KanbanEnhanced.tsx has zero non-test importers (only cardPredicateScope.test.tsx:22 and a vi.mock). It is dead published source, invisible to check:unreferenced-sources (covers packages/components only). Correct the wording; file a card for the dead module (deletion is a further narrowing → its own ruling).

F5 — FROM→TO for authors is incomplete on the namespaced spellings. view:kanbanobject-kanban and view:ganttobject-gantt retire with the registrations (pinned in kanban-family-registry-keys-retired-8257.test.ts:70-77 and bare-gantt-node-key-retired-8008.test.ts:44-48) but are named neither in the changeset "Migrating" section (:66-73) nor in the PR body. Consequence worth one sentence: in apps/console (registerPlaceholders()) those keys now fall to the protocol placeholder (packages/components/src/renderers/placeholders.tsx:48,63-66) rather than OBJUI-001; elsewhere OBJUI-001 (SchemaRenderer.tsx:1395-1428). The generated CLI list still blesses view:kanban/view:gantt (packages/cli/src/utils/known-schema-types.ts:656,658, via the placeholder registration) — objectui check green, render placeholder. In-tree: .github/prompts/component.prompt.md:71 still teaches both keys. Note: the #8008 card's route-3 text kept "the registry entry for the view:gantt lookup"; the ruling record (comment 5603555117) retires the registration outright — the PR follows the record, correctly, but must say view:gantt goes with it.

F6 — Changeset omits a touched published package. packages/components/src/renderers/complex/README-KANBAN.md is under src/, which the presence rule counts .md included (check-changeset-presence.mjs:110-121, AGENTS.md:162); @object-ui/components is private: false and absent from the changeset. Fixed group bumps it anyway; add a patch line or state why not. Levels otherwise right: minor with breaking semantics in the body (AGENTS.md:240), check-changeset-no-major green; cli/console patch for their src/ changes.

F7 — PR body is stale against head. All readings are pinned to c695a1c8d; head is 4dcf5445. The "Governed surface" section still claims GOVERNED/1 path (false after the split). "3 JSON fixtures" for kanban — 2 changed; the third (examples/schema-catalog/…/view-switcher.json:16) is ViewSwitcher views[].type (view-type vocabulary, correctly untouched, now exempted at scripts/check-doc-component-types.mjs:486-494) — say so. Refresh readings on the final head.

F8 — Minor doc consistency. content/docs/api/schema-reference.md ObjectKanbanSchema table omits data/bind while the migration path depends on "data": [] (neither is on the TS face, objectql.ts ObjectKanbanSchema; they ride the index signature — same collateral class the PR discloses for columns). plugin-kanban.mdx still authors onCardMove inside typed ObjectKanbanSchema documents while the README (:99-104) says it is not a document key.

F9 — Verified, no action. (a) Stored layer untouched: ObjectView.tsx, CreateViewDialog.tsx, objectql.ts byte-identical to merge-base; ObjectView.tsx:1339→1411 maps stored kanbanobject-kanban, :1483→1499 ganttobject-gantt; compile pins _StoredViewTypeKeepsKanban/Gantt (bare-kanban-node-key-retired-8802.test.ts:87-88). (b) The four files the PM owed this seat (comment 5610343710): ObjectGantt.navWidthDefault.test.tsx:65 and ObjectGantt.apifetch.test.tsx:41 hand type:'gantt' straight to <ObjectGantt schema>; ObjectView.test.tsx:728 and ObjectView.ganttBinding-7070.test.tsx:74 are stored views[].type. None registry-resolves bare gantt; the ruling's basis holds. (c) Every remaining in-tree type:'kanban' read site (data-objectstack, core/normalize-list-view, plugin-list, react/ElementDataSourceGate, types/p1-spec-alignment:730, phase2-schemas:668) is stored/view-type layer. (d) Mechanism deviation from the ruling's "retirementTombstone + ?: never": a member tombstone cannot claim a type literal on a discriminated union, so retiredNodeType() (tombstone.zod.ts:233) keeps an arm RetiredKanbanNodeSchema (complex.zod.ts:183, in ComplexSchema:1124) that refuses at path:['type'] naming object-kanban and warns off the stored layer; TS half is absence (complex.ts:1922, registry.ts:186). Correct and disclosed; zod-mirror parity test re-keyed (162→161 pairs, drift entry declared).

Correctness vs each ruling

Scope: no widening beyond the three rulings; deleted tests (kanban-arm-batch70-7742, types-side kanban-plugin-dialect-authoritative-7664) had only the retired arm as subject; their surviving claim is carried in suite 3. Flipped readings (columns[].items accepted, handler keys undeclared on object-kanban, ledger 67→62) are pinned rather than repaired; #8913 (option C) and #7804's KNOWN_UNDECLARED_READS carry them.

Acceptance notes


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Contract review at CONTRACT_REVIEW_TIERVerdict: PASS WITH FINDINGS (delta re-review) (audit reading; director seat, summon #18 segment 6, session_017Js5kTpTtxieBjPyScgxJ3, 2026-09-10T05xxZ)

PR objectui#8865 · head cf31b51ed6542c5c8251ed8d83df0663b0158b53 (re-read at posting 05:05:13Z; unchanged since 03:16Z) · card objectui#8802.


Delta re-review — objectui#8865 (cards #8802 / #8257 / #8008)

Verdict: PASS WITH FINDINGS

All eight REWORK findings are discharged on the new head; CI is fully green; --pair 8865 exits 0 on all three cards; no governed path; #8917 composes cleanly. Remaining findings are prose-only residues outside the delta and two cosmetic PR-body defects — none blocks landing.

Head reviewed

cf31b51ed6542c5c8251ed8d83df0663b0158b53 — matches the brief; did not move during review (PR updated_at 03:16:43Z is the body edit, not a push). Draft, mergeable_state: clean, base main @ merge-base 60e1f80b2. origin/main is now da6e191856 (moved past the merge-base; includes #8917).

The 03:1xZ "Tier notice" comments from the skills seat are not present on the PR or on #8802/#8257/#8008 as fetched (last comment on any carrier is os-warren's 5612108853 at 03:16:43Z); labels and claims are unchanged in any case.

Delta summary (4dcf544520..cf31b51ed6)

  • Main-side: 6ed28c04f merges origin/main@60e1f80b2 (21 commits). git diff-tree --cc lists zero files differing from both parents — no hand-resolved hunks, no evil merge.
  • PR-own: 616973214 (6 files, +112/−20): runner test re-key + control, README/mdx/changeset/index.tsx docblock fixes, plugin-gantt/README.md (F3 widened). cf31b51ed (2 files, +34/−21): docs only — schema-reference.md, plugin-kanban.mdx. No packages/** executable source changed in the delta; the only packages/**/src non-test hunk is the comment-only docblock at plugin-kanban/src/index.tsx:333-344.
  • Beyond F1–F8: (a) plugin-gantt/README.md:233,260,293,445,582 type: 'gantt'object-gantt — same defect class, same file already in the diff, declared in the body with evidence: acceptable under the four-condition rule. (b) A two-directional control in the runner test (plugin-integration.test.ts:36-42). (c) @object-ui/runner: patch added (tests-only change; legal per AGENTS.md:162, no-op bump inside the fixed group).

F1–F8 status (file:line on cf31b51ed6)

# Status Evidence on head
F1 CI red (runner reads kanbanComponents.kanban) Discharged packages/runner/src/plugin-integration.test.ts:19,23,67-68kanbanComponents['object-kanban']; control :36-42 asserts Object.keys === ['object-kanban'] and the three retired spellings undefined; map is single-key at plugin-kanban/src/index.tsx:305-307. Type Check and Test (shard 2/4) green on head.
F2 cross-repo Fixes defeats --pair Discharged Body lines 1-3 Fixes #8802 / #8257 / #8008; --pair 8865 exit 0 (below).
F3 README teaches type: 'kanban'; mdx imports non-exported symbol Discharged plugin-kanban/README.md:8 and :43-58 (block now object-kanban + groupBy + data: [], with the stored-layer warning); plugin-kanban.mdx:443-444 split into @object-ui/types / @object-ui/plugin-kanban (barrel index.tsx:112 exports KanbanCard/KanbanColumn, never ObjectKanbanSchema). Residue sweep over docs/READMEs/apps/examples: no retired node-key literal remains except retirement notes, ViewSwitcher views[].type vocabulary (schema-reference.md:1200, view-switcher.mdx:23, plugin-view/README.md:270, view-switcher.json:16) and the parked skills guide (see R4).
F4 false KanbanEnhanced subpath claim Discharged Corrected at .changeset/…retirement.md:110-119 and index.tsx:333-344. Verified: plugin-kanban/package.json exports = ., ./style.css; barrel never exports it; tree-wide importers are only cardPredicateScope.test.tsx:22 (relative) and a vi.mock at kanban-handler-slots-7664.test.tsx:124. Decision card #8932 filed (open). File deliberately kept.
F5 FROM→TO must name view:kanban/view:gantt Discharged .changeset:75-108 (table + console-placeholder vs OBJUI-001 + objectui check trap); same in the body. Pins: kanban-family-registry-keys-retired-8257.test.ts:70-77 (getConfig('kanban','view') etc.), bare-gantt-node-key-retired-8008.test.ts (getConfig('gantt','view') falsy, object-gantt control). Verified placeholders.tsx:48 lists both keys; known-schema-types.ts:656,658 keep the namespaced pair, bare kanban/gantt absent.
F6 changeset omits @object-ui/components Discharged .changeset:5 '@object-ui/components': patch, :8 '@object-ui/runner': patch; 7 named = 7 touched published packages (all private unset/false). Levels per AGENTS.md:240 (breaking = minor). Changeset Declaration / Bump Policy / Fixed Group checks green.
F7 stale body readings Discharged (two cosmetic residues) Readings now stated per head (pre-merge / 616973214 / cf31b51ed); governed section reads NOT GOVERNED; "2 of 3 fixtures" with the third explained. Residue: body cites the #8818 disposition as comment 5611940366 (404) — the real one is 5611957855; merge-tree hash 33eb6e6d4 is 616973214's (seat's own finding B). Both body-only.
F8 data/bind undocumented; onCardMove inside schema literals Discharged schema-reference.md:949 blockquote (#baseschema anchor exists at :45); plugin-kanban.mdx:64-68, 81-83, 244-248, 403-415onCardMove split out of every literal, Properties row removed; no onCardMove: remains inside any schema object. Doc gates green on head.

Clause-② + claim + --pair

Governed

check-governed-queue-guard.mjs --test over all 50 changed paths → exit 0, NOT GOVERNED; 0 skills/** paths in the set (the governed half stays in #8916). Firing control skills/objectui/guides/page-builder.md → exit 3. CI "Governed Surface Queue Guard" green.

CI on head

33 check-runs on cf31b51ed6: 30 success · 3 skipped by design (coverage shard, coverage, dependabot) · 0 failure · 0 in progress. Lint, Type Check, Test shards 1-4, Build & E2E, Doc Snippet/Component/Fence checks, Changeset checks, README Export Check all green. Vercel status success.

#8917 overlap (ObjectKanban.tsx)

Remaining findings (none blocking)

Acceptance notes


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Landing provenance — director seat, summon #18 segment 6 (session_017Js5kTpTtxieBjPyScgxJ3, 2026-09-10T05:05:30Z). Clearing needs:contract-review on both carriers (objectui#8865 + card objectui#8802) on the strength of the contract-review-tier PASS WITH FINDINGS (delta re-review) at #8865 (comment) (head cf31b51ed6, unchanged). Landing pre-checks (contract-review.md ①②③): ① tier verdict on the card (pointer posted); ② check-clause2-carriers.mjs --pair 8865 exit 0; ③ 33 check-runs on head, 0 red / 0 in progress; governed-surface test exit 0 (not governed); mergeable_state: clean. Next: ready → auto-merge (SQUASH) → merge-queue entry, per landing-operations.md. Executed under the maintainer's 2026-09-09 13:4xZ order 「把当前的契约复审全部处理完」 precedent; the dispatching seat keeps ACCEPT/landing-window duties (MERGED confirmation + card close-out) if it is back before the queue finishes — otherwise this seat closes out.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Why the merge was rejected: ⛔ not a conflict — the merged tree goes red, and it will every time

domain:spec @ objectui PM seat, session session_01Jmxdo7bmeqCQHLSfmLVX9w. Measured in an isolated clone; ⛔ no push, ⛔ no shared ref moved, ⛔ no merge taken on origin.

The failure

packages/plugin-kanban/src/__tests__/quickAddIsDiagnosedNotDropped-8285.test.ts — 4 failed / 5 passed.

That file does not exist on this PR's head. git cat-file -e on cf31b51edfatal: does not exist. On pure origin/main it is 9/9 green. It exists only on the merged tree — which is exactly what a merge queue builds, and exactly what mergeable_state: "clean" cannot see, because that field is computed against this PR's stale base 60e1f80b2, now 29 commits behind (the body still says 13).

'view:kanban' — an authored `quickAdd: true` draws the inert-pair warning
  AssertionError: expected [] to deeply equal [ 'inert-quick-add' ]        (:106:63)
'view:kanban' — control: a genuinely unknown prop is still reported
  AssertionError: expected [] to deeply equal [ 'unknown-prop' ]           (:112:61)
the diagnostic covers exactly the tags `index.tsx` registers this renderer under
  AssertionError: expected [ 'object-kanban' ] to deeply equal [ 'kanban', 'object-kanban' ]   (:116:30)
'view:kanban' — this DIAGNOSES; it does not declare `quickAdd`
  AssertionError: kanban inputs: expected [] to include 'objectName'       (:131:40)

The mechanism. That test greps plugin-kanban/src/index.tsx for ComponentRegistry.register('…', ObjectKanbanRenderer) and builds its manifest from the live registry — 4 such calls on origin/main, 1 on the merged tree. Its KANBAN_TAGS names both object-kanban and view:kanbankanban, and packages/sdui-parser/src/kanban-quick-add.ts hard-codes QUICK_ADD_HOST_TYPES = new Set(['object-kanban', 'kanban']). Two further assertions in the same it are unreached but would also fail: registeredKeys().length expects 2, and ComponentRegistry.has('kanban-ui') expects true — a second key this PR unregisters.

PR #8922 anticipated this in its own commit message: "Interim by ruling: the contract-side refusal retires object-kanban.quickAdd from the spec's ComponentPropsMap, and this module goes with it. The live pin's spec row reddens on the day that lands." It landed first; this is that day.

Attribution control — the failure is created by the merge, not pre-existing:

tree result
pure origin/main b97129e96, separate worktree, own install exit 09 passed
merged tree exit 14 failed / 5 passed
PR head cf31b51ed the file does not exist

The conflict half is clean, and that is a reading

git merge-tree --write-tree cf31b51ed origin/main → exit 0, tree 1bd5d6da1. A real local git merge --no-ff origin/main → exit 0, 3 files auto-merged, 0 unmerged paths, the same tree. True intersection off the real merge-base = 3 files: ObjectKanban.tsx (#8917), plugin-kanban.mdx (#8922), schema-reference.md (#8914) — all auto-merged.

FIRING CONTROL, because an exit 0 from a conflict probe is nothing without one: a throwaway commit off the merge-base rewriting schema.cardTitle || schema.titleField; in ObjectKanban.tsx — the line 4fa0eb9c4 also rewrote — makes the same probe return exit 1 with CONFLICT (content): Merge conflict in packages/plugin-kanban/src/ObjectKanban.tsx. Mutation proven on disk first (anchor 1→0, marker 0→1, sha1 changed), built through a scratch GIT_INDEX_FILE so ⛔ no ref moved and no index was written. Self-comparison control: merge-tree head head returns a tree byte-equal to head^{tree}.

⛔ Three suspects cleared, each with its own reading

suspect verdict
#8917 ObjectKanban.tsx — the overlap this PR's own body flags merges clean; ObjectKanban.titleFieldPrecedence-8308.test.tsx exit 0, 11 passed
#8931 SchemaRenderer.tsx (testIddata-testid) this PR does not touch that file; packages/react exit 0, 75 files / 892 tests
#8935 / #8872 registry-inputs-spec-parity.test.ts my leading hypothesis, and it was wrong — exit 0, 198 passed. The ceiling reads 41 on both main and the merged tree; this PR never edits that file, so the merge takes main's wholesale
#8922 packages/sdui-parser/ itself exit 0, 14 files / 199 tests — because its own test builds a synthetic manifest. Only the plugin-kanban companion test reads the live registry

Also green on the merged tree: packages/types (164 files / 3237 tests), packages/core (137 / 2936), and turbo run type-check --concurrency=281/81 tasks, 0 error TS.

⇒ What has to change

This is not a conflict to resolve. #8922's plugin-kanban companion test asserts the bare kanban registration that this PR retires, so the two are semantically incompatible in that one file and #8922 landed second. Either this PR updates quickAddIsDiagnosedNotDropped-8285.test.ts (and decides what QUICK_ADD_HOST_TYPES should say) to the surviving object-kanban key, or #8922's author does. ⛔ Merging without that ejects from the queue every time.

⚠️ This is the same class as F1, which this PR already paid for once: "a retirement's blast radius is its IMPORTERS, not its own package". F1 was packages/runner reading kanbanComponents.kanban; this is packages/plugin-kanban's own new companion test reading the registry. The difference is that this importer did not exist when the sweep was taken — it landed on main afterwards. ⇒ the importer census has to be re-taken against current main, not against the base, whenever a retirement sits open across other merges.

A repair round is dispatched. ⛔ Not enqueued until it is green on a tree merged with current main.

⚠️ Two state changes this seat did not make, recorded

At 05:05:42Z this PR was flipped out of draft and needs:contract-review was removed from its labels. ⛔ Neither was done by this seat — its last act here (5612108853) left the PR draft with the carrier hung, parked on the SELF-REVIEW escalation. Recorded, not reversed. get_reviews returns [] — there is no review of any kind on this PR.

⚠️ NOT MEASURED — stated, never green

apps/console's full suite, packages/app-shell, packages/components, the other packages/plugin-*, examples/*, packages/cli; every lint and every check:* gate; turbo run build; e2e; the changeset gates. The repo's actual required-check list and merge-queue configuration were not read — the queue mechanism above is inferred from the consistent 16–18 minute gap between each merged commit's own date and its merged_at, ⛔ not from branch-protection settings.


Generated by Claude Code

…agnostic to the surviving object-kanban tag

objectui#8922 landed `quickAddIsDiagnosedNotDropped-8285.test.ts` after this
branch was cut. It re-derives its host-tag set from
`plugin-kanban/src/index.tsx`'s `ComponentRegistry.register(…,
ObjectKanbanRenderer)` calls and builds its manifest from the LIVE registry, so
it is an importer of exactly what this branch retires — and it goes red only on
the MERGED tree, which is what the queue builds and what a `mergeable_state`
computed against a stale base cannot see. Measured on the merge of
origin/main f4abff4: 4 failed / 5 passed, against 9/9 green on pure main and a
file that does not exist on this branch's head.

The subject stays pinned: the html tier NAMES the Quick Add pair instead of
dropping the key. Only the set of tags it holds over shrinks.

`QUICK_ADD_HOST_TYPES` drops `kanban`, and that is a measurement, not a
convenience. `checkKanbanQuickAdd` has exactly one call site — `validate.ts`'s
per-prop walk — and that walk runs only in the branch where
`manifest.components[node.type]` resolved. A tag no registration produces is
answered one level up by `unknown-component`, an error, and its props are never
walked at all. On a manifest built from the live registry `<kanban quickAdd>`
therefore draws `error/unknown-component` and nothing else, against a firing
control on `<object-kanban quickAdd>` that draws `warning/inert-quick-add`.
Nothing is silently dropped by the narrowing: the retired spelling is refused BY
NAME at the tag, which is louder than this warning, and stacking both is the
two-diagnostics-for-one-mistake shape `checkMemberTypes` already refuses.

The diagnostic's remedy text is falsified by the same retirement and moves with
it: it used to send authors to `<kanban-ui>`, a tag objectui#8257 retires, so a
page written to the old advice draws `unknown-component`. It now names
`KanbanRenderer` from `@object-ui/plugin-kanban`, which is still exported and
still forwards both halves by identity. `content/docs/plugins/plugin-kanban.mdx`
carried the same false sentence and is corrected the same way.

The retired spellings keep a hand-built manifest row in `packages/sdui-parser`
asserting they are NOT hosts — the discrimination a synthetic manifest can make
and the live one cannot.

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

Copy link
Copy Markdown
Contributor

⚠️ Console Performance Budget — gauge not trustworthy

The eager closure was measured, but one of the ceilings it is measured against no longer means what it names, so this run carries no pass/fail verdict for the performance budget.

This is not a budget violation. Nothing grew: the half marked below is a verdict about the gauge, and a ceiling that has stopped measuring anything can neither clear a bundle nor condemn one.

Step Outcome
Build packages success
Check console performance budget failure

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ✅ pass
Per-chunk ceilings ✅ pass
Ceiling sensitivity (headroom) ✅ pass
Ceiling freshness (checkout vs. base branch) ⚠️ superseded ceiling

⚠️ A superseded ceiling is neither a size regression nor a drifted gauge. The bundle was measured correctly and the ceilings on the base branch are correct — but this checkout predates a change to them, so the verdicts above were weighed against numbers that are no longer in force. Nothing grew. Update this branch onto the base branch and let Bundle Analysis run again; do not widen a ceiling to clear it. The Check console performance budget step log names each superseded constant with both values (objectui#6245).

Reason: The entry chunk measured 144.1 KB, but the eager-closure half of this gate returned no trustworthy VERDICT: the report could not be read, a ceiling has drifted out of range of the regression it must catch, or (objectui#6245) a ceiling was replaced on the base branch after this checkout was made. The step log says which. This is not a passing budget — and it is not a size regression either.

See the workflow run for details.


📦 Bundle Size Report

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

Size Limits

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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

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

Size Limits

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

@os-warren
os-warren added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit adf5812 Sep 10, 2026
34 checks passed
@os-warren
os-warren deleted the claude/kanban-gantt-family-retirement branch September 10, 2026 10:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment