Skip to content

Commit c9dad99

Browse files
committed
Release v0.1.17
1 parent 711904c commit c9dad99

5 files changed

Lines changed: 57 additions & 21 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ A global [Pi coding agent](https://github.com/badlogic/pi-mono) extension that a
88

99
- New sessions start in **Build** mode.
1010
- Bare `Tab` cycles **Build → Plan → Build**, while active autocomplete dropdowns retain Pi's normal Tab completion.
11-
- The composer uses OpenCode prompt-inspired blue/orange mode colors on the rounded top-left border and left rail, complemented by Pi's border color on the right rail and rounded bottom-right border; the frame leaves open gaps at the top right and bottom left. It also includes a mode/model/thinking metadata row; cycling the thinking level updates this row without adding a duplicate status above the composer. The model is shown as `model-id [provider]` (for example, `gpt-5.6-luna [openai]`), with the model ID inheriting the terminal foreground like unselected entries in `/model` and the provider using the footer's dim text color. The footer keeps the remaining path and usage stats without duplicating model metadata.
11+
- The composer uses OpenCode prompt-inspired blue/orange mode colors on the rounded top-left border and left rail, complemented by Pi's border color on the right rail and rounded bottom-right border; dim rounded corners and dashed `` segments bridge the border colors at the top right and bottom left. It also includes a mode/model/thinking metadata row; cycling the thinking level updates this row without adding a duplicate status above the composer. The model is shown as `model-id [provider]` (for example, `gpt-5.6-luna [openai]`), with the model ID inheriting the terminal foreground like unselected entries in `/model` and the provider using the footer's dim text color. The footer keeps the remaining path and usage stats without duplicating model metadata.
1212
- `/plan`, `/build`, and the `--plan` startup flag.
1313
- Per-session plans at `~/.pi/agent/plans/<session-id>.md`.
1414
- In Plan mode, built-in `edit` and `write` are restricted to the exact plan file.

index.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -503,11 +503,21 @@ export default function planBuildModes(pi: ExtensionAPI): void {
503503
width - 1,
504504
"",
505505
);
506-
const topBorder = formatModeTopBorder(selectedMode, width);
507-
return renderModeComposer(lines, topBorder, leftRail, rightRail, metadata, railWidth, width, {
508-
truncate: (line, maxWidth) => truncateToWidth(line, maxWidth, ""),
509-
measure: visibleWidth,
510-
});
506+
const topBorder = formatModeTopBorder(selectedMode, width, ctx.ui.theme.fg("dim", "╮"));
507+
return renderModeComposer(
508+
lines,
509+
topBorder,
510+
leftRail,
511+
rightRail,
512+
metadata,
513+
ctx.ui.theme.fg("dim", "╰"),
514+
railWidth,
515+
width,
516+
{
517+
truncate: (line, maxWidth) => truncateToWidth(line, maxWidth, ""),
518+
measure: visibleWidth,
519+
},
520+
);
511521
}
512522

