@flowdular/sandbox 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +696 -0
  3. package/bin/flowdular-sandbox.mjs +394 -0
  4. package/bin/register-types.mjs +53 -0
  5. package/dist/client/assets/App-ClMOrpyg.js +11 -0
  6. package/dist/client/assets/PreviewHost-BHiHrfbQ.js +1 -0
  7. package/dist/client/assets/auto-buGEq6de.js +3 -0
  8. package/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  9. package/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  10. package/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  11. package/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  12. package/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  13. package/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  14. package/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  15. package/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  16. package/dist/client/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  17. package/dist/client/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  18. package/dist/client/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  19. package/dist/client/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  20. package/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  21. package/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  22. package/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  23. package/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  24. package/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  25. package/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  26. package/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  27. package/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  28. package/dist/client/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
  29. package/dist/client/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
  30. package/dist/client/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
  31. package/dist/client/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
  32. package/dist/client/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
  33. package/dist/client/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
  34. package/dist/client/assets/index-CvwhlQ87.js +4 -0
  35. package/dist/client/assets/styles-BPWaGf55.js +3 -0
  36. package/dist/client/assets/styles-Dx1_KOag.css +1 -0
  37. package/dist/client/favicon.svg +1 -0
  38. package/dist/server/assets/auto-CVGNYyb3.js +12785 -0
  39. package/dist/server/assets/chunk-DDJLRBDX-3OUNo51g.js +4125 -0
  40. package/dist/server/assets/nodefs-DVSI7kG0.js +24 -0
  41. package/dist/server/assets/opfs-ahp-D56iokt1.js +388 -0
  42. package/dist/server/assets/token-Bvaq7Anz.js +55 -0
  43. package/dist/server/assets/token-util-BqSvdIPQ.js +3 -0
  44. package/dist/server/assets/token-util-CktFFT4z.js +494 -0
  45. package/dist/server/entry.js +91671 -0
  46. package/dist/server/favicon.svg +1 -0
  47. package/dist/server/index.html +161 -0
  48. package/index.html +159 -0
  49. package/internal/coding-agent/package.json +44 -0
  50. package/internal/coding-agent/src/drivers/byok.ts +322 -0
  51. package/internal/coding-agent/src/drivers/claude-code.ts +290 -0
  52. package/internal/coding-agent/src/drivers/codex.ts +281 -0
  53. package/internal/coding-agent/src/index.ts +56 -0
  54. package/internal/coding-agent/src/registry.ts +119 -0
  55. package/internal/coding-agent/src/roles/contract.ts +60 -0
  56. package/internal/coding-agent/src/roles/defaults.ts +134 -0
  57. package/internal/coding-agent/src/roles/handoff.ts +41 -0
  58. package/internal/coding-agent/src/roles/registry.ts +156 -0
  59. package/internal/coding-agent/src/roles/skills.ts +72 -0
  60. package/internal/coding-agent/src/roles/sync.ts +88 -0
  61. package/internal/coding-agent/src/types.ts +102 -0
  62. package/internal/coding-agent/src/workspace.ts +275 -0
  63. package/octane.config.ts +36 -0
  64. package/package.json +68 -0
  65. package/public/favicon.svg +1 -0
  66. package/public/og.png +0 -0
  67. package/src/App.tsrx +924 -0
  68. package/src/client/ApprovalHandoff.tsrx +39 -0
  69. package/src/client/ChatPane.tsrx +694 -0
  70. package/src/client/ConnectView.tsrx +86 -0
  71. package/src/client/DiffPane.tsrx +150 -0
  72. package/src/client/EjectModal.tsrx +571 -0
  73. package/src/client/GateResults.tsrx +60 -0
  74. package/src/client/GitHubSettingsModal.tsrx +219 -0
  75. package/src/client/NewSessionForm.tsrx +214 -0
  76. package/src/client/SessionBar.tsrx +151 -0
  77. package/src/client/SessionPicker.tsrx +472 -0
  78. package/src/client/SpecEditor.tsrx +109 -0
  79. package/src/client/SpecReviewCard.tsrx +321 -0
  80. package/src/client/WorkCard.tsrx +128 -0
  81. package/src/client/WorkbenchModal.tsrx +161 -0
  82. package/src/client/WorkspaceMenu.tsrx +100 -0
  83. package/src/client/api.ts +797 -0
  84. package/src/client/attachments.ts +73 -0
  85. package/src/client/dashboard.ts +46 -0
  86. package/src/client/github-settings.ts +48 -0
  87. package/src/client/highlight.ts +105 -0
  88. package/src/client/i18n.ts +32 -0
  89. package/src/client/locales/en.json +466 -0
  90. package/src/client/locales/pl.json +466 -0
  91. package/src/client/session-labels.ts +77 -0
  92. package/src/client/state.ts +108 -0
  93. package/src/index.ts +10 -0
  94. package/src/preview/PreviewHost.tsrx +373 -0
  95. package/src/preview/load-module.ts +16 -0
  96. package/src/server/attachments.ts +308 -0
  97. package/src/server/auto-review.ts +181 -0
  98. package/src/server/checkpoints.ts +90 -0
  99. package/src/server/config.ts +460 -0
  100. package/src/server/dashboard.ts +245 -0
  101. package/src/server/delivery/configuration.ts +193 -0
  102. package/src/server/delivery/git-pr.ts +1473 -0
  103. package/src/server/delivery/index.ts +83 -0
  104. package/src/server/delivery/local.ts +151 -0
  105. package/src/server/delivery/official-modules.ts +516 -0
  106. package/src/server/delivery/plan.ts +218 -0
  107. package/src/server/delivery/policies.ts +239 -0
  108. package/src/server/delivery/record.ts +41 -0
  109. package/src/server/delivery/steps.ts +464 -0
  110. package/src/server/delivery/types.ts +158 -0
  111. package/src/server/dependencies.ts +88 -0
  112. package/src/server/diff.ts +230 -0
  113. package/src/server/gates.ts +388 -0
  114. package/src/server/index.ts +139 -0
  115. package/src/server/path-guard.ts +282 -0
  116. package/src/server/planning.ts +749 -0
  117. package/src/server/platform-client.ts +189 -0
  118. package/src/server/preview-database-host.ts +340 -0
  119. package/src/server/preview-database-protocol.ts +163 -0
  120. package/src/server/preview-database-proxy.ts +356 -0
  121. package/src/server/preview-database.ts +109 -0
  122. package/src/server/preview-ipc.ts +15 -0
  123. package/src/server/preview-modules.ts +83 -0
  124. package/src/server/preview-revision.ts +44 -0
  125. package/src/server/preview-runtime.ts +467 -0
  126. package/src/server/preview-worker-manager.ts +477 -0
  127. package/src/server/preview-worker.ts +168 -0
  128. package/src/server/reference.ts +251 -0
  129. package/src/server/routes.ts +2258 -0
  130. package/src/server/runtime.ts +188 -0
  131. package/src/server/session-owner.ts +23 -0
  132. package/src/server/sessions.ts +849 -0
  133. package/src/server/spec.ts +471 -0
  134. package/src/server/turn-lifecycle.ts +56 -0
  135. package/src/server/turns.ts +993 -0
  136. package/src/server/workspace-install.ts +266 -0
  137. package/src/server/workspace-root.ts +73 -0
  138. package/src/styles.css +1573 -0
  139. package/vite.config.ts +134 -0
