effective-progress 0.4.2 → 0.5.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.
@@ -0,0 +1,145 @@
1
+ import { formatAmount, formatElapsed, formatEta } from "./format";
2
+ import { renderTreePrefix } from "./tree";
3
+ import type { TaskRowModel } from "./types";
4
+
5
+ export const DEFAULT_BAR_WIDTH = 20;
6
+ const MIN_DESCRIPTION_WIDTH = 8;
7
+ const MIN_BAR_WIDTH = 8;
8
+ const MIN_ELAPSED_WIDTH = 3;
9
+ const MIN_AMOUNT_WIDTH = 1;
10
+ const BASELINE_ROW_WIDTH = 100;
11
+ export const MIN_DESCRIPTION_COLUMNS_FOR_TREE = 24;
12
+ const RESERVED_ELAPSED_WIDTH_UP_TO_ONE_HOUR = Array.from("59m 59s").length;
13
+ const RESERVED_ETA_WIDTH_UP_TO_ONE_HOUR = Array.from("ETA: 59m 59s").length;
14
+
15
+ const textWidth = (text: string): number => Array.from(text).length;
16
+
17
+ export interface SharedColumnWidths {
18
+ readonly row: number;
19
+ readonly description: number;
20
+ readonly bar: number;
21
+ readonly amount: number;
22
+ readonly elapsed: number;
23
+ readonly eta: number;
24
+ readonly showTree: boolean;
25
+ }
26
+
27
+ const computeWidths = (
28
+ rows: ReadonlyArray<TaskRowModel>,
29
+ now: number,
30
+ tick: number,
31
+ terminalColumns?: number,
32
+ includeTree = true,
33
+ ): Omit<SharedColumnWidths, "showTree"> => {
34
+ let hasDeterminate = false;
35
+ let description = MIN_DESCRIPTION_WIDTH;
36
+ let amount = 1;
37
+ let elapsed = RESERVED_ELAPSED_WIDTH_UP_TO_ONE_HOUR;
38
+ let eta = RESERVED_ETA_WIDTH_UP_TO_ONE_HOUR;
39
+
40
+ for (const row of rows) {
41
+ const { task, tree } = row;
42
+ const treePrefix = includeTree ? renderTreePrefix(tree) : "";
43
+ description = Math.max(description, textWidth(`${treePrefix}${task.description}`));
44
+
45
+ if (task.units._tag === "DeterminateTaskUnits") {
46
+ hasDeterminate = true;
47
+ }
48
+
49
+ amount = Math.max(amount, textWidth(formatAmount(task, tick)));
50
+ elapsed = Math.max(elapsed, textWidth(formatElapsed(task, now)));
51
+
52
+ if (task.status === "running" && task.units._tag === "DeterminateTaskUnits") {
53
+ const etaValue = formatEta(task, now);
54
+ const etaText = `ETA: ${etaValue.length > 0 ? etaValue : "--"}`;
55
+ eta = Math.max(eta, textWidth(etaText));
56
+ }
57
+ }
58
+
59
+ const bar = hasDeterminate ? DEFAULT_BAR_WIDTH : 0;
60
+ let widths = {
61
+ description,
62
+ bar,
63
+ amount,
64
+ elapsed,
65
+ eta,
66
+ };
67
+
68
+ const visible = (w: typeof widths): Array<number> =>
69
+ [w.description, w.bar, w.amount, w.elapsed, w.eta].filter((width) => width > 0);
70
+ const total = (w: typeof widths): number => {
71
+ const cols = visible(w);
72
+ return cols.reduce((sum, width) => sum + width, 0) + Math.max(0, cols.length - 1);
73
+ };
74
+
75
+ const baselineTarget = Math.max(BASELINE_ROW_WIDTH, total(widths));
76
+ const target =
77
+ terminalColumns === undefined
78
+ ? baselineTarget
79
+ : Math.max(1, Math.min(Math.max(1, Math.floor(terminalColumns)), baselineTarget));
80
+
81
+ if (total(widths) < target) {
82
+ widths.description += target - total(widths);
83
+ } else if (total(widths) > target) {
84
+ let overflow = total(widths) - target;
85
+
86
+ const reduceBy = (key: keyof typeof widths, min: number) => {
87
+ if (overflow <= 0) {
88
+ return;
89
+ }
90
+ const current = widths[key];
91
+ if (current <= min) {
92
+ return;
93
+ }
94
+ const reducible = current - min;
95
+ const delta = Math.min(reducible, overflow);
96
+ widths = { ...widths, [key]: current - delta };
97
+ overflow -= delta;
98
+ };
99
+
100
+ // Compress the description first, then optional columns.
101
+ reduceBy("description", MIN_DESCRIPTION_WIDTH);
102
+ reduceBy("eta", 0);
103
+ reduceBy("bar", MIN_BAR_WIDTH);
104
+ reduceBy("bar", 0);
105
+ reduceBy("elapsed", MIN_ELAPSED_WIDTH);
106
+ reduceBy("amount", MIN_AMOUNT_WIDTH);
107
+ reduceBy("description", 0);
108
+
109
+ if (total(widths) < target) {
110
+ widths.description += target - total(widths);
111
+ }
112
+ }
113
+
114
+ const rowWidth = total(widths);
115
+
116
+ return {
117
+ row: rowWidth,
118
+ description: widths.description,
119
+ bar: widths.bar,
120
+ amount: widths.amount,
121
+ elapsed: widths.elapsed,
122
+ eta: widths.eta,
123
+ };
124
+ };
125
+
126
+ export const computeSharedColumnWidths = (
127
+ rows: ReadonlyArray<TaskRowModel>,
128
+ now: number,
129
+ tick: number,
130
+ terminalColumns?: number,
131
+ ): SharedColumnWidths => {
132
+ const withTree = computeWidths(rows, now, tick, terminalColumns, true);
133
+ if (withTree.description >= MIN_DESCRIPTION_COLUMNS_FOR_TREE) {
134
+ return {
135
+ ...withTree,
136
+ showTree: true,
137
+ };
138
+ }
139
+
140
+ const withoutTree = computeWidths(rows, now, tick, terminalColumns, false);
141
+ return {
142
+ ...withoutTree,
143
+ showTree: false,
144
+ };
145
+ };
@@ -0,0 +1,24 @@
1
+ import type { TaskStore } from "../types";
2
+ import type { OrderedTask, TaskRowModel } from "./types";
3
+ import { computeTreeInfo } from "./tree";
4
+
5
+ const orderedVisibleTasks = (store: TaskStore): ReadonlyArray<OrderedTask> =>
6
+ store.renderOrder.flatMap((row) => {
7
+ const snapshot = store.tasks.get(row.id);
8
+ if (!snapshot || (snapshot.transient && snapshot.status !== "running")) {
9
+ return [];
10
+ }
11
+
12
+ return [
13
+ {
14
+ snapshot,
15
+ depth: row.depth,
16
+ },
17
+ ];
18
+ });
19
+
20
+ export const toTaskRows = (store: TaskStore): ReadonlyArray<TaskRowModel> =>
21
+ computeTreeInfo(orderedVisibleTasks(store)).map((entry) => ({
22
+ task: entry.snapshot,
23
+ tree: entry.tree,
24
+ }));
@@ -0,0 +1,121 @@
1
+ import { Writable } from "node:stream";
2
+ import { Clock, Context, Effect, Layer, Ref } from "effect";
3
+ import { render, type Instance } from "ink";
4
+ import type { ProgressTerminalService } from "../terminal";
5
+ import type { TaskSnapshot, TaskStore } from "../types";
6
+ import { ProgressApp } from "./app";
7
+ import { toTaskRows } from "./model";
8
+
9
+ const RENDER_INTERVAL_MILLIS = 100;
10
+
11
+ export interface InkRendererService {
12
+ readonly run: (
13
+ storeRef: Ref.Ref<TaskStore>,
14
+ dirtyRef: Ref.Ref<boolean>,
15
+ terminal: ProgressTerminalService,
16
+ isTTY: boolean,
17
+ ) => Effect.Effect<void>;
18
+ }
19
+
20
+ const hasRunningSpinners = (tasks: ReadonlyArray<TaskSnapshot>): boolean =>
21
+ tasks.some(
22
+ (task) => task.status === "running" && task.units._tag === "IndeterminateTaskUnits",
23
+ );
24
+
25
+ const createInkWritable = (terminal: ProgressTerminalService): Writable =>
26
+ new Writable({
27
+ write(chunk, _encoding, callback) {
28
+ try {
29
+ const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : `${chunk}`;
30
+ Effect.runSync(terminal.writeStderr(text));
31
+ callback();
32
+ } catch (error) {
33
+ callback(error as Error);
34
+ }
35
+ },
36
+ });
37
+
38
+ const makeDefaultInkRenderer = (): InkRendererService => ({
39
+ run: (storeRef, dirtyRef, terminal, isTTY) =>
40
+ Effect.gen(function* () {
41
+ const output = createInkWritable(terminal);
42
+ let instance: Instance | undefined;
43
+ let tick = 0;
44
+ let rendererActive = false;
45
+
46
+ const renderStore = (
47
+ store: TaskStore,
48
+ now: number,
49
+ terminalColumns: number | undefined,
50
+ ) =>
51
+ Effect.sync(() => {
52
+ const app = (
53
+ <ProgressApp
54
+ rows={toTaskRows(store)}
55
+ now={now}
56
+ tick={tick}
57
+ isTTY={isTTY}
58
+ terminalColumns={terminalColumns}
59
+ />
60
+ );
61
+ if (instance === undefined) {
62
+ instance = render(app, {
63
+ stdout: output as unknown as NodeJS.WriteStream,
64
+ stderr: output as unknown as NodeJS.WriteStream,
65
+ patchConsole: true,
66
+ exitOnCtrlC: false,
67
+ debug: false,
68
+ });
69
+ return;
70
+ }
71
+
72
+ instance.rerender(app);
73
+ });
74
+
75
+ const renderLoop = Effect.gen(function* () {
76
+ rendererActive = true;
77
+
78
+ while (true) {
79
+ const dirty = yield* Ref.getAndSet(dirtyRef, false);
80
+ const store = yield* Ref.get(storeRef);
81
+ const tasks = Array.from(store.tasks.values()).filter(
82
+ (task) => !(task.transient && task.status !== "running"),
83
+ );
84
+ const shouldRender = dirty || hasRunningSpinners(tasks);
85
+
86
+ if (shouldRender) {
87
+ const now = yield* Clock.currentTimeMillis;
88
+ const terminalColumns = isTTY ? yield* terminal.stderrColumns : undefined;
89
+ yield* renderStore(store, now, terminalColumns);
90
+ }
91
+
92
+ tick += 1;
93
+ yield* Effect.sleep(RENDER_INTERVAL_MILLIS);
94
+ }
95
+ }).pipe(
96
+ Effect.ensuring(
97
+ Effect.gen(function* () {
98
+ if (rendererActive) {
99
+ const store = yield* Ref.get(storeRef);
100
+ const now = yield* Clock.currentTimeMillis;
101
+ const terminalColumns = isTTY ? yield* terminal.stderrColumns : undefined;
102
+ yield* renderStore(store, now, terminalColumns);
103
+ }
104
+
105
+ yield* Effect.sync(() => {
106
+ instance?.unmount();
107
+ });
108
+ }),
109
+ ),
110
+ );
111
+
112
+ return yield* renderLoop;
113
+ }),
114
+ });
115
+
116
+ export class InkRenderer extends Context.Tag("stromseng.dev/effective-progress/InkRenderer")<
117
+ InkRenderer,
118
+ InkRendererService
119
+ >() {
120
+ static readonly Default = Layer.succeed(InkRenderer, InkRenderer.of(makeDefaultInkRenderer()));
121
+ }
@@ -0,0 +1,53 @@
1
+ import { Box } from "ink";
2
+ import { DEFAULT_BAR_WIDTH, type SharedColumnWidths } from "./layout";
3
+ import type { TaskRowModel } from "./types";
4
+ import {
5
+ AmountColumn,
6
+ BarColumn,
7
+ DescriptionColumn,
8
+ ElapsedColumn,
9
+ EtaColumn,
10
+ } from "./columns";
11
+
12
+ export interface TaskRowProps {
13
+ readonly row: TaskRowModel;
14
+ readonly now: number;
15
+ readonly tick: number;
16
+ readonly isTTY: boolean;
17
+ readonly widths: SharedColumnWidths;
18
+ }
19
+
20
+ export const TaskRow = ({ row, now, tick, isTTY, widths }: TaskRowProps) => {
21
+ const props = {
22
+ task: row.task,
23
+ tree: row.tree,
24
+ now,
25
+ tick,
26
+ isTTY,
27
+ showTree: widths.showTree,
28
+ } as const;
29
+
30
+ return (
31
+ <Box flexDirection="row" minWidth={widths.row}>
32
+ <Box width={widths.description} flexShrink={1} marginRight={1}>
33
+ <DescriptionColumn {...props} />
34
+ </Box>
35
+ {widths.bar > 0 ? (
36
+ <Box width={widths.bar} flexShrink={0} marginRight={1}>
37
+ <BarColumn {...props} width={Math.max(1, Math.min(widths.bar, DEFAULT_BAR_WIDTH))} />
38
+ </Box>
39
+ ) : null}
40
+ <Box width={widths.amount} flexShrink={0} marginRight={1}>
41
+ <AmountColumn {...props} />
42
+ </Box>
43
+ <Box width={widths.elapsed} flexShrink={0} marginRight={1}>
44
+ <ElapsedColumn {...props} />
45
+ </Box>
46
+ {widths.eta > 0 ? (
47
+ <Box width={widths.eta} flexShrink={0}>
48
+ <EtaColumn {...props} />
49
+ </Box>
50
+ ) : null}
51
+ </Box>
52
+ );
53
+ };
@@ -1,10 +1,4 @@
1
- import type { TaskSnapshot } from "../types";
2
- import type { TaskTreeInfo } from "./types";
3
-
4
- export interface OrderedTreeTask {
5
- readonly snapshot: TaskSnapshot;
6
- readonly depth: number;
7
- }
1
+ import type { OrderedTask, TaskTreeInfo } from "./types";
8
2
 
