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:
@@ -1,4 +1,4 @@
|
||||
import { Terminal } from "lucide-react"
|
||||
import { Terminal, X } from "lucide-react"
|
||||
import { useEffect, useRef } from "react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
@@ -30,12 +30,33 @@ function nodeLabel(nodeId: string, flow: string): string {
|
||||
return nodeId.startsWith(`${flow}.`) ? nodeId.slice(flow.length + 1) : nodeId
|
||||
}
|
||||
|
||||
/** What one node printed, or the whole flow when nothing is singled out. */
|
||||
export type LogsFilter = {
|
||||
open: boolean
|
||||
/** A node id within this flow, as the canvas asked for it. */
|
||||
node: string | null
|
||||
onOpenChange: (open: boolean) => void
|
||||
onClearNode: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* What the nodes of this flow printed, and the tracebacks of the ones that
|
||||
* failed — the detail the one-line error bubble on a node has no room for.
|
||||
*
|
||||
* Opening it is not only the dock's to do: a failing node points straight at
|
||||
* its own traceback, which is why the open state lives in the editor.
|
||||
*/
|
||||
export function LogsPanel({ flow }: { flow: string }) {
|
||||
const lines = useLiveLogs().filter((line) => line.flow === flow)
|
||||
export function LogsPanel({
|
||||
flow,
|
||||
open,
|
||||
node,
|
||||
onOpenChange,
|
||||
onClearNode,
|
||||
}: { flow: string } & LogsFilter) {
|
||||
const lines = useLiveLogs().filter(
|
||||
(line) =>
|
||||
line.flow === flow && (!node || nodeLabel(line.node, flow) === node),
|
||||
)
|
||||
const bottom = useRef<HTMLLIElement | null>(null)
|
||||
|
||||
// Follow the tail, which is where a running flow puts what just happened.
|
||||
@@ -45,7 +66,7 @@ export function LogsPanel({ flow }: { flow: string }) {
|
||||
}, [lines.length])
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<Popover open={open} onOpenChange={onOpenChange}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<PopoverTrigger asChild>
|
||||
@@ -65,9 +86,23 @@ export function LogsPanel({ flow }: { flow: string }) {
|
||||
|
||||
<PopoverContent align="center" className="w-[28rem] p-0">
|
||||
<div className="flex items-center justify-between border-b border-border px-3 py-2">
|
||||
<p className="text-xs font-medium uppercase tracking-[0.5px] text-muted-foreground">
|
||||
Logs
|
||||
</p>
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<p className="text-xs font-medium uppercase tracking-[0.5px] text-muted-foreground">
|
||||
Logs
|
||||
</p>
|
||||
{node ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 min-w-0 gap-1 px-2 font-mono text-xs"
|
||||
onClick={onClearNode}
|
||||
data-testid="clear-logs-filter"
|
||||
>
|
||||
<span className="truncate">{node}</span>
|
||||
<X className="size-3 shrink-0" />
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@@ -81,7 +116,9 @@ export function LogsPanel({ flow }: { flow: string }) {
|
||||
|
||||
{lines.length === 0 ? (
|
||||
<p className="px-3 py-6 text-center text-sm text-muted-foreground">
|
||||
Nothing yet. Anything a node prints shows up here.
|
||||
{node
|
||||
? `Nothing from ${node} yet.`
|
||||
: "Nothing yet. Anything a node prints shows up here."}
|
||||
</p>
|
||||
) : (
|
||||
<ScrollArea className="h-72">
|
||||
|
||||
Reference in New Issue
Block a user