pi-web-ui 0.74.0 → 0.76.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
@@ -10,6 +10,59 @@
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.76.0] — 2026-09-11
14
+
15
+ ### Added
16
+
17
+ - 桌面通知的诊断能力(默认不显示在界面上):`sendTestNotification()` 会立刻发一条系统通知(不受「页面不在眼前」抑制影响,且带 `requireInteraction` 不会自己滑走),并汇报走的是 service worker 还是页面通知、失败原因、**浏览器到底有没有留下这条通知**(`getNotifications()`,区分「系统层面被压住」与「浏览器直接丢了」)与判定依据(焦点 / 可见性 / 是否最小化 / 空闲秒数)。界面在 `web/src/components/NotifyToggle.tsx` 的 `SHOW_NOTIFY_TEST_PANEL` 常量后面,排障时改成 `true`。
18
+
19
+ ### Fixed
20
+
21
+ - **Windows 上窗口最小化后依然收不到任何桌面通知**(v0.75.0 只修了一半):Win11 实测,窗口最小化后 `document.hasFocus()` 仍是 `true`、`visibilityState` 仍是 `"visible"`,连 `blur`/`visibilitychange` 都不发 —— 「只看焦点」和「焦点 **且** 可见」两种条件都在这个场景下把通知全部静默掉。现在改用只有最小化会变的那组信号判定(原生窗口矩形:`screenX/screenY` 跳到屏幕外的「最小化坐标」,`outerWidth/Height` 塌成标题栏;`isCollapsedWindow`,有单测),并且在 Windows 上额外要求「最近 2 分钟内有过页面交互」才背静默 —— 这个平台的焦点/可见性都不可信,宁可多提醒一次也不漏。非 Windows 平台行为不变(其焦点/可见性可信)。
22
+ - 通知发送路径不再因 service worker 抛错而彻底静默:注册存在但还没 active(首次加载 / 刚更新后)时 `showNotification` 会失败,现在会退回页面通知,两者都失败也会把原因带出来(诊断按钮里看得到)。
23
+ - 修掉「只有第一次弹、之后怎么都不弹」:通知带固定 `tag` 时,Windows 把同 tag 的新通知当成**替掉旧条目**,而且是静默的 —— 没有横幅、没有提示音,只要系统通知中心里还躺着一条 pi-web-ui 通知,后续每一条都会被无声替换(页面上看 `showNotification` 明明成功了)。现在干脆不用 tag(也不依赖 `renotify` —— 实测它在 Windows toast 这层不起作用),每条都是全新 toast;代价是通知中心里会累积几条。
24
+
25
+ <!-- auto-i18n:start -->
26
+ ### i18n
27
+
28
+ - 前端新增 key(9):`notifyTest`、`notifyTestBody`、`notifyTestSent`、`notifyTestFailed`、`notifyTestState`、`notifyTestHeld`、`notifyTestDropped`、`notifyTestGateSuppressed`、`notifyTestGateOpen`
29
+ - 前端中文变更(1):`notifyEnableDesc`
30
+ - 前端英文变更(1):`notifyEnableDesc`
31
+ <!-- auto-i18n:end -->
32
+
33
+ ## [0.75.0] — 2026-09-11
34
+
35
+ ### Added
36
+
37
+ - 桌面通知(PWA)的 Windows 适配:
38
+ - 点击通知现在经 service worker 的 `notificationclick` 聚焦/唤回原本的窗口(匹配 URL 优先、其次任一应用窗口,都没有才新开)——之前 Windows/Linux 上点通知等于没反应,只能眼看着横幅消失。
39
+ - 通知不可用时区分原因:地址不是安全上下文(非 localhost 的 http,局域网 IP / 主机名访问的常见「在这台机器起服务、从另一台电脑打开」情形)与「浏览器不支持」分别给提示;不可用时开关置灰,不再假装能打开。
40
+ - Windows 上额外提示系统层开关:「设置 → 系统 → 通知」要允许浏览器(或已安装的应用),并关闭专注助手/勿扰;同时说明关窗即进程结束、之后不再提醒。
41
+ - 提示文字改为换行显示(原来被单行省略号截断)。
42
+
43
+ ### Fixed
44
+
45
+ - 桌面通知在 Windows 最小化后完全不提醒:抑制条件从「有焦点就跳过」改为「有焦点 **且** 页面可见才跳过」(`shouldSuppressNotify`,有单测)。Windows 上最小化窗口可能仍报 `document.hasFocus() === true`,而 Page Visibility 在最小化/被遮挡/后台标签页都会报 `hidden`——原来那套只判焦点的写法正好在本功能存在的场景下把通知全部静默掉。
46
+ - 本地 Playwright E2E 脚本在 Windows 上无法启动:`spawn` 的 cwd/脚本参数用的是 `URL.pathname`(Windows 上得到 `/E:/...`)→ 直接 ENOENT;改为 `fileURLToPath`,清理服务端进程在 win32 改用 `tests/lib/port-utils.mjs` 的 `freePort`(负数 PID 的进程组在 Windows 不存在,旧写法会留下监听进程)。`tests/sound-settings-test.mjs` 补上了通知开关的断言(渲染 / 置灰规则 / 状态与持久化一致 / 刷新后保持)。
47
+ - 设置面板「标记」列表的描述与「?」提示跟随界面语言(#111):之前发的是静态中文 guidance,任何 UI 语言都显示中文(含葡语/日语等已翻译语言)。改用与系统提示词组装同一条语言感知路径 `getGuidance(lang)`,无该字段的标记仍回退静态值。
48
+ - 消息列与输入框宽度不一致、左右边缘对不上:根因是布局里有十几处各自为政的 `max-width: 860px; margin: 0 auto` / `calc(100% - Npx)`(手机端 `.msg` 内边距 14px vs 输入框 10px、窄列下只有消息行加 48px 右 margin、宽屏聊天列另写一套 260px margin),外加 `--msgs-gutter` 探针量错了滚动条(`overflow-y: scroll` 量到叠加层滚动条 0px,而 `.messages` 的 `stable both-edges` 实际每侧占位 10px)→ Windows 上消息列恒比输入框窄 20px。现在「中央列几何」收敛成 `.main` 上的四个 token(`--chat-pad` / `--chat-max` / `--chat-rail` / `--chat-inset`),消息列、输入框、goalbar、`/` 菜单、问卷面板都只用 `--chat-inset`:任何视口宽度、宽屏聊天列开关开关、手机、窄列避开提问导航条时都自动等宽且左右边缘对齐。新增回归 `tests/chat-column-align-test.mjs`(见 `docs/architecture-core.md` 的「中央列几何」)。
49
+ - Windows 触屏笔记本 / 二合一上回车发不出去:触屏判定原来只看 `(pointer: coarse)`,这类机器的主指针(触屏或触控板)常被判为粗指针 → 回车被当成换行,只能手点发送按钮。改为「粗指针 **且** 无 hover **且** 非桌面系统」的判定(`web/src/touch-device.ts` 纯函数,有单测):Windows / ChromeOS / Linux 桌面一律按有物理键盘的桌面处理(Android 的 UA 里也带 Linux,已排除),iPad 靠 `maxTouchPoints > 1` 与真 Mac 区分。
50
+
51
+ ### Changed
52
+
53
+ - 移动端界面收紧:
54
+ - 顶栏所有控件(视图 tab / chip / 左右折叠按钮)统一高度;文件面板折叠按钮移出可横滑的 `.topbar-actions`、固定在右上角——之前窄屏上会被 tab/chip 挤出屏幕外。
55
+ - 底栏手机端改为单行紧凑显示:上下文标签与进度条收起、只留「已用 / 窗口」数字;速率前加一个小转圈(窄屏省略「工作中」文案);工作目录宽度不够时省略号截断。
56
+ - 提示词模板选择器与编辑弹窗改为「头尾固定、中段滚动」:模板多、字段区高时标题与操作按钮不再被滚走。
57
+
58
+ <!-- auto-i18n:start -->
59
+ ### i18n
60
+
61
+ - 前端新增 key(2):`notifyInsecure`、`notifyWindowsHint`
62
+ - 前端中文变更(2):`notifyEnableDesc`、`notifyDenied`
63
+ - 前端英文变更(2):`notifyEnableDesc`、`notifyDenied`
64
+ <!-- auto-i18n:end -->
65
+
13
66
  ## [0.74.0] — 2026-09-10
14
67
 
15
68
  ### Added
@@ -339,7 +392,8 @@
339
392
  - 0.35.1(2026-08-27):编辑重问保留附件(#18)+ 全窗口拖放(#19)。
340
393
  - 0.29.0(2026-08-23):全局搜索弹窗(Ctrl+K)+ 消息列表惰性窗口化。
341
394
 
342
- [Unreleased]: https://github.com/xing-shuyin/pi-web-ui/compare/v0.74.0...main
395
+ [Unreleased]: https://github.com/xing-shuyin/pi-web-ui/compare/v0.75.0...main
396
+ [0.75.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.75.0
343
397
  [0.74.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.74.0
344
398
  [0.73.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.73.0
345
399
  [0.72.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.72.0
@@ -269,7 +269,11 @@ export class MarkerService {
269
269
  if (seen.has(m.name))
270
270
  continue;
271
271
  seen.add(m.name);
272
- out.push({ name: m.name, enabled: this.isMarkerEnabled(m.name), guidance: m.guidance });
272
+ out.push({
273
+ name: m.name,
274
+ enabled: this.isMarkerEnabled(m.name),
275
+ guidance: m.getGuidance?.(this.lang()) ?? m.guidance,
276
+ });
273
277
  }
274
278
  return out;
275
279
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-web-ui",
3
- "version": "0.74.0",
3
+ "version": "0.76.0",
4
4
  "description": "Web chat interface for the pi coding agent, powered by the pi SDK (@earendil-works/pi-coding-agent) — one-command run, Docker/systemd/launchd deployable",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -42,7 +42,8 @@
42
42
  "pi": {
43
43
  "extensions": [
44
44
  "./extensions"
45
- ]
45
+ ],
46
+ "image": "https://raw.githubusercontent.com/xing-shuyin/pi-web-ui/main/assets/chat-prompts.jpeg"
46
47
  },
47
48
  "engines": {
48
49
  "node": ">=22.19.0"
@@ -10,7 +10,7 @@
10
10
  * 这里压暗加重到 0.85(默认 0.78),亮区壁纸下文字仍可读。
11
11
  *
12
12
  * 全透后 hover/浮动反馈不能依赖容器色(已是 0%),本文件尾段把这些实色
13
- * hover 统一改成 25% 半透底(折叠摘要、展开条、折叠键、复制键、滚到底按钮);
13
+ * hover 统一改成 70% 半透底(折叠摘要、展开条、折叠键、复制键、滚到底按钮);
14
14
  * 强调色 hover(accent-soft)与大部分语义按钮(发送/杀进程)不受影响;
15
15
  * 例外是 bash 工具卡的「停止」键(.toolcall-kill):红底红字在全透下是一块脏红,
16
16
  * 改成描边式(透明底 + 半透红描边 + 亮红字)。
@@ -80,7 +80,7 @@
80
80
  display: none;
81
81
  }
82
82
 
83
- /* hover / 浮动静置反馈:0% 容器色下这些实色 hover 必消失,统一给 25% 半透
83
+ /* hover / 浮动静置反馈:0% 容器色下这些实色 hover 必消失,统一给 70% 半透
84
84
  底(仍透图、可辨反馈)。强调色 hover(accent-soft)与语义按钮不受影响;
85
85
  下拉弹窗已在实色菜单内,其 hover 保留 styles.css 默认实色。 */
86
86
  .msg-collapsed:hover,
@@ -89,7 +89,7 @@
89
89
  .chead-copy:hover,
90
90
  .empty-templates-reset:hover,
91
91
  .scroll-bottom {
92
- background: color-mix(in srgb, var(--bg-elev) 25%, transparent);
92
+ background: color-mix(in srgb, var(--bg-elev) 70%, transparent);
93
93
  }
94
94
 
95
95
  /* bash 工具卡的「停止」键:全透下不做实底。容器色为 0% 时,--red-soft 底 +
@@ -1,4 +1,4 @@
1
- import{a as l,j as n}from"./markdown-Cpo0pNcR.js";import{u as P,b as O,T as M,a as W,F as Y,c as z,d as Z,e as H,f as ee,g as ne,h as te,i as se,r as ae}from"./index-BplWuHvM.js";import{D as re,o as ie}from"./xterm-B96xOxS9.js";import"./react-CtudoG1_.js";function le(t){let c=null;return{clean:t.replace(/\r?\n?\[pi-term-exit:(-?\d+)\]\r?\n?/g,(a,x)=>(c=Number(x),`\r
1
+ import{a as l,j as n}from"./markdown-Cpo0pNcR.js";import{u as P,b as O,T as M,a as W,F as Y,c as z,d as Z,e as H,f as ee,g as ne,h as te,i as se,r as ae}from"./index-BBvR_HDI.js";import{D as re,o as ie}from"./xterm-B96xOxS9.js";import"./react-CtudoG1_.js";function le(t){let c=null;return{clean:t.replace(/\r?\n?\[pi-term-exit:(-?\d+)\]\r?\n?/g,(a,x)=>(c=Number(x),`\r
2
2
  `)).replace(/\r?\n?\x1b\[90m\[(?:进程已退出,退出码 |Process exited with code )-?\d+\]\x1b\[0m\r?\n?/g,`\r
3
3
  `),exitCode:c}}function ce({conversationId:t,terminalId:c,command:p,cwd:a,title:x,active:u,running:v,exitCode:j,send:h,register:C}){const T=l.useRef(null),y=l.useRef(null),{locale:o}=P(),N=l.useRef(o);N.current=o;const E=p?JSON.stringify(p):"";l.useEffect(()=>{const m=T.current;if(!m)return;const r=new re({theme:O(),fontFamily:'"SF Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace',fontSize:13,cursorBlink:!0,scrollback:8e3}),b=new ie;r.loadAddon(b),r.open(m),y.current={term:r,fit:b},u&&r.focus();const g=()=>{r.options.theme=O()};window.addEventListener(M,g),r.attachCustomKeyEventHandler(d=>{if(d.type!=="keydown")return!0;const S=d.key?.toLowerCase();if((d.ctrlKey||d.metaKey)&&S==="v")return!1;if(d.ctrlKey&&!d.shiftKey&&!d.altKey&&S==="c"&&r.hasSelection()){const D=r.textarea;return D&&(D.value=r.getSelection(),D.select()),!1}return!0});const _=C(t,c,{write:d=>r.write(le(d).clean),dispose:()=>r.dispose()}),$=()=>{try{b.fit(),h({type:"terminal_resize",terminalId:c,conversationId:t,cols:r.cols,rows:r.rows})}catch{}},B=requestAnimationFrame(()=>{try{b.fit()}catch{}h(p?{type:"run_command",terminalId:c,conversationId:t,command:p,cols:r.cols,rows:r.rows}:{type:"terminal_create",terminalId:c,title:x,locale:N.current,conversationId:t,cwd:a,cols:r.cols,rows:r.rows})}),R=r.onData(d=>{h({type:"terminal_input",terminalId:c,conversationId:t,data:d})});let k=null;return typeof ResizeObserver<"u"&&(k=new ResizeObserver(()=>{m.offsetWidth>0&&m.offsetHeight>0&&$()}),k.observe(m)),()=>{cancelAnimationFrame(B),R.dispose(),window.removeEventListener(M,g),k?.disconnect(),_(),r.dispose(),y.current=null}},[t,c,E,h,C]),l.useEffect(()=>{if(!u)return;const m=requestAnimationFrame(()=>{const r=y.current;if(r){try{r.fit.fit(),h({type:"terminal_resize",terminalId:c,conversationId:t,cols:r.term.cols,rows:r.term.rows})}catch{}r.term.focus()}});return()=>cancelAnimationFrame(m)},[u]);const{t:F}=P(),f=l.useRef(void 0);return l.useEffect(()=>{if(v===f.current||(f.current=v,v!==!1))return;const m=y.current;m&&m.term.write(`\r
4
4
  \x1B[90m${F("exitBanner",{code:j??""})}\x1B[0m\r