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.
Files changed (69) hide show
  1. package/.claude/SKILLS.md +7 -0
  2. package/.claude/skills/sdn-plugin-abi-compliance/SKILL.md +56 -0
  3. package/.claude/todo/001-js-host-startup-and-deno.md +85 -0
  4. package/LICENSE +21 -0
  5. package/README.md +223 -0
  6. package/bin/sdn-flow-host.js +169 -0
  7. package/docs/.nojekyll +0 -0
  8. package/docs/ARCHITECTURE.md +200 -0
  9. package/docs/HOST_CAPABILITY_MODEL.md +317 -0
  10. package/docs/PLUGIN_ARCHITECTURE.md +145 -0
  11. package/docs/PLUGIN_COMPATIBILITY.md +61 -0
  12. package/docs/PLUGIN_COMPLIANCE_CHECKS.md +82 -0
  13. package/docs/PLUGIN_MANIFEST.md +94 -0
  14. package/docs/css/style.css +465 -0
  15. package/docs/index.html +218 -0
  16. package/docs/js/app.mjs +751 -0
  17. package/docs/js/editor-panel.mjs +203 -0
  18. package/docs/js/flow-canvas.mjs +515 -0
  19. package/docs/js/flow-model.mjs +391 -0
  20. package/docs/js/workers/emception.worker.js +146 -0
  21. package/docs/js/workers/pyodide.worker.js +134 -0
  22. package/native/flow_source_generator.cpp +1958 -0
  23. package/package.json +67 -0
  24. package/schemas/FlowRuntimeAbi.fbs +91 -0
  25. package/src/auth/canonicalize.js +5 -0
  26. package/src/auth/index.js +11 -0
  27. package/src/auth/permissions.js +8 -0
  28. package/src/compiler/CppFlowSourceGenerator.js +475 -0
  29. package/src/compiler/EmceptionCompilerAdapter.js +244 -0
  30. package/src/compiler/SignedArtifactCatalog.js +152 -0
  31. package/src/compiler/index.js +8 -0
  32. package/src/compiler/nativeFlowSourceGeneratorTool.js +144 -0
  33. package/src/compliance/index.js +13 -0
  34. package/src/compliance/pluginCompliance.js +11 -0
  35. package/src/deploy/FlowDeploymentClient.js +532 -0
  36. package/src/deploy/index.js +8 -0
  37. package/src/designer/FlowDesignerSession.js +158 -0
  38. package/src/designer/index.js +2 -0
  39. package/src/designer/requirements.js +184 -0
  40. package/src/generated/runtimeAbiLayouts.js +544 -0
  41. package/src/host/appHost.js +105 -0
  42. package/src/host/autoHost.js +113 -0
  43. package/src/host/browserHostAdapters.js +108 -0
  44. package/src/host/compiledFlowRuntimeHost.js +703 -0
  45. package/src/host/constants.js +55 -0
  46. package/src/host/dependencyRuntime.js +227 -0
  47. package/src/host/descriptorAbi.js +351 -0
  48. package/src/host/fetchService.js +237 -0
  49. package/src/host/httpHostAdapters.js +280 -0
  50. package/src/host/index.js +91 -0
  51. package/src/host/installedFlowHost.js +885 -0
  52. package/src/host/invocationAbi.js +440 -0
  53. package/src/host/normalize.js +372 -0
  54. package/src/host/packageManagers.js +369 -0
  55. package/src/host/profile.js +134 -0
  56. package/src/host/runtimeAbi.js +106 -0
  57. package/src/host/workspace.js +895 -0
  58. package/src/index.js +8 -0
  59. package/src/runtime/FlowRuntime.js +273 -0
  60. package/src/runtime/MethodRegistry.js +295 -0
  61. package/src/runtime/constants.js +44 -0
  62. package/src/runtime/index.js +19 -0
  63. package/src/runtime/normalize.js +377 -0
  64. package/src/transport/index.js +7 -0
  65. package/src/transport/pki.js +7 -0
  66. package/src/utils/crypto.js +7 -0
  67. package/src/utils/encoding.js +65 -0
  68. package/src/utils/wasmCrypto.js +69 -0
  69. 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
+ }