@joewinke/jatui 0.21.0 → 0.23.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 (47) hide show
  1. package/CLAUDE.md +47 -1
  2. package/dist/actions/reveal.d.ts +37 -0
  3. package/dist/actions/reveal.js +42 -0
  4. package/dist/components/AudioRecorder.svelte +749 -0
  5. package/dist/components/AudioRecorder.svelte.d.ts +36 -0
  6. package/dist/components/IconSelector.svelte +417 -0
  7. package/dist/components/IconSelector.svelte.d.ts +55 -0
  8. package/dist/components/InfiniteGrid.svelte +305 -0
  9. package/dist/components/InfiniteGrid.svelte.d.ts +71 -0
  10. package/dist/components/InfiniteSearchGrid.svelte +131 -0
  11. package/dist/components/InfiniteSearchGrid.svelte.d.ts +67 -0
  12. package/dist/components/KeyboardShortcutsOverlay.svelte +40 -3
  13. package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +31 -16
  14. package/dist/components/MediaCaptureIcon.svelte +23 -0
  15. package/dist/components/MediaCaptureIcon.svelte.d.ts +1 -1
  16. package/dist/components/ThumbnailGrid.svelte +137 -0
  17. package/dist/components/ThumbnailGrid.svelte.d.ts +42 -0
  18. package/dist/components/batch/BatchJobTable.svelte +549 -0
  19. package/dist/components/batch/BatchJobTable.svelte.d.ts +114 -0
  20. package/dist/components/batch/QuotaMeter.svelte +78 -0
  21. package/dist/components/batch/QuotaMeter.svelte.d.ts +16 -0
  22. package/dist/components/batch/batchQueue.d.ts +56 -0
  23. package/dist/components/batch/batchQueue.js +81 -0
  24. package/dist/components/batch/elapsedEstimate.d.ts +24 -0
  25. package/dist/components/batch/elapsedEstimate.js +25 -0
  26. package/dist/components/batch/pollOperation.d.ts +45 -0
  27. package/dist/components/batch/pollOperation.js +80 -0
  28. package/dist/components/batch/useElapsedEstimate.d.ts +10 -0
  29. package/dist/components/batch/useElapsedEstimate.js +38 -0
  30. package/dist/components/infiniteGridLoader.d.ts +70 -0
  31. package/dist/components/infiniteGridLoader.js +141 -0
  32. package/dist/components/table/columnResize.d.ts +38 -0
  33. package/dist/components/table/columnResize.js +117 -0
  34. package/dist/components/table/index.d.ts +2 -0
  35. package/dist/components/table/index.js +1 -0
  36. package/dist/components/variant/VariantMatrixPicker.svelte +181 -0
  37. package/dist/components/variant/VariantMatrixPicker.svelte.d.ts +37 -0
  38. package/dist/components/variant/variantCombinations.d.ts +75 -0
  39. package/dist/components/variant/variantCombinations.js +100 -0
  40. package/dist/history.d.ts +44 -0
  41. package/dist/history.js +87 -0
  42. package/dist/index.d.ts +30 -0
  43. package/dist/index.js +27 -0
  44. package/dist/styles/motion.css +100 -0
  45. package/dist/utils/keyboardShortcutRegistry.d.ts +152 -0
  46. package/dist/utils/keyboardShortcutRegistry.js +365 -0
  47. package/package.json +5 -1
