Copy nodes, bind the keys, and keep publish within reach

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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7LwYgJfpkbLCTeiAf8U4A
This commit is contained in:
2026-08-16 16:43:41 +02:00
co-authored by Claude Fable 5
parent 929bb56662
commit fa12ffd323
9 changed files with 477 additions and 222 deletions
@@ -1,6 +1,5 @@
import { useQuery } from "@tanstack/react-query"
import { useNavigate } from "@tanstack/react-router"
import { useEffect } from "react"
import type { FlowSummary, NodeTypeInfo } from "@/client"
import {
@@ -40,17 +39,6 @@ export function CommandPalette({
enabled: open,
})
useEffect(() => {
const onKey = (event: KeyboardEvent) => {
if (event.key === "k" && (event.metaKey || event.ctrlKey)) {
event.preventDefault()
onOpenChange(!open)
}
}
window.addEventListener("keydown", onKey)
return () => window.removeEventListener("keydown", onKey)
}, [open, onOpenChange])
// Picking an item re-renders the canvas, which can interrupt the dialog's
// exit animation and leave its overlay swallowing clicks. Unmounting the
// dialog outright is deterministic; the palette does not need to fade out.