At a glance
- Does
- Shows a page and waits for it to answer
- Two outlets
- Submitted carries the answer; Dismissed carries none of it
- Emits
- submitted, submittedAt, state
Set it up
Put an HTML Template step above it and leave the HTML field at its default {{html}}, or paste a whole page in. Click the arrow on the field to open the HTML Editor: highlighted markup beside a live render with your test values filled in.

This is the blank-canvas step. User Input asks one question, Form asks several, Pick from List asks which of these — HTML View lets the page decide what to ask.
The page talks back
Every page opens with a small window.watchflows object already waiting:
<button onclick="watchflows.emit({ action: 'join', id: 'abc' })">Join now</button>
<button onclick="watchflows.dismiss()">Not now</button>
watchflows.emit(payload)— answered. The flow leaves by Submitted with the payload undersubmittedas real structure, so{{submitted.meeting.id}}resolves. Called with nothing,submittedis present but null.watchflows.dismiss()— done without answering. So do the close button and Escape, unless you switch that off.watchflows.update(state)— records where the page got to without answering. The window stays open, only the latest is kept, and it rides out as{{state}}on either outlet — so a page that gets closed still reports what happened.watchflows.heartbeat()— "still going". Only a page that runs longer than the wait with nobody touching it needs this; typing, clicking and scrolling already count.
The page is self-contained: inline styles and scripts and data: images work, and so do localStorage and history.pushState routing. Anything needing the network — a CDN stylesheet, a web font, a fetch — is refused, and the view never navigates.
Configure
| Field | Default | What it does |
|---|---|---|
| HTML | {{html}} | The page, with {{...}} filled from the incoming payload. A page that renders to nothing fails the node before anything opens. |
| Title | — | The window title. Not shown when chromeless or full-screen. |
| Window | Window | Window is an ordinary centred window and brings Watchflows forward. Floating card (HUD) is a small always-on-top card that steals no focus. Full-screen takeover covers everything, including another app's full-screen Space, and has no close button. |
| Chrome | Title bar | None makes the window the page's own edges. Hidden for the takeover, which is always chromeless. |
| Width / Height | 720 × 520 | Size in points. Hidden for the takeover. |
| Closing the window counts as Dismissed | On | Off removes the close button and makes Escape and ⌘W do nothing, so only the page's own buttons, the wait, or stopping the flow end it. |
| Give up after (minutes, 0 = never) | 0 | How long to go without hearing from the page before the node fails. Anything above 24 hours is treated as 24 hours. |
Output
Both answer keys exist on the Submitted path only, merged over the incoming payload.
| Key | Type | Value |
|---|---|---|
submitted | Any | Exactly what the page passed to watchflows.emit(), as real structure |
submittedAt | String | When the page answered, ISO 8601 |
state | Any | The last thing watchflows.update() recorded — on either outlet, if the page checkpointed at all |
Example: approve something before it ships
A hotkey builds the page, HTML View floats it as a card with Approve and Reject, and the Submitted branch reads {{submitted.action}}.

Paste this into the Flow Builder to get the same flow:
On a keyboard shortcut, show a floating card with Approve and Reject buttons and branch on which one I press.
Good to know
Closing the page is not a failure. Dismissing takes the Dismissed wire, the run finishes as completed, and a wired Failure outlet never sees it. Whichever outlet is not taken is skipped in full.
Set Give up after for anything unattended. While a page is open the flow cannot run again, so a page nobody answers quietly blocks every later trigger. The wait measures silence, not age — it restarts every time the page is heard from. Running out fails the node; it never takes the Dismissed wire, but the last watchflows.update() is written into the run log.
Browser storage dies with the window. For anything that should outlive the run — a high score, a preference — get it out with emit() or update() and write it with a Database node; the next run reads it back and drops it into the page with {{...}}.
For a chromeless window or floating card, keep body transparent. No background, no padding, and one rounded root element with its own background: the window shrinks to that element and floats on the desktop with a shadow, like a widget. A page that paints its body instead keeps the configured size.