dsh-zen-remote 1.1.4 → 1.1.5
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/README.md +2 -2
- package/docs/interface.md +15 -0
- package/lib/client.js +167 -55
- package/lib/types/client/MobileSessionHeader.d.ts.map +1 -1
- package/lib/types/client/effects/native-trigger-overlay.d.ts +25 -0
- package/lib/types/client/effects/native-trigger-overlay.d.ts.map +1 -0
- package/lib/types/client/index.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 +9 -9
- package/src/client/MobileSessionHeader.tsx +26 -14
- package/src/client/effects/native-trigger-overlay.ts +102 -0
- package/src/client/index.tsx +2 -0
- package/src/client/styles/header.css.ts +34 -38
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.1.
|
|
6
|
+
<img src="https://img.shields.io/badge/release-v1.1.5-5B4CF0?style=flat-square" alt="v1.1.5">
|
|
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
|
|
|
@@ -39,7 +39,7 @@ dsh plugin add dsh-zen-remote
|
|
|
39
39
|
```jsonc
|
|
40
40
|
{
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"dsh-zen-remote": "^1.1.
|
|
42
|
+
"dsh-zen-remote": "^1.1.5" // 本地开发换成 "link:/path/to/dsh-zen-remote"
|
|
43
43
|
},
|
|
44
44
|
"dsh": { "profile": { "bundles": [
|
|
45
45
|
"@deepseek-ai/dsh-base",
|
package/docs/interface.md
CHANGED
|
@@ -115,6 +115,21 @@ composer 最左的回形针打开的是**手机本地**的文件选择器(iOS
|
|
|
115
115
|
> `v2.0.0` 及更早的小节标题用的是手机端界面的代次,与 npm 包版本号不是一条线;
|
|
116
116
|
> 两套编号并行容易看混,从 `1.1.0` 起本表统一按 **npm 包版本**记录。
|
|
117
117
|
|
|
118
|
+
### 未发布
|
|
119
|
+
|
|
120
|
+
**修复**
|
|
121
|
+
|
|
122
|
+
- 适配 DSH `0.1.1`:活动 chip 点击失效(点了退回信息卡)。三处上游变化同时命中
|
|
123
|
+
旧方案——子代理入口从 `header.actions` 槽移进新的 `header.lineage` 槽(面包屑
|
|
124
|
+
内);其根节点 class 变成 `"ZKlsPq_root "`(**带尾随空格**,`[class$="_root"]`
|
|
125
|
+
整串后缀匹配直接失配);触发器**只认可信输入**,`.click()` 与合成
|
|
126
|
+
pointer/mouse 事件一律无效(真机逐项排除了插件样式的嫌疑后实测)。因此废弃
|
|
127
|
+
「转发点击」,改为 `effects/native-trigger-overlay.ts` 把**真实的官方触发器**
|
|
128
|
+
透明地铺在 pill 上——用户手指点到的就是官方元素,可信输入天然成立;弹层在
|
|
129
|
+
0.1.1 里 portal 到 body 自行定位(336px,屏内),旧的锚定/重锚 CSS 一并删除。
|
|
130
|
+
选择器统一改为按 header 定位、按 ARIA(`aria-haspopup="tree"`)区分,不再依赖
|
|
131
|
+
槽名与根节点类名。
|
|
132
|
+
|
|
118
133
|
### 1.1.0
|
|
119
134
|
|
|
120
135
|
**新增**
|
package/lib/client.js
CHANGED
|
@@ -1094,30 +1094,42 @@ function IconTaskOutline14({ size = 14 }) {
|
|
|
1094
1094
|
return ((0, jsx_runtime_1.jsxs)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("circle", { cx: "8", cy: "8", r: "6.1", stroke: "currentColor", strokeWidth: "1.3" }), (0, jsx_runtime_1.jsx)("path", { d: "M8 4.4V8l2.5 1.7", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round", strokeLinejoin: "round" })] }));
|
|
1095
1095
|
}
|
|
1096
1096
|
/**
|
|
1097
|
-
* The official triggers our pills stand in for. Both are hidden and parked
|
|
1098
|
-
*
|
|
1099
|
-
* (styles/header.css.ts) so their
|
|
1097
|
+
* The official triggers our pills stand in for. Both are hidden and parked as
|
|
1098
|
+
* invisible anchors at the right end of the view-switch band
|
|
1099
|
+
* (styles/header.css.ts) so their popovers still mount and position from
|
|
1100
1100
|
* there; the pill is the visible control and forwards the tap.
|
|
1101
1101
|
*
|
|
1102
|
-
* Told apart by `aria-haspopup
|
|
1103
|
-
*
|
|
1104
|
-
*
|
|
1105
|
-
*
|
|
1106
|
-
*
|
|
1102
|
+
* Told apart by `aria-haspopup`: the subagent entry declares `tree` (its menu
|
|
1103
|
+
* is a session tree), the jobs entry declares none.
|
|
1104
|
+
*
|
|
1105
|
+
* Scoped to the HEADER, not to a slot (2026-08-21). DSH 0.1.1 moved the
|
|
1106
|
+
* subagent entry out of `conversation.session.header.actions` into a new
|
|
1107
|
+
* `conversation.session.header.lineage` slot inside the breadcrumb; every
|
|
1108
|
+
* slot-scoped lookup here silently stopped matching and the pill fell back to
|
|
1109
|
+
* opening the info card. The header is the stable boundary — which slot
|
|
1110
|
+
* inside it owns the entry is the suite's business, and it has changed once.
|
|
1111
|
+
*
|
|
1112
|
+
* Also deliberately NOT keyed on the root's class. In 0.1.1 that attribute is
|
|
1113
|
+
* `"ZKlsPq_root "` — with a TRAILING SPACE — so `[class$="_root"]` does not
|
|
1114
|
+
* match it at all (`[class$=]` tests the whole attribute string). The
|
|
1115
|
+
* trigger's own class has no such tail, so anchor on the trigger and reach
|
|
1116
|
+
* the root through `:has()` instead of naming it.
|
|
1107
1117
|
*/
|
|
1108
|
-
const
|
|
1109
|
-
const SUBAGENT_TRIGGER = `${
|
|
1110
|
-
const JOBS_TRIGGER = `${
|
|
1118
|
+
const HEADER = '[data-phase] header';
|
|
1119
|
+
const SUBAGENT_TRIGGER = `${HEADER} button[aria-haspopup="tree"]`;
|
|
1120
|
+
const JOBS_TRIGGER = `${HEADER} button[class$="_trigger"]:not([aria-haspopup])`;
|
|
1111
1121
|
/** One glanceable group: icon + count + state dot; opens the official popover. */
|
|
1112
1122
|
function ActivityPill({ kind, count, state, label, trigger, onFallback, Icon }) {
|
|
1113
1123
|
if (count === 0)
|
|
1114
1124
|
return null;
|
|
1115
|
-
return ((0, jsx_runtime_1.jsxs)("button", { type: "button", "data-mobile-nav": "activity-pill", "data-activity-kind": kind, "data-activity-state": state, "aria-label": label, title: label,
|
|
1116
|
-
|
|
1117
|
-
|
|
1125
|
+
return ((0, jsx_runtime_1.jsxs)("button", { type: "button", "data-mobile-nav": "activity-pill", "data-activity-kind": kind, "data-activity-state": state, "aria-label": label, title: label,
|
|
1126
|
+
/* Reached only when the official trigger is absent — when it exists,
|
|
1127
|
+
effects/native-trigger-overlay.ts lays it transparently over this
|
|
1128
|
+
pill and the tap never gets here. Scripting the official click is
|
|
1129
|
+
not an option: 0.1.1 ignores synthetic events (see that file). */
|
|
1130
|
+
onClick: () => {
|
|
1131
|
+
if (document.querySelector(trigger) === null)
|
|
1118
1132
|
onFallback();
|
|
1119
|
-
else
|
|
1120
|
-
el.click();
|
|
1121
1133
|
}, children: [(0, jsx_runtime_1.jsx)(Icon, {}), (0, jsx_runtime_1.jsx)("span", { "data-mobile-nav": "activity-count", children: count }), (0, jsx_runtime_1.jsx)("i", { "data-mobile-nav": "activity-dot", "aria-hidden": "true" })] }));
|
|
1122
1134
|
}
|
|
1123
1135
|
/**
|
|
@@ -4177,48 +4189,40 @@ exports.HEADER_CSS = `/* ---------- session header five-piece reflow (< 768px) -
|
|
|
4177
4189
|
[data-phase] header [data-slot="conversation.session.header.utilities"] > * {
|
|
4178
4190
|
display: none !important;
|
|
4179
4191
|
}
|
|
4180
|
-
/* Native background-task / subagent entries
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4192
|
+
/* Native background-task / subagent entries (rewritten for DSH 0.1.1,
|
|
4193
|
+
2026-08-21). Three upstream changes killed the old "park the root as a
|
|
4194
|
+
zero-width anchor, hide the trigger, forward the tap" design at once:
|
|
4195
|
+
|
|
4196
|
+
- the subagent entry moved out of header.actions into the new
|
|
4197
|
+
conversation.session.header.lineage slot (inside the breadcrumb);
|
|
4198
|
+
- its root's class attribute is now "ZKlsPq_root " — with a TRAILING
|
|
4199
|
+
SPACE — so a [class$="_root"] anchor matches nothing at all;
|
|
4200
|
+
- the trigger only reacts to TRUSTED input: .click() and synthetic
|
|
4201
|
+
pointer/mouse events leave aria-expanded untouched (measured on device
|
|
4202
|
+
with every plugin override stripped), so forwarding a tap in JS can
|
|
4203
|
+
never open it again.
|
|
4204
|
+
|
|
4205
|
+
The trigger's geometry is therefore owned at runtime by
|
|
4206
|
+
effects/native-trigger-overlay.ts, which lays the REAL button
|
|
4207
|
+
transparently over the activity pill — a genuine finger tap lands on the
|
|
4208
|
+
official element. Nothing in this stylesheet may size, hide, or set
|
|
4209
|
+
pointer-events on the trigger: that is exactly what would take the tap
|
|
4210
|
+
target away again.
|
|
4211
|
+
|
|
4212
|
+
CSS keeps only two chores. Dissolve the root so its box (and its
|
|
4213
|
+
stray separator, hidden below) stops occupying the breadcrumb, and let
|
|
4214
|
+
the trigger participate from wherever the overlay pins it. And no menu
|
|
4215
|
+
re-anchoring anymore: the 0.1.1 popover portals to <body> as
|
|
4216
|
+
position:fixed and positions itself from the trigger's viewport rect
|
|
4217
|
+
(measured 336px wide, fully on screen at 411px). */
|
|
4218
|
+
[data-phase] header [data-slot="conversation.session.header.actions"] > *:has(> button[class$="_trigger"]),
|
|
4219
|
+
[data-phase] header [data-slot="conversation.session.header.lineage"] > *:has(> button[class$="_trigger"]) {
|
|
4220
|
+
display: contents !important;
|
|
4203
4221
|
}
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
[data-phase] header [data-slot="conversation.session.header.actions"] > [class$="_root"] > button[class$="_trigger"] {
|
|
4222
|
+
[data-phase] header [data-slot="conversation.session.header.actions"] [class$="_separator"],
|
|
4223
|
+
[data-phase] header [data-slot="conversation.session.header.lineage"] [class$="_separator"] {
|
|
4207
4224
|
display: none !important;
|
|
4208
4225
|
}
|
|
4209
|
-
/* Anchor the popover to the RIGHT edge and clamp it to the viewport. The
|
|
4210
|
-
root now sits at the right edge with zero width, so the official
|
|
4211
|
-
\`left: 0\` would start the 336px panel off-screen; layout.css.ts's
|
|
4212
|
-
\`left: 8px\` re-anchor was written for a viewport-width containing block
|
|
4213
|
-
that this element is not. Right-anchoring is the one that holds for both
|
|
4214
|
-
(2026-08-20: the old pairing put the panel 29px past the right edge). */
|
|
4215
|
-
[data-phase] header [data-slot="conversation.session.header.actions"] [class$="_menu"] {
|
|
4216
|
-
left: auto !important;
|
|
4217
|
-
right: 0 !important;
|
|
4218
|
-
width: min(336px, calc(100vw - 32px)) !important;
|
|
4219
|
-
max-width: none !important;
|
|
4220
|
-
max-height: min(420px, calc(100dvh - 140px)) !important;
|
|
4221
|
-
}
|
|
4222
4226
|
/* The agent-preset mode badge specifically survives the blanket hide
|
|
4223
4227
|
above: layout.css.ts's shared <=1023px block targets it directly
|
|
4224
4228
|
(header [class$="_label"]:has(> svg) { display: block !important }),
|
|
@@ -4343,7 +4347,11 @@ exports.HEADER_CSS = `/* ---------- session header five-piece reflow (< 768px) -
|
|
|
4343
4347
|
touch-action: manipulation;
|
|
4344
4348
|
-webkit-tap-highlight-color: transparent;
|
|
4345
4349
|
}
|
|
4350
|
+
/* pointer-events:none so the invisible official trigger laid over this pill
|
|
4351
|
+
(effects/native-trigger-overlay.ts) is what a tap reaches. The pill is
|
|
4352
|
+
visuals only. */
|
|
4346
4353
|
[data-mobile-nav="activity-pill"] {
|
|
4354
|
+
pointer-events: none;
|
|
4347
4355
|
display: inline-flex;
|
|
4348
4356
|
align-items: center;
|
|
4349
4357
|
gap: 3px;
|
|
@@ -7483,6 +7491,108 @@ function installModalBack(ctx) {
|
|
|
7483
7491
|
}, 'dsh-zen-remote: modal back gesture');
|
|
7484
7492
|
}
|
|
7485
7493
|
};
|
|
7494
|
+
__modules["effects/native-trigger-overlay.js"] = function (require, module, exports) {
|
|
7495
|
+
"use strict";
|
|
7496
|
+
/**
|
|
7497
|
+
* Lay each official header trigger transparently over the activity pill that
|
|
7498
|
+
* stands in for it, so a real finger tap lands on the official element.
|
|
7499
|
+
*
|
|
7500
|
+
* Why not just forward the tap (what this replaced): DSH 0.1.1's subagent
|
|
7501
|
+
* trigger only reacts to TRUSTED input. Measured on device 2026-08-21 —
|
|
7502
|
+
* `.click()`, and synthetic `pointerdown`/`mousedown`/`pointerup`, all leave
|
|
7503
|
+
* `aria-expanded` at "false"; a CDP-dispatched real touch at the same point
|
|
7504
|
+
* opens it. That was verified with every plugin style override stripped off
|
|
7505
|
+
* the trigger, so it is the event path, not our CSS. Any design that scripts
|
|
7506
|
+
* the click is therefore dead, and this one moves the real control instead.
|
|
7507
|
+
*
|
|
7508
|
+
* Why this is now cheap: in 0.1.1 the popover portals to <body> as a
|
|
7509
|
+
* `position: fixed` layer (`ZKlsPq_menu`, z-index 100) and positions itself
|
|
7510
|
+
* from the trigger's viewport rect — measured 336px wide, fully on screen.
|
|
7511
|
+
* So placing the trigger over the pill also lands the popover at the pill,
|
|
7512
|
+
* and none of the old "park an anchor, re-anchor the menu" CSS is needed.
|
|
7513
|
+
*
|
|
7514
|
+
* The pill keeps the visuals (icon, count, state dot); the invisible official
|
|
7515
|
+
* trigger on top keeps the behaviour. Pills are `pointer-events: none` so a
|
|
7516
|
+
* tap can only ever reach the real thing — no double handling.
|
|
7517
|
+
*/
|
|
7518
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7519
|
+
exports.installNativeTriggerOverlay = installNativeTriggerOverlay;
|
|
7520
|
+
const PHONE = '(max-width: 767px)';
|
|
7521
|
+
const HEADER = '[data-phase] header';
|
|
7522
|
+
/** Pill → the official trigger it fronts. Same ARIA split the pills use. */
|
|
7523
|
+
const PAIRS = [
|
|
7524
|
+
{ pill: '[data-activity-kind="subagent"]', trigger: `${HEADER} button[aria-haspopup="tree"]` },
|
|
7525
|
+
{ pill: '[data-activity-kind="job"]', trigger: `${HEADER} button[class$="_trigger"]:not([aria-haspopup])` },
|
|
7526
|
+
];
|
|
7527
|
+
/** Marks a trigger we have taken over, so cleanup can find it again. */
|
|
7528
|
+
const TAKEN = 'data-zen-overlay';
|
|
7529
|
+
function place(trigger, pill) {
|
|
7530
|
+
const r = pill.getBoundingClientRect();
|
|
7531
|
+
if (r.width === 0 || r.height === 0)
|
|
7532
|
+
return;
|
|
7533
|
+
trigger.setAttribute(TAKEN, '');
|
|
7534
|
+
// Fixed, not absolute: the header's containing block is not the pill's, and
|
|
7535
|
+
// fixed rects are exactly what the portalled popover measures against.
|
|
7536
|
+
trigger.style.setProperty('position', 'fixed', 'important');
|
|
7537
|
+
trigger.style.setProperty('left', `${r.left}px`, 'important');
|
|
7538
|
+
trigger.style.setProperty('top', `${r.top}px`, 'important');
|
|
7539
|
+
trigger.style.setProperty('width', `${r.width}px`, 'important');
|
|
7540
|
+
trigger.style.setProperty('height', `${r.height}px`, 'important');
|
|
7541
|
+
trigger.style.setProperty('margin', '0', 'important');
|
|
7542
|
+
trigger.style.setProperty('padding', '0', 'important');
|
|
7543
|
+
trigger.style.setProperty('border', '0', 'important');
|
|
7544
|
+
trigger.style.setProperty('opacity', '0', 'important');
|
|
7545
|
+
trigger.style.setProperty('overflow', 'hidden', 'important');
|
|
7546
|
+
trigger.style.setProperty('pointer-events', 'auto', 'important');
|
|
7547
|
+
trigger.style.setProperty('z-index', '3', 'important');
|
|
7548
|
+
}
|
|
7549
|
+
function release(trigger) {
|
|
7550
|
+
trigger.removeAttribute(TAKEN);
|
|
7551
|
+
trigger.style.cssText = '';
|
|
7552
|
+
}
|
|
7553
|
+
function installNativeTriggerOverlay(ctx) {
|
|
7554
|
+
ctx.effect(() => {
|
|
7555
|
+
const phone = window.matchMedia(PHONE);
|
|
7556
|
+
let frame = 0;
|
|
7557
|
+
const sync = () => {
|
|
7558
|
+
for (const { pill, trigger } of PAIRS) {
|
|
7559
|
+
const t = document.querySelector(trigger);
|
|
7560
|
+
if (t === null)
|
|
7561
|
+
continue;
|
|
7562
|
+
const p = document.querySelector(pill);
|
|
7563
|
+
// No pill (count zero, or desktop): hand the trigger back untouched.
|
|
7564
|
+
if (!phone.matches || p === null) {
|
|
7565
|
+
if (t.hasAttribute(TAKEN))
|
|
7566
|
+
release(t);
|
|
7567
|
+
continue;
|
|
7568
|
+
}
|
|
7569
|
+
place(t, p);
|
|
7570
|
+
}
|
|
7571
|
+
};
|
|
7572
|
+
/* Layout settles a frame late after a re-render, and a stale rect would
|
|
7573
|
+
leave the tap target next to the pill instead of on it. */
|
|
7574
|
+
const schedule = () => {
|
|
7575
|
+
if (frame !== 0)
|
|
7576
|
+
return;
|
|
7577
|
+
frame = requestAnimationFrame(() => { frame = 0; sync(); });
|
|
7578
|
+
};
|
|
7579
|
+
const observer = new MutationObserver(schedule);
|
|
7580
|
+
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'data-activity-state', 'data-phase'] });
|
|
7581
|
+
window.addEventListener('resize', schedule);
|
|
7582
|
+
phone.addEventListener('change', schedule);
|
|
7583
|
+
schedule();
|
|
7584
|
+
return () => {
|
|
7585
|
+
if (frame !== 0)
|
|
7586
|
+
cancelAnimationFrame(frame);
|
|
7587
|
+
observer.disconnect();
|
|
7588
|
+
window.removeEventListener('resize', schedule);
|
|
7589
|
+
phone.removeEventListener('change', schedule);
|
|
7590
|
+
for (const t of document.querySelectorAll(`[${TAKEN}]`))
|
|
7591
|
+
release(t);
|
|
7592
|
+
};
|
|
7593
|
+
}, 'dsh-zen-remote: native trigger overlay');
|
|
7594
|
+
}
|
|
7595
|
+
};
|
|
7486
7596
|
__modules["effects/welcome-notice.js"] = function (require, module, exports) {
|
|
7487
7597
|
"use strict";
|
|
7488
7598
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -8058,6 +8168,7 @@ const header_status_ts_1 = require("./effects/header-status.js");
|
|
|
8058
8168
|
const gestures_ts_1 = require("./effects/gestures.js");
|
|
8059
8169
|
const turn_fold_ts_1 = require("./effects/turn-fold.js");
|
|
8060
8170
|
const modal_back_ts_1 = require("./effects/modal-back.js");
|
|
8171
|
+
const native_trigger_overlay_ts_1 = require("./effects/native-trigger-overlay.js");
|
|
8061
8172
|
const welcome_notice_ts_1 = require("./effects/welcome-notice.js");
|
|
8062
8173
|
const keyboard_guard_ts_1 = require("./effects/keyboard-guard.js");
|
|
8063
8174
|
const keyboard_avoid_ts_1 = require("./effects/keyboard-avoid.js");
|
|
@@ -8105,6 +8216,7 @@ function apply(ctx) {
|
|
|
8105
8216
|
// not carry this.
|
|
8106
8217
|
(0, turn_fold_ts_1.installTurnFold)(ctx);
|
|
8107
8218
|
(0, modal_back_ts_1.installModalBack)(ctx);
|
|
8219
|
+
(0, native_trigger_overlay_ts_1.installNativeTriggerOverlay)(ctx);
|
|
8108
8220
|
// "内测声明" first-run notice: keep it visible (CSS-hiding it leaked the
|
|
8109
8221
|
// dialog's #root inert lock — see effects/welcome-notice.ts) and offer a
|
|
8110
8222
|
// per-browser "不再弹出" opt-out instead.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MobileSessionHeader.d.ts","sourceRoot":"","sources":["../../../src/client/MobileSessionHeader.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAEjF,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"MobileSessionHeader.d.ts","sourceRoot":"","sources":["../../../src/client/MobileSessionHeader.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAEjF,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAyHjC,yEAAyE;AACzE,MAAM,MAAM,wBAAwB,GAChC,YAAY,CAAC,qCAAqC,CAAC,GACnD,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;AAE1B,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,OAAO,CAAA;IACf,EAAE,EAAE,iBAAiB,CAAA;CACtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,IAAI,WAAW,EAAE,CAQ5C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,IAAI,WAAW,EAAE,CAe3C;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,EAAE,EAAE,wBAAwB,+BA4E1F;AAED,sEAAsE;AACtE,MAAM,MAAM,0BAA0B,GAClC,YAAY,CAAC,uCAAuC,CAAC,GACrD,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;AAE1B;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,CAAC,EAAE,EAAE,0BAA0B,+BAkGtE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lay each official header trigger transparently over the activity pill that
|
|
3
|
+
* stands in for it, so a real finger tap lands on the official element.
|
|
4
|
+
*
|
|
5
|
+
* Why not just forward the tap (what this replaced): DSH 0.1.1's subagent
|
|
6
|
+
* trigger only reacts to TRUSTED input. Measured on device 2026-08-21 —
|
|
7
|
+
* `.click()`, and synthetic `pointerdown`/`mousedown`/`pointerup`, all leave
|
|
8
|
+
* `aria-expanded` at "false"; a CDP-dispatched real touch at the same point
|
|
9
|
+
* opens it. That was verified with every plugin style override stripped off
|
|
10
|
+
* the trigger, so it is the event path, not our CSS. Any design that scripts
|
|
11
|
+
* the click is therefore dead, and this one moves the real control instead.
|
|
12
|
+
*
|
|
13
|
+
* Why this is now cheap: in 0.1.1 the popover portals to <body> as a
|
|
14
|
+
* `position: fixed` layer (`ZKlsPq_menu`, z-index 100) and positions itself
|
|
15
|
+
* from the trigger's viewport rect — measured 336px wide, fully on screen.
|
|
16
|
+
* So placing the trigger over the pill also lands the popover at the pill,
|
|
17
|
+
* and none of the old "park an anchor, re-anchor the menu" CSS is needed.
|
|
18
|
+
*
|
|
19
|
+
* The pill keeps the visuals (icon, count, state dot); the invisible official
|
|
20
|
+
* trigger on top keeps the behaviour. Pills are `pointer-events: none` so a
|
|
21
|
+
* tap can only ever reach the real thing — no double handling.
|
|
22
|
+
*/
|
|
23
|
+
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
|
|
24
|
+
export declare function installNativeTriggerOverlay(ctx: ClientContext): void;
|
|
25
|
+
//# sourceMappingURL=native-trigger-overlay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-trigger-overlay.d.ts","sourceRoot":"","sources":["../../../../src/client/effects/native-trigger-overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAuC3E,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAuCpE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAA0B,MAAM,wCAAwC,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAA0B,MAAM,wCAAwC,CAAA;AAuBnG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,sCAAsC;QACtC,WAAW,EAAE,YAAY,CAAA;KAC1B;CACF;AAED,0GAA0G;AAC1G,eAAO,MAAM,MAAM,UAAgF,CAAA;AAEnG;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAsM9C"}
|
|
@@ -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-activity\"],\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 background-task / subagent entries: kept mounted, but parked as\n ZERO-WIDTH INVISIBLE ANCHORS at the right end of the view-switch band.\n Our activity pill is the visible control and forwards the tap to the\n official trigger, so the official popover \u2014 a child of this root \u2014 still\n mounts and positions from here.\n\n Why not just leave them visible in the header: each is ~103px wide while\n headerActions' grid column is 92px, and flex children that cannot shrink\n simply overflow \u2014 measured at 375px the subagent entry ran x=60..163\n straight across the centred title at x=100..275.\n\n Why not display:none them either: the menu lives inside the root, so\n hiding the root takes the popover with it and the pill would have\n nothing to open. Hide the TRIGGER (below) and keep the root as a box. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [class$=\"_root\"]:has(> button[class$=\"_trigger\"]) {\n display: block !important;\n position: absolute !important;\n right: 0 !important;\n top: 100% !important;\n width: 0 !important;\n height: 28px !important;\n overflow: visible !important;\n order: 0 !important;\n }\n /* The official trigger never shows \u2014 the pill replaced it. Programmatic\n .click() still reaches React's handler on a display:none button. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [class$=\"_root\"] > button[class$=\"_trigger\"] {\n display: none !important;\n }\n /* Anchor the popover to the RIGHT edge and clamp it to the viewport. The\n root now sits at the right edge with zero width, so the official\n `left: 0` would start the 336px panel off-screen; layout.css.ts's\n `left: 8px` re-anchor was written for a viewport-width containing block\n that this element is not. Right-anchoring is the one that holds for both\n (2026-08-20: the old pairing put the panel 29px past the right edge). */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] [class$=\"_menu\"] {\n left: auto !important;\n right: 0 !important;\n width: min(336px, calc(100vw - 32px)) !important;\n max-width: none !important;\n max-height: min(420px, calc(100dvh - 140px)) !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 /* Our own chip lives in the same slot, so the blanket hide above swallows\n it unless it is named here too (it was, on the first build: the chip\n rendered with the right counts and painted nothing). */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-activity\"] {\n display: inline-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 /* Activity chip (subagents / background tasks) rides the right end of the\n same band. It is a SIBLING of the switch button, not a child \u2014 that\n button spans the full row, so nesting would make every tap on the chip\n switch views too \u2014 and it shares titleRow as its containing block, so\n top:100% lands both on the same 28px line. z-index puts it above the\n row it overlaps. */\n [data-mobile-nav=\"header-activity\"] {\n position: absolute;\n right: 0;\n top: 100%;\n z-index: 1;\n display: inline-flex;\n align-items: center;\n gap: 9px;\n height: 28px;\n padding: 0 2px 0 10px;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-family: inherit;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"activity-pill\"] {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n height: 28px;\n padding: 0;\n border: none;\n background: transparent;\n color: inherit;\n font-family: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"activity-pill\"]:active {\n opacity: .55;\n }\n [data-mobile-nav=\"activity-count\"] {\n font-variant-numeric: tabular-nums;\n }\n /* Steady green = everything settled. */\n [data-mobile-nav=\"activity-dot\"] {\n display: block;\n width: 6px;\n height: 6px;\n margin-left: 1px;\n border-radius: 50%;\n background: var(--dsw-alias-state-success-primary, #16a34a);\n }\n /* A job that failed or was killed is not a success \u2014 never paint it green. */\n [data-activity-state=\"warning\"] [data-mobile-nav=\"activity-dot\"] {\n background: var(--dsw-alias-state-warn-primary, #d97706);\n }\n /* Pulsing blue = still running. Same state palette as the session dot. */\n [data-activity-state=\"running\"] [data-mobile-nav=\"activity-dot\"] {\n background: var(--dsw-alias-state-business-primary, #4f6ef7);\n animation: dsh-zen-activity-pulse 1.4s ease-in-out infinite;\n }\n @keyframes dsh-zen-activity-pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: .45; transform: scale(.72); }\n }\n @media (prefers-reduced-motion: reduce) {\n [data-activity-state=\"running\"] [data-mobile-nav=\"activity-dot\"] {\n animation: none;\n }\n }\n\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-activity\"],\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 background-task / subagent entries (rewritten for DSH 0.1.1,\n 2026-08-21). Three upstream changes killed the old \"park the root as a\n zero-width anchor, hide the trigger, forward the tap\" design at once:\n\n - the subagent entry moved out of header.actions into the new\n conversation.session.header.lineage slot (inside the breadcrumb);\n - its root's class attribute is now \"ZKlsPq_root \" \u2014 with a TRAILING\n SPACE \u2014 so a [class$=\"_root\"] anchor matches nothing at all;\n - the trigger only reacts to TRUSTED input: .click() and synthetic\n pointer/mouse events leave aria-expanded untouched (measured on device\n with every plugin override stripped), so forwarding a tap in JS can\n never open it again.\n\n The trigger's geometry is therefore owned at runtime by\n effects/native-trigger-overlay.ts, which lays the REAL button\n transparently over the activity pill \u2014 a genuine finger tap lands on the\n official element. Nothing in this stylesheet may size, hide, or set\n pointer-events on the trigger: that is exactly what would take the tap\n target away again.\n\n CSS keeps only two chores. Dissolve the root so its box (and its\n stray separator, hidden below) stops occupying the breadcrumb, and let\n the trigger participate from wherever the overlay pins it. And no menu\n re-anchoring anymore: the 0.1.1 popover portals to <body> as\n position:fixed and positions itself from the trigger's viewport rect\n (measured 336px wide, fully on screen at 411px). */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > *:has(> button[class$=\"_trigger\"]),\n [data-phase] header [data-slot=\"conversation.session.header.lineage\"] > *:has(> button[class$=\"_trigger\"]) {\n display: contents !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] [class$=\"_separator\"],\n [data-phase] header [data-slot=\"conversation.session.header.lineage\"] [class$=\"_separator\"] {\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 /* Our own chip lives in the same slot, so the blanket hide above swallows\n it unless it is named here too (it was, on the first build: the chip\n rendered with the right counts and painted nothing). */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-activity\"] {\n display: inline-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 /* Activity chip (subagents / background tasks) rides the right end of the\n same band. It is a SIBLING of the switch button, not a child \u2014 that\n button spans the full row, so nesting would make every tap on the chip\n switch views too \u2014 and it shares titleRow as its containing block, so\n top:100% lands both on the same 28px line. z-index puts it above the\n row it overlaps. */\n [data-mobile-nav=\"header-activity\"] {\n position: absolute;\n right: 0;\n top: 100%;\n z-index: 1;\n display: inline-flex;\n align-items: center;\n gap: 9px;\n height: 28px;\n padding: 0 2px 0 10px;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-family: inherit;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n /* pointer-events:none so the invisible official trigger laid over this pill\n (effects/native-trigger-overlay.ts) is what a tap reaches. The pill is\n visuals only. */\n [data-mobile-nav=\"activity-pill\"] {\n pointer-events: none;\n display: inline-flex;\n align-items: center;\n gap: 3px;\n height: 28px;\n padding: 0;\n border: none;\n background: transparent;\n color: inherit;\n font-family: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"activity-pill\"]:active {\n opacity: .55;\n }\n [data-mobile-nav=\"activity-count\"] {\n font-variant-numeric: tabular-nums;\n }\n /* Steady green = everything settled. */\n [data-mobile-nav=\"activity-dot\"] {\n display: block;\n width: 6px;\n height: 6px;\n margin-left: 1px;\n border-radius: 50%;\n background: var(--dsw-alias-state-success-primary, #16a34a);\n }\n /* A job that failed or was killed is not a success \u2014 never paint it green. */\n [data-activity-state=\"warning\"] [data-mobile-nav=\"activity-dot\"] {\n background: var(--dsw-alias-state-warn-primary, #d97706);\n }\n /* Pulsing blue = still running. Same state palette as the session dot. */\n [data-activity-state=\"running\"] [data-mobile-nav=\"activity-dot\"] {\n background: var(--dsw-alias-state-business-primary, #4f6ef7);\n animation: dsh-zen-activity-pulse 1.4s ease-in-out infinite;\n }\n @keyframes dsh-zen-activity-pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: .45; transform: scale(.72); }\n }\n @media (prefers-reduced-motion: reduce) {\n [data-activity-state=\"running\"] [data-mobile-nav=\"activity-dot\"] {\n animation: none;\n }\n }\n\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,k2tBAuftB,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-zen-remote",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
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",
|
|
@@ -106,18 +106,18 @@
|
|
|
106
106
|
}
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
|
-
"@deepseek-ai/dsh-api-remotes": "0.1.
|
|
110
|
-
"@deepseek-ai/dsh-client-connection": "0.1.
|
|
111
|
-
"@deepseek-ai/dsh-host-apiproxy": "0.1.
|
|
112
|
-
"@deepseek-ai/dsh-host-webserver": "0.1.
|
|
113
|
-
"@deepseek-ai/dsh-session": "0.1.
|
|
114
|
-
"@deepseek-ai/dsh-session-stats": "0.1.
|
|
115
|
-
"@deepseek-ai/dsh-token-meter": "0.1.
|
|
109
|
+
"@deepseek-ai/dsh-api-remotes": "0.1.1-rc.2",
|
|
110
|
+
"@deepseek-ai/dsh-client-connection": "0.1.1-rc.2",
|
|
111
|
+
"@deepseek-ai/dsh-host-apiproxy": "0.1.1-rc.2",
|
|
112
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
|
|
113
|
+
"@deepseek-ai/dsh-session": "0.1.1-rc.2",
|
|
114
|
+
"@deepseek-ai/dsh-session-stats": "0.1.1-rc.2",
|
|
115
|
+
"@deepseek-ai/dsh-token-meter": "0.1.1-rc.2",
|
|
116
116
|
"@types/node": "^26.2.0",
|
|
117
117
|
"@types/react": "~18.3.1",
|
|
118
118
|
"@types/react-dom": "~18.3.0",
|
|
119
119
|
"typescript": "^6.0.3",
|
|
120
|
-
"@deepseek-ai/dsh-tools": "0.1.
|
|
120
|
+
"@deepseek-ai/dsh-tools": "0.1.1-rc.2"
|
|
121
121
|
},
|
|
122
122
|
"scripts": {
|
|
123
123
|
"build": "tsc -p tsconfig.json && tsc -p tsconfig.client.json && node scripts/build-client.mjs",
|
|
@@ -60,20 +60,30 @@ function IconTaskOutline14({ size = 14 }: { size?: number }) {
|
|
|
60
60
|
type ActivityState = 'running' | 'done' | 'warning'
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* The official triggers our pills stand in for. Both are hidden and parked
|
|
64
|
-
*
|
|
65
|
-
* (styles/header.css.ts) so their
|
|
63
|
+
* The official triggers our pills stand in for. Both are hidden and parked as
|
|
64
|
+
* invisible anchors at the right end of the view-switch band
|
|
65
|
+
* (styles/header.css.ts) so their popovers still mount and position from
|
|
66
66
|
* there; the pill is the visible control and forwards the tap.
|
|
67
67
|
*
|
|
68
|
-
* Told apart by `aria-haspopup
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
68
|
+
* Told apart by `aria-haspopup`: the subagent entry declares `tree` (its menu
|
|
69
|
+
* is a session tree), the jobs entry declares none.
|
|
70
|
+
*
|
|
71
|
+
* Scoped to the HEADER, not to a slot (2026-08-21). DSH 0.1.1 moved the
|
|
72
|
+
* subagent entry out of `conversation.session.header.actions` into a new
|
|
73
|
+
* `conversation.session.header.lineage` slot inside the breadcrumb; every
|
|
74
|
+
* slot-scoped lookup here silently stopped matching and the pill fell back to
|
|
75
|
+
* opening the info card. The header is the stable boundary — which slot
|
|
76
|
+
* inside it owns the entry is the suite's business, and it has changed once.
|
|
77
|
+
*
|
|
78
|
+
* Also deliberately NOT keyed on the root's class. In 0.1.1 that attribute is
|
|
79
|
+
* `"ZKlsPq_root "` — with a TRAILING SPACE — so `[class$="_root"]` does not
|
|
80
|
+
* match it at all (`[class$=]` tests the whole attribute string). The
|
|
81
|
+
* trigger's own class has no such tail, so anchor on the trigger and reach
|
|
82
|
+
* the root through `:has()` instead of naming it.
|
|
73
83
|
*/
|
|
74
|
-
const
|
|
75
|
-
const SUBAGENT_TRIGGER = `${
|
|
76
|
-
const JOBS_TRIGGER = `${
|
|
84
|
+
const HEADER = '[data-phase] header'
|
|
85
|
+
const SUBAGENT_TRIGGER = `${HEADER} button[aria-haspopup="tree"]`
|
|
86
|
+
const JOBS_TRIGGER = `${HEADER} button[class$="_trigger"]:not([aria-haspopup])`
|
|
77
87
|
|
|
78
88
|
/** One glanceable group: icon + count + state dot; opens the official popover. */
|
|
79
89
|
function ActivityPill(
|
|
@@ -98,10 +108,12 @@ function ActivityPill(
|
|
|
98
108
|
data-activity-state={state}
|
|
99
109
|
aria-label={label}
|
|
100
110
|
title={label}
|
|
111
|
+
/* Reached only when the official trigger is absent — when it exists,
|
|
112
|
+
effects/native-trigger-overlay.ts lays it transparently over this
|
|
113
|
+
pill and the tap never gets here. Scripting the official click is
|
|
114
|
+
not an option: 0.1.1 ignores synthetic events (see that file). */
|
|
101
115
|
onClick={() => {
|
|
102
|
-
|
|
103
|
-
if (el === null) onFallback()
|
|
104
|
-
else el.click()
|
|
116
|
+
if (document.querySelector(trigger) === null) onFallback()
|
|
105
117
|
}}
|
|
106
118
|
>
|
|
107
119
|
<Icon />
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lay each official header trigger transparently over the activity pill that
|
|
3
|
+
* stands in for it, so a real finger tap lands on the official element.
|
|
4
|
+
*
|
|
5
|
+
* Why not just forward the tap (what this replaced): DSH 0.1.1's subagent
|
|
6
|
+
* trigger only reacts to TRUSTED input. Measured on device 2026-08-21 —
|
|
7
|
+
* `.click()`, and synthetic `pointerdown`/`mousedown`/`pointerup`, all leave
|
|
8
|
+
* `aria-expanded` at "false"; a CDP-dispatched real touch at the same point
|
|
9
|
+
* opens it. That was verified with every plugin style override stripped off
|
|
10
|
+
* the trigger, so it is the event path, not our CSS. Any design that scripts
|
|
11
|
+
* the click is therefore dead, and this one moves the real control instead.
|
|
12
|
+
*
|
|
13
|
+
* Why this is now cheap: in 0.1.1 the popover portals to <body> as a
|
|
14
|
+
* `position: fixed` layer (`ZKlsPq_menu`, z-index 100) and positions itself
|
|
15
|
+
* from the trigger's viewport rect — measured 336px wide, fully on screen.
|
|
16
|
+
* So placing the trigger over the pill also lands the popover at the pill,
|
|
17
|
+
* and none of the old "park an anchor, re-anchor the menu" CSS is needed.
|
|
18
|
+
*
|
|
19
|
+
* The pill keeps the visuals (icon, count, state dot); the invisible official
|
|
20
|
+
* trigger on top keeps the behaviour. Pills are `pointer-events: none` so a
|
|
21
|
+
* tap can only ever reach the real thing — no double handling.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
|
|
25
|
+
|
|
26
|
+
const PHONE = '(max-width: 767px)'
|
|
27
|
+
const HEADER = '[data-phase] header'
|
|
28
|
+
|
|
29
|
+
/** Pill → the official trigger it fronts. Same ARIA split the pills use. */
|
|
30
|
+
const PAIRS: ReadonlyArray<{ pill: string, trigger: string }> = [
|
|
31
|
+
{ pill: '[data-activity-kind="subagent"]', trigger: `${HEADER} button[aria-haspopup="tree"]` },
|
|
32
|
+
{ pill: '[data-activity-kind="job"]', trigger: `${HEADER} button[class$="_trigger"]:not([aria-haspopup])` },
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
/** Marks a trigger we have taken over, so cleanup can find it again. */
|
|
36
|
+
const TAKEN = 'data-zen-overlay'
|
|
37
|
+
|
|
38
|
+
function place(trigger: HTMLElement, pill: Element): void {
|
|
39
|
+
const r = pill.getBoundingClientRect()
|
|
40
|
+
if (r.width === 0 || r.height === 0) return
|
|
41
|
+
trigger.setAttribute(TAKEN, '')
|
|
42
|
+
// Fixed, not absolute: the header's containing block is not the pill's, and
|
|
43
|
+
// fixed rects are exactly what the portalled popover measures against.
|
|
44
|
+
trigger.style.setProperty('position', 'fixed', 'important')
|
|
45
|
+
trigger.style.setProperty('left', `${r.left}px`, 'important')
|
|
46
|
+
trigger.style.setProperty('top', `${r.top}px`, 'important')
|
|
47
|
+
trigger.style.setProperty('width', `${r.width}px`, 'important')
|
|
48
|
+
trigger.style.setProperty('height', `${r.height}px`, 'important')
|
|
49
|
+
trigger.style.setProperty('margin', '0', 'important')
|
|
50
|
+
trigger.style.setProperty('padding', '0', 'important')
|
|
51
|
+
trigger.style.setProperty('border', '0', 'important')
|
|
52
|
+
trigger.style.setProperty('opacity', '0', 'important')
|
|
53
|
+
trigger.style.setProperty('overflow', 'hidden', 'important')
|
|
54
|
+
trigger.style.setProperty('pointer-events', 'auto', 'important')
|
|
55
|
+
trigger.style.setProperty('z-index', '3', 'important')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function release(trigger: HTMLElement): void {
|
|
59
|
+
trigger.removeAttribute(TAKEN)
|
|
60
|
+
trigger.style.cssText = ''
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function installNativeTriggerOverlay(ctx: ClientContext): void {
|
|
64
|
+
ctx.effect(() => {
|
|
65
|
+
const phone = window.matchMedia(PHONE)
|
|
66
|
+
let frame = 0
|
|
67
|
+
|
|
68
|
+
const sync = (): void => {
|
|
69
|
+
for (const { pill, trigger } of PAIRS) {
|
|
70
|
+
const t = document.querySelector<HTMLElement>(trigger)
|
|
71
|
+
if (t === null) continue
|
|
72
|
+
const p = document.querySelector(pill)
|
|
73
|
+
// No pill (count zero, or desktop): hand the trigger back untouched.
|
|
74
|
+
if (!phone.matches || p === null) {
|
|
75
|
+
if (t.hasAttribute(TAKEN)) release(t)
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
place(t, p)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/* Layout settles a frame late after a re-render, and a stale rect would
|
|
82
|
+
leave the tap target next to the pill instead of on it. */
|
|
83
|
+
const schedule = (): void => {
|
|
84
|
+
if (frame !== 0) return
|
|
85
|
+
frame = requestAnimationFrame(() => { frame = 0; sync() })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const observer = new MutationObserver(schedule)
|
|
89
|
+
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'data-activity-state', 'data-phase'] })
|
|
90
|
+
window.addEventListener('resize', schedule)
|
|
91
|
+
phone.addEventListener('change', schedule)
|
|
92
|
+
schedule()
|
|
93
|
+
|
|
94
|
+
return () => {
|
|
95
|
+
if (frame !== 0) cancelAnimationFrame(frame)
|
|
96
|
+
observer.disconnect()
|
|
97
|
+
window.removeEventListener('resize', schedule)
|
|
98
|
+
phone.removeEventListener('change', schedule)
|
|
99
|
+
for (const t of document.querySelectorAll<HTMLElement>(`[${TAKEN}]`)) release(t)
|
|
100
|
+
}
|
|
101
|
+
}, 'dsh-zen-remote: native trigger overlay')
|
|
102
|
+
}
|
package/src/client/index.tsx
CHANGED
|
@@ -16,6 +16,7 @@ import { installHeaderStatusDot } from './effects/header-status.ts'
|
|
|
16
16
|
import { installGestures } from './effects/gestures.ts'
|
|
17
17
|
import { installTurnFold } from './effects/turn-fold.ts'
|
|
18
18
|
import { installModalBack } from './effects/modal-back.ts'
|
|
19
|
+
import { installNativeTriggerOverlay } from './effects/native-trigger-overlay.ts'
|
|
19
20
|
import { installWelcomeNoticeOptOut } from './effects/welcome-notice.ts'
|
|
20
21
|
import { installKeyboardGuard } from './effects/keyboard-guard.ts'
|
|
21
22
|
import { installKeyboardAvoid } from './effects/keyboard-avoid.ts'
|
|
@@ -82,6 +83,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
82
83
|
// not carry this.
|
|
83
84
|
installTurnFold(ctx)
|
|
84
85
|
installModalBack(ctx)
|
|
86
|
+
installNativeTriggerOverlay(ctx)
|
|
85
87
|
|
|
86
88
|
// "内测声明" first-run notice: keep it visible (CSS-hiding it leaked the
|
|
87
89
|
// dialog's #root inert lock — see effects/welcome-notice.ts) and offer a
|
|
@@ -267,48 +267,40 @@ export const HEADER_CSS = `/* ---------- session header five-piece reflow (< 768
|
|
|
267
267
|
[data-phase] header [data-slot="conversation.session.header.utilities"] > * {
|
|
268
268
|
display: none !important;
|
|
269
269
|
}
|
|
270
|
-
/* Native background-task / subagent entries
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
official trigger, so the official popover — a child of this root — still
|
|
274
|
-
mounts and positions from here.
|
|
270
|
+
/* Native background-task / subagent entries (rewritten for DSH 0.1.1,
|
|
271
|
+
2026-08-21). Three upstream changes killed the old "park the root as a
|
|
272
|
+
zero-width anchor, hide the trigger, forward the tap" design at once:
|
|
275
273
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
274
|
+
- the subagent entry moved out of header.actions into the new
|
|
275
|
+
conversation.session.header.lineage slot (inside the breadcrumb);
|
|
276
|
+
- its root's class attribute is now "ZKlsPq_root " — with a TRAILING
|
|
277
|
+
SPACE — so a [class$="_root"] anchor matches nothing at all;
|
|
278
|
+
- the trigger only reacts to TRUSTED input: .click() and synthetic
|
|
279
|
+
pointer/mouse events leave aria-expanded untouched (measured on device
|
|
280
|
+
with every plugin override stripped), so forwarding a tap in JS can
|
|
281
|
+
never open it again.
|
|
280
282
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
283
|
+
The trigger's geometry is therefore owned at runtime by
|
|
284
|
+
effects/native-trigger-overlay.ts, which lays the REAL button
|
|
285
|
+
transparently over the activity pill — a genuine finger tap lands on the
|
|
286
|
+
official element. Nothing in this stylesheet may size, hide, or set
|
|
287
|
+
pointer-events on the trigger: that is exactly what would take the tap
|
|
288
|
+
target away again.
|
|
289
|
+
|
|
290
|
+
CSS keeps only two chores. Dissolve the root so its box (and its
|
|
291
|
+
stray separator, hidden below) stops occupying the breadcrumb, and let
|
|
292
|
+
the trigger participate from wherever the overlay pins it. And no menu
|
|
293
|
+
re-anchoring anymore: the 0.1.1 popover portals to <body> as
|
|
294
|
+
position:fixed and positions itself from the trigger's viewport rect
|
|
295
|
+
(measured 336px wide, fully on screen at 411px). */
|
|
296
|
+
[data-phase] header [data-slot="conversation.session.header.actions"] > *:has(> button[class$="_trigger"]),
|
|
297
|
+
[data-phase] header [data-slot="conversation.session.header.lineage"] > *:has(> button[class$="_trigger"]) {
|
|
298
|
+
display: contents !important;
|
|
299
|
+
}
|
|
300
|
+
[data-phase] header [data-slot="conversation.session.header.actions"] [class$="_separator"],
|
|
301
|
+
[data-phase] header [data-slot="conversation.session.header.lineage"] [class$="_separator"] {
|
|
297
302
|
display: none !important;
|
|
298
303
|
}
|
|
299
|
-
/* Anchor the popover to the RIGHT edge and clamp it to the viewport. The
|
|
300
|
-
root now sits at the right edge with zero width, so the official
|
|
301
|
-
\`left: 0\` would start the 336px panel off-screen; layout.css.ts's
|
|
302
|
-
\`left: 8px\` re-anchor was written for a viewport-width containing block
|
|
303
|
-
that this element is not. Right-anchoring is the one that holds for both
|
|
304
|
-
(2026-08-20: the old pairing put the panel 29px past the right edge). */
|
|
305
|
-
[data-phase] header [data-slot="conversation.session.header.actions"] [class$="_menu"] {
|
|
306
|
-
left: auto !important;
|
|
307
|
-
right: 0 !important;
|
|
308
|
-
width: min(336px, calc(100vw - 32px)) !important;
|
|
309
|
-
max-width: none !important;
|
|
310
|
-
max-height: min(420px, calc(100dvh - 140px)) !important;
|
|
311
|
-
}
|
|
312
304
|
/* The agent-preset mode badge specifically survives the blanket hide
|
|
313
305
|
above: layout.css.ts's shared <=1023px block targets it directly
|
|
314
306
|
(header [class$="_label"]:has(> svg) { display: block !important }),
|
|
@@ -433,7 +425,11 @@ export const HEADER_CSS = `/* ---------- session header five-piece reflow (< 768
|
|
|
433
425
|
touch-action: manipulation;
|
|
434
426
|
-webkit-tap-highlight-color: transparent;
|
|
435
427
|
}
|
|
428
|
+
/* pointer-events:none so the invisible official trigger laid over this pill
|
|
429
|
+
(effects/native-trigger-overlay.ts) is what a tap reaches. The pill is
|
|
430
|
+
visuals only. */
|
|
436
431
|
[data-mobile-nav="activity-pill"] {
|
|
432
|
+
pointer-events: none;
|
|
437
433
|
display: inline-flex;
|
|
438
434
|
align-items: center;
|
|
439
435
|
gap: 3px;
|