@runwisp/ui 0.6.0 → 0.7.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@runwisp/ui",
3
3
  "description": "Svelte 5 UI component library for RunWisp dashboards and admin interfaces.",
4
- "version": "0.6.0",
4
+ "version": "0.7.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -58,8 +58,8 @@
58
58
  "@chromatic-com/storybook": "^4.1.3",
59
59
  "@eslint/compat": "^1.4.0",
60
60
  "@eslint/js": "^9.39.1",
61
- "@runwisp/eslint-config": "0.6.0",
62
- "@runwisp/typescript-config": "0.6.0",
61
+ "@runwisp/eslint-config": "0.7.0",
62
+ "@runwisp/typescript-config": "0.7.0",
63
63
  "@storybook/addon-a11y": "^10.1.9",
64
64
  "@storybook/addon-docs": "^10.1.9",
65
65
  "@storybook/addon-svelte-csf": "^5.0.10",
@@ -107,7 +107,7 @@
107
107
  "@floating-ui/dom": "^1.7.4",
108
108
  "@fontsource/tasa-orbiter": "^5.2.2",
109
109
  "@lucide/svelte": "^0.561.0",
110
- "@runwisp/common": "0.6.0",
110
+ "@runwisp/common": "0.7.0",
111
111
  "@tanstack/svelte-virtual": "^3.13.25",
112
112
  "ansi-to-html": "^0.7.2",
113
113
  "cron-parser": "^5.4.0",
@@ -21,9 +21,23 @@
21
21
  chunkSize?: number;
22
22
  lineHeight?: number;
23
23
  class?: string;
24
+ // highlightLine, when set, scrolls the console so the given absolute
25
+ // line number is visible and pulses a short-lived flash on that row.
26
+ // The pulse is one-shot: changing the prop to a new line restarts the
27
+ // animation; setting it to null clears immediately.
28
+ highlightLine?: number | null;
24
29
  }
25
30
 
26
- let { fetchLogs, chunkSize = 4096, lineHeight = 20, class: className = "" }: Props = $props();
31
+ let {
32
+ fetchLogs,
33
+ chunkSize = 4096,
34
+ lineHeight = 20,
35
+ class: className = "",
36
+ highlightLine = null,
37
+ }: Props = $props();
38
+
39
+ let flashLine = $state<number | null>(null);
40
+ let flashTimer: ReturnType<typeof setTimeout> | null = null;
27
41
 
28
42
  const cache = new LogCache();
29
43
  let fetcher: LogFetcher | null = $state(null);
@@ -202,6 +216,30 @@
202
216
  scrollTop = 0;
203
217
  }
204
218
 
219
+ // Search-hit deep-link: when highlightLine flips to a non-null number,
220
+ // scroll the viewport so that line is centred, and pulse a one-shot
221
+ // flash class for ~1.5s. Cleaning up the timer on prop change keeps a
222
+ // rapid sequence of jumps from leaking timers.
223
+ $effect(() => {
224
+ const target = highlightLine;
225
+ if (target === null || target === undefined) {
226
+ flashLine = null;
227
+ return;
228
+ }
229
+ if (!containerEl) return;
230
+ const targetY = lineTop(target) - containerHeight / 2 + lineHeight / 2;
231
+ const clamped = Math.max(0, Math.min(targetY, totalHeight - containerHeight));
232
+ containerEl.scrollTo({ top: clamped, behavior: "smooth" });
233
+ isAutoScroll = false;
234
+ userScrolledUp = true;
235
+ flashLine = target;
236
+ if (flashTimer !== null) clearTimeout(flashTimer);
237
+ flashTimer = setTimeout(() => {
238
+ flashLine = null;
239
+ flashTimer = null;
240
+ }, 1500);
241
+ });
242
+
205
243
  $effect(() => {
206
244
  if (!containerEl) return;
207
245
  containerHeight = containerEl.clientHeight;
@@ -242,7 +280,10 @@
242
280
 
243
281
  {#each renderedLines as line (line.num)}
244
282
  <div
245
- class="log-line absolute right-0 left-0 flex items-center hover:bg-slate-900/50"
283
+ class="log-line absolute right-0 left-0 flex items-center hover:bg-slate-900/50 {flashLine ===
284
+ line.num
285
+ ? 'log-line--flash'
286
+ : ''}"
246
287
  style="top: {lineTop(line.num)}px; height: {lineHeight}px;"
247
288
  >
248
289
  <div
@@ -376,6 +417,19 @@
376
417
  --log-line-height: 20px;
377
418
  }
378
419
 
420
+ .log-line--flash {
421
+ animation: log-line-flash 1.5s ease-out;
422
+ }
423
+
424
+ @keyframes log-line-flash {
425
+ 0% {
426
+ background-color: rgba(251, 191, 36, 0.45);
427
+ }
428
+ 100% {
429
+ background-color: transparent;
430
+ }
431
+ }
432
+
379
433
  .dot {
380
434
  display: inline-block;
381
435
  width: 4px;
@@ -28,6 +28,7 @@
28
28
  streamLogs,
29
29
  showTaskName = false,
30
30
  onDelete,
31
+ highlightLine = null,
31
32
  }: {
32
33
  run: Run | undefined;
33
34
  fetchLogs: (
@@ -42,6 +43,7 @@
42
43
  ) => () => void;
43
44
  showTaskName?: boolean;
44
45
  onDelete?: (runId: string) => void;
46
+ highlightLine?: number | null;
45
47
  } = $props();
46
48
 
47
49
  let canDelete = $derived.by(() => {
@@ -265,6 +267,7 @@
265
267
  bind:this={logConsole}
266
268
  fetchLogs={(f: number, t: number) => fetchLogs(run.id, f, t)}
267
269
  class="min-h-0 flex-1"
270
+ {highlightLine}
268
271
  />
269
272
  {/key}
270
273
  </div>