dsh-mobile 0.8.0 → 0.8.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/CONTRIBUTORS.md +24 -2
  3. package/README.en.md +8 -5
  4. package/README.md +10 -5
  5. package/SECURITY.md +1 -1
  6. package/apps/mobile/README.md +8 -4
  7. package/apps/mobile/README.zh-CN.md +8 -4
  8. package/apps/mobile/release.json +3 -3
  9. package/docs/CLIENT_MODULES.en.md +6 -2
  10. package/docs/CLIENT_MODULES.md +6 -2
  11. package/docs/CONNECTION_DIAGNOSTICS.en.md +50 -0
  12. package/docs/CONNECTION_DIAGNOSTICS.md +50 -0
  13. package/docs/IROH.en.md +6 -0
  14. package/docs/IROH.md +6 -0
  15. package/docs/MOBILE_PLUGIN_ADAPTATION.en.md +18 -0
  16. package/docs/MOBILE_PLUGIN_ADAPTATION.md +18 -0
  17. package/docs/MOBILE_PLUGIN_BASELINE.en.md +117 -0
  18. package/docs/MOBILE_PLUGIN_BASELINE.md +117 -0
  19. package/docs/README.md +2 -0
  20. package/docs/SKIN_COMPATIBILITY.en.md +1 -1
  21. package/docs/SKIN_COMPATIBILITY.md +1 -1
  22. package/docs/SLOW_CONNECTIONS.en.md +3 -1
  23. package/docs/SLOW_CONNECTIONS.md +3 -1
  24. package/lib/cli.js +6 -12
  25. package/lib/cli.js.map +1 -1
  26. package/lib/client.js +428 -126
  27. package/lib/client.js.map +1 -1
  28. package/lib/extension-worker-runtime.mjs +4 -8
  29. package/lib/extension-worker-runtime.mjs.map +1 -1
  30. package/lib/index.d.mts +16 -89
  31. package/lib/index.mjs +519 -253
  32. package/lib/index.mjs.map +1 -1
  33. package/lib/mobile-compat.js +2 -2
  34. package/lib/mobile-compat.js.map +1 -1
  35. package/lib/mobile-layout.js +13 -6
  36. package/lib/mobile-layout.js.map +1 -1
  37. package/package.json +10 -6
  38. package/packages/question-fixes/lib/client.js +1 -2
  39. package/packages/question-fixes/lib/index.mjs +1 -2
  40. package/packages/question-fixes/package.json +1 -1
@@ -2,6 +2,8 @@
2
2
 
3
3
  [English](MOBILE_PLUGIN_ADAPTATION.en.md) · [返回文档索引](README.md)
4
4
 
5
+ > 验收级别与数字出处见[插件移动端基线](MOBILE_PLUGIN_BASELINE.md);本文负责做法与示例。
6
+
5
7
  这份指南面向 DSH 插件作者,目标是**小屏可用,桌面功能不回退**。DSH Mobile 提供连接、页面布局和公共入口,不会逐个重排第三方插件的内部控件。可以先用浏览器完成适配,再补充真机验证;没有 Android 设备并不妨碍开始。
6
8
 
7
9
  ## 先确认面板所在位置
