better-dsh 0.0.0 → 0.2.2-b
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/LICENSE +24 -0
- package/README.md +294 -4
- package/control-prompt.md +37 -0
- package/cordis.patch.yml +53 -0
- package/docs/00_adr/0001-bridge-tool-layer-not-service-layer.md +14 -0
- package/docs/00_adr/0002-masking-is-presentation-only.md +15 -0
- package/docs/10_plans/A2A-messaging-channel-test-archive.md +256 -0
- package/docs/10_plans/code-mode-vs-rlm-ipython-comparison.md +137 -0
- package/docs/10_plans/dashr-blueprint-review.md +201 -0
- package/docs/10_plans/dashr-blueprint.md +561 -0
- package/docs/10_plans/dashr-compaction-window-and-archive.md +307 -0
- package/docs/10_plans/dashr-profile-layer-feasibility.md +367 -0
- package/docs/10_plans/dashr-sandbox-escalation-semantics-gap.md +171 -0
- package/docs/10_plans/dashr-security-sandbox-analysis.md +187 -0
- package/docs/10_plans/dashr-surface-invariant-and-omp-imports.md +97 -0
- package/docs/10_plans/ipython-kernel-interactive-interface-test-report.md +152 -0
- package/docs/10_plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft.md +146 -0
- package/docs/10_plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft_v3.md +50 -0
- package/docs/10_plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft_v4.md +79 -0
- package/docs/10_plans/kernel-refactoring/Dash-vs-PrimeAgent-systemprompt-toolcatalog-comparison.md +138 -0
- package/docs/10_plans/kernel-refactoring/RLM-system-prompt-injection-gap-report.md +161 -0
- package/docs/10_plans/kernel-refactoring/V0.1.5-development-plan.md +109 -0
- package/docs/10_plans/kernel-refactoring/actinoable-surface-to-llm-in-agent-runtime_dsh.md +50 -0
- package/docs/10_plans/kernel-refactoring/actinoable-surface-to-llm-in-agent-runtime_prime.md +113 -0
- package/docs/10_plans/recallable-compaction.md +147 -0
- package/docs/10_plans/spike-tag-repro.mjs +102 -0
- package/docs/10_plans/upstream-analysis.md +128 -0
- package/docs/50_test-reports/REPL-/345/267/245/345/205/267/350/260/203/347/224/250-/346/210/252/346/226/255/350/257/212/346/226/255.md +110 -0
- package/docs/50_test-reports/kernel-provisioning.md +44 -0
- package/docs/50_test-reports/repl-kernel-provisioning-test-report.md +87 -0
- package/docs/50_test-reports/upstream-dsh-0.1.2-alpha.5-local-test-report.md +81 -0
- package/docs/50_test-reports/upstream-dsh-0.1.2-alpha.5-report.md +93 -0
- package/docs/50_test-reports/v0.1.8-improved-/345/256/236/346/265/213/346/212/245/345/221/212.md +142 -0
- package/docs/50_test-reports/v0.1.8-/345/256/236/346/265/213/346/212/245/345/221/212.md +193 -0
- package/docs/50_test-reports/v0.1.8b-/345/256/236/346/265/213/346/212/245/345/221/212.md +96 -0
- package/docs/50_test-reports/v0.1.8c-/345/256/236/346/265/213/346/212/245/345/221/212.md +127 -0
- package/docs/50_test-reports/v0.1.8d-/345/256/236/346/265/213/346/212/245/345/221/212.md +150 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/README.md +138 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/code-mode-repl-only.observation.md +74 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/dsh-session-session-4a293388-9ae1-474b-87a0-9e17bb556d94.jsonl +3890 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/dsh-session-session-4a293388-9ae1-474b-87a0-9e17bb556d94.w-sample-0435.jsonl +544 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/functions.json +592 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/skills-catalog.snapshot.md +30 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/tools-sdk.output-schemas.json +1236 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/tools-sdk.python.txt +592 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/tools-sdk.typescript.txt +516 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/wire-vs-transcription.diff.md +54 -0
- package/docs/50_test-reports/v0.1.8e-/345/256/236/346/265/213/346/212/245/345/221/212.md +224 -0
- package/docs/50_test-reports/v0.1.9a-/345/256/236/346/265/213/346/212/245/345/221/212.md +168 -0
- package/docs/50_test-reports/v0.2.0b-/345/256/236/346/265/213/346/212/245/345/221/212.md +123 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/Cargo.lock +7 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/Cargo.toml +6 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/src/bin/messy.rs +8 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/src/main.rs +4 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/hashline-probe.md +5 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/Cargo.lock +7 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/Cargo.toml +7 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/build.rs +4 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/src/main.rs +13 -0
- package/docs/50_test-reports/v0.2.1-/345/256/236/346/265/213/346/212/245/345/221/212.md +110 -0
- package/docs/50_test-reports/v0.2.1b-/345/256/236/346/265/213/346/212/245/345/221/212.md +86 -0
- package/docs/50_test-reports/v0.2.1c-/345/256/236/346/265/213/346/212/245/345/221/212.md +66 -0
- package/docs/50_test-reports/v0.2.1d-/345/256/236/346/265/213/346/212/245/345/221/212.md +67 -0
- package/docs/50_test-reports/v0.2.1e-P1-/345/256/236/346/265/213/346/212/245/345/221/212.md +136 -0
- package/docs/50_test-reports/v0.2.1ef-dev-audit-report.md +73 -0
- package/docs/50_test-reports/v0.2.1f-plugin-shipped-ui-patches/345/256/236/346/265/213/346/212/245/345/221/212.md +102 -0
- package/docs/50_test-reports/v0.2.4-ios-focus-zoom-suppression/345/256/236/346/265/213/346/212/245/345/221/212.md +158 -0
- package/docs/60_exploration-and-research/bun-compile-cordis-runtime-bootstrap-research.md +348 -0
- package/docs/60_exploration-and-research/cordis-research.md +350 -0
- package/docs/60_exploration-and-research/dsh-mobile-spa-ios-input-experience-research.md +160 -0
- package/docs/60_exploration-and-research/dsh-web-profile-package-map.md +186 -0
- package/docs/60_exploration-and-research/dsh-web-ui-slot-system-research.md +310 -0
- package/docs/60_exploration-and-research/dsh-webui-strip-boundary-research.md +300 -0
- package/docs/60_exploration-and-research/ios-chat-app-bridge-research.md +324 -0
- package/docs/60_exploration-and-research/web-frontend-composability-research.md +191 -0
- package/docs/distro-blueprint.md +81 -0
- package/docs/dsh-webUI-with-rlm-mode.png +0 -0
- package/docs/repositioning-and-rebranding.md +102 -0
- package/lib/client/index.js +473 -0
- package/lib/index.d.ts +744 -0
- package/lib/index.js +11768 -0
- package/lib/kernel-env-hxaihi9C.js +195 -0
- package/lib/kernel-env.d.ts +80 -0
- package/lib/kernel-env.js +3 -0
- package/lib/py-sdk-BCaOGYz7.d.ts +125 -0
- package/lib/py-sdk-CbgYiX8O.js +691 -0
- package/lib/py-sdk.d.ts +2 -0
- package/lib/py-sdk.js +3 -0
- package/package.json +325 -4
- package/scripts/kernel-provision.mjs +35 -0
- package/index.js +0 -3
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# v0.2.1f 实测报告 — plugin-shipped-ui-patches(web-trust-fence + mobile-layout)
|
|
2
|
+
|
|
3
|
+
> 2026-09-03 · change `2026-09-03-plugin-shipped-ui-patches` · 4999 源码级实例(`dsh-v0.1.2-alpha.5` + better-dsh 0.2.1-f)
|
|
4
|
+
|
|
5
|
+
## 0. 一句话结论
|
|
6
|
+
|
|
7
|
+
两个原"user 私人手改 patch"以插件随发形态落地:**P2 = bundle patch 行覆盖(fence 腿)+ webserver/index-inject boot script(isLoopback 腿);P3 = client 半 mobile 模块(CSS + 三条件手势)**。全部机检绿(423/423 + tsc 0),4999 双腿实证通过;浏览器/真机两级确认留给 user。
|
|
8
|
+
|
|
9
|
+
## 1. 实测范围与结果
|
|
10
|
+
|
|
11
|
+
| # | 项 | 结果 |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| T1 | vitest 全量(403 基线 + web-trust 8 + mobile-gesture 12) | ✅ **423/423** |
|
|
14
|
+
| T2 | tsc --noEmit | ✅ 0 错 |
|
|
15
|
+
| T3 | `!!js` 表达式:真实 js-yaml 方言解析 + loader 语义 evaluate | ✅ env 合并 `["a.example","b.example","192.168.31.130","lan"]`;空 env = 原生等价 |
|
|
16
|
+
| T4 | dump-config 合成 | ✅ connection 行标注 `# == @deepseek-ai/dsh-web-app, patched by @pgmi-builds/better-dsh`(层序 + 出处) |
|
|
17
|
+
| T5 | **fence 腿活体**(daemon 带 `DSH_TRUSTED_HOSTS=probe.example`) | ✅ `Host: probe.example` → **401**(过栅栏、卡认证 = 过关签名);`Host: evil.example` → **403** |
|
|
18
|
+
| T6 | **isLoopback 腿活体**(boot script 注入) | ✅ shell HTML 含 `(function(){try{if(!window.__DSH_TRANSPORT__){var h=location.hostname,a=["probe.example","192.168.31.130"];…` —— 防覆盖守卫 + JSON 嵌入 authorities 全在场 |
|
|
19
|
+
| T7 | **mobile 全局** | ✅ 同页 `__DASHR_MOBILE__={"enabled":true,"breakpoint":1024,"swipeDistancePx":48,"swipeVelocityPxPerMs":0.35,"edgeBandPx":28}` |
|
|
20
|
+
| T8 | **client bundle mobile 模块** | ✅ application batch(3.7MB)含 `data-sidebar-collapsed`×2 / `__DASHR_MOBILE__`×2 / `toggleSidebar`×10 |
|
|
21
|
+
| T9 | boot graph | ✅ `@pgmi-builds/better-dsh` 行在场(沿 v0.2.1e 既验路径) |
|
|
22
|
+
|
|
23
|
+
## 2. 实现面(代码清单)
|
|
24
|
+
|
|
25
|
+
- `dashr/cordis.patch.yml`:+`connection` 行覆盖(整行重述 + `trustedHosts: !!js (env).split(/\s+/).filter(Boolean).concat(ctx.webRuntime.trustedHosts)`;⚠ `!!js` 是 scalar tag,表达式不能以 `[` 开头——flow-seq 会被 yaml 拒)。
|
|
26
|
+
- `dashr/src/web-trust.ts`(新,host 半):`buildBootScript` 纯函数 + `installWebTrust`(`webserver/index-inject` 监听,emit 时新鲜读 config)。一个内联 head script 双效:trusted hostname → `window.__DSH_TRANSPORT__={ownsHost:true}`(无 fetch/openStream,唯一效应 = isLoopback 翻真;不覆盖已有 transport);mobile 阈值 → `window.__DASHR_MOBILE__`。
|
|
27
|
+
- `dashr/src/mobile/gesture.ts`(新,纯):`isRecognizedSwipe`(起点∧距离∧速率 + 横向占优 + touch/pen only)、`isInteractiveOrigin`、`isNarrowViewport`、`resolveMobileConfig`。
|
|
28
|
+
- `dashr/src/mobile/client/index.ts`(新,client 半):`setupMobileLayout`——`<style data-plugin data-plugin-css>` 注入(媒体查询 + `[data-sidebar-collapsed]` + `!important` 覆盖内联 grid template → 侧栏轨 0)+ document 级 pointer 监听 → `ctx.layout.toggleSidebar()`(窄视口=翻 narrowExpanded)。
|
|
29
|
+
- `dashr/src/index.ts`:Config + `trustedPageAuthorities` / `mobile`(schema 默认 enabled);apply 挂 `installWebTrust`。
|
|
30
|
+
- `dashr/src/failover/client/index.ts`:client 入口挂 `setupMobileLayout`;package.json `dsh.client.inject` + `@deepseek-ai/dsh-client-ui-layout`(模块表边)。
|
|
31
|
+
- tsconfig exclude + `src/mobile/client`(沿 failover/client 的 client 半约定)。
|
|
32
|
+
|
|
33
|
+
## 3. 病因与定性回执(P2 复诊闭环)
|
|
34
|
+
|
|
35
|
+
- prod 消息 `…failed: settings are unavailable in this browser` = 浏览器侧 `isLoopback` 门(`ui-settings` memory 持久化终态不可用),非服务端 fence。
|
|
36
|
+
- 上游定性(设计笔记实证):有意 = "非 loopback 关 Host 持久化(偏好进程本地、页面照常)";无人设计 = mirror 把 memory 做成终态不可用 + trustedHosts 未传导到 host facts——当年源码 patch 存在的全部理由。
|
|
37
|
+
- B 机制恢复的正是设计意图形态(remote 页面照常 + Host 持久化)。
|
|
38
|
+
|
|
39
|
+
## 4. 验证环境事实
|
|
40
|
+
|
|
41
|
+
- 4999:systemd-run `dsh-4999-test`,`DSH_TRUSTED_HOSTS=probe.example`;profile patch 覆盖 `dashr-repl` 行 `trustedPageAuthorities: ['probe.example','192.168.31.130']`(+ 重述 python 表达式)。
|
|
42
|
+
- monorepo 副本重建:`pnpm --filter @pgmi-builds/better-dsh exec tsdown` + `pnpm exec tsx scripts/build-client.ts`(34ms)。
|
|
43
|
+
- 旋钮惰性双态:无 env / 无 authorities 时 fence 与 boot script 均与原生一致(T3 + `buildBootScript` 空态 undefined 单测)。
|
|
44
|
+
|
|
45
|
+
## 5. 待 user 确认(browser/真机级)
|
|
46
|
+
|
|
47
|
+
- [ ] 手机经 `http://192.168.31.130:4999`(或 prod 域名):Settings > Models 清单加载、settings 编辑持久化生效(isLoopback 腿端到端)。
|
|
48
|
+
- [ ] 手机窄视口:侧栏隐藏(轨 0)、快速轻扫开/关 overlay、慢速拖选文本不触发(三条件手势端到端)。
|
|
49
|
+
- [ ] 桌面 ≥1024 视口不受影响;平板窄档(920–1023,details 开)表现复核。
|
|
50
|
+
|
|
51
|
+
## 6. 遗留
|
|
52
|
+
|
|
53
|
+
- prod 部署升级(alpha.5+ + 0.2.1-f + `trustedPageAuthorities`/`DSH_TRUSTED_HOSTS` 配置 + retired 手改 patch 清单)——按年龄门精确版本流程另走。
|
|
54
|
+
- `ownsHost` off-label 风险与 connection 行形状漂移:进 upstream-alignment S7 查表(已加两项)。
|
|
55
|
+
|
|
56
|
+
## 7. Round 2(2026-09-03 user 实测反馈闭环)
|
|
57
|
+
|
|
58
|
+
- **阈值**:速率默认 0.35 → **0.2 px/ms**("要求太快"裁定)。
|
|
59
|
+
- **左滑关闭从不触发的根因**:不是速度——首版 origin 条件只认屏幕边缘带,而关闭手势的手指落在展开的侧栏本体上,全被拒。round 2 改为面板状态感知分类器 `classifySwipe`(展开态任意起点左滑即关)。
|
|
60
|
+
- **新增右栏手势**:右缘带左滑 `openDetails`、非左缘右滑 `closeDetails`(面板状态读 AppFrame 语义属性)。
|
|
61
|
+
- **4999 手机通道**:`--host 0.0.0.0` 被启动器故意拒绝("would expose remote code execution"——上游安全立场,与 prod 的 Caddy 反代拓扑呼应);改设 socat 测试反代 `dsh-4999-lan`(0.0.0.0:4990 → 127.0.0.1:4999),手机经 `http://192.168.31.130:4990/?token=…` 直测,fence(port-less 192.168.31.130 ∈ DSH_TRUSTED_HOSTS)与 isLoopback(∈ trustedPageAuthorities)双腿在此路径生效(LAN 直连 401=fence 过 + shell/boot script/RPC 200 已验)。
|
|
62
|
+
- **Models 页复诊**:三条 RPC(llm/listProviders、llm/listConfigurableProviders、settings/describe)loopback 直连全 ok——4999 后端无恙;user 手机所见的 Models 失败实为 prod(v0.2.1f 未部署)。
|
|
63
|
+
- **dsh-better-sidebar**:0.17.1 与 alpha.5 不兼容(import 的 `settingsNamespace` 已被上游 settings 面重设计移除);装 **0.18.0-alpha.0** 通过(boot graph 已含其 client.js);profile pnpm-workspace allowBuilds 补 `node-pty: true` 并 rebuild。
|
|
64
|
+
|
|
65
|
+
## 8. Round 3(2026-09-03 前端机制实证 + sidecarx 对齐)
|
|
66
|
+
|
|
67
|
+
- **机制链真浏览器实证**(CDP 驱动 google-chrome headless,经手机同款拼写 `192.168.31.130:4990`):`window.__DSH_TRANSPORT__ = {"ownsHost":true}` 翻真;Settings > Models 页渲染出目录("Enter your API keys… DeepSeek / Edit / Add a custom provider");截图 `.scratch/models-page-proof.png`。脚本位次(HTML 实测):queue script(100) → … → `__DSH_BOOT__`(3637) → **boot script(23778)** → shell 主入口(25168)——先于一切 application bundle 物化。
|
|
68
|
+
- **sidecarx 对齐**(user 参照 `sidecarx/src/webclient/src`):参照实现**无速率门槛**(`app.js:332` `SWIPE_THRESHOLD=50` + 左缘带 40px + 展开态任意起点左滑关 + `absDY>absDX` 拒绝;overlay-viewer 同款数学)——默认改为 sidecarx 同款:distance 50 / edgeBand 40 / **velocity 0(禁用)**;三条件模式保留为配置旋钮(armed 时生效,慢速拖选 ~0.13 px/ms 为标定参考)。429/429 绿。
|
|
69
|
+
- 前端机制一句话:**变量是前端运行时的(`window.__DSH_TRANSPORT__`,connection 浏览器半 apply 时读取),载体是服务端渲染的页面 HTML**(host 插件经公共事件 `webserver/index-inject` 注入的内联 head script)——这是插件触达 bundle 加载前时序的唯一途径。
|
|
70
|
+
|
|
71
|
+
## Round 4(2026-09-03)— 手势整体重写:z_dsh-alpha `1706b81` 逐条照搬 + 速率门
|
|
72
|
+
|
|
73
|
+
**触发**:user 指出 round-2 报告的 48/28/0.2 三元组无任何源码出处(系发明值),要求以旧 ui-layout 补丁(退役目录 `z_dsh-alpha`,commit `1706b81` "ui-layout: mobile swipe opens Better Sidebar right panel via its DOM toggle")为 **exact** 基线,唯一增量 = 速率门控。
|
|
74
|
+
|
|
75
|
+
**根因链(本轮三个症状全部落地)**:
|
|
76
|
+
1. "必须贴着左边缘" — round-2/3 缘带 28/40px;源码是 **X120**(`e.clientX > 120` 才拒绝),右侧起点区为**右 3/4 屏**(`x < viewport/4` 才惰性)。
|
|
77
|
+
2. "还是要划很快" — 部署物里速率确实是 0(boot script 实证);真因是 round-3 在 **pointerup** 才判定,慢划先被浏览器滚动启发式 pointercancel 吃掉。源码在 **pointermove 中途**(|dx|≥40 ∧ |dx|>|dy|×1.3 达成即触发,一次性)。
|
|
78
|
+
3. "右侧栏怎么划都出不来" — round-3 调的是上游 details 面板(`ctx.layout.openDetails()`);用户的右栏是 **Better Sidebar 浮层**,源码经 `[data-dsh-toggle-cluster]` 末按钮 DOM click 切换。上游 details 在窄视口被强制关闭,永远不可见。
|
|
79
|
+
|
|
80
|
+
**落地**(commit `0676811`):`gesture.ts`/`mobile/client/index.ts` 全重写为源码语义;断点 <768(SIDEBAR_MOBILE);唯一增量速率门默认 0.15 px/ms(慢速文本拖拽 ~0.13 被滤,舒适滑扫 ≥0.4 通过;0=关闭)。`test.pc.randomhash.app` 加入 `trustedPageAuthorities`(Caddy 反代该域 → 127.0.0.1:4999,Host 被改写为 loopback 过 fence,但页面 origin 是该域名——此前不在信任列表,isLoopback=false → Models 挂)。
|
|
81
|
+
|
|
82
|
+
**实证**(CDP 真浏览器,用户真实 URL `https://test.pc.randomhash.app`):
|
|
83
|
+
- 手机模拟 390×844 触摸序列:开左栏 ✓ / 关左栏 ✓ / 开右栏(Better Sidebar)✓ / 关右栏 ✓;`__DSH_TRANSPORT={ownsHost:true}`、`__DASHR_MOBILE` 七键全对。
|
|
84
|
+
- 桌面视口:Settings → Models 渲染 provider 列表(DeepSeek / Edit / Add provider)✓。
|
|
85
|
+
- 服务端:boot script 含新阈值与 `test.pc.randomhash.app`;client bundle rev `e9e06225516c` 含全部新标记。
|
|
86
|
+
- 单测 425/425,tsc 0。远程(Hermes/OMP a2a)独立浏览器验证已派出。
|
|
87
|
+
|
|
88
|
+
**遗留**:真机(手机/MacBook)复测;远程 agent 回报收取。
|
|
89
|
+
|
|
90
|
+
## Round 4 收口(2026-09-03)— 真机确认
|
|
91
|
+
|
|
92
|
+
user 于 round-4 部署后在手机/MacBook 经 `https://test.pc.randomhash.app` 复测,回复 **"UI ok"**——Models 页 + 移动手势(X120 左缘带 / 右 ¾ 起点区 / move 触发 / Better Sidebar 右栏 DOM toggle / 速率门 0.15)真机验收通过。round-4 遗留仅剩远程 agent(Hermes/OMP)独立回报收取(a2a 已派出)。
|
|
93
|
+
|
|
94
|
+
## 附:hashline edit 升级透传 bug(2026-09-03,commit `e27fdec`)
|
|
95
|
+
|
|
96
|
+
**症状**(user 报告):4999 会话内 agent 带齐 `sandbox_permissions:"danger-full-access"` + `justification` 重试 edit,审批卡永不弹,循环收 `[sandbox: file access denied under workspace-write mode]` + escalation-available 提示。
|
|
97
|
+
|
|
98
|
+
**证据**(session `8e966430`,zstd session log):8 次升级 edit 全部 plain 拒绝,全程 `approval/asked` 事件数 = 0;同 session 的 `write` 升级正常弹卡并被 `allowed-once`。上游 `approveEscalation` 是 fail-closed 设计——升级参数若真触达,要么弹卡要么抛专用错误文本,绝不会返回 plain 拒绝+提示。
|
|
99
|
+
|
|
100
|
+
**根因**:better-dsh vendored hashline `tool-edit.js` 在 `sandbox.resolvePolicy("edit", {path, edits}, exec)` 调用点手工重构对象,丢弃了 `sandbox_permissions`/`justification`(讽刺的是 `canonical` 里就带着——`contract.normalizeRequest` 显式回填)。`resolvePolicy` 见不到字段 → 永远返回 standing policy → 围栏拒绝。`batch_edit`/`undo_last_edit` 传 `canonical`,无此问题;`write`/`bash` 走上游 tool-fs/tool-bash 传全参,无此问题。
|
|
101
|
+
|
|
102
|
+
**修复**:调用点透传两字段;回归测试 `test/url-schema/escalation-forward.spec.ts`(stub sandbox 捕获 resolvePolicy 入参 + 预中止 signal 隔离 fs 半边)2 例;427/427 + tsc 0;已部署 4999。**注意 prod(3080,npm 0.2.1-d)同样带此 bug**,随下个发布版本收敛。
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# v0.2.4 iOS Focus 放大抑制(zoomGuard)实测报告
|
|
2
|
+
|
|
3
|
+
- **Change**: `openspec/changes/2026-09-03-ios-focus-zoom-suppression`
|
|
4
|
+
- **版本**: better-dsh 0.2.2-a → **0.2.4**(canonical `dashr/package.json`;npm 已发布线 v0.2.3 顺延一档)
|
|
5
|
+
- **日期**: 2026-09-03
|
|
6
|
+
- **范围**: 纯 host 半(boot script 增量 + config 面 + payload 扩键);client 半零改动,无需 build-client
|
|
7
|
+
- **状态**: 单测/构建层自测完成;4999 实例验证(2.3)与真机观察(3.x)待做
|
|
8
|
+
- **状态**: 4999 实例验证(2.3)✅ 完成(含一轮 blocker 返修 commit `59870fc`);真机观察(3.x)待 user
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 一、实测范围与结果表
|
|
12
|
+
|
|
13
|
+
| # | 项目 | 层面 | 结果 | 证据 |
|
|
14
|
+
|---|------|------|------|------|
|
|
15
|
+
| 1 | vitest 全量(canonical) | 单测 | ✅ **460/460**(基线 428 + 新增 32;36 files) | `cd dashr && npm test` |
|
|
16
|
+
| 2 | tsc --noEmit(含新 spec) | 类型 | ✅ 0 错 | `npx tsc --noEmit` |
|
|
17
|
+
| 3 | schema:默认值 / off 透传 / font·bogus fail-loud | 单测 | ✅ | `test/zoom-guard.spec.ts` 'config schema' |
|
|
18
|
+
| 4 | 纯函数矩阵(UA × 视口 × config;幂等;还原;bare token;大小写) | 单测 | ✅ 32 tests | 同上 |
|
|
19
|
+
| 5 | 生成脚本形状(ES5、无 `</script`、断点派生 `(max-width:767.98px)`、payload 序列化) | 单测 | ✅ | 同上 'generated zoomGuard section' |
|
|
20
|
+
| 6 | **boot script 评估端到端**(stub DOM 执行真实脚本文本) | 单测 | ✅ 全矩阵 | 同上 'boot script evaluation'(12 场景) |
|
|
21
|
+
| 7 | 构建产物复核(lib/index.js 抽取嵌入函数源重组段 eval) | 构建 | ✅ ES5-ok + provisional/reconcile/还原/风暴全绿 | `.scratch/zg-built-check.ts` |
|
|
22
|
+
| 8 | monorepo 副本 rsync + tsdown(host 半) | 构建 | ✅ 9 files 528.31 kB | `pnpm --filter better-dsh exec tsdown` |
|
|
23
|
+
| 9 | dump-config 覆盖层渲染 `mobile.zoomGuard` | 集成 | ✅ `'off'` 逐字渲染于 dashr-repl 行 | `--patch .scratch/zg-overlay.yml` |
|
|
24
|
+
| 10 | 4999 实例(boot script 注入形状 + CDP 视口矩阵 + off 逃生门 + 桌面 user 复验) | 集成 | ✅ **9/9 矩阵 + 4 冒烟 + off PASS + 桌面 PASS**(经一轮 blocker 返修,见八) | §八;脚本 `.scratch/zg-verify/` |
|
|
25
|
+
| 11 | 真机 iOS PWA 观察(放大抑制/键盘自愈/回弹) | 真机 | ⏳ **待做**(user) | tasks 3.1–3.3 |
|
|
26
|
+
|
|
27
|
+
## 二、实现要点(含 design.md 偏差说明)
|
|
28
|
+
|
|
29
|
+
1. **纯函数 = 发布源**:`src/mobile/zoom-guard.ts` 的 `isIOSClassUA` / `mergeViewportTokens` / `shouldApplyZoomGuard` 以 ES5 自包含风格编写,boot script 经 `Function.prototype.toString()` 嵌入这三个函数 —— 单测覆盖的源码与页面执行的源码字节一致(构建后产物已复核该嵌入幸存于 tsdown bundle)。
|
|
30
|
+
2. **schema enum 形式**:本 fork schemastery 无 `choice()`;采用 `z.union(['meta','off']).default('meta')`(字面量 union 即 enum,非法值 config load 即抛)。**'font' 预留值位不进 enum**(决策:未实现值若被 schema 接受则运行期静默 no-op,fail-loud 优于静默;预留以注释标注)。
|
|
31
|
+
3. **⚠ 与 design D4 的重要发现级偏差(已按 D4 精神实现)**:webserver 的 head 注入 splice 点在 `<head>` 开标签**紧后**(`packages/host/webserver/src/injections.ts` `renderIndexInjections`),即 boot script 执行时上游 stock viewport meta(`apps/web/index.html:5`)**尚未解析**——"meta 缺失 → 创建"是常态路径而非边角。若照 D4 字面创建后不管,页面将永久双 meta,依赖引擎未定义的 multi-meta 合并语义,且 spec 的"还原 stock content"场景失义。实现为 **provisional meta + MutationObserver reconcile**:脚本执行时即建 provisional meta(抑制令牌先于一切 bundle 生效,时序要求满足)→ parser 插入 stock meta 时记录真 stock、token merge 改写 **stock meta 本体**、移除 provisional → 单 meta 文档;此后 resize 进出断口均以记录的 stock 为基线改写/还原(字节级还原)。无 MutationObserver 的引擎降级为 provisional 常驻(仍具抑制)。两路径(stock 先在场 = 直接改写;stock 后到 = reconcile)均有单测钉住。
|
|
32
|
+
4. **payload 面**:`zoomGuard` 与 breakpoint 等键同构(配置了才进 payload,schema 默认保证生产必带 'meta');`zoomGuard:'off'` 时整段不生成(逃生门 = 零脚本段,非运行期判断)。
|
|
33
|
+
5. **配置生命周期**:zoomGuard 随 mobile 腿(`mobile.enabled=false` 时整个 `__DASHR_MOBILE__` 不注入,zoomGuard 一并不生效)——design D5 "同路径同生命周期" 的字面执行。
|
|
34
|
+
|
|
35
|
+
## 三、环境事实
|
|
36
|
+
|
|
37
|
+
- canonical:`/home/u1/workspaces/dashr/dashr`(node_modules 在场,vitest/tsc 直接跑)
|
|
38
|
+
- monorepo 副本:`upstream/deepseek-harness/packages/better-dsh/better-dsh`(rsync + tsdown host 半)
|
|
39
|
+
- Node v22.22.1 / vitest 4.x / tsdown 0.15.x
|
|
40
|
+
- dump-config:`DSH_HOME=.dsh-test npm run dsh -- web --dump-config --patch .scratch/zg-overlay.yml`(dump-config 只渲染 patch 层,schema 默认由单测钉住)
|
|
41
|
+
|
|
42
|
+
## 四、boot script 评估测试明细(单测第 6 项)
|
|
43
|
+
|
|
44
|
+
stub DOM(El/Document/MutationObserver/live matchMedia/resize 记录),`new Function` 执行 `buildBootScript` 真实输出:
|
|
45
|
+
|
|
46
|
+
| 场景 | 断言 |
|
|
47
|
+
|------|------|
|
|
48
|
+
| iPhone ∧ 窄 | 脚本时 provisional meta(`maximum-scale=1, user-scalable=no`);stock 解析后单 meta、content = `width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no` |
|
|
49
|
+
| 非 viewport 解析插入 | observer 不误触发,继续守望 |
|
|
50
|
+
| 宽↔窄往返 + 5 连 resize 风暴 | 离带 = 字节级还原 stock;回带 = 合并;风暴零叠加 |
|
|
51
|
+
| stock 先在场(worker 形态) | 直接改写,无 provisional |
|
|
52
|
+
| 无 stock 页面 | provisional 常驻;离带移除自建 meta |
|
|
53
|
+
| iPad / iPadOS 桌面 UA+多点 | 改写生效;Mac 桌面(单/零触点)不生效 |
|
|
54
|
+
| Android ∧ 窄 / 桌面任意宽 | stock 原样、零监听器 |
|
|
55
|
+
| iPhone 宽载入后旋转入带 | 后补改写生效 |
|
|
56
|
+
| zoomGuard off | 无脚本段、零改写、零监听 |
|
|
57
|
+
| authorities + mobile 双腿 | `__DSH_TRANSPORT__` 与 zoomGuard 互不干扰 |
|
|
58
|
+
| 断点 900 | matchMedia 收到 `(max-width:899.98px)`,payload 带 `"breakpoint":900` |
|
|
59
|
+
|
|
60
|
+
## 五、真机观察(user,PWA standalone 主用法)—— TODO
|
|
61
|
+
|
|
62
|
+
> 以下三章按 design D6 留给 user 真机执行(4999 或真实入口,iOS 版本号请记录)。
|
|
63
|
+
|
|
64
|
+
### 5.1 放大抑制判定(tasks 3.1)— TODO
|
|
65
|
+
|
|
66
|
+
- [ ] Safari 态:focus composer / permission select / settings 各 input / 切会话自动聚焦 —— 无 115–120% 放大
|
|
67
|
+
- [ ] PWA standalone 态:同上全过
|
|
68
|
+
- [ ] 双指缩放仍可用(iOS 10+ 应忽略 user-scalable=no 对 pinch 的约束)
|
|
69
|
+
- [ ] `zoomGuard:'off'` 时放大复现(逃生门复验)
|
|
70
|
+
|
|
71
|
+
### 5.2 键盘遮蔽自愈判定(tasks 3.2)— TODO
|
|
72
|
+
|
|
73
|
+
- [ ] PWA 态 unzoomed 下 focus composer:键盘弹出后 composer 是否被引擎 resize 抬到键盘上方(innerHeight/100dvh 收缩联动)
|
|
74
|
+
- [ ] 键盘关闭后 viewport 回弹(对照 dev.to 2026-07 记录的 iOS 17/18 standalone 卡死 bug)
|
|
75
|
+
- [ ] Safari 态对照记录
|
|
76
|
+
|
|
77
|
+
### 5.3 观察结论回填(tasks 3.3)— TODO
|
|
78
|
+
|
|
79
|
+
- [ ] 研究文档 v2 §6 决策点 3/4 回填
|
|
80
|
+
- [ ] follow-up 取舍:visualViewport shim / display-flip 自愈 / focus gate 讨论输入
|
|
81
|
+
|
|
82
|
+
## 六、发现的瑕疵与去向
|
|
83
|
+
|
|
84
|
+
- **发现(非瑕疵)**:head 注入 splice 位于 stock meta 之前 → 促成 provisional+reconcile 设计(见二.3);已进 upstream-alignment S7 查表第 8 条(viewport 行形状 + splice 次序两查点)。
|
|
85
|
+
- dump-config 只渲染 patch 层、不显 schema 默认 —— zoomGuard 默认值的可观测面在 payload(4999 shell 页)与单测,不在 dump-config;如需 dump 可见需显式配置(本报告第 9 项即此形态)。
|
|
86
|
+
|
|
87
|
+
## 七、Open items
|
|
88
|
+
- ~~tasks 2.3(4999 实例验证)~~ ✅ 完成(见八;返修 `59870fc` 后 9/9)
|
|
89
|
+
- tasks 3.x 真机观察 —— user
|
|
90
|
+
- 发布/npm publish 与 prod 部署 —— 另按年龄门流程,不在本 change 内
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 八、4999 实测记录与 blocker 返修轮(lead,2026-09-03 晚)
|
|
95
|
+
|
|
96
|
+
### 8.1 首轮矩阵与 blocker 发现
|
|
97
|
+
|
|
98
|
+
- 冒烟四断言全过:A)shell head 内联脚本含 zoomGuard 段;B)boot graph 含 `"id":"better-dsh"` 与 client URL;C)zoomGuard 段(HTML 偏移 25313)早于应用入口 module script(27056)——注:client-modules 的 combo 注册脚本(1286)先于我们执行,但纯工厂注册无 DOM/focus,行为层时序满足 S2;client.js 单包 URL 200 且与 `lib/client/index.js` 前缀字节一致(+84B sourceMappingURL,S7 惯例)。
|
|
99
|
+
- **CDP 视口矩阵首轮 7/9**:两个失败项均为**初始窄载入不 guard**(iOS+390、iPadOS 冒充+390);跨断口 resize 与事后 synthetic resize 均生效。
|
|
100
|
+
|
|
101
|
+
### 8.2 根因定位(诊断脚本 `diag.mjs` / `isolate.mjs`)
|
|
102
|
+
|
|
103
|
+
- 事件时序实测:boot script 执行于 head 解析期,此时布局视口为 viewport-meta 未应用前的默认宽 **980**(`docStart innerWidth=980, mq.matches=false`)→ 首评不 guard;引擎随后应用 `width=device-width` 收窄到 390,但**该转换在载入期间不向页面派发 `resize` 事件**(探针隔离三变体一致;我们的 resize 监听在位且事后手动 dispatch 立即生效)。
|
|
104
|
+
- 关键后果:deferred module script(React 挂载 → unlock effect 自动聚焦)在 DOMContentLoaded 前执行——依赖 resize 事件的补评可能晚于首次 auto-focus。**初始窄载入 = iPhone 用户打开 DSH 的主路径,blocker 级**。
|
|
105
|
+
|
|
106
|
+
### 8.3 返修(doer 第二轮,commit `59870fc`,tag `v0.2.4` 前移)
|
|
107
|
+
|
|
108
|
+
- **早期载入重评梯子**:首评未 applied 时启动 ~10ms poll 调 `re()`,三停条件 = applied / readyState complete / 200 tick(~2s);`apply(true)` 主动取消待发 tick,稳态零 timer。guard 不再依赖任何事件通知——事件缺失只影响"多快"发现,不影响"是否"发现;首次布局必然先于 deferred scripts,抑制仍落在任何输入可聚焦之前。
|
|
109
|
+
- **MQ change 监听兜底**(`mq.addEventListener('change', re)`,老引擎 `addListener` 形态)与 window resize 双通道。
|
|
110
|
+
- 测试 466/466(+6:晚翻无 resize、readyState 停止、非 iOS 零 setTimeout、MQ 通道、legacy addListener、梯子先于 stock 解析落 provisional);tsc 0。
|
|
111
|
+
|
|
112
|
+
### 8.4 返修后终验(全绿)
|
|
113
|
+
|
|
114
|
+
- **CDP 矩阵 9/9**:iOS+390 → guarded(单 meta、两 token 在位);iOS+1280 → stock;iPadOS 桌面冒充+touch+390 → guarded;Android+390 / 桌面+390 / 桌面+1280 → stock(零改写);跨断口三段(宽载入 stock → 入带 guard → 出带字节级还原 stock)。
|
|
115
|
+
- **off 逃生门 live PASS**:home 层 `.dsh-test/cordis.patch.yml` 覆盖(注:CLI `web` 子命令不认 `--patch`,该路仅 dump-config 可用)→ shell **整段不发射**、payload `zoomGuard:'off'`、iOS+390 live = stock。验后已删覆盖文件并还原默认态。
|
|
116
|
+
- **默认态恢复复验 + 桌面 user 复验 PASS**:还原后 iOS+390 → guarded;桌面(无 UA 伪装)meta stock 单条、app 完整渲染(root/composer/`[data-composer-card]`/sidebar/slots 在位,标题与占位文案正常)。
|
|
117
|
+
|
|
118
|
+
### 8.5 遗留观察(非阻塞)
|
|
119
|
+
|
|
120
|
+
- 真机 iOS Safari 上 8.2 竞态是否同样存在未证(无法本地复现 iOS 引擎);梯子设计使其无关化——真机观察(五)会给出实证。
|
|
121
|
+
- 探针实验中一次"全探针"运行曾捕捉到 resize 送达(时序非确定论),进一步佐证不能依赖事件通道。
|
|
122
|
+
- CDP 测试脚本存 `.scratch/zg-verify/`(matrix/diag/isolate/off/final 五件),可复跑。
|
|
123
|
+
|
|
124
|
+
## 九、v0.2.5 standalone 分流(change `2026-09-03-zoomguard-standalone-font-floor`)—— 骨架,4999/真机结论待回填
|
|
125
|
+
|
|
126
|
+
> 背景:真机首批观察(§五)——浏览器态 meta 改写完美(放大抑制 + pinch 保留),但 **Add to Home Screen PWA standalone 态引擎尊重 `user-scalable=no` → pinch 失效**(Discourse 注释未覆盖的形态;spec v1 的 "pinch remains engine-controlled" 括号在 standalone 不成立)。user 裁决:standalone 态完全不碰 viewport meta,改注入 16px 字号地板 CSS;browser 态维持 v0.2.4。user 主用法即 PWA。
|
|
127
|
+
|
|
128
|
+
### 9.1 实现摘要(doer,2026-09-03)
|
|
129
|
+
|
|
130
|
+
- 纯函数(`src/mobile/zoom-guard.ts`,ES5 自包含,toString 嵌入,`var ZD=`/`var ZF=` 与 ZI/ZM/ZS 同款):`isStandaloneDisplay(mqMatchesStandalone, navStandalone)` = 严格 `=== true` 双源 OR(MQ 主源 + `navigator.standalone` 老 iOS 兜底);`buildFontFloorCss(bp)` → `@media (max-width:{bp-0.02}px){ input,textarea,select,[contenteditable="true"]{ font-size:16px !important } }`(断口派生与 meta 腿一致:768→767.98、900→899.98)。
|
|
131
|
+
- boot script 分流(D1):iOS 判定后、任何 meta 逻辑前 —— `var sa=ZD((window.matchMedia&&window.matchMedia('(display-mode: standalone)').matches),(navigator.standalone===true));` standalone → `headEl()` 注入 `<style id="ios-zoom-font-floor" data-plugin="better-dsh" data-plugin-css="better-dsh/zoom-font-floor">`(textContent=ZF(rawBp))后 **return**:其后 provisional/reconcile/监听/梯子代码字面不可达,meta 字节不动。**style 注入不设宽度门**(宽度由 CSS media query 表达,宽屏 standalone 也注入、规则自然不匹配)。
|
|
132
|
+
- `'off'`:整段不发射(web-trust 门不变、零改动);`'meta'` 语义升级为自动双形态(enum 不动)。web-trust.ts / client 半零改动。
|
|
133
|
+
|
|
134
|
+
### 9.2 单测(canonical,doer)
|
|
135
|
+
|
|
136
|
+
- **478/478 + tsc 0**(v0.2.4 基线 466,+12):双源判定矩阵(含 undefined 单源兜底)、CSS 文本钉扎(selector 集合 / `!important` / 768→767.98 与 900→899.98)、standalone 矩阵(MQ 源:style 在场 + 三认领属性 + meta 字节不动 + 零 addEventListener/setTimeout/MutationObserver 痕迹 + 零窄带 MQ 探针;宽屏仍注入;navigator.standalone 单源;无 matchMedia 兜底;断口 900;browser 态显式无 style;off 双零)。
|
|
137
|
+
- stub 基建:`runBootScript` 增 `standaloneAtScriptTime`/`navStandalone` 参数,matchMedia 按查询串分流(display-mode 探针不再误读窄带裁定);browser 全矩阵零回归(mediaQueries 断言更新为含 display-mode 前导探针)。
|
|
138
|
+
|
|
139
|
+
### 9.3 4999 CDP 双形态(lead,2026-09-03 深夜)—— ✅ 8/8 全绿
|
|
140
|
+
|
|
141
|
+
**⚠ 测试方法学发现(重要)**:Chrome 的 `Emulation.setEmulatedMedia` **不支持 `display-mode` 特性**(实测 pre-nav/post-nav 设置后页面内 `matchMedia('(display-mode: standalone)').matches)` 恒 false —— Chrome 可仿真特性集不含它,DevTools Rendering 面板亦无此项)。改用 **matchMedia API 边界注入**:`Page.addScriptToEvaluateOnNewDocument` 在一切页面脚本(含 head 注入 boot script)之前包一层 `matchMedia`,仅对含 display-mode+standalone 的查询返回 `{matches:true}`,其余透传 —— boot script 全部分支逻辑照真运行(自检探针 truth=true 证实注入生效)。
|
|
142
|
+
|
|
143
|
+
| 场景 | 断言 | 结果 |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| standalone+iOS+390 | meta **字节不动**(`width=device-width, initial-scale=1` 原文)= pinch 恢复前提 | ✅ |
|
|
146
|
+
| standalone+iOS+390 | `#ios-zoom-font-floor` 在场,认领属性齐(data-plugin / data-plugin-css=better-dsh/zoom-font-floor),CSS 文本与 user 提案逐字一致 | ✅ |
|
|
147
|
+
| standalone+iOS+390 | composer computed font-size = **16px**(地板生效) | ✅ |
|
|
148
|
+
| browser+iOS+390 | meta guarded(两 token 在场)、无 font-floor | ✅ |
|
|
149
|
+
| browser+iOS+390 | **client 特性探针**:sidebar computed 首列 0px、认领样式 98 条(v0.2.1f 特性在位) | ✅ |
|
|
150
|
+
| standalone+iOS+1280 | floor 已注入(宽度无关)但 media query 不激活(composer 14px) | ✅ |
|
|
151
|
+
| standalone+Android+390 | iOS 门 → 无 floor、meta stock | ✅ |
|
|
152
|
+
| off | 双形态零发射(单测钉住;'off' 代码路径 v0.2.4 未动) | ✅ |
|
|
153
|
+
|
|
154
|
+
脚本 `.scratch/zg-verify/dual-mode.mjs`(含 matchMedia 边界注入实现);重启后 boot graph rev 已随 v0.2.5 变化,tsdown+build-client 双步完成、lib/client 在场(md5 `a88850ec…`)。
|
|
155
|
+
### 9.4 真机复验(user)—— TODO(tasks 3.1)
|
|
156
|
+
|
|
157
|
+
- [ ] PWA standalone:pinch 恢复 + focus 无放大(输入面 16px)。
|
|
158
|
+
- [ ] 浏览器态:维持 §五 结论(meta 改写 + pinch 保留)。
|