stackcanvas 0.2.0 → 0.2.1
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/README.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# stackcanvas
|
|
2
2
|
|
|
3
|
-
[Telemetry: opt-in, anonymous, no infra data — see TELEMETRY.md](TELEMETRY.md)
|
|
4
|
-
|
|
5
3
|
Live infrastructure canvas for [Claude Code](https://claude.com/claude-code).
|
|
6
4
|
The agent writes and plans your Terraform — stackcanvas shows it as a living
|
|
7
5
|
diagram. Drag new resources onto the canvas; the agent turns them into
|
|
@@ -16,7 +14,7 @@ localhost, reading your local state and plan.
|
|
|
16
14
|
2. The graph re-renders live whenever `*.tfstate` or `.stackcanvas/plan.json`
|
|
17
15
|
change — you watch the agent work.
|
|
18
16
|
3. You drag resources from the palette (or right-click existing ones to
|
|
19
|
-
request changes / removal) and hit **
|
|
17
|
+
request changes / removal) and hit **Send to agent**. Connections between two
|
|
20
18
|
not-yet-created (draft) resources aren't included in the intent yet —
|
|
21
19
|
connect drafts to existing resources, or describe the relation in the
|
|
22
20
|
draft's wishes field.
|
|
@@ -30,10 +28,6 @@ localhost, reading your local state and plan.
|
|
|
30
28
|
claude plugin marketplace add pshenok/stackcanvas
|
|
31
29
|
claude plugin install stackcanvas@stackcanvas
|
|
32
30
|
|
|
33
|
-
or without the plugin system:
|
|
34
|
-
|
|
35
|
-
claude mcp add stackcanvas -- npx -y stackcanvas
|
|
36
|
-
|
|
37
31
|
Then, inside a repo with Terraform:
|
|
38
32
|
|
|
39
33
|
/stackcanvas
|
|
@@ -41,6 +35,13 @@ Then, inside a repo with Terraform:
|
|
|
41
35
|
This is the verified path — the CI `check-plugin` job validates the plugin
|
|
42
36
|
and marketplace manifests on every push.
|
|
43
37
|
|
|
38
|
+
Or without the plugin system:
|
|
39
|
+
|
|
40
|
+
claude mcp add stackcanvas -- npx -y stackcanvas
|
|
41
|
+
|
|
42
|
+
Then, inside a repo with Terraform, just ask: *open the stackcanvas canvas
|
|
43
|
+
for this repo*.
|
|
44
|
+
|
|
44
45
|
## Other MCP clients
|
|
45
46
|
|
|
46
47
|
stackcanvas is a standard [MCP](https://modelcontextprotocol.io) server, so
|
|
@@ -98,7 +99,7 @@ to pin it — both take precedence over auto-detection.
|
|
|
98
99
|
| `open_canvas` | Start the canvas for a Terraform root, open the browser |
|
|
99
100
|
| `load_plan` | Register a plan (JSON or binary) for diff highlighting |
|
|
100
101
|
| `get_graph_summary` | Text summary of the graph for the agent |
|
|
101
|
-
| `await_canvas_intent` | Block until the user clicks
|
|
102
|
+
| `await_canvas_intent` | Block until the user clicks Send to agent; returns their edits |
|
|
102
103
|
|
|
103
104
|
## Demo
|
|
104
105
|
|
|
@@ -106,13 +107,14 @@ to pin it — both take precedence over auto-detection.
|
|
|
106
107
|
|
|
107
108
|
## Telemetry
|
|
108
109
|
|
|
109
|
-
stackcanvas can send a handful of anonymous
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
110
|
+
stackcanvas can send a handful of anonymous (pseudonymous install id — see
|
|
111
|
+
TELEMETRY.md) usage counters (installs, canvases opened, intents sent;
|
|
112
|
+
scan/drift counters reserved — five ever, see TELEMETRY.md) — **opt-in
|
|
113
|
+
only**, nothing is sent until you click **Allow** on the one-time canvas
|
|
114
|
+
banner, and `DO_NOT_TRACK=1` / `STACKCANVAS_TELEMETRY=0` always turn it off
|
|
115
|
+
regardless of that decision. No resource names, infrastructure data, or file
|
|
116
|
+
paths ever leave your machine. Full payload, consent model, and how to
|
|
117
|
+
verify it yourself: [TELEMETRY.md](TELEMETRY.md).
|
|
116
118
|
|
|
117
119
|
## Development
|
|
118
120
|
|
package/dist/cli.js
CHANGED
|
@@ -926,7 +926,7 @@ function openBrowser(url) {
|
|
|
926
926
|
}
|
|
927
927
|
|
|
928
928
|
// src/version.ts
|
|
929
|
-
var VERSION = "0.2.
|
|
929
|
+
var VERSION = "0.2.1";
|
|
930
930
|
|
|
931
931
|
// src/server.ts
|
|
932
932
|
function looksLikeTerraformRoot(dir) {
|
|
@@ -966,7 +966,11 @@ function createMcpServer(deps = {}) {
|
|
|
966
966
|
canvas.setAgentStatus("idle");
|
|
967
967
|
open(url);
|
|
968
968
|
}
|
|
969
|
-
|
|
969
|
+
const stale = canvas.getStale();
|
|
970
|
+
const warning = stale === null ? "" : `
|
|
971
|
+
|
|
972
|
+
WARNING: the graph may be incomplete \u2014 ${stale}. Tell the user.`;
|
|
973
|
+
return ok(`Canvas running at ${url}. The graph live-updates as tfstate changes. Run \`terraform plan -out=tfplan && terraform show -json tfplan > .stackcanvas/plan.json\` (or \`tofu plan \u2026\` with OpenTofu) to show the plan diff, then call await_canvas_intent to receive user edits.` + warning);
|
|
970
974
|
});
|
|
971
975
|
mcp.registerTool("load_plan", {
|
|
972
976
|
description: "Register a Terraform plan for diff highlighting on the canvas. Accepts a JSON plan (terraform show -json output) or a binary plan file.",
|
|
@@ -988,10 +992,14 @@ function createMcpServer(deps = {}) {
|
|
|
988
992
|
inputSchema: {}
|
|
989
993
|
}, async () => {
|
|
990
994
|
if (!canvas) return fail("No canvas open. Call open_canvas first.");
|
|
991
|
-
|
|
995
|
+
const stale = canvas.getStale();
|
|
996
|
+
const warning = stale === null ? "" : `
|
|
997
|
+
|
|
998
|
+
WARNING: the graph may be incomplete \u2014 ${stale}. Tell the user.`;
|
|
999
|
+
return ok(summarizeGraph(canvas.getGraph()) + warning);
|
|
992
1000
|
});
|
|
993
1001
|
mcp.registerTool("await_canvas_intent", {
|
|
994
|
-
description: "Block until the user clicks
|
|
1002
|
+
description: "Block until the user clicks Send-to-agent on the canvas, then return their requested changes as intent JSON: {intent: {add, modify, remove} | null}. null = timeout, call again in a loop to keep waiting. After receiving an intent, write the Terraform code for it. After implementing an intent, run terraform plan (write the JSON to .stackcanvas/plan.json) and call this tool again to keep the loop alive. Never run terraform apply unless the human explicitly asks.",
|
|
995
1003
|
inputSchema: {
|
|
996
1004
|
timeoutSeconds: z2.number().positive().max(3600).default(45).describe("How long to wait before returning {intent: null}. Default 45s: MCP clients commonly abort requests after 60s, so keep this under your client's request timeout and call the tool in a loop instead of passing large values.")
|
|
997
1005
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stackcanvas",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "dist/cli.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"stackcanvas": "dist/cli.js"
|
|
8
8
|
},
|
|
@@ -28,5 +28,24 @@
|
|
|
28
28
|
"@stackcanvas/server": "workspace:*",
|
|
29
29
|
"@types/node": "^22.0.0",
|
|
30
30
|
"tsup": "^8.0.0"
|
|
31
|
-
}
|
|
31
|
+
},
|
|
32
|
+
"description": "Live infrastructure canvas for Claude Code: see what your AI agent is about to do to your Terraform \u2014 and approve it \u2014 before it happens. Local-first, credentials never leave your machine.",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/pshenok/stackcanvas.git"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://stackcanvas.dev",
|
|
39
|
+
"bugs": "https://github.com/pshenok/stackcanvas/issues",
|
|
40
|
+
"author": "Kostyantyn Pshenychnyy",
|
|
41
|
+
"keywords": [
|
|
42
|
+
"terraform",
|
|
43
|
+
"opentofu",
|
|
44
|
+
"mcp",
|
|
45
|
+
"claude-code",
|
|
46
|
+
"infrastructure-as-code",
|
|
47
|
+
"devtools",
|
|
48
|
+
"visualization",
|
|
49
|
+
"ai-agent"
|
|
50
|
+
]
|
|
32
51
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #555;--xy-background-pattern-lines-color-default: #333;--xy-background-pattern-cross-color-default: #333;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1;touch-action:none}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))}*{box-sizing:border-box}body{margin:0;font-family:ui-sans-serif,system-ui,sans-serif}.app{width:100vw;height:100vh;display:flex;flex-direction:column}.topbar{display:flex;gap:12px;align-items:center;padding:8px 14px;border-bottom:1px solid #e2e8f0;background:#fff;z-index:10}.brand{font-weight:700}.agent-badge{font-size:12px;padding:2px 8px;border-radius:999px;background:#f1f5f9}.agent-writing{background:#fef9c3}.agent-planning{background:#dbeafe}.stale-banner{color:#b91c1c;font-size:12px}.notice{font-size:12px;color:#334155}.resource-node{display:flex;align-items:center;gap:8px;padding:8px 10px;border:1.5px solid #94a3b8;border-radius:8px;background:#fff;width:190px}.resource-node .labels{display:flex;flex-direction:column;overflow:hidden}.resource-node .name{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.resource-node .type{font-size:10px;color:#64748b}.resource-node.status-create{border-color:#22c55e;box-shadow:0 0 0 2px #22c55e33}.resource-node.status-update{border-color:#eab308;box-shadow:0 0 0 2px #eab30833}.resource-node.status-delete{border-color:#ef4444;box-shadow:0 0 0 2px #ef444433}.resource-node.status-replace{border-color:#a855f7;box-shadow:0 0 0 2px #a855f733}.draft{border-style:dashed;opacity:.85}.removed{border-style:dashed;border-color:#ef4444;opacity:.6;text-decoration:line-through}.modified:after{content:"";position:absolute}.react-flow__node-group{border-radius:10px;border:1px dashed #cbd5e1;background:#f8fafc80}.canvas-row{flex:1;display:flex;min-height:0}.canvas-row>.react-flow{flex:1}.inspector{width:320px;border-left:1px solid #e2e8f0;padding:12px;overflow:auto;background:#fff;font-size:13px}.inspector-head{display:flex;justify-content:space-between;align-items:center}.inspector table{width:100%;border-collapse:collapse}.inspector td{border-top:1px solid #f1f5f9;padding:4px 6px;vertical-align:top;word-break:break-all}.inspector .before code{color:#b91c1c;text-decoration:line-through}.inspector .after code{color:#15803d}.status-pill,.type-pill{font-size:11px;padding:2px 8px;border-radius:999px;background:#f1f5f9;margin-right:6px}.status-pill.status-create{background:#dcfce7}.status-pill.status-update{background:#fef9c3}.status-pill.status-delete{background:#fee2e2}.status-pill.status-replace{background:#f3e8ff}.source-row{display:flex;align-items:center;gap:6px;margin:6px 0;font-size:11px;color:#64748b}.inspector-actions{display:flex;gap:6px;margin:8px 0}.inspector-actions button{font-size:12px;padding:4px 8px;border:1px solid #e2e8f0;border-radius:6px;background:#fff;cursor:pointer}.inspector section input,.inspector section textarea{width:100%;font-size:13px;padding:6px;border:1px solid #e2e8f0;border-radius:6px;font-family:inherit}.inspector section textarea{min-height:70px;resize:vertical}.palette{width:200px;border-right:1px solid #e2e8f0;padding:10px;overflow:auto;background:#fff;display:flex;flex-direction:column;gap:4px}.palette-item{display:flex;align-items:center;gap:8px;padding:6px 8px;border:1px solid #e2e8f0;border-radius:6px;background:#fff;cursor:pointer;font-size:12px;text-align:left}.palette-item:hover{background:#f1f5f9}.palette-custom{display:flex;gap:4px;margin-top:8px}.palette-custom input{flex:1;min-width:0;font-size:12px;padding:4px}.apply{background:#0f172a;color:#fff;border:none;border-radius:6px;padding:6px 14px;cursor:pointer}.apply:disabled{opacity:.4}.context-menu{position:fixed;z-index:50;background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 8px 24px #0002;display:flex;flex-direction:column;min-width:170px;overflow:hidden}.context-menu button{border:none;background:none;text-align:left;padding:8px 12px;cursor:pointer;font-size:13px}.context-menu button:hover{background:#f1f5f9}.palette-pack{display:flex;flex-direction:column;gap:4px}.palette-pack h5{margin:6px 0 2px;font-size:11px;color:#64748b;text-transform:uppercase;letter-spacing:.05em}.consent-banner{position:fixed;right:16px;bottom:16px;z-index:40;width:300px;background:#fff;border:1px solid #e2e8f0;border-radius:10px;box-shadow:0 8px 24px #0002;padding:12px 14px;display:flex;flex-direction:column;gap:10px}.consent-banner-head{display:flex;align-items:flex-start;gap:8px}.consent-banner-head .icon{flex-shrink:0;color:#64748b;margin-top:1px}.consent-banner-head p{margin:0;font-size:12px;line-height:1.45;color:#334155}.consent-banner-head a{color:#0f172a}.consent-actions{display:flex;gap:6px;justify-content:flex-end}.consent-actions button{font-size:12px;padding:4px 10px;border:1px solid #e2e8f0;border-radius:6px;background:#fff;cursor:pointer}.consent-actions button:first-child{background:#0f172a;color:#fff;border-color:#0f172a}
|
|
1
|
+
.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #555;--xy-background-pattern-lines-color-default: #333;--xy-background-pattern-cross-color-default: #333;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1;touch-action:none}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))}*{box-sizing:border-box}body{margin:0;font-family:ui-sans-serif,system-ui,sans-serif}.app{width:100vw;height:100vh;display:flex;flex-direction:column}.topbar{display:flex;gap:12px;align-items:center;padding:8px 14px;border-bottom:1px solid #e2e8f0;background:#fff;z-index:10}.brand{font-weight:700}.agent-badge{font-size:12px;padding:2px 8px;border-radius:999px;background:#f1f5f9}.agent-writing{background:#fef9c3}.agent-planning{background:#dbeafe}.stale-banner{color:#b91c1c;font-size:12px}.notice{font-size:12px;color:#334155}.resource-node{display:flex;align-items:center;gap:8px;padding:8px 10px;border:1.5px solid #94a3b8;border-radius:8px;background:#fff;width:190px}.resource-node .labels{display:flex;flex-direction:column;overflow:hidden}.resource-node .name{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.resource-node .type{font-size:10px;color:#64748b}.resource-node.status-create{border-color:#22c55e;box-shadow:0 0 0 2px #22c55e33}.resource-node.status-update{border-color:#eab308;box-shadow:0 0 0 2px #eab30833}.resource-node.status-delete{border-color:#ef4444;box-shadow:0 0 0 2px #ef444433}.resource-node.status-replace{border-color:#a855f7;box-shadow:0 0 0 2px #a855f733}.draft{border-style:dashed;opacity:.85}.removed{border-style:dashed;border-color:#ef4444;opacity:.6;text-decoration:line-through}.modified:after{content:"";position:absolute}.react-flow__node-group{border-radius:10px;border:1px dashed #cbd5e1;background:#f8fafc80}.canvas-row{flex:1;display:flex;min-height:0;position:relative}.canvas-row>.react-flow{flex:1}.layout-hint{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;color:#94a3b8;pointer-events:none;z-index:5}.inspector{width:320px;border-left:1px solid #e2e8f0;padding:12px;overflow:auto;background:#fff;font-size:13px}.inspector-head{display:flex;justify-content:space-between;align-items:center}.inspector table{width:100%;border-collapse:collapse}.inspector td{border-top:1px solid #f1f5f9;padding:4px 6px;vertical-align:top;word-break:break-all}.inspector .before code{color:#b91c1c;text-decoration:line-through}.inspector .after code{color:#15803d}.status-pill,.type-pill{font-size:11px;padding:2px 8px;border-radius:999px;background:#f1f5f9;margin-right:6px}.status-pill.status-create{background:#dcfce7}.status-pill.status-update{background:#fef9c3}.status-pill.status-delete{background:#fee2e2}.status-pill.status-replace{background:#f3e8ff}.source-row{display:flex;align-items:center;gap:6px;margin:6px 0;font-size:11px;color:#64748b}.inspector-actions{display:flex;gap:6px;margin:8px 0}.inspector-actions button{font-size:12px;padding:4px 8px;border:1px solid #e2e8f0;border-radius:6px;background:#fff;cursor:pointer}.inspector section input,.inspector section textarea{width:100%;font-size:13px;padding:6px;border:1px solid #e2e8f0;border-radius:6px;font-family:inherit}.inspector section textarea{min-height:70px;resize:vertical}.palette{width:200px;border-right:1px solid #e2e8f0;padding:10px;overflow:auto;background:#fff;display:flex;flex-direction:column;gap:4px}.palette-item{display:flex;align-items:center;gap:8px;padding:6px 8px;border:1px solid #e2e8f0;border-radius:6px;background:#fff;cursor:pointer;font-size:12px;text-align:left}.palette-item:hover{background:#f1f5f9}.palette-custom{display:flex;gap:4px;margin-top:8px}.palette-custom input{flex:1;min-width:0;font-size:12px;padding:4px}.apply{background:#0f172a;color:#fff;border:none;border-radius:6px;padding:6px 14px;cursor:pointer}.apply:disabled{opacity:.4}.context-menu{position:fixed;z-index:50;background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 8px 24px #0002;display:flex;flex-direction:column;min-width:170px;overflow:hidden}.context-menu button{border:none;background:none;text-align:left;padding:8px 12px;cursor:pointer;font-size:13px}.context-menu button:hover{background:#f1f5f9}.palette-pack{display:flex;flex-direction:column;gap:4px}.palette-pack h5{margin:6px 0 2px;font-size:11px;color:#64748b;text-transform:uppercase;letter-spacing:.05em}.consent-banner{position:fixed;right:16px;bottom:16px;z-index:40;width:300px;background:#fff;border:1px solid #e2e8f0;border-radius:10px;box-shadow:0 8px 24px #0002;padding:12px 14px;display:flex;flex-direction:column;gap:10px}.consent-banner-head{display:flex;align-items:flex-start;gap:8px}.consent-banner-head .icon{flex-shrink:0;color:#64748b;margin-top:1px}.consent-banner-head p{margin:0;font-size:12px;line-height:1.45;color:#334155}.consent-banner-head a{color:#0f172a}.consent-actions{display:flex;gap:6px;justify-content:flex-end}.consent-actions button{font-size:12px;padding:4px 10px;border:1px solid #e2e8f0;border-radius:6px;background:#fff;cursor:pointer}.consent-actions button:first-child{background:#0f172a;color:#fff;border-color:#0f172a}
|