pi-minimal 0.2.1 → 0.2.2
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/CHANGELOG.md +19 -1
- package/README.md +3 -1
- package/dist/chrome.js +15 -0
- package/dist/code-box.js +69 -0
- package/dist/cwd.js +5 -3
- package/dist/footer.js +6 -4
- package/dist/index.js +11 -0
- package/dist/tools.js +9 -6
- package/dist/turn-rule.js +5 -3
- package/package.json +1 -1
- package/release-notes.md +20 -18
- package/src/chrome.ts +18 -0
- package/src/code-box.ts +86 -0
- package/src/cwd.ts +7 -3
- package/src/footer.ts +8 -4
- package/src/index.ts +10 -0
- package/src/tools.ts +11 -6
- package/src/turn-rule.ts +7 -3
- package/test/code-box.test.ts +55 -0
- package/test/package.test.ts +1 -1
- package/test/theme.test.ts +2 -2
- package/themes/minimal-dark.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,23 @@ Releases are cut automatically: when the `version` in `package.json` changes on
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.2.2] - 2026-10-22
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- Fenced code renders as a closed box (`╭─ lang ─╮` / `│ code │` / `╰───╯`)
|
|
17
|
+
instead of literal ``` lines, keeping syntax highlighting; long lines wrap
|
|
18
|
+
inside the box. pi-tui exposes no hook for this, so pi-minimal post-processes
|
|
19
|
+
the code token's rendered lines.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Chrome margin (status line, footer, turn rule, tool rows) follows pi's
|
|
23
|
+
`outputPad` setting so it lines up with transcript text. Set
|
|
24
|
+
`editorPaddingX` to the same value for a flush left edge.
|
|
25
|
+
- Chrome modules tolerate a stale `chrome` module after `/reload`, which
|
|
26
|
+
previously could crash the renderer when the package was updated in place.
|
|
27
|
+
- `mdCodeBlockBorder` in `minimal-dark` is a faint grey again (it frames the
|
|
28
|
+
box now) instead of background-coloured.
|
|
29
|
+
|
|
13
30
|
## [0.2.1] - 2026-10-22
|
|
14
31
|
|
|
15
32
|
### Added
|
|
@@ -57,7 +74,8 @@ Releases are cut automatically: when the `version` in `package.json` changes on
|
|
|
57
74
|
- Initial release: header/footer chrome, compact tool activity rows, working
|
|
58
75
|
status line, Herdr sibling status, Gauntlet widget hiding.
|
|
59
76
|
|
|
60
|
-
[Unreleased]: https://github.com/raviy06/pi-minimal/compare/v0.2.
|
|
77
|
+
[Unreleased]: https://github.com/raviy06/pi-minimal/compare/v0.2.2...HEAD
|
|
78
|
+
[0.2.2]: https://github.com/raviy06/pi-minimal/compare/v0.2.1...v0.2.2
|
|
61
79
|
[0.2.1]: https://github.com/raviy06/pi-minimal/compare/v0.2.0...v0.2.1
|
|
62
80
|
[0.2.0]: https://github.com/raviy06/pi-minimal/compare/v0.1.0...v0.2.0
|
|
63
81
|
[0.1.0]: https://github.com/raviy06/pi-minimal/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -35,7 +35,9 @@ Do not load this package beside the live `grok-chrome` extension. Disable or rem
|
|
|
35
35
|
- **Thinking gutter** – thinking paragraphs are prefixed with `┊` so they read as secondary.
|
|
36
36
|
- **Status line** – one line above the editor: model · thinking level · context meter (amber at 70 %, red at 90 %) · cwd · `⎇ branch ±dirty` · Herdr siblings.
|
|
37
37
|
- **Footer** – key hints appear only when the agent is idle and the editor is empty; `/compact` is suggested under context pressure.
|
|
38
|
-
- **
|
|
38
|
+
- **Code blocks** – fenced code renders as a closed box with the language as its title, keeping syntax highlighting, instead of literal ``` lines; long lines wrap inside the box.
|
|
39
|
+
- **Alignment** – chrome follows pi's `outputPad` setting. Pi's editor uses the separate `editorPaddingX` setting (default `0`); set `"editorPaddingX": 1` for a flush left edge with the transcript.
|
|
40
|
+
- **Theme** – ships `minimal-dark`: neutral tool backgrounds, visible user-message tint, dimmer thinking text, violet headings (yellow is reserved for warnings), and faint code-block frames. Select it in `/settings` → Theme, or set `"theme": "minimal-dark"`.
|
|
39
41
|
|
|
40
42
|
## Configuration
|
|
41
43
|
|
package/dist/chrome.js
CHANGED
|
@@ -1 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Horizontal margin for pi-minimal's chrome (status line, footer, turn rule, tool rows).
|
|
3
|
+
* Follows pi's `outputPad` setting so chrome lines up with transcript text. Pi's editor uses
|
|
4
|
+
* the separate `editorPaddingX` setting (default 0); set it to the same value for a flush
|
|
5
|
+
* left edge.
|
|
6
|
+
*/
|
|
7
|
+
const KEY = Symbol.for("pi-minimal.chrome-margin");
|
|
8
|
+
const store = globalThis;
|
|
9
|
+
/** Kept for modules linked against an older copy of this file across a hot reload. */
|
|
1
10
|
export const CHROME_MARGIN = 1;
|
|
11
|
+
export function chromeMargin() {
|
|
12
|
+
return store[KEY] ?? CHROME_MARGIN;
|
|
13
|
+
}
|
|
14
|
+
export function setChromeMargin(value) {
|
|
15
|
+
store[KEY] = value === 0 ? 0 : 1;
|
|
16
|
+
}
|
package/dist/code-box.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Markdown, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
2
|
+
const PATCHED = Symbol.for("pi-minimal.code-box");
|
|
3
|
+
/** Current framing implementation. Lives on globalThis so a /reload swaps the behaviour of the
|
|
4
|
+
* wrapper that an earlier load already installed on pi-tui's shared Markdown prototype. */
|
|
5
|
+
const FRAME_SLOT = Symbol.for("pi-minimal.code-box.frame");
|
|
6
|
+
const slots = globalThis;
|
|
7
|
+
export function frameCodeLines(lines, lang, width, theme) {
|
|
8
|
+
if (lines.length < 2)
|
|
9
|
+
return lines;
|
|
10
|
+
// Trailing spacer ("" after the closing fence) is preserved as-is.
|
|
11
|
+
let end = lines.length;
|
|
12
|
+
while (end > 0 && lines[end - 1] === "")
|
|
13
|
+
end -= 1;
|
|
14
|
+
if (end < 2)
|
|
15
|
+
return lines;
|
|
16
|
+
const trailer = lines.slice(end);
|
|
17
|
+
const body = lines.slice(1, end - 1);
|
|
18
|
+
const indent = theme.codeBlockIndent ?? " ";
|
|
19
|
+
const outer = Math.max(6, width);
|
|
20
|
+
// "│ " + content + " │"
|
|
21
|
+
const innerWidth = outer - 4;
|
|
22
|
+
const label = lang ? ` ${lang} ` : "";
|
|
23
|
+
const topRule = "─".repeat(Math.max(1, outer - 3 - visibleWidth(label)));
|
|
24
|
+
const top = theme.codeBlockBorder(`╭─${label}${topRule}╮`);
|
|
25
|
+
const bottom = theme.codeBlockBorder(`╰${"─".repeat(outer - 2)}╯`);
|
|
26
|
+
const edge = theme.codeBlockBorder("│");
|
|
27
|
+
const framed = [];
|
|
28
|
+
for (const line of body) {
|
|
29
|
+
const content = line.startsWith(indent) ? line.slice(indent.length) : line;
|
|
30
|
+
for (const piece of wrapTextWithAnsi(content, innerWidth)) {
|
|
31
|
+
const pad = Math.max(0, innerWidth - visibleWidth(piece));
|
|
32
|
+
framed.push(`${edge} ${piece}${" ".repeat(pad)} ${edge}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (framed.length === 0)
|
|
36
|
+
framed.push(`${edge} ${" ".repeat(innerWidth)} ${edge}`);
|
|
37
|
+
return [top, ...framed, bottom, ...trailer];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Patch pi-tui's Markdown renderer. The prototype wrapper is installed once per process; every
|
|
41
|
+
* call (including after /reload) points it at this module's `frameCodeLines`.
|
|
42
|
+
* Returns false when the renderer is not patchable.
|
|
43
|
+
*/
|
|
44
|
+
export function installCodeBox() {
|
|
45
|
+
slots[FRAME_SLOT] = frameCodeLines;
|
|
46
|
+
const proto = Markdown.prototype;
|
|
47
|
+
if (proto[PATCHED])
|
|
48
|
+
return true;
|
|
49
|
+
const original = proto.renderToken;
|
|
50
|
+
if (typeof original !== "function")
|
|
51
|
+
return false;
|
|
52
|
+
const originalRender = original;
|
|
53
|
+
proto.renderToken = function (token, width, next, style) {
|
|
54
|
+
const lines = originalRender.call(this, token, width, next, style);
|
|
55
|
+
if (token?.type !== "code" || !this.theme?.codeBlockBorder)
|
|
56
|
+
return lines;
|
|
57
|
+
const frame = slots[FRAME_SLOT];
|
|
58
|
+
if (!frame)
|
|
59
|
+
return lines;
|
|
60
|
+
try {
|
|
61
|
+
return frame(lines, token.lang, width, this.theme);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return lines;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
proto[PATCHED] = true;
|
|
68
|
+
return true;
|
|
69
|
+
}
|
package/dist/cwd.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import
|
|
3
|
+
import * as chrome from "./chrome.js";
|
|
4
4
|
import { formatGitState } from "./git-state.js";
|
|
5
|
+
/** Tolerates a stale chrome module after /reload, where only CHROME_MARGIN may exist. */
|
|
6
|
+
const margin = () => (typeof chrome.chromeMargin === "function" ? chrome.chromeMargin() : (chrome.CHROME_MARGIN ?? 1));
|
|
5
7
|
export function formatCwd(cwd) {
|
|
6
8
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
7
9
|
if (!home)
|
|
@@ -95,7 +97,7 @@ export function installCwd(ctx, sources) {
|
|
|
95
97
|
dispose: unsub,
|
|
96
98
|
invalidate() { },
|
|
97
99
|
render(width) {
|
|
98
|
-
const inner = Math.max(8, width -
|
|
100
|
+
const inner = Math.max(8, width - margin() * 2);
|
|
99
101
|
const id = ctx.model?.id || "no-model";
|
|
100
102
|
const icon = theme.fg("accent", modelIcon(id));
|
|
101
103
|
const name = theme.fg("text", shortModel(id));
|
|
@@ -136,7 +138,7 @@ export function installCwd(ctx, sources) {
|
|
|
136
138
|
.join(theme.fg("dim", " · ")));
|
|
137
139
|
}
|
|
138
140
|
const row = splitRow(left, right, inner);
|
|
139
|
-
return [`${" ".repeat(
|
|
141
|
+
return [`${" ".repeat(margin())}${row}`];
|
|
140
142
|
},
|
|
141
143
|
};
|
|
142
144
|
}, { placement: "aboveEditor" });
|
package/dist/footer.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { keyHint } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
import
|
|
3
|
+
import * as chrome from "./chrome.js";
|
|
4
4
|
import { formatTrackerLines } from "./tracker.js";
|
|
5
|
+
/** Tolerates a stale chrome module after /reload, where only CHROME_MARGIN may exist. */
|
|
6
|
+
const margin = () => (typeof chrome.chromeMargin === "function" ? chrome.chromeMargin() : (chrome.CHROME_MARGIN ?? 1));
|
|
5
7
|
/**
|
|
6
8
|
* Key hints are shown only when nothing else needs the line: idle agent, empty editor. Otherwise
|
|
7
9
|
* the footer collapses to zero lines (tracker lines excepted).
|
|
@@ -24,7 +26,7 @@ export function installFooter(ctx, sources) {
|
|
|
24
26
|
dispose: unsub,
|
|
25
27
|
invalidate() { },
|
|
26
28
|
render(width) {
|
|
27
|
-
const inner = Math.max(8, width -
|
|
29
|
+
const inner = Math.max(8, width - margin() * 2);
|
|
28
30
|
const out = [];
|
|
29
31
|
const hints = footerHints(sources, [
|
|
30
32
|
keyHint("tui.input.submit", "send"),
|
|
@@ -33,10 +35,10 @@ export function installFooter(ctx, sources) {
|
|
|
33
35
|
].filter(Boolean));
|
|
34
36
|
if (hints.length > 0) {
|
|
35
37
|
const line = theme.fg("dim", hints.join(" · "));
|
|
36
|
-
out.push(`${" ".repeat(
|
|
38
|
+
out.push(`${" ".repeat(margin())}${truncateToWidth(line, inner, "")}`);
|
|
37
39
|
}
|
|
38
40
|
for (const extra of formatTrackerLines(ctx, theme)) {
|
|
39
|
-
out.push(`${" ".repeat(
|
|
41
|
+
out.push(`${" ".repeat(margin())}${truncateToWidth(extra, inner, "")}`);
|
|
40
42
|
}
|
|
41
43
|
return out;
|
|
42
44
|
},
|
package/dist/index.js
CHANGED
|
@@ -11,11 +11,15 @@ import { installWorkingStatus } from "./working-status.js";
|
|
|
11
11
|
import { installTurnRule } from "./turn-rule.js";
|
|
12
12
|
import { installTranscript } from "./transcript.js";
|
|
13
13
|
import { createGitState } from "./git-state.js";
|
|
14
|
+
import { installCodeBox } from "./code-box.js";
|
|
15
|
+
import { setChromeMargin } from "./chrome.js";
|
|
16
|
+
import { SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
14
17
|
export default function piMinimalExtension(pi) {
|
|
15
18
|
const stopWorkingStatus = installWorkingStatus(pi);
|
|
16
19
|
const stopTurnRule = installTurnRule(pi);
|
|
17
20
|
installToolRenderers(pi);
|
|
18
21
|
installTranscript(pi);
|
|
22
|
+
installCodeBox();
|
|
19
23
|
let fastTimer;
|
|
20
24
|
let slowTimer;
|
|
21
25
|
let stealTimer;
|
|
@@ -154,6 +158,13 @@ export default function piMinimalExtension(pi) {
|
|
|
154
158
|
});
|
|
155
159
|
hideWidgets = config.hideGauntletWidgets;
|
|
156
160
|
currentCwd = ctx.cwd;
|
|
161
|
+
try {
|
|
162
|
+
const settings = SettingsManager.create(ctx.cwd, undefined, { projectTrusted: ctx.isProjectTrusted() });
|
|
163
|
+
setChromeMargin(settings.getOutputPad());
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Keep the default margin when settings are unavailable.
|
|
167
|
+
}
|
|
157
168
|
installHeader(ctx);
|
|
158
169
|
installChromeFooter(ctx);
|
|
159
170
|
installStatusLine(ctx);
|
package/dist/tools.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { Container, truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
2
|
import { rowIsRunning } from "./activity.js";
|
|
3
|
+
import * as chrome from "./chrome.js";
|
|
3
4
|
import { createLiveWatch } from "./live-watch.js";
|
|
4
5
|
import { bashBody, formatDuration, formatOutcome, layoutRow, resultText, rowGlyph, toolItem, } from "./tool-rows.js";
|
|
6
|
+
/** Tolerates a stale chrome module after /reload, where only CHROME_MARGIN may exist. */
|
|
7
|
+
const margin = () => (typeof chrome.chromeMargin === "function" ? chrome.chromeMargin() : (chrome.CHROME_MARGIN ?? 1));
|
|
5
8
|
const live = createLiveWatch(400);
|
|
6
|
-
/** Left indent of every tool row
|
|
7
|
-
const
|
|
9
|
+
/** Left indent of every tool row: chrome margin plus one cell so rows sit inside message text. */
|
|
10
|
+
const rowIndent = () => margin() + 1;
|
|
8
11
|
/** Max lines of output shown when a row is expanded. */
|
|
9
12
|
const EXPANDED_MAX_LINES = 400;
|
|
10
13
|
export function stopLiveWatch() {
|
|
@@ -44,7 +47,7 @@ function outputBlock(kind, result, theme) {
|
|
|
44
47
|
const lines = expandedLines(kind, result);
|
|
45
48
|
if (lines.length === 0)
|
|
46
49
|
return new Container();
|
|
47
|
-
const indent = " ".repeat(
|
|
50
|
+
const indent = " ".repeat(rowIndent() + 2);
|
|
48
51
|
const rule = theme.fg("borderMuted", "│");
|
|
49
52
|
return {
|
|
50
53
|
render(width) {
|
|
@@ -57,7 +60,7 @@ function outputBlock(kind, result, theme) {
|
|
|
57
60
|
function errorLineBlock(line, theme) {
|
|
58
61
|
if (!line)
|
|
59
62
|
return undefined;
|
|
60
|
-
const indent = " ".repeat(
|
|
63
|
+
const indent = " ".repeat(rowIndent() + 2);
|
|
61
64
|
const rule = theme.fg("error", "│");
|
|
62
65
|
return {
|
|
63
66
|
render(width) {
|
|
@@ -98,7 +101,7 @@ function activityRow(kind, args, theme, context) {
|
|
|
98
101
|
right = right ? `${stats} · ${right}` : stats;
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
|
-
const inner = Math.max(8, width -
|
|
104
|
+
const inner = Math.max(8, width - rowIndent());
|
|
102
105
|
const row = layoutRow(inner, kind, item, right);
|
|
103
106
|
let coloredRight;
|
|
104
107
|
if (status === "running")
|
|
@@ -112,7 +115,7 @@ function activityRow(kind, args, theme, context) {
|
|
|
112
115
|
}
|
|
113
116
|
else
|
|
114
117
|
coloredRight = theme.fg("dim", row.outcome);
|
|
115
|
-
const line = " ".repeat(
|
|
118
|
+
const line = " ".repeat(rowIndent()) +
|
|
116
119
|
theme.fg(statusColor(status), glyph) +
|
|
117
120
|
" " +
|
|
118
121
|
theme.fg("dim", row.verb) +
|
package/dist/turn-rule.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
2
|
-
import
|
|
2
|
+
import * as chrome from "./chrome.js";
|
|
3
3
|
import { formatElapsed } from "./working-status.js";
|
|
4
|
+
/** Tolerates a stale chrome module after /reload, where only CHROME_MARGIN may exist. */
|
|
5
|
+
const margin = () => (typeof chrome.chromeMargin === "function" ? chrome.chromeMargin() : (chrome.CHROME_MARGIN ?? 1));
|
|
4
6
|
export const TURN_ENTRY_TYPE = "pi-minimal.turn";
|
|
5
7
|
export function formatClock(epochMs) {
|
|
6
8
|
const d = new Date(epochMs);
|
|
@@ -70,10 +72,10 @@ export function installTurnRule(pi) {
|
|
|
70
72
|
const color = entry.data.outcome === "completed" ? "borderMuted" : entry.data.outcome === "aborted" ? "warning" : "error";
|
|
71
73
|
return {
|
|
72
74
|
render(width) {
|
|
73
|
-
const inner = Math.max(0, width -
|
|
75
|
+
const inner = Math.max(0, width - margin() * 2);
|
|
74
76
|
const rule = ruleLine(inner, label);
|
|
75
77
|
const colored = rule.replace(label, theme.fg("dim", label));
|
|
76
|
-
return ["", `${" ".repeat(
|
|
78
|
+
return ["", `${" ".repeat(margin())}${theme.fg(color, colored)}`];
|
|
77
79
|
},
|
|
78
80
|
invalidate() { },
|
|
79
81
|
};
|
package/package.json
CHANGED
package/release-notes.md
CHANGED
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
### Added
|
|
2
|
-
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
- `CHANGELOG.md` and a "Releasing" section in the README.
|
|
7
|
-
- The Publish workflow upgrades npm to >= 11.5.1 (required for OIDC trusted
|
|
8
|
-
publishing) and is re-runnable: an existing tag whose version is missing
|
|
9
|
-
from npm is published without re-tagging.
|
|
10
|
-
- `repository`, `homepage`, and `bugs` fields in `package.json`, required for
|
|
11
|
-
npm provenance verification.
|
|
2
|
+
- Fenced code renders as a closed box (`╭─ lang ─╮` / `│ code │` / `╰───╯`)
|
|
3
|
+
instead of literal ``` lines, keeping syntax highlighting; long lines wrap
|
|
4
|
+
inside the box. pi-tui exposes no hook for this, so pi-minimal post-processes
|
|
5
|
+
the code token's rendered lines.
|
|
12
6
|
|
|
13
7
|
### Changed
|
|
14
|
-
-
|
|
15
|
-
|
|
8
|
+
- Chrome margin (status line, footer, turn rule, tool rows) follows pi's
|
|
9
|
+
`outputPad` setting so it lines up with transcript text. Set
|
|
10
|
+
`editorPaddingX` to the same value for a flush left edge.
|
|
11
|
+
- Chrome modules tolerate a stale `chrome` module after `/reload`, which
|
|
12
|
+
previously could crash the renderer when the package was updated in place.
|
|
13
|
+
- `mdCodeBlockBorder` in `minimal-dark` is a faint grey again (it frames the
|
|
14
|
+
box now) instead of background-coloured.
|
|
16
15
|
|
|
17
16
|
## What's Changed
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* Fix trusted publishing: upgrade npm, make Publish re-runnable by @raviy06 in https://github.com/raviy06/pi-minimal/pull/7
|
|
18
|
+
* Add Dependabot configuration file by @raviy06 in https://github.com/raviy06/pi-minimal/pull/9
|
|
19
|
+
* Create SECURITY.md for security policy by @raviy06 in https://github.com/raviy06/pi-minimal/pull/10
|
|
20
|
+
* Pin npm 11 for trusted publishing by @raviy06 in https://github.com/raviy06/pi-minimal/pull/8
|
|
21
|
+
* Drop registry-url so npm uses OIDC instead of a placeholder token by @raviy06 in https://github.com/raviy06/pi-minimal/pull/11
|
|
22
|
+
* Add repository metadata required for npm provenance by @raviy06 in https://github.com/raviy06/pi-minimal/pull/12
|
|
23
|
+
* Frame fenced code, align chrome with outputPad (0.2.2) by @raviy06 in https://github.com/raviy06/pi-minimal/pull/13
|
|
22
24
|
|
|
23
25
|
|
|
24
|
-
**Full Changelog**: https://github.com/raviy06/pi-minimal/compare/v0.1
|
|
26
|
+
**Full Changelog**: https://github.com/raviy06/pi-minimal/compare/v0.2.1...v0.2.2
|
|
25
27
|
|
|
26
28
|
---
|
|
27
29
|
**Install / upgrade**
|
|
28
30
|
```bash
|
|
29
|
-
pi install npm:pi-minimal@0.2.
|
|
31
|
+
pi install npm:pi-minimal@0.2.2 # or: pi update --extensions
|
|
30
32
|
```
|
package/src/chrome.ts
CHANGED
|
@@ -1 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Horizontal margin for pi-minimal's chrome (status line, footer, turn rule, tool rows).
|
|
3
|
+
* Follows pi's `outputPad` setting so chrome lines up with transcript text. Pi's editor uses
|
|
4
|
+
* the separate `editorPaddingX` setting (default 0); set it to the same value for a flush
|
|
5
|
+
* left edge.
|
|
6
|
+
*/
|
|
7
|
+
const KEY = Symbol.for("pi-minimal.chrome-margin")
|
|
8
|
+
const store = globalThis as unknown as Record<symbol, number | undefined>
|
|
9
|
+
|
|
10
|
+
/** Kept for modules linked against an older copy of this file across a hot reload. */
|
|
1
11
|
export const CHROME_MARGIN = 1
|
|
12
|
+
|
|
13
|
+
export function chromeMargin(): number {
|
|
14
|
+
return store[KEY] ?? CHROME_MARGIN
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function setChromeMargin(value: number): void {
|
|
18
|
+
store[KEY] = value === 0 ? 0 : 1
|
|
19
|
+
}
|
package/src/code-box.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Markdown, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* pi-tui renders fenced code as literal ``` lines. Pi exposes no hook for the code token, so
|
|
5
|
+
* pi-minimal post-processes the rendered lines of every `code` token into a closed box:
|
|
6
|
+
*
|
|
7
|
+
* ╭─ json ──────────────╮
|
|
8
|
+
* │ { "a": 1 } │
|
|
9
|
+
* ╰─────────────────────╯
|
|
10
|
+
*
|
|
11
|
+
* Code lines longer than the box are wrapped inside it so the right edge stays intact.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
type CodeToken = { type?: string; lang?: string; text?: string }
|
|
15
|
+
type MarkdownThemeLike = {
|
|
16
|
+
codeBlockBorder: (text: string) => string
|
|
17
|
+
codeBlockIndent?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const PATCHED = Symbol.for("pi-minimal.code-box")
|
|
21
|
+
/** Current framing implementation. Lives on globalThis so a /reload swaps the behaviour of the
|
|
22
|
+
* wrapper that an earlier load already installed on pi-tui's shared Markdown prototype. */
|
|
23
|
+
const FRAME_SLOT = Symbol.for("pi-minimal.code-box.frame")
|
|
24
|
+
|
|
25
|
+
type Framer = (lines: string[], lang: string | undefined, width: number, theme: MarkdownThemeLike) => string[]
|
|
26
|
+
const slots = globalThis as unknown as Record<symbol, Framer | undefined>
|
|
27
|
+
|
|
28
|
+
export function frameCodeLines(lines: string[], lang: string | undefined, width: number, theme: MarkdownThemeLike): string[] {
|
|
29
|
+
if (lines.length < 2) return lines
|
|
30
|
+
// Trailing spacer ("" after the closing fence) is preserved as-is.
|
|
31
|
+
let end = lines.length
|
|
32
|
+
while (end > 0 && lines[end - 1] === "") end -= 1
|
|
33
|
+
if (end < 2) return lines
|
|
34
|
+
const trailer = lines.slice(end)
|
|
35
|
+
const body = lines.slice(1, end - 1)
|
|
36
|
+
const indent = theme.codeBlockIndent ?? " "
|
|
37
|
+
const outer = Math.max(6, width)
|
|
38
|
+
// "│ " + content + " │"
|
|
39
|
+
const innerWidth = outer - 4
|
|
40
|
+
|
|
41
|
+
const label = lang ? ` ${lang} ` : ""
|
|
42
|
+
const topRule = "─".repeat(Math.max(1, outer - 3 - visibleWidth(label)))
|
|
43
|
+
const top = theme.codeBlockBorder(`╭─${label}${topRule}╮`)
|
|
44
|
+
const bottom = theme.codeBlockBorder(`╰${"─".repeat(outer - 2)}╯`)
|
|
45
|
+
const edge = theme.codeBlockBorder("│")
|
|
46
|
+
|
|
47
|
+
const framed: string[] = []
|
|
48
|
+
for (const line of body) {
|
|
49
|
+
const content = line.startsWith(indent) ? line.slice(indent.length) : line
|
|
50
|
+
for (const piece of wrapTextWithAnsi(content, innerWidth)) {
|
|
51
|
+
const pad = Math.max(0, innerWidth - visibleWidth(piece))
|
|
52
|
+
framed.push(`${edge} ${piece}${" ".repeat(pad)} ${edge}`)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (framed.length === 0) framed.push(`${edge} ${" ".repeat(innerWidth)} ${edge}`)
|
|
56
|
+
return [top, ...framed, bottom, ...trailer]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type RenderToken = (this: { theme: MarkdownThemeLike }, token: CodeToken, width: number, next?: string, style?: unknown) => string[]
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Patch pi-tui's Markdown renderer. The prototype wrapper is installed once per process; every
|
|
63
|
+
* call (including after /reload) points it at this module's `frameCodeLines`.
|
|
64
|
+
* Returns false when the renderer is not patchable.
|
|
65
|
+
*/
|
|
66
|
+
export function installCodeBox(): boolean {
|
|
67
|
+
slots[FRAME_SLOT] = frameCodeLines
|
|
68
|
+
const proto = Markdown.prototype as unknown as Record<string | symbol, unknown>
|
|
69
|
+
if (proto[PATCHED]) return true
|
|
70
|
+
const original = proto.renderToken
|
|
71
|
+
if (typeof original !== "function") return false
|
|
72
|
+
const originalRender = original as RenderToken
|
|
73
|
+
proto.renderToken = function (this: { theme: MarkdownThemeLike }, token: CodeToken, width: number, next?: string, style?: unknown) {
|
|
74
|
+
const lines = originalRender.call(this, token, width, next, style)
|
|
75
|
+
if (token?.type !== "code" || !this.theme?.codeBlockBorder) return lines
|
|
76
|
+
const frame = slots[FRAME_SLOT]
|
|
77
|
+
if (!frame) return lines
|
|
78
|
+
try {
|
|
79
|
+
return frame(lines, token.lang, width, this.theme)
|
|
80
|
+
} catch {
|
|
81
|
+
return lines
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
proto[PATCHED] = true
|
|
85
|
+
return true
|
|
86
|
+
}
|
package/src/cwd.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui"
|
|
2
2
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent"
|
|
3
3
|
import path from "node:path"
|
|
4
|
-
import
|
|
4
|
+
import * as chrome from "./chrome.js"
|
|
5
5
|
import { formatGitState, type GitState } from "./git-state.js"
|
|
6
6
|
import type { Sibling } from "./herdr.js"
|
|
7
7
|
|
|
8
|
+
/** Tolerates a stale chrome module after /reload, where only CHROME_MARGIN may exist. */
|
|
9
|
+
const margin = (): number => (typeof chrome.chromeMargin === "function" ? chrome.chromeMargin() : (chrome.CHROME_MARGIN ?? 1))
|
|
10
|
+
|
|
11
|
+
|
|
8
12
|
export function formatCwd(cwd: string): string {
|
|
9
13
|
const home = process.env.HOME || process.env.USERPROFILE || ""
|
|
10
14
|
if (!home) return cwd
|
|
@@ -103,7 +107,7 @@ export function installCwd(ctx: ExtensionContext, sources: StatusLineSources): v
|
|
|
103
107
|
dispose: unsub,
|
|
104
108
|
invalidate() {},
|
|
105
109
|
render(width: number): string[] {
|
|
106
|
-
const inner = Math.max(8, width -
|
|
110
|
+
const inner = Math.max(8, width - margin() * 2)
|
|
107
111
|
const id = ctx.model?.id || "no-model"
|
|
108
112
|
const icon = theme.fg("accent", modelIcon(id))
|
|
109
113
|
const name = theme.fg("text", shortModel(id))
|
|
@@ -147,7 +151,7 @@ export function installCwd(ctx: ExtensionContext, sources: StatusLineSources): v
|
|
|
147
151
|
)
|
|
148
152
|
}
|
|
149
153
|
const row = splitRow(left, right, inner)
|
|
150
|
-
return [`${" ".repeat(
|
|
154
|
+
return [`${" ".repeat(margin())}${row}`]
|
|
151
155
|
},
|
|
152
156
|
}
|
|
153
157
|
},
|
package/src/footer.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent"
|
|
2
2
|
import { keyHint } from "@earendil-works/pi-coding-agent"
|
|
3
3
|
import { truncateToWidth } from "@earendil-works/pi-tui"
|
|
4
|
-
import
|
|
4
|
+
import * as chrome from "./chrome.js"
|
|
5
5
|
import { formatTrackerLines } from "./tracker.js"
|
|
6
6
|
|
|
7
|
+
/** Tolerates a stale chrome module after /reload, where only CHROME_MARGIN may exist. */
|
|
8
|
+
const margin = (): number => (typeof chrome.chromeMargin === "function" ? chrome.chromeMargin() : (chrome.CHROME_MARGIN ?? 1))
|
|
9
|
+
|
|
10
|
+
|
|
7
11
|
export type FooterSources = {
|
|
8
12
|
/** True while an agent run is in progress. */
|
|
9
13
|
busy: () => boolean
|
|
@@ -33,7 +37,7 @@ export function installFooter(ctx: ExtensionContext, sources: FooterSources): vo
|
|
|
33
37
|
dispose: unsub,
|
|
34
38
|
invalidate() {},
|
|
35
39
|
render(width: number): string[] {
|
|
36
|
-
const inner = Math.max(8, width -
|
|
40
|
+
const inner = Math.max(8, width - margin() * 2)
|
|
37
41
|
const out: string[] = []
|
|
38
42
|
const hints = footerHints(sources, [
|
|
39
43
|
keyHint("tui.input.submit", "send"),
|
|
@@ -42,10 +46,10 @@ export function installFooter(ctx: ExtensionContext, sources: FooterSources): vo
|
|
|
42
46
|
].filter(Boolean))
|
|
43
47
|
if (hints.length > 0) {
|
|
44
48
|
const line = theme.fg("dim", hints.join(" · "))
|
|
45
|
-
out.push(`${" ".repeat(
|
|
49
|
+
out.push(`${" ".repeat(margin())}${truncateToWidth(line, inner, "")}`)
|
|
46
50
|
}
|
|
47
51
|
for (const extra of formatTrackerLines(ctx, theme as { fg: (name: string, s: string) => string })) {
|
|
48
|
-
out.push(`${" ".repeat(
|
|
52
|
+
out.push(`${" ".repeat(margin())}${truncateToWidth(extra, inner, "")}`)
|
|
49
53
|
}
|
|
50
54
|
return out
|
|
51
55
|
},
|
package/src/index.ts
CHANGED
|
@@ -11,12 +11,16 @@ import { installWorkingStatus } from "./working-status.js"
|
|
|
11
11
|
import { installTurnRule } from "./turn-rule.js"
|
|
12
12
|
import { installTranscript } from "./transcript.js"
|
|
13
13
|
import { createGitState } from "./git-state.js"
|
|
14
|
+
import { installCodeBox } from "./code-box.js"
|
|
15
|
+
import { setChromeMargin } from "./chrome.js"
|
|
16
|
+
import { SettingsManager } from "@earendil-works/pi-coding-agent"
|
|
14
17
|
|
|
15
18
|
export default function piMinimalExtension(pi: ExtensionAPI): void {
|
|
16
19
|
const stopWorkingStatus = installWorkingStatus(pi)
|
|
17
20
|
const stopTurnRule = installTurnRule(pi)
|
|
18
21
|
installToolRenderers(pi)
|
|
19
22
|
installTranscript(pi)
|
|
23
|
+
installCodeBox()
|
|
20
24
|
let fastTimer: ReturnType<typeof setInterval> | undefined
|
|
21
25
|
let slowTimer: ReturnType<typeof setInterval> | undefined
|
|
22
26
|
let stealTimer: ReturnType<typeof setInterval> | undefined
|
|
@@ -151,6 +155,12 @@ export default function piMinimalExtension(pi: ExtensionAPI): void {
|
|
|
151
155
|
})
|
|
152
156
|
hideWidgets = config.hideGauntletWidgets
|
|
153
157
|
currentCwd = ctx.cwd
|
|
158
|
+
try {
|
|
159
|
+
const settings = SettingsManager.create(ctx.cwd, undefined, { projectTrusted: ctx.isProjectTrusted() })
|
|
160
|
+
setChromeMargin(settings.getOutputPad())
|
|
161
|
+
} catch {
|
|
162
|
+
// Keep the default margin when settings are unavailable.
|
|
163
|
+
}
|
|
154
164
|
installHeader(ctx)
|
|
155
165
|
installChromeFooter(ctx)
|
|
156
166
|
installStatusLine(ctx)
|
package/src/tools.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { ExtensionAPI, Theme, ToolRenderers } from "@earendil-works/pi-coding-agent"
|
|
2
2
|
import { Container, truncateToWidth } from "@earendil-works/pi-tui"
|
|
3
3
|
import { rowIsRunning } from "./activity.js"
|
|
4
|
+
import * as chrome from "./chrome.js"
|
|
4
5
|
import { createLiveWatch } from "./live-watch.js"
|
|
5
6
|
import {
|
|
7
|
+
|
|
6
8
|
bashBody,
|
|
7
9
|
formatDuration,
|
|
8
10
|
formatOutcome,
|
|
@@ -17,6 +19,9 @@ import {
|
|
|
17
19
|
type ToolResultLike,
|
|
18
20
|
} from "./tool-rows.js"
|
|
19
21
|
|
|
22
|
+
/** Tolerates a stale chrome module after /reload, where only CHROME_MARGIN may exist. */
|
|
23
|
+
const margin = (): number => (typeof chrome.chromeMargin === "function" ? chrome.chromeMargin() : (chrome.CHROME_MARGIN ?? 1))
|
|
24
|
+
|
|
20
25
|
type RenderOptions = {
|
|
21
26
|
expanded?: boolean
|
|
22
27
|
}
|
|
@@ -42,8 +47,8 @@ type RenderContext = {
|
|
|
42
47
|
|
|
43
48
|
const live = createLiveWatch(400)
|
|
44
49
|
|
|
45
|
-
/** Left indent of every tool row
|
|
46
|
-
const
|
|
50
|
+
/** Left indent of every tool row: chrome margin plus one cell so rows sit inside message text. */
|
|
51
|
+
const rowIndent = () => margin() + 1
|
|
47
52
|
/** Max lines of output shown when a row is expanded. */
|
|
48
53
|
const EXPANDED_MAX_LINES = 400
|
|
49
54
|
|
|
@@ -82,7 +87,7 @@ function expandedLines(kind: ToolKind, result: ToolResultLike | undefined): stri
|
|
|
82
87
|
function outputBlock(kind: ToolKind, result: ToolResultLike | undefined, theme: Theme) {
|
|
83
88
|
const lines = expandedLines(kind, result)
|
|
84
89
|
if (lines.length === 0) return new Container()
|
|
85
|
-
const indent = " ".repeat(
|
|
90
|
+
const indent = " ".repeat(rowIndent() + 2)
|
|
86
91
|
const rule = theme.fg("borderMuted", "│")
|
|
87
92
|
return {
|
|
88
93
|
render(width: number): string[] {
|
|
@@ -95,7 +100,7 @@ function outputBlock(kind: ToolKind, result: ToolResultLike | undefined, theme:
|
|
|
95
100
|
|
|
96
101
|
function errorLineBlock(line: string | undefined, theme: Theme) {
|
|
97
102
|
if (!line) return undefined
|
|
98
|
-
const indent = " ".repeat(
|
|
103
|
+
const indent = " ".repeat(rowIndent() + 2)
|
|
99
104
|
const rule = theme.fg("error", "│")
|
|
100
105
|
return {
|
|
101
106
|
render(width: number): string[] {
|
|
@@ -135,7 +140,7 @@ function activityRow(kind: ToolKind, args: ToolArgs | undefined, theme: Theme, c
|
|
|
135
140
|
right = right ? `${stats} · ${right}` : stats
|
|
136
141
|
}
|
|
137
142
|
}
|
|
138
|
-
const inner = Math.max(8, width -
|
|
143
|
+
const inner = Math.max(8, width - rowIndent())
|
|
139
144
|
const row = layoutRow(inner, kind, item, right)
|
|
140
145
|
|
|
141
146
|
let coloredRight: string
|
|
@@ -148,7 +153,7 @@ function activityRow(kind: ToolKind, args: ToolArgs | undefined, theme: Theme, c
|
|
|
148
153
|
} else coloredRight = theme.fg("dim", row.outcome)
|
|
149
154
|
|
|
150
155
|
const line =
|
|
151
|
-
" ".repeat(
|
|
156
|
+
" ".repeat(rowIndent()) +
|
|
152
157
|
theme.fg(statusColor(status), glyph) +
|
|
153
158
|
" " +
|
|
154
159
|
theme.fg("dim", row.verb) +
|
package/src/turn-rule.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"
|
|
2
2
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui"
|
|
3
|
-
import
|
|
3
|
+
import * as chrome from "./chrome.js"
|
|
4
4
|
import { formatElapsed } from "./working-status.js"
|
|
5
5
|
|
|
6
|
+
/** Tolerates a stale chrome module after /reload, where only CHROME_MARGIN may exist. */
|
|
7
|
+
const margin = (): number => (typeof chrome.chromeMargin === "function" ? chrome.chromeMargin() : (chrome.CHROME_MARGIN ?? 1))
|
|
8
|
+
|
|
9
|
+
|
|
6
10
|
export const TURN_ENTRY_TYPE = "pi-minimal.turn"
|
|
7
11
|
|
|
8
12
|
export type TurnSummary = {
|
|
@@ -83,10 +87,10 @@ export function installTurnRule(pi: ExtensionAPI): () => void {
|
|
|
83
87
|
const color = entry.data.outcome === "completed" ? "borderMuted" : entry.data.outcome === "aborted" ? "warning" : "error"
|
|
84
88
|
return {
|
|
85
89
|
render(width: number): string[] {
|
|
86
|
-
const inner = Math.max(0, width -
|
|
90
|
+
const inner = Math.max(0, width - margin() * 2)
|
|
87
91
|
const rule = ruleLine(inner, label)
|
|
88
92
|
const colored = rule.replace(label, theme.fg("dim", label))
|
|
89
|
-
return ["", `${" ".repeat(
|
|
93
|
+
return ["", `${" ".repeat(margin())}${theme.fg(color, colored)}`]
|
|
90
94
|
},
|
|
91
95
|
invalidate() {},
|
|
92
96
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import assert from "node:assert/strict"
|
|
2
|
+
import test from "node:test"
|
|
3
|
+
import { Markdown } from "@earendil-works/pi-tui"
|
|
4
|
+
import { frameCodeLines, installCodeBox } from "../src/code-box.js"
|
|
5
|
+
|
|
6
|
+
const theme = { codeBlockBorder: (s: string) => s }
|
|
7
|
+
const ESC = String.fromCharCode(27)
|
|
8
|
+
const stripAnsi = (s: string) => s.split(ESC).map((p, i) => (i === 0 ? p : p.replace(/^\[[0-9;]*m/, ""))).join("")
|
|
9
|
+
|
|
10
|
+
test("frameCodeLines replaces fences with a closed box and keeps the trailer", () => {
|
|
11
|
+
const out = frameCodeLines(["```json", ' {"a":1}', " ", "```", ""], "json", 20, theme)
|
|
12
|
+
assert.equal(out.length, 5)
|
|
13
|
+
assert.ok(out[0].startsWith("╭─ json ─") && out[0].endsWith("╮"))
|
|
14
|
+
assert.equal(out[1], '│ {"a":1} │')
|
|
15
|
+
assert.equal(out[2], "│ │")
|
|
16
|
+
assert.ok(out[3].startsWith("╰─") && out[3].endsWith("╯"))
|
|
17
|
+
for (const l of out.slice(0, 4)) assert.equal(l.length, 20, l)
|
|
18
|
+
assert.equal(out[4], "")
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test("frameCodeLines wraps long lines inside the box", () => {
|
|
22
|
+
const out = frameCodeLines(["```", " " + "x".repeat(30), "```"], undefined, 20, theme)
|
|
23
|
+
assert.equal(out.length, 4, out.join("\n"))
|
|
24
|
+
assert.equal(out[1], `│ ${"x".repeat(16)} │`)
|
|
25
|
+
assert.equal(out[2], `│ ${"x".repeat(14)} │`)
|
|
26
|
+
for (const l of out) assert.equal(l.length, 20, l)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test("frameCodeLines handles empty blocks and missing language", () => {
|
|
30
|
+
const out = frameCodeLines(["```", "```"], undefined, 10, theme)
|
|
31
|
+
assert.deepEqual(out.map((l) => l[0]), ["╭", "│", "╰"])
|
|
32
|
+
assert.deepEqual(out.map((l) => l[l.length - 1]), ["╮", "│", "╯"])
|
|
33
|
+
assert.ok(!out[0].includes(" "), "no language label")
|
|
34
|
+
assert.deepEqual(frameCodeLines(["x"], undefined, 10, theme), ["x"])
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test("installCodeBox patches pi-tui's Markdown renderer idempotently", () => {
|
|
38
|
+
assert.equal(installCodeBox(), true)
|
|
39
|
+
assert.equal(installCodeBox(), true)
|
|
40
|
+
// A reload re-points the installed wrapper at the current framer.
|
|
41
|
+
const slot = Symbol.for("pi-minimal.code-box.frame")
|
|
42
|
+
const slots = globalThis as unknown as Record<symbol, unknown>
|
|
43
|
+
assert.equal(slots[slot], frameCodeLines)
|
|
44
|
+
const id = (s: string) => s
|
|
45
|
+
const mdTheme = {
|
|
46
|
+
heading: id, link: id, linkUrl: id, code: id, codeBlock: id, codeBlockBorder: id, quote: id, quoteBorder: id,
|
|
47
|
+
hr: id, listBullet: id, bold: id, italic: id, underline: id, strikethrough: id,
|
|
48
|
+
}
|
|
49
|
+
const md = new Markdown("before\n\n```ts\nconst x = 1\n```\n\nafter", 0, 0, mdTheme as never)
|
|
50
|
+
const lines = md.render(40).map(stripAnsi)
|
|
51
|
+
assert.ok(lines.some((l) => l.startsWith("╭─ ts ") && l.endsWith("╮")), lines.join("\n"))
|
|
52
|
+
assert.ok(lines.some((l) => l.startsWith("│ const x = 1") && l.endsWith("│")), lines.join("\n"))
|
|
53
|
+
assert.ok(lines.some((l) => l.startsWith("╰─") && l.endsWith("╯")), lines.join("\n"))
|
|
54
|
+
assert.ok(!lines.some((l) => l.includes("```")), "no literal fences remain")
|
|
55
|
+
})
|
package/test/package.test.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { readFileSync } from "node:fs"
|
|
|
5
5
|
test("package manifest exposes the committed extension", () => {
|
|
6
6
|
const pkg = JSON.parse(readFileSync("package.json", "utf8"))
|
|
7
7
|
assert.equal(pkg.name, "pi-minimal")
|
|
8
|
-
assert.equal(pkg.version, "0.2.
|
|
8
|
+
assert.equal(pkg.version, "0.2.2")
|
|
9
9
|
assert.equal(pkg.license, "MIT")
|
|
10
10
|
assert.equal(pkg.type, "module")
|
|
11
11
|
assert.deepEqual(pkg.engines, { node: ">=22.19.0" })
|
package/test/theme.test.ts
CHANGED
|
@@ -37,8 +37,8 @@ test("minimal-dark variable references resolve", () => {
|
|
|
37
37
|
}
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
-
test("minimal-dark
|
|
41
|
-
assert.equal(theme.colors.mdCodeBlockBorder, "
|
|
40
|
+
test("minimal-dark frames code quietly and neutralises tool backgrounds", () => {
|
|
41
|
+
assert.equal(theme.colors.mdCodeBlockBorder, "faint")
|
|
42
42
|
assert.equal(theme.colors.mdCodeBlock, "text")
|
|
43
43
|
assert.equal(theme.colors.toolSuccessBg, theme.colors.toolPendingBg)
|
|
44
44
|
assert.notEqual(theme.colors.mdHeading, theme.colors.warning)
|