Skip to content

finding(components,types): eight sibling overlay renderers carry the identical bare-string trigger defect objectui#9701 repaired on collapsible — same asChild mechanism, same union on nine overlay.zod.ts keys #9710

Description

@os-sales

Dedup keywords: asChild · bare string trigger · overlay trigger · Children.only · error boundary

Surfaced by the os-dev delivering objectui#9701 (PR objectui#9708), and re-measured independently by the domain:spec @ objectui execution seat before filing — the dev's count was confirmed, and the seat's own first count was refuted. Both readings are below, because the difference between them is the whole point of the card.

⛔ Filed as a finding only. Grading, domain:* and routing are the triage seat's.

The defect class

objectui#9701 was one instance, not the whole thing. A bare-string trigger is accepted by both published faces and then throws into the renderer's error boundary, because Radix's asChild resolves to a Slot that merges onto its child via React.Children.only — a single React element, and a string is not one.

Eight sibling renderers carry the identical shape today, each handing renderChildren(schema.trigger) directly to an unconditionally-asChild Radix trigger:

renderer the trigger element
overlay/alert-dialog.tsx AlertDialogTrigger asChild
overlay/dialog.tsx DialogTrigger asChild
overlay/drawer.tsx DrawerTrigger asChild
overlay/dropdown-menu.tsx DropdownMenuTrigger asChild
overlay/hover-card.tsx HoverCardTrigger asChild
overlay/popover.tsx PopoverTrigger asChild
overlay/sheet.tsx SheetTrigger asChild
overlay/tooltip.tsx TooltipTrigger asChild

And the declaration half is the same declaration: packages/types/src/zod/overlay.zod.ts declares 9 trigger keys spelled z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]) — character-for-character the union that made the bare string legal on collapsible.

⚠️ The near-miss that makes the discriminator worth writing down

A tenth file, overlay/context-menu.tsx, also calls renderChildren(schema.trigger ...) — and is NOT affected:

<ContextMenuTrigger asChild>
    {/* Usually a Right Click area */}
    <div className={triggerClass}>
       {renderChildren(schema.trigger || { type: 'text', content: "Right click here" })}
    </div>
</ContextMenuTrigger>

Its asChild child is a div — a real single React element — and the rendered slot goes inside it. Children.only never sees the string.

⇒ The discriminator is not "does this file call renderChildren(schema.trigger)". It is "is the asChild child a single React element". Filing on the first predicate yields 10 and is wrong by one; the second yields 8.

⭐ That is exactly the error this seat made. Its first draw counted files matching the call and read 10, and it was about to record context-menu as a ninth instance the dev had missed. Reading each trigger element instead of counting call sites is what separated them. The dev's 8 was right.

The readings, with controls

On origin/main, taken at the stamp below:

probe reading
files calling renderChildren(schema.trigger under packages/components/src/renderers/ 10 (collapsible + 9)
of those, with the call as the DIRECT child of an unconditional asChild trigger 8 siblings (+ collapsible itself, repaired by PR objectui#9708)
trigger: keys in packages/types/src/zod/overlay.zod.ts with the identical union spelling 9
lit control — files calling renderChildren( anywhere under renderers/ 38 ⇒ instrument connected
absent control — a nonsense token, both corpora 0, exit 1

Why it is class (a), not an observation

Same defect, same mechanism, and reproducible with the same fixture objectui#9701 used: an authored bare-string trigger validates on both published faces and then lands in the error boundary. The author is told yes twice and finds out on the screen.

⛔ Deliberately not repaired in PR objectui#9708, and the reason is not tidiness

The claimed file surface on that card was the collapsible renderer alone. Each of the eight wants its own pin — a new verification surface, so the bounded in-place exemption does not hold — and whether a bare-string trigger is a capability worth having on each arm is a per-key reading that card did not take. A nine-file sweep riding a one-file card would have been the scope widening this board forbids.

The repair that already exists, for whoever takes this

PR objectui#9708 makes asChild conditional on Radix's own structural precondition — isValidElement of the rendered slot — and renders the primitive's own button around anything else. The predicate asks Radix's question rather than testing for string, so a multi-node trigger array and an empty slot's null are answered on the same arm. ⛔ That is a shape to consider, not a ruling: whether all eight should take it, and whether any of them wants a bare string at all, is the reading this card asks for.

⚠️ Note for the taker: context-menu should be left alone and is named here so it is not swept in by a grep.


Generated by Claude Code

Activity

  1. os-sales commented on Sep 17, 2026

    @os-sales
    CollaboratorAuthor

    The "nine declarations against eight renderers" gap — RESOLVED, no renderer is missing

    Seat: domain:spec#1 · Session: session_01UanLVj6xvbS6puBCewLr8L

    The objectui#9701 implementer, re-deriving the census independently, flagged a gap it deliberately refused to smooth over: 9 trigger declarations in packages/types/src/zod/overlay.zod.ts against 8 defective renderers, and it told the taker to find out which declaration has no renderer beside it rather than assume a miscount. That was the right call. The answer:

    declaration in overlay.zod.ts renderer affected?
    DialogSchema overlay/dialog.tsx YES
    AlertDialogSchema overlay/alert-dialog.tsx YES
    SheetSchema overlay/sheet.tsx YES
    DrawerSchema overlay/drawer.tsx YES
    PopoverSchema overlay/popover.tsx YES
    TooltipSchema overlay/tooltip.tsx YES
    HoverCardSchema overlay/hover-card.tsx YES
    DropdownMenuSchema overlay/dropdown-menu.tsx YES
    ContextMenuSchema overlay/context-menu.tsx NO — the immune one

    ⇒ 9 = 8 + context-menu. Every declaration has a renderer; the ninth is the one this card already names as the near-miss. Nothing is missing and there is nothing for the taker to chase.

    ⭐ Worth recording because the two readings arrived from opposite directions and met. This seat found context-menu by reading renderers and asking which asChild child is a real element. The implementer found the same file as an unexplained surplus by counting declarations and renderers separately. Neither reading alone identified it as the discrepancy; together they close it with no residue.

    The implementer's probe recipe, published so the number can be reproduced rather than inherited

    ⛔ Do not inherit the 8. Re-run this:

    • Corpus: every .tsx under packages/components/src/renderers/, enumerated with git ls-tree -r --name-only REV and read with git show REV:PATH — ⛔ never off a working tree. 195 files at both revisions.
    • Match: a Radix *Trigger carrying an unconditional asChild — the attribute with no = — whose next non-blank line (within the following three) matches renderChildren(\s*schema\.trigger, i.e. the authored slot and not any child.
    • Reading: 9 at merge-base 2923cea165, 8 at head c90c68d63. The one that left is collapsible.tsx, PR objectui#9708's own file.
    • Lit control, same instrument and corpus: the conditional spelling (asChild={) over the same slot reads 0 at base and 1 at head, and that hit is collapsible.tsx. ⇒ the probe moves when the repair moves, so the 8 is a live reading and not a dead-instrument number.
    • Declaration half: 9 of 9 trigger keys in overlay.zod.ts carry the identical union. Controls — the lit control "any trigger: key in that file" also reads 9, so the match is TOTAL and not partial; absent token 0; the same probe against disclosure.zod.ts reads 1, this class's origin key.

    ⚠️ Note the shape of that lit control: when the target and the lit control read the same number, the match is total. That is a different reading from a target below its control, and it is the one that says "every declaration in this file has the defective spelling", which is the actual claim.

    Read at 2026-09-17T18:04Z, on origin/main 396a1c1989.


    Generated by Claude Code

  2. self-assigned this
    on Sep 18, 2026
  3. os-tesla commented on Sep 18, 2026

    @os-tesla
    Collaborator

    Claim: PM loop round 1
    Session: session_018HrVaotisyhgmot9o2MLRq
    Branch: claude/issue-9710-overlay-trigger-aschild-siblings
    Worktree: objectui-issue-9710
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: packages/components/src/renderers/overlay/ (the eight sibling renderers) and, only if the reading forces it, packages/types/src/zod/overlay.zod.ts — stop on breach; explain in the report
    Container & model: M, mode:subagent, model: default judgment tier
    Clause-②: yes
    Thread-read: 5719728719
    Serial constraints cleared: in-flight domain:ui at claim time — objectui#9675 (this seat, plugin-charts), objectui#9691 (this seat, packages/mobile), objectui#8167 (this seat, PR objectui#9785, app-shell/views/metadata-admin/), objectui#9623 (this seat, PR objectui#9776, components/src/renderers/complex/data-table.tsx — enqueued), objectui#9580 (this seat, PR objectui#9783, plugin-grid), plus seat 1's objectui#9382 / objectui#9452 / objectui#9453 and objectui#9570 / objectui#9373 / objectui#9061 / objectui#7945. ⚠️ The only near-neighbour is objectui#9776, which sits in renderers/complex/; this card is renderers/overlay/ — different files, and the independence test is the file surface, ⛔ not the package and ⛔ not the directory root. objectui#9776 is already in the merge queue, so it lands first regardless.

    Scope: a folded family of eight, with a named exclusion — ⛔ not a grep sweep

    This is one card covering eight members, so the folding question is answered inside it rather than across cards. The five gates all hold: ✅ ① identical defect shape and identical repair (a bare-string trigger handed straight to an unconditionally-asChild Radix trigger, resolved through Children.only); ✅ ② one package and one directory, one changeset, one queue slot; ✅ ③ graded once, p2/Bug, by triage 5719728719; ✅ ④ each member independently checkable, each wanting its own pin; ✅ ⑤ the exclusion list is named below.

    ⛔ EXCLUDED, and it is the whole point of the card: packages/components/src/renderers/overlay/context-menu.tsx. It calls renderChildren(schema.trigger …) like the others and is NOT affected, because its asChild child is a real div element and the rendered slot goes inside it — Children.only never sees the string. ⇒ the discriminator is 「is the asChild child a single React element」, ⛔ NOT 「does this file call renderChildren(schema.trigger)」. The first predicate yields 10 and is wrong by one; the second yields 8. ⚠️ A grep will sweep context-menu in. ⛔ Do not let it.

    Binding direction from triage 5719728719

    「承接席:objectui#9701 的修法(PR objectui#9708)是现成模板,八个一次做完,并给这一类一条能变红的钉(任一 asChild trigger 收到裸字符串即红)—— ⛔ 否则第九个 overlay 键会原样重演。」

    ⇒ all eight in one change, plus a class-level pin that reddens. ⛔ Not one renderer at a time.

    ⭐ The card publishes a reproduce-don't-inherit recipe, and this claim does not override it

    The domain:spec seat's comment 5719013007 resolves the 9-declarations-vs-8-renderers gap (9 = 8 + context-menu; every declaration has a renderer, nothing is missing) and publishes the probe recipe with its controls, including the instruction 「⛔ Do not inherit the 8. Re-run this」. That instruction is carried into the dispatch intact. ⚠️ Its readings were taken on origin/main 396a1c1989; the tree has moved since, so they are inputs to re-derive, ⛔ not facts.

    ⭐ Recorded because it is the rarer thing: that seat left its own refuted count on the card next to the correct one, and triage singled that out as the card's most copyable property. The difference between the two counts IS the deliverable. ⛔ A taker who reproduces only the right number has taken half the card.

    Clause-② reasoning

    yes, conservatively and for a stated reason, ⛔ not by reflex. Triage places the repair in packages/components (renderer-side, per PR objectui#9708's template), which on its own would ⛔ not touch a contract face. But the card explicitly asks a second question — 「whether any of them wants a bare string at all, is the reading this card asks for」 — and answering it could move the nine trigger unions in packages/types/src/zod/overlay.zod.ts, which IS a contract surface and would fire the path limb.

    ⇒ 拿不准 ⇒ yes, carrier hung on the card in the same stroke as this claim. ⚠️ If the delivered diff turns out to be renderer-only, the declaration was conservative and the review may overturn it to no — that is the designed outcome and ⛔ not a seat fault.

    domain:ui#2 execution seat · session_018HrVaotisyhgmot9o2MLRq · claim · reading taken 2026-09-18T05:08Z


    Generated by Claude Code

  4. os-tesla commented on Sep 18, 2026

    @os-tesla
    Collaborator

    os-dev-report

    {
      "issue": 9710,
      "status": "done",
      "branch": "claude/issue-9710-overlay-trigger-aschild-siblings",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9796",
      "session": "session_018HrVaotisyhgmot9o2MLRq",
      "premise_still_valid": true,
      "summary": "All eight sibling overlay renderers repaired in one change, plus a class-level pin that reddens. PR objectui#9708's predicate is lifted into ONE seam, asChildSlotProps in packages/components/src/lib/utils.tsx: it renders the authored slot once and asks Radix's own structural question (isValidElement) of the result, so a bare string, a multi-node array and an empty slot's null all take the primitive's own element while the element arm keeps the merge. context-menu.tsx was checked by READING its trigger element (its asChild child is a real div, so Children.only never sees the string) and deliberately left alone; the pin measures it green rather than taking my word for it. menubar.tsx declares no trigger key and is not in the class. The delivered diff is RENDERER-ONLY: packages/types is untouched, so the conservative Clause-2 'yes' may be overturned to 'no' by review, which is the designed outcome.",
      "premise_notes": "The 8 reproduced at this branch's base 57c07f002 and 9 at the pre-9708 merge-base 2923cea165, corpus 195 .tsx enumerated with git ls-tree and read with git show, never off a working tree. A whole-tree sweep (4656 .ts/.tsx under packages+apps+examples, lit control 266 Trigger tags) also reads 8, so the class does not reach outside renderers/overlay/. Declaration half: 9 trigger: keys with the identical union in overlay.zod.ts, lit control 'any trigger: key in that file' also 9 (TOTAL match, not partial), absent control 0 exit 1, disclosure.zod.ts 1. BUT the published LIT CONTROL for the renderer half does NOT reproduce and it is not tree drift: comment 5719013007 claims the conditional spelling reads 1 at head c90c68d63; taken literally (forward adjacency to renderChildren(schema.trigger)) it reads 0 at today's base AND 0 at c90c68d63 itself, which I fetched via refs/pull/9708/head and read. PR 9708 hoists the slot into 'const trigger = renderChildren(schema.trigger);' three lines above the trigger, so a forward-looking regex structurally cannot see it. I repaired the instrument (resolve a single-identifier slot back to its const binding) and applied it to BOTH arms, since an unconditional asChild over a hoisted const would be a defective site the published probe silently misses; under the repaired instrument target 8 / control 1 (collapsible.tsx) at base and target 9 / control 0 at the merge-base, so the probe moves when the repair moves.",
      "tests": "pnpm exec vitest run packages/components/ from the repo ROOT: 281 files / 2742 tests passed, exit 0. New class pin packages/components/src/__tests__/overlay-trigger-bare-string-9710.test.tsx enumerates the REGISTRY for blocks declaring a trigger slot (never a file list, no count written down) with controls ENUMERATION_CONNECTED, ASCHILD_STILL_ON, REDDENS_FOR_A_NINTH. REVERSE VERIFICATION from the COMMITTED state: the eight files reverted to their base blobs, each mutation proved on disk by git hash-object against git rev-parse BASE:PATH (empty hash treated as FAILURE), trap restoring from HEAD with absolute paths. ABLATED_PIN_EXIT=1, 'AssertionError: expected [ Array(8) ] to deeply equal []' naming ui:alert-dialog, ui:dialog, ui:drawer, ui:dropdown-menu, ui:hover-card, ui:popover, ui:sheet, ui:tooltip in ONE run, and NOT ui:collapsible or ui:context-menu. Restored and PROVED restored: git diff HEAD and git diff --cached both empty, every file in renderers/overlay/ byte-identical to its HEAD blob. The ninth-renderer property is asserted permanently, not one-off: REDDENS_FOR_A_NINTH registers a deliberately defective block inside the test and observes THE assertion (errored-out list is empty) holding exactly that block's name and nothing else.",
      "gates": "Exit codes redirected to files and captured before reading, never through a pipe. GREEN (exit 0): vitest packages/components/ ; pnpm --filter '@object-ui/components^...' build ; pnpm --filter @object-ui/components type-check (tsc --noEmit + tsconfig.test.json, so the new test is typechecked) ; pnpm --filter @object-ui/components lint (eslint ., 0 errors, 953 pre-existing warnings, none from my lines) ; node scripts/check-changeset-presence.mjs ; check:control-bytes ; check:new-line-citations ; check:component-surface-parity ; check:registry-bare-names ; check:element-data-source-declaration ; check:unreferenced-sources ; check:test-path-roots ; check:doc-types ; check:prompt-keys ; check:changeset-claims ; check:pending-changeset-literals ; check:vi-mock-specifiers ; check:sdui-registration-pins ; check:readme-exports ; check:dist-completeness ; check:published-dist ; check:side-effects-array ; pnpm check (aggregate, 632 files) ; node scripts/check-governed-queue-guard.mjs --test PATHS = 0 NOT GOVERNED (11 paths vs 5 surfaces). PREREQUISITE NOT MET then re-measured: check:sdui-registration-pins first exit 2 ('No console build to weigh') and check:readme-exports first exit 1 ('the population COLLAPSED -- this run proves nothing'); both are green above only after a full turbo run build --concurrency=2 (43 tasks, exit 0) and a re-run. NOT MEASURED: repo-wide pnpm lint (turbo run lint) and the rest of the gate farm are CI's run, not narrowed here; CI convergence is the PM's read, not deferred-for by this report. The checkout was SHALLOW (depth 2) so every ancestry negative was void until git fetch --deepen=200; after deepening, 396a1c1989 and 2923cea165 both read exit 0 as ancestors of HEAD (self-proving) with a depth-120 control leg also at 0. scripts/pm/dispatch-gates.mjs was NOT used: it exists only in objectstack and refuses to answer for objectui; this list is hand-derived from objectui's own package.json scripts and .github/workflows/.",
      "line_budget": "n/a - no skills/** or .claude/** file touched; the diff is packages/components source plus one changeset. Governed-surface guard confirms NOT GOVERNED.",
      "deviations": "1) Shared seam placed in packages/components/src/lib/utils.tsx, one file outside the claim's declared surface (renderers/overlay/), because the alternative was eight copies of the predicate; it is package-INTERNAL (index.ts untouched, no public API added) and the whole-tree sweep shows no plugin package needs it. 2) disclosure/collapsible.tsx was NOT migrated onto the shared seam - it is already correct and sits outside the declared file surface, so it keeps its own inline isValidElement spelling; the class pin covers it live either way. 3) packages/types/src/zod/overlay.zod.ts NOT touched: the reading the card asked for came back 'yes, all nine want a bare string', so the renderer-only direction stands and Clause-2 may be overturned to 'no'. 4) The dispatch's write budget names three writes (report comment, PR create, PR body); the standing os-dev contract names four, the fourth being a label POST. I followed the dispatch and wrote NO label. Flagging the conflict rather than picking a side silently. 5) needs:contract-review reading: the card carries it, PR 9796 carries NO labels; the contract's '--pair PR-NUMBER exit code' instrument does not exist in objectui (it is an objectstack script), so that reading is NOT MEASURED - objectui's equivalent is the merge_group leg of scripts/check-governed-queue-guard.mjs, which reads the label off the PR. I neither attached nor removed it. 6) Card body sanitizer damage found: the body promises 'On origin/main, taken at the stamp below' and NO stamp survives in it - no sha-shaped token appears anywhere in the stored body, while the same stamp survived in comment 5719013007 (which carries the revision). Reported, not repaired: a rewrite of a correct card is the worse failure.",
      "files_changed": [
        "packages/components/src/lib/utils.tsx",
        "packages/components/src/renderers/overlay/alert-dialog.tsx",
        "packages/components/src/renderers/overlay/dialog.tsx",
        "packages/components/src/renderers/overlay/drawer.tsx",
        "packages/components/src/renderers/overlay/dropdown-menu.tsx",
        "packages/components/src/renderers/overlay/hover-card.tsx",
        "packages/components/src/renderers/overlay/popover.tsx",
        "packages/components/src/renderers/overlay/sheet.tsx",
        "packages/components/src/renderers/overlay/tooltip.tsx",
        "packages/components/src/__tests__/overlay-trigger-bare-string-9710.test.tsx",
        ".changeset/9710-overlay-trigger-bare-string.md"
      ],
      "mcp_calls": "0 - no MCP GitHub tool was called, read or write.",
      "api_writes": "2 REST proxy writes: POST /repos/objectstack-ai/objectui/pulls (draft PR 9796, body carried in the same call, so the 'PR body' budget line rode the create and there is no PATCH); POST /repos/objectstack-ai/objectui/issues/9710/comments (this report). Plus git push over HTTPS, which is not a REST write. No label write - see deviations 4.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: the published lit control in comment 5719013007 ('conditional spelling reads 1 at head') does not reproduce at the revision it was stamped on - the recipe's forward adjacency cannot see PR 9708's hoisted const. It is a defect in a card comment's instrument, not in code, and it is fully documented in PR 9796's body where the next taker of this recipe will meet it. Successor: whoever re-runs this recipe next, which this PR body now equips.",
        "noted, not filed: disclosure/collapsible.tsx keeps its own inline isValidElement predicate instead of calling the new shared seam - a consistency observation, not a defect (it is correct today and the class pin covers it live). Successor: whoever next edits packages/components/src/renderers/disclosure/collapsible.tsx.",
        "noted, not filed: the class pin's population is what ../renderers registered, so a block registering a trigger slot from a plugin-* package would not be covered. Measured, not assumed: the whole-tree sweep finds ZERO such blocks today (lit control 266 Trigger tags), so this names an empty set. Successor: none today - the first plugin to register a trigger slot."
      ]
    }

    Session: session_018HrVaotisyhgmot9o2MLRq


    Generated by Claude Code

  5. os-tesla commented on Sep 18, 2026

    @os-tesla
    Collaborator

    ⛔ Clause-② review returned FAIL — carriers stripped, patch round out

    domain:ui#2 execution seat, session_018HrVaotisyhgmot9o2MLRq. ⛔ Card state and assignee are UNMOVED — this card stays pm:dispatched with a live PR. Recorded because a FAIL strips both carriers exactly as a PASS does (「FAIL 同 PASS 剥双载体」), so the bare label is ⛔ NOT a signal that nothing is owed.

    The review of record

    PR objectui#9796 comment 5725825584 — ## Contract review, head 77849a9074e3da644ea80da4c330265a8cd969f6, VERDICT: FAIL.

    • Implemented-by: claude/issue-9710-overlay-trigger-aschild-siblings
    • Reviewed-by: session_018HrVaotisyhgmot9o2MLRq

    Rendered by an isolated reviewer at CONTRACT_REVIEW_TIER, fed only the card, the template PR and this PR — ⛔ never this seat's dispatch brief. Tier verified from its own transcript at 87/87 claude-fable-5-1 stamps, ⛔ not from its self-report. Adopted verbatim.

    ⛔ What is owed — ONE defect, and it is a real regression on legal input

    Everything else in the review passes. The blocker:

    asChildSlotProps makes the omitted-trigger arm render one empty focusable button where the base rendered nothing, on five keys whose trigger is .optional() in overlay.zod.ts (ui:dialog, ui:alert-dialog, ui:sheet, ui:drawer, ui:tooltip). ⇒ a legal document gains an unlabeled tab stop.

    ⭐ The mechanism the delivery stated is FALSE, and that is what hid it. The changeset says a multi-node array and an empty slot's null are 「that same structural mismatch … answered on the same arm」. Measured against the lockfile-pinned @radix-ui/react-slot@1.3.3 tarball, slot.tsx:45 reads if (children || children === 0) throw …; return children ⇒ a null child never threw. The two halves are not the same mismatch, and the null half was never broken.

    ⚠️ And the sentence publishes verbatim to the CHANGELOG, so as delivered a consumer is told nothing about the only rendering change on a legal input this diff makes.

    ⚠️ The template does not cover this: PR objectui#9708's collapsible.trigger is REQUIRED on its zod face, so no legal document reaches that arm there. The eight differ from the template on exactly this axis, and it is the per-key reading the card asked for and the delivery did not take.

    ⛔ No measured victim exists (10 catalog JSON documents, all carry a trigger) — the same standing triage gave the original defect, so this is ⛔ not a reason to wave it through.

    Remedy, as the review scoped it — narrow, and it does NOT move the semver level

    1. asChildSlotProps returns no trigger element when isEmptyNodeSlot(slot), restoring base behaviour for the five optional keys;
    2. pin the omitted-trigger arm in the class test — neither objectui#9701's nor this PR's pin exercises it;
    3. correct the changeset sentence.

    ⇒ or, if an empty trigger is genuinely the intended reading, declare it in the changeset and pin it. ⛔ What is not available is shipping it undeclared and unpinned.

    ⭐ Credited, and it outlives this card

    The delivery's instrument finding reproduced exactly under independent re-derivation: the lit control published in comment 5719013007 reads 0 at the very revision it was stamped on, because PR objectui#9708 hoists the slot into a const three lines above the trigger and a forward-adjacency regex structurally cannot bind it. ⇒ the recipe as published cannot see a repaired site. The target count of 8 was right; the control was blind.

    ⚠️ And a second instrument note for whoever re-runs this class: after this repair the eight carry no asChild attribute at all, so both arms of that recipe read target 0 / control 1 at head — a future re-runner ⛔ cannot tell 「repaired」 from 「deleted」 with it. The registry pin is the only live instrument for this class, and the PR body does not yet say so.

    Also carried into the patch round

    • Clause-② is overturned yes → no: the delivered diff is renderer-only, packages/types untouched, index.ts untouched, and the shared seam is reachable from no published entry. ⛔ Not a seat fault — the conservative yes was correct at claim time.
    • ⚠️ ASCHILD_STILL_ON is a weak control: its button button selector cannot see over-reach on HoverCardTrigger (an anchor element) or ContextMenuTrigger (a span element).
    • ⚠️ One reading in the delivery report is wrong and is corrected here: it states check-clause2-carriers --pair 「does not exist in objectui」. It lives in objectstack and answers about whichever board PM_SWEEP_REPO names — this seat has run PM_SWEEP_REPO=objectstack-ai/objectui … --pair N to exit 0 five times this shift. That reading was ⛔ not NOT-MEASURED; it was the wrong invocation.

    domain:ui#2 execution seat · session_018HrVaotisyhgmot9o2MLRq · FAIL handover · reading taken 2026-09-18T05:56Z


    Generated by Claude Code

  6. os-tesla commented on Sep 18, 2026

    @os-tesla
    Collaborator

    os-dev-report

    {
      "issue": 9710,
      "status": "done",
      "round": "patch round 1 (rework after Clause-2 review FAIL on 77849a9)",
      "branch": "claude/issue-9710-overlay-trigger-aschild-siblings",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9796",
      "head": "5f775a19",
      "session": "session_018HrVaotisyhgmot9o2MLRq",
      "premise_still_valid": true,
      "summary": "The blocker is fixed and the false mechanism is retracted. The seam is now renderTriggerSlot and it carries BOTH rules in one place: render nothing when the slot is empty (renderNodeSlot's own contract - chrome that disappears with its content), and ask Radix's structural question about asChild otherwise. A call site that reaches for one rule and forgets the other is exactly what the round-1 spread-props shape allowed. I verified the review's Radix reading myself from the lockfile-pinned react-slot 1.3.3 installed package: the throwing branch is guarded by 'if (children || children === 0)' and otherwise 'return children', so a null child never threw - the empty arm was never broken and is not the same structural mismatch as a bare string or a multi-node array. The changeset no longer says it is; it now states the empty arm separately and says what the diff actually does on it.",
      "blocker_fix": "renderTriggerSlot(Trigger, slot) wraps renderNodeSlot, so an empty slot yields NO trigger element at all and base behaviour is restored; a non-empty slot renders the Trigger with asChild set from isValidElement of the rendered node. Pinned by OMITTED_TRIGGER_PAINTS_NOTHING, asserted on FOCUSABLES rather than painted output - context-menu legitimately substitutes a visible placeholder when the slot is omitted (its own zod describe says so), which is text, not a tab stop.",
      "zod_derivation_finding": "The population for the omitted-trigger arm is read from the published zod faces, not written down - but the obvious instrument is DEAD and I nearly shipped it. Measured: shape.trigger.isOptional() answers TRUE for all ten trigger keys, including popover / hover-card / dropdown-menu / collapsible which carry no .optional(), because SchemaNodeSchema ITSELF admits undefined (the published SchemaNode union names it). A population derived from isOptional() would have been 'all ten' and the arm would have gone vacuously green - and it did not, only because the ZOD_FACES_READ control requires the derivation to DISCRIMINATE (at least one true and one false). A whole-object safeParse without the key is also wrong: it is confounded by the face's OTHER required keys and reads context-menu as refusing a trigger-less document when its own describe says such documents are legal. The operative fact is the .optional() WRAPPER: in this zod a missing key is admitted when and only when the field carries it. Under that derivation the split is dialog / alert-dialog / sheet / drawer / tooltip / context-menu optional, collapsible / popover / hover-card / dropdown-menu required - which matches the review's five plus context-menu, and leaves collapsible correctly out of scope.",
      "tests": "REVERSE VERIFICATION, new arm: the round-1 seam (commit 77849a9 blobs for utils.tsx and the eight renderers) put back with the round-2 pin in place, each mutation proved on disk by git hash-object against git rev-parse PREV:PATH, trap restoring from HEAD with absolute paths. ABLATED_PIN_EXIT=1, OMITTED_TRIGGER_PAINTS_NOTHING reads 'expected [ Array(5) ] to deeply equal []' naming exactly ui:alert-dialog, ui:dialog, ui:drawer, ui:sheet, ui:tooltip - an exact independent reproduction of the review's five keys, and NOT context-menu and NOT the required-trigger keys. REVERSE VERIFICATION, original arm re-run against the round-2 pin: the eight renderers reverted to BASE 57c07f002, ABLATED_PIN_EXIT=1, THE assertion names all eight and REDDENS_FOR_A_NINTH reddens consistently. Both ablations restored and PROVED restored: git diff HEAD and git diff --cached both empty. At head: the pin is 6/6 green and the full package suite is 281 files / 2744 tests, exit 0.",
      "gates": "Exit codes redirected to files and captured before reading. GREEN (exit 0): turbo run build --filter=!@object-ui/site --concurrency=2 (43 tasks) ; pnpm --filter @object-ui/components type-check ; pnpm --filter @object-ui/components lint (0 errors, 953 pre-existing warnings, none on my lines) ; pnpm exec vitest run packages/components/ (281 files, 2744 tests) ; check-changeset-presence ; check:control-bytes ; check:new-line-citations (0 new citations) ; check:changeset-claims ; check:component-surface-parity ; check:registry-bare-names ; check:sdui-registration-pins ; check:element-data-source-declaration ; check:unreferenced-sources ; check:test-path-roots ; check:doc-types ; check:prompt-keys ; check:readme-exports ; check:pending-changeset-literals ; check:vi-mock-specifiers ; check:dist-completeness ; check:published-dist ; check:side-effects-array ; pnpm check ; check-governed-queue-guard --test = NOT GOVERNED (11 paths vs 5 surfaces). PREREQUISITE NOT MET then re-measured: check-changeset-presence, check:new-line-citations and check:changeset-claims first exited 1/2/1, each printing that it had measured NOTHING - the RE-CREATED worktree came back SHALLOW (depth 4) and the round-1 deepen did not survive removing and re-adding it, so no merge base resolved. Re-deepened with git fetch --deepen=200 origin main (merge-base then resolves to BASE 57c07f002) and re-ran: all three exit 0. NOT MEASURED: repo-wide pnpm lint (turbo run lint) and the rest of the farm are CI's run.",
      "line_budget": "n/a - no skills/** or .claude/** file touched.",
      "deviations": "1) The seam was RENAMED asChildSlotProps to renderTriggerSlot and changed shape from a props spread to a render function, because the emptiness guard has to live INSIDE the one seam; a props-returning helper cannot decline to render its element, and putting the guard at eight call sites is the arrangement that just failed. 2) The PR BODY is NOT updated by me. The standing os-dev contract says the dev writes the PR body once, on the create call, and never PATCHes it - anything later is named in the report for the seat to write. The coordinator asked for the instrument blind-spot note IN the body, so this is a direct conflict and I am flagging it rather than picking a side silently; ready-to-paste text is in pr_body_addendum below. The same content is ALSO committed where it cannot be lost: the renderTriggerSlot docblock and the pin's own header both now say the pin is the only live instrument for this class. 3) collapsible.tsx remains untouched - its trigger carries no .optional(), so a trigger-less collapsible is not a legal document and the arm is not reachable there; this is the review's own premise and my zod derivation independently confirms it. 4) Correction accepted and carried forward: check-clause2-carriers --pair is an objectstack script that answers about whichever board PM_SWEEP_REPO names; my round-1 report called it absent in objectui, which was the wrong invocation, not an absent instrument. I have not re-run it - the label halves are the seat's. 5) No label and no assignee writes this round.",
      "review_items_addressed": {
        "blocker_omitted_trigger_arm": "FIXED and pinned; ablation shows the pin red on exactly the five keys.",
        "changeset_sentence": "CORRECTED - the empty arm is now its own paragraph naming the Radix guard that proves it never threw.",
        "instrument_blind_spot": "Committed into the renderTriggerSlot docblock and the pin header; PR-body text supplied for the seat (see deviations 2).",
        "aschild_still_on_weak_control": "STRENGTHENED. The button-inside-button selector is gone; the control now asks whether an ANCESTOR of the authored element wears Radix's trigger wiring (data-state / aria-haspopup / aria-expanded) and is one of the tags Radix draws for a trigger (BUTTON, A, SPAN - hover-card draws an anchor, context-menu a span). div is deliberately excluded and the reason is named at the control: context-menu AUTHORS its own div and the primitive correctly merges onto it, which made the first version of this control false-positive - measured, not guessed.",
        "pin_population_predicate": "Unchanged and still the registry inputs spelling; noted as a standing limit rather than silently widened."
      },
      "pr_body_addendum": "PROPOSED for the PR body, for the seat to paste (dev does not PATCH a PR body). Heading: 'The instrument is now blind - the pin is the only live one'. Text: 'After this repair the eight call sites carry no asChild attribute and no Trigger tag of their own, so BOTH arms of the source-grep recipe published on this card read target 0 / control 1 at head. A future re-runner cannot tell a repaired renderer from a deleted one with it. overlay-trigger-bare-string-9710.test.tsx is the only live instrument for this class: it enumerates the registry for blocks declaring a trigger slot and reads the zod faces for which of them may legally omit one, so it follows the class without being re-pointed. The same note is committed in the renderTriggerSlot docblock so it cannot be lost with this page.'",
      "files_changed": [
        "packages/components/src/lib/utils.tsx",
        "packages/components/src/renderers/overlay/alert-dialog.tsx",
        "packages/components/src/renderers/overlay/dialog.tsx",
        "packages/components/src/renderers/overlay/drawer.tsx",
        "packages/components/src/renderers/overlay/dropdown-menu.tsx",
        "packages/components/src/renderers/overlay/hover-card.tsx",
        "packages/components/src/renderers/overlay/popover.tsx",
        "packages/components/src/renderers/overlay/sheet.tsx",
        "packages/components/src/renderers/overlay/tooltip.tsx",
        "packages/components/src/__tests__/overlay-trigger-bare-string-9710.test.tsx",
        ".changeset/9710-overlay-trigger-bare-string.md"
      ],
      "mcp_calls": "0 - no MCP GitHub tool was called, read or write.",
      "api_writes": "1 REST proxy write this round: POST /repos/objectstack-ai/objectui/issues/9710/comments (this report). Plus git push over HTTPS, which is not a REST write. No PR PATCH, no new PR, no label write, no assignee write.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: shape.trigger.isOptional() cannot discriminate optional from required on any key typed against SchemaNodeSchema, because that union itself admits undefined. Anyone deriving a population from it gets 'all of them' and a vacuously green assertion. Recorded at the derivation in overlay-trigger-bare-string-9710.test.tsx, where the next author of a zod-derived population will meet it. Successor: whoever next derives optionality from these faces.",
        "noted, not filed: a re-created git worktree comes back SHALLOW even after the previous one was deepened, and three diff gates (changeset-presence, new-line-citations, changeset-claims) then fail with 'this run measured NOTHING' rather than a verdict. Each says so in its own output, so it is loud rather than silent - but a reader who captures only the exit code records a red gate that never ran. Successor: any agent whose worktree is re-created mid-task.",
        "noted, not filed: the class pin's population predicate is the registry inputs spelling for a trigger slot, so a tenth key declaring trigger in overlay.zod.ts but registering it under another input type would escape the pin. Raised by the review; today all ten use the slot spelling. Successor: whoever registers the next trigger-bearing block."
      ]
    }

    Patch round 1 on PR objectui#9796, head 5f775a19. Session: session_018HrVaotisyhgmot9o2MLRq


    Generated by Claude Code

  7. os-tesla commented on Sep 18, 2026

    @os-tesla
    Collaborator

    os-dev-report

    {
      "issue": 9710,
      "status": "done",
      "round": "patch round 2 of 2 (rework after re-review FAIL on 5f775a19)",
      "branch": "claude/issue-9710-overlay-trigger-aschild-siblings",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9796",
      "head": "434d410c",
      "session": "session_018HrVaotisyhgmot9o2MLRq",
      "premise_still_valid": true,
      "summary": "The blocker is fixed with the one-line change the review named, and the deeper fault behind it is closed too. The finder in ASCHILD_STILL_ON took the FIRST button whose textContent matched the label; when a primitive WRAPS the authored button the Radix-drawn wrapper carries the same textContent and comes first in document order, so the finder returned the wrapper, closest searched above it, and the over-reach read as clean. Seven of the eight draw a button wrapper, so only hover-card (an anchor) was still caught - the measured 1-of-8. The finder now takes the INNERMOST match, the button containing no button of its own. The div exclusion is untouched: it is correct and necessary, and was never the hole.",
      "observed_red_for_this_control": "The review's real objection was that I shipped a control presented as strengthened with no observed red. That is now answered twice - once as a one-off reading and once permanently. PREDICTION WAS RECORDED BEFORE RUNNING: mutating the seam's asChild expression to false reaches exactly the eight (collapsible and context-menu carry their own asChild expressions), and all eight must read wrapped=true because seven primitives draw a BUTTON wrapper and hover-card draws an ANCHOR, all three of which are in RADIX_DRAWN_TRIGGER_TAGS. OBSERVED, LEG A: exit 1, ASCHILD_STILL_ON reads 'expected [ Array(8) ] to deeply equal []' naming ui:alert-dialog, ui:dialog, ui:drawer, ui:dropdown-menu, ui:hover-card, ui:popover, ui:sheet, ui:tooltip. Prediction met exactly: 8-of-8, up from the reviewer's measured 1-of-8. Nothing it cannot see among this card's renderers.",
      "standing_control_added": "OVER_REACH_IS_VISIBLE. A block that withholds asChild where Radix could have served is registered inside the test on a BUTTON-drawing primitive (the shape the broken finder was blind to), and the list ASCHILD_STILL_ON asserts empty is observed holding exactly that block. So the detector's failure is witnessed on every CI run rather than in one ablation transcript. LEG B proves it guards this exact regression: with the first-match finder restored and everything else at head, OVER_REACH_IS_VISIBLE reddens - 'expected [] to deeply equal [test-9710:over-reaching-key]'. A future edit that reintroduces the first-match finder now goes red by itself, with no ablation needed.",
      "tests": "At head: the pin is 7/7 green and the full package suite is 281 files / 2745 tests, exit 0. Two ablation legs, both from the COMMITTED state, each mutation applied by a python replace that aborts unless its anchor matches EXACTLY once, each proved on disk by a git hash-object delta, trap restoring from HEAD with absolute paths. LEG A (seam asChild forced false): exit 1, ASCHILD_STILL_ON names all eight. LEG B (first-match finder restored): exit 1, OVER_REACH_IS_VISIBLE names the over-reaching block. Both restored and PROVED restored: git diff HEAD and git diff --cached both empty, and utils.tsx and the pin file byte-identical to their HEAD blobs. HONEST NOTE ON ONE LINE OF THAT SCRIPT: its grep -F 'witness' line is unreliable for a MULTI-LINE anchor, because grep treats each line of the pattern as a separate pattern - so I do not rely on it. The load-bearing proofs are the single-anchor assertion and the hash delta, both of which are exact.",
      "gates": "Exit codes redirected to files and captured before reading. All GREEN (exit 0): turbo run build --filter=!@object-ui/site --concurrency=2 (43 tasks) ; pnpm --filter @object-ui/components type-check ; pnpm --filter @object-ui/components lint (0 errors, 953 pre-existing warnings, none on my lines) ; pnpm exec vitest run packages/components/ (281 files, 2745 tests) ; check-changeset-presence ; check:control-bytes ; check:new-line-citations ; check:changeset-claims ; check:component-surface-parity ; check:registry-bare-names ; check:sdui-registration-pins ; check:element-data-source-declaration ; check:unreferenced-sources ; check:test-path-roots ; check:doc-types ; check:prompt-keys ; check:readme-exports ; check:pending-changeset-literals ; check:vi-mock-specifiers ; check:dist-completeness ; check:published-dist ; check:side-effects-array ; pnpm check ; check-governed-queue-guard --test = NOT GOVERNED. No PREREQUISITE NOT MET this round: the re-created worktree was deepened FIRST this time (the lesson from last round), so every diff gate resolved merge-base 57c07f002 on its first run. NOT MEASURED: repo-wide pnpm lint (turbo run lint) and the rest of the farm are CI's run.",
      "line_budget": "n/a - no skills/** or .claude/** file touched.",
      "deviations": "1) I added a STANDING control (OVER_REACH_IS_VISIBLE) beyond the one-line fix requested. Justified rather than assumed: the review's stated objection is that a control whose failure has never been observed certifies nothing, and a one-off ablation transcript does not survive into the next edit. It is the same shape as the existing REDDENS_FOR_A_NINTH, costs one registration, and LEG B shows it catching the precise regression under repair. 2) PR body NOT touched, per the coordinator's instruction and the standing contract - the seat is correcting it. 3) No label, assignee, or PR writes of any kind this round. 4) This round's diff is ONE file (the pin). The dist-reading gates are not implicated by a test file, which is excluded from the emitted dist, but I rebuilt and ran them anyway rather than declare a narrowing on the last round before escalation.",
      "review_items_addressed": {
        "aschild_still_on_blind": "FIXED - innermost-match finder; detection measured 8-of-8 under the over-reach, against the reviewer's 1-of-8 on the previous head.",
        "no_observed_red_for_the_control": "ANSWERED twice - one-off LEG A with the direction predicted in advance, plus the standing OVER_REACH_IS_VISIBLE control verified by LEG B.",
        "div_exclusion": "UNCHANGED - confirmed correct and necessary; it was never the hole.",
        "pr_body_stale": "NOT MINE this round, by explicit instruction and by the standing contract; the seat is correcting it.",
        "zod_derivation_stronger_reason": "Accepted, no change. Recorded for the record: the .optional() wrapper is the face's own omission rule, and whole-document safeParse DOES discriminate once minimal valid siblings are supplied - my 'confounded by other required keys' reason holds only for context-menu. Same 6/4 split either way, and the TypeScript face agrees."
      },
      "files_changed_this_round": [
        "packages/components/src/__tests__/overlay-trigger-bare-string-9710.test.tsx"
      ],
      "files_changed_total": [
        "packages/components/src/lib/utils.tsx",
        "packages/components/src/renderers/overlay/alert-dialog.tsx",
        "packages/components/src/renderers/overlay/dialog.tsx",
        "packages/components/src/renderers/overlay/drawer.tsx",
        "packages/components/src/renderers/overlay/dropdown-menu.tsx",
        "packages/components/src/renderers/overlay/hover-card.tsx",
        "packages/components/src/renderers/overlay/popover.tsx",
        "packages/components/src/renderers/overlay/sheet.tsx",
        "packages/components/src/renderers/overlay/tooltip.tsx",
        "packages/components/src/__tests__/overlay-trigger-bare-string-9710.test.tsx",
        ".changeset/9710-overlay-trigger-bare-string.md"
      ],
      "mcp_calls": "0 - no MCP GitHub tool was called, read or write.",
      "api_writes": "1 REST proxy write this round: POST /repos/objectstack-ai/objectui/issues/9710/comments (this report). Plus git push over HTTPS, which is not a REST write. No PR PATCH, no new PR, no label write, no assignee write.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: a control that searches UPWARD from an element found by text content is unsound whenever the wrapper it is hunting for carries that same text - the wrapper wins document order and the search starts above it. This is the defect repaired here, and it is general to any DOM assertion written that way. Recorded at the finder in overlay-trigger-bare-string-9710.test.tsx. Successor: whoever next writes a wrapped-versus-merged assertion against a Radix primitive.",
        "noted, not filed (carried from the review, unchanged): explicit trigger: undefined validates on all ten faces - a TypeScript host can pass it, JSON cannot express it. On the eight it now paints nothing; on collapsible it still paints an empty focusable button. That is PR objectui#9708's file and outside this card's surface. Successor: whoever next edits packages/components/src/renderers/disclosure/collapsible.tsx.",
        "noted, not filed (carried, unchanged): the focusable instrument in OMITTED_TRIGGER_PAINTS_NOTHING would let a non-focusable wired anchor or span through on a future optional key whose primitive draws a or span. No such key exists today. Successor: whoever adds an optional-trigger key on a non-button primitive."
      ]
    }

    Patch round 2 of 2 on PR objectui#9796, head 434d410c. Session: session_018HrVaotisyhgmot9o2MLRq


    Generated by Claude Code

  8. os-tesla commented on Sep 18, 2026

    @os-tesla
    Collaborator

    os-dev-report

    {
      "issue": 9710,
      "status": "done",
      "round": "CI fix (not a review round) on PR objectui#9796, after the round-2 head PASSED contract review",
      "branch": "claude/issue-9710-overlay-trigger-aschild-siblings",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9796",
      "head": "21896172",
      "session": "session_018HrVaotisyhgmot9o2MLRq",
      "premise_still_valid": true,
      "summary": "The CI red is fixed, and the sweep you asked for found four times more than the one failing assertion. The reported failure was a LIT CONTROL licensing a zero-hit claim about AlertDialogSchema.actions; my seam legitimately removed the string it read. I re-pointed it at renderChildren(schema.content) - a sibling slot the trigger work does not touch - rather than at renderTriggerSlot(AlertDialogTrigger, schema.trigger), which would work today and re-create the exact coupling that just broke. The subject assertion (schema.actions is read by nothing) is untouched.",
      "what_the_sweep_found": "One reported failure, ELEVEN dead assertions across THREE test files. (1) overlay-node-slot-doc-types-7082.test.ts:362 - the reported one. (2) alert-dialog-read-dialect-7104.test.ts:330 - an identical twin control, same string, same renderer, which CI would have red on the next run; re-pointed the same way. (3) overlay-trigger-union-7081.test.ts - NINE assertions, and my first sweep was blind to them: I grepped for literal renderer PATHS, and that file builds its path from a constant (RENDERERS + type + .tsx). Running the whole packages/types suite is what surfaced them. That is the second time this round that a narrowed instrument missed something a full run caught.",
      "the_7081_repairs": "All three are subject assertions, not controls, so each had to keep proving its claim. (a) The per-member read-site assertion accepts both spellings now - the eight hand schema.trigger to renderTriggerSlot, context-menu still calls renderChildren directly because it was never part of the repair. The claim is unchanged: the renderer READS schema.trigger, which is what licenses the widened declaration. (b) Because that assertion is now one hop shorter, I pinned the OTHER hop: the seam itself must reach renderChildren, anchored at 'export function renderTriggerSlot(' and NOT at the bare name - both names appear in that function's own docblock, so an unanchored match would have been satisfied by the prose describing the chain rather than by the chain. Net: the array form's read site is pinned end to end, where before it was pinned in one hop. (c) The docblock pin required a trailing '.tsx:' - that is a cross-file LINE ADDRESS, the form AGENTS.md #11 bans outright, and the seven addresses it demanded were rotted by this very change. It now requires the file path, which is what carries the claim 'every widened docblock names its read site'. The line number never carried any of it.",
      "published_docs_repaired": "Eight trigger docblocks in packages/types/src/overlay.ts named renderChildren(schema.trigger) inside each Radix trigger - a call that no longer exists after my seam landed. That JSDoc ships in .d.ts and shows in an author's editor, so my change had made published documentation false. They now name the current spelling. While editing those exact lines I also dropped the cross-file line addresses they carried (AGENTS.md #11 repairs them opportunistically in a file you are already touching, never as a sweep) - that includes two neighbours my line shifts had rotted (tooltip content, hover-card align), whose content claims were still true.",
      "not_swept": "Left alone deliberately, and reported instead of fixed, because AGENTS.md #11 forbids the sweep and I am not otherwise touching these files: stale line addresses into my eight renderers in packages/types/crud.ts, packages/types/src/zod/overlay.zod.ts, packages/types/src/__tests__/alert-dialog-footer-keys-refusal-7963.test.ts (comment only) and examples/schema-catalog/test/overlay-trigger-mirror-6939.test.tsx (comment only). None can red. ⛔ And the .changeset/*.md files carrying the same addresses are historical record - AGENTS.md #11 forbids re-addressing them outright.",
      "controls_proved_live": "You warned against adding a control that cannot fail, so both re-pointed controls were ablated rather than argued. LEG 1: renderChildren(schema.content) removed from alert-dialog.tsx (hash-proved on disk) - exit 1, the re-pointed control and its siblings red. LEG 2: the seam's route to renderChildren broken in utils.tsx (hash-proved) - exit 1, the new chain assertion red. Both restored from HEAD with a trap and proved restored. The re-pointed controls read real file bytes and can fail.",
      "tests": "packages/types: 204 files / 4771 tests, exit 0 (was 9 failures before the 7081 repairs). examples/ + scripts/: 207 files / 7288 tests + 2 skipped, exit 0 - run because my sweep found source pins in both trees. packages/components: 281 files / 2745 tests, exit 0, after a full turbo build (43 tasks). Two ablation legs as above.",
      "gates": "Exit codes redirected to files and captured before reading. All GREEN (exit 0): turbo run build --filter=!@object-ui/site --concurrency=2 ; vitest packages/types/ ; vitest examples/ scripts/ ; vitest packages/components/ ; pnpm --filter @object-ui/types type-check ; pnpm --filter @object-ui/types lint (0 errors, 289 pre-existing warnings) ; check-changeset-presence ; check:control-bytes ; check:new-line-citations ; check:changeset-claims ; check:component-surface-parity ; check:registry-bare-names ; check:sdui-registration-pins ; check:element-data-source-declaration ; check:unreferenced-sources ; check:test-path-roots ; check:doc-types ; check:prompt-keys ; check:readme-exports ; check:pending-changeset-literals ; check:vi-mock-specifiers ; check:dist-completeness ; check:published-dist ; check:side-effects-array ; check:spec-symbols ; check:doc-snippets ; check:doc-examples ; pnpm check ; check-governed-queue-guard --test = NOT GOVERNED. The worktree was deepened before any gate ran, so no PREREQUISITE NOT MET this round. NOT MEASURED: the full four-shard pnpm test is CI's run; I ran the four package trees my sweep implicated instead, and say so rather than claim shard parity.",
      "line_budget": "n/a - no skills/** or .claude/** file touched.",
      "deviations": "1) SURFACE BREACH, compelled: packages/types/src/__tests__/ (three files) is outside my claimed surface of components/src/renderers/overlay/ + lib/utils.tsx. My diff broke those pins, so repairing them is compelled rather than chosen. 2) SURFACE BREACH, compelled by rule rather than by CI: packages/types/src/overlay.ts. Nothing re-derives those docblocks so nothing would have gone red, but the standing contract requires fixing a PUBLISHED claim this round's change made false, and JSDoc ships in .d.ts. You did not ask for this one - flagging it as my judgement, in the same package as the compelled test fix, so it is one breach and not two. 3) The changeset now declares '@object-ui/types': patch beside components. The gate was already satisfied by the single changeset, so this is an honesty choice, and it is free: both packages sit in the same 40-package fixed group in .changeset/config.json, so they version together regardless. 4) No PR body PATCH, no new PR, no label or assignee writes.",
      "the_lesson": "A cross-package source-text pin is INVISIBLE from the affected package's own suite. My local narrowing ran packages/components/ and could not have seen any of this: every dead assertion lived in packages/types. Worse, the obvious mitigation is not enough either - grepping for the renderer's literal path missed the nine assertions in overlay-trigger-union-7081.test.ts, because that file composes its path from a constant. ⇒ anyone changing a renderer's source SHAPE should grep the whole tree for the STRINGS being changed, not for the file's path, and should run the suites of every package whose tests read source text - here packages/types, scripts and examples, none of which the renderer's own package suite reaches.",
      "files_changed_this_round": [
        "packages/types/src/overlay.ts",
        "packages/types/src/__tests__/overlay-node-slot-doc-types-7082.test.ts",
        "packages/types/src/__tests__/alert-dialog-read-dialect-7104.test.ts",
        "packages/types/src/__tests__/overlay-trigger-union-7081.test.ts",
        ".changeset/9710-overlay-trigger-bare-string.md"
      ],
      "mcp_calls": "0 - no MCP GitHub tool was called, read or write.",
      "api_writes": "1 REST proxy write this round: POST /repos/objectstack-ai/objectui/issues/9710/comments (this report). Plus git push over HTTPS, which is not a REST write. No PR PATCH, no new PR, no label write, no assignee write.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: stale cross-file line addresses pointing into the eight renderers survive in packages/types/src/crud.ts, packages/types/src/zod/overlay.zod.ts, packages/types/src/__tests__/alert-dialog-footer-keys-refusal-7963.test.ts and examples/schema-catalog/test/overlay-trigger-mirror-6939.test.tsx. My line shifts rotted them; none can red, and AGENTS.md #11 forbids sweeping them. Successor: whoever next edits each of those files.",
        "noted, not filed: a test that asserts a docblock contains a cross-file LINE ADDRESS mechanically enforces the form AGENTS.md #11 bans. overlay-trigger-union-7081.test.ts was doing exactly that and is repaired here; whether others exist was not measured. Successor: whoever next audits the line-citation rules.",
        "noted, not filed (carried, unchanged): explicit trigger: undefined validates on all ten faces; on collapsible it still paints an empty focusable button, in PR objectui#9708's file and outside this card's surface. Successor: whoever next edits packages/components/src/renderers/disclosure/collapsible.tsx."
      ]
    }

    CI fix on PR objectui#9796, head 21896172. Session: session_018HrVaotisyhgmot9o2MLRq


    Generated by Claude Code

  9. os-tesla commented on Sep 18, 2026

    @os-tesla
    Collaborator

    os-dev-report

    {
      "issue": 9710,
      "status": "done",
      "round": "changeset correction (prose only) on PR objectui#9796",
      "branch": "claude/issue-9710-overlay-trigger-aschild-siblings",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9796",
      "head": "26b514f0",
      "session": "session_018HrVaotisyhgmot9o2MLRq",
      "premise_still_valid": true,
      "summary": "The catch is right and the diagnosis is right: 'packages/types is untouched' was true when written in round 1 and this branch's own CI-fix round falsified it, 62 lines later. I took the fold option rather than narrowing the sentence in place, because the defect here was ONE claim stated in TWO paragraphs - and the copy that rotted was not the copy anyone was maintaining. Narrowing both would have preserved exactly that shape. The changeset now makes the claim once.",
      "the_correction": "The sixth paragraph now reads 'No published FACE moves - and one published FILE does', and carries the claim this change actually owns: no type, no accept set, no export and no runtime behaviour moves in @object-ui/types, and every changed line in packages/types/src/overlay.ts is a docblock line. The last paragraph (the '@object-ui/types ships corrected read-site documentation' one) is REMOVED, its content folded into that sixth paragraph. The changeset now ends on the context-menu note.",
      "verified_before_publishing": "Both halves of the new sentence were measured, not asserted, because this is CHANGELOG text that ships. (1) Comment-only: every added and removed line in the overlay.ts diff is a docblock ' * ' line - checked by filtering the diff for any changed line that is NOT one, which returned empty. (2) It really ships: built @object-ui/types and read the emitted packages/types/dist/overlay.d.ts - the corrected spelling renderTriggerSlot is present eight times, the old spelling renderChildren(schema.trigger) reads ZERO there. Same file, same instrument, lit control non-zero ⇒ an admissible zero, not a bare one.",
      "item_2_full_reread": "NOTHING ELSE. I re-read all six body paragraphs against the 15-file diff. The packages/types edits were comment-only in ONE file, so the only sentence they could falsify is the one asserting that file was untouched - which is the one corrected. Specifically checked and left alone: the eight renderer names and the before-state mechanism (unaffected - they describe packages/components); the seam name renderTriggerSlot (already corrected in patch round 2, still current at head); 'an element trigger keeps the merge, asserted as a control rather than assumed' (the control exists and was strengthened in patch round 2); the empty-arm paragraph's Radix guard quote (still matches the pinned react-slot); and the context-menu paragraph (its file is absent from the diff stat). One judgement call worth stating: the empty-arm paragraph lists trigger as .optional() on dialog, alert-dialog, sheet, drawer and tooltip. My own zod derivation found SIX optional keys - those five plus context-menu. The list is correct AS SCOPED, because that paragraph is about the keys whose renderers this change touches, and context-menu's renderer is deliberately unchanged; naming it there would be wrong in a different direction. Not made false by the types edits, so not touched.",
      "gates": "Exactly the two you named, exit codes captured before reading. check-changeset-presence = 0 ('14 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)'). check:changeset-claims = 0, and it raised no finding naming this changeset (grepped its output for the changeset filename: no hit). Also ran a byte-discipline scan of the edited file: zero angle-bracket fragments, zero control bytes. ⛔ Did NOT re-run the full gate set, per your instruction - this round changes no code, no test and no docblock.",
      "line_budget": "n/a - no skills/** or .claude/** file touched.",
      "deviations": "1) I chose the FOLD over the in-place narrowing. You offered both; I am naming the choice and the reason rather than leaving it to inference. 2) I built @object-ui/types to verify the .d.ts claim before publishing it. That is one build beyond 'prose only', and it is the difference between asserting that JSDoc ships and knowing it. No other gate was run. 3) No code, test or docblock touched. No PR body PATCH. No new PR. No label or assignee writes.",
      "files_changed_this_round": [
        ".changeset/9710-overlay-trigger-bare-string.md"
      ],
      "mcp_calls": "0 - no MCP GitHub tool was called, read or write.",
      "api_writes": "1 REST proxy write this round: POST /repos/objectstack-ai/objectui/issues/9710/comments (this report). Plus git push over HTTPS, which is not a REST write. No PR PATCH, no new PR, no label write, no assignee write.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: the defect class that produced this correction is 'one claim, two paragraphs' - the rotted copy was not the copy being maintained, and no gate reads changeset PROSE for internal contradiction (check:changeset-claims reads file-name claims only, by its own header). A changeset that declares a package in its front matter while its body says that package is untouched is mechanically detectable; nothing detects it today. Successor: whoever next extends check:changeset-claims.",
        "noted, not filed (carried, unchanged): stale cross-file line addresses into the eight renderers survive in packages/types/src/crud.ts, packages/types/src/zod/overlay.zod.ts, packages/types/src/__tests__/alert-dialog-footer-keys-refusal-7963.test.ts and examples/schema-catalog/test/overlay-trigger-mirror-6939.test.tsx. None can red; AGENTS.md #11 forbids sweeping them. Successor: whoever next edits each of those files.",
        "noted, not filed (carried, unchanged): explicit trigger: undefined validates on all ten faces; on collapsible it still paints an empty focusable button, in PR objectui#9708's file and outside this card's surface. Successor: whoever next edits packages/components/src/renderers/disclosure/collapsible.tsx."
      ]
    }

    Changeset correction on PR objectui#9796, head 26b514f0. Session: session_018HrVaotisyhgmot9o2MLRq


    Generated by Claude Code

  10. removed their assignment
    on Sep 18, 2026
  11. os-tesla commented on Sep 18, 2026

    @os-tesla
    Collaborator

    ⭐ LANDED — PR objectui#9796 merged 2026-09-18T09:00:51Z, after 1 FAIL, 1 CI-red round and 1 changeset correction

    Seat: domain:ui#2, session_018HrVaotisyhgmot9o2MLRq.

    Verified BY CONTENT, control lit BEFORE the merge

    ⛔ Not by sha, ⛔ not by git merge-base --is-ancestor — it false-negatives on every squash.

    pre-merge 3ae740c4 post-merge dc3893d3
    control renderChildren in packages/components/src/lib/utils.tsx 4 5
    subject A export function renderTriggerSlot( in that file 0 1, at :158
    subject B renderTriggerSlot(XTrigger, schema.trigger) in packages/types/src/overlay.ts 0 8
    the false spelling renderChildren(schema.trigger) in that published file 8 0

    ⇒ the control was already firing pre-merge, so both pre-merge zeros are readings about that tree and ⛔ not failed reads. And exactly eight overlay renderers call the seam once each — alert-dialog, dialog, drawer, dropdown-menu, hover-card, popover, sheet, tooltip. ⭐ context-menu is absent from that list, which is the near-miss this card deliberately left alone: its asChild child is a real div, so Children.only never sees the string.

    The three rounds, for whoever reads this card next

    1. Contract review FAIL (record 5725825584). The shared seam made five zod-optional trigger keys paint an empty focusable button where the base painted nothing — an unlabeled tab stop on a legal document. ⚠️ And the delivery's stated mechanism was FALSE, which is what hid it: @radix-ui/react-slot@1.3.3 slot.tsx:45 returns a null child as-is, so a null child never threw. That sentence would have published to the CHANGELOG verbatim. Patched; OMITTED_TRIGGER_PAINTS_NOTHING now holds that arm off the published zod faces rather than off a list.
    2. CI red, ⛔ explicitly NOT a third review round — the two-round limit governs review findings, not CI failures. The failure was a lit control licensing a zero-hit claim, killed by this card's own seam. Re-pointed at renderChildren(schema.content), a sibling slot the trigger work does not touch. ⭐ The sweep for it found eleven dead assertions across three files, not the one CI named — including nine in a file that composes the renderer path from a constant, which a path-shaped grep could not see.
    3. Changeset correction. The body said 「packages/types is untouched」 while the front matter declared '@object-ui/types': patch and the diff moved 62 lines there. ⚠️ Same defect class as round 1: a false sentence that publishes. Now reads 「No published FACE moves — and one published FILE does」, and this seat verified the load-bearing half independently: of the 62 changed lines in packages/types/src/overlay.ts, 0 are non-docblock, with the same filter keeping all 62 as the control.

    Card cleaned in one stroke

    The Fixes auto-close closed this card completed and left pm:dispatched and the os-tesla assignee standing — lane fact ②, 45 for 45. Cleaned via the four-step writer at 2026-09-18T09:01Z; read-back matches: labels priority:p2, domain:ui, assignees none.

    Clause ②

    This card was built at the default tier throughout. As of the maintainer's ruling of 2026-09-18T08:10Z (objectstack#18885 → objectstack#18536, record 5727163801) a non-spec lane owes no clause-② contract review at all, so the final head got ⛔ none — the ruling's doing, ⛔ not a shortcut.

    Carried forward, ⛔ not filed by this card

    • Stale cross-file line addresses into the eight renderers survive in packages/types/src/crud.ts, packages/types/src/zod/overlay.zod.ts, packages/types/src/__tests__/alert-dialog-footer-keys-refusal-7963.test.ts and examples/schema-catalog/test/overlay-trigger-mirror-6939.test.tsx. None can red; AGENTS.md [WIP] Update documentation for project #11 forbids sweeping them. Successor: whoever next edits each file.
    • ⚠️ An explicit trigger: undefined still paints an empty focusable button on collapsible — PR objectui#9708's file, outside this card's surface. Successor: whoever next edits packages/components/src/renderers/disclosure/collapsible.tsx.
    • ⭐ A gate-shaped gap this seat is filing separately: nothing reads changeset prose for internal contradiction. A changeset that declares a package in its front matter while its body says that package is untouched is mechanically detectable; check:changeset-claims judges file-name resolution only, by its own header.

    domain:ui#2 execution seat · session_018HrVaotisyhgmot9o2MLRq · landing verification · readings taken 2026-09-18T09:01Z


    Generated by Claude Code

  12. added a commit that references this issue on Sep 28, 2026
    dc3893d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions