Uh oh!
There was an error while loading. Please reload this page.
fix(types,plugin-markdown,plugin-kanban): one authority for MarkdownSchema and the in-package Kanban pair - #6971
Conversation
…Schema` and the in-package Kanban pair `MarkdownSchema` converges onto `@object-ui/types`: the plugin's second declaration differed on one member (`content`, optional there and required in types), measured as drift rather than a semantic difference — the plugin's own registration marks the input `required: true` (test-pinned), its renderer props type is non-optional, the Zod mirror is `z.string()`, and every authored `type: 'markdown'` node supplies it. plugin-markdown now re-exports the one authority. `KanbanCard` / `KanbanColumn`: the three in-package declarations converge to one. An AST comparison found `KanbanImpl.tsx` and `KanbanEnhanced.tsx` strict subset copies of `./types` with nothing typed differently, so their extra members (`cardSubtitle`, `cardFieldCells`, `coverImage`, `collapsed` — all optional) move onto the canonical declaration and both files re-point at it, keeping their export surface via re-export. Baseline `one-authority-per-exported-name-6273`: `MarkdownSchema` row deleted; the `KanbanCard` / `KanbanColumn` rows shrink from four sites to the two cross-package ones, which are escalated rather than guessed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
✅ Console Performance Budget
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
Size Limits
|
… scope the Kanban authority note C1: the "Schema API" block in packages/plugin-markdown/README.md still spelled `content` as optional — the exact outlier spelling this branch removes, in a doc that ships in the npm tarball. The canonical declaration is non-optional: `content: z.string()` at packages/types/src/zod/data-display.zod.ts:276 and `content: string` at packages/plugin-markdown/src/MarkdownImpl.tsx:31. No gate catches this; check-readme-exports judges export existence, not shape prose. C2: KanbanEnhanced.tsx's authority comment now carries the "in this package" qualifier its KanbanImpl twin already spells. The cross-package Kanban pair is deliberately open, so the unqualified wording overstated what this branch settles. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
✅ Console Performance Budget
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
Size Limits
|
C1 and C2 are discharged in 892625f. Still draft, nothing else touched. C1 (was blocking) — - content?: string, // Markdown content (supports GitHub Flavored Markdown)+ content: string, // Markdown content (supports GitHub Flavored Markdown)Re-derived against the declaration rather than taken on the review's word: C2 — -// One authority for these two names, in `./types` (objectui#6172 / #6155).+// One authority for these two names in this package: `./types` (objectui#6172 / #6155).matching Verification at 892625f, every command from the repo root:
The shrink-only baseline Generated by Claude Code Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Refs #6172 — two of the four names converge here.
MarkdownSchemais fullyconverged;
KanbanCard/KanbanColumnconverge inside@object-ui/plugin-kanban(three declarations to one).
KanbanSchema, and the remaining cross-packageKanbanCard/KanbanColumnpair, are escalated rather than guessed — see"What is NOT resolved here". A closing keyword is deliberately not used.
⛔ Draft by dispatch order. Do not mark ready, enqueue, or enable auto-merge.
Verified on
10340f225, clean tree.MarkdownSchema— converged, and the fork branch was tested and rejectedThe ruling allowed forking the name "if it turns out to be a real semantic
difference, not a drift." It is drift. Five independent statements in this
repository already say
contentis required; the plugin's own interface was theonly one saying otherwise:
plugin-markdown/src/index.tsxregistration —{ name: 'content', required: true }plugin-markdown/src/index.test.ts:44— pinscontentInput?.required === trueMarkdownImpl.tsx—MarkdownImplProps.content: string(the renderer that consumes it)types/src/zod/data-display.zod.ts:276—content: z.string(), not.optional(), parity-testedtype: 'markdown'node in the treeplugin-markdown/src/types.ts—content?: stringCorpus measurement, because this narrows the accepted set (clause ②, the
irreversible direction). 34
type: 'markdown'object literals in the tree, brace-matched rather than line-grepped. Every one that omits
contentis not a markdownnode: they are rich-text FIELD metadata (
MarkdownFieldMetadata,packages/types/src/field-types.ts:198— a different type withmax_length), plusone source comment and two doc restatements of the declaration itself. Zero
authored markdown nodes omit
content, so the narrowing changes nothing in-tree.Direction is forced, not chosen:
@object-ui/typesis the zero-workspace-dependencybottom layer (
dependencies=@objectstack/spec,zod), so it cannot re-point at aplugin without a cycle.
plugin-markdownalready depends on it, so no new edge.classNameis not lost — it is declared byBaseSchema, which both copiesextended, so it was always inherited rather than added by the plugin. The plugin's
face does gain
sanitizeandcomponents; both are optional, and neither is readby any renderer — sanitization is unconditional (
rehypeSanitizeis a fixed link inthe rehype chain). That deadness is recorded as a finding, not fixed here.
KanbanCard/KanbanColumn— the in-package ×3 converge to ×1Remedy picked by a TypeScript-AST probe (heritage clause, member set, and each
shared member's declared type and optionality), not by reading:
types.tsvsKanbanImpl.tsx—KanbanCardtypes.tsvsKanbanEnhanced.tsx—KanbanCardtypes.tsvsKanbanImpl.tsx—KanbanColumntypes.tsvsKanbanEnhanced.tsx—KanbanColumnNothing typed differently anywhere ⇒ stale copies, so the re-point branch, exactly
as #6936 treated the Breadcrumb pair. The extras (
cardSubtitle,cardFieldCells,coverImage,collapsed) move onto the one declaration in./types; all four areoptional, so every value that type-checked before still does. Both
.tsxmodules keeptheir previous export surface through
export type { … } from './types', so no importpath changes.
badgesas "typeddifferently" between
types.tsand both.tsxcopies. That was an artifact — JSDocprose inside the type literal, and a trailing member separator. After normalising
comments and separators the difference is zero. Comparing raw
getText()wouldhave produced the wrong verdict (rename instead of re-point).
Why direction (a) was not used
The gate counts
export type X =/export interface Xas an authority anddeliberately does not count
export type { X } from …. A derived downstreamdeclaration is still a declaration, so it cannot deliver the baseline deletion. The
live proof is on the tree:
ComponentMetawas converged the (a) way by PR #6297 andits
KNOWN_COLLISIONSrow is still there today.The shrink-only baseline
Re-derived on this branch's merge-base rather than inherited: 39 rows at
main = 8f1d995ef447…— the card body's table says 46 and is seven rows stale.MarkdownSchema— row deleted (39 → 38). One authority remains.KanbanCard/KanbanColumn— rows shrunk from four sites to two.reconcilecompares sites, so leaving the four-site rows would have failed the
staledirection. The two survivors are the cross-package pair.
KanbanSchema— unchanged.Ablation — direction predicted in writing before each run
Both legs mutate the fact, never the assertion, under
trap … EXIT INT TERMwithabsolute paths from
git rev-parse --show-toplevel, restoring withgit checkout HEAD -- ABSOLUTE_PATH.Leg A — predicted: restoring the deleted
MarkdownSchemarow while the collisionis gone reds in the
staledirection. Observed exactly that:MarkdownSchema — no longer collides at:, exit 1, 1 failed / 10 passed.Leg B — predicted: reverting the source convergence while the row stays deleted
reds in the
freshdirection. Observed exactly that:MarkdownSchema — a NEW colliding name, exit 1, 1 failed / 10 passed.Mutation proved on disk both times (anchored count moved 0→1 / re-export 1→0, and
git hash-objectmoved off theHEADblob); restore proved both times (hash back tothe
HEADblob andgit diff HEADempty). Together the two legs show the gate isnon-vacuous in both of its directions for this change.
What is NOT resolved here, and why it is not a guess
The cross-package
KanbanCard/KanbanColumn/KanbanSchemacollision between@object-ui/typesand@object-ui/plugin-kanbanstays open.These are two genuinely different dialects, not a stale copy —
itemswhere the pluginsays
cards,labelswhere it saysbadges;KanbanSchemashares 3 keys of 7 and 19with
columnsrequired on one side and optional on the other. That is theActionSchemasignature, whose remedy is rename — and because the bottom-layerpackage cannot re-point at a plugin, a rename here means removing a published name
from one of the two packages.
The 2026-08-25 ruling named a cross-package authority for
FormField("the@objectstack/speccopy is the contract-side authority") but its Kanban clause isabout in-package multi-declarations. It does not say which package owns the bare
name here, so that authority call is escalated on the card rather than made in a PR.
Measurement is in hand for whoever rules: the plugin's dialect is the live one —
authored corpus writes
cards13× againstitems1× (and that singleitemssite is the types copy's own demo file); every registered renderer (
kanban,kanban-ui,kanban-enhanced,object-kanban) consumes the plugin shape; and thetypes copy's distinguishing keys
draggable/onColumnAdd/onCardAddare read bynothing. Against that, renaming the
@object-ui/typestrio touches its barrel,registry.ts'sComponentSchemaMap, theComplexSchemaunion, the Zod mirror and itsparity test, plus two docs pages — a published-surface removal well outside this
dispatch's declared file surface.
#6155 is only partially addressed and stays open. It measured
KanbanCard/KanbanColumnas declared four times; after this diff they are declared twice. Theremaining two are the cross-package pair above.
Docs truth kept in step
content/docs/plugins/plugin-markdown.mdxasserted "Declared by@object-ui/plugin-markdown… the plugin ships its own copy of this name." This diffmakes that false, so the page is corrected in the same PR: it now names the declaring
package and file path (a checkable existence claim), records that
contentis requiredand why, and documents
sanitize/componentsas declared-but-unread rather thansilently listing them as behaviour.
Gates — exit codes captured by redirect BEFORE any pipe
scripts/__tests__/one-authority…+packages/{types,plugin-kanban,plugin-markdown}/10340f225type-check, the 3 affected packagesScope: 3 of 47 workspace projects(not a zero-match)check:control-bytescheck:doc-snippetscheck:doc-fencescheck:doc-typesdocs:check-linkscheck:self-importcheck:phantom-depscheck:esm-specifierscheck-changeset-presence(direct)check-changeset-no-major/-fixed/-overwriteCoverage measured, not assumed.
check:doc-snippetswas first run against anunbuilt tree and returned exit 2 —
PRECONDITION NOT MET, which the gate itselflabels "I could not run, NOT I ran and found errors." It was re-run after building the
32 packages its own
--build-filternames; only that second run is quoted. And theedited page is genuinely in the covered set:
content/docs/plugins/plugin-markdown.mdxappears 0 times in the script's declared 43-page ungated list.
Declared narrowing.
check:readme-exportsexits 1 on this tree, and it isnot attributable to this change: every one of its findings is
type entry ./dist/index.d.ts is not on disk — run pnpm build firstfor packages thisbranch never touches. After building the two edited plugins, judged self-imports rose
85 → 91 with 0 wrong-path and 0 fabricated, and no failure line names either
edited README. The repo-wide
pnpm lintwas likewise not run here; CI runs the fullfarm regardless.
Generated by Claude Code