Files
app/frontend/src/components/Flow/CommandPalette.tsx
T
stroblmeandClaude Opus 5 4215e057d1
Docs / docs (push) Successful in 22s
Playwright Tests / test-playwright (1, 2) (push) Successful in 3m14s
Playwright Tests / test-playwright (2, 2) (push) Successful in 1m53s
pre-commit / pre-commit (push) Failing after 2m13s
Test Backend / test-backend (push) Successful in 2m38s
Compose Smoke Test / test-compose (push) Successful in 38s
Playwright Tests / merge-reports (push) Successful in 1m8s
Add a global search, and stop the sidebar logo squeezing
`GET /api/v1/search/` hands the client one flat index of everything worth
jumping to — flows and the nodes inside them, dashboards and the widgets on
them, panels, secrets, modules, workers and alert channels — and cmdk matches
it in the browser, so results narrow while typing without a round trip per
keystroke. A node hit is the one thing no list endpoint could answer: it opens
its flow with that node in focus.

The panel is reached from **Search** above Documentation in the sidebar, or
⌘K anywhere. The flow canvas palette moves to ⌘P, being the narrower of the two.

The panels dialog gains an address (`/dashboards?panels`) so a panel hit has
somewhere to land, and the sidebar logo gets `shrink-0`: the rail's width
animates while the logo is already back, and a flex item short of room is
squeezed rather than clipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vGH7jqcXxWKP9wZFPyVdU
2026-08-28 22:19:08 +02:00

134 lines
3.8 KiB
TypeScript

import { useQuery } from "@tanstack/react-query"
import { useNavigate } from "@tanstack/react-router"
import type { FlowSummary, NodeTypeInfo } from "@/client"
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
import { libraryQueryOptions } from "./queries"
/**
* ⌘P: add a node, jump to another flow, or run the current one, without
* reaching for the dock.
*/
export function CommandPalette({
open,
onOpenChange,
nodeTypes,
flows,
onAddNode,
onAddSharedNode,
onRun,
}: {
open: boolean
onOpenChange: (open: boolean) => void
nodeTypes: NodeTypeInfo[]
flows: FlowSummary[]
onAddNode: (type: string) => void
onAddSharedNode: (libName: string) => void
onRun: () => void
}) {
const navigate = useNavigate()
const { data: libraryData } = useQuery({
...libraryQueryOptions(),
enabled: open,
})
// 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.
if (!open) return null
const library = libraryData ?? []
const close = (action: () => void) => {
onOpenChange(false)
action()
}
return (
<CommandDialog
open={open}
onOpenChange={onOpenChange}
title="Commands"
description="Add a node, switch flow, or run"
>
<CommandInput placeholder="Add a node, switch flow, run…" />
<CommandList>
<CommandEmpty>Nothing matches that.</CommandEmpty>
<CommandGroup heading="Add a node">
{nodeTypes.map((nodeType) => (
<CommandItem
key={nodeType.type}
value={`${nodeType.title} ${nodeType.description}`}
onSelect={() => close(() => onAddNode(nodeType.type))}
>
<span className="flex flex-col">
<span>{nodeType.title}</span>
<span className="text-xs text-muted-foreground">
{nodeType.description}
</span>
</span>
</CommandItem>
))}
</CommandGroup>
{library.length > 0 ? (
<CommandGroup heading="Reusable nodes">
{library.map((entry) => (
<CommandItem
key={entry.name}
value={`shared ${entry.name}`}
onSelect={() => close(() => onAddSharedNode(entry.name))}
>
<span className="flex flex-col">
<span className="font-mono">{entry.name}</span>
<span className="text-xs text-muted-foreground">
{(entry.used_by ?? []).length === 1
? "Used once"
: `Used ${(entry.used_by ?? []).length} times`}
, code shared with every flow using it
</span>
</span>
</CommandItem>
))}
</CommandGroup>
) : null}
{flows.length > 0 ? (
<CommandGroup heading="Flows">
{flows.map((flow) => (
<CommandItem
key={flow.name}
value={`flow ${flow.name} ${flow.title}`}
onSelect={() =>
close(() =>
navigate({
to: "/flows/$flowName",
params: { flowName: flow.name },
}),
)
}
>
{flow.title || flow.name}
</CommandItem>
))}
</CommandGroup>
) : null}
<CommandGroup heading="Actions">
<CommandItem value="run flow" onSelect={() => close(onRun)}>
Run this flow
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
)
}