@runwisp/ui 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/package.json +4 -5
  2. package/src/lib/actions/floating.ts +73 -0
  3. package/src/lib/actions/focusTrap.ts +3 -3
  4. package/src/lib/actions/portal.ts +5 -1
  5. package/src/lib/components/Accordion.svelte +6 -2
  6. package/src/lib/components/AccordionItem.svelte +80 -23
  7. package/src/lib/components/Avatar.svelte +0 -1
  8. package/src/lib/components/Badge.svelte +1 -1
  9. package/src/lib/components/CodeBlock.svelte +84 -35
  10. package/src/lib/components/CronInput.svelte +5 -2
  11. package/src/lib/components/DataGrid.svelte +100 -138
  12. package/src/lib/components/Drawer.svelte +2 -6
  13. package/src/lib/components/Dropdown.svelte +19 -57
  14. package/src/lib/components/EmptyState.svelte +2 -1
  15. package/src/lib/components/ErrorState.svelte +2 -1
  16. package/src/lib/components/FilterBar.svelte +1 -1
  17. package/src/lib/components/FilterPill.svelte +2 -2
  18. package/src/lib/components/FormField.svelte +1 -1
  19. package/src/lib/components/Heading.svelte +19 -12
  20. package/src/lib/components/Kbd.svelte +25 -10
  21. package/src/lib/components/Link.svelte +7 -1
  22. package/src/lib/components/LogConsole.svelte +40 -42
  23. package/src/lib/components/Modal.svelte +44 -33
  24. package/src/lib/components/NotificationBell.svelte +2 -1
  25. package/src/lib/components/PageContainer.svelte +1 -10
  26. package/src/lib/components/PageHeader.svelte +8 -4
  27. package/src/lib/components/Popover.svelte +9 -51
  28. package/src/lib/components/Progress.svelte +19 -6
  29. package/src/lib/components/Radio.svelte +16 -4
  30. package/src/lib/components/RadioGroup.svelte +15 -2
  31. package/src/lib/components/SearchInput.svelte +1 -1
  32. package/src/lib/components/Select.svelte +16 -47
  33. package/src/lib/components/Sparkline.svelte +2 -2
  34. package/src/lib/components/StatusDot.svelte +1 -1
  35. package/src/lib/components/Table.svelte +13 -4
  36. package/src/lib/components/TableBody.svelte +2 -1
  37. package/src/lib/components/TableCell.svelte +38 -8
  38. package/src/lib/components/TableHead.svelte +12 -4
  39. package/src/lib/components/TableRow.svelte +23 -3
  40. package/src/lib/components/TaskCard.svelte +1 -1
  41. package/src/lib/components/TimezoneSelect.svelte +2 -2
  42. package/src/lib/components/Tooltip.svelte +1 -1
  43. package/src/lib/components/accordion.svelte.ts +19 -0
  44. package/src/lib/components/dashboard/RunDetailPanel.svelte +106 -79
  45. package/src/lib/components/dashboard/RunFilterPopover.svelte +12 -21
  46. package/src/lib/components/dashboard/RunsList.svelte +17 -26
  47. package/src/lib/components/dashboard/cron-format.test.ts +1 -1
  48. package/src/lib/components/dashboard/cron-format.ts +3 -3
  49. package/src/lib/components/dashboard/run-filters.test.ts +4 -60
  50. package/src/lib/components/dashboard/run-filters.ts +29 -144
  51. package/src/lib/components/dashboard/run-helpers.test.ts +25 -6
  52. package/src/lib/components/dashboard/run-helpers.ts +40 -39
  53. package/src/lib/components/dashboard/status-config.ts +38 -83
  54. package/src/lib/components/dashboard/types.ts +0 -26
  55. package/src/lib/components/data-grid-selection.ts +2 -9
  56. package/src/lib/components/filter-spec.ts +3 -3
  57. package/src/lib/components/radio-group.ts +11 -0
  58. package/src/lib/index.ts +8 -43
  59. package/src/lib/log-console/LogCache.svelte.ts +1 -1
  60. package/src/lib/log-console/LogFetcher.svelte.ts +3 -14
  61. package/src/lib/log-console/ansi.ts +1 -1
  62. package/src/lib/log-console/highlight-scroll.test.ts +59 -0
  63. package/src/lib/log-console/highlight-scroll.ts +41 -0
  64. package/src/lib/theme-tokens.css +21 -21
  65. package/src/lib/theme.css +22 -16
  66. package/src/lib/utils/clipboard.svelte.test.ts +92 -0
  67. package/src/lib/utils/clipboard.svelte.ts +73 -0
  68. package/src/lib/utils/format.test.ts +3 -3
  69. package/src/lib/utils/format.ts +65 -94
  70. package/src/lib/utils/id.test.ts +3 -7
  71. package/src/lib/utils/id.ts +3 -2
  72. package/src/lib/utils/theme.svelte.test.ts +0 -6
  73. package/src/lib/utils/theme.svelte.ts +3 -5
  74. package/src/lib/utils/toast.svelte.ts +5 -14
  75. package/src/stories/Accordion.stories.svelte +61 -0
  76. package/src/stories/CodeBlock.stories.svelte +55 -0
  77. package/src/stories/DataGrid.stories.svelte +0 -5
  78. package/src/stories/Heading.stories.svelte +31 -0
  79. package/src/stories/Kbd.stories.svelte +36 -0
  80. package/src/stories/Link.stories.svelte +38 -0
  81. package/src/stories/LogConsole.stories.svelte +2 -2
  82. package/src/stories/PageHeader.stories.svelte +31 -0
  83. package/src/stories/Progress.stories.svelte +26 -0
  84. package/src/stories/RadioGroup.stories.svelte +43 -0
  85. package/src/stories/Table.stories.svelte +69 -0
  86. package/src/svelte-shims.d.ts +0 -5
  87. package/src/lib/components/dashboard/status-config.test.ts +0 -18
