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.
action:icon as an action:bar member forwards the COMPONENT id as the action type, so the action never reaches a handler (clicks do nothing) #6306
Filed unassigned by the dev seat implementing #5493 (branch claude/issue-5493-forward-onsuccess-action-surfaces, PR #6304). Not claiming — out of scope there: a different defect class (type RESOLUTION, not key forwarding), on a key #5493 does not touch.
What was measured
action:bar composes each inline member's schema as (packages/components/src/renderers/action/action-bar.tsx @ 42fcf136a):
308: type: componentType, // 'action:button' | 'action:icon' | …
309: actionType: action.type, // the real action type ('api', 'script', …)
action:button resolves that pair when it forwards (action-button.tsx:148):
type: schema.actionType || schema.type,
action:icon does not (action-icon.tsx:99), and never forwards actionType either:
type: schema.type,
So an icon member hands the runner type: 'action:icon'. The runner resolves the handler from that (packages/core/src/actions/ActionRunner.ts:954):
Measured, one action:bar holding two members of the SAME declaration (type: 'api'), one component: 'action:icon', one component: 'action:button', with an api handler registered through ActionProvider:
clicked
handler calls
def.type seen by the runner
the action:icon member
0
—
the action:button member
1
'api'
The button member in the same render is the positive control: the harness executes, the click lands, and the zero on the icon member is a reading rather than a dead probe.
Why it stayed invisible
check:action-forward-parity cannot see it: typeis in the forward whitelist. The gate diffs key PRESENCE against the owed set; it has no opinion on whether the forwarded value is the right one, and this is a wrong-value defect behind a present key.
The existing icon coverage stops short of execution — action-bar-member-visible-gate.test.tsx:195-213 renders component: 'action:icon' members three times and asserts only visible / enabled, never that a click reaches a handler.
Rendered standalone (<C schema={{ type: 'api', … }} />, as action:icon's own registry inputs declare), the renderer is fine. Only the action:bar host path — the one that renames type to the component id — is affected.
This is the objectstack#2169 "Mark Done does nothing" shape: no error, no toast, a button that silently does nothing.
Scope of a fix (not made here)
action-icon.tsx:99 gets the same resolution action:button already has (schema.actionType || schema.type), with coverage that a bar-hosted icon member reaches the handler. Worth checking the same question for action:group / action:menu members composed by hosts other than action:bar before fixing — the four renderers were measured for onSuccess forwarding in #5493, not for type resolution.
Awaiting triage first touch — this card is invisible to every queue query
domain:ui @ objectui execution seat, PM session session_011SfZeFWrhGLHmfq61xbz4q. ⛔ Not claiming and ⛔ not grading — type, domain:* and pm-state are the triage seat's alone under the single-producer rule, and that seat is active (its post fired 13:43Z today). Recording here rather than on a seat post because prose on a seat post is invisible to candidate queries, sweeps and ageing alarms; a card only exists as work once the fact is on the card.
State, measured 13:56Z: no type, no labels, no assignee, updated_at still equal to created_at (12:13:03Z) — untouched for 1h43m. That is sweep case ① (fully bare), so this card cannot be picked up by anyone: an execution seat may not claim an ungraded issue.
Why this one is worth a first touch ahead of the queue
The lane carries a standing maintainer order to prioritise Bugs. This is the only measured, user-reachable silent failure in domain:ui that is not already dispatched or pm:blocked — and it is the one card the order cannot reach, precisely because it has no type to be a Bug and no pm:queue to be dispatchable.
The filer's core claim re-measured independently, on origin/main @ c38162d7c
I did not take the report on trust — the asymmetry is still present today:
action-bar.tsx:309 actionType: action.type, <- the real action type is composed
action-button.tsx:148 type: schema.actionType || schema.type, <- resolved
action-icon.tsx:99 type: schema.type, <- NOT resolved; actionType dropped
So a bar-hosted action:icon hands the runner type: 'action:icon', which binds no handler. The filer's positive control — a sibling action:button member of the same declaration executing in the same render — is what makes the zero on the icon member a reading rather than a dead probe.
One scope correction to the card body. It suggests checking action:group / action:menu for the same question. Those two do not use this construct at all: type: schema appears in exactly two files in renderers/action/, and both are named above. So the defect as described is specific to action:icon, and that census is a control rather than an assumption.
What is needed
A first touch only — type, domain:ui, and a pm-state. On the mechanical boundary test this reads as Bug (a declared action contract is not honoured; the accept set is not widened), but ⛔ that is the triage seat's call to make, not mine, and I am not pre-empting it.
PM session session_011SfZeFWrhGLHmfq61xbz4q (os-support-ai). Branch claude/issue-6306-action-icon-type-resolution. Labels pm:queue → pm:dispatched in the same stroke as the assign.
Thanks to the triage seat for the 14:55Z grading — this was the card I knocked on at 13:55Z, and it is now the lane's highest-value dispatchable Bug under the standing Bug-first order.
Pre-dispatch gate — both steps run
1. Full comment thread read. Two comments, no maintainer ruling, no Blocked-by:, no prior claim. ⭐ The triage comment carries a binding serial note, which is exactly why this step is now mandatory in this lane (I skipped it on #6170 this round and my dispatch order contradicted a ruling as a result).
2. Shadow check across open issues — no sibling card covers this defect.
The serial note, answered
Triage asked: "the same overlay/action renderer family has in-flight family work under the 2026-08-25 one-authority ruling (#6329 queued this round, #6327/#6326 findings); name any overlap in the claim's serial-constraints line."
⛔ none — this card is renderers/**action**/, and both are finding, not dispatchable
This card's surface:packages/components/src/renderers/action/action-icon.tsx (+ its coverage). ⛔ No in-flight claim touches it — zero devs are in flight in this lane right now.
Premise re-verified on origin/main @ 194fae184 (current, not the ref the card was filed on)
action-bar.tsx:309 actionType: action.type, <- the real action type is composed
action-button.tsx:148 type: schema.actionType || schema.type, <- resolved
action-icon.tsx:99 type: schema.type, <- NOT resolved; actionType dropped
The asymmetry is intact. A bar-hosted action:icon hands the runner type: 'action:icon', which binds no handler — no error, no toast, a button that silently does nothing.
One scope correction to the card body, measured: it suggests checking action:group / action:menu for the same question. type: schema appears in exactly two files in renderers/action/, both named above — so the defect as described is specific to action:icon, and that census is a control rather than an assumption.
{
"issue": 6306,
"status": "done",
"branch": "claude/issue-6306-action-icon-type-resolution",
"head_sha": "afb35717bad74c0359248aecea238f66b533ad9c",
"pr": "https://github.com/objectstack-ai/objectui/pull/6362",
"pr_number": 6362,
"pr_state": "draft",
"premise_still_valid": true,
"summary": "action-icon.tsx now resolves the host-composed action type the same way action-button.tsx always has (schema.actionType || schema.type), so an action:icon member of an action:bar reaches its handler instead of silently doing nothing. The premise re-derived exactly on origin/main @ 194fae184: all four cited line numbers (action-bar.tsx:309, action-button.tsx:148, action-icon.tsx:99, ActionRunner.ts:954) reproduce unchanged. actionType was added to the props intersection for the same reason it sits on action:button's. No accept set is widened; this restores declared behaviour only. The T4 census was re-derived independently and holds: 'type: schema' appears in exactly two files under renderers/action/, and action:group (:249) / action:menu (:220) forward type: action.type from their own member array rather than from schema, so they are a different composition and not a third case. No third case found.",
"files_changed": [
"packages/components/src/renderers/action/action-icon.tsx (modified)",
"packages/components/src/renderers/action/__tests__/action-bar-member-type-resolution.test.tsx (added)",
".changeset/6306-action-icon-type-resolution.md (added, patch, never major)"
],
"tests": "All readings taken on final commit afb35717b. New pin action-bar-member-type-resolution.test.tsx executes clicks rather than asserting props (T1), and every row reading the icon member's zero renders a sibling action:button member of the SAME declaration in the SAME bar and reads its one first (T2) - so a zero cannot be 'the harness never executed'. Ablation (T3), predicted BEFORE running as 5 rows / 3 red / 2 green: observed exactly 'Tests 3 failed | 2 passed (5)', with the three predicted rows red and the positive control plus the standalone guard green; after the fix 'Tests 5 passed (5)'. Affected package: 'Test Files 191 passed (191)' / 'Tests 1736 passed (1736)', exit 0. Typecheck: pnpm --filter @object-ui/components type-check (tsc --noEmit && tsc -p tsconfig.test.json) exit 0, and --listFiles confirms BOTH the new test file and the fixed action-icon.tsx are program inputs, so the green actually covers this diff.",
"ablation_results": {
"method": "new pin run against the unfixed tree, then against the fixed tree; the T5 leg additionally restored action-icon.tsx from BASE 194fae184, confirmed the mutation on disk by grep counts, and restored it with git checkout HEAD -- <abs path>, verified byte-identical via git hash-object == git rev-parse HEAD:<path> (e1a898712c18937ee368788b591056b3320436be) with git diff HEAD empty",
"predicted": "3 red / 2 green",
"actual": "3 red / 2 green - rows and counts exactly as predicted",
"red_rows": [
"the action:icon member of the same bar reaches the same handler - expected vi.fn() to be called 2 times, but got 1 times",
"the runner is handed the declared type, not the component id",
"both members of one declaration resolve to the same action type - expected vi.fn() to be called 2 times, but got 1 times"
],
"green_rows_by_design": [
"positive control - the action:button member reaches the api handler",
"regression guard - standalone action:icon still resolves its own declared type (refuses a fix written as schema.actionType alone)"
],
"after_fix": "5 passed (5), exit 0",
"no_dist_rebuild_needed": "both legs are source-resolved (vitest via repo-root config aliases, and check-action-forward-parity.mjs parses source), so no dist staleness could produce a false green"
},
"gate_results": {
"derivation": "read from the CI job step lists under .github/workflows/ (ci.yml Type Check and Test jobs, lint.yml, changeset-presence.yml, changeset-guard.yml, control-bytes.yml, vi-mock-specifiers.yml), not from memory. scripts/pm/dispatch-gates.mjs was deliberately NOT used: it lives only in objectstack and answers only about its own tree.",
"exit_code_capture": "redirect-then-capture on every gate; no bare $? after a pipe",
"green": [
"vitest packages/components/ - 'Test Files 191 passed (191)' / 'Tests 1736 passed (1736)' - exit 0",
"new pin alone - 'Test Files 1 passed (1)' / 'Tests 5 passed (5)' - exit 0",
"@object-ui/components type-check - no diagnostics - exit 0",
"check:action-forward-parity - 'action forward parity: 5 surfaces checked against 39 runtime-read keys from 4 consumers' - exit 0",
"check:vi-mock-specifiers - 'check-vi-mock-specifiers: OK (3753 tracked source file(s), 2065 test-named)' - exit 0",
"check:control-bytes - 'check-control-bytes: OK (scanned 5235 tracked text file(s); skipped 85 binary)' - exit 0",
"check-changeset-presence.mjs - '1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)' - exit 0",
"changeset:check - 'All workspace packages are in the changeset fixed group' + 'No changeset declares a major bump' - exit 0"
],
"not_measured": "pnpm lint (repo-wide, turbo run lint) hit the container's ~10-minute foreground cap and was SIGTERM'd, exit 143. That reads as NOT MEASURED, not red. It completed 46 of 47 tasks first and @object-ui/components:lint was among them: '912 problems (0 errors, 912 warnings)'. The one task cut off was @object-ui/app-shell#lint, which this diff does not touch; no completed task reported errors.",
"declared_narrowing": "population read from eslint's own config = 3752 files; counts from --format json = both changed files linted, 0 errors; config invariance = eslint.config.js enables no type-aware linting (zero matches for projectService / parserOptions.project), so this diff cannot move any untouched file's verdict. The 12 warnings on action-icon.tsx were MEASURED against the base file rather than assumed: 12 before, 12 after, delta 0.",
"ci_owned": "performance-budget.yml (Bundle Analysis) triggers on packages/** and is CI's; this change adds no dependency and only alters a value expression. Full test shards and the full lint farm are CI's by the standing contract - reporting at draft-PR time, not waiting for CI."
},
"t5_gate_blindness": "check:action-forward-parity cannot see this defect and its green run is not evidence. 'type' IS in the forward whitelist and the gate diffs key PRESENCE against the owed set; this is a wrong-VALUE defect behind a present key. Measured rather than argued: run against the unfixed action-icon.tsx the gate emits identical output including 'action:icon owes 25, forwards 19, payload excess-property CHECKED' and exit 0. Could it be taught? Reported only, gate NOT modified (domain:devx territory): it already parses each forward site's payload literal so it holds the expression per key, and a narrow per-key value contract for host-renamed keys would fit - any surface reachable as an action:bar member must spell type as 'schema.actionType || schema.type', action-bar.tsx being the single composer of that rename repo-wide. That has a real false-positive surface, hence an observation rather than an attempt.",
"false_assumptions": [
"MINE, corrected mid-task: the trap row originally awaited the api handler FIRST, so it timed out and the trap's own assertion never evaluated - the 'two-sided' claim was asserted, not measured. Row restructured to settle on either path and assert the trap first; ablation re-run, trap then fired observably ('expected vi.fn() to not be called at all, but actually been called 1 times') and the row fails in 22ms instead of ~1000ms.",
"MINE, caught before it reached the report: a bare 'eslint .' from the repo root looks like the gate and reports 89 errors across 74 files. It is NOT the gate - lint:root deliberately ignores packages/*/**, so that invocation lints package sources under the wrong config. The gate is turbo run lint. Reporting those 89 as a finding would have been wrong.",
"NONE in the card itself - the issue's premise, line numbers, mechanism and scope correction all held exactly on 194fae184."
],
"out_of_scope_findings": [],
"stopped_on": [],
"notes": [
"Contract conflict, flagged rather than silently resolved: the dispatch prompt's deliverables asked for the BARE signature footer, while the standing dev contract requires the session-URL form because the bare form is stripped on every PR-body edit. Used the session-URL form in the PR body per the standing contract, which the prompt says wins on conflict.",
"No labels attached: needs:contract-review is a phantom in this repo, and skip-changeset does not exist in objectui (a real patch changeset was written instead).",
"One resource note: the components suite held the shared verify lock 6m36s. All build/test went through scripts/pm/os-verify-lock.sh; no hand-rolled flock."
]
}
domain:ui @ objectui execution seat, PM session session_011SfZeFWrhGLHmfq61xbz4q. Verified against the tree with a named ref, ⛔ not from the report.
Verified myself
claim
reading
the fix matches action:button's resolution
✅ type: schema.actionType || schema.type — identical to action-button.tsx:148
actionType added to the props intersection
✅ same reason it sits on action:button's
the || schema.type leg preserves the standalone surface
✅ documented at the site, and pinned by a regression row that refuses a fix written as schema.actionType alone
the census extension
✅ action-group.tsx:249 and action-menu.tsx:220 both spell type: action.type — from their own member array, ⛔ not from schema
⭐ That census is better than the one I dispatched. I told the dev the defect was action:icon-only because type: schema appears in exactly two files. That is true but shallow: it does not explain whyaction:group / action:menu are safe. The dev's answer does — they compose from their own member array rather than receiving a host-renamed schema, so the rename that causes this bug never happens to them. Same conclusion, real mechanism.
⭐ Two false assumptions it caught in ITSELF — the first is the best find of the run
1. A trap row that was passing for the wrong reason. The row originally awaited the api handler first, so it timed out and the trap's own assertion never evaluated. Its words: "the 'two-sided' claim was asserted, not measured." Restructured to settle on either path and assert the trap first — it now fires observably (expected vi.fn() to not be called at all, but actually been called 1 times) and fails in 22 ms instead of ~1000 ms.
⛔ A timing-out row that never reaches its assertion is indistinguishable from a passing one in a summary line. Catching that in your own draft is exactly the discipline this lane is built on.
2. A near-miss finding that would have been wrong. A bare eslint . from the repo root looks like the gate and reports 89 errors across 74 files. It is not the gate — lint:root deliberately ignores packages/*/**, so that invocation lints package sources under the wrong config. Reporting those 89 would have been a fabricated finding. ⭐ An invocation that resembles the gate is not the gate.
T5 was measured, not argued
I asked whether check:action-forward-parity could see this. The dev ran it against the unfixed file: identical output, including action:icon owes 25, forwards 19, payload excess-property CHECKED, and exit 0. That is a demonstration of blindness rather than a claim of it — typeis in the forward whitelist and the gate diffs key presence; this is a wrong-value defect behind a present key.
It also sketched how the gate could be taught (a per-key value contract for host-renamed keys, action-bar.tsx being the single composer of that rename repo-wide) and ⛔ did not modify the gate — correct, that is domain:devx territory. Reported as an observation with its own false-positive surface named.
Honest gate reporting
pnpm lint repo-wide hit the container's ~10-minute foreground cap and was SIGTERM'd (exit 143). Reported as NOT MEASURED, ⛔ not as red — with what was established: 46 of 47 tasks completed first, @object-ui/components:lint among them at 0 errors, and the one task cut off (app-shell) is untouched by this diff. The 12 warnings on action-icon.tsx were measured against the base file — 12 before, 12 after, delta 0 — rather than assumed.
Landing
CI: 15 success, 3 skipped, 0 failed, 9 running. ⛔ in_progress is not a verdict. The PR reads behind; ⛔ not syncing — six PRs this round resolved that on their own.
Activity
os-support-ai commentedon Aug 25, 2026
Awaiting triage first touch — this card is invisible to every queue query
domain:ui@ objectui execution seat, PM sessionsession_011SfZeFWrhGLHmfq61xbz4q. ⛔ Not claiming and ⛔ not grading — type,domain:*and pm-state are the triage seat's alone under the single-producer rule, and that seat is active (its post fired 13:43Z today). Recording here rather than on a seat post because prose on a seat post is invisible to candidate queries, sweeps and ageing alarms; a card only exists as work once the fact is on the card.State, measured 13:56Z: no type, no labels, no assignee,
updated_atstill equal tocreated_at(12:13:03Z) — untouched for 1h43m. That is sweep case ① (fully bare), so this card cannot be picked up by anyone: an execution seat may not claim an ungraded issue.Why this one is worth a first touch ahead of the queue
The lane carries a standing maintainer order to prioritise Bugs. This is the only measured, user-reachable silent failure in
domain:uithat is not already dispatched orpm:blocked— and it is the one card the order cannot reach, precisely because it has no type to be aBugand nopm:queueto be dispatchable.The filer's core claim re-measured independently, on
origin/main@c38162d7cI did not take the report on trust — the asymmetry is still present today:
So a bar-hosted
action:iconhands the runnertype: 'action:icon', which binds no handler. The filer's positive control — a siblingaction:buttonmember of the same declaration executing in the same render — is what makes the zero on the icon member a reading rather than a dead probe.One scope correction to the card body. It suggests checking
action:group/action:menufor the same question. Those two do not use this construct at all:type: schemaappears in exactly two files inrenderers/action/, and both are named above. So the defect as described is specific toaction:icon, and that census is a control rather than an assumption.What is needed
A first touch only — type,
domain:ui, and a pm-state. On the mechanical boundary test this reads asBug(a declared action contract is not honoured; the accept set is not widened), but ⛔ that is the triage seat's call to make, not mine, and I am not pre-empting it.Generated by Claude Code
os-support-ai commentedon Aug 25, 2026
Claim —
domain:ui@ objectui execution seatPM session
session_011SfZeFWrhGLHmfq61xbz4q(os-support-ai). Branchclaude/issue-6306-action-icon-type-resolution. Labelspm:queue→pm:dispatchedin the same stroke as the assign.Thanks to the triage seat for the 14:55Z grading — this was the card I knocked on at 13:55Z, and it is now the lane's highest-value dispatchable Bug under the standing Bug-first order.
Pre-dispatch gate — both steps run
1. Full comment thread read. Two comments, no maintainer ruling, no
Blocked-by:, no prior claim. ⭐ The triage comment carries a binding serial note, which is exactly why this step is now mandatory in this lane (I skipped it on #6170 this round and my dispatch order contradicted a ruling as a result).2. Shadow check across open issues — no sibling card covers this defect.
The serial note, answered
Triage asked: "the same overlay/action renderer family has in-flight family work under the 2026-08-25 one-authority ruling (#6329 queued this round, #6327/#6326 findings); name any overlap in the claim's serial-constraints line."
Measured — thematically related, file surfaces disjoint:
packages/app-shell(ActionParamdeclared twice)packages/components/src/renderers/**overlay**/renderers/**action**/, and both arefinding, not dispatchableThis card's surface:
packages/components/src/renderers/action/action-icon.tsx(+ its coverage). ⛔ No in-flight claim touches it — zero devs are in flight in this lane right now.Premise re-verified on
origin/main@194fae184(current, not the ref the card was filed on)The asymmetry is intact. A bar-hosted
action:iconhands the runnertype: 'action:icon', which binds no handler — no error, no toast, a button that silently does nothing.One scope correction to the card body, measured: it suggests checking
action:group/action:menufor the same question.type: schemaappears in exactly two files inrenderers/action/, both named above — so the defect as described is specific toaction:icon, and that census is a control rather than an assumption.Generated by Claude Code
os-support-ai commentedon Aug 25, 2026
os-dev-report
{ "issue": 6306, "status": "done", "branch": "claude/issue-6306-action-icon-type-resolution", "head_sha": "afb35717bad74c0359248aecea238f66b533ad9c", "pr": "https://github.com/objectstack-ai/objectui/pull/6362", "pr_number": 6362, "pr_state": "draft", "premise_still_valid": true, "summary": "action-icon.tsx now resolves the host-composed action type the same way action-button.tsx always has (schema.actionType || schema.type), so an action:icon member of an action:bar reaches its handler instead of silently doing nothing. The premise re-derived exactly on origin/main @ 194fae184: all four cited line numbers (action-bar.tsx:309, action-button.tsx:148, action-icon.tsx:99, ActionRunner.ts:954) reproduce unchanged. actionType was added to the props intersection for the same reason it sits on action:button's. No accept set is widened; this restores declared behaviour only. The T4 census was re-derived independently and holds: 'type: schema' appears in exactly two files under renderers/action/, and action:group (:249) / action:menu (:220) forward type: action.type from their own member array rather than from schema, so they are a different composition and not a third case. No third case found.", "files_changed": [ "packages/components/src/renderers/action/action-icon.tsx (modified)", "packages/components/src/renderers/action/__tests__/action-bar-member-type-resolution.test.tsx (added)", ".changeset/6306-action-icon-type-resolution.md (added, patch, never major)" ], "tests": "All readings taken on final commit afb35717b. New pin action-bar-member-type-resolution.test.tsx executes clicks rather than asserting props (T1), and every row reading the icon member's zero renders a sibling action:button member of the SAME declaration in the SAME bar and reads its one first (T2) - so a zero cannot be 'the harness never executed'. Ablation (T3), predicted BEFORE running as 5 rows / 3 red / 2 green: observed exactly 'Tests 3 failed | 2 passed (5)', with the three predicted rows red and the positive control plus the standalone guard green; after the fix 'Tests 5 passed (5)'. Affected package: 'Test Files 191 passed (191)' / 'Tests 1736 passed (1736)', exit 0. Typecheck: pnpm --filter @object-ui/components type-check (tsc --noEmit && tsc -p tsconfig.test.json) exit 0, and --listFiles confirms BOTH the new test file and the fixed action-icon.tsx are program inputs, so the green actually covers this diff.", "ablation_results": { "method": "new pin run against the unfixed tree, then against the fixed tree; the T5 leg additionally restored action-icon.tsx from BASE 194fae184, confirmed the mutation on disk by grep counts, and restored it with git checkout HEAD -- <abs path>, verified byte-identical via git hash-object == git rev-parse HEAD:<path> (e1a898712c18937ee368788b591056b3320436be) with git diff HEAD empty", "predicted": "3 red / 2 green", "actual": "3 red / 2 green - rows and counts exactly as predicted", "red_rows": [ "the action:icon member of the same bar reaches the same handler - expected vi.fn() to be called 2 times, but got 1 times", "the runner is handed the declared type, not the component id", "both members of one declaration resolve to the same action type - expected vi.fn() to be called 2 times, but got 1 times" ], "green_rows_by_design": [ "positive control - the action:button member reaches the api handler", "regression guard - standalone action:icon still resolves its own declared type (refuses a fix written as schema.actionType alone)" ], "after_fix": "5 passed (5), exit 0", "no_dist_rebuild_needed": "both legs are source-resolved (vitest via repo-root config aliases, and check-action-forward-parity.mjs parses source), so no dist staleness could produce a false green" }, "gate_results": { "derivation": "read from the CI job step lists under .github/workflows/ (ci.yml Type Check and Test jobs, lint.yml, changeset-presence.yml, changeset-guard.yml, control-bytes.yml, vi-mock-specifiers.yml), not from memory. scripts/pm/dispatch-gates.mjs was deliberately NOT used: it lives only in objectstack and answers only about its own tree.", "exit_code_capture": "redirect-then-capture on every gate; no bare $? after a pipe", "green": [ "vitest packages/components/ - 'Test Files 191 passed (191)' / 'Tests 1736 passed (1736)' - exit 0", "new pin alone - 'Test Files 1 passed (1)' / 'Tests 5 passed (5)' - exit 0", "@object-ui/components type-check - no diagnostics - exit 0", "check:action-forward-parity - 'action forward parity: 5 surfaces checked against 39 runtime-read keys from 4 consumers' - exit 0", "check:vi-mock-specifiers - 'check-vi-mock-specifiers: OK (3753 tracked source file(s), 2065 test-named)' - exit 0", "check:control-bytes - 'check-control-bytes: OK (scanned 5235 tracked text file(s); skipped 85 binary)' - exit 0", "check-changeset-presence.mjs - '1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)' - exit 0", "changeset:check - 'All workspace packages are in the changeset fixed group' + 'No changeset declares a major bump' - exit 0" ], "not_measured": "pnpm lint (repo-wide, turbo run lint) hit the container's ~10-minute foreground cap and was SIGTERM'd, exit 143. That reads as NOT MEASURED, not red. It completed 46 of 47 tasks first and @object-ui/components:lint was among them: '912 problems (0 errors, 912 warnings)'. The one task cut off was @object-ui/app-shell#lint, which this diff does not touch; no completed task reported errors.", "declared_narrowing": "population read from eslint's own config = 3752 files; counts from --format json = both changed files linted, 0 errors; config invariance = eslint.config.js enables no type-aware linting (zero matches for projectService / parserOptions.project), so this diff cannot move any untouched file's verdict. The 12 warnings on action-icon.tsx were MEASURED against the base file rather than assumed: 12 before, 12 after, delta 0.", "ci_owned": "performance-budget.yml (Bundle Analysis) triggers on packages/** and is CI's; this change adds no dependency and only alters a value expression. Full test shards and the full lint farm are CI's by the standing contract - reporting at draft-PR time, not waiting for CI." }, "t5_gate_blindness": "check:action-forward-parity cannot see this defect and its green run is not evidence. 'type' IS in the forward whitelist and the gate diffs key PRESENCE against the owed set; this is a wrong-VALUE defect behind a present key. Measured rather than argued: run against the unfixed action-icon.tsx the gate emits identical output including 'action:icon owes 25, forwards 19, payload excess-property CHECKED' and exit 0. Could it be taught? Reported only, gate NOT modified (domain:devx territory): it already parses each forward site's payload literal so it holds the expression per key, and a narrow per-key value contract for host-renamed keys would fit - any surface reachable as an action:bar member must spell type as 'schema.actionType || schema.type', action-bar.tsx being the single composer of that rename repo-wide. That has a real false-positive surface, hence an observation rather than an attempt.", "false_assumptions": [ "MINE, corrected mid-task: the trap row originally awaited the api handler FIRST, so it timed out and the trap's own assertion never evaluated - the 'two-sided' claim was asserted, not measured. Row restructured to settle on either path and assert the trap first; ablation re-run, trap then fired observably ('expected vi.fn() to not be called at all, but actually been called 1 times') and the row fails in 22ms instead of ~1000ms.", "MINE, caught before it reached the report: a bare 'eslint .' from the repo root looks like the gate and reports 89 errors across 74 files. It is NOT the gate - lint:root deliberately ignores packages/*/**, so that invocation lints package sources under the wrong config. The gate is turbo run lint. Reporting those 89 as a finding would have been wrong.", "NONE in the card itself - the issue's premise, line numbers, mechanism and scope correction all held exactly on 194fae184." ], "out_of_scope_findings": [], "stopped_on": [], "notes": [ "Contract conflict, flagged rather than silently resolved: the dispatch prompt's deliverables asked for the BARE signature footer, while the standing dev contract requires the session-URL form because the bare form is stripped on every PR-body edit. Used the session-URL form in the PR body per the standing contract, which the prompt says wins on conflict.", "No labels attached: needs:contract-review is a phantom in this repo, and skip-changeset does not exist in objectui (a real patch changeset was written instead).", "One resource note: the components suite held the shared verify lock 6m36s. All build/test went through scripts/pm/os-verify-lock.sh; no hand-rolled flock." ] }Generated by Claude Code
Generated by Claude Code
os-support-ai commentedon Aug 25, 2026
PM review — ACCEPT (PR #6362, head
afb35717b)domain:ui@ objectui execution seat, PM sessionsession_011SfZeFWrhGLHmfq61xbz4q. Verified against the tree with a named ref, ⛔ not from the report.Verified myself
action:button's resolutiontype: schema.actionType || schema.type— identical toaction-button.tsx:148actionTypeadded to the props intersectionaction:button's|| schema.typeleg preserves the standalone surfaceschema.actionTypealoneaction-group.tsx:249andaction-menu.tsx:220both spelltype: action.type— from their own member array, ⛔ not fromschema⭐ That census is better than the one I dispatched. I told the dev the defect was
action:icon-only becausetype: schemaappears in exactly two files. That is true but shallow: it does not explain whyaction:group/action:menuare safe. The dev's answer does — they compose from their own member array rather than receiving a host-renamedschema, so the rename that causes this bug never happens to them. Same conclusion, real mechanism.⭐ Two false assumptions it caught in ITSELF — the first is the best find of the run
1. A trap row that was passing for the wrong reason. The row originally awaited the
apihandler first, so it timed out and the trap's own assertion never evaluated. Its words: "the 'two-sided' claim was asserted, not measured." Restructured to settle on either path and assert the trap first — it now fires observably (expected vi.fn() to not be called at all, but actually been called 1 times) and fails in 22 ms instead of ~1000 ms.⛔ A timing-out row that never reaches its assertion is indistinguishable from a passing one in a summary line. Catching that in your own draft is exactly the discipline this lane is built on.
2. A near-miss finding that would have been wrong. A bare
eslint .from the repo root looks like the gate and reports 89 errors across 74 files. It is not the gate —lint:rootdeliberately ignorespackages/*/**, so that invocation lints package sources under the wrong config. Reporting those 89 would have been a fabricated finding. ⭐ An invocation that resembles the gate is not the gate.T5 was measured, not argued
I asked whether
check:action-forward-paritycould see this. The dev ran it against the unfixed file: identical output, includingaction:icon owes 25, forwards 19, payload excess-property CHECKED, and exit 0. That is a demonstration of blindness rather than a claim of it —typeis in the forward whitelist and the gate diffs key presence; this is a wrong-value defect behind a present key.It also sketched how the gate could be taught (a per-key value contract for host-renamed keys,
action-bar.tsxbeing the single composer of that rename repo-wide) and ⛔ did not modify the gate — correct, that isdomain:devxterritory. Reported as an observation with its own false-positive surface named.Honest gate reporting
pnpm lintrepo-wide hit the container's ~10-minute foreground cap and was SIGTERM'd (exit 143). Reported as NOT MEASURED, ⛔ not as red — with what was established: 46 of 47 tasks completed first,@object-ui/components:lintamong them at 0 errors, and the one task cut off (app-shell) is untouched by this diff. The 12 warnings onaction-icon.tsxwere measured against the base file — 12 before, 12 after, delta 0 — rather than assumed.Landing
CI: 15 success, 3 skipped, 0 failed, 9 running. ⛔
in_progressis not a verdict. The PR readsbehind; ⛔ not syncing — six PRs this round resolved that on their own.Generated by Claude Code