@@ -124,6 +126,22 @@ h('button', { className: 'icon-button', 'aria-label': label }, h(Icon, { 'aria-h
124
126
 
125
127
  在窄屏与宽屏下,分别打开抽屉、详情和设置,检查实际可见性、焦点及 `document.elementFromPoint()` 命中。`smoke:plugin-portal-layers` 的受控场景检查正层级 body 菜单和浏览器顶层对话框,不代表所有社区插件均已验收。
126
128
 
129
+ ## 宿主兜底:触控命中区
130
+
131
+ 主槽打开第三方插件面板时,移动层会做三件兜底的事:
132
+
133
+ - 粗指针下,面板中的普通按钮、选择框和文本输入控件获得 **44×44 CSS px 的最小布局尺寸**。这会改变控件盒子及其视觉尺寸,不是不可见的命中区扩展;开关、复选框、范围滑块等控件保持自有几何,多行输入由面板自己处理;
134
+ - 面板根若是 `width:100vw` 或固定像素宽,会被收进可用宽度,不再横向溢出;
135
+ - 面板内容区可以滚动并阻止滚动链,不把整个主区也变成第二层滚动容器;插件自己的滚动容器仍是第一选择。
136
+
137
+ 这是**兜底**,不是替插件做适配。插件若自带一套触控几何、不希望被覆盖,在根节点加:
138
+
139
+ ```html
140
+ <main data-dsh-adapt="off">…</main>
141
+ ```
142
+
143
+ 根节点退出时,尺寸和外层滚动覆盖也一起退出;内部控件组也可以使用该属性保留自有控件尺寸。这些规则只在粗指针下生效,不修改细指针页面的布局或滚动。完整适配仍由插件作者按可用空间实现;这不是任意插件组合的兼容保证。
144
+
127
145
  ## 验证:代码检查、浏览器与真机分别证明什么
128
146
 
129
147
  ### 结构检查
@@ -0,0 +1,117 @@
1
+ # Plugin mobile baseline
2
+
3
+ [中文](MOBILE_PLUGIN_BASELINE.md) · [Documentation index](README.md) · [Adaptation guide (how to)](MOBILE_PLUGIN_ADAPTATION.en.md)
4
+
5
+ This document gives a plugin-owned Web panel in a browser or WebView a baseline that can be accepted or rejected: three levels, the source of every number, a stylesheet to copy, and how to prove it holds. It answers "how good is good enough"; **how** to get there is in the [adaptation guide](MOBILE_PLUGIN_ADAPTATION.en.md), and this project's own visual numbers live in the [design reference](https://github.com/saya-ch/dsh-mobile/blob/main/design-system/dsh-mobile/MASTER.md).
6
+
7
+ It is an **informative adaptation guide** and does not change the existing boundary: the mobile layer owns connectivity and page layout, it does not rearrange a third-party plugin's internal controls, and a panel's small-screen usability is the plugin author's responsibility. The levels are not a release gate; they exist for issue checklists, PR self-review, or an agreement between an author and their users. Missing a level is not a defect — per the [contributing guide](https://github.com/saya-ch/dsh-mobile/blob/main/CONTRIBUTING.md), a compatibility request is not a guarantee for every third-party combination. **This baseline is this project's adaptation guidance, not an official DSH standard, and does not represent the DSH team's position.**
8
+
9
+ ## Three levels
10
+
11
+ | Level | Name | Requirement (must / should) | Typical evidence |
12
+ | --- | --- | --- | --- |
13
+ | L1 | Tappable | **Must**: main Web actions have a hit region of at least 44×44 CSS px (48px for a primary action where space allows); adjacent actions keep spacing; inputs and editable areas inherit at least 16px type; keyboard focus stays visible; no page-level horizontal overflow at 375px; collapsing width never hides the only entry point | Renders at 375px and 390px widths with hit-region measurements |
14
+ | L2 | Comfortable | **Should**: switch on the panel's **measured width** (600px or less suggested); horizontal chip/tab strips scroll sideways; inline text actions collapse to an icon with an accessible name; body text follows `--dsh-content-font-size`; edge-to-edge areas reserve `env(safe-area-inset-*)`; an independent scroll container sets `overscroll-behavior: contain`; forms collapse to one column in the narrow tier | The same panel in a narrow and a wide container |
15
+ | L3 | Regressible | **Should**: the static preview can render at a requested width; the structural assertions for L1/L2 are wired into the render check; the desktop render has a baseline to compare; device-only gaps are written down | Assertion output, screenshot baseline, PR notes |
16
+
17
+ L1 can be prechecked in a desktop browser at 390px; that does not cover a device's keyboard, text scaling or safe areas. L2 must be checked where the **panel width differs from the viewport width** (a narrow desktop column, a drawer). L3 pins the first two; it is not a new interaction requirement.
18
+
19
+ ## Where the numbers come from
20
+
21
+ Web, iOS and Android use different units and scopes. Project recommendations do not replace platform or accessibility requirements, and pt, dp and CSS px must not be treated as equivalent.
22
+
23
+ | Requirement | Source | Note |
24
+ | --- | --- | --- |
25
+ | 44×44 CSS px hit region | This project's [design reference](https://github.com/saya-ch/dsh-mobile/blob/main/design-system/dsh-mobile/MASTER.md) (Web targets ≥44px, 48px for primary actions); [Apple HIG · Buttons](https://developer.apple.com/design/human-interface-guidelines/buttons) (hit region of at least 44×44 pt); [WCAG 2.2 SC 2.5.5](https://www.w3.org/WAI/WCAG22/Understanding/target-size-enhanced.html) (AAA, 44×44 CSS px) | pt, dp and CSS px are three different units; nothing is converted here, only the magnitude is shared |
26
+ | 24×24 CSS px | [WCAG 2.2 SC 2.5.8](https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html) (AA, with a spacing exception) | A minimum, not a target; this project uses 44 |
27
+ | 48dp | [Android minimum interactive component size](https://developer.android.com/reference/kotlin/androidx/compose/material/minimumInteractiveComponentSize.modifier) | Native controls only; do not convert it into a CSS px value for a Web panel |
28
+ | Input type ≥16px | [Design reference](https://github.com/saya-ch/dsh-mobile/blob/main/design-system/dsh-mobile/MASTER.md) (the mobile shell already enforces it); iOS zooms the page when a focused form control's font size is below 16px | Do not shrink inputs below that to make a layout fit |
29
+ | `env(safe-area-inset-*)` | [MDN · env()](https://developer.mozilla.org/en-US/docs/Web/CSS/env) | Requires `viewport-fit=cover`; add it only where the content really reaches the edge and the shell has not already handled it |
30
+ | `@container` | [MDN · Container queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries) | Components lay out by container width; all major browsers have supported it since 2023, so keep a fluid fallback for older engines |
31
+ | `overscroll-behavior: contain` | [MDN · overscroll-behavior](https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior) | Stops scroll chaining: reaching the panel's end must not drag the page behind it |
32
+ | `prefers-reduced-motion` | [MDN · prefers-reduced-motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) | Drop decorative animation; a loading indicator may stay |
33
+
34
+ ### Two common misuses of 44px
35
+
36
+ - **A hit region is not a visual size.** A round checkbox may stay visually 24px and use an associated label for its full click target; enlarging the whole card is not adaptation.
37
+ - **Do not grow hit areas by covering neighbours.** Expanded regions must not overlap adjacent controls or change wrapping and focus order; use spacing, not overlap.
38
+
39
+ ## A stylesheet to copy
40
+
41
+ Replace `plugin-panel` with your own root class. Enable `data-mobile="1"` only when the measured panel enters its narrow tier. Wide panels are unchanged; a narrow desktop column can also enable it. The switch itself (container query or measured width) is in the [adaptation guide](MOBILE_PLUGIN_ADAPTATION.en.md#adapt-to-available-space).
42
+
43
+ The attribute name is the plugin's own convention — the guide's script example uses `data-narrow`. The name does not matter; what matters is that the whole narrow tier hangs off **one** switch, and that wide panels omit it; the value names a width tier, not a device type.
44
+
45
+ ```css
46
+ /* Type follows the shell setting; without the variable it stays inherited, so the desktop is unchanged. */
47
+ .plugin-panel[data-mobile="1"] { font-size: var(--dsh-content-font-size, inherit); }
48
+
49
+ /* Apply these classes to ordinary actions/text fields, not native switches or ranges. Layout boxes change. */
50
+ .plugin-panel[data-mobile="1"] :is(.action, .icon-button, .primary-action, .text-input, select) { min-width: 44px; min-height: 44px; }
51
+ .plugin-panel[data-mobile="1"] .icon-button { min-width: 44px; }
52
+ .plugin-panel[data-mobile="1"] .primary-action { min-height: 48px; }
53
+
54
+ /* Use <label class="check-label"><input type="checkbox">Name</label>, without covering adjacent controls. */
55
+ .plugin-panel[data-mobile="1"] .check-label { display: inline-flex; align-items: center; gap: 8px; min-width: 44px; min-height: 44px; padding: 10px; box-sizing: border-box; }
56
+ .plugin-panel[data-mobile="1"] .check-label input { width: 24px; height: 24px; margin: 0; }
57
+
58
+ /* Horizontal chip strips scroll sideways; a 1px divider would hang alone in a scrolling row. */
59
+ .plugin-panel[data-mobile="1"] .chips { display: flex; flex-wrap: nowrap; gap: 8px; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none; }
60
+ .plugin-panel[data-mobile="1"] .chips::-webkit-scrollbar { display: none; }
61
+ .plugin-panel[data-mobile="1"] .chip { flex: 0 0 auto; min-height: 44px; }
62
+ .plugin-panel[data-mobile="1"] .chips .divider { display: none; }
63
+
64
+ /* Inline text actions collapse to icons; the text stays in the menu or detail view, and the accessible name must stay. */
65
+ .plugin-panel[data-mobile="1"] .action-label { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
66
+ .plugin-panel[data-mobile="1"] .row-action { min-width: 44px; padding: 0; justify-content: center; }
67
+
68
+ /* Hide only repeated decorative shortcuts, not the sole operating instructions. */
69
+ .plugin-panel[data-mobile="1"] .kbd { display: none; }
70
+
71
+ /* Edge-to-edge areas reserve the safe area; the panel's own scroll container stops chaining. */
72
+ .plugin-panel[data-mobile="1"] .content { padding-bottom: max(28px, calc(14px + env(safe-area-inset-bottom))); }
73
+ .plugin-panel[data-mobile="1"] .scroll-body { overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
74
+
75
+ /* Forms collapse to one column; a multi-line input keeps a usable height. */
76
+ .plugin-panel[data-mobile="1"] .form-row { grid-template-columns: minmax(0, 1fr); }
77
+ .plugin-panel[data-mobile="1"] textarea { min-height: 96px; }
78
+
79
+ /* Collapse decorative/repeated copy only, never essential operating instructions. */
80
+ .plugin-panel[data-mobile="1"] .title { font-size: 1.2em; line-height: 1.4; }
81
+ .plugin-panel[data-mobile="1"] .subtitle { display: none; }
82
+
83
+ @media (prefers-reduced-motion: reduce) {
84
+ .plugin-panel[data-mobile="1"] .chips { scroll-behavior: auto; }
85
+ }
86
+ ```
87
+
88
+ ## How to prove it
89
+
90
+ | Level | Evidence | Note |
91
+ | --- | --- | --- |
92
+ | L1 | A 390×844 render plus hit-region measurements | Measure the layout box, not the CSS declaration; a `min-height` can still be compressed, clipped or covered |
93
+ | L2 | One render in a narrow container and one in a wide container | The narrow container means a ≤600px panel, not a shrunken viewport — that is exactly what the measured width or container query is for |
94
+ | L3 | Structural assertions + a desktop baseline | Assert at least: the narrow marker appears, the 44px rule exists, the safe-area and type rules exist, every narrow rule is scoped to the same switch, and collapsed actions keep an accessible name; compare the desktop baseline in a fixed browser/font/viewport environment and explain expected differences; string-presence assertions do not replace geometry or interaction checks |
95
+
96
+ Static checks establish only the declarations they inspect. Geometry and hit testing need a browser, and the following additionally need device evidence with explicit coverage:
97
+
98
+ - the visible area and focus scrolling after the soft keyboard opens;
99
+ - the real `env(safe-area-inset-*)` values;
100
+ - momentum scrolling and rebound;
101
+ - wrapping and truncation at a larger system font size.
102
+
103
+ ## How this sits with the other documents
104
+
105
+ | Document | Question it answers |
106
+ | --- | --- |
107
+ | [Adaptation guide](MOBILE_PLUGIN_ADAPTATION.en.md) | How: host behavior, example code, item-by-item technique, overlay layering |
108
+ | This document | What counts as done: levels, number provenance, baseline stylesheet, acceptance evidence |
109
+ | [Design reference](https://github.com/saya-ch/dsh-mobile/blob/main/design-system/dsh-mobile/MASTER.md) | This project's visual and numeric rules (color, type, radius, hit region) |
110
+ | [Contributing guide](https://github.com/saya-ch/dsh-mobile/blob/main/CONTRIBUTING.md) | How to submit, which checks to run, the boundary of compatibility requests |
111
+
112
+ ## Boundaries
113
+
114
+ - It covers plugin-owned Web panels only; background services and plugins that depend on native windows are out of scope.
115
+ - It does not ask for a redesign and does not justify a second component tree per platform; lists and details should stay one implementation.
116
+ - The mobile layer must not inject third-party CSS by class name: a renamed class breaks the rule and the desktop at the same time.
117
+ - Do not use a user agent or `pointer: coarse` alone instead of a measured width; hybrid input (touch laptops, tablets with a mouse) makes both signals wrong.
@@ -0,0 +1,117 @@
1
+ # 插件移动端基线
2
+
3
+ [English](MOBILE_PLUGIN_BASELINE.en.md) · [返回文档索引](README.md) · [适配指南(做法与示例)](MOBILE_PLUGIN_ADAPTATION.md)
4
+
5
+ 这份文档给**插件自有的网页面板(浏览器或 WebView)**定一个可验收的基线:三级要求、每条数字的出处、一份可直接复制的样式,以及怎么证明它成立。它回答「做到什么程度算过」;怎么做见[适配指南](MOBILE_PLUGIN_ADAPTATION.md),本项目自己的视觉与数值口径见[设计参考](https://github.com/saya-ch/dsh-mobile/blob/main/design-system/dsh-mobile/MASTER.md)。
6
+
7
+ 这是**说明性适配指南**,不改变既有边界:移动层负责连接与页面布局,不重排第三方插件的内部控件;面板在小屏上是否好用由插件作者负责。级别不是发布门槛,用于 issue 勾选、PR 自检,或作者与用户之间的约定。未达标不等于缺陷——按[贡献指南](https://github.com/saya-ch/dsh-mobile/blob/main/CONTRIBUTING.md)的口径,兼容请求不构成对任意第三方组合的承诺。**本基线是本项目的适配建议,不是 DSH 官方标准,也不代表 DSH 官方立场。**
8
+
9
+ ## 三个级别
10
+
11
+ | 级别 | 名称 | 要求(必须 / 应当) | 典型证据 |
12
+ | --- | --- | --- | --- |
13
+ | L1 | 可点 | **必须**:主要 Web 操作命中区至少 44×44 CSS px(主操作有空间时 48px);相邻操作保留间距;输入/可编辑区域继承不小于 16px 的字号;键盘焦点可见;375px 宽下没有页面级横向溢出;不因收窄而隐藏唯一入口 | 375px 与 390px 宽度的截图,附命中区尺寸 |
14
+ | L2 | 顺手 | **应当**:按面板**实测宽度**切档(建议 ≤600px);横向 chip/标签条可横滑;行内文字动作收成图标并保留可访问名称;正文跟随 `--dsh-content-font-size`;贴边区域让出 `env(safe-area-inset-*)`;独立滚动容器设 `overscroll-behavior: contain`;表单在窄档收成单列 | 同一面板在窄容器与宽容器各一张 |
15
+ | L3 | 可回归 | **应当**:静态预览能指定渲染宽度;把 L1/L2 的结构断言接进渲染检查;桌面渲染有基线可对比;真机未验证项写明 | 断言输出、截图基线、PR 说明 |
16
+
17
+ L1 可以先在 390px 桌面浏览器预检,但这不覆盖真机键盘、字体缩放和安全区;L2 必须在**面板宽度不等于视口宽度**的场合验(桌面窄栏、抽屉);L3 是把前两级钉住的手段,不是新的交互要求。
18
+
19
+ ## 数字与出处的对照
20
+
21
+ Web、iOS 和 Android 的单位与适用范围不同。项目建议不代替平台规范或无障碍要求,也不能把 pt、dp 与 CSS px 直接等同。
22
+
23
+ | 要求 | 出处 | 说明 |
24
+ | --- | --- | --- |
25
+ | 44×44 CSS px 命中区 | 本项目[设计参考](https://github.com/saya-ch/dsh-mobile/blob/main/design-system/dsh-mobile/MASTER.md)(Web 目标 ≥44px,主操作 48px);[Apple HIG · Buttons](https://developer.apple.com/design/human-interface-guidelines/buttons)(命中区至少 44×44 pt);[WCAG 2.2 SC 2.5.5](https://www.w3.org/WAI/WCAG22/Understanding/target-size-enhanced.html)(AAA,44×44 CSS px) | pt、dp、CSS px 是三种单位,不做换算;这里只是沿用同一个数量级 |
26
+ | 24×24 CSS px | [WCAG 2.2 SC 2.5.8](https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html)(AA,含间距例外) | 这是最小值,不是目标;本项目取 44 |
27
+ | 48dp | [Android 交互组件最小尺寸](https://developer.android.com/reference/kotlin/androidx/compose/material/minimumInteractiveComponentSize.modifier) | 只适用于原生控件;不要换算成 CSS px 写进 Web 面板 |
28
+ | 输入字号 ≥16px | [设计参考](https://github.com/saya-ch/dsh-mobile/blob/main/design-system/dsh-mobile/MASTER.md)(移动外壳已强制);iOS 在聚焦字号小于 16px 的表单控件时会自动放大页面 | 插件不要把输入框写成更小的固定字号来「塞进」布局 |
29
+ | `env(safe-area-inset-*)` | [MDN · env()](https://developer.mozilla.org/en-US/docs/Web/CSS/env) | 需要 `viewport-fit=cover`;只在确实贴边、且外壳未处理的区域追加,避免重复留白 |
30
+ | `@container` | [MDN · 容器查询](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries) | 组件按容器宽度排版;主流浏览器自 2023 年起支持,更旧的引擎要保留流式回退 |
31
+ | `overscroll-behavior: contain` | [MDN · overscroll-behavior](https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior) | 阻止滚动链:面板到底时不要把背后的页面带走 |
32
+ | `prefers-reduced-motion` | [MDN · prefers-reduced-motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) | 收起装饰性动画;加载指示可以保留 |
33
+
34
+ ### 关于 44px 的两点常见误用
35
+
36
+ - **命中区不是视觉尺寸**。圆形勾选框可以保持 24px 视觉,用关联的标签提供完整点击区域;把整张卡片放大不是适配。
37
+ - **不要把相邻操作「盖」大**。扩大的命中区不应覆盖邻居,也不应改变换行与焦点顺序;用间距而不是重叠来满足 44px。
38
+
39
+ ## 一份可复制的基线样式
40
+
41
+ 把 `plugin-panel` 换成你自己的根类名;`data-mobile="1"` 只在面板实测宽度进入窄档时启用,宽档不命中这些规则,桌面窄栏也可以启用。开关本身(容器查询或实测宽度)见[适配指南](MOBILE_PLUGIN_ADAPTATION.md#按可用空间适配)。
42
+
43
+ 属性名是插件自己的约定,指南的脚本示例用的是 `data-narrow`;叫什么不重要,重要的是**整档样式只挂在同一个开关上**,并且宽档不出现该值;它表示窄档,不是设备类型。
44
+
45
+ ```css
46
+ /* 字号跟随外壳设置;没有该变量时保持继承,不影响桌面。 */
47
+ .plugin-panel[data-mobile="1"] { font-size: var(--dsh-content-font-size, inherit); }
48
+
49
+ /* 给普通动作和文本输入加对应类;不要用于原生开关或范围滑块。这会改变布局尺寸。 */
50
+ .plugin-panel[data-mobile="1"] :is(.action, .icon-button, .primary-action, .text-input, select) { min-width: 44px; min-height: 44px; }
51
+ .plugin-panel[data-mobile="1"] .icon-button { min-width: 44px; }
52
+ .plugin-panel[data-mobile="1"] .primary-action { min-height: 48px; }
53
+
54
+ /* 使用 <label class="check-label"><input type="checkbox">名称</label>,不覆盖邻近控件。 */
55
+ .plugin-panel[data-mobile="1"] .check-label { display: inline-flex; align-items: center; gap: 8px; min-width: 44px; min-height: 44px; padding: 10px; box-sizing: border-box; }
56
+ .plugin-panel[data-mobile="1"] .check-label input { width: 24px; height: 24px; margin: 0; }
57
+
58
+ /* 横向 chip 条改为可横滑;分隔条在横滑行里会挂成孤线。 */
59
+ .plugin-panel[data-mobile="1"] .chips { display: flex; flex-wrap: nowrap; gap: 8px; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none; }
60
+ .plugin-panel[data-mobile="1"] .chips::-webkit-scrollbar { display: none; }
61
+ .plugin-panel[data-mobile="1"] .chip { flex: 0 0 auto; min-height: 44px; }
62
+ .plugin-panel[data-mobile="1"] .chips .divider { display: none; }
63
+
64
+ /* 行内文字动作收成图标;文字仍留在菜单/详情里,可访问名称不能丢。 */
65
+ .plugin-panel[data-mobile="1"] .action-label { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
66
+ .plugin-panel[data-mobile="1"] .row-action { min-width: 44px; padding: 0; justify-content: center; }
67
+
68
+ /* 仅隐藏重复的装饰性快捷键提示,唯一的操作说明应保留。 */
69
+ .plugin-panel[data-mobile="1"] .kbd { display: none; }
70
+
71
+ /* 贴边区域让出安全区;面板自己的滚动容器阻止滚动链。 */
72
+ .plugin-panel[data-mobile="1"] .content { padding-bottom: max(28px, calc(14px + env(safe-area-inset-bottom))); }
73
+ .plugin-panel[data-mobile="1"] .scroll-body { overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
74
+
75
+ /* 表单收成单列,多行输入保留可用高度。 */
76
+ .plugin-panel[data-mobile="1"] .form-row { grid-template-columns: minmax(0, 1fr); }
77
+ .plugin-panel[data-mobile="1"] textarea { min-height: 96px; }
78
+
79
+ /* 仅对装饰性、重复文案使用收起规则,不隐藏必要操作说明。 */
80
+ .plugin-panel[data-mobile="1"] .title { font-size: 1.2em; line-height: 1.4; }
81
+ .plugin-panel[data-mobile="1"] .subtitle { display: none; }
82
+
83
+ @media (prefers-reduced-motion: reduce) {
84
+ .plugin-panel[data-mobile="1"] .chips { scroll-behavior: auto; }
85
+ }
86
+ ```
87
+
88
+ ## 怎么证明
89
+
90
+ | 级别 | 证据 | 说明 |
91
+ | --- | --- | --- |
92
+ | L1 | 390×844 渲染 + 命中区尺寸 | 用实际布局盒量,不要只读 CSS 声明;`min-height` 仍可能被压缩、裁切或遮挡 |
93
+ | L2 | 窄容器与宽容器各一张 | 窄容器指 ≤600px 的面板,不是把视口缩小;这正是实测宽度与容器查询要解决的问题 |
94
+ | L3 | 结构断言 + 桌面基线 | 断言至少覆盖:窄档标记出现、44px 规则存在、安全区与字号变量存在、窄档规则都挂在同一个开关下、被收起的动作仍有可访问名称;在固定浏览器、字体和尺寸下对比桌面基线,说明预期差异;字符串存在断言不代替几何和交互检查 |
95
+
96
+ 静态检查只能证明相应声明存在。几何和命中需要浏览器验证,以下项目还需真机验证并注明覆盖范围:
97
+
98
+ - 软键盘弹出后的可视区与焦点滚动;
99
+ - 真实的 `env(safe-area-inset-*)` 数值;
100
+ - 惯性滚动与回弹手感;
101
+ - 系统字号放大后的换行与截断。
102
+
103
+ ## 与既有文档的分工
104
+
105
+ | 文档 | 回答的问题 |
106
+ | --- | --- |
107
+ | [适配指南](MOBILE_PLUGIN_ADAPTATION.md) | 怎么做:宿主行为、示例代码、逐项做法、浮层层级 |
108
+ | 本文 | 做到什么算过:级别、数字出处、基线样式、验收口径 |
109
+ | [设计参考](https://github.com/saya-ch/dsh-mobile/blob/main/design-system/dsh-mobile/MASTER.md) | 本项目自己的视觉与数值口径(颜色、字号、圆角、命中区) |
110
+ | [贡献指南](https://github.com/saya-ch/dsh-mobile/blob/main/CONTRIBUTING.md) | 怎么提交、跑哪些检查、兼容请求的边界 |
111
+
112
+ ## 边界
113
+
114
+ - 只覆盖插件自有的网页面板(浏览器或 WebView);后台服务型、依赖原生窗口的插件不适用。
115
+ - 不要求重做交互,不要求复制一套移动端组件树;详情与列表仍应只有一份实现。
116
+ - 不在移动层按类名注入第三方 CSS:类名一变就碎,而且会同时影响桌面。
117
+ - 不以 UA 或单纯 `pointer: coarse` 代替实测宽度;混合输入设备(触屏笔记本、带鼠标的平板)会让这两类判断都失真。
package/docs/README.md CHANGED
@@ -28,6 +28,7 @@
28
28
 
29
29
  | 功能 | 中文 | English | 使用状态 |
30
30
  | --- | --- | --- | --- |
31
+ | 区分电脑、DNS、出口与手机的诊断证据 | [连接诊断](CONNECTION_DIAGNOSTICS.md) | [Connection diagnostics](CONNECTION_DIAGNOSTICS.en.md) | 分层报告为 0.8.2 候选;不代表手机路径已验收 |
31
32
  | 核对移动字号、字体与 App 页面缩放 | [显示大小](MOBILE_COMPOSER_TOOLBAR.md#显示大小与可用空间) | [Display size](MOBILE_COMPOSER_TOOLBAR.en.md#display-size-and-available-space) | 字体按主题能力显示;App 缩放与网页字号独立 |
32
33
  | 为移动页面选择加载模块 | [模块设置](CLIENT_MODULES.md) | [Module settings](CLIENT_MODULES.en.md) | 电脑默认与设备覆盖分别保存,不自动刷新会话 |
33
34
  | 允许受控路由网络访问 LAN Gateway | [额外可信网段](TRUSTED_NETWORKS.md) | [Trusted networks](TRUSTED_NETWORKS.en.md) | 不建立 VPN,不自动扩大防火墙规则 |
@@ -43,6 +44,7 @@
43
44
  ## 开发与安全 / Development and security
44
45
 
45
46
  0.6.3 的 [扩展 Worker 指南](EXTENSION_WORKERS.md)/[Worker guide](EXTENSION_WORKERS.en.md) 介绍按扩展启用、预算与显式恢复;此功能不迁移普通 Cordis 插件,也不是权限沙箱。
47
+ [插件移动端基线](MOBILE_PLUGIN_BASELINE.md)/[Plugin mobile baseline](MOBILE_PLUGIN_BASELINE.en.md) 把「小屏可用、桌面不回退」拆成三级要求与验收证据;做法与示例见[适配指南](MOBILE_PLUGIN_ADAPTATION.md)/[guide](MOBILE_PLUGIN_ADAPTATION.en.md)。
46
48
 
47
49
  - [贡献指南](https://github.com/saya-ch/dsh-mobile/blob/main/CONTRIBUTING.md):本地检查、浏览器与 Android 验证、发布要求。
48
50
  - [安全说明](../SECURITY.md):配对授权、TLS、组件清理与私密漏洞反馈。
@@ -25,4 +25,4 @@ For example, the published `maid-atelier` 0.1.7 package mounts its background in
25
25
 
26
26
  An isolated DSH 0.2.0-rc.2 combination with Mobile 0.6.4 and the published npm `maid-atelier` 0.1.7 reproduces the see-through navy sidebar, unreadable dark labels, missing background and a blank populated Trajectory view. The trace data remains present: a broad skin rule changes the mobile root to a column Flex layout and collapses the content region to zero height. Plugin 0.6.5 protects the plugin-owned root's Grid geometry and fixes this blank trace, without changing the skin's palette, shadows or decoration mounting.
27
27
 
28
- This skin binds light sidebar labels to the stock `sidebarCol`, absent from the mobile layout. Missing backgrounds and some text-contrast problems still need skin adaptation; the trace fix does not resolve them. Device-level exclusion restores default mobile presentation while preserving the computer skin. Plugin 0.6.4 and earlier do not contain the layout protection.
28
+ This skin binds light sidebar labels to the stock `sidebarCol`, absent from the mobile layout. Missing backgrounds and some text-contrast problems still need skin adaptation; the trace fix does not resolve them. Device-level selection stops optional client modules from loading, not host-injected page styles, so it does not guarantee a default appearance. This device setting does not change the computer's installed plugins or loading choices. Plugin 0.6.4 and earlier do not contain the layout protection.
@@ -25,4 +25,4 @@ DSH Mobile 在已认证的移动页面中使用专用根布局,继续渲染 DS
25
25
 
26
26
  在 DSH 0.2.0-rc.2、Mobile 0.6.4 与正式 npm `maid-atelier` 0.1.7 的隔离组合中,已复现深蓝侧栏透底、深色文字难读、背景缺失和有内容会话的轨迹空白。轨迹数据没有丢失:皮肤的宽泛根布局规则把移动容器改为纵向 Flex,内容区域高度缩为 0。插件 0.6.5 保护自有根容器的 Grid 几何规则,修复该轨迹空白,不改皮肤配色、阴影或装饰挂载。
27
27
 
28
- 该皮肤把浅色侧栏文字绑定到原生 `sidebarCol`;移动布局没有该节点。缺失背景及部分文字对比度仍需皮肤适配,不能视为已随轨迹修复解决。按上面的设备模块选择排除皮肤后,移动页面可恢复默认样式,电脑皮肤保持。插件 0.6.4 及更早版本不包含这项布局保护。
28
+ 该皮肤把浅色侧栏文字绑定到原生 `sidebarCol`;移动布局没有该节点。缺失背景及部分文字对比度仍需皮肤适配,不能视为已随轨迹修复解决。设备模块选择只能阻止可选客户端模块加载,不能移除服务端注入的整页样式,因此不保证恢复默认外观;电脑上的插件安装和加载选择不受该设备设置影响。插件 0.6.4 及更早版本不包含这项布局保护。
@@ -8,6 +8,8 @@ Start with the actual channel rather than treating every delay as a DSH heartbea
8
8
 
9
9
  ## Identify the stage that fails
10
10
 
11
+ Component readiness, system DNS and computer egress are distinct observations, not proof of phone or public-route failure. See [connection diagnostics](CONNECTION_DIAGNOSTICS.en.md) for layered reports and the same-tailnet Funnel limitation.
12
+
11
13
  First record the DSH, DSH Mobile plugin and Android app versions. On the computer, check the current channel under **Mobile Access → Diagnostics**, then inspect DSH startup logs and the browser's developer tools under Network → WS and update the component involved. The plugin and app release independently; their version numbers do not need to match. Remove Tokens, Cookies, and complete pairing URLs before sharing logs.
12
14
 
13
15
  - **WebSocket never connects**: check for `101 Switching Protocols` and errors such as `401`, `403`, `404`, or `502`. Authentication, Host/Origin checks, proxy upgrade support, paths, and version differences can all cause this. Heartbeat adjustments do not resolve these failures. Allow blocked third-party plugin paths individually in Connection diagnostics; DSH's built-in paths do not need to be added again.
@@ -30,7 +32,7 @@ Success and failure each have one fixed sampling slot: the first result is immed
30
32
 
31
33
  Plugin 0.6.13 and later distinguish request receive timeouts as `408 request_timeout` and oversized headers as `431 headers_too_large`; other malformed HTTP requests remain `400 bad_request`. Older versions may return 400 with the same short JSON for all of them, so a status and a 24-byte body do not identify the cause.
32
34
 
33
- The `http failure` diagnostics distinguish three per-Gateway categories: `parser` for HTTP parsing/receive failures, `target` for invalid request targets, and `upstream` for an authenticated `POST /api/session/prompt` rejected upstream. The first event of each kind is immediate; repeats aggregate for 30 seconds, with `suppressed` counting earlier suppressed events of that kind. Logs exclude raw errors, request bodies, raw packets, Cookies, Tokens, queries and arbitrary URLs. No record does not establish success.
35
+ The `http failure` diagnostics distinguish four per-Gateway categories: `parser` for HTTP parsing/receive failures, `transport` for connection resets and other fixed transport errors, `target` for invalid request targets, and `upstream` for an authenticated `POST /api/session/prompt` rejected upstream. `transport` has no HTTP status and does not establish a returned 400; parser code `other` does not identify the cause either. The first event of each kind is immediate; repeats aggregate for 30 seconds. `suppressed` counts earlier suppressed events of that kind, not necessarily the same code, path or device. Logs exclude raw errors, request bodies, raw packets, Cookies, Tokens, queries and arbitrary URLs. No record does not establish success.
34
36
 
35
37
  `slow api` samples authenticated, forwarded `/api/` requests taking at least 3 seconds: a bounded method, a fixed route category such as `/api/session` (unknown categories become `/api/other`), the status and `durationMs`. The duration spans entry into upstream forwarding until the response closes, including a client-abandoned response; it is a proxy wait, not a measurement of upstream processing time or proof of the slow layer. Logs exclude path suffixes such as Session IDs, query strings, request bodies and arbitrary raw paths. Each Gateway records its first event immediately and aggregates subsequent samples within 30 seconds. This changes no timeout, retry or caching policy.
36
38
 
@@ -8,6 +8,8 @@
8
8
 
9
9
  ## 先判断重连发生在哪一步
10
10
 
11
+ 电脑诊断中的组件就绪、系统 DNS 和当前出口是不同证据,不能据此直接判定手机或公网故障。分层报告和同 tailnet Funnel 的限制见[连接诊断](CONNECTION_DIAGNOSTICS.md)。
12
+
11
13
  先记录 DSH、DSH Mobile 插件和 Android App 的版本,再在电脑端 **移动访问 → 诊断** 检查当前通道;查看 DSH 启动日志和浏览器开发者工具的 Network → WS,按问题涉及的组件更新。插件与 App 独立发版,版本号不必相同。分享日志时去掉 Token、Cookie 和完整配对地址。
12
14
 
13
15
  - **WebSocket 没有连接成功**:检查请求是否获得 `101 Switching Protocols`,并留意 `401`、`403`、`404`、`502` 等错误。认证、Host/Origin、反向代理升级支持、连接路径或版本差异都可能造成这种情况。调整心跳不能解决这些问题;第三方插件被拦截的路径可在连接诊断中按需允许,不必把 DSH 内置路径全部重新添加。
@@ -30,7 +32,7 @@
30
32
 
31
33
  插件 0.6.13 起把请求接收超时区分为 `408 request_timeout`,请求头过大区分为 `431 headers_too_large`;其他非法 HTTP 请求仍为 `400 bad_request`。旧版可能把这几类情况都返回成 400 和相同的短 JSON,因此状态码与“24 字节”不足以定位原因。
32
34
 
33
- `http failure` 日志按网关区分 `parser`、`target`、`upstream` 三类:分别表示 HTTP 解析/接收失败、非法请求目标、已认证的 `POST /api/session/prompt` 被上游拒绝。每类首条立即记录,后续 30 秒内聚合,下一条的 `suppressed` 是此前被抑制的同类数量。日志不保留原始报错、请求正文、原始包、Cookie、Token、查询参数或任意 URL;不记录某条也不能证明它成功。
35
+ `http failure` 日志按网关区分 `parser`、`transport`、`target`、`upstream` 四类:分别表示 HTTP 解析/接收失败、连接重置等传输错误、非法请求目标、已认证的 `POST /api/session/prompt` 被上游拒绝。`transport` 没有 HTTP 状态,不表示服务端返回了 400;`parser` 的 `other` 也不足以确定具体原因。每类首条立即记录,后续 30 秒内聚合,下一条的 `suppressed` 是此前被抑制的同类数量,不保证来自相同错误码、路径或设备。日志不保留原始报错、请求正文、原始包、Cookie、Token、查询参数或任意 URL;不记录某条也不能证明它成功。
34
36
 
35
37
  `slow api` 日志采样已认证并转发、耗时至少 3 秒的 `/api/` 请求:固定方法、`/api/session` 等固定路由类别(未知类别归为 `/api/other`)、状态和 `durationMs`。耗时从进入上游代理转发算到响应关闭,包含客户端中途放弃的响应;它是代理等待时间,不是上游处理耗时,也不能单凭该数字确定哪一层慢。日志不保留会话 ID 等路径后缀、查询参数、请求正文或任意原始路径,每个网关首条立即记录、后续采样在 30 秒窗口内聚合。不改变超时、重试或缓存策略。
36
38
 
package/lib/cli.js CHANGED
@@ -8,7 +8,7 @@ import { generate } from "selfsigned";
8
8
  import { performance } from "node:perf_hooks";
9
9
  import "@deepseek-ai/cordis";
10
10
  import "@deepseek-ai/schemastery";
11
- //#region src/exec-file.ts
11
+ // src/exec-file.ts
12
12
  /** Capture both output streams even when a desktop host wraps execFile without Node's promisify metadata. */
13
13
  function execFileText(file, args, options = {}) {
14
14
  return new Promise((resolve, reject) => {
@@ -32,8 +32,7 @@ function execFileText(file, args, options = {}) {
32
32
  });
33
33
  });
34
34
  }
35
- //#endregion
36
- //#region src/private-file.ts
35
+ // src/private-file.ts
37
36
  let userSidTask;
38
37
  function commandFailureDetails(error) {
39
38
  if (error === null || typeof error !== "object") return {};
@@ -153,8 +152,7 @@ Object.freeze([
153
152
  [3758096384n, 4],
154
153
  [4026531840n, 4]
155
154
  ]);
156
- //#endregion
157
- //#region src/managed-setup.ts
155
+ // src/managed-setup.ts
158
156
  const VIRTUAL_INTERFACE_MARKERS = [
159
157
  "bridge",
160
158
  "docker",
@@ -511,8 +509,7 @@ async function refreshManagedServerCertificate(setup, address) {
511
509
  keyFile: setup.tls.keyFile
512
510
  });
513
511
  }
514
- //#endregion
515
- //#region src/lan-setup.ts
512
+ // src/lan-setup.ts
516
513
  const FIREWALL_TCP_RULE = "DSH Mobile HTTPS";
517
514
  const FIREWALL_UDP_RULE = "DSH Mobile Discovery";
518
515
  async function runElevatedPowerShell(script) {
@@ -603,8 +600,7 @@ Object.freeze({
603
600
  assetBytes: 33554432,
604
601
  assetDepth: 8
605
602
  });
606
- //#endregion
607
- //#region src/extensions.ts
603
+ // src/extensions.ts
608
604
  /** A controlled business failure returned by an extension action or route. */
609
605
  var MobileExtensionError = class extends Error {
610
606
  code;
@@ -621,8 +617,7 @@ function assertExtensionId(value) {
621
617
  if (typeof value !== "string" || !/^[a-z][a-z0-9-]{0,63}$/u.test(value)) throw new MobileExtensionError("invalid_manifest", "extension id is invalid");
622
618
  return value;
623
619
  }
624
- //#endregion
625
- //#region src/cli.ts
620
+ // src/cli.ts
626
621
  function parseOptions(args) {
627
622
  let address;
628
623
  let port = 3443;
@@ -816,7 +811,6 @@ main().catch((error) => {
816
811
  console.error(error instanceof Error ? error.message : String(error));
817
812
  process.exitCode = 1;
818
813
  });
819
- //#endregion
820
814
  export {};
821
815
 
822
816
  //# sourceMappingURL=cli.js.map