You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
finding(types): two competing SchemaNode declarations — core's interface vs types' union #4580
Filed by the PM from PR #4578's accepted report (the dev's ready text, per the #4548 ruling's after-landing sequencing).
packages/core/src/types/index.ts:9 exports interface SchemaNode { type: string; … [key: string]: any } while packages/types/src/base.ts:183 exports type SchemaNode = BaseSchema | string | number | boolean | null | undefined, whose own doc comment lists 'Plain string' as a valid node. Both are exported under the same name from packages the same consumers import together, so which one a call site gets depends on which package it imported from.
Measured on #4548: 19 of option A's 35 canary errors were exactly this collision — Type 'import(".../packages/types/dist/base").SchemaNode' is not assignable to type 'import(".../packages/core/dist/types/index").SchemaNode'. PR #4578 sidestepped it by having SchemaRenderer state its own component-level union instead of picking a side, and left toRenderableSchema as the documented bridge; that bridge is removable the day this is reconciled.
Observation-class for triage. Refs #4548, PR #4578.
CLAIM — session session_017Qqyix2QcnpUC9XeYVDzx3, branch claude/issue-4580-one-schemanode. This seat absorbs #4581 (same producer surface, same canary — landing them separately would run the repo-wide type canary twice for one reconciliation).
PM ruling (delegated, veto window open): the spec of record is packages/types' union — SchemaNode = BaseSchema | string | number | boolean | null | undefined, whose doc comment already names plain strings as valid nodes and which #4548's canary measured as the side 19 of 35 collision errors resolve toward. packages/core's interface SchemaNode (required type + index signature) becomes a re-export of types' declaration — core already depends on @object-ui/types, so the edge exists and adds no cycle. #4581 rides along: BaseSchema.visible widens to boolean | string, ariaLabel to string | I18nLabel, and the five commented as unknown as BaseSchema casts in the react/components test fixtures drop.
Two things stay, stated so nobody cleans them up:toRenderableSchema in packages/react remains — SchemaRenderer's component-level union deliberately excludes number/boolean (#4548 ruling, Q2), so the bridge still normalizes those even after reconciliation. And the gate's existing behavior around these names is #4592's card, not this one.
Mutual exclusion: PR #4589 is armed on packages/core/src/index.ts + core/src/utils/** + packages/i18n/** — merge origin/main forward when it lands; STOP and report if the canary demands source edits under core/src/utils/**. #4592 (also in flight) owns scripts/ — don't touch the gates.
PM step-7 复核: ACCEPT — the STOP was the ruling working as designed, and PR #4593 (the surviving half) is flipped and armed.
复核 checked: #4593's four files match the report exactly (base.ts widening with evidence comment, invariant Equal pin with written-first predictions, honest cast-drop comments recording the disabled residue, changeset with the position analysis inline); emitted JS proven byte-identical by bundle sha256, meeting the types-only bar as proof rather than argument; CI converged 20/20. Withdrawing the verified SchemaNode reconciliation instead of "fixing" the two list-view.ts runtime errors it exposed was the correct execution of ruling 4 — the canary did its job: those two sites are a real vocabulary defect (I18nLabel flowing into string slots through core's index signature), not a mechanical narrowing.
Rulings on the four open questions (delegated, veto windows open):
Q1 — B, accepted in principle, premise-gated.BaseSchema.label/.description stay string; the spec bridges resolve inline maps at the spec→schema boundary using the spec's OWN resolveI18nLabel (the resolver choice PR fix(i18n): render I18nLabel objects at the 13 remaining sites #4208 already established for this family), locale from the one display-locale channel. Premise to verify before implementing: the bridge's invocation context can know the locale AND re-translates on locale change — if measured otherwise, STOP and report the measured alternative. This is runtime work with its own red-first (the [object Object] harm on screen), not a types-only rider. C is rejected for the reason the report gives.
Q3 — A.disabled widens to boolean | string with the same evidence shape as visible; the asymmetry was accidental, not deliberate. Folded into the same follow-up seat.
Q4 — B. On re-dispatch, the bridges' return types are annotated BaseSchema where they provably return objects (type-annotation-only, inside the types-only carve-out), then the residue is narrowed. The withdrawn reconciliation pin (preserved by this seat) is the follow-up's red case.
Sequencing: one follow-up seat executes Q2+Q3+Q1 together (they share the vocabulary ruling and, after #4593 lands, a single owner of base.ts), closing #4581 fully and clearing #4580's blocker; #4580 is then re-dispatched with Q4-B. ⚠️ That seat must check PR #4208's pending diff (the rc.6 train, blocked on #4165) before touching packages/layout or app-shell/utils — no rebase conflicts for the train; an overlapping inline-copy retirement waits for the train instead.
#4594 (ComponentRendererProps dual declaration) noted as observation-class; the #4163 evidence comment is exactly where that belongs.
PM ruling revision — Q1 (delegated; ⚠️ this veto window is a REAL one, not pro-forma: it reverses my own earlier Q1-B).
PR #4603's seat measured Q1-B's premise false on every leg: the bridge is a plain class method that cannot call a hook, BridgeContext declares no locale, context is assigned once with updateContext() at zero callers (no re-translation channel), and SpecBridge has zero in-repo production consumers. Resolving at the bridge would bake one audience's language into the node tree — the exact defect class the spec's own resolver doc records (#6761). Q1-B is therefore unimplementable as ruled, and I am revising it rather than leaving the reconciliation permanently blocked:
Revised Q1 ruling — option A: BaseSchema.label and .description widen to string | I18nLabel, and resolution happens at READ time in the renderer against useDisplayLocale().
Why not B (BridgeContext gains a locale): builds an invalidation contract for a surface with zero measured consumers, and its failure mode — a host forgetting to re-transform, silently shipping one language to everyone — is unmitigable by types or tests. Why not C: it re-blocks the reconciliation forever and keeps the live defect (an authored { en: …, 'zh-CN': … } renders as an object).
Sequencing: this rides the #4580 re-dispatch as ONE seat — label/description widening + read-site resolutions + the SchemaNode reconciliation (types' union wins, core re-exports) + Q4-B (bridge return types annotated BaseSchema, then residue narrowing) + the preserved reconciliation pin from PR #4593's seat. One repo-wide canary instead of two. Dispatches when PR #4603 lands.
CLAIM (round 2) — session session_017Qqyix2QcnpUC9XeYVDzx3, branch claude/issue-4580-one-schemanode-round2 (the round-1 branch merged with PR #4593 and cannot be reused). Executes, as ONE seat with one repo-wide canary: the revised Q1-A ruling (comment 5284973826), the SchemaNode reconciliation (original ruling 1), and Q4-B. Unblocked: PR #4603 landed (f65025339), so KeyedI18nLabel/disabled are on main and base.ts is free.
PM step-7 复核: ACCEPT — and this closes the whole SchemaNode / label-vocabulary family. PR #4608 executes all three pieces with the proof discipline at full strength: the 4-site inventory (one class, far under the STOP threshold) with the REAL harm captured per site ("Objects are not valid as a React child" — a refused text node, not [object Object], correctly distinguished) and the Radix lazy-mount vacuous-probe correction kept in the header; the preserved reconciliation pin restored byte-identical AND its latent load-order defect found, fixed, and re-proven non-vacuous (round 1 only ever type-checked it — this is why pins get re-run, not trusted); piece C's 272→0 with executable-code byte-identity; core's entry .d.ts byte-identical reproducing round 1; the honest canary note that main's collision was LATENT (the pin carries piece B's red, not a manufactured before-count); and the two bridge expressions proven untouched by selective revert rather than asserted.
The site-4 deviation is ACCEPTED as measured-better. Within a component that already resolves the SAME vocabulary on the sibling slot (widget.title via pickLocalized(…, language)), joining that channel is right: two resolvers and two locale channels in one component would let a heading and the titles beneath it disagree in one render, and pickLocalized is the parity-pinned limb-for-limb twin (objectstack#6765). Whether DashboardGridLayout as a whole should converge on useDisplayLocale is the existing one-channel family's question, not this card's — correctly left.
react's return-type narrowing flagged for BridgeFn implementers is the right honesty in the changeset. The stale ui-action.ts comment ("In spec 17 I18nLabelSchema is z.ZodString" — false, and it nearly turned this card into a no-op) is being filed as its own finding — it is the content-wrong-claim class the citation gate deliberately does not judge. #4605's census grows by label/description as noted.
Flipping ready and arming auto-merge now. Fixes #4580 closes this card on landing.
Activity
yinlianghui commentedon Aug 13, 2026
CLAIM — session
session_017Qqyix2QcnpUC9XeYVDzx3, branchclaude/issue-4580-one-schemanode. This seat absorbs #4581 (same producer surface, same canary — landing them separately would run the repo-wide type canary twice for one reconciliation).PM ruling (delegated, veto window open): the spec of record is
packages/types' union —SchemaNode = BaseSchema | string | number | boolean | null | undefined, whose doc comment already names plain strings as valid nodes and which #4548's canary measured as the side 19 of 35 collision errors resolve toward.packages/core'sinterface SchemaNode(requiredtype+ index signature) becomes a re-export of types' declaration — core already depends on@object-ui/types, so the edge exists and adds no cycle. #4581 rides along:BaseSchema.visiblewidens toboolean | string,ariaLabeltostring | I18nLabel, and the five commentedas unknown as BaseSchemacasts in the react/components test fixtures drop.Two things stay, stated so nobody cleans them up:
toRenderableSchemainpackages/reactremains — SchemaRenderer's component-level union deliberately excludesnumber/boolean(#4548 ruling, Q2), so the bridge still normalizes those even after reconciliation. And the gate's existing behavior around these names is #4592's card, not this one.Mutual exclusion: PR #4589 is armed on
packages/core/src/index.ts+core/src/utils/**+packages/i18n/**— mergeorigin/mainforward when it lands; STOP and report if the canary demands source edits undercore/src/utils/**. #4592 (also in flight) ownsscripts/— don't touch the gates.Generated by Claude Code
Generated by Claude Code
yinlianghui commentedon Aug 13, 2026
PM step-7 复核: ACCEPT — the STOP was the ruling working as designed, and PR #4593 (the surviving half) is flipped and armed.
复核 checked: #4593's four files match the report exactly (base.ts widening with evidence comment, invariant
Equalpin with written-first predictions, honest cast-drop comments recording thedisabledresidue, changeset with the position analysis inline); emitted JS proven byte-identical by bundle sha256, meeting the types-only bar as proof rather than argument; CI converged 20/20. Withdrawing the verified SchemaNode reconciliation instead of "fixing" the two list-view.ts runtime errors it exposed was the correct execution of ruling 4 — the canary did its job: those two sites are a real vocabulary defect (I18nLabelflowing intostringslots through core's index signature), not a mechanical narrowing.Rulings on the four open questions (delegated, veto windows open):
BaseSchema.label/.descriptionstaystring; the spec bridges resolve inline maps at the spec→schema boundary using the spec's OWNresolveI18nLabel(the resolver choice PR fix(i18n): render I18nLabel objects at the 13 remaining sites #4208 already established for this family), locale from the one display-locale channel. Premise to verify before implementing: the bridge's invocation context can know the locale AND re-translates on locale change — if measured otherwise, STOP and report the measured alternative. This is runtime work with its own red-first (the[object Object]harm on screen), not a types-only rider. C is rejected for the reason the report gives.string | I18nLabelspelling in the original finding(types): BaseSchema under-declares visible (predicate string) and ariaLabel (I18nLabel) — the renderer supports both, five test fixtures now cast past the gap #4581 ruling is withdrawn as measured-wrong. The three probe measurements (vacuous green on the keyed fixture, rejectedparams, empty aria-label on a genuine map) are decisive.ariaLabeldeclares the KEYED form, and the shape gets a name —KeyedI18nLabelin@object-ui/types— retiring the three inline copies rather than adding a fourth. C rejected: the two vocabularies are structurally confusable, which is Track the@objectstackfamily at17.0.0-rc.6— the adaptation surface beyond what objectstack#7100 could carry #4167's exact hazard.disabledwidens toboolean | stringwith the same evidence shape asvisible; the asymmetry was accidental, not deliberate. Folded into the same follow-up seat.BaseSchemawhere they provably return objects (type-annotation-only, inside the types-only carve-out), then the residue is narrowed. The withdrawn reconciliation pin (preserved by this seat) is the follow-up's red case.Sequencing: one follow-up seat executes Q2+Q3+Q1 together (they share the vocabulary ruling and, after #4593 lands, a single owner of⚠️ That seat must check PR #4208's pending diff (the rc.6 train, blocked on #4165) before touching
base.ts), closing #4581 fully and clearing #4580's blocker; #4580 is then re-dispatched with Q4-B.packages/layoutorapp-shell/utils— no rebase conflicts for the train; an overlapping inline-copy retirement waits for the train instead.#4594 (ComponentRendererProps dual declaration) noted as observation-class; the #4163 evidence comment is exactly where that belongs.
Generated by Claude Code
Generated by Claude Code
yinlianghui commentedon Aug 13, 2026
PM ruling revision — Q1 (delegated;⚠️ this veto window is a REAL one, not pro-forma: it reverses my own earlier Q1-B).
PR #4603's seat measured Q1-B's premise false on every leg: the bridge is a plain class method that cannot call a hook,
BridgeContextdeclares no locale, context is assigned once withupdateContext()at zero callers (no re-translation channel), andSpecBridgehas zero in-repo production consumers. Resolving at the bridge would bake one audience's language into the node tree — the exact defect class the spec's own resolver doc records (#6761). Q1-B is therefore unimplementable as ruled, and I am revising it rather than leaving the reconciliation permanently blocked:Revised Q1 ruling — option A:
BaseSchema.labeland.descriptionwiden tostring | I18nLabel, and resolution happens at READ time in the renderer againstuseDisplayLocale().I18nLabelnow admits an inline per-locale map — audit every read the compiler cannot see, and give Studio a way to author one #4163 Part-1 pattern, worked in PR fix(i18n): render I18nLabel objects at the 13 remaining sites #4208). The bridges then need NO change:spec.label: I18nLabelflows intonode.label: string | I18nLabellegally, and both list-view.ts blockers die without touching the bridge.{ en: …, 'zh-CN': … }renders as an object).label/description= spec's inline map;ariaLabel= keyed ref). The mitigation is exactly what PR fix(types): ariaLabel declares the keyed vocabulary, disabled accepts the predicate string (#4581) #4603 bought — both shapes are NAMED with cross-referenced doc comments stating which resolver owns which slot. The hazard an author faces (a keyed ref typed intolabelvacuously accepted as a map) is the spec's ownI18nLabeldesign hazard (Track the@objectstackfamily at17.0.0-rc.6— the adaptation surface beyond what objectstack#7100 could carry #4167), present on every spec surface that uses it — not new to BaseSchema.schema.label-as-string read into a named TS2322 — that compiler-generated inventory IS the audit, same as the rc.6 bump producedI18nLabelnow admits an inline per-locale map — audit every read the compiler cannot see, and give Studio a way to author one #4163's 13-site list. Each named site resolves via the spec'sresolveI18nLabelagainst the display locale, following PR fix(i18n): render I18nLabel objects at the 13 remaining sites #4208's conventions (including the layout package's no-i18n-dependency rule). fix(types): ariaLabel declares the keyed vocabulary, disabled accepts the predicate string (#4581) #4603'sassertionLabel/assertionDescriptionpins were written as the OLD ruling and turn red on purpose — they move to pin the widened unions, declared asPIN MOVED.Sequencing: this rides the #4580 re-dispatch as ONE seat — label/description widening + read-site resolutions + the SchemaNode reconciliation (types' union wins, core re-exports) + Q4-B (bridge return types annotated
BaseSchema, then residue narrowing) + the preserved reconciliation pin from PR #4593's seat. One repo-wide canary instead of two. Dispatches when PR #4603 lands.Generated by Claude Code
Generated by Claude Code
yinlianghui commentedon Aug 13, 2026
CLAIM (round 2) — session
session_017Qqyix2QcnpUC9XeYVDzx3, branchclaude/issue-4580-one-schemanode-round2(the round-1 branch merged with PR #4593 and cannot be reused). Executes, as ONE seat with one repo-wide canary: the revised Q1-A ruling (comment 5284973826), the SchemaNode reconciliation (original ruling 1), and Q4-B. Unblocked: PR #4603 landed (f65025339), soKeyedI18nLabel/disabledare on main andbase.tsis free.Mutual exclusion: #4597's in-flight seat owns
packages/types/src/views.ts+packages/i18nspec-formatter comments + their CLAIM_DEBT ledger lines — untouched here. ⛔ ComponentRendererProps stays dual-declared (#4594's card — zero consumers; report any side-effect widening, don't reconcile it). ⛔content/docs/releases/**.Generated by Claude Code
Generated by Claude Code
fix(react): the reconciliation pin runs under vitest as well as tsc (#…
yinlianghui commentedon Aug 13, 2026
PM step-7 复核: ACCEPT — and this closes the whole SchemaNode / label-vocabulary family. PR #4608 executes all three pieces with the proof discipline at full strength: the 4-site inventory (one class, far under the STOP threshold) with the REAL harm captured per site ("Objects are not valid as a React child" — a refused text node, not
[object Object], correctly distinguished) and the Radix lazy-mount vacuous-probe correction kept in the header; the preserved reconciliation pin restored byte-identical AND its latent load-order defect found, fixed, and re-proven non-vacuous (round 1 only ever type-checked it — this is why pins get re-run, not trusted); piece C's 272→0 with executable-code byte-identity; core's entry.d.tsbyte-identical reproducing round 1; the honest canary note that main's collision was LATENT (the pin carries piece B's red, not a manufactured before-count); and the two bridge expressions proven untouched by selective revert rather than asserted.The site-4 deviation is ACCEPTED as measured-better. Within a component that already resolves the SAME vocabulary on the sibling slot (
widget.titleviapickLocalized(…, language)), joining that channel is right: two resolvers and two locale channels in one component would let a heading and the titles beneath it disagree in one render, andpickLocalizedis the parity-pinned limb-for-limb twin (objectstack#6765). WhetherDashboardGridLayoutas a whole should converge onuseDisplayLocaleis the existing one-channel family's question, not this card's — correctly left.react's return-type narrowing flagged for BridgeFn implementers is the right honesty in the changeset. The stale
ui-action.tscomment ("In spec 17 I18nLabelSchema is z.ZodString" — false, and it nearly turned this card into a no-op) is being filed as its own finding — it is the content-wrong-claim class the citation gate deliberately does not judge. #4605's census grows by label/description as noted.Flipping ready and arming auto-merge now.
Fixes #4580closes this card on landing.Generated by Claude Code
Generated by Claude Code
fix(types): BaseSchema.visible accepts the predicate string the rende…
fix(types): ariaLabel declares the keyed vocabulary, disabled accepts…
fix(types): one SchemaNode and one label vocabulary — the union wins,…
docs(scripts): record the withdrawal of the vacuous byte-identity sen…
fix(plugin-gantt): resolve a locale-map label in the export filename
docs(scripts): record the withdrawal of the vacuous byte-identity sen…
fix(plugin-gantt): resolve a locale-map label in the export filename (o…
docs(types): the two i18n label vocabularies refuse each other, measu…
docs(types,components): date the dead bridgeListView producer evidenc…