renderChildren only handles objects and arrays — a scalar root (string,
number, bool) fell through to `return null`, rendering an empty <ul>. So
a Python node returning `output = "hello"` (single string item) showed
as empty in the Output column.
At depth 0, dispatch scalar roots to a single labelless leaf with the
same quoting/coloring as nested leaves.
The pin loader was storing port items verbatim in the on-wire shape
[{json: ..., binary: null}]. The executor's WrapPin treats each pin-array
element as one item's whole payload, so the wrapper leaked in: scripts saw
items[0] == {"json": ..., "binary": null} and items[0]["test"] raised
KeyError instead of returning the value.
Now the loader unwraps each item to its .json payload before storing.
Updated the existing test assertion and added a multi-item regression.
Backend: PythonTransformNode wraps script execution in a try/catch that
runs the exception through IronPython's ExceptionOperations.FormatException.
Users now see a proper Python-style traceback (file, line, type, message)
instead of the bare CLR message (which was often empty or just "unexpected
token"). Source is named "script" for readable File lines in the trace.
Frontend: findLatestIo also returns status + error; NodeEditorDialog
renders a red-bordered traceback block in the Output column when the
node failed, replacing the misleading "No data yet" state.
The Run button in NodeEditorDialog and the Test-run button in the editor
toolbar both POST /api/workflows/{id}/executions, which reads the graph
from the DB. Any un-saved edit (Python script tweak, config change, a
freshly-dragged node) was silently ignored.
Both entry points now await the workflow save before starting the
execution. New Vitest pins the save-then-post ordering in the dialog.
The textarea sits above the highlight overlay in paint order (same stacking
context, later in DOM). With bg-slate-950 on the textarea, its opaque
background painted over the overlay's light text — every code field (Python
Transform's script, any format:"code" or expression field) rendered as an
apparently-empty black box.
Swap: bg-slate-950 moves onto the overlay; the textarea becomes bg-transparent
and keeps its border, caret, and selection styling.
Left column shows the port-0 inputs as a clickable JsonTree that inserts
{input.<path>} at the last-focused parameter caret; middle column carries
the node label, optional connection dropdown, SchemaForm, WebhookUrlHint
(for http.webhook), Run/Delete/Done buttons; right column shows the
port-0 outputs read-only. ESC and backdrop-click both call onClose;
changes flow through onPatch so the parent still owns the toolbar Save.
Adds a `server` alias export to src/testing/msw so the dialog test file
can import the shared MSW server under the shorter name used by newer
tests without breaking the existing `mswServer` import sites.
POST behaviour unchanged (JSON body). GET on the same URL now triggers the
workflow with the query string collapsed into a JSON object: /wh/wf/hi?a=1&name=x
becomes {"a":"1","name":"x"}, and repeated keys collapse into a string
array (?tag=a&tag=b → {"tag":["a","b"]}). No query string yields {} —
matches the empty-POST-body semantics.
HEAD returns 200 empty so Slack/Discord link previews don't fire the workflow
AND don't flag the URL as broken. Other verbs (PUT/PATCH/DELETE/OPTIONS) still
return 405 JSON with Allow: POST, GET.
Frontend WebhookUrlHint now advertises both verbs and drops the POST prefix
from the displayed URL so the raw URL is copy-and-clickable.
6 new xUnit tests cover: GET-unknown-404, GET-with-query, GET-repeated-keys,
GET-empty-query, HEAD-200, PUT-still-405 (updated Allow header).
Backend: POST /api/connections/{id}/test decrypts the stored config, calls
provider.OpenAsync with a 10s CancellationTokenSource, closes the returned
handle in a finally, and returns {ok: bool, error?: string}. Always 200
unless the id is unknown (404) — the outcome of the round-trip belongs in
the body, not the status. 2 new xUnit tests cover the 404 path and the
"connection refused" failure path (points at 127.0.0.1:1).
Frontend: Plug icon on each ConnectionsPage row. Click → animated icon
during the round-trip, then an inline pill: 'Connected' (green check) on
success or the truncated error (red X, full text on hover) on failure.
Auto-clears after 6s. Row state is tracked per-id in a plain useState;
the shared react-query cache stays untouched. 1 new Vitest covers both
outcomes via MSW handlers.
Before, the Test run button fired and returned an execution id but there was
no on-screen confirmation, so users assumed nothing happened. Adds an inline
status bar under the toolbar that shows 'Running…', then either 'Started
execution <id> · View execution →' (auto-clears in 6s) or 'Test run failed:
<msg>' (auto-clears in 8s).
When the selected node is http.webhook, the config panel now shows the full
POST URL (${origin}/wh/${workflowId}/${path}) with a one-click Copy button
and an active-workflow reminder. Removes the guesswork of assembling the
URL by hand from the design spec.
React Flow's built-in keyboard handling was silently no-op'd because
deleteKeyCode wasn't set (v12 defaults it to null when the prop is absent
in the strict TS types we consume). Set it to ['Delete', 'Backspace'] and
add an onNodesDelete handler that filters edges whose endpoints just
vanished — applyNodeChanges only touches nodes, so without this the graph
kept dangling edges.
Also fixes a tsc regression the same touch caught in ConnectionsPage.test
where a request.json() cast leaked 'never' back into a later access.
Backend: PUT /api/connections/{id} with UpdateConnectionRequest {Name, Config}.
Provider is immutable; a secret field left blank in the incoming config is
merged from the stored row so users can rename or rotate non-secret fields
without re-typing every credential. Invalidates the ConnectionInstanceRegistry
cache on update so the next GetConnection() re-opens with the fresh config.
409 on unique-name-per-provider collision, 404 on unknown id, 400 on non-
object config. 6 new xUnit tests cover the merge, overwrite, 404, 409, 400
paths plus the happy-path round-trip.
Frontend: ConnectionsPage gains an Edit pencil per row. The form is shared
between create and edit modes; provider select is disabled in edit; secret
fields render blank with an inline hint that leaving them blank keeps the
stored value. One new Vitest covers the edit flow.