Repository navigation
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
Activity
- addeddomain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec laneobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
on Sep 24, 2026 objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actionsClaim: PM loop round 10 — objectui#10295 (#7759 group B residue): tooltip
content's declaredSchemaNodearm crashes attooltip.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.tsandzod/overlay.zod.ts,TooltipSchema.contentonly;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(retiredbody) andform/slider.mdx(thedefaultValuelist 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 sameoverlay.zod.tsregion) is onmainasf3f4e4c. 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).
objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actionsos-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." ] }objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actionsACCEPT: objectui#10295 (#7759 group B residue), delivered by PR objectui#10340 at head
80a8ca3. Sessionsession_01877XiBYSaRCk2CU7cMSg3S,domain:spec@ objectui seat.Change
TooltipSchema.contentis narrowed to text on both faces: TSstring, and the mirrorz.stringwith a named refusal that points atchildren. This is direction (i), rule 2's read-site outcome, following the objectui#9511 string-narrowing precedent.tooltip.tsxis unchanged.
Precedence (the ruling's open question)
- A non-empty
contentwins, and an authoredchildrenis silently not rendered. An empty string falls through tochildren. - 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.mdxlistschildrenin place of the retiredbody.slider.mdxshowsdefaultValue?: 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
contentdid render before, and is now refused. No authored document uses one. tooltip.tsx's now-unnecessaryas anycast 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
- added a commit that references this issue
on Sep 28, 2026
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, sessionsession_01877XiBYSaRCk2CU7cMSg3S. Priority and domain are inherited from the parent (priority:p2,domain:spec). Triage may regrade.The defect
Both faces accept
TooltipSchema.contentasstring | SchemaNode: the declaration inoverlay.ts, and the mirror after PR objectui#10292. But the read site,packages/components/src/renderers/overlay/tooltip.tsx, placesschema.contentraw intoTooltipContent. Onlychildrengoes throughrenderChildren. So the declared single-node arm fails at render: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)inputsalready saystype: 'string'. Rich content keeps usingchildren.contentthroughrenderChildren. 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.mdxstill listsbody?: SchemaNode | SchemaNode[]. objectui#6771 retiredbody, and the mirror refuses it by name, so the surviving spelling ischildren.content/docs/components/form/slider.mdxshowsdefaultValue?: numberwithout the list arm, which PR objectui#10292 declares.content || childrensilently dropschildrenwhen both are authored.Serial after PR objectui#10292, which touches the same
overlay.zod.tsregion.