The floating bars used to make way for a side panel, which hid Publish
exactly when a node had just been edited. They keep their lane now and
the panel is inset out of it; the enlarged code editor stays a floating
surface between the two bars rather than covering them.
Shortcuts are one small registry (`lib/shortcuts.ts`): undo, redo, ⌘K,
copy, paste, and ⌘S — which publishes the flow, or applies the code when
the editor has focus. Copying carries a node's own source through
localStorage, so a paste works in another flow too.
A flow is now named where a node is, at the top of its panel and only
there, and both take effect on Confirm. Flow-level edits go through the
undo stack with everything else, clicking the canvas puts the flow panel
away, a failing node opens the logs filtered to its own traceback, and
the panel carries its test id on a phone as well.
A device read over a proprietary protocol was showing the fallback used
for a function node. A connector's type cannot be in the built-in icon
map — that is the point of it being a connector — so they share a plug.
Every one of these could be written as a function node, and until now had
to be. But nine node types account for most of what an actual Node-RED
installation contains — inject alone is placed 58 times, change 62 — and
a rule you fill in reads better on a canvas, and survives editing better,
than the same five lines repeated eighty times.
Inject, switch, change, filter-unchanged, join, trigger, command, file
and ntfy. They configure themselves through the editor's generated form,
so none of them needed frontend work beyond an icon.
The stateful ones (filter-unchanged, trigger) keep what they remember in
the engine's own state under a reserved prefix, never as a message. That
is the line: code you write is a pure function of its inputs, node types
the engine ships may remember things.
Verified against a live instance — a hook feeding change into
filter-unchanged into switch scales a raw reading, swallows a change
inside the deadband, and routes the rest to the right branch.
The bubble opened with the loader's own frame — `exec(compile(...))`
inside `_load_function` — before anything about the user's code, and
then said all of it twice, because a load failure arrives both as the
node's status and as a node_error issue carrying the same text. It is
now `Type: message (line N)`, deduplicated, with the line taken from the
node's own file. Full tracebacks still go to the server log.
Long messages grow the bubble instead of stretching it, and scroll only
past 240px. The tooltip arrow is gone: it read as a rotated square notch
under the bubble, and no better anywhere else it appeared.
- One dot per node now carries the whole story: primary while running, sage
after a good run, red when anything is wrong, with the explanation on hover.
The corner badge is gone, along with the second way of saying the same thing.
- A node that publishes something flashes a ring, so a running flow is legible
without reading the edge values. Nodes that consume but publish nothing stay
quiet, which is why the event carries an output count.
- Flow settings open in the same panel its nodes use, from a pencil in the
dock: the title, the name, and deleting the flow. NodePanel and FlowPanel
share the panel chrome rather than each drawing their own.
- Renaming is a server operation, because a flow's name is the namespace of its
messages: the directory moves and every other flow reading `old.message` is
repointed, instead of being left pointing at a flow that no longer exists.
An expired or orphaned session left the app on a half-rendered page instead of
the login screen: a properly signed token naming a user who no longer exists
answered 404, which the client does not treat as an authentication failure. All
failures in get_current_user are 401 now, and the client stops retrying them,
so a dead session goes straight back to the login screen.
- Clicking an edge names the two nodes it runs between, not just the message.
- A node with a problem shows one badge carrying the whole explanation, rather
than a corner badge and a status dot saying the same thing twice. The dot is
back to what it is good at: whether the node ran.
- The sidebar's collapse control sits in the sidebar, where a phone still finds
one in the chrome because there is no sidebar on screen to hold it.
The browser half of M3. Flows open on a full-bleed canvas with their chrome
floating over it: flow tabs top, dock bottom, node settings in a panel on the
right that leaves the graph visible and running behind it.
- Connections are derived, not stored. A node declares the messages it reads
and publishes; every matching pair draws an edge, so two producers of one
message converge on their consumer. Dragging output to input is shorthand
for pointing that input at the producer's message, and asks before it
replaces an existing one.
- Values land on the edges as they flow, over a websocket that feeds a store
outside React, so a value arriving re-renders its own chip and nothing else.
Clicking an edge shows the last payload and when it arrived.
- Node source is edited in Monaco, loaded only when a panel opens and themed
from the design tokens.
- Edits autosave; identical documents are skipped server-side, so a quiet
canvas writes nothing.
- Validation from the API shows on the node it belongs to and is summarised in
the dock, where each entry pans to its node.
- Works on a phone: touch-connect, 44px dock targets, and the node panel
becomes a full-screen sheet.
Two new tokens (--status-success, --font-mono) are mirrored in the website repo
and recorded in DESIGN-GUIDELINES.md.