effective-progress 0.10.0 → 0.11.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.
package/dist/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
+ import { t as __exportAll } from "./chunk-DQk6qfdC.mjs";
1
2
  import { Brand, Cause, Clock, Context, Effect, Exit, FiberRef, Layer, Option, Schema } from "effect";
2
3
  import { dual } from "effect/Function";
3
- import { Box, Text, render } from "ink";
4
- import stringWidth from "fast-string-width";
4
+ import { Box, Text, render, useBoxMetrics } from "ink";
5
+ import { createContext, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
5
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
7
  import cliSpinners from "cli-spinners";
7
- import { createContext, memo, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
8
- import { VirtualList } from "ink-virtual-list";
8
+ import stringWidth from "fast-string-width";
9
9
 
10
10
  //#region src/types.ts
11
11
  const TaskIdSchema = Schema.Number.pipe(Schema.brand("TaskId"));
@@ -18,6 +18,10 @@ const TaskUnitsSchema = Schema.Struct({
18
18
  processed: Schema.Number,
19
19
  total: Schema.optional(Schema.Number)
20
20
  });
21
+ const TaskProgressSampleSchema = Schema.Struct({
22
+ timestamp: Schema.Number,
23
+ processed: Schema.Number
24
+ });
21
25
  const TaskSnapshotSchema = Schema.Struct({
22
26
  id: TaskIdSchema,
23
27
  parentId: Schema.NullOr(TaskIdSchema),
@@ -27,7 +31,9 @@ const TaskSnapshotSchema = Schema.Struct({
27
31
  transient: Schema.Boolean,
28
32
  units: TaskUnitsSchema,
29
33
  startedAt: Schema.Number,
30
- completedAt: Schema.NullOr(Schema.Number)
34
+ completedAt: Schema.NullOr(Schema.Number),
35
+ progressSamples: Schema.Array(TaskProgressSampleSchema),
36
+ metadata: Schema.Unknown
31
37
  });
32
38
  const TaskSnapshot = (snapshot) => snapshot;
33
39
  var Task = class extends Context.Tag("stromseng.dev/effective-progress/Task")() {};
@@ -62,6 +68,8 @@ const decodeProgressTaskEvent = Schema.decodeUnknownSync(ProgressTaskEventSchema
62
68
 
63
69
  //#endregion
64
70
  //#region src/renderer/store.ts
71
+ const ETA_SAMPLE_WINDOW_MILLIS = 3e4;
72
+ const ETA_SAMPLE_MAX_LENGTH = 1e3;
65
73
  const hasExplicitTotal = (options) => Object.prototype.hasOwnProperty.call(options, "total");
66
74
  const sanitizeTotalOnAdd = (total) => {
67
75
  if (total === void 0) return;
@@ -85,7 +93,25 @@ const normalizeUnits = (counts) => {
85
93
  total: counts.total
86
94
  };
87
95
  };
88
- const updatedSnapshot = (snapshot, options) => {
96
+ /**
97
+ * Returns a new progress sample deque with the latest processed count appended.
98
+ *
99
+ * Samples are retained for the ETA rolling window and capped by count so very chatty tasks do not
100
+ * grow memory without bound.
101
+ */
102
+ const appendProgressSample = (samples, now, processed) => {
103
+ const previousSamples = samples ?? [];
104
+ if (previousSamples.at(-1)?.processed === processed) return previousSamples;
105
+ const windowStart = now - ETA_SAMPLE_WINDOW_MILLIS;
106
+ const nextSamples = [...previousSamples, {
107
+ timestamp: now,
108
+ processed
109
+ }];
110
+ while (nextSamples.length > 2 && (nextSamples.length > ETA_SAMPLE_MAX_LENGTH || nextSamples[1].timestamp < windowStart)) nextSamples.shift();
111
+ return nextSamples;
112
+ };
113
+ /** Applies mutable task fields and records a progress sample when the processed count changes. */
114
+ const updatedSnapshot = (snapshot, options, now) => {
89
115
  const currentUnits = snapshot.units;
90
116
  const units = options.succeeded === void 0 && options.failed === void 0 && options.total === void 0 && !hasExplicitTotal(options) ? currentUnits : normalizeUnits({
91
117
  succeeded: options.succeeded ?? currentUnits.succeeded,
@@ -93,28 +119,14 @@ const updatedSnapshot = (snapshot, options) => {
93
119
  total: hasExplicitTotal(options) ? sanitizeTotalOnUpdate(options.total) : currentUnits.total
94
120
  });
95
121
  return TaskSnapshot({
96
- id: snapshot.id,
97
- parentId: snapshot.parentId,
122
+ ...snapshot,
98
123
  description: options.description ?? snapshot.description,
99
- status: snapshot.status,
100
124
  countDisplay: options.countDisplay ?? snapshot.countDisplay,
101
125
  transient: options.transient ?? snapshot.transient,
102
126
  units,
103
- startedAt: snapshot.startedAt,
104
- completedAt: snapshot.completedAt
127
+ progressSamples: appendProgressSample(snapshot.progressSamples, now, units.processed)
105
128
  });
106
129
  };
107
- const withTransient = (snapshot, transient) => TaskSnapshot({
108
- id: snapshot.id,
109
- parentId: snapshot.parentId,
110
- description: snapshot.description,
111
- status: snapshot.status,
112
- countDisplay: snapshot.countDisplay,
113
- transient,
114
- units: snapshot.units,
115
- startedAt: snapshot.startedAt,
116
- completedAt: snapshot.completedAt
117
- });
118
130
  const findInsertionIndex = (renderOrder, parentId) => {
119
131
  if (parentId === null) return {
120
132
  index: renderOrder.length,
@@ -156,7 +168,8 @@ const makeProgressRenderStore = () => {
156
168
  let nextTaskId = 0;
157
169
  let state = {
158
170
  tasks: /* @__PURE__ */ new Map(),
159
- renderOrder: []
171
+ renderOrder: [],
172
+ columns: /* @__PURE__ */ new Map()
160
173
  };
161
174
  let pendingEvents = [];
162
175
  let publishedPublication = {
@@ -245,7 +258,12 @@ const makeProgressRenderStore = () => {
245
258
  transient: (parentSnapshot?.transient ?? false) || (options.transient ?? false),
246
259
  units,
247
260
  startedAt: now,
248
- completedAt: null
261
+ completedAt: null,
262
+ progressSamples: [{
263
+ timestamp: now,
264
+ processed: units.processed
265
+ }],
266
+ metadata: options.metadata
249
267
  });
250
268
  updateState((current) => {
251
269
  const nextTasks = new Map(current.tasks);
@@ -259,7 +277,8 @@ const makeProgressRenderStore = () => {
259
277
  return {
260
278
  state: {
261
279
  tasks: nextTasks,
262
- renderOrder: nextRenderOrder
280
+ renderOrder: nextRenderOrder,
281
+ columns: options.columns ? new Map(current.columns).set(taskId, options.columns) : current.columns
263
282
  },
264
283
  events: [new TaskAddedEvent({
265
284
  taskId,
@@ -273,14 +292,15 @@ const makeProgressRenderStore = () => {
273
292
  });
274
293
  return taskId;
275
294
  }),
276
- updateTask: (taskId, options) => Effect.sync(() => {
295
+ updateTask: (taskId, options) => Effect.gen(function* () {
296
+ const now = yield* Clock.currentTimeMillis;
277
297
  updateState((current) => {
278
298
  const currentTask = current.tasks.get(taskId);
279
299
  if (!currentTask) return {
280
300
  state: current,
281
301
  events: []
282
302
  };
283
- const nextTask = updatedSnapshot(currentTask, options);
303
+ const nextTask = updatedSnapshot(currentTask, options, now);
284
304
  const nextTasks = new Map(current.tasks);
285
305
  nextTasks.set(taskId, nextTask);
286
306
  const events = [new TaskUpdatedEvent({
@@ -296,7 +316,10 @@ const makeProgressRenderStore = () => {
296
316
  if (options.transient !== void 0) for (const candidateId of subtreeTaskIds(current.renderOrder, taskId).slice(1)) {
297
317
  const candidate = current.tasks.get(candidateId);
298
318
  if (!candidate) continue;
299
- const nextCandidate = withTransient(candidate, nextTask.transient);
319
+ const nextCandidate = TaskSnapshot({
320
+ ...candidate,
321
+ transient: nextTask.transient
322
+ });
300
323
  nextTasks.set(candidateId, nextCandidate);
301
324
  events.push(new TaskUpdatedEvent({
302
325
  taskId: candidateId,
@@ -306,39 +329,37 @@ const makeProgressRenderStore = () => {
306
329
  return {
307
330
  state: {
308
331
  tasks: nextTasks,
309
- renderOrder: current.renderOrder
332
+ renderOrder: current.renderOrder,
333
+ columns: current.columns
310
334
  },
311
335
  events
312
336
  };
313
337
  });
314
338
  }),
315
- incrementSucceeded: (taskId, amount = 1) => Effect.sync(() => {
339
+ incrementSucceeded: (taskId, amount = 1) => Effect.gen(function* () {
340
+ const now = yield* Clock.currentTimeMillis;
316
341
  updateState((current) => {
317
342
  const currentTask = current.tasks.get(taskId);
318
343
  if (!currentTask) return {
319
344
  state: current,
320
345
  events: []
321
346
  };
347
+ const units = normalizeUnits({
348
+ succeeded: currentTask.units.succeeded + amount,
349
+ failed: currentTask.units.failed,
350
+ total: currentTask.units.total
351
+ });
322
352
  const nextTasks = new Map(current.tasks);
323
353
  nextTasks.set(taskId, TaskSnapshot({
324
- id: currentTask.id,
325
- parentId: currentTask.parentId,
326
- description: currentTask.description,
327
- status: currentTask.status,
328
- countDisplay: currentTask.countDisplay,
329
- transient: currentTask.transient,
330
- units: normalizeUnits({
331
- succeeded: currentTask.units.succeeded + amount,
332
- failed: currentTask.units.failed,
333
- total: currentTask.units.total
334
- }),
335
- startedAt: currentTask.startedAt,
336
- completedAt: currentTask.completedAt
354
+ ...currentTask,
355
+ units,
356
+ progressSamples: appendProgressSample(currentTask.progressSamples, now, units.processed)
337
357
  }));
338
358
  return {
339
359
  state: {
340
360
  tasks: nextTasks,
341
- renderOrder: current.renderOrder
361
+ renderOrder: current.renderOrder,
362
+ columns: current.columns
342
363
  },
343
364
  events: [new TaskAdvancedEvent({
344
365
  taskId,
@@ -348,33 +369,30 @@ const makeProgressRenderStore = () => {
348
369
  };
349
370
  });
350
371
  }),
351
- incrementFailed: (taskId, amount = 1) => Effect.sync(() => {
372
+ incrementFailed: (taskId, amount = 1) => Effect.gen(function* () {
373
+ const now = yield* Clock.currentTimeMillis;
352
374
  updateState((current) => {
353
375
  const currentTask = current.tasks.get(taskId);
354
376
  if (!currentTask) return {
355
377
  state: current,
356
378
  events: []
357
379
  };
380
+ const units = normalizeUnits({
381
+ succeeded: currentTask.units.succeeded,
382
+ failed: currentTask.units.failed + amount,
383
+ total: currentTask.units.total
384
+ });
358
385
  const nextTasks = new Map(current.tasks);
359
386
  nextTasks.set(taskId, TaskSnapshot({
360
- id: currentTask.id,
361
- parentId: currentTask.parentId,
362
- description: currentTask.description,
363
- status: currentTask.status,
364
- countDisplay: currentTask.countDisplay,
365
- transient: currentTask.transient,
366
- units: normalizeUnits({
367
- succeeded: currentTask.units.succeeded,
368
- failed: currentTask.units.failed + amount,
369
- total: currentTask.units.total
370
- }),
371
- startedAt: currentTask.startedAt,
372
- completedAt: currentTask.completedAt
387
+ ...currentTask,
388
+ units,
389
+ progressSamples: appendProgressSample(currentTask.progressSamples, now, units.processed)
373
390
  }));
374
391
  return {
375
392
  state: {
376
393
  tasks: nextTasks,
377
- renderOrder: current.renderOrder
394
+ renderOrder: current.renderOrder,
395
+ columns: current.columns
378
396
  },
379
397
  events: [new TaskAdvancedEvent({
380
398
  taskId,
@@ -392,41 +410,46 @@ const makeProgressRenderStore = () => {
392
410
  state: current,
393
411
  events: []
394
412
  };
413
+ if (currentTask.status !== "running") return {
414
+ state: current,
415
+ events: []
416
+ };
395
417
  const nextTasks = new Map(current.tasks);
396
418
  if (currentTask.transient) {
397
419
  const removedTaskIds = subtreeTaskIds(current.renderOrder, taskId);
398
420
  for (const removedTaskId of removedTaskIds) nextTasks.delete(removedTaskId);
421
+ const nextColumns = new Map(current.columns);
422
+ for (const removedTaskId of removedTaskIds) nextColumns.delete(removedTaskId);
399
423
  return {
400
424
  state: {
401
425
  tasks: nextTasks,
402
- renderOrder: removeFromRenderOrder(current.renderOrder, taskId)
426
+ renderOrder: removeFromRenderOrder(current.renderOrder, taskId),
427
+ columns: nextColumns
403
428
  },
404
429
  events: [new TaskCompletedEvent({ taskId }), ...removedTaskIds.map((removedTaskId) => new TaskRemovedEvent({ taskId: removedTaskId }))]
405
430
  };
406
431
  }
432
+ const units = currentTask.units.total !== void 0 ? currentTask.units.processed < currentTask.units.total ? normalizeUnits({
433
+ succeeded: currentTask.units.succeeded + (currentTask.units.total - currentTask.units.processed),
434
+ failed: currentTask.units.failed,
435
+ total: currentTask.units.total
436
+ }) : currentTask.units : currentTask.units.processed > 0 ? normalizeUnits({
437
+ succeeded: currentTask.units.succeeded,
438
+ failed: currentTask.units.failed,
439
+ total: currentTask.units.processed
440
+ }) : currentTask.units;
407
441
  nextTasks.set(taskId, TaskSnapshot({
408
- id: currentTask.id,
409
- parentId: currentTask.parentId,
410
- description: currentTask.description,
442
+ ...currentTask,
411
443
  status: "done",
412
- countDisplay: currentTask.countDisplay,
413
- transient: currentTask.transient,
414
- units: currentTask.units.total !== void 0 ? currentTask.units.processed < currentTask.units.total ? normalizeUnits({
415
- succeeded: currentTask.units.succeeded + (currentTask.units.total - currentTask.units.processed),
416
- failed: currentTask.units.failed,
417
- total: currentTask.units.total
418
- }) : currentTask.units : currentTask.units.processed > 0 ? normalizeUnits({
419
- succeeded: currentTask.units.succeeded,
420
- failed: currentTask.units.failed,
421
- total: currentTask.units.processed
422
- }) : currentTask.units,
423
- startedAt: currentTask.startedAt,
424
- completedAt: now
444
+ units,
445
+ completedAt: now,
446
+ progressSamples: appendProgressSample(currentTask.progressSamples, now, units.processed)
425
447
  }));
426
448
  return {
427
449
  state: {
428
450
  tasks: nextTasks,
429
- renderOrder: current.renderOrder
451
+ renderOrder: current.renderOrder,
452
+ columns: current.columns
430
453
  },
431
454
  events: [new TaskCompletedEvent({ taskId })]
432
455
  };
@@ -440,43 +463,146 @@ const makeProgressRenderStore = () => {
440
463
  state: current,
441
464
  events: []
442
465
  };
466
+ if (currentTask.status !== "running") return {
467
+ state: current,
468
+ events: []
469
+ };
443
470
  const nextTasks = new Map(current.tasks);
444
471
  if (currentTask.transient) {
445
472
  const removedTaskIds = subtreeTaskIds(current.renderOrder, taskId);
446
473
  for (const removedTaskId of removedTaskIds) nextTasks.delete(removedTaskId);
474
+ const nextColumns = new Map(current.columns);
475
+ for (const removedTaskId of removedTaskIds) nextColumns.delete(removedTaskId);
447
476
  return {
448
477
  state: {
449
478
  tasks: nextTasks,
450
- renderOrder: removeFromRenderOrder(current.renderOrder, taskId)
479
+ renderOrder: removeFromRenderOrder(current.renderOrder, taskId),
480
+ columns: nextColumns
451
481
  },
452
482
  events: [new TaskFailedEvent({ taskId }), ...removedTaskIds.map((removedTaskId) => new TaskRemovedEvent({ taskId: removedTaskId }))]
453
483
  };
454
484
  }
455
485
  nextTasks.set(taskId, TaskSnapshot({
456
- id: currentTask.id,
457
- parentId: currentTask.parentId,
458
- description: currentTask.description,
486
+ ...currentTask,
459
487
  status: "failed",
460
- countDisplay: currentTask.countDisplay,
461
- transient: currentTask.transient,
462
- units: currentTask.units,
463
- startedAt: currentTask.startedAt,
464
488
  completedAt: now
465
489
  }));
466
490
  return {
467
491
  state: {
468
492
  tasks: nextTasks,
469
- renderOrder: current.renderOrder
493
+ renderOrder: current.renderOrder,
494
+ columns: current.columns
470
495
  },
471
496
  events: [new TaskFailedEvent({ taskId })]
472
497
  };
473
498
  });
474
499
  }),
475
500
  getTask: (taskId) => Effect.sync(() => Option.fromNullable(state.tasks.get(taskId))),
476
- listTasks: Effect.sync(() => Array.from(state.tasks.values()))
501
+ listTasks: Effect.sync(() => Array.from(state.tasks.values())),
502
+ setMetadata: (taskId, metadata) => Effect.sync(() => {
503
+ updateState((current) => {
504
+ const currentTask = current.tasks.get(taskId);
505
+ if (!currentTask) return {
506
+ state: current,
507
+ events: []
508
+ };
509
+ const nextTasks = new Map(current.tasks);
510
+ nextTasks.set(taskId, TaskSnapshot({
511
+ ...currentTask,
512
+ metadata
513
+ }));
514
+ return {
515
+ state: {
516
+ tasks: nextTasks,
517
+ renderOrder: current.renderOrder,
518
+ columns: current.columns
519
+ },
520
+ events: []
521
+ };
522
+ });
523
+ }),
524
+ getMetadata: (taskId) => Effect.sync(() => {
525
+ return state.tasks.get(taskId)?.metadata;
526
+ })
477
527
  };
478
528
  };
479
529
 
530
+ //#endregion
531
+ //#region src/renderer/hooks/use-now-clock.ts
532
+ const useNowClock = (active, intervalMillis) => {
533
+ const [now, setNow] = useState(() => Date.now());
534
+ useEffect(() => {
535
+ if (!active) return;
536
+ setNow(Date.now());
537
+ const interval = setInterval(() => {
538
+ setNow(Date.now());
539
+ }, intervalMillis);
540
+ return () => {
541
+ clearInterval(interval);
542
+ };
543
+ }, [active, intervalMillis]);
544
+ return now;
545
+ };
546
+
547
+ //#endregion
548
+ //#region src/renderer/context/now-context.tsx
549
+ const NOW_INTERVAL_MILLIS = 1e3;
550
+ const NowContext = createContext(Date.now());
551
+ const NowProvider = ({ active, children, nowOverride }) => {
552
+ const liveNow = useNowClock(active, NOW_INTERVAL_MILLIS);
553
+ const now = nowOverride ?? liveNow;
554
+ return /* @__PURE__ */ jsx(NowContext.Provider, {
555
+ value: now,
556
+ children
557
+ });
558
+ };
559
+ const useNow = () => useContext(NowContext);
560
+
561
+ //#endregion
562
+ //#region src/renderer/hooks/use-spinner-clock.ts
563
+ const normalizeIntervalMillis = (intervalMillis) => Math.max(1, intervalMillis);
564
+ const getSpinnerTickAtTime = (baseTick, startedAt, now, intervalMillis) => {
565
+ const elapsedMillis = Math.max(0, now - startedAt);
566
+ return baseTick + Math.floor(elapsedMillis / normalizeIntervalMillis(intervalMillis));
567
+ };
568
+ const useSpinnerClock = (active, intervalMillis) => {
569
+ const [tick, setTick] = useState(0);
570
+ const tickRef = useRef(tick);
571
+ useEffect(() => {
572
+ tickRef.current = tick;
573
+ }, [tick]);
574
+ useEffect(() => {
575
+ if (!active) return;
576
+ const baseTick = tickRef.current;
577
+ const startedAt = Date.now();
578
+ const updateTick = () => {
579
+ setTick(getSpinnerTickAtTime(baseTick, startedAt, Date.now(), intervalMillis));
580
+ };
581
+ updateTick();
582
+ const interval = setInterval(() => {
583
+ updateTick();
584
+ }, normalizeIntervalMillis(intervalMillis));
585
+ return () => {
586
+ clearInterval(interval);
587
+ };
588
+ }, [active, intervalMillis]);
589
+ return tick;
590
+ };
591
+
592
+ //#endregion
593
+ //#region src/renderer/context/spinner-context.tsx
594
+ const DEFAULT_SPINNER_INTERVAL_MILLIS = cliSpinners.dots.interval;
595
+ const SpinnerContext = createContext(0);
596
+ const SpinnerProvider = ({ active, children, intervalMillis = DEFAULT_SPINNER_INTERVAL_MILLIS, tickOverride }) => {
597
+ const liveTick = useSpinnerClock(active, intervalMillis);
598
+ const tick = tickOverride ?? liveTick;
599
+ return /* @__PURE__ */ jsx(SpinnerContext.Provider, {
600
+ value: tick,
601
+ children
602
+ });
603
+ };
604
+ const useSpinnerTick = () => useContext(SpinnerContext);
605
+
480
606
  //#endregion
481
607
  //#region src/renderer/shared/determinate.ts
482
608
  const isDeterminate$2 = (task) => task.units.total !== void 0;
@@ -497,17 +623,58 @@ const formatDurationSeconds = (seconds) => {
497
623
  const mins = Math.floor(value % 3600 / 60);
498
624
  return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
499
625
  };
626
+ const formatClockDurationSeconds = (seconds) => {
627
+ const value = Math.max(0, Math.floor(seconds));
628
+ const hours = Math.floor(value / 3600);
629
+ const mins = Math.floor(value % 3600 / 60);
630
+ const secs = value % 60;
631
+ const clock = `${`${mins}`.padStart(2, "0")}:${`${secs}`.padStart(2, "0")}`;
632
+ return hours > 0 ? `${`${hours}`.padStart(2, "0")}:${clock}` : clock;
633
+ };
634
+ /**
635
+ * Estimates remaining time from the task's retained progress sample deque.
636
+ *
637
+ * Returns undefined until there are at least two samples with positive processed and time deltas.
638
+ */
639
+ const getSmoothedEtaMillis = (task) => {
640
+ const { processed, total } = task.units;
641
+ const remaining = total - processed;
642
+ if (processed <= 0 || remaining <= 0) return;
643
+ const samples = task.progressSamples;
644
+ const lastSample = samples.at(-1);
645
+ if (lastSample === void 0) return;
646
+ const firstSample = samples[0];
647
+ if (firstSample === void 0 || firstSample === lastSample) return;
648
+ const deltaProcessed = lastSample.processed - firstSample.processed;
649
+ const deltaMillis = lastSample.timestamp - firstSample.timestamp;
650
+ if (deltaProcessed <= 0 || deltaMillis <= 0) return;
651
+ return Math.max(0, Math.floor(remaining * deltaMillis / deltaProcessed));
652
+ };
500
653
  const formatElapsed = (task, now) => {
501
654
  return formatDurationSeconds(Math.max(0, (task.completedAt ?? now) - task.startedAt) / 1e3);
502
655
  };
656
+ const formatElapsedClock = (task, now) => {
657
+ return formatClockDurationSeconds(Math.max(0, (task.completedAt ?? now) - task.startedAt) / 1e3);
658
+ };
503
659
  const formatEta = (task, now) => {
504
660
  if (task.status !== "running" || !isDeterminate$1(task)) return "";
505
661
  const { processed, total } = task.units;
506
662
  const remaining = total - processed;
507
663
  if (processed <= 0 || remaining <= 0) return "";
508
- const elapsedMillis = Math.max(1, now - task.startedAt);
509
- return formatDurationSeconds(Math.max(0, Math.floor(elapsedMillis / processed * remaining)) / 1e3);
664
+ const etaMillis = getSmoothedEtaMillis(task);
665
+ if (etaMillis === void 0) return "";
666
+ return formatClockDurationSeconds(etaMillis / 1e3);
667
+ };
668
+ const formatEtaClock = (task, now) => {
669
+ if (task.status !== "running" || !isDeterminate$1(task)) return;
670
+ const { processed, total } = task.units;
671
+ const remaining = total - processed;
672
+ if (processed <= 0 || remaining <= 0) return;
673
+ const etaMillis = getSmoothedEtaMillis(task);
674
+ if (etaMillis === void 0) return;
675
+ return formatClockDurationSeconds(etaMillis / 1e3);
510
676
  };
677
+ const formatElapsedEta = (task, now) => `${formatElapsedClock(task, now)}<${formatEtaClock(task, now) ?? "00:00"}`;
511
678
  const formatDeterminateAmountParts = (task) => {
512
679
  if (!isDeterminate$1(task)) return;
513
680
  const totalText = `${task.units.total}`;
@@ -606,38 +773,17 @@ const renderAmount = (task, layout) => {
606
773
  /* @__PURE__ */ jsx(Text, { children: ` ${processed}/${total}` })
607
774
  ] });
608
775
  };
609
- const defaultAmountColumnConfig = {
610
- minWidth: 4,
611
- sticky: true
612
- };
613
- const createAmountColumn = (config) => {
614
- const resolvedConfig = {
615
- ...defaultAmountColumnConfig,
616
- ...config
617
- };
618
- let amountLayout = emptyAmountLayout;
619
- return {
620
- Component: ({ row }) => /* @__PURE__ */ jsx(Text, {
621
- wrap: "truncate-end",
622
- children: renderAmount(row.task, amountLayout)
623
- }),
624
- measure: ({ rows }) => {
625
- amountLayout = measureAmountLayout(rows);
626
- return {
627
- minWidth: amountLayout.preferredWidth > 0 ? Math.min(resolvedConfig.minWidth, amountLayout.preferredWidth) : 0,
628
- preferredWidth: amountLayout.preferredWidth,
629
- maxWidth: amountLayout.preferredWidth
630
- };
631
- },
632
- noWrap: false,
633
- sticky: resolvedConfig.sticky
634
- };
635
- };
776
+ const AmountCell = ({ task, layout }) => /* @__PURE__ */ jsx(Text, {
777
+ wrap: "truncate-end",
778
+ children: renderAmount(task, layout)
779
+ });
636
780
 
637
781
  //#endregion
638
782
  //#region src/renderer/columns/bar-column.tsx
639
- const DEFAULT_BAR_WIDTH = 30;
640
783
  const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
784
+ const prepareBar = (rows) => {
785
+ return { hasDeterminateRows: rows.some((row) => row.derived.isDeterminate) };
786
+ };
641
787
  const renderProgressBar = (task, width) => {
642
788
  if (!isDeterminate$2(task)) return /* @__PURE__ */ jsx(Text, { children: ` `.repeat(Math.max(0, width)) });
643
789
  const displayTotal = Math.max(task.units.total, task.units.succeeded + task.units.failed);
@@ -664,94 +810,21 @@ const renderProgressBar = (task, width) => {
664
810
  ]
665
811
  });
666
812
  };
667
- const defaultBarColumnConfig = {
668
- minWidth: 4,
669
- barWidth: DEFAULT_BAR_WIDTH,
670
- sticky: true
671
- };
672
- const createBarColumn = (config) => {
673
- const resolvedConfig = {
674
- ...defaultBarColumnConfig,
675
- ...config
676
- };
677
- return {
678
- Component: ({ row, width }) => /* @__PURE__ */ jsx(Text, {
679
- wrap: "truncate-end",
680
- children: renderProgressBar(row.task, width)
681
- }),
682
- measure: ({ rows }) => {
683
- const hasDeterminateRows = rows.some((row) => row.derived.isDeterminate);
684
- const preferredWidth = hasDeterminateRows ? resolvedConfig.barWidth : 0;
685
- return {
686
- minWidth: hasDeterminateRows ? Math.min(resolvedConfig.minWidth, preferredWidth) : 0,
687
- preferredWidth,
688
- maxWidth: preferredWidth
689
- };
690
- },
691
- noWrap: false,
692
- sticky: resolvedConfig.sticky
693
- };
694
- };
695
-
696
- //#endregion
697
- //#region src/renderer/hooks/use-spinner-clock.ts
698
- const normalizeIntervalMillis = (intervalMillis) => Math.max(1, intervalMillis);
699
- const getSpinnerTickAtTime = (baseTick, startedAt, now, intervalMillis) => {
700
- const elapsedMillis = Math.max(0, now - startedAt);
701
- return baseTick + Math.floor(elapsedMillis / normalizeIntervalMillis(intervalMillis));
702
- };
703
- const useSpinnerClock = (active, intervalMillis) => {
704
- const [tick, setTick] = useState(0);
705
- const tickRef = useRef(tick);
706
- useEffect(() => {
707
- tickRef.current = tick;
708
- }, [tick]);
709
- useEffect(() => {
710
- if (!active) return;
711
- const baseTick = tickRef.current;
712
- const startedAt = Date.now();
713
- const updateTick = () => {
714
- setTick(getSpinnerTickAtTime(baseTick, startedAt, Date.now(), intervalMillis));
715
- };
716
- updateTick();
717
- const interval = setInterval(() => {
718
- updateTick();
719
- }, normalizeIntervalMillis(intervalMillis));
720
- return () => {
721
- clearInterval(interval);
722
- };
723
- }, [active, intervalMillis]);
724
- return tick;
725
- };
726
-
727
- //#endregion
728
- //#region src/renderer/context/spinner-context.tsx
729
- const DEFAULT_SPINNER_INTERVAL_MILLIS = cliSpinners.dots.interval;
730
- const SpinnerContext = createContext(0);
731
- const SpinnerProvider = ({ active, children, intervalMillis = DEFAULT_SPINNER_INTERVAL_MILLIS, tickOverride }) => {
732
- const liveTick = useSpinnerClock(active, intervalMillis);
733
- const tick = tickOverride ?? liveTick;
734
- return /* @__PURE__ */ jsx(SpinnerContext.Provider, {
735
- value: tick,
736
- children
737
- });
738
- };
739
- const useSpinnerTick = () => useContext(SpinnerContext);
813
+ const BarCell = ({ task, width }) => /* @__PURE__ */ jsx(Text, {
814
+ wrap: "truncate-end",
815
+ children: renderProgressBar(task, width ?? 0)
816
+ });
740
817
 
741
818
  //#endregion
742
819
  //#region src/renderer/columns/description-column.tsx
743
820
  const MIN_TREE_DESCRIPTION_TEXT_WIDTH = 6;
744
- const defaultDescriptionColumnConfig = {
745
- minWidth: 1,
746
- spinnerType: "dots",
747
- sticky: true
748
- };
821
+ const DEFAULT_SPINNER_TYPE = "dots";
749
822
  const isDeterminate = (task) => task.units.total !== void 0;
750
823
  const getSpinnerFrame = (tick, spinnerType) => {
751
824
  const frames = cliSpinners[spinnerType].frames;
752
825
  return frames[(tick % frames.length + frames.length) % frames.length] ?? frames[0] ?? "";
753
826
  };
754
- const getTaskIndicator = (task, tick, spinnerType = defaultDescriptionColumnConfig.spinnerType) => {
827
+ const getTaskIndicator = (task, tick, spinnerType = DEFAULT_SPINNER_TYPE) => {
755
828
  if (task.status === "running") return {
756
829
  symbol: getSpinnerFrame(tick, spinnerType),
757
830
  color: "yellow"
@@ -782,412 +855,279 @@ const getTaskIndicator = (task, tick, spinnerType = defaultDescriptionColumnConf
782
855
  color: "green"
783
856
  };
784
857
  };
785
- const TaskIndicatorGlyph = ({ task, spinnerType = defaultDescriptionColumnConfig.spinnerType }) => {
786
- const indicator = getTaskIndicator(task, useSpinnerTick(), spinnerType);
858
+ const prepareDescription = (rows) => ({
859
+ minTreeWidth: rows.reduce((max, row) => Math.max(max, row.derived.treePrefixWidth + 2 + MIN_TREE_DESCRIPTION_TEXT_WIDTH), MIN_TREE_DESCRIPTION_TEXT_WIDTH + 2),
860
+ preferredWidth: rows.reduce((max, row) => Math.max(max, row.derived.treePrefixWidth + 2 + row.derived.descriptionWidth), 2)
861
+ });
862
+ const TaskIndicatorGlyph = ({ task, tick, spinnerType = DEFAULT_SPINNER_TYPE }) => {
863
+ const indicator = getTaskIndicator(task, tick, spinnerType);
787
864
  return /* @__PURE__ */ jsx(Text, {
788
865
  color: indicator.color,
789
866
  children: indicator.symbol
790
867
  });
791
868
  };
792
- const createDescriptionColumn = (config) => {
793
- const resolvedConfig = {
794
- ...defaultDescriptionColumnConfig,
795
- ...config,
796
- spinnerType: config?.spinnerType ?? defaultDescriptionColumnConfig.spinnerType
797
- };
798
- let minTreeWidth = MIN_TREE_DESCRIPTION_TEXT_WIDTH + 2;
799
- const Component = ({ row, width }) => {
800
- const showTree = width >= minTreeWidth;
801
- const treePrefix = showTree ? row.derived.treePrefix : "";
802
- if (!showTree && width <= 1) return /* @__PURE__ */ jsx(TaskIndicatorGlyph, {
803
- task: row.task,
804
- spinnerType: resolvedConfig.spinnerType
805
- });
806
- if (!showTree && width === 2) return /* @__PURE__ */ jsxs(Text, {
807
- wrap: "truncate-end",
808
- children: [/* @__PURE__ */ jsx(TaskIndicatorGlyph, {
809
- task: row.task,
810
- spinnerType: resolvedConfig.spinnerType
811
- }), "…"]
812
- });
813
- return /* @__PURE__ */ jsxs(Text, {
814
- wrap: "truncate-end",
815
- children: [
816
- treePrefix,
817
- /* @__PURE__ */ jsx(TaskIndicatorGlyph, {
818
- task: row.task,
819
- spinnerType: resolvedConfig.spinnerType
820
- }),
821
- ` ${row.task.description}`
822
- ]
823
- });
824
- };
825
- return {
826
- Component,
827
- measure: ({ rows }) => {
828
- const hasNestedRows = rows.some((row) => row.tree.depth > 0);
829
- minTreeWidth = rows.reduce((max, row) => Math.max(max, row.derived.treePrefixWidth + 2 + MIN_TREE_DESCRIPTION_TEXT_WIDTH), MIN_TREE_DESCRIPTION_TEXT_WIDTH + 2);
830
- return {
831
- minWidth: resolvedConfig.minWidth,
832
- preferredWidth: Math.max(rows.reduce((max, row) => Math.max(max, row.derived.treePrefixedDescriptionWidth + 2), resolvedConfig.minWidth), hasNestedRows ? minTreeWidth : resolvedConfig.minWidth),
833
- maxWidth: void 0
834
- };
835
- },
836
- noWrap: false,
837
- sticky: resolvedConfig.sticky,
838
- stickyMaxWidth: resolvedConfig.stickyMaxWidth
839
- };
840
- };
841
-
842
- //#endregion
843
- //#region src/renderer/hooks/use-now-clock.ts
844
- const useNowClock = (active, intervalMillis) => {
845
- const [now, setNow] = useState(() => Date.now());
846
- useEffect(() => {
847
- if (!active) return;
848
- setNow(Date.now());
849
- const interval = setInterval(() => {
850
- setNow(Date.now());
851
- }, intervalMillis);
852
- return () => {
853
- clearInterval(interval);
854
- };
855
- }, [active, intervalMillis]);
856
- return now;
869
+ const DescriptionCell = ({ cell, width, minTreeWidth, spinnerTick }) => {
870
+ const showTree = width === void 0 || width >= minTreeWidth;
871
+ const treePrefix = showTree ? cell.derived.treePrefix : "";
872
+ if (!showTree && width !== void 0 && width <= 1) return /* @__PURE__ */ jsx(TaskIndicatorGlyph, {
873
+ task: cell.task,
874
+ tick: spinnerTick
875
+ });
876
+ if (!showTree && width !== void 0 && width === 2) return /* @__PURE__ */ jsxs(Text, {
877
+ wrap: "truncate-end",
878
+ children: [/* @__PURE__ */ jsx(TaskIndicatorGlyph, {
879
+ task: cell.task,
880
+ tick: spinnerTick
881
+ }), "…"]
882
+ });
883
+ return /* @__PURE__ */ jsxs(Text, {
884
+ wrap: "truncate-end",
885
+ children: [
886
+ treePrefix,
887
+ /* @__PURE__ */ jsx(TaskIndicatorGlyph, {
888
+ task: cell.task,
889
+ tick: spinnerTick
890
+ }),
891
+ ` ${cell.task.description}`
892
+ ]
893
+ });
857
894
  };
858
895
 
859
896
  //#endregion
860
- //#region src/renderer/context/now-context.tsx
861
- const NOW_INTERVAL_MILLIS = 1e3;
862
- const NowContext = createContext(Date.now());
863
- const NowProvider = ({ active, children, nowOverride }) => {
864
- const liveNow = useNowClock(active, NOW_INTERVAL_MILLIS);
865
- const now = nowOverride ?? liveNow;
866
- return /* @__PURE__ */ jsx(NowContext.Provider, {
867
- value: now,
868
- children
897
+ //#region src/renderer/columns/elapsed-eta-column.tsx
898
+ const ElapsedEtaCell = ({ task, now }) => {
899
+ return /* @__PURE__ */ jsx(Text, {
900
+ wrap: "truncate-end",
901
+ color: "gray",
902
+ children: formatElapsedEta(task, now)
869
903
  });
870
904
  };
871
- const useNow = () => useContext(NowContext);
872
905
 
873
906
  //#endregion
874
907
  //#region src/renderer/columns/elapsed-column.tsx
875
- const defaultElapsedColumnConfig = {
876
- minWidth: 2,
877
- justify: "right",
878
- sticky: true
879
- };
880
- const ElapsedText = ({ task, width }) => {
908
+ const ElapsedCell = ({ task, now }) => {
881
909
  return /* @__PURE__ */ jsx(Text, {
882
910
  wrap: "truncate-end",
883
911
  color: "gray",
884
- children: formatElapsed(task, useNow()).slice(0, Math.max(0, width))
912
+ children: formatElapsed(task, now)
885
913
  });
886
914
  };
887
- const createElapsedColumn = (config) => {
888
- const resolvedConfig = {
889
- ...defaultElapsedColumnConfig,
890
- ...config
891
- };
892
- const Component = ({ row, width }) => /* @__PURE__ */ jsx(ElapsedText, {
893
- task: row.task,
894
- width
895
- });
896
- return {
897
- Component,
898
- measure: ({ rows, now }) => {
899
- const width = rows.reduce((max, row) => Math.max(max, formatElapsed(row.task, now).length), resolvedConfig.minWidth);
900
- return {
901
- minWidth: resolvedConfig.minWidth,
902
- preferredWidth: width,
903
- maxWidth: width
904
- };
905
- },
906
- getLayoutDependency: ({ rows, now }) => rows.reduce((max, row) => Math.max(max, formatElapsed(row.task, now).length), 0),
907
- justify: resolvedConfig.justify,
908
- noWrap: true,
909
- sticky: resolvedConfig.sticky
910
- };
911
- };
912
915
 
913
916
  //#endregion
914
917
  //#region src/renderer/columns/eta-column.tsx
915
- const primaryUnit = (duration) => duration.split(" ")[0] ?? duration;
916
- const cropTextToWidth = (text, width) => {
917
- if (width <= 0) return "";
918
- return text.slice(0, width);
918
+ const EtaCell = ({ task, now }) => {
919
+ const eta = formatEta(task, now);
920
+ if (eta === "") return null;
921
+ return /* @__PURE__ */ jsx(Text, {
922
+ wrap: "truncate-end",
923
+ color: "gray",
924
+ children: `ETA: ${eta}`
925
+ });
919
926
  };
920
- const etaDurationText = (row, now) => {
921
- const eta = formatEta(row.task, now);
922
- return eta === "" ? void 0 : eta;
927
+
928
+ //#endregion
929
+ //#region src/columns.tsx
930
+ var columns_exports = /* @__PURE__ */ __exportAll({
931
+ amount: () => amount,
932
+ bar: () => bar,
933
+ defaults: () => defaults,
934
+ description: () => description,
935
+ elapsed: () => elapsed,
936
+ elapsedEta: () => elapsedEta,
937
+ eta: () => eta,
938
+ resolveColumnSizeValue: () => resolveColumnSizeValue,
939
+ spacer: () => spacer
940
+ });
941
+ const DEFAULT_BAR_SIZE = 30;
942
+ const resolveBarSize = (size) => {
943
+ if (size === "fullwidth") return size;
944
+ if (typeof size !== "number" || !Number.isFinite(size)) return DEFAULT_BAR_SIZE;
945
+ return Math.max(1, Math.floor(size));
923
946
  };
924
- const renderEtaText = (row, now, width) => {
925
- const duration = etaDurationText(row, now);
926
- if (duration === void 0) return {
927
- mode: "compact",
928
- text: ""
929
- };
930
- const prefixed = `ETA: ${duration}`;
931
- if (width >= prefixed.length) return {
932
- mode: "prefixed",
933
- text: prefixed
934
- };
935
- if (width >= duration.length) return {
936
- mode: "duration",
937
- text: duration
938
- };
947
+ const spacer = ({ flexGrow, flexShrink, flexBasis, minWidth } = {}) => ({
948
+ render: () => null,
949
+ flexGrow,
950
+ flexShrink,
951
+ flexBasis,
952
+ minWidth
953
+ });
954
+ const description = () => ({
955
+ prepare: prepareDescription,
956
+ flexShrink: 1,
957
+ flexBasis: (prepared) => prepared.preferredWidth,
958
+ minWidth: 1,
959
+ render: (cell, ctx) => /* @__PURE__ */ jsx(DescriptionCell, {
960
+ cell,
961
+ width: ctx.width,
962
+ minTreeWidth: ctx.prepared.minTreeWidth,
963
+ spinnerTick: ctx.spinnerTick
964
+ })
965
+ });
966
+ const bar = ({ size } = {}) => {
967
+ const resolvedSize = resolveBarSize(size);
939
968
  return {
940
- mode: "compact",
941
- text: cropTextToWidth(primaryUnit(duration), width)
942
- };
943
- };
944
- const defaultEtaColumnConfig = {
945
- minWidth: 3,
946
- justify: "right",
947
- sticky: true
948
- };
949
- const EtaText = ({ row, width, justify }) => {
950
- const rendered = renderEtaText(row, useNow(), width);
951
- return /* @__PURE__ */ jsx(Box, {
952
- width,
953
- justifyContent: rendered.mode === "prefixed" ? justify === "right" ? "flex-end" : "flex-start" : "flex-start",
954
- children: /* @__PURE__ */ jsx(Text, {
955
- wrap: "truncate-end",
956
- color: "gray",
957
- children: rendered.text
969
+ prepare: prepareBar,
970
+ flexGrow: (prepared) => prepared.hasDeterminateRows && resolvedSize === "fullwidth" ? 1 : 0,
971
+ flexShrink: (prepared) => prepared.hasDeterminateRows ? 1 : 0,
972
+ flexBasis: (prepared) => prepared.hasDeterminateRows ? resolvedSize === "fullwidth" ? DEFAULT_BAR_SIZE : resolvedSize : 0,
973
+ minWidth: (prepared) => prepared.hasDeterminateRows ? resolvedSize === "fullwidth" ? 4 : resolvedSize : 0,
974
+ render: ({ task }, ctx) => /* @__PURE__ */ jsx(BarCell, {
975
+ task,
976
+ width: ctx.width
958
977
  })
959
- });
960
- };
961
- const createEtaColumn = (config) => {
962
- const resolvedConfig = {
963
- ...defaultEtaColumnConfig,
964
- ...config
965
- };
966
- const Component = ({ row, width }) => /* @__PURE__ */ jsx(EtaText, {
967
- row,
968
- width,
969
- justify: resolvedConfig.justify
970
- });
971
- return {
972
- Component,
973
- measure: ({ rows, now }) => {
974
- const width = rows.reduce((max, row) => {
975
- const duration = etaDurationText(row, now);
976
- if (duration === void 0) return max;
977
- return Math.max(max, `ETA: ${duration}`.length);
978
- }, resolvedConfig.minWidth);
979
- return {
980
- minWidth: resolvedConfig.minWidth,
981
- preferredWidth: width,
982
- maxWidth: width
983
- };
984
- },
985
- getLayoutDependency: ({ rows, now }) => rows.reduce((max, row) => {
986
- const duration = etaDurationText(row, now);
987
- if (duration === void 0) return max;
988
- return Math.max(max, `ETA: ${duration}`.length);
989
- }, 0),
990
- justify: resolvedConfig.justify,
991
- noWrap: true,
992
- sticky: resolvedConfig.sticky
993
978
  };
994
979
  };
995
-
996
- //#endregion
997
- //#region src/renderer/default-columns.tsx
998
- const defaultRendererv2Columns = [
999
- createDescriptionColumn(),
1000
- createBarColumn(),
1001
- createAmountColumn(),
1002
- createElapsedColumn(),
1003
- createEtaColumn()
980
+ const amount = () => ({
981
+ prepare: measureAmountLayout,
982
+ align: "right",
983
+ render: ({ task }, ctx) => /* @__PURE__ */ jsx(AmountCell, {
984
+ task,
985
+ layout: ctx.prepared
986
+ })
987
+ });
988
+ const elapsed = () => ({
989
+ align: "right",
990
+ flexShrink: 0,
991
+ render: ({ task }, ctx) => /* @__PURE__ */ jsx(ElapsedCell, {
992
+ task,
993
+ now: ctx.now
994
+ })
995
+ });
996
+ const elapsedEta = () => ({
997
+ align: "right",
998
+ flexShrink: 0,
999
+ minWidth: 11,
1000
+ render: ({ task }, ctx) => /* @__PURE__ */ jsx(ElapsedEtaCell, {
1001
+ task,
1002
+ now: ctx.now
1003
+ })
1004
+ });
1005
+ const eta = () => ({
1006
+ align: "right",
1007
+ flexShrink: 0,
1008
+ minWidth: 8,
1009
+ render: ({ task }, ctx) => /* @__PURE__ */ jsx(EtaCell, {
1010
+ task,
1011
+ now: ctx.now
1012
+ })
1013
+ });
1014
+ const defaults = () => [
1015
+ description(),
1016
+ bar(),
1017
+ amount(),
1018
+ elapsedEta()
1004
1019
  ];
1020
+ const resolveColumnSizeValue = (value, prepared) => {
1021
+ if (typeof value === "function") return value(prepared);
1022
+ return value;
1023
+ };
1005
1024
 
1006
1025
  //#endregion
1007
- //#region src/renderer/width-allocator.ts
1008
- const RENDERER_COLUMN_GAP = 1;
1009
- const clampWidth = (width, minWidth, maxWidth) => maxWidth === void 0 ? Math.max(minWidth, width) : Math.max(minWidth, Math.min(maxWidth, width));
1010
- const normalizeMeasurement = (definition, context) => {
1011
- if (definition.fixedWidth !== void 0) {
1012
- const width = Math.max(0, definition.fixedWidth);
1013
- return {
1014
- definition,
1015
- minWidth: width,
1016
- maxWidth: width,
1017
- preferredWidth: width,
1018
- sticky: definition.sticky === true,
1019
- stickyLimit: width
1020
- };
1021
- }
1022
- const measured = definition.measure(context);
1023
- const minWidth = Math.max(0, measured.minWidth);
1024
- const maxWidth = measured.maxWidth === void 0 ? void 0 : Math.max(minWidth, measured.maxWidth);
1025
- const preferredWidth = clampWidth(measured.preferredWidth, minWidth, maxWidth);
1026
- const stickyLimit = definition.sticky !== true ? void 0 : definition.stickyMaxWidth !== void 0 ? Math.max(minWidth, definition.stickyMaxWidth) : maxWidth;
1027
- return {
1028
- definition,
1029
- minWidth,
1030
- maxWidth,
1031
- preferredWidth,
1032
- sticky: definition.sticky === true,
1033
- stickyLimit
1034
- };
1035
- };
1036
- const visibleIndices = (widths) => widths.flatMap((width, index) => width > 0 ? [index] : []);
1037
- const totalVisibleWidth = (widths) => {
1038
- const indices = visibleIndices(widths);
1039
- if (indices.length === 0) return 0;
1040
- return indices.reduce((sum, index, visibleIndex) => {
1041
- const padding = visibleIndex < indices.length - 1 ? RENDERER_COLUMN_GAP : 0;
1042
- return sum + widths[index] + padding;
1043
- }, 0);
1044
- };
1045
- const nextDistinctWidth = (entries, widest) => entries.find((entry) => entry.width < widest)?.width;
1046
- const shrinkWidestFirst = (widths, columns, targetWidth, canShrink) => {
1047
- while (totalVisibleWidth(widths) > targetWidth) {
1048
- const shrinkable = widths.map((width, index) => ({
1049
- index,
1050
- width,
1051
- minWidth: columns[index].minWidth
1052
- })).filter(({ index, width, minWidth }) => width > minWidth && canShrink(index)).sort((left, right) => right.width - left.width || right.index - left.index);
1053
- if (shrinkable.length === 0) break;
1054
- const overflow = totalVisibleWidth(widths) - targetWidth;
1055
- const widest = shrinkable[0].width;
1056
- const cohort = shrinkable.filter((entry) => entry.width === widest);
1057
- const maxUniformDrop = widest - Math.max(nextDistinctWidth(shrinkable, widest) ?? 0, ...cohort.map((entry) => entry.minWidth));
1058
- const uniformDrop = Math.min(maxUniformDrop, Math.floor(overflow / cohort.length));
1059
- if (uniformDrop > 0) {
1060
- for (const { index } of cohort) widths[index] = Math.max(columns[index].minWidth, widths[index] - uniformDrop);
1061
- continue;
1062
- }
1063
- let changed = false;
1064
- for (const { index, minWidth } of cohort) {
1065
- if (totalVisibleWidth(widths) <= targetWidth) break;
1066
- if (widths[index] <= minWidth) continue;
1067
- widths[index] = widths[index] - 1;
1068
- changed = true;
1069
- }
1070
- if (!changed) break;
1071
- }
1072
- return widths;
1073
- };
1074
- const forceFitFromRight = (widths, columns, targetWidth, canShrink) => {
1075
- while (totalVisibleWidth(widths) > targetWidth) {
1076
- const rightmostVisibleIndex = widths.findLastIndex((width, index) => width > 0 && canShrink(index));
1077
- if (rightmostVisibleIndex < 0) break;
1078
- widths[rightmostVisibleIndex] = widths[rightmostVisibleIndex] - 1;
1079
- }
1080
- return widths;
1081
- };
1082
- const applyStickyGrowth = (widths, columns, targetWidth, previousStickyWidths) => {
1083
- const stickyTargets = columns.map((column, index) => {
1084
- if (!column.sticky) return;
1085
- return clampWidth(Math.max(previousStickyWidths.get(index) ?? 0, column.preferredWidth), column.minWidth, column.stickyLimit);
1026
+ //#region src/renderer/column-resolver.ts
1027
+ const NO_PREPARE = Symbol("no-prepare");
1028
+ const getColumnsForRow = (row, columns) => columns.get(row.task.id) ?? defaults();
1029
+ const toCellInfo = (row) => row;
1030
+ const maxDefined = (values) => values.reduce((maxValue, value) => value === void 0 ? maxValue : Math.max(maxValue ?? Number.NEGATIVE_INFINITY, value), void 0);
1031
+ const resolvePreparedGroups = (defs, cellInfos) => {
1032
+ const groupedRows = /* @__PURE__ */ new Map();
1033
+ defs.forEach((def, rowIndex) => {
1034
+ if (!def?.prepare) return;
1035
+ const key = def.prepare;
1036
+ const rows = groupedRows.get(key);
1037
+ const cell = cellInfos[rowIndex];
1038
+ if (!cell) return;
1039
+ if (rows) rows.push(cell);
1040
+ else groupedRows.set(key, [cell]);
1086
1041
  });
1087
- if (targetWidth === void 0) stickyTargets.forEach((target, index) => {
1088
- if (target !== void 0) widths[index] = Math.max(widths[index], target);
1042
+ const groups = [{
1043
+ key: NO_PREPARE,
1044
+ prepared: void 0
1045
+ }];
1046
+ for (const [key, rows] of groupedRows) groups.push({
1047
+ key,
1048
+ prepared: key(rows)
1089
1049
  });
1090
- else {
1091
- let remaining = targetWidth - totalVisibleWidth(widths);
1092
- if (remaining > 0) stickyTargets.forEach((target, index) => {
1093
- if (target === void 0 || remaining <= 0) return;
1094
- const deficit = Math.max(0, target - widths[index]);
1095
- const growth = Math.min(deficit, remaining);
1096
- widths[index] = widths[index] + growth;
1097
- remaining -= growth;
1050
+ return groups;
1051
+ };
1052
+ const getPreparedFor = (def, groups) => {
1053
+ if (!def?.prepare) return;
1054
+ return groups.find((group) => group.key === def.prepare)?.prepared;
1055
+ };
1056
+ const resolveColumns = (rows, columns) => {
1057
+ const columnsByRow = rows.map((row) => getColumnsForRow(row, columns));
1058
+ const cellInfos = rows.map(toCellInfo);
1059
+ const maxColumnCount = columnsByRow.reduce((max, defs) => Math.max(max, defs.length), 0);
1060
+ return Array.from({ length: maxColumnCount }, (_, index) => {
1061
+ const defsAtIndex = columnsByRow.map((defs) => defs[index]);
1062
+ const preparedGroups = resolvePreparedGroups(defsAtIndex, cellInfos);
1063
+ const entries = defsAtIndex.map((column) => column === void 0 ? void 0 : {
1064
+ column,
1065
+ prepared: getPreparedFor(column, preparedGroups)
1098
1066
  });
1099
- }
1100
- const nextStickyWidths = /* @__PURE__ */ new Map();
1101
- stickyTargets.forEach((target, index) => {
1102
- if (target === void 0) return;
1103
- nextStickyWidths.set(index, clampWidth(Math.max(target, widths[index]), columns[index].minWidth, columns[index].stickyLimit));
1067
+ return {
1068
+ index,
1069
+ rows,
1070
+ entries,
1071
+ flexGrow: maxDefined(entries.map((entry) => entry === void 0 ? void 0 : resolveColumnSizeValue(entry.column.flexGrow, entry.prepared))),
1072
+ flexShrink: maxDefined(entries.map((entry) => entry === void 0 ? void 0 : resolveColumnSizeValue(entry.column.flexShrink, entry.prepared))),
1073
+ flexBasis: maxDefined(entries.map((entry) => entry === void 0 ? void 0 : resolveColumnSizeValue(entry.column.flexBasis, entry.prepared))),
1074
+ minWidth: maxDefined(entries.map((entry) => entry === void 0 ? void 0 : resolveColumnSizeValue(entry.column.minWidth, entry.prepared)))
1075
+ };
1104
1076
  });
1105
- return {
1106
- widths,
1107
- nextStickyWidths
1108
- };
1109
- };
1110
- const planColumnLayout = (definitions, rows, now, terminalColumns, previousStickyWidths) => {
1111
- const measureContext = {
1112
- rows,
1113
- now
1114
- };
1115
- const columns = definitions.map((definition) => normalizeMeasurement(definition, measureContext));
1116
- let widths = columns.map((column) => column.preferredWidth);
1117
- if (terminalColumns !== void 0) {
1118
- widths = shrinkWidestFirst([...widths], columns, terminalColumns, (index) => columns[index].definition.fixedWidth === void 0);
1119
- if (totalVisibleWidth(widths) > terminalColumns) widths = forceFitFromRight([...widths], columns, terminalColumns, (index) => columns[index].definition.fixedWidth === void 0);
1120
- }
1121
- const stickyResult = applyStickyGrowth(widths, columns, terminalColumns, previousStickyWidths);
1122
- return {
1123
- columns: stickyResult.widths.map((width, index) => ({
1124
- definition: columns[index].definition,
1125
- width
1126
- })).filter(({ width }) => width > 0),
1127
- nextStickyWidths: stickyResult.nextStickyWidths
1128
- };
1129
1077
  };
1130
1078
 
1131
1079
  //#endregion
1132
1080
  //#region src/renderer/public-api.tsx
1133
- const ProgressRow = memo(({ row, rowIndex, terminalColumns, columns }) => /* @__PURE__ */ jsx(Box, {
1134
- flexDirection: "row",
1135
- width: terminalColumns,
1136
- columnGap: RENDERER_COLUMN_GAP,
1137
- children: columns.map((column, columnIndex) => {
1138
- const Component = column.definition.Component;
1139
- return /* @__PURE__ */ jsx(Box, {
1140
- height: 1,
1141
- width: column.width,
1142
- minWidth: column.width,
1143
- flexBasis: column.width,
1144
- flexGrow: 0,
1145
- flexShrink: 0,
1146
- justifyContent: column.definition.justify === "right" ? "flex-end" : "flex-start",
1147
- children: /* @__PURE__ */ jsx(Component, {
1148
- row,
1149
- rowIndex,
1150
- width: column.width
1151
- })
1152
- }, columnIndex);
1153
- })
1154
- }), (previousProps, nextProps) => previousProps.row === nextProps.row && previousProps.rowIndex === nextProps.rowIndex && previousProps.terminalColumns === nextProps.terminalColumns && previousProps.columns === nextProps.columns);
1155
- const layoutDependencyKeyFor = (columns, context) => columns.map((column, index) => `${index}:${column.getLayoutDependency?.(context) ?? "static"}`).join("|");
1156
- const CreateProgressRenderer = (columns) => {
1157
- return ({ rows, terminalColumns, terminalRows }) => {
1158
- const stickyWidthsRef = useRef(/* @__PURE__ */ new Map());
1159
- const now = useNow();
1160
- const layout = useMemo(() => rows.length === 0 ? {
1161
- columns: [],
1162
- nextStickyWidths: /* @__PURE__ */ new Map()
1163
- } : planColumnLayout(columns, rows, now, terminalColumns, stickyWidthsRef.current), [
1164
- columns,
1165
- layoutDependencyKeyFor(columns, {
1166
- rows,
1167
- now
1168
- }),
1169
- rows,
1170
- terminalColumns
1171
- ]);
1172
- useEffect(() => {
1173
- stickyWidthsRef.current = rows.length === 0 ? /* @__PURE__ */ new Map() : layout.nextStickyWidths;
1174
- }, [layout.nextStickyWidths, rows.length]);
1175
- if (layout.columns.length === 0) return null;
1176
- return /* @__PURE__ */ jsx(VirtualList, {
1177
- items: rows,
1178
- keyExtractor: (row) => `${row.task.id}`,
1179
- selectedIndex: Math.max(0, rows.length - 1),
1180
- height: terminalRows ?? "auto",
1181
- itemHeight: 1,
1182
- showOverflowIndicators: true,
1183
- renderItem: ({ item: row, index: rowIndex }) => /* @__PURE__ */ jsx(ProgressRow, {
1184
- row,
1185
- rowIndex,
1186
- terminalColumns,
1187
- columns: layout.columns
1188
- })
1189
- });
1190
- };
1081
+ const justifyContentForAlign = (align) => {
1082
+ if (align === "right") return "flex-end";
1083
+ if (align === "center") return "center";
1084
+ return "flex-start";
1085
+ };
1086
+ const renderNode = (node) => {
1087
+ if (typeof node === "string" || typeof node === "number") return /* @__PURE__ */ jsx(Text, {
1088
+ wrap: "truncate-end",
1089
+ children: node
1090
+ });
1091
+ return node;
1092
+ };
1093
+ const ColumnPosition = ({ position }) => {
1094
+ const ref = useRef(null);
1095
+ const { width, hasMeasured } = useBoxMetrics(ref);
1096
+ const now = useNow();
1097
+ const spinnerTick = useSpinnerTick();
1098
+ return /* @__PURE__ */ jsx(Box, {
1099
+ ref,
1100
+ flexDirection: "column",
1101
+ flexGrow: position.flexGrow,
1102
+ flexShrink: position.flexShrink ?? 0,
1103
+ flexBasis: position.flexBasis,
1104
+ minWidth: position.minWidth,
1105
+ children: position.rows.map((row, rowIndex) => {
1106
+ const entry = position.entries[rowIndex];
1107
+ const column = entry?.column;
1108
+ const cell = row;
1109
+ const output = column?.render(cell, {
1110
+ width: hasMeasured ? width : void 0,
1111
+ now,
1112
+ spinnerTick,
1113
+ prepared: entry?.prepared
1114
+ }) ?? null;
1115
+ return /* @__PURE__ */ jsx(Box, {
1116
+ height: 1,
1117
+ justifyContent: justifyContentForAlign(column?.align),
1118
+ children: renderNode(output)
1119
+ }, row.task.id);
1120
+ })
1121
+ });
1122
+ };
1123
+ const ProgressRenderer = ({ rows, columns }) => {
1124
+ if (rows.length === 0) return null;
1125
+ return /* @__PURE__ */ jsx(Box, {
1126
+ flexDirection: "row",
1127
+ columnGap: 1,
1128
+ overflow: "hidden",
1129
+ children: resolveColumns(rows, columns).map((position) => /* @__PURE__ */ jsx(ColumnPosition, { position }, position.index))
1130
+ });
1191
1131
  };
1192
1132
 
1193
1133
  //#endregion
@@ -1292,31 +1232,22 @@ const useProgressRenderView = (store) => {
1292
1232
  //#endregion
1293
1233
  //#region src/renderer/renderer-service.tsx
1294
1234
  const MAX_FPS = 24;
1295
- const CreateProgressRoot = (columns) => {
1296
- const ProgressRenderer = CreateProgressRenderer(columns);
1297
- return ({ store, getTerminalColumns, getTerminalRows }) => {
1298
- const { renderSnapshot, hasRunningTasks } = useProgressRenderView(store);
1299
- return /* @__PURE__ */ jsx(SpinnerProvider, {
1235
+ const ProgressRoot = ({ store }) => {
1236
+ const { renderSnapshot, hasRunningTasks, publication } = useProgressRenderView(store);
1237
+ return /* @__PURE__ */ jsx(SpinnerProvider, {
1238
+ active: hasRunningTasks,
1239
+ children: /* @__PURE__ */ jsx(NowProvider, {
1300
1240
  active: hasRunningTasks,
1301
- children: /* @__PURE__ */ jsx(NowProvider, {
1302
- active: hasRunningTasks,
1303
- children: /* @__PURE__ */ jsx(ProgressRenderer, {
1304
- rows: renderSnapshot.rows,
1305
- terminalColumns: getTerminalColumns(),
1306
- terminalRows: getTerminalRows()
1307
- })
1241
+ children: /* @__PURE__ */ jsx(ProgressRenderer, {
1242
+ rows: renderSnapshot.rows,
1243
+ columns: publication.snapshot.columns
1308
1244
  })
1309
- });
1310
- };
1245
+ })
1246
+ });
1311
1247
  };
1312
- const makeRendererv2InkRendererService = (columns) => {
1313
- const ProgressRoot = CreateProgressRoot(columns);
1314
- return { run: (store, stdio, isTTY) => {
1315
- const proot = /* @__PURE__ */ jsx(ProgressRoot, {
1316
- store,
1317
- getTerminalColumns: () => isTTY ? stdio.stderr.columns : void 0,
1318
- getTerminalRows: () => isTTY ? stdio.stderr.rows : void 0
1319
- });
1248
+ const makeRendererv2InkRendererService = () => {
1249
+ return { run: (store, stdio) => {
1250
+ const proot = /* @__PURE__ */ jsx(ProgressRoot, { store });
1320
1251
  return Effect.sync(() => render(proot, {
1321
1252
  stdout: stdio.stdout,
1322
1253
  stderr: stdio.stderr,
@@ -1337,7 +1268,7 @@ const makeRendererv2InkRendererService = (columns) => {
1337
1268
  //#endregion
1338
1269
  //#region src/services/ink-renderer.tsx
1339
1270
  var InkRenderer = class InkRenderer extends Context.Tag("stromseng.dev/effective-progress/InkRenderer")() {
1340
- static Default = Layer.succeed(InkRenderer, InkRenderer.of(makeRendererv2InkRendererService(defaultRendererv2Columns)));
1271
+ static Default = Layer.succeed(InkRenderer, InkRenderer.of(makeRendererv2InkRendererService()));
1341
1272
  };
1342
1273
 
1343
1274
  //#endregion
@@ -1352,16 +1283,16 @@ var ProgressStdio = class ProgressStdio extends Context.Tag("stromseng.dev/effec
1352
1283
 
1353
1284
  //#endregion
1354
1285
  //#region src/services/progress.ts
1286
+ /** Builds the scoped implementation used by `ProgressService.task(...)` without auto-providing services. */
1355
1287
  const makeProgressService = Effect.gen(function* () {
1356
1288
  const stdio = yield* ProgressStdio;
1357
1289
  const inkRenderer = yield* InkRenderer;
1358
1290
  const outerConsole = yield* Effect.console;
1359
- const isTTY = Boolean(stdio.stderr.isTTY);
1360
1291
  const store = makeProgressRenderStore();
1361
1292
  const currentParentRef = yield* FiberRef.make(Option.none());
1362
1293
  const scope = yield* Effect.scope;
1363
1294
  const log = (...args) => args.length === 0 ? Effect.void : outerConsole.log(...args);
1364
- yield* Effect.forkIn(inkRenderer.run(store, stdio, isTTY), scope);
1295
+ yield* Effect.forkIn(inkRenderer.run(store, stdio), scope);
1365
1296
  yield* Effect.sleep("0 millis");
1366
1297
  const addTask = (options) => Effect.gen(function* () {
1367
1298
  const resolvedParentId = options.parentId === void 0 ? yield* FiberRef.get(currentParentRef) : Option.some(options.parentId);
@@ -1377,7 +1308,27 @@ const makeProgressService = Effect.gen(function* () {
1377
1308
  const failTask = store.failTask;
1378
1309
  const getTask = store.getTask;
1379
1310
  const listTasks = store.listTasks;
1380
- const runTask = dual(2, (effect, options) => Effect.gen(function* () {
1311
+ const setMetadata = store.setMetadata;
1312
+ const getMetadata = store.getMetadata;
1313
+ const makeTaskHandle = (taskId) => ({
1314
+ id: taskId,
1315
+ getMetadata: getMetadata(taskId),
1316
+ setMetadata: (metadata) => setMetadata(taskId, metadata),
1317
+ updateMetadata: (f) => Effect.flatMap(getMetadata(taskId), (current) => setMetadata(taskId, f(current))),
1318
+ incrementSucceeded: (amount) => incrementSucceeded(taskId, amount),
1319
+ incrementFailed: (amount) => incrementFailed(taskId, amount),
1320
+ update: (options) => updateTask(taskId, options),
1321
+ complete: completeTask(taskId),
1322
+ fail: failTask(taskId),
1323
+ getSnapshot: getTask(taskId).pipe(Effect.map(Option.getOrThrow))
1324
+ });
1325
+ const autoFinalizeIfRunning = (taskId, exit) => Effect.gen(function* () {
1326
+ const task = yield* getTask(taskId);
1327
+ if (Option.isNone(task) || task.value.status !== "running") return;
1328
+ if (Exit.isSuccess(exit)) yield* completeTask(taskId);
1329
+ else yield* failTask(taskId);
1330
+ });
1331
+ const scopedTask = dual(2, (effect, options) => Effect.gen(function* () {
1381
1332
  const inheritedParentId = yield* FiberRef.get(currentParentRef);
1382
1333
  const resolvedParentId = options.parentId === void 0 ? inheritedParentId : Option.some(options.parentId);
1383
1334
  const taskId = yield* addTask({
@@ -1397,17 +1348,29 @@ const makeProgressService = Effect.gen(function* () {
1397
1348
  log,
1398
1349
  getTask,
1399
1350
  listTasks,
1400
- runTask,
1401
- withTask: dual(2, (effect, options) => runTask(Effect.gen(function* () {
1402
- const taskId = yield* Task;
1403
- const exit = yield* Effect.exit(effect);
1404
- if (Exit.isSuccess(exit)) yield* completeTask(taskId);
1405
- else yield* failTask(taskId);
1406
- return yield* Exit.match(exit, {
1407
- onFailure: Effect.failCause,
1408
- onSuccess: Effect.succeed
1409
- });
1410
- }), options))
1351
+ setMetadata,
1352
+ getMetadata,
1353
+ task: dual(2, (effectOrCallback, options) => {
1354
+ if (typeof effectOrCallback === "function") return scopedTask(Effect.gen(function* () {
1355
+ const taskId = yield* Task;
1356
+ const handle = makeTaskHandle(taskId);
1357
+ const exit = yield* Effect.exit(effectOrCallback(handle));
1358
+ yield* autoFinalizeIfRunning(taskId, exit);
1359
+ return yield* Exit.match(exit, {
1360
+ onFailure: Effect.failCause,
1361
+ onSuccess: Effect.succeed
1362
+ });
1363
+ }), options);
1364
+ return scopedTask(Effect.gen(function* () {
1365
+ const taskId = yield* Task;
1366
+ const exit = yield* Effect.exit(effectOrCallback);
1367
+ yield* autoFinalizeIfRunning(taskId, exit);
1368
+ return yield* Exit.match(exit, {
1369
+ onFailure: Effect.failCause,
1370
+ onSuccess: Effect.succeed
1371
+ });
1372
+ }), options);
1373
+ })
1411
1374
  };
1412
1375
  return Progress.of(service);
1413
1376
  });
@@ -1439,9 +1402,17 @@ const provideProgress = (effect) => Effect.gen(function* () {
1439
1402
  if (Option.isSome(existing)) return yield* Effect.provideService(effect, Progress, existing.value);
1440
1403
  return yield* Effect.scoped(effect.pipe(Effect.provide(Progress.Default)));
1441
1404
  });
1442
- const task = dual(2, (effect, options) => {
1405
+ /**
1406
+ * Runs an effect inside a task, creating and providing a `Progress` service automatically when one
1407
+ * is not already present in the environment.
1408
+ *
1409
+ * The effect form tracks success and failure from the effect exit. The callback form exposes a
1410
+ * typed `TaskHandle` for task-local updates, typed metadata, and explicit completion or failure,
1411
+ * and otherwise auto-finalizes from the callback exit if the task is still `running`.
1412
+ */
1413
+ const task = dual(2, (effectOrCallback, options) => {
1443
1414
  return provideProgress(Effect.gen(function* () {
1444
- return yield* (yield* Progress).withTask(effect, options);
1415
+ return yield* (yield* Progress).task(effectOrCallback, options);
1445
1416
  }));
1446
1417
  });
1447
1418
  const wrapEffects = (effects, tap) => Array.isArray(effects) ? effects.map(tap) : Object.fromEntries(Object.entries(effects).map(([k, effect]) => [k, tap(effect)]));
@@ -1464,10 +1435,14 @@ const isTaskFullyProcessed = (progress, taskId) => Effect.gen(function* () {
1464
1435
  const { processed, total } = taskOption.value.units;
1465
1436
  return total !== void 0 && processed >= total;
1466
1437
  });
1438
+ /**
1439
+ * Runs multiple effects under a single parent task and keeps the task counters in sync with the
1440
+ * child effect outcomes.
1441
+ */
1467
1442
  const all = dual(2, (effects, options) => provideProgress(Effect.gen(function* () {
1468
1443
  const progress = yield* Progress;
1469
- return yield* progress.runTask(Effect.gen(function* () {
1470
- const taskId = yield* Task;
1444
+ return yield* progress.task((handle) => Effect.gen(function* () {
1445
+ const taskId = handle.id;
1471
1446
  const exit = yield* Effect.exit(Effect.all(wrapEffects(effects, (effect) => wrapTrackedEffect(progress, taskId, effect)), {
1472
1447
  concurrency: options.concurrency,
1473
1448
  batching: options.batching,
@@ -1490,10 +1465,13 @@ const all = dual(2, (effects, options) => provideProgress(Effect.gen(function* (
1490
1465
  countDisplay: allCountDisplay(options.mode)
1491
1466
  });
1492
1467
  })));
1468
+ /**
1469
+ * Runs `Effect.forEach` under a single parent task and advances the task counters as items finish.
1470
+ */
1493
1471
  const forEach = dual(3, (iterable, f, options) => provideProgress(Effect.gen(function* () {
1494
1472
  const progress = yield* Progress;
1495
- return yield* progress.runTask(Effect.gen(function* () {
1496
- const taskId = yield* Task;
1473
+ return yield* progress.task((handle) => Effect.gen(function* () {
1474
+ const taskId = handle.id;
1497
1475
  const exit = yield* Effect.exit(Effect.forEach(iterable, (item, index) => wrapTrackedEffect(progress, taskId, f(item, index)), {
1498
1476
  concurrency: options.concurrency,
1499
1477
  batching: options.batching,
@@ -1515,4 +1493,4 @@ const forEach = dual(3, (iterable, f, options) => provideProgress(Effect.gen(fun
1515
1493
  })));
1516
1494
 
1517
1495
  //#endregion
1518
- export { Progress, ProgressStdio, ProgressTaskEventSchema, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskCountDisplaySchema, TaskFailedEvent, TaskId, TaskRemovedEvent, TaskSnapshot, TaskSnapshotSchema, TaskStatusSchema, TaskUnitsSchema, TaskUpdatedEvent, all, decodeProgressTaskEvent, forEach, task };
1496
+ export { columns_exports as Columns, Progress, ProgressStdio, ProgressTaskEventSchema, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskCountDisplaySchema, TaskFailedEvent, TaskId, TaskProgressSampleSchema, TaskRemovedEvent, TaskSnapshot, TaskSnapshotSchema, TaskStatusSchema, TaskUnitsSchema, TaskUpdatedEvent, all, decodeProgressTaskEvent, forEach, task };