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 }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
RadioField 完全不读 multiple —— spec 允许 radio 带 multiple: true,实际静默渲染成单值 radio 组 #4015
PM seat note (2026-08-24, domain:ui takeover). This card is the decision anchor for the maintainer's 2026-08-22 Option-C ruling, not a unit of dispatchable work. The objectui residue the ruling named (the field-type-alias.ts docblock) landed as PR #5907. The enforcement itself lands upstream at objectstack#11437 (PR #11560, entering the merge queue per the spec seat's relay of 2026-08-24T03:28Z). The card was carrying pm:queue + a stale assignee with no remote branch, which advertised it as dispatchable when nothing here is dispatchable; corrected to pm:blocked with the upstream dependency named above so the unlock scan can reach it.
Findings triage — held (finding stays; first grade).
Premise verified on objectui origin/main @ 11c1e71: packages/fields/src/widgets/RadioField.tsx has zeromultiple read points (counter-probed: 19 radio/Radio hits in the same file, so the scan reads it), while field-type-alias.ts:148-152 moves multiple:true types only when MULTI_VALUE_FORM_TYPES has a second widget — and its own test (field-type-alias.multiple.test.ts:70) lists radio in UNMOVED_MULTI_CAPABLE, i.e. spec-legal multiple with nowhere to go. The card's fact pattern holds exactly.
Why held rather than promoted or escalated: the fix direction is an unresolved contract fork the filer correctly declined to pick — map radio + multiple to checkboxes (renderer-layer) vs narrow the spec so radio refuses multiple (acceptance-surface change, domain:spec red-line territory). This is the same "published authoring surface accepts a value the runtime silently ignores" class as ui#3985 (mobileNavMode), which is already in the decision box; pushing a second zero-victim vocabulary card in front of the maintainer before that one is ruled would duplicate the question without adding evidence. No occurrence of radio + multiple in real metadata has been measured, and radio's labelling: 'group' means no a11y hole rides on it.
Restart conditions: (1) the ui#3985 ruling lands — its enforce-or-remove direction for inert authoring capabilities is precedent-setting for this card; re-grade in its light; (2) a real metadata instance of { type: 'radio', multiple: true } is reported (a user then holds a silently-wrong control); (3) any PR touches spec MULTI_CAPABLE_TYPES or RadioField.tsx's value model — resolve then at marginal cost.
Findings triage (re-verify @ origin/mainda81093): HOLD — RadioField.tsx still has zero multiple hits, so the silent single-value rendering stands. The fork (map to checkboxes vs narrow the spec so radio refuses multiple) is a contract-direction call, and no measurement yet shows the shape in real metadata. finding stands.
Findings triage (re-verify @ origin/maine1ade8f): HOLD — packages/fields/src/widgets/RadioField.tsx still has zeromultiple hits (counter-probed against options, 8 hits in the same file, so the scan is live rather than mis-pathed), and the silent single-value rendering therefore stands unchanged. The fork is still a contract-direction call — map multiple: true to a checkbox group, or narrow the spec so radio refuses multiple — and there is still no measurement showing the shape in real metadata, which is what would decide it. finding stands.
Findings triage (cadence re-verify @ origin/main5e2e9fa): HOLD as finding. packages/fields/src/widgets/RadioField.tsx still contains zero occurrences of multiple (counter-probed: the same grep finds multiple in sibling widgets FileField/GridField/ImageField, so the scanner is fine) — a spec-legal radio + multiple: true still silently renders a single-value group. Declared≠enforced on the widget's read side, dormant until an author actually writes that combination; promote with any RadioField touch or a fields-parity sweep.
Findings cadence re-check @ origin/main6d01319: HOLD — finding stands, premise survived a file edit.
RadioField.tsx still has zero multiple read points (counter-check: the file matches 13 radio hits, so the scanner reads it) — notably the premise survived e7663f2 (#4244), which touched the widget family without adding the branch. The fork is still undecided and still two-directional: mapping radio + multiple to checkboxes is a renderer fix here, narrowing MULTI_CAPABLE_TYPES is an objectstack spec acceptance-surface change (domain:spec red-line territory over there) — and nobody has yet measured whether the shape occurs in real metadata, which is the fact that would pick the direction. Restart condition: promote (or escalate cross-repo) once any measurement shows {type:'radio', multiple:true} authored in the wild, or when #3986's MULTI_VALUE_FORM_TYPES table is next revised.
Findings centralized round, batch 2 (triage seat, 2026-08-17): state transition finding → pm:on-hold per the 2026-08-13 status-semantics ruling; type Bug (declared≠enforced: spec-legal radio + multiple: true silently renders single-value). The standing grade is unchanged and absorbed: the fork (map to checkboxes in the renderer vs narrow spec MULTI_CAPABLE_TYPES — the latter an accept-set change in packages/spec/src/data/field-value.zod.ts:204) waits on the #3985 precedent, which is now pm:queue (verified open this round).
Restart-when: closed #3985 — re-grade this card in the light of that ruling's enforce-or-remove direction. Trigger files: packages/fields/src/widgets/RadioField.tsx, spec field-value.zod.ts (MULTI_CAPABLE_TYPES).
On-hold daily batch (triage seat, round of ~14:47Z fire): Restart-when fired — #3985 closed completed 08-17 (PR #4921 merged), establishing the enforce-or-remove precedent this card was parked for. pm:on-hold → pm:queue: re-grade RadioField's multiple under that precedent (the claimer reads #3985's landed shape first; if the precedent's sweep already covered this key, a with-evidence zero-work close is the right delivery).
Maintainer ruling recorded (2026-08-22, decision-inbox digest with the skills seat, session_01ApyDuQY2fkunMCqXiqvBhR)
The full decision inbox (46 cards) was presented with per-card four-axis recommendations; the maintainer accepted the batch, verbatim: 「接受所有」.
Ruled: Option C — reject at the entrance: refuse radio + multiple: true at the schema/publish layer with a diagnostic prescribing the correctly-named multi-choice types (checkboxes/multiselect/tags), while leaving MULTI_CAPABLE_TYPES and isMultiValueField untouched so at-rest data keeps its read path and no stored-shape migration is paid. Option A (rendering the combination as checkboxes) is explicitly not taken. Attached condition: the ruling flips to B (narrow the spec sets themselves, with a migration path) if customer stock is found — i.e. deployed tenant metadata actually carrying {type: 'radio', multiple: true} with stored data, in which case entrance rejection alone would strand them and B's migration becomes required. The implementation lands on the spec side (objectstack, domain:spec lane); this card is the record and decision anchor. The field-type-alias.ts:28-37 comment omission of radio is corrected by the landing fork.
State: needs-user-decision removed → pm:queue (ready for dispatch).
{
"issue": 4015,
"status": "needs_decision",
"branch": null,
"pr": null,
"premise_still_valid": true,
"summary": "No code written, no worktree, no branch, no PR — and that is the delivery. The card's FACT still holds at objectui origin/main 515f171f3 (RadioField.tsx greps 0 for `multiple`, control probe 23 for `RadioGroup|value`), but the DISPATCH's premise does not: the fork this dispatch asked me to decide by measurement was already ruled by the maintainer on 2026-08-22 (comment 5377369219) — Option C, refuse `radio + multiple: true` at the schema/publish layer, Option A (render as checkboxes) explicitly not taken, and 「The implementation lands on the spec side (objectstack, `domain:spec` lane)」, with this card kept as 'the record and decision anchor'. I therefore filed the ruled implementation upstream as objectstack-ai/objectstack#11437 (unassigned, `domain:spec`) — pre-searched: 450 objectstack issues created since the ruling, zero about this — and stopped rather than shipping the renderer-side dev warning this dispatch ordered, because that shape was never weighed by the maintainer and the dispatch's own landing instruction (a `Fixes`-style closing keyword aimed at THIS card) would CLOSE the anchor card while the ruled fix is unlanded. Two further blockers for PM: (1) the ⚠️ claim-precedence rule trips literally — #4015 carries a claim comment from a different session id (`session_012u2pRjcqAYtoEjgr3wwhnK`, 2026-08-21), though the evidence says that claim is SPENT (it returned its own os-dev-report, its PM closed the round, the maintainer then re-queued the card, and `git ls-remote` shows zero `claude/issue-4015*` branches), so I did not assign myself; (2) the ruling's named objectui residue — correcting `field-type-alias.ts:28-37` — is fenced out of this dispatch (⛔ do not touch MULTI_VALUE_FORM_TYPES), so it needs an explicit fence widening, not a quiet edit.",
"tests": "No diff exists, so no build/test/gate is owed and none was run; every measurement below is read-only against fetched refs (objectui origin/main 515f171f3, objectstack origin/main 5d163792c). MEASUREMENT 1, as this dispatch specified it (read MULTI_VALUE_FORM_TYPES and the comment explaining why radio is not in it): it does NOT settle the shape. `field-type-alias.ts:25-45` explains why the TABLE is narrow — only `select` needs a different widget id, for a `labelling`/IDREF reason — and asserts at :30-32 'but every other member renders both arities INSIDE one widget: `LookupField`, `FileField` and `ImageField`', which silently OMITS radio and thereby implies radio branches internally (it does not). So that docblock records no decision that radio is single-value; it records a false premise. What DOES settle option A out inside this repo is a different landed artifact I found while measuring: `packages/app-shell/src/utils/paramValueShape.ts:142` declares `radio: { base: 'string', cardinality: 'scalar', note: 'Single option value string.' }`, where `'scalar'` is defined at :63-70 as 'always a single `base` value', pinned by a drift test at `paramValueShape.test.ts:93` (`['radio -> string', { type: 'radio' }, 'string']`) — rendering checkboxes would make the widget emit `string[]` against a machine-checked contract saying `string`. MEASUREMENT 2 (occurrence in real metadata), re-measured rather than relayed: a walk of BOTH repos (node_modules/.git/dist/build/coverage excluded) scanned 10,977 files and found 21 `type: 'radio'` declarations — the control probe proving the scanner reads real declarations — and 0 carrying `multiple: true`; scan exit code captured directly (SCAN_EXIT=0, no pipe); the scanner is kept at /tmp/claude-0/-home-user/68ad4d1a-c111-591c-9633-f2fb64fa1803/scratchpad/issue-4015/scan.py for re-run. The single window match was `select + multiple: true` on the line ADJACENT to a radio entry in objectui `paramValueShape.test.ts:93`, i.e. a false positive of the +/-6-line window, not a radio occurrence. UPSTREAM STATE: ruling C has not landed — objectstack `field-value.zod.ts:204-206` still lists radio in MULTI_CAPABLE_TYPES (expected under C, which leaves it alone) and no schema/publish refusal exists; `field.zod.ts:818`'s published `multiple` description still reads 'Applicable for select, lookup, file, image' with radio absent. PRE-FILING SEARCH for #11437: paged the objectstack issues API (`state=all`, `since=2026-08-22`), 710 fetched, 450 created after the ruling timestamp, 1 title/body radio mention (#11430, a driver-sql varchar card) — no existing upstream card for this ruling. Post-filing read-back of #11437 confirms body survived the sanitizer (4,958 bytes; 「接受所有」, `paramValueShape.ts:142`, the session-URL footer and the `objectstack-ai/objectui#4015` back-link all present). Closing-keyword audit on that body: no fix/close/resolve token within 40 chars of any issue number. Control-byte audit on every file I authored: clean (`grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` -> no match).",
"open_questions": [
{
"question": "The 2026-08-22 maintainer ruling puts enforcement at the spec ENTRANCE (objectstack#11437) and says the implementation lands 'not here'. Does objectui ALSO ship the renderer-side deduped dev warning this dispatch ordered — a shape the maintainer's decision inbox never saw — and if objectui ships anything, is it instead the docblock correction the ruling explicitly names as owed?",
"options": [
"A - Ship the dispatched warning: a deduped warnOnce in RadioField.tsx naming the field and the mismatch, test seam, changeset. MUST be `Part of #4015`, never `Fixes` (the card is the ruling's anchor and its fix is unlanded). Note this repo already owns a matching precedent with a reset seam — `reportRetiredFieldType`/`resetRetiredFieldTypeReports` in @object-ui/core — so the convention cost is low.",
"B - Ship nothing in objectui. The entrance rejection in #11437 is the whole ruled change; #4015 stays open as the anchor until that lands.",
"C - Ship only the residue the ruling itself names: correct `packages/fields/src/field-type-alias.ts:28-37`, whose 'every other member renders both arities INSIDE one widget' sentence omits radio and is what made this gap invisible through 13 days of triage. Docs-only, no runtime surface, `Part of #4015`. Requires PM to widen this dispatch's fence, which currently forbids touching that surface."
],
"recommendation": "C, falling back to B — and explicitly NOT A as an independent deliverable. REAL BUSINESS NEED: measured at zero (10,977 files, 21 radio declarations, 0 with `multiple: true`), and once the entrance rejection lands the shape is not authorable at all, so a render-time warning would ship with no measured consumer; the only residual population is exactly the deployed-tenant stock case, which the ruling handles by flipping to Option B with a migration path, not by console output. LONG-TERM SOUNDNESS: contract-first says repair the producer, which is precisely what #11437 does; a second enforcement point in the consumer repo for a shape the producer refuses is duplicated enforcement across a repo boundary that someone must later retire as unreachable. Meanwhile the `field-type-alias.ts:28-37` sentence is a LIVE defect that actively misleads the next reader — it asserts a branch that does not exist — and correcting it costs no runtime surface. AI-AUTHORED METADATA HARD TO GET WRONG (decisive): the ruled diagnostic stops the author at publish time, in the authoring loop, prescribing the correct type; a `console.warn` fires at render, in a viewer's browser, long after authoring, and is invisible to an AI author's toolchain — shipping it risks reading as 'the mismatch is handled' while the authoring-time hole stays open until #11437 lands. STARTUP SCOPE DISCIPLINE: A adds published runtime surface (module-level dedup cache, test seam, changeset) to a shipped package for a shape with zero pull, on a card the maintainer designated a record anchor; C removes a falsehood and adds nothing. If PM still wants A, it is defensible only as defence-in-depth for pre-existing stock and must land AFTER #11437 with `Part of #4015`, never before and never as `Fixes`."
}
],
"out_of_scope_findings": [
"filed as objectstack-ai/objectstack#11437 (unassigned, `domain:spec`): the ruled Option C implementation — refuse `radio` + `multiple: true` at the spec schema/publish layer with a diagnostic prescribing checkboxes/multiselect/tags, leaving MULTI_CAPABLE_TYPES and isMultiValueField untouched. Not a side finding: it is THIS card's ruled fix, routed to the repo and lane the maintainer named.",
"NOT filed, deliberately: the `field-type-alias.ts:28-37` docblock omission of `radio`. The 2026-08-22 ruling assigns it to 'the landing fork', and the previous dev declined to file it for the same reason — a separate card would duplicate #4015's own residue. It is recorded here and in #11437 instead; see open question option C."
]
}
Delivery: no code, and the reason is the ruling above this comment, not a blocker
This dispatch asked me to decide the fork by two measurements and then ship a renderer-side
diagnostic. The fork was already ruled 13 hours before I was dispatched (2026-08-22,
comment 5377369219): Option C, entrance rejection in packages/spec; Option A (render
as checkboxes) explicitly not taken; implementation "lands on the spec side (objectstack, domain:spec lane)", with this card kept as "the record and decision anchor".
So the ruled work is now filed where the maintainer put it: objectstack-ai/objectstack#11437 - unassigned, domain:spec, quoting the ruling verbatim,
carrying the evidence table, the flip-to-B condition, and a back-link that deliberately uses no closing keyword so this anchor card cannot be shut from over there.
The two ordered measurements, and what they actually said
Measurement 1 does not settle it - and it is itself the defect. packages/fields/src/field-type-alias.ts:25-45 does not record a decision that radio is
single-value. It explains why the table is narrow (only select needs a different widget
id, for an IDREF/labelling reason) and then asserts at :30-32:
radio on the select branch and user storing like lookup - but every other member
renders both arities INSIDE one widget: LookupField, FileField and ImageField ...
radio is omitted from that enumeration, so the sentence implies radio branches internally.
It does not. That false premise is what kept this gap invisible through 13 days of triage -
and the ruling assigns its correction to "the landing fork".
What actually rules Option A out inside this repo is a different landed artifact, found
while measuring: packages/app-shell/src/utils/paramValueShape.ts:142 declares radio: { base: 'string', cardinality: 'scalar', note: 'Single option value string.' },
with 'scalar' defined at :63-70 as "always a single base value", pinned by a drift test
at paramValueShape.test.ts:93. Rendering checkboxes would emit string[] against a
machine-checked contract that says string.
Measurement 2, re-measured rather than relayed: both repos walked - 10,977 files
scanned, 21 type: 'radio' declarations found (control probe, so the scanner reads real
declarations), 0 carrying multiple: true. The single window match is select + multiple
on the line adjacent to a radio entry, i.e. a false positive of the window, not an occurrence.
Three things PM must rule before any objectui code lands on this card
Does objectui ship a renderer warning at all? The maintainer's inbox never saw that
option. On all four axes it loses to the entrance rejection (zero measured occurrences;
contract-first says repair the producer; a render-time console.warn is invisible to an AI
author's toolchain, which is exactly the axis the ruling turned on). See the open question.
The landing instruction is unsafe as written. A Fixes-style closing keyword aimed at
this card would shut the ruling's anchor while its implementation is unlanded. Any
objectui PR on this card must say Part of instead.
Claim precedence tripped, and I did not assign myself. This card carries a claim
comment from a different session id (session_012u2pRjcqAYtoEjgr3wwhnK, 2026-08-21).
The evidence says that claim is spent - it returned its own os-dev-report, its PM
closed the round, the maintainer then re-queued the card, and git ls-remote shows zero claude/issue-4015* branches on the remote - but the rule as written says stop, so I
stopped short of assigning rather than quietly overriding it. Nothing here is in flight.
PM ruling — C, and the dispatch that produced this question was mine and was wrong
What I got wrong
I dispatched this card asking the dev to decide by measurement a fork the maintainer had already ruled on 2026-08-22 (comment 5377369219). Worse, my brief stated the opposite of that ruling — "narrowing the spec is NOT this card's move" — when Option C puts the fix exactly there, on the spec side, in the domain:spec lane, with this card kept as the record and decision anchor. And my landing instruction (Fixes #4015) would have closed the anchor while the ruled fix is unlanded.
The dev wrote no code, filed the ruled implementation upstream as objectstack#11437 after a pre-filing dedupe search (450 issues created since the ruling, zero matching), and stopped. That was the delivery. Reading the ruling before implementing is what this seat asks of every dev; the dispatch that failed to do it was the PM's.
The ruling on the remaining question: ship C, not A
packages/fields/src/field-type-alias.ts:28-37 asserts "every other member renders both arities INSIDE one widget: LookupField, FileField and ImageField" — and omits radio, implying radio branches internally. It does not. That false premise is what kept this gap invisible through thirteen days of triage, and it misleads the next reader today regardless of when #11437 lands. Docs-only, no runtime surface, Part of #4015. The fence has been widened for exactly that prose and nothing else.
Not A (a render-time console.warn), for the reasons the dev measured rather than asserted:
Zero measured demand: a walk of both repos scanned 10,977 files, found 21type: 'radio' declarations (the control that proves the scanner reads real declarations) and 0 carrying multiple: true.
Once the entrance rejection lands, the shape is not authorable at all, so the warning would ship with no measured consumer.
A console.warn fires in a viewer's browser at render time, long after authoring and invisible to the toolchain an AI metadata author actually runs. The ruled diagnostic stops the author at publish time, inside the authoring loop. Shipping the warning would risk reading as "the mismatch is handled" while the authoring-time hole stays open.
It would add published runtime surface (module-level dedup cache, test seam, changeset) to a shipped package for a shape with zero pull.
If defence-in-depth is ever wanted for deployed-tenant stock, it lands after#11437 — never before, and never as a closing keyword on this card.
Two measurements worth keeping
The dispatch told the dev to let MULTI_VALUE_FORM_TYPES' docblock settle the shape. It cannot — it explains why the table is narrow (only select needs a different widget id, for a labelling/IDREF reason) and records no decision that radio is single-value. It records a false premise. Finding that the instrument does not measure what it was cited to measure, and saying so, is worth more than an answer.
What does settle Option A out inside this repo is a different landed artifact: packages/app-shell/src/utils/paramValueShape.ts:142 declares radio: { base: 'string', cardinality: 'scalar' }, pinned by a drift test at paramValueShape.test.ts:93. Rendering checkboxes would make the widget emit string[] against a machine-checked contract saying string. That is a stronger argument than the one the ruling itself used, and it was found by measuring rather than by reasoning from the card.
Claim precedence — the dev's reading is better than the rule I wrote, and the rule now says so
This card carries a claim comment from a different session id (session_012u2pRjcqAYtoEjgr3wwhnK, 2026-08-21), so the literal rule said "taken". The dev established, with evidence rather than assumption, that the claim is spent: that session returned its own os-dev-report, its PM closed the round, the maintainer subsequently re-queued the card, and git ls-remote shows zeroclaude/issue-4015* branches. Not self-assigning on a literal reading, and reporting the conflict, was right.
The seat's standing rule is amended: a claim comment from a different session id blocks, unless it is demonstrably spent — report returned, round closed, card re-queued, and no branch on the remote. The burden is evidence, not assumption, and the evidence goes in the new claim comment so the next reader does not re-derive it.
#4015 stays open as the ruling's anchor until objectstack#11437 lands.
File surface:packages/fields/src/field-type-alias.ts — docblock prose only (lines ~25-45). The table's membership and every runtime behaviour stay untouched; RadioField.tsx is explicitly NOT edited.
Scope, per the PM ruling on my needs_decision report (option C): correct the false
sentence at :30-32, which asserts "every other member renders both arities INSIDE one
widget: LookupField, FileField and ImageField" while omitting radio — implying radio
branches on arity internally, which it does not. Option A (rendering the combination as
checkboxes) is explicitly not taken, per the maintainer ruling of 2026-08-22. The ruled
enforcement itself lives at the spec entrance and is tracked upstream at objectstack-ai/objectstack#11437 (filed, not merged).
On the earlier claim from a different session (session_012u2pRjcqAYtoEjgr3wwhnK,
2026-08-21) — it is spent, established by evidence rather than assumption, recorded here
so nobody has to re-derive it:
it returned its own os-dev-report on this card (status: needs_decision, pr: null);
its PM posted the round-closing comment immediately after;
the maintainer then ruled and re-queued this card (needs-user-decision -> pm:queue, 2026-08-22), a state transition that post-dates the claim;
git ls-remote --heads origin 'refs/heads/claude/issue-4015*' returns zero branches, so nothing was ever pushed under it.
Nothing is in flight on this card. This card itself stays open as the maintainer's
decision anchor until the entrance rejection lands; my PR will say Part of only.
{
"issue": 4015,
"status": "done",
"branch": "claude/issue-4015-field-type-alias-docblock",
"pr": "https://github.com/objectstack-ai/objectui/pull/5907",
"premise_still_valid": true,
"summary": "Shipped PM-ruled option C and nothing else: a comment-only correction to the `MULTI_VALUE_FORM_TYPES` docblock in `packages/fields/src/field-type-alias.ts` (19 comment lines added, 3 rewritten, zero statements touched; `RadioField.tsx` untouched, table membership untouched). The docblock asserted that every `MULTI_CAPABLE_TYPES` member other than `select` renders both arities inside one widget — naming LookupField/FileField/ImageField and silently omitting `radio`, which reads `multiple` nowhere — and that false premise is what kept this gap invisible through 13 days of triage. The new prose names radio as the exception and pins it to evidence I hold rather than to a promise: `@object-ui/app-shell`'s `PARAM_VALUE_SHAPES` declares `radio` with `cardinality: 'scalar'` (drift-tested at `paramValueShape.test.ts:93`), so a checkbox rendering would emit `string[]` where that contract says `string`; the producer-side refusal is named as tracked in objectstack-ai/objectstack#11437 and explicitly described as filed, not landed. Draft PR #5907 opens with `Part of objectui#4015` — never a closing keyword — so the maintainer's decision anchor stays open; verified after opening that #4015 is still `state=open`. Option A and any render-time diagnostic were not shipped, per the ruling and PM's adoption of the four-axis argument. Empty-frontmatter changeset declares the change releases nothing, which the presence gate accepts as an explicit exemption rather than a workaround.",
"tests": "Union re-run AFTER the final commit, at `10b763b2d` (`git status --porcelain` empty at that point, so the union measured the tree that was pushed). Exit codes captured before any pipe (`cmd > log 2>&1; EXIT=$?`), and each claim below quotes the tool's own verdict line, never a bare `$?`. DEPS CLOSURE FIRST (new worktree): `pnpm --workspace-concurrency=2 --filter '@object-ui/fields^...' build` (flag before the filter) -> `os-verify-lock: VERDICT command-exit 0 · held the lock 53s · waited 0s`. TYPE-CHECK: `pnpm --filter @object-ui/fields type-check` -> `TYPECHECK_EXIT=0`, with the script name echoed in the log (`> @object-ui/fields type-check` / `tsc --noEmit && tsc -p tsconfig.test.json`), so this is not a zero-match pnpm filter reading as green; note objectui spells it `type-check` with a hyphen. VITEST: my first spelling (`pnpm --filter @object-ui/fields test -- <path>`) was REFUSED by the repo's own vitest guard with `VITEST_EXIT=1`, which printed the correct spelling; re-run as instructed from the repo root, `pnpm exec vitest run packages/fields/src/field-type-alias.multiple.test.ts --maxWorkers=2` -> `Test Files 1 passed (1)`, `Tests 10 passed (10)`, `VITEST_EXIT=0`. That file is the only test in the repo touching this module (grep over `packages/**` for `field-type-alias`). GATES at `10b763b2d`: `check-changeset-presence.mjs` -> `PRESENCE_EXIT=0`, `✅ 1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)` + `Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption`; `check-changeset-no-major.mjs` -> `NO_MAJOR_EXIT=0`, `✅ No changeset declares a 'major' bump.`; `check-changeset-fixed.mjs` -> `CHANGESET_FIXED_EXIT=0`, `✅ All workspace packages are in the changeset fixed group.`; `check-control-bytes.mjs` -> `CONTROL_BYTES_EXIT=0`, `✅ check-control-bytes: OK (scanned 4909 tracked text file(s); skipped 85 binary)`; plus a self-scan of both authored files with `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` -> no match. LINT: `pnpm --filter @object-ui/fields lint` -> `LINT_EXIT=0`, `✖ 870 problems (0 errors, 870 warnings)` across 152 files (the package's pre-existing baseline); the changed file itself reports 0 errors and 0 warnings via a single-file `--format json` run that returned exactly 1 result entry, proving the file is inside eslint's own population rather than ignored. DECLARED NARROWING: lint ran as the affected package's own full gate, not the repo farm (CI runs the farm once regardless); invariance evidence — the flat config sets no `parserOptions.project`/`projectService`, so linting is not type-aware and a comment-only edit cannot move any untouched file's verdict. NO ABLATION IS AVAILABLE and none was staged: the diff is comment-only, so there is no behaviour to mutate and deleting the prose would leave every gate green by construction — saying so plainly rather than theatre. The substitute is proof the new sentence is TRUE: `grep -c multiple packages/fields/src/widgets/RadioField.tsx` -> 0 (grep exit 1 = genuinely zero) with control probe `grep -cE 'RadioGroup|value'` -> 23 (exit 0), against LookupField 20, FileField 16, ImageField 7, and `UserField.tsx:58` delegating via `<LookupField {...(props as any)} field={fieldProp} />`. EDIT LANDED ON DISK, proven by anchored greps and not by an editor exit code: injected anchors ``radio` is the EXCEPTION`, `reads `multiple` nowhere`, `PARAM_VALUE_SHAPES`, `objectstack-ai/objectstack#11437` each 1 hit; removed anchors `other member renders both arities` and `— but every` each 0 hits; `git diff --numstat` = `19 3`, and a diff filter counting changed lines that are NOT comment prose returned 0. My first proof attempt used the anchor `radio is the EXCEPTION` WITHOUT backticks and read as 0 hits — a wrong-anchor false negative, corrected with `grep -F` on the real text rather than quietly re-running until something matched. HEAVY LEGS all went through `os-verify-lock.sh`; one 2-minute tool timeout killed a wrapped call mid-run (`exit 143`) — I checked `--status` immediately and confirmed the lock had been released (a different agent's worktree held it 11s later), and the killed leg's first command had already printed `LINT_EXIT=0`. TEARDOWN: node_modules removed, `git worktree remove` succeeded WITHOUT `--force` (exit 0), `git worktree list` shows no 4015 entry. `git stash` was never used.",
"open_questions": [],
"out_of_scope_findings": [
"filed earlier this session as objectstack-ai/objectstack#11437 (unassigned, `domain:spec`): the ruled option-C implementation — refuse an authored `radio` + `multiple: true` at the spec schema/publish entrance, leaving MULTI_CAPABLE_TYPES and isMultiValueField untouched. Still filed and unlanded; this PR's prose says so explicitly rather than claiming it as done.",
"NOT filed after a pre-filing search, deliberately: objectui has no `scripts/pm/os-verify-lock.sh`, so heavy legs here route to the container-global entry point of the same name in the sibling checkout. A scan of 400 recent objectui issues/PRs found 21 already citing that script, i.e. this is established practice across seats rather than a gap — filing it would have been noise."
]
}
Delivered: the ruling's named correction, and nothing else
Draft PR #5907 — Part of objectui#4015, deliberately not a closing keyword, so this
card stays open as the maintainer's decision anchor until the producer-side refusal lands.
Verified after opening: #4015 is still state=open.
Before (:30-32), false:
radio on the select branch and user storing like lookup - but every other member
renders both arities INSIDE one widget: LookupField, FileField and ImageField ...
After: radio is named as the exception - RadioField reads multiple nowhere, making it
the one MULTI_CAPABLE_TYPES member with no multi-arity renderer - pinned to @object-ui/app-shell's PARAM_VALUE_SHAPES (radio -> cardinality: 'scalar',
drift-tested), with the producer-side work named as filed, not landed
(objectstack-ai/objectstack#11437).
What the verification could and could not be
No ablation is available, and I did not stage one. A comment-only diff has no behaviour to
mutate, and deleting the prose leaves every gate green by construction. What I owe instead is
proof the new sentence is true, and proof the edit reached disk:
One correction worth recording: my first landed-on-disk probe used the anchor radio is the EXCEPTION without backticks and read as 0 hits on a file that had in fact
been edited - a wrong-anchor false negative. I fixed the anchor rather than quietly re-running
until something matched.
Notes the next seat will want
The repo's vitest guard refusedpnpm --filter <pkg> test -- <path> and printed the
correct spelling; the passing run is pnpm exec vitest run <path> from the repo root.
scripts/pm/os-verify-lock.sh does not exist in objectui; heavy legs went through the
container-global entry point in the sibling checkout. A scan of 400 recent objectui
issues/PRs found 21 already citing it, so that is established practice, not a gap - no
issue filed.
No doc-version-claims gate exists here; the underlying rule was honoured directly (no
version literal in the prose).
Relay from the objectstack spec seat (session_01T9cDbY2NBiVJWYx3BpWfH2) — two updates on the Option C ruling this issue anchors; this issue stays OPEN as the decision record.
The spec fork is implemented and in landing: objectstack PR test(plugin-form): object-form fields fixtures name fields as strings; the { name } row pins a stored read the declared member refuses (objectui#11550) #11560 (accepted by contract review, entering the merge queue) refuses an authored { type: 'radio', multiple: true } at FieldSchema's parse seam with a diagnostic prescribing checkboxes / multiselect / tags. Per the ruling, MULTI_CAPABLE_TYPES / isMultiValueField and the objectql fallback stay untouched (at-rest reads intact, no migration), and that untouched half is now pinned by a test. The flip-to-B condition was re-measured at the merge base: zero occurrences of the pair in either repo.
The correction the ruling assigns to the landing fork routes to this repo's lane: packages/fields/src/field-type-alias.ts:28-37 states "every other member renders both arities INSIDE one widget: LookupField, FileField and ImageField" — silently omitting radio, the one member with no renderer for its multi arity; that sentence is what made the gap invisible. With the entrance refusal landing spec-side, the truthful correction is: radio's multi arity is now refused at the spec entrance (objectstack#11437 / PR test(plugin-form): object-form fields fixtures name fields as strings; the { name } row pins a stored read the declared member refuses (objectui#11550) #11560), so the alias table's members-render-both-arities claim should name radio as the deliberate exception. Small prose fix, no behaviour — routing and scheduling are this repo's seat's call.
Reopen path: if a measurement ever shows radio + multiple: true metadata reaching RadioField from a pre-refusal spec pin (stored legacy metadata or an old published spec), reopen with that reading — that would be a new card's worth of scope, not this anchor's.
Activity
os-zhuang commentedon Aug 10, 2026
Findings triage — held (
findingstays; first grade).origin/main@11c1e71:packages/fields/src/widgets/RadioField.tsxhas zeromultipleread points (counter-probed: 19 radio/Radio hits in the same file, so the scan reads it), whilefield-type-alias.ts:148-152movesmultiple:truetypes only whenMULTI_VALUE_FORM_TYPEShas a second widget — and its own test (field-type-alias.multiple.test.ts:70) listsradioinUNMOVED_MULTI_CAPABLE, i.e. spec-legalmultiplewith nowhere to go. The card's fact pattern holds exactly.radio + multipleto checkboxes (renderer-layer) vs narrow the spec so radio refusesmultiple(acceptance-surface change,domain:specred-line territory). This is the same "published authoring surface accepts a value the runtime silently ignores" class as ui#3985 (mobileNavMode), which is already in the decision box; pushing a second zero-victim vocabulary card in front of the maintainer before that one is ruled would duplicate the question without adding evidence. No occurrence ofradio + multiplein real metadata has been measured, and radio'slabelling: 'group'means no a11y hole rides on it.{ type: 'radio', multiple: true }is reported (a user then holds a silently-wrong control); (3) any PR touches specMULTI_CAPABLE_TYPESorRadioField.tsx's value model — resolve then at marginal cost.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
claude commentedon Aug 10, 2026
Findings triage (re-verify @
origin/mainda81093): HOLD —RadioField.tsxstill has zeromultiplehits, so the silent single-value rendering stands. The fork (map to checkboxes vs narrow the spec so radio refusesmultiple) is a contract-direction call, and no measurement yet shows the shape in real metadata.findingstands.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
claude commentedon Aug 11, 2026
Findings triage (re-verify @
origin/maine1ade8f): HOLD —packages/fields/src/widgets/RadioField.tsxstill has zeromultiplehits (counter-probed againstoptions, 8 hits in the same file, so the scan is live rather than mis-pathed), and the silent single-value rendering therefore stands unchanged. The fork is still a contract-direction call — mapmultiple: trueto a checkbox group, or narrow the spec soradiorefusesmultiple— and there is still no measurement showing the shape in real metadata, which is what would decide it.findingstands.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 11, 2026
Findings triage (cadence re-verify @
origin/main5e2e9fa): HOLD asfinding.packages/fields/src/widgets/RadioField.tsxstill contains zero occurrences ofmultiple(counter-probed: the same grep findsmultiplein sibling widgetsFileField/GridField/ImageField, so the scanner is fine) — a spec-legalradio+multiple: truestill silently renders a single-value group. Declared≠enforced on the widget's read side, dormant until an author actually writes that combination; promote with any RadioField touch or a fields-parity sweep.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 11, 2026
Findings cadence re-check @
origin/main6d01319: HOLD —findingstands, premise survived a file edit.RadioField.tsxstill has zeromultipleread points (counter-check: the file matches 13radiohits, so the scanner reads it) — notably the premise survived e7663f2 (#4244), which touched the widget family without adding the branch. The fork is still undecided and still two-directional: mappingradio + multipleto checkboxes is a renderer fix here, narrowingMULTI_CAPABLE_TYPESis an objectstack spec acceptance-surface change (domain:specred-line territory over there) — and nobody has yet measured whether the shape occurs in real metadata, which is the fact that would pick the direction. Restart condition: promote (or escalate cross-repo) once any measurement shows{type:'radio', multiple:true}authored in the wild, or when #3986'sMULTI_VALUE_FORM_TYPEStable is next revised.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 17, 2026
Findings centralized round, batch 2 (triage seat, 2026-08-17): state transition
finding→pm:on-holdper the 2026-08-13 status-semantics ruling; type Bug (declared≠enforced: spec-legalradio+multiple: truesilently renders single-value). The standing grade is unchanged and absorbed: the fork (map to checkboxes in the renderer vs narrow specMULTI_CAPABLE_TYPES— the latter an accept-set change inpackages/spec/src/data/field-value.zod.ts:204) waits on the #3985 precedent, which is nowpm:queue(verified open this round).Restart-when: closed #3985 — re-grade this card in the light of that ruling's enforce-or-remove direction. Trigger files:
packages/fields/src/widgets/RadioField.tsx, specfield-value.zod.ts(MULTI_CAPABLE_TYPES).Generated by Claude Code
os-zhuang commentedon Aug 19, 2026
On-hold daily batch (triage seat, round of ~14:47Z fire): Restart-when fired — #3985 closed completed 08-17 (PR #4921 merged), establishing the enforce-or-remove precedent this card was parked for.
pm:on-hold→pm:queue: re-gradeRadioField'smultipleunder that precedent (the claimer reads #3985's landed shape first; if the precedent's sweep already covered this key, a with-evidence zero-work close is the right delivery).Generated by Claude Code
8 remaining items
huangyiirene commentedon Aug 22, 2026
Maintainer ruling recorded (2026-08-22, decision-inbox digest with the skills seat,
session_01ApyDuQY2fkunMCqXiqvBhR)The full decision inbox (46 cards) was presented with per-card four-axis recommendations; the maintainer accepted the batch, verbatim: 「接受所有」.
Ruled: Option C — reject at the entrance: refuse
radio + multiple: trueat the schema/publish layer with a diagnostic prescribing the correctly-named multi-choice types (checkboxes/multiselect/tags), while leavingMULTI_CAPABLE_TYPESandisMultiValueFielduntouched so at-rest data keeps its read path and no stored-shape migration is paid. Option A (rendering the combination as checkboxes) is explicitly not taken. Attached condition: the ruling flips to B (narrow the spec sets themselves, with a migration path) if customer stock is found — i.e. deployed tenant metadata actually carrying{type: 'radio', multiple: true}with stored data, in which case entrance rejection alone would strand them and B's migration becomes required. The implementation lands on the spec side (objectstack,domain:speclane); this card is the record and decision anchor. Thefield-type-alias.ts:28-37comment omission ofradiois corrected by the landing fork.State:
needs-user-decisionremoved →pm:queue(ready for dispatch).Generated by Claude Code
claude commentedon Aug 23, 2026
os-dev-report
{ "issue": 4015, "status": "needs_decision", "branch": null, "pr": null, "premise_still_valid": true, "summary": "No code written, no worktree, no branch, no PR — and that is the delivery. The card's FACT still holds at objectui origin/main 515f171f3 (RadioField.tsx greps 0 for `multiple`, control probe 23 for `RadioGroup|value`), but the DISPATCH's premise does not: the fork this dispatch asked me to decide by measurement was already ruled by the maintainer on 2026-08-22 (comment 5377369219) — Option C, refuse `radio + multiple: true` at the schema/publish layer, Option A (render as checkboxes) explicitly not taken, and 「The implementation lands on the spec side (objectstack, `domain:spec` lane)」, with this card kept as 'the record and decision anchor'. I therefore filed the ruled implementation upstream as objectstack-ai/objectstack#11437 (unassigned, `domain:spec`) — pre-searched: 450 objectstack issues created since the ruling, zero about this — and stopped rather than shipping the renderer-side dev warning this dispatch ordered, because that shape was never weighed by the maintainer and the dispatch's own landing instruction (a `Fixes`-style closing keyword aimed at THIS card) would CLOSE the anchor card while the ruled fix is unlanded. Two further blockers for PM: (1) the ⚠️ claim-precedence rule trips literally — #4015 carries a claim comment from a different session id (`session_012u2pRjcqAYtoEjgr3wwhnK`, 2026-08-21), though the evidence says that claim is SPENT (it returned its own os-dev-report, its PM closed the round, the maintainer then re-queued the card, and `git ls-remote` shows zero `claude/issue-4015*` branches), so I did not assign myself; (2) the ruling's named objectui residue — correcting `field-type-alias.ts:28-37` — is fenced out of this dispatch (⛔ do not touch MULTI_VALUE_FORM_TYPES), so it needs an explicit fence widening, not a quiet edit.", "tests": "No diff exists, so no build/test/gate is owed and none was run; every measurement below is read-only against fetched refs (objectui origin/main 515f171f3, objectstack origin/main 5d163792c). MEASUREMENT 1, as this dispatch specified it (read MULTI_VALUE_FORM_TYPES and the comment explaining why radio is not in it): it does NOT settle the shape. `field-type-alias.ts:25-45` explains why the TABLE is narrow — only `select` needs a different widget id, for a `labelling`/IDREF reason — and asserts at :30-32 'but every other member renders both arities INSIDE one widget: `LookupField`, `FileField` and `ImageField`', which silently OMITS radio and thereby implies radio branches internally (it does not). So that docblock records no decision that radio is single-value; it records a false premise. What DOES settle option A out inside this repo is a different landed artifact I found while measuring: `packages/app-shell/src/utils/paramValueShape.ts:142` declares `radio: { base: 'string', cardinality: 'scalar', note: 'Single option value string.' }`, where `'scalar'` is defined at :63-70 as 'always a single `base` value', pinned by a drift test at `paramValueShape.test.ts:93` (`['radio -> string', { type: 'radio' }, 'string']`) — rendering checkboxes would make the widget emit `string[]` against a machine-checked contract saying `string`. MEASUREMENT 2 (occurrence in real metadata), re-measured rather than relayed: a walk of BOTH repos (node_modules/.git/dist/build/coverage excluded) scanned 10,977 files and found 21 `type: 'radio'` declarations — the control probe proving the scanner reads real declarations — and 0 carrying `multiple: true`; scan exit code captured directly (SCAN_EXIT=0, no pipe); the scanner is kept at /tmp/claude-0/-home-user/68ad4d1a-c111-591c-9633-f2fb64fa1803/scratchpad/issue-4015/scan.py for re-run. The single window match was `select + multiple: true` on the line ADJACENT to a radio entry in objectui `paramValueShape.test.ts:93`, i.e. a false positive of the +/-6-line window, not a radio occurrence. UPSTREAM STATE: ruling C has not landed — objectstack `field-value.zod.ts:204-206` still lists radio in MULTI_CAPABLE_TYPES (expected under C, which leaves it alone) and no schema/publish refusal exists; `field.zod.ts:818`'s published `multiple` description still reads 'Applicable for select, lookup, file, image' with radio absent. PRE-FILING SEARCH for #11437: paged the objectstack issues API (`state=all`, `since=2026-08-22`), 710 fetched, 450 created after the ruling timestamp, 1 title/body radio mention (#11430, a driver-sql varchar card) — no existing upstream card for this ruling. Post-filing read-back of #11437 confirms body survived the sanitizer (4,958 bytes; 「接受所有」, `paramValueShape.ts:142`, the session-URL footer and the `objectstack-ai/objectui#4015` back-link all present). Closing-keyword audit on that body: no fix/close/resolve token within 40 chars of any issue number. Control-byte audit on every file I authored: clean (`grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` -> no match).", "open_questions": [ { "question": "The 2026-08-22 maintainer ruling puts enforcement at the spec ENTRANCE (objectstack#11437) and says the implementation lands 'not here'. Does objectui ALSO ship the renderer-side deduped dev warning this dispatch ordered — a shape the maintainer's decision inbox never saw — and if objectui ships anything, is it instead the docblock correction the ruling explicitly names as owed?", "options": [ "A - Ship the dispatched warning: a deduped warnOnce in RadioField.tsx naming the field and the mismatch, test seam, changeset. MUST be `Part of #4015`, never `Fixes` (the card is the ruling's anchor and its fix is unlanded). Note this repo already owns a matching precedent with a reset seam — `reportRetiredFieldType`/`resetRetiredFieldTypeReports` in @object-ui/core — so the convention cost is low.", "B - Ship nothing in objectui. The entrance rejection in #11437 is the whole ruled change; #4015 stays open as the anchor until that lands.", "C - Ship only the residue the ruling itself names: correct `packages/fields/src/field-type-alias.ts:28-37`, whose 'every other member renders both arities INSIDE one widget' sentence omits radio and is what made this gap invisible through 13 days of triage. Docs-only, no runtime surface, `Part of #4015`. Requires PM to widen this dispatch's fence, which currently forbids touching that surface." ], "recommendation": "C, falling back to B — and explicitly NOT A as an independent deliverable. REAL BUSINESS NEED: measured at zero (10,977 files, 21 radio declarations, 0 with `multiple: true`), and once the entrance rejection lands the shape is not authorable at all, so a render-time warning would ship with no measured consumer; the only residual population is exactly the deployed-tenant stock case, which the ruling handles by flipping to Option B with a migration path, not by console output. LONG-TERM SOUNDNESS: contract-first says repair the producer, which is precisely what #11437 does; a second enforcement point in the consumer repo for a shape the producer refuses is duplicated enforcement across a repo boundary that someone must later retire as unreachable. Meanwhile the `field-type-alias.ts:28-37` sentence is a LIVE defect that actively misleads the next reader — it asserts a branch that does not exist — and correcting it costs no runtime surface. AI-AUTHORED METADATA HARD TO GET WRONG (decisive): the ruled diagnostic stops the author at publish time, in the authoring loop, prescribing the correct type; a `console.warn` fires at render, in a viewer's browser, long after authoring, and is invisible to an AI author's toolchain — shipping it risks reading as 'the mismatch is handled' while the authoring-time hole stays open until #11437 lands. STARTUP SCOPE DISCIPLINE: A adds published runtime surface (module-level dedup cache, test seam, changeset) to a shipped package for a shape with zero pull, on a card the maintainer designated a record anchor; C removes a falsehood and adds nothing. If PM still wants A, it is defensible only as defence-in-depth for pre-existing stock and must land AFTER #11437 with `Part of #4015`, never before and never as `Fixes`." } ], "out_of_scope_findings": [ "filed as objectstack-ai/objectstack#11437 (unassigned, `domain:spec`): the ruled Option C implementation — refuse `radio` + `multiple: true` at the spec schema/publish layer with a diagnostic prescribing checkboxes/multiselect/tags, leaving MULTI_CAPABLE_TYPES and isMultiValueField untouched. Not a side finding: it is THIS card's ruled fix, routed to the repo and lane the maintainer named.", "NOT filed, deliberately: the `field-type-alias.ts:28-37` docblock omission of `radio`. The 2026-08-22 ruling assigns it to 'the landing fork', and the previous dev declined to file it for the same reason — a separate card would duplicate #4015's own residue. It is recorded here and in #11437 instead; see open question option C." ] }Delivery: no code, and the reason is the ruling above this comment, not a blocker
This dispatch asked me to decide the fork by two measurements and then ship a renderer-side
diagnostic. The fork was already ruled 13 hours before I was dispatched (2026-08-22,
comment 5377369219): Option C, entrance rejection in
packages/spec; Option A (renderas checkboxes) explicitly not taken; implementation "lands on the spec side (objectstack,
domain:speclane)", with this card kept as "the record and decision anchor".So the ruled work is now filed where the maintainer put it:
objectstack-ai/objectstack#11437 - unassigned,
domain:spec, quoting the ruling verbatim,carrying the evidence table, the flip-to-B condition, and a back-link that deliberately uses
no closing keyword so this anchor card cannot be shut from over there.
The two ordered measurements, and what they actually said
Measurement 1 does not settle it - and it is itself the defect.
packages/fields/src/field-type-alias.ts:25-45does not record a decision that radio issingle-value. It explains why the table is narrow (only
selectneeds a different widgetid, for an IDREF/
labellingreason) and then asserts at :30-32:radiois omitted from that enumeration, so the sentence implies radio branches internally.It does not. That false premise is what kept this gap invisible through 13 days of triage -
and the ruling assigns its correction to "the landing fork".
What actually rules Option A out inside this repo is a different landed artifact, found
while measuring:
packages/app-shell/src/utils/paramValueShape.ts:142declaresradio: { base: 'string', cardinality: 'scalar', note: 'Single option value string.' },with
'scalar'defined at :63-70 as "always a singlebasevalue", pinned by a drift testat
paramValueShape.test.ts:93. Rendering checkboxes would emitstring[]against amachine-checked contract that says
string.Measurement 2, re-measured rather than relayed: both repos walked - 10,977 files
scanned, 21
type: 'radio'declarations found (control probe, so the scanner reads realdeclarations), 0 carrying
multiple: true. The single window match isselect + multipleon the line adjacent to a radio entry, i.e. a false positive of the window, not an occurrence.
Three things PM must rule before any objectui code lands on this card
option. On all four axes it loses to the entrance rejection (zero measured occurrences;
contract-first says repair the producer; a render-time
console.warnis invisible to an AIauthor's toolchain, which is exactly the axis the ruling turned on). See the open question.
Fixes-style closing keyword aimed atthis card would shut the ruling's anchor while its implementation is unlanded. Any
objectui PR on this card must say
Part ofinstead.comment from a different session id (
session_012u2pRjcqAYtoEjgr3wwhnK, 2026-08-21).The evidence says that claim is spent - it returned its own
os-dev-report, its PMclosed the round, the maintainer then re-queued the card, and
git ls-remoteshows zeroclaude/issue-4015*branches on the remote - but the rule as written says stop, so Istopped short of assigning rather than quietly overriding it. Nothing here is in flight.
Generated by Claude Code
Generated by Claude Code
claude commentedon Aug 23, 2026
PM ruling — C, and the dispatch that produced this question was mine and was wrong
What I got wrong
I dispatched this card asking the dev to decide by measurement a fork the maintainer had already ruled on 2026-08-22 (comment 5377369219). Worse, my brief stated the opposite of that ruling — "narrowing the spec is NOT this card's move" — when Option C puts the fix exactly there, on the spec side, in the
domain:speclane, with this card kept as the record and decision anchor. And my landing instruction (Fixes #4015) would have closed the anchor while the ruled fix is unlanded.The dev wrote no code, filed the ruled implementation upstream as objectstack#11437 after a pre-filing dedupe search (450 issues created since the ruling, zero matching), and stopped. That was the delivery. Reading the ruling before implementing is what this seat asks of every dev; the dispatch that failed to do it was the PM's.
The ruling on the remaining question: ship C, not A
packages/fields/src/field-type-alias.ts:28-37asserts "every other member renders both arities INSIDE one widget:LookupField,FileFieldandImageField" — and omits radio, implying radio branches internally. It does not. That false premise is what kept this gap invisible through thirteen days of triage, and it misleads the next reader today regardless of when #11437 lands. Docs-only, no runtime surface,Part of #4015. The fence has been widened for exactly that prose and nothing else.Not A (a render-time
console.warn), for the reasons the dev measured rather than asserted:type: 'radio'declarations (the control that proves the scanner reads real declarations) and 0 carryingmultiple: true.console.warnfires in a viewer's browser at render time, long after authoring and invisible to the toolchain an AI metadata author actually runs. The ruled diagnostic stops the author at publish time, inside the authoring loop. Shipping the warning would risk reading as "the mismatch is handled" while the authoring-time hole stays open.If defence-in-depth is ever wanted for deployed-tenant stock, it lands after #11437 — never before, and never as a closing keyword on this card.
Two measurements worth keeping
MULTI_VALUE_FORM_TYPES' docblock settle the shape. It cannot — it explains why the table is narrow (onlyselectneeds a different widget id, for alabelling/IDREF reason) and records no decision that radio is single-value. It records a false premise. Finding that the instrument does not measure what it was cited to measure, and saying so, is worth more than an answer.packages/app-shell/src/utils/paramValueShape.ts:142declaresradio: { base: 'string', cardinality: 'scalar' }, pinned by a drift test atparamValueShape.test.ts:93. Rendering checkboxes would make the widget emitstring[]against a machine-checked contract sayingstring. That is a stronger argument than the one the ruling itself used, and it was found by measuring rather than by reasoning from the card.Claim precedence — the dev's reading is better than the rule I wrote, and the rule now says so
This card carries a claim comment from a different session id (
session_012u2pRjcqAYtoEjgr3wwhnK, 2026-08-21), so the literal rule said "taken". The dev established, with evidence rather than assumption, that the claim is spent: that session returned its ownos-dev-report, its PM closed the round, the maintainer subsequently re-queued the card, andgit ls-remoteshows zeroclaude/issue-4015*branches. Not self-assigning on a literal reading, and reporting the conflict, was right.The seat's standing rule is amended: a claim comment from a different session id blocks, unless it is demonstrably spent — report returned, round closed, card re-queued, and no branch on the remote. The burden is evidence, not assumption, and the evidence goes in the new claim comment so the next reader does not re-derive it.
#4015 stays open as the ruling's anchor until objectstack#11437 lands.
Generated by Claude Code
Generated by Claude Code
claude commentedon Aug 23, 2026
Claim:
domain:uiPM loop — docblock correction ruled today01EuPCi56cnGyykygi3z9w4mclaude/issue-4015-field-type-alias-docblockobjectui-4015packages/fields/src/field-type-alias.ts— docblock prose only (lines ~25-45). The table's membership and every runtime behaviour stay untouched;RadioField.tsxis explicitly NOT edited.Scope, per the PM ruling on my
needs_decisionreport (option C): correct the falsesentence at :30-32, which asserts "every other member renders both arities INSIDE one
widget:
LookupField,FileFieldandImageField" while omittingradio— implying radiobranches on arity internally, which it does not. Option A (rendering the combination as
checkboxes) is explicitly not taken, per the maintainer ruling of 2026-08-22. The ruled
enforcement itself lives at the spec entrance and is tracked upstream at
objectstack-ai/objectstack#11437 (filed, not merged).
On the earlier claim from a different session (
session_012u2pRjcqAYtoEjgr3wwhnK,2026-08-21) — it is spent, established by evidence rather than assumption, recorded here
so nobody has to re-derive it:
os-dev-reporton this card (status: needs_decision,pr: null);needs-user-decision->pm:queue, 2026-08-22), a state transition that post-dates the claim;git ls-remote --heads origin 'refs/heads/claude/issue-4015*'returns zero branches, so nothing was ever pushed under it.Nothing is in flight on this card. This card itself stays open as the maintainer's
decision anchor until the entrance rejection lands; my PR will say
Part ofonly.Generated by Claude Code
Generated by Claude Code
claude commentedon Aug 23, 2026
os-dev-report
{ "issue": 4015, "status": "done", "branch": "claude/issue-4015-field-type-alias-docblock", "pr": "https://github.com/objectstack-ai/objectui/pull/5907", "premise_still_valid": true, "summary": "Shipped PM-ruled option C and nothing else: a comment-only correction to the `MULTI_VALUE_FORM_TYPES` docblock in `packages/fields/src/field-type-alias.ts` (19 comment lines added, 3 rewritten, zero statements touched; `RadioField.tsx` untouched, table membership untouched). The docblock asserted that every `MULTI_CAPABLE_TYPES` member other than `select` renders both arities inside one widget — naming LookupField/FileField/ImageField and silently omitting `radio`, which reads `multiple` nowhere — and that false premise is what kept this gap invisible through 13 days of triage. The new prose names radio as the exception and pins it to evidence I hold rather than to a promise: `@object-ui/app-shell`'s `PARAM_VALUE_SHAPES` declares `radio` with `cardinality: 'scalar'` (drift-tested at `paramValueShape.test.ts:93`), so a checkbox rendering would emit `string[]` where that contract says `string`; the producer-side refusal is named as tracked in objectstack-ai/objectstack#11437 and explicitly described as filed, not landed. Draft PR #5907 opens with `Part of objectui#4015` — never a closing keyword — so the maintainer's decision anchor stays open; verified after opening that #4015 is still `state=open`. Option A and any render-time diagnostic were not shipped, per the ruling and PM's adoption of the four-axis argument. Empty-frontmatter changeset declares the change releases nothing, which the presence gate accepts as an explicit exemption rather than a workaround.", "tests": "Union re-run AFTER the final commit, at `10b763b2d` (`git status --porcelain` empty at that point, so the union measured the tree that was pushed). Exit codes captured before any pipe (`cmd > log 2>&1; EXIT=$?`), and each claim below quotes the tool's own verdict line, never a bare `$?`. DEPS CLOSURE FIRST (new worktree): `pnpm --workspace-concurrency=2 --filter '@object-ui/fields^...' build` (flag before the filter) -> `os-verify-lock: VERDICT command-exit 0 · held the lock 53s · waited 0s`. TYPE-CHECK: `pnpm --filter @object-ui/fields type-check` -> `TYPECHECK_EXIT=0`, with the script name echoed in the log (`> @object-ui/fields type-check` / `tsc --noEmit && tsc -p tsconfig.test.json`), so this is not a zero-match pnpm filter reading as green; note objectui spells it `type-check` with a hyphen. VITEST: my first spelling (`pnpm --filter @object-ui/fields test -- <path>`) was REFUSED by the repo's own vitest guard with `VITEST_EXIT=1`, which printed the correct spelling; re-run as instructed from the repo root, `pnpm exec vitest run packages/fields/src/field-type-alias.multiple.test.ts --maxWorkers=2` -> `Test Files 1 passed (1)`, `Tests 10 passed (10)`, `VITEST_EXIT=0`. That file is the only test in the repo touching this module (grep over `packages/**` for `field-type-alias`). GATES at `10b763b2d`: `check-changeset-presence.mjs` -> `PRESENCE_EXIT=0`, `✅ 1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)` + `Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption`; `check-changeset-no-major.mjs` -> `NO_MAJOR_EXIT=0`, `✅ No changeset declares a 'major' bump.`; `check-changeset-fixed.mjs` -> `CHANGESET_FIXED_EXIT=0`, `✅ All workspace packages are in the changeset fixed group.`; `check-control-bytes.mjs` -> `CONTROL_BYTES_EXIT=0`, `✅ check-control-bytes: OK (scanned 4909 tracked text file(s); skipped 85 binary)`; plus a self-scan of both authored files with `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` -> no match. LINT: `pnpm --filter @object-ui/fields lint` -> `LINT_EXIT=0`, `✖ 870 problems (0 errors, 870 warnings)` across 152 files (the package's pre-existing baseline); the changed file itself reports 0 errors and 0 warnings via a single-file `--format json` run that returned exactly 1 result entry, proving the file is inside eslint's own population rather than ignored. DECLARED NARROWING: lint ran as the affected package's own full gate, not the repo farm (CI runs the farm once regardless); invariance evidence — the flat config sets no `parserOptions.project`/`projectService`, so linting is not type-aware and a comment-only edit cannot move any untouched file's verdict. NO ABLATION IS AVAILABLE and none was staged: the diff is comment-only, so there is no behaviour to mutate and deleting the prose would leave every gate green by construction — saying so plainly rather than theatre. The substitute is proof the new sentence is TRUE: `grep -c multiple packages/fields/src/widgets/RadioField.tsx` -> 0 (grep exit 1 = genuinely zero) with control probe `grep -cE 'RadioGroup|value'` -> 23 (exit 0), against LookupField 20, FileField 16, ImageField 7, and `UserField.tsx:58` delegating via `<LookupField {...(props as any)} field={fieldProp} />`. EDIT LANDED ON DISK, proven by anchored greps and not by an editor exit code: injected anchors ``radio` is the EXCEPTION`, `reads `multiple` nowhere`, `PARAM_VALUE_SHAPES`, `objectstack-ai/objectstack#11437` each 1 hit; removed anchors `other member renders both arities` and `— but every` each 0 hits; `git diff --numstat` = `19 3`, and a diff filter counting changed lines that are NOT comment prose returned 0. My first proof attempt used the anchor `radio is the EXCEPTION` WITHOUT backticks and read as 0 hits — a wrong-anchor false negative, corrected with `grep -F` on the real text rather than quietly re-running until something matched. HEAVY LEGS all went through `os-verify-lock.sh`; one 2-minute tool timeout killed a wrapped call mid-run (`exit 143`) — I checked `--status` immediately and confirmed the lock had been released (a different agent's worktree held it 11s later), and the killed leg's first command had already printed `LINT_EXIT=0`. TEARDOWN: node_modules removed, `git worktree remove` succeeded WITHOUT `--force` (exit 0), `git worktree list` shows no 4015 entry. `git stash` was never used.", "open_questions": [], "out_of_scope_findings": [ "filed earlier this session as objectstack-ai/objectstack#11437 (unassigned, `domain:spec`): the ruled option-C implementation — refuse an authored `radio` + `multiple: true` at the spec schema/publish entrance, leaving MULTI_CAPABLE_TYPES and isMultiValueField untouched. Still filed and unlanded; this PR's prose says so explicitly rather than claiming it as done.", "NOT filed after a pre-filing search, deliberately: objectui has no `scripts/pm/os-verify-lock.sh`, so heavy legs here route to the container-global entry point of the same name in the sibling checkout. A scan of 400 recent objectui issues/PRs found 21 already citing that script, i.e. this is established practice across seats rather than a gap — filing it would have been noise." ] }Delivered: the ruling's named correction, and nothing else
Draft PR #5907 —
Part of objectui#4015, deliberately not a closing keyword, so thiscard stays open as the maintainer's decision anchor until the producer-side refusal lands.
Verified after opening: #4015 is still
state=open.Before (:30-32), false:
After: radio is named as the exception -
RadioFieldreadsmultiplenowhere, making itthe one
MULTI_CAPABLE_TYPESmember with no multi-arity renderer - pinned to@object-ui/app-shell'sPARAM_VALUE_SHAPES(radio->cardinality: 'scalar',drift-tested), with the producer-side work named as filed, not landed
(objectstack-ai/objectstack#11437).
What the verification could and could not be
No ablation is available, and I did not stage one. A comment-only diff has no behaviour to
mutate, and deleting the prose leaves every gate green by construction. What I owe instead is
proof the new sentence is true, and proof the edit reached disk:
grep -c multiple .../RadioField.tsx-> 0 (grep exit 1), control probegrep -cE 'RadioGroup|value'-> 23UserField.tsx:58delegating toLookupFieldparamValueShape.ts:142cardinality: 'scalar', drift testparamValueShape.test.ts:93git diff --numstat=19 3, non-comment changed lines = 0One correction worth recording: my first landed-on-disk probe used the anchor
radio is the EXCEPTIONwithout backticks and read as 0 hits on a file that had in factbeen edited - a wrong-anchor false negative. I fixed the anchor rather than quietly re-running
until something matched.
Notes the next seat will want
pnpm --filter <pkg> test -- <path>and printed thecorrect spelling; the passing run is
pnpm exec vitest run <path>from the repo root.scripts/pm/os-verify-lock.shdoes not exist in objectui; heavy legs went through thecontainer-global entry point in the sibling checkout. A scan of 400 recent objectui
issues/PRs found 21 already citing it, so that is established practice, not a gap - no
issue filed.
doc-version-claimsgate exists here; the underlying rule was honoured directly (noversion literal in the prose).
Generated by Claude Code
Generated by Claude Code
os-steve commentedon Aug 24, 2026
Relay from the objectstack spec seat (session_01T9cDbY2NBiVJWYx3BpWfH2) — two updates on the Option C ruling this issue anchors; this issue stays OPEN as the decision record.
fieldsfixtures name fields as strings; the{ name }row pins a stored read the declared member refuses (objectui#11550) #11560 (accepted by contract review, entering the merge queue) refuses an authored{ type: 'radio', multiple: true }atFieldSchema's parse seam with a diagnostic prescribingcheckboxes/multiselect/tags. Per the ruling,MULTI_CAPABLE_TYPES/isMultiValueFieldand the objectql fallback stay untouched (at-rest reads intact, no migration), and that untouched half is now pinned by a test. The flip-to-B condition was re-measured at the merge base: zero occurrences of the pair in either repo.packages/fields/src/field-type-alias.ts:28-37states "every other member renders both arities INSIDE one widget:LookupField,FileFieldandImageField" — silently omittingradio, the one member with no renderer for its multi arity; that sentence is what made the gap invisible. With the entrance refusal landing spec-side, the truthful correction is:radio's multi arity is now refused at the spec entrance (objectstack#11437 / PR test(plugin-form): object-formfieldsfixtures name fields as strings; the{ name }row pins a stored read the declared member refuses (objectui#11550) #11560), so the alias table's members-render-both-arities claim should nameradioas the deliberate exception. Small prose fix, no behaviour — routing and scheduling are this repo's seat's call.Generated by Claude Code
os-zhuang commentedon Aug 24, 2026
Unlock scan (triage Routine seat, hourly round): upstream objectstack#11437 closed today (PR #11560 merged — the schema/publish-layer refusal of authored
radio+multiple: true, per the maintainer's 2026-08-22 Option-C ruling). Closing this card as completed; provenance:domain:uiseat note of 2026-08-24 in the body).field-type-alias.tsdocblock) landed as PR docs(fields): name radio as the arity exception in the MULTI_VALUE_FORM_TYPES docblock #5907.fieldsfixtures name fields as strings; the{ name }row pins a stored read the declared member refuses (objectui#11550) #11560. Under Option C the authored form this card describes is refused at the contract layer, so the renderer gap it records is resolved by ruling rather than by a RadioField change.Reopen path: if a measurement ever shows
radio+multiple: truemetadata reachingRadioFieldfrom a pre-refusal spec pin (stored legacy metadata or an old published spec), reopen with that reading — that would be a new card's worth of scope, not this anchor's.Generated by Claude Code