pi-cc-extensions 0.8.62 → 0.8.63

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/README.en.md CHANGED
@@ -39,7 +39,7 @@ Run `/reload` after installation.
39
39
  | --------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------- |
40
40
  | Claude Code Output | Tool summaries, expand/collapse, rich edit/write diffs, and`on` / `compact` / `off` modes | `/ccstyle` |
41
41
  | Markdown enhancements | Mermaid art, admonitions, URL linking, and more | Automatic |
42
- | Fullscreen mode | Tool card/group click-to-toggle, previews, hover highlight, and a back-to-bottom button | `TUIMODE=fullscreen` or `--tui-mode fullscreen` |
42
+ | Fullscreen mode | Tool card/group click to expand, double-click to collapse, previews, hover highlight, and a back-to-bottom button | `TUIMODE=fullscreen` or `--tui-mode fullscreen` |
43
43
  | Settings panel | `Style / Diff / Thinking / Feature` tabs: startup header toggle and wheel step | `/ccstyle` |
44
44
  | Context inspection | Usage breakdown and previews for the system prompt, tools, skills, and messages | `/context` |
45
45
  | Session/Subagent references | Search and inject effective context from previous Sessions or existing SubAgents | `@` |
@@ -68,6 +68,8 @@ Run `/reload` after installation.
68
68
  }
69
69
  ```
70
70
 
71
+ > **Fullscreen clicks**: click `click to show more` to expand tool cards, thinking, Skill, and compact summaries. Click a truncated Input/Output header to open the full preview. Double-click an expanded panel to collapse it. Single clicks on an expanded card still select text and open links.
72
+ >
71
73
  > **Tip**: set `markdown.mermaid` to `final` via `~/.pi/agent/settings.json` or the Mermaid diagrams option in `/settings`. Default `streaming` redraws per frame; `final` renders once at completion.
72
74
 
73
75
  ## Local development
package/README.md CHANGED
@@ -39,7 +39,7 @@ pi install git:github.com/minuque/pi-cc-extensions
39
39
  | --------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------- |
40
40
  | Claude Code UI | 工具摘要、折叠展开、rich edit/write diff,以及`on` / `compact` / `off` 三种模式 | `/ccstyle` |
41
41
  | Markdown 增强 | Mermaid 图、提示框、URL 链接化等 | 自动生效 |
42
- | Fullscreen mode | 工具卡/group 点击展开与收起、预览、hover 高亮、回到底部按钮 | `TUIMODE=fullscreen` 或 `--tui-mode fullscreen` |
42
+ | Fullscreen mode | 工具卡/group 单击展开、双击收起、预览、hover 高亮、回到底部按钮 | `TUIMODE=fullscreen` 或 `--tui-mode fullscreen` |
43
43
  | 配置面板 | `Style / Diff / Thinking / Feature` 四页签,含启动头开关与滚轮步进 | `/ccstyle` |
44
44
  | 上下文检查 | 查看上下文占用,并预览 System prompt、Tools、Skills 和消息内容 | `/context` |
45
45
  | Session/Subagent 引用 | 搜索并注入历史 Session 或现有 SubAgent 的有效上下文 | `@` |
@@ -74,6 +74,8 @@ pi install git:github.com/minuque/pi-cc-extensions
74
74
  }
75
75
  ```
76
76
 
77
+ > **Fullscreen 点击**:单击 `click to show more` 展开工具卡、思考、Skill 和 compact 摘要。单击截断的 Input/Output 打开全量预览。双击展开面板收起。展开卡上的单击仍可选择文本、打开链接。
78
+ >
77
79
  > **建议**:`markdown.mermaid` 设为 `final`(`~/.pi/agent/settings.json` 或 `/settings` 面板的 Mermaid diagrams 选项)。默认 `streaming` 逐帧重绘,`final` 渲染最终版更稳定。
78
80
 
79
81
  ## 本地开发
@@ -280,7 +280,7 @@ function hiddenPreviewHint(
280
280
 
281
281
  const expandedThinking = new Set<number>();
282
282
 
283
- /** 折叠预览 + 展开全文。fullscreen 点击 hint 展开、再点整块收起,对齐工具卡。 */
283
+ /** 折叠预览 + 展开全文。fullscreen 点击 hint 展开、双击整块收起,对齐工具卡。 */
284
284
  export class ThinkingPreviewBlock implements Component {
285
285
  private heading: string;
286
286
  private text: string;
@@ -322,70 +322,104 @@ function editWriteExpandedCard(theme: any): any {
322
322
  );
323
323
  }
324
324
 
325
- /** compact 展开卡:外卡片保持 userMessageBg 原色;内部 tool call card(addToolChild)
325
+ /** compact 展开卡:外卡片保持 userMessageBg 原色;内部 tool call card
326
326
  * 背景更深一层且只覆盖内容区(左右内缩、上下限首尾文本行),形成嵌套层次。
327
- * 逐行重建背景同时修复嵌套 Box 的右侧截断。 */
328
- function expandedToolCard(theme: any): any {
327
+ * hits:非工具子卡(thinking)的行区间,供展开后点击 hint。 */
328
+ function layoutExpandedToolCard(
329
+ theme: any,
330
+ children: any[],
331
+ width: number,
332
+ ): { lines: string[]; hits: Array<{ child: any; start: number; end: number }> } {
329
333
  const slot = "userMessageBg";
330
334
  const toolBgAnsi = darkenBgAnsi(theme, slot);
335
+ const innerWidth = Math.max(0, width - 2);
336
+ const lines: string[] = [];
337
+ const hits: Array<{ child: any; start: number; end: number }> = [];
338
+ const isThinkingPreview = (child: any) => typeof child?.setHintHovered === "function";
339
+ lines.push(paddedBackgroundRow(theme, slot, "", width));
340
+ // 展开不再显示摘要行:跳过第一个 child 的首行空白,避免白占一行
341
+ let skipLeadingBlank = true;
342
+ for (const child of children) {
343
+ const childLines = child.render(innerWidth);
344
+ // 展开的 thinking 与 tool 一样走嵌套深色卡,避免贴在外卡同色底上看不见。
345
+ const nest = child.__ccToolCard || (isThinkingPreview(child) && child.expanded === true);
346
+ if (!nest) {
347
+ let start = 0;
348
+ if (skipLeadingBlank) {
349
+ while (start < childLines.length && !hasVisibleText(childLines[start])) start++;
350
+ skipLeadingBlank = false;
351
+ }
352
+ const rangeStart = lines.length;
353
+ for (let i = start; i < childLines.length; i++) {
354
+ lines.push(paddedBackgroundRow(theme, slot, childLines[i], width));
355
+ }
356
+ if (lines.length > rangeStart) hits.push({ child, start: rangeStart, end: lines.length });
357
+ continue;
358
+ }
359
+ // 工具卡:前导 1 空行 + 首尾文本行之间的内容区(深色);不保留尾随,
360
+ // 相邻工具卡之间正好 1 空行,底部由卡片 padding 收尾。
361
+ skipLeadingBlank = false;
362
+ let first = -1;
363
+ let last = -1;
364
+ for (let i = 0; i < childLines.length; i++) {
365
+ if (hasVisibleText(childLines[i])) {
366
+ if (first < 0) first = i;
367
+ last = i;
368
+ }
369
+ }
370
+ if (first < 0) {
371
+ for (const line of childLines) {
372
+ lines.push(paddedBackgroundRow(theme, slot, line, width));
373
+ }
374
+ continue;
375
+ }
376
+ lines.push(paddedBackgroundRow(theme, slot, "", width));
377
+ const rangeStart = lines.length;
378
+ // 子卡片内部上下各 1 行深色 padding
379
+ lines.push(toolCardBgRow(theme, slot, toolBgAnsi, "", width));
380
+ for (let i = first; i <= last; i++) {
381
+ lines.push(toolCardBgRow(theme, slot, toolBgAnsi, childLines[i], width));
382
+ }
383
+ lines.push(toolCardBgRow(theme, slot, toolBgAnsi, "", width));
384
+ if (isThinkingPreview(child)) hits.push({ child, start: rangeStart, end: lines.length });
385
+ }
386
+ lines.push(paddedBackgroundRow(theme, slot, "", width));
387
+ return { lines, hits };
388
+ }
389
+
390
+ function compactRoundCard(
391
+ cardItems: Array<{ child?: any; tool?: any }>,
392
+ toolRender: (tool: any, width: number) => string[],
393
+ ): any {
331
394
  const children: any[] = [];
395
+ for (const item of cardItems) {
396
+ if (item.child) children.push(item.child);
397
+ else if (item.tool) {
398
+ const tool = item.tool;
399
+ children.push({
400
+ __ccToolCard: true,
401
+ render: (innerWidth: number) => toolRender(tool, innerWidth),
402
+ invalidate: () => tool.invalidate?.(),
403
+ });
404
+ }
405
+ }
332
406
  return {
333
407
  children,
334
- addChild(child: any) {
335
- children.push(child);
336
- },
337
- addToolChild(child: any) {
338
- child.__ccToolCard = true;
339
- children.push(child);
340
- },
341
408
  render(width: number): string[] {
342
- const innerWidth = Math.max(0, width - 2);
343
- const lines: string[] = [];
344
- lines.push(paddedBackgroundRow(theme, slot, "", width));
345
- // 展开不再显示摘要行:跳过第一个 child 的首行空白,避免白占一行
346
- let skipLeadingBlank = true;
347
- for (const child of children) {
348
- const childLines = child.render(innerWidth);
349
- if (!child.__ccToolCard) {
350
- let start = 0;
351
- if (skipLeadingBlank) {
352
- while (start < childLines.length && !hasVisibleText(childLines[start])) start++;
353
- skipLeadingBlank = false;
354
- }
355
- for (let i = start; i < childLines.length; i++) {
356
- lines.push(paddedBackgroundRow(theme, slot, childLines[i], width));
357
- }
358
- continue;
359
- }
360
- // 工具卡:前导 1 空行 + 首尾文本行之间的内容区(深色);不保留尾随,
361
- // 相邻工具卡之间正好 1 空行,底部由卡片 padding 收尾。
362
- skipLeadingBlank = false;
363
- let first = -1;
364
- let last = -1;
365
- for (let i = 0; i < childLines.length; i++) {
366
- if (hasVisibleText(childLines[i])) {
367
- if (first < 0) first = i;
368
- last = i;
369
- }
370
- }
371
- if (first < 0) {
372
- for (const line of childLines) {
373
- lines.push(paddedBackgroundRow(theme, slot, line, width));
374
- }
375
- continue;
376
- }
377
- lines.push(paddedBackgroundRow(theme, slot, "", width));
378
- // 子卡片内部上下各 1 行深色 padding
379
- lines.push(toolCardBgRow(theme, slot, toolBgAnsi, "", width));
380
- for (let i = first; i <= last; i++) {
381
- lines.push(toolCardBgRow(theme, slot, toolBgAnsi, childLines[i], width));
382
- }
383
- lines.push(toolCardBgRow(theme, slot, toolBgAnsi, "", width));
409
+ return ["", ...layoutExpandedToolCard(themeOf(), children, width).lines];
410
+ },
411
+ childAtRow(localRow: number, width: number) {
412
+ if (localRow < 1) return null;
413
+ const row = localRow - 1;
414
+ const { hits } = layoutExpandedToolCard(themeOf(), children, width);
415
+ for (const hit of hits) {
416
+ if (row >= hit.start && row < hit.end) return hit.child;
384
417
  }
385
- lines.push(paddedBackgroundRow(theme, slot, "", width));
386
- return lines;
418
+ return null;
419
+ },
420
+ invalidate() {
421
+ for (const child of children) child.invalidate?.();
387
422
  },
388
- invalidate() {},
389
423
  };
390
424
  }
391
425
 
@@ -477,7 +511,7 @@ function compactEditWriteLine(
477
511
  component: any,
478
512
  width: number,
479
513
  writeMetadata?: WriteExecutionMetadataStore,
480
- options: { hint?: boolean } = {},
514
+ options: { hint?: boolean; flushLeft?: boolean } = {},
481
515
  ): string[] {
482
516
  const theme = themeOf();
483
517
  const name = String(component.toolName ?? "tool");
@@ -511,7 +545,8 @@ function compactEditWriteLine(
511
545
  statsStyled = ` ${theme.fg("dim", "(")}${theme.fg("success", `+${stats.added}`)} ${theme.fg("error", `-${stats.removed}`)}${theme.fg("dim", ")")}`;
512
546
  }
513
547
  }
514
- const iconPart = ` ${theme.fg(iconColor, icon)} `;
548
+ // 展开卡 Box(1,1) 已 pad;折叠行自己留 1 格前导空格
549
+ const iconPart = `${options.flushLeft ? "" : " "}${theme.fg(iconColor, icon)} `;
515
550
  const namePart = theme.fg("toolTitle", name);
516
551
  const hintText =
517
552
  options.hint !== false && component.expanded !== true ? ` • ${showMoreHintText()}` : "";
@@ -603,6 +638,7 @@ function compactEditWriteLines(
603
638
  state.ccstyleIoView,
604
639
  component.args,
605
640
  component,
641
+ true, // 外层 Box(1,1) 已 pad
606
642
  );
607
643
  component.resultRendererComponent = detail;
608
644
  }
@@ -614,12 +650,16 @@ function compactEditWriteLines(
614
650
  const box = editWriteExpandedCard(theme);
615
651
  box.addChild({
616
652
  render(innerWidth: number): string[] {
617
- return compactEditWriteLine(component, innerWidth, writeMetadata, { hint: false }).slice(1);
653
+ return compactEditWriteLine(component, innerWidth, writeMetadata, {
654
+ hint: false,
655
+ flushLeft: true,
656
+ }).slice(1);
618
657
  },
619
658
  invalidate() {},
620
659
  });
621
660
  box.addChild(detail);
622
- return box.render(width);
661
+ // 与原生 ToolExecutionComponent 一样:Spacer(1) + Box,否则贴上一条 tool 少 1 行间距
662
+ return ["", ...box.render(width)];
623
663
  }
624
664
 
625
665
  function compactAssistantLineComponent(
@@ -980,26 +1020,11 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
980
1020
  tool.updateDisplay?.();
981
1021
  }
982
1022
  }
983
- round.anchor.contentContainer.addChild({
984
- render(width: number): string[] {
985
- const theme = themeOf();
986
- const box = expandedToolCard(theme);
987
- for (const item of cardItems) {
988
- if (item.child) box.addChild(item.child);
989
- else if (item.tool) {
990
- const tool = item.tool;
991
- box.addToolChild({
992
- render: (innerWidth: number) => patch.toolOriginalRender.call(tool, innerWidth),
993
- invalidate: () => tool.invalidate?.(),
994
- });
995
- }
996
- }
997
- return ["", ...box.render(width)];
998
- },
999
- invalidate() {
1000
- for (const item of cardItems) item.child?.invalidate?.();
1001
- },
1002
- });
1023
+ round.anchor.contentContainer.addChild(
1024
+ compactRoundCard(cardItems, (tool, innerWidth) =>
1025
+ patch.toolOriginalRender.call(tool, innerWidth),
1026
+ ),
1027
+ );
1003
1028
  // 展开卡内工具会显示 error,外层仍挂 abort/length,避免只藏在折叠工具里。
1004
1029
  appendStopStatus(round.anchor, stopStatus);
1005
1030
  return;
@@ -3,6 +3,7 @@ import { ThinkingPreviewBlock } from "../../feature/compact-thinking.ts";
3
3
  import { patchRegistry, TOOL_MOUSE_OWNER_KEY } from "../../utils/patch-keys.ts";
4
4
  import { ToolGroupComponent } from "../tool/grouping.ts";
5
5
  import { isCompactAssistantComponent, setHoveredCompactAssistant } from "../compact-mode.ts";
6
+ import { isMessageDisplayComponent } from "../tool/message-display.ts";
6
7
  import { config } from "../../config/config.ts";
7
8
  import { isLazyProxyTui } from "../../utils/fullscreen-detect.ts";
8
9
  import { setToolTuiFullscreen } from "../tool/show-more-hint.ts";
@@ -153,6 +154,34 @@ function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
153
154
  tui.requestRender?.();
154
155
  }
