@clef-sh/ui 0.1.20 → 0.1.21

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 (103) hide show
  1. package/dist/client/assets/index-DPWHjBbB.js +34 -0
  2. package/dist/client/assets/index-qsLTYpc9.css +2 -0
  3. package/dist/client/clef.svg +2 -0
  4. package/dist/client/index.html +3 -31
  5. package/dist/client-lib/components/Button.d.ts +1 -1
  6. package/dist/client-lib/components/Button.d.ts.map +1 -1
  7. package/dist/client-lib/components/CopyButton.d.ts.map +1 -1
  8. package/dist/client-lib/components/EnvBadge.d.ts.map +1 -1
  9. package/dist/client-lib/components/MatrixGrid.d.ts.map +1 -1
  10. package/dist/client-lib/components/Sidebar.d.ts +1 -1
  11. package/dist/client-lib/components/Sidebar.d.ts.map +1 -1
  12. package/dist/client-lib/components/StatusDot.d.ts.map +1 -1
  13. package/dist/client-lib/components/SyncPanel.d.ts.map +1 -1
  14. package/dist/client-lib/components/TopBar.d.ts +6 -0
  15. package/dist/client-lib/components/TopBar.d.ts.map +1 -1
  16. package/dist/client-lib/primitives/Badge.d.ts +11 -0
  17. package/dist/client-lib/primitives/Badge.d.ts.map +1 -0
  18. package/dist/client-lib/primitives/Card.d.ts +28 -0
  19. package/dist/client-lib/primitives/Card.d.ts.map +1 -0
  20. package/dist/client-lib/primitives/Dialog.d.ts +30 -0
  21. package/dist/client-lib/primitives/Dialog.d.ts.map +1 -0
  22. package/dist/client-lib/primitives/EmptyState.d.ts +10 -0
  23. package/dist/client-lib/primitives/EmptyState.d.ts.map +1 -0
  24. package/dist/client-lib/primitives/Field.d.ts +36 -0
  25. package/dist/client-lib/primitives/Field.d.ts.map +1 -0
  26. package/dist/client-lib/primitives/Input.d.ts +6 -0
  27. package/dist/client-lib/primitives/Input.d.ts.map +1 -0
  28. package/dist/client-lib/primitives/Stat.d.ts +11 -0
  29. package/dist/client-lib/primitives/Stat.d.ts.map +1 -0
  30. package/dist/client-lib/primitives/Table.d.ts +37 -0
  31. package/dist/client-lib/primitives/Table.d.ts.map +1 -0
  32. package/dist/client-lib/primitives/Tabs.d.ts +29 -0
  33. package/dist/client-lib/primitives/Tabs.d.ts.map +1 -0
  34. package/dist/client-lib/primitives/Toast.d.ts +16 -0
  35. package/dist/client-lib/primitives/Toast.d.ts.map +1 -0
  36. package/dist/client-lib/primitives/Toolbar.d.ts +29 -0
  37. package/dist/client-lib/primitives/Toolbar.d.ts.map +1 -0
  38. package/dist/client-lib/primitives/index.d.ts +23 -0
  39. package/dist/client-lib/primitives/index.d.ts.map +1 -0
  40. package/dist/client-lib/theme.d.ts +18 -41
  41. package/dist/client-lib/theme.d.ts.map +1 -1
  42. package/dist/server/api.d.ts.map +1 -1
  43. package/dist/server/api.js +215 -0
  44. package/dist/server/api.js.map +1 -1
  45. package/dist/server/envelope.d.ts +15 -0
  46. package/dist/server/envelope.d.ts.map +1 -0
  47. package/dist/server/envelope.js +310 -0
  48. package/dist/server/envelope.js.map +1 -0
  49. package/package.json +7 -2
  50. package/src/client/App.tsx +16 -41
  51. package/src/client/components/Button.tsx +13 -22
  52. package/src/client/components/CopyButton.tsx +5 -12
  53. package/src/client/components/EnvBadge.tsx +30 -15
  54. package/src/client/components/MatrixGrid.tsx +108 -252
  55. package/src/client/components/Sidebar.tsx +123 -199
  56. package/src/client/components/StatusDot.tsx +10 -15
  57. package/src/client/components/SyncPanel.tsx +14 -62
  58. package/src/client/components/TopBar.tsx +11 -36
  59. package/src/client/index.html +1 -30
  60. package/src/client/main.tsx +1 -0
  61. package/src/client/primitives/Badge.test.tsx +47 -0
  62. package/src/client/primitives/Badge.tsx +64 -0
  63. package/src/client/primitives/Card.test.tsx +50 -0
  64. package/src/client/primitives/Card.tsx +85 -0
  65. package/src/client/primitives/Dialog.test.tsx +55 -0
  66. package/src/client/primitives/Dialog.tsx +96 -0
  67. package/src/client/primitives/EmptyState.test.tsx +25 -0
  68. package/src/client/primitives/EmptyState.tsx +38 -0
  69. package/src/client/primitives/Field.test.tsx +46 -0
  70. package/src/client/primitives/Field.tsx +95 -0
  71. package/src/client/primitives/Input.tsx +26 -0
  72. package/src/client/primitives/Stat.test.tsx +32 -0
  73. package/src/client/primitives/Stat.tsx +52 -0
  74. package/src/client/primitives/Table.test.tsx +58 -0
  75. package/src/client/primitives/Table.tsx +113 -0
  76. package/src/client/primitives/Tabs.test.tsx +44 -0
  77. package/src/client/primitives/Tabs.tsx +100 -0
  78. package/src/client/primitives/Toast.test.tsx +77 -0
  79. package/src/client/primitives/Toast.tsx +89 -0
  80. package/src/client/primitives/Toolbar.test.tsx +50 -0
  81. package/src/client/primitives/Toolbar.tsx +86 -0
  82. package/src/client/primitives/index.ts +43 -0
  83. package/src/client/public/clef.svg +2 -0
  84. package/src/client/screens/BackendScreen.tsx +104 -363
  85. package/src/client/screens/DiffView.tsx +187 -378
  86. package/src/client/screens/EnvelopeScreen.test.tsx +542 -0
  87. package/src/client/screens/EnvelopeScreen.tsx +948 -0
  88. package/src/client/screens/GitLogView.tsx +48 -106
  89. package/src/client/screens/ImportScreen.tsx +105 -308
  90. package/src/client/screens/LintView.tsx +184 -379
  91. package/src/client/screens/ManifestScreen.tsx +283 -445
  92. package/src/client/screens/MatrixView.tsx +75 -91
  93. package/src/client/screens/NamespaceEditor.tsx +234 -609
  94. package/src/client/screens/PolicyView.tsx +183 -453
  95. package/src/client/screens/RecipientsScreen.tsx +71 -350
  96. package/src/client/screens/ResetScreen.tsx +67 -237
  97. package/src/client/screens/ScanScreen.tsx +85 -249
  98. package/src/client/screens/SchemaEditor.test.tsx +237 -0
  99. package/src/client/screens/SchemaEditor.tsx +435 -0
  100. package/src/client/screens/ServiceIdentitiesScreen.tsx +251 -788
  101. package/src/client/styles.css +77 -0
  102. package/src/client/theme.ts +27 -48
  103. package/dist/client/assets/index-Db6WgHgY.js +0 -38
@@ -1,8 +1,7 @@
1
1
  import React, { useState, useEffect } from "react";
2
- import { theme } from "../theme";
3
2
  import { apiFetch } from "../api";
4
- import { TopBar } from "../components/TopBar";
5
3
  import { Button } from "../components/Button";
4
+ import { Toolbar } from "../primitives";
6
5
  import type {
7
6
  ClefManifest,
8
7
  BackendType,
@@ -57,22 +56,26 @@ const KEY_PLACEHOLDERS: Record<string, string> = {
57
56
  };
58
57
 
59
58
  const ALL_BACKENDS: BackendType[] = ["age", "awskms", "gcpkms", "azurekv", "pgp", "hsm"];
59
+
60
+ const TEXT_INPUT_BASE =
61
+ "w-full box-border rounded-md border border-edge bg-ink-850 px-3 py-2 font-mono text-[12px] text-bone outline-none focus-visible:border-gold-500 placeholder:text-ash-dim";
62
+
63
+ const SELECT_CLASSES =
64
+ "w-full rounded-md border border-edge bg-ink-850 px-2.5 py-1.5 font-sans text-[13px] text-bone cursor-pointer outline-none focus-visible:border-gold-500";
65
+
60
66
  export function BackendScreen({ manifest, setView, reloadManifest }: BackendScreenProps) {
61
67
  const [step, setStep] = useState<1 | 2 | 3 | 4>(1);
62
68
  const [config, setConfig] = useState<BackendConfigResponse | null>(null);
63
69
 
64
- // Step 1 state
65
70
  const [targetBackend, setTargetBackend] = useState<BackendType>("age");
66
71
  const [targetKey, setTargetKey] = useState("");
67
72
  const [scope, setScope] = useState<"all" | "single">("all");
68
73
  const [selectedEnv, setSelectedEnv] = useState("");
69
74
 
70
- // Step 2 state
71
75
  const [previewResult, setPreviewResult] = useState<MigrationResponse | null>(null);
72
76
  const [needsConfirmation, setNeedsConfirmation] = useState(false);
73
77
  const [confirmed, setConfirmed] = useState(false);
74
78
 
75
- // Step 3/4 state
76
79
  const [applyResult, setApplyResult] = useState<MigrationResponse | null>(null);
77
80
 
78
81
  const [loading, setLoading] = useState(false);
@@ -197,61 +200,43 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
197
200
  .length ?? 0;
198
201
 
199
202
  return (
200
- <div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }}>
201
- <TopBar title="Backend" subtitle="clef migrate-backend — change encryption backend" />
203
+ <div className="flex flex-1 flex-col overflow-hidden">
204
+ <Toolbar>
205
+ <div>
206
+ <Toolbar.Title>Backend</Toolbar.Title>
207
+ <Toolbar.Subtitle>clef migrate-backend — change encryption backend</Toolbar.Subtitle>
208
+ </div>
209
+ </Toolbar>
202
210
 
203
- <div style={{ flex: 1, overflow: "auto", padding: 24 }}>
204
- <div style={{ maxWidth: 620, margin: "0 auto" }}>
211
+ <div className="flex-1 overflow-auto p-6">
212
+ <div className="mx-auto max-w-[620px]">
205
213
  {/* Step indicator */}
206
- <div
207
- style={{
208
- display: "flex",
209
- alignItems: "center",
210
- gap: 0,
211
- marginBottom: 32,
212
- }}
213
- >
214
+ <div className="mb-8 flex items-center">
214
215
  {([1, 2, 3, 4] as const).map((s, i) => (
215
216
  <React.Fragment key={s}>
216
- <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
217
+ <div className="flex items-center gap-2">
217
218
  <div
218
- style={{
219
- width: 24,
220
- height: 24,
221
- borderRadius: "50%",
222
- background: step >= s ? theme.accent : theme.surface,
223
- border: `1px solid ${step >= s ? theme.accent : theme.border}`,
224
- display: "flex",
225
- alignItems: "center",
226
- justifyContent: "center",
227
- fontFamily: theme.mono,
228
- fontSize: 11,
229
- fontWeight: 700,
230
- color: step >= s ? "#000" : theme.textDim,
231
- }}
219
+ className={`flex h-6 w-6 items-center justify-center rounded-full font-mono text-[11px] font-bold ${
220
+ step >= s
221
+ ? "bg-gold-500 border border-gold-500 text-ink-950"
222
+ : "bg-ink-850 border border-edge text-ash-dim"
223
+ }`}
232
224
  >
233
225
  {s}
234
226
  </div>
235
227
  <span
236
- style={{
237
- fontFamily: theme.sans,
238
- fontSize: 12,
239
- color: step >= s ? theme.text : theme.textDim,
240
- fontWeight: step === s ? 600 : 400,
241
- }}
228
+ className={`font-sans text-[12px] ${
229
+ step >= s ? "text-bone" : "text-ash-dim"
230
+ } ${step === s ? "font-semibold" : "font-normal"}`}
242
231
  >
243
232
  {s === 1 ? "Configure" : s === 2 ? "Preview" : s === 3 ? "Migrate" : "Done"}
244
233
  </span>
245
234
  </div>
246
235
  {i < 3 && (
247
236
  <div
248
- style={{
249
- flex: 1,
250
- height: 1,
251
- background: step > s ? theme.accent : theme.border,
252
- margin: "0 12px",
253
- minWidth: 20,
254
- }}
237
+ className={`mx-3 h-px min-w-[20px] flex-1 ${
238
+ step > s ? "bg-gold-500" : "bg-edge"
239
+ }`}
255
240
  />
256
241
  )}
257
242
  </React.Fragment>
@@ -259,18 +244,7 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
259
244
  </div>
260
245
 
261
246
  {error && (
262
- <div
263
- style={{
264
- background: theme.redDim,
265
- border: `1px solid ${theme.red}44`,
266
- borderRadius: 8,
267
- padding: "12px 16px",
268
- marginBottom: 16,
269
- fontFamily: theme.sans,
270
- fontSize: 13,
271
- color: theme.red,
272
- }}
273
- >
247
+ <div className="mb-4 rounded-lg border border-stop-500/30 bg-stop-500/10 px-4 py-3 font-sans text-[13px] text-stop-500">
274
248
  {error}
275
249
  </div>
276
250
  )}
@@ -278,50 +252,27 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
278
252
  {/* ── Step 1: Configure ─────────────────────────────────────── */}
279
253
  {step === 1 && (
280
254
  <div>
281
- {/* Current config */}
282
255
  {config && (
283
- <div style={{ marginBottom: 24 }}>
256
+ <div className="mb-6">
284
257
  <Label>Current Configuration</Label>
285
- <div
286
- style={{
287
- background: theme.surface,
288
- border: `1px solid ${theme.border}`,
289
- borderRadius: 8,
290
- padding: 14,
291
- }}
292
- >
293
- <div
294
- style={{
295
- fontFamily: theme.mono,
296
- fontSize: 12,
297
- color: theme.text,
298
- marginBottom: 8,
299
- }}
300
- >
258
+ <div className="rounded-lg border border-edge bg-ink-850 p-3.5">
259
+ <div className="mb-2 font-mono text-[12px] text-bone">
301
260
  Default backend:{" "}
302
- <span style={{ color: theme.accent, fontWeight: 600 }}>
261
+ <span className="font-semibold text-gold-500">
303
262
  {BACKEND_LABELS[config.global.default_backend]}
304
263
  </span>
305
264
  </div>
306
265
  {config.environments.map((env) => (
307
266
  <div
308
267
  key={env.name}
309
- style={{
310
- display: "flex",
311
- alignItems: "center",
312
- gap: 8,
313
- fontFamily: theme.mono,
314
- fontSize: 11,
315
- color: theme.textMuted,
316
- marginBottom: 2,
317
- }}
268
+ className="mb-px flex items-center gap-2 font-mono text-[11px] text-ash"
318
269
  >
319
270
  <span>
320
- {env.protected ? "\uD83D\uDD12 " : ""}
271
+ {env.protected ? "🔒 " : ""}
321
272
  {env.name}
322
273
  </span>
323
- <span style={{ color: theme.textDim }}>{"\u2192"}</span>
324
- <span style={{ color: env.hasOverride ? theme.yellow : theme.textMuted }}>
274
+ <span className="text-ash-dim">→</span>
275
+ <span className={env.hasOverride ? "text-warn-500" : "text-ash"}>
325
276
  {BACKEND_LABELS[env.effective.backend]}
326
277
  {env.hasOverride ? " (override)" : ""}
327
278
  </span>
@@ -331,22 +282,15 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
331
282
  </div>
332
283
  )}
333
284
 
334
- {/* Target backend */}
335
- <div style={{ marginBottom: 20 }}>
285
+ <div className="mb-5">
336
286
  <Label>Target Backend</Label>
337
- <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
287
+ <div className="flex flex-col gap-1.5">
338
288
  {ALL_BACKENDS.map((b) => (
339
289
  <label
340
290
  key={b}
341
- style={{
342
- display: "flex",
343
- alignItems: "center",
344
- gap: 8,
345
- cursor: "pointer",
346
- fontFamily: theme.sans,
347
- fontSize: 13,
348
- color: targetBackend === b ? theme.text : theme.textMuted,
349
- }}
291
+ className={`flex cursor-pointer items-center gap-2 font-sans text-[13px] ${
292
+ targetBackend === b ? "text-bone" : "text-ash"
293
+ }`}
350
294
  >
351
295
  <input
352
296
  type="radio"
@@ -357,7 +301,7 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
357
301
  setTargetBackend(b);
358
302
  setTargetKey("");
359
303
  }}
360
- style={{ accentColor: theme.accent }}
304
+ className="accent-gold-500"
361
305
  data-testid={`backend-radio-${b}`}
362
306
  />
363
307
  {BACKEND_LABELS[b]}
@@ -366,9 +310,8 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
366
310
  </div>
367
311
  </div>
368
312
 
369
- {/* Key input (non-age) */}
370
313
  {targetBackend !== "age" && (
371
- <div style={{ marginBottom: 20 }}>
314
+ <div className="mb-5">
372
315
  <Label>Key Identifier</Label>
373
316
  <input
374
317
  type="text"
@@ -376,63 +319,39 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
376
319
  onChange={(e) => setTargetKey(e.target.value)}
377
320
  placeholder={KEY_PLACEHOLDERS[targetBackend]}
378
321
  data-testid="backend-key-input"
379
- style={{
380
- width: "100%",
381
- background: theme.surface,
382
- border: `1px solid ${theme.border}`,
383
- borderRadius: 6,
384
- padding: "8px 12px",
385
- fontFamily: theme.mono,
386
- fontSize: 12,
387
- color: theme.text,
388
- outline: "none",
389
- boxSizing: "border-box",
390
- }}
322
+ className={TEXT_INPUT_BASE}
391
323
  />
392
324
  </div>
393
325
  )}
394
326
 
395
- {/* Scope */}
396
- <div style={{ marginBottom: 24 }}>
327
+ <div className="mb-6">
397
328
  <Label>Scope</Label>
398
- <div style={{ display: "flex", gap: 16, marginBottom: 8 }}>
329
+ <div className="mb-2 flex gap-4">
399
330
  <label
400
- style={{
401
- display: "flex",
402
- alignItems: "center",
403
- gap: 6,
404
- cursor: "pointer",
405
- fontFamily: theme.sans,
406
- fontSize: 13,
407
- color: scope === "all" ? theme.text : theme.textMuted,
408
- }}
331
+ className={`flex cursor-pointer items-center gap-1.5 font-sans text-[13px] ${
332
+ scope === "all" ? "text-bone" : "text-ash"
333
+ }`}
409
334
  >
410
335
  <input
411
336
  type="radio"
412
337
  name="scope"
413
338
  checked={scope === "all"}
414
339
  onChange={() => setScope("all")}
415
- style={{ accentColor: theme.accent }}
340
+ className="accent-gold-500"
416
341
  />
417
342
  All environments
418
343
  </label>
419
344
  <label
420
- style={{
421
- display: "flex",
422
- alignItems: "center",
423
- gap: 6,
424
- cursor: "pointer",
425
- fontFamily: theme.sans,
426
- fontSize: 13,
427
- color: scope === "single" ? theme.text : theme.textMuted,
428
- }}
345
+ className={`flex cursor-pointer items-center gap-1.5 font-sans text-[13px] ${
346
+ scope === "single" ? "text-bone" : "text-ash"
347
+ }`}
429
348
  >
430
349
  <input
431
350
  type="radio"
432
351
  name="scope"
433
352
  checked={scope === "single"}
434
353
  onChange={() => setScope("single")}
435
- style={{ accentColor: theme.accent }}
354
+ className="accent-gold-500"
436
355
  />
437
356
  Single environment
438
357
  </label>
@@ -442,18 +361,7 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
442
361
  value={selectedEnv}
443
362
  onChange={(e) => setSelectedEnv(e.target.value)}
444
363
  data-testid="env-select"
445
- style={{
446
- width: "100%",
447
- background: theme.surface,
448
- border: `1px solid ${theme.border}`,
449
- borderRadius: 6,
450
- padding: "7px 10px",
451
- fontFamily: theme.sans,
452
- fontSize: 13,
453
- color: theme.text,
454
- outline: "none",
455
- cursor: "pointer",
456
- }}
364
+ className={SELECT_CLASSES}
457
365
  >
458
366
  {environments.map((env) => (
459
367
  <option key={env.name} value={env.name}>
@@ -465,43 +373,17 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
465
373
  )}
466
374
  </div>
467
375
 
468
- {/* Protected env confirmation */}
469
376
  {needsConfirmation && (
470
- <div
471
- style={{
472
- background: theme.yellowDim,
473
- border: `1px solid ${theme.yellow}44`,
474
- borderRadius: 8,
475
- padding: "12px 16px",
476
- marginBottom: 16,
477
- }}
478
- >
479
- <div
480
- style={{
481
- fontFamily: theme.sans,
482
- fontSize: 13,
483
- color: theme.yellow,
484
- marginBottom: 8,
485
- }}
486
- >
377
+ <div className="mb-4 rounded-lg border border-warn-500/30 bg-warn-500/10 px-4 py-3">
378
+ <div className="mb-2 font-sans text-[13px] text-warn-500">
487
379
  This migration affects protected environments.
488
380
  </div>
489
- <label
490
- style={{
491
- display: "flex",
492
- alignItems: "center",
493
- gap: 8,
494
- cursor: "pointer",
495
- fontFamily: theme.sans,
496
- fontSize: 12,
497
- color: theme.text,
498
- }}
499
- >
381
+ <label className="flex cursor-pointer items-center gap-2 font-sans text-[12px] text-bone">
500
382
  <input
501
383
  type="checkbox"
502
384
  checked={confirmed}
503
385
  onChange={(e) => setConfirmed(e.target.checked)}
504
- style={{ accentColor: theme.yellow }}
386
+ className="accent-warn-500"
505
387
  data-testid="protected-confirm"
506
388
  />
507
389
  I understand and want to proceed
@@ -526,76 +408,51 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
526
408
  {/* ── Step 2: Preview ───────────────────────────────────────── */}
527
409
  {step === 2 && previewResult && (
528
410
  <div>
529
- <div
530
- style={{
531
- fontFamily: theme.sans,
532
- fontSize: 13,
533
- color: theme.textMuted,
534
- marginBottom: 20,
535
- }}
536
- >
411
+ <div className="mb-5 font-sans text-[13px] text-ash">
537
412
  Migrating to{" "}
538
- <span style={{ color: theme.accent, fontWeight: 600 }}>
539
- {BACKEND_LABELS[targetBackend]}
540
- </span>
413
+ <span className="font-semibold text-gold-500">{BACKEND_LABELS[targetBackend]}</span>
541
414
  {scope === "single" ? ` (${selectedEnv} only)` : " (all environments)"}
542
415
  </div>
543
416
 
544
- {/* Events / files to migrate */}
545
417
  {previewResult.events.filter((e) => e.type === "info").length > 0 && (
546
- <div style={{ marginBottom: 16 }}>
547
- <SectionLabel color={theme.green}>
418
+ <div className="mb-4">
419
+ <SectionLabel toneClass="text-go-500">
548
420
  Files to migrate ({previewResult.events.filter((e) => e.type === "info").length}
549
421
  )
550
422
  </SectionLabel>
551
423
  {previewResult.events
552
424
  .filter((e) => e.type === "info")
553
425
  .map((e, i) => (
554
- <FileRow key={i} icon={"\u2192"} iconColor={theme.green} label={e.message} />
426
+ <FileRow key={i} icon="" iconClass="text-go-500" label={e.message} />
555
427
  ))}
556
428
  </div>
557
429
  )}
558
430
 
559
- {/* Skipped files */}
560
431
  {previewResult.events.filter((e) => e.type === "skip").length > 0 && (
561
- <div style={{ marginBottom: 16 }}>
562
- <SectionLabel color={theme.textDim}>
432
+ <div className="mb-4">
433
+ <SectionLabel toneClass="text-ash-dim">
563
434
  Already on target (
564
435
  {previewResult.events.filter((e) => e.type === "skip").length})
565
436
  </SectionLabel>
566
437
  {previewResult.events
567
438
  .filter((e) => e.type === "skip")
568
439
  .map((e, i) => (
569
- <FileRow
570
- key={i}
571
- icon={"\u21B7"}
572
- iconColor={theme.textDim}
573
- label={e.message}
574
- />
440
+ <FileRow key={i} icon="↷" iconClass="text-ash-dim" label={e.message} />
575
441
  ))}
576
442
  </div>
577
443
  )}
578
444
 
579
- {/* Warnings */}
580
445
  {previewResult.result.warnings.length > 0 && (
581
- <div style={{ marginBottom: 16 }}>
446
+ <div className="mb-4">
582
447
  {previewResult.result.warnings.map((w, i) => (
583
- <div
584
- key={i}
585
- style={{
586
- fontFamily: theme.mono,
587
- fontSize: 11,
588
- color: theme.yellow,
589
- marginBottom: 4,
590
- }}
591
- >
592
- {"\u26A0"} {w}
448
+ <div key={i} className="mb-1 font-mono text-[11px] text-warn-500">
449
+ {w}
593
450
  </div>
594
451
  ))}
595
452
  </div>
596
453
  )}
597
454
 
598
- <div style={{ display: "flex", gap: 10, marginTop: 24 }}>
455
+ <div className="mt-6 flex gap-2.5">
599
456
  <Button variant="ghost" onClick={handleReset}>
600
457
  Back
601
458
  </Button>
@@ -613,115 +470,46 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
613
470
 
614
471
  {/* ── Step 3: Executing ─────────────────────────────────────── */}
615
472
  {step === 3 && (
616
- <div
617
- style={{
618
- display: "flex",
619
- flexDirection: "column",
620
- alignItems: "center",
621
- paddingTop: 40,
622
- }}
623
- >
624
- <div
625
- style={{
626
- width: 40,
627
- height: 40,
628
- border: `3px solid ${theme.border}`,
629
- borderTopColor: theme.accent,
630
- borderRadius: "50%",
631
- animation: "spin 1s linear infinite",
632
- marginBottom: 16,
633
- }}
634
- />
635
- <div
636
- style={{
637
- fontFamily: theme.sans,
638
- fontSize: 14,
639
- color: theme.textMuted,
640
- }}
641
- >
473
+ <div className="flex flex-col items-center pt-10">
474
+ <div className="mb-4 h-10 w-10 animate-spin rounded-full border-[3px] border-edge border-t-gold-500" />
475
+ <div className="font-sans text-[14px] text-ash">
642
476
  Migrating... this may take a moment
643
477
  </div>
644
- <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
645
478
  </div>
646
479
  )}
647
480
 
648
481
  {/* ── Step 4: Result ────────────────────────────────────────── */}
649
482
  {step === 4 && applyResult && (
650
483
  <div>
651
- <div
652
- style={{
653
- display: "flex",
654
- flexDirection: "column",
655
- alignItems: "center",
656
- paddingTop: 20,
657
- paddingBottom: 32,
658
- }}
659
- >
484
+ <div className="flex flex-col items-center pt-5 pb-8">
660
485
  <div
661
- style={{
662
- width: 56,
663
- height: 56,
664
- borderRadius: "50%",
665
- background: applyResult.result.rolledBack ? theme.redDim : theme.greenDim,
666
- border: `1px solid ${applyResult.result.rolledBack ? theme.red + "44" : theme.green + "44"}`,
667
- display: "flex",
668
- alignItems: "center",
669
- justifyContent: "center",
670
- fontSize: 24,
671
- color: applyResult.result.rolledBack ? theme.red : theme.green,
672
- marginBottom: 16,
673
- }}
486
+ className={`mb-4 flex h-14 w-14 items-center justify-center rounded-full text-[24px] ${
487
+ applyResult.result.rolledBack
488
+ ? "border border-stop-500/30 bg-stop-500/10 text-stop-500"
489
+ : "border border-go-500/30 bg-go-500/10 text-go-500"
490
+ }`}
674
491
  >
675
- {applyResult.result.rolledBack ? "\u26A0" : "\u2713"}
492
+ {applyResult.result.rolledBack ? "" : ""}
676
493
  </div>
677
-
678
494
  <div
679
- style={{
680
- fontFamily: theme.sans,
681
- fontWeight: 600,
682
- fontSize: 16,
683
- color: applyResult.result.rolledBack ? theme.red : theme.green,
684
- marginBottom: 8,
685
- }}
495
+ className={`mb-2 font-sans text-[16px] font-semibold ${
496
+ applyResult.result.rolledBack ? "text-stop-500" : "text-go-500"
497
+ }`}
686
498
  >
687
499
  {applyResult.result.rolledBack ? "Migration failed" : "Migration complete"}
688
500
  </div>
689
-
690
501
  {applyResult.result.rolledBack && applyResult.result.error && (
691
- <div
692
- style={{
693
- fontFamily: theme.mono,
694
- fontSize: 12,
695
- color: theme.red,
696
- marginBottom: 8,
697
- textAlign: "center",
698
- }}
699
- >
502
+ <div className="mb-2 text-center font-mono text-[12px] text-stop-500">
700
503
  {applyResult.result.error}
701
504
  </div>
702
505
  )}
703
-
704
506
  {applyResult.result.rolledBack && (
705
- <div
706
- style={{
707
- fontFamily: theme.sans,
708
- fontSize: 12,
709
- color: theme.textMuted,
710
- marginBottom: 8,
711
- }}
712
- >
507
+ <div className="mb-2 font-sans text-[12px] text-ash">
713
508
  All changes have been rolled back.
714
509
  </div>
715
510
  )}
716
-
717
511
  {!applyResult.result.rolledBack && (
718
- <div
719
- style={{
720
- fontFamily: theme.mono,
721
- fontSize: 12,
722
- color: theme.textMuted,
723
- }}
724
- >
512
+ <div className="font-mono text-[12px] text-ash">
725
513
  {applyResult.result.migratedFiles.length} migrated,{" "}
726
514
  {applyResult.result.skippedFiles.length} skipped,{" "}
727
515
  {applyResult.result.verifiedFiles.length} verified
@@ -729,44 +517,24 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
729
517
  )}
730
518
  </div>
731
519
 
732
- {/* Warnings */}
733
520
  {applyResult.result.warnings.length > 0 && (
734
- <div style={{ marginBottom: 16 }}>
521
+ <div className="mb-4">
735
522
  {applyResult.result.warnings.map((w, i) => (
736
- <div
737
- key={i}
738
- style={{
739
- fontFamily: theme.mono,
740
- fontSize: 11,
741
- color: theme.yellow,
742
- marginBottom: 4,
743
- }}
744
- >
745
- {"\u26A0"} {w}
523
+ <div key={i} className="mb-1 font-mono text-[11px] text-warn-500">
524
+ {w}
746
525
  </div>
747
526
  ))}
748
527
  </div>
749
528
  )}
750
529
 
751
530
  {!applyResult.result.rolledBack && (
752
- <div
753
- style={{
754
- background: theme.surface,
755
- border: `1px solid ${theme.border}`,
756
- borderRadius: 6,
757
- padding: "10px 14px",
758
- marginBottom: 24,
759
- fontFamily: theme.mono,
760
- fontSize: 11,
761
- color: theme.textMuted,
762
- }}
763
- >
764
- git add clef.yaml .sops.yaml secrets/ && git commit -m "chore: migrate backend to{" "}
765
- {targetBackend}"
531
+ <div className="mb-6 rounded-md border border-edge bg-ink-850 px-3.5 py-2.5 font-mono text-[11px] text-ash">
532
+ git add clef.yaml .sops.yaml secrets/ &amp;&amp; git commit -m "chore: migrate
533
+ backend to {targetBackend}"
766
534
  </div>
767
535
  )}
768
536
 
769
- <div style={{ display: "flex", gap: 10 }}>
537
+ <div className="flex gap-2.5">
770
538
  <Button variant="primary" onClick={() => setView("matrix")}>
771
539
  View in Matrix
772
540
  </Button>
@@ -784,54 +552,27 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
784
552
 
785
553
  function Label({ children }: { children: React.ReactNode }) {
786
554
  return (
787
- <div
788
- style={{
789
- fontFamily: theme.sans,
790
- fontSize: 12,
791
- fontWeight: 600,
792
- color: theme.textMuted,
793
- marginBottom: 8,
794
- letterSpacing: "0.05em",
795
- textTransform: "uppercase",
796
- }}
797
- >
555
+ <div className="mb-2 font-sans text-[12px] font-semibold uppercase tracking-[0.05em] text-ash">
798
556
  {children}
799
557
  </div>
800
558
  );
801
559
  }
802
560
 
803
- function SectionLabel({ children, color }: { children: React.ReactNode; color: string }) {
561
+ function SectionLabel({ children, toneClass }: { children: React.ReactNode; toneClass: string }) {
804
562
  return (
805
563
  <div
806
- style={{
807
- fontFamily: theme.sans,
808
- fontSize: 11,
809
- fontWeight: 600,
810
- color,
811
- letterSpacing: "0.06em",
812
- textTransform: "uppercase",
813
- marginBottom: 8,
814
- }}
564
+ className={`mb-2 font-sans text-[11px] font-semibold uppercase tracking-[0.06em] ${toneClass}`}
815
565
  >
816
566
  {children}
817
567
  </div>
818
568
  );
819
569
  }
820
570
 
821
- function FileRow({ icon, iconColor, label }: { icon: string; iconColor: string; label: string }) {
571
+ function FileRow({ icon, iconClass, label }: { icon: string; iconClass: string; label: string }) {
822
572
  return (
823
- <div
824
- style={{
825
- display: "flex",
826
- alignItems: "center",
827
- gap: 10,
828
- padding: "5px 10px",
829
- borderRadius: 6,
830
- marginBottom: 3,
831
- }}
832
- >
833
- <span style={{ color: iconColor, fontFamily: theme.mono, fontSize: 13 }}>{icon}</span>
834
- <span style={{ fontFamily: theme.mono, fontSize: 12, color: theme.text }}>{label}</span>
573
+ <div className="mb-px flex items-center gap-2.5 rounded-md px-2.5 py-1">
574
+ <span className={`font-mono text-[13px] ${iconClass}`}>{icon}</span>
575
+ <span className="font-mono text-[12px] text-bone">{label}</span>
835
576
  </div>
836
577
  );
837
578
  }