Skip to main content

UI Events

These events fire when Claude Code draws part of the interface, and when a person uses a control that a mod drew. The second table lists the events that the $.ui methods raise.

A deny from a hook refuses a scroll, focus, or close. An invalid tree draws the engine's own drawing.

Official pages: Draw in the Interface, Mods Reference. Source: the program (types-2.1.304.d.ts).

Example​

This hook adds a count to the spinner.

let calls = 0
on('tool.call', ($, e, next) => { calls += 1; return next(e) })
on('ui.render', { component: 'Spinner' }, async ($, e, next) =>
next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } }))

Engine Events​

ItemStatusWhat it doesHow it worksWhere it can be used
ui.renderDocumentedFires when the engine is about to draw a component: once for each input value (props, viewport width), at plugin load, or on $.ui.invalidate("ui.render").Input RenderInput. Result RenderElement: the tree to draw. next(e) gives the engine's drawing. An invalid tree draws the engine's drawing.on('ui.render', ...). In tests: $.ui.render(...) on the test engine (not on a hook's $).
ui.resolveDocumentedFires when the plugins load (not on each draw), once for each surface, component, and plugin. e names the surface and component, never the props.Input ResolveInput. Result ElementTable: the element table of the surface. A hook may restyle an element or leave one out.on('ui.resolve', ...). Also $.ui.resolve(...).
ui.pressDocumentedFires when a Button that a render hook drew is pressed on a surface. e is { plugin, element, component, surface }. element is the button's key. Set a key on buttons that share a label: until 2.1.304 a click was lost when same-label buttons with no key were redrawn unchanged.Input UiPressArgument. Result { element }. next(e) lets the press through. Returning { element } takes it.on('ui.press', ...).
ui.inputDocumentedFires when an Input that a render hook drew changes or is submitted. e is { plugin, element, component, surface, kind, value }.Input UiInputArgument. Result { element, value }. next({ ...e, value }) rewrites the typing.on('ui.input', ...).
ui.selectDocumentedFires when a Select that a render hook drew is picked from. e is { plugin, element, component, surface, value }.Input UiSelectArgument. Result { element, value }. next({ ...e, value }) rewrites the pick.on('ui.select', ...).
ui.messageDocumentedFires when a Client that this plugin drew posts from its surface module (surface.post(data)). Only this plugin's hooks see it.Input UiMessageArgument. Result { props? }. next({ ...e, data }) rewrites the data. One call for each instance in each frame.on('ui.message', ...).
ui.faultDocumentedFires when a Client that this plugin drew failed on a surface: its module did not load, its drawing failed, or its code failed after it drew.Input UiFaultInput. Result {}. Only this plugin's hooks see it. e.phase says when and e.reason says why.on('ui.fault', ...).
ui.scrollDocumentedFires before the window of a site moves: the person's wheel or scroll keys on a Pane body or the AbovePrompt band (at its edges too), or $.ui.scroll.Input UiScrollInput. Result {} or { deny }. next({ ...e, offset }) scrolls elsewhere.on('ui.scroll', ...). Also $.ui.scroll(...).
ui.focusDocumentedFires before the focus ring of a site moves: the person's Tab, arrows, or click in a Pane or the band, an autoFocus element taking it, or $.ui.focus.Input UiFocusInput. Result {} or { deny }. next({ ...e, element }) moves the focus to another element.on('ui.focus', ...). Also $.ui.focus(...).

Events Raised by UI Calls​

For each call, a hook above the caller returns next(e), { deny: reason }, or { value }.

ItemStatusThe argumentInput and value
ui.toastDocumented$.ui.toast(text, { timeoutMs })Input { text: string, timeoutMs?: number }. Value void.
ui.statusDocumented$.ui.status(text). text undefined clears the line.Input { text: string | undefined }. Value void.
ui.logDocumented$.ui.log(text, { to }). to is always present. next({ ...e, to: "debug" }) keeps a line off screen.Input { text: string, to: UiLogSink }. Value void.
ui.noticeDocumented$.ui.notice(tool_use_id, text)Input { tool_use_id: string, text: string | undefined }. Value void.
ui.invalidateDocumented$.ui.invalidate(event)Input { event: InvalidatableEventName }. Value void.
ui.openDocumented$.ui.open({ id, title, focus }). A hook above may retitle it or refuse with { deny }, never rename it.Input PaneOpenArgs. Value UiOpenResult.
ui.closeDocumented$.ui.close({ id }) with origin plugin. The engine raises it too, for the person (person) and for an unload (unload).Input PaneCloseInput. Value void.
ui.panesDocumented$.ui.panes()Input NoArgs. Value readonly UiPane[].
ui.selectionDocumented$.ui.selection()Input NoArgs. Value UiSelection | undefined.
ui.copyDocumented$.ui.copy({ text, surface }). surface takes the session's first surface when left out.Input UiCopyArgs. Value UiCopyResult.
ui.viewDocumented$.ui.view({ agentId }): whose transcript the main view shows. None means the main conversation.Input SiteView. Value UiViewResult.
ui.notifyDocumented$.ui.notify(text, { title }). title is the plugin's name when left out. Two mods that notify again on hearing a notification can echo each other without end.Input { text: string, title?: string }. Value UiNotifyResult.
ui.blitDocumented$.ui.blit(...): the cells of a Raster or the source of a keyed Image. A hook above may rewrite either, or { deny }.Input UiBlitArgs. Value UiBlitResult.