155
156
 
157
+ const EXPAND_PANEL_DOUBLE_CLICK_MS = 400;
158
+ let lastExpandPanelClick: { card: any; at: number } | null = null;
159
+
160
+ function isExpandPanelDoubleClick(card: any): boolean {
161
+ const now = Date.now();
162
+ const prev = lastExpandPanelClick;
163
+ lastExpandPanelClick = { card, at: now };
164
+ return Boolean(prev && prev.card === card && now - prev.at <= EXPAND_PANEL_DOUBLE_CLICK_MS);
165
+ }
166
+
167
+ function clearExpandPanelDoubleClick(): void {
168
+ lastExpandPanelClick = null;
169
+ }
170
+
171
+ function collapseExpandedCard(tui: any, card: any): boolean {
172
+ if (!isExpandPanelDoubleClick(card)) return false;
173
+ card.setExpanded(false);
174
+ setHoveredToolCallId(null);
175
+ setHoveredToolGroup(null);
176
+ setHoveredThinking(null);
177
+ setHoveredToolIo(null, null);
178
+ setHoveredCompactAssistant(null);
179
+ card.invalidate?.();
180
+ tui.requestRender?.();
181
+ clearExpandPanelDoubleClick();
182
+ return true;
183
+ }
184
+
156
185
  function toggleToolAtMouseClick(tui: any, packet: SgrMousePacket): boolean {
157
186
  const region = interactionRegionAt(packet);
158
187
  if (!region) return false;
@@ -160,16 +189,9 @@ function toggleToolAtMouseClick(tui: any, packet: SgrMousePacket): boolean {
160
189
  if (region.kind === "show-more") return tryOpenToolIoShowMore(region);
161
190
  const component = region.component;
162
191
  if (!component) return false;
163
- if (region.kind === "expanded-card") {
164
- component.setExpanded(false);
165
- setHoveredToolCallId(null);
166
- setHoveredToolGroup(null);
167
- setHoveredToolIo(null, null);
168
- component.invalidate?.();
169
- tui.requestRender?.();
170
- return true;
171
- }
192
+ if (region.kind === "expanded-card") return collapseExpandedCard(tui, component);
172
193
  component.setExpanded(true);
194
+ clearExpandPanelDoubleClick();
173
195
  setHoveredToolCallId(null);
174
196
  setHoveredToolGroup(null);
175
197
  setHoveredToolIo(null, null);
@@ -242,10 +264,10 @@ const FULLSCREEN_VIEWPORT_PATCH = Symbol("ccstyle.fullscreen-viewport-patch");
242
264
  const FULLSCREEN_WHEEL_SCROLL_ORIGINAL = Symbol("ccstyle.fullscreen-wheel-scroll-original");
243
265
 
244
266
  /**
245
- * 官方 fullscreen 工具卡点击:collapsed hint 点击展开
246
- * (有且仅保持一个展开:展开前收起其他工具卡),expanded 整卡二次点击收起,
247
- * 截断头 show-more 打开全量预览;回到底部按钮 scrollToBottom。
248
- * 滚动条列、含 OSC8 链接行、非工具区域放行官方。
267
+ * 官方 fullscreen 工具卡点击:collapsed hint 单击展开
268
+ * (有且仅保持一个展开:展开前收起其他工具卡),expanded 整卡双击收起,
269
+ * 截断头 show-more 单击打开全量预览;回到底部按钮 scrollToBottom。
270
+ * 滚动条列、含 OSC8 链接行、非工具区域、展开卡单击放行官方。
249
271
  */
250
272
  function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
251
273
  const layout = tui.currentLayout;
@@ -276,7 +298,8 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
276
298
  const isGroup = component instanceof ToolGroupComponent;
277
299
  const isAssistant = isCompactAssistantComponent(component);
278
300
  const isThinking = component instanceof ThinkingPreviewBlock;
279
- if (!isTool && !isGroup && !isAssistant && !isThinking) return false;
301
+ const isMessage = isMessageDisplayComponent(component);
302
+ if (!isTool && !isGroup && !isAssistant && !isThinking && !isMessage) return false;
280
303
  if (!component.expanded) {
281
304
  // collapsed 仅按钮文本可展开,不能把同一行正文/留白变成点击区。
282
305
  const hint = collapsedHintHitbox(line);
@@ -286,11 +309,14 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
286
309
  collectFullscreenToolCards(hit.box.component, others);
287
310
  for (const other of others) {
288
311
  if (other !== component && other.expanded) {
312
+ // 展开 round 内 thinking 时不要把外层 compact 卡收起。
313
+ if (isThinking && isCompactAssistantComponent(other)) continue;
289
314
  other.setExpanded(false);
290
315
  other.invalidate?.();
291
316
  }
292
317
  }
293
318
  component.setExpanded(true);
319
+ clearExpandPanelDoubleClick();
294
320
  } else {
295
321
  // 普通工具截断头 show-more:打开全量预览(不收起)。
296
322
  const view = isTool ? component.resultRendererComponent : null;
@@ -312,8 +338,8 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
312
338
  }
313
339
  }
314
340
  }