@@ -1,25 +1,17 @@
1
1
  // SPDX-FileCopyrightText: PoppyCake, s.r.o.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import type { Run, RunStatus } from "@runwisp/common";
5
- import { formatDuration } from "../../utils/format.js";
4
+ import type { APIPaths, Run, RunStatus, Trigger } from "@runwisp/common";
5
+ import { formatBytes, formatDuration } from "../../utils/format.js";
6
6
 
7
7
  export interface RunVerdict {
8
8
  /** Verb phrase for the outcome, ending in its preposition when `timed`. */
9
9
  verb: string;
10
- /**
11
- * Whether the phrase expects a duration after it. False for statuses that
12
- * never produced one (nothing ran, or nothing has yet), so the caller
13
- * renders the verb alone rather than "skipped after —".
14
- */
10
+ /** Whether a duration follows the verb; false when nothing ran yet. */
15
11
  timed: boolean;
16
12
  }
17
13
 
18
- /**
19
- * The run's outcome as a verb phrase, so a detail view can state it as one
20
- * sentence ("succeeded in 933ms") instead of a status badge competing with a
21
- * separate duration readout for the same glance.
22
- */
14
+ // The run's outcome as a verb phrase, read as one sentence: "succeeded in 933ms".
23
15
  const RUN_VERDICTS: Record<RunStatus, RunVerdict> = {
24
16
  succeeded: { verb: "succeeded in", timed: true },
25
17
  failed: { verb: "failed after", timed: true },
@@ -54,12 +46,8 @@ export function runDuration(
54
46
  }
55
47
 
56
48
  /**
57
- * Gap between when a run was scheduled (`createdAt`, the cron tick) and when
58
- * it actually started (`startedAt`), formatted — or undefined when the two are
59
- * within a second of each other. This is the visible face of `jitter`: a
60
- * jittered run is created at its tick but starts later inside the window, and
61
- * this is by how much. It also surfaces queue-wait, since a queued run is
62
- * created when it joins the line and started when the line clears.
49
+ * How long a run waited between `createdAt` and `startedAt` (jitter or queue
50
+ * wait), or undefined when it started within a second.
63
51
  */
64
52
  export function runStartDelay(run: Pick<Run, "createdAt" | "startedAt">): string | undefined {
65
53
  if (!run.startedAt) return undefined;
@@ -68,14 +56,7 @@ export function runStartDelay(run: Pick<Run, "createdAt" | "startedAt">): string
68
56
  return formatDuration(delay);
69
57
  }
70
58
 
71
- /**
72
- * Display suffix for a run's instance slot. A service configured with more than
73
- * one instance gets a 1-based suffix (`#1`, `#2`, …) on every one of its runs;
74
- * a single-instance task (or any non-service, where `instanceCount` is 1)
75
- * returns an empty string so the bare task name is shown. `instanceIndex` is
76
- * the stored 0-based slot; `instanceCount` is the task's currently configured
77
- * instance count.
78
- */
59
+ /** 1-based `#N` suffix for a multi-instance service run, else "". */
79
60
  export function instanceSuffix(instanceIndex: number, instanceCount: number): string {
80
61
  if (instanceCount > 1) {
81
62
  return `#${String(instanceIndex + 1)}`;
@@ -83,23 +64,32 @@ export function instanceSuffix(instanceIndex: number, instanceCount: number): st
83
64
  return "";
84
65
  }
85
66
 
67
+ // Typed against the generated paths so a route rename fails the build.
68
+ const RAW_LOG_PATH: keyof APIPaths = "/api/runs/{runId}/log/raw";
69
+
70
+ /** The full log of a run as one plain-text download. */
71
+ export function runLogDownloadUrl(runId: string): string {
72
+ return RAW_LOG_PATH.replace("{runId}", encodeURIComponent(runId));
73
+ }
74
+
75
+ /** Labels per trigger source: `short` for the row badge, `long` for descriptions. */
76
+ export const TRIGGER_LABELS: Record<Trigger, { short: string; long: string }> = {
77
+ cron: { short: "Cron", long: "Scheduled (cron)" },
78
+ api: { short: "API", long: "REST API" },
79
+ ui: { short: "UI", long: "UI" },
80
+ cli: { short: "CLI", long: "CLI" },
81
+ station: { short: "Station", long: "Control plane" },
82
+ service: { short: "Service", long: "Service auto-start" },
83
+ startup: { short: "Startup", long: "On daemon start" },
84
+ hook: { short: "Hook", long: "Hook" },
85
+ };
86
+
86
87
  /** Human label for why a run fired (the `triggeredBy` source). */
87
88
  export function formatTriggeredByLabel(triggeredBy: Run["triggeredBy"]): string {
88
- if (triggeredBy === "api") return "API";
89
- if (triggeredBy === "ui") return "UI";
90
- if (triggeredBy === "cli") return "CLI";
91
- if (triggeredBy === "cron") return "Cron";
92
- if (triggeredBy === "service") return "Service";
93
- if (triggeredBy === "startup") return "Startup";
94
- if (triggeredBy === "hook") return "Hook";
95
- return "Station";
89
+ return TRIGGER_LABELS[triggeredBy].short;
96
90
  }
97
91
 
98
- /**
99
- * "retry #N" label when a run is a retry of an earlier one, else undefined.
100
- * A run is a retry if it carries a positive attempt number or points back at
101
- * the run it re-attempts.
102
- */
92
+ /** "retry #N" when the run re-attempts an earlier one, else undefined. */
103
93
  export function runRetryLabel(run: Pick<Run, "retryAttempt" | "retryOfRunId">): string | undefined {
104
94
  if (run.retryAttempt > 0 || run.retryOfRunId) {
105
95
  return `retry #${String(run.retryAttempt)}`;
@@ -107,6 +97,17 @@ export function runRetryLabel(run: Pick<Run, "retryAttempt" | "retryOfRunId">):
107
97
  return undefined;
108
98
  }
109
99
 
100
+ /**
101
+ * "peak 48 MB · CPU 1s" for a run whose resources were measured (shell runs
102
+ * only), else undefined.
103
+ */
104
+ export function runUsageLabel(run: Pick<Run, "peakMemoryBytes" | "cpuTimeMs">): string | undefined {
105
+ const parts: string[] = [];
106
+ if (run.peakMemoryBytes !== undefined) parts.push("peak " + formatBytes(run.peakMemoryBytes));
107
+ if (run.cpuTimeMs !== undefined) parts.push("CPU " + formatDuration(run.cpuTimeMs));
108
+ return parts.length > 0 ? parts.join(" · ") : undefined;
109
+ }
110
+
110
111
  /**
111
112
  * Monotonic position of a run's status in its lifecycle. Used to reject stale
112
113
  * updates (e.g. a `pending` HTTP response arriving after an SSE already
@@ -16,25 +16,44 @@ import {
16
16
  HeartCrack,
17
17
  } from "@lucide/svelte";
18
18
  import type { Component } from "svelte";
19
- import { displayStatus, type RunStatus, type Run } from "@runwisp/common";
19
+ import type { RunStatus } from "@runwisp/common";
20
20
 
21
- export interface RunStatusConfig {
21
+ interface RunStatusConfig {
22
22
  icon: Component;
23
23
  color: string;
24
24
  bg: string;
25
- border: string;
26
25
  dot: string;
27
26
  badge: string;
28
27
  /** One-sentence explanation of what this status means, for tooltips. */
29
28
  description: string;
30
29
  }
31
30
 
31
+ // Full literal class strings (not built from a tone name) so Tailwind's
32
+ // scanner picks them up.
33
+ const DANGER = {
34
+ color: "text-danger-surface",
35
+ bg: "bg-danger-soft",
36
+ dot: "bg-danger-surface",
37
+ badge: "bg-danger-soft text-danger-soft-text",
38
+ };
39
+ const WARNING = {
40
+ color: "text-warning-surface",
41
+ bg: "bg-warning-soft",
42
+ dot: "bg-warning-surface",
43
+ badge: "bg-warning-soft text-warning-soft-text",
44
+ };
45
+ const NEUTRAL = {
46
+ color: "text-on-surface-muted",
47
+ bg: "bg-surface-sunken",
48
+ dot: "bg-on-surface-faint",
49
+ badge: "bg-surface-sunken text-on-surface",
50
+ };
51
+
32
52
  export const RUN_STATUS_CONFIG: Record<RunStatus, RunStatusConfig> = {
33
53
  running: {
34
54
  icon: LoaderCircle,
35
55
  color: "text-info-surface",
36
56
  bg: "bg-info-soft",
37
- border: "border-info-soft",
38
57
  dot: "bg-info-surface animate-pulse",
39
58
  badge: "bg-info-soft text-info-soft-text",
40
59
  description: "This run is executing right now.",
@@ -43,151 +62,87 @@ export const RUN_STATUS_CONFIG: Record<RunStatus, RunStatusConfig> = {
43
62
  icon: CircleCheck,
44
63
  color: "text-success-surface",
45
64
  bg: "bg-success-soft",
46
- border: "border-success-soft",
47
65
  dot: "bg-success-surface",
48
66
  badge: "bg-success-soft text-success-soft-text",
49
67
  description: "The run finished with exit code 0 - everything OK.",
50
68
  },
51
69
  failed: {
70
+ ...DANGER,
52
71
  icon: CircleX,
53
- color: "text-danger-surface",
54
- bg: "bg-danger-soft",
55
- border: "border-danger-soft",
56
- dot: "bg-danger-surface",
57
- badge: "bg-danger-soft text-danger-soft-text",
58
72
  description: "The run exited with a non-zero code.",
59
73
  },
60
74
  crashed: {
75
+ ...DANGER,
61
76
  icon: CircleAlert,
62
- color: "text-danger-surface",
63
- bg: "bg-danger-soft",
64
- border: "border-danger-soft",
65
- dot: "bg-danger-surface",
66
- badge: "bg-danger-soft text-danger-soft-text",
67
77
  description:
68
78
  "The process was killed, or the daemon found it still 'running' after a hard crash and marked it crashed. It was not resumed.",
69
79
  },
70
80
  stopped: {
81
+ ...WARNING,
71
82
  icon: CircleStop,
72
- color: "text-warning-surface",
73
- bg: "bg-warning-soft",
74
- border: "border-warning-soft",
75
- dot: "bg-warning-surface",
76
- badge: "bg-warning-soft text-warning-soft-text",
77
83
  description: "A human or an external script manually stopped this run before it finished.",
78
84
  },
79
85
  timeout: {
86
+ ...WARNING,
80
87
  icon: TimerOff,
81
- color: "text-warning-surface",
82
- bg: "bg-warning-soft",
83
- border: "border-warning-soft",
84
- dot: "bg-warning-surface",
85
- badge: "bg-warning-soft text-warning-soft-text",
86
88
  description:
87
89
  "The run exceeded its configured timeout and was terminated. Timeout duration can be changed.",
88
90
  },
89
91
  skipped: {
92
+ ...NEUTRAL,
90
93
  icon: SkipForward,
91
- color: "text-on-surface-muted",
92
- bg: "bg-surface-sunken",
93
- border: "border-outline-faint",
94
- dot: "bg-on-surface-faint",
95
- badge: "bg-surface-sunken text-on-surface",
96
94
  description:
97
95
  'Skipped by the concurrency policy (on_overlap = "skip") because a previous run was still going.',
98
96
  },
99
97
  log_overflow: {
98
+ ...DANGER,
100
99
  icon: FileExclamationPoint,
101
- color: "text-danger-surface",
102
- bg: "bg-danger-soft",
103
- border: "border-danger-soft",
104
- dot: "bg-danger-surface",
105
- badge: "bg-danger-soft text-danger-soft-text",
106
100
  description: "The run hit log_max_size and was handled because of log_on_full.",
107
101
  },
108
102
  queue_full: {
103
+ ...WARNING,
109
104
  icon: SkipForward,
110
- color: "text-warning-surface",
111
- bg: "bg-warning-soft",
112
- border: "border-warning-soft",
113
- dot: "bg-warning-surface",
114
- badge: "bg-warning-soft text-warning-soft-text",
115
105
  description: "Skipped because the task's queue was already at max_queued.",
116
106
  },
117
107
  dst_skipped: {
108
+ ...NEUTRAL,
118
109
  icon: SkipForward,
119
- color: "text-on-surface-muted",
120
- bg: "bg-surface-sunken",
121
- border: "border-outline-faint",
122
- dot: "bg-on-surface-faint",
123
- badge: "bg-surface-sunken text-on-surface",
124
110
  description:
125
111
  "Skipped: this cron tick was the duplicate half of a DST fall-back, so it was recorded but not run.",
126
112
  },
127
113
  daemon_stopped: {
114
+ ...WARNING,
128
115
  icon: CircleStop,
129
- color: "text-warning-surface",
130
- bg: "bg-warning-soft",
131
- border: "border-warning-soft",
132
- dot: "bg-warning-surface",
133
- badge: "bg-warning-soft text-warning-soft-text",
134
116
  description:
135
117
  "The daemon shut down while this run was in flight and it exceeded shutdown_timeout; it was not resumed.",
136
118
  },
137
119
  missed: {
120
+ ...DANGER,
138
121
  icon: CalendarX,
139
- color: "text-danger-surface",
140
- bg: "bg-danger-soft",
141
- border: "border-danger-soft",
142
- dot: "bg-danger-surface",
143
- badge: "bg-danger-soft text-danger-soft-text",
144
122
  description:
145
123
  "A scheduled run never happened because the daemon was down. Detected and recorded on restart.",
146
124
  },
147
125
  start_failed: {
126
+ ...DANGER,
148
127
  icon: OctagonX,
149
- color: "text-danger-surface",
150
- bg: "bg-danger-soft",
151
- border: "border-danger-soft",
152
- dot: "bg-danger-surface",
153
- badge: "bg-danger-soft text-danger-soft-text",
154
128
  description:
155
129
  "The run (or service instance) kept failing; after restart_attempts consecutive failures, RunWisp gave up restarting it automatically.",
156
130
  },
157
131
  unhealthy: {
132
+ ...DANGER,
158
133
  icon: HeartCrack,
159
- color: "text-danger-surface",
160
- bg: "bg-danger-soft",
161
- border: "border-danger-soft",
162
- dot: "bg-danger-surface",
163
- badge: "bg-danger-soft text-danger-soft-text",
164
134
  description:
165
135
  "The service instance kept failing its health_check, so RunWisp stopped it. Its restart policy decides what happens next.",
166
136
  },
167
137
  pending: {
138
+ ...NEUTRAL,
168
139
  icon: Clock,
169
- color: "text-on-surface-muted",
170
- bg: "bg-surface-sunken",
171
- border: "border-outline",
172
- dot: "bg-on-surface-faint",
173
- badge: "bg-surface-sunken text-on-surface",
174
140
  description: "Queued and waiting to start.",
175
141
  },
176
142
  ended: {
177
- icon: CircleDashed,
143
+ ...NEUTRAL,
178
144
  color: "text-on-surface-faint",
179
- bg: "bg-surface-sunken",
180
- border: "border-outline-faint",
181
- dot: "bg-on-surface-faint",
182
- badge: "bg-surface-sunken text-on-surface",
145
+ icon: CircleDashed,
183
146
  description: "The run finished. No specific end reason was recorded.",
184
147
  },
185
148
  };
186
-
187
- export function getRunStatusConfig(status: RunStatus): RunStatusConfig {
188
- return RUN_STATUS_CONFIG[status];
189
- }
190
-
191
- export function runDisplayStatus(run: Pick<Run, "status" | "endReason">): RunStatus {
192
- return displayStatus(run.status, run.endReason);
193
- }
@@ -1,32 +1,6 @@
1
1
  // SPDX-FileCopyrightText: PoppyCake, s.r.o.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import type { Task as CommonTask, Run as CommonRun } from "@runwisp/common";
5
-
6
- /**
7
- * Web UI component types.
8
- * Source of truth for status values: {@link RunStatus} from `@runwisp/common`.
9
- */
10
-
11
- export type Task = CommonTask;
12
-
13
- export type Run = CommonRun;
14
-
15
- export interface DaemonState {
16
- name: string;
17
- version: string;
18
- uptime: string;
19
- status: "connected" | "disconnected";
20
- host: string;
21
- backendUrl: string;
22
- cpus: number;
23
- memory: string;
24
- os: string;
25
- arch: string;
26
- workDir: string;
27
- fingerprint: string;
28
- }
29
-
30
4
  export interface DaemonStats {
31
5
  cpuUsage: number;
32
6
  memUsage: number;
@@ -1,14 +1,6 @@
1
1
  // SPDX-FileCopyrightText: PoppyCake, s.r.o.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- // DataGrid's header checkbox (checked / indeterminate / unchecked) must
5
- // reflect which rows on the current page are ACTUALLY selected, not merely
6
- // how many rows are selected. A bare count comparison is wrong the moment
7
- // `selectedRows` holds a different set of rows than `pagedRows` but happens
8
- // to be the same size — e.g. rows selected on a page the grid has since
9
- // paged/filtered away from. Extracted so the membership check is unit
10
- // testable without a component-render harness.
11
-
12
4
  export interface SelectionState {
13
5
  allSelected: boolean;
14
6
  someSelected: boolean;
@@ -17,7 +9,8 @@ export interface SelectionState {
17
9
  /**
18
10
  * Header checkbox state for `pagedRows`: fully checked only when every row
19
11
  * currently on the page is selected, indeterminate when some (but not all)
20
- * of them are. Matches rows by `rowKey`, not by array position or count.
12
+ * of them are. Matches rows by `rowKey`, not by count: the selection can
13
+ * hold rows from a page the grid has since moved away from.
21
14
  */
22
15
  export function selectionState<T>(
23
16
  pagedRows: readonly T[],
@@ -3,7 +3,7 @@
3
3
 
4
4
  // Declarative filter spec. A `FilterField[]` + one bound `FilterValues` object
5
5
  // drives <FilterBar>, the client engine (`applyFilters`), and the URL sync
6
- // helper — the same way DataGrid's `Column[]` drives the table. Add a field
6
+ // helper, the same way DataGrid's `Column[]` drives the table. Add a field
7
7
  // type here, and every table that adopts it renders identically.
8
8
 
9
9
  import type { SelectOption } from "./Select.svelte";
@@ -44,7 +44,7 @@ export type FilterField =
44
44
  primary?: boolean;
45
45
  };
46
46
 
47
- // All values are strings — DOM inputs are string-valued and URL params are
47
+ // All values are strings, DOM inputs are string-valued and URL params are
48
48
  // strings; the client engine coerces (e.g. Number()) where a field needs it.
49
49
  export type FilterValues = Record<string, string>;
50
50
 
@@ -65,7 +65,7 @@ export function isBlank(v: string | undefined): boolean {
65
65
  return !v || v === "all";
66
66
  }
67
67
 
68
- // Which value keys a field owns — daterange owns two, everything else one.
68
+ // Which value keys a field owns, daterange owns two, everything else one.
69
69
  // Used by chips, active-state detection, clear, and URL (de)serialization so
70
70
  // none of them has to special-case the range type.
71
71
  export function fieldKeys(field: FilterField): string[] {
@@ -0,0 +1,11 @@
1
+ // SPDX-FileCopyrightText: PoppyCake, s.r.o.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ export const RADIO_GROUP_KEY = Symbol("radio-group");
5
+
6
+ // What a RadioGroup hands its Radio children: the shared input name and the
7
+ // selected value, read and written through the group's bindable `value`.
8
+ export interface RadioGroupContext {
9
+ readonly name: string;
10
+ value: string;
11
+ }
package/src/lib/index.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  // SPDX-FileCopyrightText: PoppyCake, s.r.o.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- export { default as theme } from "./theme.css?inline";
5
-
6
4
  export { default as Logo } from "./components/Logo.svelte";
7
5
 
8
6
  export { default as PageContainer } from "./components/PageContainer.svelte";
@@ -19,13 +17,6 @@ export { default as Spinner } from "./components/Spinner.svelte";
19
17
 
20
18
  export { default as Button } from "./components/Button.svelte";
21
19
  export { default as LinkButton } from "./components/LinkButton.svelte";
22
- export {
23
- BUTTON_BASE,
24
- BUTTON_SIZES,
25
- BUTTON_VARIANTS,
26
- type ButtonSize,
27
- type ButtonVariant,
28
- } from "./components/button-styles.js";
29
20
  export { default as Checkbox } from "./components/Checkbox.svelte";
30
21
  export { default as CronInput } from "./components/CronInput.svelte";
31
22
  export { default as DurationInput } from "./components/DurationInput.svelte";
@@ -33,7 +24,7 @@ export { default as FormField } from "./components/FormField.svelte";
33
24
  export { default as Input } from "./components/Input.svelte";
34
25
  export { default as Radio } from "./components/Radio.svelte";
35
26
  export { default as RadioGroup } from "./components/RadioGroup.svelte";
36
- export { default as Select, type SelectOption } from "./components/Select.svelte";
27
+ export { default as Select } from "./components/Select.svelte";
37
28
  export { default as Textarea } from "./components/Textarea.svelte";
38
29
  export { default as TimezoneSelect } from "./components/TimezoneSelect.svelte";
39
30
  export { default as Toggle } from "./components/Toggle.svelte";
@@ -88,71 +79,45 @@ export { default as ThemeToggle } from "./components/ThemeToggle.svelte";
88
79
 
89
80
  export { default as CodeBlock } from "./components/CodeBlock.svelte";
90
81
  export { default as TaskCard } from "./components/TaskCard.svelte";
91
- export type { TaskCardAccent } from "./components/TaskCard.svelte";
92
82
 
93
83
  export { default as LogConsole } from "./components/LogConsole.svelte";
94
84
  export type { FetchLogsFn, LogEvent, LogSlice } from "./log-console/types.js";
95
- export { isLogEvent } from "./log-console/types.js";
96
- export { LogCache } from "./log-console/LogCache.svelte.js";
97
- export { LogFetcher } from "./log-console/LogFetcher.svelte.js";
98
85
 
99
86
  export { default as RunDetailPanel } from "./components/dashboard/RunDetailPanel.svelte";
100
87
  export { default as RunsList } from "./components/dashboard/RunsList.svelte";
101
88
  export { default as RunFilterPopover } from "./components/dashboard/RunFilterPopover.svelte";
102
89
  export type { RunOutputMatch } from "./components/dashboard/RunsList.svelte";
103
90
  export {
91
+ activeFilterCount,
104
92
  emptyRunFilters,
105
93
  FAILURE_STATUS_TOKEN,
106
- NEEDS_ATTENTION_STATUSES,
107
- isNeedsAttention,
108
- activeFilterCount,
109
- activeDimensions,
110
- dimensionActive,
111
- clearDimension,
112
- clearPopoverFilters,
113
94
  humanizeStatus,
114
- statusChipLabel,
115
95
  exitCodeRange,
116
96
  type ExitCodeRange,
117
97
  type RunsListFilters,
118
- type RunsListSortDirection,
119
- type FilterDimension,
120
98
  } from "./components/dashboard/run-filters.js";
121
- export {
122
- getRunStatusConfig,
123
- runDisplayStatus,
124
- RUN_STATUS_CONFIG,
125
- type RunStatusConfig,
126
- } from "./components/dashboard/status-config.js";
99
+ export { RUN_STATUS_CONFIG } from "./components/dashboard/status-config.js";
127
100
  export {
128
101
  runDuration,
129
- runStartDelay,
130
102
  runPhaseOrder,
131
103
  formatTriggeredByLabel,
132
- runRetryLabel,
133
104
  instanceSuffix,
134
105
  } from "./components/dashboard/run-helpers.js";
135
- export { humanizeCron, type HumanizedCron } from "./components/dashboard/cron-format.js";
136
- export type { DaemonState, DaemonStats } from "./components/dashboard/types.js";
106
+ export { humanizeCron } from "./components/dashboard/cron-format.js";
107
+ export type { DaemonStats } from "./components/dashboard/types.js";
137
108
 
138
- export { toast, type Toast, type ToastType } from "./utils/toast.svelte.js";
139
- export {
140
- themeStore,
141
- THEME_STORAGE_KEY,
142
- type ThemePreference,
143
- type ResolvedTheme,
144
- } from "./utils/theme.svelte.js";
109
+ export { toast } from "./utils/toast.svelte.js";
145
110
  export { extractErrorMessage } from "./utils/error.js";
146
111
  export {
147
112
  formatBytes,
148
113
  formatRelativeTime,
149
114
  formatRelativeTimeWithAbsolute,
150
- formatDateTime,
151
115
  formatDuration,
152
116
  formatFullDateTime,
153
117
  } from "./utils/format.js";
154
118
  export { formatShortId } from "./utils/id.js";
155
119
  export { debounce } from "./utils/debounce.js";
156
120
  export { TickingNow } from "./utils/ticking-now.svelte.js";
121
+ export { CopyFeedback, copyText } from "./utils/clipboard.svelte.js";
157
122
  export { RunMotion } from "./utils/run-motion.js";
158
- export { arrival, leave, shift, prefersReducedMotion } from "./actions/row-motion.js";
123
+ export { arrival, leave, shift } from "./actions/row-motion.js";
@@ -14,7 +14,7 @@ import { visibleColumns } from "./ansi.js";
14
14
  */
15
15
  // Streams pinned to the front of the overlay, in render order. The companion
16
16
  // Set backs the membership test in `overlayRows`. Both are module-level,
17
- // immutable, and non-reactive — a fixed lookup table, not component state.
17
+ // immutable, and non-reactive, a fixed lookup table, not component state.
18
18
  const PREFERRED_STREAM_ORDER = ["stdout", "stderr"] as const;
19
19
  const PREFERRED_STREAMS: ReadonlySet<string> = new Set(PREFERRED_STREAM_ORDER);
20
20
 
@@ -1,13 +1,12 @@
1
1
  // SPDX-FileCopyrightText: PoppyCake, s.r.o.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import { createLoggerFactory } from "@runwisp/common";
4
+ import { createLogger } from "@runwisp/common";
5
5
  import { SvelteSet } from "svelte/reactivity";
6
6
  import type { LogCache } from "./LogCache.svelte.js";
7
7
  import type { FetchLogsFn } from "./types.js";
8
8
  import { isLogEvent } from "./types.js";
9
9
 
10
- const createLogger = createLoggerFactory();
11
10
  const logger = createLogger("LogFetcher");
12
11
 
13
12
  /**
@@ -20,8 +19,8 @@ const logger = createLogger("LogFetcher");
20
19
  */
21
20
  export class LogFetcher {
22
21
  private readonly cache: LogCache;
23
- private fetchLogsFn: FetchLogsFn | undefined;
24
- private chunkSize: number;
22
+ private readonly fetchLogsFn: FetchLogsFn | undefined;
23
+ private readonly chunkSize = 4096;
25
24
 
26
25
  readonly MAX_INFLIGHT = 2;
27
26
  readonly MIN_REQUEST_INTERVAL_MS = 140;
@@ -38,23 +37,13 @@ export class LogFetcher {
38
37
  constructor(
39
38
  cache: LogCache,
40
39
  fetchLogsFn: FetchLogsFn | undefined,
41
- chunkSize: number = 4096,
42
40
  onDataLoaded?: (min: number, max: number) => void,
43
41
  ) {
44
42
  this.cache = cache;
45
43
  this.fetchLogsFn = fetchLogsFn;
46
- this.chunkSize = Math.max(1, Math.floor(chunkSize));
47
44
  this.onDataLoaded = onDataLoaded;
48
45
  }
49
46
 
50
- setFetchLogsFn(fn: FetchLogsFn | undefined) {
51
- this.fetchLogsFn = fn;
52
- }
53
-
54
- setChunkSize(size: number) {
55
- this.chunkSize = Math.max(1, Math.floor(size));
56
- }
57
-
58
47
  enqueue(from: number, to: number) {
59
48
  if (!this.fetchLogsFn) return;
60
49
 
@@ -45,7 +45,7 @@ const ANSI_ESCAPE = /\x1b\[[0-9;?]*[A-Za-z]/g;
45
45
  const TAB_WIDTH = 8;
46
46
 
47
47
  // Count the visible columns a log line occupies once ANSI escape sequences
48
- // are stripped. Tabs are charged the full tab width — an over-estimate that
48
+ // are stripped. Tabs are charged the full tab width, an over-estimate that
49
49
  // only adds trailing slack to the horizontal scroll surface, never clips.
50
50
  export function visibleColumns(text: string): number {
51
51
  let columns = 0;