9
3
  const treeAncestorPrefix = (tree: TaskTreeInfo): string =>
10
4
  tree.ancestorHasNextSibling
@@ -22,8 +16,8 @@ export const renderTreePrefix = (tree: TaskTreeInfo): string => {
22
16
  };
23
17
 
24
18
  export const computeTreeInfo = (
25
- ordered: ReadonlyArray<OrderedTreeTask>,
26
- ): ReadonlyArray<OrderedTreeTask & { readonly tree: TaskTreeInfo }> => {
19
+ ordered: ReadonlyArray<OrderedTask>,
20
+ ): ReadonlyArray<OrderedTask & { readonly tree: TaskTreeInfo }> => {
27
21
  const hasNextSiblingByIndex: Array<boolean> = Array.from({ length: ordered.length }, () => false);
28
22
 
29
23
  for (let i = 0; i < ordered.length; i++) {
@@ -0,0 +1,18 @@
1
+ import type { TaskSnapshot } from "../types";
2
+
3
+ export interface TaskTreeInfo {
4
+ readonly depth: number;
5
+ readonly hasNextSibling: boolean;
6
+ readonly hasChildren: boolean;
7
+ readonly ancestorHasNextSibling: ReadonlyArray<boolean>;
8
+ }
9
+
10
+ export interface OrderedTask {
11
+ readonly snapshot: TaskSnapshot;
12
+ readonly depth: number;
13
+ }
14
+
15
+ export interface TaskRowModel {
16
+ readonly task: TaskSnapshot;
17
+ readonly tree: TaskTreeInfo;
18
+ }
package/src/runtime.ts CHANGED
@@ -1,48 +1,22 @@
1
1
  import { Clock, Context, Effect, Exit, FiberRef, Layer, Option, Ref } from "effect";
2
2
  import { dual } from "effect/Function";
3
- import { mergeWith } from "es-toolkit/object";
4
- import { formatWithOptions } from "node:util";
5
- import type { PartialDeep } from "type-fest";
6
- import { makeProgressConsole } from "./console";
7
- import { Columns, FrameRenderer } from "./renderer";
3
+ import { InkRenderer } from "./ink-renderer";
8
4
  import { ProgressTerminal } from "./terminal";
9
5
  import type {
10
6
  AddTaskOptions,
11
7
  ProgressService,
12
8
  RenderRow,
13
- RendererConfigShape,
14
9
  TaskStore,
15
10
  UpdateTaskOptions,
16
11
  } from "./types";
17
12
  import {
18
- decodeProgressBarConfigSync,
19
- decodeRendererConfigSync,
20
- defaultProgressBarConfig,
21
- defaultRendererConfig,
22
13
  DeterminateTaskUnits,
23
14
  IndeterminateTaskUnits,
24
- ProgressBarConfig,
25
- RendererConfig,
26
15
  Task,
27
16
  TaskId,
28
17
  TaskSnapshot,
29
18
  } from "./types";
30
19
 
31
- const mergeConfig = <T extends Record<PropertyKey, any>>(
32
- base: T,
33
- override: PartialDeep<T> | undefined,
34
- ): T =>
35
- mergeWith(
36
- structuredClone(base),
37
- (override ?? {}) as Record<PropertyKey, any>,
38
- (_targetValue, sourceValue) => {
39
- if (Array.isArray(sourceValue)) {
40
- return sourceValue;
41
- }
42
- return undefined;
43
- },
44
- ) as T;
45
-
46
20
  const updatedSnapshot = (snapshot: TaskSnapshot, options: UpdateTaskOptions): TaskSnapshot => {
47
21
  const currentUnits = snapshot.units;
48
22
  const units = (() => {
@@ -82,7 +56,6 @@ const updatedSnapshot = (snapshot: TaskSnapshot, options: UpdateTaskOptions): Ta
82
56
  status: snapshot.status,
83
57
  transient: options.transient ?? snapshot.transient,
84
58
  units,
85
- config: snapshot.config,
86
59
  startedAt: snapshot.startedAt,
87
60
  completedAt: snapshot.completedAt,
88
61
  });
@@ -96,7 +69,6 @@ const withTransient = (snapshot: TaskSnapshot, transient: boolean): TaskSnapshot
96
69
  status: snapshot.status,
97
70
  transient,
98
71
  units: snapshot.units,
99
- config: snapshot.config,
100
72
  startedAt: snapshot.startedAt,
101
73
  completedAt: snapshot.completedAt,
102
74
  });
@@ -130,62 +102,28 @@ const removeFromRenderOrder = (
130
102
  return next;
131
103
  };
132
104
 
133
- const withDefaultColumns = (rendererConfig: RendererConfigShape): RendererConfigShape => ({
134
- ...rendererConfig,
135
- columns: rendererConfig.columns.length > 0 ? rendererConfig.columns : Columns.defaults(),
136
- });
137
-
138
105
  const makeProgressService = Effect.gen(function* () {
139
- const rendererConfigOption = yield* Effect.serviceOption(RendererConfig);
140
- const progressBarConfigOption = yield* Effect.serviceOption(ProgressBarConfig);
141
-
142
- const rendererConfig = withDefaultColumns(
143
- decodeRendererConfigSync(
144
- mergeConfig(
145
- defaultRendererConfig,
146
- Option.isSome(rendererConfigOption) ? rendererConfigOption.value : undefined,
147
- ),
148
- ),
149
- );
150
-
151
- const progressBarConfig = decodeProgressBarConfigSync(
152
- mergeConfig(
153
- defaultProgressBarConfig,
154
- Option.isSome(progressBarConfigOption) ? progressBarConfigOption.value : undefined,
155
- ),
156
- );
157
-
158
106
  const terminal = yield* ProgressTerminal;
159
- const frameRenderer = yield* FrameRenderer;
107
+ const inkRenderer = yield* InkRenderer;
108
+ const outerConsole = yield* Effect.console;
160
109
  const isTTY = yield* terminal.isTTY;
161
- const maxRetainedLogLines = Math.max(0, Math.floor(rendererConfig.maxLogLines ?? 0));
162
110
 
163
111
  const nextTaskIdRef = yield* Ref.make(0);
164
112
  const storeRef = yield* Ref.make<TaskStore>({
165
113
  tasks: new Map<TaskId, TaskSnapshot>(),
166
114
  renderOrder: [],
167
115
  });
168
- const logsRef = yield* Ref.make<ReadonlyArray<string>>([]);
169
- const pendingLogsRef = yield* Ref.make<ReadonlyArray<string>>([]);
170
116
  const dirtyRef = yield* Ref.make(true);
171
117
  const currentParentRef = yield* FiberRef.make(Option.none<TaskId>());
172
118
  const scope = yield* Effect.scope;
173
119
 
174
- yield* Effect.forkIn(
175
- frameRenderer.run(
176
- storeRef,
177
- logsRef,
178
- pendingLogsRef,
179
- dirtyRef,
180
- terminal,
181
- isTTY,
182
- rendererConfig,
183
- maxRetainedLogLines,
184
- ),
185
- scope,
186
- );
187
-
188
120
  const markDirty = Ref.set(dirtyRef, true);
121
+ const log = (...args: ReadonlyArray<unknown>) =>
122
+ args.length === 0 ? Effect.void : outerConsole.log(...args);
123
+
124
+ yield* Effect.forkIn(inkRenderer.run(storeRef, dirtyRef, terminal, isTTY), scope);
125
+ // Let the renderer fiber start so queued logs are reliably flushed on scope teardown.
126
+ yield* Effect.sleep("0 millis");
189
127
 
190
128
  const addTask = (options: AddTaskOptions) =>
191
129
  Effect.gen(function* () {
@@ -202,10 +140,6 @@ const makeProgressService = Effect.gen(function* () {
202
140
  const parentSnapshot = Option.isSome(resolvedParentId)
203
141
  ? store.tasks.get(resolvedParentId.value)
204
142
  : undefined;
205
- const inheritedProgressBarConfig = parentSnapshot?.config ?? progressBarConfig;
206
- const resolvedProgressBarConfig = decodeProgressBarConfigSync(
207
- mergeConfig(inheritedProgressBarConfig, options.progressbar),
208
- );
209
143
 
210
144
  const now = yield* Clock.currentTimeMillis;
211
145
  const parentIdValue = Option.getOrNull(resolvedParentId);
@@ -216,7 +150,6 @@ const makeProgressService = Effect.gen(function* () {
216
150
  status: "running",
217
151
  transient: parentSnapshot?.transient ?? options.transient ?? false,
218
152
  units,
219
- config: resolvedProgressBarConfig,
220
153
  startedAt: now,
221
154
  completedAt: null,
222
155
  });
@@ -292,7 +225,6 @@ const makeProgressService = Effect.gen(function* () {
292
225
  status: snapshot.status,
293
226
  transient: snapshot.transient,
294
227
  units,
295
- config: snapshot.config,
296
228
  startedAt: snapshot.startedAt,
297
229
  completedAt: snapshot.completedAt,
298
230
  }),
@@ -332,7 +264,6 @@ const makeProgressService = Effect.gen(function* () {
332
264
  total: snapshot.units.total,
333
265
  })
334
266
  : snapshot.units,
335
- config: snapshot.config,
336
267
  startedAt: snapshot.startedAt,
337
268
  completedAt: now,
338
269
  }),
@@ -367,7 +298,6 @@ const makeProgressService = Effect.gen(function* () {
367
298
  status: "failed",
368
299
  transient: snapshot.transient,
369
300
  units: snapshot.units,
370
- config: snapshot.config,
371
301
  startedAt: snapshot.startedAt,
372
302
  completedAt: now,
373
303
  }),
@@ -377,36 +307,6 @@ const makeProgressService = Effect.gen(function* () {
377
307
  yield* markDirty;
378
308
  });
379
309
 
380
- const appendLog = (args: ReadonlyArray<unknown>) =>
381
- Effect.gen(function* () {
382
- if (args.length === 0) {
383
- return;
384
- }
385
-
386
- const message = formatWithOptions(
387
- {
388
- colors: isTTY,
389
- depth: 6,
390
- },
391
- ...args,
392
- );
393
-
394
- yield* Ref.update(pendingLogsRef, (logs) => [...logs, message]);
395
- if (maxRetainedLogLines > 0) {
396
- yield* Ref.update(logsRef, (logs) => {
397
- const next = [...logs, message];
398
- if (next.length <= maxRetainedLogLines) {
399
- return next;
400
- }
401
- return next.slice(next.length - maxRetainedLogLines);
402
- });
403
- }
404
-
405
- yield* markDirty;
406
- });
407
-
408
- const log = (...args: ReadonlyArray<unknown>) => appendLog(args);
409
-
410
310
  const getTask = (taskId: TaskId) =>
411
311
  Ref.get(storeRef).pipe(Effect.map((store) => Option.fromNullable(store.tasks.get(taskId))));
412
312
 
@@ -416,7 +316,6 @@ const makeProgressService = Effect.gen(function* () {
416
316
  2,
417
317
  <A, E, R>(effect: Effect.Effect<A, E, R>, options: AddTaskOptions) =>
418
318
  Effect.gen(function* () {
419
- const outerConsole = yield* Effect.console;
420
319
  const inheritedParentId = yield* FiberRef.get(currentParentRef);
421
320
  const resolvedParentId =
422
321
  options.parentId === undefined ? inheritedParentId : Option.some(options.parentId);
@@ -428,10 +327,7 @@ const makeProgressService = Effect.gen(function* () {
428
327
  });
429
328
 
430
329
  return yield* Effect.locally(
431
- Effect.withConsole(
432
- Effect.provideService(effect, Task, taskId),
433
- makeProgressConsole(log, outerConsole),
434
- ),
330
+ Effect.provideService(effect, Task, taskId),
435
331
  currentParentRef,
436
332
  Option.some(taskId),
437
333
  );
@@ -484,11 +380,11 @@ export class Progress extends Context.Tag("stromseng.dev/effective-progress/Prog
484
380
  static readonly Default = Layer.unwrapEffect(
485
381
  Effect.gen(function* () {
486
382
  const terminalOption = yield* Effect.serviceOption(ProgressTerminal);
487
- const frameRendererOption = yield* Effect.serviceOption(FrameRenderer);
383
+ const inkRendererOption = yield* Effect.serviceOption(InkRenderer);
488
384
  let layer: Layer.Layer<Progress, never, any> = Layer.scoped(Progress, makeProgressService);
489
385
 
490
- if (Option.isNone(frameRendererOption)) {
491
- layer = layer.pipe(Layer.provide(FrameRenderer.Default));
386
+ if (Option.isNone(inkRendererOption)) {
387
+ layer = layer.pipe(Layer.provide(InkRenderer.Default));
492
388
  }
493
389
  if (Option.isNone(terminalOption)) {
494
390
  layer = layer.pipe(Layer.provide(ProgressTerminal.Default));