Skip to content

Add addEventListener/removeEventListener with DOM-model on* semantics#573

Open
ochafik wants to merge 1 commit intomainfrom
claude/dom-event-model
Open

Add addEventListener/removeEventListener with DOM-model on* semantics#573
ochafik wants to merge 1 commit intomainfrom
claude/dom-event-model

Conversation

@ochafik
Copy link
Copy Markdown
Contributor

@ochafik ochafik commented Mar 29, 2026

Adds DOM-model event semantics to on* setters and introduces addEventListener/removeEventListener for multi-listener support. The on* setters now have replace semantics (like el.onclick) with getters, and coexist with addEventListener listeners — both fire on dispatch.

How it works

New ProtocolWithEvents base class between Protocol and App/AppBridge. Each notification event gets a slot with two independent channels:

Feature on* setter addEventListener
Semantics Replace (single slot) Append (listener array)
Clear app.ontoolinput = undefined removeEventListener(event, fn)
Getter app.ontoolinput returns handler N/A

Dispatch order: onEventDispatch() (side-effects) → on* handler → addEventListener listeners.

Other changes

  • Getters for all on* properties on both App and AppBridge
  • replaceRequestHandler for request-handler on* setters (replace semantics, no double-set throw)
  • Reentrancy-safe dispatch (listener array snapshot-copied)
  • useHostStyleVariables and useHostFonts now use addEventListener with proper cleanup

Test plan

  • Build + all unit tests pass
  • All examples build
  • on* replace semantics, coexistence with addEventListener, getters, clearing
  • Double-set protection on direct setRequestHandler/setNotificationHandler
  • E2E tests

🤖 Generated with Claude Code

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 29, 2026

📖 Docs Preview Deployed

Preview (stable) https://pr-573.mcp-ext-apps-docs-preview.pages.dev
This commit https://e0353bf5.mcp-ext-apps-docs-preview.pages.dev
Commit b814955

Includes drafts and future-dated posts. All pages served with noindex, nofollow — search engines will not crawl this preview.

@ochafik ochafik requested a review from idosal March 29, 2026 02:46
it("App notification setters replace (DOM onclick model)", async () => {
const a: unknown[] = [];
const b: unknown[] = [];
app.ontoolinput = (p) => a.push(p);

it("App notification setter can be cleared with undefined", async () => {
const a: unknown[] = [];
app.ontoolinput = (p) => a.push(p);
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 29, 2026

Open in StackBlitz

@modelcontextprotocol/ext-apps

npm i https://pkg.pr.new/@modelcontextprotocol/ext-apps@573

@modelcontextprotocol/server-basic-preact

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-preact@573

@modelcontextprotocol/server-basic-react

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-react@573

@modelcontextprotocol/server-basic-solid

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-solid@573

@modelcontextprotocol/server-basic-svelte

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-svelte@573

@modelcontextprotocol/server-basic-vanillajs

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-vanillajs@573

@modelcontextprotocol/server-basic-vue

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-vue@573

@modelcontextprotocol/server-budget-allocator

npm i https://pkg.pr.new/@modelcontextprotocol/server-budget-allocator@573

@modelcontextprotocol/server-cohort-heatmap

npm i https://pkg.pr.new/@modelcontextprotocol/server-cohort-heatmap@573

@modelcontextprotocol/server-customer-segmentation

npm i https://pkg.pr.new/@modelcontextprotocol/server-customer-segmentation@573

@modelcontextprotocol/server-debug

npm i https://pkg.pr.new/@modelcontextprotocol/server-debug@573

@modelcontextprotocol/server-map

npm i https://pkg.pr.new/@modelcontextprotocol/server-map@573

@modelcontextprotocol/server-pdf

npm i https://pkg.pr.new/@modelcontextprotocol/server-pdf@573

@modelcontextprotocol/server-scenario-modeler

npm i https://pkg.pr.new/@modelcontextprotocol/server-scenario-modeler@573

@modelcontextprotocol/server-shadertoy

npm i https://pkg.pr.new/@modelcontextprotocol/server-shadertoy@573

@modelcontextprotocol/server-sheet-music

npm i https://pkg.pr.new/@modelcontextprotocol/server-sheet-music@573

@modelcontextprotocol/server-system-monitor

npm i https://pkg.pr.new/@modelcontextprotocol/server-system-monitor@573

@modelcontextprotocol/server-threejs

npm i https://pkg.pr.new/@modelcontextprotocol/server-threejs@573

@modelcontextprotocol/server-transcript

npm i https://pkg.pr.new/@modelcontextprotocol/server-transcript@573

@modelcontextprotocol/server-video-resource

npm i https://pkg.pr.new/@modelcontextprotocol/server-video-resource@573

@modelcontextprotocol/server-wiki-explorer

npm i https://pkg.pr.new/@modelcontextprotocol/server-wiki-explorer@573

commit: a1ebc1b

…ntListener

Fixes #551 (useHostStyles broken: fonts clobber theme/CSS variables)
Fixes #225 (user onhostcontextchanged and SDK hooks mutually exclusive)

The on* setters now follow the DOM event model:
- Replace semantics (like el.onclick) with getters
- Coexist with addEventListener/removeEventListener listeners
- Both channels fire on dispatch: on* handler then listeners

Introduces ProtocolWithEvents base class between Protocol and
App/AppBridge. Each notification event gets a slot with two independent
channels (singular on* handler + addEventListener listener array).

useHostStyles hooks now use addEventListener with proper cleanup,
so they compose correctly with user on* handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ochafik ochafik force-pushed the claude/dom-event-model branch from f121180 to a1ebc1b Compare March 29, 2026 02:50
@ochafik ochafik requested review from antonpk1 and localden March 29, 2026 03:12
@antonpk1
Copy link
Copy Markdown
Contributor

love it! I think we should keep app.onhostcontextchanged as deprecated to avoid surprises with backward compatibility for folks who update

@mel-anthropic
Copy link
Copy Markdown
Contributor

love it! I think we should keep app.onhostcontextchanged as deprecated to avoid surprises with backward compatibility for folks who update

If we keep the old handler, would it be possible to add a console warning when it's being overwritten and that it's deprecated?

params: McpUiResourceTeardownRequest["params"],
extra: RequestHandlerExtra,
) => McpUiResourceTeardownResult | Promise<McpUiResourceTeardownResult>)
| undefined,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any issues with this being undefined? Wouldn't if (callback) skip on undefined?

| ((
params: ListToolsRequest["params"],
extra: RequestHandlerExtra,
) => Promise<ListToolsResult>)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previous implementations like this would break, right?

app.onlisttools = () => ({ tools: ["foo"]})

Do we need to document this?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

useHostStyles internals override each other bug: app.onhostcontextchanged handlers silently overwrite each other

4 participants