One shell for both editors. Flows and dashboards each get a searchable
overview under the padded shell, their editors move to the full-bleed
canvas, and the floating chrome is shared: a title bar that only says
what you are looking at, and a bottom dock carrying everything else —
the flow bar's status, settings and Publish moved down there, the
add-flow button moved to the overview.
Dashboards gain the rest of M4's visualization work:
- widgets are picked by clicking them, with the header as the drag
handle so a slider still slides and a switch still flips while
editing; settings moved into the flows' SidePanel
- react-grid-layout for drag and edge-resize, so the stored x/y finally
mean something; a dashboard nobody arranged is shelf-packed once
- a per-dashboard grid size, so a panel can be matched to its screen
- the chart widget, drawn with uPlot: several messages on one axis, fed
from the stored history plus the live socket tail, coloured from the
new --chart-1..5 ramp
- /view/{name}: the URL a wall panel is pointed at — no sidebar, no
footer, no editing, and no editor code, since routes are split
- a widget wired to a payload type it cannot carry, or wired to nothing
at all, carries the same red dot a failing node does; the picker
records the type it bound and WidgetDef refuses a mismatch on save
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7LwYgJfpkbLCTeiAf8U4A
32 lines
1.2 KiB
TypeScript
32 lines
1.2 KiB
TypeScript
import { motion } from "motion/react"
|
|
import type { ReactNode } from "react"
|
|
|
|
import { SidebarTrigger } from "@/components/ui/sidebar"
|
|
import { slideUp, transitions } from "@/lib/motion"
|
|
|
|
/**
|
|
* What you are looking at, floating top-centre over a full-bleed canvas.
|
|
*
|
|
* Reference rather than navigation: switching to another flow or dashboard is
|
|
* what the overviews are for, so this bar carries the name and nothing that
|
|
* takes you away from it. Shared by the flow editor and the dashboards so the
|
|
* two shells read as one.
|
|
*/
|
|
export function CanvasTitle({ children }: { children: ReactNode }) {
|
|
return (
|
|
<motion.div
|
|
variants={slideUp}
|
|
initial="hidden"
|
|
animate="visible"
|
|
exit="exit"
|
|
transition={transitions.emphasized}
|
|
className="pointer-events-auto absolute left-1/2 top-4 z-10 flex max-w-[calc(100%-2rem)] -translate-x-1/2 items-center gap-1 rounded-full border border-border bg-card/80 px-1.5 py-1 shadow-e2 backdrop-blur-md"
|
|
>
|
|
{/* The sidebar carries its own collapse control; a phone has no sidebar
|
|
on screen to carry it. */}
|
|
<SidebarTrigger className="size-8 shrink-0 text-muted-foreground md:hidden" />
|
|
{children}
|
|
</motion.div>
|
|
)
|
|
}
|