@intentius/behold 0.7.0 → 0.9.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/AGENTS.md +94 -2
- package/README.md +133 -2
- package/demos.json +25 -1
- package/dist/cli.js +3176 -330
- package/example-argo-estate/README.md +71 -0
- package/example-argo-estate/app-a/chant.config.ts +18 -0
- package/example-argo-estate/app-a/manifests/app.yaml +54 -0
- package/example-argo-estate/app-a/package.json +13 -0
- package/example-argo-estate/app-a/src/app.ts +35 -0
- package/example-argo-estate/app-a/tsconfig.json +1 -0
- package/example-argo-estate/app-b/chant.config.ts +18 -0
- package/example-argo-estate/app-b/manifests/app.yaml +54 -0
- package/example-argo-estate/app-b/package.json +13 -0
- package/example-argo-estate/app-b/src/app.ts +34 -0
- package/example-argo-estate/app-b/tsconfig.json +1 -0
- package/example-argo-estate/control-plane/chant.config.ts +29 -0
- package/example-argo-estate/control-plane/package.json +13 -0
- package/example-argo-estate/control-plane/src/argo.ts +69 -0
- package/example-argo-estate/control-plane/tsconfig.json +1 -0
- package/example-argo-estate/package-lock.json +1949 -0
- package/example-argo-estate/package.json +10 -0
- package/example-carve/README.md +194 -0
- package/example-carve/app/chant.config.ts +6 -0
- package/example-carve/app/package-lock.json +1075 -0
- package/example-carve/app/package.json +13 -0
- package/example-carve/app/src/carved.ts +30 -0
- package/example-carve/app/tsconfig.json +1 -0
- package/example-carve/carve-report.json +872 -0
- package/example-carve/legacy-tf/cdn.tf +23 -0
- package/example-carve/legacy-tf/compute.tf +63 -0
- package/example-carve/legacy-tf/floci-override.tf.disabled +61 -0
- package/example-carve/legacy-tf/modules/cdn/main.tf +72 -0
- package/example-carve/legacy-tf/naming.tf +10 -0
- package/example-carve/legacy-tf/network.tf +119 -0
- package/example-carve/legacy-tf/observability.tf +18 -0
- package/example-carve/legacy-tf/outputs.tf +16 -0
- package/example-carve/legacy-tf/storage.tf +33 -0
- package/example-carve/legacy-tf/terraform.tfstate +602 -0
- package/example-carve/legacy-tf/versions.tf +40 -0
- package/example-flux-estate/README.md +22 -12
- package/example-flux-estate/app-a/package.json +2 -2
- package/example-flux-estate/app-a/src/app.ts +2 -1
- package/example-flux-estate/app-b/chant.config.ts +4 -3
- package/example-flux-estate/app-b/package.json +2 -2
- package/example-flux-estate/app-b/src/app.ts +5 -3
- package/example-flux-estate/control-plane/package.json +2 -2
- package/example-flux-estate/control-plane/src/flux.ts +10 -2
- package/example-flux-estate/package-lock.json +17 -17
- package/example-k8s/README.md +16 -2
- package/example-k8s/base/deployment.yaml +21 -0
- package/example-k8s/base/kustomization.yaml +3 -0
- package/example-k8s/base/service.yaml +12 -0
- package/example-k8s/chant.config.ts +10 -0
- package/example-k8s/overlays/dev/kustomization.yaml +6 -0
- package/example-k8s/package-lock.json +18 -18
- package/example-k8s/package.json +3 -3
- package/example-writes/package-lock.json +14 -14
- package/example-writes/package.json +3 -3
- package/package.json +9 -6
- package/web/app.js +1027 -42
- package/web/carve-steps.js +610 -0
- package/web/carve-steps.test.js +233 -0
- package/web/demos.js +71 -0
- package/web/demos.test.js +83 -0
- package/web/icons/cncf/argo.svg +1 -0
- package/web/icons/cncf/flux.svg +1 -0
- package/web/icons/cncf/helm.svg +1 -0
- package/web/icons/k8s/c-role.svg +1 -0
- package/web/icons/k8s/cm.svg +1 -0
- package/web/icons/k8s/crb.svg +1 -0
- package/web/icons/k8s/crd.svg +1 -0
- package/web/icons/k8s/cronjob.svg +1 -0
- package/web/icons/k8s/deploy.svg +1 -0
- package/web/icons/k8s/ds.svg +1 -0
- package/web/icons/k8s/ep.svg +1 -0
- package/web/icons/k8s/group.svg +1 -0
- package/web/icons/k8s/hpa.svg +1 -0
- package/web/icons/k8s/ing.svg +1 -0
- package/web/icons/k8s/job.svg +1 -0
- package/web/icons/k8s/limits.svg +1 -0
- package/web/icons/k8s/netpol.svg +1 -0
- package/web/icons/k8s/ns.svg +1 -0
- package/web/icons/k8s/pod.svg +1 -0
- package/web/icons/k8s/psp.svg +1 -0
- package/web/icons/k8s/pv.svg +1 -0
- package/web/icons/k8s/pvc.svg +1 -0
- package/web/icons/k8s/quota.svg +1 -0
- package/web/icons/k8s/rb.svg +1 -0
- package/web/icons/k8s/role.svg +1 -0
- package/web/icons/k8s/rs.svg +1 -0
- package/web/icons/k8s/sa.svg +1 -0
- package/web/icons/k8s/sc.svg +1 -0
- package/web/icons/k8s/secret.svg +1 -0
- package/web/icons/k8s/sts.svg +1 -0
- package/web/icons/k8s/svc.svg +1 -0
- package/web/icons/k8s/user.svg +1 -0
- package/web/icons/k8s/vol.svg +1 -0
- package/web/index.html +285 -85
- package/web/json-view.js +334 -0
- package/web/json-view.test.js +218 -0
- package/web/layout-store.js +278 -0
- package/web/layout-store.test.js +388 -0
- package/web/panel.js +28 -0
- package/web/theme.js +114 -6
package/web/index.html
CHANGED
|
@@ -5,14 +5,42 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>behold</title>
|
|
7
7
|
<style>
|
|
8
|
+
/* Pre-paint fallback (#229). These are the DEFAULT theme's tokens —
|
|
9
|
+
Catppuccin Mocha run through theme.js's tokensFor() — not a palette of
|
|
10
|
+
their own, so the first frame is already the frame initTheme() lands
|
|
11
|
+
on and there is no flash of a different app. (It used to be GitHub
|
|
12
|
+
Primer dark, which is why behold's first paint read as every other
|
|
13
|
+
generated tool.) The ONLY hardcoded colours in the whole stylesheet
|
|
14
|
+
live in this block; smoke/ui-smoke.mjs recomputes them from theme.js
|
|
15
|
+
and fails if the two drift apart. */
|
|
8
16
|
:root {
|
|
9
|
-
--bg: #
|
|
10
|
-
--
|
|
11
|
-
--degraded: #
|
|
17
|
+
--bg: #1e1e2e; --panel: #2e2f40; --line: #3b3c4a; --fg: #cdd6f4; --muted: #777a8c;
|
|
18
|
+
--edge: #56596d; --managed: #a6e3a1; --foreign: #f9e2af; --pending: #89b4fa;
|
|
19
|
+
--degraded: #f38ba8; --neutral: #6d7187; --runtime: #94e2d5;
|
|
20
|
+
--rule: #4e4f64; --focus: #a3b4d0; --active: #3d4660; --well: #11101f; --shadow: #050507;
|
|
21
|
+
}
|
|
22
|
+
/* The non-colour half of the identity: type and radii. These can't derive
|
|
23
|
+
from a colour theme, so they're the one static token block — but they
|
|
24
|
+
ARE tokens, and nothing below hardcodes a size or a radius.
|
|
25
|
+
Type: a terminal-adjacent control plane, so mono does what mono is for —
|
|
26
|
+
ids, addresses, statuses, counts, the diagnosis lines — and the sans
|
|
27
|
+
carries labels only. Three sizes, not five near-identical ones.
|
|
28
|
+
(No vendored typeface: behold ships no binaries. A licensed .woff2 via
|
|
29
|
+
a release asset is the follow-up if the system stacks aren't enough.)
|
|
30
|
+
Radii: sharp on controls, soft on the shells they sit in. */
|
|
31
|
+
:root {
|
|
32
|
+
--font-ui: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
33
|
+
--font-mono: ui-monospace, "SF Mono", SFMono-Regular, "Cascadia Code", "JetBrains Mono", "IBM Plex Mono", Menlo, Consolas, monospace;
|
|
34
|
+
--t-display: 15px;
|
|
35
|
+
--t-body: 12.5px;
|
|
36
|
+
--t-caption: 10.5px;
|
|
37
|
+
--t-caps: .07em;
|
|
38
|
+
--r-ctl: 3px;
|
|
39
|
+
--r-shell: 10px;
|
|
12
40
|
}
|
|
13
41
|
* { box-sizing: border-box; }
|
|
14
42
|
html, body { margin: 0; height: 100%; background: var(--bg); color: var(--fg);
|
|
15
|
-
font:
|
|
43
|
+
font: var(--t-body)/1.45 var(--font-ui); }
|
|
16
44
|
/* #186: no header row at all — the graph runs full-bleed; every piece
|
|
17
45
|
of state the header used to carry (meta line, statusbar) lives in the
|
|
18
46
|
floating panel's footer instead. */
|
|
@@ -21,31 +49,45 @@
|
|
|
21
49
|
#app.inspect-collapsed { grid-template-columns: 1fr 0; }
|
|
22
50
|
#app.inspect-collapsed #inspect { display: none; }
|
|
23
51
|
#graph { grid-row: 1; grid-column: 1; overflow: auto; position: relative; }
|
|
24
|
-
#inspect { grid-row: 1; grid-column: 2; border-left: 1px solid var(--
|
|
52
|
+
#inspect { grid-row: 1; grid-column: 2; border-left: 1px solid var(--rule); background: var(--panel);
|
|
25
53
|
padding: 14px; overflow: auto; position: relative; }
|
|
26
|
-
|
|
27
|
-
|
|
54
|
+
/* Section labels are the sans voice: small caps, tracked, muted. Every
|
|
55
|
+
heading in this pane (identity, live, drift, field ownership — app.js
|
|
56
|
+
appends them) is styled here rather than inline, so the scale stays in
|
|
57
|
+
one place. */
|
|
58
|
+
#inspect h2, #inspect h3 { color: var(--muted); font-size: var(--t-caption); font-weight: 600;
|
|
59
|
+
text-transform: uppercase; letter-spacing: var(--t-caps); }
|
|
60
|
+
#inspect h2 { margin: 0 0 10px; }
|
|
61
|
+
#inspect h3 { margin: 16px 0 6px; }
|
|
28
62
|
/* Declared/identity: key ABOVE value, value full-width — long ARNs wrap
|
|
29
|
-
readably instead of being crushed into a squeezed right column.
|
|
63
|
+
readably instead of being crushed into a squeezed right column. The
|
|
64
|
+
VALUE is data — an id, an ARN, a physical id, a status — so it's mono;
|
|
65
|
+
the key stays sans. */
|
|
30
66
|
#inspect dl { margin: 0; }
|
|
31
|
-
#inspect dt { color: var(--muted); font-size:
|
|
32
|
-
#inspect dd { margin:
|
|
67
|
+
#inspect dt { color: var(--muted); font-size: var(--t-caption); margin-top: 9px; }
|
|
68
|
+
#inspect dd { margin: 2px 0 0; font: var(--t-body)/1.45 var(--font-mono); overflow-wrap: anywhere; }
|
|
69
|
+
#inspect p { font-size: var(--t-body); }
|
|
70
|
+
/* #192: the queried-address diagnosis line — a request path, so mono. */
|
|
71
|
+
#inspect p.queried { color: var(--muted); font: var(--t-caption)/1.45 var(--font-mono);
|
|
72
|
+
overflow-wrap: anywhere; margin-top: 2px; }
|
|
33
73
|
/* Drag handle on the pane's left edge → resize; chevron → collapse. */
|
|
34
74
|
#inspect-resize { position: absolute; left: -3px; top: 0; bottom: 0; width: 7px; cursor: col-resize; z-index: 6; }
|
|
35
|
-
#inspect-resize:hover { background: var(--
|
|
75
|
+
#inspect-resize:hover { background: var(--focus); opacity: .5; }
|
|
36
76
|
#inspect-collapse { position: absolute; top: 12px; right: 12px; z-index: 6; background: none;
|
|
37
|
-
border: none; color: var(--muted); font-size:
|
|
77
|
+
border: none; color: var(--muted); font-size: var(--t-display); cursor: pointer; line-height: 1; padding: 2px; }
|
|
38
78
|
#inspect-collapse:hover { color: var(--fg); }
|
|
39
79
|
#inspect-reopen { position: fixed; right: 0; top: 50%; transform: translateY(-50%); z-index: 30; display: none;
|
|
40
|
-
background: var(--panel); color: var(--muted); border: 1px solid var(--
|
|
41
|
-
border-radius:
|
|
42
|
-
|
|
80
|
+
background: var(--panel); color: var(--muted); border: 1px solid var(--rule); border-right: none;
|
|
81
|
+
border-radius: var(--r-ctl) 0 0 var(--r-ctl); padding: 10px 5px; cursor: pointer;
|
|
82
|
+
writing-mode: vertical-rl; font-size: var(--t-caption); letter-spacing: var(--t-caps); }
|
|
83
|
+
#inspect-reopen:hover { color: var(--fg); border-color: var(--focus); }
|
|
43
84
|
#app.inspect-collapsed #inspect-reopen { display: block; }
|
|
44
85
|
/* The graph SVG is pinhole's, self-styled. It fills the pane; behold drives
|
|
45
86
|
zoom/pan by mutating its viewBox (see setupPanZoom in app.js) — pinch or
|
|
46
87
|
⌘/Ctrl+scroll to zoom at the cursor, drag to pan. It starts fit-to-pane;
|
|
47
88
|
the "⤢ fit" button resets. behold also adds a selection highlight. */
|
|
48
|
-
#graph svg { display: block; width: 100%; height: 100%; cursor: grab; touch-action: none;
|
|
89
|
+
#graph svg { display: block; width: 100%; height: 100%; cursor: grab; touch-action: none;
|
|
90
|
+
user-select: none; -webkit-user-select: none; }
|
|
49
91
|
#graph svg.grabbing { cursor: grabbing; }
|
|
50
92
|
/* Edge hover-highlight — trace a single edge through an overlapping bundle.
|
|
51
93
|
pinhole already paints a fat transparent hit-path per edge (pointer-events
|
|
@@ -53,6 +95,28 @@
|
|
|
53
95
|
visible line. The hovered edge is also raised to the top in JS so it isn't
|
|
54
96
|
buried under the bundle. */
|
|
55
97
|
#graph g[data-edge-from] .pin-edge-line { transition: stroke .08s ease, stroke-width .08s ease; }
|
|
98
|
+
/* ---- Motion (#229) ---------------------------------------------------
|
|
99
|
+
behold spends motion exactly twice, and never for decoration:
|
|
100
|
+
1. the graph fades through on every re-render, so a refresh reads as a
|
|
101
|
+
new frame instead of a flicker (the SVG element is replaced whole,
|
|
102
|
+
so the animation fires once per render by construction);
|
|
103
|
+
2. a node whose drift `_status` CHANGED since the last render glows
|
|
104
|
+
once in its new status colour — the diff is a module-level
|
|
105
|
+
id → _status map in app.js (markStatusChanges), and `--pulse` is
|
|
106
|
+
set per node to the status hue it just became.
|
|
107
|
+
Both are off entirely under prefers-reduced-motion. */
|
|
108
|
+
#graph svg { animation: graph-fade-through .18s ease-out; }
|
|
109
|
+
@keyframes graph-fade-through { from { opacity: 0; } to { opacity: 1; } }
|
|
110
|
+
#graph [data-node-id].status-changed { animation: status-pulse .85s ease-out 2; }
|
|
111
|
+
@keyframes status-pulse {
|
|
112
|
+
0%, 100% { filter: drop-shadow(0 0 0 var(--pulse, var(--pending))); }
|
|
113
|
+
45% { filter: drop-shadow(0 0 7px var(--pulse, var(--pending))); }
|
|
114
|
+
}
|
|
115
|
+
@media (prefers-reduced-motion: reduce) {
|
|
116
|
+
#graph svg, #graph [data-node-id].status-changed { animation: none; }
|
|
117
|
+
#graph g[data-edge-from] .pin-edge-line { transition: none; }
|
|
118
|
+
#graph [data-layout-resize] { transition: none; }
|
|
119
|
+
}
|
|
56
120
|
#graph g[data-edge-from]:hover .pin-edge-line,
|
|
57
121
|
#graph g[data-edge-from].edge-hi .pin-edge-line { stroke: var(--pending); stroke-width: 3.2; }
|
|
58
122
|
/* Edge kind by reason (logical view): a data dependency reads as a dashed
|
|
@@ -62,15 +126,27 @@
|
|
|
62
126
|
#graph g[data-edge-from][data-edge-via] { cursor: help; }
|
|
63
127
|
/* Fit reset + a zoom hint, floating top-right of the graph pane. */
|
|
64
128
|
#zoom-toggle { position: absolute; top: 8px; right: 8px; z-index: 2; background: var(--panel);
|
|
65
|
-
color: var(--muted); border: 1px solid var(--
|
|
66
|
-
font
|
|
67
|
-
#zoom-toggle:hover { border-color: var(--
|
|
129
|
+
color: var(--muted); border: 1px solid var(--rule); border-radius: var(--r-ctl); padding: 4px 10px;
|
|
130
|
+
font: var(--t-caption)/1.3 var(--font-mono); cursor: pointer; }
|
|
131
|
+
#zoom-toggle:hover { border-color: var(--focus); color: var(--fg); }
|
|
132
|
+
/* Hand layout (#228): the escape hatch, beside ⤢ fit. Hidden by app.js
|
|
133
|
+
until something on this lens is actually hand-placed — it is both the
|
|
134
|
+
reset and the only indication that dagre isn't the whole story here. */
|
|
135
|
+
#layout-reset { position: absolute; top: 8px; right: 66px; z-index: 2; background: var(--panel);
|
|
136
|
+
color: var(--muted); border: 1px solid var(--rule); border-radius: var(--r-ctl); padding: 4px 10px;
|
|
137
|
+
font: var(--t-caption)/1.3 var(--font-mono); cursor: pointer; }
|
|
138
|
+
#layout-reset:hover { border-color: var(--focus); color: var(--fg); }
|
|
139
|
+
/* A containment box's title moves it and its corner handle resizes it.
|
|
140
|
+
The handle carries opacity="0" as a presentation attribute so a static
|
|
141
|
+
export never shows it — this rule outranks the attribute. */
|
|
142
|
+
#graph [data-layout-resize] { opacity: 0; transition: opacity .1s ease; }
|
|
143
|
+
#graph [data-layout-box]:hover [data-layout-resize] { opacity: .85; }
|
|
68
144
|
/* The way out of waves view, floating top-LEFT so it sits where the eye
|
|
69
145
|
already is when observe drops you into the wave-laned graph. */
|
|
70
146
|
#back-to-infra { position: absolute; top: 8px; left: 8px; z-index: 2; background: var(--panel);
|
|
71
|
-
color: var(--muted); border: 1px solid var(--
|
|
72
|
-
font
|
|
73
|
-
#back-to-infra:hover { border-color: var(--
|
|
147
|
+
color: var(--muted); border: 1px solid var(--rule); border-radius: var(--r-ctl); padding: 4px 10px;
|
|
148
|
+
font: var(--t-caption)/1.3 var(--font-mono); cursor: pointer; }
|
|
149
|
+
#back-to-infra:hover { border-color: var(--focus); color: var(--fg); }
|
|
74
150
|
/* Blocking load scrim: covers everything (clicks can't pass through) while
|
|
75
151
|
a live view change is in flight. */
|
|
76
152
|
#loading-overlay { position: fixed; inset: 0; z-index: 100; display: flex;
|
|
@@ -78,11 +154,14 @@
|
|
|
78
154
|
background: color-mix(in srgb, var(--bg) 72%, transparent); backdrop-filter: blur(1.5px);
|
|
79
155
|
cursor: progress; }
|
|
80
156
|
#loading-overlay[hidden] { display: none; }
|
|
81
|
-
#loading-overlay #loading-msg { font
|
|
157
|
+
#loading-overlay #loading-msg { font: var(--t-body)/1.4 var(--font-mono); color: var(--muted); }
|
|
82
158
|
#loading-overlay .spinner { width: 34px; height: 34px; border-radius: 50%;
|
|
83
|
-
border: 3px solid var(--line); border-top-color: var(--
|
|
159
|
+
border: 3px solid var(--line); border-top-color: var(--focus);
|
|
84
160
|
animation: spin 0.7s linear infinite; }
|
|
85
161
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
162
|
+
/* Selection is one of the few places the FULL accent still shows — the
|
|
163
|
+
drift hues own saturation now, and "which node am I looking at" earns
|
|
164
|
+
one of them. */
|
|
86
165
|
.sel rect, .sel path, .sel > rect { stroke: var(--pending) !important; stroke-width: 2.5 !important; }
|
|
87
166
|
.err { padding: 24px; color: var(--foreign); }
|
|
88
167
|
/* #72: a classified precondition failure (chant's lint gate, a project
|
|
@@ -91,16 +170,65 @@
|
|
|
91
170
|
are usually expected states on a real (imperfect) project, not bugs.
|
|
92
171
|
Generalizes the old M2 (#54) tier-only `.tier-note`. */
|
|
93
172
|
.precondition-error { padding: 24px; max-width: 640px; }
|
|
94
|
-
.precondition-error-title { color: var(--fg); font-size:
|
|
95
|
-
.precondition-error-message { color: var(--muted); white-space: pre-wrap; font:
|
|
96
|
-
.precondition-error-remedy { color: var(--pending); margin-top: 14px; }
|
|
173
|
+
.precondition-error-title { color: var(--fg); font-size: var(--t-display); font-weight: 600; margin-bottom: 10px; }
|
|
174
|
+
.precondition-error-message { color: var(--muted); white-space: pre-wrap; font: var(--t-body)/1.55 var(--font-mono); }
|
|
175
|
+
.precondition-error-remedy { color: var(--pending); margin-top: 14px; font: var(--t-body)/1.5 var(--font-mono); }
|
|
97
176
|
.precondition-error-remedy::before { content: "→ "; }
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
177
|
+
/* #259: the raw /api payload behind the card, collapsed. */
|
|
178
|
+
.precondition-error-raw { margin-top: 18px; border-top: 1px solid var(--line); padding-top: 10px; }
|
|
179
|
+
.precondition-error-rawlabel { color: var(--muted); font-size: var(--t-caption); text-transform: uppercase;
|
|
180
|
+
letter-spacing: var(--t-caps); margin-bottom: 5px; }
|
|
181
|
+
#actions button, #inspect button { background: var(--panel); color: var(--fg); border: 1px solid var(--rule);
|
|
182
|
+
border-radius: var(--r-ctl); padding: 4px 12px; font-size: var(--t-body); cursor: pointer; }
|
|
183
|
+
#actions button:hover, #inspect button:hover { border-color: var(--focus); }
|
|
101
184
|
#actions button.approve { border-color: var(--managed); color: var(--managed); }
|
|
102
185
|
#inspect button { border-color: var(--foreign); color: var(--foreign); }
|
|
103
186
|
|
|
187
|
+
/* ---- Collapsible JSON (#259) -----------------------------------------
|
|
188
|
+
web/json-view.js builds this; nothing else styles it. Two colours and
|
|
189
|
+
one font: keys and scalars are --fg (the data), every bracket, comma,
|
|
190
|
+
chevron, count and affordance is --muted (the punctuation around it).
|
|
191
|
+
The status hues are deliberately NOT used — they carry meaning in this
|
|
192
|
+
pane, and unlike --fg/--muted they clear no contrast floor across the
|
|
193
|
+
552 palettes (#240), so a "strings are green" scheme would be
|
|
194
|
+
unreadable on some of them.
|
|
195
|
+
Indent is 2ch — the same two spaces the copied text carries — with a
|
|
196
|
+
--line rule down it so a deep tree still tracks by eye. */
|
|
197
|
+
.jsonv { font: var(--t-body)/1.5 var(--font-mono); color: var(--fg); overflow-wrap: anywhere; }
|
|
198
|
+
.jsonv-children { padding-left: 2ch; border-left: 1px solid var(--line); }
|
|
199
|
+
.jsonv-node[data-open="0"] > .jsonv-children,
|
|
200
|
+
.jsonv-node[data-open="0"] > .jsonv-tail { display: none; }
|
|
201
|
+
.jsonv-node[data-open="1"] > .jsonv-line .jsonv-summary,
|
|
202
|
+
.jsonv-node[data-open="1"] > .jsonv-line .jsonv-close-inline { display: none; }
|
|
203
|
+
.jsonv-key { color: var(--fg); }
|
|
204
|
+
.jsonv-punct, .jsonv-summary, .jsonv-toggle, .jsonv-null { color: var(--muted); }
|
|
205
|
+
.jsonv-head { cursor: pointer; border-radius: var(--r-ctl); }
|
|
206
|
+
.jsonv-head:hover .jsonv-toggle { color: var(--fg); }
|
|
207
|
+
.jsonv-toggle { display: inline-block; width: 1.2ch; user-select: none; }
|
|
208
|
+
/* The two affordances stay quiet until the row is under the pointer or a
|
|
209
|
+
keyboard focus is inside it — a tree of visible "copy" links reads as
|
|
210
|
+
chrome, not as data. */
|
|
211
|
+
.jsonv-copy, .jsonv-more { color: var(--muted); font-size: var(--t-caption); cursor: pointer;
|
|
212
|
+
margin-left: 8px; user-select: none; }
|
|
213
|
+
.jsonv-copy { opacity: 0; }
|
|
214
|
+
.jsonv-line:hover > .jsonv-copy, .jsonv-copy:focus-visible, .jsonv-copy[data-copied] { opacity: 1; }
|
|
215
|
+
.jsonv-copy:hover, .jsonv-more:hover { color: var(--fg); }
|
|
216
|
+
.jsonv-copy[data-copied="1"] { color: var(--managed); }
|
|
217
|
+
.jsonv-copy[data-copied="0"] { color: var(--degraded); }
|
|
218
|
+
.jsonv-head:focus-visible, .jsonv-copy:focus-visible, .jsonv-more:focus-visible {
|
|
219
|
+
outline: 1px solid var(--focus); outline-offset: 1px; }
|
|
220
|
+
/* In the inspect pane the tree replaces a <dd>'s text, so it inherits the
|
|
221
|
+
pane's own mono scale and needs no extra gap. */
|
|
222
|
+
#inspect dd > .jsonv { margin-top: 1px; }
|
|
223
|
+
/* A value PAIR (was/now, declared/live, baseline/live) whose sides are
|
|
224
|
+
trees rather than scalars — see pairCell() in app.js. */
|
|
225
|
+
#inspect .pair-row { display: flex; align-items: baseline; gap: 6px; margin-top: 3px; }
|
|
226
|
+
#inspect .pair-row > .jsonv { flex: 1; min-width: 0; }
|
|
227
|
+
#inspect .pair-label { color: var(--muted); font-size: var(--t-caption); flex: none; }
|
|
228
|
+
/* The op log's JSON lines (#259): a report chant emitted as an object
|
|
229
|
+
reads as a tree in the now-line, not as one 900-column line. */
|
|
230
|
+
#nowline .jsonv { white-space: normal; margin: 2px 0; }
|
|
231
|
+
|
|
104
232
|
/* ---- Floating control panel ------------------------------------------
|
|
105
233
|
An Adobe-style palette holding the controls that used to line the
|
|
106
234
|
header and its strips: zoom, scope lenses, substrate pills, the model
|
|
@@ -113,64 +241,85 @@
|
|
|
113
241
|
is the discoverable surface, the palette the fast one. Sits above the
|
|
114
242
|
graph's own floating buttons (z 2) and below toasts (60) / the load
|
|
115
243
|
scrim (100) / ⌘K (110). */
|
|
244
|
+
/* #229: the shell is the soft thing (10px, a real drop shadow derived
|
|
245
|
+
from the theme, a palette-tinted --rule border); everything INSIDE it
|
|
246
|
+
is sharp (3px). That contrast is what makes it read as a tool palette
|
|
247
|
+
rather than a card of stacked pills. */
|
|
116
248
|
#panel { position: fixed; width: 272px; z-index: 40; background: var(--panel);
|
|
117
|
-
border: 1px solid var(--
|
|
118
|
-
box-shadow: 0 10px
|
|
249
|
+
border: 1px solid var(--rule); border-radius: var(--r-shell); display: flex; flex-direction: column;
|
|
250
|
+
box-shadow: 0 10px 30px color-mix(in srgb, var(--shadow) 55%, transparent); user-select: none; }
|
|
119
251
|
#panel.dragging { opacity: .95; }
|
|
120
252
|
/* #184: tabs-only — the strip doubles as the drag handle and carries the
|
|
121
253
|
⌘K pill + collapse chevron. Collapsing hides only the body, so the
|
|
122
|
-
panel collapses TO the strip.
|
|
254
|
+
panel collapses TO the strip.
|
|
255
|
+
#229: the strip is slightly recessed off the body, and the active tab
|
|
256
|
+
is a FILL, not an underline — an underlined tab is the single most
|
|
257
|
+
generated-looking control on the web. */
|
|
123
258
|
#panel-tabs { display: flex; align-items: stretch; border-bottom: 1px solid var(--line);
|
|
124
|
-
|
|
259
|
+
background: color-mix(in srgb, var(--well) 45%, var(--panel));
|
|
260
|
+
border-radius: calc(var(--r-shell) - 1px) calc(var(--r-shell) - 1px) 0 0;
|
|
261
|
+
cursor: grab; padding: 3px 4px 3px 3px; gap: 2px; }
|
|
125
262
|
#panel.dragging #panel-tabs { cursor: grabbing; }
|
|
126
|
-
#panel.collapsed #panel-tabs { border-bottom: none; }
|
|
263
|
+
#panel.collapsed #panel-tabs { border-bottom: none; border-radius: calc(var(--r-shell) - 1px); }
|
|
127
264
|
#panel.collapsed #panel-body { display: none; }
|
|
128
|
-
#panel-tabs button[data-tab] { flex: 1; background: none; border: none; border-
|
|
129
|
-
color: var(--muted); font
|
|
265
|
+
#panel-tabs button[data-tab] { flex: 1; background: none; border: none; border-radius: var(--r-ctl);
|
|
266
|
+
color: var(--muted); font: var(--t-caption)/1 var(--font-ui); padding: 6px 0; cursor: pointer; }
|
|
130
267
|
#panel-tabs button[data-tab]:hover { color: var(--fg); }
|
|
131
|
-
#panel-tabs button[data-tab].active { color: var(--fg);
|
|
132
|
-
#panel-tabs #hintk { flex: none; align-self: center;
|
|
268
|
+
#panel-tabs button[data-tab].active { color: var(--fg); background: var(--active); font-weight: 600; }
|
|
269
|
+
#panel-tabs #hintk { flex: none; align-self: center; padding: 2px 6px; margin-left: 2px; }
|
|
133
270
|
#panel-collapse { flex: none; align-self: center; background: none; border: none; color: var(--muted);
|
|
134
|
-
font-size:
|
|
271
|
+
font-size: var(--t-body); cursor: pointer; line-height: 1; padding: 2px 4px; }
|
|
135
272
|
#panel-collapse:hover { color: var(--fg); }
|
|
136
273
|
#panel-body { overflow-y: auto; max-height: min(62vh, 540px); padding: 10px 12px;
|
|
137
|
-
font-size:
|
|
274
|
+
font-size: var(--t-body); user-select: text; }
|
|
138
275
|
#panel-body section { display: none; }
|
|
139
276
|
#panel-body section.active { display: block; }
|
|
140
|
-
#panel h3 { margin: 14px 0 6px; font-size:
|
|
141
|
-
color: var(--muted); font-weight: 600; }
|
|
277
|
+
#panel h3 { margin: 14px 0 6px; font-size: var(--t-caption); text-transform: uppercase;
|
|
278
|
+
letter-spacing: var(--t-caps); color: var(--muted); font-weight: 600; }
|
|
142
279
|
#panel h3:first-child { margin-top: 0; }
|
|
143
|
-
#panel .panel-muted { color: var(--muted); font
|
|
280
|
+
#panel .panel-muted { color: var(--muted); font: var(--t-caption)/1.5 var(--font-mono);
|
|
281
|
+
margin: 4px 0; overflow-wrap: anywhere; }
|
|
144
282
|
#panel .prow { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 2px 0; }
|
|
145
|
-
/* A radio-like option row: the current value
|
|
283
|
+
/* A radio-like option row: the current value is FILLED, with a hairline of
|
|
284
|
+
the full accent down its leading edge — the one saturated mark in the
|
|
285
|
+
chrome, so the eye finds the current lens without the whole row
|
|
286
|
+
shouting in accent-coloured text. */
|
|
146
287
|
#panel .opt { display: block; width: 100%; text-align: left; background: none;
|
|
147
|
-
border: 1px solid transparent; border-radius:
|
|
148
|
-
padding: 4px 8px; cursor: pointer; }
|
|
149
|
-
#panel .opt:hover { border-color: var(--line); }
|
|
150
|
-
#
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
#panel
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
#panel
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
288
|
+
border: 1px solid transparent; border-radius: var(--r-ctl); color: var(--fg);
|
|
289
|
+
font-size: var(--t-body); padding: 4px 8px; cursor: pointer; }
|
|
290
|
+
#panel .opt:hover:not(:disabled) { border-color: var(--line); }
|
|
291
|
+
/* #268: a demo whose prerequisites are missing stays visible and says why
|
|
292
|
+
— dimmed and unclickable, not removed from the catalog. */
|
|
293
|
+
#panel .opt:disabled { opacity: .5; cursor: not-allowed; }
|
|
294
|
+
#panel .opt.active { background: var(--active); border-color: var(--rule); color: var(--fg);
|
|
295
|
+
box-shadow: inset 2px 0 0 var(--pending); }
|
|
296
|
+
#panel select { width: 100%; background: var(--well); color: var(--fg); border: 1px solid var(--line);
|
|
297
|
+
border-radius: var(--r-ctl); padding: 4px 8px; font-size: var(--t-body); cursor: pointer; }
|
|
298
|
+
#panel select:hover { border-color: var(--focus); }
|
|
299
|
+
#panel button.act { background: var(--well); color: var(--fg); border: 1px solid var(--rule);
|
|
300
|
+
border-radius: var(--r-ctl); padding: 3px 10px; font-size: var(--t-caption); cursor: pointer; }
|
|
301
|
+
#panel button.act:hover { border-color: var(--focus); }
|
|
302
|
+
#panel input.panel-input { flex: 1; min-width: 0; background: var(--well); color: var(--fg);
|
|
303
|
+
border: 1px solid var(--line); border-radius: var(--r-ctl); padding: 4px 8px;
|
|
304
|
+
font: var(--t-caption)/1.4 var(--font-mono); }
|
|
305
|
+
#panel input.panel-input:focus { border-color: var(--focus); outline: none; }
|
|
306
|
+
/* Model tab rows: legend counts (static) and clickable node rows. Node
|
|
307
|
+
ids are data — mono, so they align and scan like a terminal listing. */
|
|
161
308
|
#panel .count-row, #panel .node-row { display: flex; align-items: center; gap: 8px; padding: 2px 0; }
|
|
162
|
-
#panel .node-row { cursor: pointer; border-radius:
|
|
163
|
-
|
|
309
|
+
#panel .node-row { cursor: pointer; border-radius: var(--r-ctl); padding: 3px 6px;
|
|
310
|
+
font: var(--t-caption)/1.5 var(--font-mono); }
|
|
311
|
+
#panel .node-row:hover { background: var(--well); }
|
|
164
312
|
#panel .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; display: inline-block; }
|
|
165
313
|
#panel .grow { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
166
|
-
#panel .tag { color: var(--muted); font
|
|
314
|
+
#panel .tag { color: var(--muted); font: var(--t-caption)/1.5 var(--font-mono); white-space: nowrap; }
|
|
167
315
|
|
|
168
316
|
/* Substrate readiness (M5, #54) — one row per substrate on the panel's
|
|
169
317
|
Substrates tab: status dot + name + state, with its actions (bring up /
|
|
170
318
|
reset / run pipeline) visible inline again. #73 moved those into ⌘K;
|
|
171
319
|
they remain there too — same actions, two surfaces. */
|
|
172
320
|
#substrates { display: flex; flex-direction: column; gap: 2px; }
|
|
173
|
-
.sub { display: flex; align-items: center; gap: 8px; padding: 3px 0;
|
|
321
|
+
.sub { display: flex; align-items: center; gap: 8px; padding: 3px 0;
|
|
322
|
+
font: var(--t-caption)/1.6 var(--font-mono); }
|
|
174
323
|
.sub .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
|
|
175
324
|
.sub.up .dot { background: var(--managed); }
|
|
176
325
|
.sub.down .dot { background: var(--degraded); }
|
|
@@ -184,34 +333,81 @@
|
|
|
184
333
|
tab now, stacked vertically under the deploy actions. */
|
|
185
334
|
#actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
|
|
186
335
|
#dial { display: none; flex-direction: column; align-items: stretch; gap: 8px;
|
|
187
|
-
font-size:
|
|
336
|
+
font-size: var(--t-body); margin-top: 10px; }
|
|
188
337
|
.dial-track { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
|
|
189
338
|
.dial-arrow { color: var(--muted); }
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
.dial-step:
|
|
193
|
-
|
|
339
|
+
/* The lifecycle steps are controls, so they take the control radius too —
|
|
340
|
+
the pill shape was the last soft edge left inside the shell. */
|
|
341
|
+
.dial-step { background: var(--well); color: var(--fg); border: 1px solid var(--rule);
|
|
342
|
+
border-radius: var(--r-ctl); padding: 3px 12px; font-size: var(--t-body); cursor: pointer; }
|
|
343
|
+
.dial-step:hover:not(:disabled) { border-color: var(--focus); }
|
|
344
|
+
.dial-step.active { background: var(--active); border-color: var(--rule); color: var(--fg);
|
|
345
|
+
box-shadow: inset 2px 0 0 var(--pending); }
|
|
194
346
|
.dial-step:disabled { opacity: .5; cursor: not-allowed; }
|
|
195
|
-
.dial-detail { display: flex; flex-wrap: wrap; gap: 4px 12px; color: var(--muted);
|
|
347
|
+
.dial-detail { display: flex; flex-wrap: wrap; gap: 4px 12px; color: var(--muted);
|
|
348
|
+
font: var(--t-caption)/1.6 var(--font-mono); }
|
|
349
|
+
|
|
350
|
+
/* The carve walkthrough (#254) — the panel's Carve tab. Six steps on the
|
|
351
|
+
same track the deploy dial uses, so "where am I in this" reads the same
|
|
352
|
+
way in both places; below it, the step's own panel. Every colour here is
|
|
353
|
+
a shared token, so all 552 palettes get it for free. */
|
|
354
|
+
#tab-carve { display: flex; flex-direction: column; gap: 10px; }
|
|
355
|
+
#tab-carve .dial-track { gap: 4px; }
|
|
356
|
+
.carve-step { padding: 3px 8px; font-size: var(--t-caption); }
|
|
357
|
+
.carve-step.done { color: var(--managed); border-color: color-mix(in srgb, var(--managed) 45%, var(--rule)); }
|
|
358
|
+
.carve-step.blocked { opacity: .55; }
|
|
359
|
+
.carve-body { display: flex; flex-direction: column; gap: 6px; }
|
|
360
|
+
/* Data — chant's output, a file, a command — is mono and boxed; prose
|
|
361
|
+
about it stays in the panel's own voice. */
|
|
362
|
+
.carve-pre { margin: 0; background: var(--well); border: 1px solid var(--line);
|
|
363
|
+
border-radius: var(--r-ctl); padding: 6px 8px; max-height: 240px; overflow: auto;
|
|
364
|
+
font: var(--t-caption)/1.5 var(--font-mono); color: var(--fg); white-space: pre-wrap;
|
|
365
|
+
overflow-wrap: anywhere; }
|
|
366
|
+
.carve-pre.carve-cmd { color: var(--muted); }
|
|
367
|
+
.carve-artifact { display: flex; flex-direction: column; gap: 3px; }
|
|
368
|
+
.carve-path { font: var(--t-caption)/1.5 var(--font-mono); overflow-wrap: anywhere; }
|
|
369
|
+
.carve-cut, .carve-honesty { margin: 2px 0; }
|
|
370
|
+
/* The one thing the walkthrough will not do for you, and the refusals it
|
|
371
|
+
hands back — same card, because they are the same kind of statement. */
|
|
372
|
+
.carve-refusal { border: 1px solid var(--degraded); border-left: 3px solid var(--degraded);
|
|
373
|
+
border-radius: var(--r-ctl); padding: 6px 8px; background: var(--well); }
|
|
374
|
+
.carve-refusal.carve-human { border-color: var(--pending); border-left-color: var(--pending); }
|
|
375
|
+
.carve-refusal-title { font-size: var(--t-body); margin-bottom: 2px; }
|
|
376
|
+
.carve-cmd-row { align-items: center; gap: 6px; }
|
|
377
|
+
.carve-cmd-text { font: var(--t-caption)/1.6 var(--font-mono); background: var(--well);
|
|
378
|
+
border: 1px solid var(--line); border-radius: var(--r-ctl); padding: 3px 6px;
|
|
379
|
+
overflow-wrap: anywhere; }
|
|
380
|
+
.carve-cmd-note, .carve-section { margin: 2px 0; }
|
|
381
|
+
.carve-endcard { border: 1px solid var(--managed); border-left: 3px solid var(--managed);
|
|
382
|
+
border-radius: var(--r-ctl); padding: 6px 8px; background: var(--well); }
|
|
383
|
+
.carve-endcard-title { font-size: var(--t-body); margin-bottom: 2px; }
|
|
384
|
+
.carve-caveat > summary { cursor: pointer; color: var(--muted);
|
|
385
|
+
font: var(--t-caption)/1.6 var(--font-mono); }
|
|
386
|
+
/* The still frame of the morph the follow-up animates: a carved card
|
|
387
|
+
wears the managed edge its new owner paints everything with. */
|
|
388
|
+
#graph [data-node-id].carved rect:first-of-type { stroke: var(--managed); stroke-width: 2; }
|
|
196
389
|
|
|
197
390
|
#nowline { grid-column: 1 / 3; grid-row: 2; margin: 0; max-height: 160px; overflow: auto; display: none;
|
|
198
|
-
background:
|
|
199
|
-
color: var(--fg); white-space: pre-wrap; }
|
|
391
|
+
background: var(--well); border-top: 1px solid var(--rule); padding: 8px 12px;
|
|
392
|
+
font: var(--t-caption)/1.6 var(--font-mono); color: var(--fg); white-space: pre-wrap; }
|
|
200
393
|
/* #186: the panel's footer — the state the removed header carried. The
|
|
201
394
|
statusbar (#73 "hide controls, never state": current zoom + env +
|
|
202
395
|
stack + tier, see renderStatusbar() in app.js) and the meta line.
|
|
203
396
|
Hidden when the panel collapses, like the body. */
|
|
204
|
-
|
|
205
|
-
|
|
397
|
+
/* Counts, env, zoom, tier — all of it is state read at a glance, so the
|
|
398
|
+
whole footer is mono. */
|
|
399
|
+
#panel-foot { border-top: 1px solid var(--line); padding: 7px 12px;
|
|
400
|
+
display: flex; flex-direction: column; gap: 3px;
|
|
401
|
+
font: var(--t-caption)/1.5 var(--font-mono); }
|
|
206
402
|
#panel.collapsed #panel-foot { display: none; }
|
|
207
|
-
#panel-foot .meta { color: var(--muted);
|
|
208
|
-
#statusbar { color: var(--muted);
|
|
403
|
+
#panel-foot .meta { color: var(--muted); overflow-wrap: anywhere; }
|
|
404
|
+
#statusbar { color: var(--muted); cursor: pointer; }
|
|
209
405
|
/* #131: why the current zoom rendered what it did. Same muted strip,
|
|
210
406
|
italic so it reads as a caption on the state rather than more state. */
|
|
211
407
|
.statusbar-note { font-style: italic; opacity: 0.85; }
|
|
212
|
-
#hintk { color: var(--muted); border: 1px solid var(--
|
|
213
|
-
padding: 2px 8px; font
|
|
214
|
-
#hintk:hover { border-color: var(--
|
|
408
|
+
#hintk { color: var(--muted); border: 1px solid var(--rule); border-radius: var(--r-ctl);
|
|
409
|
+
padding: 2px 8px; font: var(--t-caption)/1.4 var(--font-mono); cursor: pointer; white-space: nowrap; }
|
|
410
|
+
#hintk:hover { border-color: var(--focus); color: var(--fg); }
|
|
215
411
|
/* Command palette (⌘K / Ctrl+K, #73) — ported from spicypath's FG-036
|
|
216
412
|
pattern (../spicypath/src/index.html), theme vars mapped onto behold's
|
|
217
413
|
own: --panel/--line/--fg/--bg already exist; --pending stands in for
|
|
@@ -221,18 +417,22 @@
|
|
|
221
417
|
applyPaletteContrast() in app.js) so the selected row stays legible
|
|
222
418
|
across all 552 themes, light or dark. */
|
|
223
419
|
#palette { position: fixed; inset: 0; z-index: 110; display: none; align-items: flex-start;
|
|
224
|
-
justify-content: center; background:
|
|
420
|
+
justify-content: center; background: color-mix(in srgb, var(--shadow) 62%, transparent); }
|
|
225
421
|
#palette.on { display: flex; }
|
|
226
422
|
#palette .box { margin-top: 12vh; width: min(560px, 92vw); background: var(--panel);
|
|
227
|
-
border: 1px solid var(--
|
|
228
|
-
overflow: hidden; }
|
|
229
|
-
|
|
230
|
-
|
|
423
|
+
border: 1px solid var(--rule); border-radius: var(--r-shell);
|
|
424
|
+
box-shadow: 0 16px 44px color-mix(in srgb, var(--shadow) 62%, transparent); overflow: hidden; }
|
|
425
|
+
/* You type commands here — mono, at the one display size, is the whole
|
|
426
|
+
point of the surface. */
|
|
427
|
+
#pal-input { width: 100%; box-sizing: border-box; background: var(--well); color: var(--fg);
|
|
428
|
+
border: 0; border-bottom: 1px solid var(--rule); padding: 12px 14px;
|
|
429
|
+
font: var(--t-display)/1.3 var(--font-mono); outline: none; }
|
|
231
430
|
#pal-list { max-height: 50vh; overflow: auto; padding: 5px; }
|
|
232
|
-
#pal-list .row { padding: 7px 11px; border-radius:
|
|
431
|
+
#pal-list .row { padding: 7px 11px; border-radius: var(--r-ctl); color: var(--fg);
|
|
432
|
+
font: var(--t-body)/1.5 var(--font-mono);
|
|
233
433
|
cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
234
434
|
#pal-list .row.sel { background: var(--pending); color: var(--pal-sel-fg, var(--fg)); }
|
|
235
|
-
#pal-list .empty { padding: 8px 11px; color: var(--muted); font-size:
|
|
435
|
+
#pal-list .empty { padding: 8px 11px; color: var(--muted); font-size: var(--t-body); }
|
|
236
436
|
</style>
|
|
237
437
|
</head>
|
|
238
438
|
<body>
|