dsh-zen-remote 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/docs/interface.md +1 -0
- package/lib/client.js +458 -33
- package/lib/types/client/effects/keyboard-guard.d.ts +27 -0
- package/lib/types/client/effects/keyboard-guard.d.ts.map +1 -0
- package/lib/types/client/effects/turn-fold.d.ts.map +1 -1
- package/lib/types/client/effects/welcome-notice.d.ts +20 -0
- package/lib/types/client/effects/welcome-notice.d.ts.map +1 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/styles/base.css.d.ts +1 -1
- package/lib/types/client/styles/base.css.d.ts.map +1 -1
- package/lib/types/client/styles/chips.css.d.ts +1 -1
- package/lib/types/client/styles/chips.css.d.ts.map +1 -1
- package/lib/types/client/styles/compat.css.d.ts +1 -1
- package/lib/types/client/styles/compat.css.d.ts.map +1 -1
- package/lib/types/client/styles/content.css.d.ts +2 -0
- package/lib/types/client/styles/content.css.d.ts.map +1 -0
- package/lib/types/client/styles/header.css.d.ts +1 -1
- package/lib/types/client/styles/header.css.d.ts.map +1 -1
- package/lib/types/client/styles/index.d.ts +3 -2
- package/lib/types/client/styles/index.d.ts.map +1 -1
- package/package.json +7 -9
- package/pwa/app.css +7 -2
- package/src/client/effects/keyboard-guard.ts +121 -0
- package/src/client/effects/turn-fold.ts +14 -0
- package/src/client/effects/welcome-notice.ts +66 -0
- package/src/client/index.tsx +12 -0
- package/src/client/styles/base.css.ts +28 -13
- package/src/client/styles/chips.css.ts +10 -10
- package/src/client/styles/compat.css.ts +51 -0
- package/src/client/styles/content.css.ts +103 -0
- package/src/client/styles/header.css.ts +41 -7
- package/src/client/styles/index.ts +5 -3
package/README.md
CHANGED
|
@@ -207,11 +207,31 @@ open http://127.0.0.1:3088/lan-gate/admin
|
|
|
207
207
|
- 真 PWA:manifest + service worker,可装到主屏、可离线打开
|
|
208
208
|
- 真 Web Push:VAPID + aes128gcm,通知默认不带对话正文
|
|
209
209
|
- `push_notify` 工具:模型可在关键节点自己推一条,带限流
|
|
210
|
+
- 「内测声明」弹窗注入「不再弹出」可选项:远程访问每次刷新都会重弹声明,点一次后本设备记住选择、以后自动关闭
|
|
210
211
|
|
|
211
212
|
深度说明:[界面](docs/interface.md) · [公网接入](docs/remote-access.md)
|
|
212
213
|
|
|
213
214
|
---
|
|
214
215
|
|
|
216
|
+
## 已适配的第三方插件
|
|
217
|
+
|
|
218
|
+
移动端 UI 对下列插件做了专门适配。所有适配都锚定对应插件自己的 DOM 标记:
|
|
219
|
+
没装该插件时规则不生效,装了未列出的插件也不会被误伤。
|
|
220
|
+
|
|
221
|
+
| 插件 | 移动端适配内容 | 实测版本 |
|
|
222
|
+
| --- | --- | --- |
|
|
223
|
+
| [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) | 会话页头部提供工作台入口按钮;面板变手机全宽抽屉并避让刘海安全区;底部居中的关闭按钮 | 0.12.2 |
|
|
224
|
+
| [@nanmicoder/dsh-agent-teams](https://github.com/NanmiCoder/dsh-agent-teams) | AgentTeams 活动浮层挪到会话头部下方(原位置压住头部按钮)、会话列表页自动隐藏;子代理会话头部保留可点的父会话标题,一键切回主会话 | 0.1.6 |
|
|
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.0 |
|
|
227
|
+
| dsh-at-file | @文件引用,配合附件上传的 `@` 路径引用使用 | — |
|
|
228
|
+
| [dsh-vision-toolkit](https://www.npmjs.com/package/@anionex/dsh-vision-toolkit) | 图像 Q&A/OCR,配合手机端附件上传使用 | — |
|
|
229
|
+
| [dsh-web-ui 全家桶](https://www.npmjs.com/package/@linxin666/dsh-web-ui-all) | 沿用上游 dsh-web-mobile 的兼容规则(文件树 / 预览浮层限宽居中等) | — |
|
|
230
|
+
|
|
231
|
+
各项适配的技术细节(锚点选择器、断点、取舍记录)见[界面文档](docs/interface.md)的「兼容插件」一节。
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
215
235
|
## 已知问题
|
|
216
236
|
|
|
217
237
|
**iOS 26.x 独立 PWA 视口缩水**:加到主屏后视口底部会少掉一条状态栏高度,普通 Safari 标签页正常。这是 iOS 系统缺陷,缺掉的区域在文档之外,CSS 够不着;本插件做了三层缓解(浅色 manifest 背景 + 安全区补偿 + 强制重排),能减轻但不保证复原。彻底恢复只能整个 App 退出重开。
|
package/docs/interface.md
CHANGED
|
@@ -162,6 +162,7 @@ composer 最左的回形针打开的是**手机本地**的文件选择器(iOS
|
|
|
162
162
|
## 兼容插件
|
|
163
163
|
|
|
164
164
|
- [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar)(移动端全宽抽屉,本插件在会话页头部给它留入口位)——本机实测 **0.12.2**
|
|
165
|
+
- [@nanmicoder/dsh-agent-teams](https://github.com/NanmiCoder/dsh-agent-teams)(AgentTeams 活动浮层:手机端挪到会话头部下方、避开安全区,会话列表页隐藏;子代理会话头部保留可点的父会话面包屑用于切回)——本机实测 **0.1.6**
|
|
165
166
|
- [dsh-usage-stats](https://github.com/Ychris12138/dsh-usage-stats)(用量与余额,主屏 chips 行可直接打开)
|
|
166
167
|
- [@opendsh/dsh-plugin-scheduled-tasks](https://github.com/Ceelog/dsh-plugins)(定时任务,主屏 chips 行自动收割入口)——本机实测 **0.2.0**
|
|
167
168
|
- dsh-at-file(@文件引用)
|
package/lib/client.js
CHANGED
|
@@ -1692,24 +1692,39 @@ exports.BASE_CSS = `
|
|
|
1692
1692
|
}
|
|
1693
1693
|
}
|
|
1694
1694
|
|
|
1695
|
-
/* ---------- beta-notice
|
|
1696
|
-
User-directed (2026-08-17):
|
|
1697
|
-
including desktop. This is the ONE place this plugin
|
|
1698
|
-
its own ">=1024px strict no-op" rule
|
|
1699
|
-
Selector notes:
|
|
1695
|
+
/* ---------- beta-notice handling (ALL widths — deliberate exception) ----
|
|
1696
|
+
User-directed (2026-08-17): tone down DSH's beta/preview notices
|
|
1697
|
+
everywhere, including desktop. This is the ONE place this plugin
|
|
1698
|
+
intentionally breaks its own ">=1024px strict no-op" rule.
|
|
1700
1699
|
- The hero "预览版" badge only carries a hashed class; per repo convention
|
|
1701
1700
|
the stable part is the suffix (_previewBadge), matched with [class$=].
|
|
1702
|
-
- The first-run "内测声明" modal
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
block taps; the app never requires acknowledging this dialog to work. */
|
|
1701
|
+
- The first-run "内测声明" modal must NOT be hidden with CSS: while it is
|
|
1702
|
+
mounted it holds document #root inert, so display:none leaves the whole
|
|
1703
|
+
app unclickable (2026-08-18 incident). effects/welcome-notice.ts instead
|
|
1704
|
+
injects a "不再弹出" opt-out button; this styles it. */
|
|
1707
1705
|
[class$="_previewBadge"] {
|
|
1708
1706
|
display: none !important;
|
|
1709
1707
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1708
|
+
/* Mirrors the official primitives Button (md capsule, outline variant):
|
|
1709
|
+
36px height, 18px radius, --dsw-alias-border-l2 hairline. */
|
|
1710
|
+
.zen-welcome-optout {
|
|
1711
|
+
display: inline-flex;
|
|
1712
|
+
align-items: center;
|
|
1713
|
+
justify-content: center;
|
|
1714
|
+
height: 36px;
|
|
1715
|
+
padding: 0 14px;
|
|
1716
|
+
margin-right: 8px;
|
|
1717
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, .15));
|
|
1718
|
+
border-radius: 18px;
|
|
1719
|
+
background: transparent;
|
|
1720
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
1721
|
+
font-size: 14px;
|
|
1722
|
+
line-height: 22px;
|
|
1723
|
+
cursor: pointer;
|
|
1724
|
+
-webkit-tap-highlight-color: transparent;
|
|
1725
|
+
}
|
|
1726
|
+
.zen-welcome-optout:hover {
|
|
1727
|
+
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));
|
|
1713
1728
|
}
|
|
1714
1729
|
`;
|
|
1715
1730
|
};
|
|
@@ -2901,6 +2916,57 @@ exports.COMPAT_CSS = ` /* ---------- dsh-web-ui family compatibility ----------
|
|
|
2901
2916
|
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));
|
|
2902
2917
|
}
|
|
2903
2918
|
}
|
|
2919
|
+
|
|
2920
|
+
/* ---------- dsh-agent-teams: phone floater reposition (2026-08-18) ----------
|
|
2921
|
+
THIRD-PARTY COMPAT RULE — @nanmicoder/dsh-agent-teams (the AgentTeams
|
|
2922
|
+
activity floater: a collapsed badge + expandable panel, body-portaled
|
|
2923
|
+
under its own mount marker div[data-agent-teams-host]). Its own CSS
|
|
2924
|
+
fixes both at top:56px (<=640px) viewport coordinates and never reads
|
|
2925
|
+
env(safe-area-inset-*), so on a notched iPhone the badge lands INSIDE
|
|
2926
|
+
the safe-area + this plugin's 76px session header band — overlapping
|
|
2927
|
+
the info and workbench buttons (real-device report, 2026-08-18).
|
|
2928
|
+
Move both below the header instead: safe-area + 76px header chrome
|
|
2929
|
+
(48px title row + 28px view-switch row, header.css.ts) + 8px gap.
|
|
2930
|
+
Anchored on the plugin's own mount marker + class-suffix selectors per
|
|
2931
|
+
this repo's hashed-class convention — when dsh-agent-teams is not
|
|
2932
|
+
installed the marker never exists and this whole section is inert
|
|
2933
|
+
(same presence-gating as the dsh-better-sidebar rules above).
|
|
2934
|
+
!important because their positions come from html-level CSS vars
|
|
2935
|
+
(--agent-teams-panel-top et al.) whose style tag order vs ours is not
|
|
2936
|
+
guaranteed. z-index: their 2147483000 would float the badge over every
|
|
2937
|
+
one of this plugin's own overlays (drawer 40, info sheet 55-57, close
|
|
2938
|
+
pill 70); 30 keeps it above content but below all of them. */
|
|
2939
|
+
@media (max-width: 767px) {
|
|
2940
|
+
[data-agent-teams-host] [class$="_badge"],
|
|
2941
|
+
[data-agent-teams-host] [class$="_panel"] {
|
|
2942
|
+
top: calc(var(--mnav-sat) + 84px) !important;
|
|
2943
|
+
right: 10px !important;
|
|
2944
|
+
z-index: 30 !important;
|
|
2945
|
+
}
|
|
2946
|
+
[data-agent-teams-host] [class$="_panel"] {
|
|
2947
|
+
left: 10px !important;
|
|
2948
|
+
width: auto !important;
|
|
2949
|
+
/* Their min-height:min(560px, calc(100dvh - 56 - 56)) resolves against
|
|
2950
|
+
the OLD top var — on short phones it exceeds the space left below
|
|
2951
|
+
the moved-down top edge and would force overflow; content-sized with
|
|
2952
|
+
a hard cap replaces it. 76px bottom clearance keeps the collapsed
|
|
2953
|
+
composer's top edge visible under the panel. */
|
|
2954
|
+
min-height: 0 !important;
|
|
2955
|
+
max-height: calc(100dvh - var(--mnav-sat) - 84px - var(--mnav-sab) - 76px) !important;
|
|
2956
|
+
}
|
|
2957
|
+
/* The floater belongs to the SESSION page: on the home list it hovered
|
|
2958
|
+
over the session rows (user feedback, 2026-08-18). The phone page
|
|
2959
|
+
stack is this plugin's own overlay — the official conversation (and
|
|
2960
|
+
with it the floater's "current session") stays mounted underneath, so
|
|
2961
|
+
agent-teams keeps rendering it; hide it whenever the home level is the
|
|
2962
|
+
visible one (data-view flips to "session" inside a session, and the
|
|
2963
|
+
badge/panel come right back). :has() is already a documented
|
|
2964
|
+
requirement of this plugin (Chromium 105+, docs/interface.md). */
|
|
2965
|
+
body:has([data-mobile-nav="home"][data-view="home"]) [data-agent-teams-host] [class$="_badge"],
|
|
2966
|
+
body:has([data-mobile-nav="home"][data-view="home"]) [data-agent-teams-host] [class$="_panel"] {
|
|
2967
|
+
display: none !important;
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2904
2970
|
`;
|
|
2905
2971
|
};
|
|
2906
2972
|
__modules["styles/misc.css.js"] = function (require, module, exports) {
|
|
@@ -3678,21 +3744,55 @@ exports.HEADER_CSS = `/* ---------- session header five-piece reflow (< 768px) -
|
|
|
3678
3744
|
min-width: 0;
|
|
3679
3745
|
overflow: hidden;
|
|
3680
3746
|
}
|
|
3681
|
-
/*
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3747
|
+
/* Subagent sessions keep ONE ancestor crumb (2026-08-18): the PC header
|
|
3748
|
+
lets you tap the parent session's title to switch back — that affordance
|
|
3749
|
+
was lost on phone when the whole parent chain was hidden as "多余项".
|
|
3750
|
+
Show only the immediate parent (for agent-teams member sessions that IS
|
|
3751
|
+
the captain/main session; deeper ancestors stay hidden — a 3-level chain
|
|
3752
|
+
cannot fit the ~206px center column at 390px). The parent crumb is the
|
|
3753
|
+
official breadcrumb <button> (dsh-client-ui-conversation renders
|
|
3754
|
+
open(parentId) as its onClick), so tapping needs no JS from this plugin,
|
|
3755
|
+
and main sessions (chain length 1) have no :nth-last-child(2) seg at
|
|
3756
|
+
all — the centered single title is pixel-identical to before. */
|
|
3757
|
+
[data-phase] header [class$="_crumbSeg"]:not(:nth-last-child(-n+2)) {
|
|
3685
3758
|
display: none !important;
|
|
3686
3759
|
}
|
|
3760
|
+
[data-phase] header [class$="_crumbSeg"]:nth-last-child(2) {
|
|
3761
|
+
display: inline-flex;
|
|
3762
|
+
align-items: center;
|
|
3763
|
+
flex: none;
|
|
3764
|
+
min-width: 0;
|
|
3765
|
+
max-width: 38%;
|
|
3766
|
+
}
|
|
3767
|
+
/* Ancestors BEFORE the parent seg are hidden, which would leave the
|
|
3768
|
+
parent seg's own leading "/" dangling at the start of the title. */
|
|
3769
|
+
[data-phase] header [class$="_crumbSeg"]:nth-last-child(2) [class$="_crumbSep"] {
|
|
3770
|
+
display: none !important;
|
|
3771
|
+
}
|
|
3772
|
+
/* Parent crumb: small tertiary text so the 19px current title stays the
|
|
3773
|
+
visual anchor; min-height keeps a finger-sized target inside the 48px
|
|
3774
|
+
row. Specificity (0,4,1) outranks the shared 19px _crumb rule below. */
|
|
3775
|
+
[data-phase] header [class$="_crumbSeg"]:nth-last-child(2) [class$="_crumb"] {
|
|
3776
|
+
font-size: 13px;
|
|
3777
|
+
font-weight: 400;
|
|
3778
|
+
color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .45));
|
|
3779
|
+
min-height: 44px;
|
|
3780
|
+
display: inline-flex;
|
|
3781
|
+
align-items: center;
|
|
3782
|
+
padding: 0 2px 0 6px;
|
|
3783
|
+
touch-action: manipulation;
|
|
3784
|
+
-webkit-tap-highlight-color: transparent;
|
|
3785
|
+
}
|
|
3687
3786
|
[data-phase] header [class$="_crumbSeg"]:last-child {
|
|
3688
3787
|
display: flex;
|
|
3689
3788
|
align-items: center;
|
|
3690
3789
|
min-width: 0;
|
|
3691
3790
|
max-width: 100%;
|
|
3692
3791
|
}
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3792
|
+
/* The last seg's own "/" is the divider between parent and current title —
|
|
3793
|
+
it only exists when there IS an ancestor (index > 0), so it can simply
|
|
3794
|
+
stay visible now that the parent seg shows. (It used to be hidden here:
|
|
3795
|
+
with the parent hidden it dangled before the title.) */
|
|
3696
3796
|
/* BOTH selectors are required: [class$=] matches the whole class
|
|
3697
3797
|
ATTRIBUTE's suffix, and the current-session title button's attribute is
|
|
3698
3798
|
"wSkVaW_crumb wSkVaW_crumbCurrent" — it ends in _crumbCurrent, so the _crumb
|
|
@@ -4840,10 +4940,14 @@ exports.CHIPS_CSS = `/* ---------- chips row + settings entry (S5) ---------- */
|
|
|
4840
4940
|
}
|
|
4841
4941
|
|
|
4842
4942
|
/* Plugin-entry chips row: one horizontally-scrolling line of 34px pills
|
|
4843
|
-
between the workspace title and the session list (prototype spec).
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4943
|
+
between the workspace title and the session list (prototype spec).
|
|
4944
|
+
Scrollbar hidden on BOTH engines (2026-08-18 user decision): iOS
|
|
4945
|
+
overlay scrollbars never showed anyway, but desktop browsers painted a
|
|
4946
|
+
permanent 2px strip under the pills. This revisits the AGENTS.md
|
|
4947
|
+
"silently cut off the last tab" lesson deliberately — with 4+ chips the
|
|
4948
|
+
row now always overflows mid-chip (a partially visible pill is the
|
|
4949
|
+
scroll affordance), unlike the early incident where the chips fit
|
|
4950
|
+
flush and the hidden overflow was undiscoverable. */
|
|
4847
4951
|
[data-mobile-nav="chip-row"] {
|
|
4848
4952
|
flex: 0 0 auto;
|
|
4849
4953
|
display: flex;
|
|
@@ -4853,14 +4957,10 @@ exports.CHIPS_CSS = `/* ---------- chips row + settings entry (S5) ---------- */
|
|
|
4853
4957
|
overflow-x: auto;
|
|
4854
4958
|
overscroll-behavior: contain;
|
|
4855
4959
|
-webkit-overflow-scrolling: touch;
|
|
4856
|
-
scrollbar-width:
|
|
4960
|
+
scrollbar-width: none;
|
|
4857
4961
|
}
|
|
4858
4962
|
[data-mobile-nav="chip-row"]::-webkit-scrollbar {
|
|
4859
|
-
|
|
4860
|
-
}
|
|
4861
|
-
[data-mobile-nav="chip-row"]::-webkit-scrollbar-thumb {
|
|
4862
|
-
background: var(--dsw-alias-border-l2, rgba(0, 0, 0, .16));
|
|
4863
|
-
border-radius: 2px;
|
|
4963
|
+
display: none;
|
|
4864
4964
|
}
|
|
4865
4965
|
[data-mobile-nav="chip"],
|
|
4866
4966
|
[data-mobile-nav="chip-more"] {
|
|
@@ -4975,6 +5075,113 @@ exports.CHIPS_CSS = `/* ---------- chips row + settings entry (S5) ---------- */
|
|
|
4975
5075
|
}
|
|
4976
5076
|
`;
|
|
4977
5077
|
};
|
|
5078
|
+
__modules["styles/content.css.js"] = function (require, module, exports) {
|
|
5079
|
+
"use strict";
|
|
5080
|
+
// content — chat markdown readability on phones (2026-08-18). Shares no
|
|
5081
|
+
// selector with any other file; appended last in styles/index.ts.
|
|
5082
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5083
|
+
exports.CONTENT_CSS = void 0;
|
|
5084
|
+
exports.CONTENT_CSS = `/* ---------- chat markdown content (< 768px) ---------- */
|
|
5085
|
+
|
|
5086
|
+
@media (max-width: 767px) {
|
|
5087
|
+
/* Inline code: upstream renders it display:inline-flex — an atomic inline
|
|
5088
|
+
box that can never wrap across lines, so one long code span walks off
|
|
5089
|
+
the right edge of the screen. Plain inline keeps the background pill
|
|
5090
|
+
per line fragment and lets long tokens break anywhere. Deliberately
|
|
5091
|
+
unscoped (any :not(pre) > code, not just the _markdown_ module):
|
|
5092
|
+
inline code also renders outside that container (user bubbles,
|
|
5093
|
+
reasoning rows), and on a phone there is no width at which an
|
|
5094
|
+
unwrappable inline box is right. !important beats the upstream
|
|
5095
|
+
display:inline-flex on specificity ties. */
|
|
5096
|
+
:not(pre) > code {
|
|
5097
|
+
display: inline !important;
|
|
5098
|
+
overflow-wrap: anywhere;
|
|
5099
|
+
/* iOS WebKit: overflow-wrap alone still failed to break long tokens in
|
|
5100
|
+
inline code on a real iPhone (screenshot-verified 2026-08-18, li >
|
|
5101
|
+
code path case) while desktop Chromium broke them fine. break-all is
|
|
5102
|
+
the everywhere-supported hammer; acceptable for code pills. */
|
|
5103
|
+
word-break: break-all;
|
|
5104
|
+
}
|
|
5105
|
+
/* Belt-and-suspenders for engines that compute soft-wrap opportunities
|
|
5106
|
+
from the block container rather than the inline itself (historical
|
|
5107
|
+
WebKit behavior): inherit anywhere through the whole transcript so the
|
|
5108
|
+
block side agrees. Prose is untouched until something would overflow;
|
|
5109
|
+
the table is already pinned wide by min-width: max-content below. */
|
|
5110
|
+
[data-chat-flow],
|
|
5111
|
+
[class*="_markdown_"] {
|
|
5112
|
+
overflow-wrap: anywhere;
|
|
5113
|
+
}
|
|
5114
|
+
|
|
5115
|
+
/* Markdown tables: the official _tableScroll_ wrapper is already a
|
|
5116
|
+
horizontal scroll viewport around a width:max-content table, but its
|
|
5117
|
+
cells are capped at min(30vw, 320px) — barely 120px on a phone, which
|
|
5118
|
+
crushes every column into a vertical word-stack. Let columns size to
|
|
5119
|
+
their content instead and read the overflow by scrolling the wrapper.
|
|
5120
|
+
480px (not a vw cap — 85vw was ~320px on a 375px phone and still
|
|
5121
|
+
wrapped mid-length cells) keeps typical cells on one line; only truly
|
|
5122
|
+
prose-heavy cells wrap, instead of dragging the table kilometers
|
|
5123
|
+
wide. */
|
|
5124
|
+
[class*="_tableScroll_"] th,
|
|
5125
|
+
[class*="_tableScroll_"] td {
|
|
5126
|
+
max-width: 480px !important;
|
|
5127
|
+
}
|
|
5128
|
+
|
|
5129
|
+
/* iOS WebKit fallback: upstream's own table { width: max-content } is what
|
|
5130
|
+
keeps the table at its natural width inside the scroll wrapper, but
|
|
5131
|
+
WebKit's max-content support on table boxes is incomplete — on a real
|
|
5132
|
+
iPhone the table fell back to shrink-to-fit and every column got
|
|
5133
|
+
crushed to its min-width (the "ra/nk" vertical-word-stack screenshot,
|
|
5134
|
+
2026-08-18). Redeclare the natural-width intent with both width and
|
|
5135
|
+
min-width so whichever keyword the engine honors wins; the wrapper
|
|
5136
|
+
already scrolls the overflow. */
|
|
5137
|
+
[class*="_tableScroll_"] table {
|
|
5138
|
+
width: max-content !important;
|
|
5139
|
+
min-width: max-content !important;
|
|
5140
|
+
max-width: none !important;
|
|
5141
|
+
}
|
|
5142
|
+
|
|
5143
|
+
/* Turn-tail metadata (2026-08-18): the timing stats ("18:06 · 用时 29秒 ·
|
|
5144
|
+
首 token 0.9秒 · 163 tok/s") are desktop-hover-revealed (official
|
|
5145
|
+
[data-time-hover-root] gate holds them at opacity 0 until :hover /
|
|
5146
|
+
:focus-within) — a phone never hovers, so they were simply invisible;
|
|
5147
|
+
and even ignoring that, the row is nowrap + overflow:hidden (plus this
|
|
5148
|
+
plugin's own legacy ellipsis clamp in layout.css.ts), which clipped the
|
|
5149
|
+
text. On phones: always visible, own full-width line under the action
|
|
5150
|
+
icons, wrapping freely, one type size down. !important + the extra
|
|
5151
|
+
[class$="_actions"] hop out-specifies both upstream and layout.css.ts. */
|
|
5152
|
+
/* Pin the tail row's width chain before allowing wrap: with wrap enabled
|
|
5153
|
+
the row's intrinsic (shrink-to-fit) width collapses to its widest
|
|
5154
|
+
single icon, taking the whole tail down to ~16px. */
|
|
5155
|
+
[data-chat-flow-kind="turn-tail"] [class$="_root"],
|
|
5156
|
+
[data-chat-flow-kind="turn-tail"] [class$="_actions"] {
|
|
5157
|
+
width: 100%;
|
|
5158
|
+
}
|
|
5159
|
+
[data-chat-flow-kind="turn-tail"] [class$="_actions"] {
|
|
5160
|
+
flex-wrap: wrap;
|
|
5161
|
+
row-gap: 2px;
|
|
5162
|
+
}
|
|
5163
|
+
/* Wide companion to the rule below: mirrors the upstream hover-gate
|
|
5164
|
+
selector shape directly, with no assumptions about the tail's DOM
|
|
5165
|
+
structure, so the stats can't stay invisible even if the phone's tail
|
|
5166
|
+
renders differently than desktop. */
|
|
5167
|
+
[data-time-hover-root] [class$="_timeEnd"],
|
|
5168
|
+
[data-time-hover-root] [class$="_timeStart"] {
|
|
5169
|
+
opacity: 1 !important;
|
|
5170
|
+
transition: none !important;
|
|
5171
|
+
}
|
|
5172
|
+
[data-chat-flow-kind="turn-tail"] [class$="_actions"] [class$="_timeEnd"] {
|
|
5173
|
+
opacity: 1 !important;
|
|
5174
|
+
flex-basis: 100% !important;
|
|
5175
|
+
white-space: normal !important;
|
|
5176
|
+
overflow: visible !important;
|
|
5177
|
+
text-overflow: clip !important;
|
|
5178
|
+
padding-left: 0 !important;
|
|
5179
|
+
font-size: 12px;
|
|
5180
|
+
line-height: 18px;
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
`;
|
|
5184
|
+
};
|
|
4978
5185
|
__modules["styles/index.js"] = function (require, module, exports) {
|
|
4979
5186
|
"use strict";
|
|
4980
5187
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -4989,6 +5196,7 @@ const composer_css_ts_1 = require("./styles/composer.css.js");
|
|
|
4989
5196
|
const info_css_ts_1 = require("./styles/info.css.js");
|
|
4990
5197
|
const turn_fold_css_ts_1 = require("./styles/turn-fold.css.js");
|
|
4991
5198
|
const chips_css_ts_1 = require("./styles/chips.css.js");
|
|
5199
|
+
const content_css_ts_1 = require("./styles/content.css.js");
|
|
4992
5200
|
/**
|
|
4993
5201
|
* All mobile styles, concatenated in the exact order of the original
|
|
4994
5202
|
* single-file stylesheet (base → layout → compat → misc, where misc keeps
|
|
@@ -4999,10 +5207,11 @@ const chips_css_ts_1 = require("./styles/chips.css.js");
|
|
|
4999
5207
|
* win that tie too), the turn-process fold (turn-fold, S8, which shares no
|
|
5000
5208
|
* selector with any of them), and finally the chips row / settings entry /
|
|
5001
5209
|
* portal fix (chips, S5) — all appended in this order so their <768px rules
|
|
5002
|
-
* win ties against the shared <=1023px block
|
|
5003
|
-
*
|
|
5210
|
+
* win ties against the shared <=1023px block, then the chat markdown
|
|
5211
|
+
* readability overrides (content, which shares no selector with any of
|
|
5212
|
+
* them). Injected as ONE <style data-plugin> tag — do not reorder.
|
|
5004
5213
|
*/
|
|
5005
|
-
exports.MOBILE_CSS = [base_css_ts_1.BASE_CSS, layout_css_ts_1.LAYOUT_CSS, compat_css_ts_1.COMPAT_CSS, misc_css_ts_1.MISC_CSS, home_css_ts_1.HOME_CSS, header_css_ts_1.HEADER_CSS, composer_css_ts_1.COMPOSER_CSS, info_css_ts_1.INFO_CSS, turn_fold_css_ts_1.TURN_FOLD_CSS, chips_css_ts_1.CHIPS_CSS].join('\n');
|
|
5214
|
+
exports.MOBILE_CSS = [base_css_ts_1.BASE_CSS, layout_css_ts_1.LAYOUT_CSS, compat_css_ts_1.COMPAT_CSS, misc_css_ts_1.MISC_CSS, home_css_ts_1.HOME_CSS, header_css_ts_1.HEADER_CSS, composer_css_ts_1.COMPOSER_CSS, info_css_ts_1.INFO_CSS, turn_fold_css_ts_1.TURN_FOLD_CSS, chips_css_ts_1.CHIPS_CSS, content_css_ts_1.CONTENT_CSS].join('\n');
|
|
5006
5215
|
};
|
|
5007
5216
|
__modules["debug.js"] = function (require, module, exports) {
|
|
5008
5217
|
"use strict";
|
|
@@ -6295,11 +6504,13 @@ function installTurnFold(ctx) {
|
|
|
6295
6504
|
const running = flow.querySelector(':scope > [role="status"]') !== null;
|
|
6296
6505
|
const groups = groupsOf(flow);
|
|
6297
6506
|
const live = new Set();
|
|
6507
|
+
const liveItems = new Set();
|
|
6298
6508
|
groups.forEach((group, index) => {
|
|
6299
6509
|
if (group.items.length === 0)
|
|
6300
6510
|
return;
|
|
6301
6511
|
const open = expanded.has(group.key);
|
|
6302
6512
|
for (const item of group.items) {
|
|
6513
|
+
liveItems.add(item);
|
|
6303
6514
|
item.setAttribute(FOLD, '');
|
|
6304
6515
|
if (open)
|
|
6305
6516
|
item.setAttribute(OPEN, '');
|
|
@@ -6332,6 +6543,18 @@ function installTurnFold(ctx) {
|
|
|
6332
6543
|
if (key === undefined || !live.has(key))
|
|
6333
6544
|
chip.remove();
|
|
6334
6545
|
}
|
|
6546
|
+
// Unmark elements that stopped being process items. Without this, an
|
|
6547
|
+
// assistant-step row folded whole while it streamed ONLY reasoning kept
|
|
6548
|
+
// its row-level fold after prose started arriving in the same DOM node
|
|
6549
|
+
// (React updates the row in place), hiding the streaming reply until a
|
|
6550
|
+
// completion re-render rebuilt the row — the "reply only appears when
|
|
6551
|
+
// the turn finishes" phone bug (2026-08-18).
|
|
6552
|
+
for (const stale of flow.querySelectorAll(`[${FOLD}]`)) {
|
|
6553
|
+
if (liveItems.has(stale))
|
|
6554
|
+
continue;
|
|
6555
|
+
stale.removeAttribute(FOLD);
|
|
6556
|
+
stale.removeAttribute(OPEN);
|
|
6557
|
+
}
|
|
6335
6558
|
};
|
|
6336
6559
|
const schedule = () => {
|
|
6337
6560
|
// The locale subscription below lives outside attach/detach, so this
|
|
@@ -6405,6 +6628,198 @@ function installTurnFold(ctx) {
|
|
|
6405
6628
|
}, 'dsh-mobile-nav: turn process fold');
|
|
6406
6629
|
}
|
|
6407
6630
|
};
|
|
6631
|
+
__modules["effects/welcome-notice.js"] = function (require, module, exports) {
|
|
6632
|
+
"use strict";
|
|
6633
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6634
|
+
exports.installWelcomeNoticeOptOut = installWelcomeNoticeOptOut;
|
|
6635
|
+
/** Per-browser opt-out flag; localStorage because remote (non-loopback)
|
|
6636
|
+
connections never persist the acknowledgement host-side. */
|
|
6637
|
+
const OPTOUT_KEY = 'dsh-zen-remote:welcome-notice-optout';
|
|
6638
|
+
/** Localized aria-labels of the DSH first-run notice dialog
|
|
6639
|
+
(@deepseek-ai/dsh-client-ui-settings-models WELCOME_NOTICE_COPY). */
|
|
6640
|
+
const NOTICE_LABELS = ['内测声明', 'Internal Testing Notice'];
|
|
6641
|
+
/**
|
|
6642
|
+
* "内测声明" first-run notice, opt-out half (ALL widths — same user-directed
|
|
6643
|
+
* exception as the _previewBadge rule in styles/base.css.ts).
|
|
6644
|
+
*
|
|
6645
|
+
* Why not CSS: the notice dialog keeps document #root inert while MOUNTED and
|
|
6646
|
+
* only restores it on unmount (its acknowledge button). display:none hid the
|
|
6647
|
+
* dialog without unmounting it, which left the whole app permanently
|
|
6648
|
+
* unclickable on remote connections (2026-08-18 incident) — remote browsers
|
|
6649
|
+
* re-mount the notice on every page load because their acknowledgement is
|
|
6650
|
+
* memory-only (`connection.isLoopback ? "host" : "memory"` upstream).
|
|
6651
|
+
*
|
|
6652
|
+
* So the dialog now shows normally, plus one injected "不再弹出" button. The
|
|
6653
|
+
* user chooses: acknowledging via that button records the opt-out in this
|
|
6654
|
+
* browser's localStorage, and later mounts are auto-acknowledged by clicking
|
|
6655
|
+
* the dialog's own continue button — the component unmounts through its
|
|
6656
|
+
* normal path and #root's inert is properly restored.
|
|
6657
|
+
*/
|
|
6658
|
+
function installWelcomeNoticeOptOut(ctx) {
|
|
6659
|
+
ctx.effect(() => {
|
|
6660
|
+
const handle = (dialog) => {
|
|
6661
|
+
// The notice's action row holds exactly one button ("继续"/"Continue").
|
|
6662
|
+
const continueBtn = dialog.querySelector('button');
|
|
6663
|
+
if (continueBtn === null)
|
|
6664
|
+
return;
|
|
6665
|
+
if (localStorage.getItem(OPTOUT_KEY) !== null) {
|
|
6666
|
+
continueBtn.click();
|
|
6667
|
+
return;
|
|
6668
|
+
}
|
|
6669
|
+
const row = continueBtn.parentElement;
|
|
6670
|
+
if (row === null || row.querySelector('.zen-welcome-optout') !== null)
|
|
6671
|
+
return;
|
|
6672
|
+
const optOut = document.createElement('button');
|
|
6673
|
+
optOut.type = 'button';
|
|
6674
|
+
optOut.className = 'zen-welcome-optout';
|
|
6675
|
+
optOut.textContent =
|
|
6676
|
+
dialog.getAttribute('aria-label') === '内测声明' ? '不再弹出' : "Don't show again";
|
|
6677
|
+
optOut.addEventListener('click', () => {
|
|
6678
|
+
localStorage.setItem(OPTOUT_KEY, '1');
|
|
6679
|
+
continueBtn.click();
|
|
6680
|
+
});
|
|
6681
|
+
row.insertBefore(optOut, continueBtn);
|
|
6682
|
+
};
|
|
6683
|
+
const scan = () => {
|
|
6684
|
+
for (const label of NOTICE_LABELS) {
|
|
6685
|
+
const dialog = document.querySelector(`[role="dialog"][aria-label="${label}"]`);
|
|
6686
|
+
if (dialog !== null)
|
|
6687
|
+
handle(dialog);
|
|
6688
|
+
}
|
|
6689
|
+
};
|
|
6690
|
+
// The dialog portals straight under <body>, so childList on body is
|
|
6691
|
+
// enough; scan() once first in case it mounted before this plugin loaded.
|
|
6692
|
+
scan();
|
|
6693
|
+
const observer = new MutationObserver(scan);
|
|
6694
|
+
observer.observe(document.body, { childList: true });
|
|
6695
|
+
return () => observer.disconnect();
|
|
6696
|
+
}, 'dsh-mobile-nav: welcome-notice opt-out');
|
|
6697
|
+
}
|
|
6698
|
+
};
|
|
6699
|
+
__modules["effects/keyboard-guard.js"] = function (require, module, exports) {
|
|
6700
|
+
"use strict";
|
|
6701
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6702
|
+
exports.installKeyboardGuard = installKeyboardGuard;
|
|
6703
|
+
/** Phone breakpoint — same query every phone-only effect in this plugin uses. */
|
|
6704
|
+
const PHONE_QUERY = '(max-width: 767px)';
|
|
6705
|
+
/** The official composer slot wrapper (same marker composer.css.ts styles). */
|
|
6706
|
+
const COMPOSER = '[data-slot="conversation.composer.bar"]';
|
|
6707
|
+
/** A tap/keystroke older than this no longer explains a focus. */
|
|
6708
|
+
const INTENT_WINDOW_MS = 1000;
|
|
6709
|
+
/**
|
|
6710
|
+
* S9 — keep the phone keyboard down until the user asks for it.
|
|
6711
|
+
*
|
|
6712
|
+
* dsh-client-ui-conversation focuses the composer textarea on every
|
|
6713
|
+
* sessionId change (lib/client.js:3423 — el.focus({preventScroll:true}) in a
|
|
6714
|
+
* [locked, sessionId] effect). Sensible on desktop; on a phone it pops the
|
|
6715
|
+
* software keyboard over half the screen every time a session opens.
|
|
6716
|
+
*
|
|
6717
|
+
* Rule: focus on the composer textarea survives only when the user asked for
|
|
6718
|
+
* it — a tap on the textarea itself or typing on a hardware keyboard.
|
|
6719
|
+
* Anything else (session-open autofocus, push-deep-link opens, the refocus
|
|
6720
|
+
* side effects of the other composer buttons — slash-command toggle, attach,
|
|
6721
|
+
* send) is blurred.
|
|
6722
|
+
*
|
|
6723
|
+
* Two triggers, because one is not enough:
|
|
6724
|
+
* - focusin catches the autofocus the moment it happens;
|
|
6725
|
+
* - a body MutationObserver re-runs the check after transcript swaps
|
|
6726
|
+
* (opening a session re-renders the flow but may reuse the same textarea,
|
|
6727
|
+
* and a focus that landed before this plugin loaded never fired focusin
|
|
6728
|
+
* for us at all).
|
|
6729
|
+
* Once focus is user-granted it stays granted until the textarea blurs, so
|
|
6730
|
+
* the observer never yanks a keyboard the user opened (e.g. while the agent
|
|
6731
|
+
* streams and the user pauses typing).
|
|
6732
|
+
*/
|
|
6733
|
+
function installKeyboardGuard(ctx) {
|
|
6734
|
+
ctx.effect(() => {
|
|
6735
|
+
const narrow = window.matchMedia(PHONE_QUERY);
|
|
6736
|
+
let lastIntent = 0;
|
|
6737
|
+
let granted = false;
|
|
6738
|
+
let observer = null;
|
|
6739
|
+
let frame = 0;
|
|
6740
|
+
const composerTextarea = (node) => node instanceof HTMLElement && node.tagName === 'TEXTAREA' && node.closest(COMPOSER) !== null
|
|
6741
|
+
? node
|
|
6742
|
+
: null;
|
|
6743
|
+
const onPointerDown = (event) => {
|
|
6744
|
+
// Only the textarea itself grants the keyboard. A tap on any other
|
|
6745
|
+
// composer control (slash-command toggle, attach, model menu, send)
|
|
6746
|
+
// must not: several of them refocus the input as a side effect, which
|
|
6747
|
+
// popped the keyboard on every command-button tap.
|
|
6748
|
+
if (composerTextarea(event.target) !== null) {
|
|
6749
|
+
lastIntent = Date.now();
|
|
6750
|
+
granted = true;
|
|
6751
|
+
}
|
|
6752
|
+
};
|
|
6753
|
+
const onKeyDown = () => {
|
|
6754
|
+
lastIntent = Date.now();
|
|
6755
|
+
if (composerTextarea(document.activeElement) !== null)
|
|
6756
|
+
granted = true;
|
|
6757
|
+
};
|
|
6758
|
+
const sweep = () => {
|
|
6759
|
+
const el = composerTextarea(document.activeElement);
|
|
6760
|
+
if (el === null)
|
|
6761
|
+
return;
|
|
6762
|
+
if (granted || Date.now() - lastIntent < INTENT_WINDOW_MS) {
|
|
6763
|
+
granted = true;
|
|
6764
|
+
return;
|
|
6765
|
+
}
|
|
6766
|
+
el.blur();
|
|
6767
|
+
};
|
|
6768
|
+
const onFocusIn = (event) => {
|
|
6769
|
+
if (composerTextarea(event.target) === null)
|
|
6770
|
+
return;
|
|
6771
|
+
sweep();
|
|
6772
|
+
};
|
|
6773
|
+
const onFocusOut = (event) => {
|
|
6774
|
+
if (composerTextarea(event.target) === null)
|
|
6775
|
+
return;
|
|
6776
|
+
granted = false;
|
|
6777
|
+
};
|
|
6778
|
+
const schedule = () => {
|
|
6779
|
+
if (observer === null || frame !== 0)
|
|
6780
|
+
return;
|
|
6781
|
+
// setTimeout, not requestAnimationFrame: rAF pauses in hidden/
|
|
6782
|
+
// backgrounded pages, where the autofocus still happens — the sweep
|
|
6783
|
+
// must run there too or the keyboard pops when the page is foregrounded.
|
|
6784
|
+
frame = window.setTimeout(() => {
|
|
6785
|
+
frame = 0;
|
|
6786
|
+
sweep();
|
|
6787
|
+
}, 100);
|
|
6788
|
+
};
|
|
6789
|
+
const attach = () => {
|
|
6790
|
+
if (observer !== null)
|
|
6791
|
+
return;
|
|
6792
|
+
document.addEventListener('pointerdown', onPointerDown, { capture: true, passive: true });
|
|
6793
|
+
document.addEventListener('keydown', onKeyDown, { capture: true, passive: true });
|
|
6794
|
+
document.addEventListener('focusin', onFocusIn, true);
|
|
6795
|
+
document.addEventListener('focusout', onFocusOut, true);
|
|
6796
|
+
observer = new MutationObserver(schedule);
|
|
6797
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
6798
|
+
sweep();
|
|
6799
|
+
};
|
|
6800
|
+
const detach = () => {
|
|
6801
|
+
document.removeEventListener('pointerdown', onPointerDown, true);
|
|
6802
|
+
document.removeEventListener('keydown', onKeyDown, true);
|
|
6803
|
+
document.removeEventListener('focusin', onFocusIn, true);
|
|
6804
|
+
document.removeEventListener('focusout', onFocusOut, true);
|
|
6805
|
+
observer?.disconnect();
|
|
6806
|
+
observer = null;
|
|
6807
|
+
if (frame !== 0)
|
|
6808
|
+
window.clearTimeout(frame);
|
|
6809
|
+
frame = 0;
|
|
6810
|
+
granted = false;
|
|
6811
|
+
};
|
|
6812
|
+
if (narrow.matches)
|
|
6813
|
+
attach();
|
|
6814
|
+
const onChange = (event) => (event.matches ? attach() : detach());
|
|
6815
|
+
narrow.addEventListener('change', onChange);
|
|
6816
|
+
return () => {
|
|
6817
|
+
narrow.removeEventListener('change', onChange);
|
|
6818
|
+
detach();
|
|
6819
|
+
};
|
|
6820
|
+
}, 'dsh-mobile-nav: composer keyboard guard');
|
|
6821
|
+
}
|
|
6822
|
+
};
|
|
6408
6823
|
__modules["index.js"] = function (require, module, exports) {
|
|
6409
6824
|
"use strict";
|
|
6410
6825
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -6426,6 +6841,8 @@ const aionui_compat_ts_1 = require("./effects/aionui-compat.js");
|
|
|
6426
6841
|
const header_status_ts_1 = require("./effects/header-status.js");
|
|
6427
6842
|
const gestures_ts_1 = require("./effects/gestures.js");
|
|
6428
6843
|
const turn_fold_ts_1 = require("./effects/turn-fold.js");
|
|
6844
|
+
const welcome_notice_ts_1 = require("./effects/welcome-notice.js");
|
|
6845
|
+
const keyboard_guard_ts_1 = require("./effects/keyboard-guard.js");
|
|
6429
6846
|
const locales_ts_1 = require("./locales.js");
|
|
6430
6847
|
/** Required services (cordis fiber inject — the loader passes all module exports as an object plugin). */
|
|
6431
6848
|
exports.inject = ['slots', 'layout', 'locale', 'sessionLogDownload', 'sessions', 'workspaces'];
|
|
@@ -6469,6 +6886,14 @@ function apply(ctx) {
|
|
|
6469
6886
|
// effects/turn-fold.ts for why the keyed conversation.chat.node seat could
|
|
6470
6887
|
// not carry this.
|
|
6471
6888
|
(0, turn_fold_ts_1.installTurnFold)(ctx);
|
|
6889
|
+
// "内测声明" first-run notice: keep it visible (CSS-hiding it leaked the
|
|
6890
|
+
// dialog's #root inert lock — see effects/welcome-notice.ts) and offer a
|
|
6891
|
+
// per-browser "不再弹出" opt-out instead.
|
|
6892
|
+
(0, welcome_notice_ts_1.installWelcomeNoticeOptOut)(ctx);
|
|
6893
|
+
// S9: opening a session must not pop the phone keyboard — the official
|
|
6894
|
+
// composer autofocuses on every sessionId change; keep focus only when the
|
|
6895
|
+
// user tapped the composer (or typed) themselves.
|
|
6896
|
+
(0, keyboard_guard_ts_1.installKeyboardGuard)(ctx);
|
|
6472
6897
|
// Page-stack store (apply world) — created before any registration so
|
|
6473
6898
|
// every slot below (the phone home screen, the session header's back
|
|
6474
6899
|
// button) shares the exact same handle/instance.
|