@runwisp/ui 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/package.json +39 -47
  2. package/src/astro/CTABand.astro +19 -12
  3. package/src/astro/Hero.astro +3 -3
  4. package/src/astro/SiteFooter.astro +4 -4
  5. package/src/astro/SiteHeader.astro +4 -4
  6. package/src/lib/actions/portal.ts +0 -3
  7. package/src/lib/assets/runwisp-logo.svg +1 -8
  8. package/src/lib/components/AccordionItem.svelte +4 -10
  9. package/src/lib/components/Alert.svelte +11 -11
  10. package/src/lib/components/Avatar.svelte +1 -1
  11. package/src/lib/components/Badge.svelte +13 -13
  12. package/src/lib/components/Breadcrumb.svelte +3 -3
  13. package/src/lib/components/Button.svelte +2 -2
  14. package/src/lib/components/Card.svelte +4 -4
  15. package/src/lib/components/Checkbox.svelte +12 -10
  16. package/src/lib/components/CodeBlock.svelte +7 -13
  17. package/src/lib/components/CronInput.svelte +12 -15
  18. package/src/lib/components/DataGrid.svelte +14 -13
  19. package/src/lib/components/Drawer.svelte +9 -9
  20. package/src/lib/components/Dropdown.svelte +12 -20
  21. package/src/lib/components/DurationInput.svelte +10 -14
  22. package/src/lib/components/EmptyState.svelte +1 -1
  23. package/src/lib/components/ErrorState.svelte +4 -4
  24. package/src/lib/components/FeatureCard.svelte +4 -4
  25. package/src/lib/components/FormField.svelte +4 -6
  26. package/src/lib/components/Heading.svelte +4 -1
  27. package/src/lib/components/Input.svelte +10 -12
  28. package/src/lib/components/Kbd.svelte +1 -1
  29. package/src/lib/components/Link.svelte +2 -3
  30. package/src/lib/components/LinkButton.svelte +2 -2
  31. package/src/lib/components/LogConsole.svelte +22 -21
  32. package/src/lib/components/Logo.svelte +3 -1
  33. package/src/lib/components/Modal.svelte +9 -11
  34. package/src/lib/components/PageContainer.svelte +1 -1
  35. package/src/lib/components/PageHeader.svelte +1 -1
  36. package/src/lib/components/Pagination.svelte +4 -4
  37. package/src/lib/components/Popover.svelte +5 -7
  38. package/src/lib/components/Progress.svelte +6 -4
  39. package/src/lib/components/Prose.svelte +4 -4
  40. package/src/lib/components/Radio.svelte +11 -10
  41. package/src/lib/components/RadioGroup.svelte +3 -5
  42. package/src/lib/components/Select.svelte +23 -36
  43. package/src/lib/components/Separator.svelte +4 -1
  44. package/src/lib/components/Skeleton.svelte +6 -6
  45. package/src/lib/components/StatusIndicator.svelte +7 -18
  46. package/src/lib/components/Table.svelte +1 -1
  47. package/src/lib/components/TableCell.svelte +1 -1
  48. package/src/lib/components/TableRow.svelte +1 -5
  49. package/src/lib/components/Tabs.svelte +10 -13
  50. package/src/lib/components/TaskCard.svelte +7 -4
  51. package/src/lib/components/Textarea.svelte +10 -11
  52. package/src/lib/components/ThemeToggle.svelte +1 -1
  53. package/src/lib/components/ToastContainer.svelte +3 -8
  54. package/src/lib/components/Toggle.svelte +2 -3
  55. package/src/lib/components/Tooltip.svelte +12 -10
  56. package/src/lib/components/button-styles.ts +17 -18
  57. package/src/lib/components/dashboard/RunDetailPanel.svelte +160 -249
  58. package/src/lib/components/dashboard/RunFilterPopover.svelte +31 -25
  59. package/src/lib/components/dashboard/RunsList.svelte +55 -50
  60. package/src/lib/components/dashboard/run-filters.test.ts +31 -33
  61. package/src/lib/components/dashboard/run-filters.ts +33 -33
  62. package/src/lib/components/dashboard/run-helpers.test.ts +42 -17
  63. package/src/lib/components/dashboard/run-helpers.ts +53 -17
  64. package/src/lib/components/dashboard/status-config.ts +9 -8
  65. package/src/lib/theme-tokens.css +179 -114
  66. package/src/lib/theme.css +49 -35
  67. package/src/lib/utils/error.ts +0 -3
  68. package/src/lib/utils/format.test.ts +2 -6
  69. package/src/lib/utils/format.ts +28 -3
