sdn-flow 0.2.0
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/.claude/SKILLS.md +7 -0
- package/.claude/skills/sdn-plugin-abi-compliance/SKILL.md +56 -0
- package/.claude/todo/001-js-host-startup-and-deno.md +85 -0
- package/LICENSE +21 -0
- package/README.md +223 -0
- package/bin/sdn-flow-host.js +169 -0
- package/docs/.nojekyll +0 -0
- package/docs/ARCHITECTURE.md +200 -0
- package/docs/HOST_CAPABILITY_MODEL.md +317 -0
- package/docs/PLUGIN_ARCHITECTURE.md +145 -0
- package/docs/PLUGIN_COMPATIBILITY.md +61 -0
- package/docs/PLUGIN_COMPLIANCE_CHECKS.md +82 -0
- package/docs/PLUGIN_MANIFEST.md +94 -0
- package/docs/css/style.css +465 -0
- package/docs/index.html +218 -0
- package/docs/js/app.mjs +751 -0
- package/docs/js/editor-panel.mjs +203 -0
- package/docs/js/flow-canvas.mjs +515 -0
- package/docs/js/flow-model.mjs +391 -0
- package/docs/js/workers/emception.worker.js +146 -0
- package/docs/js/workers/pyodide.worker.js +134 -0
- package/native/flow_source_generator.cpp +1958 -0
- package/package.json +67 -0
- package/schemas/FlowRuntimeAbi.fbs +91 -0
- package/src/auth/canonicalize.js +5 -0
- package/src/auth/index.js +11 -0
- package/src/auth/permissions.js +8 -0
- package/src/compiler/CppFlowSourceGenerator.js +475 -0
- package/src/compiler/EmceptionCompilerAdapter.js +244 -0
- package/src/compiler/SignedArtifactCatalog.js +152 -0
- package/src/compiler/index.js +8 -0
- package/src/compiler/nativeFlowSourceGeneratorTool.js +144 -0
- package/src/compliance/index.js +13 -0
- package/src/compliance/pluginCompliance.js +11 -0
- package/src/deploy/FlowDeploymentClient.js +532 -0
- package/src/deploy/index.js +8 -0
- package/src/designer/FlowDesignerSession.js +158 -0
- package/src/designer/index.js +2 -0
- package/src/designer/requirements.js +184 -0
- package/src/generated/runtimeAbiLayouts.js +544 -0
- package/src/host/appHost.js +105 -0
- package/src/host/autoHost.js +113 -0
- package/src/host/browserHostAdapters.js +108 -0
- package/src/host/compiledFlowRuntimeHost.js +703 -0
- package/src/host/constants.js +55 -0
- package/src/host/dependencyRuntime.js +227 -0
- package/src/host/descriptorAbi.js +351 -0
- package/src/host/fetchService.js +237 -0
- package/src/host/httpHostAdapters.js +280 -0
- package/src/host/index.js +91 -0
- package/src/host/installedFlowHost.js +885 -0
- package/src/host/invocationAbi.js +440 -0
- package/src/host/normalize.js +372 -0
- package/src/host/packageManagers.js +369 -0
- package/src/host/profile.js +134 -0
- package/src/host/runtimeAbi.js +106 -0
- package/src/host/workspace.js +895 -0
- package/src/index.js +8 -0
- package/src/runtime/FlowRuntime.js +273 -0
- package/src/runtime/MethodRegistry.js +295 -0
- package/src/runtime/constants.js +44 -0
- package/src/runtime/index.js +19 -0
- package/src/runtime/normalize.js +377 -0
- package/src/transport/index.js +7 -0
- package/src/transport/pki.js +7 -0
- package/src/utils/crypto.js +7 -0
- package/src/utils/encoding.js +65 -0
- package/src/utils/wasmCrypto.js +69 -0
- package/tools/run-plugin-compliance-check.mjs +153 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Compatibility Model
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
`space-data-module-sdk` is the canonical module/plugin artifact model, and
|
|
6
|
+
`sdn-flow` is the canonical flow-composition model built on top of it.
|
|
7
|
+
Compatibility layers exist only to bridge older hosts and older metadata
|
|
8
|
+
formats into those models.
|
|
9
|
+
|
|
10
|
+
## What Stays Canonical
|
|
11
|
+
|
|
12
|
+
These remain canonical:
|
|
13
|
+
|
|
14
|
+
- typed manifest-driven methods from the shared module contract
|
|
15
|
+
- schema-tagged frame streams
|
|
16
|
+
- embedded FlatBuffer manifests
|
|
17
|
+
- compiled single-WASM flow deployment artifacts
|
|
18
|
+
|
|
19
|
+
## What Can Be Generated
|
|
20
|
+
|
|
21
|
+
Legacy compatibility surfaces may be generated from the canonical manifest, for
|
|
22
|
+
example:
|
|
23
|
+
|
|
24
|
+
- host-specific metadata JSON
|
|
25
|
+
- request-handler shims
|
|
26
|
+
- timer/cron wrappers
|
|
27
|
+
- protocol dispatch wrappers
|
|
28
|
+
|
|
29
|
+
Those artifacts are downstream views of the manifest, not alternate sources of
|
|
30
|
+
truth.
|
|
31
|
+
|
|
32
|
+
Compatibility wrappers must not become a shadow runtime model. In particular:
|
|
33
|
+
|
|
34
|
+
- do not describe a runtime as "portable WASI" unless its required imports and
|
|
35
|
+
host bindings are actually portable
|
|
36
|
+
- do not let host-specific wrappers own business logic that should live in the
|
|
37
|
+
compiled flow runtime
|
|
38
|
+
- do not let generated JSON metadata become the canonical contract
|
|
39
|
+
|
|
40
|
+
## Deployment Compatibility
|
|
41
|
+
|
|
42
|
+
Compatibility should never change the deploy boundary:
|
|
43
|
+
|
|
44
|
+
- the deployable unit is still one compiled WASM runtime artifact
|
|
45
|
+
- the runtime artifact still embeds a callable manifest
|
|
46
|
+
- authorization and transport rules still wrap the compiled artifact
|
|
47
|
+
|
|
48
|
+
## Host-Specific Responsibilities
|
|
49
|
+
|
|
50
|
+
Older hosts may need adapters for:
|
|
51
|
+
|
|
52
|
+
- manifest translation
|
|
53
|
+
- request/response wrapper generation
|
|
54
|
+
- timer model bridging
|
|
55
|
+
- installation and loading conventions
|
|
56
|
+
|
|
57
|
+
Those adapters belong in host/tooling repos, not in the core runtime contract.
|
|
58
|
+
|
|
59
|
+
See also:
|
|
60
|
+
|
|
61
|
+
- [Host Capability Model](./HOST_CAPABILITY_MODEL.md)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Plugin Compliance Checks
|
|
2
|
+
|
|
3
|
+
`space-data-module-sdk` owns the canonical plugin/module API and ABI
|
|
4
|
+
compliance checks for Space Data Network repositories.
|
|
5
|
+
|
|
6
|
+
`sdn-flow` consumes those checks and may provide thin wrappers so flow repos can
|
|
7
|
+
run the same canonical validation without forking the rules.
|
|
8
|
+
|
|
9
|
+
Use this tooling when a repo touches:
|
|
10
|
+
|
|
11
|
+
- plugin manifests
|
|
12
|
+
- plugin ABI export symbols
|
|
13
|
+
- compiled WASM plugin artifacts
|
|
14
|
+
- capability declarations
|
|
15
|
+
- external interface declarations
|
|
16
|
+
|
|
17
|
+
## Canonical Rules
|
|
18
|
+
|
|
19
|
+
The compliance checker enforces the portable module/plugin rules defined in
|
|
20
|
+
`space-data-module-sdk`:
|
|
21
|
+
|
|
22
|
+
- plugins are manifest-defined deployable units
|
|
23
|
+
- plugins embed a FlatBuffer manifest
|
|
24
|
+
- plugin artifacts expose:
|
|
25
|
+
- `plugin_get_manifest_flatbuffer`
|
|
26
|
+
- `plugin_get_manifest_flatbuffer_size`
|
|
27
|
+
- manifests declare method contracts, not ad hoc payloads
|
|
28
|
+
- capability and external-interface surfaces are explicit
|
|
29
|
+
|
|
30
|
+
## Commands
|
|
31
|
+
|
|
32
|
+
Scan a repo for plugin manifests:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
node tools/run-plugin-compliance-check.mjs --repo-root .
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Target the repo scan with `sdn-plugin-compliance.json` at the repo root:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"scanDirectories": ["examples/plugins"],
|
|
43
|
+
"manifestPaths": ["plugins/example/manifest.json"],
|
|
44
|
+
"allowEmpty": false
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
When that file exists, the checker uses it instead of crawling the entire repo.
|
|
49
|
+
Use `allowEmpty: true` only in repos that do not yet ship canonical plugin
|
|
50
|
+
manifests but still need the shared check wired in.
|
|
51
|
+
|
|
52
|
+
Validate one manifest directly:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
node tools/run-plugin-compliance-check.mjs --manifest ./examples/plugins/basic-propagator/manifest.json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Validate manifest plus compiled ABI exports:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
node tools/run-plugin-compliance-check.mjs \
|
|
62
|
+
--manifest ./manifest.json \
|
|
63
|
+
--wasm ./dist/plugin.wasm
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Emit JSON:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
node tools/run-plugin-compliance-check.mjs --repo-root . --json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Shared-Repo Pattern
|
|
73
|
+
|
|
74
|
+
Other Space Data Network repositories should not fork this checker.
|
|
75
|
+
|
|
76
|
+
They should:
|
|
77
|
+
|
|
78
|
+
1. load the shared checker from `space-data-module-sdk`
|
|
79
|
+
2. optionally call it through thin wrappers in `sdn-flow`
|
|
80
|
+
3. keep only thin repo-local wrapper scripts if they want shorter commands
|
|
81
|
+
|
|
82
|
+
That preserves one plugin ABI and one compliance implementation.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Plugin Manifest
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
The manifest is the canonical description of a plugin's runtime contract.
|
|
6
|
+
Its schema and canonical validation rules are sourced from
|
|
7
|
+
`space-data-module-sdk`; this document explains how `sdn-flow` consumes that
|
|
8
|
+
module contract.
|
|
9
|
+
|
|
10
|
+
It should be treated as:
|
|
11
|
+
|
|
12
|
+
- machine-readable
|
|
13
|
+
- embeddable in compiled artifacts
|
|
14
|
+
- callable by hosts through manifest export functions
|
|
15
|
+
- stable enough to drive tooling, validation, and deployment
|
|
16
|
+
|
|
17
|
+
## Required Fields
|
|
18
|
+
|
|
19
|
+
A practical manifest should define at least:
|
|
20
|
+
|
|
21
|
+
- `pluginId`
|
|
22
|
+
- `name`
|
|
23
|
+
- `version`
|
|
24
|
+
- `pluginFamily`
|
|
25
|
+
- `methods`
|
|
26
|
+
|
|
27
|
+
Each method should define:
|
|
28
|
+
|
|
29
|
+
- `methodId`
|
|
30
|
+
- `inputPorts`
|
|
31
|
+
- `outputPorts`
|
|
32
|
+
- `maxBatch`
|
|
33
|
+
- `drainPolicy`
|
|
34
|
+
|
|
35
|
+
Each port should define:
|
|
36
|
+
|
|
37
|
+
- `portId`
|
|
38
|
+
- accepted schema sets
|
|
39
|
+
- `minStreams`
|
|
40
|
+
- `maxStreams`
|
|
41
|
+
- `required`
|
|
42
|
+
|
|
43
|
+
## Optional Fields
|
|
44
|
+
|
|
45
|
+
Depending on host needs, manifests may also declare:
|
|
46
|
+
|
|
47
|
+
- `capabilities`
|
|
48
|
+
- `externalInterfaces`
|
|
49
|
+
- `timers`
|
|
50
|
+
- `protocols`
|
|
51
|
+
- `schemasUsed`
|
|
52
|
+
- `buildArtifacts`
|
|
53
|
+
|
|
54
|
+
For storage plugins, manifests should also make backend selection explicit.
|
|
55
|
+
Typical patterns are:
|
|
56
|
+
|
|
57
|
+
- a logical database interface exposed to the flow
|
|
58
|
+
- a host-service storage-adapter interface that declares whether the plugin can
|
|
59
|
+
run against memory, persistent host storage, or both
|
|
60
|
+
|
|
61
|
+
Capability guidance:
|
|
62
|
+
|
|
63
|
+
- keep capability IDs coarse and stable across hosts
|
|
64
|
+
- put runtime-specific transport details in `externalInterfaces[*].properties`
|
|
65
|
+
- use `externalInterfaces` to describe HTTP endpoints, TCP/UDP or raw-socket
|
|
66
|
+
usage, filesystem paths, pipe/stream bindings, database surfaces, IPFS
|
|
67
|
+
services, and SDS protocol bindings
|
|
68
|
+
- if a capability depends on a specific host profile, document that explicitly
|
|
69
|
+
instead of implying generic-WASI portability
|
|
70
|
+
|
|
71
|
+
## Runtime Interpretation
|
|
72
|
+
|
|
73
|
+
The manifest is used to:
|
|
74
|
+
|
|
75
|
+
- register methods in the runtime
|
|
76
|
+
- validate input port shape and stream counts
|
|
77
|
+
- validate output routing
|
|
78
|
+
- determine capability requirements
|
|
79
|
+
- describe host bindings to visual editors and deployment tooling
|
|
80
|
+
- derive deployment metadata and compatibility views
|
|
81
|
+
|
|
82
|
+
## Embedded Manifest Rule
|
|
83
|
+
|
|
84
|
+
The manifest must be embedded as FlatBuffer bytes in deployable artifacts. Hosts
|
|
85
|
+
must be able to obtain the bytes directly from the artifact via callable
|
|
86
|
+
exports.
|
|
87
|
+
|
|
88
|
+
## Example
|
|
89
|
+
|
|
90
|
+
See:
|
|
91
|
+
|
|
92
|
+
- [Basic Propagator Plugin](../examples/plugins/basic-propagator/README.md)
|
|
93
|
+
- [Basic Sensor Plugin](../examples/plugins/basic-sensor/README.md)
|
|
94
|
+
- [Host Capability Model](./HOST_CAPABILITY_MODEL.md)
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════════════
|
|
2
|
+
sdn-flow IDE — Dark theme (VS Code / Node-RED inspired)
|
|
3
|
+
═══════════════════════════════════════════════════════ */
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
--bg-darkest: #1e1e1e;
|
|
7
|
+
--bg-dark: #252526;
|
|
8
|
+
--bg-medium: #2d2d2d;
|
|
9
|
+
--bg-light: #333333;
|
|
10
|
+
--bg-hover: #3c3c3c;
|
|
11
|
+
--bg-active: #094771;
|
|
12
|
+
--border: #3c3c3c;
|
|
13
|
+
--border-light: #505050;
|
|
14
|
+
--text: #cccccc;
|
|
15
|
+
--text-dim: #858585;
|
|
16
|
+
--text-bright: #e0e0e0;
|
|
17
|
+
--accent: #0078d4;
|
|
18
|
+
--accent-hover: #1a8cef;
|
|
19
|
+
--deploy: #388a34;
|
|
20
|
+
--deploy-hover: #45a840;
|
|
21
|
+
--error: #f14c4c;
|
|
22
|
+
--warn: #cca700;
|
|
23
|
+
--success: #89d185;
|
|
24
|
+
--kind-trigger: #c586c0;
|
|
25
|
+
--kind-transform: #569cd6;
|
|
26
|
+
--kind-analyzer: #4ec9b0;
|
|
27
|
+
--kind-publisher: #ce9178;
|
|
28
|
+
--kind-responder: #dcdcaa;
|
|
29
|
+
--kind-renderer: #d16969;
|
|
30
|
+
--kind-sink: #d7ba7d;
|
|
31
|
+
--node-bg: #1e1e1e;
|
|
32
|
+
--node-border: #505050;
|
|
33
|
+
--node-selected:#0078d4;
|
|
34
|
+
--wire-color: #858585;
|
|
35
|
+
--wire-active: #569cd6;
|
|
36
|
+
--port-size: 10px;
|
|
37
|
+
--header-h: 38px;
|
|
38
|
+
--status-h: 24px;
|
|
39
|
+
--palette-w: 180px;
|
|
40
|
+
--right-w: 380px;
|
|
41
|
+
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
42
|
+
--mono: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
46
|
+
|
|
47
|
+
html, body {
|
|
48
|
+
height: 100%; width: 100%;
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
font-family: var(--font);
|
|
51
|
+
font-size: 13px;
|
|
52
|
+
color: var(--text);
|
|
53
|
+
background: var(--bg-darkest);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* ─── Scrollbars ─── */
|
|
57
|
+
::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
58
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
59
|
+
::-webkit-scrollbar-thumb { background: #4a4a4a; border-radius: 4px; }
|
|
60
|
+
::-webkit-scrollbar-thumb:hover { background: #5a5a5a; }
|
|
61
|
+
|
|
62
|
+
/* ─── Toolbar ─── */
|
|
63
|
+
#toolbar {
|
|
64
|
+
height: var(--header-h);
|
|
65
|
+
background: var(--bg-dark);
|
|
66
|
+
border-bottom: 1px solid var(--border);
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
padding: 0 12px;
|
|
70
|
+
gap: 8px;
|
|
71
|
+
user-select: none;
|
|
72
|
+
-webkit-app-region: drag;
|
|
73
|
+
}
|
|
74
|
+
.toolbar-left, .toolbar-center, .toolbar-right {
|
|
75
|
+
display: flex; align-items: center; gap: 6px;
|
|
76
|
+
}
|
|
77
|
+
.toolbar-left { flex: 0 0 auto; }
|
|
78
|
+
.toolbar-center { flex: 1; justify-content: center; }
|
|
79
|
+
.toolbar-right { flex: 0 0 auto; }
|
|
80
|
+
|
|
81
|
+
.brand { display: flex; align-items: center; gap: 6px; }
|
|
82
|
+
.brand-name { font-weight: 600; font-size: 14px; color: var(--text-bright); letter-spacing: 0.5px; }
|
|
83
|
+
|
|
84
|
+
.tb-btn {
|
|
85
|
+
display: inline-flex; align-items: center; gap: 5px;
|
|
86
|
+
padding: 4px 10px;
|
|
87
|
+
background: var(--bg-light);
|
|
88
|
+
color: var(--text);
|
|
89
|
+
border: 1px solid var(--border);
|
|
90
|
+
border-radius: 3px;
|
|
91
|
+
font-size: 12px;
|
|
92
|
+
cursor: pointer;
|
|
93
|
+
transition: background 0.15s;
|
|
94
|
+
-webkit-app-region: no-drag;
|
|
95
|
+
}
|
|
96
|
+
.tb-btn:hover { background: var(--bg-hover); }
|
|
97
|
+
.tb-btn:active { background: var(--bg-active); }
|
|
98
|
+
|
|
99
|
+
.tb-btn-accent { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
100
|
+
.tb-btn-accent:hover { background: var(--accent-hover); }
|
|
101
|
+
|
|
102
|
+
.tb-btn-deploy { background: var(--deploy); border-color: var(--deploy); color: #fff; }
|
|
103
|
+
.tb-btn-deploy:hover { background: var(--deploy-hover); }
|
|
104
|
+
|
|
105
|
+
.tb-sep { width: 1px; height: 20px; background: var(--border); }
|
|
106
|
+
|
|
107
|
+
.tb-select {
|
|
108
|
+
padding: 3px 6px;
|
|
109
|
+
background: var(--bg-light);
|
|
110
|
+
color: var(--text);
|
|
111
|
+
border: 1px solid var(--border);
|
|
112
|
+
border-radius: 3px;
|
|
113
|
+
font-size: 12px;
|
|
114
|
+
cursor: pointer;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.crc-badge {
|
|
118
|
+
padding: 2px 8px;
|
|
119
|
+
background: var(--bg-light);
|
|
120
|
+
border: 1px solid var(--border);
|
|
121
|
+
border-radius: 3px;
|
|
122
|
+
font-family: var(--mono);
|
|
123
|
+
font-size: 11px;
|
|
124
|
+
color: var(--text-dim);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.icon-btn {
|
|
128
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
129
|
+
width: 24px; height: 24px;
|
|
130
|
+
background: none; border: none;
|
|
131
|
+
color: var(--text-dim);
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
border-radius: 3px;
|
|
134
|
+
}
|
|
135
|
+
.icon-btn:hover { background: var(--bg-hover); color: var(--text); }
|
|
136
|
+
|
|
137
|
+
/* ─── Main Layout ─── */
|
|
138
|
+
#main {
|
|
139
|
+
display: flex;
|
|
140
|
+
height: calc(100vh - var(--header-h) - var(--status-h));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* ─── Palette ─── */
|
|
144
|
+
#palette {
|
|
145
|
+
width: var(--palette-w);
|
|
146
|
+
min-width: 140px;
|
|
147
|
+
background: var(--bg-dark);
|
|
148
|
+
border-right: 1px solid var(--border);
|
|
149
|
+
overflow-y: auto;
|
|
150
|
+
flex-shrink: 0;
|
|
151
|
+
user-select: none;
|
|
152
|
+
}
|
|
153
|
+
.palette-header {
|
|
154
|
+
padding: 8px 12px 4px;
|
|
155
|
+
font-size: 11px;
|
|
156
|
+
text-transform: uppercase;
|
|
157
|
+
letter-spacing: 1px;
|
|
158
|
+
color: var(--text-dim);
|
|
159
|
+
}
|
|
160
|
+
.palette-list { padding: 4px 8px; }
|
|
161
|
+
.palette-item {
|
|
162
|
+
display: flex; align-items: center; gap: 8px;
|
|
163
|
+
padding: 6px 10px;
|
|
164
|
+
border-radius: 4px;
|
|
165
|
+
cursor: grab;
|
|
166
|
+
font-size: 12px;
|
|
167
|
+
transition: background 0.12s;
|
|
168
|
+
}
|
|
169
|
+
.palette-item:hover { background: var(--bg-hover); }
|
|
170
|
+
.palette-item:active { cursor: grabbing; }
|
|
171
|
+
|
|
172
|
+
.kind-dot {
|
|
173
|
+
width: 10px; height: 10px;
|
|
174
|
+
border-radius: 50%;
|
|
175
|
+
flex-shrink: 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.palette-btn {
|
|
179
|
+
width: 100%;
|
|
180
|
+
padding: 6px 10px;
|
|
181
|
+
margin: 2px 0;
|
|
182
|
+
background: var(--bg-light);
|
|
183
|
+
color: var(--text);
|
|
184
|
+
border: 1px solid var(--border);
|
|
185
|
+
border-radius: 4px;
|
|
186
|
+
font-size: 12px;
|
|
187
|
+
cursor: pointer;
|
|
188
|
+
text-align: left;
|
|
189
|
+
}
|
|
190
|
+
.palette-btn:hover { background: var(--bg-hover); }
|
|
191
|
+
|
|
192
|
+
/* ─── Canvas ─── */
|
|
193
|
+
#canvas-wrap {
|
|
194
|
+
flex: 1;
|
|
195
|
+
position: relative;
|
|
196
|
+
overflow: hidden;
|
|
197
|
+
background: var(--bg-darkest);
|
|
198
|
+
}
|
|
199
|
+
#flow-canvas {
|
|
200
|
+
width: 100%; height: 100%;
|
|
201
|
+
cursor: default;
|
|
202
|
+
}
|
|
203
|
+
.canvas-bg { cursor: crosshair; }
|
|
204
|
+
|
|
205
|
+
/* Selection rectangle */
|
|
206
|
+
#select-rect {
|
|
207
|
+
position: absolute;
|
|
208
|
+
border: 1px dashed var(--accent);
|
|
209
|
+
background: rgba(0, 120, 212, 0.08);
|
|
210
|
+
pointer-events: none;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* ─── Flow Nodes ─── */
|
|
214
|
+
.flow-node {
|
|
215
|
+
cursor: move;
|
|
216
|
+
}
|
|
217
|
+
.flow-node .node-outline {
|
|
218
|
+
fill: none;
|
|
219
|
+
stroke: var(--node-border);
|
|
220
|
+
stroke-width: 1.5;
|
|
221
|
+
transition: stroke 0.15s;
|
|
222
|
+
}
|
|
223
|
+
.flow-node.selected .node-outline {
|
|
224
|
+
stroke: var(--node-selected);
|
|
225
|
+
stroke-width: 2;
|
|
226
|
+
}
|
|
227
|
+
.flow-node .node-body {
|
|
228
|
+
fill: var(--node-bg);
|
|
229
|
+
}
|
|
230
|
+
.flow-node .node-body-bottom {
|
|
231
|
+
pointer-events: none;
|
|
232
|
+
}
|
|
233
|
+
.flow-node .node-title-bg {
|
|
234
|
+
pointer-events: none;
|
|
235
|
+
}
|
|
236
|
+
.flow-node .node-title {
|
|
237
|
+
fill: #fff;
|
|
238
|
+
font-size: 10px;
|
|
239
|
+
font-weight: 600;
|
|
240
|
+
font-family: var(--font);
|
|
241
|
+
pointer-events: none;
|
|
242
|
+
}
|
|
243
|
+
.flow-node .node-sublabel {
|
|
244
|
+
fill: var(--text-dim);
|
|
245
|
+
font-size: 7px;
|
|
246
|
+
font-family: var(--mono);
|
|
247
|
+
pointer-events: none;
|
|
248
|
+
opacity: 0.7;
|
|
249
|
+
}
|
|
250
|
+
.flow-node .node-status {
|
|
251
|
+
fill: rgba(255,255,255,0.3);
|
|
252
|
+
transition: fill 0.3s;
|
|
253
|
+
}
|
|
254
|
+
.flow-node .node-status.running { fill: #fff; }
|
|
255
|
+
.flow-node .node-status.success { fill: var(--success); }
|
|
256
|
+
.flow-node .node-status.error { fill: var(--error); }
|
|
257
|
+
|
|
258
|
+
/* ─── Ports ─── */
|
|
259
|
+
.port {
|
|
260
|
+
r: 5;
|
|
261
|
+
fill: var(--bg-dark);
|
|
262
|
+
stroke: var(--border-light);
|
|
263
|
+
stroke-width: 1.5;
|
|
264
|
+
cursor: crosshair;
|
|
265
|
+
transition: fill 0.15s, stroke 0.15s;
|
|
266
|
+
}
|
|
267
|
+
.port:hover, .port.active {
|
|
268
|
+
fill: var(--accent);
|
|
269
|
+
stroke: var(--accent);
|
|
270
|
+
}
|
|
271
|
+
.port-label {
|
|
272
|
+
fill: var(--text-dim);
|
|
273
|
+
font-size: 8px;
|
|
274
|
+
font-family: var(--mono);
|
|
275
|
+
pointer-events: none;
|
|
276
|
+
opacity: 0.8;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* ─── Wires ─── */
|
|
280
|
+
.wire {
|
|
281
|
+
fill: none;
|
|
282
|
+
stroke: var(--wire-color);
|
|
283
|
+
stroke-width: 2;
|
|
284
|
+
pointer-events: stroke;
|
|
285
|
+
cursor: pointer;
|
|
286
|
+
transition: stroke 0.15s;
|
|
287
|
+
}
|
|
288
|
+
.wire:hover { stroke: var(--wire-active); stroke-width: 2.5; }
|
|
289
|
+
.wire.selected { stroke: var(--node-selected); stroke-width: 2.5; }
|
|
290
|
+
.wire.temp { stroke: var(--accent); stroke-width: 2; stroke-dasharray: 6 3; pointer-events: none; }
|
|
291
|
+
.wire.animated {
|
|
292
|
+
stroke-dasharray: 8 4;
|
|
293
|
+
animation: wire-flow 0.6s linear infinite;
|
|
294
|
+
}
|
|
295
|
+
@keyframes wire-flow { to { stroke-dashoffset: -12; } }
|
|
296
|
+
|
|
297
|
+
/* ─── Right Panel ─── */
|
|
298
|
+
#right-panel {
|
|
299
|
+
width: var(--right-w);
|
|
300
|
+
min-width: 260px;
|
|
301
|
+
background: var(--bg-dark);
|
|
302
|
+
border-left: 1px solid var(--border);
|
|
303
|
+
display: flex;
|
|
304
|
+
flex-direction: column;
|
|
305
|
+
flex-shrink: 0;
|
|
306
|
+
position: relative;
|
|
307
|
+
}
|
|
308
|
+
.resize-handle-v {
|
|
309
|
+
position: absolute;
|
|
310
|
+
left: -3px; top: 0; bottom: 0;
|
|
311
|
+
width: 6px;
|
|
312
|
+
cursor: col-resize;
|
|
313
|
+
z-index: 10;
|
|
314
|
+
}
|
|
315
|
+
.resize-handle-h {
|
|
316
|
+
height: 4px;
|
|
317
|
+
background: var(--border);
|
|
318
|
+
cursor: row-resize;
|
|
319
|
+
flex-shrink: 0;
|
|
320
|
+
}
|
|
321
|
+
.resize-handle-h:hover { background: var(--accent); }
|
|
322
|
+
|
|
323
|
+
.panel-section { flex: 1; display: flex; flex-direction: column; min-height: 80px; overflow: hidden; }
|
|
324
|
+
.panel-header {
|
|
325
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
326
|
+
padding: 6px 10px;
|
|
327
|
+
background: var(--bg-medium);
|
|
328
|
+
border-bottom: 1px solid var(--border);
|
|
329
|
+
font-size: 11px;
|
|
330
|
+
text-transform: uppercase;
|
|
331
|
+
letter-spacing: 0.8px;
|
|
332
|
+
color: var(--text-dim);
|
|
333
|
+
flex-shrink: 0;
|
|
334
|
+
user-select: none;
|
|
335
|
+
}
|
|
336
|
+
.panel-tag {
|
|
337
|
+
font-size: 9px;
|
|
338
|
+
padding: 1px 5px;
|
|
339
|
+
background: var(--bg-darkest);
|
|
340
|
+
border: 1px solid var(--border);
|
|
341
|
+
border-radius: 3px;
|
|
342
|
+
color: var(--text-dim);
|
|
343
|
+
text-transform: none;
|
|
344
|
+
letter-spacing: 0;
|
|
345
|
+
}
|
|
346
|
+
.panel-body { flex: 1; overflow-y: auto; padding: 8px 10px; }
|
|
347
|
+
|
|
348
|
+
.empty-state {
|
|
349
|
+
display: flex; align-items: center; justify-content: center;
|
|
350
|
+
height: 100%;
|
|
351
|
+
color: var(--text-dim);
|
|
352
|
+
font-style: italic;
|
|
353
|
+
font-size: 12px;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* ─── Properties Form ─── */
|
|
357
|
+
.prop-group { margin-bottom: 10px; }
|
|
358
|
+
.prop-label {
|
|
359
|
+
display: block;
|
|
360
|
+
font-size: 11px;
|
|
361
|
+
color: var(--text-dim);
|
|
362
|
+
margin-bottom: 3px;
|
|
363
|
+
}
|
|
364
|
+
.prop-input, .prop-select {
|
|
365
|
+
width: 100%;
|
|
366
|
+
padding: 4px 8px;
|
|
367
|
+
background: var(--bg-darkest);
|
|
368
|
+
color: var(--text);
|
|
369
|
+
border: 1px solid var(--border);
|
|
370
|
+
border-radius: 3px;
|
|
371
|
+
font-size: 12px;
|
|
372
|
+
font-family: var(--font);
|
|
373
|
+
}
|
|
374
|
+
.prop-input:focus, .prop-select:focus {
|
|
375
|
+
outline: none;
|
|
376
|
+
border-color: var(--accent);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/* ─── Monaco Editor Container ─── */
|
|
380
|
+
#editor-container {
|
|
381
|
+
flex: 1;
|
|
382
|
+
min-height: 150px;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/* ─── Terminal ─── */
|
|
386
|
+
.terminal {
|
|
387
|
+
flex: 1;
|
|
388
|
+
padding: 8px 10px;
|
|
389
|
+
background: var(--bg-darkest);
|
|
390
|
+
font-family: var(--mono);
|
|
391
|
+
font-size: 12px;
|
|
392
|
+
line-height: 1.5;
|
|
393
|
+
overflow-y: auto;
|
|
394
|
+
white-space: pre-wrap;
|
|
395
|
+
word-break: break-all;
|
|
396
|
+
color: var(--text-dim);
|
|
397
|
+
}
|
|
398
|
+
.terminal .term-info { color: var(--text); }
|
|
399
|
+
.terminal .term-error { color: var(--error); }
|
|
400
|
+
.terminal .term-warn { color: var(--warn); }
|
|
401
|
+
.terminal .term-success { color: var(--success); }
|
|
402
|
+
.terminal .term-cmd {
|
|
403
|
+
color: var(--accent);
|
|
404
|
+
display: block;
|
|
405
|
+
margin-top: 4px;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/* ─── Status Bar ─── */
|
|
409
|
+
#statusbar {
|
|
410
|
+
height: var(--status-h);
|
|
411
|
+
background: var(--accent);
|
|
412
|
+
color: #fff;
|
|
413
|
+
display: flex;
|
|
414
|
+
align-items: center;
|
|
415
|
+
justify-content: space-between;
|
|
416
|
+
padding: 0 12px;
|
|
417
|
+
font-size: 12px;
|
|
418
|
+
user-select: none;
|
|
419
|
+
}
|
|
420
|
+
#statusbar.error { background: var(--error); }
|
|
421
|
+
#statusbar.success { background: var(--deploy); }
|
|
422
|
+
#status-right { display: flex; gap: 4px; }
|
|
423
|
+
.status-sep { opacity: 0.5; }
|
|
424
|
+
|
|
425
|
+
/* ─── Wallet Dialog ─── */
|
|
426
|
+
dialog {
|
|
427
|
+
background: var(--bg-dark);
|
|
428
|
+
color: var(--text);
|
|
429
|
+
border: 1px solid var(--border);
|
|
430
|
+
border-radius: 8px;
|
|
431
|
+
padding: 0;
|
|
432
|
+
width: 480px;
|
|
433
|
+
max-height: 80vh;
|
|
434
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
|
|
435
|
+
}
|
|
436
|
+
dialog::backdrop { background: rgba(0,0,0,0.5); }
|
|
437
|
+
.dialog-header {
|
|
438
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
439
|
+
padding: 10px 14px;
|
|
440
|
+
border-bottom: 1px solid var(--border);
|
|
441
|
+
font-weight: 600;
|
|
442
|
+
}
|
|
443
|
+
#wallet-mount { padding: 16px; min-height: 200px; }
|
|
444
|
+
|
|
445
|
+
/* ─── Drag Ghost ─── */
|
|
446
|
+
.drag-ghost {
|
|
447
|
+
position: fixed;
|
|
448
|
+
pointer-events: none;
|
|
449
|
+
z-index: 9999;
|
|
450
|
+
opacity: 0.7;
|
|
451
|
+
padding: 6px 12px;
|
|
452
|
+
background: var(--bg-light);
|
|
453
|
+
border: 1px solid var(--border);
|
|
454
|
+
border-radius: 4px;
|
|
455
|
+
font-size: 12px;
|
|
456
|
+
color: var(--text);
|
|
457
|
+
white-space: nowrap;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/* ─── Responsive ─── */
|
|
461
|
+
@media (max-width: 900px) {
|
|
462
|
+
:root { --palette-w: 50px; --right-w: 280px; }
|
|
463
|
+
.palette-item span:not(.kind-dot) { display: none; }
|
|
464
|
+
.palette-header { font-size: 9px; padding: 6px 4px 2px; }
|
|
465
|
+
}
|