/** * The one kind→colour map — the Studio visual language for handler kinds and * graph node kinds. Pure data + helpers so components and the graph canvas * agree on colour and read the same in Storybook and tests. */ /** Canonical hex per kind (matches `studio-redesign.html`). */ export const KIND_COLOR: Record = { handler: "#5b8def", action: "#7c9cff", query: "#36c6a8", event: "#c08bff", listener: "#9aa7ff", actor: "#ffb454", workflow: "#ff7eb6", projection: "#5ad1ff", command: "#7c9cff", // structural / topology kinds app: "#39d98a", plugin: "#8a97ad", capability: "#8a97ad", route: "#5ad1ff", sinkStage: "#5c6a82", sourceStage: "#5c6a82", hook: "#8a97ad", externalCall: "#ffca45", }; const FALLBACK = "#8a97ad"; /** Hex colour for a kind (falls back to a neutral dim). */ export function kindColor(kind: string | undefined): string { return (kind && KIND_COLOR[kind]) || FALLBACK; } /** Map a graph node kind to a KindBadge variant (semantic palette). */ export function kindVariant( kind: string | undefined, ): "info" | "success" | "warning" | "danger" | "neutral" { switch (kind) { case "handler": case "action": case "command": case "query": case "route": return "info"; case "app": return "success"; case "actor": case "externalCall": return "warning"; default: return "neutral"; } } /** * Map a dotted telemetry record kind (e.g. `action.completed`, `event.published`, * `external.call.failed`) to a colour key in `KIND_COLOR`, so a live record reads * with the same colour as its handler kind on the Map. */ export function recordColorKey(recordKind: string): string { const base = recordKind.split(".")[0] ?? recordKind; switch (base) { case "handler": case "action": case "event": case "query": case "actor": case "projection": case "listener": case "workflow": return base; case "reaction": return "workflow"; // reactions are workflow-family sagas case "external": return "externalCall"; case "source": return "sourceStage"; case "sink": return "sinkStage"; case "hook": return "hook"; default: return base; } } /** The legend order shown under the canvas. */ export const KIND_LEGEND: readonly string[] = [ "handler", "action", "query", "event", "listener", "actor", "workflow", "projection", ]; /** * Lucide icon name per kind — the one glyph that reads a node's role at a * glance on the service map. Names match `lucide-vue-next` exports; the canvas * resolves them dynamically so components and the legend agree. */ export const KIND_ICON: Record = { // sources — what enters the app route: "Globe", inboundWebhook: "Webhook", cron: "Clock", command: "Terminal", // handlers — what runs handler: "Component", action: "Zap", query: "Search", // effects — what running produces event: "Radio", externalCall: "ExternalLink", workflow: "GitBranch", actor: "Box", projection: "LayoutGrid", listener: "Ear", // structural app: "Boxes", plugin: "Plug", capability: "Plug", sinkStage: "ArrowDownToLine", sourceStage: "ArrowUpFromLine", hook: "Anchor", }; const ICON_FALLBACK = "Circle"; /** Lucide icon name for a kind (falls back to a neutral circle). */ export function kindIcon(kind: string | undefined): string { return (kind && KIND_ICON[kind]) || ICON_FALLBACK; }