513523
override handleInput(data: string): void {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@janvitos/pi-plan-build",
3-
"version": "0.1.16",
3+
"version": "0.1.17",
44
"description": "Plan safely, approve explicitly, then implement here or in a clean session.",
55
"type": "module",
66
"license": "MIT",

utils.test.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,11 @@ test("mode composer uses colored rails and mode/thinking metadata", () => {
7474
const buildRail = formatModeRail("build");
7575
assert.equal(planRail, "\x1b[38;2;245;167;66m│\x1b[0m");
7676
assert.equal(buildRail, "\x1b[38;2;92;156;245m│\x1b[0m");
77-
assert.equal(formatModeTopBorder("plan", 4), "\x1b[38;2;245;167;66m╭──\x1b[0m");
78-
assert.equal(formatModeTopBorder("build", 1), "");
77+
assert.equal(
78+
formatModeTopBorder("plan", 4, "\x1b[2m╮\x1b[22m"),
79+
"\x1b[38;2;245;167;66m╭─┄\x1b[0m\x1b[2m╮\x1b[22m",
80+
);
81+
assert.equal(formatModeTopBorder("build", 2, "\x1b[2m╮\x1b[22m"), "");
7982
assert.equal(
8083
formatModeMetadata("plan", "high", theme, thinkingColor),
8184
"\x1b[38;2;245;167;66m│\x1b[0m \x1b[38;2;245;167;66mplan\x1b[0m\x1b[38;2;128;128;128m · \x1b[39m\x1b[38;2;0;255;0mhigh\x1b[39m",
@@ -107,37 +110,57 @@ test("footer helpers preserve compact counts and safe home-relative paths", () =
107110
assert.equal(formatFooterCwd("/home/username/project", "/home/user"), "/home/username/project");
108111
});
109112

110-
test("mode composer completes the right and lower borders while preserving corner gaps", () => {
113+
test("mode composer joins border colors with dashed transitions and dim corners", () => {
111114
const ansiPattern = /\x1b\[[0-?]*[ -/]*[@-~]/gu;
112115
const lineWidth = {
113116
truncate: (line: string, width: number) => line.replace(ansiPattern, "").length <= width ? line : line.slice(0, width),
114117
measure: (line: string) => line.replace(ansiPattern, "").length,
115118
};
116119
const lines = ["top border", " first", " second", "────────────────", " autocomplete"];
117-
assert.deepEqual(renderModeComposer(lines, "╭──────────────", "│ ", "│", "│ plan · high", 2, 16, lineWidth), [
118-
"╭──────────────",
120+
assert.deepEqual(renderModeComposer(lines, "╭─────────────┄╮", "│ ", "│", "│ plan · high", "╰", 2, 16, lineWidth), [
121+
"╭─────────────┄╮",
122+
"│ │",
119123
"│ first │",
120124
"│ second │",
121125
"│ │",
122126
"│ plan · high │",
123-
"─────────────╯",
127+
"╰┄─────────────╯",
124128
"",
125129
" autocomplete",
126130
]);
131+
132+
const styledGlyph = "\x1b[38;2;157;124;216m─\x1b[39m";
133+
const realisticLines = ["top", " prompt", styledGlyph.repeat(16)];
134+
const realisticResult = renderModeComposer(
135+
realisticLines,
136+
"╭─────────────┄╮",
137+
"│ ",
138+
"│",
139+
"metadata",
140+
"╰",
141+
2,
142+
16,
143+
lineWidth,
144+
);
145+
assert.equal(realisticResult.every((line) => lineWidth.measure(line) <= 16), true);
146+
assert.equal(realisticResult[5]?.replace(ansiPattern, ""), "╰┄─────────────╯");
147+
assert.equal(realisticResult[5]?.replace(ansiPattern, "").includes("[39m"), false);
148+
127149
assert.deepEqual(
128150
renderModeComposer(
129151
["top", " prompt", "\x1b[38;2;128;128;128m────\x1b[0m"],
130-
"╭─",
152+
"╭─┄╮",
131153
"│ ",
132154
"│",
133155
"metadata",
156+
"╰",
134157
2,
135158
4,
136159
lineWidth,
137160
),
138-
["╭─", "│ p│", "│ │", "met│", "\x1b[38;2;128;128;128m─╯\x1b[0m", ""],
161+
["╭─┄╮", "│ │", "│ p│", "│ │", "met│", "\x1b[38;2;128;128;128m─╯\x1b[0m", ""],
139162
);
140-
assert.deepEqual(renderModeComposer(lines, "top", "│ ", "│", "metadata", 0, 16, lineWidth), lines);
163+
assert.deepEqual(renderModeComposer(lines, "top", "│ ", "│", "metadata", "╰", 0, 16, lineWidth), lines);
141164
});
142165

143166
test("plan review preserves the complete plan without truncation", () => {

utils.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ export function formatModeRail(mode: Mode): string {
4242
return formatModeColor(mode, "│");
4343
}
4444

45-
export function formatModeTopBorder(mode: Mode, width: number): string {
46-
if (width <= 1) return "";
47-
return formatModeColor(mode, `╭${"─".repeat(width - 2)}`);
45+
export function formatModeTopBorder(mode: Mode, width: number, topRightCorner: string): string {
46+
if (width <= 2) return "";
47+
return `${formatModeColor(mode, `╭${"─".repeat(width - 3)}┄`)}${topRightCorner}`;
4848
}
4949

5050
export function formatModeMetadata(
@@ -95,6 +95,7 @@ export function renderModeComposer(
9595
leftRailPrefix: string,
9696
rightRail: string,
9797
metadata: string,
98+
bottomLeftCorner: string,
9899
reservedWidth: number,
99100
width: number,
100101
lineWidth: LineWidthTools,
@@ -111,11 +112,13 @@ export function renderModeComposer(
111112
const promptLines = lines
112113
.slice(1, bottomBorderIndex)
113114
.map((line) => addRightRail(leftRailPrefix + line.slice(reservedPrefix.length)));
114-
const bottomBorder = lineWidth.truncate(lines[bottomBorderIndex]!, width)
115-
.replace(/^((?:\x1b\[[0-?]*[ -/]*[@-~])*)./u, "$1 ")
115+
const ansiSequence = "(?:\\x1b\\[[0-?]*[ -/]*[@-~])*";
116+
const bottomBorder = bottomLeftCorner + lineWidth.truncate(lines[bottomBorderIndex]!, width)
117+
.replace(new RegExp(`^(${ansiSequence}).${ansiSequence}.`, "u"), "$1┄")
116118
.replace(/.(?=(?:\x1b\[[0-?]*[ -/]*[@-~])*$)/u, "╯");
117119
return [
118120
topBorder,
121+
addRightRail(leftRailPrefix),
119122
...promptLines,
120123
addRightRail(leftRailPrefix),
121124
addRightRail(metadata),

0 commit comments

Comments
 (0)