@@ -0,0 +1,549 @@
1
+ <script module lang="ts">
2
+ import type { JobRow } from './batchQueue';
3
+
4
+ export interface JobContext {
5
+ id: string;
6
+ /** 1-based attempt number. */
7
+ attempt: number;
8
+ /** Aborted when the job or the batch is cancelled — pass it to fetch/pollOperation. */
9
+ signal: AbortSignal;
10
+ /** Report 0..1 progress for the row's bar. */
11
+ reportProgress: (fraction: number) => void;
12
+ }
13
+
14
+ /** Per-row controls handed to the cell snippets. */
15
+ export interface RowControls {
16
+ index: number;
17
+ retry: () => void;
18
+ skip: () => void;
19
+ unskip: () => void;
20
+ cancel: () => void;
21
+ moveUp: () => void;
22
+ moveDown: () => void;
23
+ canMoveUp: boolean;
24
+ canMoveDown: boolean;
25
+ /** Live elapsed ms for this row's current/last attempt. */
26
+ elapsedMs: number;
27
+ }
28
+
29
+ export type BatchPhase = 'idle' | 'running' | 'paused';
30
+
31
+ /** Batch-level controls handed to the `toolbar` snippet. */
32
+ export interface BatchControls {
33
+ phase: BatchPhase;
34
+ start: () => void;
35
+ pause: () => void;
36
+ cancel: () => Promise<void>;
37
+ retryFailed: () => void;
38
+ etaMs: number | null;
39
+ }
40
+
41
+ export type { JobRow };
42
+ </script>
43
+
44
+ <script lang="ts" generics="T">
45
+ /**
46
+ * BatchJobTable (jatui-m4uz3.7) — generic queue runner.
47
+ *
48
+ * Harvested from vid-gen's BatchGenerator: a table of jobs with per-row
49
+ * status / progress / elapsed / retry / skip / reorder, a concurrency cap,
50
+ * an optional per-run process limit (quota budget), a rolling-average ETA,
51
+ * and toast + showConfirm feedback instead of alert()/confirm().
52
+ *
53
+ * The host supplies `jobs` (bind it) and an async `run(data, ctx)`; the
54
+ * component owns the scheduling. Every default cell is a snippet override.
55
+ * Mount <ToastHost /> and <ConfirmDialog /> once in the app root.
56
+ */
57
+ import { onDestroy, onMount, type Snippet } from 'svelte';
58
+ import { showToast } from '../../stores/toast';
59
+ import { showConfirm } from '../../stores/confirmDialog';
60
+ import {
61
+ formatElapsed,
62
+ isProcessLimitReached,
63
+ moveJob,
64
+ nextJobsToStart,
65
+ summarizeJobs,
66
+ type BatchSummary,
67
+ type JobStatus
68
+ } from './batchQueue';
69
+ import { useElapsedEstimate } from './useElapsedEstimate';
70
+
71
+ interface Props {
72
+ jobs: JobRow<T>[];
73
+ run: (data: T, ctx: JobContext) => Promise<unknown>;
74
+ /** Max jobs running at once. Default 3. */
75
+ concurrencyLimit?: number;
76
+ /** Max jobs to start per run (Start → drained). `null` = unlimited. */
77
+ processLimit?: number | null;
78
+ autoStart?: boolean;
79
+ /** Toast on batch completion / process limit. Default true. */
80
+ notify?: boolean;
81
+ /** showConfirm before cancelling running jobs or clearing rows. Default true. */
82
+ confirmDestructive?: boolean;
83
+ /** Accessible name for the table. */
84
+ caption?: string;
85
+ emptyText?: string;
86
+ /** Extra <th> cells for host data columns (after the # column). */
87
+ headers?: Snippet;
88
+ /** Matching <td> cells for each row. */
89
+ cells?: Snippet<[JobRow<T>, RowControls]>;
90
+ statusCell?: Snippet<[JobRow<T>, RowControls]>;
91
+ progressCell?: Snippet<[JobRow<T>, RowControls]>;
92
+ elapsedCell?: Snippet<[JobRow<T>, RowControls]>;
93
+ actionsCell?: Snippet<[JobRow<T>, RowControls]>;
94
+ /** Extra toolbar content (e.g. a QuotaMeter), right-aligned. */
95
+ toolbar?: Snippet<[BatchControls]>;
96
+ onJobDone?: (job: JobRow<T>) => void;
97
+ onJobFailed?: (job: JobRow<T>, error: unknown) => void;
98
+ onBatchDone?: (summary: BatchSummary) => void;
99
+ class?: string;
100
+ }
101
+
102
+ let {
103
+ jobs = $bindable(),
104
+ run,
105
+ concurrencyLimit = 3,
106
+ processLimit = null,
107
+ autoStart = false,
108
+ notify = true,
109
+ confirmDestructive = true,
110
+ caption = 'Batch jobs',
111
+ emptyText = 'No jobs in the queue.',
112
+ headers,
113
+ cells,
114
+ statusCell,
115
+ progressCell,
116
+ elapsedCell,
117
+ actionsCell,
118
+ toolbar,
119
+ onJobDone,
120
+ onJobFailed,
121
+ onBatchDone,
122
+ class: className = ''
123
+ }: Props = $props();
124
+
125
+ let phase = $state<BatchPhase>('idle');
126
+ let startedThisRun = 0;
127
+ let settledThisRun = 0;
128
+ const controllers = new Map<string, AbortController>();
129
+ const eta = useElapsedEstimate({ window: 10 });
130
+
131
+ const summary = $derived(summarizeJobs(jobs));
132
+
133
+ // Ticking clock for elapsed/ETA — only while something is running.
134
+ let now = $state(Date.now());
135
+ $effect(() => {
136
+ if (summary.running === 0) return;
137
+ now = Date.now();
138
+ const t = setInterval(() => (now = Date.now()), 500);
139
+ return () => clearInterval(t);
140
+ });
141
+
142
+ function elapsedOf(job: JobRow<T>): number {
143
+ if (job.startedAt == null) return 0;
144
+ return (job.finishedAt ?? now) - job.startedAt;
145
+ }
146
+
147
+ const etaMs = $derived(
148
+ summary.queued + summary.running === 0
149
+ ? null
150
+ : eta.remainingMs(
151
+ summary.queued,
152
+ jobs.filter((j) => j.status === 'running').map(elapsedOf),
153
+ concurrencyLimit
154
+ )
155
+ );
156
+
157
+ const find = (id: string) => jobs.find((j) => j.id === id);
158
+
159
+ // ── Scheduling ──────────────────────────────────────────────────────────
160
+ function limits() {
161
+ return { concurrencyLimit, processLimit, startedThisRun };
162
+ }
163
+
164
+ function dispatch() {
165
+ if (phase !== 'running') return;
166
+ const ids = nextJobsToStart(jobs, limits());
167
+ for (const id of ids) void startJob(id);
168
+ if (ids.length === 0 && summarizeJobs(jobs).running === 0) finishRun();
169
+ }
170
+
171
+ function finishRun() {
172
+ phase = 'idle';
173
+ if (settledThisRun === 0) return;
174
+ const s = summarizeJobs(jobs);
175
+ const limited = isProcessLimitReached(jobs, limits());
176
+ if (notify) {
177
+ if (limited) {
178
+ showToast({
179
+ type: 'warning',
180
+ message: `Process limit of ${processLimit} reached — ${s.queued} job${s.queued === 1 ? '' : 's'} left queued.`
181
+ });
182
+ } else if (s.failed > 0) {
183
+ showToast({ type: 'warning', message: `Batch finished: ${s.done} done, ${s.failed} failed.` });
184
+ } else {
185
+ showToast({ type: 'success', message: `Batch finished: ${s.done} done.` });
186
+ }
187
+ }
188
+ onBatchDone?.(s);
189
+ }
190
+
191
+ async function startJob(id: string) {
192
+ const job = find(id);
193
+ if (!job || job.status !== 'queued') return;
194
+ const ctrl = new AbortController();
195
+ controllers.set(id, ctrl);
196
+ startedThisRun++;
197
+ job.status = 'running';
198
+ job.attempts += 1;
199
+ job.progress = 0;
200
+ job.startedAt = Date.now();
201
+ job.finishedAt = undefined;
202
+ job.error = undefined;
203
+
204
+ const ctx: JobContext = {
205
+ id,
206
+ attempt: job.attempts,
207
+ signal: ctrl.signal,
208
+ reportProgress: (fraction) => {
209
+ const j = find(id);
210
+ if (j && !ctrl.signal.aborted && j.status === 'running') {
211
+ j.progress = Math.min(1, Math.max(0, fraction));
212
+ }
213
+ }
214
+ };
215
+
216
+ let outcome: JobStatus;
217
+ let result: unknown;
218
+ let error: unknown;
219
+ try {
220
+ result = await run($state.snapshot(job.data) as T, ctx);
221
+ outcome = ctrl.signal.aborted ? 'cancelled' : 'done';
222
+ } catch (err) {
223
+ error = err;
224
+ outcome = ctrl.signal.aborted ? 'cancelled' : 'failed';
225
+ }
226
+ controllers.delete(id);
227
+
228
+ const j = find(id);
229
+ if (j) {
230
+ j.status = outcome;
231
+ j.finishedAt = Date.now();
232
+ if (outcome === 'done') {
233
+ j.progress = 1;
234
+ j.result = result;
235
+ if (j.startedAt != null) eta.record(j.finishedAt - j.startedAt);
236
+ onJobDone?.(j);
237
+ } else if (outcome === 'failed') {
238
+ j.error = error instanceof Error ? error.message : String(error);
239
+ onJobFailed?.(j, error);
240
+ }
241
+ settledThisRun++;
242
+ }
243
+ dispatch();
244
+ }
245
+
246
+ // ── Batch controls ──────────────────────────────────────────────────────
247
+ export function start() {
248
+ if (phase === 'running') return;
249
+ if (phase === 'idle') {
250
+ startedThisRun = 0;
251
+ settledThisRun = 0;
252
+ }
253
+ phase = 'running';
254
+ dispatch();
255
+ }
256
+
257
+ export function pause() {
258
+ if (phase === 'running') phase = 'paused';
259
+ }
260
+
261
+ export async function cancel() {
262
+ const running = summarizeJobs(jobs).running;
263
+ if (running > 0 && confirmDestructive) {
264
+ const ok = await showConfirm({
265
+ title: 'Cancel batch?',
266
+ body: `${running} running job${running === 1 ? '' : 's'} will be aborted. Queued jobs stay queued.`,
267
+ confirmLabel: 'Cancel batch',
268
+ cancelLabel: 'Keep running',
269
+ danger: true
270
+ });
271
+ if (!ok) return;
272
+ }
273
+ phase = 'idle';
274
+ for (const c of controllers.values()) c.abort();
275
+ }
276
+
277
+ export function retryFailed() {
278
+ let n = 0;
279
+ for (const j of jobs) {
280
+ if (j.status === 'failed' || j.status === 'cancelled') {
281
+ requeue(j);
282
+ n++;
283
+ }
284
+ }
285
+ if (n > 0) dispatch();
286
+ }
287
+
288
+ export async function clearFinished() {
289
+ const n = jobs.filter((j) => j.status === 'done' || j.status === 'skipped').length;
290
+ if (n === 0) return;
291
+ if (confirmDestructive) {
292
+ const ok = await showConfirm({
293
+ title: 'Clear finished jobs?',
294
+ body: `Remove ${n} done or skipped row${n === 1 ? '' : 's'} from the table.`,
295
+ confirmLabel: 'Clear',
296
+ danger: true
297
+ });
298
+ if (!ok) return;
299
+ }
300
+ jobs = jobs.filter((j) => j.status !== 'done' && j.status !== 'skipped');
301
+ }
302
+
303
+ function requeue(j: JobRow<T>) {
304
+ j.status = 'queued';
305
+ j.error = undefined;
306
+ j.progress = undefined;
307
+ j.startedAt = undefined;
308
+ j.finishedAt = undefined;
309
+ }
310
+
311
+ // ── Row controls ────────────────────────────────────────────────────────
312
+ function controlsFor(job: JobRow<T>, index: number): RowControls {
313
+ return {
314
+ index,
315
+ elapsedMs: elapsedOf(job),
316
+ canMoveUp: job.status === 'queued' && index > 0,
317
+ canMoveDown: job.status === 'queued' && index < jobs.length - 1,
318
+ retry: () => {
319
+ const j = find(job.id);
320
+ if (j && (j.status === 'failed' || j.status === 'cancelled')) {
321
+ requeue(j);
322
+ dispatch();
323
+ }
324
+ },
325
+ skip: () => {
326
+ const j = find(job.id);
327
+ if (j?.status === 'queued') j.status = 'skipped';
328
+ },
329
+ unskip: () => {
330
+ const j = find(job.id);
331
+ if (j?.status === 'skipped') {
332
+ j.status = 'queued';
333
+ dispatch();
334
+ }
335
+ },
336
+ cancel: () => controllers.get(job.id)?.abort(),
337
+ moveUp: () => (jobs = moveJob(jobs, job.id, -1)),
338
+ moveDown: () => (jobs = moveJob(jobs, job.id, 1))
339
+ };
340
+ }
341
+
342
+ onMount(() => {
343
+ if (autoStart) start();
344
+ });
345
+ onDestroy(() => {
346
+ for (const c of controllers.values()) c.abort();
347
+ });
348
+
349
+ const batchControls: BatchControls = {
350
+ get phase() {
351
+ return phase;
352
+ },
353
+ get etaMs() {
354
+ return etaMs;
355
+ },
356
+ start,
357
+ pause,
358
+ cancel,
359
+ retryFailed
360
+ };
361
+
362
+ const STATUS_META: Record<JobStatus, { label: string; cls: string }> = {
363
+ queued: { label: 'Queued', cls: 'badge-ghost' },
364
+ running: { label: 'Running', cls: 'badge-info' },
365
+ done: { label: 'Done', cls: 'badge-success' },
366
+ failed: { label: 'Failed', cls: 'badge-error' },
367
+ skipped: { label: 'Skipped', cls: 'badge-neutral' },
368
+ cancelled: { label: 'Cancelled', cls: 'badge-warning' }
369
+ };
370
+
371
+ const retryable = $derived(summary.failed + summary.cancelled);
372
+ const clearable = $derived(summary.done + summary.skipped);
373
+ const hasWork = $derived(summary.queued > 0);
374
+ </script>
375
+
376
+ <div class="flex flex-col gap-3 {className}">
377
+ <!-- Toolbar -->
378
+ <div class="flex flex-wrap items-center gap-2">
379
+ {#if phase === 'running'}
380
+ <button type="button" class="btn btn-sm" onclick={pause}>Pause</button>
381
+ {:else}
382
+ <button type="button" class="btn btn-sm btn-primary" disabled={!hasWork} onclick={start}>
383
+ {phase === 'paused' ? 'Resume' : 'Start'}
384
+ </button>
385
+ {/if}
386
+ <button
387
+ type="button"
388
+ class="btn btn-sm btn-ghost"
389
+ disabled={phase === 'idle' && summary.running === 0}
390
+ onclick={cancel}
391
+ >
392
+ Cancel
393
+ </button>
394
+ <button type="button" class="btn btn-sm btn-ghost" disabled={retryable === 0} onclick={retryFailed}>
395
+ Retry failed{retryable ? ` (${retryable})` : ''}
396
+ </button>
397
+ <button type="button" class="btn btn-sm btn-ghost" disabled={clearable === 0} onclick={clearFinished}>
398
+ Clear finished
399
+ </button>
400
+ {#if toolbar}
401
+ <div class="ml-auto">{@render toolbar(batchControls)}</div>
402
+ {/if}
403
+ </div>
404
+
405
+ <!-- Overall progress -->
406
+ <div class="flex flex-col gap-1">
407
+ <div class="flex flex-wrap items-baseline justify-between gap-2 text-xs" aria-live="polite">
408
+ <span class="tabular-nums">
409
+ <span class="font-medium">{summary.settled} / {summary.total}</span>
410
+ <span class="text-base-content/60">
411
+ · {summary.done} done{#if summary.failed}, <span class="text-error">{summary.failed} failed</span
412
+ >{/if}{#if summary.running}, {summary.running} running{/if}{#if summary.queued}, {summary.queued}
413
+ queued{/if}
414
+ </span>
415
+ </span>
416
+ <span class="text-base-content/60 tabular-nums">
417
+ {#if phase === 'paused'}
418
+ Paused
419
+ {:else if etaMs != null && phase === 'running'}
420
+ ETA {formatElapsed(etaMs)}
421
+ {:else if phase === 'running'}
422
+ Estimating…
423
+ {/if}
424
+ </span>
425
+ </div>
426
+ <progress
427
+ class="progress progress-primary h-1.5 w-full"
428
+ value={summary.fraction}
429
+ max="1"
430
+ aria-label="Batch progress: {summary.settled} of {summary.total} settled"
431
+ ></progress>
432
+ </div>
433
+
434
+ <div class="overflow-x-auto">
435
+ <table class="table table-sm">
436
+ <caption class="sr-only">{caption}</caption>
437
+ <thead>
438
+ <tr>
439
+ <th class="w-8">#</th>
440
+ {@render headers?.()}
441
+ <th>Status</th>
442
+ <th class="w-40">Progress</th>
443
+ <th class="w-20">Elapsed</th>
444
+ <th class="w-px text-right"><span class="sr-only">Actions</span></th>
445
+ </tr>
446
+ </thead>
447
+ <tbody>
448
+ {#each jobs as job, index (job.id)}
449
+ {@const c = controlsFor(job, index)}
450
+ <tr class={job.status === 'skipped' || job.status === 'cancelled' ? 'opacity-60' : ''}>
451
+ <td class="text-base-content/50 tabular-nums">{index + 1}</td>
452
+ {@render cells?.(job, c)}
453
+ <td>
454
+ {#if statusCell}
455
+ {@render statusCell(job, c)}
456
+ {:else}
457
+ <span class="badge badge-sm {STATUS_META[job.status].cls}">
458
+ {STATUS_META[job.status].label}{job.attempts > 1 ? ` · try ${job.attempts}` : ''}
459
+ </span>
460
+ {#if job.error}
461
+ <p class="text-error mt-1 max-w-xs truncate text-xs" title={job.error}>{job.error}</p>
462
+ {/if}
463
+ {/if}
464
+ </td>
465
+ <td>
466
+ {#if progressCell}
467
+ {@render progressCell(job, c)}
468
+ {:else if job.status === 'running'}
469
+ <progress
470
+ class="progress progress-info h-1.5 w-full"
471
+ value={job.progress ?? undefined}
472
+ max="1"
473
+ aria-label="Job {index + 1} progress"
474
+ ></progress>
475
+ {:else if job.status === 'done'}
476
+ <progress class="progress progress-success h-1.5 w-full" value="1" max="1" aria-hidden="true"
477
+ ></progress>
478
+ {/if}
479
+ </td>
480
+ <td class="tabular-nums">
481
+ {#if elapsedCell}
482
+ {@render elapsedCell(job, c)}
483
+ {:else if job.startedAt != null}
484
+ {formatElapsed(c.elapsedMs)}
485
+ {:else}
486
+ <span class="text-base-content/40">—</span>
487
+ {/if}
488
+ </td>
489
+ <td class="text-right whitespace-nowrap">
490
+ {#if actionsCell}
491
+ {@render actionsCell(job, c)}
492
+ {:else}
493
+ <div class="join">
494
+ {#if job.status === 'queued'}
495
+ <button
496
+ type="button"
497
+ class="btn btn-ghost btn-xs join-item"
498
+ disabled={!c.canMoveUp}
499
+ aria-label="Move job {index + 1} up"
500
+ onclick={c.moveUp}>↑</button
501
+ >
502
+ <button
503
+ type="button"
504
+ class="btn btn-ghost btn-xs join-item"
505
+ disabled={!c.canMoveDown}
506
+ aria-label="Move job {index + 1} down"
507
+ onclick={c.moveDown}>↓</button
508
+ >
509
+ <button
510
+ type="button"
511
+ class="btn btn-ghost btn-xs join-item"
512
+ aria-label="Skip job {index + 1}"
513
+ onclick={c.skip}>Skip</button
514
+ >
515
+ {:else if job.status === 'running'}
516
+ <button
517
+ type="button"
518
+ class="btn btn-ghost btn-xs join-item"
519
+ aria-label="Cancel job {index + 1}"
520
+ onclick={c.cancel}>Cancel</button
521
+ >
522
+ {:else if job.status === 'failed' || job.status === 'cancelled'}
523
+ <button
524
+ type="button"
525
+ class="btn btn-ghost btn-xs join-item"
526
+ aria-label="Retry job {index + 1}"
527
+ onclick={c.retry}>Retry</button
528
+ >
529
+ {:else if job.status === 'skipped'}
530
+ <button
531
+ type="button"
532
+ class="btn btn-ghost btn-xs join-item"
533
+ aria-label="Restore job {index + 1} to the queue"
534
+ onclick={c.unskip}>Unskip</button
535
+ >
536
+ {/if}
537
+ </div>
538
+ {/if}
539
+ </td>
540
+ </tr>
541
+ {:else}
542
+ <tr>
543
+ <td colspan="99" class="text-base-content/50 py-8 text-center">{emptyText}</td>
544
+ </tr>
545
+ {/each}
546
+ </tbody>
547
+ </table>
548
+ </div>
549
+ </div>
@@ -0,0 +1,114 @@
1
+ import type { JobRow } from './batchQueue';
2
+ export interface JobContext {
3
+ id: string;
4
+ /** 1-based attempt number. */
5
+ attempt: number;
6
+ /** Aborted when the job or the batch is cancelled — pass it to fetch/pollOperation. */
7
+ signal: AbortSignal;
8
+ /** Report 0..1 progress for the row's bar. */
9
+ reportProgress: (fraction: number) => void;
10
+ }
11
+ /** Per-row controls handed to the cell snippets. */
12
+ export interface RowControls {
13
+ index: number;
14
+ retry: () => void;
15
+ skip: () => void;
16
+ unskip: () => void;
17
+ cancel: () => void;
18
+ moveUp: () => void;
19
+ moveDown: () => void;
20
+ canMoveUp: boolean;
21
+ canMoveDown: boolean;
22
+ /** Live elapsed ms for this row's current/last attempt. */
23
+ elapsedMs: number;
24
+ }
25
+ export type BatchPhase = 'idle' | 'running' | 'paused';
26
+ /** Batch-level controls handed to the `toolbar` snippet. */
27
+ export interface BatchControls {
28
+ phase: BatchPhase;
29
+ start: () => void;
30
+ pause: () => void;
31
+ cancel: () => Promise<void>;
32
+ retryFailed: () => void;
33
+ etaMs: number | null;
34
+ }
35
+ export type { JobRow };
36
+ /**
37
+ * BatchJobTable (jatui-m4uz3.7) — generic queue runner.
38
+ *
39
+ * Harvested from vid-gen's BatchGenerator: a table of jobs with per-row
40
+ * status / progress / elapsed / retry / skip / reorder, a concurrency cap,
41
+ * an optional per-run process limit (quota budget), a rolling-average ETA,
42
+ * and toast + showConfirm feedback instead of alert()/confirm().
43
+ *
44
+ * The host supplies `jobs` (bind it) and an async `run(data, ctx)`; the
45
+ * component owns the scheduling. Every default cell is a snippet override.
46
+ * Mount <ToastHost /> and <ConfirmDialog /> once in the app root.
47
+ */
48
+ import { type Snippet } from 'svelte';
49
+ import { type BatchSummary } from './batchQueue';
50
+ declare function $$render<T>(): {
51
+ props: {
52
+ jobs: JobRow<T>[];
53
+ run: (data: T, ctx: JobContext) => Promise<unknown>;
54
+ /** Max jobs running at once. Default 3. */
55
+ concurrencyLimit?: number;
56
+ /** Max jobs to start per run (Start → drained). `null` = unlimited. */
57
+ processLimit?: number | null;
58
+ autoStart?: boolean;
59
+ /** Toast on batch completion / process limit. Default true. */
60
+ notify?: boolean;
61
+ /** showConfirm before cancelling running jobs or clearing rows. Default true. */
62
+ confirmDestructive?: boolean;
63
+ /** Accessible name for the table. */
64
+ caption?: string;
65
+ emptyText?: string;
66
+ /** Extra <th> cells for host data columns (after the # column). */
67
+ headers?: Snippet;
68
+ /** Matching <td> cells for each row. */
69
+ cells?: Snippet<[JobRow<T>, RowControls]>;
70
+ statusCell?: Snippet<[JobRow<T>, RowControls]>;
71
+ progressCell?: Snippet<[JobRow<T>, RowControls]>;
72
+ elapsedCell?: Snippet<[JobRow<T>, RowControls]>;
73
+ actionsCell?: Snippet<[JobRow<T>, RowControls]>;
74
+ /** Extra toolbar content (e.g. a QuotaMeter), right-aligned. */
75
+ toolbar?: Snippet<[BatchControls]>;
76
+ onJobDone?: (job: JobRow<T>) => void;
77
+ onJobFailed?: (job: JobRow<T>, error: unknown) => void;
78
+ onBatchDone?: (summary: BatchSummary) => void;
79
+ class?: string;
80
+ };
81
+ exports: {
82
+ start: () => void;
83
+ pause: () => void;
84
+ cancel: () => Promise<void>;
85
+ retryFailed: () => void;
86
+ clearFinished: () => Promise<void>;
87
+ };
88
+ bindings: "jobs";
89
+ slots: {};
90
+ events: {};
91
+ };
92
+ declare class __sveltets_Render<T> {
93
+ props(): ReturnType<typeof $$render<T>>['props'];
94
+ events(): ReturnType<typeof $$render<T>>['events'];
95
+ slots(): ReturnType<typeof $$render<T>>['slots'];
96
+ bindings(): "jobs";
97
+ exports(): {
98
+ start: () => void;
99
+ pause: () => void;
100
+ cancel: () => Promise<void>;
101
+ retryFailed: () => void;
102
+ clearFinished: () => Promise<void>;
103
+ };
104
+ }
105
+ interface $$IsomorphicComponent {
106
+ new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
107
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
108
+ } & ReturnType<__sveltets_Render<T>['exports']>;
109
+ <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
110
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
111
+ }
112
+ declare const BatchJobTable: $$IsomorphicComponent;
113
+ type BatchJobTable<T> = InstanceType<typeof BatchJobTable<T>>;
114
+ export default BatchJobTable;