pi-stereo-view 0.1.5 → 0.1.7
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 +13 -0
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/preferences.ts +8 -7
- package/src/timestamps.ts +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.7 - 2026-10-24
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Keep panel zoom and split on/off temporary to the current session. New sessions always show both panels using the saved layout, order and sizes.
|
|
8
|
+
- Ignore zoom and split-state values from older settings files without resetting panel sizes or timestamp and copy-button preferences.
|
|
9
|
+
|
|
10
|
+
## 0.1.6 - 2026-10-24
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Hide clock placeholders and copy buttons for blocks without a known timestamp, including system notices. Their content remains visible without extra control rows.
|
|
15
|
+
|
|
3
16
|
## 0.1.5 - 2026-10-23
|
|
4
17
|
|
|
5
18
|
### Added
|
package/README.md
CHANGED
|
@@ -115,7 +115,7 @@ Press **Escape** or `[×]` to close search and restore your previous scroll posi
|
|
|
115
115
|
|
|
116
116
|
## Copy a block
|
|
117
117
|
|
|
118
|
-
Click the dim `⧉` icon below the right corner of a message, thinking, tool or other history block to copy its text. Copy buttons work in split and zoomed panels, even with timestamps hidden.
|
|
118
|
+
Click the dim `⧉` icon below the right corner of a message, thinking, tool or other history block to copy its text. Blocks without a known time have no copy button. Copy buttons work in split and zoomed panels, even with timestamps hidden.
|
|
119
119
|
|
|
120
120
|
Messages and thinking keep their original text/Markdown, without terminal colours, clocks or copy buttons. Copying a tool includes its name, arguments, saved result and linked late output. A late-output block also has its own button to copy just that block. Collapsed and off-screen text is included.
|
|
121
121
|
|
|
@@ -129,7 +129,7 @@ Use **Show copy buttons** in `≡` or `/stereo-view settings` to hide the icons
|
|
|
129
129
|
|
|
130
130
|
Conversation and Activity can show a dim local `HH:mm:ss` clock above each block on the left, with a small inset. The copy icon sits below the block on the right, so neither control takes width away from the text. New tool calls show their start time.
|
|
131
131
|
|
|
132
|
-
Older tool calls may show `~HH:mm:ss` for an approximate time.
|
|
132
|
+
Older tool calls may show `~HH:mm:ss` for an approximate time. Blocks without a known time show neither a clock nor a copy button; their content stays visible.
|
|
133
133
|
|
|
134
134
|
Use `≡` or `/stereo-view settings` to hide or show clocks. This setting survives restart. Clocks appear in split and zoomed panels, not in combined history. They are display-only and are not added to the conversation sent to the model.
|
|
135
135
|
|
|
@@ -157,7 +157,7 @@ Keyboard scrolling (`PgUp`/`PgDn` and Pi's other scroll keys) moves the active p
|
|
|
157
157
|
|
|
158
158
|
## Settings and limits
|
|
159
159
|
|
|
160
|
-
Your layout, panel order, sizes,
|
|
160
|
+
Your layout, panel order, sizes, timestamp and copy-button settings are saved automatically. Each new session opens both panels in that arrangement. Zooming one panel or turning split off is temporary and does not carry over to another session.
|
|
161
161
|
|
|
162
162
|
- Tested with Pi 1.1.0. Requires Node.js 22.19 or newer.
|
|
163
163
|
- Regular mode shows one combined history.
|
package/package.json
CHANGED
package/src/preferences.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
-
/**
|
|
5
|
+
/** Display state; mode and zoom are session-only, while arrangement and display choices are saved. */
|
|
6
6
|
export interface Preferences {
|
|
7
7
|
mode: "split" | "combined";
|
|
8
8
|
/** Left means first: left in columns, top in rows. */
|
|
@@ -35,19 +35,18 @@ export async function loadPreferences(file: string): Promise<Preferences> {
|
|
|
35
35
|
// The first version only had columns; keep that saved arrangement.
|
|
36
36
|
const layout = record.layout === undefined ? "columns" : record.layout;
|
|
37
37
|
const chatHeight = record.chatHeight === undefined ? defaults.chatHeight : record.chatHeight;
|
|
38
|
-
const zoom = record.zoom === undefined ? defaults.zoom : record.zoom;
|
|
39
38
|
const showTimestamps = record.showTimestamps === undefined ? defaults.showTimestamps : record.showTimestamps;
|
|
40
39
|
const showCopyButtons = record.showCopyButtons === undefined ? defaults.showCopyButtons : record.showCopyButtons;
|
|
41
|
-
if ((record.
|
|
42
|
-
(record.side !== "left" && record.side !== "right") ||
|
|
40
|
+
if ((record.side !== "left" && record.side !== "right") ||
|
|
43
41
|
(record.chatWidth !== null && (typeof record.chatWidth !== "number" || !Number.isInteger(record.chatWidth) ||
|
|
44
42
|
record.chatWidth < 24 || record.chatWidth > 240)) ||
|
|
45
43
|
(layout !== "columns" && layout !== "rows") ||
|
|
46
44
|
(chatHeight !== null && (typeof chatHeight !== "number" || !Number.isInteger(chatHeight) || chatHeight < 4 || chatHeight > 240)) ||
|
|
47
|
-
|
|
45
|
+
typeof showTimestamps !== "boolean" || typeof showCopyButtons !== "boolean") {
|
|
48
46
|
throw new Error("Invalid pi-stereo-view preferences");
|
|
49
47
|
}
|
|
50
|
-
|
|
48
|
+
// Legacy files may contain mode/zoom. Every session starts with both panels visible.
|
|
49
|
+
return { mode: "split", side: record.side, chatWidth: record.chatWidth, layout, chatHeight, zoom: null, showTimestamps, showCopyButtons };
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
/** Atomically save only the display settings, never session messages. */
|
|
@@ -55,7 +54,9 @@ export async function savePreferences(file: string, preferences: Preferences): P
|
|
|
55
54
|
await mkdir(path.dirname(file), { recursive: true });
|
|
56
55
|
const temporary = file + "." + randomUUID() + ".tmp";
|
|
57
56
|
try {
|
|
58
|
-
|
|
57
|
+
const { side, chatWidth, layout, chatHeight, showTimestamps, showCopyButtons } = preferences;
|
|
58
|
+
const saved = { side, chatWidth, layout, chatHeight, showTimestamps, showCopyButtons };
|
|
59
|
+
await writeFile(temporary, JSON.stringify(saved, null, 2) + "\n", { mode: 0o600 });
|
|
59
60
|
await rename(temporary, file);
|
|
60
61
|
} finally {
|
|
61
62
|
await rm(temporary, { force: true });
|
package/src/timestamps.ts
CHANGED
|
@@ -32,7 +32,7 @@ const validTime = (value: unknown): number | undefined => {
|
|
|
32
32
|
/** Format a reliable source in the process's local zone; never substitute now. */
|
|
33
33
|
export function timestampLabel(value: unknown): string {
|
|
34
34
|
const time = validTime(value);
|
|
35
|
-
if (time === undefined) return "
|
|
35
|
+
if (time === undefined) return "";
|
|
36
36
|
const date = new Date(time);
|
|
37
37
|
return [date.getHours(), date.getMinutes(), date.getSeconds()]
|
|
38
38
|
.map(part => String(part).padStart(2, "0")).join(":");
|
|
@@ -176,15 +176,15 @@ export class TranscriptTimestamps {
|
|
|
176
176
|
toolCallId?: string;
|
|
177
177
|
};
|
|
178
178
|
if (name === "UserMessageComponent" || name === "SkillInvocationMessageComponent" || name === "BashExecutionComponent") {
|
|
179
|
-
return this.orderedLabels.get(component) ?? "
|
|
179
|
+
return this.orderedLabels.get(component) ?? "";
|
|
180
180
|
}
|
|
181
181
|
if (name === "ToolExecutionComponent") {
|
|
182
182
|
const id = native.toolCallId!;
|
|
183
183
|
const start = this.starts.get(id);
|
|
184
184
|
if (start !== undefined) return timestampLabel(start);
|
|
185
|
-
if (this.liveCalls.has(id)) return "
|
|
185
|
+
if (this.liveCalls.has(id)) return "";
|
|
186
186
|
const owner = this.owners.get(id);
|
|
187
|
-
return validTime(owner) === undefined ? "
|
|
187
|
+
return validTime(owner) === undefined ? "" : "~" + timestampLabel(owner);
|
|
188
188
|
}
|
|
189
189
|
const message = name === "AssistantMessageComponent" ? native.lastMessage : native.message;
|
|
190
190
|
return timestampLabel(message ? validTime(message.timestamp) ?? this.messageTimes.get(message) : native.entry?.timestamp);
|
|
@@ -245,13 +245,13 @@ export class CompactMessage extends Container {
|
|
|
245
245
|
};
|
|
246
246
|
}
|
|
247
247
|
private renderContent(width: number): string[] {
|
|
248
|
-
const label = this.
|
|
248
|
+
const label = this.label();
|
|
249
249
|
const gutter = this.gutter();
|
|
250
250
|
const theme = this.theme();
|
|
251
251
|
// Only the scrollbar needs a side gutter; controls have their own rows.
|
|
252
252
|
this.contentWidth = Math.max(1, width - gutter);
|
|
253
|
-
const time = label && visibleWidth(label) + 1 <= this.contentWidth ? " " + label : "";
|
|
254
|
-
const copy = this.copy && this.showCopy() && this.contentWidth >= COPY_CONTROL_WIDTH ? COPY_CONTROL : "";
|
|
253
|
+
const time = label && this.show() && visibleWidth(label) + 1 <= this.contentWidth ? " " + label : "";
|
|
254
|
+
const copy = label && this.copy && this.showCopy() && this.contentWidth >= COPY_CONTROL_WIDTH ? COPY_CONTROL : "";
|
|
255
255
|
const content = this.nativeCache.render(this.contentWidth, theme);
|
|
256
256
|
this.contentHeight = content.length;
|
|
257
257
|
const cached = this.formatted;
|