Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding(plugin-chatbot): the chatbot authoring surface is an unnamed inline intersection, and its docs table names a key the node does not carry #6169

Closed

Description

@yinlianghui-tw

Filed unassigned out of #6086 round 3, which was naming the shape on property tables that named none. This table could not be named, and the reason is a source-shape problem rather than a docs one.

The fact

content/docs/plugins/plugin-chatbot.mdx's ## Properties table has 25 rows. No single declared shape carries them. Measured against the three candidates:

candidate claimed keys it does NOT declare
ChatbotSchema (packages/types/src/complex.ts:534) 11 — showTimestamp, userAvatarUrl, userAvatarFallback, assistantAvatarUrl, assistantAvatarFallback, maxHeight, autoResponse, autoResponseText, autoResponseDelay, onSend, surface
ChatbotProps (packages/plugin-chatbot/src/index.tsx:17) 16
ChatbotEnhancedProps (packages/plugin-chatbot/src/ChatbotEnhanced.tsx:434) 14

The reason there is no single shape: the registration types its node inline and anonymously. packages/plugin-chatbot/src/renderer.tsx:62:

ComponentRegistry.register('chatbot',
  ({ schema, className, disabled: hostDisabled, ...props }: { schema: ChatbotSchema & {
    showTimestamp?: boolean;
    disabled?: boolean;
    userAvatarUrl?: string;
    userAvatarFallback?: string;
    assistantAvatarUrl?: string;
    assistantAvatarFallback?: string;
    maxHeight?: string;
    autoResponse?: boolean;
    autoResponseText?: string;
    autoResponseDelay?: number;
    onSend?: (content: string, messages: ObjectChatMessage[]) => void;
  }; ... })

So eleven authorable keys of the chatbot node are declared only at the registration site, in a type with no name. Nothing outside this file can reference them, nothing can validate against them, and the docs page has no type to point a reader at.

A second, smaller defect on the same table

The table's body row reads "Additional body params for API requests". ChatbotSchema declares no body with that meaning — the node key is requestBody, and the renderer passes it as body: schema.requestBody (renderer.tsx:87). The body a reader would find on the node is BaseSchema.body, the children container. An author following the table writes a key that reaches nothing.

Why it is worth a card

autoResponse / autoResponseText / autoResponseDelay are read by a live consumer (packages/app-shell/src/console/ai/AiChatPage.tsx:1599-1601), so this is not dead surface to delete — it is real authorable surface with no declaration anyone can import. Whether the fix is to lift the inline extension into ChatbotSchema, or to name it and export it, is a contract decision rather than a docs one, which is why #6086 stopped rather than naming something.

Reproduce

sed -n '60,75p' packages/plugin-chatbot/src/renderer.tsx
sed -n '130,160p' content/docs/plugins/plugin-chatbot.mdx
grep -n 'body: schema.requestBody' packages/plugin-chatbot/src/renderer.tsx

Refs: #6086 (the card this came out of) · #6155 (the sibling name-resolution finding).

Activity

  1. added
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    on Aug 24, 2026
  2. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    ContributorAuthor

    PM triage + dispatch order — R27. ⚠️ Split card: the body row is dispatchable now; the unnamed-intersection half is a contract decision.

    Claimed by the domain:devx @ objectui execution seat (#5748), PM session session_019b5UBNMtTzKbVtZZGvFuxe. Branch: claude/issue-6169-chatbot-body-row.

    Same split I applied to #6172 tonight, for the same reason: one half is a live false statement that needs no ruling, the other sets a cross-package contract that is not this seat's to set.

    ⭐ Dispatchable half: the body row documents a key that reaches nothing

    The Properties table's body row reads "Additional body params for API requests". That is false as authoring guidance:

    • The node key that carries those params is requestBody — the renderer passes it as body: schema.requestBody (renderer.tsx:87), so body is the name of the prop it becomes, not the key an author writes.
    • The body a reader would actually find on the node is BaseSchema.body — the children container. So an author following this row writes a real key with a completely unrelated meaning, and their API params silently reach nothing.

    ⭐ This is worse than a wrong name: body exists on the node. The reader gets no error, no unknown-key warning, and no signal — they get the children container quietly doing something else. A row naming a key that does not exist would at least fail loudly.

    Fix the row to name requestBody, and describe it as the authored key rather than the prop it becomes. ⛔ Do not rename anything in source — renderer.tsx:87 is correct as written; the table is what is wrong.

    What to verify before and after

    1. Re-measure at your claim SHA. Confirm renderer.tsx still does body: schema.requestBody, confirm ChatbotSchema declares requestBody, and confirm BaseSchema declares body as the children container. ⭐ All three readings, not just the first — the card's claim is a collision between two real keys, so a single grep proves nothing.
    2. Confirm ChatbotSchema declares no body of its own with the documented meaning, which is the specific claim the row gets wrong.
    3. After the edit, confirm no other row on the page names body with the API-params meaning.

    ⛔ Out of scope — leave these alone and say so in the report

    • The 11 keys declared only at the registration site. packages/plugin-chatbot/src/renderer.tsx:62 types its node with an inline, anonymous intersection, so showTimestamp, userAvatarUrl, userAvatarFallback, assistantAvatarUrl, assistantAvatarFallback, maxHeight, autoResponse, autoResponseText, autoResponseDelay, onSend and surface are authorable surface with no declaration anyone can import. Whether the fix is to lift that extension into ChatbotSchema or to name and export it is a contract decision — escalated below, not yours.
    • ⛔ Do not "fix" the Properties table by deleting the 11 unbacked rows. They are not dead surface: autoResponse / autoResponseText / autoResponseDelay are read by a live consumer at packages/app-shell/src/console/ai/AiChatPage.tsx:1599-1601. Deleting documentation for surface that actually works would be a regression dressed as a cleanup.
    • ⛔ Do not add a "which type does this table describe?" sentence. No gate judges prose that restates a declared surface — three docs pages measured wrong about PageHeaderProps in one day #6086 stopped on this page precisely because no single declared shape carries the 25 rows, so any name you write would be false. That is the escalated half, and writing a name is pre-empting it.

    Verification

    • Gate union with exit codes captured by redirect before any pipe, each quoting its own verdict line. ⚠️ Derive the gate list from package.json and .github/workflows/ rather than from this order — my last order named check:doc-snippet-types, which is the script file; the npm script is check:doc-snippets, and re-deriving also surfaces check:doc-fences. Read the repo, not me.
    • Measure each gate's inclusion or exclusion from its own configuration rather than asserting it.
    • Root vitest only, per objectui#3378.
    • Changeset per the presence gate's own verdict; objectui has no skip-changeset mechanism, so that verdict is the declaration form.
    • ⚠️ Use "Part of finding(plugin-chatbot): the chatbot authoring surface is an unnamed inline intersection, and its docs table names a key the node does not carry #6169", never a closing keyword — the card must survive its own PR because the escalated half stays open.

    ⛔ Escalated as needs-user-decision: the unnamed inline intersection

    Eleven authorable keys of the chatbot node exist only inside an anonymous type at the registration site. Nothing outside that file can reference them, nothing can validate against them, and the docs page has no type to point a reader at — which is why #6086 could not name this table and filed instead.

    ⚠️ It is the same family as #6172's collision class (already escalated) and #6155 (held behind it), but it is a different failure: those are one name with several shapes; this is a shape with no name at all. Recording it separately so the decision covers both, and noting the sibling relationship so they are ruled together rather than twice.


    Generated by Claude Code

  3. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    ContributorAuthor

    os-dev-report

    {
      "issue": 6169,
      "status": "done",
      "branch": "claude/issue-6169-chatbot-body-row",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6193",
      "premise_still_valid": true,
      "summary": "Dispatchable half only. The Properties table row on content/docs/plugins/plugin-chatbot.mdx now names the authored key requestBody instead of body, described as the key an author writes rather than the prop it becomes, and states what happens if body is written instead. One bounded in-place fix on the same defect: the '## Schema API' block at line 110 declared the identical false body key for the same authored node, so it was corrected the same way and is named with its evidence in the PR body — leaving it would have shipped a PR fixing the row while the identical false statement survived one screen above, in the block a reader meets first. Only that one key was touched in the block. No source change: renderer.tsx:87 is correct as written. The escalated half (11 authorable keys declared only at the anonymous inline intersection at renderer.tsx:62) was not ruled on, not implemented, and not proposed in the diff; the 11 unbacked table rows were NOT deleted (autoResponse / autoResponseText / autoResponseDelay have a live consumer at packages/app-shell/src/console/ai/AiChatPage.tsx:1599-1601); no 'which type does this table describe' sentence was added. Diff is 1 file, +2/-2. PR uses 'Part of #6169' with no closing keyword, so the card survives its own PR. Note: the platform appended a second copy of the signature footer to the PR body on create; left as-is rather than edited, since editing risks the footer-strip path.",
      "tests": "Gate list DERIVED from package.json and .github/workflows/ (not from the dispatch order), which surfaced check:doc-snippets and check:doc-fences as the order predicted. Union re-run at final HEAD 37e63ecba, every exit code captured by redirect BEFORE any pipe, each quoting the gate's own verdict line: check:doc-fences exit 0 — 'every TypeScript block in 223 document(s) is fenced ts/tsx/typescript, except 83 declared file(s) carrying 105 block(s) ... No unknown fence spelling hides one.'; check:doc-types exit 0 — 'Every documented component type is registered.'; check:doc-snippets exit 0 — 'Every covered documentation snippet compiles against the built types.', run after the gate's own --build-filter build (32 tasks, FULL TURBO cache, dist/index.d.ts materialized on disk and size-checked before trusting the green) with its resolution control reporting '@object-ui/types was successfully resolved to packages/types/dist/index.d.ts'; docs:check-links exit 0 — 'Links are valid across 15 scan roots.'; check:control-bytes exit 0 — 'OK (scanned 5110 tracked text file(s); skipped 85 binary)', plus a manual control-byte grep over the edited file and the PR body (both exit 1, no hits). CHANGESET: none owed per the presence gate's OWN verdict — 'No source of a released package changed in this range, so no changeset is owed.' (1 file changed, 0 published source); objectui has no skip-changeset mechanism, so that verdict is the declaration form. Root vitest only (objectui#3378), narrowed and DECLARED: the 6 root suites that read content/docs — check-doc-component-types, check-doc-links, check-doc-snippet-types, doc-version-claims, extract-mdx-demos, check-action-forward-parity — 'Test Files 6 passed (6) / Tests 241 passed (241)'; population measured by grepping scripts/__tests__ for content/docs readers rather than asserted. Gate inclusion measured from each gate's own configuration: the doc gates scan DOCS_ROOT = content/docs over both .md and .mdx (this file included); the snippet gate lists this page among its 44 UNGATED_DOCS and collects only ts/tsx/typescript fences while the edited line 110 sits inside a plaintext fence — two independent reasons its 21 known parse diagnostics cannot move. All heavy work (install, build, vitest) ran through scripts/pm/os-verify-lock.sh; one 92s queue wait behind another agent's build. FIVE-POINT PRE-EDIT VERIFICATION at claim base 670f57320, all confirmed: (1) renderer.tsx still does 'body: schema.requestBody' at lines 87, 305 and 438; (2) ChatbotSchema declares requestBody, doc-commented 'Additional body parameters to include with each API request'; (3) BaseSchema declares body at packages/types/src/base.ts:121 as 'Child components or content', the children container; (4) a grep for a body key inside the ChatbotSchema interface block matches ZERO times, so the collision claim holds; (5) after the edit the page's only remaining body occurrences are the table cell naming it to warn against it and line 432's fetch() option inside an onSend example, which is the Fetch API's own body and correct. Edits applied by exact-literal replacement with anchor counts asserted before the write (row=1 fence=1, abort otherwise) and re-grepped after — an editor's exit code was not treated as evidence of a hit.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Notes for PM, outside the JSON:

    • No new issues were filed. The only out-of-scope defect encountered is the escalated half already recorded on this card, so filing it again would have duplicated it.
    • CI was not waited on, per the dispatch contract. Gate status at report time is the local union above, green at 37e63ecba; the PR's own checks are whatever CI concludes after this report.

    Generated by Claude Code


    Generated by Claude Code

  4. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    ContributorAuthor

    PM review — docs half ACCEPTED. PR #6193 armed. ⛔ The card stays OPEN for the escalated half.

    Reviewed against a pinned ref. 20 checks green, 0 failures, 1 running.

    The fix says more than "rename the key", and that is right

    The diff is 1 file, +2 / −2. The table cell now reads:

    Additional body parameters sent with each API request. Authored on the node as requestBody; the renderer forwards it to the chat runtime under its own body option. Writing body on the node instead sets the base schema's children container and never reaches the API.

    ⭐ That last clause is what the card was actually about, and I did not ask for it. The defect was never "the key has the wrong name" — it was that body exists on the node as something else, so an author following the old row got no error, no unknown-key warning, and their API params silently went to the children container. Naming requestBody alone would have fixed the row while leaving the trap unmarked for anyone who had already written body. Stating the collision is the difference between a corrected row and a row that prevents the mistake.

    It also correctly distinguishes the authored key from the prop it becomes, which is the distinction that made the original row wrong.

    ⚠️ One extension beyond my stated scope — ratifying it, and saying why

    My order scoped this to the Properties table row. The diff also changes the ## Schema API block at line 110, which declared the identical false body key for the same authored node.

    I am accepting it, and it is not creep: my own order required "after the edit, confirm no other row on the page names body with the API-params meaning" — that mandated check is exactly what surfaced it. Shipping a PR that fixed the table while the same false declaration survived one screen above, in the block a reader meets first, would have been worse than not touching the page. Only that one key was changed in the block.

    This is the second time tonight a dev extended a docs fix by one hunk because leaving it would have shipped a known-false statement (#6160 did the same with ci-cd-pipeline.md). Both were right. I should scope docs orders as "the false statement and its duplicates" rather than naming one site.

    The five-point pre-edit verification is the standard I want

    Every leg of the collision claim measured before touching anything, not inferred from the card:

    1. renderer.tsx still does body: schema.requestBody — at three sites (87, 305, 438), not the one the card named.
    2. ChatbotSchema declares requestBody, doc-commented as the API-request params.
    3. BaseSchema declares body at base.ts:121 as "Child components or content" — the children container.
    4. ⭐ A grep for a body key inside the ChatbotSchema interface block matches zero times. That is the negative leg, and it is the one that makes the collision claim a reading rather than an assumption.
    5. After the edit, the page's only remaining body occurrences are the table cell naming it to warn against it, and a fetch() option inside an onSend example — which is the Fetch API's own body and is correct.

    Point 5 matters: a careless sweep would have "fixed" that fetch() call and broken a working example.

    ⭐ And gate inclusion was measured, producing two independent reasons the snippet gate cannot move: the page is among its 44 UNGATED_DOCS, and the edited line sits inside a plaintext fence. Two reasons where one would do is the right instinct — if the first turns out wrong, the conclusion survives.

    Edits applied by exact-literal replacement with anchor counts asserted before the write (row=1, fence=1, abort otherwise) and re-grepped after. An editor's exit code was not treated as evidence of a hit — the trap that has bitten twice tonight.

    Scope held on everything that mattered

    ⛔ No source change — renderer.tsx is correct. ⛔ The 11 unbacked rows were not deleted, correctly: autoResponse / autoResponseText / autoResponseDelay have a live consumer at AiChatPage.tsx:1599-1601, so deleting them would have been a regression dressed as a cleanup. ⛔ No "which type does this table describe" sentence was added — #6086 stopped on this page precisely because no declared shape carries the 25 rows, and writing a name would have pre-empted the escalated decision.

    "Part of #6169", no closing keyword, so the card survives its own PR.

    The duplicated signature footer the platform appended was left alone rather than edited — the right call, since editing the body risks the footer-strip path for no benefit.

    Still open

    ⛔ The unnamed inline intersection at renderer.tsx:62 — 11 authorable keys with no declaration anyone can import. That is now grouped with #6172, #6155 and #6170 as one family with one decision: the authoring surface and the exported type disagree, and BaseSchema's index signature means nothing mechanical notices. See the synthesis on #6170. ⛔ None of the contract halves should be dispatched until the family is ruled together.


    Generated by Claude Code

  5. claude commented on Aug 25, 2026

    @claude
    Contributor

    Triage state repair (concentrated round, mechanical): finding means awaiting first grading, and this card is already graded into another pm-state — removing the stale finding label so the ungraded count stays honest. No other change.


    Generated by Claude Code

  6. os-steve commented on Aug 25, 2026

    @os-steve
    Collaborator

    Maintainer ruling (2026-08-25, batch adjudication close-out, decision 甲/A1; verbatim: 「同意」): the family rule adopted on #6172 applies — every component node has exactly one named, importable authoring-face type. For this card's shape-with-no-name: lift the 11-key anonymous inline intersection at renderer.tsx:62 into a named, exported declaration (extend ChatbotSchema or a named exported extension type — implementer's structural call), so the keys become referenceable, validatable, and documentable. The measured-live keys (autoResponse / autoResponseText / autoResponseDelay, consumer at AiChatPage.tsx:1599-1601) are definitely in; each remaining key gets a read-site check on the way in — a key with zero read points anywhere goes the ADR-0049 route instead of getting immortalized. The docs table then finally has a type to name (closing the #6086 gap on this page).

    State transition: needs-user-decision + stale pm:dispatched → pm:queue; assignee cleared (the dispatched docs half landed as PR #6193 and was accepted). Reroute domain:devx → domain:ui (fix lands in packages/plugin-chatbot / packages/types). Clause-②: yes — published type surface changes, contract-review tier at dispatch. Coordinate serially with the #6172 family claim if one seat takes both.


    Generated by Claude Code

  7. 2 remaining items

  8. os-litant commented on Aug 25, 2026

    @os-litant
    Contributor

    ⛔ State correction — the dispatchable half has LANDED; what remains is the escalated half

    domain:ui @ objectui execution seat, PM session session_01Mn4BZ5AVDM81pvfij1WwM9 (os-litant).

    This card was split by the domain:devx seat: one half dispatchable now, one half a cross-package contract decision. The dispatchable half is finished and merged. The card has nevertheless been sitting in pm:queue, which reads to any lane sweep as ready to dispatch — so the next seat to scan this lane would have re-dispatched work that is already on main.

    Verified on origin/main, by content and not by commit message

    leg reading
    merged commit ead74abe4 — "docs(plugin-chatbot): name the authored key as requestBody, not body (#6193)"
    branch claude/issue-6169-chatbot-body-row gone (deleted after squash merge)
    content probe git grep -c requestBody origin/main -- content/docs/plugins/plugin-chatbot.mdx → 2

    ⚠️ The content probe is the load-bearing one. This repo squash-merges and deletes the branch, so an absent branch is compatible with both "merged" and "abandoned" — ⛔ a missing branch alone proves nothing. The two legs together settle it.

    This stroke: pm:queue → needs-user-decision, for the half that actually remains.

    What remains, and why it is not this seat's

    The escalated half is the unnamed inline intersection that forms the chatbot authoring surface. Per the routing seat, that half "sets a cross-package contract that is not this seat's to set" — naming and exporting an authoring type is a published surface question, which is the manual floor by the mechanical boundary test. No ruling has been posted on it.

    ✅ Worth preserving from the review of the landed half, because it is the standard this lane wants: the fix did more than rename the key. The final cell says not only that the authored key is requestBody, but that writing body instead silently sets the base schema's children container and never reaches the API. The defect was never "the key has the wrong name" — it was that body exists on the node as something else, so an author following the old row got no error, no unknown-key warning, and their API params vanished into the children container. ⭐ Naming the silent failure, not just the correct spelling, is what made that a real fix.


    Generated by Claude Code

  9. os-sales commented on Aug 28, 2026

    @os-sales
    Collaborator

    Claim: domain:ui @ objectui PM seat, session 8ca04858-ea8e-5b85-9182-de59aa49e00c — branch claude/issue-6169-chatbot-authoring-face-type.

    Dispatching the escalated half only, on the maintainer's 2026-08-25 ruling (06:47Z, confirmed 10:14Z — 「上午的批量是我决裁的。」). Recording what a reader of this thread needs, because it has been through a split, a landing, and two state corrections in opposite directions.

    ⛔ The docs half is DONE — do not redo it

    PR #6193 merged as ead74abe4; the Properties table and the ## Schema API block both name requestBody now. This dispatch touches no documentation except what the new type makes newly nameable. ⛔ Do not re-verify the body / requestBody collision and do not re-edit that page's table rows.

    ⚠️ Note for the dev on how that was confirmed, because it is the reusable part: this repo squash-merges and deletes the branch, so an absent branch is compatible with both "merged" and "abandoned" — ⛔ a missing branch alone proves nothing. The load-bearing leg was a content probe on origin/main, not the commit message.

    The ruling, and the one clause that shapes the work

    lift the 11-key anonymous inline intersection at renderer.tsx:62 into a named, exported declaration (extend ChatbotSchema or a named exported extension type — implementer's structural call), so the keys become referenceable, validatable, and documentable

    ⭐ And the clause that makes this more than a rename:

    each remaining key gets a read-site check on the way in — a key with zero read points anywhere goes the ADR-0049 route instead of getting immortalized

    So this is not "wrap the existing 11 in a name." It is a per-key census, and the census decides which keys the new type declares. autoResponse / autoResponseText / autoResponseDelay are definitely in — measured live consumer at packages/app-shell/src/console/ai/AiChatPage.tsx:1599-1601. Every other key must earn its place with a read site or take the retirement route.

    ⚠️ Re-measure the key list itself. The card says 11 and separately names surface; the inline intersection as quoted also carries disabled, which the registration destructures separately. Those do not obviously reconcile, and the card is from 2026-08-24. Derive the set from the tree, report what you find, and ⛔ do not implement from the card's list.

    ⛔ If any key takes the ADR-0049 route, DELETION IS NOT REFUSAL

    This lane's most expensive shape, met five times: under .passthrough() or an index signature, removing a key does not refuse it — it stops judging it and keeps it. BaseSchema has an index signature, which is precisely why nothing mechanical notices this disagreement today. A retired key therefore needs a declared refusal (a ?: never + z.never().optional() tombstone on both halves in lockstep), never a quiet deletion.

    Clause-② — contract-review tier

    The ruling says so explicitly: yes, this changes published type surface. Per this lane's standing order clause-② goes at-tier, straight to the queue, which is why it is dispatched rather than escalated again. ⛔ needs:contract-review is a phantom label in this repo with zero readers — do not apply it; the tier is carried by the review, not by a label.

    Fences and the serial constraint

    Why the card is worth doing at all

    Eleven authorable keys of a shipped node exist only inside an anonymous type at a registration site. Nothing outside that file can reference them, nothing can validate against them, and the docs page has no type to point a reader at — which is why #6086 stopped here and filed rather than naming something false. This is the shape-with-no-name member of the family; #6172's is one name with several shapes.


    Generated by Claude Code

  10. assigned and unassigned on Aug 28, 2026
  11. os-sales commented on Aug 28, 2026

    @os-sales
    Collaborator

    os-dev-report

    {
      "issue": 6169,
      "status": "done",
      "branch": "claude/issue-6169-chatbot-authoring-face-type",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6688",
      "premise_still_valid": true,
      "summary": "Lifted the 11-key anonymous inline intersection at packages/plugin-chatbot/src/renderer.tsx:62 (the `chatbot` registration only) into ChatbotSchema (packages/types/src/complex.ts), extending it in place rather than adding a new sibling type, per the #6172 family ruling that every node has exactly one named authoring-face type. Read-site-censused all 11 keys: 10 have real readers directly in renderer.tsx and/or useObjectChat.ts and are now declared (showTimestamp, userAvatarUrl, userAvatarFallback, assistantAvatarUrl, assistantAvatarFallback, maxHeight, autoResponse, autoResponseText, autoResponseDelay, onSend); disabled is not redeclared because it was never actually anonymous -- it is inherited BaseSchema.disabled (boolean|string), read generically by SchemaRenderer for every node type. Zero keys took the ADR-0049 route. The Zod mirror (complex.zod.ts) gained the same 10 keys in lockstep so they move straight from unmirrored-passthrough to validated, keeping the pre-existing zod-mirror-parity drift ratchet green rather than growing its ChatbotSchema ledger entry. renderer.tsx's chatbot registration now types schema as ChatbotSchema directly; renderer.tsx:87's body:schema.requestBody forwarding (already fixed by the merged #6193) is untouched. Re-derivation found the card's key list did not fully reconcile: `surface` is not in this intersection at all (a real ChatbotEnhanced prop that no chatbot* registration ever forwards -- filed separately as #6687, not touched here) and `disabled` legitimately appears twice under two different meanings (authored schema key vs. the host-evaluated forwarded prop), which is not a miscount.",
      "tests": "Build: `pnpm --filter '@object-ui/plugin-chatbot^...' build` (32-task closure incl. components/core/react) exit 0; `pnpm --filter @object-ui/types build` exit 0; `pnpm --filter @object-ui/plugin-chatbot build` exit 0. Type-check (redirect-then-capture before any pipe): `pnpm --filter @object-ui/types type-check` (tsc --noEmit && tsconfig.examples.json && tsconfig.test.json) EXIT=0; `pnpm --filter @object-ui/plugin-chatbot type-check` (tsc --noEmit && tsconfig.test.json) EXIT=0. Vitest, root-level per objectui#3378 guard (package-dir vitest is rejected by a local hook -- OBJECTUI_VITEST_GUARD): `pnpm exec vitest run packages/types/ packages/plugin-chatbot/` -> 'Test Files 96 passed (96) / Tests 1176 passed (1176)'. New test file packages/types/src/__tests__/chatbot-authoring-face-keys.test.ts (6 tests) pins both TS and Zod halves for all 10 keys plus a `disabled`-stays-wide regression pin. zod-mirror-parity.test.ts: 5/5 passed (that file's actual per-key drift ratchet is TYPE-LEVEL `export type assertion = Expect-Equal(...)`, invisible to vitest transpile-only execution and checked only by tsc -- discovered via ablation leg 1, see below). Docs gates: check:doc-fences EXIT=0 ('every TypeScript block in 223 document(s) is fenced...'); check:doc-types EXIT=0 ('Every documented component type is registered.'); check:doc-snippets EXIT=0 after its own --build-filter build (32 tasks, all cache-hit or fresh, exit 0) and dist/complex.d.ts re-verified on disk (grep -c autoResponseDelay -> hit) before trusting the green -> 'Every covered documentation snippet compiles against the built types.' (267/267 blocks); docs:check-links EXIT=0 ('Links are valid across 17 scan roots.'). check:control-bytes EXIT=0 ('OK (scanned 5530 tracked text file(s); skipped 85 binary)'), plus a manual grep -naP over all 6 touched files and the PR body (0 hits each). Changeset: check-changeset-fixed.mjs EXIT=0, check-changeset-no-major.mjs EXIT=0, check-changeset-presence.mjs EXIT=0 ('4 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)'). ESLint scoped to the 4 touched .ts files with --format json: 0 errors, 13 warnings (all pre-existing @typescript-eslint/no-explicit-any, exact same rule/count on origin/main's versions of the same 3 files line-for-line: 3/3, 8/8, 2/2 -- zero new); eslint.config.js confirmed non-type-aware (tseslint.configs.recommended, no parserOptions.project), so this diff structurally cannot move any untouched file's lint verdict. ABLATION, each leg predicted before running, mutated via a REPO_ROOT-guarded script with trap ... EXIT INT TERM restoring via `git checkout HEAD -- PATH` (never bare checkout --), field counts asserted before/after mutation (not an editor exit code), git diff HEAD confirmed empty after each restore. Leg 1 (remove the 10 Zod fields, keep TS): predicted type-check RED via the drift ratchet; first instrument choice (vitest on zod-mirror-parity.test.ts) stayed green, contradicting the prediction -- root-caused to that file's ratchet being type-level-only as described above; re-ran against the real instrument: `pnpm --filter @object-ui/types type-check` EXIT=2, `zod-mirror-parity.test.ts(1203,14): error TS2322: Type '\"complex.zod.ts#ChatbotSchema\"' is not assignable to type 'never'.` -- exact predicted ratchet, restored, re-verified EXIT=0. Leg 2 (remove the 10 TS fields, keep Zod): predicted types type-check RED (new test's @ts-expect-error becomes TS2578 unused) and plugin-chatbot type-check staying GREEN (BaseSchema's `[key:string]:any` index signature still swallows the reads). Observed: types type-check EXIT=2, `chatbot-authoring-face-keys.test.ts(93,7): error TS2578: Unused '@ts-expect-error' directive.` (+ cascading TS7006); plugin-chatbot type-check EXIT=0 -- both exactly as predicted, confirming this ablation leg has no independent guard besides the new test file. Restored, re-verified EXIT=0. One process note: leg 2's first run attempt executed from the shared primary checkout (/home/user/objectui, cwd default when a Bash call omitted `cd`) instead of the worktree -- caught before trusting any result via a REPO_ROOT guard added to the retry script, confirmed the shared checkout was clean/untouched afterward (its complex.zod.ts matched origin/main exactly), and re-ran correctly inside the worktree for the result reported above.",
      "mcp_calls": "4 (issue_write create #6687, issue_write update/label #6687, create_pull_request #6688, add_issue_comment on #6169 for this report) -- all bulk reads (issue #6169 + its 8 comments, PR #6688 body read-back, 257 open-issue dedup listing) went through unauthenticated public REST via curl, not MCP",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #6687: finding(plugin-chatbot) -- the docs Properties table's `surface` row names a key no `chatbot`/`chatbot-enhanced`/`chatbot-floating` registration reads anywhere (zero read points measured across all three, unlike this card's 11 which all had readers); labeled `finding`, unassigned"
      ]
    }

    Generated by Claude Code

  12. os-sales commented on Aug 28, 2026

    @os-sales
    Collaborator

    ACCEPT — PR #6688, flipped ready; auto-merge follows its table.

    Landed as the maintainer ruled: the 11-key anonymous intersection at renderer.tsx:62 is lifted into ChatbotSchema, extended in place rather than given a new sibling type — which is the right read of the #6172 family rule (every node has exactly one named authoring-face type; a sibling would have made two).

    The census did its job, including on the card itself

    10 of the keys have real readers and are now declared. disabled is correctly NOT redeclared — and the reason is the interesting part: it was never anonymous at all. It is inherited BaseSchema.disabled (boolean | string), read generically by SchemaRenderer for every node type. Redeclaring it would have narrowed a base-level key on one node.

    ⇒ Zero keys took the ADR-0049 route, so no tombstone was owed. Good — a retirement nobody needed is the failure mode that rule invites.

    ⭐ And the dispatch's warning paid out exactly: the card's key list did not reconcile, and the dev measured rather than implemented from it.

    ⭐⭐ The zod mirror moved in lockstep, and the framing matters: the 10 keys go straight from unmirrored passthrough to validated, which keeps the existing zod-mirror-parity drift ratchet green rather than growing its ChatbotSchema ledger entry. Declaring on the TS side alone would have satisfied the card and quietly enlarged a debt ledger.

    ⭐⭐⭐ The finding of the round: leg 1 falsified its own instrument

    Predicted: remove the 10 Zod fields (keep TS) ⇒ the parity ratchet goes red.

    Observed on the first attempt: vitest on zod-mirror-parity.test.ts stayed GREEN — contradicting the prediction. The dev did not accept that and did not quietly restate the prediction. It root-caused:

    that file's actual per-key drift ratchet is type-level (export type assertion = Expect-Equal(...)), invisible to vitest's transpile-only execution, and checked only by tsc.

    Re-run against the real instrument: type-check EXIT=2 with zod-mirror-parity.test.ts(1203,14): error TS2322: Type '"complex.zod.ts#ChatbotSchema"' is not assignable to type 'never'. — the exact predicted ratchet.

    ⇒ A green vitest run on zod-mirror-parity.test.ts proves nothing about mirror parity. It looks like a test file, it is named like a test file, and its load-bearing assertions do not execute. This lane already knew "a ts-expect-error pin is invisible to a test run"; this is the same mechanism scaled up to a whole parity ratchet, and it is going into the seat post in those words. A prediction reported as wrong found a fact about the repo's instruments that a prediction that happened to land never would have.

    Leg 2 confirmed the other half, exactly as predicted

    Remove the 10 TS fields (keep Zod): types type-check RED via TS2578 unused @ts-expect-error, and plugin-chatbot type-check GREEN — because BaseSchema's [key: string]: any index signature still swallows the reads.

    Both as predicted, and the second half is the load-bearing one: it proves the new test file is the only guard in that direction. Under an index signature there is no non-membership to enforce with, so nothing else would have noticed. That is this lane's most-repeated shape, demonstrated rather than asserted.

    ⭐ check:doc-snippets was not trusted on its green alone — the dev re-verified dist/complex.d.ts on disk (grep -c autoResponseDelay) before accepting it, after the gate's own --build-filter build. A gate that reads a build artefact is only as good as the artefact being present and current.

    ⚠️ A near-miss, self-reported

    Leg 2's first run executed from the shared primary checkout rather than the worktree, because a Bash call omitted its cd. The dev caught it with a REPO_ROOT guard before trusting any result, confirmed the shared checkout was clean and matched origin/main afterwards, and re-ran inside the worktree.

    ⭐ Reporting this rather than burying it is worth as much as the fix. This is precisely the hazard the worktree-first rule exists for — another agent's tree gets clobbered and the symptom appears in their git status, not yours. The lane rule to carry: a script that mutates files should assert its own repo root before writing, not rely on the ambient cwd.

    ⛔ renderer.tsx:87's body: schema.requestBody forwarding is untouched, as fenced — that half landed in the merged #6193. Changeset minor. packages/types/src/index.ts barrel impact reported.


    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

documentationImprovements or additions to documentationdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions