@caddis/cli 0.4.44 → 0.4.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundles/antigravity-plugin/plugin.json +1 -1
- package/bundles/antigravity-plugin/scripts/claude-oss.ps1 +15 -3
- package/bundles/antigravity-plugin/scripts/claude-oss.sh +12 -1
- package/bundles/antigravity-plugin/skills/_registry.md +1 -0
- package/bundles/antigravity-plugin/skills/flow-studio/README.md +92 -0
- package/bundles/antigravity-plugin/skills/flow-studio/SKILL.md +102 -0
- package/bundles/antigravity-plugin/skills/flow-studio/examples/README.md +19 -0
- package/bundles/antigravity-plugin/skills/flow-studio/examples/agent-journey.json +103 -0
- package/bundles/antigravity-plugin/skills/flow-studio/examples/approval-flow.json +134 -0
- package/bundles/antigravity-plugin/skills/flow-studio/examples/ci-cd-pipeline.json +132 -0
- package/bundles/antigravity-plugin/skills/flow-studio/examples/hostile.json +1783 -0
- package/bundles/antigravity-plugin/skills/flow-studio/examples/incident-timeline.json +109 -0
- package/bundles/antigravity-plugin/skills/flow-studio/examples/lineage.json +1778 -0
- package/bundles/antigravity-plugin/skills/flow-studio/examples/order-desk.json +99 -0
- package/bundles/antigravity-plugin/skills/flow-studio/examples/service-map.json +133 -0
- package/bundles/antigravity-plugin/skills/flow-studio/examples/state-machine.json +106 -0
- package/bundles/antigravity-plugin/skills/flow-studio/examples/ticket-lifecycle.json +107 -0
- package/bundles/antigravity-plugin/skills/flow-studio/model.schema.json +142 -0
- package/bundles/antigravity-plugin/skills/flow-studio/package.json +11 -0
- package/bundles/antigravity-plugin/skills/flow-studio/react/FlowStudioView.test.tsx +82 -0
- package/bundles/antigravity-plugin/skills/flow-studio/react/FlowStudioView.tsx +67 -0
- package/bundles/antigravity-plugin/skills/flow-studio/react/README.md +37 -0
- package/bundles/antigravity-plugin/skills/flow-studio/react/index.ts +3 -0
- package/bundles/antigravity-plugin/skills/flow-studio/react/native/FlowCanvas.tsx +58 -0
- package/bundles/antigravity-plugin/skills/flow-studio/react/native/Inspector.tsx +27 -0
- package/bundles/antigravity-plugin/skills/flow-studio/react/native/index.ts +6 -0
- package/bundles/antigravity-plugin/skills/flow-studio/react/native/native.css +18 -0
- package/bundles/antigravity-plugin/skills/flow-studio/react/native/native.test.tsx +161 -0
- package/bundles/antigravity-plugin/skills/flow-studio/react/native/useFlowGeometry.ts +22 -0
- package/bundles/antigravity-plugin/skills/flow-studio/react/native/useTrace.ts +29 -0
- package/bundles/antigravity-plugin/skills/flow-studio/react/native/useViewport.ts +75 -0
- package/bundles/antigravity-plugin/skills/flow-studio/react/states.tsx +13 -0
- package/bundles/antigravity-plugin/skills/flow-studio/references/design-language.md +169 -0
- package/bundles/antigravity-plugin/skills/flow-studio/references/implement-in-app.md +213 -0
- package/bundles/antigravity-plugin/skills/flow-studio/references/model-schema.md +254 -0
- package/bundles/antigravity-plugin/skills/flow-studio/references/notes-and-spec.md +74 -0
- package/bundles/antigravity-plugin/skills/flow-studio/references/patterns.md +415 -0
- package/bundles/antigravity-plugin/skills/flow-studio/references/ux-rules.md +91 -0
- package/bundles/antigravity-plugin/skills/flow-studio/references/verify.md +124 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/build-engine.mjs +95 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/build.mjs +54 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/check.mjs +680 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/default-notes.json +67 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/auto-entry-guard.mjs +5 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/auto-entry-guard.test.mjs +14 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/auto-entry.test.mjs +31 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/build-engine.test.mjs +54 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/check-args.mjs +18 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/check-args.test.mjs +15 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/check-selftest.test.mjs +135 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/cli.test.mjs +179 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/demo-pack.mjs +23 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/demo-pack.test.mjs +34 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/docs.test.mjs +128 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/embed.mjs +24 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/embed.test.mjs +29 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/library.test.mjs +26 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/react-proof-cleanup.mjs +12 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/react-proof-cleanup.test.mjs +45 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/react-proof-page.mjs +3 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/react-proof-page.test.mjs +9 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/react-proof.mjs +92 -0
- package/bundles/antigravity-plugin/skills/flow-studio/scripts/validate.mjs +42 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/autolayout.test.ts +78 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/autolayout.ts +132 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/chrome.ts +175 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/controls.ts +239 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/ctx.ts +134 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/diff.test.ts +31 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/diff.ts +34 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/engine.ts +193 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/inspector.ts +126 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/interact.ts +253 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/layout.test.ts +144 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/layout.ts +95 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/lib.ts +20 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/metrics.test.ts +57 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/metrics.ts +29 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/model.test.ts +167 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/model.ts +250 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/notes.ts +100 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/page.ts +21 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/playback.test.ts +104 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/playback.ts +106 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/playbackui.ts +193 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/render.ts +203 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/routing.test.ts +33 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/routing.ts +43 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/rules.test.ts +135 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/rules.ts +86 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/selection.test.ts +12 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/selection.ts +4 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/stateCss.test.ts +73 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/stateCss.ts +45 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/styles.css +190 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/trace.test.ts +40 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/trace.ts +40 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/viewport.test.ts +55 -0
- package/bundles/antigravity-plugin/skills/flow-studio/src/viewport.ts +45 -0
- package/bundles/antigravity-plugin/skills/flow-studio/templates/engine.html +2656 -0
- package/bundles/antigravity-plugin/skills/flow-studio/templates/engine.shell.html +23 -0
- package/bundles/antigravity-plugin/skills/flow-studio/templates/flow-studio.css +190 -0
- package/bundles/antigravity-plugin/skills/flow-studio/templates/flow-studio.lib.d.mts +1 -0
- package/bundles/antigravity-plugin/skills/flow-studio/templates/flow-studio.lib.mjs +2491 -0
- package/bundles/antigravity-plugin/skills/flow-studio/templates/shell-body.html +50 -0
- package/bundles/antigravity-plugin/skills/flow-studio/tsconfig.json +17 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/README.md +92 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/SKILL.md +102 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/examples/README.md +19 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/examples/agent-journey.json +103 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/examples/approval-flow.json +134 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/examples/ci-cd-pipeline.json +132 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/examples/hostile.json +1783 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/examples/incident-timeline.json +109 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/examples/lineage.json +1778 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/examples/order-desk.json +99 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/examples/service-map.json +133 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/examples/state-machine.json +106 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/examples/ticket-lifecycle.json +107 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/model.schema.json +142 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/package.json +11 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/react/FlowStudioView.test.tsx +82 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/react/FlowStudioView.tsx +67 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/react/README.md +37 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/react/index.ts +3 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/react/native/FlowCanvas.tsx +58 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/react/native/Inspector.tsx +27 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/react/native/index.ts +6 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/react/native/native.css +18 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/react/native/native.test.tsx +161 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/react/native/useFlowGeometry.ts +22 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/react/native/useTrace.ts +29 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/react/native/useViewport.ts +75 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/react/states.tsx +13 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/references/design-language.md +169 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/references/implement-in-app.md +213 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/references/model-schema.md +254 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/references/notes-and-spec.md +74 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/references/patterns.md +415 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/references/ux-rules.md +91 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/references/verify.md +124 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/build-engine.mjs +95 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/build.mjs +54 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/check.mjs +680 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/default-notes.json +67 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/auto-entry-guard.mjs +5 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/auto-entry-guard.test.mjs +14 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/auto-entry.test.mjs +31 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/build-engine.test.mjs +54 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/check-args.mjs +18 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/check-args.test.mjs +15 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/check-selftest.test.mjs +135 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/cli.test.mjs +179 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/demo-pack.mjs +23 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/demo-pack.test.mjs +34 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/docs.test.mjs +128 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/embed.mjs +24 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/embed.test.mjs +29 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/library.test.mjs +26 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/react-proof-cleanup.mjs +12 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/react-proof-cleanup.test.mjs +45 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/react-proof-page.mjs +3 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/react-proof-page.test.mjs +9 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/react-proof.mjs +92 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/validate.mjs +42 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/autolayout.test.ts +78 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/autolayout.ts +132 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/chrome.ts +175 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/controls.ts +239 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/ctx.ts +134 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/diff.test.ts +31 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/diff.ts +34 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/engine.ts +193 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/inspector.ts +126 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/interact.ts +253 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/layout.test.ts +144 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/layout.ts +95 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/lib.ts +20 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/metrics.test.ts +57 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/metrics.ts +29 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/model.test.ts +167 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/model.ts +250 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/notes.ts +100 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/page.ts +21 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/playback.test.ts +104 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/playback.ts +106 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/playbackui.ts +193 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/render.ts +203 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/routing.test.ts +33 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/routing.ts +43 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/rules.test.ts +135 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/rules.ts +86 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/selection.test.ts +12 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/selection.ts +4 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/stateCss.test.ts +73 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/stateCss.ts +45 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/styles.css +190 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/trace.test.ts +40 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/trace.ts +40 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/viewport.test.ts +55 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/src/viewport.ts +45 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/templates/engine.html +2656 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/templates/engine.shell.html +23 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/templates/flow-studio.css +190 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/templates/flow-studio.lib.d.mts +1 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/templates/flow-studio.lib.mjs +2491 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/templates/shell-body.html +50 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/tsconfig.json +17 -0
- package/bundles/manifest.json +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { autoLayout } from "../../src/autolayout.ts";
|
|
3
|
+
import { computeGeometry } from "../../src/layout.ts";
|
|
4
|
+
import type { Model } from "../../src/model.ts";
|
|
5
|
+
import { defaultView, nodeVisible } from "../../src/rules.ts";
|
|
6
|
+
import type { View } from "../../src/viewport.ts";
|
|
7
|
+
|
|
8
|
+
// Shared default. A fresh Set() per render would change the memo identity below and recompute
|
|
9
|
+
// the geometry on every render. Never mutate it.
|
|
10
|
+
const EMPTY_COLLAPSED = new Set<string>();
|
|
11
|
+
|
|
12
|
+
/** Layout is world-space; the returned view is applied as a transform by the canvas. */
|
|
13
|
+
export function useFlowGeometry(model: Model, view: View, collapsed: Set<string> = EMPTY_COLLAPSED, mode = defaultView(model)) {
|
|
14
|
+
const laidOut = useMemo(() => model.layout === "auto" ? autoLayout(model) : model, [model]);
|
|
15
|
+
const geometry = useMemo(() => computeGeometry(laidOut, {
|
|
16
|
+
visible: id => {
|
|
17
|
+
const node = laidOut.nodes.find(item => item.id === id);
|
|
18
|
+
return !!node && nodeVisible(laidOut, mode, node, collapsed);
|
|
19
|
+
},
|
|
20
|
+
}), [laidOut, collapsed, mode]);
|
|
21
|
+
return { model: laidOut, geometry, view };
|
|
22
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import type { Model } from "../../src/model.ts";
|
|
3
|
+
import { canonOf, defaultView, effectiveLinks, initialCollapsed, nodeVisible, toggleGroupState } from "../../src/rules.ts";
|
|
4
|
+
import { connected, relatives } from "../../src/trace.ts";
|
|
5
|
+
|
|
6
|
+
export function useTrace(model: Model, mode = defaultView(model)) {
|
|
7
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
8
|
+
const [hover, setHover] = useState<string | null>(null);
|
|
9
|
+
const [collapsed, setCollapsed] = useState<Set<string>>(() => initialCollapsed(model));
|
|
10
|
+
const nodes = useMemo(() => new Map(model.nodes.map(node => [node.id, node])), [model]);
|
|
11
|
+
const links = useMemo(() => effectiveLinks(model, mode, collapsed, nodes), [model, mode, collapsed, nodes]);
|
|
12
|
+
const visible = useMemo(() => new Set(model.nodes.filter(node => nodeVisible(model, mode, node, collapsed)).map(node => node.id)), [model, mode, collapsed]);
|
|
13
|
+
const focus = selected ?? hover;
|
|
14
|
+
const canonical = (id: string) => canonOf(nodes, id);
|
|
15
|
+
const upstream = useMemo(() => focus ? relatives(links, canonical, focus, "up") : new Set<string>(), [links, focus, nodes]);
|
|
16
|
+
const downstream = useMemo(() => focus ? relatives(links, canonical, focus, "down") : new Set<string>(), [links, focus, nodes]);
|
|
17
|
+
const connectedNodes = useMemo(() => focus ? connected(links, canonical, focus) : new Set<string>(), [links, focus, nodes]);
|
|
18
|
+
const toggleGroup = (id: string) => {
|
|
19
|
+
const next = toggleGroupState(model, mode, collapsed, id);
|
|
20
|
+
if (next) { setCollapsed(next.collapsed); setSelected(next.selected); }
|
|
21
|
+
};
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const onKey = (event: KeyboardEvent) => { if (event.key === "Escape") setSelected(null); };
|
|
24
|
+
document.addEventListener("keydown", onKey);
|
|
25
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
26
|
+
}, []);
|
|
27
|
+
useEffect(() => { if (selected && !visible.has(selected)) setSelected(null); }, [visible, selected]);
|
|
28
|
+
return { selected, setSelected, hover, setHover, collapsed, toggleGroup, links, visible, connected: connectedNodes, upstream, downstream };
|
|
29
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useCallback, useRef, useState, type KeyboardEventHandler, type PointerEventHandler } from "react";
|
|
2
|
+
import { clampPan, fitRect, zoomAt, type Rect, type Size, type View } from "../../src/viewport.ts";
|
|
3
|
+
|
|
4
|
+
export interface ViewportBind {
|
|
5
|
+
/** Spread onto the viewport element. The hook owns this ref: it measures the element and listens here. */
|
|
6
|
+
ref: (element: HTMLElement | null) => void;
|
|
7
|
+
onPointerDown: PointerEventHandler<HTMLElement>;
|
|
8
|
+
onPointerMove: PointerEventHandler<HTMLElement>;
|
|
9
|
+
onPointerUp: PointerEventHandler<HTMLElement>;
|
|
10
|
+
onPointerCancel: PointerEventHandler<HTMLElement>;
|
|
11
|
+
onKeyDown: KeyboardEventHandler<HTMLElement>;
|
|
12
|
+
tabIndex: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Canvas state and DOM bindings. Coordinates passed to zoomBy are local to the viewport. */
|
|
16
|
+
export function useViewport(bounds: Rect, initial: View = { x: 0, y: 0, k: 1 }) {
|
|
17
|
+
const [view, setView] = useState<View>(initial);
|
|
18
|
+
const viewport = useRef<HTMLElement | null>(null);
|
|
19
|
+
const drag = useRef<{ x: number; y: number; view: View } | null>(null);
|
|
20
|
+
const zoomBy = useCallback((factor: number, x: number, y: number) => {
|
|
21
|
+
setView(previous => zoomAt(previous, factor, x, y));
|
|
22
|
+
}, []);
|
|
23
|
+
/** Zoom by a factor about the center of the attached viewport element. */
|
|
24
|
+
const zoomAboutCenter = useCallback((factor: number) => {
|
|
25
|
+
const element = viewport.current;
|
|
26
|
+
zoomBy(factor, (element?.clientWidth ?? 0) / 2, (element?.clientHeight ?? 0) / 2);
|
|
27
|
+
}, [zoomBy]);
|
|
28
|
+
const fit = useCallback((size?: Size) => {
|
|
29
|
+
const measured = size ?? { w: viewport.current?.clientWidth ?? 0, h: viewport.current?.clientHeight ?? 0 };
|
|
30
|
+
setView(fitRect(bounds, measured, 24, 44));
|
|
31
|
+
}, [bounds]);
|
|
32
|
+
// React's synthetic onWheel listener is passive, so its preventDefault cannot stop the page
|
|
33
|
+
// scrolling. Wheel zoom must be a native listener with { passive: false } on the viewport element.
|
|
34
|
+
const onWheelZoom = useCallback((event: WheelEvent) => {
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
const element = event.currentTarget as HTMLElement | null;
|
|
37
|
+
if (!element) return;
|
|
38
|
+
const rect = element.getBoundingClientRect();
|
|
39
|
+
zoomBy(Math.exp(-event.deltaY * 0.001), event.clientX - rect.left, event.clientY - rect.top);
|
|
40
|
+
}, [zoomBy]);
|
|
41
|
+
// The element ref is also what makes fit() work before the first pointer or wheel interaction.
|
|
42
|
+
const setViewportElement = useCallback((element: HTMLElement | null) => {
|
|
43
|
+
if (viewport.current === element) return;
|
|
44
|
+
viewport.current?.removeEventListener("wheel", onWheelZoom);
|
|
45
|
+
viewport.current = element;
|
|
46
|
+
element?.addEventListener("wheel", onWheelZoom, { passive: false });
|
|
47
|
+
}, [onWheelZoom]);
|
|
48
|
+
const stop: PointerEventHandler<HTMLElement> = event => {
|
|
49
|
+
if (drag.current) event.currentTarget.releasePointerCapture?.(event.pointerId);
|
|
50
|
+
drag.current = null;
|
|
51
|
+
};
|
|
52
|
+
const bind: ViewportBind = {
|
|
53
|
+
tabIndex: 0,
|
|
54
|
+
ref: setViewportElement,
|
|
55
|
+
onPointerDown: event => {
|
|
56
|
+
if (event.button !== 0 || (event.target as Element).closest("button,a,input")) return;
|
|
57
|
+
drag.current = { x: event.clientX, y: event.clientY, view };
|
|
58
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
59
|
+
},
|
|
60
|
+
onPointerMove: event => {
|
|
61
|
+
if (!drag.current) return;
|
|
62
|
+
const { x, y, view: start } = drag.current;
|
|
63
|
+
const size = { w: event.currentTarget.clientWidth, h: event.currentTarget.clientHeight };
|
|
64
|
+
setView(clampPan({ ...start, x: start.x + event.clientX - x, y: start.y + event.clientY - y }, bounds, size));
|
|
65
|
+
},
|
|
66
|
+
onPointerUp: stop,
|
|
67
|
+
onPointerCancel: stop,
|
|
68
|
+
onKeyDown: event => {
|
|
69
|
+
if (event.key === "+" || event.key === "=") { event.preventDefault(); zoomAboutCenter(1.2); }
|
|
70
|
+
if (event.key === "-") { event.preventDefault(); zoomAboutCenter(1 / 1.2); }
|
|
71
|
+
if (event.key === "0") { event.preventDefault(); fit(); }
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
return { view, bind, fit, zoomBy, zoomAboutCenter, setView };
|
|
75
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Model } from "../src/model.ts";
|
|
2
|
+
|
|
3
|
+
export function FlowLoading() {
|
|
4
|
+
return <p role="status">Loading flow…</p>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function FlowEmpty({ model }: { model: Model }) {
|
|
8
|
+
return model.nodes.length === 0 ? <p role="status">No flow items to show.</p> : null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function FlowError({ message, onRetry }: { message: string; onRetry?: () => void }) {
|
|
12
|
+
return <div role="alert"><p>{message}</p>{onRetry && <button type="button" onClick={onRetry}>Retry</button>}</div>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Design language
|
|
2
|
+
|
|
3
|
+
Grounded in `src/styles.css`. This document defines the visual design system, CSS custom properties, typography, spatial scales, state encodings, motion catalogue, and dark theme strategy for flow-studio.
|
|
4
|
+
|
|
5
|
+
## 1. Design tokens
|
|
6
|
+
|
|
7
|
+
Tokens are defined on `:root` and adapted for dark mode via `@media (prefers-color-scheme: dark)` and `:root[data-theme="dark"]`.
|
|
8
|
+
|
|
9
|
+
| Token | Light value | Dark value | Description / Purpose |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `--bg` | `#f6f8fa` | `#0d1117` | Canvas and page background |
|
|
12
|
+
| `--panel` | `#ffffff` | `#161b22` | Cards, drawer, top header, surfaces |
|
|
13
|
+
| `--panel2` | `#f6f8fa` | `#0d1117` | Secondary surface, subtle contrast, table header |
|
|
14
|
+
| `--fg` | `#1f2328` | `#e6edf3` | Primary text and foreground elements |
|
|
15
|
+
| `--muted` | `#59636e` | `#9198a1` | Secondary text, captions, inactive icons |
|
|
16
|
+
| `--edge` | `#d0d7de` | `#30363d` | Element borders, tool dividers, card outlines |
|
|
17
|
+
| `--rule` | `#d8dee4` | `#2b323a` | Dividers, grid separators, table borders |
|
|
18
|
+
| `--accent` | `#0969da` | `#4493f8` | Primary brand accent, selected state, hot trails |
|
|
19
|
+
| `--accent-solid` | `#0969da` | `#1f6feb` | Solid button background, active tab pill |
|
|
20
|
+
| `--ok` | `#1a7f37` | `#3fb950` | Healthy, passed, on-time indicators |
|
|
21
|
+
| `--warn` | `#9a6700` | `#d29922` | Warning, degraded, late indicators |
|
|
22
|
+
| `--crit` | `#cf222e` | `#f85149` | Critical failure, breach, error indicators |
|
|
23
|
+
| `--up` | `#0a7ea4` | `#6fcbe6` | Upstream relation count and badge color |
|
|
24
|
+
| `--down` | `#8250df` | `#d0a0ee` | Downstream relation count and notes marker |
|
|
25
|
+
| `--glow` | `rgba(9,105,218,.22)` | `rgba(68,147,248,.35)` | Accent halo for selection, hover, and canvas focus |
|
|
26
|
+
| `--shadow` | `0 1px 0 rgba(31,35,40,.04), 0 6px 18px rgba(31,35,40,.08)` | `0 1px 0 rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.45)` | Elevated card and modal elevation shadow |
|
|
27
|
+
| `--grid` | `rgba(31,35,40,.035)` | `rgba(255,255,255,.04)` | Viewport grid pattern dots and lines |
|
|
28
|
+
| `--ok-bg` | `rgba(26,127,55,.10)` | `rgba(63,185,80,.12)` | Subtle tinted background for healthy states |
|
|
29
|
+
| `--warn-bg` | `rgba(154,103,0,.10)` | `rgba(210,153,34,.14)` | Subtle tinted background for warning states |
|
|
30
|
+
| `--crit-bg` | `rgba(207,34,46,.08)` | `rgba(248,81,73,.12)` | Subtle tinted background for critical states |
|
|
31
|
+
| `--info-bg` | `rgba(9,105,218,.10)` | `rgba(68,147,248,.14)` | Subtle tinted background for informational badges |
|
|
32
|
+
| `--mono` | `"IBM Plex Mono", ui-monospace, ...` | Same | Monospace font family for metrics, counters, and code |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 2. Typography scale
|
|
37
|
+
|
|
38
|
+
Flow-studio uses system sans-serif (`Inter, "Segoe UI", system-ui, -apple-system, sans-serif`) for readable text and `--mono` for tabular numerals and technical metadata.
|
|
39
|
+
|
|
40
|
+
| Role | Size | Weight | Line height | Letter spacing | Font family |
|
|
41
|
+
|---|---|---|---|---|---|
|
|
42
|
+
| Page title (`h1`) | 20px | 700 | 1.2 | normal | Sans-serif |
|
|
43
|
+
| KPI / Tile numbers (`.tn`, `.cn`) | 20px | 600 | 1.1 | tabular-nums | Sans-serif |
|
|
44
|
+
| Inspector node title (`.nname`) | 15px | 600 | 1.25 | normal | Sans-serif |
|
|
45
|
+
| Drawer heading (`#drawer h2`) | 15px | 700 | 1.3 | normal | Sans-serif |
|
|
46
|
+
| Body / Default | 14px | 400 | 1.45 | normal | Sans-serif |
|
|
47
|
+
| Card title (`.node .t`) | 13px | 600 | 1.25 | normal | Sans-serif |
|
|
48
|
+
| Lane label (`.bandlabel b`) | 13px | 700 | 1.2 | normal | Sans-serif |
|
|
49
|
+
| Card subtitle / state (`.node .s`) | 12px | 400 | 1.3 | normal | Sans-serif |
|
|
50
|
+
| Help text / Footer | 12px | 400 | 1.4 | normal | Sans-serif |
|
|
51
|
+
| Column header (`#cols .hd`) | 10.5px | 700 | 1.2 | 0.08em (caps) | Sans-serif |
|
|
52
|
+
| Phase pill (`#cols .ph`) | 10px | 700 | 1.2 | 0.14em (caps) | Sans-serif |
|
|
53
|
+
| Evidence / Badges (`.ev`, `.pill`) | 9.5px to 10.5px | 700 | 1.2 | 0.03em (caps) | Sans-serif |
|
|
54
|
+
| Metric badges & code (`.mono`, `code`) | 11px to 11.5px | 500 | 1.2 | normal | Monospace |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 3. Spacing scale
|
|
59
|
+
|
|
60
|
+
Layout elements follow a strict 4px grid rhythm:
|
|
61
|
+
|
|
62
|
+
| Scale | Value | Typical usage |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| 2xs | 2px to 4px | Inline icon gaps, badge vertical padding, bottom border highlights |
|
|
65
|
+
| xs | 4px to 7px | Button vertical padding (`4px 11px`), card vertical padding (`5px 10px 7px`), playbar gaps (`7px`) |
|
|
66
|
+
| sm | 8px to 10px | Card horizontal padding (`10px`), toolbar item gaps, summary grid gaps |
|
|
67
|
+
| md | 12px to 16px | Page padding (`12px 14px 20px`), header gaps, inspector body padding |
|
|
68
|
+
| lg | 20px to 28px | Section margins, top header padding, grid line pitch (28px canvas grid) |
|
|
69
|
+
| xl | 32px to 44px | Column header heights (44px), tool button dimensions (34px x 32px) |
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 4. Border radius and shadow tiers
|
|
74
|
+
|
|
75
|
+
### Border radius
|
|
76
|
+
- **Sharp (0px):** Segmented buttons inside groups, summary tiles inside strips, tools.
|
|
77
|
+
- **Small (6px):** Buttons, search inputs, dropdowns, segmented group wrappers.
|
|
78
|
+
- **Medium (8px):** Node cards (`.node`).
|
|
79
|
+
- **Pill (9px to 10px):** Edge labels (`.elabel`), lane bands (`.band` radius 10px).
|
|
80
|
+
- **Large (12px):** Workspace container (`.workspace`).
|
|
81
|
+
- **Circular (50%):** Markers, status dots, tokens, and avatar chips.
|
|
82
|
+
|
|
83
|
+
### Shadow tiers
|
|
84
|
+
- **Subtle border shadow (`var(--shadow)`):** `0 1px 0 rgba(31,35,40,.04), 0 6px 18px rgba(31,35,40,.08)` (Light) / `0 1px 0 rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.45)` (Dark).
|
|
85
|
+
- **Interactive hover:** `box-shadow: 0 0 0 3px var(--glow), var(--shadow)`.
|
|
86
|
+
- **Slide-in drawer elevation:** `box-shadow: -14px 0 34px rgba(0,0,0,.16), -1px 0 0 var(--glow)`.
|
|
87
|
+
- **Marker pulse halo:** `box-shadow: 0 0 0 3px rgba(130,80,223,.3)`.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 5. Glow tiers
|
|
92
|
+
|
|
93
|
+
Glows communicate focus, connection, and selection:
|
|
94
|
+
1. **Normal:** `box-shadow: 0 0 14px -4px color-mix(in srgb, <tone> 55%, transparent)` on healthy or highlighted cards.
|
|
95
|
+
2. **Hover:** `box-shadow: 0 0 0 3px var(--glow)` with border color change to `--accent`.
|
|
96
|
+
3. **Selected / Lit:** `box-shadow: 0 0 0 2px var(--glow), 0 0 18px var(--glow)` plus animated ripple or trail.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 6. State encoding table
|
|
101
|
+
|
|
102
|
+
The engine generates CSS from 12 canonical domain states defined in `src/stateCss.ts`:
|
|
103
|
+
|
|
104
|
+
| State ID | Tone | Border Style | Edge Style | Motion | Bucket | Semantic Meaning |
|
|
105
|
+
|---|---|---|---|---|---|---|
|
|
106
|
+
| `ok` | `ok` | `solid` | `flow` | `flow` | `good` | Fully healthy, active, operational |
|
|
107
|
+
| `healthy` | `ok` | `solid` | `flow` | `flow` | `good` | System running within normal operating SLAs |
|
|
108
|
+
| `planned` | `muted` | `dashed` | `dash` | `flow-slow` | `neutral` | Future work or planned integration |
|
|
109
|
+
| `in-progress` | `accent` | `solid` | `flow` | `flow` | `neutral` | Actively processing or undergoing update |
|
|
110
|
+
| `waiting` | `warn` | `dashed` | `dash-short` | `breathe` | `neutral` | Blocked on upstream dependency or approval |
|
|
111
|
+
| `late` | `warn` | `solid` | `flow` | `flow` | `neutral` | Running behind schedule but executing |
|
|
112
|
+
| `degraded` | `warn` | `dashed` | `dash` | `breathe` | `neutral` | Performance or capacity reduced |
|
|
113
|
+
| `failed` | `crit` | `solid` | `flow` | `flow` | `gap` | Execution halted due to fatal defect |
|
|
114
|
+
| `breached` | `crit` | `solid` | `flow` | `flow` | `gap` | SLA threshold crossed |
|
|
115
|
+
| `blocked` | `crit` | `dashed` | `dot` | `none` | `gap` | Cannot proceed due to hard impediment |
|
|
116
|
+
| `unknown` | `warn` | `dotted` | `dot` | `breathe` | `gap` | No telemetry or unverified source |
|
|
117
|
+
| `skipped` | `muted` | `dotted` | `dot` | `none` | `neutral` | Deliberately omitted execution step |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 7. Motion catalogue
|
|
122
|
+
|
|
123
|
+
`src/styles.css` defines 12 `@keyframes`; the count treats the three flow dash variants (`flow`, `flowd`, `flowc`) separately. Motion is never decorative; every animation carries status information.
|
|
124
|
+
|
|
125
|
+
| Animation name | Duration | Easing | Meaning / State Carried |
|
|
126
|
+
|---|---|---|---|
|
|
127
|
+
| `beat` | 2.6s | ease (default), infinite | Live indicator pulse on the header brand dot. Indicates the engine session is active. |
|
|
128
|
+
| `fadein` | 0.6s / 1s (0.5s delay) | ease (default) | Lane bands and the edge layer fade in during initial canvas load. |
|
|
129
|
+
| `rise` | 0.5s / 0.55s / 1.2s | ease (default) / ease-out | Cards, tiles and summary cells enter with subtle upward movement (`translateY(8px)` to `none`); `1.2s` marks a node added by a live update. |
|
|
130
|
+
| `flow` | 3s or 7s | linear infinite | Dash-offset marching on links whose state has `edge: "flow"` (`3s` with `motion: "flow"`, `7s` with `motion: "flow-slow"`). Indicates active data transit. |
|
|
131
|
+
| `flowd` | 3s or 7s | linear infinite | Same marching for `edge: "dash"` links. |
|
|
132
|
+
| `flowc` | 3s or 7s | linear infinite | Same marching for `edge: "dash-short"` links. |
|
|
133
|
+
| `flowhot` | 0.9s | linear infinite | Faster marching on highlighted (hot) edges: search trails, relations, playback. |
|
|
134
|
+
| `selpulse` | 2.4s / 1.8s | ease-out infinite | Expanding glowing ring around a selected card (`2.4s`) or the active playback node (`1.8s`). |
|
|
135
|
+
| `breathe` | 3.2s | ease-in-out infinite | Soft pulsing box-shadow on warning or waiting nodes (`color-mix` with `--warn` or the state tone). |
|
|
136
|
+
| `failflash` | 0.9s | ease-out 1 | Red box-shadow burst indicating SLA breach or what-if blast-radius failure. |
|
|
137
|
+
| `newring` | 1.2s / 1.8s | ease-out 1 | Accent ring on a node changed by a live update (`1.2s`) or newly appeared in a view (`1.8s`). |
|
|
138
|
+
| `ring` | - | - | Defined but referenced by no rule today; reserved. |
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 8. Reduced motion fallback rules
|
|
143
|
+
|
|
144
|
+
Accessibility rules respect user OS preferences:
|
|
145
|
+
|
|
146
|
+
```css
|
|
147
|
+
@media (prefers-reduced-motion: reduce) {
|
|
148
|
+
*, *::before, *::after {
|
|
149
|
+
animation: none !important;
|
|
150
|
+
transition: none !important;
|
|
151
|
+
}
|
|
152
|
+
path.edge.hot {
|
|
153
|
+
stroke-dasharray: none;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
- When reduced motion is active, token animations are immediate without transitions.
|
|
159
|
+
- Selection rings remain solid outlines without pulsating box-shadows.
|
|
160
|
+
- Links show solid or static dashed strokes without moving dashes.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 9. Dark theme strategy
|
|
165
|
+
|
|
166
|
+
1. The page listens to system preference: `@media (prefers-color-scheme: dark)` applies dark tokens to `:root:not([data-theme="light"])`.
|
|
167
|
+
2. Manual override is supported via the `data-theme="dark"` attribute set on `<html>` or `:root`.
|
|
168
|
+
3. The theme toggle button switches between light and dark without page reload.
|
|
169
|
+
4. Colors do not invert naively; backgrounds use `#0d1117`, card panels use `#161b22`, borders use `#30363d`, and semantic colors (ok, warn, crit) use high-contrast luminance tuned for dark backgrounds.
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Implement a flow in an application
|
|
2
|
+
|
|
3
|
+
Use an approved flow model as input. Validate it against `model.schema.json` before connecting live data. The demo page shows the intended interaction. Keep this guide with the skill when copying files into an application.
|
|
4
|
+
|
|
5
|
+
In the commands, `<skill>` is the path to the flow-studio skill folder in your checkout. Your app has no copy of the skill's scripts, so commands that run them always name the `<skill>` path.
|
|
6
|
+
|
|
7
|
+
## 1. Pick the route
|
|
8
|
+
|
|
9
|
+
| Requirement | Route | Implementation |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| The app may use the flow engine's own styling and controls | Fast wrapper / library embed | `templates/flow-studio.lib.mjs` provides the full self-contained engine. |
|
|
12
|
+
| The app must use its own design tokens, buttons, and drawer | Native components & hooks | Pure modules (`src/`) supply geometry, layout, and trace logic to app components. |
|
|
13
|
+
|
|
14
|
+
The files are copyable source, not a published npm package. Both routes need a caller that supplies a valid `Model`; neither fetches app data for you.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 2. Lock the model and data contract
|
|
19
|
+
|
|
20
|
+
1. Save the approved model as JSON. Run `node --experimental-strip-types <skill>/scripts/validate.mjs <model.json>`. Fix any reported errors.
|
|
21
|
+
2. Define a typed state for the app's data source: `loading`, `ready` with a `Model`, `empty`, or `error` with a user-facing message and retry action.
|
|
22
|
+
3. Map node IDs from immutable business keys (e.g. `order-service-uuid`). Map link IDs from immutable relation keys. Never use array indices or volatile titles.
|
|
23
|
+
4. Keep absent values absent. Do not turn a missing measurement into `0`. Use state words and timestamps (`asOf`) to distinguish measured data from declarations.
|
|
24
|
+
5. On refresh error, keep the last valid model visible and show a notification banner. On a valid refresh, pass the new model with stable IDs so `engine.update(model)` preserves pan, zoom, and active selection.
|
|
25
|
+
6. Render all model strings as plain text. Never use raw `innerHTML` for titles, subtitles, or labels.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 3. Test-first workflow in the target app
|
|
30
|
+
|
|
31
|
+
1. **Verify skill tests:** Run the test suite:
|
|
32
|
+
```bash
|
|
33
|
+
node --experimental-strip-types --no-warnings --test "src/*.test.ts" "scripts/lib/*.test.mjs"
|
|
34
|
+
```
|
|
35
|
+
2. **Author mapping test:** Write an app test verifying the data-to-model mapper produces stable IDs, preserves absent fields, and handles loading/error states. See the test fail before implementing the mapper.
|
|
36
|
+
3. **Author component test:** Write a test verifying component mount, node selection callback, and update behavior.
|
|
37
|
+
4. **Implement iteratively:** Implement mapping and component wiring, re-running the focused test after each step until green.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 4. Integration guides by framework
|
|
42
|
+
|
|
43
|
+
### A. React
|
|
44
|
+
|
|
45
|
+
Copy `templates/flow-studio.lib.mjs`, `templates/flow-studio.lib.d.mts`, `templates/flow-studio.css`, `react/`, and `src/` into the app, keeping the relative layout.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import React, { useEffect, useRef } from "react";
|
|
49
|
+
import { FlowStudioView, FlowLoading, FlowError } from "./react/index";
|
|
50
|
+
import type { Model } from "./src/model";
|
|
51
|
+
import "./templates/flow-studio.css";
|
|
52
|
+
|
|
53
|
+
export function FlowContainer({ model, status, error, onRetry }: { model?: Model; status: string; error?: string; onRetry: () => void }) {
|
|
54
|
+
const [selectedId, setSelectedId] = React.useState<string | null>(null);
|
|
55
|
+
|
|
56
|
+
if (status === "loading") return <FlowLoading />;
|
|
57
|
+
if (status === "error") return <FlowError message={error!} onRetry={onRetry} />;
|
|
58
|
+
if (!model || model.nodes.length === 0) return <div>No data available</div>;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div style={{ height: "80vh", width: "100%" }}>
|
|
62
|
+
<FlowStudioView
|
|
63
|
+
model={model}
|
|
64
|
+
onSelect={(id) => setSelectedId(id)}
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### B. Vue (Vue 3 / Vite)
|
|
72
|
+
|
|
73
|
+
Copy `templates/flow-studio.lib.mjs`, `templates/flow-studio.lib.d.mts`, and `templates/flow-studio.css` to your application assets directory.
|
|
74
|
+
|
|
75
|
+
```vue
|
|
76
|
+
<script setup lang="ts">
|
|
77
|
+
import { ref, onMounted, onBeforeUnmount, watch } from "vue";
|
|
78
|
+
import { mount } from "../assets/flow-studio.lib.mjs";
|
|
79
|
+
import "../assets/flow-studio.css";
|
|
80
|
+
|
|
81
|
+
const props = defineProps<{ model: any }>();
|
|
82
|
+
const emit = defineEmits(["select"]);
|
|
83
|
+
const containerRef = ref<HTMLDivElement | null>(null);
|
|
84
|
+
let engineInstance: any = null;
|
|
85
|
+
|
|
86
|
+
onMounted(() => {
|
|
87
|
+
if (containerRef.value && props.model) {
|
|
88
|
+
engineInstance = mount(containerRef.value, props.model, {
|
|
89
|
+
onSelect: (id: string | null) => emit("select", id)
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
watch(() => props.model, (newModel) => {
|
|
95
|
+
if (engineInstance && newModel) {
|
|
96
|
+
engineInstance.update(newModel);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
onBeforeUnmount(() => {
|
|
101
|
+
if (engineInstance) {
|
|
102
|
+
engineInstance.destroy();
|
|
103
|
+
engineInstance = null;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
</script>
|
|
107
|
+
|
|
108
|
+
<template>
|
|
109
|
+
<div ref="containerRef" class="flow-container"></div>
|
|
110
|
+
</template>
|
|
111
|
+
|
|
112
|
+
<style scoped>
|
|
113
|
+
.flow-container { width: 100%; height: 80vh; position: relative; }
|
|
114
|
+
</style>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### C. Svelte (Svelte 4 / 5)
|
|
118
|
+
|
|
119
|
+
Copy the same library files as for Vue. The import is aliased because Svelte 5 exports its own `mount`.
|
|
120
|
+
|
|
121
|
+
```svelte
|
|
122
|
+
<script lang="ts">
|
|
123
|
+
import { onMount, onDestroy } from "svelte";
|
|
124
|
+
import { mount as mountFlow } from "./flow-studio.lib.mjs";
|
|
125
|
+
import "./flow-studio.css";
|
|
126
|
+
|
|
127
|
+
export let model: any;
|
|
128
|
+
export let onSelect: (id: string | null) => void = () => {};
|
|
129
|
+
|
|
130
|
+
let container: HTMLDivElement;
|
|
131
|
+
let engine: any;
|
|
132
|
+
|
|
133
|
+
onMount(() => {
|
|
134
|
+
engine = mountFlow(container, model, { onSelect });
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
$: if (engine && model) {
|
|
138
|
+
engine.update(model);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
onDestroy(() => {
|
|
142
|
+
if (engine) engine.destroy();
|
|
143
|
+
});
|
|
144
|
+
</script>
|
|
145
|
+
|
|
146
|
+
<div bind:this={container} class="flow-container"></div>
|
|
147
|
+
|
|
148
|
+
<style>
|
|
149
|
+
.flow-container { width: 100%; height: 80vh; position: relative; }
|
|
150
|
+
</style>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### D. Vanilla HTML & JavaScript
|
|
154
|
+
|
|
155
|
+
```html
|
|
156
|
+
<!DOCTYPE html>
|
|
157
|
+
<html lang="en">
|
|
158
|
+
<head>
|
|
159
|
+
<meta charset="utf-8">
|
|
160
|
+
<link rel="stylesheet" href="./templates/flow-studio.css">
|
|
161
|
+
<style>
|
|
162
|
+
#flowHost { width: 100vw; height: 100vh; }
|
|
163
|
+
</style>
|
|
164
|
+
</head>
|
|
165
|
+
<body>
|
|
166
|
+
<div id="flowHost"></div>
|
|
167
|
+
<script type="module">
|
|
168
|
+
import { mount } from "./templates/flow-studio.lib.mjs";
|
|
169
|
+
|
|
170
|
+
const response = await fetch("/api/lineage-model");
|
|
171
|
+
const model = await response.json();
|
|
172
|
+
|
|
173
|
+
const engine = mount(document.getElementById("flowHost"), model, {
|
|
174
|
+
onSelect: (selectedId) => {
|
|
175
|
+
console.log("Selected node:", selectedId);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Expose for external live event updates
|
|
180
|
+
window.updateFlow = (newModel) => engine.update(newModel);
|
|
181
|
+
</script>
|
|
182
|
+
</body>
|
|
183
|
+
</html>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 5. State synchronization and host events
|
|
189
|
+
|
|
190
|
+
`mount()` returns the engine API, and every mount also publishes the same API object on `window.FlowStudio`. Hold the value that `mount()` returns in a variable named `engine` in the examples below:
|
|
191
|
+
|
|
192
|
+
1. **Selection listening:** pass `onSelect(nodeId: string | null)` as a mount option to receive updates when a user selects a card or chip, or clears the selection.
|
|
193
|
+
2. **Programmatic selection:** call `engine.select(nodeId)` (or `window.FlowStudio.select(nodeId)`) to focus and inspect a node from outside the canvas, for example from an app table row.
|
|
194
|
+
3. **Reading active state:** call `engine.selected()`, `engine.view()`, or `engine.mode()` to read the current canvas state.
|
|
195
|
+
4. **Live topology updates:** call `engine.update(newModel)` to refresh node metrics, statuses, or structure without losing the user's pan and zoom. A model that fails validation is rejected, the last good graph stays, and the engine shows a banner.
|
|
196
|
+
|
|
197
|
+
In the React wrapper route the engine instance is private to `FlowStudioView`: use the `onSelect` prop for selection changes and drive new data through the `model` prop (the wrapper calls `update` itself). Use `window.FlowStudio` for a direct call only while exactly one view is mounted.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 6. Verifying the app in browser check harness
|
|
202
|
+
|
|
203
|
+
Run the skill's check harness against your local dev server:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
node <skill>/scripts/check.mjs http://127.0.0.1:5173/flow --out shots --channel msedge --skip hostile
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The harness is not copied into your app, so the command always names the `<skill>` path. Run it from a folder whose `node_modules` resolves `playwright`.
|
|
210
|
+
|
|
211
|
+
- When tests fail, inspect the screenshot in `shots/` and read the error detail.
|
|
212
|
+
- Ensure the app container provides at least 540px height for desktop viewports.
|
|
213
|
+
- `--skip` takes a comma-separated list of groups (`notes`, `playback`, `update`, `hostile`). Skip a group only when the app intentionally omits that feature. Record every skipped group in your verification notes. Never treat a skipped check as a pass.
|