315
- // 整卡二次点击:内部工具仍归所属 group,保持整体展开/收起语义。
316
- card.setExpanded(false);
341
+ // 双击收起:内部工具仍归所属 group。单击放行官方(选择/链接)。
342
+ return collapseExpandedCard(tui, card);
317
343
  }
318
344
  // 点击后清 hover 高亮。
319
345
  setHoveredToolCallId(null);
@@ -389,6 +415,9 @@ function handleFullscreenToolHover(tui: any, packet: SgrMousePacket): void {
389
415
  } else if (isCompactAssistantComponent(component)) {
390
416
  // compact 摘要行:折叠时仅提示文字高亮,展开卡整体高亮。
391
417
  if (component.expanded || overHint) target = { kind: "assistant", component };
418
+ } else if (isMessageDisplayComponent(component)) {
419
+ // skill/compaction/branch:折叠只点 hint;展开后双击收起。
420
+ if (component.expanded || overHint) target = { kind: "assistant", component };
392
421
  }
393
422
  }
394
423
  }
@@ -787,6 +816,7 @@ export function teardownToolMouseInteraction(
787
816
  setHoveredThinking(null);
788
817
  setHoveredToolIo(null, null);
789
818
  setHoveredCompactAssistant(null);
819
+ clearExpandPanelDoubleClick();
790
820
  try {
791
821
  if (isLazyProxyTui(getToolMouseTui())) releaseFullscreenToolMouseMotion(getToolMouseTui());
792
822
  else getToolMouseTui()?.terminal?.write?.(TOOL_MOUSE_DISABLE);
@@ -2,6 +2,7 @@ import { visibleWidth } from "@earendil-works/pi-tui";
2
2
  import { ThinkingPreviewBlock } from "../../feature/compact-thinking.ts";
3
3
  import { isCompactAssistantComponent } from "../compact-mode.ts";
4
4
  import { ToolGroupComponent } from "../tool/grouping.ts";
5
+ import { isMessageDisplayComponent } from "../tool/message-display.ts";
5
6
  import { isToolExecutionComponent, stripTerminalSequencesPreservingLayout } from "./packets.ts";
6
7
  import { getScrollButtonWidget } from "./scroll.ts";
7
8
 
@@ -77,6 +78,29 @@ export function isScrollbarColumnAt(layout: any, x: number): boolean {
77
78
  return hit;
78
79
  }
79
80
 
81
+ /** compact 展开卡用 childAtRow 映射 thinking;其余按 render 行数下钻。 */
82
+ function nestedChildAtRow(node: any, localRow: number, width: number): any {
83
+ if (typeof node?.childAtRow === "function") {
84
+ return node.childAtRow(localRow, width);
85
+ }
86
+ if (!Array.isArray(node?.children)) return null;
87
+ let offset = 0;
88
+ for (const child of node.children) {
89
+ let count = 0;
90
+ try {
91
+ const rendered = child.render?.(width);
92
+ if (Array.isArray(rendered)) count = rendered.length;
93
+ } catch {
94
+ count = 0;
95
+ }
96
+ if (localRow < offset + count) {
97
+ return nestedChildAtRow(child, localRow - offset, width);
98
+ }
99
+ offset += count;
100
+ }
101
+ return null;
102
+ }
103
+
80
104
  /**
81
105
  * 布局 leaf box 的组件通常是容器(documentContainer/dock 容器等),工具卡与
82
106
  * widget 在其 children 内。按局部行遍历组件树,定位实际命中的子组件。
@@ -95,12 +119,21 @@ export function componentAtLocalRow(
95
119
  return { component, row: localRow };
96
120
  }
97
121
  if (isCompactAssistantComponent(component)) {
98
- // compact 摘要行整体作为可展开卡片(折叠摘要 / 展开内容)。
122
+ // 折叠:整行摘要。展开:先命中内部 thinking hint,其余仍归外层卡片。
123
+ if (component.expanded === true) {
124
+ const inner = nestedChildAtRow(component, localRow, width);
125
+ if (inner instanceof ThinkingPreviewBlock) {
126
+ return { component: inner, row: localRow };
127
+ }
128
+ }
99
129
  return { component, row: localRow };
100
130
  }
101
131
  if (component instanceof ThinkingPreviewBlock) {
102
132
  return { component, row: localRow };
103
133
  }
134
+ if (isMessageDisplayComponent(component)) {
135
+ return { component, row: localRow };
136
+ }
104
137
  if (component === getScrollButtonWidget()) {
105
138
  return { component, row: localRow };
106
139
  }
@@ -139,7 +172,8 @@ export function collectFullscreenToolCards(
139
172
  isToolExecutionComponent(component) ||
140
173
  component instanceof ToolGroupComponent ||
141
174
  isCompactAssistantComponent(component) ||
142
- component instanceof ThinkingPreviewBlock
175
+ component instanceof ThinkingPreviewBlock ||
176
+ isMessageDisplayComponent(component)
143
177
  ) {
144
178
  out.push(component);
145
179
  return;
@@ -134,7 +134,7 @@ export function installMessageDisplayRendering(): () => void {
134
134
  }
135
135
 
136
136
  /** 三个消息组件共享的结构特征;CustomMessageComponent 用 rebuild 无 updateDisplay,不命中。 */
137
- function isMessageDisplayComponent(value: any): boolean {
137
+ export function isMessageDisplayComponent(value: any): boolean {
138
138
  return Boolean(
139
139
  value &&
140
140
  typeof value === "object" &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cc-extensions",
3
- "version": "0.8.62",
3
+ "version": "0.8.63",
4
4
  "license": "MIT",
5
5
  "description": "A Pi productivity suite with Claude Code-style UI, context inspection, and agent/session references",
6
6
  "type": "module",