crewforth 3.0.3 → 3.1.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +211 -0
- package/README.md +3 -3
- package/VERSION +1 -1
- package/adopt.sh +86 -34
- package/kit/CLAUDE.md +1 -1
- package/kit/README.md +1 -1
- package/kit/agents/crew-commit-agent.md +7 -3
- package/kit/agents/crew-frontend-expert.md +4 -4
- package/kit/agents/crew-performance-expert.md +1 -1
- package/kit/agents/crew-review-agent.md +1 -1
- package/kit/eval/doctor.sh +57 -3
- package/kit/eval/golden-routing.txt +13 -0
- package/kit/eval/lib/git-bash.sh +11 -2
- package/kit/eval/parser-conformance.sh +1 -0
- package/kit/eval/routing-eval.sh +7 -3
- package/kit/eval/smoke-test.sh +2545 -214
- package/kit/hooks/commit-msg +1 -1
- package/kit/hooks/context-usage.sh +7 -1
- package/kit/hooks/guard-bash.sh +1608 -103
- package/kit/hooks/guard-commit-scan.sh +138 -19
- package/kit/hooks/guard-powershell.sh +89 -15
- package/kit/hooks/guard-write.sh +98 -22
- package/kit/hooks/no-bash-guard.ps1 +71 -0
- package/kit/hooks/pre-commit +5 -2
- package/kit/hooks/prompt-approval.sh +484 -0
- package/kit/hooks/route-hint.sh +13 -5
- package/kit/hooks/session-rehydrate.sh +18 -0
- package/kit/hooks/skill-trust.sh +17 -2
- package/kit/owned-blobs.tsv +43 -0
- package/kit/settings.json +20 -4
- package/kit/skills/a11y/SKILL.md +1 -0
- package/kit/skills/crew-doctor/SKILL.md +2 -2
- package/kit/skills/frontend/SKILL.md +3 -3
- package/kit/skills/frontend-flutter/SKILL.md +94 -0
- package/kit/skills/frontend-flutter/references/adaptive-and-accessible.md +55 -0
- package/kit/skills/frontend-flutter/references/platform-async-release.md +59 -0
- package/kit/skills/frontend-flutter/references/rebuilds-and-performance.md +36 -0
- package/kit/studio/server/hooks/studio-gate.sh +35 -1
- package/kit/studio/server/index.js +14 -4
- package/kit/studio/server/lib/graph.js +64 -3
- package/kit/studio/server/lib/kit.js +16 -2
- package/kit/studio/server/lib/permissions.js +77 -2
- package/kit/studio/server/lib/projects.js +60 -7
- package/kit/studio/server/lib/session.js +16 -2
- package/kit/studio/web/app.js +1765 -573
- package/kit/studio/web/approvals.js +124 -0
- package/kit/studio/web/canvas.js +932 -885
- package/kit/studio/web/chat.js +322 -145
- package/kit/studio/web/convo.js +174 -0
- package/kit/studio/web/dock.js +221 -0
- package/kit/studio/web/graph-plan.js +457 -0
- package/kit/studio/web/icons/builtin.svg +1 -0
- package/kit/studio/web/index.html +116 -50
- package/kit/studio/web/inspect.js +110 -0
- package/kit/studio/web/list-plan.js +112 -0
- package/kit/studio/web/list.js +157 -0
- package/kit/studio/web/liveness.js +66 -0
- package/kit/studio/web/nav.js +237 -0
- package/kit/studio/web/newsession.js +235 -0
- package/kit/studio/web/press.js +65 -0
- package/kit/studio/web/style.css +1281 -1014
- package/kit/studio/web/theme.js +43 -0
- package/kit/studio/web/timeline-plan.js +291 -0
- package/kit/studio/web/timeline.js +400 -0
- package/kit/studio/web/tokens.css +131 -0
- package/kit/studio/web/toolbar-fit.js +35 -0
- package/package.json +5 -4
package/kit/studio/web/style.css
CHANGED
|
@@ -1,81 +1,56 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/* Colour, type, space and radius come from tokens.css, which is generated from
|
|
2
|
+
the design system; this file holds no colour literal of its own. What the
|
|
3
|
+
design draws between two tokens is mixed from them here, by name, so a tone
|
|
4
|
+
exists in one place and follows both themes. */
|
|
2
5
|
:root {
|
|
3
|
-
|
|
4
|
-
--
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
--
|
|
8
|
-
--
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
--
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
/* Edge tones. A branch that did not finish on its own terms is the one
|
|
19
|
-
thing on this canvas that must not be read as "some other agent colour",
|
|
20
|
-
so it gets a hue of its own rather than the node's.
|
|
21
|
-
|
|
22
|
-
The four weights are tokens and not literals because the same stroke sits
|
|
23
|
-
on white here and on near-black in the dark theme: 0.3 that reads as
|
|
24
|
-
"finished, out of the way" on #0d1017 reads as "barely drawn" on #f7f8fa. */
|
|
25
|
-
--cv-fail: #d92d3f;
|
|
26
|
-
--cv-edge-live: 1;
|
|
27
|
-
--cv-edge-wake: 0.52;
|
|
28
|
-
--cv-edge-done: 0.42;
|
|
29
|
-
--cv-edge-quiet: 0.24;
|
|
30
|
-
|
|
31
|
-
--radius: 10px;
|
|
32
|
-
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
33
|
-
--sans: ui-sans-serif, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
:root:not([data-theme="light"]) {
|
|
37
|
-
--bg: #0d1017;
|
|
38
|
-
--surface: #141821;
|
|
39
|
-
--surface-2: #1b202b;
|
|
40
|
-
--line: #262c39;
|
|
41
|
-
--ink: #e6e9f0;
|
|
42
|
-
--ink-dim: #9aa7cc;
|
|
43
|
-
--ink-faint: #5c6472;
|
|
44
|
-
|
|
45
|
-
--cv-fail: #ff6b7a;
|
|
6
|
+
/* The dot grid behind the graph: quieter than a hairline, still on the page. */
|
|
7
|
+
--grid-dot: color-mix(in srgb, var(--line) 65%, var(--ground));
|
|
8
|
+
/* Depth under a lifted card. Navy rather than black, so it belongs to the
|
|
9
|
+
ground in both themes. */
|
|
10
|
+
--shade: color-mix(in srgb, var(--brand-navy) 45%, transparent);
|
|
11
|
+
--shade-soft: color-mix(in srgb, var(--brand-navy) 20%, transparent);
|
|
12
|
+
|
|
13
|
+
/* A branch that did not finish on its own terms is the one thing on this
|
|
14
|
+
canvas that must not be read as "some other agent colour". */
|
|
15
|
+
--cv-fail: var(--status-fail);
|
|
16
|
+
|
|
17
|
+
/* Edge weights. Tokens and not literals because the same stroke sits on
|
|
18
|
+
navy here and on near-white in the light theme: 0.3 that reads as
|
|
19
|
+
"finished, out of the way" on the dark ground reads as "barely drawn" on
|
|
20
|
+
the light one. The three blocks mirror the three in tokens.css. */
|
|
46
21
|
--cv-edge-live: 0.95;
|
|
47
22
|
--cv-edge-wake: 0.44;
|
|
48
|
-
|
|
49
|
-
|
|
23
|
+
|
|
24
|
+
/* The frame, in the spec's measures. */
|
|
25
|
+
--bar-h: 56px;
|
|
26
|
+
--nav-w: 272px;
|
|
27
|
+
--rail-w: 56px;
|
|
28
|
+
--toolbar-h: 48px;
|
|
29
|
+
--inspector-w: 344px;
|
|
50
30
|
}
|
|
51
31
|
|
|
52
32
|
@media (prefers-color-scheme: light) {
|
|
53
33
|
:root:not([data-theme="dark"]) {
|
|
54
|
-
--bg: #f7f8fa;
|
|
55
|
-
--surface: #ffffff;
|
|
56
|
-
--surface-2: #eef0f4;
|
|
57
|
-
--line: #d9dde5;
|
|
58
|
-
--ink: #1a1d24;
|
|
59
|
-
--ink-dim: #5c6472;
|
|
60
|
-
--ink-faint: #8d95a3;
|
|
61
|
-
|
|
62
|
-
--cv-fail: #d92d3f;
|
|
63
34
|
--cv-edge-live: 1;
|
|
64
35
|
--cv-edge-wake: 0.52;
|
|
65
|
-
--cv-edge-done: 0.42;
|
|
66
|
-
--cv-edge-quiet: 0.24;
|
|
67
36
|
}
|
|
68
37
|
}
|
|
69
38
|
|
|
39
|
+
:root[data-theme="light"] {
|
|
40
|
+
--cv-edge-live: 1;
|
|
41
|
+
--cv-edge-wake: 0.52;
|
|
42
|
+
}
|
|
43
|
+
|
|
70
44
|
* { box-sizing: border-box; }
|
|
71
45
|
|
|
72
46
|
body {
|
|
73
47
|
margin: 0;
|
|
74
|
-
background: var(--
|
|
48
|
+
background: var(--ground);
|
|
75
49
|
color: var(--ink);
|
|
76
|
-
font-family: var(--sans);
|
|
77
|
-
font-size:
|
|
50
|
+
font-family: var(--font-sans);
|
|
51
|
+
font-size: var(--type-body-sm-size);
|
|
78
52
|
line-height: 1.5;
|
|
53
|
+
-webkit-font-smoothing: antialiased;
|
|
79
54
|
/* Fixed height, not min-height: the canvas sizes itself to its box, so a
|
|
80
55
|
body that can grow past the window puts the bottom of the graph below the
|
|
81
56
|
fold while the fit maths still believes everything is visible. */
|
|
@@ -85,64 +60,143 @@ body {
|
|
|
85
60
|
flex-direction: column;
|
|
86
61
|
}
|
|
87
62
|
|
|
88
|
-
/*
|
|
63
|
+
/* One focus ring everywhere: 2px of the accent, 2px off the control. A control
|
|
64
|
+
that draws its own focus state says so next to its rule. */
|
|
65
|
+
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
66
|
+
|
|
67
|
+
/* ---- top bar ----
|
|
68
|
+
Left to right: the mark, where you are, what the session's agents are doing,
|
|
69
|
+
whether the panel is being fed, and the controls. */
|
|
89
70
|
.bar {
|
|
71
|
+
flex: none;
|
|
72
|
+
height: var(--bar-h);
|
|
90
73
|
display: flex;
|
|
91
74
|
align-items: center;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
padding: 12px 20px;
|
|
75
|
+
gap: var(--space-4);
|
|
76
|
+
padding: 0 var(--space-4);
|
|
95
77
|
border-bottom: 1px solid var(--line);
|
|
96
|
-
background: var(--
|
|
78
|
+
background: var(--ground);
|
|
79
|
+
min-width: 0;
|
|
97
80
|
}
|
|
81
|
+
.bar-sep { flex: none; width: 1px; height: 24px; background: var(--line); }
|
|
82
|
+
.bar-fill { flex: 1; min-width: 0; }
|
|
98
83
|
|
|
99
|
-
.brand { display: flex; align-items: center; gap: 10px; }
|
|
100
|
-
|
|
84
|
+
.brand { display: flex; align-items: center; gap: 10px; flex: none; color: inherit; text-decoration: none; border-radius: var(--radius-sm); }
|
|
101
85
|
.brand h1 {
|
|
102
86
|
margin: 0;
|
|
103
87
|
font-size: 15px;
|
|
104
|
-
font-weight:
|
|
105
|
-
letter-spacing: 0.
|
|
88
|
+
font-weight: 700;
|
|
89
|
+
letter-spacing: -0.2px;
|
|
90
|
+
white-space: nowrap;
|
|
106
91
|
}
|
|
107
92
|
|
|
108
|
-
/* Crewforth's own mark
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
.brand-mark { width: 19px; height: 19px; flex: none; }
|
|
93
|
+
/* Crewforth's own mark: three chevrons, rear to lead, each in its own token.
|
|
94
|
+
The lead one is the accent in both themes. Never recoloured, never reordered. */
|
|
95
|
+
.brand-mark { width: 24px; height: 24px; flex: none; }
|
|
112
96
|
.brand-mark polyline { fill: none; stroke-linecap: round; stroke-linejoin: round; }
|
|
113
|
-
.brand-mark .bar-1 { stroke:
|
|
114
|
-
.brand-mark .bar-2 { stroke:
|
|
115
|
-
.brand-mark .bar-3 { stroke:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
97
|
+
.brand-mark .bar-1 { stroke: var(--chevron-1); }
|
|
98
|
+
.brand-mark .bar-2 { stroke: var(--chevron-2); }
|
|
99
|
+
.brand-mark .bar-3 { stroke: var(--chevron-3); }
|
|
100
|
+
|
|
101
|
+
/* The product name carries the weight; "Studio" steps back beside it. */
|
|
102
|
+
.brand-accent { color: var(--ink-dim); font-weight: 600; }
|
|
103
|
+
|
|
104
|
+
/* Project, then session. The project is a control (it opens that project's
|
|
105
|
+
list); the session is where you already are, so it is only a name. */
|
|
106
|
+
/* It gives way first, down to a floor; past that the summary folds (app.js, fitBar). */
|
|
107
|
+
.crumb { display: flex; align-items: center; gap: 6px; flex: 0 1 auto; min-width: 120px; overflow: hidden; font-size: 13px; }
|
|
108
|
+
.crumb-project {
|
|
109
|
+
flex: 0 1 auto; min-width: 0; max-width: 220px;
|
|
110
|
+
background: none; border: 0; padding: 2px 4px; margin: 0 -4px; border-radius: var(--radius-sm);
|
|
111
|
+
font: inherit; color: var(--ink-dim); cursor: pointer;
|
|
112
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
125
113
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
/*
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
114
|
+
.crumb-project:hover { color: var(--ink); }
|
|
115
|
+
.crumb-sep { width: 12px; height: 12px; flex: none; color: var(--ink-faint); }
|
|
116
|
+
.crumb-session { flex: 0 1 auto; min-width: 40px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
117
|
+
|
|
118
|
+
/* ---- shared small parts ---- */
|
|
119
|
+
|
|
120
|
+
/* One-colour stroke icons, in the colour of the text beside them. */
|
|
121
|
+
.ic {
|
|
122
|
+
width: 16px; height: 16px; flex: none;
|
|
123
|
+
fill: none; stroke: currentColor; stroke-width: 1.6;
|
|
124
|
+
stroke-linecap: round; stroke-linejoin: round;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* A status dot. The tone is one of the status tokens; with none it is a hollow
|
|
128
|
+
ring — something that is over, or a state nobody gave a colour to. It never
|
|
129
|
+
stands alone: the word is always beside it. */
|
|
130
|
+
.dot { width: 8px; height: 8px; border-radius: var(--radius-pill); flex: none; display: inline-block; }
|
|
131
|
+
.dot[data-tone="busy"] { background: var(--status-busy); }
|
|
132
|
+
.dot[data-tone="waiting"] { background: var(--status-waiting); }
|
|
133
|
+
.dot[data-tone="idle"] { background: var(--status-idle); }
|
|
134
|
+
.dot[data-tone="good"] { background: var(--status-good); }
|
|
135
|
+
.dot[data-tone="bad"] { background: var(--status-bad); }
|
|
136
|
+
.dot[data-tone="fail"] { background: var(--status-fail); }
|
|
137
|
+
.dot[data-tone="none"] { background: transparent; box-shadow: inset 0 0 0 1.5px var(--ink-dim); }
|
|
138
|
+
|
|
139
|
+
.pill {
|
|
140
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
141
|
+
height: 24px; padding: 0 var(--space-2);
|
|
142
|
+
border-radius: var(--radius-sm);
|
|
143
|
+
background: var(--surface-raised);
|
|
144
|
+
border: 1px solid transparent;
|
|
145
|
+
font-family: inherit; font-size: 12px; font-weight: 600; color: var(--ink);
|
|
146
|
+
white-space: nowrap; flex: none;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.badge {
|
|
150
|
+
display: inline-flex; align-items: center; flex: none;
|
|
151
|
+
height: 20px; padding: 0 6px;
|
|
152
|
+
border-radius: var(--radius-pill);
|
|
153
|
+
border: 1px solid var(--line);
|
|
154
|
+
background: none;
|
|
155
|
+
font-family: var(--font-mono); font-size: 11px; color: var(--ink-dim);
|
|
156
|
+
white-space: nowrap;
|
|
142
157
|
}
|
|
143
|
-
.
|
|
144
|
-
.pulse.off { color: var(--bad); }
|
|
158
|
+
.badge[hidden] { display: none; }
|
|
145
159
|
|
|
160
|
+
.btn {
|
|
161
|
+
display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
|
|
162
|
+
height: 36px; padding: 0 14px; flex: none;
|
|
163
|
+
border-radius: var(--radius-md);
|
|
164
|
+
border: 1px solid var(--line);
|
|
165
|
+
background: var(--surface);
|
|
166
|
+
color: var(--ink);
|
|
167
|
+
font-family: inherit; font-size: 13px; font-weight: 600;
|
|
168
|
+
white-space: nowrap; cursor: pointer;
|
|
169
|
+
}
|
|
170
|
+
.btn:hover { background: var(--surface-raised); }
|
|
171
|
+
.btn:disabled { opacity: 0.6; cursor: default; }
|
|
172
|
+
.btn[hidden] { display: none; }
|
|
173
|
+
.btn.ghost { background: transparent; }
|
|
174
|
+
.btn.ghost:hover { background: var(--surface-raised); }
|
|
175
|
+
.btn.icon { width: 36px; padding: 0; color: var(--ink-dim); }
|
|
176
|
+
.btn.icon:hover { color: var(--ink); }
|
|
177
|
+
.btn.sm { height: 30px; padding: 0 10px; font-size: 12px; }
|
|
178
|
+
.btn.icon.sm { width: 30px; padding: 0; }
|
|
179
|
+
/* The one filled control on the page. */
|
|
180
|
+
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
|
|
181
|
+
.btn.primary:hover { background: color-mix(in srgb, var(--accent) 88%, var(--ink)); }
|
|
182
|
+
.btn.on { color: var(--studio-accent); border-color: color-mix(in srgb, var(--studio-accent) 45%, transparent); }
|
|
183
|
+
|
|
184
|
+
.seg {
|
|
185
|
+
display: flex; flex: none; padding: 3px; gap: 2px;
|
|
186
|
+
background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-md);
|
|
187
|
+
}
|
|
188
|
+
.seg-btn {
|
|
189
|
+
flex: 1; height: 28px; padding: 0 var(--space-2);
|
|
190
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
|
191
|
+
border: 0; border-radius: 7px; background: none;
|
|
192
|
+
font-family: inherit; font-size: 13px; font-weight: 600; color: var(--ink-dim);
|
|
193
|
+
cursor: pointer; white-space: nowrap;
|
|
194
|
+
}
|
|
195
|
+
.seg-btn:hover { color: var(--ink); }
|
|
196
|
+
.seg-btn[aria-selected="true"] { background: var(--surface-raised); color: var(--ink); }
|
|
197
|
+
.seg-btn .badge { height: 18px; }
|
|
198
|
+
|
|
199
|
+
/* The small bordered button the inspector still uses for copy and widen. */
|
|
146
200
|
.ghost {
|
|
147
201
|
background: transparent;
|
|
148
202
|
border: 1px solid var(--line);
|
|
@@ -150,88 +204,49 @@ body {
|
|
|
150
204
|
border-radius: 8px;
|
|
151
205
|
padding: 4px 10px;
|
|
152
206
|
cursor: pointer;
|
|
207
|
+
font-family: inherit;
|
|
153
208
|
font-size: 13px;
|
|
154
209
|
}
|
|
155
210
|
.ghost:hover { color: var(--ink); border-color: var(--ink-faint); }
|
|
156
211
|
|
|
157
|
-
/* ----
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
.
|
|
162
|
-
|
|
163
|
-
.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
212
|
+
/* ---- summary chips ----
|
|
213
|
+
Each chip is a count and a filter. Pressed, its frame is the selection
|
|
214
|
+
colour and the canvas steps every other agent back. */
|
|
215
|
+
.chips { display: flex; align-items: center; gap: 6px; flex: none; }
|
|
216
|
+
.chip { cursor: pointer; }
|
|
217
|
+
.chip:hover { border-color: var(--line); }
|
|
218
|
+
.chip[aria-pressed="true"] { border-color: var(--studio-accent); box-shadow: 0 0 0 1px var(--studio-accent); }
|
|
219
|
+
.chip-compact { display: none; gap: 5px; }
|
|
220
|
+
.chip-compact .ic { width: 12px; height: 12px; color: var(--ink-dim); }
|
|
221
|
+
.bar[data-compact] .chip, .bar[data-compact] .note-pill { display: none; }
|
|
222
|
+
.bar[data-compact] .chip-compact { display: inline-flex; }
|
|
223
|
+
/* Not a status and not a filter: a number that has to stay in view. */
|
|
224
|
+
.note-pill { color: var(--status-waiting); background: color-mix(in srgb, var(--status-waiting) 12%, transparent); }
|
|
225
|
+
|
|
226
|
+
/* ---- live indicator ---- */
|
|
227
|
+
.live { display: inline-flex; align-items: center; gap: 6px; flex: none; font-size: 12px; color: var(--ink-dim); white-space: nowrap; }
|
|
228
|
+
.live strong { color: var(--ink); font-weight: 600; }
|
|
229
|
+
.live[data-state="stale"] strong { color: var(--status-waiting); }
|
|
230
|
+
.live[data-state="offline"] strong { color: var(--status-fail); }
|
|
171
231
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
border-radius: 50%;
|
|
175
|
-
border: 2px solid var(--idle);
|
|
176
|
-
flex: none;
|
|
177
|
-
}
|
|
178
|
-
.ring[data-status="busy"] {
|
|
179
|
-
border-color: var(--busy);
|
|
180
|
-
background: color-mix(in srgb, var(--busy) 30%, transparent);
|
|
181
|
-
animation: breathe 1.6s ease-in-out infinite;
|
|
182
|
-
}
|
|
183
|
-
.ring[data-status="waiting"] {
|
|
184
|
-
border-color: var(--waiting);
|
|
185
|
-
border-style: dashed;
|
|
186
|
-
}
|
|
187
|
-
.ring[data-status="idle"] { border-color: var(--idle); }
|
|
232
|
+
/* ---- layout ---- */
|
|
233
|
+
.meta { font-family: var(--font-mono); font-size: 11px; color: var(--ink-faint); }
|
|
188
234
|
|
|
189
235
|
@keyframes breathe {
|
|
190
|
-
0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--busy) 45%, transparent); }
|
|
191
|
-
50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--busy) 0%, transparent); }
|
|
192
|
-
}
|
|
193
|
-
@media (prefers-reduced-motion: reduce) {
|
|
194
|
-
.ring[data-status="busy"] { animation: none; }
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
.who { min-width: 0; }
|
|
198
|
-
.who .name { font-weight: 600; font-size: 14px; }
|
|
199
|
-
.who .path {
|
|
200
|
-
font-family: var(--mono);
|
|
201
|
-
font-size: 12px;
|
|
202
|
-
color: var(--ink-dim);
|
|
203
|
-
white-space: nowrap;
|
|
204
|
-
overflow: hidden;
|
|
205
|
-
text-overflow: ellipsis;
|
|
206
|
-
}
|
|
207
|
-
.who .waiting-for {
|
|
208
|
-
font-size: 12px;
|
|
209
|
-
color: var(--waiting);
|
|
210
|
-
margin-top: 3px;
|
|
236
|
+
0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--status-busy) 45%, transparent); }
|
|
237
|
+
50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--status-busy) 0%, transparent); }
|
|
211
238
|
}
|
|
212
239
|
|
|
213
|
-
.stat { text-align: right; font-family: var(--mono); font-size: 11px; color: var(--ink-faint); white-space: nowrap; }
|
|
214
|
-
.stat .status { display: block; font-size: 12px; color: var(--ink-dim); text-transform: uppercase; letter-spacing: 0.05em; }
|
|
215
|
-
|
|
216
240
|
/* ---- honest empty / unmeasured states ---- */
|
|
217
241
|
.note {
|
|
218
|
-
padding:
|
|
242
|
+
padding: var(--space-6) var(--space-3);
|
|
219
243
|
text-align: center;
|
|
244
|
+
font-size: 13px;
|
|
220
245
|
color: var(--ink-dim);
|
|
221
|
-
background: var(--surface);
|
|
222
246
|
}
|
|
223
247
|
.note strong { display: block; color: var(--ink); margin-bottom: 4px; font-weight: 600; }
|
|
224
|
-
.note .why { font-family: var(--mono); font-size: 12px; color: var(--ink-faint); margin-top: 6px; }
|
|
225
|
-
.note.unmeasured strong { color: var(--waiting); }
|
|
226
|
-
|
|
227
|
-
.foot {
|
|
228
|
-
padding: 10px 20px;
|
|
229
|
-
border-top: 1px solid var(--line);
|
|
230
|
-
font-family: var(--mono);
|
|
231
|
-
font-size: 11px;
|
|
232
|
-
color: var(--ink-faint);
|
|
233
|
-
background: var(--surface);
|
|
234
|
-
}
|
|
248
|
+
.note .why { font-family: var(--font-mono); font-size: 12px; color: var(--ink-faint); margin-top: 6px; }
|
|
249
|
+
.note.unmeasured strong { color: var(--status-waiting); }
|
|
235
250
|
|
|
236
251
|
/* ============================================================== shell === */
|
|
237
252
|
|
|
@@ -239,24 +254,29 @@ body {
|
|
|
239
254
|
position: relative;
|
|
240
255
|
flex: 1; min-height: 0;
|
|
241
256
|
display: grid;
|
|
242
|
-
/*
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
257
|
+
/* Six columns: navigator, divider, stage, inspector, divider, conversation.
|
|
258
|
+
The two dividers are real columns, so dragging one moves a boundary rather
|
|
259
|
+
than overlaying it. The inspector's column is `auto`: it is as wide as a
|
|
260
|
+
docked inspector and nothing at all when the inspector floats or is closed.
|
|
261
|
+
The conversation sits on the right, beside the graph rather than under it
|
|
262
|
+
— a reply is read next to the work it is about, not below the fold. */
|
|
263
|
+
grid-template-columns: var(--side-w, var(--nav-w)) 5px minmax(0, 1fr) auto 5px var(--chat-w, 480px);
|
|
247
264
|
}
|
|
248
265
|
/* Placed explicitly, never by auto-flow. A `display: none` child occupies no
|
|
249
|
-
grid cell, so hiding the
|
|
250
|
-
the graph was handed the
|
|
251
|
-
.shell > .side
|
|
252
|
-
.shell > #resizer
|
|
253
|
-
.shell > .stage
|
|
254
|
-
.shell > #chat-split { grid-column:
|
|
255
|
-
.shell > .chat
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
266
|
+
grid cell, so hiding the navigator slid every remaining column one to the
|
|
267
|
+
left: the graph was handed the rail's slot and the conversation took the rest. */
|
|
268
|
+
.shell > .side { grid-column: 1; }
|
|
269
|
+
.shell > #resizer { grid-column: 2; }
|
|
270
|
+
.shell > .stage { grid-column: 3; }
|
|
271
|
+
.shell > #chat-split { grid-column: 5; }
|
|
272
|
+
.shell > .chat,
|
|
273
|
+
.shell > .newpanel { grid-column: 6; }
|
|
274
|
+
|
|
275
|
+
/* Collapsed, the navigator leaves its rail behind, so the graph does not sit
|
|
276
|
+
under the control that brings it back. */
|
|
277
|
+
.shell.no-chat { grid-template-columns: var(--side-w, var(--nav-w)) 5px minmax(0, 1fr) auto 0 0; }
|
|
278
|
+
.shell.no-side { grid-template-columns: var(--rail-w) 0 minmax(0, 1fr) auto 5px var(--chat-w, 480px); }
|
|
279
|
+
.shell.no-side.no-chat { grid-template-columns: var(--rail-w) 0 minmax(0, 1fr) auto 0 0; }
|
|
260
280
|
.shell.no-side > .side, .shell.no-side > #resizer { display: none; }
|
|
261
281
|
|
|
262
282
|
.resizer {
|
|
@@ -268,144 +288,592 @@ body {
|
|
|
268
288
|
/* A 5px target is hard to hit; the hit area is wider than the line. */
|
|
269
289
|
content: ''; position: absolute; inset: 0 -4px; z-index: 5;
|
|
270
290
|
}
|
|
271
|
-
.resizer:hover, .resizer
|
|
291
|
+
.resizer:hover, .resizer.dragging { background: var(--studio-accent); }
|
|
272
292
|
body.resizing { cursor: col-resize; user-select: none; }
|
|
273
293
|
body.resizing .cv-root { pointer-events: none; }
|
|
274
294
|
|
|
295
|
+
/* ---- navigator ---- */
|
|
275
296
|
.side {
|
|
276
297
|
border-right: 1px solid var(--line);
|
|
277
298
|
background: var(--surface);
|
|
278
299
|
display: flex;
|
|
279
300
|
flex-direction: column;
|
|
301
|
+
gap: var(--space-3);
|
|
302
|
+
padding: var(--space-4) var(--space-3) var(--space-3);
|
|
280
303
|
min-height: 0;
|
|
304
|
+
min-width: 0;
|
|
281
305
|
overflow: hidden;
|
|
282
306
|
}
|
|
283
|
-
.
|
|
284
|
-
|
|
285
|
-
.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
307
|
+
.nav-top { display: flex; align-items: center; gap: 6px; flex: none; }
|
|
308
|
+
/* The input is the field, so the focus ring it gets is the field's; the icon
|
|
309
|
+
sits inside its left padding. */
|
|
310
|
+
.nav-search { position: relative; flex: 1; min-width: 0; display: block; color: var(--ink-dim); }
|
|
311
|
+
.nav-search .ic { position: absolute; left: 10px; top: 10px; pointer-events: none; }
|
|
312
|
+
.nav-search input {
|
|
313
|
+
width: 100%; height: 36px; padding: 0 10px 0 34px;
|
|
314
|
+
border: 1px solid var(--line); border-radius: var(--radius-md);
|
|
315
|
+
background: var(--ground);
|
|
316
|
+
color: var(--ink); font-family: inherit; font-size: 13px;
|
|
317
|
+
}
|
|
318
|
+
.nav-search input::placeholder { color: var(--ink-faint); }
|
|
319
|
+
|
|
320
|
+
.nav-list { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 14px; margin: 0 -4px; padding: 0 4px; }
|
|
321
|
+
.nav-list[hidden] { display: none; }
|
|
322
|
+
|
|
323
|
+
.proj { display: flex; flex-direction: column; gap: var(--space-1); flex: none; }
|
|
324
|
+
.proj-head {
|
|
325
|
+
display: flex; align-items: center; gap: 6px; min-width: 0;
|
|
326
|
+
padding: 2px var(--space-1); border-radius: var(--radius-sm); cursor: pointer;
|
|
327
|
+
}
|
|
328
|
+
.proj-head:hover .proj-name { color: var(--ink); }
|
|
329
|
+
.proj-caret { width: 12px; height: 12px; color: var(--ink-faint); transition: transform 0.12s; }
|
|
330
|
+
.proj-head[aria-expanded="true"] .proj-caret { transform: rotate(90deg); }
|
|
331
|
+
.proj-name { min-width: 0; font-size: 13px; font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
332
|
+
.proj-head.gone .proj-name { color: var(--ink-faint); text-decoration: line-through; }
|
|
333
|
+
.row-fill { flex: 1; min-width: 4px; }
|
|
334
|
+
|
|
335
|
+
/* The version badge. Grey when there is nothing to do; orange, and a button,
|
|
336
|
+
when the project is behind and a click copies the way to fix that. */
|
|
337
|
+
.badge-old {
|
|
338
|
+
color: var(--status-waiting);
|
|
339
|
+
border-color: color-mix(in srgb, var(--status-waiting) 32%, var(--line));
|
|
340
|
+
cursor: pointer;
|
|
292
341
|
}
|
|
293
|
-
.
|
|
294
|
-
.
|
|
342
|
+
.badge-old:hover { background: color-mix(in srgb, var(--status-waiting) 12%, transparent); }
|
|
343
|
+
.badge-ahead { color: var(--studio-accent); border-color: color-mix(in srgb, var(--studio-accent) 35%, transparent); }
|
|
344
|
+
.badge-unknown, .badge-none { color: var(--ink-faint); }
|
|
295
345
|
|
|
296
|
-
.
|
|
297
|
-
display:
|
|
298
|
-
padding:
|
|
346
|
+
.srow {
|
|
347
|
+
display: flex; flex-direction: column; gap: 2px; flex: none; min-width: 0;
|
|
348
|
+
padding: var(--space-2) 6px var(--space-2) var(--space-3);
|
|
349
|
+
border-radius: var(--radius-md); border: 1px solid transparent;
|
|
350
|
+
cursor: pointer;
|
|
351
|
+
}
|
|
352
|
+
.srow:hover { background: var(--surface-raised); }
|
|
353
|
+
.srow[aria-current="true"] { background: var(--surface-raised); border-color: var(--studio-accent); }
|
|
354
|
+
.srow.remote { cursor: default; }
|
|
355
|
+
.srow-top { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
|
|
356
|
+
.nm {
|
|
357
|
+
min-width: 0; font-size: 13px; line-height: 18px; color: var(--ink);
|
|
358
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
299
359
|
}
|
|
300
|
-
.srow {
|
|
301
|
-
.
|
|
302
|
-
.
|
|
303
|
-
.srow
|
|
304
|
-
.srow .
|
|
305
|
-
.
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
360
|
+
.srow[aria-current="true"] .nm { font-weight: 600; }
|
|
361
|
+
.mname { min-width: 0; font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
362
|
+
.sub { font-size: 12px; line-height: 16px; color: var(--ink-dim); white-space: nowrap; flex: none; }
|
|
363
|
+
.srow-sub { padding-left: var(--space-4); flex: initial; overflow: hidden; text-overflow: ellipsis; }
|
|
364
|
+
.srow.remote .srow-sub { padding-left: var(--space-6); }
|
|
365
|
+
.sub.bad, .nav-line.bad { color: var(--status-bad); }
|
|
366
|
+
mark { background: color-mix(in srgb, var(--studio-accent) 30%, transparent); color: inherit; border-radius: 2px; }
|
|
367
|
+
|
|
368
|
+
.row-more {
|
|
369
|
+
flex: none; width: 22px; height: 22px; padding: 0;
|
|
370
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
371
|
+
border: 0; border-radius: var(--radius-sm); background: none; color: var(--ink-faint); cursor: pointer;
|
|
372
|
+
}
|
|
373
|
+
.row-more .ic { stroke-width: 2.4; }
|
|
374
|
+
.row-more:hover { background: var(--ground); color: var(--ink); }
|
|
375
|
+
.srow-rename {
|
|
376
|
+
flex: 1; min-width: 0; height: 22px; padding: 0 6px;
|
|
377
|
+
border: 1px solid var(--line); border-radius: var(--radius-sm);
|
|
378
|
+
background: var(--ground); color: var(--ink); font-family: inherit; font-size: 13px;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.nav-line {
|
|
382
|
+
display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); flex: none;
|
|
383
|
+
padding: 0 var(--space-1) 0 var(--space-3);
|
|
384
|
+
font-size: 12px; color: var(--ink-faint);
|
|
385
|
+
}
|
|
386
|
+
.link { background: none; border: 0; padding: 0; font: inherit; color: var(--accent); cursor: pointer; }
|
|
387
|
+
.link:hover { text-decoration: underline; }
|
|
388
|
+
|
|
389
|
+
.nav-foot {
|
|
390
|
+
flex: none; display: flex; flex-direction: column; gap: var(--space-1);
|
|
391
|
+
border-top: 1px solid var(--line); padding-top: var(--space-3);
|
|
392
|
+
}
|
|
393
|
+
.nav-foot[hidden] { display: none; }
|
|
394
|
+
.nav-foot-label {
|
|
395
|
+
align-self: flex-start; background: none; border: 0; padding: 0 var(--space-1); cursor: pointer;
|
|
396
|
+
font-family: inherit;
|
|
397
|
+
font-size: 11px; line-height: var(--type-label-line); font-weight: var(--type-label-weight);
|
|
398
|
+
letter-spacing: var(--type-label-tracking); text-transform: uppercase; color: var(--ink-dim);
|
|
399
|
+
}
|
|
400
|
+
.nav-foot-label:hover { color: var(--ink); }
|
|
401
|
+
|
|
402
|
+
/* ---- menu and toast ---- */
|
|
403
|
+
.menu {
|
|
404
|
+
position: fixed; z-index: 60; min-width: 190px; max-width: 272px;
|
|
405
|
+
display: flex; flex-direction: column; gap: 2px; padding: var(--space-1);
|
|
406
|
+
background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-md);
|
|
407
|
+
box-shadow: 0 12px 30px -12px var(--shade);
|
|
408
|
+
}
|
|
409
|
+
.menu[hidden] { display: none; }
|
|
410
|
+
.menu-item {
|
|
411
|
+
display: flex; align-items: center; gap: var(--space-2);
|
|
412
|
+
height: 30px; padding: 0 10px; text-align: left;
|
|
413
|
+
border: 0; border-radius: 7px; background: none;
|
|
414
|
+
font-family: inherit; font-size: 13px; color: var(--ink); cursor: pointer;
|
|
415
|
+
}
|
|
416
|
+
.menu-item:hover, .menu-item:focus-visible { background: var(--surface-raised); }
|
|
417
|
+
.menu-item.primary { color: var(--accent); font-weight: 600; }
|
|
418
|
+
.menu-item[aria-checked="true"] { background: var(--surface-raised); box-shadow: inset 0 0 0 1px var(--studio-accent); }
|
|
419
|
+
.menu-note {
|
|
420
|
+
padding: var(--space-1) 10px; font-size: 12px; line-height: 18px; color: var(--ink-dim);
|
|
421
|
+
overflow-wrap: anywhere;
|
|
309
422
|
}
|
|
310
|
-
.
|
|
311
|
-
.who .path { font-size: 11px; }
|
|
312
|
-
.stat { font-size: 10px; }
|
|
313
|
-
.stat .status { font-size: 10px; }
|
|
423
|
+
.menu-note + .menu-note { font-family: var(--font-mono); color: var(--ink); }
|
|
314
424
|
|
|
315
|
-
.
|
|
425
|
+
.toast {
|
|
426
|
+
position: fixed; z-index: 70; left: 50%; bottom: var(--space-6); transform: translateX(-50%);
|
|
427
|
+
max-width: min(560px, calc(100vw - 2 * var(--space-4)));
|
|
428
|
+
padding: var(--space-2) var(--space-3);
|
|
429
|
+
background: var(--surface-raised); border: 1px solid var(--line); border-radius: var(--radius-md);
|
|
430
|
+
box-shadow: 0 12px 30px -12px var(--shade);
|
|
431
|
+
font-size: 13px; color: var(--ink); overflow-wrap: anywhere;
|
|
432
|
+
}
|
|
433
|
+
.toast[hidden] { display: none; }
|
|
316
434
|
|
|
317
435
|
/* ============================================================== stage === */
|
|
318
436
|
|
|
319
|
-
.
|
|
320
|
-
|
|
437
|
+
/* The toolbar on top, the canvas under it. A column, so the canvas is exactly
|
|
438
|
+
the box that is left and the fit maths measures what is drawn. */
|
|
439
|
+
.stage { position: relative; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
|
|
440
|
+
|
|
441
|
+
.toolbar {
|
|
442
|
+
flex: none;
|
|
443
|
+
height: var(--toolbar-h);
|
|
444
|
+
display: flex; align-items: center; gap: var(--space-2);
|
|
445
|
+
padding: 0 var(--space-3);
|
|
446
|
+
border-bottom: 1px solid var(--line);
|
|
447
|
+
background: var(--ground);
|
|
448
|
+
}
|
|
449
|
+
/* Which view this is: the graph, or the same session against time. */
|
|
450
|
+
.tb-views { flex: none; }
|
|
451
|
+
.tb-views .seg-btn { display: inline-flex; align-items: center; gap: 6px; }
|
|
452
|
+
.tb-follow { display: inline-flex; align-items: center; gap: var(--space-2); flex: none; }
|
|
453
|
+
.tb-follow[hidden], .tb-range[hidden] { display: none; }
|
|
454
|
+
.tb-range { cursor: default; display: inline-flex; align-items: center; justify-content: center; }
|
|
455
|
+
/* An on/off control that reads as one. */
|
|
456
|
+
.switch {
|
|
457
|
+
width: 32px; height: 18px; padding: 0; flex: none; position: relative; cursor: pointer;
|
|
458
|
+
border: 0; border-radius: var(--radius-pill); background: var(--line);
|
|
459
|
+
}
|
|
460
|
+
.switch > span {
|
|
461
|
+
position: absolute; top: 2px; left: 2px; width: 14px; height: 14px;
|
|
462
|
+
border-radius: var(--radius-pill); background: var(--ink-dim);
|
|
463
|
+
}
|
|
464
|
+
.switch[aria-checked="true"] { background: var(--accent); }
|
|
465
|
+
.switch[aria-checked="true"] > span { left: 16px; background: var(--on-accent); }
|
|
466
|
+
.switch:disabled { opacity: 0.5; cursor: default; }
|
|
467
|
+
/* Shown only at the step below at which the toolbar has dropped what it holds. */
|
|
468
|
+
#tb-options { display: none; }
|
|
469
|
+
.toolbar-fill { flex: 1; min-width: 0; }
|
|
470
|
+
/* Group, Density, Show: the name of the choice in the quiet colour, the choice itself in the text colour. */
|
|
471
|
+
.tb-menu { color: var(--ink-dim); gap: 6px; }
|
|
472
|
+
.tb-menu > span { color: var(--ink); }
|
|
473
|
+
/* The menu's own name is one run of text: split into two flex items, the gap was added to the space between its words. */
|
|
474
|
+
.tb-menu > .tb-name { color: inherit; }
|
|
475
|
+
.tb-menu .ic { width: 12px; height: 12px; }
|
|
476
|
+
.tb-zoom {
|
|
477
|
+
width: 44px; height: 30px; padding: 0; flex: none;
|
|
478
|
+
background: none; border: 0; border-radius: var(--radius-sm); cursor: pointer;
|
|
479
|
+
font-family: var(--font-mono); font-size: 12px; color: var(--ink-dim); text-align: center;
|
|
480
|
+
}
|
|
481
|
+
.tb-zoom:hover { color: var(--ink); }
|
|
482
|
+
/* What a toolbar that does not hold everything gives up, a step at a time. The step is measured by the page
|
|
483
|
+
(toolbar-fit.js) and written to data-fit; no width is assumed here, because how wide the words are depends on
|
|
484
|
+
the font and on the choices the menus are showing.
|
|
485
|
+
1: it keeps every control and drops the words of the ones that have an icon to stand on. */
|
|
486
|
+
.toolbar:is([data-fit="1"], [data-fit="2"], [data-fit="3"]) .tb-words,
|
|
487
|
+
.toolbar:is([data-fit="1"], [data-fit="2"], [data-fit="3"]) .toolbar-note { display: none; }
|
|
488
|
+
/* 2: nothing is dropped without a way to reach it: "View options" holds what these did. */
|
|
489
|
+
.toolbar:is([data-fit="2"], [data-fit="3"]) :is(#tb-group, #tb-density, #tb-show, #tb-expand, #tb-fold) { display: none; }
|
|
490
|
+
.toolbar:is([data-fit="2"], [data-fit="3"]) #tb-options { display: inline-flex; }
|
|
491
|
+
/* 3: the − and + stay; the figure between them goes. It is still on their labels. And "View options" is "View":
|
|
492
|
+
measured at 360 px, the width of a small phone, the full name ran 24 px over. */
|
|
493
|
+
.toolbar[data-fit="3"] #tb-zoom,
|
|
494
|
+
.toolbar[data-fit="3"] .tb-range,
|
|
495
|
+
.toolbar[data-fit="3"] .tb-follow .sub,
|
|
496
|
+
.toolbar[data-fit="3"] .tb-more { display: none; }
|
|
497
|
+
/* …and the row closes up. At 320 px it was still 17 px over with its usual gaps. */
|
|
498
|
+
.toolbar[data-fit="3"] { gap: var(--space-1); padding: 0 var(--space-2); }
|
|
499
|
+
/* What the panel has to say about the stream or the last action: a reason, in
|
|
500
|
+
words, where the eye already is. Empty most of the time. */
|
|
501
|
+
.toolbar-note {
|
|
502
|
+
min-width: 0; font-family: var(--font-mono); font-size: 11px; color: var(--ink-faint);
|
|
503
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
504
|
+
}
|
|
321
505
|
|
|
322
|
-
|
|
506
|
+
.canvas { flex: 1; min-height: 0; position: relative; overflow: hidden; background: var(--ground); }
|
|
507
|
+
.canvas[hidden] { display: none; }
|
|
508
|
+
|
|
509
|
+
/* ---------------------------------------------------------- the stage ---
|
|
510
|
+
One line for what is true of the whole stage, a first-run state, and the look of a picture that is old. */
|
|
511
|
+
.phone-head { display: none; }
|
|
512
|
+
.stage-note {
|
|
513
|
+
display: flex; align-items: center; gap: var(--space-3); flex: none; min-width: 0;
|
|
514
|
+
padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--line); font-size: 13px;
|
|
515
|
+
background: var(--surface); color: var(--ink-dim);
|
|
516
|
+
}
|
|
517
|
+
.stage-note[hidden], .stage-note [hidden] { display: none; }
|
|
518
|
+
.stage-note[data-kind="offline"] {
|
|
519
|
+
color: var(--ink); background: color-mix(in srgb, var(--status-fail) 10%, var(--surface));
|
|
520
|
+
border-bottom-color: color-mix(in srgb, var(--status-fail) 45%, var(--line));
|
|
521
|
+
}
|
|
522
|
+
.stage-note-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
523
|
+
/* The last picture stays while the server is away, and is drawn as what it is: old. */
|
|
524
|
+
.stage[data-offline="true"] .canvas,
|
|
525
|
+
.stage[data-offline="true"] .tl,
|
|
526
|
+
.stage[data-offline="true"] .ls,
|
|
527
|
+
.stage[data-offline="true"] .attention { opacity: 0.45; }
|
|
528
|
+
.first-run {
|
|
529
|
+
flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-3);
|
|
530
|
+
padding: var(--space-6); text-align: center; color: var(--ink-dim);
|
|
531
|
+
}
|
|
532
|
+
.first-run[hidden] { display: none; }
|
|
533
|
+
.first-run strong { font-size: 18px; color: var(--ink); }
|
|
534
|
+
.first-run p { margin: 0; max-width: 46ch; line-height: 1.5; }
|
|
535
|
+
.stage[data-first-run="true"] .canvas,
|
|
536
|
+
.stage[data-first-run="true"] .tl,
|
|
537
|
+
.stage[data-first-run="true"] .ls,
|
|
538
|
+
.stage[data-first-run="true"] .toolbar { display: none; }
|
|
539
|
+
/* Rows that are not there yet: the list is being read. */
|
|
540
|
+
.skel { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-3); }
|
|
541
|
+
.skel span { height: 44px; border-radius: var(--radius-md); background: var(--surface-raised); opacity: 0.6; }
|
|
542
|
+
|
|
543
|
+
/* ------------------------------------------------------------ the list ---
|
|
544
|
+
One column, in the order of what needs the reader. */
|
|
545
|
+
.ls { flex: 1; min-height: 0; min-width: 0; overflow-y: auto; background: var(--ground); padding: var(--space-3) var(--space-4) var(--space-6); }
|
|
546
|
+
.ls[hidden] { display: none; }
|
|
547
|
+
.ls-sec { max-width: 760px; margin: 0 auto var(--space-4); }
|
|
548
|
+
.ls-head {
|
|
549
|
+
display: flex; align-items: center; gap: var(--space-2); width: 100%; height: 36px; padding: 0 var(--space-1);
|
|
550
|
+
background: none; border: 0; color: var(--ink); font: inherit; text-align: left;
|
|
551
|
+
}
|
|
552
|
+
button.ls-head { cursor: pointer; }
|
|
553
|
+
.ls-title { font-size: 12px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-dim); }
|
|
554
|
+
.ls-sec[data-tone="waiting"] .ls-title { color: var(--status-waiting); }
|
|
555
|
+
.ls-sec[data-tone="fail"] .ls-title { color: var(--status-fail); }
|
|
556
|
+
.ls-row {
|
|
557
|
+
display: flex; align-items: center; gap: var(--space-3); width: 100%; min-height: 56px; box-sizing: border-box;
|
|
558
|
+
padding: var(--space-2) var(--space-3); margin-bottom: var(--space-2);
|
|
559
|
+
border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface);
|
|
560
|
+
color: var(--ink); font: inherit; text-align: left; cursor: pointer;
|
|
561
|
+
}
|
|
562
|
+
.ls-row:hover { border-color: var(--studio-accent); }
|
|
563
|
+
.ls-row.on { border-color: var(--studio-accent); box-shadow: 0 0 0 1px var(--studio-accent); }
|
|
564
|
+
.ls-row.dim { opacity: 0.35; }
|
|
565
|
+
.ls-row .cv-tile { flex: none; }
|
|
566
|
+
.ls-text { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; }
|
|
567
|
+
.ls-name { font-family: var(--font-mono); font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
568
|
+
.ls-line { font-size: 12px; color: var(--ink-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
569
|
+
.ls-aside { font-family: var(--font-mono); }
|
|
570
|
+
.ls-chev { display: inline-flex; color: var(--ink-faint); flex: none; }
|
|
571
|
+
.ls-chev .ic { width: 14px; height: 14px; }
|
|
572
|
+
/* A request: who wants what, the time left, and the three answers. */
|
|
573
|
+
.ls-card {
|
|
574
|
+
display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-3); margin-bottom: var(--space-2);
|
|
575
|
+
border-radius: var(--radius-md); border: 1px solid color-mix(in srgb, var(--status-waiting) 45%, var(--line));
|
|
576
|
+
background: color-mix(in srgb, var(--status-waiting) 8%, var(--surface));
|
|
577
|
+
}
|
|
578
|
+
.ls-card-head { display: flex; align-items: flex-start; gap: var(--space-2); min-width: 0; }
|
|
579
|
+
.ls-card-who { display: flex; flex-direction: column; min-width: 0; }
|
|
580
|
+
.ls-clock { font-family: var(--font-mono); font-size: 13px; font-weight: 600; color: var(--status-waiting); flex: none; font-variant-numeric: tabular-nums; }
|
|
581
|
+
.ls-cmd {
|
|
582
|
+
margin: 0; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); background: var(--ground);
|
|
583
|
+
font-family: var(--font-mono); font-size: 12px; line-height: 1.45; white-space: pre-wrap; overflow-wrap: anywhere;
|
|
584
|
+
max-height: 120px; overflow-y: auto;
|
|
585
|
+
}
|
|
586
|
+
.ls-where { align-self: flex-start; background: none; border: 0; padding: 0; cursor: pointer; font: inherit; font-size: 12px; color: var(--accent); }
|
|
587
|
+
.ls-acts { display: flex; gap: var(--space-2); flex-wrap: wrap; }
|
|
588
|
+
.ls-acts .btn { flex: 1 1 auto; justify-content: center; }
|
|
589
|
+
.ls-empty { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-2); color: var(--ink-dim); text-align: center; }
|
|
590
|
+
.ls-empty strong { color: var(--ink); font-size: 15px; }
|
|
591
|
+
/* The inspector's two ways out: a cross beside a panel, an arrow back from a page. */
|
|
592
|
+
.ihead-back { display: none; }
|
|
593
|
+
#menu-btn { display: none; }
|
|
594
|
+
/* The List's first two sections are what the strip says; in that view it would say it twice. */
|
|
595
|
+
.stage[data-view="list"] .attention { display: none; }
|
|
596
|
+
|
|
597
|
+
/* ---------------------------------------------------------- timeline ---
|
|
598
|
+
A label column and a track. Rows scroll; the axis above them and the drawer below them stay. */
|
|
599
|
+
.tl { flex: 1; min-height: 0; min-width: 0; display: flex; flex-direction: column; background: var(--ground); --tl-label-w: 280px; }
|
|
600
|
+
.tl[hidden] { display: none; }
|
|
601
|
+
.tl-axis { display: flex; height: 32px; flex: none; border-bottom: 1px solid var(--line); }
|
|
602
|
+
.tl-axis[hidden], .tl-body[hidden], .tl-empty[hidden], .tl-note[hidden], .tl-drawer[hidden], .tl-now[hidden] { display: none; }
|
|
603
|
+
.tl-axis-label {
|
|
604
|
+
width: var(--tl-label-w); flex: none; box-sizing: border-box; padding: 0 var(--space-3);
|
|
605
|
+
display: flex; align-items: center; background: var(--surface); border-right: 1px solid var(--line);
|
|
606
|
+
font-size: 11px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-faint);
|
|
607
|
+
}
|
|
608
|
+
.tl-axis-track { flex: 1; min-width: 0; position: relative; overflow: hidden; }
|
|
609
|
+
.tl-tick {
|
|
610
|
+
position: absolute; top: 0; bottom: 0; padding-left: 6px; display: flex; align-items: center;
|
|
611
|
+
border-left: 1px solid var(--line); font-family: var(--font-mono); font-size: 11px; color: var(--ink-faint); white-space: nowrap;
|
|
612
|
+
}
|
|
613
|
+
.tl-body { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; position: relative; }
|
|
614
|
+
.tl-rows { min-height: 100%; background: linear-gradient(to right, var(--surface) var(--tl-label-w), transparent var(--tl-label-w)); }
|
|
615
|
+
.tl-row { display: flex; height: 34px; border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent); }
|
|
616
|
+
.tl-row.tl-group { height: 30px; background: color-mix(in srgb, var(--surface) 60%, var(--ground)); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
|
|
617
|
+
.tl-row.tl-agent { cursor: pointer; }
|
|
618
|
+
.tl-row.tl-agent:hover, .tl-row.on { background: color-mix(in srgb, var(--studio-accent) 9%, transparent); }
|
|
619
|
+
.tl-row.on { box-shadow: inset 2px 0 0 var(--studio-accent); }
|
|
620
|
+
.tl-row.dim .tl-lab, .tl-bar.dim { opacity: 0.3; }
|
|
621
|
+
.tl-row.tl-more { height: 30px; }
|
|
622
|
+
.tl-lab {
|
|
623
|
+
width: var(--tl-label-w); flex: none; box-sizing: border-box; min-width: 0;
|
|
624
|
+
display: flex; align-items: center; gap: var(--space-2); padding: 0 var(--space-3);
|
|
625
|
+
background: none; border: 0; border-right: 1px solid var(--line); color: var(--ink); font: inherit; text-align: left;
|
|
626
|
+
}
|
|
627
|
+
button.tl-lab { cursor: pointer; }
|
|
628
|
+
.tl-lab.indent { padding-left: var(--space-6); }
|
|
629
|
+
.tl-lab .cv-tile { flex: none; }
|
|
630
|
+
.tl-lab .sub { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
631
|
+
.tl-lab .sub[data-tone="fail"] { color: var(--status-fail); }
|
|
632
|
+
.tl-lab .sub[data-tone="wait"] { color: var(--status-waiting); }
|
|
633
|
+
.tl-name { flex: 0 1 auto; min-width: 0; font-family: var(--font-mono); font-size: 12px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
634
|
+
.tl-group .tl-name { flex-shrink: 0; max-width: 62%; }
|
|
635
|
+
.tl-chev { display: inline-flex; color: var(--ink-dim); }
|
|
636
|
+
.tl-chev .ic { width: 12px; height: 12px; }
|
|
637
|
+
.tl-lab[aria-expanded="true"] .tl-chev { rotate: 90deg; }
|
|
638
|
+
.tl-more-btn { background: none; border: 0; padding: 0; cursor: pointer; font: inherit; font-size: 12px; font-weight: 600; color: var(--accent); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
639
|
+
.tl-more-btn:hover { text-decoration: underline; }
|
|
640
|
+
.tl-track { flex: 1; min-width: 0; position: relative; overflow: hidden; }
|
|
641
|
+
/* A bar: where an agent was in time. Its colour is its status and nothing else. */
|
|
642
|
+
.tl-bar {
|
|
643
|
+
position: absolute; top: 11px; height: 12px; padding: 0; border: 0; border-radius: 3px; cursor: pointer;
|
|
644
|
+
background: var(--ink-faint);
|
|
645
|
+
}
|
|
646
|
+
.tl-group .tl-bar { top: 9px; }
|
|
647
|
+
.tl-bar.thin { height: 6px; border-radius: 2px; top: 9px; }
|
|
648
|
+
.tl-bar.thin[data-lane="1"] { top: 19px; }
|
|
649
|
+
.tl-group .tl-bar.thin { top: 7px; }
|
|
650
|
+
.tl-group .tl-bar.thin[data-lane="1"] { top: 17px; }
|
|
651
|
+
.tl-bar[data-tone="busy"] { background: var(--status-busy); }
|
|
652
|
+
.tl-bar[data-tone="done"] { background: color-mix(in srgb, var(--status-good) 55%, var(--ground)); }
|
|
653
|
+
.tl-bar[data-tone="fail"] { background: var(--status-fail); }
|
|
654
|
+
.tl-bar[data-tone="wait"] { background: var(--status-waiting); }
|
|
655
|
+
.tl-bar[data-tone="quiet"] { background: color-mix(in srgb, var(--ink-dim) 45%, var(--ground)); }
|
|
656
|
+
/* A bar that runs off the edge of what is shown is cut square there. */
|
|
657
|
+
.tl-bar[data-cut-left] { border-top-left-radius: 0; border-bottom-left-radius: 0; }
|
|
658
|
+
.tl-bar[data-cut-right] { border-top-right-radius: 0; border-bottom-right-radius: 0; }
|
|
659
|
+
.tl-bar.on { outline: 2px solid var(--studio-accent); outline-offset: 1px; }
|
|
660
|
+
.tl-x { position: absolute; top: 7px; margin-left: 3px; font-size: 14px; line-height: 20px; font-weight: 700; color: var(--status-fail); pointer-events: none; }
|
|
661
|
+
.tl-untimed { position: absolute; left: var(--space-3); top: 0; bottom: 0; display: flex; align-items: center; font-size: 12px; color: var(--ink-faint); }
|
|
662
|
+
.tl-now { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--studio-accent); pointer-events: none; z-index: 2; }
|
|
663
|
+
.tl-now::before {
|
|
664
|
+
content: attr(data-label); position: sticky; top: 2px; display: block; width: max-content; translate: calc(-100% + 2px) 0;
|
|
665
|
+
padding: 0 6px; height: 20px; line-height: 20px; border-radius: var(--radius-sm);
|
|
666
|
+
background: var(--studio-accent); color: var(--ground); font-family: var(--font-mono); font-size: 11px; font-weight: 700;
|
|
667
|
+
}
|
|
668
|
+
.tl-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-2); color: var(--ink-dim); text-align: center; padding: var(--space-6); }
|
|
669
|
+
.tl-empty strong { color: var(--ink); font-size: 15px; }
|
|
670
|
+
.tl-away { flex: none; display: flex; align-items: center; gap: var(--space-3); padding: var(--space-2) var(--space-3); border-top: 1px solid var(--line); font-size: 13px; color: var(--ink-dim); background: var(--surface); }
|
|
671
|
+
.tl-away[hidden] { display: none; }
|
|
672
|
+
.tl-note { flex: none; padding: 6px var(--space-3); border-top: 1px solid var(--line); font-size: 12px; color: var(--ink-faint); background: var(--surface); }
|
|
673
|
+
.tl-note[data-measured="false"] { color: var(--status-waiting); }
|
|
674
|
+
/* The drawer: the chosen agent, under the rows. */
|
|
675
|
+
.tl-drawer {
|
|
676
|
+
flex: none; display: flex; flex-wrap: wrap; gap: var(--space-3) var(--space-8); align-items: flex-start; max-height: 40%; overflow-y: auto;
|
|
677
|
+
padding: var(--space-4); border-top: 1px solid var(--line); background: var(--surface);
|
|
678
|
+
}
|
|
679
|
+
.tl-d-main { flex: 0 1 360px; min-width: 0; display: flex; flex-direction: column; gap: var(--space-2); }
|
|
680
|
+
.tl-d-head { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
|
|
681
|
+
.tl-d-name { font-family: var(--font-mono); font-size: 14px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
682
|
+
.tl-d-task { font-size: 15px; font-weight: 600; line-height: 1.35; overflow-wrap: anywhere; }
|
|
683
|
+
.tl-d-facts { white-space: normal; }
|
|
684
|
+
/* The error is text to be read: it takes a line of its own before it is squeezed. With `flex: 1 1 0` and no wrap
|
|
685
|
+
it was the one thing that gave way, and in a 720 px window it was 26 px wide: a letter to a line. */
|
|
686
|
+
.tl-d-error { flex: 1 1 320px; min-width: 0; }
|
|
687
|
+
.tl-d-errtext {
|
|
688
|
+
margin: 0 0 var(--space-1); padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm);
|
|
689
|
+
background: var(--ground); border: 1px solid var(--line); max-height: 96px; overflow-y: auto;
|
|
690
|
+
font-family: var(--font-mono); font-size: 12px; line-height: 1.45; white-space: pre-wrap; overflow-wrap: anywhere;
|
|
691
|
+
color: var(--status-fail);
|
|
692
|
+
}
|
|
693
|
+
.tl-d-acts { flex: none; display: flex; align-items: center; gap: var(--space-2); margin-left: auto; }
|
|
694
|
+
|
|
695
|
+
/* ---- needs attention ----
|
|
696
|
+
A strip that exists only while something needs someone. Each chip goes to its
|
|
697
|
+
agent on the canvas. */
|
|
698
|
+
.attention {
|
|
699
|
+
flex: none; height: 44px;
|
|
700
|
+
display: flex; align-items: center; gap: var(--space-2);
|
|
701
|
+
padding: 0 var(--space-4);
|
|
702
|
+
border-bottom: 1px solid var(--line);
|
|
703
|
+
background: var(--surface);
|
|
704
|
+
overflow: hidden;
|
|
705
|
+
}
|
|
706
|
+
.attention[hidden] { display: none; }
|
|
707
|
+
.cv-label, .attention-label {
|
|
708
|
+
flex: none;
|
|
709
|
+
font-size: 11px; line-height: var(--type-label-line); font-weight: var(--type-label-weight);
|
|
710
|
+
letter-spacing: var(--type-label-tracking); text-transform: uppercase; color: var(--ink-dim);
|
|
711
|
+
}
|
|
712
|
+
.attention-label { margin-right: var(--space-1); }
|
|
713
|
+
.att {
|
|
714
|
+
display: inline-flex; align-items: center; gap: var(--space-2); flex: none;
|
|
715
|
+
height: 30px; padding: 0 10px;
|
|
716
|
+
border-radius: var(--radius-pill); border: 1px solid var(--line);
|
|
717
|
+
background: var(--surface); color: var(--ink);
|
|
718
|
+
font-family: inherit; font-size: 12px; white-space: nowrap; cursor: pointer;
|
|
719
|
+
}
|
|
720
|
+
.att:hover { background: var(--surface-raised); }
|
|
721
|
+
.att-name { font-family: var(--font-mono); }
|
|
722
|
+
.att-says { color: var(--ink-dim); }
|
|
723
|
+
.attention-hint { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
724
|
+
|
|
725
|
+
/* -------------------------------------------------------------- dock ---
|
|
726
|
+
The approval dock: a row under the canvas, there only while a tool call is
|
|
727
|
+
waiting. It is part of the stage's column, so the canvas above it stays
|
|
728
|
+
whole and usable; nothing is covered and nothing is modal. */
|
|
729
|
+
.dock {
|
|
730
|
+
flex: none; display: grid; align-items: center;
|
|
731
|
+
grid-template-columns: auto minmax(0, 1fr) auto auto;
|
|
732
|
+
gap: var(--space-2) var(--space-4);
|
|
733
|
+
min-height: 72px; padding: var(--space-2) var(--space-4);
|
|
734
|
+
border-top: 1px solid color-mix(in srgb, var(--status-waiting) 45%, var(--line));
|
|
735
|
+
background: color-mix(in srgb, var(--status-waiting) 8%, var(--surface));
|
|
736
|
+
}
|
|
737
|
+
/* `display: grid` outranks the UA's `[hidden]`; without these the dock and its parts would stay. */
|
|
738
|
+
.dock[hidden], .dock [hidden] { display: none; }
|
|
739
|
+
.dock:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
|
740
|
+
.dock-timer { position: relative; width: 36px; height: 36px; flex: none; }
|
|
741
|
+
.dock-timer svg { width: 36px; height: 36px; rotate: -90deg; }
|
|
742
|
+
.dock-ring-track, .dock-ring { fill: none; stroke-width: 3; }
|
|
743
|
+
.dock-ring-track { stroke: var(--line); }
|
|
744
|
+
.dock-ring { stroke: var(--status-waiting); stroke-linecap: round; }
|
|
745
|
+
.dock-secs {
|
|
746
|
+
position: absolute; inset: 0; display: grid; place-items: center;
|
|
747
|
+
font-family: var(--font-mono); font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums;
|
|
748
|
+
}
|
|
749
|
+
.dock-text { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
|
|
750
|
+
.dock-head { display: flex; align-items: baseline; gap: var(--space-2); font-size: 13px; min-width: 0; }
|
|
751
|
+
.dock-word { font-weight: 600; color: var(--status-waiting); flex: none; }
|
|
752
|
+
.dock[data-state="denied"] .dock-word, .dock[data-state="timed-out"] .dock-word { color: var(--status-bad); }
|
|
753
|
+
.dock-who {
|
|
754
|
+
background: none; border: none; padding: 0; cursor: pointer; font: inherit; font-weight: 600; color: var(--ink);
|
|
755
|
+
min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
756
|
+
}
|
|
757
|
+
.dock-who:hover { text-decoration: underline; }
|
|
758
|
+
.dock-tool { font-family: var(--font-mono); font-size: 12px; color: var(--ink-dim); flex: none; }
|
|
759
|
+
.dock-where { font-size: 12px; color: var(--ink-faint); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
760
|
+
/* The command: one line until it is asked for, then all of it. */
|
|
761
|
+
.dock-cmd {
|
|
762
|
+
background: none; border: none; padding: 0; cursor: pointer; text-align: left;
|
|
763
|
+
font-family: var(--font-mono); font-size: 12px; color: var(--ink);
|
|
764
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; max-width: 100%;
|
|
765
|
+
}
|
|
766
|
+
.dock-cmd.open { white-space: pre-wrap; overflow-wrap: anywhere; overflow-y: auto; max-height: 30vh; }
|
|
767
|
+
.dock-note { font-size: 12px; color: var(--ink-dim); }
|
|
768
|
+
.dock-nav { display: flex; align-items: center; gap: 2px; flex: none; }
|
|
769
|
+
.dock-count { font-size: 12px; color: var(--ink-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
770
|
+
.dock-acts { display: flex; gap: var(--space-2); flex: none; }
|
|
771
|
+
/* Two rows: what is asked, then the answers under it. app.js sets the form from the dock's own width, so a
|
|
772
|
+
dock squeezed between two open panels gets it too. */
|
|
773
|
+
.dock[data-form="stacked"] { grid-template-columns: auto minmax(0, 1fr) auto; }
|
|
774
|
+
.dock[data-form="stacked"] .dock-acts { grid-column: 1 / -1; justify-content: flex-end; flex-wrap: wrap; }
|
|
775
|
+
|
|
776
|
+
/* ============================================================== graph ===
|
|
777
|
+
A dot grid reads as a workspace rather than a void, and it makes panning
|
|
323
778
|
legible — without it the canvas looks frozen while it moves. */
|
|
324
779
|
.cv-root {
|
|
325
|
-
background-image: radial-gradient(var(--
|
|
326
|
-
background-size:
|
|
780
|
+
background-image: radial-gradient(var(--grid-dot) 1px, transparent 1.2px);
|
|
781
|
+
background-size: 20px 20px;
|
|
327
782
|
cursor: grab;
|
|
328
783
|
touch-action: none;
|
|
329
784
|
user-select: none;
|
|
785
|
+
|
|
786
|
+
/* Two tones the design draws between tokens. A wire at rest is a hairline
|
|
787
|
+
that still reads on the ground; a group's well is the ground showing
|
|
788
|
+
through a card. */
|
|
789
|
+
--edge-rest: color-mix(in srgb, var(--line) 80%, var(--ink-dim));
|
|
790
|
+
--well: color-mix(in srgb, var(--surface) 55%, var(--ground));
|
|
330
791
|
}
|
|
331
792
|
.cv-root.cv-panning { cursor: grabbing; }
|
|
332
793
|
|
|
333
794
|
.cv-viewport { position: absolute; inset: 0; transform-origin: 0 0; }
|
|
334
|
-
.cv-edges { position: absolute; overflow: visible; pointer-events: none; }
|
|
795
|
+
.cv-edges { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; }
|
|
335
796
|
.cv-nodes { position: absolute; inset: 0; }
|
|
336
797
|
|
|
337
798
|
/* ------------------------------------------------------------- edge ---
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
the
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
tone — and motion is only ever the third. That is what makes the
|
|
344
|
-
reduced-motion path below a removal of speed rather than of information. */
|
|
799
|
+
A wire says the state of what it leads to, in the same colour that state has
|
|
800
|
+
everywhere else, and never in colour alone: weight and dash change with it.
|
|
801
|
+
Motion is only ever the third channel, and only a working wire has it. That
|
|
802
|
+
is what makes the reduced-motion path below a removal of speed rather than
|
|
803
|
+
of information. */
|
|
345
804
|
|
|
346
805
|
.cv-edge {
|
|
347
806
|
fill: none;
|
|
348
|
-
stroke
|
|
807
|
+
stroke: var(--edge-rest);
|
|
808
|
+
stroke-width: 1.5;
|
|
349
809
|
stroke-linecap: round;
|
|
350
810
|
stroke-dasharray: none;
|
|
351
811
|
stroke-dashoffset: 0;
|
|
352
|
-
opacity: var(--cv-edge-done);
|
|
353
812
|
/* One dash period. The flow animation travels exactly this far per
|
|
354
813
|
iteration, so a state can change its dash pattern without putting a seam
|
|
355
814
|
in the loop. */
|
|
356
815
|
--dash-period: 13px;
|
|
357
816
|
}
|
|
358
817
|
|
|
359
|
-
/* Working. The brightest
|
|
818
|
+
/* Working. The brightest and the only one that travels. */
|
|
360
819
|
.cv-edge[data-state="live"] {
|
|
820
|
+
stroke: var(--status-busy);
|
|
361
821
|
opacity: var(--cv-edge-live);
|
|
362
|
-
stroke-width:
|
|
822
|
+
stroke-width: 1.8;
|
|
363
823
|
stroke-dasharray: 6px 7px;
|
|
364
824
|
--dash-period: 13px;
|
|
365
825
|
animation: cv-flow 0.9s linear infinite;
|
|
366
826
|
}
|
|
367
827
|
|
|
368
|
-
/* Spawned, nothing back yet. Same
|
|
828
|
+
/* Spawned, nothing back yet. Same colour, shorter dash, moving slowly:
|
|
369
829
|
something is on this wire, and it has not produced anything. */
|
|
370
830
|
.cv-edge[data-state="waking"] {
|
|
831
|
+
stroke: var(--status-busy);
|
|
371
832
|
opacity: var(--cv-edge-wake);
|
|
372
|
-
stroke-width: 1.
|
|
833
|
+
stroke-width: 1.5;
|
|
373
834
|
stroke-dasharray: 2px 7px;
|
|
374
835
|
--dash-period: 9px;
|
|
375
836
|
animation: cv-flow 1.9s linear infinite;
|
|
376
837
|
}
|
|
377
838
|
|
|
839
|
+
/* Waiting on the viewer. Dashed like work, still like a question. */
|
|
840
|
+
.cv-edge[data-state="waiting"] {
|
|
841
|
+
stroke: var(--status-waiting);
|
|
842
|
+
opacity: 1;
|
|
843
|
+
stroke-width: 1.8;
|
|
844
|
+
stroke-dasharray: 5px 4px;
|
|
845
|
+
}
|
|
846
|
+
|
|
378
847
|
/* Finished. Solid and pulled back, so live work stands out against it. */
|
|
379
848
|
.cv-edge[data-state="done"] {
|
|
380
|
-
|
|
381
|
-
|
|
849
|
+
stroke: var(--edge-rest);
|
|
850
|
+
opacity: 1;
|
|
851
|
+
stroke-width: 1.5;
|
|
382
852
|
stroke-dasharray: none;
|
|
383
853
|
}
|
|
384
854
|
|
|
385
855
|
/* Ended, stale. Still there, saying nothing. */
|
|
386
856
|
.cv-edge[data-state="quiet"] {
|
|
387
|
-
|
|
388
|
-
|
|
857
|
+
stroke: var(--edge-rest);
|
|
858
|
+
opacity: var(--cv-edge-wake);
|
|
859
|
+
stroke-width: 1.5;
|
|
389
860
|
stroke-dasharray: 2px 6px;
|
|
390
861
|
}
|
|
391
862
|
|
|
392
|
-
/* Wrong, and said in colour
|
|
393
|
-
itself is set to var(--cv-fail) rather than the child's palette colour. */
|
|
863
|
+
/* Wrong, and said in the colour failure has everywhere. */
|
|
394
864
|
.cv-edge[data-state="failed"] {
|
|
865
|
+
stroke: var(--cv-fail);
|
|
395
866
|
opacity: 1;
|
|
396
|
-
stroke-width: 2
|
|
397
|
-
stroke-dasharray:
|
|
867
|
+
stroke-width: 2;
|
|
868
|
+
stroke-dasharray: none;
|
|
398
869
|
}
|
|
399
870
|
|
|
400
|
-
/*
|
|
401
|
-
|
|
402
|
-
twelve independent decisions. */
|
|
403
|
-
.cv-edge[data-group="member"] { stroke-width: 1.4; }
|
|
404
|
-
.cv-edge[data-group="member"][data-state="live"] { stroke-width: 1.8; }
|
|
871
|
+
/* The way from the session to what is selected. */
|
|
872
|
+
.cv-edge.cv-path { stroke: var(--studio-accent); stroke-width: 2.4; opacity: 1; }
|
|
405
873
|
|
|
406
|
-
/* Arrival. The
|
|
874
|
+
/* Arrival. The wire draws itself from the parent toward the card that just
|
|
407
875
|
appeared. Both the pattern and the offset live in the keyframes, so with
|
|
408
|
-
the animation switched off the
|
|
876
|
+
the animation switched off the wire simply renders in its final state
|
|
409
877
|
wearing its own status dash. */
|
|
410
878
|
.cv-edge.cv-drawing { animation: cv-draw 0.3s ease-out; }
|
|
411
879
|
|
|
@@ -415,370 +883,237 @@ body.resizing .cv-root { pointer-events: none; }
|
|
|
415
883
|
to { stroke-dasharray: var(--edge-len) var(--edge-len); stroke-dashoffset: 0; }
|
|
416
884
|
}
|
|
417
885
|
|
|
418
|
-
/* -------------------------------------------------------------
|
|
886
|
+
/* ------------------------------------------------------------- card ---
|
|
887
|
+
One frame for every kind of thing on the canvas. Its colour is the
|
|
888
|
+
surface's; what changes with status is the dot, its word, and — for the two
|
|
889
|
+
states that need someone — the frame. */
|
|
419
890
|
|
|
420
891
|
.cv-node {
|
|
421
|
-
position: absolute;
|
|
422
|
-
width: 236px;
|
|
423
|
-
min-height: 104px;
|
|
892
|
+
position: absolute; left: 0; top: 0;
|
|
424
893
|
box-sizing: border-box;
|
|
894
|
+
display: flex; flex-direction: column; gap: var(--space-1);
|
|
895
|
+
padding: 9px var(--space-3);
|
|
425
896
|
background: var(--surface);
|
|
426
897
|
border: 1px solid var(--line);
|
|
427
|
-
border-radius:
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
.cv-node
|
|
434
|
-
.cv-node.cv-
|
|
435
|
-
.
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
.cv-node
|
|
440
|
-
|
|
441
|
-
.cv-accent {
|
|
442
|
-
position: absolute; left: 0; top: 12px; bottom: 12px; width: 3px;
|
|
443
|
-
border-radius: 0 3px 3px 0; background: var(--node-color);
|
|
444
|
-
}
|
|
898
|
+
border-radius: var(--radius-md);
|
|
899
|
+
cursor: pointer;
|
|
900
|
+
transition: border-color 0.15s, opacity 0.25s, scale 0.25s;
|
|
901
|
+
}
|
|
902
|
+
.cv-node:hover { border-color: var(--ink-faint); }
|
|
903
|
+
.cv-node.cv-dragging { cursor: grabbing; z-index: 10; box-shadow: 0 12px 30px -12px var(--shade); }
|
|
904
|
+
.cv-node.cv-selected { border-color: var(--studio-accent); box-shadow: 0 0 0 1px var(--studio-accent); }
|
|
905
|
+
.cv-node.cv-born { opacity: 0; scale: 0.94; }
|
|
906
|
+
/* Only a card that IS one agent takes the frame of its state. A group's frame stays plain: one failed member is
|
|
907
|
+
said by its bar and by the member, not by reddening eleven others. */
|
|
908
|
+
.cv-node[data-kind="agent"][data-state="failed"] { border-color: color-mix(in srgb, var(--cv-fail) 45%, var(--line)); }
|
|
909
|
+
.cv-node[data-kind="agent"][data-state="waiting"] { border-color: color-mix(in srgb, var(--status-waiting) 50%, var(--line)); }
|
|
910
|
+
.cv-node[data-kind="agent"][data-state="quiet"] > * { opacity: 0.72; }
|
|
445
911
|
|
|
446
912
|
/* A running card, legibly alive when nothing else on the canvas is moving.
|
|
447
|
-
One pulse per card and no more: the accent bar used to breathe as well, and
|
|
448
|
-
two rhythms on one card is the wall of throbbing this is trying to avoid.
|
|
449
|
-
|
|
450
913
|
The ring is drawn whether or not it breathes, so "running" survives with
|
|
451
914
|
motion off; breathing is the only part the animation adds. Only opacity is
|
|
452
915
|
animated — the shadow itself never changes, so nothing re-rasterises. */
|
|
453
|
-
.cv-node[data-state="live"]::after {
|
|
916
|
+
.cv-node[data-kind="agent"][data-state="live"]::after {
|
|
454
917
|
content: '';
|
|
455
918
|
position: absolute; inset: -2px;
|
|
456
|
-
border-radius:
|
|
919
|
+
border-radius: 12px;
|
|
457
920
|
pointer-events: none;
|
|
458
|
-
box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--
|
|
921
|
+
box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--status-busy) 55%, transparent);
|
|
459
922
|
animation: cv-pulse 2.4s ease-in-out infinite;
|
|
460
923
|
}
|
|
461
924
|
@keyframes cv-pulse { 0%, 100% { opacity: 0.9; } 50% { opacity: 0.28; } }
|
|
462
925
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
.cv-
|
|
466
|
-
.cv-
|
|
467
|
-
.cv-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
.cv-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
473
|
-
.cv-type {
|
|
474
|
-
font-family: var(--mono); font-size: 11px; font-weight: 600;
|
|
475
|
-
letter-spacing: 0.06em; text-transform: uppercase; color: var(--node-color);
|
|
476
|
-
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
477
|
-
}
|
|
478
|
-
.cv-type.cv-unknown::after {
|
|
479
|
-
content: '?'; margin-left: 5px; color: var(--ink-faint);
|
|
480
|
-
border: 1px solid var(--line); border-radius: 3px; padding: 0 3px; font-size: 9px;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
.cv-chip {
|
|
484
|
-
font-family: var(--mono); font-size: 10px; letter-spacing: 0.05em;
|
|
485
|
-
text-transform: uppercase; padding: 1px 6px; border-radius: 999px;
|
|
486
|
-
border: 1px solid var(--line); color: var(--ink-faint); white-space: nowrap; flex: none;
|
|
487
|
-
}
|
|
488
|
-
.cv-chip[data-status="running"] { color: var(--busy); border-color: color-mix(in srgb, var(--busy) 45%, transparent); }
|
|
489
|
-
.cv-chip[data-status="done"] { color: var(--good); border-color: color-mix(in srgb, var(--good) 40%, transparent); }
|
|
490
|
-
.cv-chip[data-status="stale"] { color: var(--waiting); border-color: color-mix(in srgb, var(--waiting) 40%, transparent); }
|
|
491
|
-
.cv-chip[data-status="starting"]{ color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
|
|
492
|
-
|
|
493
|
-
.cv-desc {
|
|
494
|
-
margin-top: 7px; font-size: 13px; line-height: 1.35; color: var(--ink);
|
|
495
|
-
display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2;
|
|
496
|
-
-webkit-box-orient: vertical; overflow: hidden;
|
|
497
|
-
}
|
|
498
|
-
.cv-node[data-kind="session"] .cv-desc { font-family: var(--mono); font-size: 12px; color: var(--ink-dim); }
|
|
499
|
-
|
|
500
|
-
.cv-foot { margin-top: 9px; display: flex; flex-wrap: wrap; gap: 5px; }
|
|
501
|
-
.cv-bit {
|
|
502
|
-
font-family: var(--mono); font-size: 10px; color: var(--ink-faint);
|
|
503
|
-
background: var(--surface-2); border-radius: 4px; padding: 1px 6px;
|
|
926
|
+
.cv-row { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
|
|
927
|
+
.cv-fill { flex: 1; min-width: 0; }
|
|
928
|
+
.cv-indent { padding-left: 32px; }
|
|
929
|
+
.cv-title { font-size: 13px; font-weight: 600; }
|
|
930
|
+
.cv-name {
|
|
931
|
+
min-width: 0; font-family: var(--font-mono); font-size: 12px; line-height: 16px; color: var(--ink);
|
|
932
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
504
933
|
}
|
|
505
|
-
.cv-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
/* Ports sit on whichever axis the flow runs along. */
|
|
510
|
-
.cv-port {
|
|
511
|
-
position: absolute; width: 9px; height: 9px; border-radius: 50%;
|
|
512
|
-
background: var(--surface); border: 2px solid var(--node-color);
|
|
934
|
+
.cv-task {
|
|
935
|
+
min-width: 0; font-size: 13px; line-height: 18px; color: var(--ink-dim);
|
|
936
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
513
937
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
.cv-
|
|
526
|
-
|
|
527
|
-
|
|
938
|
+
.cv-sub { font-size: 12px; line-height: 16px; color: var(--ink-dim); white-space: nowrap; flex: none; }
|
|
939
|
+
.cv-word { font-size: 11px; color: var(--ink-dim); white-space: nowrap; flex: none; }
|
|
940
|
+
|
|
941
|
+
/* The tile says whose agent it is. Crewforth's mark keeps its own colours; the
|
|
942
|
+
mark for everything else is drawn in the quiet text colour. */
|
|
943
|
+
.cv-tile {
|
|
944
|
+
width: 24px; height: 24px; flex: none; position: relative;
|
|
945
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
946
|
+
border-radius: var(--radius-sm); border: 1px solid var(--line);
|
|
947
|
+
background: var(--surface-raised); color: var(--ink-dim);
|
|
948
|
+
}
|
|
949
|
+
.cv-tile svg { width: 64%; height: 64%; display: block; }
|
|
950
|
+
.cv-tile-sm { width: 18px; height: 18px; }
|
|
951
|
+
/* A type nobody declared is marked as one. It is not quietly drawn as built-in. */
|
|
952
|
+
.cv-tile[data-source="unknown"]::after {
|
|
953
|
+
content: '?';
|
|
954
|
+
position: absolute; right: -5px; top: -5px;
|
|
955
|
+
width: 12px; height: 12px; border-radius: var(--radius-pill);
|
|
956
|
+
display: flex; align-items: center; justify-content: center;
|
|
528
957
|
background: var(--surface); border: 1px solid var(--line);
|
|
529
|
-
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
958
|
+
font-family: var(--font-mono); font-size: 9px; line-height: 1; color: var(--ink-dim);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
.cv-pill {
|
|
962
|
+
display: inline-flex; align-items: center; gap: 5px; flex: none;
|
|
963
|
+
height: 20px; padding: 0 6px;
|
|
964
|
+
border-radius: var(--radius-sm); background: var(--surface-raised);
|
|
965
|
+
font-size: 11px; font-weight: 600; color: var(--ink); white-space: nowrap;
|
|
966
|
+
}
|
|
967
|
+
.cv-pill[hidden] { display: none; }
|
|
968
|
+
.cv-node[data-kind="agent"][data-state="failed"] .cv-pill { color: var(--cv-fail); }
|
|
969
|
+
|
|
970
|
+
/* An agent at compact density: one line. */
|
|
971
|
+
.cv-node.cv-chip { flex-direction: row; align-items: center; gap: var(--space-2); padding: 0 10px; border-radius: 8px; }
|
|
972
|
+
.cv-chip .cv-name { flex: 0 1 auto; }
|
|
973
|
+
.cv-chip .cv-task { flex: 1 1 0; font-size: 12px; }
|
|
974
|
+
|
|
975
|
+
/* The session: where everything starts. */
|
|
976
|
+
.cv-session { cursor: pointer; }
|
|
977
|
+
|
|
978
|
+
/* A folded group is one card standing for several: a second card shows behind
|
|
979
|
+
it. Open, it is a well holding its members. */
|
|
980
|
+
.cv-group { gap: var(--space-2); padding: 10px var(--space-3); }
|
|
981
|
+
.cv-group:not(.cv-open) { box-shadow: 6px -6px 0 -1px var(--well), 6px -6px 0 0 var(--line); }
|
|
982
|
+
.cv-group.cv-selected:not(.cv-open) { box-shadow: 0 0 0 1px var(--studio-accent), 6px -6px 0 -1px var(--well), 6px -6px 0 0 var(--line); }
|
|
983
|
+
.cv-group.cv-open { background: var(--well); padding: 10px var(--space-2) var(--space-2); gap: 6px; cursor: default; }
|
|
984
|
+
.cv-group-head { height: 20px; cursor: pointer; }
|
|
985
|
+
.cv-group.cv-open .cv-group-head { padding: 0 var(--space-1); }
|
|
986
|
+
.cv-fold {
|
|
987
|
+
width: 20px; height: 20px; padding: 0; flex: none;
|
|
988
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
989
|
+
background: none; border: 0; border-radius: var(--radius-sm); color: var(--ink-dim); cursor: pointer;
|
|
990
|
+
}
|
|
991
|
+
.cv-fold .ic { width: 12px; height: 12px; }
|
|
992
|
+
.cv-fold:hover { color: var(--ink); background: var(--surface-raised); }
|
|
993
|
+
|
|
994
|
+
/* How the group went: one segment per status, as wide as its share. The words
|
|
995
|
+
are beside it when the group is folded and in its title when it is open. */
|
|
996
|
+
.cv-bar { display: flex; gap: 2px; height: 6px; flex: 1; min-width: 48px; }
|
|
997
|
+
.cv-group.cv-open > .cv-bar { height: 4px; flex: none; margin: 0 var(--space-1); }
|
|
998
|
+
.cv-bar span { display: block; border-radius: 2px; min-width: 2px; background: var(--ink-faint); }
|
|
999
|
+
.cv-bar span[data-tone="good"] { background: var(--status-good); }
|
|
1000
|
+
.cv-bar span[data-tone="busy"] { background: var(--status-busy); }
|
|
1001
|
+
.cv-bar span[data-tone="waiting"] { background: var(--status-waiting); }
|
|
1002
|
+
.cv-bar span[data-tone="fail"] { background: var(--status-fail); }
|
|
1003
|
+
.cv-bar span[data-tone="idle"] { background: var(--status-idle); }
|
|
1004
|
+
|
|
1005
|
+
/* A member inside an open group: a row with its mark, or a chip in a grid. */
|
|
1006
|
+
.cv-member {
|
|
1007
|
+
position: absolute; left: 0; top: 0; box-sizing: border-box;
|
|
1008
|
+
display: flex; align-items: center; gap: var(--space-2);
|
|
1009
|
+
padding: 0 10px;
|
|
1010
|
+
background: var(--surface); border: 1px solid var(--line); border-radius: 8px;
|
|
1011
|
+
cursor: pointer;
|
|
539
1012
|
}
|
|
540
|
-
|
|
541
|
-
.cv-
|
|
542
|
-
.cv-
|
|
543
|
-
.cv-
|
|
1013
|
+
.cv-member:hover { border-color: var(--ink-faint); }
|
|
1014
|
+
.cv-member.cv-selected { border-color: var(--studio-accent); box-shadow: 0 0 0 1px var(--studio-accent); }
|
|
1015
|
+
.cv-member[data-state="failed"] .cv-word { color: var(--cv-fail); }
|
|
1016
|
+
.cv-member-chip .cv-name { font-family: var(--font-sans); color: var(--ink-dim); flex: 1 1 0; }
|
|
1017
|
+
|
|
1018
|
+
/* A status the filter did not ask for, stepped back. */
|
|
1019
|
+
.cv-node.cv-dim, .cv-member.cv-dim { opacity: 0.22; }
|
|
1020
|
+
.cv-edge.cv-dim { opacity: 0.08; }
|
|
544
1021
|
|
|
545
1022
|
.cv-empty {
|
|
546
1023
|
position: absolute; inset: 0; display: grid; place-content: center;
|
|
547
1024
|
text-align: center; color: var(--ink-dim); padding: 24px; pointer-events: none;
|
|
548
1025
|
}
|
|
549
1026
|
.cv-empty strong { display: block; color: var(--ink); margin-bottom: 6px; font-size: 15px; }
|
|
1027
|
+
/* `display: grid` outranks the UA's `[hidden] { display: none }`. */
|
|
1028
|
+
.cv-empty[hidden] { display: none; }
|
|
1029
|
+
|
|
1030
|
+
/* ---------------------------------------------------- map and legend ---
|
|
1031
|
+
Zoomed out, a card's words stop being readable. What still has to be found
|
|
1032
|
+
is where things are and which of them need someone, and the map keeps both:
|
|
1033
|
+
every box, and the two colours that mean "look here". */
|
|
1034
|
+
.cv-minimap {
|
|
1035
|
+
position: absolute; left: var(--space-6); bottom: var(--space-6); z-index: 20;
|
|
1036
|
+
width: 200px; height: 88px; overflow: hidden;
|
|
1037
|
+
background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-md);
|
|
1038
|
+
cursor: pointer;
|
|
1039
|
+
}
|
|
1040
|
+
.cv-minimap[hidden] { display: none; }
|
|
1041
|
+
.cv-map-box { fill: var(--line); }
|
|
1042
|
+
.cv-map-box[data-tone="waiting"] { fill: var(--status-waiting); }
|
|
1043
|
+
.cv-map-box[data-tone="fail"] { fill: var(--status-fail); }
|
|
1044
|
+
.cv-map-box[data-tone="selected"] { fill: var(--studio-accent); }
|
|
1045
|
+
.cv-map-frame { fill: none; stroke: var(--studio-accent); stroke-width: 1.2; }
|
|
1046
|
+
|
|
1047
|
+
.cv-legend {
|
|
1048
|
+
position: absolute; right: var(--space-6); bottom: var(--space-6); z-index: 20;
|
|
1049
|
+
width: 260px; box-sizing: border-box; padding: var(--space-3);
|
|
1050
|
+
display: flex; flex-direction: column; gap: var(--space-2);
|
|
1051
|
+
background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-md);
|
|
1052
|
+
cursor: default;
|
|
1053
|
+
}
|
|
1054
|
+
.cv-legend[hidden] { display: none; }
|
|
1055
|
+
.cv-legend-head { display: flex; align-items: center; justify-content: space-between; }
|
|
1056
|
+
.cv-legend-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-1) var(--space-2); }
|
|
1057
|
+
.cv-legend-item { display: flex; align-items: center; gap: var(--space-2); font-size: 12px; color: var(--ink-dim); white-space: nowrap; }
|
|
1058
|
+
.cv-legend-line { width: 28px; height: 0; flex: none; border-top: 1.5px solid var(--edge-rest); }
|
|
1059
|
+
.cv-legend-line[data-state="live"] { border-top: 1.5px dashed var(--status-busy); }
|
|
1060
|
+
.cv-legend-line[data-state="waiting"] { border-top: 1.5px dashed var(--status-waiting); }
|
|
1061
|
+
.cv-legend-line[data-state="failed"] { border-top-color: var(--cv-fail); }
|
|
1062
|
+
.cv-x {
|
|
1063
|
+
width: 20px; height: 20px; padding: 0; display: inline-flex; align-items: center; justify-content: center;
|
|
1064
|
+
background: none; border: 0; border-radius: var(--radius-sm); color: var(--ink-dim); cursor: pointer;
|
|
1065
|
+
}
|
|
1066
|
+
.cv-x .ic { width: 12px; height: 12px; }
|
|
1067
|
+
.cv-x:hover { color: var(--ink); background: var(--surface-raised); }
|
|
1068
|
+
|
|
1069
|
+
.cv-tip {
|
|
1070
|
+
position: absolute; z-index: 30; max-width: 300px; pointer-events: none;
|
|
1071
|
+
display: flex; flex-direction: column; gap: 2px;
|
|
1072
|
+
padding: var(--space-2) var(--space-3);
|
|
1073
|
+
background: var(--surface-raised); border: 1px solid var(--line); border-radius: var(--radius-md);
|
|
1074
|
+
box-shadow: 0 12px 30px -12px var(--shade);
|
|
1075
|
+
font-size: 12px; line-height: 18px; color: var(--ink);
|
|
1076
|
+
}
|
|
1077
|
+
.cv-tip[hidden] { display: none; }
|
|
1078
|
+
.cv-tip strong { font-family: var(--font-mono); font-weight: 600; }
|
|
1079
|
+
|
|
1080
|
+
/* The small chip the inspector's gate summary is made of. */
|
|
1081
|
+
.cv-bit {
|
|
1082
|
+
font-family: var(--font-mono); font-size: 10px; color: var(--ink-faint);
|
|
1083
|
+
background: var(--surface-raised); border-radius: 4px; padding: 1px 6px;
|
|
1084
|
+
}
|
|
550
1085
|
|
|
551
1086
|
/* --------------------------------------------------------- inspector --- */
|
|
552
1087
|
|
|
1088
|
+
/* Docked beside the canvas when there is room for it, floating over the canvas
|
|
1089
|
+
when there is not. Room means: 1280px or more, and no conversation open —
|
|
1090
|
+
navigator, inspector and conversation side by side leave no graph at all.
|
|
1091
|
+
Floating is the base rule, so a width the queries below do not name gets the
|
|
1092
|
+
form that never squeezes the canvas. */
|
|
553
1093
|
.inspector {
|
|
554
|
-
width:
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
letter-spacing: 0.06em; text-transform: uppercase;
|
|
1094
|
+
width: var(--inspector-w);
|
|
1095
|
+
min-height: 0;
|
|
1096
|
+
display: flex; flex-direction: column;
|
|
1097
|
+
border-left: 1px solid var(--line);
|
|
1098
|
+
background: var(--surface);
|
|
560
1099
|
}
|
|
561
|
-
|
|
1100
|
+
/* No grid-column while it floats: an absolutely placed grid child that names a
|
|
1101
|
+
column is positioned inside that column, and column 4 is empty then. */
|
|
1102
|
+
.shell > .inspector {
|
|
1103
|
+
position: absolute; z-index: 30;
|
|
1104
|
+
/* It stops above the approval dock: the answers are never under a panel. */
|
|
1105
|
+
top: var(--toolbar-h); bottom: var(--dock-h, 0px);
|
|
1106
|
+
right: calc(var(--chat-w, 480px) + 5px);
|
|
1107
|
+
box-shadow: -12px 0 24px -18px var(--shade);
|
|
1108
|
+
}
|
|
1109
|
+
.shell.no-chat > .inspector { right: 0; }
|
|
1110
|
+
/* `display: flex` outranks the UA's `[hidden] { display: none }`, so the hidden
|
|
1111
|
+
panel kept its width. Restate it explicitly. */
|
|
1112
|
+
.inspector[hidden] { display: none; }
|
|
1113
|
+
|
|
562
1114
|
.inspector dl { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 6px 12px; }
|
|
563
1115
|
.inspector dt { font-size: 11px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.05em; }
|
|
564
|
-
.inspector dd { margin: 0; font-family: var(--mono); font-size: 12px; word-break: break-word; }
|
|
565
|
-
.inspector .tools { margin-top: 16px; display: flex; flex-wrap: wrap; gap: 5px; }
|
|
566
|
-
|
|
567
|
-
@media (max-width: 900px) {
|
|
568
|
-
.shell { grid-template-columns: 1fr; }
|
|
569
|
-
.side, .resizer { display: none; }
|
|
570
|
-
.inspector { display: none; }
|
|
571
|
-
}
|
|
572
|
-
/* =================================================== three readings ===
|
|
573
|
-
One picture, read three ways, chosen by how much of a card would actually
|
|
574
|
-
survive on screen. Nothing moves between them: the layout metrics are
|
|
575
|
-
identical in all three, so the shape of the graph is the constant and only
|
|
576
|
-
what is written inside a card changes.
|
|
577
|
-
|
|
578
|
-
The idea is not "hide things at low zoom" — hiding alone still leaves an
|
|
579
|
-
unreadable title. What survives is drawn at a constant SCREEN size: a world
|
|
580
|
-
font-size of 11px/k is 11px after the canvas transform, whatever k is.
|
|
581
|
-
canvas.js publishes k as --k and the reading as data-lod on the root, and
|
|
582
|
-
every card responds through the cascade, so a band change costs nothing per
|
|
583
|
-
node and cannot move the world that decided the band.
|
|
584
|
-
|
|
585
|
-
Registered so that a malformed write falls back to 1 rather than blanking
|
|
586
|
-
250 cards. k is clamped to MIN_K = 0.06 in canvas.js, so no divisor here is
|
|
587
|
-
ever zero. (The selfcheck's CSS parser drops at-rules, so this registration
|
|
588
|
-
is invisible to the gate — it is verified in a browser.) */
|
|
589
|
-
@property --k { syntax: '<number>'; initial-value: 1; inherits: true; }
|
|
590
|
-
|
|
591
|
-
/* NEAR — k >= 0.70. The card exactly as it has always been drawn, so it has
|
|
592
|
-
no rules of its own here. */
|
|
593
|
-
|
|
594
|
-
/* Blocks a smaller reading gives up fade rather than vanish, so the eye reads
|
|
595
|
-
a substitution instead of a loss. */
|
|
596
|
-
.cv-desc, .cv-foot, .cv-chip { transition: opacity 120ms; }
|
|
597
|
-
/* The same element is the 14px source glyph up close and the hue disc far
|
|
598
|
-
out; it travels there rather than being swapped. */
|
|
599
|
-
.cv-mark { transition: transform 160ms ease, width 160ms ease, height 160ms ease; }
|
|
600
|
-
|
|
601
|
-
/* MID — 0.20 <= k < 0.70, under the label budget. Short type, status chip,
|
|
602
|
-
mark, fold caret. The description and the foot bits are dropped not because
|
|
603
|
-
they are small but because they are DARK: the description fills the lower
|
|
604
|
-
half of the card in unreadable grey, and forty of those lower halves is the
|
|
605
|
-
noise floor that stops the type line reading. Removing them is what creates
|
|
606
|
-
the contrast the type needs. */
|
|
607
|
-
[data-lod="mid"] .cv-desc,
|
|
608
|
-
[data-lod="mid"] .cv-foot { opacity: 0; }
|
|
609
|
-
|
|
610
|
-
/* The chip keeps its channel and loses its words. Its text is a 10px absolute
|
|
611
|
-
glyph, so at the bottom of this band it is 2px on screen — unreadable, and
|
|
612
|
-
measured in a browser it was eating the width the type line needed: SESSION
|
|
613
|
-
was clipping to SESSI and REVIEWER to REVIEWE. As a dot it still says the
|
|
614
|
-
status in colour and gives 45 world px back to the name. */
|
|
615
|
-
[data-lod="mid"] .cv-chip {
|
|
616
|
-
font-size: 0;
|
|
617
|
-
padding: 0;
|
|
618
|
-
width: calc(7px / var(--k)); /* 7 SCREEN px at any k */
|
|
619
|
-
height: calc(7px / var(--k));
|
|
620
|
-
border-radius: 50%;
|
|
621
|
-
background: currentColor;
|
|
622
|
-
border-color: currentColor;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
/* The real type line is taken out of the flow and redrawn counter-scaled, as
|
|
626
|
-
the short name: the crew- prefix and the -expert / -agent suffix dropped. */
|
|
627
|
-
[data-lod="mid"] .cv-type,
|
|
628
|
-
[data-lod="far"] .cv-type { font-size: 0; letter-spacing: 0; }
|
|
629
|
-
[data-lod="mid"] .cv-type::after,
|
|
630
|
-
[data-lod="far"] .cv-type::after { content: none; }
|
|
631
|
-
[data-lod="mid"] .cv-type::before {
|
|
632
|
-
content: attr(data-short);
|
|
633
|
-
font-size: calc(11px / var(--k)); /* 11 SCREEN px at any k */
|
|
634
|
-
letter-spacing: 0.04em;
|
|
635
|
-
transition: font-size 120ms ease;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
/* MID, THE LIVE EXEMPTION. `far` below already establishes this principle —
|
|
639
|
-
"with 250 strokes on screen the only bright ones must be the branches that
|
|
640
|
-
are alive" — and gives the live card its name, its mark and a z-index. It
|
|
641
|
-
was never applied to THIS band, which is the one a viewer actually sits in:
|
|
642
|
-
measured at 60% with fourteen agents, and there the running card is
|
|
643
|
-
distinguishable from thirteen finished ones only by the hue of a 7px disc.
|
|
644
|
-
The panel's own README promises "which agent is running, what it is doing
|
|
645
|
-
right now"; at mid it was keeping neither.
|
|
646
|
-
The description is dropped at mid because forty dark lower halves are a
|
|
647
|
-
noise floor that stops the type line reading. ONE is not a floor — it is
|
|
648
|
-
the signal, and it is the only field that says WHICH agent. It is drawn
|
|
649
|
-
counter-scaled like every other survivor in this block, so it clears the
|
|
650
|
-
same 9px screen floor that set the band rather than undercutting it, and
|
|
651
|
-
clamped to one line so it cannot reclaim the whole lower half. */
|
|
652
|
-
[data-lod="mid"] .cv-node[data-state="live"] .cv-desc {
|
|
653
|
-
opacity: 1;
|
|
654
|
-
font-size: calc(11px / var(--k)); /* 11 SCREEN px at any k */
|
|
655
|
-
-webkit-line-clamp: 1; line-clamp: 1;
|
|
656
|
-
}
|
|
657
|
-
/* The chip keeps its WORD here instead of collapsing to a disc. The disc
|
|
658
|
-
exists because 10px of chip text is 2px on screen at the bottom of this
|
|
659
|
-
band and was clipping the type line; the running card is the one place that
|
|
660
|
-
trade is wrong, so its chip is counter-scaled instead of silenced. Every
|
|
661
|
-
property the disc rule set has to be undone explicitly, or the word renders
|
|
662
|
-
inside a 7px circle. */
|
|
663
|
-
[data-lod="mid"] .cv-node[data-state="live"] .cv-chip {
|
|
664
|
-
font-size: calc(9px / var(--k));
|
|
665
|
-
padding: 0 calc(5px / var(--k));
|
|
666
|
-
width: auto; height: auto;
|
|
667
|
-
border-radius: calc(999px / var(--k));
|
|
668
|
-
background: transparent;
|
|
669
|
-
border-color: color-mix(in srgb, var(--busy) 45%, transparent);
|
|
670
|
-
}
|
|
671
|
-
/* Rides above its neighbours for the same reason it does at `far`: the words
|
|
672
|
-
it just kept must not sit under the next card. */
|
|
673
|
-
[data-lod="mid"] .cv-node[data-state="live"] { z-index: 5; }
|
|
674
|
-
|
|
675
|
-
/* FAR — k < 0.20, or more drawn nodes than the label budget. No text is
|
|
676
|
-
attempted for an ordinary node, because none would survive. Three things
|
|
677
|
-
still resolve at this size and each gets a channel: POSITION (which type
|
|
678
|
-
group, which depth — the layout already encodes it), HUE (--node-color, the
|
|
679
|
-
one place colour is decided) and a RING (status). */
|
|
680
|
-
[data-lod="far"] {
|
|
681
|
-
/* Screen diameter of the disc: floored so it survives the tightest node
|
|
682
|
-
pitch, capped so it never outgrows its cell. Divided back out of k to get
|
|
683
|
-
the world size that draws it. */
|
|
684
|
-
--dot-screen: clamp(5px, calc(88 * var(--k) * 1px), 16px);
|
|
685
|
-
--dots: calc(var(--dot-screen) / var(--k));
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
/* The card body stops being a rectangle, because the rectangle is the wall. */
|
|
689
|
-
[data-lod="far"] .cv-node {
|
|
690
|
-
background: transparent;
|
|
691
|
-
border-color: transparent;
|
|
692
|
-
box-shadow: none;
|
|
693
|
-
/* Pinned rather than left to min-height. A long description pushes the box
|
|
694
|
-
past 104px, and since the disc is centred on the box rather than on the
|
|
695
|
-
cell, cards with more text put their disc a few world px lower than their
|
|
696
|
-
neighbours — a lattice of dots that does not quite line up. Nothing
|
|
697
|
-
visible is clipped: what overflows here is already at opacity 0. */
|
|
698
|
-
height: 104px;
|
|
699
|
-
}
|
|
700
|
-
/* Except the one the user picked, which still has to be findable. */
|
|
701
|
-
[data-lod="far"] .cv-node.cv-selected {
|
|
702
|
-
box-shadow: 0 0 0 calc(1.5px / var(--k)) var(--node-color);
|
|
703
|
-
}
|
|
704
|
-
[data-lod="far"] .cv-desc,
|
|
705
|
-
[data-lod="far"] .cv-foot,
|
|
706
|
-
[data-lod="far"] .cv-chip { opacity: 0; }
|
|
707
|
-
[data-lod="far"] .cv-accent,
|
|
708
|
-
[data-lod="far"] .cv-port,
|
|
709
|
-
[data-lod="far"] .cv-fold { display: none; }
|
|
710
|
-
/* The card ring is a 236x104 rectangle; the disc carries "running" instead. */
|
|
711
|
-
[data-lod="far"] .cv-node[data-state="live"]::after { display: none; }
|
|
712
|
-
|
|
713
|
-
/* .cv-mark becomes the disc, in place. */
|
|
714
|
-
[data-lod="far"] .cv-mark {
|
|
715
|
-
position: absolute;
|
|
716
|
-
left: 18px; /* the padding it already sat behind */
|
|
717
|
-
top: 12px;
|
|
718
|
-
width: var(--dots);
|
|
719
|
-
height: var(--dots);
|
|
720
|
-
/* 93 and 33 carry a 14px mark from the head to the card's visual centre —
|
|
721
|
-
(236-14)/2 - 18 and (104-14)/2 - 12 — and the half-diameter term holds the
|
|
722
|
-
disc on that centre as it grows. */
|
|
723
|
-
transform: translate(calc(93px + 7px - var(--dots) / 2), calc(33px + 7px - var(--dots) / 2));
|
|
724
|
-
border-radius: 50%;
|
|
725
|
-
background: var(--node-color);
|
|
726
|
-
/* A screen-constant hairline around every agent circle. */
|
|
727
|
-
box-shadow: 0 0 0 calc(1.4px / var(--k)) color-mix(in srgb, var(--ink) 34%, transparent);
|
|
728
|
-
}
|
|
729
|
-
/* An 11-ray burst is mush at 7px. */
|
|
730
|
-
[data-lod="far"] .cv-mark > * { display: none; }
|
|
731
|
-
/* Source survives as SHAPE, which reads at 7px where a glyph does not. */
|
|
732
|
-
[data-lod="far"] .cv-node[data-source="kit"] .cv-mark { border-radius: 3px; }
|
|
733
|
-
/* Status survives as the ring — the chip's colour, moved. */
|
|
734
|
-
[data-lod="far"] .cv-node[data-state="live"] .cv-mark {
|
|
735
|
-
box-shadow: 0 0 0 calc(2px / var(--k)) var(--busy);
|
|
736
|
-
}
|
|
737
|
-
[data-lod="far"] .cv-node[data-state="failed"] .cv-mark {
|
|
738
|
-
box-shadow: 0 0 0 calc(2px / var(--k)) var(--cv-fail);
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
/* Who keeps a label out here. This is what makes the far band a picture
|
|
742
|
-
rather than a field of anonymous dots, and it answers the question the panel
|
|
743
|
-
exists for — which agent is running, and what is it doing — without a click.
|
|
744
|
-
Running and failed branches keep their words whatever the budget says; the
|
|
745
|
-
session and the workflows keep theirs because they are few and they anchor
|
|
746
|
-
the picture.
|
|
747
|
-
|
|
748
|
-
POSITIVE SELECTORS ONLY. The selfcheck's matcher treats a pseudo-class it
|
|
749
|
-
does not model as a non-match, so a :not() rule here would resolve to "no
|
|
750
|
-
match" in the gate and pass green for a rule the browser is running and the
|
|
751
|
-
test never saw. */
|
|
752
|
-
[data-lod="far"] .cv-node[data-state="live"] .cv-type::before,
|
|
753
|
-
[data-lod="far"] .cv-node[data-state="failed"] .cv-type::before,
|
|
754
|
-
[data-lod="far"] .cv-node[data-kind="session"] .cv-type::before,
|
|
755
|
-
[data-lod="far"] .cv-node[data-kind="workflow"] .cv-type::before {
|
|
756
|
-
content: attr(data-far);
|
|
757
|
-
font-size: calc(11px / var(--k));
|
|
758
|
-
letter-spacing: 0.04em;
|
|
759
|
-
transition: font-size 120ms ease;
|
|
760
|
-
}
|
|
761
|
-
/* Centred under its own disc and allowed out of the card, because at this size
|
|
762
|
-
the card is no longer a box — the disc is the node. */
|
|
763
|
-
[data-lod="far"] .cv-node[data-state="live"] .cv-type,
|
|
764
|
-
[data-lod="far"] .cv-node[data-state="failed"] .cv-type,
|
|
765
|
-
[data-lod="far"] .cv-node[data-kind="session"] .cv-type,
|
|
766
|
-
[data-lod="far"] .cv-node[data-kind="workflow"] .cv-type {
|
|
767
|
-
position: absolute;
|
|
768
|
-
left: 50%; /* = 118px, the disc's centre */
|
|
769
|
-
top: calc(52px + var(--dots) * 0.62);
|
|
770
|
-
transform: translateX(-50%);
|
|
771
|
-
overflow: visible;
|
|
772
|
-
text-overflow: clip;
|
|
773
|
-
}
|
|
774
|
-
/* Their words ride above the field rather than under the next card. */
|
|
775
|
-
[data-lod="far"] .cv-node[data-state="live"],
|
|
776
|
-
[data-lod="far"] .cv-node[data-state="failed"] { z-index: 5; }
|
|
777
|
-
|
|
778
|
-
/* With 250 strokes on screen the only bright ones must be the branches that
|
|
779
|
-
are alive and the branches that went wrong. */
|
|
780
|
-
[data-lod="far"] .cv-edge[data-state="done"],
|
|
781
|
-
[data-lod="far"] .cv-edge[data-state="quiet"] { opacity: var(--cv-edge-quiet); }
|
|
1116
|
+
.inspector dd { margin: 0; font-family: var(--font-mono); font-size: 12px; word-break: break-word; }
|
|
782
1117
|
|
|
783
1118
|
/* ------------------------------------------------------- stillness ---
|
|
784
1119
|
Two ways the canvas stops moving, and one set of rules for both.
|
|
@@ -795,17 +1130,8 @@ body.resizing .cv-root { pointer-events: none; }
|
|
|
795
1130
|
|
|
796
1131
|
[data-motion="still"] .cv-edge,
|
|
797
1132
|
[data-motion="still"] .cv-edge.cv-drawing,
|
|
798
|
-
[data-motion="still"] .cv-node::after { animation: none; }
|
|
1133
|
+
[data-motion="still"] .cv-node[data-kind][data-state]::after { animation: none; }
|
|
799
1134
|
[data-motion="still"] .cv-node.cv-born { opacity: 1; scale: 1; }
|
|
800
|
-
/* A reading change is motion too — the mark travels to the card's centre and
|
|
801
|
-
the label resizes. `.cv-node { transition: none }` does not reach a child;
|
|
802
|
-
that is specificity, not the media query, so each of these carries at least
|
|
803
|
-
the weight of the rule it cancels. */
|
|
804
|
-
[data-motion="still"] .cv-mark,
|
|
805
|
-
[data-motion="still"] .cv-type::before,
|
|
806
|
-
[data-motion="still"] .cv-desc,
|
|
807
|
-
[data-motion="still"] .cv-foot,
|
|
808
|
-
[data-motion="still"] .cv-chip { transition: none; }
|
|
809
1135
|
|
|
810
1136
|
/* A media query adds no specificity. `.cv-edge` alone loses to
|
|
811
1137
|
`.cv-edge[data-state="live"]` and the flow keeps running, so each selector
|
|
@@ -813,13 +1139,8 @@ body.resizing .cv-root { pointer-events: none; }
|
|
|
813
1139
|
@media (prefers-reduced-motion: reduce) {
|
|
814
1140
|
.cv-edge[data-state],
|
|
815
1141
|
.cv-edge.cv-drawing,
|
|
816
|
-
.cv-node[data-state]::after { animation: none; }
|
|
1142
|
+
.cv-node[data-kind][data-state]::after { animation: none; }
|
|
817
1143
|
.cv-node { transition: none; }
|
|
818
|
-
.cv-node .cv-mark,
|
|
819
|
-
.cv-node .cv-type::before,
|
|
820
|
-
.cv-node .cv-desc,
|
|
821
|
-
.cv-node .cv-foot,
|
|
822
|
-
.cv-node .cv-chip { transition: none; }
|
|
823
1144
|
/* The birth class comes off a frame later, which with no transition left is
|
|
824
1145
|
a blink from invisible to placed. Land it in its final state instead. */
|
|
825
1146
|
.cv-node.cv-born { opacity: 1; scale: 1; }
|
|
@@ -827,27 +1148,71 @@ body.resizing .cv-root { pointer-events: none; }
|
|
|
827
1148
|
|
|
828
1149
|
/* ==================================================== inspector detail === */
|
|
829
1150
|
|
|
830
|
-
.inspector { width: 340px; transition: width 0.18s; display: flex; flex-direction: column; padding: 0; }
|
|
831
|
-
.inspector.wide { width: min(760px, 55vw); }
|
|
832
1151
|
|
|
833
|
-
.ihead { display: flex; align-items: center; gap:
|
|
834
|
-
.ihead
|
|
835
|
-
.
|
|
836
|
-
.
|
|
1152
|
+
.ihead { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-3) var(--space-3) 0 var(--space-4); flex: none; }
|
|
1153
|
+
.ihead .cv-tile { flex: none; }
|
|
1154
|
+
.iname { flex: 1; min-width: 0; margin: 0; font-size: 15px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1155
|
+
.imeta { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-2) var(--space-4) 0; flex: none; }
|
|
1156
|
+
.imeta .sub { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
1157
|
+
/* The task in full: it is the one place the whole sentence is shown. */
|
|
1158
|
+
.itask { padding: var(--space-3) var(--space-4); font-size: 16px; font-weight: 600; line-height: 1.35; overflow-wrap: anywhere; flex: none; max-height: 30%; overflow-y: auto; }
|
|
1159
|
+
.itask:empty { padding-bottom: 0; }
|
|
837
1160
|
|
|
838
|
-
.itabs { display: flex; gap:
|
|
1161
|
+
.itabs { display: flex; gap: var(--space-1); padding: 0 var(--space-3); border-bottom: 1px solid var(--line); flex: none; }
|
|
839
1162
|
.itab {
|
|
840
1163
|
background: none; border: none; cursor: pointer;
|
|
841
|
-
font-family:
|
|
842
|
-
color: var(--ink-
|
|
1164
|
+
font-family: inherit; font-size: 13px; font-weight: 500;
|
|
1165
|
+
color: var(--ink-dim); padding: var(--space-2) 6px; border-bottom: 2px solid transparent; margin-bottom: -1px;
|
|
843
1166
|
}
|
|
844
|
-
.itab:hover { color: var(--ink
|
|
845
|
-
.itab
|
|
1167
|
+
.itab:hover { color: var(--ink); }
|
|
1168
|
+
.itab[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--accent); }
|
|
846
1169
|
|
|
847
|
-
.ibody { flex: 1; overflow-y: auto; padding:
|
|
1170
|
+
.ibody { flex: 1; overflow-y: auto; padding: var(--space-4); min-height: 0; }
|
|
1171
|
+
.ifoot { display: flex; gap: var(--space-2); padding: var(--space-3) var(--space-4); border-top: 1px solid var(--line); flex: none; }
|
|
1172
|
+
.ifoot .btn:first-child { flex: 1; justify-content: center; }
|
|
1173
|
+
|
|
1174
|
+
.isec { margin-top: var(--space-4); }
|
|
1175
|
+
.isec:first-child { margin-top: 0; }
|
|
1176
|
+
.isec-h {
|
|
1177
|
+
display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
|
|
1178
|
+
margin: 0 0 var(--space-2); font-size: 11px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase;
|
|
1179
|
+
color: var(--ink-faint);
|
|
1180
|
+
}
|
|
1181
|
+
.itiles { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); }
|
|
1182
|
+
.itile { display: flex; flex-direction: column; gap: 2px; padding: var(--space-2) var(--space-3); border-radius: var(--radius-md); background: var(--surface-raised); min-width: 0; }
|
|
1183
|
+
.itile-k { font-size: 11px; color: var(--ink-dim); }
|
|
1184
|
+
.itile strong { font-size: 18px; font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
1185
|
+
.itile strong.bad { color: var(--status-bad); }
|
|
1186
|
+
/* Not measured is a sentence, not a number: it is set smaller and in the waiting tone, so it cannot pass for one. */
|
|
1187
|
+
.itile.unmeasured strong { font-size: 13px; color: var(--status-waiting); }
|
|
1188
|
+
.inow {
|
|
1189
|
+
display: flex; flex-direction: column; gap: 2px; padding: var(--space-2) var(--space-3);
|
|
1190
|
+
border-radius: var(--radius-md); background: var(--surface-raised);
|
|
1191
|
+
font-family: var(--font-mono); font-size: 12px; overflow-wrap: anywhere;
|
|
1192
|
+
}
|
|
1193
|
+
.inow-tool { color: var(--studio-accent); }
|
|
1194
|
+
.inow-text { color: var(--ink-dim); }
|
|
1195
|
+
.ichips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
1196
|
+
.ichip {
|
|
1197
|
+
height: 24px; padding: 0 var(--space-2); border-radius: var(--radius-sm); cursor: pointer;
|
|
1198
|
+
border: 1px solid var(--line); background: none; color: var(--ink);
|
|
1199
|
+
font-family: var(--font-mono); font-size: 12px;
|
|
1200
|
+
}
|
|
1201
|
+
.ichip:hover { background: var(--surface-raised); }
|
|
1202
|
+
.ilink { background: none; border: none; padding: 0; cursor: pointer; color: var(--accent); font: inherit; font-size: 13px; text-align: left; }
|
|
1203
|
+
.ilink:hover { text-decoration: underline; }
|
|
1204
|
+
.ibanner {
|
|
1205
|
+
padding: var(--space-3); margin-bottom: var(--space-4); border-radius: var(--radius-md);
|
|
1206
|
+
border: 1px solid color-mix(in srgb, var(--status-waiting) 45%, var(--line));
|
|
1207
|
+
background: color-mix(in srgb, var(--status-waiting) 8%, transparent);
|
|
1208
|
+
font-size: 13px; line-height: 1.45;
|
|
1209
|
+
}
|
|
1210
|
+
.ibanner p { margin: 0 0 var(--space-3); }
|
|
1211
|
+
.ibanner-acts { display: flex; gap: var(--space-2); flex-wrap: wrap; }
|
|
1212
|
+
.iallow-row { display: flex; align-items: center; gap: var(--space-2); padding: 6px 0; border-bottom: 1px solid var(--line); }
|
|
1213
|
+
.iallow-tool { font-family: var(--font-mono); font-size: 12px; font-weight: 600; }
|
|
1214
|
+
.ihint .btn { margin-top: var(--space-2); }
|
|
848
1215
|
.ihint { color: var(--ink-faint); font-size: 13px; line-height: 1.5; padding: 6px 0; }
|
|
849
|
-
.ibar { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
|
|
850
|
-
.ibar .ghost { font-family: var(--mono); font-size: 11px; padding: 2px 8px; }
|
|
851
1216
|
|
|
852
1217
|
/* ------------------------------------------------------- activity list --- */
|
|
853
1218
|
|
|
@@ -856,8 +1221,8 @@ body.resizing .cv-root { pointer-events: none; }
|
|
|
856
1221
|
display: grid; grid-template-columns: 22px 62px 1fr; gap: 8px; align-items: baseline;
|
|
857
1222
|
padding: 5px 0; border-bottom: 1px solid var(--line);
|
|
858
1223
|
}
|
|
859
|
-
.istep-n { font-family: var(--mono); font-size: 10px; color: var(--ink-faint); text-align: right; }
|
|
860
|
-
.istep-tool { font-family: var(--mono); font-size: 11px; color: var(--accent); }
|
|
1224
|
+
.istep-n { font-family: var(--font-mono); font-size: 10px; color: var(--ink-faint); text-align: right; }
|
|
1225
|
+
.istep-tool { font-family: var(--font-mono); font-size: 11px; color: var(--studio-accent); }
|
|
861
1226
|
.istep-label { font-size: 12px; color: var(--ink-dim); overflow-wrap: anywhere; }
|
|
862
1227
|
.md-narration { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--line); opacity: 0.85; }
|
|
863
1228
|
|
|
@@ -872,18 +1237,18 @@ h5.md-h, h6.md-h { font-size: 12px; color: var(--ink-dim); text-transform: upper
|
|
|
872
1237
|
.md-list { margin: 0 0 9px; padding-left: 20px; }
|
|
873
1238
|
.md-list li { margin-bottom: 3px; }
|
|
874
1239
|
.md code {
|
|
875
|
-
font-family: var(--mono); font-size: 0.9em;
|
|
876
|
-
background: var(--surface-
|
|
1240
|
+
font-family: var(--font-mono); font-size: 0.9em;
|
|
1241
|
+
background: var(--surface-raised); border-radius: 4px; padding: 1px 4px;
|
|
877
1242
|
}
|
|
878
1243
|
.md-code {
|
|
879
|
-
background: var(--surface-
|
|
1244
|
+
background: var(--surface-raised); border: 1px solid var(--line); border-radius: 8px;
|
|
880
1245
|
padding: 10px 12px; overflow-x: auto; margin: 0 0 10px;
|
|
881
|
-
font-family: var(--mono); font-size: 11.5px; line-height: 1.5;
|
|
1246
|
+
font-family: var(--font-mono); font-size: 11.5px; line-height: 1.5;
|
|
882
1247
|
}
|
|
883
1248
|
.md-code code { background: none; padding: 0; }
|
|
884
1249
|
.md-quote {
|
|
885
1250
|
margin: 0 0 10px; padding: 6px 0 6px 12px;
|
|
886
|
-
border-left: 2px solid var(--accent); color: var(--ink-dim);
|
|
1251
|
+
border-left: 2px solid var(--studio-accent); color: var(--ink-dim);
|
|
887
1252
|
}
|
|
888
1253
|
.md-hr { border: none; border-top: 1px solid var(--line); margin: 16px 0; }
|
|
889
1254
|
.md-link { color: var(--accent); }
|
|
@@ -892,11 +1257,7 @@ h5.md-h, h6.md-h { font-size: 12px; color: var(--ink-dim); text-transform: upper
|
|
|
892
1257
|
.md-table th, .md-table td {
|
|
893
1258
|
border: 1px solid var(--line); padding: 5px 9px; text-align: left; vertical-align: top;
|
|
894
1259
|
}
|
|
895
|
-
.md-table th { background: var(--surface-
|
|
896
|
-
|
|
897
|
-
/* `display: flex` on .inspector outranks the UA's `[hidden] { display: none }`,
|
|
898
|
-
so the hidden panel kept its width. Restate it explicitly. */
|
|
899
|
-
.inspector[hidden] { display: none; }
|
|
1260
|
+
.md-table th { background: var(--surface-raised); font-weight: 600; white-space: nowrap; }
|
|
900
1261
|
|
|
901
1262
|
/* `overflow-wrap: anywhere` keeps long paths inside the panel, but inside a
|
|
902
1263
|
table it shreds every cell into one letter per line. Tables get their real
|
|
@@ -904,134 +1265,23 @@ h5.md-h, h6.md-h { font-size: 12px; color: var(--ink-dim); text-transform: upper
|
|
|
904
1265
|
.md-table th, .md-table td { overflow-wrap: normal; min-width: 76px; }
|
|
905
1266
|
.md-table td { hyphens: none; }
|
|
906
1267
|
|
|
907
|
-
/* ================================================= project sidebar === */
|
|
908
|
-
|
|
909
|
-
.side-filter { padding: 0 12px 8px; flex: none; }
|
|
910
|
-
.side-filter input {
|
|
911
|
-
width: 100%; box-sizing: border-box;
|
|
912
|
-
background: var(--bg); border: 1px solid var(--line); border-radius: 7px;
|
|
913
|
-
color: var(--ink); font-family: var(--mono); font-size: 11px; padding: 5px 8px;
|
|
914
|
-
}
|
|
915
|
-
.side-filter input:focus { outline: none; border-color: var(--accent); }
|
|
916
|
-
.side-filter input::placeholder { color: var(--ink-faint); }
|
|
917
|
-
|
|
918
|
-
.pgroup {
|
|
919
|
-
display: grid; grid-template-columns: 14px 1fr auto; gap: 7px; align-items: center;
|
|
920
|
-
padding: 7px 14px; cursor: pointer; border-top: 1px solid var(--line);
|
|
921
|
-
background: var(--surface-2);
|
|
922
|
-
}
|
|
923
|
-
.pgroup:hover { background: color-mix(in srgb, var(--accent) 8%, var(--surface-2)); }
|
|
924
|
-
.pgroup.current { box-shadow: inset 3px 0 0 var(--accent); }
|
|
925
|
-
.pcaret { font-size: 9px; color: var(--ink-faint); }
|
|
926
|
-
.plabel {
|
|
927
|
-
font-size: 12px; font-weight: 600;
|
|
928
|
-
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
929
|
-
}
|
|
930
|
-
.pcount { display: flex; gap: 5px; align-items: center; font-family: var(--mono); font-size: 10px; color: var(--ink-faint); }
|
|
931
|
-
|
|
932
|
-
.srow {
|
|
933
|
-
display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: center;
|
|
934
|
-
padding: 6px 14px 6px 28px; border-top: 1px solid var(--line);
|
|
935
|
-
background: var(--surface); cursor: pointer;
|
|
936
|
-
}
|
|
937
|
-
.srow:hover { background: var(--surface-2); }
|
|
938
|
-
.srow[aria-current="true"] { background: var(--surface-2); box-shadow: inset 3px 0 0 var(--good); }
|
|
939
|
-
.sname {
|
|
940
|
-
font-size: 12px; color: var(--ink);
|
|
941
|
-
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
942
|
-
}
|
|
943
|
-
.srow[aria-current="true"] .sname { font-weight: 600; }
|
|
944
|
-
.sright { display: flex; gap: 5px; align-items: center; }
|
|
945
|
-
.sagents { font-family: var(--mono); font-size: 10px; color: var(--ink-faint); }
|
|
946
|
-
.sbadge {
|
|
947
|
-
font-family: var(--mono); font-size: 9px; color: var(--accent);
|
|
948
|
-
border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
|
|
949
|
-
border-radius: 4px; padding: 0 4px;
|
|
950
|
-
}
|
|
951
|
-
.ptrunc {
|
|
952
|
-
padding: 5px 14px 5px 28px; font-family: var(--mono); font-size: 10px;
|
|
953
|
-
color: var(--ink-faint); background: var(--surface); border-top: 1px solid var(--line);
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
/* ------------------------------------------------------- kit version --- */
|
|
957
|
-
|
|
958
|
-
.kitbadge {
|
|
959
|
-
font-family: var(--mono); font-size: 9px; padding: 0 4px;
|
|
960
|
-
border-radius: 4px; border: 1px solid var(--line); color: var(--ink-faint);
|
|
961
|
-
white-space: nowrap;
|
|
962
|
-
}
|
|
963
|
-
.kitbadge.ok { color: var(--good); border-color: color-mix(in srgb, var(--good) 35%, transparent); }
|
|
964
|
-
.kitbadge.old { color: var(--waiting); border-color: color-mix(in srgb, var(--waiting) 50%, transparent);
|
|
965
|
-
background: color-mix(in srgb, var(--waiting) 12%, transparent); }
|
|
966
|
-
.kitbadge.ahead { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 35%, transparent); }
|
|
967
|
-
.kitbadge.unknown { color: var(--ink-faint); }
|
|
968
|
-
|
|
969
|
-
.pgroup.gone .plabel { color: var(--ink-faint); text-decoration: line-through; }
|
|
970
|
-
|
|
971
|
-
.kitline {
|
|
972
|
-
display: block; font-family: var(--mono); font-size: 10px;
|
|
973
|
-
color: var(--ink-faint); letter-spacing: 0.03em; margin-top: 1px;
|
|
974
|
-
}
|
|
975
|
-
.kitline.warn { color: var(--waiting); }
|
|
976
|
-
|
|
977
|
-
.ptrunc.pmissing { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
978
|
-
.ptrunc.pmissing .ghost { font-family: var(--mono); font-size: 10px; padding: 1px 6px; }
|
|
979
|
-
|
|
980
|
-
/* ------------------------------------------------------- fold control --- */
|
|
981
|
-
|
|
982
|
-
.cv-fold {
|
|
983
|
-
background: transparent; border: 1px solid var(--line); border-radius: 5px;
|
|
984
|
-
color: var(--ink-dim); cursor: pointer; flex: none;
|
|
985
|
-
font-family: var(--mono); font-size: 9px; line-height: 1; padding: 2px 5px;
|
|
986
|
-
}
|
|
987
|
-
.cv-fold:hover { color: var(--ink); border-color: var(--node-color); }
|
|
988
|
-
.cv-fold[hidden] { display: none; }
|
|
989
|
-
|
|
990
|
-
/* A folded node is a container, so it reads as one: stacked edges behind the
|
|
991
|
-
card stand in for the children it is holding. */
|
|
992
|
-
.cv-node.cv-folded { box-shadow: 0 1px 2px rgb(0 0 0 / 0.18), 6px 6px 0 -2px var(--surface-2), 6px 6px 0 -1px var(--line); }
|
|
993
|
-
.cv-node[data-kind="workflow"] { border-style: dashed; }
|
|
994
|
-
.cv-node[data-kind="workflow"] .cv-desc { font-family: var(--mono); font-size: 11px; color: var(--ink-dim); }
|
|
995
|
-
|
|
996
|
-
/* The fold control is an affordance, not decoration: when a node is holding
|
|
997
|
-
children back it says how many, in the node's own colour. */
|
|
998
|
-
.cv-node.cv-folded .cv-fold {
|
|
999
|
-
color: var(--node-color);
|
|
1000
|
-
border-color: color-mix(in srgb, var(--node-color) 45%, transparent);
|
|
1001
|
-
background: color-mix(in srgb, var(--node-color) 12%, transparent);
|
|
1002
|
-
font-weight: 600;
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
1268
|
/* ---------------------------------------------------------- origins --- */
|
|
1006
1269
|
|
|
1007
1270
|
/* Which machine a row came from, shown only once more than one is in view. */
|
|
1008
1271
|
.origin {
|
|
1009
1272
|
margin-left: 6px; padding: 0 4px; border-radius: 4px;
|
|
1010
|
-
font-family: var(--mono); font-size: 9px; font-weight: 400;
|
|
1011
|
-
color: var(--accent);
|
|
1012
|
-
border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent);
|
|
1273
|
+
font-family: var(--font-mono); font-size: 9px; font-weight: 400;
|
|
1274
|
+
color: var(--studio-accent);
|
|
1275
|
+
border: 1px solid color-mix(in srgb, var(--studio-accent) 32%, transparent);
|
|
1013
1276
|
vertical-align: 1px;
|
|
1014
1277
|
}
|
|
1015
|
-
.meta.warn { color: var(--waiting); }
|
|
1016
|
-
.ptrunc.pdown { color: var(--bad); }
|
|
1278
|
+
.meta.warn { color: var(--status-waiting); }
|
|
1017
1279
|
|
|
1018
1280
|
/* ================================================== conversation ====== */
|
|
1019
1281
|
|
|
1020
|
-
.stage { position: relative; min-width: 0; display: block; }
|
|
1021
|
-
.canvas { position: absolute; inset: 0; }
|
|
1022
|
-
|
|
1023
|
-
/* The inspector floats over the canvas rather than taking a column of its own:
|
|
1024
|
-
with the conversation on the right there is no third column to spare, and a
|
|
1025
|
-
node's details are something you open and dismiss. */
|
|
1026
|
-
.inspector {
|
|
1027
|
-
position: absolute; top: 0; right: 0; bottom: 0; z-index: 30;
|
|
1028
|
-
border-left: 1px solid var(--line);
|
|
1029
|
-
box-shadow: -12px 0 24px -18px rgb(0 0 0 / 0.7);
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
1282
|
.hsplit { height: 5px; background: var(--line); cursor: row-resize; position: relative; }
|
|
1033
1283
|
.hsplit::after { content: ''; position: absolute; inset: -4px 0; }
|
|
1034
|
-
.hsplit:hover, .hsplit
|
|
1284
|
+
.hsplit:hover, .hsplit.dragging { background: var(--studio-accent); }
|
|
1035
1285
|
.hsplit[hidden] { display: none; }
|
|
1036
1286
|
body.vresizing { cursor: row-resize; user-select: none; }
|
|
1037
1287
|
|
|
@@ -1045,283 +1295,246 @@ body.vresizing { cursor: row-resize; user-select: none; }
|
|
|
1045
1295
|
.chat[hidden] { display: none; }
|
|
1046
1296
|
|
|
1047
1297
|
.chat-head {
|
|
1048
|
-
display: flex; align-items: center; gap:
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
.chat-title {
|
|
1052
|
-
font-size: 11px; font-weight: 600; letter-spacing: 0.08em;
|
|
1053
|
-
text-transform: uppercase; color: var(--ink-faint);
|
|
1298
|
+
display: flex; align-items: center; gap: var(--space-2);
|
|
1299
|
+
height: var(--toolbar-h); padding: 0 var(--space-2) 0 var(--space-3);
|
|
1300
|
+
border-bottom: 1px solid var(--line); flex: none;
|
|
1054
1301
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1302
|
+
|
|
1303
|
+
/* The strip above a conversation: what kind of session it is, how far it has gone, and Stop. */
|
|
1304
|
+
.pane-head {
|
|
1305
|
+
display: flex; align-items: center; gap: var(--space-2);
|
|
1306
|
+
min-height: 44px; padding: 6px var(--space-3); border-bottom: 1px solid var(--line); flex: none; min-width: 0;
|
|
1307
|
+
/* On a narrow panel the strip takes a second line instead of pushing Stop out of reach. */
|
|
1308
|
+
flex-wrap: wrap;
|
|
1058
1309
|
}
|
|
1059
|
-
.
|
|
1060
|
-
.
|
|
1061
|
-
.
|
|
1062
|
-
.chat-
|
|
1063
|
-
.chat-stop {
|
|
1310
|
+
.pane-head .sub { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
1311
|
+
.pane-head .pane-cost { font-family: var(--font-mono); }
|
|
1312
|
+
.pane-ungated { color: var(--status-bad); background: color-mix(in srgb, var(--status-bad) 12%, transparent); }
|
|
1313
|
+
.chat-stop { flex: none; }
|
|
1314
|
+
.chat-stop .ic { fill: currentColor; stroke: none; }
|
|
1064
1315
|
|
|
1065
|
-
.chat-log { flex: 1; overflow-y: auto; padding:
|
|
1316
|
+
.chat-log { flex: 1; overflow-y: auto; padding: var(--space-4); min-height: 0; }
|
|
1066
1317
|
|
|
1067
|
-
/* Who said it
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
.msg { display: flex; flex-direction: column; gap: 3px; margin-bottom: 14px; }
|
|
1318
|
+
/* Who said it is read from the side it sits on: what the viewer wrote is a bubble on the right, what the session
|
|
1319
|
+
said runs the width of the panel under its name. */
|
|
1320
|
+
.msg { display: flex; flex-direction: column; gap: var(--space-1); margin-bottom: var(--space-4); }
|
|
1071
1321
|
.msg-user { align-items: flex-end; }
|
|
1072
|
-
.msg-assistant { align-items:
|
|
1322
|
+
.msg-assistant { align-items: stretch; }
|
|
1073
1323
|
|
|
1074
|
-
.msg-role {
|
|
1075
|
-
font-family: var(--mono); font-size: 9px; text-transform: uppercase;
|
|
1076
|
-
letter-spacing: 0.08em; color: var(--ink-faint); padding: 0 4px;
|
|
1077
|
-
}
|
|
1078
|
-
.msg-user .msg-role { color: var(--accent); }
|
|
1324
|
+
.msg-role { font-size: 12px; font-weight: 600; color: var(--ink-dim); }
|
|
1079
1325
|
|
|
1080
|
-
.msg-body {
|
|
1081
|
-
max-width: 88%;
|
|
1082
|
-
min-width: 0;
|
|
1083
|
-
font-size: 13px; line-height: 1.55;
|
|
1084
|
-
border-radius: 12px;
|
|
1085
|
-
padding: 8px 12px;
|
|
1086
|
-
overflow-wrap: anywhere;
|
|
1087
|
-
}
|
|
1326
|
+
.msg-body { min-width: 0; font-size: 14px; line-height: 1.5; overflow-wrap: anywhere; }
|
|
1088
1327
|
.msg-user .msg-body {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
}
|
|
1092
|
-
.msg-assistant .msg-body {
|
|
1093
|
-
background: var(--surface-2);
|
|
1094
|
-
border-bottom-left-radius: 3px;
|
|
1328
|
+
max-width: 88%; padding: var(--space-2) var(--space-3); border-radius: var(--radius-md);
|
|
1329
|
+
background: color-mix(in srgb, var(--accent) 16%, var(--surface-raised));
|
|
1095
1330
|
}
|
|
1331
|
+
.msg-pending .msg-body { opacity: 0.6; }
|
|
1096
1332
|
.msg-text { white-space: pre-wrap; }
|
|
1097
1333
|
|
|
1098
|
-
/* A reply carrying a table or a code block needs the room; a sentence does not. */
|
|
1099
|
-
.msg-assistant .msg-body:has(.md-table, .md-code) { max-width: 100%; }
|
|
1100
|
-
|
|
1101
1334
|
.msg-streaming .msg-body { color: var(--ink-dim); }
|
|
1102
|
-
.thinking { font-family: var(--mono); font-size: 11px; color: var(--ink-faint); }
|
|
1335
|
+
.thinking { font-family: var(--font-mono); font-size: 11px; color: var(--ink-faint); }
|
|
1103
1336
|
.thinking::after { content: ''; animation: dots 1.2s steps(4, end) infinite; }
|
|
1104
1337
|
@keyframes dots { 0% { content: ''; } 25% { content: '.'; } 50% { content: '..'; } 75% { content: '...'; } }
|
|
1105
1338
|
|
|
1106
|
-
/*
|
|
1107
|
-
.
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
background: var(--
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
.tool-
|
|
1115
|
-
|
|
1116
|
-
.
|
|
1117
|
-
.
|
|
1118
|
-
|
|
1119
|
-
.
|
|
1339
|
+
/* A tool call: one line that says what ran and how it ended. Its output opens under it. */
|
|
1340
|
+
.tool { margin: var(--space-2) 0; }
|
|
1341
|
+
.tool-row {
|
|
1342
|
+
display: flex; align-items: baseline; gap: var(--space-2); width: 100%; min-width: 0;
|
|
1343
|
+
padding: 6px var(--space-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
|
|
1344
|
+
background: none; color: var(--ink); cursor: pointer; text-align: left;
|
|
1345
|
+
font-family: var(--font-mono); font-size: 12px;
|
|
1346
|
+
}
|
|
1347
|
+
.tool-row:hover { background: var(--surface-raised); }
|
|
1348
|
+
.tool-name { color: var(--studio-accent); flex: none; }
|
|
1349
|
+
.tool-label { flex: 1 1 0; min-width: 0; color: var(--ink-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1350
|
+
.tool-sum { flex: 0 1 auto; min-width: 0; max-width: 45%; color: var(--ink-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1351
|
+
.tool-sum[hidden] { display: none; }
|
|
1352
|
+
.tool-row[data-state="ok"] .tool-sum { color: var(--status-good); }
|
|
1353
|
+
.tool-row[data-state="error"] .tool-sum { color: var(--status-bad); }
|
|
1354
|
+
.tool-row[data-state="refused"] { border-color: color-mix(in srgb, var(--status-bad) 50%, var(--line)); }
|
|
1355
|
+
.tool-row[data-state="refused"] .tool-sum { color: var(--status-bad); }
|
|
1356
|
+
.tool-out {
|
|
1357
|
+
margin: var(--space-1) 0 0; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm);
|
|
1358
|
+
background: var(--ground); border: 1px solid var(--line);
|
|
1359
|
+
font-family: var(--font-mono); font-size: 12px; line-height: 1.45;
|
|
1360
|
+
white-space: pre-wrap; overflow-wrap: anywhere; max-height: 320px; overflow-y: auto;
|
|
1361
|
+
}
|
|
1362
|
+
.tool-out[hidden] { display: none; }
|
|
1363
|
+
|
|
1364
|
+
/* The session handing work to an agent: a card that goes to that agent on the graph. */
|
|
1365
|
+
.deleg {
|
|
1366
|
+
display: flex; align-items: center; gap: var(--space-2); width: 100%; min-width: 0;
|
|
1367
|
+
margin: var(--space-2) 0; padding: var(--space-2) var(--space-3);
|
|
1368
|
+
border: 1px solid var(--line); border-radius: var(--radius-md);
|
|
1369
|
+
background: var(--surface-raised); color: var(--ink); cursor: pointer; text-align: left; font: inherit;
|
|
1370
|
+
}
|
|
1371
|
+
.deleg:hover:not(:disabled) { border-color: var(--studio-accent); }
|
|
1372
|
+
.deleg:disabled { cursor: default; }
|
|
1373
|
+
.deleg .cv-tile { flex: none; }
|
|
1374
|
+
.deleg-text { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; }
|
|
1375
|
+
.deleg-type { font-family: var(--font-mono); font-size: 12px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1376
|
+
.deleg-task { font-size: 12px; color: var(--ink-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1377
|
+
|
|
1378
|
+
/* A request waiting in this session, said where the conversation is being read. */
|
|
1379
|
+
.chat-remind {
|
|
1380
|
+
display: flex; align-items: center; gap: var(--space-2); flex: none;
|
|
1381
|
+
padding: var(--space-2) var(--space-3); font-size: 13px;
|
|
1382
|
+
border-top: 1px solid color-mix(in srgb, var(--status-waiting) 45%, var(--line));
|
|
1383
|
+
background: color-mix(in srgb, var(--status-waiting) 8%, var(--surface));
|
|
1384
|
+
}
|
|
1385
|
+
.chat-remind[hidden] { display: none; }
|
|
1386
|
+
.chat-remind-text { flex: 1 1 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1387
|
+
.chat-review { background: none; border: none; padding: 0; cursor: pointer; font: inherit; font-weight: 600; color: var(--accent); flex: none; }
|
|
1388
|
+
.chat-review:hover { text-decoration: underline; }
|
|
1389
|
+
|
|
1390
|
+
.chat-note { font-size: 12px; line-height: 18px; color: var(--ink-faint); padding: 4px 0; overflow-wrap: anywhere; }
|
|
1391
|
+
.chat-note.bad { color: var(--status-bad); }
|
|
1392
|
+
|
|
1393
|
+
.chat-form { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-3); border-top: 1px solid var(--line); flex: none; }
|
|
1120
1394
|
.chat-form[hidden] { display: none; }
|
|
1121
1395
|
.chat-input {
|
|
1122
|
-
|
|
1123
|
-
background: var(--
|
|
1124
|
-
color: var(--ink); font-family: inherit; font-size:
|
|
1396
|
+
resize: none; box-sizing: border-box; width: 100%;
|
|
1397
|
+
background: var(--ground); border: 1px solid var(--line); border-radius: var(--radius-md);
|
|
1398
|
+
color: var(--ink); font-family: inherit; font-size: 14px; line-height: 20px; padding: var(--space-2) var(--space-3);
|
|
1125
1399
|
}
|
|
1126
|
-
.chat-input:focus { outline: none; border-color: var(--accent); }
|
|
1127
1400
|
.chat-input:disabled { opacity: 0.5; }
|
|
1128
|
-
.chat-
|
|
1129
|
-
|
|
1130
|
-
cursor: pointer; font-size: 12px; font-weight: 600; padding: 0 16px; align-self: stretch;
|
|
1131
|
-
}
|
|
1132
|
-
.chat-send:hover { filter: brightness(1.1); }
|
|
1133
|
-
|
|
1134
|
-
.ghost.primary {
|
|
1135
|
-
color: var(--accent);
|
|
1136
|
-
border-color: color-mix(in srgb, var(--accent) 45%, transparent);
|
|
1137
|
-
font-size: 12px;
|
|
1138
|
-
}
|
|
1139
|
-
.ghost.primary:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); }
|
|
1140
|
-
|
|
1141
|
-
.srow.owned .sname::after {
|
|
1142
|
-
content: 'owned'; margin-left: 6px; padding: 0 4px; border-radius: 4px;
|
|
1143
|
-
font-family: var(--mono); font-size: 9px; color: var(--good);
|
|
1144
|
-
border: 1px solid color-mix(in srgb, var(--good) 35%, transparent);
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
/* `display: grid` outranks the UA's `[hidden] { display: none }`, so the empty
|
|
1148
|
-
note kept painting over a graph that had nodes. Same trap as .inspector. */
|
|
1149
|
-
.cv-empty[hidden] { display: none; }
|
|
1150
|
-
|
|
1151
|
-
/* ------------------------------------------------------- permissions --- */
|
|
1152
|
-
|
|
1153
|
-
.perm-queue { flex: none; border-top: 1px solid var(--line); padding: 10px 14px; max-height: 46vh; overflow-y: auto; }
|
|
1154
|
-
.perm-queue[hidden] { display: none; }
|
|
1155
|
-
|
|
1156
|
-
.perm {
|
|
1157
|
-
border: 1px solid color-mix(in srgb, var(--waiting) 45%, var(--line));
|
|
1158
|
-
background: color-mix(in srgb, var(--waiting) 7%, transparent);
|
|
1159
|
-
border-radius: 9px; padding: 9px 11px; margin-bottom: 8px;
|
|
1160
|
-
}
|
|
1161
|
-
.perm-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
|
|
1162
|
-
.perm-tool {
|
|
1163
|
-
font-family: var(--mono); font-size: 11px; font-weight: 600;
|
|
1164
|
-
letter-spacing: 0.05em; text-transform: uppercase; color: var(--waiting);
|
|
1165
|
-
}
|
|
1166
|
-
.perm-clock { font-family: var(--mono); font-size: 10px; color: var(--ink-faint); }
|
|
1167
|
-
.perm-detail {
|
|
1168
|
-
margin: 7px 0 0; padding: 7px 9px; border-radius: 6px;
|
|
1169
|
-
background: var(--surface-2); font-family: var(--mono); font-size: 11.5px;
|
|
1170
|
-
line-height: 1.45; white-space: pre-wrap; overflow-wrap: anywhere;
|
|
1171
|
-
max-height: 140px; overflow-y: auto;
|
|
1172
|
-
}
|
|
1173
|
-
.perm-acts { display: flex; gap: 7px; margin-top: 9px; flex-wrap: wrap; }
|
|
1174
|
-
.perm-btn {
|
|
1175
|
-
background: transparent; border: 1px solid var(--line); border-radius: 7px;
|
|
1176
|
-
color: var(--ink-dim); cursor: pointer; font-size: 12px; padding: 4px 11px;
|
|
1177
|
-
}
|
|
1178
|
-
.perm-btn:hover:not(:disabled) { color: var(--ink); border-color: var(--ink-faint); }
|
|
1179
|
-
.perm-btn:disabled { opacity: 0.4; cursor: default; }
|
|
1180
|
-
.perm-btn.ok { color: var(--good); border-color: color-mix(in srgb, var(--good) 40%, transparent); }
|
|
1181
|
-
.perm-btn.bad { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 40%, transparent); }
|
|
1182
|
-
|
|
1183
|
-
.chat-state.ungated { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 50%, transparent); }
|
|
1401
|
+
.chat-form-foot { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
|
|
1402
|
+
.chat-form-foot .sub { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
1184
1403
|
|
|
1185
1404
|
/* ------------------------------------------------------ quick replies --- */
|
|
1186
1405
|
|
|
1187
|
-
.quick { display: flex; flex-wrap: wrap; gap:
|
|
1188
|
-
.quick-
|
|
1189
|
-
|
|
1190
|
-
text-transform: uppercase; color: var(--ink-faint);
|
|
1191
|
-
}
|
|
1192
|
-
.quick-btn {
|
|
1193
|
-
background: transparent; border: 1px solid color-mix(in srgb, var(--accent) 38%, transparent);
|
|
1194
|
-
border-radius: 999px; color: var(--accent); cursor: pointer;
|
|
1195
|
-
font-size: 12px; padding: 3px 12px; max-width: 100%;
|
|
1196
|
-
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
1197
|
-
}
|
|
1198
|
-
.quick-btn:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
|
|
1406
|
+
.quick { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; margin-top: var(--space-2); }
|
|
1407
|
+
.quick-btn { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
|
|
1408
|
+
.quick-hint { flex-basis: 100%; }
|
|
1199
1409
|
|
|
1200
1410
|
/* --------------------------------------------------------- session tabs --- */
|
|
1201
1411
|
|
|
1202
|
-
|
|
1203
|
-
.
|
|
1412
|
+
/* `min-width: 0` on both: a flex item is as wide as its longest unbreakable line unless told otherwise, and a
|
|
1413
|
+
conversation is full of them — a path, a command. Without it one long line widened the panel past the window. */
|
|
1414
|
+
.panes { flex: 1; min-height: 0; min-width: 0; display: flex; }
|
|
1415
|
+
.pane { flex: 1; min-height: 0; min-width: 0; display: flex; flex-direction: column; }
|
|
1204
1416
|
.pane[hidden] { display: none; }
|
|
1205
1417
|
|
|
1206
1418
|
.tabs { display: flex; gap: 3px; flex: 1; overflow-x: auto; align-items: center; }
|
|
1207
1419
|
.tab {
|
|
1208
1420
|
display: flex; align-items: center; gap: 6px;
|
|
1209
|
-
background: transparent; border: 1px solid
|
|
1210
|
-
color: var(--ink-dim); cursor: pointer; font-family:
|
|
1211
|
-
padding:
|
|
1421
|
+
height: 30px; background: transparent; border: 1px solid transparent; border-radius: var(--radius-sm);
|
|
1422
|
+
color: var(--ink-dim); cursor: pointer; font-family: inherit; font-size: 12px; font-weight: 600;
|
|
1423
|
+
padding: 0 var(--space-2); white-space: nowrap; flex: none;
|
|
1212
1424
|
}
|
|
1213
1425
|
.tab:hover { color: var(--ink); }
|
|
1214
|
-
.tab
|
|
1426
|
+
.tab[aria-selected="true"] { color: var(--ink); background: var(--surface-raised); border-color: var(--line); }
|
|
1215
1427
|
|
|
1216
|
-
.tab-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--idle); flex: none; }
|
|
1217
|
-
.tab-dot[data-state="working"] { background: var(--busy); animation: breathe 1.6s ease-in-out infinite; }
|
|
1218
|
-
.tab-dot[data-state="idle"] { background: var(--good); }
|
|
1219
|
-
.tab-dot[data-state="starting"]{ background: var(--accent); }
|
|
1220
|
-
.tab-dot[data-state="exited"], .tab-dot[data-state="failed"] { background: var(--
|
|
1428
|
+
.tab-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--status-idle); flex: none; }
|
|
1429
|
+
.tab-dot[data-state="working"] { background: var(--status-busy); animation: breathe 1.6s ease-in-out infinite; }
|
|
1430
|
+
.tab-dot[data-state="idle"] { background: var(--status-good); }
|
|
1431
|
+
.tab-dot[data-state="starting"]{ background: var(--studio-accent); }
|
|
1432
|
+
.tab-dot[data-state="exited"], .tab-dot[data-state="failed"] { background: var(--status-fail); }
|
|
1221
1433
|
|
|
1222
1434
|
.tab-badge {
|
|
1223
|
-
background: var(--surface-
|
|
1435
|
+
background: var(--surface-raised); border-radius: 999px; padding: 0 5px;
|
|
1224
1436
|
font-size: 9px; color: var(--ink-faint);
|
|
1225
1437
|
}
|
|
1226
|
-
.tab-badge.warn { background: color-mix(in srgb, var(--waiting) 22%, transparent); color: var(--waiting); }
|
|
1438
|
+
.tab-badge.warn { background: color-mix(in srgb, var(--status-waiting) 22%, transparent); color: var(--status-waiting); }
|
|
1227
1439
|
.tab-x { color: var(--ink-faint); font-size: 13px; line-height: 1; padding: 0 1px; }
|
|
1228
|
-
.tab-x:hover { color: var(--bad); }
|
|
1440
|
+
.tab-x:hover { color: var(--status-bad); }
|
|
1229
1441
|
|
|
1230
1442
|
@media (prefers-reduced-motion: reduce) { .tab-dot[data-state="working"] { animation: none; } }
|
|
1231
1443
|
|
|
1232
1444
|
/* -------------------------------------------------------- kit telemetry --- */
|
|
1233
1445
|
|
|
1234
1446
|
.kit-sum { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 10px; }
|
|
1235
|
-
.kit-sum .cv-bit[data-verdict="BLOCK"] { color: var(--bad); }
|
|
1236
|
-
.kit-sum .cv-bit[data-verdict="ASK"] { color: var(--waiting); }
|
|
1237
|
-
.kit-sum .cv-bit[data-verdict="ALLOW"] { color: var(--good); }
|
|
1447
|
+
.kit-sum .cv-bit[data-verdict="BLOCK"] { color: var(--status-bad); }
|
|
1448
|
+
.kit-sum .cv-bit[data-verdict="ASK"] { color: var(--status-waiting); }
|
|
1449
|
+
.kit-sum .cv-bit[data-verdict="ALLOW"] { color: var(--status-good); }
|
|
1238
1450
|
|
|
1239
1451
|
.gate-row {
|
|
1240
1452
|
display: grid; grid-template-columns: auto 1fr auto; gap: 8px; align-items: baseline;
|
|
1241
1453
|
padding: 4px 0; border-bottom: 1px solid var(--line); font-size: 12px;
|
|
1242
1454
|
}
|
|
1243
|
-
.gate-when { font-family: var(--mono); font-size: 10px; color: var(--ink-faint); }
|
|
1455
|
+
.gate-when { font-family: var(--font-mono); font-size: 10px; color: var(--ink-faint); }
|
|
1244
1456
|
.gate-name { color: var(--ink-dim); overflow-wrap: anywhere; }
|
|
1245
|
-
.gate-sec { font-family: var(--mono); font-size: 10px; color: var(--ink-faint); }
|
|
1457
|
+
.gate-sec { font-family: var(--font-mono); font-size: 10px; color: var(--ink-faint); }
|
|
1246
1458
|
.gate-verdict {
|
|
1247
|
-
font-family: var(--mono); font-size: 9px; letter-spacing: 0.05em;
|
|
1459
|
+
font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.05em;
|
|
1248
1460
|
border: 1px solid var(--line); border-radius: 4px; padding: 0 5px; white-space: nowrap;
|
|
1249
1461
|
}
|
|
1250
|
-
.gate-verdict[data-verdict="BLOCK"] { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 40%, transparent); }
|
|
1251
|
-
.gate-verdict[data-verdict="ASK"] { color: var(--waiting); border-color: color-mix(in srgb, var(--waiting) 40%, transparent); }
|
|
1252
|
-
.gate-verdict[data-verdict="ALLOW"] { color: var(--good); border-color: color-mix(in srgb, var(--good) 35%, transparent); }
|
|
1462
|
+
.gate-verdict[data-verdict="BLOCK"] { color: var(--status-bad); border-color: color-mix(in srgb, var(--status-bad) 40%, transparent); }
|
|
1463
|
+
.gate-verdict[data-verdict="ASK"] { color: var(--status-waiting); border-color: color-mix(in srgb, var(--status-waiting) 40%, transparent); }
|
|
1464
|
+
.gate-verdict[data-verdict="ALLOW"] { color: var(--status-good); border-color: color-mix(in srgb, var(--status-good) 35%, transparent); }
|
|
1253
1465
|
|
|
1254
|
-
.inspector dd.bad { color: var(--bad); }
|
|
1255
|
-
.ihint strong { display: block; color: var(--waiting); margin-bottom: 3px; }
|
|
1466
|
+
.inspector dd.bad { color: var(--status-bad); }
|
|
1467
|
+
.ihint strong { display: block; color: var(--status-waiting); margin-bottom: 3px; }
|
|
1256
1468
|
|
|
1257
|
-
/* A node that holds others opens on click, so it says so before you try. */
|
|
1258
|
-
.cv-node.cv-container { cursor: zoom-in; }
|
|
1259
|
-
.cv-node.cv-container:not(.cv-folded) { cursor: zoom-out; }
|
|
1260
|
-
.cv-node.cv-container.cv-folded .cv-desc::after {
|
|
1261
|
-
content: ' — click to open';
|
|
1262
|
-
color: var(--ink-faint);
|
|
1263
|
-
font-size: 11px;
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
/* --------------------------------------------------- reachable elsewhere --- */
|
|
1267
|
-
|
|
1268
|
-
.reach-head { border-top: 1px solid var(--line); padding-top: 10px; }
|
|
1269
|
-
.reach-head[hidden] { display: none; }
|
|
1270
|
-
#reach { max-height: 24vh; }
|
|
1271
|
-
/* Listed, not openable: the transcript is on the other machine. */
|
|
1272
|
-
.reach-row { cursor: default; opacity: 0.92; }
|
|
1273
|
-
.reach-row .name { font-size: 13px; }
|
|
1274
1469
|
|
|
1275
1470
|
/* ------------------------------------------------ history and read-only --- */
|
|
1276
1471
|
|
|
1277
1472
|
/* The seam between what was said before a fork and what is being said now. */
|
|
1278
1473
|
.chat-seam {
|
|
1279
1474
|
display: flex; align-items: center; gap: 10px;
|
|
1280
|
-
margin: 14px 0; font-family: var(--mono); font-size: 10px;
|
|
1475
|
+
margin: 14px 0; font-family: var(--font-mono); font-size: 10px;
|
|
1281
1476
|
letter-spacing: 0.05em; color: var(--ink-faint); text-transform: uppercase;
|
|
1282
1477
|
}
|
|
1283
1478
|
.chat-seam::before, .chat-seam::after {
|
|
1284
1479
|
content: ''; flex: 1; height: 1px; background: var(--line);
|
|
1285
1480
|
}
|
|
1286
1481
|
|
|
1482
|
+
/* The foot of a conversation Studio only reads: why there is no message box, and the two ways on. */
|
|
1287
1483
|
.chat-ro {
|
|
1288
|
-
flex: none;
|
|
1289
|
-
font-size:
|
|
1290
|
-
background: var(--surface-2);
|
|
1484
|
+
flex: none; display: flex; flex-direction: column; gap: var(--space-3);
|
|
1485
|
+
padding: var(--space-3); border-top: 1px solid var(--line); font-size: 13px; line-height: 1.45;
|
|
1291
1486
|
}
|
|
1292
1487
|
.chat-ro[hidden] { display: none; }
|
|
1293
|
-
.chat-ro
|
|
1294
|
-
|
|
1295
|
-
.chat-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1488
|
+
.chat-ro-lead { margin: 0; color: var(--ink-dim); }
|
|
1489
|
+
.chat-ro-lead strong { color: var(--ink); font-weight: 600; }
|
|
1490
|
+
.chat-ro-row { display: flex; align-items: flex-start; gap: var(--space-3); }
|
|
1491
|
+
.chat-ro-row .btn { flex: none; min-width: 132px; justify-content: center; }
|
|
1492
|
+
.chat-ro-row .sub { white-space: normal; flex: 1 1 0; min-width: 0; line-height: 18px; }
|
|
1493
|
+
.chat-ro-cmd { font-family: var(--font-mono); font-size: 12px; color: var(--ink); overflow-wrap: anywhere; }
|
|
1494
|
+
|
|
1495
|
+
/* ----------------------------------------------------- new session ---
|
|
1496
|
+
The same column the conversation takes, and the same width. */
|
|
1497
|
+
.newpanel {
|
|
1498
|
+
min-width: 0; min-height: 0; display: flex; flex-direction: column;
|
|
1499
|
+
background: var(--surface); border-left: 1px solid var(--line);
|
|
1500
|
+
}
|
|
1501
|
+
.newpanel[hidden] { display: none; }
|
|
1502
|
+
.np-head { display: flex; align-items: center; height: var(--toolbar-h); padding: 0 var(--space-2) 0 var(--space-4); border-bottom: 1px solid var(--line); flex: none; }
|
|
1503
|
+
.np-title { margin: 0; font-size: 16px; font-weight: 700; }
|
|
1504
|
+
.np-body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-4); }
|
|
1505
|
+
.np-field { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
|
|
1506
|
+
.np-label { font-size: 13px; font-weight: 600; color: var(--ink); padding: 0; }
|
|
1507
|
+
.np-field .sub, .np-modes > .sub, .np-note .sub { white-space: normal; line-height: 18px; }
|
|
1508
|
+
.np-field code { font-family: var(--font-mono); font-size: 12px; color: var(--ink); overflow-wrap: anywhere; }
|
|
1509
|
+
.np-select, .np-first {
|
|
1510
|
+
box-sizing: border-box; width: 100%;
|
|
1511
|
+
background: var(--ground); border: 1px solid var(--line); border-radius: var(--radius-md); color: var(--ink);
|
|
1512
|
+
}
|
|
1513
|
+
.np-select { height: 40px; padding: 0 var(--space-3); font-family: var(--font-mono); font-size: 13px; }
|
|
1514
|
+
.np-first { resize: none; padding: var(--space-2) var(--space-3); font-family: inherit; font-size: 14px; line-height: 20px; }
|
|
1515
|
+
.np-modes { border: 0; margin: 0; padding: 0; min-width: 0; display: flex; flex-direction: column; gap: var(--space-2); }
|
|
1516
|
+
.np-modes legend { margin-bottom: var(--space-2); }
|
|
1517
|
+
.np-mode {
|
|
1518
|
+
display: flex; gap: var(--space-2); padding: var(--space-3); cursor: pointer;
|
|
1519
|
+
border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--ground);
|
|
1520
|
+
}
|
|
1521
|
+
.np-mode.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--ground)); }
|
|
1522
|
+
.np-mode input { margin: 2px 0 0; flex: none; accent-color: var(--accent); }
|
|
1523
|
+
.np-mode-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
1524
|
+
.np-mode-text .sub { white-space: normal; line-height: 18px; }
|
|
1525
|
+
.np-mode-word { font-size: 13px; font-weight: 600; }
|
|
1526
|
+
.np-note { display: flex; gap: var(--space-2); padding: var(--space-3); border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--ground); }
|
|
1527
|
+
.np-note .ic { color: var(--ink-dim); margin-top: 1px; }
|
|
1528
|
+
.np-note .sub { flex: 1 1 0; min-width: 0; }
|
|
1529
|
+
.np-error { font-size: 13px; color: var(--status-bad); }
|
|
1530
|
+
.np-error[hidden] { display: none; }
|
|
1531
|
+
.np-foot { display: flex; gap: var(--space-2); justify-content: flex-end; padding: var(--space-3) var(--space-4); border-top: 1px solid var(--line); flex: none; }
|
|
1302
1532
|
|
|
1303
1533
|
/* The chat divider only exists while there is a conversation beside it. */
|
|
1304
1534
|
#chat-split[hidden] { display: none; }
|
|
1305
1535
|
|
|
1306
1536
|
.chat-wide { position: relative; }
|
|
1307
|
-
.chat-head .chat-grow {
|
|
1308
|
-
|
|
1309
|
-
@media (max-width: 1100px) {
|
|
1310
|
-
/* Two side panels and a graph do not fit; the conversation wins because it is
|
|
1311
|
-
the thing being read. */
|
|
1312
|
-
.shell { grid-template-columns: 0 0 1fr 5px var(--chat-w, 420px); }
|
|
1313
|
-
.shell > .side, .shell > #resizer { display: none; }
|
|
1314
|
-
}
|
|
1315
|
-
@media (max-width: 760px) {
|
|
1316
|
-
.shell, .shell.no-chat, .shell.no-side { grid-template-columns: 1fr; }
|
|
1317
|
-
.shell > .side, .shell > #resizer, .shell > #chat-split { display: none; }
|
|
1318
|
-
.inspector { display: none; }
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
/* A live session is opened from the fleet the same way it is from the list. */
|
|
1322
|
-
.fleet-row.clickable { cursor: pointer; }
|
|
1323
|
-
.fleet-row.clickable:hover { background: var(--surface-2); }
|
|
1324
|
-
.fleet-row[aria-current="true"] { background: var(--surface-2); box-shadow: inset 3px 0 0 var(--good); }
|
|
1537
|
+
.chat-head .chat-grow { flex: none; }
|
|
1325
1538
|
|
|
1326
1539
|
/* ============================================================ scrollbars === */
|
|
1327
1540
|
|
|
@@ -1349,63 +1562,40 @@ body.vresizing { cursor: row-resize; user-select: none; }
|
|
|
1349
1562
|
/* Where two scrollable panes meet, the bar should not read as a third border. */
|
|
1350
1563
|
.side *::-webkit-scrollbar, .chat *::-webkit-scrollbar, .inspector *::-webkit-scrollbar { width: 8px; }
|
|
1351
1564
|
|
|
1352
|
-
/* -------------------------------------------------------
|
|
1565
|
+
/* ------------------------------------------------------- navigator rail --- */
|
|
1353
1566
|
|
|
1354
|
-
/*
|
|
1355
|
-
|
|
1356
|
-
|
|
1567
|
+
/* What the navigator leaves behind when it is collapsed: its three tabs as
|
|
1568
|
+
icons, and at the foot the control that opens it again. It sits where the
|
|
1569
|
+
navigator was, on the edge it acts on. */
|
|
1357
1570
|
.side-rail {
|
|
1358
1571
|
position: absolute; left: 0; top: 0; bottom: 0; z-index: 25;
|
|
1359
|
-
width:
|
|
1360
|
-
display: flex; flex-direction: column; align-items: center;
|
|
1361
|
-
|
|
1572
|
+
width: var(--rail-w);
|
|
1573
|
+
display: flex; flex-direction: column; align-items: center; gap: 6px;
|
|
1574
|
+
padding: var(--space-3) 0;
|
|
1362
1575
|
background: var(--surface);
|
|
1363
|
-
border
|
|
1364
|
-
color: var(--ink-faint);
|
|
1365
|
-
cursor: pointer;
|
|
1366
|
-
padding: 0;
|
|
1576
|
+
border-right: 1px solid var(--line);
|
|
1367
1577
|
}
|
|
1368
1578
|
.side-rail[hidden] { display: none; }
|
|
1369
|
-
.
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
unlabelled sliver. */
|
|
1375
|
-
.side-rail-label {
|
|
1376
|
-
writing-mode: vertical-rl;
|
|
1377
|
-
font-family: var(--mono); font-size: 10px; letter-spacing: 0.14em;
|
|
1378
|
-
text-transform: uppercase;
|
|
1579
|
+
.rail-btn {
|
|
1580
|
+
width: 40px; height: 40px; padding: 0; flex: none;
|
|
1581
|
+
display: flex; align-items: center; justify-content: center;
|
|
1582
|
+
border: 0; border-radius: var(--radius-md); background: none;
|
|
1583
|
+
color: var(--ink-dim); cursor: pointer;
|
|
1379
1584
|
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
.shell.no-side { grid-template-columns: 22px 0 1fr 5px var(--chat-w, 420px); }
|
|
1383
|
-
.shell.no-side.no-chat { grid-template-columns: 22px 0 1fr 0 0; }
|
|
1384
|
-
|
|
1385
|
-
@media (max-width: 760px) { .side-rail { display: none; } }
|
|
1585
|
+
.rail-btn:hover, .rail-btn.on { background: var(--surface-raised); color: var(--ink); }
|
|
1586
|
+
.rail-fill { flex: 1; }
|
|
1386
1587
|
|
|
1387
1588
|
/* --------------------------------------------------------- full screen --- */
|
|
1388
1589
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
:fullscreen .
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
/* ---------------------------------------------------------- node mark --- */
|
|
1400
|
-
|
|
1401
|
-
.cv-mark { width: 14px; height: 14px; flex: none; color: var(--node-color); }
|
|
1402
|
-
/* Both logos carry their own colours; only the generic glyphs take the node's. */
|
|
1403
|
-
.cv-node[data-source="kit"] .cv-mark,
|
|
1404
|
-
.cv-node[data-source="builtin"] .cv-mark { color: inherit; }
|
|
1405
|
-
/* A type the palette does not know keeps a neutral mark, like its colour. */
|
|
1406
|
-
.cv-mark.cv-mark-unknown { color: var(--ink-faint); opacity: 0.7; }
|
|
1407
|
-
.cv-node[data-source="kit"] .cv-mark { opacity: 1; }
|
|
1408
|
-
.cv-node[data-source="builtin"] .cv-mark { opacity: 0.85; }
|
|
1590
|
+
/* Full screen is the canvas and nothing beside it: the navigator, its rail,
|
|
1591
|
+
the inspector and the conversation step out until Esc. */
|
|
1592
|
+
:root:fullscreen .shell { grid-template-columns: 0 0 minmax(0, 1fr) 0 0 0; }
|
|
1593
|
+
:root:fullscreen .shell > .side,
|
|
1594
|
+
:root:fullscreen .shell > .side-rail,
|
|
1595
|
+
:root:fullscreen .shell > .resizer,
|
|
1596
|
+
:root:fullscreen .shell > .inspector,
|
|
1597
|
+
:root:fullscreen .shell > .chat,
|
|
1598
|
+
:root:fullscreen .shell > .newpanel { display: none; }
|
|
1409
1599
|
|
|
1410
1600
|
/* ------------------------------------------------- tab bar that fits --- */
|
|
1411
1601
|
|
|
@@ -1420,34 +1610,111 @@ body.vresizing { cursor: row-resize; user-select: none; }
|
|
|
1420
1610
|
.tab-dot, .tab-x, .tab-badge { flex: none; }
|
|
1421
1611
|
|
|
1422
1612
|
/* The status and the controls keep their size; the tabs are what scrolls. */
|
|
1423
|
-
.chat-state, .chat-cost, .chat-stop, .chat-grow { flex: none; }
|
|
1424
|
-
.chat-cost { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
1425
1613
|
|
|
1426
|
-
/* Below this the status text is what has to go, not the way out of a tab. */
|
|
1427
|
-
@media (max-width: 1400px) {
|
|
1428
|
-
.chat-head .chat-cost { display: none; }
|
|
1429
|
-
}
|
|
1430
1614
|
|
|
1431
|
-
/*
|
|
1432
|
-
|
|
1433
|
-
.
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1615
|
+
/* ======================================================== breakpoints ===
|
|
1616
|
+
Four widths, one frame. Each query names only what changes at that width;
|
|
1617
|
+
everything it does not name is the frame as drawn above.
|
|
1618
|
+
|
|
1619
|
+
>= 1280 everything open, the inspector docked beside the canvas
|
|
1620
|
+
1024 - 1279 the inspector floats over the canvas, which keeps its width
|
|
1621
|
+
640 - 1023 the navigator is its rail; opened, it floats over the canvas
|
|
1622
|
+
< 640 one column: the stage, and whatever is opened covers it
|
|
1623
|
+
|
|
1624
|
+
app.js collapses the navigator on entering the third band and restores the
|
|
1625
|
+
viewer's own choice on leaving it, so the rail is what a narrow window opens
|
|
1626
|
+
on without the remembered state being overwritten. */
|
|
1627
|
+
|
|
1628
|
+
@media (min-width: 1280px) {
|
|
1629
|
+
/* Docked: back in the grid, in the column kept for it. */
|
|
1630
|
+
.shell.no-chat > .inspector { position: static; grid-column: 4; box-shadow: none; }
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
@media (max-width: 1279px) {
|
|
1634
|
+
/* Beside the navigator the conversation takes two fifths of the window at most. At 1024 the design's 480px
|
|
1635
|
+
left the graph 262px (measured); this leaves it 332. */
|
|
1636
|
+
.shell { grid-template-columns: var(--side-w, var(--nav-w)) 5px minmax(0, 1fr) auto 5px min(var(--chat-w, 480px), 40vw); }
|
|
1637
|
+
.shell.no-side { grid-template-columns: var(--rail-w) 0 minmax(0, 1fr) auto 5px min(var(--chat-w, 480px), 40vw); }
|
|
1638
|
+
/* The dock goes to two rows: what is asked, then the answers under it. */
|
|
1639
|
+
.dock { grid-template-columns: auto minmax(0, 1fr) auto; }
|
|
1640
|
+
.dock-acts { grid-column: 1 / -1; justify-content: flex-end; flex-wrap: wrap; }
|
|
1641
|
+
/* Below this the status text is what has to go, not the way out of a tab. */
|
|
1642
|
+
.pane-head .pane-cost { display: none; }
|
|
1643
|
+
/* The mark stays and the word beside it goes: the room is the breadcrumb's. */
|
|
1644
|
+
.brand h1, .brand + .bar-sep { display: none; }
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
@media (max-width: 1023px) {
|
|
1648
|
+
/* The bar keeps the mark and drops the word beside it; the session keeps its
|
|
1649
|
+
name and drops its project; New session keeps its plus. */
|
|
1650
|
+
.bar { gap: var(--space-3); }
|
|
1651
|
+
.bar-sep, .crumb-project, .crumb-sep, .live-detail { display: none; }
|
|
1652
|
+
.bar .btn.primary > span { display: none; }
|
|
1653
|
+
.bar .btn.primary { width: 36px; padding: 0; }
|
|
1654
|
+
|
|
1655
|
+
.shell,
|
|
1656
|
+
.shell.no-side { grid-template-columns: var(--rail-w) 0 minmax(0, 1fr) auto 5px min(var(--chat-w, 480px), 50vw); }
|
|
1657
|
+
.shell.no-chat,
|
|
1658
|
+
.shell.no-side.no-chat { grid-template-columns: var(--rail-w) 0 minmax(0, 1fr) auto 0 0; }
|
|
1659
|
+
.shell > #resizer { display: none; }
|
|
1660
|
+
.shell > .side {
|
|
1661
|
+
grid-column: auto;
|
|
1662
|
+
position: absolute; z-index: 40;
|
|
1663
|
+
left: 0; top: 0; bottom: 0;
|
|
1664
|
+
width: min(var(--side-w, var(--nav-w)), 86vw);
|
|
1665
|
+
box-shadow: 12px 0 24px -18px var(--shade);
|
|
1666
|
+
}
|
|
1667
|
+
.shell > .inspector { right: calc(min(var(--chat-w, 480px), 50vw) + 5px); }
|
|
1668
|
+
.shell.no-chat > .inspector { right: 0; }
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
@media (max-width: 639px) {
|
|
1672
|
+
/* The Timeline keeps its track: the label column gives up its width, and the drawer stacks. */
|
|
1673
|
+
.tl { --tl-label-w: 148px; }
|
|
1674
|
+
.tl-lab .sub { display: none; }
|
|
1675
|
+
.tl-drawer { flex-direction: column; gap: var(--space-3); max-height: 55%; }
|
|
1676
|
+
.tl-d-main, .tl-d-error { flex: none; width: 100%; }
|
|
1677
|
+
/* The bar keeps the mark, the word "Studio" and the controls that act on a
|
|
1678
|
+
session. Full screen is the one that goes: a phone has no window to leave. */
|
|
1679
|
+
.bar { gap: var(--space-2); padding: 0 var(--space-3); }
|
|
1680
|
+
.bar .crumb, .bar .chips, .bar #fullscreen { display: none; }
|
|
1681
|
+
/* The session's name and its summary sit under the bar, above the view. */
|
|
1682
|
+
.phone-head { display: flex; flex-direction: column; gap: var(--space-2); flex: none; padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--line); min-width: 0; }
|
|
1683
|
+
.phone-head:empty { display: none; }
|
|
1684
|
+
.phone-head .crumb { min-width: 0; }
|
|
1685
|
+
.phone-head .chips { flex-wrap: wrap; }
|
|
1686
|
+
/* The menu button opens the navigator as a drawer over the stage. */
|
|
1687
|
+
#menu-btn { display: inline-flex; }
|
|
1688
|
+
.shell.side-open > .side {
|
|
1689
|
+
display: flex; grid-column: auto; position: absolute; z-index: 45; left: 0; top: 0; bottom: 0;
|
|
1690
|
+
width: min(86vw, 320px); box-shadow: 12px 0 24px -18px var(--shade);
|
|
1691
|
+
}
|
|
1692
|
+
/* A request's three answers are thumb-sized, one under the other. */
|
|
1693
|
+
.ls { padding: var(--space-3); }
|
|
1694
|
+
.ls-acts { flex-direction: column; }
|
|
1695
|
+
.ls-acts .btn { height: 44px; }
|
|
1696
|
+
/* The inspector is a page here: back, not a cross. */
|
|
1697
|
+
.ihead-back { display: inline-flex; }
|
|
1698
|
+
.ihead-close { display: none; }
|
|
1699
|
+
/* The map and the legend are for a canvas with room around the graph; here they would cover it. */
|
|
1700
|
+
.cv-minimap, .cv-legend { display: none; }
|
|
1701
|
+
/* "Studio" comes back beside the mark, as the phone design has it. */
|
|
1702
|
+
.brand h1 { display: block; font-size: 0; }
|
|
1703
|
+
.brand-accent { font-size: 15px; font-weight: 700; color: var(--ink); }
|
|
1704
|
+
|
|
1705
|
+
.shell,
|
|
1706
|
+
.shell.no-chat,
|
|
1707
|
+
.shell.no-side,
|
|
1708
|
+
.shell.no-side.no-chat { grid-template-columns: 0 0 minmax(0, 1fr) auto 0 0; }
|
|
1709
|
+
.shell > .side,
|
|
1710
|
+
.shell > .side-rail,
|
|
1711
|
+
.shell > #chat-split { display: none; }
|
|
1712
|
+
/* The conversation and the inspector each take the whole stage: there is no
|
|
1713
|
+
second column to put them in. */
|
|
1714
|
+
/* They stop above the approval dock, as the inspector does: a request can be answered while a conversation
|
|
1715
|
+
covers the rest of the stage. */
|
|
1716
|
+
.shell > .chat,
|
|
1717
|
+
.shell > .newpanel { grid-column: auto; position: absolute; z-index: 35; inset: 0 0 var(--dock-h, 0px) 0; border-left: none; }
|
|
1718
|
+
.shell > .inspector,
|
|
1719
|
+
.shell.no-chat > .inspector { left: 0; right: 0; top: 0; width: auto; border-left: none; }
|
|
1439
1720
|
}
|
|
1440
|
-
.twisty:hover { background: var(--surface-2); color: var(--ink); }
|
|
1441
|
-
.twisty:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
1442
|
-
|
|
1443
|
-
.side-head [data-fold-for] { cursor: pointer; user-select: none; }
|
|
1444
|
-
.side-head [data-fold-for]:hover { color: var(--ink); }
|
|
1445
|
-
|
|
1446
|
-
/* Two selector classes, so this outranks the `display` these bodies get from
|
|
1447
|
-
.fleet / .sessions / .side-filter. A single .is-folded would tie and lose. */
|
|
1448
|
-
[data-fold-body].is-folded { display: none; }
|
|
1449
|
-
|
|
1450
|
-
/* A folded section stops competing for the leftover height: without this the
|
|
1451
|
-
sidebar keeps a tall blank gap where the project tree used to be. */
|
|
1452
|
-
.side-block.folded { flex: none; }
|
|
1453
|
-
.side-block.grow.folded { border-bottom: 1px solid var(--line); }
|