@@ -17,8 +17,26 @@
17
17
  * - apps/ui toggles the `.dark` class on <html>.
18
18
  * - Starlight (apps/docs) writes data-theme="dark" on the documentElement.
19
19
  * The union selector covers both. Light mode is the default.
20
+ *
21
+ * Webfonts are @imported here (not in theme.css) so every consumer that reads
22
+ * these tokens also gets the faces they name — apps/docs imports this file
23
+ * alone. Both families ship in the binary; nothing is fetched from a CDN.
20
24
  */
21
25
 
26
+ @import "@fontsource/tasa-orbiter/400.css";
27
+ @import "@fontsource/tasa-orbiter/500.css";
28
+ @import "@fontsource/tasa-orbiter/600.css";
29
+ @import "@fontsource/tasa-orbiter/700.css";
30
+ @import "@fontsource-variable/geist-mono/index.css";
31
+
32
+ :root {
33
+ /* Typography — body sans for prose, mono for all chrome and identifiers.
34
+ * Geist Mono is variable (100–900), so heading weight 800 is a real cut. */
35
+ --rw-font-sans: "TASA Orbiter", system-ui, -apple-system, sans-serif;
36
+ --rw-font-mono:
37
+ "Geist Mono Variable", "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
38
+ }
39
+
22
40
  :root {
23
41
  /* Primary — Wisp Teal (brand = #0E7C86, the artifact's deep teal). Deeper
24
42
  * and less cyan than Aurora (running/info) so the two stay distinct. */
@@ -46,20 +64,21 @@
46
64
  --rw-color-aurora-800: oklch(0.35 0.07 192);
47
65
  --rw-color-aurora-900: oklch(0.28 0.055 192);
48
66
 
49
- /* Mist — cool slate-blue neutrals (the artifact's #EDF1F4 / #E0E6EC /
50
- * #18212E family). No purple tint; hue ~248, low chroma rising into the
51
- * mids the way the design's slate does. */
52
- --rw-color-mist-50: oklch(0.985 0.004 240);
53
- --rw-color-mist-100: oklch(0.956 0.006 240); /* ≈ ground #EDF1F4 */
54
- --rw-color-mist-200: oklch(0.922 0.01 248); /* ≈ line #E0E6EC */
55
- --rw-color-mist-300: oklch(0.83 0.016 248);
56
- --rw-color-mist-400: oklch(0.676 0.026 248); /* ≈ faint #8B99A7 */
57
- --rw-color-mist-500: oklch(0.6 0.03 248);
58
- --rw-color-mist-600: oklch(0.531 0.032 248); /* ≈ muted #5E6E7E */
59
- --rw-color-mist-700: oklch(0.4 0.031 252);
60
- --rw-color-mist-800: oklch(0.31 0.029 255);
61
- --rw-color-mist-900: oklch(0.245 0.028 258); /* ≈ ink #18212E */
62
- --rw-color-mist-950: oklch(0.17 0.022 258);
67
+ /* Mist — teal-gray neutrals (the runbook's #F3F6F6 / #E0E7E7 /
68
+ * #14262A / #0C1719 family). No purple tint; hue ~195-205, low chroma,
69
+ * so bg-mist-* utilities agree with the new teal-black surfaces. The light
70
+ * end stays near-neutral to match the desaturated canvas. */
71
+ --rw-color-mist-50: oklch(0.972 0.003 200); /* ≈ #F3F6F6 */
72
+ --rw-color-mist-100: oklch(0.945 0.004 200); /* ≈ #E9EEEE */
73
+ --rw-color-mist-200: oklch(0.925 0.005 200); /* ≈ #E0E7E7 */
74
+ --rw-color-mist-300: oklch(0.83 0.016 198);
75
+ --rw-color-mist-400: oklch(0.676 0.02 200);
76
+ --rw-color-mist-500: oklch(0.6 0.022 200);
77
+ --rw-color-mist-600: oklch(0.531 0.024 202);
78
+ --rw-color-mist-700: oklch(0.42 0.024 203);
79
+ --rw-color-mist-800: oklch(0.32 0.024 204);
80
+ --rw-color-mist-900: oklch(0.266 0.025 204); /* ≈ #16292B */
81
+ --rw-color-mist-950: oklch(0.195 0.016 208); /* ≈ #0C1719 */
63
82
 
64
83
  /* Success — Field Green (success status = #1F9D57, a true grass green) */
65
84
  --rw-color-success-50: oklch(0.97 0.02 150);
@@ -112,8 +131,11 @@
112
131
  --rw-ansi-bright-cyan: var(--rw-color-aurora-300);
113
132
  --rw-ansi-bright-white: var(--rw-color-mist-50);
114
133
 
115
- /* Console device surface — the log console is a "black box" instrument
116
- * with its own fixed palette that never follows the light/dark UI theme.
134
+ /* Fixed-dark sibling palettes — the `--rw-con-*` log console (a "black box"
135
+ * device) and the `--rw-term-*`/`--rw-syn-*` website terminal surfaces both
136
+ * keep their own dark palette in BOTH themes; they never flip light/dark. */
137
+
138
+ /* Console device surface — the log console instrument.
117
139
  * Exact values from the approved execution-detail design. */
118
140
  --rw-con-bg: #0e141b; /* body / gutter backing */
119
141
  --rw-con-panel: #11181f; /* toolbar + footer chrome */
@@ -121,62 +143,97 @@
121
143
  --rw-con-dim: #6c7d8c; /* footer + muted readouts */
122
144
  --rw-con-gutter: #3c4a58; /* line numbers + sentinel rule */
123
145
 
124
- /* Light-mode semantic tokens (default) */
146
+ /* Terminal surface — always dark in both themes (website --term-* set) */
147
+ --rw-term: #0a1116;
148
+ --rw-term-2: #0d151b;
149
+ --rw-term-line: #1d2932;
150
+ --rw-term-line-2: #2a3a45;
151
+ --rw-term-text: #dbe6ec;
152
+ --rw-term-muted: #7d919c;
153
+ --rw-term-faint: #566873;
154
+ --rw-term-teal: #2fd4dd;
155
+ --rw-term-ok: #3fc26b;
156
+ --rw-term-warn: #e0a52e;
157
+ --rw-term-bad: #f15a4d;
158
+ /* Syntax highlighting */
159
+ --rw-syn-comment: #5b6b75;
160
+ --rw-syn-kw: #79c7ff;
161
+ --rw-syn-str: #7fd6a0;
162
+ --rw-syn-num: #e0a52e;
163
+ --rw-syn-punc: #9aa7b0;
164
+ --rw-syn-fn: #2fd4dd;
165
+
166
+ /* Elevation — light-mode drop shadows; flipped to `none` in dark so the
167
+ * UI reads as borders-only there. */
168
+ --rw-shadow-xs: 0 1px 2px rgba(16, 41, 43, 0.04);
169
+ --rw-shadow-sm: 0 1px 2px rgba(16, 41, 43, 0.04), 0 12px 28px -20px rgba(16, 41, 43, 0.22);
170
+ --rw-shadow-md: 0 1px 2px rgba(16, 41, 43, 0.06), 0 22px 44px -26px rgba(16, 41, 43, 0.4);
171
+ --rw-shadow-lg: 0 1px 2px rgba(16, 41, 43, 0.06), 0 30px 60px -28px rgba(16, 41, 43, 0.45);
172
+ --rw-shadow-xl: 0 1px 2px rgba(16, 41, 43, 0.06), 0 40px 80px -30px rgba(16, 41, 43, 0.5);
173
+
174
+ /* Light-mode semantic tokens (default) — runbook palette (exact hex).
175
+ *
176
+ * The canvas carries only a whisper of the brand teal. The website can
177
+ * afford a minty `--ground` because dark panes and terminals cover most
178
+ * of it; in a dashboard the canvas is most of the pixels, and a saturated
179
+ * mint under white panels reads washed-out. Structure comes from the
180
+ * hairline outlines, not from tinting the page. */
125
181
  /* Surfaces */
126
- --rw-surface: var(--rw-color-mist-50);
127
- --rw-surface-raised: white;
128
- --rw-surface-overlay: white;
129
- --rw-surface-sunken: var(--rw-color-mist-100);
182
+ --rw-surface: #f3f6f6;
183
+ --rw-surface-raised: #ffffff;
184
+ --rw-surface-sunken: #e9eeee;
185
+ --rw-surface-overlay: #ffffff;
130
186
  /* On-surface text */
131
- --rw-on-surface: var(--rw-color-mist-900);
132
- --rw-on-surface-muted: var(--rw-color-mist-600);
133
- --rw-on-surface-faint: var(--rw-color-mist-400);
187
+ --rw-on-surface: #14262a;
188
+ --rw-on-surface-muted: #5a6d70;
189
+ --rw-on-surface-faint: #869798;
134
190
  /* Borders / outlines */
135
- --rw-outline: var(--rw-color-mist-200);
136
- --rw-outline-hover: var(--rw-color-mist-300);
137
- --rw-outline-faint: var(--rw-color-mist-100);
138
- --rw-ring: var(--rw-color-wisp-500);
139
- --rw-backdrop: oklch(0.12 0.012 280 / 0.5);
191
+ --rw-outline: #e0e7e7;
192
+ --rw-outline-hover: #c8d2d2;
193
+ --rw-outline-faint: #edf1f1;
194
+ --rw-ring: #15a0a8;
195
+ --rw-backdrop: rgba(243, 246, 246, 0.78);
140
196
  /* Primary */
141
- --rw-primary: var(--rw-color-wisp-600);
142
- --rw-primary-hover: var(--rw-color-wisp-700);
143
- --rw-primary-active: var(--rw-color-wisp-800);
144
- --rw-on-primary: white;
145
- --rw-primary-soft: var(--rw-color-wisp-100);
146
- --rw-primary-soft-text: var(--rw-color-wisp-700);
147
- /* Danger */
148
- --rw-danger: var(--rw-color-danger-600);
149
- --rw-danger-hover: var(--rw-color-danger-700);
150
- --rw-danger-active: var(--rw-color-danger-800);
151
- --rw-on-danger: white;
152
- --rw-danger-soft: var(--rw-color-danger-50);
153
- --rw-danger-soft-text: var(--rw-color-danger-700);
197
+ --rw-primary: #15a0a8;
198
+ --rw-primary-hover: #0e7c86;
199
+ --rw-primary-active: #0a6068;
200
+ --rw-on-primary: #ffffff;
201
+ --rw-primary-soft: rgba(21, 160, 168, 0.1);
202
+ --rw-primary-soft-text: #15a0a8;
203
+ --rw-primary-soft-border: rgba(21, 160, 168, 0.25);
154
204
  /* Success */
155
- --rw-success: var(--rw-color-success-600);
156
- --rw-success-hover: var(--rw-color-success-700);
157
- --rw-on-success: white;
158
- --rw-success-soft: var(--rw-color-success-50);
159
- --rw-success-soft-text: var(--rw-color-success-700);
205
+ --rw-success: #1e8a4a;
206
+ --rw-success-hover: #1a753e;
207
+ --rw-success-active: #156033;
208
+ --rw-on-success: #ffffff;
209
+ --rw-success-soft: rgba(30, 138, 74, 0.1);
210
+ --rw-success-soft-text: #1e8a4a;
211
+ --rw-success-soft-border: rgba(30, 138, 74, 0.25);
212
+ /* Danger */
213
+ --rw-danger: #d6453d;
214
+ --rw-danger-hover: #b8352b;
215
+ --rw-danger-active: #9a2a22;
216
+ --rw-on-danger: #ffffff;
217
+ --rw-danger-soft: rgba(214, 69, 61, 0.1);
218
+ --rw-danger-soft-text: #d6453d;
219
+ --rw-danger-soft-border: rgba(214, 69, 61, 0.25);
160
220
  /* Warning */
161
- --rw-warning: var(--rw-color-warning-500);
162
- --rw-warning-hover: var(--rw-color-warning-600);
163
- --rw-on-warning: white;
164
- --rw-warning-soft: var(--rw-color-warning-50);
165
- --rw-warning-soft-text: var(--rw-color-warning-700);
166
- /* Info */
167
- --rw-info: var(--rw-color-aurora-600);
168
- --rw-info-hover: var(--rw-color-aurora-700);
169
- --rw-on-info: white;
170
- --rw-info-soft: var(--rw-color-aurora-50);
171
- --rw-info-soft-text: var(--rw-color-aurora-700);
172
- /* Soft callout borders — sit on the matching *-soft surface */
173
- --rw-primary-soft-border: var(--rw-color-wisp-200);
174
- --rw-danger-soft-border: var(--rw-color-danger-200);
175
- --rw-success-soft-border: var(--rw-color-success-200);
176
- --rw-warning-soft-border: var(--rw-color-warning-200);
177
- --rw-info-soft-border: var(--rw-color-aurora-200);
221
+ --rw-warning: #c4781a;
222
+ --rw-warning-hover: #a36414;
223
+ --rw-warning-active: #82500f;
224
+ --rw-on-warning: #ffffff;
225
+ --rw-warning-soft: rgba(196, 120, 26, 0.1);
226
+ --rw-warning-soft-text: #c4781a;
227
+ --rw-warning-soft-border: rgba(196, 120, 26, 0.25);
228
+ /* Info — matches primary teal */
229
+ --rw-info: #15a0a8;
230
+ --rw-info-hover: #0e7c86;
231
+ --rw-on-info: #ffffff;
232
+ --rw-info-soft: rgba(21, 160, 168, 0.1);
233
+ --rw-info-soft-text: #15a0a8;
234
+ --rw-info-soft-border: rgba(21, 160, 168, 0.25);
178
235
  /* Glass */
179
- --rw-glass: oklch(1 0 0 / 0.8);
236
+ --rw-glass: rgba(255, 255, 255, 0.78);
180
237
  --rw-glass-blur: blur(12px);
181
238
  }
182
239
 
@@ -184,58 +241,66 @@
184
241
  * attribute for Starlight. Both selectors apply the same token set. */
185
242
  :root.dark,
186
243
  :root[data-theme="dark"] {
187
- --rw-surface: var(--rw-color-mist-950);
188
- --rw-surface-raised: var(--rw-color-mist-900);
189
- --rw-surface-overlay: var(--rw-color-mist-900);
190
- --rw-surface-sunken: oklch(0.08 0.012 280);
191
- --rw-on-surface: var(--rw-color-mist-100);
192
- --rw-on-surface-muted: var(--rw-color-mist-400);
193
- --rw-on-surface-faint: var(--rw-color-mist-600);
194
- --rw-outline: var(--rw-color-mist-800);
195
- --rw-outline-hover: var(--rw-color-mist-700);
196
- --rw-outline-faint: var(--rw-color-mist-800);
197
- --rw-ring: var(--rw-color-wisp-400);
198
- --rw-backdrop: oklch(0 0 0 / 0.7);
199
- /* Primary */
200
- --rw-primary: var(--rw-color-wisp-500);
201
- --rw-primary-hover: var(--rw-color-wisp-400);
202
- --rw-primary-active: var(--rw-color-wisp-300);
203
- --rw-on-primary: white;
204
- --rw-primary-soft: oklch(0.22 0.06 270);
205
- --rw-primary-soft-text: var(--rw-color-wisp-300);
206
- /* Danger */
207
- --rw-danger: var(--rw-color-danger-500);
208
- --rw-danger-hover: var(--rw-color-danger-400);
209
- --rw-danger-active: var(--rw-color-danger-300);
210
- --rw-on-danger: white;
211
- --rw-danger-soft: oklch(0.2 0.04 355);
212
- --rw-danger-soft-text: var(--rw-color-danger-300);
244
+ /* Surfaces */
245
+ --rw-surface: #0c1719;
246
+ --rw-surface-raised: #122226;
247
+ --rw-surface-sunken: #080f11;
248
+ --rw-surface-overlay: rgba(8, 15, 17, 0.86);
249
+ /* On-surface text */
250
+ --rw-on-surface: #e8eff4;
251
+ --rw-on-surface-muted: #94a6b1;
252
+ --rw-on-surface-faint: #5f7079;
253
+ /* Borders / outlines */
254
+ --rw-outline: #1d2932;
255
+ --rw-outline-hover: #2a3a45;
256
+ --rw-outline-faint: #1d2932;
257
+ --rw-ring: #2fd4dd;
258
+ --rw-backdrop: rgba(4, 10, 12, 0.7);
259
+ /* Primary — bright teal in dark, matches info */
260
+ --rw-primary: #2fd4dd;
261
+ --rw-primary-hover: #5fe3ea;
262
+ --rw-primary-active: #5fe3ea;
263
+ --rw-on-primary: #04181a;
264
+ --rw-primary-soft: rgba(21, 160, 168, 0.14);
265
+ --rw-primary-soft-text: #2fd4dd;
266
+ --rw-primary-soft-border: rgba(47, 212, 221, 0.28);
213
267
  /* Success */
214
- --rw-success: var(--rw-color-success-500);
215
- --rw-success-hover: var(--rw-color-success-400);
216
- --rw-on-success: white;
217
- --rw-success-soft: oklch(0.2 0.04 165);
218
- --rw-success-soft-text: var(--rw-color-success-300);
268
+ --rw-success: #3fc26b;
269
+ --rw-success-hover: #5fd487;
270
+ --rw-on-success: #04181a;
271
+ --rw-success-soft: rgba(63, 194, 107, 0.14);
272
+ --rw-success-soft-text: #3fc26b;
273
+ --rw-success-soft-border: rgba(63, 194, 107, 0.28);
274
+ /* Danger */
275
+ --rw-danger: #f15a4d;
276
+ --rw-danger-hover: #ff7568;
277
+ --rw-danger-active: #ff8a7e;
278
+ --rw-on-danger: #04181a;
279
+ --rw-danger-soft: rgba(241, 90, 77, 0.14);
280
+ --rw-danger-soft-text: #f15a4d;
281
+ --rw-danger-soft-border: rgba(241, 90, 77, 0.28);
219
282
  /* Warning */
220
- --rw-warning: var(--rw-color-warning-400);
221
- --rw-warning-hover: var(--rw-color-warning-300);
222
- --rw-on-warning: var(--rw-color-mist-950);
223
- --rw-warning-soft: oklch(0.2 0.04 85);
224
- --rw-warning-soft-text: var(--rw-color-warning-300);
225
- /* Info */
226
- --rw-info: var(--rw-color-aurora-500);
227
- --rw-info-hover: var(--rw-color-aurora-400);
228
- --rw-on-info: white;
229
- --rw-info-soft: oklch(0.2 0.04 180);
230
- --rw-info-soft-text: var(--rw-color-aurora-300);
231
- /* Soft callout borders */
232
- --rw-primary-soft-border: oklch(0.32 0.06 270);
233
- --rw-danger-soft-border: oklch(0.32 0.06 355);
234
- --rw-success-soft-border: oklch(0.32 0.06 165);
235
- --rw-warning-soft-border: oklch(0.32 0.06 85);
236
- --rw-info-soft-border: oklch(0.32 0.06 180);
283
+ --rw-warning: #e0a52e;
284
+ --rw-warning-hover: #ecb845;
285
+ --rw-on-warning: #04181a;
286
+ --rw-warning-soft: rgba(224, 165, 46, 0.14);
287
+ --rw-warning-soft-text: #e0a52e;
288
+ --rw-warning-soft-border: rgba(224, 165, 46, 0.28);
289
+ /* Info — bright teal in dark */
290
+ --rw-info: #2fd4dd;
291
+ --rw-info-hover: #5fe3ea;
292
+ --rw-on-info: #04181a;
293
+ --rw-info-soft: rgba(47, 212, 221, 0.14);
294
+ --rw-info-soft-text: #2fd4dd;
295
+ --rw-info-soft-border: rgba(47, 212, 221, 0.28);
237
296
  /* Glass */
238
- --rw-glass: oklch(0.12 0.012 280 / 0.9);
297
+ --rw-glass: rgba(8, 15, 17, 0.9);
298
+ /* Elevation — borders-only in dark, no drop shadow */
299
+ --rw-shadow-xs: none;
300
+ --rw-shadow-sm: none;
301
+ --rw-shadow-md: none;
302
+ --rw-shadow-lg: none;
303
+ --rw-shadow-xl: none;
239
304
  }
240
305
 
241
306
  /* Platform polish — focus ring, text selection, scrollbar. Lifted from
package/src/lib/theme.css CHANGED
@@ -14,10 +14,6 @@
14
14
 
15
15
  @import "tailwindcss";
16
16
  @import "./theme-tokens.css";
17
- @import "@fontsource/tasa-orbiter/400.css";
18
- @import "@fontsource/tasa-orbiter/500.css";
19
- @import "@fontsource/tasa-orbiter/600.css";
20
- @import "@fontsource/tasa-orbiter/700.css";
21
17
 
22
18
  @plugin "@tailwindcss/forms";
23
19
  @plugin "@tailwindcss/typography";
@@ -87,9 +83,10 @@
87
83
  --color-warning-600: var(--rw-color-warning-600);
88
84
  --color-warning-700: var(--rw-color-warning-700);
89
85
 
90
- /* Typography */
91
- --font-sans: "TASA Orbiter", system-ui, -apple-system, sans-serif;
92
- --font-mono: "JetBrains Mono", "Fira Code", monospace;
86
+ /* Typography — stacks and @font-face live in theme-tokens.css so the
87
+ * non-Tailwind consumers (apps/docs) share one definition. */
88
+ --font-sans: var(--rw-font-sans);
89
+ --font-mono: var(--rw-font-mono);
93
90
 
94
91
  /* Font sizes */
95
92
  --text-2xs: 0.625rem;
@@ -125,31 +122,24 @@
125
122
  --spacing-20: 5rem;
126
123
  --spacing-24: 6rem;
127
124
 
128
- /* Border radius */
125
+ /* Border radius — runbook scale (small radii are signature). lg/xl/2xl/3xl
126
+ * remain for legacy call sites but should not be used going forward. */
129
127
  --radius-none: 0;
130
- --radius-sm: 0.25rem;
131
- --radius-md: 0.375rem;
128
+ --radius-sm: 0.1875rem; /* 3px — controls */
129
+ --radius-md: 0.25rem; /* 4px — panels / cards */
132
130
  --radius-lg: 0.5rem;
133
131
  --radius-xl: 0.75rem;
134
132
  --radius-2xl: 1rem;
135
133
  --radius-3xl: 1.5rem;
136
134
  --radius-full: 9999px;
137
135
 
138
- /* Shadows — soft, ethereal */
139
- --shadow-xs: 0 1px 2px 0 oklch(0.18 0.02 280 / 0.05);
140
- --shadow-sm:
141
- 0 1px 3px 0 oklch(0.18 0.02 280 / 0.08), 0 1px 2px -1px oklch(0.18 0.02 280 / 0.08);
142
- --shadow-md:
143
- 0 4px 6px -1px oklch(0.18 0.02 280 / 0.08), 0 2px 4px -2px oklch(0.18 0.02 280 / 0.08);
144
- --shadow-lg:
145
- 0 10px 15px -3px oklch(0.18 0.02 280 / 0.08), 0 4px 6px -4px oklch(0.18 0.02 280 / 0.08);
146
- --shadow-xl:
147
- 0 20px 25px -5px oklch(0.18 0.02 280 / 0.1), 0 8px 10px -6px oklch(0.18 0.02 280 / 0.1);
148
-
149
- /* Glow effects for wisp theme */
150
- --shadow-glow-sm: 0 0 10px oklch(0.58 0.18 270 / 0.15);
151
- --shadow-glow-md: 0 0 20px oklch(0.58 0.18 270 / 0.2);
152
- --shadow-glow-lg: 0 0 30px oklch(0.58 0.18 270 / 0.25);
136
+ /* Shadows — mapped to the flip tokens in theme-tokens.css so every
137
+ * shadow-* utility resolves to `none` in dark (borders-only look). */
138
+ --shadow-xs: var(--rw-shadow-xs);
139
+ --shadow-sm: var(--rw-shadow-sm);
140
+ --shadow-md: var(--rw-shadow-md);
141
+ --shadow-lg: var(--rw-shadow-lg);
142
+ --shadow-xl: var(--rw-shadow-xl);
153
143
  }
154
144
 
155
145
  /* Semantic Design Tokens — Tailwind utility aliases.
@@ -212,14 +202,30 @@
212
202
  --color-warning-soft-border: var(--rw-warning-soft-border);
213
203
  --color-info-soft-border: var(--rw-info-soft-border);
214
204
 
215
- /* Transition durations */
216
- --duration-fast: 100ms;
217
- --duration-normal: 200ms;
218
- --duration-slow: 300ms;
205
+ /* Terminal surface — fixed dark in both themes */
206
+ --color-term: var(--rw-term);
207
+ --color-term-2: var(--rw-term-2);
208
+ --color-term-line: var(--rw-term-line);
209
+ --color-term-line-2: var(--rw-term-line-2);
210
+ --color-term-text: var(--rw-term-text);
211
+ --color-term-muted: var(--rw-term-muted);
212
+ --color-term-faint: var(--rw-term-faint);
213
+ --color-term-teal: var(--rw-term-teal);
214
+ --color-term-ok: var(--rw-term-ok);
215
+ --color-term-warn: var(--rw-term-warn);
216
+ --color-term-bad: var(--rw-term-bad);
217
+ /* Syntax highlighting */
218
+ --color-syn-comment: var(--rw-syn-comment);
219
+ --color-syn-kw: var(--rw-syn-kw);
220
+ --color-syn-str: var(--rw-syn-str);
221
+ --color-syn-num: var(--rw-syn-num);
222
+ --color-syn-punc: var(--rw-syn-punc);
223
+ --color-syn-fn: var(--rw-syn-fn);
219
224
 
220
- /* Easings */
221
- --ease-default: cubic-bezier(0.4, 0, 0.2, 1);
222
- --ease-in: cubic-bezier(0.4, 0, 1, 1);
225
+ /* Motion. Deliberately minimal: hover, focus and state changes are
226
+ * instant, so there is no scale of durations to pick from. The few things
227
+ * that still move — a drawer sliding, a toggle knob, an accordion's
228
+ * height, a progress bar's width — set their own timing inline. */
223
229
  --ease-out: cubic-bezier(0, 0, 0.2, 1);
224
230
  }
225
231
 
@@ -245,6 +251,17 @@ body {
245
251
  color: var(--rw-on-surface);
246
252
  }
247
253
 
254
+ /* Headings are mono chrome — the runbook voice. Components should not fight this. */
255
+ h1,
256
+ h2,
257
+ h3 {
258
+ font-family: var(--font-mono);
259
+ font-weight: 800;
260
+ line-height: 1.1;
261
+ letter-spacing: -0.02em;
262
+ text-wrap: balance;
263
+ }
264
+
248
265
  /* Utility classes for the design system */
249
266
  .rw-text-gradient {
250
267
  background: linear-gradient(135deg, var(--rw-color-wisp-600), var(--rw-color-aurora-500));
@@ -278,9 +295,6 @@ body {
278
295
  }
279
296
  }
280
297
 
281
- .btn-scale {
282
- transition: all 0.15s ease-in-out;
283
- }
284
298
  .btn-scale:active {
285
299
  transform: scale(0.97);
286
300
  }
@@ -1,9 +1,6 @@
1
1
  // SPDX-FileCopyrightText: PoppyCake, s.r.o.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- /**
5
- * Extracts a human-readable message from an unknown caught value.
6
- */
7
4
  export function extractErrorMessage(
8
5
  err: unknown,
9
6
  fallback = "An unexpected error occurred",
@@ -124,12 +124,8 @@ describe("formatRelativeTimeWithAbsolute", () => {
124
124
  describe("formatRelativeTime", () => {
125
125
  it("advances as the supplied now advances", () => {
126
126
  const date = new Date("2024-01-01T12:00:00Z");
127
- expect(formatRelativeTime(date, new Date("2024-01-01T14:00:00Z"))).toBe(
128
- "about 2 hours ago",
129
- );
130
- expect(formatRelativeTime(date, new Date("2024-01-01T15:00:00Z"))).toBe(
131
- "about 3 hours ago",
132
- );
127
+ expect(formatRelativeTime(date, new Date("2024-01-01T14:00:00Z"))).toBe("2 hours ago");
128
+ expect(formatRelativeTime(date, new Date("2024-01-01T15:00:00Z"))).toBe("3 hours ago");
133
129
  });
134
130
  });
135
131
 
@@ -1,7 +1,32 @@
1
1
  // SPDX-FileCopyrightText: PoppyCake, s.r.o.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import { formatDistance } from "date-fns";
4
+ // Signed magnitude thresholds for Intl.RelativeTimeFormat, largest-first
5
+ // division. Mirrors the buckets date-fns' formatDistance used to pick.
6
+ const RELATIVE_DIVISIONS: [amount: number, unit: Intl.RelativeTimeFormatUnit][] = [
7
+ [60, "seconds"],
8
+ [60, "minutes"],
9
+ [24, "hours"],
10
+ [7, "days"],
11
+ [4.34524, "weeks"],
12
+ [12, "months"],
13
+ [Number.POSITIVE_INFINITY, "years"],
14
+ ];
15
+
16
+ // formatDistance renders a signed date delta as "N units ago" / "in N units"
17
+ // using the platform Intl formatter. Negative delta (date before base) reads as
18
+ // past, positive as future.
19
+ function formatDistance(date: Date, base: Date): string {
20
+ const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: "always" });
21
+ let delta = (date.getTime() - base.getTime()) / 1000;
22
+ for (const [amount, unit] of RELATIVE_DIVISIONS) {
23
+ if (Math.abs(delta) < amount) {
24
+ return rtf.format(Math.round(delta), unit);
25
+ }
26
+ delta /= amount;
27
+ }
28
+ return rtf.format(Math.round(delta), "years");
29
+ }
5
30
 
6
31
  export function formatBytes(bytes: number): string {
7
32
  const units: [string, ...string[]] = ["B", "KB", "MB", "GB", "TB", "PB"];
@@ -17,7 +42,7 @@ export function formatBytes(bytes: number): string {
17
42
  }
18
43
 
19
44
  export function formatRelativeTime(dateStr: string | Date, now: Date = new Date()): string {
20
- return formatDistance(new Date(dateStr), now, { addSuffix: true });
45
+ return formatDistance(new Date(dateStr), now);
21
46
  }
22
47
 
23
48
  export function formatRelativeTimeWithAbsolute(
@@ -25,7 +50,7 @@ export function formatRelativeTimeWithAbsolute(
25
50
  now: Date = new Date(),
26
51
  ): string {
27
52
  const date = new Date(dateStr);
28
- const relative = formatDistance(date, now, { addSuffix: true }).replace("about ", "");
53
+ const relative = formatDistance(date, now);
29
54
  const diffMs = Math.abs(now.getTime() - date.getTime());
30
55
  const diffDays = diffMs / (1000 * 60 * 60 * 24);
31
56