Skip to content

feat: add runtime in-page channel events - #358

Draft
posva wants to merge 4 commits into
devframes:mainfrom
posva:feat/in-page-channel-events
Draft

feat: add runtime in-page channel events#358
posva wants to merge 4 commits into
devframes:mainfrom
posva:feat/in-page-channel-events

Conversation

@posva

@posva posva commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Problem

Event handlers could only be installed on the receiving endpoint through its initial functions option, preventing runtime subscription and cleanup. The sending endpoint also exposed callEvent(), which reads like an RPC call and obscures that events cross endpoints: page-script emission is handled by a panel, and panel emission is handled by the page script.

Change

  • Add typed cross-endpoint emit() / on(); on() returns an unsubscribe function.
  • Keep every protocol function declaration required, while allowing type: 'event' declarations to omit handler.
  • Keep local lifecycle events under channel.events and retain callEvent() as a deprecated alias.

Tested with in-page channel tests, package typechecks, build, and API snapshots.

Copilot AI lite review requested due to automatic review settings September 4, 2026 15:31
@coldtea-pr-lens

coldtea-pr-lens Bot commented Sep 4, 2026

Copy link
Copy Markdown

◈ PR Lens

🟢 +0 new · 🟠 ~5 changed · 🔴 -0 removed · 2 flows · 8 files · commit 3eb32b9


Architecture

Architecture diagram for devframes/devframe at 3eb32b9

5 components touched across 2 lanes.

Open full size


Inside the changed components — 1 view

Component view — In-Page Channel Endpoints and Registry

Internal modules powering emit/on messaging and dynamic listener registration across page script and panel endpoints.

Architecture view of Component view — In-Page Channel Endpoints and Registry in devframes/devframe

Data flow

Data flow diagram for devframes/devframe at 3eb32b9

Panel emits event to page script · Page script fans event out to panel listeners

Open full size


The other flows — 1 sequence

Page script fans event out to panel listeners

Sequence diagram of Page script fans event out to panel listeners in devframes/devframe

Drill down
Client Runtimes & UI — 4 components
🟡 CHANGED In-Page Channel Bridge

Provides bidirectional messaging, event fanout, and shared state between host page scripts and devframe panels.

🟡 CHANGED Panel Channel Endpoint

Connects a devframe panel to the channel, exposing emit() and on() methods while buffering emitted events during connection setup.

🟡 CHANGED Page Script Channel Endpoint

Runs in the host page, accepting panel connections, fanning out emitted events across connected panel peers, and handling incoming calls.

🟡 CHANGED Function & Event Registry

Maintains static function declarations alongside dynamic runtime listeners registered via on(), dispatching calls and events.

Built-in Devframes — 1 component
🟡 CHANGED A11y Inspector Devframe

Accessibility inspector devframe whose panel UI uses channel.emit to send scan and highlight actions to the page script.


View

  • Architecture lens
  • Data flow lens
  • Expand every detail
  • Show unchanged neighbours

Tip

Show unchanged neighbours lists the components this change did not touch alongside the ones it did, so the drill-down shows what the changed code sits next to.

🪧 More tips
  • Run PR Lens on your own machine: npx skills add coldteadotai/pr-lens installs the agent skill. Then tell your coding agent: "Diagram the change you just made with PR Lens and attach it to the pull request."
  • Draw a diff before it is even a pull request: npx @coldtea/pr-lens-cli analyze --base origin/main reads the diff with your own model key, and npx @coldtea/pr-lens-cli render .pr-lens/graph.json draws the same lenses on your machine.
  • The boxes under View are live. Tick Architecture lens or Data flow lens to choose which diagrams appear, or Expand every detail to open every drill-down at once. The comment redraws in place a few seconds later.
  • GitHub will not let you zoom an image in a comment. The link under each diagram opens it full size on a page of its own, where you can.
  • The CLI's render picks up .github/pr-lens.yml automatically and applies your corrections (renames, exclusions, lane pins) at draw time.
  • Would you rather run it from CI on a key of your own? Add .github/workflows/pr-lens.yml with coldteadotai/pr-lens/packages/action@v0 and a model key in your repository secrets, say GEMINI_API_KEY. The Action asks Gemini by default, or OpenAI and any endpoint speaking /chat/completions through its provider input.
  • PR Lens is free for open source. A star on the repository is what keeps it going.
  • Push a new commit and the whole comment re-renders for the new head. An older run never overwrites a newer one, so a slow render cannot put a stale diagram back.
  • The diagrams follow your GitHub theme, so dark mode gets the dark render and light mode the light one, and the moving dots show this pull request's data in motion.

◈ Rendered by PR Lens · crafted with ❤️ by the Coldtea team · Come say hi on Discord

@vercel

vercel Bot commented Sep 4, 2026

Copy link
Copy Markdown

@posva is attempting to deploy a commit to the NuxtLabs Team on Vercel.

A member of the Team first needs to authorize it.

Copilot AI left a comment

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.

🟡 Changes recommended

defineChannelFunction() can unintentionally allow missing handlers when type is omitted due to generic inference, weakening type safety for the helper API.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds runtime event subscription and a clearer emission API to the in-page channel so panels/page scripts can dynamically subscribe/unsubscribe to in-page events, while keeping compatibility with the existing callEvent() API.

