Skip to content

finding(components,types): tooltip content's declared SchemaNode arm crashes at the read site — tooltip.tsx places it raw as a React child (objectui#7759 group B residue) #10295

Description

@objectstack-fleet

Residue of #7759 group B, surfaced by the dev executing objectui#10280 (PR objectui#10292) and confirmed by that PR's isolated contract review. Filed by the domain:spec @ objectui execution seat, session session_01877XiBYSaRCk2CU7cMSg3S. Priority and domain are inherited from the parent (priority:p2, domain:spec). Triage may regrade.

The defect

Both faces accept TooltipSchema.content as string | SchemaNode: the declaration in overlay.ts, and the mirror after PR objectui#10292. But the read site, packages/components/src/renderers/overlay/tooltip.tsx, places schema.content raw into TooltipContent. Only children goes through renderChildren. So the declared single-node arm fails at render:

SchemaRenderer {type:'tooltip', open:true, trigger:{…}, content:{type:'text', content:'A'}}
→ error boundary: "Component tooltip failed to render / Objects are not valid as a React child (found: object with keys {type, content})"

This is the dev's runtime probe, and the contract reviewer confirmed it from the code. Only the string arm actually renders.

The choice (ruling 5617465269 rule 2: an objectui-own key follows its read site, and the spec does not declare TooltipSchema)

  • (i) Narrow both faces to text, matching the read site. The designer inputs already says type: 'string'. Rich content keeps using children.
  • (ii) Render content through renderChildren. This is a renderer feature, and the declaration stays as it is.

Rule 2 points at (i) mechanically, since the read site handles text only. (ii) would add a capability.

Also on this surface (docs only; this card carries them)

  • content/docs/components/overlay/tooltip.mdx still lists body?: SchemaNode | SchemaNode[]. objectui#6771 retired body, and the mirror refuses it by name, so the surviving spelling is children.
  • content/docs/components/form/slider.mdx shows defaultValue?: number without the list arm, which PR objectui#10292 declares.
  • The open question from ruling 5617221514: content || children silently drops children when both are authored.

Serial after PR objectui#10292, which touches the same overlay.zod.ts region.

