@vitest-agent/ui 4.1.0 → 5.0.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/README.md +3 -3
- package/dispatcher/cells/single-file-fail.js +1 -5
- package/dispatcher/cells/single-file-pass.js +1 -5
- package/dispatcher/cells/single-file-threshold.js +1 -5
- package/dispatcher/cells/single-project-fail.js +1 -5
- package/dispatcher/cells/single-project-pass.js +1 -5
- package/dispatcher/cells/single-project-threshold.js +1 -5
- package/dispatcher/cells/single-test-fail.js +1 -5
- package/dispatcher/cells/single-test-pass.js +1 -5
- package/dispatcher/cells/workspace-fail.js +1 -5
- package/dispatcher/cells/workspace-pass.js +1 -5
- package/dispatcher/cells/workspace-threshold.js +1 -5
- package/dispatcher/dispatch.js +20 -25
- package/index.d.ts +45 -316
- package/index.js +4 -17
- package/{render-ink → ink}/CountColumns.js +2 -2
- package/{render-ink → ink}/CoverageBlock.js +2 -2
- package/{render-ink → ink}/FailureSection.js +2 -2
- package/{render-ink → ink}/FailuresSection.js +2 -2
- package/{render-ink → ink}/ModuleHeader.js +1 -1
- package/{render-ink → ink}/ProjectRow.js +1 -1
- package/{render-ink → ink}/StatusIcon.js +2 -2
- package/{render-ink → ink}/StreamApp.js +14 -5
- package/{render-ink → ink}/SuggestedActions.js +2 -2
- package/{render-ink → ink}/TagColumns.js +1 -1
- package/{render-ink → ink}/TestRow.js +1 -1
- package/{render-ink → ink}/TrendLine.js +2 -2
- package/ink/dispatch-ink.js +61 -0
- package/{dispatcher → ink}/ink-helpers.js +1 -1
- package/ink.d.ts +789 -0
- package/ink.js +15 -0
- package/package.json +12 -7
- package/reducer.js +2 -1
- package/render-agent.js +2 -0
- package/{render-ink/spinner.js → spinner.js} +1 -1
- package/synthesize.js +2 -1
- package/render-ink/index.js +0 -15
package/README.md
CHANGED
|
@@ -11,8 +11,8 @@ Pure rendering primitives for vitest-agent. Owns the `RunEvent` reducer, the sha
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
13
|
- **Reducer** — pure `(state, event) => state` fold over the `RunEvent` discriminated union; `reduceRenderStateAll` for one-shot replay
|
|
14
|
-
- **Dispatcher matrix** — `dispatch`
|
|
15
|
-
- **Ink components** — `StreamApp` and the supporting primitives (`ModuleHeader`, `TestRow`, `CoverageBlock`, `TrendLine`, `FailureSection`, etc.) for live `stream`-mode frames
|
|
14
|
+
- **Dispatcher matrix** — `dispatch` routes a `DispatchInputs` to the matching `(RunShape, RunOutcome)` cell; `classifyRunShape` and `classifyOutcome` derive the coordinates
|
|
15
|
+
- **Ink components** (`@vitest-agent/ui/ink`) — `StreamApp` and the supporting primitives (`ModuleHeader`, `TestRow`, `CoverageBlock`, `TrendLine`, `FailureSection`, etc.) for live `stream`-mode frames, plus `dispatchInk` / `inkDispatcherTable`, the Ink half of the matrix. The package root never loads React or Ink; only this subpath does
|
|
16
16
|
- **Synthesizers** — `synthesizeRunEvents` from live Vitest modules; `synthesizeFromAgentReport` from a stored `AgentReport`
|
|
17
17
|
- **Footer builder** — `buildFooter` assembles the L1 MCP-tool-pointer footer; `dominantClassification` picks the most actionable failure class
|
|
18
18
|
|
|
@@ -24,7 +24,7 @@ npm install @vitest-agent/ui
|
|
|
24
24
|
pnpm add @vitest-agent/ui
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
React and Ink are
|
|
27
|
+
React and Ink are optional peers, needed only for the `@vitest-agent/ui/ink` subpath:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
npm install react ink
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { buildFooter } from "../footer.js";
|
|
2
2
|
import { formatDisplayDuration } from "../../format-duration.js";
|
|
3
3
|
import { formatFailure, soleModulePath } from "../helpers.js";
|
|
4
|
-
import { renderAgentStringAsInk } from "../ink-helpers.js";
|
|
5
4
|
|
|
6
5
|
//#region src/dispatcher/cells/single-file-fail.ts
|
|
7
6
|
const DEFAULT_WIDTH = 80;
|
|
@@ -20,10 +19,7 @@ const renderAgent = (inputs) => {
|
|
|
20
19
|
}
|
|
21
20
|
return `${sections.join("\n")}\n${buildFooter(inputs)}`;
|
|
22
21
|
};
|
|
23
|
-
const renderSingleFileFail = {
|
|
24
|
-
agent: renderAgent,
|
|
25
|
-
ink: (inputs) => renderAgentStringAsInk(renderAgent(inputs))
|
|
26
|
-
};
|
|
22
|
+
const renderSingleFileFail = { agent: renderAgent };
|
|
27
23
|
|
|
28
24
|
//#endregion
|
|
29
25
|
export { renderSingleFileFail };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { buildFooter } from "../footer.js";
|
|
2
2
|
import { formatDisplayDuration } from "../../format-duration.js";
|
|
3
3
|
import { soleModulePath } from "../helpers.js";
|
|
4
|
-
import { renderAgentStringAsInk } from "../ink-helpers.js";
|
|
5
4
|
import { Fmt } from "@effected/cli";
|
|
6
5
|
|
|
7
6
|
//#region src/dispatcher/cells/single-file-pass.ts
|
|
@@ -11,10 +10,7 @@ const renderAgent = (inputs) => {
|
|
|
11
10
|
const { passCount, durationMs } = inputs.state.totals;
|
|
12
11
|
return `${modulePath}: ${Fmt.plural(passCount, "test")} passed (${formatDisplayDuration(durationMs)})\n${buildFooter(inputs)}`;
|
|
13
12
|
};
|
|
14
|
-
const renderSingleFilePass = {
|
|
15
|
-
agent: renderAgent,
|
|
16
|
-
ink: (inputs) => renderAgentStringAsInk(renderAgent(inputs))
|
|
17
|
-
};
|
|
13
|
+
const renderSingleFilePass = { agent: renderAgent };
|
|
18
14
|
|
|
19
15
|
//#endregion
|
|
20
16
|
export { renderSingleFilePass };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { buildFooter } from "../footer.js";
|
|
2
2
|
import { formatDisplayDuration } from "../../format-duration.js";
|
|
3
3
|
import { formatCoverageSummaryLines, soleModulePath } from "../helpers.js";
|
|
4
|
-
import { renderAgentStringAsInk } from "../ink-helpers.js";
|
|
5
4
|
import { Fmt } from "@effected/cli";
|
|
6
5
|
|
|
7
6
|
//#region src/dispatcher/cells/single-file-threshold.ts
|
|
@@ -13,10 +12,7 @@ const renderAgent = (inputs) => {
|
|
|
13
12
|
lines.push(...formatCoverageSummaryLines(inputs.state));
|
|
14
13
|
return `${lines.join("\n")}\n${buildFooter(inputs)}`;
|
|
15
14
|
};
|
|
16
|
-
const renderSingleFileThreshold = {
|
|
17
|
-
agent: renderAgent,
|
|
18
|
-
ink: (inputs) => renderAgentStringAsInk(renderAgent(inputs))
|
|
19
|
-
};
|
|
15
|
+
const renderSingleFileThreshold = { agent: renderAgent };
|
|
20
16
|
|
|
21
17
|
//#endregion
|
|
22
18
|
export { renderSingleFileThreshold };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { buildFooter } from "../footer.js";
|
|
2
2
|
import { formatFailure, formatTotals } from "../helpers.js";
|
|
3
|
-
import { renderAgentStringAsInk } from "../ink-helpers.js";
|
|
4
3
|
|
|
5
4
|
//#region src/dispatcher/cells/single-project-fail.ts
|
|
6
5
|
const DEFAULT_WIDTH = 80;
|
|
@@ -25,10 +24,7 @@ const renderAgent = (inputs) => {
|
|
|
25
24
|
if (moduleLines.length > 1) sections.push(moduleLines);
|
|
26
25
|
return `${sections.map((section) => section.join("\n")).join("\n\n")}\n${buildFooter(inputs)}`;
|
|
27
26
|
};
|
|
28
|
-
const renderSingleProjectFail = {
|
|
29
|
-
agent: renderAgent,
|
|
30
|
-
ink: (inputs) => renderAgentStringAsInk(renderAgent(inputs))
|
|
31
|
-
};
|
|
27
|
+
const renderSingleProjectFail = { agent: renderAgent };
|
|
32
28
|
|
|
33
29
|
//#endregion
|
|
34
30
|
export { renderSingleProjectFail };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { buildFooter } from "../footer.js";
|
|
2
2
|
import { formatTotals } from "../helpers.js";
|
|
3
|
-
import { renderAgentStringAsInk } from "../ink-helpers.js";
|
|
4
3
|
import { Fmt } from "@effected/cli";
|
|
5
4
|
|
|
6
5
|
//#region src/dispatcher/cells/single-project-pass.ts
|
|
@@ -10,10 +9,7 @@ const renderAgent = (inputs) => {
|
|
|
10
9
|
const collected = inputs.state.collectedModules ?? inputs.state.moduleOrder.length;
|
|
11
10
|
return `${(collected === 0 ? [formatTotals(inputs.state)] : [formatTotals(inputs.state), `${Fmt.plural(collected, "module")} all-passed.`]).join("\n\n")}\n${buildFooter(inputs)}`;
|
|
12
11
|
};
|
|
13
|
-
const renderSingleProjectPass = {
|
|
14
|
-
agent: renderAgent,
|
|
15
|
-
ink: (inputs) => renderAgentStringAsInk(renderAgent(inputs))
|
|
16
|
-
};
|
|
12
|
+
const renderSingleProjectPass = { agent: renderAgent };
|
|
17
13
|
|
|
18
14
|
//#endregion
|
|
19
15
|
export { renderSingleProjectPass };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { buildFooter } from "../footer.js";
|
|
2
2
|
import { formatBelowTargetTable, formatCoverageSummaryLines, formatTotals, formatTrendLine } from "../helpers.js";
|
|
3
|
-
import { renderAgentStringAsInk } from "../ink-helpers.js";
|
|
4
3
|
|
|
5
4
|
//#region src/dispatcher/cells/single-project-threshold.ts
|
|
6
5
|
const BELOW_TARGET_LIMIT = 5;
|
|
@@ -16,10 +15,7 @@ const renderAgent = (inputs) => {
|
|
|
16
15
|
if (below.length > 0) sections.push([...below]);
|
|
17
16
|
return `${sections.map((section) => section.join("\n")).join("\n\n")}\n${buildFooter(inputs)}`;
|
|
18
17
|
};
|
|
19
|
-
const renderSingleProjectThreshold = {
|
|
20
|
-
agent: renderAgent,
|
|
21
|
-
ink: (inputs) => renderAgentStringAsInk(renderAgent(inputs))
|
|
22
|
-
};
|
|
18
|
+
const renderSingleProjectThreshold = { agent: renderAgent };
|
|
23
19
|
|
|
24
20
|
//#endregion
|
|
25
21
|
export { renderSingleProjectThreshold };
|
|
@@ -2,7 +2,6 @@ import { buildFooter } from "../footer.js";
|
|
|
2
2
|
import { statusGlyph } from "../../theme.js";
|
|
3
3
|
import { formatDisplayDuration } from "../../format-duration.js";
|
|
4
4
|
import { formatFailure, formatTestName, soleTest } from "../helpers.js";
|
|
5
|
-
import { renderAgentStringAsInk } from "../ink-helpers.js";
|
|
6
5
|
|
|
7
6
|
//#region src/dispatcher/cells/single-test-fail.ts
|
|
8
7
|
const DEFAULT_WIDTH = 80;
|
|
@@ -14,10 +13,7 @@ const renderAgent = (inputs) => {
|
|
|
14
13
|
const failure = inputs.state.failures[0];
|
|
15
14
|
return `${failure === void 0 ? `${header}\n` : `${header}\n${formatFailure(failure, DEFAULT_WIDTH).join("\n")}\n`}${buildFooter(inputs)}`;
|
|
16
15
|
};
|
|
17
|
-
const renderSingleTestFail = {
|
|
18
|
-
agent: renderAgent,
|
|
19
|
-
ink: (inputs) => renderAgentStringAsInk(renderAgent(inputs))
|
|
20
|
-
};
|
|
16
|
+
const renderSingleTestFail = { agent: renderAgent };
|
|
21
17
|
|
|
22
18
|
//#endregion
|
|
23
19
|
export { renderSingleTestFail };
|
|
@@ -2,7 +2,6 @@ import { buildFooter } from "../footer.js";
|
|
|
2
2
|
import { statusGlyph } from "../../theme.js";
|
|
3
3
|
import { formatDisplayDuration } from "../../format-duration.js";
|
|
4
4
|
import { formatTestName, soleTest } from "../helpers.js";
|
|
5
|
-
import { renderAgentStringAsInk } from "../ink-helpers.js";
|
|
6
5
|
|
|
7
6
|
//#region src/dispatcher/cells/single-test-pass.ts
|
|
8
7
|
const renderAgent = (inputs) => {
|
|
@@ -11,10 +10,7 @@ const renderAgent = (inputs) => {
|
|
|
11
10
|
const duration = test.durationMs ?? 0;
|
|
12
11
|
return `${statusGlyph("success")} ${formatTestName(test)} (${formatDisplayDuration(duration)})\n${buildFooter(inputs)}`;
|
|
13
12
|
};
|
|
14
|
-
const renderSingleTestPass = {
|
|
15
|
-
agent: renderAgent,
|
|
16
|
-
ink: (inputs) => renderAgentStringAsInk(renderAgent(inputs))
|
|
17
|
-
};
|
|
13
|
+
const renderSingleTestPass = { agent: renderAgent };
|
|
18
14
|
|
|
19
15
|
//#endregion
|
|
20
16
|
export { renderSingleTestPass };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { buildFooter } from "../footer.js";
|
|
2
2
|
import { formatFailure, formatProjectsTable, formatTrendLine, formatWorkspaceTotal } from "../helpers.js";
|
|
3
|
-
import { renderAgentStringAsInk } from "../ink-helpers.js";
|
|
4
3
|
|
|
5
4
|
//#region src/dispatcher/cells/workspace-fail.ts
|
|
6
5
|
const DEFAULT_WIDTH = 80;
|
|
@@ -18,10 +17,7 @@ const renderAgent = (inputs) => {
|
|
|
18
17
|
if (inputs.projects.length > 0) sections.push([formatWorkspaceTotal(inputs.projects)]);
|
|
19
18
|
return `${sections.map((section) => section.join("\n")).join("\n\n")}\n${buildFooter(inputs)}`;
|
|
20
19
|
};
|
|
21
|
-
const renderWorkspaceFail = {
|
|
22
|
-
agent: renderAgent,
|
|
23
|
-
ink: (inputs) => renderAgentStringAsInk(renderAgent(inputs))
|
|
24
|
-
};
|
|
20
|
+
const renderWorkspaceFail = { agent: renderAgent };
|
|
25
21
|
|
|
26
22
|
//#endregion
|
|
27
23
|
export { renderWorkspaceFail };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { buildFooter } from "../footer.js";
|
|
2
2
|
import { formatCoverageJudgmentLine, formatProjectsTable, formatTrendLine, formatWorkspaceTotal } from "../helpers.js";
|
|
3
|
-
import { renderAgentStringAsInk } from "../ink-helpers.js";
|
|
4
3
|
|
|
5
4
|
//#region src/dispatcher/cells/workspace-pass.ts
|
|
6
5
|
const renderAgent = (inputs) => {
|
|
@@ -16,10 +15,7 @@ const renderAgent = (inputs) => {
|
|
|
16
15
|
if (inputs.projects.length > 0) sections.push([formatWorkspaceTotal(inputs.projects)]);
|
|
17
16
|
return `${sections.map((section) => section.join("\n")).join("\n\n")}\n${buildFooter(inputs)}`;
|
|
18
17
|
};
|
|
19
|
-
const renderWorkspacePass = {
|
|
20
|
-
agent: renderAgent,
|
|
21
|
-
ink: (inputs) => renderAgentStringAsInk(renderAgent(inputs))
|
|
22
|
-
};
|
|
18
|
+
const renderWorkspacePass = { agent: renderAgent };
|
|
23
19
|
|
|
24
20
|
//#endregion
|
|
25
21
|
export { renderWorkspacePass };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { buildFooter } from "../footer.js";
|
|
2
2
|
import { formatBelowTargetTable, formatCoverageSummaryLines, formatProjectsTable, formatTrendLine, formatWorkspaceTotal } from "../helpers.js";
|
|
3
|
-
import { renderAgentStringAsInk } from "../ink-helpers.js";
|
|
4
3
|
|
|
5
4
|
//#region src/dispatcher/cells/workspace-threshold.ts
|
|
6
5
|
const BELOW_TARGET_LIMIT = 10;
|
|
@@ -19,10 +18,7 @@ const renderAgent = (inputs) => {
|
|
|
19
18
|
if (inputs.projects.length > 0) sections.push([formatWorkspaceTotal(inputs.projects)]);
|
|
20
19
|
return `${sections.map((section) => section.join("\n")).join("\n\n")}\n${buildFooter(inputs)}`;
|
|
21
20
|
};
|
|
22
|
-
const renderWorkspaceThreshold = {
|
|
23
|
-
agent: renderAgent,
|
|
24
|
-
ink: (inputs) => renderAgentStringAsInk(renderAgent(inputs))
|
|
25
|
-
};
|
|
21
|
+
const renderWorkspaceThreshold = { agent: renderAgent };
|
|
26
22
|
|
|
27
23
|
//#endregion
|
|
28
24
|
export { renderWorkspaceThreshold };
|
package/dispatcher/dispatch.js
CHANGED
|
@@ -10,9 +10,7 @@ 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
|
+
import { formatScopedCoverageNote, formatStrayOutputNote } from "@vitest-agent/sdk";
|
|
16
14
|
|
|
17
15
|
//#region src/dispatcher/dispatch.ts
|
|
18
16
|
/**
|
|
@@ -56,11 +54,27 @@ const dispatcherTable = {
|
|
|
56
54
|
const dispatch = (inputs, opts) => {
|
|
57
55
|
const body = dispatcherTable[inputs.shape][inputs.outcome].agent(inputs, opts);
|
|
58
56
|
const note = scopedCoverageNoteFor(inputs);
|
|
59
|
-
|
|
57
|
+
const withNote = note !== null ? `${body}\n${note}` : body;
|
|
58
|
+
const stray = strayOutputNoteFor(inputs);
|
|
59
|
+
return stray !== null ? `${withNote}\n${stray}\n` : withNote;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* The stray-output note for a run that wrote past Vitest's console capture,
|
|
63
|
+
* or `null` when it wrote nothing stray. `dispatch` and `dispatchInk` append
|
|
64
|
+
* it after the cell's output (and the scoped-coverage note); cells never emit
|
|
65
|
+
* it themselves.
|
|
66
|
+
*
|
|
67
|
+
* @param inputs - the classified dispatch inputs
|
|
68
|
+
* @returns the note's lines joined, or `null`
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
const strayOutputNoteFor = (inputs) => {
|
|
72
|
+
const stray = inputs.state.strayOutput;
|
|
73
|
+
return stray === void 0 ? null : formatStrayOutputNote(stray).join("\n");
|
|
60
74
|
};
|
|
61
75
|
/**
|
|
62
76
|
* Build the scoped-coverage note for this run, or `null` on a full
|
|
63
|
-
* (non-scoped) run. Shared by {@link dispatch} and
|
|
77
|
+
* (non-scoped) run. Shared by {@link dispatch} and the Ink entry's `dispatchInk` so
|
|
64
78
|
* both render paths surface the same information (issue #160 gap 1) —
|
|
65
79
|
* Vitest's coverage thresholds are meaningless against a subset of the
|
|
66
80
|
* project's test files, so every cell's own threshold-flavored coverage
|
|
@@ -73,25 +87,6 @@ const scopedCoverageNoteFor = (inputs) => {
|
|
|
73
87
|
if (cov === null || cov.scoped !== true) return null;
|
|
74
88
|
return formatScopedCoverageNote(cov.scopedFiles ?? 0, cov.totalFiles);
|
|
75
89
|
};
|
|
76
|
-
/**
|
|
77
|
-
* Dispatch to the cell that matches `(inputs.shape, inputs.outcome)`
|
|
78
|
-
* and return its Ink-half React tree. Returns `null` when the matched
|
|
79
|
-
* cell does not expose an `ink` half — callers should fall back to
|
|
80
|
-
* the agent string in that case.
|
|
81
|
-
*
|
|
82
|
-
* @param inputs - the classified dispatch inputs
|
|
83
|
-
* @param opts - cell rendering options
|
|
84
|
-
* @returns the Ink React element, or `null` when the cell has no Ink half
|
|
85
|
-
* @public
|
|
86
|
-
*/
|
|
87
|
-
const dispatchInk = (inputs, opts) => {
|
|
88
|
-
const cell = dispatcherTable[inputs.shape][inputs.outcome];
|
|
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));
|
|
94
|
-
};
|
|
95
90
|
|
|
96
91
|
//#endregion
|
|
97
|
-
export { dispatch,
|
|
92
|
+
export { dispatch, dispatcherTable, scopedCoverageNoteFor, strayOutputNoteFor };
|