package/src/styles.css ADDED
@@ -0,0 +1,1573 @@
1
+ /* Sandbox shell. Layout only: every colour, size, and font comes from the
2
+ @flowdular/sdk/ui tokens so the preview matches the real application. */
3
+
4
+ .sandbox {
5
+ display: flex;
6
+ /* Application bar, an optional notice, the session bar, then the work. The
7
+ bars take what they need and the work takes the rest, whatever number of
8
+ bars this view has, so a long transcript never pushes the composer off the
9
+ screen. */
10
+ flex-direction: column;
11
+ height: 100vh;
12
+ min-width: 0;
13
+ overflow: hidden;
14
+ background: var(--surface);
15
+ }
16
+
17
+ .sandbox__body,
18
+ .sandbox > .sandbox__scroll {
19
+ flex: 1 1 auto;
20
+ min-height: 0;
21
+ }
22
+
23
+ .sandbox__notice {
24
+ padding: 12px 18px 0;
25
+ }
26
+
27
+ .sandbox__notice:empty {
28
+ display: none;
29
+ }
30
+
31
+ .sandbox__bar {
32
+ display: flex;
33
+ flex: 0 0 auto;
34
+ flex-wrap: wrap;
35
+ align-items: center;
36
+ gap: 10px;
37
+ min-height: var(--topbar-h);
38
+ padding: 0 16px;
39
+ background: var(--surface);
40
+ border-bottom: 1px solid var(--line);
41
+ }
42
+
43
+ .sandbox__brand {
44
+ display: inline-flex;
45
+ align-items: center;
46
+ gap: 8px;
47
+ padding: 0;
48
+ font-family: var(--font-sans);
49
+ font-size: 15px;
50
+ font-weight: 600;
51
+ color: var(--ink);
52
+ letter-spacing: -0.01em;
53
+ background: transparent;
54
+ border: 0;
55
+ cursor: pointer;
56
+ }
57
+
58
+ /* Workspace and connection, the only global controls beside the brand. */
59
+ .workspace-menu {
60
+ position: relative;
61
+ }
62
+
63
+ .workspace-menu__button {
64
+ display: inline-flex;
65
+ align-items: center;
66
+ gap: 10px;
67
+ max-width: 320px;
68
+ padding: 8px 12px;
69
+ background: transparent;
70
+ border: 1px solid transparent;
71
+ border-radius: var(--r-lg);
72
+ cursor: pointer;
73
+ transition: background var(--t-fast) var(--ease);
74
+ }
75
+
76
+ .workspace-menu__button:hover,
77
+ .workspace-menu__button[aria-expanded='true'] {
78
+ background: var(--surface-2);
79
+ }
80
+
81
+ .workspace-menu__label {
82
+ display: grid;
83
+ min-width: 0;
84
+ text-align: left;
85
+ }
86
+
87
+ .workspace-menu__label b {
88
+ font-size: var(--text-md);
89
+ font-weight: 600;
90
+ overflow: hidden;
91
+ text-overflow: ellipsis;
92
+ white-space: nowrap;
93
+ }
94
+
95
+ .workspace-menu__label small {
96
+ font-size: var(--text-sm);
97
+ color: var(--ink-3);
98
+ overflow: hidden;
99
+ text-overflow: ellipsis;
100
+ white-space: nowrap;
101
+ }
102
+
103
+ .workspace-menu__panel {
104
+ position: absolute;
105
+ top: calc(100% + 6px);
106
+ right: 0;
107
+ z-index: 40;
108
+ width: 300px;
109
+ }
110
+
111
+ /* One session, its state, and the actions that belong to it. */
112
+ .session-bar {
113
+ display: flex;
114
+ flex-wrap: wrap;
115
+ align-items: center;
116
+ gap: 10px;
117
+ padding: 10px 18px;
118
+ background: var(--surface);
119
+ border-bottom: 1px solid var(--line-2);
120
+ }
121
+
122
+ .session-bar__back {
123
+ display: inline-flex;
124
+ align-items: center;
125
+ gap: 4px;
126
+ padding: 4px 8px 4px 4px;
127
+ font-family: var(--font-sans);
128
+ font-size: var(--text-sm);
129
+ font-weight: 500;
130
+ color: var(--ink-3);
131
+ background: transparent;
132
+ border: 0;
133
+ border-radius: var(--r);
134
+ cursor: pointer;
135
+ }
136
+
137
+ .session-bar__back:hover {
138
+ color: var(--ink);
139
+ background: var(--surface-2);
140
+ }
141
+
142
+ .session-bar__back svg {
143
+ transform: rotate(90deg);
144
+ }
145
+
146
+ .session-bar__title {
147
+ display: inline-flex;
148
+ align-items: baseline;
149
+ gap: 8px;
150
+ min-width: 0;
151
+ }
152
+
153
+ .session-bar__add {
154
+ display: inline-flex;
155
+ align-items: center;
156
+ gap: 4px;
157
+ padding: 2px 6px;
158
+ font-family: var(--font-sans);
159
+ font-size: var(--text-xs);
160
+ font-weight: 600;
161
+ letter-spacing: 0.04em;
162
+ text-transform: uppercase;
163
+ color: var(--ink-3);
164
+ background: transparent;
165
+ border: 0;
166
+ border-radius: var(--r-sm);
167
+ cursor: pointer;
168
+ transition: color var(--t-fast) var(--ease);
169
+ }
170
+
171
+ .session-bar__add:hover:not(:disabled) {
172
+ color: var(--primary);
173
+ }
174
+
175
+ .session-bar__add:disabled {
176
+ cursor: not-allowed;
177
+ opacity: 0.5;
178
+ }
179
+
180
+ .session-bar__title b {
181
+ font-size: var(--text-md);
182
+ font-weight: 600;
183
+ overflow: hidden;
184
+ text-overflow: ellipsis;
185
+ white-space: nowrap;
186
+ }
187
+
188
+ .session-bar__title small {
189
+ flex: none;
190
+ font-size: var(--text-sm);
191
+ color: var(--ink-3);
192
+ }
193
+
194
+ .sandbox__spacer {
195
+ flex: 1;
196
+ }
197
+
198
+ /* The conversation is the page. The work card floats over it on the right,
199
+ inside the same surface, instead of sitting in a separate panel. */
200
+ .sandbox__body {
201
+ --sandbox-preview-width: clamp(
202
+ calc(var(--sidebar-w) * 1.5),
203
+ 38vw,
204
+ calc(var(--sidebar-w) * 2.4)
205
+ );
206
+ position: relative;
207
+ display: grid;
208
+ grid-template-columns: minmax(0, 1fr);
209
+ min-width: 0;
210
+ min-height: 0;
211
+ }
212
+
213
+ .sandbox__chat {
214
+ display: grid;
215
+ grid-template-rows: minmax(0, 1fr) auto;
216
+ min-width: 0;
217
+ min-height: 0;
218
+ }
219
+
220
+ .sandbox__side {
221
+ position: absolute;
222
+ inset: 0 0 0 auto;
223
+ z-index: 1;
224
+ width: var(--sandbox-preview-width);
225
+ display: grid;
226
+ grid-template-rows: minmax(0, 1fr);
227
+ min-width: 0;
228
+ min-height: 0;
229
+ padding: 18px 18px 18px 0;
230
+ }
231
+
232
+ @media (min-width: 1101px) {
233
+ /* The card overlays the chat surface, but never its text or controls. */
234
+ .sandbox__chat > .sandbox__scroll,
235
+ .sandbox__chat > .chat__composer {
236
+ padding-right: calc(var(--sandbox-preview-width) + var(--content-pad));
237
+ }
238
+ }
239
+
240
+ @media (max-width: 1100px) {
241
+ .sandbox__body {
242
+ grid-template-columns: minmax(0, 1fr);
243
+ }
244
+
245
+ .sandbox__side {
246
+ display: none;
247
+ }
248
+ }
249
+
250
+ .sandbox__pane-head {
251
+ display: flex;
252
+ flex-wrap: wrap;
253
+ align-items: center;
254
+ gap: 8px;
255
+ padding: 10px 18px;
256
+ background: var(--surface);
257
+ border-bottom: 1px solid var(--line-2);
258
+ }
259
+
260
+ .sandbox__pane-title {
261
+ font-size: var(--text-sm);
262
+ font-weight: 600;
263
+ letter-spacing: 0.04em;
264
+ text-transform: uppercase;
265
+ color: var(--ink-3);
266
+ }
267
+
268
+ /* Controls in a pane header stay compact so the header reads as a header. */
269
+ .workbench__head .ui-select,
270
+ .work-card__head .ui-select,
271
+ .sandbox__pane-head .ui-select {
272
+ width: auto;
273
+ min-width: 150px;
274
+ max-width: 220px;
275
+ }
276
+
277
+ .sandbox__scroll {
278
+ min-height: 0;
279
+ overflow-y: auto;
280
+ overflow-x: hidden;
281
+ padding: 18px;
282
+ }
283
+
284
+ .sandbox__column {
285
+ width: min(760px, 100%);
286
+ margin: 0 auto;
287
+ }
288
+
289
+ /* Chat */
290
+ .chat {
291
+ display: grid;
292
+ align-content: start;
293
+ gap: 10px;
294
+ }
295
+
296
+ .chat__turn {
297
+ display: grid;
298
+ gap: 6px;
299
+ padding: 12px 14px;
300
+ background: var(--surface);
301
+ border: 1px solid var(--line);
302
+ border-radius: var(--r-md);
303
+ }
304
+
305
+ .chat__turn--user {
306
+ background: var(--primary-tint);
307
+ border-color: var(--primary-soft);
308
+ }
309
+
310
+ .chat__meta {
311
+ display: flex;
312
+ flex-wrap: wrap;
313
+ align-items: center;
314
+ gap: 8px;
315
+ font-size: var(--text-xs);
316
+ font-weight: 600;
317
+ letter-spacing: 0.06em;
318
+ text-transform: uppercase;
319
+ color: var(--ink-3);
320
+ }
321
+
322
+ /* Which module an entry belongs to, quiet enough to ignore in a session that
323
+ only has one. */
324
+ .chat__module {
325
+ font-size: var(--text-xs);
326
+ font-weight: 500;
327
+ letter-spacing: 0;
328
+ text-transform: none;
329
+ color: var(--ink-3);
330
+ }
331
+
332
+ .chat__text {
333
+ margin: 0;
334
+ font-size: var(--text-md);
335
+ line-height: 1.55;
336
+ white-space: pre-wrap;
337
+ overflow-wrap: anywhere;
338
+ }
339
+
340
+ .chat__events {
341
+ display: grid;
342
+ gap: 4px;
343
+ margin: 0;
344
+ padding: 0;
345
+ list-style: none;
346
+ }
347
+
348
+ .chat__event {
349
+ display: flex;
350
+ align-items: center;
351
+ gap: 8px;
352
+ min-width: 0;
353
+ font-family: var(--font-mono);
354
+ font-size: var(--text-sm);
355
+ color: var(--ink-3);
356
+ }
357
+
358
+ .chat__event-text {
359
+ min-width: 0;
360
+ overflow: hidden;
361
+ text-overflow: ellipsis;
362
+ white-space: nowrap;
363
+ }
364
+
365
+ .chat__event--error {
366
+ color: var(--danger);
367
+ }
368
+
369
+ .chat__composer {
370
+ display: grid;
371
+ justify-items: center;
372
+ gap: 8px;
373
+ padding: 12px 14px;
374
+ background: var(--surface);
375
+ border-top: 1px solid var(--line);
376
+ }
377
+
378
+ .chat__composer > * {
379
+ width: min(760px, 100%);
380
+ }
381
+
382
+ .chat__composer-row {
383
+ display: flex;
384
+ flex-wrap: wrap;
385
+ align-items: center;
386
+ gap: 12px;
387
+ }
388
+
389
+ .chat__composer-status {
390
+ margin: 0;
391
+ font-size: var(--text-sm);
392
+ color: var(--ink-3);
393
+ }
394
+
395
+ /* Role and agent sit with the send action, as quiet underlined pickers rather
396
+ than boxed controls that compete with the conversation. */
397
+ .chat__picker {
398
+ height: 26px;
399
+ padding: 0 22px 0 2px;
400
+ font-family: var(--font-sans);
401
+ font-size: var(--text-sm);
402
+ font-weight: 500;
403
+ color: var(--ink-2);
404
+ appearance: none;
405
+ background-color: transparent;
406
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364738F' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
407
+ background-repeat: no-repeat;
408
+ background-position: right 2px center;
409
+ background-size: 14px;
410
+ border: 0;
411
+ border-bottom: 1px solid var(--line);
412
+ border-radius: 0;
413
+ cursor: pointer;
414
+ transition: border-color var(--t-fast) var(--ease);
415
+ }
416
+
417
+ .chat__picker:hover {
418
+ border-bottom-color: var(--ink-3);
419
+ }
420
+
421
+ .chat__picker:focus-visible {
422
+ border-bottom-color: var(--primary);
423
+ outline: 0;
424
+ }
425
+
426
+ /* A handoff is the orchestrator speaking, not a specialist: it names who works
427
+ next and carries the action that starts them. */
428
+ .chat__handoff {
429
+ display: grid;
430
+ gap: 8px;
431
+ padding: 12px 14px;
432
+ background: var(--surface);
433
+ border: 1px dashed var(--line);
434
+ border-left: 3px solid var(--primary);
435
+ border-radius: var(--r-md);
436
+ }
437
+
438
+ .chat__handoff--question,
439
+ .chat__handoff--approval {
440
+ background: var(--warning-bg);
441
+ border-color: var(--warning-line);
442
+ border-left-color: var(--warning);
443
+ }
444
+
445
+ .chat__handoff--blocked {
446
+ border-left-color: var(--danger);
447
+ }
448
+
449
+ .chat__handoff--review {
450
+ border-left-color: var(--success);
451
+ }
452
+
453
+ .chat__handoff .ui-form__actions {
454
+ margin-left: 0;
455
+ }
456
+
457
+ /* The specification the operator approves, and what the session changed in it.
458
+ Long lists stay readable: they wrap, they never scroll sideways, and only an
459
+ identifier is monospaced. */
460
+ .spec-review {
461
+ display: grid;
462
+ gap: 10px;
463
+ padding: 12px;
464
+ background: var(--surface);
465
+ border: 1px solid var(--line);
466
+ border-radius: var(--r-md);
467
+ min-width: 0;
468
+ }
469
+
470
+ .spec-review__head {
471
+ display: flex;
472
+ flex-wrap: wrap;
473
+ align-items: center;
474
+ gap: 8px;
475
+ font-size: var(--text-sm);
476
+ }
477
+
478
+ .spec-review__path {
479
+ font-size: var(--text-xs);
480
+ color: var(--ink-3);
481
+ overflow-wrap: anywhere;
482
+ }
483
+
484
+ .spec-review__block {
485
+ display: grid;
486
+ gap: 6px;
487
+ min-width: 0;
488
+ }
489
+
490
+ .spec-review__title {
491
+ margin: 0;
492
+ font-size: var(--text-xs);
493
+ font-weight: 600;
494
+ letter-spacing: 0.06em;
495
+ text-transform: uppercase;
496
+ color: var(--ink-3);
497
+ }
498
+
499
+ .spec-review__text,
500
+ .spec-review__empty {
501
+ margin: 0;
502
+ font-size: var(--text-sm);
503
+ line-height: 1.5;
504
+ overflow-wrap: anywhere;
505
+ }
506
+
507
+ .spec-review__empty {
508
+ color: var(--ink-3);
509
+ }
510
+
511
+ .spec-review__facts {
512
+ display: grid;
513
+ grid-template-columns: auto minmax(0, 1fr);
514
+ gap: 2px 10px;
515
+ margin: 0;
516
+ font-size: var(--text-sm);
517
+ }
518
+
519
+ .spec-review__facts dt {
520
+ color: var(--ink-3);
521
+ }
522
+
523
+ .spec-review__facts dd {
524
+ margin: 0;
525
+ overflow-wrap: anywhere;
526
+ }
527
+
528
+ .spec-review__changes,
529
+ .spec-review__list {
530
+ display: grid;
531
+ gap: 4px;
532
+ margin: 0;
533
+ padding: 0;
534
+ list-style: none;
535
+ font-size: var(--text-sm);
536
+ }
537
+
538
+ .spec-review__change,
539
+ .spec-review__item {
540
+ display: flex;
541
+ flex-wrap: wrap;
542
+ align-items: baseline;
543
+ gap: 6px;
544
+ padding: 3px 6px;
545
+ border-radius: var(--r-sm);
546
+ overflow-wrap: anywhere;
547
+ }
548
+
549
+ .spec-review__scenario {
550
+ display: grid;
551
+ gap: 2px;
552
+ padding: 6px;
553
+ border-radius: var(--r-sm);
554
+ overflow-wrap: anywhere;
555
+ }
556
+
557
+ .spec-review__change--added,
558
+ .spec-review__item--added {
559
+ background: var(--success-bg);
560
+ }
561
+
562
+ .spec-review__change--removed,
563
+ .spec-review__item--removed {
564
+ background: var(--danger-bg);
565
+ }
566
+
567
+ .spec-review__change--changed,
568
+ .spec-review__item--changed {
569
+ background: var(--warning-bg);
570
+ }
571
+
572
+ .spec-review__sign {
573
+ font-family: var(--font-mono);
574
+ font-weight: 600;
575
+ color: var(--ink-3);
576
+ }
577
+
578
+ .spec-review__field {
579
+ font-size: var(--text-xs);
580
+ font-weight: 600;
581
+ letter-spacing: 0.04em;
582
+ text-transform: uppercase;
583
+ color: var(--ink-3);
584
+ }
585
+
586
+ .spec-review__value {
587
+ color: var(--ink-2);
588
+ }
589
+
590
+ .spec-review__ask {
591
+ display: grid;
592
+ gap: 8px;
593
+ }
594
+
595
+ /* The specification as text, for the operator who would rather fix it than
596
+ describe it. */
597
+ .spec-editor {
598
+ display: grid;
599
+ grid-template-rows: auto auto minmax(0, 1fr);
600
+ gap: 8px;
601
+ height: 100%;
602
+ padding: 12px;
603
+ }
604
+
605
+ .spec-editor__bar {
606
+ display: flex;
607
+ flex-wrap: wrap;
608
+ align-items: center;
609
+ gap: 8px;
610
+ font-size: var(--text-sm);
611
+ }
612
+
613
+ .spec-editor__error {
614
+ margin: 0;
615
+ font-size: var(--text-sm);
616
+ color: var(--danger);
617
+ }
618
+
619
+ .spec-editor__notice {
620
+ margin: 0;
621
+ font-size: var(--text-sm);
622
+ color: var(--ink-3);
623
+ }
624
+
625
+ .spec-editor__text {
626
+ min-height: 60vh;
627
+ font-family: var(--font-mono);
628
+ font-size: var(--text-sm);
629
+ line-height: 1.5;
630
+ white-space: pre;
631
+ resize: none;
632
+ }
633
+
634
+ /* A recovery action, kept quiet: it sits under a turn and only offers to roll
635
+ the workspace back to that point. */
636
+ .chat__restore {
637
+ display: flex;
638
+ flex-wrap: wrap;
639
+ align-items: center;
640
+ gap: 8px;
641
+ }
642
+
643
+ .chat__restore-toggle,
644
+ .chat__restore-go,
645
+ .chat__restore-cancel {
646
+ display: inline-flex;
647
+ align-items: center;
648
+ gap: 4px;
649
+ padding: 2px 6px;
650
+ font-family: var(--font-sans);
651
+ font-size: var(--text-xs);
652
+ font-weight: 600;
653
+ letter-spacing: 0.04em;
654
+ text-transform: uppercase;
655
+ color: var(--ink-3);
656
+ background: transparent;
657
+ border: 0;
658
+ border-radius: var(--r-sm);
659
+ cursor: pointer;
660
+ transition: color var(--t-fast) var(--ease);
661
+ }
662
+
663
+ .chat__restore-toggle:hover,
664
+ .chat__restore-go:hover:not(:disabled),
665
+ .chat__restore-cancel:hover:not(:disabled) {
666
+ color: var(--primary);
667
+ }
668
+
669
+ .chat__restore-go:disabled,
670
+ .chat__restore-cancel:disabled {
671
+ cursor: not-allowed;
672
+ opacity: 0.5;
673
+ }
674
+
675
+ .chat__restore-ask {
676
+ font-size: var(--text-sm);
677
+ color: var(--ink-3);
678
+ }
679
+
680
+ .chat__restore-error {
681
+ width: 100%;
682
+ font-size: var(--text-sm);
683
+ color: var(--danger);
684
+ }
685
+
686
+ .chat__auto {
687
+ display: inline-flex;
688
+ align-items: center;
689
+ gap: 6px;
690
+ font-size: var(--text-sm);
691
+ color: var(--ink-3);
692
+ white-space: nowrap;
693
+ }
694
+
695
+ .chat__selection {
696
+ display: grid;
697
+ gap: 4px;
698
+ padding: 8px 10px;
699
+ font-size: var(--text-sm);
700
+ background: var(--primary-tint);
701
+ border: 1px solid var(--primary-soft);
702
+ border-radius: var(--r);
703
+ }
704
+
705
+ /* The attachment strip sits above the textarea: one chip per file, an image
706
+ thumbnail or a file icon, its name, its size, and a remove control. */
707
+ .chat__attachments {
708
+ display: flex;
709
+ flex-wrap: wrap;
710
+ gap: 8px;
711
+ }
712
+
713
+ .chat__attachment {
714
+ display: flex;
715
+ align-items: center;
716
+ gap: 8px;
717
+ max-width: 240px;
718
+ padding: 4px 6px 4px 4px;
719
+ background: var(--surface-2);
720
+ border: 1px solid var(--line);
721
+ border-radius: var(--r);
722
+ }
723
+
724
+ .chat__attachment--error {
725
+ border-color: var(--danger);
726
+ background: var(--danger-bg);
727
+ }
728
+
729
+ .chat__attachment-thumb {
730
+ flex: none;
731
+ width: 40px;
732
+ height: 40px;
733
+ object-fit: cover;
734
+ border-radius: var(--r-sm);
735
+ background: var(--surface);
736
+ }
737
+
738
+ .chat__attachment-thumb--file {
739
+ display: flex;
740
+ align-items: center;
741
+ justify-content: center;
742
+ color: var(--ink-3);
743
+ border: 1px solid var(--line);
744
+ }
745
+
746
+ .chat__attachment-info {
747
+ display: grid;
748
+ gap: 2px;
749
+ min-width: 0;
750
+ }
751
+
752
+ .chat__attachment-name {
753
+ overflow: hidden;
754
+ font-size: var(--text-sm);
755
+ text-overflow: ellipsis;
756
+ white-space: nowrap;
757
+ }
758
+
759
+ .chat__attachment-size {
760
+ font-size: var(--text-xs);
761
+ color: var(--ink-3);
762
+ }
763
+
764
+ .chat__attachment-remove {
765
+ display: flex;
766
+ flex: none;
767
+ align-items: center;
768
+ justify-content: center;
769
+ padding: 2px;
770
+ color: var(--ink-3);
771
+ background: transparent;
772
+ border: 0;
773
+ border-radius: var(--r-sm);
774
+ cursor: pointer;
775
+ transition: color var(--t-fast) var(--ease);
776
+ }
777
+
778
+ .chat__attachment-remove:hover {
779
+ color: var(--danger);
780
+ }
781
+
782
+ .chat__attachment-error {
783
+ width: 100%;
784
+ font-size: var(--text-sm);
785
+ color: var(--danger);
786
+ }
787
+
788
+ /* The attach button matches the quiet composer pickers, and the file input it
789
+ opens stays out of the layout. */
790
+ .chat__attach {
791
+ display: flex;
792
+ align-items: center;
793
+ justify-content: center;
794
+ width: 26px;
795
+ height: 26px;
796
+ color: var(--ink-2);
797
+ background: transparent;
798
+ border: 1px solid var(--line);
799
+ border-radius: var(--r-sm);
800
+ cursor: pointer;
801
+ transition:
802
+ color var(--t-fast) var(--ease),
803
+ border-color var(--t-fast) var(--ease);
804
+ }
805
+
806
+ .chat__attach:hover:not(:disabled) {
807
+ color: var(--primary);
808
+ border-color: var(--primary);
809
+ }
810
+
811
+ .chat__attach:disabled {
812
+ cursor: not-allowed;
813
+ opacity: 0.5;
814
+ }
815
+
816
+ .chat__attach-input {
817
+ display: none;
818
+ }
819
+
820
+ /* The work card: preview or code, floating on the right of the conversation.
821
+ A click opens the same content full screen. */
822
+ .work-card {
823
+ position: relative;
824
+ display: grid;
825
+ grid-template-rows: auto minmax(0, 1fr);
826
+ min-width: 0;
827
+ min-height: 0;
828
+ background: var(--surface);
829
+ border: 1px solid var(--line);
830
+ border-radius: var(--r-lg);
831
+ box-shadow:
832
+ 0 1px 2px rgb(16 24 40 / 6%),
833
+ 0 18px 40px rgb(16 24 40 / 8%);
834
+ overflow: hidden;
835
+ transition:
836
+ box-shadow var(--t-base) var(--ease),
837
+ border-color var(--t-base) var(--ease);
838
+ }
839
+
840
+ .work-card:hover {
841
+ border-color: var(--primary-soft);
842
+ box-shadow:
843
+ 0 1px 2px rgb(16 24 40 / 8%),
844
+ 0 24px 52px rgb(16 24 40 / 12%);
845
+ }
846
+
847
+ .work-card__head {
848
+ display: flex;
849
+ align-items: center;
850
+ gap: 8px;
851
+ padding: 10px 12px;
852
+ border-bottom: 1px solid var(--line-2);
853
+ }
854
+
855
+ .work-card__stage {
856
+ position: relative;
857
+ min-width: 0;
858
+ min-height: 0;
859
+ background: var(--surface-2);
860
+ overflow: hidden;
861
+ }
862
+
863
+ .work-card__stage--code {
864
+ overflow-y: auto;
865
+ background: var(--bg);
866
+ }
867
+
868
+ /* The preview renders at a real workspace width and is scaled to the card, so
869
+ the card shows the true desktop layout instead of a narrow rendering. */
870
+ .work-card__viewport {
871
+ position: absolute;
872
+ inset: 0;
873
+ overflow: hidden;
874
+ }
875
+
876
+ .work-card__viewport .preview-frame {
877
+ width: 1440px;
878
+ height: 900px;
879
+ transform-origin: top left;
880
+ }
881
+
882
+ .work-card__open {
883
+ position: absolute;
884
+ inset: 0;
885
+ z-index: 2;
886
+ display: grid;
887
+ place-items: center;
888
+ padding: 0;
889
+ background: transparent;
890
+ border: 0;
891
+ cursor: zoom-in;
892
+ }
893
+
894
+ .work-card__hint {
895
+ display: inline-flex;
896
+ align-items: center;
897
+ gap: 6px;
898
+ padding: 6px 10px;
899
+ font-size: var(--text-sm);
900
+ font-weight: 500;
901
+ color: var(--ink-0);
902
+ background: rgb(16 24 40 / 72%);
903
+ border-radius: var(--r);
904
+ opacity: 0;
905
+ transition: opacity var(--t-base) var(--ease);
906
+ }
907
+
908
+ .work-card__open:hover .work-card__hint,
909
+ .work-card__open:focus-visible .work-card__hint {
910
+ opacity: 1;
911
+ }
912
+
913
+ .work-card__foot {
914
+ display: flex;
915
+ flex-wrap: wrap;
916
+ align-items: center;
917
+ gap: 6px;
918
+ padding: 8px 12px;
919
+ border-top: 1px solid var(--line-2);
920
+ }
921
+
922
+ /* Full-screen workbench: the same preview at working size, or the diff. */
923
+ .workbench {
924
+ position: fixed;
925
+ inset: 0;
926
+ z-index: 80;
927
+ display: grid;
928
+ place-items: center;
929
+ padding: 24px;
930
+ background: rgb(12 18 34 / 55%);
931
+ }
932
+
933
+ .workbench__panel {
934
+ display: grid;
935
+ grid-template-rows: auto minmax(0, 1fr);
936
+ width: min(1600px, 100%);
937
+ height: min(960px, 100%);
938
+ min-width: 0;
939
+ background: var(--surface);
940
+ border: 1px solid var(--line);
941
+ border-radius: var(--r-lg);
942
+ box-shadow: 0 24px 64px rgb(16 24 40 / 24%);
943
+ overflow: hidden;
944
+ }
945
+
946
+ .workbench__head {
947
+ display: flex;
948
+ flex-wrap: wrap;
949
+ align-items: center;
950
+ gap: 8px;
951
+ padding: 10px 14px;
952
+ border-bottom: 1px solid var(--line);
953
+ }
954
+
955
+ .workbench__body {
956
+ position: relative;
957
+ min-width: 0;
958
+ min-height: 0;
959
+ background: var(--surface-2);
960
+ overflow: hidden;
961
+ }
962
+
963
+ .workbench__body--scroll {
964
+ overflow-y: auto;
965
+ background: var(--bg);
966
+ }
967
+
968
+ .preview-frame {
969
+ width: 100%;
970
+ height: 100%;
971
+ border: 0;
972
+ background: var(--surface);
973
+ }
974
+
975
+ .preview-frame__wrap {
976
+ position: relative;
977
+ min-height: 0;
978
+ background: var(--surface-2);
979
+ }
980
+
981
+ .preview-frame__wrap--selecting::after {
982
+ position: absolute;
983
+ inset: 0;
984
+ content: '';
985
+ border: 2px dashed var(--primary);
986
+ pointer-events: none;
987
+ }
988
+
989
+ /* Eject: a delivery screen, not a banner. */
990
+ .eject__panel {
991
+ display: grid;
992
+ grid-template-rows: auto minmax(0, 1fr) auto;
993
+ width: min(560px, 100%);
994
+ max-height: min(680px, 100%);
995
+ background: var(--surface);
996
+ border: 1px solid var(--line);
997
+ border-radius: var(--r-lg);
998
+ box-shadow: 0 24px 64px rgb(16 24 40 / 24%);
999
+ overflow: hidden;
1000
+ }
1001
+
1002
+ .eject__head {
1003
+ display: flex;
1004
+ align-items: center;
1005
+ gap: 8px;
1006
+ padding: 14px 18px;
1007
+ border-bottom: 1px solid var(--line-2);
1008
+ }
1009
+
1010
+ .eject__title {
1011
+ display: inline-flex;
1012
+ align-items: center;
1013
+ gap: 8px;
1014
+ font-size: var(--text-lg);
1015
+ font-weight: 600;
1016
+ }
1017
+
1018
+ .eject__body {
1019
+ display: grid;
1020
+ align-content: start;
1021
+ gap: 14px;
1022
+ min-height: 0;
1023
+ padding: 18px;
1024
+ overflow-y: auto;
1025
+ }
1026
+
1027
+ .eject__lead {
1028
+ margin: 0;
1029
+ font-size: var(--text-md);
1030
+ }
1031
+
1032
+ .eject__steps {
1033
+ display: grid;
1034
+ gap: 8px;
1035
+ margin: 0;
1036
+ padding: 0;
1037
+ list-style: none;
1038
+ }
1039
+
1040
+ .eject__step {
1041
+ display: flex;
1042
+ align-items: center;
1043
+ gap: 10px;
1044
+ font-size: var(--text-md);
1045
+ color: var(--ink-3);
1046
+ }
1047
+
1048
+ .eject__step--passed {
1049
+ color: var(--ink);
1050
+ }
1051
+
1052
+ .eject__step--failed {
1053
+ color: var(--danger);
1054
+ }
1055
+
1056
+ .eject__step--note {
1057
+ color: var(--warning);
1058
+ }
1059
+
1060
+ .eject__error {
1061
+ display: block;
1062
+ max-height: 40vh;
1063
+ overflow: auto;
1064
+ font-family: var(--font-mono);
1065
+ font-size: var(--text-sm);
1066
+ white-space: pre-wrap;
1067
+ overflow-wrap: anywhere;
1068
+ }
1069
+
1070
+ .eject__step--running {
1071
+ color: var(--primary-hover);
1072
+ }
1073
+
1074
+ .eject__step-dot {
1075
+ width: 6px;
1076
+ height: 6px;
1077
+ background: var(--ink-4);
1078
+ border-radius: 50%;
1079
+ }
1080
+
1081
+ .eject__step-icon {
1082
+ display: inline-grid;
1083
+ place-items: center;
1084
+ width: 22px;
1085
+ height: 22px;
1086
+ background: var(--surface-2);
1087
+ border-radius: 50%;
1088
+ }
1089
+
1090
+ .eject__step--passed .eject__step-icon {
1091
+ color: var(--success);
1092
+ background: var(--success-bg);
1093
+ }
1094
+
1095
+ .eject__step--failed .eject__step-icon {
1096
+ color: var(--danger);
1097
+ background: var(--danger-bg);
1098
+ }
1099
+
1100
+ .eject__step-icon--spin {
1101
+ color: var(--primary);
1102
+ background: var(--primary-tint);
1103
+ animation: eject-spin 1.1s linear infinite;
1104
+ }
1105
+
1106
+ @keyframes eject-spin {
1107
+ to {
1108
+ transform: rotate(360deg);
1109
+ }
1110
+ }
1111
+
1112
+ @media (prefers-reduced-motion: reduce) {
1113
+ .eject__step-icon--spin {
1114
+ animation: none;
1115
+ }
1116
+ }
1117
+
1118
+ .eject__step-label {
1119
+ min-width: 0;
1120
+ overflow-wrap: anywhere;
1121
+ }
1122
+
1123
+ .eject__step-detail {
1124
+ font-family: var(--font-mono);
1125
+ font-size: var(--text-sm);
1126
+ color: var(--ink-3);
1127
+ overflow-wrap: anywhere;
1128
+ }
1129
+
1130
+ .eject__done {
1131
+ display: grid;
1132
+ gap: 4px;
1133
+ padding: 14px 16px;
1134
+ background: var(--success-bg);
1135
+ border: 1px solid var(--success-line);
1136
+ border-radius: var(--r);
1137
+ }
1138
+
1139
+ .eject__body .ui-checkbox {
1140
+ margin-top: 4px;
1141
+ padding-top: 10px;
1142
+ border-top: 1px solid var(--line-2);
1143
+ }
1144
+
1145
+ .eject__foot {
1146
+ display: flex;
1147
+ align-items: center;
1148
+ justify-content: flex-end;
1149
+ gap: 8px;
1150
+ padding: 12px 18px;
1151
+ border-top: 1px solid var(--line-2);
1152
+ }
1153
+
1154
+ /* GitHub delivery settings use the same calm, three-part modal anatomy as the
1155
+ eject plan. Secrets stay write-only and the form remains scannable. */
1156
+ .github-settings__panel {
1157
+ display: grid;
1158
+ grid-template-rows: auto minmax(0, 1fr) auto;
1159
+ width: min(720px, 100%);
1160
+ max-height: min(760px, 100%);
1161
+ background: var(--surface);
1162
+ border: 1px solid var(--line);
1163
+ border-radius: var(--r-lg);
1164
+ box-shadow: 0 24px 64px rgb(16 24 40 / 24%);
1165
+ overflow: hidden;
1166
+ }
1167
+
1168
+ .github-settings__head,
1169
+ .github-settings__foot {
1170
+ display: flex;
1171
+ align-items: center;
1172
+ gap: 8px;
1173
+ padding: 14px 18px;
1174
+ }
1175
+
1176
+ .github-settings__head {
1177
+ border-bottom: 1px solid var(--line-2);
1178
+ }
1179
+
1180
+ .github-settings__title {
1181
+ display: inline-flex;
1182
+ align-items: center;
1183
+ gap: 8px;
1184
+ font-size: var(--text-lg);
1185
+ font-weight: 600;
1186
+ }
1187
+
1188
+ .github-settings__body {
1189
+ display: grid;
1190
+ gap: 14px;
1191
+ min-height: 0;
1192
+ padding: 18px;
1193
+ overflow-y: auto;
1194
+ }
1195
+
1196
+ .github-settings__body > .ui-help {
1197
+ margin: 0;
1198
+ }
1199
+
1200
+ .github-settings__foot {
1201
+ justify-content: flex-end;
1202
+ border-top: 1px solid var(--line-2);
1203
+ }
1204
+
1205
+ /* Session picker */
1206
+ .sessions {
1207
+ display: grid;
1208
+ grid-template-columns: minmax(0, 1fr);
1209
+ gap: 18px;
1210
+ width: min(calc(var(--sidebar-w) * 5), 100%);
1211
+ margin: 0 auto;
1212
+ padding: 8px 0 24px;
1213
+ }
1214
+
1215
+ .sessions > * {
1216
+ min-width: 0;
1217
+ }
1218
+
1219
+ .sessions__actions {
1220
+ display: flex;
1221
+ flex-wrap: wrap;
1222
+ gap: 0.5em;
1223
+ }
1224
+
1225
+ .sessions__head {
1226
+ display: grid;
1227
+ gap: 6px;
1228
+ }
1229
+
1230
+ .sessions__head h1 {
1231
+ margin: 0;
1232
+ font-size: var(--text-2xl);
1233
+ letter-spacing: -0.01em;
1234
+ }
1235
+
1236
+ .sessions__head p {
1237
+ margin: 0;
1238
+ font-size: var(--text-md);
1239
+ color: var(--ink-3);
1240
+ }
1241
+
1242
+ .sessions__start {
1243
+ background: var(--surface);
1244
+ border: 1px solid var(--line);
1245
+ border-radius: var(--r-lg);
1246
+ box-shadow: 0 1px 2px rgb(16 24 40 / 5%);
1247
+ }
1248
+
1249
+ .sessions__brief {
1250
+ min-height: 108px;
1251
+ font-size: var(--text-base);
1252
+ line-height: 1.55;
1253
+ resize: vertical;
1254
+ }
1255
+
1256
+ .sessions__composer {
1257
+ display: grid;
1258
+ gap: 10px;
1259
+ padding: 16px;
1260
+ }
1261
+
1262
+ .sessions__composer-row {
1263
+ display: flex;
1264
+ align-items: center;
1265
+ gap: 12px;
1266
+ }
1267
+
1268
+ .sessions__blocked {
1269
+ display: grid;
1270
+ gap: 6px;
1271
+ margin: 12px 0 0;
1272
+ padding: 0;
1273
+ list-style: none;
1274
+ }
1275
+
1276
+ .sessions__blocked li {
1277
+ display: flex;
1278
+ flex-wrap: wrap;
1279
+ align-items: center;
1280
+ gap: 8px;
1281
+ font-size: var(--text-sm);
1282
+ color: var(--ink-3);
1283
+ }
1284
+
1285
+ .sessions__team {
1286
+ margin: 10px 0 0;
1287
+ }
1288
+
1289
+ .sessions__grid {
1290
+ display: grid;
1291
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
1292
+ gap: 12px;
1293
+ }
1294
+
1295
+ .sessions__toolbar {
1296
+ display: flex;
1297
+ align-items: center;
1298
+ justify-content: space-between;
1299
+ gap: 8px;
1300
+ }
1301
+
1302
+ /* The card opens the session; its actions sit in a corner of their own so a
1303
+ click on the menu never opens the session by accident. */
1304
+ .session-card {
1305
+ position: relative;
1306
+ background: var(--surface);
1307
+ border: 1px solid var(--line);
1308
+ border-radius: var(--r-lg);
1309
+ transition:
1310
+ border-color var(--t-base) var(--ease),
1311
+ box-shadow var(--t-base) var(--ease),
1312
+ transform var(--t-base) var(--ease);
1313
+ }
1314
+
1315
+ .session-card:hover {
1316
+ border-color: var(--primary-soft);
1317
+ box-shadow: 0 8px 20px rgb(16 24 40 / 8%);
1318
+ transform: translateY(-1px);
1319
+ }
1320
+
1321
+ .session-card--archived {
1322
+ opacity: 0.72;
1323
+ }
1324
+
1325
+ .session-card__open {
1326
+ display: grid;
1327
+ align-content: start;
1328
+ gap: 10px;
1329
+ width: 100%;
1330
+ padding: 16px 44px 16px 16px;
1331
+ font: inherit;
1332
+ color: inherit;
1333
+ text-align: left;
1334
+ background: transparent;
1335
+ border: 0;
1336
+ border-radius: var(--r-lg);
1337
+ cursor: pointer;
1338
+ }
1339
+
1340
+ .session-card__actions {
1341
+ position: absolute;
1342
+ top: 10px;
1343
+ right: 10px;
1344
+ }
1345
+
1346
+ .session-card__menu {
1347
+ display: grid;
1348
+ width: 28px;
1349
+ height: 28px;
1350
+ place-items: center;
1351
+ color: var(--ink-3);
1352
+ background: transparent;
1353
+ border: 1px solid transparent;
1354
+ border-radius: var(--r);
1355
+ cursor: pointer;
1356
+ }
1357
+
1358
+ .session-card__menu:hover,
1359
+ .session-card__menu[aria-expanded='true'] {
1360
+ color: var(--ink);
1361
+ background: var(--surface-2);
1362
+ border-color: var(--line);
1363
+ }
1364
+
1365
+ .session-card__panel {
1366
+ position: absolute;
1367
+ top: calc(100% + 4px);
1368
+ right: 0;
1369
+ z-index: 30;
1370
+ width: 260px;
1371
+ }
1372
+
1373
+ .session-card__confirm {
1374
+ margin: 0;
1375
+ padding: 6px 8px 10px;
1376
+ }
1377
+
1378
+ .session-card__title {
1379
+ display: grid;
1380
+ gap: 2px;
1381
+ min-width: 0;
1382
+ }
1383
+
1384
+ .session-card__title b {
1385
+ font-size: var(--text-base);
1386
+ font-weight: 600;
1387
+ overflow-wrap: anywhere;
1388
+ }
1389
+
1390
+ .session-card__title small {
1391
+ font-family: var(--font-mono);
1392
+ font-size: var(--text-sm);
1393
+ color: var(--ink-3);
1394
+ overflow-wrap: anywhere;
1395
+ }
1396
+
1397
+ .session-card__meta {
1398
+ display: flex;
1399
+ flex-wrap: wrap;
1400
+ align-items: center;
1401
+ gap: 8px;
1402
+ }
1403
+
1404
+ .session-card__meta small {
1405
+ font-size: var(--text-sm);
1406
+ color: var(--ink-3);
1407
+ }
1408
+
1409
+ .session-card__foot {
1410
+ display: flex;
1411
+ align-items: center;
1412
+ justify-content: space-between;
1413
+ gap: 8px;
1414
+ font-size: var(--text-sm);
1415
+ color: var(--ink-3);
1416
+ }
1417
+
1418
+ /* Diff */
1419
+ .diff {
1420
+ display: grid;
1421
+ gap: 12px;
1422
+ min-width: 0;
1423
+ }
1424
+
1425
+ .diff__group {
1426
+ display: grid;
1427
+ gap: 12px;
1428
+ min-width: 0;
1429
+ }
1430
+
1431
+ /* One header per module, so a session that spans modules reads as grouped
1432
+ files instead of one long list. */
1433
+ .diff__module {
1434
+ display: flex;
1435
+ align-items: baseline;
1436
+ gap: 8px;
1437
+ padding: 0 2px;
1438
+ }
1439
+
1440
+ .diff__file {
1441
+ min-width: 0;
1442
+ background: var(--surface);
1443
+ border: 1px solid var(--line);
1444
+ border-radius: var(--r-md);
1445
+ overflow: hidden;
1446
+ }
1447
+
1448
+ .diff__file-head {
1449
+ display: flex;
1450
+ flex-wrap: wrap;
1451
+ align-items: center;
1452
+ gap: 8px;
1453
+ padding: 8px 12px;
1454
+ background: var(--surface-2);
1455
+ border-bottom: 1px solid var(--line);
1456
+ }
1457
+
1458
+ .diff__path {
1459
+ font-family: var(--font-mono);
1460
+ font-size: var(--text-sm);
1461
+ overflow-wrap: anywhere;
1462
+ }
1463
+
1464
+ .diff__stat {
1465
+ font-family: var(--font-mono);
1466
+ font-size: var(--text-xs);
1467
+ }
1468
+
1469
+ .diff__stat--add {
1470
+ color: var(--success);
1471
+ }
1472
+
1473
+ .diff__stat--del {
1474
+ color: var(--danger);
1475
+ }
1476
+
1477
+ .diff__hunk {
1478
+ overflow-x: auto;
1479
+ border-top: 1px solid var(--line-2);
1480
+ }
1481
+
1482
+ .diff__hunk-header {
1483
+ padding: 4px 12px;
1484
+ font-family: var(--font-mono);
1485
+ font-size: var(--text-xs);
1486
+ color: var(--ink-3);
1487
+ background: var(--surface-2);
1488
+ }
1489
+
1490
+ .diff__line {
1491
+ display: grid;
1492
+ grid-template-columns: 48px 1fr;
1493
+ font-family: var(--font-mono);
1494
+ font-size: var(--text-sm);
1495
+ line-height: 1.5;
1496
+ white-space: pre;
1497
+ }
1498
+
1499
+ .diff__line--add {
1500
+ background: var(--success-bg);
1501
+ }
1502
+
1503
+ .diff__line--del {
1504
+ background: var(--danger-bg);
1505
+ }
1506
+
1507
+ .diff__gutter {
1508
+ padding: 0 8px;
1509
+ color: var(--ink-4);
1510
+ text-align: right;
1511
+ user-select: none;
1512
+ }
1513
+
1514
+ .diff__code {
1515
+ padding: 0 12px;
1516
+ overflow-wrap: normal;
1517
+ }
1518
+
1519
+ /* Syntax tokens, tuned for both diff backgrounds. */
1520
+ .tok-keyword {
1521
+ color: var(--primary-hover);
1522
+ font-weight: 500;
1523
+ }
1524
+
1525
+ .tok-string {
1526
+ color: var(--success);
1527
+ }
1528
+
1529
+ .tok-comment {
1530
+ color: var(--ink-4);
1531
+ font-style: italic;
1532
+ }
1533
+
1534
+ .tok-number {
1535
+ color: var(--warning);
1536
+ }
1537
+
1538
+ .tok-type {
1539
+ color: var(--brand-weft);
1540
+ }
1541
+
1542
+ /* Connect screen */
1543
+ .connect {
1544
+ display: grid;
1545
+ place-items: center;
1546
+ min-height: 100vh;
1547
+ padding: 32px 16px;
1548
+ background: var(--bg);
1549
+ }
1550
+
1551
+ .connect__card {
1552
+ width: min(560px, 100%);
1553
+ background: var(--surface);
1554
+ border: 1px solid var(--line);
1555
+ border-radius: var(--r-lg);
1556
+ }
1557
+
1558
+ .connect__head {
1559
+ display: grid;
1560
+ gap: 6px;
1561
+ padding: 22px 24px 0;
1562
+ }
1563
+
1564
+ .connect__head h1 {
1565
+ margin: 0;
1566
+ font-size: var(--text-2xl);
1567
+ }
1568
+
1569
+ .connect__head p {
1570
+ margin: 0;
1571
+ font-size: var(--text-md);
1572
+ color: var(--ink-3);
1573
+ }