Changes:

  • Introduces typed channel.emit() plus runtime channel.on() (returning an unsubscribe function) on both page-script and panel endpoints; keeps callEvent() as a deprecated alias.
  • Allows type: 'event' function declarations to omit handler, enabling purely runtime-driven listeners.
  • Updates tests, type tests, docs, and the a11y devframe to use emit() and validate the new runtime subscription behavior.
File summaries
File Description
tests/snapshots/tsnapi/devframe/in-page-channel.snapshot.d.ts Updates public type snapshots to include emit()/on() and relaxed event handler requirements.
plugins/a11y/app/lib/channel.ts Migrates a11y panel-side event sending from callEvent() to emit().
packages/devframe/src/in-page-channel/types.ts Updates public types/docs for protocol semantics; enables event declarations without handlers; adds emit()/on() to endpoint interfaces.
packages/devframe/src/in-page-channel/types.test-d.ts Adds type-level tests for event-without-handler and runtime on() typing.
packages/devframe/src/in-page-channel/panel.ts Adds emit() and on() to the panel endpoint implementation; makes functions required at runtime.
packages/devframe/src/in-page-channel/page-script.ts Adds emit() and on() to the page-script endpoint implementation; deprecates callEvent() to alias emit().
packages/devframe/src/in-page-channel/internal.ts Extends the local registry to support runtime listeners via on() and resolves handlers accordingly.
packages/devframe/src/in-page-channel/index.ts Adjusts defineChannelFunction generics (ARGS default).
packages/devframe/src/in-page-channel/in-page-channel.test.ts Updates runtime tests to validate emit() + runtime subscription/unsubscribe behavior.
docs/content/8.references/5.browser-api.md Adds an in-page channel endpoint API reference table.
docs/content/1.guide/12.in-page-channel.md Updates guide examples and wording to use emit() + runtime on() subscriptions.
Review details

Suppressed comments (1)

packages/devframe/src/in-page-channel/index.ts:40

  • defineChannelFunction() leaves TYPE without a default, so calls that omit type can infer TYPE as the full InPageFunctionType union. With the new conditional handler optionality for events, that can accidentally make handler optional (e.g. defineChannelFunction({ name: 'x' }) becomes type-valid), weakening the helper’s compile-time safety.
export function defineChannelFunction<
  NAME extends string,
  TYPE extends InPageFunctionType,
  ARGS extends any[] = [],
  RETURN = void,
  const AS extends RpcArgsSchema | undefined = undefined,
  const RS extends RpcReturnSchema | undefined = undefined,
>(
  • Files reviewed: 10/11 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +54 to +56
| `emit(name, ...args)` | Fans an event out to every connected panel. | Sends an event to the page script, buffering while connecting. |
| `on(name, listener)` | Subscribes to events emitted by a panel. | Subscribes to events emitted by the page script. Returns an unsubscribe function. |
| `call(name, ...args)` | Available through a specific `PanelPeer`. | Calls a page-script function and awaits its result. |
Comment thread docs/content/1.guide/12.in-page-channel.md Outdated
Copilot AI review requested due to automatic review settings September 4, 2026 15:36
NAME extends string,
TYPE extends InPageFunctionType,
ARGS extends any[],
ARGS extends any[] = [],

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

needed because handler is optional if type is event

Copilot AI left a comment

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.

🟡 Changes recommended

The new runtime listener registry currently allows subscribing to undeclared method names and can silently treat missing non-event handlers as no-ops, which risks bypassing intended validation and masking misconfiguration.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

packages/devframe/src/in-page-channel/page-script.ts:67

  • options.functions is assumed present at runtime, but if a non-TypeScript consumer passes {} / omits it, Object.entries(options.functions) will throw a low-signal error. A small runtime assertion would make misconfiguration easier to diagnose.
    packages/devframe/src/in-page-channel/panel.ts:66
  • options.functions is now required by the types, but at runtime (JS usage) Object.entries(options.functions) will throw a generic Cannot convert undefined or null to object if it’s missing. Adding an explicit runtime assertion here would produce a clearer error for consumers who aren’t typechecked.
  • Files reviewed: 10/11 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment on lines +178 to +185
on(name, listener) {
let registered = listeners.get(name)
if (!registered) {
registered = new Set()
listeners.set(name, registered)
}
registered.add(listener)
return () => {
Comment on lines +200 to +204
if (definition?.args?.length)
await validateArgs(definition.name, definition.args, args)
const result = await definition.handler(...args)
if (definition.jsonSerializable)
const result = await definition?.handler?.(...args)
for (const listener of [...(listeners.get(name) ?? [])])
listener(...args)
...args: FnArgs<PanelFunctions<P>[K]>
) => void
/** Subscribe to an event emitted by a panel. Returns an unsubscribe function. */
on: <K extends keyof PageScriptFunctions<P> & string>(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this is also wrong: it's probably allowing too many, it should only allow those of type: 'event'

@posva
posva marked this pull request as draft September 4, 2026 15:54
@posva

posva commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

I'm realizing some stuff is still not good enough, so marking as draft

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.

2 participants