Skip to content

Give notify and confirm somewhere to appear - #45

Merged
einari merged 1 commit into
mainfrom
work/interaction-hosts-20260923
Sep 23, 2026
Merged

einari merged 1 commit into
mainfrom
work/interaction-hosts-20260923

Conversation

@einari

@einari einari commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

A document can ask for two things that have no backend and no route behind them: say something to the user, and ask the user something. Every other action ends up at a host that knows how to do it; these two are purely what the user sees, so until now a host either invented a surface or the action went to the console.

What is here

useInteractionHosts() returns the notify, confirm and onUnsupported parts of a BrowserDispatcherHost together with the surfaces that back them:

const { host, surfaces } = useInteractionHosts();
const dispatcher = useMemo(() => createBrowserDispatcher({ ...host, executeCommand, navigate }), [host]);

return <InteractionScope dispatcher={dispatcher}>{children}{surfaces}</InteractionScope>;

They are handed out together because they are wired the same way and forgetting to render one is the failure they exist to prevent. An application with a design system of its own passes its own notify after the spread and keeps the rest.

Why confirm stops being a browser prompt

window.confirm blocks the event loop, cannot be styled, and cannot be driven by a test. A confirm exists to gate what follows it, which is exactly the thing worth asserting on - and the specifications here do: the command does not run until the question is answered, runs when the answer is yes, and does not when it is no. Dismissing the question declines it, because a gate that opens when ignored is not one.

The unresolved-action surface

Only useful while an application is being wired, and an application that has wired everything will never see it. It exists because a modelled button that silently does nothing is the hardest kind of defect to trace: nothing failed, nothing was reported, and the document is correct. It renders rather than logs, since the person who would fix it is looking at the screen. The same action is reported once however often it is asked for, so an unsupported action on a hover or an interval cannot grow until it covers the application it is describing.

Two smaller things

InteractionScope's context is now optional. A document whose messages are literal text and whose actions take no arguments resolves nothing, which is an ordinary document rather than a degenerate one; requiring a context anyway made every host invent an empty resolver. Everything reading the scope still gets a context that is definitely there - only the author is spared.

Writing the specifications turned up that NotificationLevel has three members and not four. The model, the language and its documentation all agree on info/warning/error, so the spec was corrected rather than the model.

Not here

Dialogs. Showing one means resolving a dialog template from the application model, and nothing in React can reach the application yet - that is the SceneApplicationProvider work. Guessing at that seam now would be harder to correct than leaving it.

Verification

yarn ci across all eight workspaces. Two SceneCommandForm specifications in components fail on this machine, and fail identically on a clean main, so they are not from this change.

A document can ask for two things that have no backend and no route behind them:
say something to the user, and ask the user something. Every other action ends up
at a host that knows how to do it, but these two are purely what the user sees,
so until now a host either invented a surface or the action went to the console.

The browser's own prompt was the fallback for confirm, and it is a poor one: it
blocks the event loop, cannot be styled, and cannot be driven by a test. A confirm
exists to gate what follows it, which makes it worth being able to assert on. The
new surface asks in the page, and declining is what happens when the question is
dismissed, because a gate that opens when ignored is not one.

The third surface reports what this host cannot do at all. That is only useful
while an application is being wired, and an application that has wired everything
will never see it - but a modelled button that silently does nothing is the
hardest kind of defect to trace, because nothing failed and the document is
correct. It is shown rather than logged since the person who would fix it is
looking at the screen. The same action is reported once however often it is asked
for, so an unsupported action on an interval cannot grow until it covers the
application it is describing.

The three are handed out together because they are wired the same way and
forgetting to render one is the failure they exist to prevent. An application with
a design system of its own passes its own notify after the spread and keeps the
rest.

A scope's context is now optional. A document whose messages are literal text and
whose actions take no arguments resolves nothing, which is an ordinary document
rather than a degenerate one, and requiring a context anyway made every host
invent an empty resolver - a place for a mistake to hide.

Dialogs are deliberately not here. Showing one means resolving a dialog template
from the application model, and nothing in React can reach the application yet.
Guessing at that seam now would be harder to correct than leaving it.
@einari einari added the minor label Sep 23, 2026
@einari
einari merged commit 60c2233 into main Sep 23, 2026
3 of 4 checks passed
@einari
einari deleted the work/interaction-hosts-20260923 branch September 23, 2026 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant