pi-web-ui 0.74.0 → 0.75.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,39 @@
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.75.0] — 2026-09-11
14
+
15
+ ### Added
16
+
17
+ - 桌面通知(PWA)的 Windows 适配:
18
+ - 点击通知现在经 service worker 的 `notificationclick` 聚焦/唤回原本的窗口(匹配 URL 优先、其次任一应用窗口,都没有才新开)——之前 Windows/Linux 上点通知等于没反应,只能眼看着横幅消失。
19
+ - 通知不可用时区分原因:地址不是安全上下文(非 localhost 的 http,局域网 IP / 主机名访问的常见「在这台机器起服务、从另一台电脑打开」情形)与「浏览器不支持」分别给提示;不可用时开关置灰,不再假装能打开。
20
+ - Windows 上额外提示系统层开关:「设置 → 系统 → 通知」要允许浏览器(或已安装的应用),并关闭专注助手/勿扰;同时说明关窗即进程结束、之后不再提醒。
21
+ - 提示文字改为换行显示(原来被单行省略号截断)。
22
+
23
+ ### Fixed
24
+
25
+ - 桌面通知在 Windows 最小化后完全不提醒:抑制条件从「有焦点就跳过」改为「有焦点 **且** 页面可见才跳过」(`shouldSuppressNotify`,有单测)。Windows 上最小化窗口可能仍报 `document.hasFocus() === true`,而 Page Visibility 在最小化/被遮挡/后台标签页都会报 `hidden`——原来那套只判焦点的写法正好在本功能存在的场景下把通知全部静默掉。
26
+ - 本地 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` 补上了通知开关的断言(渲染 / 置灰规则 / 状态与持久化一致 / 刷新后保持)。
27
+ - 设置面板「标记」列表的描述与「?」提示跟随界面语言(#111):之前发的是静态中文 guidance,任何 UI 语言都显示中文(含葡语/日语等已翻译语言)。改用与系统提示词组装同一条语言感知路径 `getGuidance(lang)`,无该字段的标记仍回退静态值。
28
+ - 消息列与输入框宽度不一致、左右边缘对不上:根因是布局里有十几处各自为政的 `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` 的「中央列几何」)。
29
+ - Windows 触屏笔记本 / 二合一上回车发不出去:触屏判定原来只看 `(pointer: coarse)`,这类机器的主指针(触屏或触控板)常被判为粗指针 → 回车被当成换行,只能手点发送按钮。改为「粗指针 **且** 无 hover **且** 非桌面系统」的判定(`web/src/touch-device.ts` 纯函数,有单测):Windows / ChromeOS / Linux 桌面一律按有物理键盘的桌面处理(Android 的 UA 里也带 Linux,已排除),iPad 靠 `maxTouchPoints > 1` 与真 Mac 区分。
30
+
31
+ ### Changed
32
+
33
+ - 移动端界面收紧:
34
+ - 顶栏所有控件(视图 tab / chip / 左右折叠按钮)统一高度;文件面板折叠按钮移出可横滑的 `.topbar-actions`、固定在右上角——之前窄屏上会被 tab/chip 挤出屏幕外。
35
+ - 底栏手机端改为单行紧凑显示:上下文标签与进度条收起、只留「已用 / 窗口」数字;速率前加一个小转圈(窄屏省略「工作中」文案);工作目录宽度不够时省略号截断。
36
+ - 提示词模板选择器与编辑弹窗改为「头尾固定、中段滚动」:模板多、字段区高时标题与操作按钮不再被滚走。
37
+
38
+ <!-- auto-i18n:start -->
39
+ ### i18n
40
+
41
+ - 前端新增 key(2):`notifyInsecure`、`notifyWindowsHint`
42
+ - 前端中文变更(2):`notifyEnableDesc`、`notifyDenied`
43
+ - 前端英文变更(2):`notifyEnableDesc`、`notifyDenied`
44
+ <!-- auto-i18n:end -->
45
+
13
46
  ## [0.74.0] — 2026-09-10
14
47
 
15
48
  ### Added
@@ -339,7 +372,8 @@
339
372
  - 0.35.1(2026-08-27):编辑重问保留附件(#18)+ 全窗口拖放(#19)。
340
373
  - 0.29.0(2026-08-23):全局搜索弹窗(Ctrl+K)+ 消息列表惰性窗口化。
341
374
 
342
- [Unreleased]: https://github.com/xing-shuyin/pi-web-ui/compare/v0.74.0...main
375
+ [Unreleased]: https://github.com/xing-shuyin/pi-web-ui/compare/v0.75.0...main
376
+ [0.75.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.75.0
343
377
  [0.74.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.74.0
344
378
  [0.73.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.73.0
345
379
  [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.75.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-B-YjvCxZ.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