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.
finding(components): seven widgets re-read the raw schema.disabled beside SchemaRenderer's evaluated verdict — 9 TS2322 once the #7087 narrowings drop, and ui:button / form grey out on any predicate string #7238
Filed unassigned while building objectui#7087 (branch claude/issue-7087-disabled-narrowing, maintainer ruling 2026-09-01 option 1: the 18 concrete schemas' disabled?: boolean narrowings drop and disabled inherits BaseSchema's boolean | string). The dispatch's Zone-2 item 3 asked for the readers to be verified rather than assumed, and to STOP rather than widen any reader that consumes disabled as a strict boolean. This is that report. The #7087 PR touches packages/types only; nothing here is fixed there.
What holds
The ruling's premise is true at the renderer. packages/react/src/SchemaRenderer.tsx (lines 1289-1299 on origin/main @ 67dadd602) gates disabled / disabledOn through hasDeclaredPredicate + evaluateEnablementPredicate, sets _disabled, strips the raw key from the outgoing spread (line 1463) and forwards the VERDICT as a real disabled prop (disabled: __disabled || undefined, line 1584). ActionSchema.disabled's three readers are predicate-aware already: packages/core/src/actions/ActionRunner.ts:1004-1015, packages/app-shell/src/views/DeclaredActionsBar.tsx:179 / :394, packages/components/src/renderers/complex/data-table.tsx:401.
What does not
The schema the renderer hands the component is evaluatedSchema itself (schemaForComponent, lines 1343-1366) — the RAW authored disabled stays on it. Seven in-scope widgets read that raw value beside the host verdict. Measured with the #7087 change applied and the closure built, pnpm --filter @object-ui/components type-check on 06103d1 reports 9 × TS2322Type 'string | boolean | undefined' is not assignable to type 'boolean | undefined':
(a) DOM passthrough, compile-only — disabled={schema.disabled}:
Runtime is correct through SchemaRenderer for these six: the host's forwarded disabled prop is spread AFTER the raw one (toFormControlDomProps keeps disabled by name — packages/components/src/lib/form-control-dom-props.ts; pickDomProps iterates Object.keys, so an undefined verdict overrides too; collapsible spreads props bare). The narrowing was load-bearing for their COMPILE, not their behaviour.
(b) raw read that drops the verdict, runtime defect — packages/components/src/renderers/form/form.tsx:1008 destructures disabled from schema (FormSchema) and strips the host prop unused (disabled: _disabledProp, line 2804). The raw value feeds :2653 (every field's disabled), :2994 (cancel button), :3006 (submit button) — the three form.tsx errors. A predicate string is truthy however it evaluates: disabled: "${data.status === 'locked'}" on a form greys the whole form out even when the predicate is false.
(c) truthy OR, runtime defect, compiles — packages/components/src/renderers/form/button.tsx:42: const isDisabled = schema.disabled || props.disabled || isLoading; — props is any, so no TS error, but a predicate string on ButtonSchema.disabled disables the button regardless of verdict.
Not affected (no raw read; they take the host prop through their spread): radio-group, switch, toggle, slider, date-picker, calendar, input-otp, file-upload, toggle-group (group-level).
(b) and (c) are reachable TODAY through JSON metadata and through any BaseSchema-typed node, independently of #7087 — the base type has advertised the predicate since #4581. #7087 makes them reachable from the concrete TS types too, and turns (a) from silent into a type error.
The fix shape already in the tree
packages/plugin-chatbot/src/renderer.tsx (objectui#6169) consumes the host-evaluated prop instead of re-reading the raw key — its comment: the raw value may be an expression STRING, which is truthy however it evaluates — one carrier for one question (AGENTS.md #0.1). The same move at the nine sites (read the forwarded disabled prop; in form.tsx stop discarding _disabledProp) removes the 9 TS2322 and the two runtime defects without a cast. Widening a reader is Clause-② content beyond the #7087 ruling, so it is filed here for routing rather than folded into that PR: either it rides in the #7087 PR under the same contract review, or it lands first and the #7087 PR turns green on rebase-by-queue. Until one of those, the #7087 PR's Type Check job is expected red on exactly these nine lines.
Claim: PM loop round 1
Session: session_01NRRumy89BYdW9ogbcdHTho
Branch: claude/issue-7238-disabled-one-carrier
Worktree: objectui-issue-7238
Domain: domain:ui
File surface: packages/components/src/renderers/form/input.tsx, textarea.tsx, checkbox.tsx, select.tsx, combobox.tsx, form.tsx, button.tsx, packages/components/src/renderers/disclosure/collapsible.tsx, tests under packages/components/src/, one changeset (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus — node scripts/pm/dispatch-gates.mjs --tier over these paths is objectstack-derived and a floor only; the content limb does not fire: the widgets converge on the verdict the renderer already forwards, no type moves, no accept set changes (the accept-set change is #7087's, reviewed separately on PR #7240)
Clause-②: no
Serial constraints cleared: PR #7240 (#7087) is parked draft and touches only packages/types — this card lands FIRST and unblocks it; PR #7241 (#7188, landing) touched packages/components/src/renderers/complex/data-table.tsx, disjoint from these files; #7216 is in flight on plugin-list. No other card in the lane names these eight files. Same-day churn is checked by the dev's own fetch at dispatch.
Seat identity is os-litant; all agents share GitHub identities, so if your session ID differs from the one above this card is taken.
Grading note (代扫): the objectui triage seat is not in session; graded by the execution seat as bug · priority:p2 · domain:ui (published library; the runtime defect in form / ui:button is reachable through JSON today) and dispatched in the same pass because it is the dependency of a parked, maintainer-ruled PR.
No ruling on this card. The binding context is the #7087 ruling (option 1 — disabled is boolean | string on every BaseSchema extender) and the one-carrier rule: the renderer evaluates the predicate and forwards the verdict as the disabled prop; widgets read that carrier, never the raw key.
Readings (the card's own, measured 2026-09-02 01:30Z on 67dadd602 with #7087's diff applied): six DOM passthroughs disabled={schema.disabled} (input.tsx:48, textarea.tsx:45, checkbox.tsx:44, select.tsx:52, combobox.tsx:28, collapsible.tsx:20) — compile-only once the narrowing drops; form.tsx:1008 destructures the raw disabled and discards _disabledProp (:2804), feeding :2653 / :2994 / :3006; button.tsx:42 ORs the raw value. Premise stands.
session: session_01NRRumy89BYdW9ogbcdHTho (dev, dispatched by the domain:ui seat)
branch: claude/issue-7238-disabled-one-carrier (pushed empty as the write-route probe)
worktree: /home/user/objectui-issue-7238, base 67dadd602
Scope: the eight files named in the card (six DOM passthroughs, form.tsx, button.tsx) plus tests and one changeset for @object-ui/components. packages/types is not touched here (that is #7087 / PR #7240). renderers/complex/data-table.tsx is not touched (PR #7241).
{
"issue": 7238,
"status": "done",
"branch": "claude/issue-7238-disabled-one-carrier",
"pr": "https://github.com/objectstack-ai/objectui/pull/7318",
"premise_still_valid": true,
"summary": "All nine sites converged onto SchemaRenderer's evaluated verdict, in the plugin-chatbot (#6169) shape: each of the eight files now destructures the forwarded `disabled` prop by name and no widget reads `schema.disabled`. form.tsx stops discarding the host prop (`_disabledProp` removed, `const disabled = hostDisabled === true`); button.tsx computes `isDisabled = hostDisabled || isLoading`. packages/types untouched. ONE CARD SUB-CLAIM FALSIFIED, and the PR body says so rather than shipping the prediction: claim (c), that a predicate string greys ui:button out, is NOT what the tree does. `disabled` also rode buttonProps into toFormControlDomProps, which forwards it by name and keeps the key even when the value is `undefined` (pickDomProps iterates Object.keys); spread AFTER `disabled={isDisabled}`, the host verdict already won, so both predicate polarities were correct. The real ui:button runtime defect is the `loading` leg of that OR, which never reached the element: `loading: true` rendered the spinner on a live, clickable button. Card claims (a) and (b) hold exactly as written. HEAD 78be0e9b2 = my commit f1f3fa6ef plus two merges of origin/main (64d624ded, then d717e8bc3); PR shows mergeable_state `behind` because main moved again during the run. Files changed 11, +304/-24.",
"files_changed": [
"packages/components/src/renderers/form/input.tsx",
"packages/components/src/renderers/form/textarea.tsx",
"packages/components/src/renderers/form/checkbox.tsx",
"packages/components/src/renderers/form/select.tsx",
"packages/components/src/renderers/form/combobox.tsx",
"packages/components/src/renderers/form/form.tsx",
"packages/components/src/renderers/form/button.tsx",
"packages/components/src/renderers/disclosure/collapsible.tsx",
"packages/components/src/__tests__/disabled-verdict-one-carrier.test.tsx (new, 206 lines)",
"packages/components/src/__tests__/form-renderers.test.tsx (the one direct-usage census hit)",
".changeset/7238-disabled-one-carrier.md (patch, @object-ui/components)"
],
"direct_usage_census": "None of the eight files exports its component, so the only host-free mount is ComponentRegistry.get(...). Enumerated every such call site across packages/, apps/, examples/. NO production path renders any of the eight directly: plugin-form composes { type: 'form' } nodes and hands them to SchemaRenderer with no root-level `disabled`; every other consumer goes through the renderer. So no boolean-only raw read was kept anywhere. Exactly ONE test relied on the raw read - form-renderers.test.tsx 'should handle disabled state', which mounted renderComponent({ type: 'input', disabled: true }) with no host; it now renders through the real SchemaRenderer. Every other direct-render test authors `disabled` on a FIELD (fields: [{ ..., disabled: true }]), which travels the untouched fieldDisabled path at form.tsx:2135. Also noted: packages/components/src/SchemaRenderer.tsx is a dead unexported second renderer that spreads the raw schema and evaluates nothing - filed as #7319, not touched.",
"ts2322_probe": "06103d1d6 (claude/issue-7087-disabled-narrowing) merged in as a scratch merge, @object-ui/types REBUILT first (packages/components overrides the root tsconfig `paths`, so it resolves @object-ui/types through node_modules to dist/index.d.ts - an unbuilt probe would have measured the old narrowings), then `pnpm --filter @object-ui/components run type-check`. WITH the fix: 0 error TS2322, 0 total TS errors. With the eight files reverted to 67dadd602: exactly 9 error TS2322, exit 2 - form.tsx x3, input/textarea/checkbox/select/combobox/collapsible x1 each; button.tsx contributes 0 because `props` is `any` there. 9 -> 0. Scratch merge aborted before committing and the restore verified: empty `git diff HEAD`, no MERGE_HEAD. packages/types is NOT in this PR.",
"gates": {
"vitest packages/components/ packages/fields/ packages/react/ @ f1f3fa6ef": "Test Files 422 passed (422) / Tests 5041 passed (5041)",
"vitest packages/components/ @ 32b214a04": "Test Files 224 passed (224) / Tests 2065 passed (2065)",
"turbo run build --filter='@object-ui/components^...' @ 78be0e9b2": "Tasks: 7 successful, 7 total",
"turbo run type-check --filter=@object-ui/components @ 78be0e9b2": "Tasks: 9 successful, 9 total; 0 error TS (runs `tsc --noEmit && tsc -p tsconfig.test.json`, so the new test file IS type-checked - not the NOT-MEASURED trap)",
"pnpm --filter @object-ui/components lint (eslint .) @ f1f3fa6ef": "944 problems (0 errors, 944 warnings) - all pre-existing; this is the package's own CI unit, not a narrowing",
"check:control-bytes @ 78be0e9b2": "OK (scanned 6014 tracked text file(s); skipped 85 binary)",
"check:vi-mock-specifiers @ 78be0e9b2": "OK (4135 tracked source file(s), 2404 test-named; 0 non-static)",
"check:vi-mock-inherit @ 78be0e9b2": "OK (118 call site(s) on @object-ui/react judged: 118 inherit, 0 auto-mocked)",
"check:self-import @ 78be0e9b2": "No package names itself inside its own src/",
"check:phantom-deps @ 78be0e9b2": "Every in-scope import is declared by the package that publishes it",
"check-changeset-presence.mjs @ 78be0e9b2": "10 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/7238-disabled-one-carrier.md",
"check-changeset-no-major.mjs @ 78be0e9b2": "No changeset declares a `major` bump",
"check:sdui-registration-pins @ f1f3fa6ef (console built first)": "All 16 registration(s) a sideEffects array promises are present in the built console (517 chunks weighed; the 3 ruled control(s) are in the derived set)",
"check:eager-closure @ f1f3fa6ef (console built first)": "exit 0"
},
"tests": "PRE-FIX MEASUREMENT on the unmodified tree @ 67dadd602 - `pnpm exec vitest run packages/components/src/__tests__/disabled-verdict-one-carrier.test.tsx` reported `Tests 2 failed | 23 passed (25)`: form FALSE-predicate red (AssertionError: expected true to be false) and ui:button `loading` red (expected false to be true). The 23 green are the discriminating controls - the TRUE-predicate polarity of every case, the literal `disabled: true` polarity, and BOTH polarities of all six DOM pass-throughs; a one-polarity pin would have gone green on a widget that dropped `disabled` entirely. Every case renders through the real SchemaRenderer and the real registry, with the predicate resolved against SchemaRendererContext's dataSource. POST-FIX the same file is 25 passed (25). ABLATION, taken AFTER the commit so the restore leg has a real reference: form.tsx and button.tsx reverted to 67dadd602. Mutation confirmed ON DISK by anchor text in both directions, not by an exit code - `hostDisabled` count went to 0 in both files, `disabled = false,` back to 1 in form.tsx, `schema.disabled || props.disabled` back to 1 in button.tsx, and `git diff HEAD --stat` non-empty (2 files, 6 insertions, 37 deletions). Mutated run: `Tests 2 failed | 23 passed (25)` - the same two cases, the 23 controls still green. NO REBUILD LEG IS OWED and this is the stated condition, not an omission: the root vitest.config.mts aliases @object-ui/components (line 284) and @object-ui/react (line 282) to packages/*/src, so the resolution path never touches dist/ and the mutated SOURCE is what runs. Restore leg given the same treatment as the mutation leg: `git checkout HEAD -- ABSOLUTE_PATH` (HEAD named explicitly, never a bare checkout, so a poisoned index cannot serve it), proven by an EMPTY `git diff HEAD` AND a `git hash-object` match against each file's HEAD blob (af6f4bc30954e0a4baa4d6bd4509560a9e3d1802 for form.tsx, f5f1a0a0d597485019771ae58569ba42ab2a0fc7 for button.tsx; an empty hash would have been read as failure), then `Tests 25 passed (25)`. The whole script carried a `trap restore EXIT INT TERM` with absolute paths resolved from `git rev-parse --show-toplevel`.",
"deviations": [
"DECLARED NARROWING - the suite re-run on the FINAL head 78be0e9b2 did not land. The shared verify lock was held continuously by the sibling agent in /home/user/objectui-issue-6190 (holder pid 2444, a single repo-wide chain of build + type-check + vitest + lint, still held at 1563s); three bounded acquisitions with unlocked work in between (~24 minutes of queueing) all returned exit 99 without acquiring. The narrowing is a measurement, not a skip: `git diff --stat 32b214a04 78be0e9b2 -- packages/components/src` is ONE added file, page-header-action-ids.dist.spec.tsx, which matches DIST_PIN_GLOB (packages/*/src/**/*.dist.spec.tsx, vitest.config.mts:167) and therefore runs only in the opt-in `dist` project behind OBJECTUI_DIST_PINS=1 (line 186) - never in the unit/dom/dom-heavy projects a `packages/components/` path filter drives. No production source under packages/components/src moved between the last full green and HEAD. The merge's other content (packages/types, packages/plugin-form) is covered against this package by the green type-check ON 78be0e9b2. CI runs the full farm on the PR regardless.",
"check:sdui-registration-pins and check:eager-closure are quoted at f1f3fa6ef (which contains 100% of my diff) rather than at 78be0e9b2, for the same lock reason - both need a fresh console bundle, a 4m20s build. First attempt at them returned PREREQUISITE NOT MET (exit 2: 'No console build to weigh at apps/console/dist/assets. This is exit 2, not a pass'); the quoted greens are from after the console was built, not from that run.",
"form-renderers.test.tsx was edited - it is inside the declared file surface (tests under packages/components/src) and it is the single direct-usage census hit, so leaving it would have been the raw read surviving in a pin."
],
"mcp_calls": "6 - add_issue_comment (claim), create_pull_request, pull_request_read (PR body readback), search_issues (dedupe for the finding, with #7238 itself returned as the known-must-hit control), issue_write (filed #7319), add_issue_comment (this report). Card body and comments were read through the public-repo embedded JSON payload at zero quota.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7319: packages/components/src/SchemaRenderer.tsx is a dead second renderer with the same export name as the real one - nothing imports it, it is not in the package barrel, and it evaluates no gate at all (bare truthy `hidden` check, and `{...schema}` spreads the RAW node, so it would defeat exactly the one-carrier contract this card establishes). Labelled `finding`, unassigned, recommendation: delete."
]
}
ACCEPT — PR #7318 reviewed by the domain:ui seat (session session_01NRRumy89BYdW9ogbcdHTho), landing gated on CI.
Read by content, not by the report. Branch fetched locally; three-dot diff against origin/main is exactly the 11 files the report lists (+304/−24). The eight widgets destructure the forwarded disabled prop by name; form.tsx drops disabled = false off schema and the _disabledProp discard, and reads hostDisabled === true; button.tsx takes disabled out of props so toFormControlDomProps can no longer overwrite isDisabled. Residual raw reads at the PR head: a grep for schema.disabled across the eight files hits only three comment lines; the same grep at the fork point 67dadd602 hits input.tsx ×1 and button.tsx ×1 (the control that proves the grep is live). hostDisabled appears 3× in each of the five untouched-by-me pass-throughs.
Report corrections accepted. The card's sub-claim (c) — a predicate string greys ui:button out — was falsified by measurement: the host verdict already overwrote the raw read through the DOM pass-through spread, so the real casualty was the loading leg. The PR body reports the measurement instead of the prediction; the card's (a) and (b) stand. Recorded here so the card's text is not read as the tree's behaviour.
Gates.check-governed-merges.mjs --test on the final file list → NOT governed (ordinary queue landing). Commit trailers present on f1f3fa6ef; no model identifiers in the diff. Changeset present, patch on @object-ui/components, no major. At review time (06:34Z) the PR's 30 check runs read: Lint, Bundle Analysis, README Export Check, Doc Snippet Type Check, Build & E2E, Governed Surface Queue Guard and every changeset gate completed: success; Type Check and Test (shard 1–4) still in_progress. The dev's declared narrowing (no full-suite re-run on the final head because the shared verify lock was held by the sibling #6190 chain; the only source delta between the last green and the head is a *.dist.spec.tsx file outside the unit projects) is accepted as a measurement, and CI's full farm is the closing read.
Not reproduced locally: the dev's worktree was removed at hand-off and the shared checkout carries no node_modules, so the 2-red/23-green pre-fix reading and the ablation are taken from the report's on-disk-verified evidence plus CI, not from a seat-side run.
Landing plan. Once Type Check and the four test shards are completed: success: ready + auto-merge (SQUASH), enqueue confirmed positively via git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-7318-*'. After it lands: execute Unlock-action: re-check PR #7240 on #7087 (merge origin/main into claude/issue-7087-disabled-narrowing, confirm Type Check green on the new head, then ready + auto-merge). Side finding #7319 (dead second SchemaRenderer in components) stays finding, unassigned.
Landed — PR #7318 merged as b9f5ff128 (squash, one parent ad3d4029a; merged 2026-09-02T07:00:11Z).
Landing confirmed by content on origin/main, not by PR head sha:
git grep -c 'hostDisabled' origin/main -- packages/components/src/renderers/form/form.tsx → 2; the same probe at the fork point 67dadd602 → 0 (control).
schema.disabled || props.disabled in button.tsx survives only inside the explanatory comment on line 49 — the live OR is gone.
packages/components/src/__tests__/disabled-verdict-one-carrier.test.tsx and .changeset/7238-disabled-one-carrier.md present on origin/main.
Merge-queue refs for PR 7318: 0 remaining (drained; PR 7320's entry now sits on this build).
Card closed by the PR's Fixes line; pm:dispatched stripped in the same step (labels now bug, priority:p2, domain:ui). Next: the Unlock-action: re-check PR #7240 on #7087 runs now — origin/main merges cleanly into claude/issue-7087-disabled-narrowing (read-only git merge-tree reports no conflicts), so the branch is updated from main and its Type Check is re-read before it goes ready. Session session_01NRRumy89BYdW9ogbcdHTho.
Activity
os-litant commentedon Sep 2, 2026
Claim: PM loop round 1
Session:
session_01NRRumy89BYdW9ogbcdHThoBranch:
claude/issue-7238-disabled-one-carrierWorktree:
objectui-issue-7238Domain:
domain:uiFile surface:
packages/components/src/renderers/form/input.tsx,textarea.tsx,checkbox.tsx,select.tsx,combobox.tsx,form.tsx,button.tsx,packages/components/src/renderers/disclosure/collapsible.tsx, tests underpackages/components/src/, one changeset (stop on breach; explain in the report)Container & model: M,
mode:subagent,model: opus—node scripts/pm/dispatch-gates.mjs --tierover these paths is objectstack-derived and a floor only; the content limb does not fire: the widgets converge on the verdict the renderer already forwards, no type moves, no accept set changes (the accept-set change is #7087's, reviewed separately on PR #7240)Clause-②: no
Serial constraints cleared: PR #7240 (#7087) is parked draft and touches only
packages/types— this card lands FIRST and unblocks it; PR #7241 (#7188, landing) touchedpackages/components/src/renderers/complex/data-table.tsx, disjoint from these files; #7216 is in flight onplugin-list. No other card in the lane names these eight files. Same-day churn is checked by the dev's own fetch at dispatch.Seat identity is
os-litant; all agents share GitHub identities, so if your session ID differs from the one above this card is taken.Grading note (代扫): the objectui triage seat is not in session; graded by the execution seat as
bug·priority:p2·domain:ui(published library; the runtime defect inform/ui:buttonis reachable through JSON today) and dispatched in the same pass because it is the dependency of a parked, maintainer-ruled PR.No ruling on this card. The binding context is the #7087 ruling (option 1 —
disabledisboolean | stringon everyBaseSchemaextender) and the one-carrier rule: the renderer evaluates the predicate and forwards the verdict as thedisabledprop; widgets read that carrier, never the raw key.Readings (the card's own, measured 2026-09-02 01:30Z on
67dadd602with #7087's diff applied): six DOM passthroughsdisabled={schema.disabled}(input.tsx:48,textarea.tsx:45,checkbox.tsx:44,select.tsx:52,combobox.tsx:28,collapsible.tsx:20) — compile-only once the narrowing drops;form.tsx:1008destructures the rawdisabledand discards_disabledProp(:2804), feeding:2653/:2994/:3006;button.tsx:42ORs the raw value. Premise stands.Generated by Claude Code
os-litant commentedon Sep 2, 2026
Claiming this card.
session_01NRRumy89BYdW9ogbcdHTho(dev, dispatched by thedomain:uiseat)claude/issue-7238-disabled-one-carrier(pushed empty as the write-route probe)/home/user/objectui-issue-7238, base67dadd602Scope: the eight files named in the card (six DOM passthroughs,
form.tsx,button.tsx) plus tests and one changeset for@object-ui/components.packages/typesis not touched here (that is #7087 / PR #7240).renderers/complex/data-table.tsxis not touched (PR #7241).Generated by Claude Code
Generated by Claude Code
os-litant commentedon Sep 2, 2026
os-dev-report
{ "issue": 7238, "status": "done", "branch": "claude/issue-7238-disabled-one-carrier", "pr": "https://github.com/objectstack-ai/objectui/pull/7318", "premise_still_valid": true, "summary": "All nine sites converged onto SchemaRenderer's evaluated verdict, in the plugin-chatbot (#6169) shape: each of the eight files now destructures the forwarded `disabled` prop by name and no widget reads `schema.disabled`. form.tsx stops discarding the host prop (`_disabledProp` removed, `const disabled = hostDisabled === true`); button.tsx computes `isDisabled = hostDisabled || isLoading`. packages/types untouched. ONE CARD SUB-CLAIM FALSIFIED, and the PR body says so rather than shipping the prediction: claim (c), that a predicate string greys ui:button out, is NOT what the tree does. `disabled` also rode buttonProps into toFormControlDomProps, which forwards it by name and keeps the key even when the value is `undefined` (pickDomProps iterates Object.keys); spread AFTER `disabled={isDisabled}`, the host verdict already won, so both predicate polarities were correct. The real ui:button runtime defect is the `loading` leg of that OR, which never reached the element: `loading: true` rendered the spinner on a live, clickable button. Card claims (a) and (b) hold exactly as written. HEAD 78be0e9b2 = my commit f1f3fa6ef plus two merges of origin/main (64d624ded, then d717e8bc3); PR shows mergeable_state `behind` because main moved again during the run. Files changed 11, +304/-24.", "files_changed": [ "packages/components/src/renderers/form/input.tsx", "packages/components/src/renderers/form/textarea.tsx", "packages/components/src/renderers/form/checkbox.tsx", "packages/components/src/renderers/form/select.tsx", "packages/components/src/renderers/form/combobox.tsx", "packages/components/src/renderers/form/form.tsx", "packages/components/src/renderers/form/button.tsx", "packages/components/src/renderers/disclosure/collapsible.tsx", "packages/components/src/__tests__/disabled-verdict-one-carrier.test.tsx (new, 206 lines)", "packages/components/src/__tests__/form-renderers.test.tsx (the one direct-usage census hit)", ".changeset/7238-disabled-one-carrier.md (patch, @object-ui/components)" ], "direct_usage_census": "None of the eight files exports its component, so the only host-free mount is ComponentRegistry.get(...). Enumerated every such call site across packages/, apps/, examples/. NO production path renders any of the eight directly: plugin-form composes { type: 'form' } nodes and hands them to SchemaRenderer with no root-level `disabled`; every other consumer goes through the renderer. So no boolean-only raw read was kept anywhere. Exactly ONE test relied on the raw read - form-renderers.test.tsx 'should handle disabled state', which mounted renderComponent({ type: 'input', disabled: true }) with no host; it now renders through the real SchemaRenderer. Every other direct-render test authors `disabled` on a FIELD (fields: [{ ..., disabled: true }]), which travels the untouched fieldDisabled path at form.tsx:2135. Also noted: packages/components/src/SchemaRenderer.tsx is a dead unexported second renderer that spreads the raw schema and evaluates nothing - filed as #7319, not touched.", "ts2322_probe": "06103d1d6 (claude/issue-7087-disabled-narrowing) merged in as a scratch merge, @object-ui/types REBUILT first (packages/components overrides the root tsconfig `paths`, so it resolves @object-ui/types through node_modules to dist/index.d.ts - an unbuilt probe would have measured the old narrowings), then `pnpm --filter @object-ui/components run type-check`. WITH the fix: 0 error TS2322, 0 total TS errors. With the eight files reverted to 67dadd602: exactly 9 error TS2322, exit 2 - form.tsx x3, input/textarea/checkbox/select/combobox/collapsible x1 each; button.tsx contributes 0 because `props` is `any` there. 9 -> 0. Scratch merge aborted before committing and the restore verified: empty `git diff HEAD`, no MERGE_HEAD. packages/types is NOT in this PR.", "gates": { "vitest packages/components/ packages/fields/ packages/react/ @ f1f3fa6ef": "Test Files 422 passed (422) / Tests 5041 passed (5041)", "vitest packages/components/ @ 32b214a04": "Test Files 224 passed (224) / Tests 2065 passed (2065)", "turbo run build --filter='@object-ui/components^...' @ 78be0e9b2": "Tasks: 7 successful, 7 total", "turbo run type-check --filter=@object-ui/components @ 78be0e9b2": "Tasks: 9 successful, 9 total; 0 error TS (runs `tsc --noEmit && tsc -p tsconfig.test.json`, so the new test file IS type-checked - not the NOT-MEASURED trap)", "pnpm --filter @object-ui/components lint (eslint .) @ f1f3fa6ef": "944 problems (0 errors, 944 warnings) - all pre-existing; this is the package's own CI unit, not a narrowing", "check:control-bytes @ 78be0e9b2": "OK (scanned 6014 tracked text file(s); skipped 85 binary)", "check:vi-mock-specifiers @ 78be0e9b2": "OK (4135 tracked source file(s), 2404 test-named; 0 non-static)", "check:vi-mock-inherit @ 78be0e9b2": "OK (118 call site(s) on @object-ui/react judged: 118 inherit, 0 auto-mocked)", "check:self-import @ 78be0e9b2": "No package names itself inside its own src/", "check:phantom-deps @ 78be0e9b2": "Every in-scope import is declared by the package that publishes it", "check-changeset-presence.mjs @ 78be0e9b2": "10 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/7238-disabled-one-carrier.md", "check-changeset-no-major.mjs @ 78be0e9b2": "No changeset declares a `major` bump", "check:sdui-registration-pins @ f1f3fa6ef (console built first)": "All 16 registration(s) a sideEffects array promises are present in the built console (517 chunks weighed; the 3 ruled control(s) are in the derived set)", "check:eager-closure @ f1f3fa6ef (console built first)": "exit 0" }, "tests": "PRE-FIX MEASUREMENT on the unmodified tree @ 67dadd602 - `pnpm exec vitest run packages/components/src/__tests__/disabled-verdict-one-carrier.test.tsx` reported `Tests 2 failed | 23 passed (25)`: form FALSE-predicate red (AssertionError: expected true to be false) and ui:button `loading` red (expected false to be true). The 23 green are the discriminating controls - the TRUE-predicate polarity of every case, the literal `disabled: true` polarity, and BOTH polarities of all six DOM pass-throughs; a one-polarity pin would have gone green on a widget that dropped `disabled` entirely. Every case renders through the real SchemaRenderer and the real registry, with the predicate resolved against SchemaRendererContext's dataSource. POST-FIX the same file is 25 passed (25). ABLATION, taken AFTER the commit so the restore leg has a real reference: form.tsx and button.tsx reverted to 67dadd602. Mutation confirmed ON DISK by anchor text in both directions, not by an exit code - `hostDisabled` count went to 0 in both files, `disabled = false,` back to 1 in form.tsx, `schema.disabled || props.disabled` back to 1 in button.tsx, and `git diff HEAD --stat` non-empty (2 files, 6 insertions, 37 deletions). Mutated run: `Tests 2 failed | 23 passed (25)` - the same two cases, the 23 controls still green. NO REBUILD LEG IS OWED and this is the stated condition, not an omission: the root vitest.config.mts aliases @object-ui/components (line 284) and @object-ui/react (line 282) to packages/*/src, so the resolution path never touches dist/ and the mutated SOURCE is what runs. Restore leg given the same treatment as the mutation leg: `git checkout HEAD -- ABSOLUTE_PATH` (HEAD named explicitly, never a bare checkout, so a poisoned index cannot serve it), proven by an EMPTY `git diff HEAD` AND a `git hash-object` match against each file's HEAD blob (af6f4bc30954e0a4baa4d6bd4509560a9e3d1802 for form.tsx, f5f1a0a0d597485019771ae58569ba42ab2a0fc7 for button.tsx; an empty hash would have been read as failure), then `Tests 25 passed (25)`. The whole script carried a `trap restore EXIT INT TERM` with absolute paths resolved from `git rev-parse --show-toplevel`.", "deviations": [ "DECLARED NARROWING - the suite re-run on the FINAL head 78be0e9b2 did not land. The shared verify lock was held continuously by the sibling agent in /home/user/objectui-issue-6190 (holder pid 2444, a single repo-wide chain of build + type-check + vitest + lint, still held at 1563s); three bounded acquisitions with unlocked work in between (~24 minutes of queueing) all returned exit 99 without acquiring. The narrowing is a measurement, not a skip: `git diff --stat 32b214a04 78be0e9b2 -- packages/components/src` is ONE added file, page-header-action-ids.dist.spec.tsx, which matches DIST_PIN_GLOB (packages/*/src/**/*.dist.spec.tsx, vitest.config.mts:167) and therefore runs only in the opt-in `dist` project behind OBJECTUI_DIST_PINS=1 (line 186) - never in the unit/dom/dom-heavy projects a `packages/components/` path filter drives. No production source under packages/components/src moved between the last full green and HEAD. The merge's other content (packages/types, packages/plugin-form) is covered against this package by the green type-check ON 78be0e9b2. CI runs the full farm on the PR regardless.", "check:sdui-registration-pins and check:eager-closure are quoted at f1f3fa6ef (which contains 100% of my diff) rather than at 78be0e9b2, for the same lock reason - both need a fresh console bundle, a 4m20s build. First attempt at them returned PREREQUISITE NOT MET (exit 2: 'No console build to weigh at apps/console/dist/assets. This is exit 2, not a pass'); the quoted greens are from after the console was built, not from that run.", "form-renderers.test.tsx was edited - it is inside the declared file surface (tests under packages/components/src) and it is the single direct-usage census hit, so leaving it would have been the raw read surviving in a pin." ], "mcp_calls": "6 - add_issue_comment (claim), create_pull_request, pull_request_read (PR body readback), search_issues (dedupe for the finding, with #7238 itself returned as the known-must-hit control), issue_write (filed #7319), add_issue_comment (this report). Card body and comments were read through the public-repo embedded JSON payload at zero quota.", "open_questions": [], "out_of_scope_findings": [ "filed as #7319: packages/components/src/SchemaRenderer.tsx is a dead second renderer with the same export name as the real one - nothing imports it, it is not in the package barrel, and it evaluates no gate at all (bare truthy `hidden` check, and `{...schema}` spreads the RAW node, so it would defeat exactly the one-carrier contract this card establishes). Labelled `finding`, unassigned, recommendation: delete." ] }Generated by Claude Code
Generated by Claude Code
os-litant commentedon Sep 2, 2026
ACCEPT — PR #7318 reviewed by the
domain:uiseat (sessionsession_01NRRumy89BYdW9ogbcdHTho), landing gated on CI.Read by content, not by the report. Branch fetched locally; three-dot diff against
origin/mainis exactly the 11 files the report lists (+304/−24). The eight widgets destructure the forwardeddisabledprop by name;form.tsxdropsdisabled = falseoffschemaand the_disabledPropdiscard, and readshostDisabled === true;button.tsxtakesdisabledout ofpropssotoFormControlDomPropscan no longer overwriteisDisabled. Residual raw reads at the PR head: a grep forschema.disabledacross the eight files hits only three comment lines; the same grep at the fork point67dadd602hitsinput.tsx×1 andbutton.tsx×1 (the control that proves the grep is live).hostDisabledappears 3× in each of the five untouched-by-me pass-throughs.Report corrections accepted. The card's sub-claim (c) — a predicate string greys
ui:buttonout — was falsified by measurement: the host verdict already overwrote the raw read through the DOM pass-through spread, so the real casualty was theloadingleg. The PR body reports the measurement instead of the prediction; the card's (a) and (b) stand. Recorded here so the card's text is not read as the tree's behaviour.Gates.
check-governed-merges.mjs --teston the final file list → NOT governed (ordinary queue landing). Commit trailers present onf1f3fa6ef; no model identifiers in the diff. Changeset present,patchon@object-ui/components, nomajor. At review time (06:34Z) the PR's 30 check runs read: Lint, Bundle Analysis, README Export Check, Doc Snippet Type Check, Build & E2E, Governed Surface Queue Guard and every changeset gatecompleted: success;Type CheckandTest (shard 1–4)stillin_progress. The dev's declared narrowing (no full-suite re-run on the final head because the shared verify lock was held by the sibling #6190 chain; the only source delta between the last green and the head is a*.dist.spec.tsxfile outside the unit projects) is accepted as a measurement, and CI's full farm is the closing read.Not reproduced locally: the dev's worktree was removed at hand-off and the shared checkout carries no
node_modules, so the 2-red/23-green pre-fix reading and the ablation are taken from the report's on-disk-verified evidence plus CI, not from a seat-side run.Landing plan. Once
Type Checkand the four test shards arecompleted: success: ready + auto-merge (SQUASH), enqueue confirmed positively viagit ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-7318-*'. After it lands: executeUnlock-action: re-check PR #7240on #7087 (mergeorigin/mainintoclaude/issue-7087-disabled-narrowing, confirmType Checkgreen on the new head, then ready + auto-merge). Side finding #7319 (dead secondSchemaRendererin components) staysfinding, unassigned.Generated by Claude Code
os-litant commentedon Sep 2, 2026
Landed — PR #7318 merged as
b9f5ff128(squash, one parentad3d4029a; merged 2026-09-02T07:00:11Z).Landing confirmed by content on
origin/main, not by PR head sha:git grep -c 'hostDisabled' origin/main -- packages/components/src/renderers/form/form.tsx→ 2; the same probe at the fork point67dadd602→ 0 (control).schema.disabled || props.disabledinbutton.tsxsurvives only inside the explanatory comment on line 49 — the live OR is gone.packages/components/src/__tests__/disabled-verdict-one-carrier.test.tsxand.changeset/7238-disabled-one-carrier.mdpresent onorigin/main.Card closed by the PR's
Fixesline;pm:dispatchedstripped in the same step (labels nowbug,priority:p2,domain:ui). Next: theUnlock-action: re-check PR #7240on #7087 runs now —origin/mainmerges cleanly intoclaude/issue-7087-disabled-narrowing(read-onlygit merge-treereports no conflicts), so the branch is updated frommainand itsType Checkis re-read before it goes ready. Sessionsession_01NRRumy89BYdW9ogbcdHTho.Generated by Claude Code