Skip to content

plugin-chatbot: chatbot and chatbot-enhanced spread the whole SDUI node onto the host element — 14 non-DOM attributes reach the DOM #4431

Description

@yinlianghui

Measured by the #4425 phase-1 DOM-leak sweep gate (packages/app-shell/src/__tests__/widget-dom-leak-sweep.test.tsx). Recorded in that gate's ledger, not fixed there — phase 1 is measurement only.

The defect

Both registrations in packages/plugin-chatbot/src/renderer.tsx destructure exactly two keys and forward everything else:

ComponentRegistry.register('chatbot',
  ({ schema, className, ...props }) => …  <Chatbot … {...props} />

SchemaRenderer hands a registered component the authored node's own keys, the contents of its props container, and the host's trailing props. Only schema and className are removed, so all the rest becomes DOM attributes — React passes unknown lowercase attributes through in silence and stringifies object values.

Measured, through the real SDUI path

One render of a node carrying the canary set, under SchemaRendererProvider with a data-source adapter attached. Both plugin-chatbot:chatbot and plugin-chatbot:chatbot-enhanced emit the same 14 attributes:

name="canary_node"            bind="data.revenue"
events="[object Object]"      props="[object Object]"
arialabel="Canary label"      ariadescribedby="canary-desc"
datasource="[object Object]"  colorvariant="success"
zzcanary="CANARY-STR"         zzcanaryobj="[object Object]"
zzcanarynum="42"              zzcanarycamel="CANARY-CAMEL"
reference_to="contacts"       zzcanaryprop="CANARY-PROP"

Raw, from the sweep's failure output on the chatbot root element:

class="flex flex-col border rounded-lg bg-background overflow-hidden"
style="max-height: 500px;" id="canary-node" name="canary_node"
bind="data.revenue" events="[object Object]" arialabel="Canary label"
ariadescribedby="canary-desc" zzcanary="CANARY-STR"
zzcanaryobj="[object Object]" zzcanarynum="42" zzcanarycamel="CANARY-CAMEL"
reference_to="contacts" props="[object Object]" colorvariant="success"
zzcanaryprop="CANARY-PROP" aria-label="Canary label"
aria-describedby="canary-desc" data-obj-id="canary-node"
data-obj-type="plugin-chatbot:chatbot" datasource="[object Object]"

Two of these matter beyond tidiness:

  • datasource="[object Object]" is the injected adapter, not a schema key. It only appears when a host actually passes one — i.e. on a real deployment, never in a fixture that renders the widget bare. This is the same prop whose absence from a schema-only measurement shipped a six-key first pass in PR fix(plugin-dashboard): keep schema-shaped props off the KPI card DOM (#4357) #4428.
  • arialabel / ariadescribedby are the camelCase authored forms sitting next to the resolved aria-label / aria-describedby the renderer already emits correctly, so the element carries each ARIA value twice under two spellings, one of them meaningless to assistive technology.

plugin-chatbot:chatbot-floating is clean — its content mounts through a portal and its root does not spread onto a host element. Only the two registrations above are affected.

Not in scope of the gate that found it

#4425 phase 1 is measurement only and touches no widget source. These two targets are ledgered in the sweep with this issue as their owner, and the ledger assertion is exact set equality: fixing the leak turns the gate red until the ledger row is deleted in the same change. The row cannot outlive the defect.

Fix shape

Whatever #4425 phase 2 rules for the SDUI widget prop contract generally. If that decision lands on the toDomProps whitelist (#3291), these two call sites are covered by it and this issue closes with the ledger row. If it does not, they still need closing on their own terms — a deny-list here would have to enumerate the open tail, which is the half a deny-list cannot finish.


Generated by Claude Code

Activity

  1. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Triage (objectstack triage seat, cross-repo sweep): labeled bug + pm:blocked.

    Blocked-by: #4425

    Rationale: same disposition as #4432 — measured, ledgered with this issue as owner, fix shape explicitly deferred to #4425 phase 2 ("if the toDomProps whitelist wins, these two call sites are covered by it and this issue closes with the ledger row"). The datasource="[object Object]" leak (adapter, deployment-only) and the duplicated ARIA spellings are the two flagged-beyond-tidiness items to verify in whatever fix lands. Unblocks when #4425's phase-2 contract decision is made.


    Generated by Claude Code

  2. self-assigned this
    on Aug 12, 2026
  3. yinlianghui commented on Aug 12, 2026

    @yinlianghui
    CollaboratorAuthor

    CLAIM — session session_017Qqyix2QcnpUC9XeYVDzx3, branch claude/issue-4431-chatbot-whitelist.

    Ruling: unblocked by the #4425 phase-2 ruling (whitelist promoted to the SDUI widget contract — see the ruling comment there, veto window open). This card is migration step 1 and carries the shared infrastructure: (1) measure toDomProps's dependency-direction home per the #4409 method (expected: lift to @object-ui/core, packages/fields re-exports unchanged); (2) convert both chatbot and chatbot-enhanced registrations to consume-or-whitelist — the triage-flagged pair must be explicitly pinned: datasource (injected adapter — consumed, never stringified onto DOM) and the camelCase arialabel/ariadescribedby (must not appear; the resolved aria-label/aria-describedby must remain); (3) delete BOTH ledger rows in widget-dom-leak-sweep.test.tsx in the same PR — the two-way ratchet makes the fix red until the rows go, which IS the red-first. chatbot-floating untouched (measured clean).

    Grading: patch @object-ui/plugin-chatbot; if the helper lift adds a core export, minor @object-ui/core per emitted-.d.ts measurement; fields re-export graded by its own .d.ts diff.

    Surface (mutual exclusion): packages/plugin-chatbot/**, packages/core (helper lift only), packages/fields (re-export only), packages/app-shell/src/__tests__/widget-dom-leak-sweep.test.tsx (row deletion only), changesets. #4432 gates on this PR (shared ledger file + helper home).


    Generated by Claude Code


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions