@yinan_chen/pi-claude-code-ui 0.9.1 → 0.9.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.
package/README.md CHANGED
@@ -29,7 +29,7 @@ pi -e npm:@yinan_chen/pi-claude-code-ui
29
29
  - **Claude-style views** — conversation transcript adapter, with Pi native fallback
30
30
  - **Sticky message bubble** — pins the user message currently being answered at the top while scrolling; click to jump back (built in since v0.2.0; formerly the sticky-first-message extension)
31
31
  - **Skill recollapse** — expanded skill invocations automatically fold back to `/skill:name args`, covering four blind spots such as /tree and /fork backfill (built in since v0.2.0; formerly the skill-block-recollapse extension)
32
- - **Full third-party renderer takeover** — cards from any extension (including future unmatched ones) render automatically in a unified `● card` style, covering tools / messages / entries at all three layers, zero config and zero adaptation; dispatch-layer interception is unaffected by load order (v0.7.0)
32
+ - **Generic fallback for foreign cards** — messages no renderer claims (no extension registered the customType) are taken over automatically as a unified `● card`, zero config and zero adaptation; extensions that register their own renderer keep it (dispatch-layer interception, unaffected by load order; v0.7.0)
33
33
  - **Rich diff rendering** — syntax-highlighted diffs for edit/write tools (vendored, MIT)
34
34
  - **Subagent panel** — background agent status at a glance
35
35
  - **Working-state hints** — working message / running effect
@@ -103,7 +103,7 @@ You can also edit `~/.pi/agent/lean-tidy.json` directly (when the file doesn't e
103
103
  | `resultPrefixGap` | `1` | Spaces between the prefix symbol and content, 0–4. |
104
104
  | `normalizeForeignRenderers` | `true` | Rewrite the leading ⎿ of third-party extension output to resultPrefix (unifies the style of any unmatched extension's rendering; turn it off to see the third-party original). |
105
105
  | `workflowRendering` ⟳ | `"claude"` | Workflow completion card style: claude = ● Workflow(name) (default); native = keep the bundled card. Dispatch-layer interception, unaffected by package load order. |
106
- | `foreignCardStyle` | `"generic"` | Cards for unmatched customTypes: generic = take over as a unified `● card` (zero config); symbols = unify symbols only, keep layout. |
106
+ | `foreignCardStyle` | `"generic"` | Cards without their own renderer: generic = take over renderer-less customTypes automatically as a unified `● card` (zero config); symbols = unify symbols only, keep layout. |
107
107
  | `foreignCardSkip` | `[]` | customType blocklist never taken over (skip by type when an interactive third-party card misbehaves). |
108
108
  | `diff.diffViewMode` | `"auto"` | Diff display mode: auto / split / unified. |
109
109
  | `diff.diffIndicatorMode` | `"bars"` | Diff change indicator: bars / classic / none. |
package/README.zh-CN.md CHANGED
@@ -29,7 +29,7 @@ pi -e npm:@yinan_chen/pi-claude-code-ui
29
29
  - **Claude 式视图** — 对话转录适配器,Pi 原生回退
30
30
  - **吸附消息泡(sticky)** — 滚动时顶部吸附显示当前正在回答的用户消息原文,点击回位(v0.2.0 内置,原 sticky-first-message 扩展)
31
31
  - **Skill 折叠(recollapse)** — 展开的 skill 全文自动折回 `/skill:名 参数`,覆盖 /tree、/fork 回填等四处盲区(v0.2.0 内置,原 skill-block-recollapse 扩展)
32
- - **第三方渲染全接管** — 任何扩展的卡片(含未来未适配的)自动渲染为统一的 `● 卡片` 风格,工具/消息/条目三层全覆盖,零配置零适配;分发层拦截不受加载顺序影响(v0.7.0)
32
+ - **第三方卡片兜底接管** — 无自带渲染方的消息(无任何扩展注册该 customType)自动接管为统一的 `● 卡片`,零配置零适配;扩展自己注册的渲染器原样保留,分发层拦截不受加载顺序影响(v0.7.0)
33
33
  - **富 diff 渲染** — edit/write 工具的语法高亮 diff(vendored, MIT)
34
34
  - **子代理面板** — 后台 agent 状态一目了然
35
35
  - **工作状态提示** — working message / running effect
@@ -103,7 +103,7 @@ pi -e npm:@yinan_chen/pi-claude-code-ui
103
103
  | `resultPrefixGap` | `1` | 前缀符号与内容之间的空格数(0–4) |
104
104
  | `normalizeForeignRenderers` | `true` | 第三方扩展卡片输出里行首 `⎿` 自动规范化为 `resultPrefix`(任何未适配扩展的渲染都能统一风格;关闭可看第三方原版) |
105
105
  | `workflowRendering` ⟳ | `"claude"` | workflow 完成卡片样式:`● Workflow(name)` 风格 / `native` 保留包内卡片。分发层拦截实现,不受包加载顺序影响 |
106
- | `foreignCardStyle` | `"generic"` | 未适配扩展的卡片:自动接管为通用 `● 卡片`(零配置);`symbols` = 仅统一符号不接管布局 |
106
+ | `foreignCardStyle` | `"generic"` | 无自带渲染方的卡片:自动接管为通用 `● 卡片`(零配置);`symbols` = 仅统一符号不接管布局 |
107
107
  | `foreignCardSkip` | `[]` | 不接管的 customType 黑名单(交互型第三方卡片异常时按类型跳过) |
108
108
  | `diff.diffViewMode` | `"auto"` | diff 视图:`auto`/`split`/`unified` |
109
109
  | `diff.diffIndicatorMode` | `"bars"` | 变更指示条:`bars`/`classic`/`none` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yinan_chen/pi-claude-code-ui",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Claude Code-style UI for Pi: transcript view, rich diffs, subagent panels and working-message status.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -39,7 +39,13 @@ export function normalizeForeignLines(lines: readonly string[]): string[] {
39
39
 
40
40
  /**
41
41
  * patch 渲染分发器(ExtensionRunner:同时有 getMessageRenderer + getEntryRenderer 原型方法)。
42
- * 分发层拦截:不受包加载顺序影响,__leanTidyImpl.rendererOverride(customType) 命中即改道。
42
+ * 两层接管语义(v0.9.2):
43
+ * 1. 替换层(impl.rendererOverride:skip 名单 > subagent/workflow 内置替换)先行,
44
+ * 无视原查表——替换型的优先级不受包加载顺序影响;
45
+ * 2. 未命中替换层时先走原查表拿「有主」判定:查到已注册渲染器(有主)就让路返回它;
46
+ * 3. 查不到(无主)才问 impl.genericFallback(generic 兜底,仅 message 侧——原生对无
47
+ * renderer 的 entry 完全不显示,entry 侧挖出数据型条目只是噪音),兜底不接则回落原生。
48
+ * trampoline:wrapper 每次调用现读 globalThis.__leanTidyImpl,/reload 后新配置立即生效。
43
49
  */
44
50
  export function patchRendererDispatch(cls: any): boolean {
45
51
  const proto = cls?.prototype;
@@ -47,15 +53,20 @@ export function patchRendererDispatch(cls: any): boolean {
47
53
  proto[FLAG] = true;
48
54
  for (const method of ["getMessageRenderer", "getEntryRenderer"]) {
49
55
  const orig = proto[method];
50
- // kind("getMessageRenderer"/"getEntryRenderer")随转发传给 impl:generic 兜底只挂 message 侧——
51
- // 原生对无 renderer 的 entry 完全不显示(addCustomEntryToChat 直接 return),entry 侧挖出数据型条目只是噪音。
56
+ // kind("getMessageRenderer"/"getEntryRenderer")随转发传给 impl 两层。
52
57
  proto[method] = function (customType: string) {
53
58
  const impl = (globalThis as any).__leanTidyImpl;
54
59
  try {
55
60
  const override = impl?.rendererOverride?.(customType, method);
56
61
  if (override) return override;
57
62
  } catch {}
58
- return orig.call(this, customType);
63
+ const native = orig.call(this, customType);
64
+ if (native) return native;
65
+ try {
66
+ const fallback = impl?.genericFallback?.(customType, method);
67
+ if (fallback) return fallback;
68
+ } catch {}
69
+ return native;
59
70
  };
60
71
  }
61
72
  return true;
@@ -1,7 +1,7 @@
1
1
  /**
2
- * generic-card — 未适配 customType 的通用 Claude 风格卡片(v0.7.0)。
2
+ * generic-card — 无主 customType(无任何渲染方认领)的通用 Claude 风格卡片(v0.7.0)。
3
3
  *
4
- * foreignCardStyle="generic"(默认)时,任何没有内置适配的 custom 消息 /
4
+ * foreignCardStyle="generic"(默认)时,原查表落空的无主 custom 消息 /
5
5
  * 会话条目都自动渲染为:
6
6
  *
7
7
  * ● Label
package/src/index.ts CHANGED
@@ -93,7 +93,7 @@ interface Config {
93
93
  normalizeForeignRenderers: boolean;
94
94
  /** workflow 完成卡片(subagents:workflow entry)样式:claude = ● Workflow(name) 风格(默认);native = 保留包内卡片。 */
95
95
  workflowRendering: "claude" | "native";
96
- /** 未适配 customType 的卡片:generic = 自动接管为通用 ● 卡片(默认);symbols = 仅统一符号不接管布局。 */
96
+ /** 无主 customType(无自带渲染方)的卡片:generic = 自动接管为通用 ● 卡片(默认);symbols = 仅统一符号不接管布局。 */
97
97
  foreignCardStyle: "generic" | "symbols";
98
98
  /** 不接管的 customType 黑名单(保留第三方/原生渲染)。 */
99
99
  foreignCardSkip: string[];
@@ -402,6 +402,8 @@ export default async function leanTidy(pi: ExtensionAPI): Promise<void> {
402
402
  if (DEBUG) {
403
403
  try { writeFileSync(LOG, `=== lean-tidy load #${loadCounter} ${new Date().toISOString()} ===\n`); } catch {}
404
404
  }
405
+ // generic 兜底接管计数(每次加载重置,与日志头对齐):同名去重、聚合计数,仅诊断通道可见。
406
+ const takeoverCounts = new Map<string, number>();
405
407
  let noticeContext: any;
406
408
  let lastWarning: string | undefined;
407
409
  (globalThis as any).__leanTidyClaude?.dispose?.();
@@ -432,13 +434,21 @@ export default async function leanTidy(pi: ExtensionAPI): Promise<void> {
432
434
  claude && chat ? claude.renderChat(container, width) : Container.prototype.render.call(container, width),
433
435
  normalizeForeign: (lines: readonly string[]): string[] =>
434
436
  config.normalizeForeignRenderers ? normalizeForeignLines(lines) : (lines as string[]),
435
- /** 分发层拦截:内置适配表优先,其余 generic 自动接管,黑名单跳过;不受包加载顺序影响。 */
436
- rendererOverride: (customType: string, via?: "getMessageRenderer" | "getEntryRenderer"): any => {
437
+ /** 分发层拦截·替换层:skip 名单 > subagent/workflow 内置替换,先行于原查表;generic 兜底已拆至 genericFallback。 */
438
+ rendererOverride: (customType: string, _via?: "getMessageRenderer" | "getEntryRenderer"): any => {
437
439
  if (config.foreignCardSkip.includes(customType)) return undefined;
438
440
  if (customType === "subagent-notification") return config.subagentRendering === "claude" ? subagentRenderer : undefined;
439
441
  if (customType === "subagents:workflow") return config.workflowRendering === "claude" ? workflowRenderer : undefined;
440
- // generic 兜底仅限 message 分发:entry 侧原生对无 renderer 完全不显示(数据型条目是噪音),回落原生。
441
- return via !== "getEntryRenderer" && config.foreignCardStyle === "generic" ? genericCardRenderer : undefined;
442
+ return undefined;
443
+ },
444
+ /** 分发层拦截·兜底层:仅原查表落空(无主)且 message 侧才接管为 generic 卡片——不偷任何已注册渲染器。 */
445
+ genericFallback: (customType: string, via?: "getMessageRenderer" | "getEntryRenderer"): any => {
446
+ // skip 判断在这里是必要的而非防御:替换层对名单返回 undefined 让路,wrapper 仍会继续问兜底层。
447
+ if (via === "getEntryRenderer" || config.foreignCardStyle !== "generic" || config.foreignCardSkip.includes(customType)) return undefined;
448
+ const count = (takeoverCounts.get(customType) ?? 0) + 1;
449
+ takeoverCounts.set(customType, count);
450
+ diag("generic takeover:", customType, `x${count}`);
451
+ return genericCardRenderer;
442
452
  },
443
453
  } as RuntimeImpl;
444
454
  for (const method of IMPL_METHODS) {
package/src/settings.ts CHANGED
@@ -113,7 +113,7 @@ export const SETTING_DEFS: readonly SettingDef[] = [
113
113
  },
114
114
  {
115
115
  id: "foreignCardStyle", label: "Foreign card style", type: "enum", values: ["generic", "symbols"], defaultValue: "generic", reloadRequired: false,
116
- description: "Cards for unmatched customTypes: generic = take over as a generic card (default); symbols = unify symbols only, keep layout.",
116
+ description: "Cards without their own renderer: generic = take over as a generic card (default); symbols = unify symbols only, keep layout.",
117
117
  },
118
118
  {
119
119
  id: "foreignCardSkip", label: "Card takeover blocklist", type: "readonly-array", defaultValue: [], reloadRequired: false,