pi-message-sidebar 1.5.0 → 1.6.0

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.6.0
4
+
5
+ > Readability pass driven by a live populated PTY audit: less cropping, more signal per row.
6
+
7
+ - Collapsed message previews now use up to two wrapped lines instead of one clipped line.
8
+ - Single header row: `Messages 5/5 · #5 01:26` replaces the two-row header.
9
+ - Goal recap shows the objective on its own line(s) again, with a second line when dock space allows.
10
+ - cmux row drops the label and leads with the surface ref (`surface:38 · title`), so operational identity never truncates away.
11
+ - Status dock is priority-ordered (identity, goal status, objective, runtime, hint) and degrades to goal-status-only before dropping the goal.
12
+ - Dock budget raised to five rows at normal heights; tiny-height behavior unchanged.
13
+
14
+
3
15
  ## 1.5.0
4
16
 
5
17
  - Replaced selected/newest pinning with a row-aware contiguous chronological viewport.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-message-sidebar",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Persistent responsive message history sidebar for Pi",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -134,7 +134,7 @@ export class SidebarComponent implements Component {
134
134
  if (signature === this.cachedSignature) return this.cachedLines;
135
135
 
136
136
  const fullHeader = this.renderHeader(safeWidth);
137
- const headerRows = this.messages.length > 0 && targetHeight === 1 ? 0 : targetHeight <= 5 ? 1 : 2;
137
+ const headerRows = this.messages.length > 0 && targetHeight === 1 ? 0 : 1;
138
138
  const header = fullHeader.slice(0, Math.min(headerRows, targetHeight));
139
139
  const available = Math.max(0, targetHeight - header.length);
140
140
  // At tiny heights, keep one chronological message row before allocating dock chrome.
@@ -146,7 +146,7 @@ export class SidebarComponent implements Component {
146
146
  this.options.getThinkingLevel(),
147
147
  this.focused,
148
148
  this.options.getCmuxContext,
149
- Math.min(4, Math.max(0, available - messageReserve)),
149
+ Math.min(5, Math.max(0, available - messageReserve)),
150
150
  );
151
151
  const bodyHeight = Math.max(0, targetHeight - header.length - dock.length);
152
152
  const result = [...header, ...this.renderBody(safeWidth, bodyHeight), ...dock];
@@ -165,14 +165,10 @@ export class SidebarComponent implements Component {
165
165
  private renderHeader(width: number): string[] {
166
166
  const position = this.selectedIndex() + 1;
167
167
  const count = this.messages.length;
168
- const location = count > 0 ? `${position}/${count}` : "0";
169
168
  const selected = this.messages[this.selectedIndex()];
170
- const detail = selected
171
- ? `${this.focused ? "Selected" : "Current"} #${selected.index} ${formatTime(selected.timestamp)}`
172
- : "User prompts";
169
+ const detail = selected ? ` ${FG_FAINT}·${RST} ${FG_DIM}#${selected.index} ${formatTime(selected.timestamp)}${RST}` : "";
173
170
  return [
174
- fillRow(` ${BOLD}${FG_BRIGHT}Messages${RST} ${FG_FAINT}${location}${RST}`, width, BG_HDR),
175
- fillRow(` ${FG_DIM}${detail}${RST}`, width, BG),
171
+ fillRow(` ${BOLD}${FG_BRIGHT}Messages${RST} ${FG_FAINT}${count > 0 ? `${position}/${count}` : "0"}${RST}${detail}`, width, BG_HDR),
176
172
  ];
177
173
  }
178
174
 
@@ -241,10 +237,20 @@ export class SidebarComponent implements Component {
241
237
  const selected = message.id === this.selectedId;
242
238
  const background = selected ? BG_SEL : BG;
243
239
  const arrow = selected && this.focused ? `${FG_ACC}›${RST}` : " ";
244
- if (!this.expandedIds.has(message.id) || maxRows <= 1) {
240
+ if (!this.expandedIds.has(message.id)) {
241
+ // Collapsed preview: up to two wrapped lines so real prompts stay readable.
245
242
  const prefix = ` ${arrow} `;
246
- const text = truncateToWidth(message.text.replace(/\s+/g, " "), Math.max(0, width - visibleWidth(prefix)), "…");
247
- return [fillRow(`${prefix}${selected ? FG_BRIGHT : FG_NORM}${text}${RST}`, width, background)];
243
+ const textWidth = Math.max(1, width - visibleWidth(prefix));
244
+ const wrapped = wrapText(message.text, textWidth);
245
+ const rows = Math.max(1, Math.min(2, maxRows));
246
+ const shown = wrapped.slice(0, rows);
247
+ if (wrapped.length > shown.length && shown.length > 0) {
248
+ const overflow = shown[shown.length - 1]!;
249
+ shown[shown.length - 1] = truncateToWidth(overflow, Math.max(1, textWidth - 1), "") + "…";
250
+ }
251
+ return shown.map((line, position) =>
252
+ fillRow(`${position === 0 ? prefix : " "}${selected ? FG_BRIGHT : FG_NORM}${line}${RST}`, width, background),
253
+ );
248
254
  }
249
255
 
250
256
  const number = `${FG_FAINT}#${message.index}${RST}`;
@@ -99,7 +99,7 @@ function goalStatus(goal: ThreadGoal): { icon: string; color: string; label: str
99
99
  }
100
100
  }
101
101
 
102
- function renderGoal(width: number, goal: ThreadGoal): string[] {
102
+ function renderGoal(width: number, goal: ThreadGoal, objectiveLines: number): string[] {
103
103
  const status = goalStatus(goal);
104
104
  const budget = goal.tokenBudget
105
105
  ? `${formatTokens(goal.usage.tokensUsed)}/${formatTokens(goal.tokenBudget)}`
@@ -108,7 +108,7 @@ function renderGoal(width: number, goal: ThreadGoal): string[] {
108
108
  const metadata = `${status.color}${status.icon} ${status.label}${RST} ${overBudget ? FG_ERR : FG_MID}${budget}${RST} ${FG_FAINT}${formatDuration(goal.usage.activeSeconds)}${RST}`;
109
109
  const objective = wrapText(goal.objective, Math.max(1, width - 4));
110
110
  const lines = [fillRow(` ${metadata}`, width, BG)];
111
- if (objective[0]) lines.push(fillRow(` ${FG_BRIGHT}${objective[0]}${RST}`, width, BG));
111
+ for (const line of objective.slice(0, Math.max(0, objectiveLines))) lines.push(fillRow(` ${FG_BRIGHT}${line}${RST}`, width, BG));
112
112
  return lines;
113
113
  }
114
114
 
@@ -124,15 +124,13 @@ function renderWorkspace(width: number, ctx: ExtensionContext, footerData: Reado
124
124
  }
125
125
 
126
126
  function renderCmux(width: number, cmux: CmuxContext): string {
127
- const prefix = ` ${FG_FAINT}cmux${RST} `;
128
- const available = Math.max(0, width - visibleWidth(prefix));
129
- const surfaceText = cmux.surfaceRef ?? "";
130
- const surface = truncateToWidth(surfaceText, available, "");
127
+ // Operational identity first: the surface ref never truncates away.
128
+ const surface = cmux.surfaceRef ?? "";
131
129
  const separator = surface ? ` ${FG_FAINT}·${RST} ` : "";
132
- const titleBudget = Math.max(0, available - visibleWidth(separator) - visibleWidth(surface));
133
- const titleText = cmux.workspaceTitle ?? cmux.workspaceRef ?? "cmux";
130
+ const titleText = cmux.workspaceTitle ?? cmux.workspaceRef ?? "";
131
+ const titleBudget = Math.max(0, width - 2 - visibleWidth(surface) - visibleWidth(separator));
134
132
  const title = truncateToWidth(titleText, titleBudget, "…");
135
- return fillRow(`${prefix}${FG_BRIGHT}${title}${RST}${separator}${FG_INFO}${surface}${RST}`, width, BG);
133
+ return fillRow(` ${FG_INFO}${surface}${RST}${separator}${FG_BRIGHT}${title}${RST}`, width, BG);
136
134
  }
137
135
 
138
136
  function renderRuntime(width: number, ctx: ExtensionContext, footerData: ReadonlyFooterDataProvider | null, thinkingLevel: string, usage: Usage): string | null {
@@ -165,9 +163,9 @@ export function renderStatusDock(
165
163
  thinkingLevel: string,
166
164
  focused: boolean,
167
165
  getCmuxContext: () => CmuxContext | null,
168
- maxRows = 4,
166
+ maxRows = 5,
169
167
  ): string[] {
170
- const limit = Math.max(0, Math.min(4, Math.floor(maxRows)));
168
+ const limit = Math.max(0, Math.min(5, Math.floor(maxRows)));
171
169
  if (limit === 0) return [];
172
170
  const hint = fillRow(
173
171
  ` ${FG_DIM}${focused ? "↑↓ move Enter open c copy Esc close" : "Ctrl+Shift+H focus"}${RST}`,
@@ -179,18 +177,31 @@ export function renderStatusDock(
179
177
  const usage = computeUsage(ctx);
180
178
  const goal = readSessionGoal(ctx);
181
179
  const cmux = getCmuxContext();
182
- const rows: string[] = [];
183
180
 
184
- // A cmux surface reference is operational identity. Keep it ahead of optional
185
- // title, runtime, and status text so truncation cannot discard it.
186
- if (cmux) rows.push(renderCmux(width, cmux));
187
- if (goal) rows.push(...renderGoal(width, goal));
188
- if (!cmux && !goal) rows.push(renderWorkspace(width, ctx, footerData));
189
- const runtime = renderRuntime(width, ctx, footerData, thinkingLevel, usage);
190
- if (runtime) rows.push(runtime);
191
- if (!goal) {
181
+ // Priority order: identity, goal status, first objective line, runtime, then
182
+ // the second objective line. Truncation drops the least important rows last.
183
+ const rows: string[] = [];
184
+ const push = (line: string | string[] | null): boolean => {
185
+ if (!line) return true;
186
+ const added = Array.isArray(line) ? line : [line];
187
+ if (rows.length + added.length > limit - 1) return false;
188
+ rows.push(...added);
189
+ return true;
190
+ };
191
+ if (cmux) push(renderCmux(width, cmux));
192
+ else if (!goal) push(renderWorkspace(width, ctx, footerData));
193
+ if (goal) {
194
+ if (!push(renderGoal(width, goal, 1)) && rows.length + 1 <= limit - 1) {
195
+ // Degrade to goal status without the objective line before dropping it entirely.
196
+ rows.push(...renderGoal(width, goal, 0));
197
+ }
198
+ const runtime = renderRuntime(width, ctx, footerData, thinkingLevel, usage);
199
+ if (!push(runtime)) return [...rows.slice(0, limit - 1), hint];
200
+ push(renderGoal(width, goal, 2).slice(2));
201
+ } else {
202
+ push(renderRuntime(width, ctx, footerData, thinkingLevel, usage));
192
203
  const status = validExtensionStatuses(footerData)[0];
193
- if (status) rows.push(row(width, "stat", `${FG_MID}${status}${RST}`));
204
+ if (status) push(row(width, "stat", `${FG_MID}${status}${RST}`));
194
205
  }
195
206
 
196
207
  return [...rows.slice(0, limit - 1), hint];