pi-goal-x 0.27.1 → 0.27.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.
|
@@ -134,6 +134,39 @@ function fit(value: string, width: number): string {
|
|
|
134
134
|
return visibleWidth(value) > width ? truncateToWidth(value, width, "…") : value;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
// ── terminal-height bound (spec 2026-08-10-widget-height-bound-scrollback-fix) ──
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Minimum rows reserved below/around the widget so pi's frame stays usable
|
|
141
|
+
* when the widget is at its cap: status line (1) + editor minimum (3) +
|
|
142
|
+
* footer (1) + a chat row (1). With the widget capped at
|
|
143
|
+
* `terminalRows − WIDGET_HEIGHT_RESERVE`, the widget never fills the terminal:
|
|
144
|
+
* the chat row stays in the frame, the widget's top lines stay within the
|
|
145
|
+
* viewport (no pi-tui full-render scrollback wipe), and the editor/footer
|
|
146
|
+
* stay visible — the equal-height scroll-up failure is impossible. Kept at
|
|
147
|
+
* the minimum that still guarantees the chat row: a larger reserve would
|
|
148
|
+
* slice dashboards that genuinely fit (e.g. the expanded 24-line dashboard
|
|
149
|
+
* on a 30-row terminal renders unchanged at 24 + 5 chrome + 1 chat = 30).
|
|
150
|
+
*/
|
|
151
|
+
export const WIDGET_HEIGHT_RESERVE = 6;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Deterministic height guard: cap rendered lines to the terminal height minus
|
|
155
|
+
* the reserved chrome. Head slice, because the dashboard's content priority
|
|
156
|
+
* is top-down (identity → status → progress → tasks → details → hints); the
|
|
157
|
+
* goal identity/status and the interactive task list stay in frame. Pure
|
|
158
|
+
* index function — stable across renders, so the widget never shrinks on its
|
|
159
|
+
* own and can never trigger pi-tui's clearOnShrink full-render
|
|
160
|
+
* (`\x1b[2J\x1b[H\x1b[3J` scrollback wipe). No-op (unbounded) when
|
|
161
|
+
* terminalRows is missing (mock TUI, headless contexts, /goal-status text).
|
|
162
|
+
*/
|
|
163
|
+
export function boundWidgetRenderLines(lines: string[], terminalRows: number | undefined): string[] {
|
|
164
|
+
if (!terminalRows || terminalRows <= 0) return lines;
|
|
165
|
+
const cap = Math.max(1, terminalRows - WIDGET_HEIGHT_RESERVE);
|
|
166
|
+
if (lines.length <= cap) return lines;
|
|
167
|
+
return lines.slice(0, cap);
|
|
168
|
+
}
|
|
169
|
+
|
|
137
170
|
function heading(theme: Theme, width: number, left: string, right = ""): string {
|
|
138
171
|
if (!right) return fit(left, width);
|
|
139
172
|
const rightPart = ` ${right}`;
|
|
@@ -190,18 +223,18 @@ export function renderAuditResultCardView(
|
|
|
190
223
|
return renderAuditResultCard(deriveAuditResultCard(view.verdict, view.report), theme, width);
|
|
191
224
|
}
|
|
192
225
|
|
|
193
|
-
export function renderGoalWidgetLines(goal: GoalWidgetRecord | null, theme: Theme, width: number, options: { openGoalCount?: number; auditorProgress?: AuditorWidgetProgress | null; disableTasks?: boolean; stalled?: boolean; ledgerEvents?: GoalLedgerEvent[]; expanded?: boolean; debug?: boolean; model?: GoalDashboardModel | null; compactScrollOffset?: number; expandedScrollOffset?: number; expandedTaskRows?: number; keybindings?: GoalDashboardKeybindings } = {}): string[] {
|
|
226
|
+
export function renderGoalWidgetLines(goal: GoalWidgetRecord | null, theme: Theme, width: number, options: { openGoalCount?: number; auditorProgress?: AuditorWidgetProgress | null; disableTasks?: boolean; stalled?: boolean; ledgerEvents?: GoalLedgerEvent[]; expanded?: boolean; debug?: boolean; model?: GoalDashboardModel | null; compactScrollOffset?: number; expandedScrollOffset?: number; expandedTaskRows?: number; keybindings?: GoalDashboardKeybindings; /** Terminal-height bound (see boundWidgetRenderLines); undefined = unbounded. */ terminalRows?: number } = {}): string[] {
|
|
194
227
|
// When auditor progress is active, show the structured audit dashboard
|
|
195
228
|
// instead of the normal goal widget (§15.3).
|
|
196
229
|
if (options.auditorProgress) {
|
|
197
|
-
return renderAuditorWidgetLines(options.auditorProgress, theme, width, {
|
|
230
|
+
return boundWidgetRenderLines(renderAuditorWidgetLines(options.auditorProgress, theme, width, {
|
|
198
231
|
showToolDetails: options.expanded === true || options.debug === true,
|
|
199
|
-
});
|
|
232
|
+
}), options.terminalRows);
|
|
200
233
|
}
|
|
201
234
|
const openGoalCount = options.openGoalCount ?? 0;
|
|
202
235
|
if (!goal) {
|
|
203
236
|
// Unfocused panel when open goals exist; nothing when the pool is empty.
|
|
204
|
-
return openGoalCount > 0 ? renderUnfocusedDashboard(openGoalCount, theme, width) : [];
|
|
237
|
+
return boundWidgetRenderLines(openGoalCount > 0 ? renderUnfocusedDashboard(openGoalCount, theme, width) : [], options.terminalRows);
|
|
205
238
|
}
|
|
206
239
|
const safeWidth = Math.max(1, width);
|
|
207
240
|
const otherCount = Math.max(0, openGoalCount - 1);
|
|
@@ -215,7 +248,7 @@ export function renderGoalWidgetLines(goal: GoalWidgetRecord | null, theme: Them
|
|
|
215
248
|
const lines = options.expanded
|
|
216
249
|
? renderExpandedDashboard(model, theme, safeWidth, { scrollOffset: options.expandedScrollOffset, rows: options.expandedTaskRows, keybindings: options.keybindings })
|
|
217
250
|
: renderCompactDashboard(model, theme, safeWidth, { scrollOffset: options.compactScrollOffset, keybindings: options.keybindings });
|
|
218
|
-
return clampLinesToWidth(lines, width);
|
|
251
|
+
return boundWidgetRenderLines(clampLinesToWidth(lines, width), options.terminalRows);
|
|
219
252
|
}
|
|
220
253
|
|
|
221
254
|
export class GoalWidgetComponent implements Component {
|
|
@@ -318,10 +351,17 @@ export class GoalWidgetComponent implements Component {
|
|
|
318
351
|
render(width: number): string[] {
|
|
319
352
|
const settings = this.getSettings();
|
|
320
353
|
this.lastRenderWidth = Math.max(1, width);
|
|
354
|
+
// Terminal-height bound (spec 2026-08-10): read pi's terminal rows at
|
|
355
|
+
// render time so the widget never fills the terminal — the chat/editor
|
|
356
|
+
// stay visible and scrollable and the widget can never trigger pi-tui's
|
|
357
|
+
// shrink full-render (\x1b[2J\x1b[H\x1b[3J) scrollback wipe. Same cast
|
|
358
|
+
// pattern as the questionnaire churn guard; mock TUIs without a
|
|
359
|
+
// `terminal` render unbounded.
|
|
360
|
+
const terminalRows = (this.tui as unknown as { terminal?: { rows?: number } }).terminal?.rows;
|
|
321
361
|
// §15.4: a finished audit shows its result card until cleared.
|
|
322
362
|
const auditResult = this.getAuditResult();
|
|
323
363
|
if (auditResult) {
|
|
324
|
-
return clampLinesToWidth(renderAuditResultCardView(auditResult, this.theme, width), width);
|
|
364
|
+
return boundWidgetRenderLines(clampLinesToWidth(renderAuditResultCardView(auditResult, this.theme, width), width), terminalRows);
|
|
325
365
|
}
|
|
326
366
|
const goal = this.getGoal();
|
|
327
367
|
const otherCount = Math.max(0, this.getOpenGoalCount() - 1);
|
|
@@ -345,11 +385,12 @@ export class GoalWidgetComponent implements Component {
|
|
|
345
385
|
expandedScrollOffset: this.expandedScrollOffset,
|
|
346
386
|
expandedTaskRows: expandedTaskViewportRows(this.lastRenderWidth),
|
|
347
387
|
keybindings: settings.keybindings?.dashboard,
|
|
388
|
+
terminalRows,
|
|
348
389
|
});
|
|
349
390
|
if (this.getDebugMode()) {
|
|
350
391
|
lines.push(...this.renderDebugPanel(width));
|
|
351
392
|
}
|
|
352
|
-
return clampLinesToWidth(lines, width);
|
|
393
|
+
return boundWidgetRenderLines(clampLinesToWidth(lines, width), terminalRows);
|
|
353
394
|
}
|
|
354
395
|
|
|
355
396
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-x",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.2",
|
|
4
4
|
"description": "Goal mode extension for pi: durable long-running objectives, five model tools, structured tasks, independent completion audit, Sisyphus mode, auto-continue, and a status overlay.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "pi-goal-x contributors",
|