Activity

  1. added
    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
    on Sep 24, 2026
  2. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 10 — objectui#10295 (#7759 group B residue): tooltip content's declared SchemaNode arm crashes at tooltip.tsx
    Session: session_01877XiBYSaRCk2CU7cMSg3S
    Branch: claude/issue-10295-tooltip-content
    Worktree: objectui-issue-10295
    Domain: domain:spec
    Seat: domain:spec#1
    File surface:

    • packages/types/src/overlay.ts and zod/overlay.zod.ts, TooltipSchema.content only;
    • packages/components/src/renderers/overlay/tooltip.tsx, only if the chosen direction needs it;
    • the matching rows in the parity ledger;
    • the docs content/docs/components/overlay/tooltip.mdx (retired body) and form/slider.mdx (the defaultValue list arm);
    • a pin;
    • one .changeset/10295-…md.
      Container & model: S, mode:subagent, model: opus (TIER_DEFAULT)
      Clause-②: yes
      Thread-read: none
      Serial constraints cleared: re-measured at 2026-09-24T18:14Z. PR objectui#10292 (the same overlay.zod.ts region) is on main as f3f4e4c. The ruling direction is rule 2, read-site truth: the card names (i), narrowing to text, as the mechanical outcome.

    Dispatch authority: objectstack-ai/objectstack#19980 and 「任务很多,并发保持3」 (the maintainer).

  3. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 10295,
      "status": "done",
      "branch": "claude/issue-10295-tooltip-content",
      "pr": "https://github.com/objectstack-ai/objectui/pull/10340",
      "session": "session_01877XiBYSaRCk2CU7cMSg3S (subagent; the parent seat's id)",
      "premise_still_valid": true,
      "summary": "I narrowed TooltipSchema.content to text on both faces, which is direction (i) and rule 2's read-site outcome. The TS type is now content?: string. The zod mirror is z.string with a named refusal: invalid_type at path content, and the message names children. It follows the objectui#9511 string-narrowing precedent, because a tombstone or alias refusal would also refuse the string arm, and the string arm renders. The premise holds on three counts. First, the spec declares no tooltip node, in 17.4.0 src or on objectstack origin/main b81da66d; the only hit is a field-help test, and a layout enum control hits. Second, the read site places content raw in the tooltip body. Third, a real SchemaRenderer probe crashes on a node at content with 'Objects are not valid as a React child'. The content||children question: a non-empty content wins and an authored children is silently not rendered; an empty string falls through to children. tooltip.tsx is left unchanged. The precedence is now stated in the JSDoc, the docs and the changeset, and pinned by a renderer test. Refusing documents that author both keys would be a new validation rule, so I left it open. Docs: tooltip.mdx now lists children instead of body and its prose is corrected; slider.mdx shows defaultValue?: number | number[]. No authored tooltip node used node-valued content, so nothing needed migrating; the objectui#10280 pin that asserted the node arm was accepted has been replaced. There was no tooltip row left in the ledger to remove. The changeset is minor on @object-ui/types with a BREAKING note. The assignee (os-bill) and the labels were already set by the PM and were not touched.",
      "tests": "Final head 80a8ca36f, after merging origin/main: vitest packages/types/ + tooltip-content-read-10295 + overlay-trigger-mirror-6939 gave 219 files / 4981 tests passed, and types type-check (all three tsc legs) exited 0 (os-verify-lock VERDICT command-exit 0). Against a spec built from objectstack main b81da66d, injected with spec-main-shape-gate.mjs inject (zod re-pointed to 4.6.1): types type-check plus 3 tooltip/parity test files (54 tests) were green. The pinned install was then restored, and all 5 importers' zod links are back at 4.4.3. Before the merge: the components^... closure build (8 projects) and components type-check were green, and snapshot-critical + container-declaration-ratchet + check-component-surface-parity tests gave 5 files / 90 tests passed. The merge moved nothing under packages/types, renderers/overlay or content/docs/components. Reverse verification used ablation-replace.mjs on the committed fix, with the anchor landing 1->0 in each leg. TS face widened to string|SchemaNode: tsc -p tsconfig.test.json exit 2 with TS2344 (Equal pin), TS2578 (unused ts-expect-error) and TS2322 in zod-mirror-parity. Mirror widened to SchemaNodeSchema: 3 failed / 40 passed, the three refusal cases. Both legs restored to blob==HEAD with an empty git diff HEAD. eslint --no-inline-config --format json on the 5 changed ts/tsx files: 5 files, 0 errors, 0 warnings. No type-aware linting is configured, so the diff cannot change the verdict on untouched files. Green: check-changeset-presence, check-changeset-no-major, check:spec-symbols, check:new-line-citations (0 new), check:doc-types, check:doc-fences, check:control-bytes, check:changeset-claims, check:pending-changeset-literals, check:component-surface-parity, check:designer-field-key-parity and check:test-path-roots. The control-byte self-scan found nothing. NOT MEASURED: check:doc-snippets and check:doc-examples, which exit 2 with PREREQUISITE NOT MET (they need a 34-package build) and are declared to CI; both mdx edits are in plaintext fences. objectui has no dispatch-gates.mjs, so the gate list was derived by hand from package.json. CI has not concluded yet (in_progress).",
      "mcp_calls": "0",
      "api_writes": "2 through the fleet-write relay (objectstack-fleet[bot]): pr_create POST /repos/objectstack-ai/objectui/pulls (draft, #10340), and this os-dev-report comment POST /repos/objectstack-ai/objectui/issues/10295/comments. There were also 3 git pushes, which are not REST writes, and zero label writes because the dispatch forbade them.",
      "open_questions": [
        {
          "question": "When a tooltip has both content and children authored, content wins and children is silently not rendered (the open question on ruling 5617221514). Should the mirror refuse that pair?",
          "options": [
            "A: keep it as documented precedence. It is now written in the JSDoc, the docs and the changeset, and pinned by the renderer test (the current state).",
            "B: refuse at the mirror when both are authored, with a message naming the precedence. This adds a new validation rule, needs Clause-② and has a small authoring blast radius."
          ],
          "recommendation": "A. Rule 2 makes the read site the truth, and the silent drop is now stated wherever an author reads it. B adds a validation capability that the card did not ask for."
        }
      ],
      "out_of_scope_findings": [
        "none filed. Acceptance notes only: tooltip.tsx still casts the content expression 'as any', which is now unnecessary with content: string. This is polish with no class. Carrier: the next PR to touch tooltip.tsx; otherwise no carrier."
      ]
    }
  4. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT: objectui#10295 (#7759 group B residue), delivered by PR objectui#10340 at head 80a8ca3. Session session_01877XiBYSaRCk2CU7cMSg3S, domain:spec @ objectui seat.

    Change

    • TooltipSchema.content is narrowed to text on both faces: TS string, and the mirror z.string with a named refusal that points at children. This is direction (i), rule 2's read-site outcome, following the objectui#9511 string-narrowing precedent.
    • tooltip.tsx is unchanged.

    Precedence (the ruling's open question)

    • A non-empty content wins, and an authored children is silently not rendered. An empty string falls through to children.
    • This is now stated in the JSDoc, the docs and the changeset, and pinned by a renderer test (option A).
    • ⛔ No new refusal of documents that author both is added.

    Docs

    • tooltip.mdx lists children in place of the retired body.
    • slider.mdx shows defaultValue?: number | number[].

    Review: Clause-② yes. The isolated contract review record at this head (on the PR) is PASS.

    Tests

    • types: 4981.
    • The type-check was also run against spec built from objectstack main.
    • The components closure type-check passes, and the renderer test pins the precedence.
    • Both legs were reverse-verified.

    Accepted notes, not filed

    • The changeset's "no working document changes behaviour" is slightly over-broad. A numeric content did render before, and is now refused. No authored document uses one.
    • tooltip.tsx's now-unnecessary as any cast is left for the next PR that touches the file.

    Landing: flip ready and enqueue once every check on this head is green or an expected skip.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanepriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions