@vitest-agent/ui 2.2.13 → 2.3.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.
@@ -36,7 +36,11 @@ const classifyRunShape = (state, projects) => {
36
36
  * run whose only non-passing signal is one or more timed-out tests
37
37
  * (`totals.timeoutCount > 0`, `totals.failCount === 0`) also
38
38
  * classifies as `some-fail` — timeouts are not passes, and must not
39
- * route to a passing cell (issue #224).
39
+ * route to a passing cell (issue #224). A run whose only non-passing
40
+ * signal is a process-level unhandled error (a non-empty
41
+ * `unhandledErrors`, all counts otherwise clean) also classifies as `some-fail` —
42
+ * a green-looking count must never hide an unhandled error behind an
43
+ * all-pass cell (issue #240).
40
44
  *
41
45
  * @param state - the fully-reduced render state
42
46
  * @returns the outcome classification
@@ -44,6 +48,7 @@ const classifyRunShape = (state, projects) => {
44
48
  */
45
49
  const classifyOutcome = (state) => {
46
50
  if (state.totals.failCount > 0) return "some-fail";
51
+ if (state.unhandledErrors.length > 0) return "some-fail";
47
52
  if (state.totals.timeoutCount > 0) return "some-fail";
48
53
  if (state.coverage !== null && state.coverage.violations.length > 0) return "threshold-violation";
49
54
  return "all-pass";
@@ -10,6 +10,9 @@ import { renderSingleTestThreshold } from "./cells/single-test-threshold.js";
10
10
  import { renderWorkspaceFail } from "./cells/workspace-fail.js";
11
11
  import { renderWorkspacePass } from "./cells/workspace-pass.js";
12
12
  import { renderWorkspaceThreshold } from "./cells/workspace-threshold.js";
13
+ import { formatScopedCoverageNote } from "@vitest-agent/sdk";
14
+ import { Box, Text } from "ink";
15
+ import { createElement } from "react";
13
16
 
14
17
  //#region src/dispatcher/dispatch.ts
15
18
  /**
@@ -51,7 +54,24 @@ const dispatcherTable = {
51
54
  * @public
52
55
  */
53
56
  const dispatch = (inputs, opts) => {
54
- return dispatcherTable[inputs.shape][inputs.outcome].agent(inputs, opts);
57
+ const body = dispatcherTable[inputs.shape][inputs.outcome].agent(inputs, opts);
58
+ const note = scopedCoverageNoteFor(inputs);
59
+ return note !== null ? `${body}\n${note}` : body;
60
+ };
61
+ /**
62
+ * Build the scoped-coverage note for this run, or `null` on a full
63
+ * (non-scoped) run. Shared by {@link dispatch} and {@link dispatchInk} so
64
+ * both render paths surface the same information (issue #160 gap 1) —
65
+ * Vitest's coverage thresholds are meaningless against a subset of the
66
+ * project's test files, so every cell's own threshold-flavored coverage
67
+ * text is followed by an explanation of why to disregard it.
68
+ *
69
+ * @internal
70
+ */
71
+ const scopedCoverageNoteFor = (inputs) => {
72
+ const cov = inputs.state.coverage;
73
+ if (cov === null || cov.scoped !== true) return null;
74
+ return formatScopedCoverageNote(cov.scopedFiles ?? 0, cov.totalFiles);
55
75
  };
56
76
  /**
57
77
  * Dispatch to the cell that matches `(inputs.shape, inputs.outcome)`
@@ -66,7 +86,11 @@ const dispatch = (inputs, opts) => {
66
86
  */
67
87
  const dispatchInk = (inputs, opts) => {
68
88
  const cell = dispatcherTable[inputs.shape][inputs.outcome];
69
- return cell.ink !== void 0 ? cell.ink(inputs, opts) : null;
89
+ if (cell.ink === void 0) return null;
90
+ const element = cell.ink(inputs, opts);
91
+ const note = scopedCoverageNoteFor(inputs);
92
+ if (note === null) return element;
93
+ return createElement(Box, { flexDirection: "column" }, element, createElement(Text, null, note));
70
94
  };
71
95
 
72
96
  //#endregion
@@ -102,9 +102,13 @@ const formatTrendLine = (trend) => {
102
102
  * project carrying failures or violations.
103
103
  */
104
104
  const formatProjectRow = (project, nameWidth) => {
105
- const total = project.passCount + project.failCount + project.skipCount;
106
- const glyph = project.failCount > 0 ? "✗" : "✓";
107
- const counts = project.failCount > 0 ? `${project.passCount}/${total} passed, ${project.failCount} failed` : `${project.passCount} passed`;
105
+ const timeoutCount = project.timeoutCount ?? 0;
106
+ const total = project.passCount + project.failCount + project.skipCount + timeoutCount;
107
+ const glyph = project.failCount > 0 || timeoutCount > 0 ? "✗" : "✓";
108
+ const countParts = [`${project.passCount}/${total} passed`];
109
+ if (project.failCount > 0) countParts.push(`${project.failCount} failed`);
110
+ if (timeoutCount > 0) countParts.push(`${timeoutCount} timed out`);
111
+ const counts = project.failCount > 0 || timeoutCount > 0 ? countParts.join(", ") : `${project.passCount} passed`;
108
112
  const tagSuffix = formatTagCountSuffix(project.tagCounts);
109
113
  const base = ` ${glyph} ${project.name.padEnd(nameWidth)} ${counts} (${formatDisplayDuration(project.durationMs)})`;
110
114
  return tagSuffix.length === 0 ? base : `${base} ${tagSuffix}`;
@@ -132,36 +136,46 @@ const formatWorkspaceTotal = (projects) => {
132
136
  let pass = 0;
133
137
  let fail = 0;
134
138
  let skip = 0;
139
+ let timeout = 0;
135
140
  let durationMs = 0;
136
141
  for (const p of projects) {
137
142
  pass += p.passCount;
138
143
  fail += p.failCount;
139
144
  skip += p.skipCount;
145
+ timeout += p.timeoutCount ?? 0;
140
146
  durationMs += p.durationMs;
141
147
  }
142
- const total = pass + fail + skip;
148
+ const total = pass + fail + skip + timeout;
143
149
  const parts = [`${pass}/${total} passed`];
144
150
  if (fail > 0) parts.push(`${fail} failed`);
151
+ if (timeout > 0) parts.push(`${timeout} timed out`);
145
152
  if (skip > 0) parts.push(`${skip} skipped`);
146
153
  return `Total: ${parts.join(", ")} (${formatDisplayDuration(durationMs)})`;
147
154
  };
148
- const TABLE_COL_FILE = 60;
155
+ const TABLE_COL_FILE_MIN = 60;
149
156
  /**
150
157
  * Format the `Files below aspirational target:` block — a pipe-delimited
151
158
  * table truncated to the first `limit` files with a "+N more" suffix.
152
159
  * Returns an empty array when `belowTarget` is empty.
160
+ *
161
+ * The file column is sized to the longest path among the printed rows
162
+ * (never narrower than {@link TABLE_COL_FILE_MIN}), so a printed path is
163
+ * never truncated (issue #237 follow-up) — only the omitted rows (past
164
+ * `limit`) are unaffected, since their width never entered the
165
+ * calculation.
153
166
  */
154
167
  const formatBelowTargetTable = (belowTarget, limit) => {
155
168
  if (belowTarget.length === 0) return [];
156
169
  const top = belowTarget.slice(0, limit);
157
170
  const omitted = belowTarget.length - top.length;
171
+ const fileColWidth = Math.max(TABLE_COL_FILE_MIN, ...top.map((f) => f.file.length + 2));
158
172
  const header = [
159
173
  "Files below aspirational target:",
160
- buildTableSeparator(),
161
- buildTableHeader(),
162
- buildTableSeparator()
174
+ buildTableSeparator(fileColWidth),
175
+ buildTableHeader(fileColWidth),
176
+ buildTableSeparator(fileColWidth)
163
177
  ];
164
- const rows = top.map((file) => buildTableRow(file));
178
+ const rows = top.map((file) => buildTableRow(file, fileColWidth));
165
179
  const footer = [];
166
180
  if (omitted > 0) footer.push(`… ${omitted} more (use the test_coverage MCP tool for the full list)`);
167
181
  return [
@@ -170,14 +184,14 @@ const formatBelowTargetTable = (belowTarget, limit) => {
170
184
  ...footer
171
185
  ];
172
186
  };
173
- const buildTableSeparator = () => {
174
- return `${"-".repeat(TABLE_COL_FILE)}|---------|---------|---------|---------|-------------------`;
187
+ const buildTableSeparator = (fileColWidth) => {
188
+ return `${"-".repeat(fileColWidth)}|---------|---------|---------|---------|-------------------`;
175
189
  };
176
- const buildTableHeader = () => {
177
- return ` ${"File".padEnd(59)}| % Stmts | % Branch| % Funcs | % Lines | Uncovered Line #s`;
190
+ const buildTableHeader = (fileColWidth) => {
191
+ return ` ${"File".padEnd(fileColWidth - 1)}| % Stmts | % Branch| % Funcs | % Lines | Uncovered Line #s`;
178
192
  };
179
- const buildTableRow = (file) => {
180
- return `${` ${truncate(file.file, 58).padEnd(59)}`}|${pctCell(file.summary.statements)}|${pctCell(file.summary.branches)}|${pctCell(file.summary.functions)}|${pctCell(file.summary.lines)}|${` ${file.uncoveredLines}`}`;
193
+ const buildTableRow = (file, fileColWidth) => {
194
+ return `${` ${file.file.padEnd(fileColWidth - 1)}`}|${pctCell(file.summary.statements)}|${pctCell(file.summary.branches)}|${pctCell(file.summary.functions)}|${pctCell(file.summary.lines)}|${` ${file.uncoveredLines}`}`;
181
195
  };
182
196
  const pctCell = (n) => {
183
197
  const text = `${Math.round(n)}`;
package/index.d.ts CHANGED
@@ -41,7 +41,7 @@ type FileCoverageReport = typeof FileCoverageReport.Type;
41
41
  * Per-test record accumulated as `TestStarted` / `TestFinished` events arrive.
42
42
  * @public
43
43
  */
44
- declare const TestRecord: Schema.Struct<{
44
+ export declare const TestRecord: Schema.Struct<{
45
45
  readonly testName: Schema.String;
46
46
  readonly suitePath: Schema.$Array<Schema.String>;
47
47
  readonly status: Schema.Union<readonly [Schema.Literals<readonly ["passed", "failed", "skipped", "pending"]>, Schema.Literals<readonly ["running", "timed-out"]>]>;
@@ -55,13 +55,13 @@ declare const TestRecord: Schema.Struct<{
55
55
  }>>;
56
56
  }>;
57
57
  /** @public */
58
- type TestRecord = typeof TestRecord.Type;
58
+ export type TestRecord = typeof TestRecord.Type;
59
59
  /**
60
60
  * Per-module aggregation. The reducer keeps modules keyed on
61
61
  * `modulePath` so out-of-order events can update the right slot.
62
62
  * @public
63
63
  */
64
- declare const ModuleRecord: Schema.Struct<{
64
+ export declare const ModuleRecord: Schema.Struct<{
65
65
  readonly modulePath: Schema.String;
66
66
  readonly status: Schema.Literals<readonly ["queued", "running", "finished"]>;
67
67
  readonly passCount: Schema.Number;
@@ -98,14 +98,14 @@ declare const ModuleRecord: Schema.Struct<{
98
98
  readonly tagCounts: Schema.optional<Schema.$Record<Schema.String, Schema.Number>>;
99
99
  }>;
100
100
  /** @public */
101
- type ModuleRecord = typeof ModuleRecord.Type;
101
+ export type ModuleRecord = typeof ModuleRecord.Type;
102
102
  /**
103
103
  * One row in the failure list. A failure can exist before its
104
104
  * classification arrives (`FailureClassified` is emitted from a
105
105
  * separate pipeline), so `classification` is nullable.
106
106
  * @public
107
107
  */
108
- declare const FailureRecord: Schema.Struct<{
108
+ export declare const FailureRecord: Schema.Struct<{
109
109
  readonly modulePath: Schema.String;
110
110
  readonly testName: Schema.String;
111
111
  readonly suitePath: Schema.$Array<Schema.String>;
@@ -120,13 +120,13 @@ declare const FailureRecord: Schema.Struct<{
120
120
  readonly classification: Schema.NullOr<Schema.Literals<readonly ["stable", "new-failure", "persistent", "flaky", "recovered"]>>;
121
121
  }>;
122
122
  /** @public */
123
- type FailureRecord = typeof FailureRecord.Type;
123
+ export type FailureRecord = typeof FailureRecord.Type;
124
124
  /**
125
125
  * Coverage block, populated only after `CoverageReady`. Threshold
126
126
  * violations are accumulated separately as they arrive.
127
127
  * @public
128
128
  */
129
- declare const CoverageRenderState: Schema.Struct<{
129
+ export declare const CoverageRenderState: Schema.Struct<{
130
130
  readonly metrics: Schema.Struct<{
131
131
  readonly statements: Schema.Number;
132
132
  readonly branches: Schema.Number;
@@ -154,27 +154,30 @@ declare const CoverageRenderState: Schema.Struct<{
154
154
  readonly expected: Schema.Number;
155
155
  readonly actual: Schema.Number;
156
156
  }>>;
157
+ readonly scoped: Schema.optional<Schema.Boolean>;
158
+ readonly scopedFiles: Schema.optional<Schema.Number>;
159
+ readonly totalFiles: Schema.optional<Schema.Number>;
157
160
  }>;
158
161
  /** @public */
159
- type CoverageRenderState = typeof CoverageRenderState.Type;
162
+ export type CoverageRenderState = typeof CoverageRenderState.Type;
160
163
  /**
161
164
  * A `SuggestedAction` event, denormalized into the queue the renderer reads.
162
165
  * @public
163
166
  */
164
- declare const SuggestedActionRecord: Schema.Struct<{
167
+ export declare const SuggestedActionRecord: Schema.Struct<{
165
168
  readonly severity: Schema.Literals<readonly ["info", "warn", "blocker"]>;
166
169
  readonly title: Schema.String;
167
170
  readonly detail: Schema.String;
168
171
  readonly targetTool: Schema.optional<Schema.String>;
169
172
  }>;
170
173
  /** @public */
171
- type SuggestedActionRecord = typeof SuggestedActionRecord.Type;
174
+ export type SuggestedActionRecord = typeof SuggestedActionRecord.Type;
172
175
  /**
173
176
  * Per-run totals, recomputed on every `ModuleFinished` and reconciled
174
177
  * against `RunFinished` (which carries the runner's own count).
175
178
  * @public
176
179
  */
177
- declare const RenderTotals: Schema.Struct<{
180
+ export declare const RenderTotals: Schema.Struct<{
178
181
  readonly passCount: Schema.Number;
179
182
  readonly failCount: Schema.Number;
180
183
  readonly skipCount: Schema.Number;
@@ -182,7 +185,7 @@ declare const RenderTotals: Schema.Struct<{
182
185
  readonly durationMs: Schema.Number;
183
186
  }>;
184
187
  /** @public */
185
- type RenderTotals = typeof RenderTotals.Type;
188
+ export type RenderTotals = typeof RenderTotals.Type;
186
189
  /**
187
190
  * The renderer state. Both the human (Ink) and agent (string)
188
191
  * renderers read this shape; the reducer is the only producer.
@@ -194,7 +197,7 @@ type RenderTotals = typeof RenderTotals.Type;
194
197
  * rather than the suite completing.
195
198
  * @public
196
199
  */
197
- declare const RenderState: Schema.Struct<{
200
+ export declare const RenderState: Schema.Struct<{
198
201
  readonly phase: Schema.Literals<readonly ["idle", "running", "finished", "timed-out"]>;
199
202
  readonly runId: Schema.NullOr<Schema.String>;
200
203
  readonly configHash: Schema.NullOr<Schema.String>;
@@ -272,6 +275,9 @@ declare const RenderState: Schema.Struct<{
272
275
  readonly expected: Schema.Number;
273
276
  readonly actual: Schema.Number;
274
277
  }>>;
278
+ readonly scoped: Schema.optional<Schema.Boolean>;
279
+ readonly scopedFiles: Schema.optional<Schema.Number>;
280
+ readonly totalFiles: Schema.optional<Schema.Number>;
275
281
  }>>;
276
282
  readonly trend: Schema.NullOr<Schema.Struct<{
277
283
  readonly direction: Schema.Literals<readonly ["improving", "regressing", "stable"]>;
@@ -307,16 +313,29 @@ declare const RenderState: Schema.Struct<{
307
313
  * fixtures) falls back to `moduleOrder.length`.
308
314
  */
309
315
  readonly collectedModules: Schema.optional<Schema.Number>;
316
+ /**
317
+ * Process-level errors with no owning module, folded from
318
+ * `RunFinished.unhandledErrors`. A run that never carried the field
319
+ * (older replay data, hand-built fixtures) defaults to `[]`. See
320
+ * issue #240.
321
+ */
322
+ readonly unhandledErrors: Schema.$Array<Schema.Struct<{
323
+ readonly message: Schema.String;
324
+ readonly stack: Schema.optional<Schema.String>;
325
+ readonly diff: Schema.optional<Schema.String>;
326
+ readonly expected: Schema.optional<Schema.String>;
327
+ readonly received: Schema.optional<Schema.String>;
328
+ }>>;
310
329
  }>;
311
330
  /** @public */
312
- type RenderState = typeof RenderState.Type;
331
+ export type RenderState = typeof RenderState.Type;
313
332
  /**
314
333
  * Initial state — what the reducer returns when no events have been
315
334
  * applied yet. Exported so tests and the renderers' bootstrapping
316
335
  * code share one definition.
317
336
  * @public
318
337
  */
319
- declare const initialRenderState: RenderState;
338
+ export declare const initialRenderState: RenderState;
320
339
  //#endregion
321
340
  //#region src/contracts/dispatcher.d.ts
322
341
  /**
@@ -356,6 +375,7 @@ interface ProjectSummary {
356
375
  readonly failCount: number;
357
376
  readonly skipCount: number;
358
377
  readonly durationMs: number;
378
+ readonly timeoutCount?: number;
359
379
  readonly tagCounts?: Record<string, number>;
360
380
  readonly belowTarget?: number;
361
381
  readonly violations?: number;
@@ -549,6 +569,7 @@ declare const AgentReport: Schema.Struct<{
549
569
  }>>;
550
570
  readonly scoped: Schema.withDecodingDefaultKey<Schema.Boolean, never>;
551
571
  readonly scopedFiles: Schema.optional<Schema.$Array<Schema.String>>;
572
+ readonly totalFiles: Schema.optional<Schema.Number>;
552
573
  readonly lowCoverage: Schema.$Array<Schema.Struct<{
553
574
  readonly file: Schema.String;
554
575
  readonly summary: Schema.Struct<{
@@ -587,6 +608,10 @@ declare const AgentReport: Schema.Struct<{
587
608
  readonly sample: Schema.optional<Schema.String>;
588
609
  }>>;
589
610
  readonly truncated: Schema.optional<Schema.Boolean>;
611
+ readonly fromFailingTests: Schema.optional<Schema.Struct<{
612
+ readonly total: Schema.Number;
613
+ readonly files: Schema.Number;
614
+ }>>;
590
615
  }>>;
591
616
  }>;
592
617
  /** @public */
@@ -604,22 +629,22 @@ type TestClassification = typeof TestClassification.Type;
604
629
  * Coverage metric whose threshold can be violated independently.
605
630
  * @public
606
631
  */
607
- declare const CoverageMetric: Schema.Literals<readonly ["lines", "branches", "functions", "statements"]>;
632
+ export declare const CoverageMetric: Schema.Literals<readonly ["lines", "branches", "functions", "statements"]>;
608
633
  /** @public */
609
- type CoverageMetric = typeof CoverageMetric.Type;
634
+ export type CoverageMetric = typeof CoverageMetric.Type;
610
635
  /**
611
636
  * Severity tier for a suggested action emitted during a run.
612
637
  * @public
613
638
  */
614
- declare const ActionSeverity: Schema.Literals<readonly ["info", "warn", "blocker"]>;
639
+ export declare const ActionSeverity: Schema.Literals<readonly ["info", "warn", "blocker"]>;
615
640
  /** @public */
616
- type ActionSeverity = typeof ActionSeverity.Type;
641
+ export type ActionSeverity = typeof ActionSeverity.Type;
617
642
  /**
618
643
  * A coverage gap surfaced to the renderer — a per-file shortfall that
619
644
  * a renderer may want to call out individually.
620
645
  * @public
621
646
  */
622
- declare const CoverageGap: Schema.Struct<{
647
+ export declare const CoverageGap: Schema.Struct<{
623
648
  readonly file: Schema.String;
624
649
  readonly missing: Schema.Struct<{
625
650
  readonly statements: Schema.Number;
@@ -630,7 +655,7 @@ declare const CoverageGap: Schema.Struct<{
630
655
  readonly uncoveredLines: Schema.optional<Schema.String>;
631
656
  }>;
632
657
  /** @public */
633
- type CoverageGap = typeof CoverageGap.Type;
658
+ export type CoverageGap = typeof CoverageGap.Type;
634
659
  /**
635
660
  * The reducer projects this union into `RenderState`. Renderers
636
661
  * never read the raw event stream — they read the projected state.
@@ -641,7 +666,7 @@ type CoverageGap = typeof CoverageGap.Type;
641
666
  * been updated to consume the new state shape.
642
667
  * @public
643
668
  */
644
- declare const RunEvent: Schema.Union<readonly [Schema.TaggedStruct<"RunStarted", {
669
+ export declare const RunEvent: Schema.Union<readonly [Schema.TaggedStruct<"RunStarted", {
645
670
  readonly runId: Schema.String;
646
671
  readonly startedAt: Schema.String;
647
672
  readonly configHash: Schema.String;
@@ -758,6 +783,9 @@ declare const RunEvent: Schema.Union<readonly [Schema.TaggedStruct<"RunStarted",
758
783
  }>;
759
784
  readonly uncoveredLines: Schema.optional<Schema.String>;
760
785
  }>>;
786
+ readonly scoped: Schema.optional<Schema.Boolean>;
787
+ readonly scopedFiles: Schema.optional<Schema.Number>;
788
+ readonly totalFiles: Schema.optional<Schema.Number>;
761
789
  }>, Schema.TaggedStruct<"ThresholdViolation", {
762
790
  readonly metric: Schema.Literals<readonly ["lines", "branches", "functions", "statements"]>;
763
791
  readonly expected: Schema.Number;
@@ -787,9 +815,22 @@ declare const RunEvent: Schema.Union<readonly [Schema.TaggedStruct<"RunStarted",
787
815
  * tracks failing modules (report replay). See issue #204.
788
816
  */
789
817
  readonly collectedModules: Schema.optional<Schema.Number>;
818
+ /**
819
+ * Process-level errors with no owning module (an unhandled
820
+ * rejection, a worker crash) — Vitest's `unhandledErrors`
821
+ * argument to `onTestRunEnd`. Optional so hand-built fixtures
822
+ * and older replay data keep decoding. See issue #240.
823
+ */
824
+ readonly unhandledErrors: Schema.optional<Schema.$Array<Schema.Struct<{
825
+ readonly message: Schema.String;
826
+ readonly stack: Schema.optional<Schema.String>;
827
+ readonly diff: Schema.optional<Schema.String>;
828
+ readonly expected: Schema.optional<Schema.String>;
829
+ readonly received: Schema.optional<Schema.String>;
830
+ }>>>;
790
831
  }>]>;
791
832
  /** @public */
792
- type RunEvent = typeof RunEvent.Type;
833
+ export type RunEvent = typeof RunEvent.Type;
793
834
  /**
794
835
  * Convenience: discriminator-keyed map of the individual variants.
795
836
  *
@@ -1003,7 +1044,7 @@ interface Cell {
1003
1044
  * @returns the run shape classification
1004
1045
  * @public
1005
1046
  */
1006
- declare const classifyRunShape: (state: RenderState, projects: ReadonlyArray<ProjectSummary>) => RunShape;
1047
+ export declare const classifyRunShape: (state: RenderState, projects: ReadonlyArray<ProjectSummary>) => RunShape;
1007
1048
  /**
1008
1049
  * Compute the outcome class from the reduced state.
1009
1050
  *
@@ -1014,13 +1055,17 @@ declare const classifyRunShape: (state: RenderState, projects: ReadonlyArray<Pro
1014
1055
  * run whose only non-passing signal is one or more timed-out tests
1015
1056
  * (`totals.timeoutCount > 0`, `totals.failCount === 0`) also
1016
1057
  * classifies as `some-fail` — timeouts are not passes, and must not
1017
- * route to a passing cell (issue #224).
1058
+ * route to a passing cell (issue #224). A run whose only non-passing
1059
+ * signal is a process-level unhandled error (a non-empty
1060
+ * `unhandledErrors`, all counts otherwise clean) also classifies as `some-fail` —
1061
+ * a green-looking count must never hide an unhandled error behind an
1062
+ * all-pass cell (issue #240).
1018
1063
  *
1019
1064
  * @param state - the fully-reduced render state
1020
1065
  * @returns the outcome classification
1021
1066
  * @public
1022
1067
  */
1023
- declare const classifyOutcome: (state: RenderState) => RunOutcome;
1068
+ export declare const classifyOutcome: (state: RenderState) => RunOutcome;
1024
1069
  //#endregion
1025
1070
  //#region src/dispatcher/dispatch.d.ts
1026
1071
  /**
@@ -1030,7 +1075,7 @@ declare const classifyOutcome: (state: RenderState) => RunOutcome;
1030
1075
  *
1031
1076
  * @public
1032
1077
  */
1033
- declare const dispatcherTable: Readonly<Record<RunShape, Readonly<Record<RunOutcome, Cell>>>>;
1078
+ export declare const dispatcherTable: Readonly<Record<RunShape, Readonly<Record<RunOutcome, Cell>>>>;
1034
1079
  /**
1035
1080
  * Dispatch to the cell that matches `(inputs.shape, inputs.outcome)`
1036
1081
  * and return its agent-half string output.
@@ -1040,7 +1085,7 @@ declare const dispatcherTable: Readonly<Record<RunShape, Readonly<Record<RunOutc
1040
1085
  * @returns the agent-string output for the matched cell
1041
1086
  * @public
1042
1087
  */
1043
- declare const dispatch: (inputs: DispatchInputs, opts: CellOptions) => string;
1088
+ export declare const dispatch: (inputs: DispatchInputs, opts: CellOptions) => string;
1044
1089
  /**
1045
1090
  * Dispatch to the cell that matches `(inputs.shape, inputs.outcome)`
1046
1091
  * and return its Ink-half React tree. Returns `null` when the matched
@@ -1052,7 +1097,7 @@ declare const dispatch: (inputs: DispatchInputs, opts: CellOptions) => string;
1052
1097
  * @returns the Ink React element, or `null` when the cell has no Ink half
1053
1098
  * @public
1054
1099
  */
1055
- declare const dispatchInk: (inputs: DispatchInputs, opts: CellOptions) => ReactElement | null;
1100
+ export declare const dispatchInk: (inputs: DispatchInputs, opts: CellOptions) => ReactElement | null;
1056
1101
  //#endregion
1057
1102
  //#region src/dispatcher/footer.d.ts
1058
1103
  /**
@@ -1066,7 +1111,7 @@ declare const dispatchInk: (inputs: DispatchInputs, opts: CellOptions) => ReactE
1066
1111
  * @returns the most actionable classification, or `null` when none applies
1067
1112
  * @public
1068
1113
  */
1069
- declare const dominantClassification: (state: RenderState) => TestClassification | null;
1114
+ export declare const dominantClassification: (state: RenderState) => TestClassification | null;
1070
1115
  /**
1071
1116
  * Compose the trailing footer lines for a dispatcher cell. Returns an
1072
1117
  * empty string when no pointer applies. The footer starts with a
@@ -1077,7 +1122,7 @@ declare const dominantClassification: (state: RenderState) => TestClassification
1077
1122
  * @returns the footer string, or an empty string when no pointer applies
1078
1123
  * @public
1079
1124
  */
1080
- declare const buildFooter: (inputs: DispatchInputs) => string;
1125
+ export declare const buildFooter: (inputs: DispatchInputs) => string;
1081
1126
  //#endregion
1082
1127
  //#region src/format-duration.d.ts
1083
1128
  /**
@@ -1105,7 +1150,7 @@ declare const buildFooter: (inputs: DispatchInputs) => string;
1105
1150
  * @returns formatted duration string
1106
1151
  * @public
1107
1152
  */
1108
- declare const formatDisplayDuration: (ms: number) => string;
1153
+ export declare const formatDisplayDuration: (ms: number) => string;
1109
1154
  //#endregion
1110
1155
  //#region src/pubsub/Channel.d.ts
1111
1156
  declare const RunEventChannel_base: Context.ServiceClass<RunEventChannel, "vitest-agent-ui/RunEventChannel", PubSub.PubSub<{
@@ -1247,6 +1292,9 @@ declare const RunEventChannel_base: Context.ServiceClass<RunEventChannel, "vites
1247
1292
  };
1248
1293
  readonly uncoveredLines?: string | undefined;
1249
1294
  }[];
1295
+ readonly scoped?: boolean | undefined;
1296
+ readonly scopedFiles?: number | undefined;
1297
+ readonly totalFiles?: number | undefined;
1250
1298
  } | {
1251
1299
  readonly _tag: "ThresholdViolation";
1252
1300
  readonly metric: "branches" | "functions" | "lines" | "statements";
@@ -1273,6 +1321,13 @@ declare const RunEventChannel_base: Context.ServiceClass<RunEventChannel, "vites
1273
1321
  readonly durationMs: number;
1274
1322
  readonly timeoutCount?: number | undefined;
1275
1323
  readonly collectedModules?: number | undefined;
1324
+ readonly unhandledErrors?: readonly {
1325
+ readonly message: string;
1326
+ readonly stack?: string | undefined;
1327
+ readonly diff?: string | undefined;
1328
+ readonly expected?: string | undefined;
1329
+ readonly received?: string | undefined;
1330
+ }[] | undefined;
1276
1331
  }>>;
1277
1332
  /**
1278
1333
  * Service tag for the run-event channel. Consumers depend on this tag;
@@ -1281,7 +1336,7 @@ declare const RunEventChannel_base: Context.ServiceClass<RunEventChannel, "vites
1281
1336
  *
1282
1337
  * @public
1283
1338
  */
1284
- declare class RunEventChannel extends RunEventChannel_base {}
1339
+ export declare class RunEventChannel extends RunEventChannel_base {}
1285
1340
  /**
1286
1341
  * Default live layer providing an unbounded Effect `PubSub` for
1287
1342
  * the run-event channel. The PubSub is scoped — when the surrounding
@@ -1297,7 +1352,7 @@ declare class RunEventChannel extends RunEventChannel_base {}
1297
1352
  *
1298
1353
  * @public
1299
1354
  */
1300
- declare const RunEventChannelLive: Layer.Layer<RunEventChannel>;
1355
+ export declare const RunEventChannelLive: Layer.Layer<RunEventChannel>;
1301
1356
  //#endregion
1302
1357
  //#region src/pubsub/Publisher.d.ts
1303
1358
  /**
@@ -1311,7 +1366,7 @@ declare const RunEventChannelLive: Layer.Layer<RunEventChannel>;
1311
1366
  * @returns an Effect that resolves to the publish success boolean
1312
1367
  * @public
1313
1368
  */
1314
- declare const publish: (event: RunEvent) => Effect.Effect<boolean, never, RunEventChannel>;
1369
+ export declare const publish: (event: RunEvent) => Effect.Effect<boolean, never, RunEventChannel>;
1315
1370
  /**
1316
1371
  * Emit a batch of events onto the channel in order.
1317
1372
  *
@@ -1323,7 +1378,7 @@ declare const publish: (event: RunEvent) => Effect.Effect<boolean, never, RunEve
1323
1378
  * @returns an Effect that resolves when all events are enqueued
1324
1379
  * @public
1325
1380
  */
1326
- declare const publishAll: (events: Iterable<RunEvent>) => Effect.Effect<void, never, RunEventChannel>;
1381
+ export declare const publishAll: (events: Iterable<RunEvent>) => Effect.Effect<void, never, RunEventChannel>;
1327
1382
  //#endregion
1328
1383
  //#region src/pubsub/Subscriber.d.ts
1329
1384
  /**
@@ -1337,7 +1392,7 @@ declare const publishAll: (events: Iterable<RunEvent>) => Effect.Effect<void, ne
1337
1392
  * @returns an Effect that resolves to the terminal render state
1338
1393
  * @public
1339
1394
  */
1340
- declare const accumulateUntilFinished: (initial?: RenderState) => Effect.Effect<RenderState, never, RunEventChannel>;
1395
+ export declare const accumulateUntilFinished: (initial?: RenderState) => Effect.Effect<RenderState, never, RunEventChannel>;
1341
1396
  /**
1342
1397
  * Run a callback for every projected `RenderState` as events
1343
1398
  * arrive. The stream terminates when the PubSub shuts down (typically
@@ -1353,7 +1408,7 @@ declare const accumulateUntilFinished: (initial?: RenderState) => Effect.Effect<
1353
1408
  * @returns an Effect that completes when the PubSub shuts down
1354
1409
  * @public
1355
1410
  */
1356
- declare const forEachRenderState: <R, E>(onState: (state: RenderState) => Effect.Effect<void, E, R>, initial?: RenderState) => Effect.Effect<void, E, R | RunEventChannel>;
1411
+ export declare const forEachRenderState: <R, E>(onState: (state: RenderState) => Effect.Effect<void, E, R>, initial?: RenderState) => Effect.Effect<void, E, R | RunEventChannel>;
1357
1412
  /**
1358
1413
  * Build an Effect `Stream` of accumulated states over the channel.
1359
1414
  *
@@ -1365,7 +1420,7 @@ declare const forEachRenderState: <R, E>(onState: (state: RenderState) => Effect
1365
1420
  * @returns an Effect that resolves to a Stream of render states
1366
1421
  * @public
1367
1422
  */
1368
- declare const renderStateStream: (initial?: RenderState) => Effect.Effect<Stream.Stream<RenderState>, never, RunEventChannel>;
1423
+ export declare const renderStateStream: (initial?: RenderState) => Effect.Effect<Stream.Stream<RenderState>, never, RunEventChannel>;
1369
1424
  /**
1370
1425
  * Convenience: subscribe and produce a low-level dequeue of raw
1371
1426
  * `RunEvent` values. Mostly for tests and ad-hoc consumers; most
@@ -1375,7 +1430,7 @@ declare const renderStateStream: (initial?: RenderState) => Effect.Effect<Stream
1375
1430
  * @returns an Effect that resolves to a dequeue of raw run events
1376
1431
  * @public
1377
1432
  */
1378
- declare const subscribeRaw: () => Effect.Effect<PubSub.Subscription<RunEvent>, never, RunEventChannel | Scope.Scope>;
1433
+ export declare const subscribeRaw: () => Effect.Effect<PubSub.Subscription<RunEvent>, never, RunEventChannel | Scope.Scope>;
1379
1434
  //#endregion
1380
1435
  //#region src/reducer.d.ts
1381
1436
  /**
@@ -1390,7 +1445,7 @@ declare const subscribeRaw: () => Effect.Effect<PubSub.Subscription<RunEvent>, n
1390
1445
  * @returns the next render state
1391
1446
  * @public
1392
1447
  */
1393
- declare const reduceRenderState: (state: RenderState, event: RunEvent) => RenderState;
1448
+ export declare const reduceRenderState: (state: RenderState, event: RunEvent) => RenderState;
1394
1449
  /**
1395
1450
  * Fold a sequence of events into a final `RenderState`.
1396
1451
  *
@@ -1402,7 +1457,7 @@ declare const reduceRenderState: (state: RenderState, event: RunEvent) => Render
1402
1457
  * @returns the terminal render state after all events are applied
1403
1458
  * @public
1404
1459
  */
1405
- declare const reduceRenderStateAll: (events: ReadonlyArray<RunEvent>, seed?: RenderState) => RenderState;
1460
+ export declare const reduceRenderStateAll: (events: ReadonlyArray<RunEvent>, seed?: RenderState) => RenderState;
1406
1461
  //#endregion
1407
1462
  //#region src/render-agent.d.ts
1408
1463
  /**
@@ -1449,7 +1504,7 @@ interface RenderAgentOptions {
1449
1504
  * @returns the agent-format string
1450
1505
  * @public
1451
1506
  */
1452
- declare const renderAgent: (state: RenderState, options?: RenderAgentOptions) => string;
1507
+ export declare const renderAgent: (state: RenderState, options?: RenderAgentOptions) => string;
1453
1508
  //#endregion
1454
1509
  //#region src/render-ink/CountColumns.d.ts
1455
1510
  /**
@@ -1474,7 +1529,7 @@ interface CountColumnsProps {
1474
1529
  *
1475
1530
  * @public
1476
1531
  */
1477
- declare const DURATION_CELL_WIDTH = 7;
1532
+ export declare const DURATION_CELL_WIDTH = 7;
1478
1533
  /**
1479
1534
  * Renders four colored count columns (pass ✓, fail ✗, skip ↷, timeout ⧖)
1480
1535
  * for an aggregate row. Counts render right-aligned in fixed 4-digit cells so
@@ -1483,7 +1538,7 @@ declare const DURATION_CELL_WIDTH = 7;
1483
1538
  *
1484
1539
  * @public
1485
1540
  */
1486
- declare const CountColumns: FC<CountColumnsProps>;
1541
+ export declare const CountColumns: FC<CountColumnsProps>;
1487
1542
  //#endregion
1488
1543
  //#region src/render-ink/CoverageBlock.d.ts
1489
1544
  /**
@@ -1503,7 +1558,7 @@ interface CoverageBlockProps {
1503
1558
  *
1504
1559
  * @public
1505
1560
  */
1506
- declare const CoverageBlock: FC<CoverageBlockProps>;
1561
+ export declare const CoverageBlock: FC<CoverageBlockProps>;
1507
1562
  //#endregion
1508
1563
  //#region src/render-ink/FailureSection.d.ts
1509
1564
  /**
@@ -1523,7 +1578,7 @@ interface FailureSectionProps {
1523
1578
  *
1524
1579
  * @public
1525
1580
  */
1526
- declare const FailureSection: FC<FailureSectionProps>;
1581
+ export declare const FailureSection: FC<FailureSectionProps>;
1527
1582
  //#endregion
1528
1583
  //#region src/render-ink/FailuresSection.d.ts
1529
1584
  /**
@@ -1543,7 +1598,7 @@ interface FailuresSectionProps {
1543
1598
  *
1544
1599
  * @public
1545
1600
  */
1546
- declare const FailuresSection: FC<FailuresSectionProps>;
1601
+ export declare const FailuresSection: FC<FailuresSectionProps>;
1547
1602
  //#endregion
1548
1603
  //#region src/render-ink/ModuleHeader.d.ts
1549
1604
  /**
@@ -1561,7 +1616,7 @@ interface ModuleHeaderProps {
1561
1616
  *
1562
1617
  * @public
1563
1618
  */
1564
- declare const ModuleHeader: FC<ModuleHeaderProps>;
1619
+ export declare const ModuleHeader: FC<ModuleHeaderProps>;
1565
1620
  //#endregion
1566
1621
  //#region src/render-ink/ProjectRow.d.ts
1567
1622
  /**
@@ -1607,7 +1662,7 @@ interface ProjectRowProps {
1607
1662
  *
1608
1663
  * @public
1609
1664
  */
1610
- declare const ProjectRow: FC<ProjectRowProps>;
1665
+ export declare const ProjectRow: FC<ProjectRowProps>;
1611
1666
  //#endregion
1612
1667
  //#region src/render-ink/StatusIcon.d.ts
1613
1668
  /**
@@ -1630,7 +1685,7 @@ interface StatusIconProps {
1630
1685
  *
1631
1686
  * @public
1632
1687
  */
1633
- declare const StatusIcon: FC<StatusIconProps>;
1688
+ export declare const StatusIcon: FC<StatusIconProps>;
1634
1689
  //#endregion
1635
1690
  //#region src/render-ink/StreamApp.d.ts
1636
1691
  /**
@@ -1652,7 +1707,7 @@ interface StreamAppProps {
1652
1707
  *
1653
1708
  * @public
1654
1709
  */
1655
- declare const StreamApp: FC<StreamAppProps>;
1710
+ export declare const StreamApp: FC<StreamAppProps>;
1656
1711
  //#endregion
1657
1712
  //#region src/render-ink/SuggestedActions.d.ts
1658
1713
  /**
@@ -1670,7 +1725,7 @@ interface SuggestedActionsProps {
1670
1725
  *
1671
1726
  * @public
1672
1727
  */
1673
- declare const SuggestedActions: FC<SuggestedActionsProps>;
1728
+ export declare const SuggestedActions: FC<SuggestedActionsProps>;
1674
1729
  //#endregion
1675
1730
  //#region src/render-ink/spinner.d.ts
1676
1731
  /**
@@ -1690,13 +1745,13 @@ declare const SuggestedActions: FC<SuggestedActionsProps>;
1690
1745
  *
1691
1746
  * @public
1692
1747
  */
1693
- declare const SPINNER_FRAMES: readonly ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
1748
+ export declare const SPINNER_FRAMES: readonly ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
1694
1749
  /**
1695
1750
  * How long each spinner frame is held, in milliseconds.
1696
1751
  *
1697
1752
  * @public
1698
1753
  */
1699
- declare const SPINNER_FRAME_MS = 80;
1754
+ export declare const SPINNER_FRAME_MS = 80;
1700
1755
  /**
1701
1756
  * Resolve the spinner glyph for a frame index. The index wraps modulo
1702
1757
  * the frame count and tolerates negative values, so a wall-clock-derived
@@ -1706,7 +1761,7 @@ declare const SPINNER_FRAME_MS = 80;
1706
1761
  * @returns the Braille glyph for the given frame
1707
1762
  * @public
1708
1763
  */
1709
- declare const spinnerFrame: (index: number) => string;
1764
+ export declare const spinnerFrame: (index: number) => string;
1710
1765
  /**
1711
1766
  * Derive the spinner frame index from a wall-clock timestamp. Using the
1712
1767
  * clock — rather than a monotonic counter — keeps the animation correct
@@ -1716,7 +1771,7 @@ declare const spinnerFrame: (index: number) => string;
1716
1771
  * @returns the frame index for the given timestamp
1717
1772
  * @public
1718
1773
  */
1719
- declare const spinnerFrameForTime: (nowMs: number) => number;
1774
+ export declare const spinnerFrameForTime: (nowMs: number) => number;
1720
1775
  //#endregion
1721
1776
  //#region src/render-ink/TagColumns.d.ts
1722
1777
  /**
@@ -1727,7 +1782,7 @@ declare const spinnerFrameForTime: (nowMs: number) => number;
1727
1782
  *
1728
1783
  * @public
1729
1784
  */
1730
- declare const tagUnion: (rows: ReadonlyArray<Record<string, number> | undefined>) => ReadonlyArray<string>;
1785
+ export declare const tagUnion: (rows: ReadonlyArray<Record<string, number> | undefined>) => ReadonlyArray<string>;
1731
1786
  /**
1732
1787
  * Props for the `TagColumns` component.
1733
1788
  *
@@ -1745,7 +1800,7 @@ interface TagColumnsProps {
1745
1800
  *
1746
1801
  * @public
1747
1802
  */
1748
- declare const TagColumns: FC<TagColumnsProps>;
1803
+ export declare const TagColumns: FC<TagColumnsProps>;
1749
1804
  //#endregion
1750
1805
  //#region src/render-ink/TestRow.d.ts
1751
1806
  /**
@@ -1764,7 +1819,7 @@ interface TestRowProps {
1764
1819
  *
1765
1820
  * @public
1766
1821
  */
1767
- declare const TestRow: FC<TestRowProps>;
1822
+ export declare const TestRow: FC<TestRowProps>;
1768
1823
  //#endregion
1769
1824
  //#region src/render-ink/TrendLine.d.ts
1770
1825
  /**
@@ -1784,7 +1839,7 @@ interface TrendLineProps {
1784
1839
  *
1785
1840
  * @public
1786
1841
  */
1787
- declare const TrendLine: FC<TrendLineProps>;
1842
+ export declare const TrendLine: FC<TrendLineProps>;
1788
1843
  //#endregion
1789
1844
  //#region src/synthesize.d.ts
1790
1845
  /**
@@ -1795,7 +1850,7 @@ declare const TrendLine: FC<TrendLineProps>;
1795
1850
  *
1796
1851
  * @public
1797
1852
  */
1798
- declare const SUITE_LOAD_FAILURE_LABEL = "test suite failed to load";
1853
+ export declare const SUITE_LOAD_FAILURE_LABEL = "test suite failed to load";
1799
1854
  /**
1800
1855
  * Optional metadata threaded through the synthesized event stream.
1801
1856
  *
@@ -1833,6 +1888,12 @@ interface SynthesizedCoverage {
1833
1888
  readonly expected: number;
1834
1889
  readonly actual: number;
1835
1890
  }>;
1891
+ /** Set when this run's coverage was scoped to a subset of test files (issue #160). */
1892
+ readonly scoped?: boolean;
1893
+ /** Number of test files actually run, when `scoped` is true. */
1894
+ readonly scopedFiles?: number;
1895
+ /** Total test-file count for the project, when known. */
1896
+ readonly totalFiles?: number;
1836
1897
  }
1837
1898
  /**
1838
1899
  * Project a fully-completed Vitest run into the canonical
@@ -1844,7 +1905,7 @@ interface SynthesizedCoverage {
1844
1905
  * @returns an ordered array of run events
1845
1906
  * @public
1846
1907
  */
1847
- declare const synthesizeRunEvents: (modules: ReadonlyArray<VitestTestModule>, options?: SynthesizeOptions) => RunEvent[];
1908
+ export declare const synthesizeRunEvents: (modules: ReadonlyArray<VitestTestModule>, options?: SynthesizeOptions) => RunEvent[];
1848
1909
  /**
1849
1910
  * Options for {@link synthesizeFromAgentReport}.
1850
1911
  *
@@ -1877,7 +1938,7 @@ interface SynthesizeFromAgentReportOptions {
1877
1938
  * @returns an ordered array of run events
1878
1939
  * @public
1879
1940
  */
1880
- declare const synthesizeFromAgentReport: (report: AgentReport, options?: SynthesizeFromAgentReportOptions) => RunEvent[];
1941
+ export declare const synthesizeFromAgentReport: (report: AgentReport, options?: SynthesizeFromAgentReportOptions) => RunEvent[];
1881
1942
  //#endregion
1882
1943
  //#region src/index.d.ts
1883
1944
  /**
@@ -1887,7 +1948,7 @@ declare const synthesizeFromAgentReport: (report: AgentReport, options?: Synthes
1887
1948
  *
1888
1949
  * @public
1889
1950
  */
1890
- declare const CURRENT_UI_VERSION: string;
1951
+ export declare const CURRENT_UI_VERSION: string;
1891
1952
  //#endregion
1892
- export { ActionSeverity, type AgentCellFn, type AgentReport, CURRENT_UI_VERSION, type Cell, type CellOptions, CountColumns, type CountColumnsProps, CoverageBlock, type CoverageBlockProps, FileCoverageReport as CoverageFile, CoverageGap, CoverageMetric, CoverageRenderState, type CoverageTotals, DURATION_CELL_WIDTH, type DispatchInputs, FailureRecord, FailureSection, type FailureSectionProps, FailuresSection, type FailuresSectionProps, type InkCellFn, type MetricThresholds, ModuleHeader, type ModuleHeaderProps, ModuleRecord, ProjectRow, type ProjectRowProps, type ProjectSummary, type RenderAgentOptions, RenderState, RenderTotals, RunEvent, type RunEventByTag, RunEventChannel, RunEventChannelLive, type RunOutcome, type RunShape, SPINNER_FRAMES, SPINNER_FRAME_MS, SUITE_LOAD_FAILURE_LABEL, StatusIcon, type StatusIconKind, type StatusIconProps, StreamApp, type StreamAppProps, SuggestedActionRecord, SuggestedActions, type SuggestedActionsProps, type SynthesizeFromAgentReportOptions, type SynthesizeOptions, type SynthesizedCoverage, TagColumns, type TagColumnsProps, type TestClassification, TestRecord, TestRow, type TestRowProps, TrendLine, type TrendLineProps, type TrendSummary, type VitestModuleDiagnostic, type VitestModuleError, type VitestParsedStack, type VitestTestCase, type VitestTestDiagnostic, type VitestTestError, type VitestTestModule, type VitestTestResult, type VitestTestSuite, accumulateUntilFinished, buildFooter, classifyOutcome, classifyRunShape, dispatch, dispatchInk, dispatcherTable, dominantClassification, forEachRenderState, formatDisplayDuration, initialRenderState, publish, publishAll, reduceRenderState, reduceRenderStateAll, renderAgent, renderStateStream, spinnerFrame, spinnerFrameForTime, subscribeRaw, synthesizeFromAgentReport, synthesizeRunEvents, tagUnion };
1953
+ export { type AgentCellFn, type AgentReport, type Cell, type CellOptions, type CountColumnsProps, type CoverageBlockProps, FileCoverageReport as CoverageFile, type CoverageTotals, type DispatchInputs, type FailureSectionProps, type FailuresSectionProps, type InkCellFn, type MetricThresholds, type ModuleHeaderProps, type ProjectRowProps, type ProjectSummary, type RenderAgentOptions, type RunEventByTag, type RunOutcome, type RunShape, type StatusIconKind, type StatusIconProps, type StreamAppProps, type SuggestedActionsProps, type SynthesizeFromAgentReportOptions, type SynthesizeOptions, type SynthesizedCoverage, type TagColumnsProps, type TestClassification, type TestRowProps, type TrendLineProps, type TrendSummary, type VitestModuleDiagnostic, type VitestModuleError, type VitestParsedStack, type VitestTestCase, type VitestTestDiagnostic, type VitestTestError, type VitestTestModule, type VitestTestResult, type VitestTestSuite };
1893
1954
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -33,7 +33,7 @@ import { ActionSeverity, CoverageFile, CoverageGap, CoverageMetric, CoverageRend
33
33
  *
34
34
  * @public
35
35
  */
36
- const CURRENT_UI_VERSION = "2.2.13";
36
+ const CURRENT_UI_VERSION = "2.3.1";
37
37
 
38
38
  //#endregion
39
39
  export { ActionSeverity, CURRENT_UI_VERSION, CountColumns, CoverageBlock, CoverageFile, CoverageGap, CoverageMetric, CoverageRenderState, DURATION_CELL_WIDTH, FailureRecord, FailureSection, FailuresSection, ModuleHeader, ModuleRecord, ProjectRow, RenderState, RenderTotals, RunEvent, RunEventChannel, RunEventChannelLive, SPINNER_FRAMES, SPINNER_FRAME_MS, SUITE_LOAD_FAILURE_LABEL, StatusIcon, StreamApp, SuggestedActionRecord, SuggestedActions, TagColumns, TestRecord, TestRow, TrendLine, accumulateUntilFinished, buildFooter, classifyOutcome, classifyRunShape, dispatch, dispatchInk, dispatcherTable, dominantClassification, forEachRenderState, formatDisplayDuration, initialRenderState, publish, publishAll, reduceRenderState, reduceRenderStateAll, renderAgent, renderStateStream, spinnerFrame, spinnerFrameForTime, subscribeRaw, synthesizeFromAgentReport, synthesizeRunEvents, tagUnion };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest-agent/ui",
3
- "version": "2.2.13",
3
+ "version": "2.3.1",
4
4
  "private": false,
5
5
  "description": "Shared event-sourced renderer for vitest-agent. React Ink view for humans, plain-string view for agents, both fed by the same reducer over a RunEvent stream.",
6
6
  "keywords": [
@@ -39,8 +39,8 @@
39
39
  "./package.json": "./package.json"
40
40
  },
41
41
  "dependencies": {
42
- "@vitest-agent/sdk": "2.4.13",
43
- "effect": "4.0.0-rc.109"
42
+ "@vitest-agent/sdk": "2.5.1",
43
+ "effect": "4.0.0-rc.112"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "ink": "^7.1.0",
package/reducer.js CHANGED
@@ -175,7 +175,10 @@ const reduceRenderState = (state, event) => {
175
175
  metrics: e.metrics,
176
176
  thresholds: e.thresholds,
177
177
  gaps: e.gaps,
178
- violations: state.coverage?.violations ?? []
178
+ violations: state.coverage?.violations ?? [],
179
+ ...e.scoped !== void 0 ? { scoped: e.scoped } : {},
180
+ ...e.scopedFiles !== void 0 ? { scopedFiles: e.scopedFiles } : {},
181
+ ...e.totalFiles !== void 0 ? { totalFiles: e.totalFiles } : {}
179
182
  }
180
183
  }),
181
184
  ThresholdViolation: (e) => {
@@ -219,7 +222,8 @@ const reduceRenderState = (state, event) => {
219
222
  timeoutCount: e.timeoutCount ?? state.totals.timeoutCount,
220
223
  durationMs: e.durationMs
221
224
  },
222
- ...e.collectedModules !== void 0 && { collectedModules: e.collectedModules }
225
+ ...e.collectedModules !== void 0 && { collectedModules: e.collectedModules },
226
+ ...e.unhandledErrors !== void 0 && { unhandledErrors: e.unhandledErrors }
223
227
  }),
224
228
  RunTimedOut: () => ({
225
229
  ...state,
package/render-agent.js CHANGED
@@ -14,6 +14,10 @@ const formatHeader = (state) => {
14
14
  if (failCount > 0) parts.push(`${failCount} failed`);
15
15
  if (timeoutCount > 0) parts.push(`${timeoutCount} timed out`);
16
16
  if (skipCount > 0) parts.push(`${skipCount} skipped`);
17
+ if (state.unhandledErrors.length > 0) {
18
+ const noun = state.unhandledErrors.length === 1 ? "unhandled error" : "unhandled errors";
19
+ parts.push(`${state.unhandledErrors.length} ${noun}`);
20
+ }
17
21
  return `Tests: ${parts.join(", ")} (${formatDisplayDuration(durationMs)})`;
18
22
  };
19
23
  const formatModulesSection = (state) => {
@@ -61,6 +65,18 @@ const formatFailuresSection = (state, width, includeStack) => {
61
65
  for (const f of state.failures) lines.push(formatFailure(f, width, includeStack));
62
66
  return lines.join("\n");
63
67
  };
68
+ const formatUnhandledError = (err, width, includeStack) => {
69
+ const firstLine = err.message.split("\n", 1)[0] ?? "";
70
+ const lines = [`- ${truncate(firstLine, Math.max(20, width - 2))}`];
71
+ if (includeStack && err.stack !== void 0) for (const stackLine of err.stack.split("\n")) lines.push(` ${truncate(stackLine, Math.max(20, width - 2))}`);
72
+ return lines.join("\n");
73
+ };
74
+ const formatUnhandledErrorsSection = (state, width, includeStack) => {
75
+ if (state.unhandledErrors.length === 0) return null;
76
+ const lines = ["Unhandled errors:"];
77
+ for (const err of state.unhandledErrors) lines.push(formatUnhandledError(err, width, includeStack));
78
+ return lines.join("\n");
79
+ };
64
80
  const formatPercent = (n) => {
65
81
  const rounded = Math.round(n * 10) / 10;
66
82
  return Number.isInteger(rounded) ? `${rounded}%` : `${rounded}%`;
@@ -128,6 +144,8 @@ const renderAgent = (state, options = {}) => {
128
144
  const sections = [formatHeader(state)];
129
145
  const failures = formatFailuresSection(state, width, includeStack);
130
146
  if (failures !== null) sections.push(failures);
147
+ const unhandledErrors = formatUnhandledErrorsSection(state, width, includeStack);
148
+ if (unhandledErrors !== null) sections.push(unhandledErrors);
131
149
  const modules = formatModulesSection(state);
132
150
  if (modules !== null) sections.push(modules);
133
151
  const coverage = formatCoverageSection(state, maxGaps);
@@ -58,6 +58,21 @@ const sumCounts = (modules) => {
58
58
  timeoutCount
59
59
  };
60
60
  };
61
+ /**
62
+ * Build a `ProjectSummary` from a project's name and its rolled-up
63
+ * counts. Includes `timeoutCount` only when nonzero, matching
64
+ * `summarizeProject`'s convention in `@vitest-agent/reporter` (issue #242).
65
+ *
66
+ * @public
67
+ */
68
+ const buildProjectSummary = (name, counts) => ({
69
+ name,
70
+ passCount: counts.passCount,
71
+ failCount: counts.failCount,
72
+ skipCount: counts.skipCount,
73
+ durationMs: 0,
74
+ ...counts.timeoutCount > 0 ? { timeoutCount: counts.timeoutCount } : {}
75
+ });
61
76
  const mergeTagCounts = (modules) => {
62
77
  const out = {};
63
78
  for (const m of modules) for (const [tag, count] of Object.entries(m.tagCounts ?? {})) out[tag] = (out[tag] ?? 0) + count;
@@ -166,6 +181,21 @@ const InlineError = ({ failure }) => {
166
181
  });
167
182
  };
168
183
  const failureKey = (f) => `failure:${f.modulePath}::${f.suitePath.join("/")}::${f.testName}`;
184
+ /** A process-level unhandled error, rendered in the aggregate Unhandled errors block. */
185
+ const UnhandledErrorItem = ({ error }) => /* @__PURE__ */ jsxs(Box, {
186
+ flexDirection: "column",
187
+ children: [/* @__PURE__ */ jsxs(Text, { children: [
188
+ " ",
189
+ /* @__PURE__ */ jsx(Text, {
190
+ color: "red",
191
+ children: "✗"
192
+ }),
193
+ " unhandled error"
194
+ ] }), /* @__PURE__ */ jsxs(Text, {
195
+ dimColor: true,
196
+ children: [" ", error.message.split("\n", 1)[0] ?? ""]
197
+ })]
198
+ });
169
199
  const testKey = (modulePath, t) => `test:${modulePath}::${t.suitePath.join("/")}::${t.testName}`;
170
200
  const failurePath = (f) => [
171
201
  f.modulePath,
@@ -248,6 +278,13 @@ const liveRegion = (state, shape, nowMs, frame) => {
248
278
  const timedOut = state.phase === "timed-out";
249
279
  const finished = state.phase === "finished" || state.phase === "timed-out";
250
280
  const ordered = state.moduleOrder.map((p) => state.modules[p]).filter((m) => m !== void 0);
281
+ const unhandledErrorsSection = state.unhandledErrors.length > 0 ? /* @__PURE__ */ jsxs(Box, {
282
+ flexDirection: "column",
283
+ children: [/* @__PURE__ */ jsx(Text, {
284
+ bold: true,
285
+ children: "Unhandled errors:"
286
+ }), state.unhandledErrors.map((err, i) => /* @__PURE__ */ jsx(UnhandledErrorItem, { error: err }, `unhandled:${i}:${err.message}`))]
287
+ }) : null;
251
288
  if (shape === "single-test") {
252
289
  const sole = ordered[0];
253
290
  if (sole === void 0 || sole.tests.length === 0) return /* @__PURE__ */ jsx(Text, {
@@ -260,10 +297,14 @@ const liveRegion = (state, shape, nowMs, frame) => {
260
297
  children: "discovering tests…"
261
298
  });
262
299
  const failure = only.status === "failed" || only.status === "timed-out" ? state.failures.find((f) => f.testName === only.testName && f.modulePath === sole.modulePath && samePath(f.suitePath, only.suitePath)) : void 0;
263
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(TestRow, {
264
- test: only,
265
- indent: 0
266
- }), failure !== void 0 ? /* @__PURE__ */ jsx(InlineError, { failure }) : null] });
300
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
301
+ /* @__PURE__ */ jsx(TestRow, {
302
+ test: only,
303
+ indent: 0
304
+ }),
305
+ failure !== void 0 ? /* @__PURE__ */ jsx(InlineError, { failure }) : null,
306
+ unhandledErrorsSection
307
+ ] });
267
308
  }
268
309
  if (groups.length === 0) return /* @__PURE__ */ jsx(Text, {
269
310
  dimColor: true,
@@ -297,13 +338,7 @@ const liveRegion = (state, shape, nowMs, frame) => {
297
338
  if (runningVisible < MAX_LIVE_RUNNING_ROWS) {
298
339
  runningVisible++;
299
340
  const counts = sumCounts(g.modules);
300
- const summary = {
301
- name: g.name,
302
- passCount: counts.passCount,
303
- failCount: counts.failCount,
304
- skipCount: counts.skipCount,
305
- durationMs: 0
306
- };
341
+ const summary = buildProjectSummary(g.name, counts);
307
342
  rows.push(/* @__PURE__ */ jsx(ProjectRow, {
308
343
  project: summary,
309
344
  counts,
@@ -318,13 +353,7 @@ const liveRegion = (state, shape, nowMs, frame) => {
318
353
  } else runningOverflow++;
319
354
  } else {
320
355
  const counts = sumCounts(g.modules);
321
- const summary = {
322
- name: g.name,
323
- passCount: counts.passCount,
324
- failCount: counts.failCount,
325
- skipCount: counts.skipCount,
326
- durationMs: 0
327
- };
356
+ const summary = buildProjectSummary(g.name, counts);
328
357
  rows.push(/* @__PURE__ */ jsx(ProjectRow, {
329
358
  project: summary,
330
359
  counts,
@@ -356,6 +385,7 @@ const liveRegion = (state, shape, nowMs, frame) => {
356
385
  ]
357
386
  }) : null,
358
387
  failuresSection,
388
+ unhandledErrorsSection,
359
389
  coverageItem,
360
390
  trendItem,
361
391
  totalsItem(nameWidth + 4)
@@ -406,6 +436,7 @@ const liveRegion = (state, shape, nowMs, frame) => {
406
436
  ]
407
437
  }) : null,
408
438
  failuresSection,
439
+ unhandledErrorsSection,
409
440
  coverageItem,
410
441
  trendItem,
411
442
  totalsItem()
@@ -438,6 +469,7 @@ const liveRegion = (state, shape, nowMs, frame) => {
438
469
  }), failure !== void 0 ? /* @__PURE__ */ jsx(InlineError, { failure }) : null]
439
470
  }, testKey(sole.modulePath, t));
440
471
  }),
472
+ unhandledErrorsSection,
441
473
  coverageItem,
442
474
  trendItem,
443
475
  totalsItem()
@@ -452,16 +484,7 @@ const liveRegion = (state, shape, nowMs, frame) => {
452
484
  const StreamApp = ({ state, frameIndex, nowMs }) => {
453
485
  const now = nowMs ?? Date.now();
454
486
  const frame = spinnerFrame(frameIndex);
455
- const projects = groupByProject(state).map((g) => {
456
- const c = sumCounts(g.modules);
457
- return {
458
- name: g.name,
459
- passCount: c.passCount,
460
- failCount: c.failCount,
461
- skipCount: c.skipCount,
462
- durationMs: 0
463
- };
464
- });
487
+ const projects = groupByProject(state).map((g) => buildProjectSummary(g.name, sumCounts(g.modules)));
465
488
  const shape = classifyRunShape(state, projects);
466
489
  const liveContent = liveRegion(state, shape, now, frame);
467
490
  return /* @__PURE__ */ jsx(Box, {
@@ -471,4 +494,4 @@ const StreamApp = ({ state, frameIndex, nowMs }) => {
471
494
  };
472
495
 
473
496
  //#endregion
474
- export { StreamApp };
497
+ export { StreamApp, buildProjectSummary };
package/synthesize.js CHANGED
@@ -147,7 +147,10 @@ const synthesizeRunEvents = (modules, options = {}) => {
147
147
  _tag: "CoverageReady",
148
148
  metrics: cov.metrics,
149
149
  thresholds: cov.thresholds,
150
- gaps: cov.gaps
150
+ gaps: cov.gaps,
151
+ ...cov.scoped !== void 0 ? { scoped: cov.scoped } : {},
152
+ ...cov.scopedFiles !== void 0 ? { scopedFiles: cov.scopedFiles } : {},
153
+ ...cov.totalFiles !== void 0 ? { totalFiles: cov.totalFiles } : {}
151
154
  });
152
155
  if (cov.violations !== void 0) for (const v of cov.violations) events.push({
153
156
  _tag: "ThresholdViolation",
@@ -189,7 +192,7 @@ const coverageReportToBlock = (report) => {
189
192
  const cov = report.coverage;
190
193
  if (cov === void 0) return void 0;
191
194
  const violations = [];
192
- for (const metric of [
195
+ if (!cov.scoped) for (const metric of [
193
196
  "lines",
194
197
  "branches",
195
198
  "functions",
@@ -212,7 +215,10 @@ const coverageReportToBlock = (report) => {
212
215
  metrics: cov.totals,
213
216
  thresholds: cov.thresholds.global,
214
217
  gaps,
215
- violations
218
+ violations,
219
+ ...cov.scoped ? { scoped: cov.scoped } : {},
220
+ ...cov.scopedFiles !== void 0 ? { scopedFiles: cov.scopedFiles.length } : {},
221
+ ...cov.totalFiles !== void 0 ? { totalFiles: cov.totalFiles } : {}
216
222
  };
217
223
  };
218
224
  /**
@@ -331,7 +337,10 @@ const synthesizeFromAgentReport = (report, options = {}) => {
331
337
  _tag: "CoverageReady",
332
338
  metrics: coverage.metrics,
333
339
  thresholds: coverage.thresholds,
334
- gaps: coverage.gaps
340
+ gaps: coverage.gaps,
341
+ ...coverage.scoped !== void 0 ? { scoped: coverage.scoped } : {},
342
+ ...coverage.scopedFiles !== void 0 ? { scopedFiles: coverage.scopedFiles } : {},
343
+ ...coverage.totalFiles !== void 0 ? { totalFiles: coverage.totalFiles } : {}
335
344
  });
336
345
  if (coverage.violations !== void 0) for (const v of coverage.violations) events.push({
337
346
  _tag: "ThresholdViolation",
@@ -362,7 +371,8 @@ const synthesizeFromAgentReport = (report, options = {}) => {
362
371
  skipCount: report.summary.skipped,
363
372
  durationMs: report.summary.duration,
364
373
  ...totalTimeoutCount > 0 && { timeoutCount: totalTimeoutCount },
365
- ...report.summary.modules !== void 0 && { collectedModules: report.summary.modules }
374
+ ...report.summary.modules !== void 0 && { collectedModules: report.summary.modules },
375
+ ...report.unhandledErrors.length > 0 && { unhandledErrors: report.unhandledErrors }
366
376
  });
367
377
  return events;
368
378
  };