dsh-zen-remote 1.0.2 → 1.0.3
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/AGENTS.md +8 -1
- package/README.md +8 -8
- package/docs/interface.md +26 -6
- package/lib/client.js +34 -0
- package/lib/types/client/styles/composer.css.d.ts +1 -1
- package/lib/types/client/styles/composer.css.d.ts.map +1 -1
- package/lib/types/client/styles/header.css.d.ts +1 -1
- package/lib/types/client/styles/header.css.d.ts.map +1 -1
- package/package.json +21 -20
- package/scripts/sync-doc-version.mjs +83 -0
- package/src/client/styles/composer.css.ts +25 -0
- package/src/client/styles/header.css.ts +9 -0
package/AGENTS.md
CHANGED
|
@@ -29,9 +29,16 @@
|
|
|
29
29
|
pnpm install
|
|
30
30
|
pnpm build # tsc host + tsc client + build-client.mjs → lib/(产物入库,改 src/ 必须重跑并提交 lib/)
|
|
31
31
|
pnpm verify # 两个 tsconfig 的 --noEmit 类型检查
|
|
32
|
-
pnpm test # 网关
|
|
32
|
+
pnpm test # 网关 node:test 用例 + 三个界面自检脚本 + 文档版本号一致性检查,一条命令全跑
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
**版本号**:README 里的 release 徽章和 profile 依赖示例由
|
|
36
|
+
`scripts/sync-doc-version.mjs` 按 `package.json` 改写,挂在 `version` 生命周期
|
|
37
|
+
脚本上——`npm version patch` 会把改好的 README 带进同一个发版提交,不用手改
|
|
38
|
+
(徽章曾经一路卡在 v1.0.0 到 1.0.2)。`pnpm test` 里的 `--check` 会在漏同步时
|
|
39
|
+
把测试挂掉。文档里的锚点变了就更新那个脚本:找不到标记它直接非零退出,不会
|
|
40
|
+
默默通过。
|
|
41
|
+
|
|
35
42
|
## 深度文档
|
|
36
43
|
|
|
37
44
|
| 文件 | 内容 |
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
<p align="center">
|
|
5
5
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-0B7285?style=flat-square" alt="MIT"></a>
|
|
6
|
-
<img src="https://img.shields.io/badge/release-v1.0.
|
|
6
|
+
<img src="https://img.shields.io/badge/release-v1.0.3-5B4CF0?style=flat-square" alt="v1.0.3">
|
|
7
7
|
<img src="https://img.shields.io/badge/DSH-Web%20Profile-5B4CF0?style=flat-square" alt="DSH Web Profile">
|
|
8
8
|
</p>
|
|
9
9
|
|
|
@@ -27,7 +27,7 @@ dsh plugin add dsh-zen-remote
|
|
|
27
27
|
|
|
28
28
|
装完重启 `dsh web`,手机界面与网关一起生效,不需要再手写任何配置行。
|
|
29
29
|
|
|
30
|
-
> 兼容性:在 DSH `0.1.0-rc.
|
|
30
|
+
> 兼容性:在 DSH `0.1.0-rc.7`(web profile)上开发并实测,最后验证 2026-08-20。
|
|
31
31
|
|
|
32
32
|
卸载:`dsh plugin remove dsh-zen-remote`(或从 profile 的 `dependencies` 与 `bundles` 里删掉那两行),重启 `dsh web` 即恢复原状;要清掉配对数据再删 `~/.dsh/lan-gate-state.json` 与 `~/.dsh/lan-gate.config.json`。
|
|
33
33
|
|
|
@@ -39,7 +39,7 @@ dsh plugin add dsh-zen-remote
|
|
|
39
39
|
```jsonc
|
|
40
40
|
{
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"dsh-zen-remote": "^1.0.
|
|
42
|
+
"dsh-zen-remote": "^1.0.3" // 本地开发换成 "link:/path/to/dsh-zen-remote"
|
|
43
43
|
},
|
|
44
44
|
"dsh": { "profile": { "bundles": [
|
|
45
45
|
"@deepseek-ai/dsh-base",
|
|
@@ -220,11 +220,11 @@ open http://127.0.0.1:3088/lan-gate/admin
|
|
|
220
220
|
|
|
221
221
|
| 插件 | 移动端适配内容 | 实测版本 |
|
|
222
222
|
| --- | --- | --- |
|
|
223
|
-
| [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) | 会话页头部提供工作台入口按钮;面板变手机全宽抽屉并避让刘海安全区;底部居中的关闭按钮 | 0.
|
|
224
|
-
| [@nanmicoder/dsh-agent-teams](https://github.com/NanmiCoder/dsh-agent-teams) | AgentTeams 活动浮层挪到会话头部下方(原位置压住头部按钮)、会话列表页自动隐藏;子代理会话头部保留可点的父会话标题,一键切回主会话 | 0.1.
|
|
225
|
-
| [dsh-usage-stats](https://github.com/Ychris12138/dsh-usage-stats) | 用量与余额入口收进主屏 chips 行 |
|
|
226
|
-
| [@opendsh/dsh-plugin-scheduled-tasks](https://github.com/Ceelog/dsh-plugins) | 定时任务入口收进主屏 chips 行 | 0.2.
|
|
227
|
-
| dsh-at-file | @文件引用,配合附件上传的 `@` 路径引用使用 |
|
|
223
|
+
| [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) | 会话页头部提供工作台入口按钮;面板变手机全宽抽屉并避让刘海安全区;底部居中的关闭按钮 | 0.14.0 |
|
|
224
|
+
| [@nanmicoder/dsh-agent-teams](https://github.com/NanmiCoder/dsh-agent-teams) | AgentTeams 活动浮层挪到会话头部下方(原位置压住头部按钮)、会话列表页自动隐藏;子代理会话头部保留可点的父会话标题,一键切回主会话 | 0.1.7 |
|
|
225
|
+
| [dsh-usage-stats](https://github.com/Ychris12138/dsh-usage-stats) | 用量与余额入口收进主屏 chips 行 | 0.2.5 |
|
|
226
|
+
| [@opendsh/dsh-plugin-scheduled-tasks](https://github.com/Ceelog/dsh-plugins) | 定时任务入口收进主屏 chips 行 | 0.2.2 |
|
|
227
|
+
| dsh-at-file | @文件引用,配合附件上传的 `@` 路径引用使用 | 0.6.5 |
|
|
228
228
|
| [dsh-vision-toolkit](https://www.npmjs.com/package/@anionex/dsh-vision-toolkit) | 图像 Q&A/OCR,配合手机端附件上传使用 | — |
|
|
229
229
|
| [dsh-web-ui 全家桶](https://www.npmjs.com/package/@linxin666/dsh-web-ui-all) | 沿用上游 dsh-web-mobile 的兼容规则(文件树 / 预览浮层限宽居中等) | — |
|
|
230
230
|
|
package/docs/interface.md
CHANGED
|
@@ -42,7 +42,8 @@ v1.0.0(fork 自 [mexiaosqwq/dsh-web-mobile](https://github.com/mexiaosqwq/dsh-
|
|
|
42
42
|
|
|
43
43
|
- 底排控件全部图标化(附件 · 权限 · 模型 · 上下文环 · 发送),不再是挤在一起的文字按钮;
|
|
44
44
|
- 权限、模型这两个官方弹出菜单被 CSS 整成从底部升起的 sheet 样式(结构还是官方的,只换了皮肤);
|
|
45
|
-
-
|
|
45
|
+
- `conversation.input.dock` 里的入口(git 分支等)压成一行可横滚的 mini chips,挪到输入卡外上方;
|
|
46
|
+
- 例外是 DSH 原生的待办卡片(`data-testid="todo-panel"`):它不是 chip 而是可就地展开的卡片,因此单独豁免出 26px 胶囊笼子,保持整行宽度与自然高度——展开后的列表由官方自己限高 180px 并内部滚动,不会顶到输入框;
|
|
46
47
|
- composer 顶部不画分割线,改用渐变 mask——消息滚动到顶部/底部时自然淡入淡出,比一条硬边界更贴近原生 app。
|
|
47
48
|
|
|
48
49
|
### 会话信息卡
|
|
@@ -93,6 +94,25 @@ composer 最左的回形针打开的是**手机本地**的文件选择器(iOS
|
|
|
93
94
|
|
|
94
95
|
## 更新日志
|
|
95
96
|
|
|
97
|
+
> 小节标题用的是手机端界面的代次(v2.0 = app 化重写),与 npm 包版本号(当前
|
|
98
|
+
> `1.0.2`)不是同一条线。
|
|
99
|
+
|
|
100
|
+
### 未发布
|
|
101
|
+
|
|
102
|
+
**修复**
|
|
103
|
+
|
|
104
|
+
- DSH 原生待办卡片在手机端点了没反应:`conversation.input.dock` 的 mini chip
|
|
105
|
+
规则给槽内所有条目套了 `max-height: 26px` + `overflow: hidden`,而官方待办
|
|
106
|
+
是就地展开的卡片而非 chip。展开动作其实一直是成功的(`aria-expanded` 翻了、
|
|
107
|
+
8 行待办以 216px 挂载完毕),只是整张卡被剪在 26px 里,看上去像点不动。已按
|
|
108
|
+
`data-testid="todo-panel"` 单独豁免(2026-08-20 真机视口实测)。
|
|
109
|
+
|
|
110
|
+
**内部**
|
|
111
|
+
|
|
112
|
+
- 构建与实测基准从 DSH `0.1.0-rc.6` 升到 `0.1.0-rc.7`(peer 与 dev 依赖同步);
|
|
113
|
+
- README 的 release 徽章与依赖示例改由 `scripts/sync-doc-version.mjs` 跟着
|
|
114
|
+
`package.json` 走,挂在 `version` 生命周期脚本上,`pnpm test` 里带一致性检查。
|
|
115
|
+
|
|
96
116
|
### v2.0.0
|
|
97
117
|
|
|
98
118
|
**新增**
|
|
@@ -161,11 +181,11 @@ composer 最左的回形针打开的是**手机本地**的文件选择器(iOS
|
|
|
161
181
|
|
|
162
182
|
## 兼容插件
|
|
163
183
|
|
|
164
|
-
- [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar)(移动端全宽抽屉,本插件在会话页头部给它留入口位)——本机实测 **0.
|
|
165
|
-
- [@nanmicoder/dsh-agent-teams](https://github.com/NanmiCoder/dsh-agent-teams)(AgentTeams 活动浮层:手机端挪到会话头部下方、避开安全区,会话列表页隐藏;子代理会话头部保留可点的父会话面包屑用于切回)——本机实测 **0.1.
|
|
166
|
-
- [dsh-usage-stats](https://github.com/Ychris12138/dsh-usage-stats)(用量与余额,主屏 chips 行可直接打开)
|
|
167
|
-
- [@opendsh/dsh-plugin-scheduled-tasks](https://github.com/Ceelog/dsh-plugins)(定时任务,主屏 chips 行自动收割入口)——本机实测 **0.2.
|
|
168
|
-
- dsh-at-file(@文件引用)
|
|
184
|
+
- [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar)(移动端全宽抽屉,本插件在会话页头部给它留入口位)——本机实测 **0.14.0**
|
|
185
|
+
- [@nanmicoder/dsh-agent-teams](https://github.com/NanmiCoder/dsh-agent-teams)(AgentTeams 活动浮层:手机端挪到会话头部下方、避开安全区,会话列表页隐藏;子代理会话头部保留可点的父会话面包屑用于切回)——本机实测 **0.1.7**
|
|
186
|
+
- [dsh-usage-stats](https://github.com/Ychris12138/dsh-usage-stats)(用量与余额,主屏 chips 行可直接打开)——本机实测 **0.2.5**
|
|
187
|
+
- [@opendsh/dsh-plugin-scheduled-tasks](https://github.com/Ceelog/dsh-plugins)(定时任务,主屏 chips 行自动收割入口)——本机实测 **0.2.2**
|
|
188
|
+
- dsh-at-file(@文件引用)——本机实测 **0.6.5**
|
|
169
189
|
- [dsh-vision-toolkit](https://www.npmjs.com/package/@anionex/dsh-vision-toolkit)(图像 Q&A/OCR,配合手机端附件上传使用)
|
|
170
190
|
- [dsh-web-ui 全家桶](https://www.npmjs.com/package/@linxin666/dsh-web-ui-all)(文件树 / 预览 / 任务看板 / SSH / 宠物 / 会话统计 / 远程配对 / 设置)——沿用上游兼容规则,本次未扩展
|
|
171
191
|
|
package/lib/client.js
CHANGED
|
@@ -3853,6 +3853,15 @@ exports.HEADER_CSS = `/* ---------- session header five-piece reflow (< 768px) -
|
|
|
3853
3853
|
[data-phase] header [data-slot="conversation.session.header.utilities"] > * {
|
|
3854
3854
|
display: none !important;
|
|
3855
3855
|
}
|
|
3856
|
+
/* Native DSH task surfaces are real header actions, not phone-shell
|
|
3857
|
+
controls. Keep their roots visible so the background-task and subagent
|
|
3858
|
+
menus can still be opened on a phone. Restore the root's containing block
|
|
3859
|
+
as well: both native menus are absolutely positioned from this element,
|
|
3860
|
+
and layout.css.ts's compact-header rule otherwise changes it to static. */
|
|
3861
|
+
[data-phase] header [data-slot="conversation.session.header.actions"] > [class$="_root"]:has(> button[class$="_trigger"]) {
|
|
3862
|
+
display: block !important;
|
|
3863
|
+
position: relative !important;
|
|
3864
|
+
}
|
|
3856
3865
|
/* The agent-preset mode badge specifically survives the blanket hide
|
|
3857
3866
|
above: layout.css.ts's shared <=1023px block targets it directly
|
|
3858
3867
|
(header [class$="_label"]:has(> svg) { display: block !important }),
|
|
@@ -4278,6 +4287,31 @@ exports.COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- */
|
|
|
4278
4287
|
padding: 0 10px !important;
|
|
4279
4288
|
font-size: 11.5px !important;
|
|
4280
4289
|
}
|
|
4290
|
+
/* --- 7a. the native to-do card opts OUT of the 26px pill cage ---
|
|
4291
|
+
conversation.input.dock is not only a chip rail: DSH's own TodoPanel
|
|
4292
|
+
(dsh-client-ui-conversation) registers here, and it is an expandable
|
|
4293
|
+
card, not a pill — tapping its header renders the to-do list INSIDE
|
|
4294
|
+
itself. The cage's max-height:26px + overflow:hidden left that list
|
|
4295
|
+
rendered but clipped to nothing, so on a phone the bar looked dead:
|
|
4296
|
+
aria-expanded flipped, 8 rows mounted at 216px, and the card still
|
|
4297
|
+
painted 26px tall (measured live, 2026-08-20). Give it the official
|
|
4298
|
+
geometry back — full row, natural height, 12px radius. Its list caps
|
|
4299
|
+
itself at 180px with its own scroller, so this cannot run into the
|
|
4300
|
+
composer. Anchored on the official data-testid, a stable contract
|
|
4301
|
+
marker rather than a hashed class name. */
|
|
4302
|
+
[data-slot="conversation.input.dock"] > [data-testid="todo-panel"] {
|
|
4303
|
+
flex: 1 1 auto !important;
|
|
4304
|
+
min-width: 0 !important;
|
|
4305
|
+
max-width: none !important;
|
|
4306
|
+
min-height: 0 !important;
|
|
4307
|
+
max-height: none !important;
|
|
4308
|
+
overflow: visible !important;
|
|
4309
|
+
border-radius: 12px !important;
|
|
4310
|
+
/* The cage shrinks dock text to 11.5px/18px; the card's rows inherit it
|
|
4311
|
+
and turn cramped. Restore the official 13px body scale. */
|
|
4312
|
+
font-size: 13px !important;
|
|
4313
|
+
line-height: normal !important;
|
|
4314
|
+
}
|
|
4281
4315
|
|
|
4282
4316
|
/* --- 7b. attachment chips (S7.1) ---
|
|
4283
4317
|
Our own dock entry opts OUT of the 26px pill cage above: an image chip is
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const COMPOSER_CSS = "/* ---------- phone composer (< 768px) ---------- */\n\n/* The file picker the attachment button drives (S7) \u2014 hidden at EVERY width,\n deliberately OUTSIDE the phone media block below. A bare <input type=\"file\">\n renders as a native \"Choose Files\" control, and the slot wrapper around it is\n `display: contents`, so an un-hidden one becomes a flex item of the official\n tool row. Scoping this to < 768px once put that control in the DESKTOP\n composer (caught on a live deploy, 2026-08-17): the button that drives it is\n phone-only, but the input it drives is in the DOM at all widths. */\n[data-mobile-nav=\"attach-picker\"] {\n display: none !important;\n}\n\n@media (max-width: 767px) {\n /* --- 1. flatten the two official groups ---\n `_tools` and `_trailing` only exist to cluster controls left/right.\n display:contents dissolves them into `_row`, so the six leaf controls\n become siblings in one flex line and `order` alone drives the layout:\n [attach \u00B7 + \u00B7 permission \u00B7 model] \u2026elastic gap\u2026 [context ring \u00B7 send]. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] {\n justify-content: flex-start !important;\n gap: 5px !important;\n padding: 2px 8px 8px !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] {\n display: contents !important;\n }\n\n /* --- 2. the running order ---\n The attachment button (S3 placeholder, S7 wires it up) sits leftmost;\n the official \"+\" command menu keeps its seat right next to it, where\n the two \"insert something\" affordances read as one cluster. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] [data-mobile-nav=\"attach\"] {\n order: 1 !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_add\"] {\n order: 2 !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] {\n order: 3 !important;\n flex: 0 1 auto !important;\n min-width: 0 !important;\n gap: 6px !important;\n }\n /* The model seat carries the elastic gap: everything after it is pushed to\n the right edge, so no spacer element is needed. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] {\n order: 4 !important;\n flex: 0 1 auto !important;\n min-width: 0 !important;\n margin-right: auto !important;\n }\n /* Third-party input.right entries park next to the ring rather than\n landing at order 0 (= far left) once the groups are flattened. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.right\"] > * {\n order: 5 !important;\n }\n /* ContextMeter \u2014 the only `span` ending in _root inside the row. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > span[class$=\"_root\"] {\n order: 6 !important;\n flex: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_primary\"] {\n order: 7 !important;\n }\n\n /* --- 3. permission + model as icon-only pills (real-device round 2, 2026-08-17) ---\n S3's icon-and-label capsules read as noise on an actual phone \u2014 there\n is no room to usefully show a preset name or a model id, so the label\n text is now hidden outright and both triggers collapse to a plain\n ~44x30 icon button. This AGREES with (rather than fights) the official\n container query (`@container (width <= 460px) { .trigger:has(.triggerIcon)\n .triggerLabel { display: none } }`) that S3 had to override \u2014 no need\n to override it back. Accessible name is unaffected: both official\n triggers already ship a descriptive aria-label independent of the\n visible text (PermissionSelect: t('input.accessMode', {name}); the\n model trigger: t('trigger.aria'/'trigger.ariaEffort')) \u2014 verified live\n in dsh-client-ui-conversation / dsh-client-ui-model-selection lib/\n client.js, 2026-08-17 \u2014 so hiding the label costs nothing for screen\n readers. The permission trigger only renders an icon for the\n \"read-only\" / \"workspace-write\" presets (permissionGlyphs in\n dsh-client-ui-conversation) \u2014 \"Full access\" and any host-configured\n preset name fall back to chevron-only, a known gap in the official\n markup this plugin cannot fill without inventing new icon meaning.\n The model trigger never renders an icon at all (only label + optional\n effort text + chevron), so its ::before below draws one from a\n primitives icon path (Sparkle \u2014 the closest existing \"model\" glyph) as\n a CSS-only pseudo-element: it survives React re-renders for free\n (unlike a DOM-injected node, which would need a MutationObserver, see\n the preview-full-toggle pitfall in AGENTS.md) and does not touch\n accessible-name computation (empty generated content). S3's\n rtl-ellipsis trick on the model label is simply inert under\n display:none now; left alone rather than unpicked. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [class$=\"_triggerLabel\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] > [class$=\"_triggerLabel\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] > [class$=\"_triggerEffort\"] {\n display: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] button[class$=\"_trigger\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] {\n background: var(--dsw-specific-selector, rgba(127, 127, 127, .12)) !important;\n width: 44px !important;\n height: 30px !important;\n max-width: none !important;\n min-width: 0 !important;\n padding: 0 !important;\n gap: 2px !important;\n justify-content: center !important;\n border-radius: 999px !important;\n touch-action: manipulation !important;\n }\n /* PermissionSelect wraps its trigger in the Menu primitive's root span,\n which must shrink to the icon button's fixed width. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] > span:has(> button[class$=\"_trigger\"]) {\n flex: 0 0 auto !important;\n min-width: 0 !important;\n }\n /* ic_ds_sparkle_16 (@deepseek-ai/dsh-client-ui-primitives IconSparkle16\n path, copied verbatim) as a mask so it inherits currentColor like every\n other icon in the row \u2014 the model trigger has no official icon slot to\n hook into. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"]::before {\n content: '';\n width: 16px;\n height: 16px;\n flex: none;\n background: currentColor;\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6.1 3.1Q6.6 7.8 11.3 8.3Q6.6 8.8 6.1 13.5Q5.6 8.8 0.9 8.3Q5.6 7.8 6.1 3.1Z'/%3E%3Cpath d='M11.9 1Q12.2 3.7 14.9 4Q12.2 4.3 11.9 7Q11.6 4.3 8.9 4Q11.6 3.7 11.9 1Z'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6.1 3.1Q6.6 7.8 11.3 8.3Q6.6 8.8 6.1 13.5Q5.6 8.8 0.9 8.3Q5.6 7.8 6.1 3.1Z'/%3E%3Cpath d='M11.9 1Q12.2 3.7 14.9 4Q12.2 4.3 11.9 7Q11.6 4.3 8.9 4Q11.6 3.7 11.9 1Z'/%3E%3C/svg%3E\");\n -webkit-mask-size: contain;\n mask-size: contain;\n -webkit-mask-repeat: no-repeat;\n mask-repeat: no-repeat;\n }\n\n /* --- 4. both menus become bottom sheets ---\n The permission menu is the Menu primitive (role=menu, absolute, side=top)\n and the model menu is ModelSelect's own `_menu` (absolute, bottom+right).\n Neither has a transformed ancestor between it and the viewport, so\n position:fixed re-anchors both to the screen edge. Only the shell moves \u2014\n the items, the two-level model panes and every selection handler stay\n official. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"] {\n position: fixed !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n top: auto !important;\n transform: none !important;\n width: 100% !important;\n min-width: 0 !important;\n max-width: none !important;\n max-height: min(70dvh, 520px) !important;\n box-sizing: border-box !important;\n border-radius: 16px 16px 0 0 !important;\n border-bottom: none !important;\n padding: 8px 8px calc(8px + var(--mnav-sab)) !important;\n z-index: 60 !important;\n box-shadow: 0 -8px 32px rgba(0, 0, 0, .18) !important;\n }\n /* 44pt+ rows in both sheets (Menu items, model options, and the model\n sheet's two root cells that drill into the model / effort panes). */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"] [role=\"menuitem\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"] [class$=\"_option\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"] [class$=\"_cell\"] {\n min-height: 48px !important;\n border-radius: 12px !important;\n font-size: 15px !important;\n }\n /* --- 4b. the official scroll-to-bottom button must not poke through the\n sheet (real-device follow-up, 2026-08-17) ---\n ChatView's own \"jump to latest\" button (aria-label t(\"chat.toBottom\"))\n is `position: sticky; z-index: 8` inside the message column, not\n `position: fixed` \u2014 it never escapes to the same top-level stacking\n context our sheets get promoted to, so raising the sheet's z-index\n further does nothing (measured live: it still rendered on top at\n z-index 60 vs 8). Rather than chase engine-specific stacking-context\n semantics (Chromium and WebKit do not always agree here \u2014 see AGENTS.md\n CDP/document.hidden lesson for another instance of that), this hides\n the button outright while either sheet is open and lets it reappear on\n close: a plain `display: none` behind a live `:has()` read is correct\n regardless of which stacking rules the engine happens to apply.\n Selector: no hashed classes (dsh-client-ui-conversation lib/client.js,\n verified 2026-08-17) \u2014 `data-chat-flow` is the one stable attribute on\n the message column, and the button's wrapper is its only sibling\n (ChatView only ever renders the column and, conditionally, this one\n slot), so the adjacent-sibling combinator pins it precisely. */\n body:has([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"]) [data-chat-flow] + div,\n body:has([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"]) [data-chat-flow] + div {\n display: none !important;\n }\n /* --- 5. input box: two lines minimum, five lines maximum ---\n The official autosizer drives the box off the hidden mirror's height, so\n a min-height on the mirror IS the min-height of the field; the scroll cap\n rides the official custom property. 52px = 2 x 24px line box + 4px pad,\n 124px = 5 lines. The hero card keeps its own one-line collapse (misc.css\n pins _scroll/_grow while the placeholder shows). */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] [class$=\"_mirror\"] {\n min-height: 52px !important;\n }\n [class$=\"_composerSeat\"] {\n --dsh-composer-text-max-height: 124px;\n }\n\n /* --- 6. no divider above OR below the message list ---\n Instead of a rule the messages butt against, the message scroller\n fades out over its last 26px (S3, no divider above the composer) AND\n fades in over its first 20px (S3.1 real-device round 2: the header's\n own bottom line is removed too, styles/header.css.ts's\n `header::after` rule). The mask lives on the scroll body (NOT on the\n header or the composer): a mask clips everything it paints, and both\n surfaces host position:fixed children (composer's permission/model\n sheets, any future header overlay) that must not be clipped.\n `mask-image` can only be declared once per element, so both fades are\n ONE linear-gradient rather than two separate declarations (the second\n would silently replace the first) \u2014 this is the merge of what used to\n be S3's bottom-only mask. */\n [class$=\"_scrollBody\"] {\n -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 20px, #000 calc(100% - 26px), transparent 100%);\n mask-image: linear-gradient(to bottom, transparent 0, #000 20px, #000 calc(100% - 26px), transparent 100%);\n }\n [class$=\"_composerSeat\"],\n [class$=\"_composerStack\"] {\n border-top: none !important;\n }\n\n /* --- 7. dock entries become mini chips above the input card ---\n conversation.input.dock is display:contents (inline style), so its\n entries are stacked rows of the composer column. Forcing the slot itself\n to flex turns them into ONE scrollable chip line outside and above the\n card. Written against the slot, not against any particular plugin's chip\n (the git branch chip and the todo panel are third-party and may not be\n installed at all). */\n [data-slot=\"conversation.input.dock\"] {\n display: flex !important;\n flex-flow: row nowrap !important;\n align-items: center !important;\n gap: 6px !important;\n min-width: 0 !important;\n margin: 0 16px !important;\n overflow-x: auto !important;\n overflow-y: hidden !important;\n scrollbar-width: none !important;\n }\n [data-slot=\"conversation.input.dock\"]::-webkit-scrollbar {\n display: none !important;\n }\n [data-slot=\"conversation.input.dock\"] > * {\n flex: 0 0 auto !important;\n max-width: 70% !important;\n min-height: 26px !important;\n max-height: 26px !important;\n border-radius: 999px !important;\n font-size: 11.5px !important;\n line-height: 18px !important;\n overflow: hidden !important;\n }\n /* The git-graph chip carries a 34px tablet target from misc.css; that\n selector is more specific, so restate it at the phone breakpoint. */\n [data-slot=\"conversation.input.dock\"] [data-gitgraph-chip-anchor] [data-gitgraph-chip] {\n min-height: 26px !important;\n padding: 0 10px !important;\n font-size: 11.5px !important;\n }\n\n /* --- 7b. attachment chips (S7.1) ---\n Our own dock entry opts OUT of the 26px pill cage above: an image chip is\n a 48px tile. The row sits tight under the card's top edge, so the chips\n read as part of the composer rather than as a floating strip. */\n [data-slot=\"conversation.input.dock\"] > [data-mobile-nav=\"attach-chips\"] {\n display: flex !important;\n align-items: center !important;\n gap: 6px !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n border-radius: 0 !important;\n overflow: visible !important;\n margin-bottom: -2px;\n }\n [data-mobile-nav=\"attach-chip\"] {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 auto;\n max-width: 62vw;\n background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12));\n color: var(--dsw-alias-label-primary, inherit);\n }\n [data-mobile-nav=\"attach-chip\"][data-kind=\"file\"] {\n height: 26px;\n padding: 0 4px 0 8px;\n border-radius: 999px;\n font-size: 11.5px;\n line-height: 18px;\n }\n /* An image chip is the thumbnail: no name line, the filename lives in the\n title attribute (and in the draft text right below). */\n [data-mobile-nav=\"attach-chip\"][data-kind=\"image\"] {\n width: 48px;\n height: 48px;\n padding: 0;\n border-radius: 10px;\n overflow: hidden;\n }\n [data-mobile-nav=\"attach-chip-art\"] {\n position: relative;\n display: grid;\n place-items: center;\n flex: none;\n overflow: hidden;\n }\n [data-kind=\"file\"] > [data-mobile-nav=\"attach-chip-art\"] {\n width: 16px;\n height: 16px;\n opacity: .7;\n }\n [data-kind=\"image\"] > [data-mobile-nav=\"attach-chip-art\"] {\n width: 100%;\n height: 100%;\n }\n /* The <img> is stacked over the paperclip, not swapped for it: a format the\n engine cannot decode (HEIC outside WebKit) simply paints nothing and the\n icon shows through \u2014 no onError state to carry. */\n [data-mobile-nav=\"attach-chip-art\"] img {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n background: var(--dsw-alias-bg-layer-2, rgba(127, 127, 127, .18));\n }\n [data-mobile-nav=\"attach-chip-name\"] {\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n }\n [data-mobile-nav=\"attach-chip-remove\"] {\n display: grid;\n place-items: center;\n flex: none;\n width: 18px;\n height: 18px;\n padding: 0;\n border: none;\n border-radius: 999px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n touch-action: manipulation;\n }\n /* On a tile the \u00D7 floats in the corner over the picture, so it needs its own\n ground to stay legible against an arbitrary photo. */\n [data-kind=\"image\"] > [data-mobile-nav=\"attach-chip-remove\"] {\n position: absolute;\n top: 2px;\n right: 2px;\n background: rgba(0, 0, 0, .55);\n color: #fff;\n }\n [data-mobile-nav=\"attach-chip-remove\"]:active {\n transform: scale(.9);\n }\n\n /* --- 8. the official stats strip leaves the composer ---\n Its data moves into the session info card (S4). The row is the composer\n dock's own `_root` entry; the slot itself stays live for later entries. */\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"] {\n display: none !important;\n }\n\n /* --- 9. the attachment button (S7; its file input is hidden at the top of\n this file, at every width) --- */\n [data-mobile-nav=\"attach\"] {\n position: relative;\n width: 28px !important;\n height: 28px !important;\n flex: none !important;\n display: grid !important;\n place-items: center !important;\n padding: 0 !important;\n border: none !important;\n border-radius: 999px !important;\n background: var(--dsw-specific-selector, rgba(127, 127, 127, .12));\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n }\n [data-mobile-nav=\"attach\"]:active {\n transform: scale(.94);\n transition: transform .12s;\n }\n /* Busy: a ring sweeps around the paperclip. Drawn with a conic gradient in\n a pseudo-element rather than a spinner node, so the official React tree\n around us has nothing extra to re-render (same reasoning as the model\n pill's mask icon, S3.1). */\n [data-mobile-nav=\"attach\"][data-busy]::after {\n content: \"\";\n position: absolute;\n inset: -2px;\n border-radius: 999px;\n background: conic-gradient(from 0deg, transparent 0 65%, currentColor 100%);\n mask: radial-gradient(closest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));\n -webkit-mask: radial-gradient(closest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));\n animation: mnav-attach-spin .9s linear infinite;\n pointer-events: none;\n }\n @keyframes mnav-attach-spin {\n to { transform: rotate(1turn); }\n }\n @media (prefers-reduced-motion: reduce) {\n [data-mobile-nav=\"attach\"][data-busy]::after {\n animation-duration: 3s;\n }\n }\n /* Failure note: one line above the button, tap-to-dismiss. Sits on the\n composer card (z 2) rather than in an overlay layer \u2014 nothing here needs\n to clear the permission/model sheets. */\n [data-mobile-nav=\"attach-error\"] {\n position: absolute;\n bottom: calc(100% + 6px);\n left: 0;\n z-index: 2;\n max-width: 62vw;\n padding: 5px 8px;\n border-radius: 8px;\n font-size: 12px;\n line-height: 1.3;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, .82));\n color: var(--dsw-alias-label-primary, #fff);\n box-shadow: 0 2px 8px rgba(0, 0, 0, .24);\n }\n\n /* --- 9. home-indicator clearance (S4.1, 2026-08-17) ---\n Owned HERE, not by the gateway half. That plugin used to carry\n [data-slot=\"conversation.composer\"] { padding-bottom: max(env(safe-area-inset-bottom), 8px) }\n in both pwa/app.css and the gateway's inline DEVICE_CSS. Both were\n INERT and had always been: that slot element is `display: contents`\n (measured 2026-08-17 \u2014 the slot wrapper generates no box, so padding on\n it is discarded), which is why raising it never moved anything. The rule\n is deleted on the PWA side and restated here on an element that actually\n lays out, reading --mnav-sab so ?mobile-nav-inset=54,34 can regress it\n off-device (env() is hard 0 on desktop \u2014 the whole reason S2.1 exists).\n\n Target: the card should look EQUALLY inset on all four sides. This same\n element carries the card's side inset (`padding: 0 16px` officially,\n measured 16px left and 16px right at 390px), so the bottom gap is simply\n capped at that same 16px. Clearing the FULL 34px inset \u2014 the naive\n max(sab, 8px) \u2014 is what read as \"too thick\": it is more than double the\n side margin, so the card looked shoved up off the bottom edge.\n\n clamp() says all three requirements at once:\n - floor 8px -> a device with no home indicator (sab: 0) keeps the\n official 8px exactly, so this is a strict no-op there;\n - track sab -> a shallower inset than 16px is honoured as-is;\n - cap 16px -> a full-size indicator (sab: 34) lands on 16px, equal\n to the side inset, which is the look being asked for.\n Keep the 16px in step with the side padding above if that ever changes;\n that equality IS the spec here, not a coincidence. */\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] {\n padding-bottom: clamp(8px, var(--mnav-sab), 16px) !important;\n }\n}\n\n/* The attachment button and its preview row only exist for the phone shell.\n Both render at every width (the slots are not breakpoint-aware), so both\n need hiding here \u2014 the dock-row rules that shape the chips live in the\n < 768px block and would otherwise leave a bare, unstyled chip line in the\n desktop composer. */\n@media (min-width: 768px) {\n [data-mobile-nav=\"attach\"],\n [data-mobile-nav=\"attach-chips\"] {\n display: none !important;\n }\n}\n";
|
|
1
|
+
export declare const COMPOSER_CSS = "/* ---------- phone composer (< 768px) ---------- */\n\n/* The file picker the attachment button drives (S7) \u2014 hidden at EVERY width,\n deliberately OUTSIDE the phone media block below. A bare <input type=\"file\">\n renders as a native \"Choose Files\" control, and the slot wrapper around it is\n `display: contents`, so an un-hidden one becomes a flex item of the official\n tool row. Scoping this to < 768px once put that control in the DESKTOP\n composer (caught on a live deploy, 2026-08-17): the button that drives it is\n phone-only, but the input it drives is in the DOM at all widths. */\n[data-mobile-nav=\"attach-picker\"] {\n display: none !important;\n}\n\n@media (max-width: 767px) {\n /* --- 1. flatten the two official groups ---\n `_tools` and `_trailing` only exist to cluster controls left/right.\n display:contents dissolves them into `_row`, so the six leaf controls\n become siblings in one flex line and `order` alone drives the layout:\n [attach \u00B7 + \u00B7 permission \u00B7 model] \u2026elastic gap\u2026 [context ring \u00B7 send]. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] {\n justify-content: flex-start !important;\n gap: 5px !important;\n padding: 2px 8px 8px !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] {\n display: contents !important;\n }\n\n /* --- 2. the running order ---\n The attachment button (S3 placeholder, S7 wires it up) sits leftmost;\n the official \"+\" command menu keeps its seat right next to it, where\n the two \"insert something\" affordances read as one cluster. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] [data-mobile-nav=\"attach\"] {\n order: 1 !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_add\"] {\n order: 2 !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] {\n order: 3 !important;\n flex: 0 1 auto !important;\n min-width: 0 !important;\n gap: 6px !important;\n }\n /* The model seat carries the elastic gap: everything after it is pushed to\n the right edge, so no spacer element is needed. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] {\n order: 4 !important;\n flex: 0 1 auto !important;\n min-width: 0 !important;\n margin-right: auto !important;\n }\n /* Third-party input.right entries park next to the ring rather than\n landing at order 0 (= far left) once the groups are flattened. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.right\"] > * {\n order: 5 !important;\n }\n /* ContextMeter \u2014 the only `span` ending in _root inside the row. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > span[class$=\"_root\"] {\n order: 6 !important;\n flex: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_primary\"] {\n order: 7 !important;\n }\n\n /* --- 3. permission + model as icon-only pills (real-device round 2, 2026-08-17) ---\n S3's icon-and-label capsules read as noise on an actual phone \u2014 there\n is no room to usefully show a preset name or a model id, so the label\n text is now hidden outright and both triggers collapse to a plain\n ~44x30 icon button. This AGREES with (rather than fights) the official\n container query (`@container (width <= 460px) { .trigger:has(.triggerIcon)\n .triggerLabel { display: none } }`) that S3 had to override \u2014 no need\n to override it back. Accessible name is unaffected: both official\n triggers already ship a descriptive aria-label independent of the\n visible text (PermissionSelect: t('input.accessMode', {name}); the\n model trigger: t('trigger.aria'/'trigger.ariaEffort')) \u2014 verified live\n in dsh-client-ui-conversation / dsh-client-ui-model-selection lib/\n client.js, 2026-08-17 \u2014 so hiding the label costs nothing for screen\n readers. The permission trigger only renders an icon for the\n \"read-only\" / \"workspace-write\" presets (permissionGlyphs in\n dsh-client-ui-conversation) \u2014 \"Full access\" and any host-configured\n preset name fall back to chevron-only, a known gap in the official\n markup this plugin cannot fill without inventing new icon meaning.\n The model trigger never renders an icon at all (only label + optional\n effort text + chevron), so its ::before below draws one from a\n primitives icon path (Sparkle \u2014 the closest existing \"model\" glyph) as\n a CSS-only pseudo-element: it survives React re-renders for free\n (unlike a DOM-injected node, which would need a MutationObserver, see\n the preview-full-toggle pitfall in AGENTS.md) and does not touch\n accessible-name computation (empty generated content). S3's\n rtl-ellipsis trick on the model label is simply inert under\n display:none now; left alone rather than unpicked. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [class$=\"_triggerLabel\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] > [class$=\"_triggerLabel\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] > [class$=\"_triggerEffort\"] {\n display: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] button[class$=\"_trigger\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] {\n background: var(--dsw-specific-selector, rgba(127, 127, 127, .12)) !important;\n width: 44px !important;\n height: 30px !important;\n max-width: none !important;\n min-width: 0 !important;\n padding: 0 !important;\n gap: 2px !important;\n justify-content: center !important;\n border-radius: 999px !important;\n touch-action: manipulation !important;\n }\n /* PermissionSelect wraps its trigger in the Menu primitive's root span,\n which must shrink to the icon button's fixed width. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] > span:has(> button[class$=\"_trigger\"]) {\n flex: 0 0 auto !important;\n min-width: 0 !important;\n }\n /* ic_ds_sparkle_16 (@deepseek-ai/dsh-client-ui-primitives IconSparkle16\n path, copied verbatim) as a mask so it inherits currentColor like every\n other icon in the row \u2014 the model trigger has no official icon slot to\n hook into. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"]::before {\n content: '';\n width: 16px;\n height: 16px;\n flex: none;\n background: currentColor;\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6.1 3.1Q6.6 7.8 11.3 8.3Q6.6 8.8 6.1 13.5Q5.6 8.8 0.9 8.3Q5.6 7.8 6.1 3.1Z'/%3E%3Cpath d='M11.9 1Q12.2 3.7 14.9 4Q12.2 4.3 11.9 7Q11.6 4.3 8.9 4Q11.6 3.7 11.9 1Z'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6.1 3.1Q6.6 7.8 11.3 8.3Q6.6 8.8 6.1 13.5Q5.6 8.8 0.9 8.3Q5.6 7.8 6.1 3.1Z'/%3E%3Cpath d='M11.9 1Q12.2 3.7 14.9 4Q12.2 4.3 11.9 7Q11.6 4.3 8.9 4Q11.6 3.7 11.9 1Z'/%3E%3C/svg%3E\");\n -webkit-mask-size: contain;\n mask-size: contain;\n -webkit-mask-repeat: no-repeat;\n mask-repeat: no-repeat;\n }\n\n /* --- 4. both menus become bottom sheets ---\n The permission menu is the Menu primitive (role=menu, absolute, side=top)\n and the model menu is ModelSelect's own `_menu` (absolute, bottom+right).\n Neither has a transformed ancestor between it and the viewport, so\n position:fixed re-anchors both to the screen edge. Only the shell moves \u2014\n the items, the two-level model panes and every selection handler stay\n official. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"] {\n position: fixed !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n top: auto !important;\n transform: none !important;\n width: 100% !important;\n min-width: 0 !important;\n max-width: none !important;\n max-height: min(70dvh, 520px) !important;\n box-sizing: border-box !important;\n border-radius: 16px 16px 0 0 !important;\n border-bottom: none !important;\n padding: 8px 8px calc(8px + var(--mnav-sab)) !important;\n z-index: 60 !important;\n box-shadow: 0 -8px 32px rgba(0, 0, 0, .18) !important;\n }\n /* 44pt+ rows in both sheets (Menu items, model options, and the model\n sheet's two root cells that drill into the model / effort panes). */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"] [role=\"menuitem\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"] [class$=\"_option\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"] [class$=\"_cell\"] {\n min-height: 48px !important;\n border-radius: 12px !important;\n font-size: 15px !important;\n }\n /* --- 4b. the official scroll-to-bottom button must not poke through the\n sheet (real-device follow-up, 2026-08-17) ---\n ChatView's own \"jump to latest\" button (aria-label t(\"chat.toBottom\"))\n is `position: sticky; z-index: 8` inside the message column, not\n `position: fixed` \u2014 it never escapes to the same top-level stacking\n context our sheets get promoted to, so raising the sheet's z-index\n further does nothing (measured live: it still rendered on top at\n z-index 60 vs 8). Rather than chase engine-specific stacking-context\n semantics (Chromium and WebKit do not always agree here \u2014 see AGENTS.md\n CDP/document.hidden lesson for another instance of that), this hides\n the button outright while either sheet is open and lets it reappear on\n close: a plain `display: none` behind a live `:has()` read is correct\n regardless of which stacking rules the engine happens to apply.\n Selector: no hashed classes (dsh-client-ui-conversation lib/client.js,\n verified 2026-08-17) \u2014 `data-chat-flow` is the one stable attribute on\n the message column, and the button's wrapper is its only sibling\n (ChatView only ever renders the column and, conditionally, this one\n slot), so the adjacent-sibling combinator pins it precisely. */\n body:has([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"]) [data-chat-flow] + div,\n body:has([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"]) [data-chat-flow] + div {\n display: none !important;\n }\n /* --- 5. input box: two lines minimum, five lines maximum ---\n The official autosizer drives the box off the hidden mirror's height, so\n a min-height on the mirror IS the min-height of the field; the scroll cap\n rides the official custom property. 52px = 2 x 24px line box + 4px pad,\n 124px = 5 lines. The hero card keeps its own one-line collapse (misc.css\n pins _scroll/_grow while the placeholder shows). */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] [class$=\"_mirror\"] {\n min-height: 52px !important;\n }\n [class$=\"_composerSeat\"] {\n --dsh-composer-text-max-height: 124px;\n }\n\n /* --- 6. no divider above OR below the message list ---\n Instead of a rule the messages butt against, the message scroller\n fades out over its last 26px (S3, no divider above the composer) AND\n fades in over its first 20px (S3.1 real-device round 2: the header's\n own bottom line is removed too, styles/header.css.ts's\n `header::after` rule). The mask lives on the scroll body (NOT on the\n header or the composer): a mask clips everything it paints, and both\n surfaces host position:fixed children (composer's permission/model\n sheets, any future header overlay) that must not be clipped.\n `mask-image` can only be declared once per element, so both fades are\n ONE linear-gradient rather than two separate declarations (the second\n would silently replace the first) \u2014 this is the merge of what used to\n be S3's bottom-only mask. */\n [class$=\"_scrollBody\"] {\n -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 20px, #000 calc(100% - 26px), transparent 100%);\n mask-image: linear-gradient(to bottom, transparent 0, #000 20px, #000 calc(100% - 26px), transparent 100%);\n }\n [class$=\"_composerSeat\"],\n [class$=\"_composerStack\"] {\n border-top: none !important;\n }\n\n /* --- 7. dock entries become mini chips above the input card ---\n conversation.input.dock is display:contents (inline style), so its\n entries are stacked rows of the composer column. Forcing the slot itself\n to flex turns them into ONE scrollable chip line outside and above the\n card. Written against the slot, not against any particular plugin's chip\n (the git branch chip and the todo panel are third-party and may not be\n installed at all). */\n [data-slot=\"conversation.input.dock\"] {\n display: flex !important;\n flex-flow: row nowrap !important;\n align-items: center !important;\n gap: 6px !important;\n min-width: 0 !important;\n margin: 0 16px !important;\n overflow-x: auto !important;\n overflow-y: hidden !important;\n scrollbar-width: none !important;\n }\n [data-slot=\"conversation.input.dock\"]::-webkit-scrollbar {\n display: none !important;\n }\n [data-slot=\"conversation.input.dock\"] > * {\n flex: 0 0 auto !important;\n max-width: 70% !important;\n min-height: 26px !important;\n max-height: 26px !important;\n border-radius: 999px !important;\n font-size: 11.5px !important;\n line-height: 18px !important;\n overflow: hidden !important;\n }\n /* The git-graph chip carries a 34px tablet target from misc.css; that\n selector is more specific, so restate it at the phone breakpoint. */\n [data-slot=\"conversation.input.dock\"] [data-gitgraph-chip-anchor] [data-gitgraph-chip] {\n min-height: 26px !important;\n padding: 0 10px !important;\n font-size: 11.5px !important;\n }\n /* --- 7a. the native to-do card opts OUT of the 26px pill cage ---\n conversation.input.dock is not only a chip rail: DSH's own TodoPanel\n (dsh-client-ui-conversation) registers here, and it is an expandable\n card, not a pill \u2014 tapping its header renders the to-do list INSIDE\n itself. The cage's max-height:26px + overflow:hidden left that list\n rendered but clipped to nothing, so on a phone the bar looked dead:\n aria-expanded flipped, 8 rows mounted at 216px, and the card still\n painted 26px tall (measured live, 2026-08-20). Give it the official\n geometry back \u2014 full row, natural height, 12px radius. Its list caps\n itself at 180px with its own scroller, so this cannot run into the\n composer. Anchored on the official data-testid, a stable contract\n marker rather than a hashed class name. */\n [data-slot=\"conversation.input.dock\"] > [data-testid=\"todo-panel\"] {\n flex: 1 1 auto !important;\n min-width: 0 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n overflow: visible !important;\n border-radius: 12px !important;\n /* The cage shrinks dock text to 11.5px/18px; the card's rows inherit it\n and turn cramped. Restore the official 13px body scale. */\n font-size: 13px !important;\n line-height: normal !important;\n }\n\n /* --- 7b. attachment chips (S7.1) ---\n Our own dock entry opts OUT of the 26px pill cage above: an image chip is\n a 48px tile. The row sits tight under the card's top edge, so the chips\n read as part of the composer rather than as a floating strip. */\n [data-slot=\"conversation.input.dock\"] > [data-mobile-nav=\"attach-chips\"] {\n display: flex !important;\n align-items: center !important;\n gap: 6px !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n border-radius: 0 !important;\n overflow: visible !important;\n margin-bottom: -2px;\n }\n [data-mobile-nav=\"attach-chip\"] {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 auto;\n max-width: 62vw;\n background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12));\n color: var(--dsw-alias-label-primary, inherit);\n }\n [data-mobile-nav=\"attach-chip\"][data-kind=\"file\"] {\n height: 26px;\n padding: 0 4px 0 8px;\n border-radius: 999px;\n font-size: 11.5px;\n line-height: 18px;\n }\n /* An image chip is the thumbnail: no name line, the filename lives in the\n title attribute (and in the draft text right below). */\n [data-mobile-nav=\"attach-chip\"][data-kind=\"image\"] {\n width: 48px;\n height: 48px;\n padding: 0;\n border-radius: 10px;\n overflow: hidden;\n }\n [data-mobile-nav=\"attach-chip-art\"] {\n position: relative;\n display: grid;\n place-items: center;\n flex: none;\n overflow: hidden;\n }\n [data-kind=\"file\"] > [data-mobile-nav=\"attach-chip-art\"] {\n width: 16px;\n height: 16px;\n opacity: .7;\n }\n [data-kind=\"image\"] > [data-mobile-nav=\"attach-chip-art\"] {\n width: 100%;\n height: 100%;\n }\n /* The <img> is stacked over the paperclip, not swapped for it: a format the\n engine cannot decode (HEIC outside WebKit) simply paints nothing and the\n icon shows through \u2014 no onError state to carry. */\n [data-mobile-nav=\"attach-chip-art\"] img {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n background: var(--dsw-alias-bg-layer-2, rgba(127, 127, 127, .18));\n }\n [data-mobile-nav=\"attach-chip-name\"] {\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n }\n [data-mobile-nav=\"attach-chip-remove\"] {\n display: grid;\n place-items: center;\n flex: none;\n width: 18px;\n height: 18px;\n padding: 0;\n border: none;\n border-radius: 999px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n touch-action: manipulation;\n }\n /* On a tile the \u00D7 floats in the corner over the picture, so it needs its own\n ground to stay legible against an arbitrary photo. */\n [data-kind=\"image\"] > [data-mobile-nav=\"attach-chip-remove\"] {\n position: absolute;\n top: 2px;\n right: 2px;\n background: rgba(0, 0, 0, .55);\n color: #fff;\n }\n [data-mobile-nav=\"attach-chip-remove\"]:active {\n transform: scale(.9);\n }\n\n /* --- 8. the official stats strip leaves the composer ---\n Its data moves into the session info card (S4). The row is the composer\n dock's own `_root` entry; the slot itself stays live for later entries. */\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"] {\n display: none !important;\n }\n\n /* --- 9. the attachment button (S7; its file input is hidden at the top of\n this file, at every width) --- */\n [data-mobile-nav=\"attach\"] {\n position: relative;\n width: 28px !important;\n height: 28px !important;\n flex: none !important;\n display: grid !important;\n place-items: center !important;\n padding: 0 !important;\n border: none !important;\n border-radius: 999px !important;\n background: var(--dsw-specific-selector, rgba(127, 127, 127, .12));\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n }\n [data-mobile-nav=\"attach\"]:active {\n transform: scale(.94);\n transition: transform .12s;\n }\n /* Busy: a ring sweeps around the paperclip. Drawn with a conic gradient in\n a pseudo-element rather than a spinner node, so the official React tree\n around us has nothing extra to re-render (same reasoning as the model\n pill's mask icon, S3.1). */\n [data-mobile-nav=\"attach\"][data-busy]::after {\n content: \"\";\n position: absolute;\n inset: -2px;\n border-radius: 999px;\n background: conic-gradient(from 0deg, transparent 0 65%, currentColor 100%);\n mask: radial-gradient(closest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));\n -webkit-mask: radial-gradient(closest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));\n animation: mnav-attach-spin .9s linear infinite;\n pointer-events: none;\n }\n @keyframes mnav-attach-spin {\n to { transform: rotate(1turn); }\n }\n @media (prefers-reduced-motion: reduce) {\n [data-mobile-nav=\"attach\"][data-busy]::after {\n animation-duration: 3s;\n }\n }\n /* Failure note: one line above the button, tap-to-dismiss. Sits on the\n composer card (z 2) rather than in an overlay layer \u2014 nothing here needs\n to clear the permission/model sheets. */\n [data-mobile-nav=\"attach-error\"] {\n position: absolute;\n bottom: calc(100% + 6px);\n left: 0;\n z-index: 2;\n max-width: 62vw;\n padding: 5px 8px;\n border-radius: 8px;\n font-size: 12px;\n line-height: 1.3;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, .82));\n color: var(--dsw-alias-label-primary, #fff);\n box-shadow: 0 2px 8px rgba(0, 0, 0, .24);\n }\n\n /* --- 9. home-indicator clearance (S4.1, 2026-08-17) ---\n Owned HERE, not by the gateway half. That plugin used to carry\n [data-slot=\"conversation.composer\"] { padding-bottom: max(env(safe-area-inset-bottom), 8px) }\n in both pwa/app.css and the gateway's inline DEVICE_CSS. Both were\n INERT and had always been: that slot element is `display: contents`\n (measured 2026-08-17 \u2014 the slot wrapper generates no box, so padding on\n it is discarded), which is why raising it never moved anything. The rule\n is deleted on the PWA side and restated here on an element that actually\n lays out, reading --mnav-sab so ?mobile-nav-inset=54,34 can regress it\n off-device (env() is hard 0 on desktop \u2014 the whole reason S2.1 exists).\n\n Target: the card should look EQUALLY inset on all four sides. This same\n element carries the card's side inset (`padding: 0 16px` officially,\n measured 16px left and 16px right at 390px), so the bottom gap is simply\n capped at that same 16px. Clearing the FULL 34px inset \u2014 the naive\n max(sab, 8px) \u2014 is what read as \"too thick\": it is more than double the\n side margin, so the card looked shoved up off the bottom edge.\n\n clamp() says all three requirements at once:\n - floor 8px -> a device with no home indicator (sab: 0) keeps the\n official 8px exactly, so this is a strict no-op there;\n - track sab -> a shallower inset than 16px is honoured as-is;\n - cap 16px -> a full-size indicator (sab: 34) lands on 16px, equal\n to the side inset, which is the look being asked for.\n Keep the 16px in step with the side padding above if that ever changes;\n that equality IS the spec here, not a coincidence. */\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] {\n padding-bottom: clamp(8px, var(--mnav-sab), 16px) !important;\n }\n}\n\n/* The attachment button and its preview row only exist for the phone shell.\n Both render at every width (the slots are not breakpoint-aware), so both\n need hiding here \u2014 the dock-row rules that shape the chips live in the\n < 768px block and would otherwise leave a bare, unstyled chip line in the\n desktop composer. */\n@media (min-width: 768px) {\n [data-mobile-nav=\"attach\"],\n [data-mobile-nav=\"attach-chips\"] {\n display: none !important;\n }\n}\n";
|
|
2
2
|
//# sourceMappingURL=composer.css.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composer.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/composer.css.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"composer.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/composer.css.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,YAAY,m3yBA0fxB,CAAA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const HEADER_CSS = "/* ---------- session header five-piece reflow (< 768px) ---------- */\n\n/* New elements render unconditionally (React does not know about media\n queries); default them to hidden so 768px+ never sees them, then\n re-enable inside the phone block below. Belt-and-braces alongside the\n scoped rules \u2014 mirrors the existing [data-mobile-nav=\"*\"] desktop no-op\n list in misc.css.ts. */\n[data-mobile-nav=\"header-back\"],\n[data-mobile-nav=\"header-viewrow\"],\n[data-mobile-nav=\"header-info\"],\n[data-mobile-nav=\"header-workbench\"],\n/* The workbench close pill's default-hidden used to live in compat.css.ts \u2014\n but EVERYTHING in layout/compat/misc sits inside one shared\n (max-width: 1023px) block (opened at the top of layout, closed at the end\n of misc), so that \"top-level\" rule silently never applied at >=1024 and\n the pill rendered as an unstyled button on desktop (2026-08-17 report).\n This file is appended after the shared block closes, so here it is truly\n global; the <=767px :has() show rule in compat overrides it with\n !important. */\n[data-mobile-nav=\"better-sidebar-close\"] {\n display: none;\n}\n\n@media (max-width: 767px) {\n /* The Files action targeted the dsh-web-ui-all (aionui) explorer sheet \u2014\n a different, unrelated suite from the workbench entry this slice adds\n (dsh-better-sidebar). Hidden here only; 768-1023px keeps it exactly as\n v1.0.0 shipped it. */\n [data-mobile-nav=\"files\"] {\n display: none !important;\n }\n\n /* --- three-column header: [back 92px] [title, truly centered] [utilities 92px] --- */\n /* The bottom padding is the view-switch row's seat: that row is\n position:absolute (see the bottom of this file \u2014 it renders two levels\n deep inside headerActions and can never be a grid item of titleRow), so\n without a reserve it would hang over the message list. 28px = its\n height. Unconditional rather than header:has([data-mobile-nav=\n \"header-viewrow\"]) on purpose: :has() is silently dropped by pre-105\n WebViews (see AGENTS.md) and a 28px overlap on the first message is a\n worse failure than 28px of empty band in the (never observed in\n practice) single-view session. Total header chrome: 48 + 28 = 76px.\n Left/right 8px (S2.1 report leftover, fixed in S4): the official header\n had zero horizontal padding, so the utilities button cluster's right\n edge sat flush against the screen edge (x=390 at 390px width, no\n margin). The 92px grid columns absorb this fine \u2014 they're fixed track\n widths, only the center 1fr column shrinks by 16px. */\n [data-phase] header {\n position: relative;\n padding: 0 8px 28px !important;\n }\n /* No header bottom line \u2014 fade instead (real-device round 2, 2026-08-17).\n The official header draws its border as a `::after` 1px bar (the\n `border-bottom` on the header itself is transparent, just a layout\n reserve \u2014 dsh-client-ui-conversation lib/client.js \".wSkVaW_header{\n border-bottom:1px solid #0000}.wSkVaW_header:after{...height:1px;\n position:absolute;bottom:1px...}\", verified live 2026-08-17), so\n hiding the pseudo-element is enough; the header's own border-bottom\n never had a visible color to begin with. The message scroller now\n fades in from under the header instead (styles/composer.css.ts, \"no\n divider above OR below the message list\" \u2014 the two edges share one\n mask-image on [class$=\"_scrollBody\"]). */\n [data-phase] header::after {\n display: none !important;\n }\n /* grid-row: 1 on every item is load-bearing, not decoration (S2.1 fix for\n the \"\u6807\u9898\u88AB\u6324\u4E0B\u53BB\" report). The three items are placed with explicit\n grid-column but arrive in DOM order crumbs(2) \u2192 headerActions(1) \u2192\n headerUtilities(3): sparse auto-placement never moves its cursor\n backwards, so headerActions and headerUtilities were pushed onto an\n implicit SECOND row. Measured at 390px: row1 = crumbs 28px, row2 =\n back button 44px, titleRow = 72px with the title stuck at the top\n instead of centered. Pinning the row makes it one 48px band again. */\n [data-phase] header [class$=\"_titleRow\"] {\n position: relative;\n display: grid;\n grid-template-columns: 92px 1fr 92px;\n grid-template-rows: minmax(48px, auto);\n align-items: center;\n min-height: 48px;\n padding: 0 !important;\n }\n /* Flatten the official cluster so its two children (crumbs, headerActions)\n become direct grid items of titleRow instead of a nested flex box. */\n [data-phase] header [class$=\"_titleCluster\"] {\n display: contents;\n }\n /* margin-left: 0 cancels layout.css.ts's shared <=1023px\n \"margin-left: auto !important\" on this exact selector (it pushed the\n Files button to the row's right edge in the old non-grid header;\n inside our grid column it instead shoves the back button 48px right of\n the column's start \u2014 the auto margin absorbs the leftover space\n between the button's content width and the 92px column). */\n [data-phase] header [class$=\"_headerActions\"] {\n grid-column: 1;\n grid-row: 1;\n justify-self: start;\n margin-left: 0 !important;\n display: flex;\n align-items: center;\n min-width: 0;\n }\n /* layout.css.ts's shared <=1023px block hides this element outright\n (header > :first-child > :last-child { display: none !important },\n specificity (0,3,1) \u2014 v1.0.0 relocated the desktop Session-log capsule\n to the drawer footer on every narrow width). That selector is repeated\n here at equal specificity so the later source order wins the display\n property; the class-suffix selector alone (0,2,1) would silently lose. */\n [data-phase] header [class$=\"_headerUtilities\"],\n [data-phase] header > :first-child > :last-child {\n grid-column: 3;\n grid-row: 1;\n justify-self: end;\n display: flex !important;\n align-items: center;\n gap: 4px;\n min-width: 0;\n }\n /* justify-self: stretch (the grid default) so this fills the whole 1fr\n column as a definite-width box \u2014 center/center-content happens inside\n it. justify-self: center (tried first) sizes the item to its content\n with no automatic column cap, and the obvious fix \u2014 max-width: 100% \u2014\n resolves against the WRONG box here (measured 93.6px at 390px, where\n the column is ~206px): the item's DOM parent (titleCluster) is\n display:contents, and percentage max-width does not reliably resolve\n through a boxless ancestor to the grid track. Stretching sidesteps the\n percentage entirely \u2014 but layout.css.ts's shared <=1023px block ALSO\n caps this exact selector at max-width: 24vw !important (93.6px at\n 390px \u2014 the value this rule used to silently inherit once its own\n max-width was dropped in favor of stretch), so that must be reset to\n none here too. */\n [data-phase] header [class$=\"_crumbs\"] {\n grid-column: 2;\n grid-row: 1;\n justify-self: stretch;\n max-width: none !important;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n overflow: hidden;\n }\n /* Subagent sessions keep ONE ancestor crumb (2026-08-18): the PC header\n lets you tap the parent session's title to switch back \u2014 that affordance\n was lost on phone when the whole parent chain was hidden as \"\u591A\u4F59\u9879\".\n Show only the immediate parent (for agent-teams member sessions that IS\n the captain/main session; deeper ancestors stay hidden \u2014 a 3-level chain\n cannot fit the ~206px center column at 390px). The parent crumb is the\n official breadcrumb <button> (dsh-client-ui-conversation renders\n open(parentId) as its onClick), so tapping needs no JS from this plugin,\n and main sessions (chain length 1) have no :nth-last-child(2) seg at\n all \u2014 the centered single title is pixel-identical to before. */\n [data-phase] header [class$=\"_crumbSeg\"]:not(:nth-last-child(-n+2)) {\n display: none !important;\n }\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) {\n display: inline-flex;\n align-items: center;\n flex: none;\n min-width: 0;\n max-width: 38%;\n }\n /* Ancestors BEFORE the parent seg are hidden, which would leave the\n parent seg's own leading \"/\" dangling at the start of the title. */\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) [class$=\"_crumbSep\"] {\n display: none !important;\n }\n /* Parent crumb: small tertiary text so the 19px current title stays the\n visual anchor; min-height keeps a finger-sized target inside the 48px\n row. Specificity (0,4,1) outranks the shared 19px _crumb rule below. */\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) [class$=\"_crumb\"] {\n font-size: 13px;\n font-weight: 400;\n color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .45));\n min-height: 44px;\n display: inline-flex;\n align-items: center;\n padding: 0 2px 0 6px;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-phase] header [class$=\"_crumbSeg\"]:last-child {\n display: flex;\n align-items: center;\n min-width: 0;\n max-width: 100%;\n }\n /* The last seg's own \"/\" is the divider between parent and current title \u2014\n it only exists when there IS an ancestor (index > 0), so it can simply\n stay visible now that the parent seg shows. (It used to be hidden here:\n with the parent hidden it dangled before the title.) */\n /* BOTH selectors are required: [class$=] matches the whole class\n ATTRIBUTE's suffix, and the current-session title button's attribute is\n \"wSkVaW_crumb wSkVaW_crumbCurrent\" \u2014 it ends in _crumbCurrent, so the _crumb\n suffix selector alone silently missed the one element that matters\n (measured live: the title stayed 14px while the rule sat in the bundle,\n 2026-08-17). */\n [data-phase] header [class$=\"_crumb\"],\n [data-phase] header [class$=\"_crumbCurrent\"] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n max-width: 100%;\n /* Real-device feedback (2026-08-17): the session title read too small\n next to the enlarged header icons \u2014 19px sits just under the home\n screen's 22px workspace title, keeping the two-level hierarchy. */\n font-size: 19px;\n font-weight: 600;\n }\n\n /* Running-status dot: no official element to reposition (the header\n renders only the crumb title, the two action slots, and the tablist \u2014\n no status indicator), so this is the plan's documented fallback \u2014\n read the data ourselves and draw it (effects/header-status.ts stamps\n data-mobile-nav-dot on the frame). A ::after on the title crumb keeps\n it truly inline with the (centered, truncated) text instead of a\n separately positioned box that would fight the centering math. */\n [data-phase] header [class$=\"_crumbCurrent\"]::after {\n content: '';\n display: none;\n width: 6px;\n height: 6px;\n margin-left: 6px;\n border-radius: 50%;\n vertical-align: middle;\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot] header [class$=\"_crumbCurrent\"]::after {\n display: inline-block;\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"ongoing\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-business-primary, #4f6ef7);\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"warning\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-warn-primary, #d97706);\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"done\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-success-primary, #16a34a);\n }\n\n /* Everything else the official header puts in these two list slots \u2014 the\n agent-preset mode badge, the jobs/subagent trigger, the desktop\n Session-log download capsule, and this plugin's own legacy directory\n toggle \u2014 is the \"\u4E0D\u5E38\u9A7B\" set the design defers to the S4 info card.\n Scoped by the slots' own [data-slot] wrapper (a stable, non-hashed\n contract marker) rather than enumerating each registrant, so a future\n third-party header action is hidden by default too. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > *,\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > * {\n display: none !important;\n }\n /* The agent-preset mode badge specifically survives the blanket hide\n above: layout.css.ts's shared <=1023px block targets it directly\n (header [class$=\"_label\"]:has(> svg) { display: block !important }),\n specificity (0,3,1), higher than the [data-slot] wrapper rule (0,2,1) \u2014\n display wins on specificity before source order, so a same-selector\n override is needed here too. */\n [data-phase] header [class$=\"_label\"]:has(> svg) {\n display: none !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-back\"] {\n display: inline-flex !important;\n }\n /* header-viewrow is position:absolute (see below), so its re-shown\n display value must match the flex layout its own rule declares. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-viewrow\"] {\n display: flex !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"header-info\"],\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"header-workbench\"] {\n display: inline-flex !important;\n }\n\n /* Back button: 44pt touch target, leftmost column. */\n [data-mobile-nav=\"header-back\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-back\"]:active {\n opacity: .6;\n }\n\n /* Info + workbench utility buttons, rightmost column. */\n [data-mobile-nav=\"header-info\"],\n [data-mobile-nav=\"header-workbench\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 34px;\n height: 34px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-secondary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-info\"]:active,\n [data-mobile-nav=\"header-workbench\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n /* Icon family unification (real-device round 2, 2026-08-17): the \u24D8 text\n glyph is gone (MobileSessionHeader.tsx now renders IconInfoOutline16,\n a local 16px SVG built to match the primitives icon family) and the\n workbench button's IconPanelLeftOutline16 is mirrored into a\n panel-RIGHT glyph \u2014 there's no IconPanelRightOutline16 in primitives\n (checked lib/types/icons/index.d.ts), and the plugin's own right-side\n panel semantics are exactly the left icon flipped. Both buttons now\n carry a same-size (16px), same-stroke-weight icon. */\n [data-mobile-nav=\"header-workbench\"] svg {\n transform: scaleX(-1);\n }\n\n /* Official Chat/Trajectory tablist: removed from layout entirely (S2.1 \u2014\n visibility:hidden still held a 27px row, so the header carried the\n view-switch row's band TWICE: 72 + 27 + gap = 104px measured at 390px,\n and a real iPhone added ~54px of notch on top of that). The header's own\n padding-bottom above is now the row's seat.\n display:none does NOT break the view switch: HTMLElement.click()\n dispatches synthetically and fires React's handler regardless of\n rendering \u2014 only real pointer hit-testing needs a box, and this plugin\n never relies on it (MobileSessionHeader always calls .click()). */\n [data-phase] header [class$=\"_tabs\"][role=\"tablist\"] {\n display: none;\n }\n\n /* View-switch row: \"current view name + dots\", replacing the (still\n present, still clickable) official tablist visually. It renders inside\n headerActions/headerUtilities, but titleRow's own position:relative\n (set above) makes IT the containing block for absolute descendants \u2014\n display:contents on titleCluster does not break that search \u2014 so\n top:100% sits directly under the title regardless of the title row's\n actual height, with no hardcoded offset to keep in sync. */\n [data-mobile-nav=\"header-viewrow\"] {\n position: absolute;\n left: 0;\n right: 0;\n top: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 28px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-family: inherit;\n font-size: 13px;\n line-height: 18px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] > i {\n display: block;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: currentColor;\n opacity: .35;\n font-style: normal;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] > i[data-active] {\n opacity: 1;\n }\n}\n";
|
|
1
|
+
export declare const HEADER_CSS = "/* ---------- session header five-piece reflow (< 768px) ---------- */\n\n/* New elements render unconditionally (React does not know about media\n queries); default them to hidden so 768px+ never sees them, then\n re-enable inside the phone block below. Belt-and-braces alongside the\n scoped rules \u2014 mirrors the existing [data-mobile-nav=\"*\"] desktop no-op\n list in misc.css.ts. */\n[data-mobile-nav=\"header-back\"],\n[data-mobile-nav=\"header-viewrow\"],\n[data-mobile-nav=\"header-info\"],\n[data-mobile-nav=\"header-workbench\"],\n/* The workbench close pill's default-hidden used to live in compat.css.ts \u2014\n but EVERYTHING in layout/compat/misc sits inside one shared\n (max-width: 1023px) block (opened at the top of layout, closed at the end\n of misc), so that \"top-level\" rule silently never applied at >=1024 and\n the pill rendered as an unstyled button on desktop (2026-08-17 report).\n This file is appended after the shared block closes, so here it is truly\n global; the <=767px :has() show rule in compat overrides it with\n !important. */\n[data-mobile-nav=\"better-sidebar-close\"] {\n display: none;\n}\n\n@media (max-width: 767px) {\n /* The Files action targeted the dsh-web-ui-all (aionui) explorer sheet \u2014\n a different, unrelated suite from the workbench entry this slice adds\n (dsh-better-sidebar). Hidden here only; 768-1023px keeps it exactly as\n v1.0.0 shipped it. */\n [data-mobile-nav=\"files\"] {\n display: none !important;\n }\n\n /* --- three-column header: [back 92px] [title, truly centered] [utilities 92px] --- */\n /* The bottom padding is the view-switch row's seat: that row is\n position:absolute (see the bottom of this file \u2014 it renders two levels\n deep inside headerActions and can never be a grid item of titleRow), so\n without a reserve it would hang over the message list. 28px = its\n height. Unconditional rather than header:has([data-mobile-nav=\n \"header-viewrow\"]) on purpose: :has() is silently dropped by pre-105\n WebViews (see AGENTS.md) and a 28px overlap on the first message is a\n worse failure than 28px of empty band in the (never observed in\n practice) single-view session. Total header chrome: 48 + 28 = 76px.\n Left/right 8px (S2.1 report leftover, fixed in S4): the official header\n had zero horizontal padding, so the utilities button cluster's right\n edge sat flush against the screen edge (x=390 at 390px width, no\n margin). The 92px grid columns absorb this fine \u2014 they're fixed track\n widths, only the center 1fr column shrinks by 16px. */\n [data-phase] header {\n position: relative;\n padding: 0 8px 28px !important;\n }\n /* No header bottom line \u2014 fade instead (real-device round 2, 2026-08-17).\n The official header draws its border as a `::after` 1px bar (the\n `border-bottom` on the header itself is transparent, just a layout\n reserve \u2014 dsh-client-ui-conversation lib/client.js \".wSkVaW_header{\n border-bottom:1px solid #0000}.wSkVaW_header:after{...height:1px;\n position:absolute;bottom:1px...}\", verified live 2026-08-17), so\n hiding the pseudo-element is enough; the header's own border-bottom\n never had a visible color to begin with. The message scroller now\n fades in from under the header instead (styles/composer.css.ts, \"no\n divider above OR below the message list\" \u2014 the two edges share one\n mask-image on [class$=\"_scrollBody\"]). */\n [data-phase] header::after {\n display: none !important;\n }\n /* grid-row: 1 on every item is load-bearing, not decoration (S2.1 fix for\n the \"\u6807\u9898\u88AB\u6324\u4E0B\u53BB\" report). The three items are placed with explicit\n grid-column but arrive in DOM order crumbs(2) \u2192 headerActions(1) \u2192\n headerUtilities(3): sparse auto-placement never moves its cursor\n backwards, so headerActions and headerUtilities were pushed onto an\n implicit SECOND row. Measured at 390px: row1 = crumbs 28px, row2 =\n back button 44px, titleRow = 72px with the title stuck at the top\n instead of centered. Pinning the row makes it one 48px band again. */\n [data-phase] header [class$=\"_titleRow\"] {\n position: relative;\n display: grid;\n grid-template-columns: 92px 1fr 92px;\n grid-template-rows: minmax(48px, auto);\n align-items: center;\n min-height: 48px;\n padding: 0 !important;\n }\n /* Flatten the official cluster so its two children (crumbs, headerActions)\n become direct grid items of titleRow instead of a nested flex box. */\n [data-phase] header [class$=\"_titleCluster\"] {\n display: contents;\n }\n /* margin-left: 0 cancels layout.css.ts's shared <=1023px\n \"margin-left: auto !important\" on this exact selector (it pushed the\n Files button to the row's right edge in the old non-grid header;\n inside our grid column it instead shoves the back button 48px right of\n the column's start \u2014 the auto margin absorbs the leftover space\n between the button's content width and the 92px column). */\n [data-phase] header [class$=\"_headerActions\"] {\n grid-column: 1;\n grid-row: 1;\n justify-self: start;\n margin-left: 0 !important;\n display: flex;\n align-items: center;\n min-width: 0;\n }\n /* layout.css.ts's shared <=1023px block hides this element outright\n (header > :first-child > :last-child { display: none !important },\n specificity (0,3,1) \u2014 v1.0.0 relocated the desktop Session-log capsule\n to the drawer footer on every narrow width). That selector is repeated\n here at equal specificity so the later source order wins the display\n property; the class-suffix selector alone (0,2,1) would silently lose. */\n [data-phase] header [class$=\"_headerUtilities\"],\n [data-phase] header > :first-child > :last-child {\n grid-column: 3;\n grid-row: 1;\n justify-self: end;\n display: flex !important;\n align-items: center;\n gap: 4px;\n min-width: 0;\n }\n /* justify-self: stretch (the grid default) so this fills the whole 1fr\n column as a definite-width box \u2014 center/center-content happens inside\n it. justify-self: center (tried first) sizes the item to its content\n with no automatic column cap, and the obvious fix \u2014 max-width: 100% \u2014\n resolves against the WRONG box here (measured 93.6px at 390px, where\n the column is ~206px): the item's DOM parent (titleCluster) is\n display:contents, and percentage max-width does not reliably resolve\n through a boxless ancestor to the grid track. Stretching sidesteps the\n percentage entirely \u2014 but layout.css.ts's shared <=1023px block ALSO\n caps this exact selector at max-width: 24vw !important (93.6px at\n 390px \u2014 the value this rule used to silently inherit once its own\n max-width was dropped in favor of stretch), so that must be reset to\n none here too. */\n [data-phase] header [class$=\"_crumbs\"] {\n grid-column: 2;\n grid-row: 1;\n justify-self: stretch;\n max-width: none !important;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n overflow: hidden;\n }\n /* Subagent sessions keep ONE ancestor crumb (2026-08-18): the PC header\n lets you tap the parent session's title to switch back \u2014 that affordance\n was lost on phone when the whole parent chain was hidden as \"\u591A\u4F59\u9879\".\n Show only the immediate parent (for agent-teams member sessions that IS\n the captain/main session; deeper ancestors stay hidden \u2014 a 3-level chain\n cannot fit the ~206px center column at 390px). The parent crumb is the\n official breadcrumb <button> (dsh-client-ui-conversation renders\n open(parentId) as its onClick), so tapping needs no JS from this plugin,\n and main sessions (chain length 1) have no :nth-last-child(2) seg at\n all \u2014 the centered single title is pixel-identical to before. */\n [data-phase] header [class$=\"_crumbSeg\"]:not(:nth-last-child(-n+2)) {\n display: none !important;\n }\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) {\n display: inline-flex;\n align-items: center;\n flex: none;\n min-width: 0;\n max-width: 38%;\n }\n /* Ancestors BEFORE the parent seg are hidden, which would leave the\n parent seg's own leading \"/\" dangling at the start of the title. */\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) [class$=\"_crumbSep\"] {\n display: none !important;\n }\n /* Parent crumb: small tertiary text so the 19px current title stays the\n visual anchor; min-height keeps a finger-sized target inside the 48px\n row. Specificity (0,4,1) outranks the shared 19px _crumb rule below. */\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) [class$=\"_crumb\"] {\n font-size: 13px;\n font-weight: 400;\n color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .45));\n min-height: 44px;\n display: inline-flex;\n align-items: center;\n padding: 0 2px 0 6px;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-phase] header [class$=\"_crumbSeg\"]:last-child {\n display: flex;\n align-items: center;\n min-width: 0;\n max-width: 100%;\n }\n /* The last seg's own \"/\" is the divider between parent and current title \u2014\n it only exists when there IS an ancestor (index > 0), so it can simply\n stay visible now that the parent seg shows. (It used to be hidden here:\n with the parent hidden it dangled before the title.) */\n /* BOTH selectors are required: [class$=] matches the whole class\n ATTRIBUTE's suffix, and the current-session title button's attribute is\n \"wSkVaW_crumb wSkVaW_crumbCurrent\" \u2014 it ends in _crumbCurrent, so the _crumb\n suffix selector alone silently missed the one element that matters\n (measured live: the title stayed 14px while the rule sat in the bundle,\n 2026-08-17). */\n [data-phase] header [class$=\"_crumb\"],\n [data-phase] header [class$=\"_crumbCurrent\"] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n max-width: 100%;\n /* Real-device feedback (2026-08-17): the session title read too small\n next to the enlarged header icons \u2014 19px sits just under the home\n screen's 22px workspace title, keeping the two-level hierarchy. */\n font-size: 19px;\n font-weight: 600;\n }\n\n /* Running-status dot: no official element to reposition (the header\n renders only the crumb title, the two action slots, and the tablist \u2014\n no status indicator), so this is the plan's documented fallback \u2014\n read the data ourselves and draw it (effects/header-status.ts stamps\n data-mobile-nav-dot on the frame). A ::after on the title crumb keeps\n it truly inline with the (centered, truncated) text instead of a\n separately positioned box that would fight the centering math. */\n [data-phase] header [class$=\"_crumbCurrent\"]::after {\n content: '';\n display: none;\n width: 6px;\n height: 6px;\n margin-left: 6px;\n border-radius: 50%;\n vertical-align: middle;\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot] header [class$=\"_crumbCurrent\"]::after {\n display: inline-block;\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"ongoing\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-business-primary, #4f6ef7);\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"warning\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-warn-primary, #d97706);\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"done\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-success-primary, #16a34a);\n }\n\n /* Everything else the official header puts in these two list slots \u2014 the\n agent-preset mode badge, the jobs/subagent trigger, the desktop\n Session-log download capsule, and this plugin's own legacy directory\n toggle \u2014 is the \"\u4E0D\u5E38\u9A7B\" set the design defers to the S4 info card.\n Scoped by the slots' own [data-slot] wrapper (a stable, non-hashed\n contract marker) rather than enumerating each registrant, so a future\n third-party header action is hidden by default too. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > *,\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > * {\n display: none !important;\n }\n /* Native DSH task surfaces are real header actions, not phone-shell\n controls. Keep their roots visible so the background-task and subagent\n menus can still be opened on a phone. Restore the root's containing block\n as well: both native menus are absolutely positioned from this element,\n and layout.css.ts's compact-header rule otherwise changes it to static. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [class$=\"_root\"]:has(> button[class$=\"_trigger\"]) {\n display: block !important;\n position: relative !important;\n }\n /* The agent-preset mode badge specifically survives the blanket hide\n above: layout.css.ts's shared <=1023px block targets it directly\n (header [class$=\"_label\"]:has(> svg) { display: block !important }),\n specificity (0,3,1), higher than the [data-slot] wrapper rule (0,2,1) \u2014\n display wins on specificity before source order, so a same-selector\n override is needed here too. */\n [data-phase] header [class$=\"_label\"]:has(> svg) {\n display: none !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-back\"] {\n display: inline-flex !important;\n }\n /* header-viewrow is position:absolute (see below), so its re-shown\n display value must match the flex layout its own rule declares. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-viewrow\"] {\n display: flex !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"header-info\"],\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"header-workbench\"] {\n display: inline-flex !important;\n }\n\n /* Back button: 44pt touch target, leftmost column. */\n [data-mobile-nav=\"header-back\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-back\"]:active {\n opacity: .6;\n }\n\n /* Info + workbench utility buttons, rightmost column. */\n [data-mobile-nav=\"header-info\"],\n [data-mobile-nav=\"header-workbench\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 34px;\n height: 34px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-secondary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-info\"]:active,\n [data-mobile-nav=\"header-workbench\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n /* Icon family unification (real-device round 2, 2026-08-17): the \u24D8 text\n glyph is gone (MobileSessionHeader.tsx now renders IconInfoOutline16,\n a local 16px SVG built to match the primitives icon family) and the\n workbench button's IconPanelLeftOutline16 is mirrored into a\n panel-RIGHT glyph \u2014 there's no IconPanelRightOutline16 in primitives\n (checked lib/types/icons/index.d.ts), and the plugin's own right-side\n panel semantics are exactly the left icon flipped. Both buttons now\n carry a same-size (16px), same-stroke-weight icon. */\n [data-mobile-nav=\"header-workbench\"] svg {\n transform: scaleX(-1);\n }\n\n /* Official Chat/Trajectory tablist: removed from layout entirely (S2.1 \u2014\n visibility:hidden still held a 27px row, so the header carried the\n view-switch row's band TWICE: 72 + 27 + gap = 104px measured at 390px,\n and a real iPhone added ~54px of notch on top of that). The header's own\n padding-bottom above is now the row's seat.\n display:none does NOT break the view switch: HTMLElement.click()\n dispatches synthetically and fires React's handler regardless of\n rendering \u2014 only real pointer hit-testing needs a box, and this plugin\n never relies on it (MobileSessionHeader always calls .click()). */\n [data-phase] header [class$=\"_tabs\"][role=\"tablist\"] {\n display: none;\n }\n\n /* View-switch row: \"current view name + dots\", replacing the (still\n present, still clickable) official tablist visually. It renders inside\n headerActions/headerUtilities, but titleRow's own position:relative\n (set above) makes IT the containing block for absolute descendants \u2014\n display:contents on titleCluster does not break that search \u2014 so\n top:100% sits directly under the title regardless of the title row's\n actual height, with no hardcoded offset to keep in sync. */\n [data-mobile-nav=\"header-viewrow\"] {\n position: absolute;\n left: 0;\n right: 0;\n top: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 28px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-family: inherit;\n font-size: 13px;\n line-height: 18px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] > i {\n display: block;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: currentColor;\n opacity: .35;\n font-style: normal;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] > i[data-active] {\n opacity: 1;\n }\n}\n";
|
|
2
2
|
//# sourceMappingURL=header.css.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"header.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/header.css.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"header.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/header.css.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,UAAU,+xkBAyYtB,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-zen-remote",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "把 DeepSeek Harness 变成手机 App:移动端界面重排(两级页面栈、会话列表主屏、composer 重排、手势、附件上传)+ 配对码认证网关 + PWA 安装 + 真 Web Push。Complete mobile plugin for DeepSeek Harness (DSH): app-shell mobile UI, pairing-code gateway for public-internet access behind your own reverse proxy, installable PWA, offline service worker, and real Web Push agent-done notifications.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -80,16 +80,16 @@
|
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
82
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
83
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.
|
|
84
|
-
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.
|
|
85
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.
|
|
86
|
-
"@deepseek-ai/dsh-client-ui-layout": "^0.1.0-rc.
|
|
87
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.
|
|
88
|
-
"@deepseek-ai/dsh-client-ui-sidebar": "^0.1.0-rc.
|
|
89
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.
|
|
90
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.
|
|
91
|
-
"@deepseek-ai/dsh-session": "^0.1.0-rc.
|
|
92
|
-
"@deepseek-ai/dsh-session-log-export": "^0.1.0-rc.
|
|
83
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.7",
|
|
84
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.7",
|
|
85
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.7",
|
|
86
|
+
"@deepseek-ai/dsh-client-ui-layout": "^0.1.0-rc.7",
|
|
87
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.7",
|
|
88
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "^0.1.0-rc.7",
|
|
89
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.7",
|
|
90
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.7",
|
|
91
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.7",
|
|
92
|
+
"@deepseek-ai/dsh-session-log-export": "^0.1.0-rc.7",
|
|
93
93
|
"react": "^18.2.0",
|
|
94
94
|
"@deepseek-ai/dsh-tools": ">=0.0.1-rc.1 <0.1.0 || >=0.1.0-rc.1 <0.2.0-0"
|
|
95
95
|
},
|
|
@@ -105,22 +105,23 @@
|
|
|
105
105
|
}
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
|
-
"@deepseek-ai/dsh-api-remotes": "0.1.0-rc.
|
|
109
|
-
"@deepseek-ai/dsh-client-connection": "0.1.0-rc.
|
|
110
|
-
"@deepseek-ai/dsh-host-apiproxy": "0.1.0-rc.
|
|
111
|
-
"@deepseek-ai/dsh-host-webserver": "0.1.0-rc.
|
|
112
|
-
"@deepseek-ai/dsh-session": "0.1.0-rc.
|
|
113
|
-
"@deepseek-ai/dsh-session-stats": "0.1.0-rc.
|
|
114
|
-
"@deepseek-ai/dsh-token-meter": "0.1.0-rc.
|
|
108
|
+
"@deepseek-ai/dsh-api-remotes": "0.1.0-rc.7",
|
|
109
|
+
"@deepseek-ai/dsh-client-connection": "0.1.0-rc.7",
|
|
110
|
+
"@deepseek-ai/dsh-host-apiproxy": "0.1.0-rc.7",
|
|
111
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.0-rc.7",
|
|
112
|
+
"@deepseek-ai/dsh-session": "0.1.0-rc.7",
|
|
113
|
+
"@deepseek-ai/dsh-session-stats": "0.1.0-rc.7",
|
|
114
|
+
"@deepseek-ai/dsh-token-meter": "0.1.0-rc.7",
|
|
115
115
|
"@types/node": "^26.2.0",
|
|
116
116
|
"@types/react": "~18.3.1",
|
|
117
117
|
"@types/react-dom": "~18.3.0",
|
|
118
118
|
"typescript": "^6.0.3",
|
|
119
|
-
"@deepseek-ai/dsh-tools": "0.1.0-rc.
|
|
119
|
+
"@deepseek-ai/dsh-tools": "0.1.0-rc.7"
|
|
120
120
|
},
|
|
121
121
|
"scripts": {
|
|
122
122
|
"build": "tsc -p tsconfig.json && tsc -p tsconfig.client.json && node scripts/build-client.mjs",
|
|
123
123
|
"verify": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
|
|
124
|
-
"test": "node --test \"test/*.test.cjs\" && node scripts/check-sunk-viewport.mjs && node scripts/check-attach-upload.mjs && node scripts/check-upload-endpoint.mjs"
|
|
124
|
+
"test": "node --test \"test/*.test.cjs\" && node scripts/check-sunk-viewport.mjs && node scripts/check-attach-upload.mjs && node scripts/check-upload-endpoint.mjs && node scripts/sync-doc-version.mjs --check",
|
|
125
|
+
"version": "node scripts/sync-doc-version.mjs && git add README.md"
|
|
125
126
|
}
|
|
126
127
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrite the version strings embedded in README.md to match package.json.
|
|
3
|
+
*
|
|
4
|
+
* Runs from the `version` npm lifecycle script, which fires AFTER the version
|
|
5
|
+
* bump and BEFORE the release commit — so `npm version patch` carries the
|
|
6
|
+
* refreshed README into the same commit instead of leaving the badge stuck on
|
|
7
|
+
* whatever it said at 1.0.0 (it sat there through 1.0.1 and 1.0.2).
|
|
8
|
+
*
|
|
9
|
+
* Every target is required: an unmatched marker exits non-zero rather than
|
|
10
|
+
* quietly succeeding, because a silent no-op is exactly the failure this
|
|
11
|
+
* script exists to prevent. Idempotent — running it when nothing changed is a
|
|
12
|
+
* no-op with exit 0.
|
|
13
|
+
*
|
|
14
|
+
* Also usable standalone: `node scripts/sync-doc-version.mjs [--check]`.
|
|
15
|
+
* `--check` reports drift without writing, for CI.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFileSync, writeFileSync } from 'node:fs'
|
|
19
|
+
import { dirname, join } from 'node:path'
|
|
20
|
+
import { fileURLToPath } from 'node:url'
|
|
21
|
+
|
|
22
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
23
|
+
const version = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version
|
|
24
|
+
const checkOnly = process.argv.includes('--check')
|
|
25
|
+
|
|
26
|
+
/** Each target names the file, what it looks for, and what it becomes. */
|
|
27
|
+
const targets = [
|
|
28
|
+
{
|
|
29
|
+
file: 'README.md',
|
|
30
|
+
label: 'release badge',
|
|
31
|
+
pattern: /badge\/release-v\d+\.\d+\.\d+-/,
|
|
32
|
+
replace: `badge/release-v${version}-`,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
file: 'README.md',
|
|
36
|
+
label: 'release badge alt text',
|
|
37
|
+
pattern: /alt="v\d+\.\d+\.\d+"/,
|
|
38
|
+
replace: `alt="v${version}"`,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
file: 'README.md',
|
|
42
|
+
label: 'profile dependency example',
|
|
43
|
+
pattern: /"dsh-zen-remote": "\^\d+\.\d+\.\d+"/,
|
|
44
|
+
replace: `"dsh-zen-remote": "^${version}"`,
|
|
45
|
+
},
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
const edits = new Map()
|
|
49
|
+
const missing = []
|
|
50
|
+
const drifted = []
|
|
51
|
+
|
|
52
|
+
for (const target of targets) {
|
|
53
|
+
const path = join(root, target.file)
|
|
54
|
+
const before = edits.get(target.file) ?? readFileSync(path, 'utf8')
|
|
55
|
+
if (!target.pattern.test(before)) {
|
|
56
|
+
missing.push(`${target.file}: ${target.label} (${target.pattern})`)
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
const after = before.replace(target.pattern, target.replace)
|
|
60
|
+
if (after !== before) drifted.push(`${target.file}: ${target.label} -> ${target.replace}`)
|
|
61
|
+
edits.set(target.file, after)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (missing.length > 0) {
|
|
65
|
+
console.error(`sync-doc-version: marker not found — the docs moved, update this script:\n ${missing.join('\n ')}`)
|
|
66
|
+
process.exit(1)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (checkOnly) {
|
|
70
|
+
if (drifted.length === 0) {
|
|
71
|
+
console.log(`sync-doc-version: docs already at ${version}`)
|
|
72
|
+
process.exit(0)
|
|
73
|
+
}
|
|
74
|
+
console.error(`sync-doc-version: docs are out of date (package.json is ${version}):\n ${drifted.join('\n ')}`)
|
|
75
|
+
process.exit(1)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const [file, content] of edits) writeFileSync(join(root, file), content)
|
|
79
|
+
console.log(
|
|
80
|
+
drifted.length === 0
|
|
81
|
+
? `sync-doc-version: docs already at ${version}`
|
|
82
|
+
: `sync-doc-version: synced to ${version}\n ${drifted.join('\n ')}`,
|
|
83
|
+
)
|
|
@@ -286,6 +286,31 @@ export const COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- *
|
|
|
286
286
|
padding: 0 10px !important;
|
|
287
287
|
font-size: 11.5px !important;
|
|
288
288
|
}
|
|
289
|
+
/* --- 7a. the native to-do card opts OUT of the 26px pill cage ---
|
|
290
|
+
conversation.input.dock is not only a chip rail: DSH's own TodoPanel
|
|
291
|
+
(dsh-client-ui-conversation) registers here, and it is an expandable
|
|
292
|
+
card, not a pill — tapping its header renders the to-do list INSIDE
|
|
293
|
+
itself. The cage's max-height:26px + overflow:hidden left that list
|
|
294
|
+
rendered but clipped to nothing, so on a phone the bar looked dead:
|
|
295
|
+
aria-expanded flipped, 8 rows mounted at 216px, and the card still
|
|
296
|
+
painted 26px tall (measured live, 2026-08-20). Give it the official
|
|
297
|
+
geometry back — full row, natural height, 12px radius. Its list caps
|
|
298
|
+
itself at 180px with its own scroller, so this cannot run into the
|
|
299
|
+
composer. Anchored on the official data-testid, a stable contract
|
|
300
|
+
marker rather than a hashed class name. */
|
|
301
|
+
[data-slot="conversation.input.dock"] > [data-testid="todo-panel"] {
|
|
302
|
+
flex: 1 1 auto !important;
|
|
303
|
+
min-width: 0 !important;
|
|
304
|
+
max-width: none !important;
|
|
305
|
+
min-height: 0 !important;
|
|
306
|
+
max-height: none !important;
|
|
307
|
+
overflow: visible !important;
|
|
308
|
+
border-radius: 12px !important;
|
|
309
|
+
/* The cage shrinks dock text to 11.5px/18px; the card's rows inherit it
|
|
310
|
+
and turn cramped. Restore the official 13px body scale. */
|
|
311
|
+
font-size: 13px !important;
|
|
312
|
+
line-height: normal !important;
|
|
313
|
+
}
|
|
289
314
|
|
|
290
315
|
/* --- 7b. attachment chips (S7.1) ---
|
|
291
316
|
Our own dock entry opts OUT of the 26px pill cage above: an image chip is
|
|
@@ -266,6 +266,15 @@ export const HEADER_CSS = `/* ---------- session header five-piece reflow (< 768
|
|
|
266
266
|
[data-phase] header [data-slot="conversation.session.header.utilities"] > * {
|
|
267
267
|
display: none !important;
|
|
268
268
|
}
|
|
269
|
+
/* Native DSH task surfaces are real header actions, not phone-shell
|
|
270
|
+
controls. Keep their roots visible so the background-task and subagent
|
|
271
|
+
menus can still be opened on a phone. Restore the root's containing block
|
|
272
|
+
as well: both native menus are absolutely positioned from this element,
|
|
273
|
+
and layout.css.ts's compact-header rule otherwise changes it to static. */
|
|
274
|
+
[data-phase] header [data-slot="conversation.session.header.actions"] > [class$="_root"]:has(> button[class$="_trigger"]) {
|
|
275
|
+
display: block !important;
|
|
276
|
+
position: relative !important;
|
|
277
|
+
}
|
|
269
278
|
/* The agent-preset mode badge specifically survives the blanket hide
|
|
270
279
|
above: layout.css.ts's shared <=1023px block targets it directly
|
|
271
280
|
(header [class$="_label"]:has(> svg) { display: block !important }),
|