dsh-zen-remote 1.1.11 → 1.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +24 -0
- package/README.md +7 -3
- package/README.zh-CN.md +7 -3
- package/docs/interface.md +5 -2
- package/dsh-push.mjs +25 -1
- package/lan-gate.mjs +16 -1
- package/lib/client.js +684 -43
- package/lib/lan-gate-server.cjs +107 -4
- package/lib/types/client/MobileHome.d.ts +7 -2
- package/lib/types/client/MobileHome.d.ts.map +1 -1
- package/lib/types/client/MobileSessionInfo.d.ts +2 -2
- package/lib/types/client/MobileSessionInfo.d.ts.map +1 -1
- package/lib/types/client/compat/store.d.ts +62 -0
- package/lib/types/client/compat/store.d.ts.map +1 -0
- package/lib/types/client/compat/types.d.ts +84 -0
- package/lib/types/client/compat/types.d.ts.map +1 -0
- package/lib/types/client/debug.d.ts +1 -1
- package/lib/types/client/debug.d.ts.map +1 -1
- package/lib/types/client/effects/aionui-compat.d.ts +1 -1
- package/lib/types/client/effects/aionui-compat.d.ts.map +1 -1
- package/lib/types/client/effects/gestures.d.ts +1 -1
- package/lib/types/client/effects/gestures.d.ts.map +1 -1
- package/lib/types/client/effects/header-status.d.ts +1 -1
- package/lib/types/client/effects/header-status.d.ts.map +1 -1
- package/lib/types/client/effects/keyboard-avoid.d.ts +1 -1
- package/lib/types/client/effects/keyboard-avoid.d.ts.map +1 -1
- package/lib/types/client/effects/keyboard-guard.d.ts +1 -1
- package/lib/types/client/effects/keyboard-guard.d.ts.map +1 -1
- package/lib/types/client/effects/modal-back.d.ts +1 -1
- package/lib/types/client/effects/modal-back.d.ts.map +1 -1
- package/lib/types/client/effects/model-sheet-extras.d.ts +33 -0
- package/lib/types/client/effects/model-sheet-extras.d.ts.map +1 -0
- package/lib/types/client/effects/native-trigger-overlay.d.ts +1 -1
- package/lib/types/client/effects/native-trigger-overlay.d.ts.map +1 -1
- package/lib/types/client/effects/phone-chrome.d.ts +1 -1
- package/lib/types/client/effects/phone-chrome.d.ts.map +1 -1
- package/lib/types/client/effects/turn-fold.d.ts +1 -1
- package/lib/types/client/effects/turn-fold.d.ts.map +1 -1
- package/lib/types/client/effects/welcome-notice.d.ts +1 -1
- package/lib/types/client/effects/welcome-notice.d.ts.map +1 -1
- package/lib/types/client/effects/workbench-ref-close.d.ts +1 -1
- package/lib/types/client/effects/workbench-ref-close.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/nav-store.d.ts +2 -2
- package/lib/types/client/nav-store.d.ts.map +1 -1
- package/lib/types/client/session-dot.d.ts +11 -3
- package/lib/types/client/session-dot.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/composer.css.d.ts +1 -1
- package/lib/types/client/styles/composer.css.d.ts.map +1 -1
- package/lib/types/client/styles/info.css.d.ts +1 -1
- package/lib/types/client/styles/info.css.d.ts.map +1 -1
- package/package.json +49 -26
- package/pwa/app.css +47 -7
- package/scripts/check-client-externals.mjs +48 -0
- package/scripts/check-compat-store.mjs +113 -0
- package/scripts/check-host-shadow.mjs +145 -0
- package/scripts/check-info-sheet-stacking.mjs +55 -0
- package/src/client/MobileAttachButton.tsx +1 -1
- package/src/client/MobileHome.tsx +33 -7
- package/src/client/MobileSessionInfo.tsx +6 -4
- package/src/client/chips-store.ts +1 -1
- package/src/client/compat/store.ts +205 -0
- package/src/client/compat/types.ts +115 -0
- package/src/client/debug.ts +1 -1
- package/src/client/effects/aionui-compat.ts +1 -1
- package/src/client/effects/gestures.ts +1 -1
- package/src/client/effects/header-status.ts +22 -3
- package/src/client/effects/keyboard-avoid.ts +1 -1
- package/src/client/effects/keyboard-guard.ts +1 -1
- package/src/client/effects/modal-back.ts +1 -1
- package/src/client/effects/model-sheet-extras.ts +159 -0
- package/src/client/effects/native-trigger-overlay.ts +1 -1
- package/src/client/effects/phone-chrome.ts +1 -1
- package/src/client/effects/turn-fold.ts +1 -1
- package/src/client/effects/welcome-notice.ts +1 -1
- package/src/client/effects/workbench-ref-close.ts +1 -1
- package/src/client/index.tsx +70 -6
- package/src/client/nav-store.ts +2 -2
- package/src/client/session-dot.ts +13 -4
- package/src/client/styles/compat.css.ts +43 -11
- package/src/client/styles/composer.css.ts +184 -9
- package/src/client/styles/info.css.ts +21 -0
package/src/client/index.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ClientContext, SessionId, WorkspaceId } from '
|
|
1
|
+
import type { ClientContext, SessionId, WorkspaceId } from './compat/types.ts'
|
|
2
2
|
import { MobileNavToggle } from './MobileNavToggle.tsx'
|
|
3
3
|
import { MobileNavOverlay } from './MobileNavOverlay.tsx'
|
|
4
4
|
import { MobileDrawerFooter } from './MobileDrawerFooter.tsx'
|
|
@@ -17,6 +17,7 @@ import { installHeaderStatusDot } from './effects/header-status.ts'
|
|
|
17
17
|
import { installGestures } from './effects/gestures.ts'
|
|
18
18
|
import { installTurnFold } from './effects/turn-fold.ts'
|
|
19
19
|
import { installModalBack } from './effects/modal-back.ts'
|
|
20
|
+
import { installModelSheetExtras } from './effects/model-sheet-extras.ts'
|
|
20
21
|
import { installNativeTriggerOverlay } from './effects/native-trigger-overlay.ts'
|
|
21
22
|
import { installWelcomeNoticeOptOut } from './effects/welcome-notice.ts'
|
|
22
23
|
import { installKeyboardGuard } from './effects/keyboard-guard.ts'
|
|
@@ -34,6 +35,23 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
34
35
|
/** Required services (cordis fiber inject — the loader passes all module exports as an object plugin). */
|
|
35
36
|
export const inject = ['slots', 'layout', 'locale', 'sessionLogDownload', 'sessions', 'workspaces']
|
|
36
37
|
|
|
38
|
+
/**
|
|
39
|
+
* 0.1.2 把新建会话和归档会话的界面级操作挪到了 uiWorkspace 服务;
|
|
40
|
+
* 0.1.1 没有这个服务,那两个操作还在 ctx.workspaces 上。这里按存在探测,
|
|
41
|
+
* 不把 uiWorkspace 写进 inject(写了 0.1.1 就永远不激活)。
|
|
42
|
+
* 类型用本地最小接口,不 import 那个 dsh-client-ui-workspace 包
|
|
43
|
+
* ——0.1.1 装不到。
|
|
44
|
+
*/
|
|
45
|
+
interface UiWorkspaceLike {
|
|
46
|
+
startSession(workspaceId?: WorkspaceId): void
|
|
47
|
+
archiveSession(sessionId: SessionId): Promise<void>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 0.1.1 的 ctx.workspaces 上还有 startSession;0.1.2 的类型里没有了。 */
|
|
51
|
+
interface LegacyWorkspacesLike {
|
|
52
|
+
startSession(workspaceId?: WorkspaceId): void
|
|
53
|
+
}
|
|
54
|
+
|
|
37
55
|
/**
|
|
38
56
|
* Mobile-adaptive shell, browser half: injects the mobile stylesheet, then
|
|
39
57
|
* contributes the directory toggle to the session header and the backdrop +
|
|
@@ -89,6 +107,9 @@ export function apply(ctx: ClientContext): void {
|
|
|
89
107
|
installTurnFold(ctx)
|
|
90
108
|
installModalBack(ctx)
|
|
91
109
|
installNativeTriggerOverlay(ctx)
|
|
110
|
+
// Third-party composer entries (speed chip, vision toggle) move into the
|
|
111
|
+
// model sheet — the row has no width to spare and both are model settings.
|
|
112
|
+
installModelSheetExtras(ctx)
|
|
92
113
|
|
|
93
114
|
// "内测声明" first-run notice: keep it visible (CSS-hiding it leaked the
|
|
94
115
|
// dialog's #root inert lock — see effects/welcome-notice.ts) and offer a
|
|
@@ -163,7 +184,13 @@ export function apply(ctx: ClientContext): void {
|
|
|
163
184
|
forkSession: (id: SessionId) => ctx.sessions.fork({ sessionId: id }),
|
|
164
185
|
openSession: (id: SessionId) => ctx.sessions.open(id),
|
|
165
186
|
renameSession: (id: SessionId, title: string) => ctx.sessions.binding(id)?.session.rename(title),
|
|
166
|
-
|
|
187
|
+
// 0.1.2 的界面级归档在 uiWorkspace(顺带清当前选中);懒查——回调是用户
|
|
188
|
+
// 点击才跑,那时服务必已注册。本插件的 inject 不包含 uiWorkspace(0.1.1
|
|
189
|
+
// 没有它),所以启动时不能查一次就用:可能早于它注册。
|
|
190
|
+
archiveSession: (id: SessionId) => {
|
|
191
|
+
const ui = ctx.get('uiWorkspace') as UiWorkspaceLike | undefined
|
|
192
|
+
return ui === undefined ? ctx.workspaces.archiveSession(id) : ui.archiveSession(id)
|
|
193
|
+
},
|
|
167
194
|
downloadSessionLog: (id: SessionId) => ctx.sessionLogDownload.download(id),
|
|
168
195
|
}),
|
|
169
196
|
}, MobileSessionInfo))
|
|
@@ -215,11 +242,26 @@ export function apply(ctx: ClientContext): void {
|
|
|
215
242
|
store: nav,
|
|
216
243
|
inject: () => ({
|
|
217
244
|
openSession: (id: SessionId) => ctx.sessions.open(id),
|
|
218
|
-
|
|
245
|
+
// uiWorkspace 懒查:回调是用户点击才跑,服务那时必已注册(inject 不含
|
|
246
|
+
// uiWorkspace,启动时可能先于它注册,不能启动查一次就用)。0.1.1 上恒
|
|
247
|
+
// undefined,退回 ctx.workspaces.startSession——0.1.1 专用分支,0.1.2 的
|
|
248
|
+
// 类型里该方法已不存在,故对类型做 LegacyWorkspacesLike cast,运行时走不到。
|
|
249
|
+
startSession: (workspaceId?: WorkspaceId) => {
|
|
250
|
+
const ui = ctx.get('uiWorkspace') as UiWorkspaceLike | undefined
|
|
251
|
+
return ui === undefined
|
|
252
|
+
? (ctx.workspaces as unknown as LegacyWorkspacesLike).startSession(workspaceId)
|
|
253
|
+
: ui.startSession(workspaceId)
|
|
254
|
+
},
|
|
219
255
|
// S5 session-log chip — the same service call MobileDrawerFooter uses.
|
|
220
256
|
downloadSessionLog: (id: SessionId) => ctx.sessionLogDownload.download(id),
|
|
221
|
-
// Row swipe action — the same binding MobileSessionInfo archives with
|
|
222
|
-
|
|
257
|
+
// Row swipe action — the same binding MobileSessionInfo archives with;
|
|
258
|
+
// on 0.1.2 the uiWorkspace variant also clears the current selection
|
|
259
|
+
// when the archived session is the one selected. Same lazy lookup as
|
|
260
|
+
// the session-info sheet's archive binding above.
|
|
261
|
+
archiveSession: (id: SessionId) => {
|
|
262
|
+
const ui = ctx.get('uiWorkspace') as UiWorkspaceLike | undefined
|
|
263
|
+
return ui === undefined ? ctx.workspaces.archiveSession(id) : ui.archiveSession(id)
|
|
264
|
+
},
|
|
223
265
|
}),
|
|
224
266
|
}, MobileHome))
|
|
225
267
|
|
|
@@ -240,7 +282,10 @@ export function apply(ctx: ClientContext): void {
|
|
|
240
282
|
order: 5,
|
|
241
283
|
locale: NS,
|
|
242
284
|
inject: () => ({
|
|
243
|
-
|
|
285
|
+
// The footer slot contract hands the id over as a loose string;
|
|
286
|
+
// download() wants the branded SessionId — pure type-layer narrowing
|
|
287
|
+
// (runtime value is a session id either way, same as MobileHome.tsx).
|
|
288
|
+
downloadSessionLog: (sessionId: string) => ctx.sessionLogDownload.download(sessionId as SessionId),
|
|
244
289
|
toggleSidebar: () => ctx.layout.toggleSidebar(),
|
|
245
290
|
}),
|
|
246
291
|
}, MobileDrawerFooter))
|
|
@@ -254,3 +299,22 @@ import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
|
254
299
|
import type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'
|
|
255
300
|
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
|
256
301
|
import type {} from '@deepseek-ai/dsh-session-log-export/client'
|
|
302
|
+
// 0.1.2-only package: augments GlobalStandardProps with
|
|
303
|
+
// useSessions/useSessionPendingInteraction and SessionStandardProps with
|
|
304
|
+
// sessionId/useSession/useProjection, and declares the `ctx.uiSession`
|
|
305
|
+
// service. 0.1.1 has no such package — but this is `import type`, so the
|
|
306
|
+
// build output carries no trace of it and the runtime is unaffected.
|
|
307
|
+
import type {} from '@deepseek-ai/dsh-client-ui-session/client'
|
|
308
|
+
// 0.1.2-only controller packages: their `/client` type entries merge
|
|
309
|
+
// `ctx.sessions` / `ctx.workspaces` onto the cordis Context (0.1.1 declared
|
|
310
|
+
// both from dsh-client-runtime). Type-only again — nothing to load at
|
|
311
|
+
// runtime, so a 0.1.1 installation is unaffected.
|
|
312
|
+
import type {} from '@deepseek-ai/dsh-api-session-controller/client'
|
|
313
|
+
import type {} from '@deepseek-ai/dsh-api-workspace-controller/client'
|
|
314
|
+
// 0.1.2-only, type-only: the renderer declares `ctx.slots` on the cordis
|
|
315
|
+
// Context (the service this plugin registers every slot through) and
|
|
316
|
+
// `dsh-subagent/client` types the subagent catalog rows the session header
|
|
317
|
+
// reads (SessionListState.subagentsByParent). 0.1.1 had both declared in the
|
|
318
|
+
// dsh-client-runtime bundle that no longer exists.
|
|
319
|
+
import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
|
|
320
|
+
import type {} from '@deepseek-ai/dsh-subagent/client'
|
package/src/client/nav-store.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { defineStore } from '
|
|
2
|
-
import type { WorkspaceId } from '
|
|
1
|
+
import { defineStore } from './compat/store.ts'
|
|
2
|
+
import type { WorkspaceId } from './compat/types.ts'
|
|
3
3
|
|
|
4
4
|
/** The two levels of the phone page stack. */
|
|
5
5
|
export type MobileView = 'home' | 'session'
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { hasPendingInteraction } from './compat/types.ts'
|
|
2
|
+
import type { MobileSessionRow } from './compat/types.ts'
|
|
2
3
|
import type { StateDotState } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -6,11 +7,19 @@ import type { StateDotState } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
|
6
7
|
* Shared by the home-screen row dots (MobileHome.tsx) and the session
|
|
7
8
|
* header's running indicator (effects/header-status.ts), which reads it
|
|
8
9
|
* outside React — see that module for why.
|
|
9
|
-
*
|
|
10
|
+
*
|
|
11
|
+
* Pending interaction has two version-specific sources: `pending` is the
|
|
12
|
+
* 0.1.2 source (the uiSession service / useSessionPendingInteraction map,
|
|
13
|
+
* which this dot's caller resolves), and `hasPendingInteraction(row)` is the
|
|
14
|
+
* 0.1.1 source (the row's own field). Each version hits exactly one of the
|
|
15
|
+
* two: on 0.1.2 the row field is gone so the row check is always false, and
|
|
16
|
+
* on 0.1.1 `pending` is always false because the caller has no uiSession.
|
|
17
|
+
* @param row - session row.
|
|
18
|
+
* @param pending - whether the uiSession pending map has this row (0.1.2).
|
|
10
19
|
* @returns the dot state, or undefined when the row needs no dot.
|
|
11
20
|
*/
|
|
12
|
-
export function dotState(row:
|
|
13
|
-
if (
|
|
21
|
+
export function dotState(row: MobileSessionRow, pending?: boolean): StateDotState | undefined {
|
|
22
|
+
if (pending === true || hasPendingInteraction(row)) return 'warning'
|
|
14
23
|
if (row.running) return 'ongoing'
|
|
15
24
|
if (row.completed === true) return 'done'
|
|
16
25
|
return undefined
|
|
@@ -877,22 +877,39 @@ export const COMPAT_CSS = ` /* ---------- dsh-web-ui family compatibility -----
|
|
|
877
877
|
|
|
878
878
|
/* ---------- dsh-vision-router: composer vision toggle (2026-08-26) ----------
|
|
879
879
|
THIRD-PARTY COMPAT RULE — dsh-vision-router registers a Vision mode
|
|
880
|
-
toggle into conversation.input.right (
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
880
|
+
toggle into conversation.input.right (slot id
|
|
881
|
+
"vision-router-mode-toggle"): a bordered pill sized by INLINE styles —
|
|
882
|
+
minHeight 28, padding 4px 8px, fontSize 12, white-space nowrap —
|
|
883
|
+
roughly 90px of unshrinkable width.
|
|
884
|
+
|
|
885
|
+
Its children changed shape in 2.1.x (re-measured on the real UI
|
|
886
|
+
2026-09-04, plugin 2.1.1). Both forms are handled:
|
|
887
|
+
- text form (<= 2.0.x): <span>👁</span><span>label</span> plus a
|
|
888
|
+
<span>✓</span> while active;
|
|
889
|
+
- svg form (2.1.x, lib/client-presentation-boundary.js): a 14px eye
|
|
890
|
+
<svg>, <span>label</span>, plus a SECOND 12px check <svg> while
|
|
891
|
+
active.
|
|
892
|
+
The 2.1.1 update is what the user hit: the label span was still hidden
|
|
893
|
+
but the new eye svg was not, so it rendered NEXT TO our ::before eye —
|
|
894
|
+
two eyes in a 28px button — and the active check svg had nothing hiding
|
|
895
|
+
it at all, eating width the phone row does not have.
|
|
885
896
|
|
|
886
897
|
The phone composer row is nowrap by design (composer.css.ts section 1)
|
|
887
898
|
and the model seat is its only shrinkable item, so that pill squeezes
|
|
888
899
|
the model name out of the row (user report, 2026-08-26).
|
|
889
900
|
|
|
890
901
|
Phone treatment: a 28x28 round icon button in the attach button's
|
|
891
|
-
language (same size, same neutral fill)
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
902
|
+
language (same size, same neutral fill), showing exactly ONE 16px eye
|
|
903
|
+
and nothing else. Every span is hidden (label, and the text form's
|
|
904
|
+
emoji and check); of the svg form's icons only the first survives, the
|
|
905
|
+
trailing check svg is hidden by a sibling combinator so the count is
|
|
906
|
+
never assumed. Our own ::before eye is now a FALLBACK, gated on
|
|
907
|
+
:has(svg) so it draws only for the text form — the plugin ships its own
|
|
908
|
+
icon from 2.1.x on, and drawing over it is what produced the double
|
|
909
|
+
eye. Generated content rather than a DOM-injected icon, for the same
|
|
910
|
+
survives-React-re-render reason as the auto-approve shield below: the
|
|
911
|
+
button re-renders on every state flip, so an injected node would need
|
|
912
|
+
an observer while ::before does not. The
|
|
896
913
|
accessible name is unaffected: the button ships aria-label + title with
|
|
897
914
|
the full enable/disable copy, and aria-pressed keeps conveying the
|
|
898
915
|
state; visually the plugin's own inline styles still flip border +
|
|
@@ -935,12 +952,27 @@ export const COMPAT_CSS = ` /* ---------- dsh-web-ui family compatibility -----
|
|
|
935
952
|
background: var(--dsw-specific-selector, rgba(127, 127, 127, .12)) !important;
|
|
936
953
|
box-shadow: none !important;
|
|
937
954
|
}
|
|
938
|
-
/*
|
|
955
|
+
/* Text form: emoji + label + active check are all spans — all out. */
|
|
939
956
|
[data-slot="conversation.composer.bar"] [data-vision-router-mode-toggle] > span {
|
|
940
957
|
display: none !important;
|
|
941
958
|
}
|
|
959
|
+
/* Svg form (2.1.x): the first svg IS the eye, so keep it and match the
|
|
960
|
+
row's other icons at 16px. */
|
|
961
|
+
[data-slot="conversation.composer.bar"] [data-vision-router-mode-toggle] > svg:first-of-type {
|
|
962
|
+
width: 16px !important;
|
|
963
|
+
height: 16px !important;
|
|
964
|
+
}
|
|
965
|
+
/* ...and drop every svg after it — today that is the active-state check
|
|
966
|
+
mark. A sibling combinator rather than :nth-child(2): the count is the
|
|
967
|
+
plugin's business, this only says "one icon, the first one". */
|
|
968
|
+
[data-slot="conversation.composer.bar"] [data-vision-router-mode-toggle] > svg ~ svg {
|
|
969
|
+
display: none !important;
|
|
970
|
+
}
|
|
942
971
|
/* The one svg: eye outline + iris as a currentColor mask — brand while
|
|
943
972
|
active, dimmed with the button by the plugin's own disabled opacity. */
|
|
973
|
+
[data-slot="conversation.composer.bar"] [data-vision-router-mode-toggle]:has(svg)::before {
|
|
974
|
+
content: none !important;
|
|
975
|
+
}
|
|
944
976
|
[data-slot="conversation.composer.bar"] [data-vision-router-mode-toggle]::before {
|
|
945
977
|
content: '';
|
|
946
978
|
width: 16px;
|
|
@@ -65,12 +65,28 @@ export const COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- *
|
|
|
65
65
|
}
|
|
66
66
|
${ROW} > [class$="_tools"] > [class$="_modes"] {
|
|
67
67
|
order: 3 !important;
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
/* Rigid on purpose. This seat holds the permission chip, whose own
|
|
69
|
+
wrapper (the plugin's element inside _modes) is \`flex: 0 0 auto\` and
|
|
70
|
+
will NOT follow the seat down — so a shrinkable seat does not make the
|
|
71
|
+
chip narrower, it just slices the seat out from under a chip that
|
|
72
|
+
stays 44px wide, and the overflow lands on top of the model pill.
|
|
73
|
+
Measured 2026-09-04 on DSH 0.1.2 at 390px: seat 90-120 (29.7px) with
|
|
74
|
+
the chip still 90-134, model starting at 125 — a 9px overlap, exactly
|
|
75
|
+
the "the two chips touch" report. With the seat rigid: seat 90-134,
|
|
76
|
+
model 139-246, overlap 0; and at 320px the model pill shrinks to 52px
|
|
77
|
+
and still nothing overlaps.
|
|
78
|
+
This also restores what section 1 says out loud — the model pill is
|
|
79
|
+
the ONLY shrinkable item in the row. \`min-width: 0\` is gone with the
|
|
80
|
+
shrink: it only ever mattered as the companion of \`flex: 0 1\`. */
|
|
81
|
+
flex: 0 0 auto !important;
|
|
70
82
|
gap: 6px !important;
|
|
71
83
|
}
|
|
72
|
-
/* The
|
|
73
|
-
the
|
|
84
|
+
/* The elastic gap used to live here as \`margin-right: auto\`, which broke
|
|
85
|
+
the moment a session had no model pill: a subagent session has none, so
|
|
86
|
+
the gap vanished and the stop / send buttons bunched up in the middle of
|
|
87
|
+
the row instead of sitting at the right edge (reported 2026-09-06). The
|
|
88
|
+
gap belongs to the RIGHT group, not to the last item of the left one —
|
|
89
|
+
see the two rules below the order list. */
|
|
74
90
|
${MODEL} {
|
|
75
91
|
order: 4 !important;
|
|
76
92
|
/* A definite flex-basis (not auto) breaks the circular sizing between
|
|
@@ -81,7 +97,6 @@ export const COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- *
|
|
|
81
97
|
flex: 0 1 min(48vw, 200px) !important;
|
|
82
98
|
min-width: 0 !important;
|
|
83
99
|
max-width: min(48vw, 200px) !important;
|
|
84
|
-
margin-right: auto !important;
|
|
85
100
|
}
|
|
86
101
|
/* Third-party input.right entries park next to the ring rather than
|
|
87
102
|
landing at order 0 (= far left) once the groups are flattened. */
|
|
@@ -96,6 +111,22 @@ export const COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- *
|
|
|
96
111
|
${ROW} > [class$="_trailing"] > [class$="_primary"] {
|
|
97
112
|
order: 7 !important;
|
|
98
113
|
}
|
|
114
|
+
/* The elastic gap. It sits in front of whichever of the two right-hand
|
|
115
|
+
items comes first, so the group hugs the right edge no matter what the
|
|
116
|
+
left side happens to contain — with a model pill, without one (subagent
|
|
117
|
+
sessions), with or without the third-party entries at order 5.
|
|
118
|
+
Both get \`margin-left: auto\` and the last rule takes it back off the
|
|
119
|
+
send button whenever a ring precedes it: two auto margins would SHARE
|
|
120
|
+
the free space and open a second gap between the ring and send. A
|
|
121
|
+
sibling combinator answers "is there a ring before me" without assuming
|
|
122
|
+
anything about the host's nesting. */
|
|
123
|
+
${ROW} > [class$="_trailing"] > span[class$="_root"],
|
|
124
|
+
${ROW} > [class$="_trailing"] > [class$="_primary"] {
|
|
125
|
+
margin-left: auto !important;
|
|
126
|
+
}
|
|
127
|
+
${ROW} > [class$="_trailing"] > span[class$="_root"] ~ [class$="_primary"] {
|
|
128
|
+
margin-left: 0 !important;
|
|
129
|
+
}
|
|
99
130
|
|
|
100
131
|
/* --- 3. permission as icon-only pill; model as name+effort pill ---
|
|
101
132
|
(real-device round 2, 2026-08-17; model pill reworked 2026-08-25:
|
|
@@ -216,6 +247,115 @@ export const COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- *
|
|
|
216
247
|
border-radius: 12px !important;
|
|
217
248
|
font-size: 15px !important;
|
|
218
249
|
}
|
|
250
|
+
/* --- 4a. third-party composer entries live in the model sheet ---
|
|
251
|
+
effects/model-sheet-extras.ts parks the \`conversation.input.right\`
|
|
252
|
+
container inside the model menu while that menu is open and puts it back
|
|
253
|
+
when it closes; these rules are the two halves of that trip.
|
|
254
|
+
|
|
255
|
+
In the row: hidden, but ONLY once the effect has marked the container as
|
|
256
|
+
one it manages. Keying the hide off the marker rather than off "not
|
|
257
|
+
currently parked" is what makes the failure direction safe — if the
|
|
258
|
+
effect declines to act (nothing in the slot but the vision toggle, so
|
|
259
|
+
nothing worth moving) or never runs at all, the marker is absent, this
|
|
260
|
+
rule does not match, and the controls stay exactly where the host put
|
|
261
|
+
them. The inverse spelling hid them in the row whenever they were not in
|
|
262
|
+
the sheet, which in those same cases left them reachable from neither
|
|
263
|
+
place (caught in review, 2026-09-06).
|
|
264
|
+
Hiding by ancestor rather than by a display rule on the container itself
|
|
265
|
+
is what lets the parked copy style itself freely — same element, two
|
|
266
|
+
homes, two rule sets. \`!important\` because the slot container carries an
|
|
267
|
+
inline \`display: contents\`. */
|
|
268
|
+
${ROW} > [class$="_trailing"] > [data-slot="conversation.input.right"][data-zen-sheet-extras] {
|
|
269
|
+
display: none !important;
|
|
270
|
+
}
|
|
271
|
+
/* Parked in the sheet: a column of full-width rows in the sheet's own
|
|
272
|
+
language, so a relocated control reads as a sibling of 模型 and 推理等级
|
|
273
|
+
rather than a chip that wandered in. */
|
|
274
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] {
|
|
275
|
+
display: flex !important;
|
|
276
|
+
flex-direction: column !important;
|
|
277
|
+
gap: 2px !important;
|
|
278
|
+
width: 100% !important;
|
|
279
|
+
}
|
|
280
|
+
/* Undo the row's compaction on every parked control: back to full width and
|
|
281
|
+
the 48px the sheet's own cells use. \`> * > *\` reaches both shapes without
|
|
282
|
+
naming either plugin — subscriptions wraps its trigger in a positioning
|
|
283
|
+
div, vision-router puts its button straight in the slot. */
|
|
284
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] > *,
|
|
285
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] > * > button {
|
|
286
|
+
width: 100% !important;
|
|
287
|
+
max-width: none !important;
|
|
288
|
+
min-width: 0 !important;
|
|
289
|
+
justify-content: flex-start !important;
|
|
290
|
+
}
|
|
291
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] button {
|
|
292
|
+
box-sizing: border-box !important;
|
|
293
|
+
min-height: 48px !important;
|
|
294
|
+
height: auto !important;
|
|
295
|
+
padding: 0 12px !important;
|
|
296
|
+
gap: 10px !important;
|
|
297
|
+
border-radius: 12px !important;
|
|
298
|
+
border: none !important;
|
|
299
|
+
background: transparent !important;
|
|
300
|
+
font-size: 15px !important;
|
|
301
|
+
/* Both plugins center their chip label, which is right for a pill in the
|
|
302
|
+
row and wrong for a full-width row: the sheet's own cells start their
|
|
303
|
+
label at the left edge. justify-content covers the flex triggers,
|
|
304
|
+
text-align the ones that are not flex containers. */
|
|
305
|
+
text-align: left !important;
|
|
306
|
+
justify-content: flex-start !important;
|
|
307
|
+
}
|
|
308
|
+
/* Order comes from the row (compat.css.ts puts the vision toggle at 5 so it
|
|
309
|
+
parks beside the context ring), and that rule still matches in here — the
|
|
310
|
+
sheet is a DOM descendant of the composer bar even though it paints as a
|
|
311
|
+
fixed layer. Reset it so the sheet follows DOM order instead of
|
|
312
|
+
inheriting a decision that was about a different layout. */
|
|
313
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] > * {
|
|
314
|
+
order: 0 !important;
|
|
315
|
+
}
|
|
316
|
+
/* The vision toggle is squeezed to a 28px icon in the row (compat.css.ts);
|
|
317
|
+
in the sheet it is a row, so its label comes back and the icon leads. */
|
|
318
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] [data-vision-router-mode-toggle] > span {
|
|
319
|
+
display: inline !important;
|
|
320
|
+
}
|
|
321
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] [data-vision-router-mode-toggle] > svg:first-of-type {
|
|
322
|
+
width: 18px !important;
|
|
323
|
+
height: 18px !important;
|
|
324
|
+
}
|
|
325
|
+
/* The speed chip opens a menu of its own, anchored \`bottom: 100%\` to its
|
|
326
|
+
trigger. Inside the sheet — which is \`overflow: hidden\` so its rounded
|
|
327
|
+
top clips its content — that menu would be cut off. Same escape section 4
|
|
328
|
+
uses for the host's two menus: position: fixed leaves the clip behind
|
|
329
|
+
(the sheet sets \`transform: none\`, so nothing between here and the
|
|
330
|
+
viewport captures fixed positioning) and it lands as its own sheet on top
|
|
331
|
+
of this one. */
|
|
332
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] [role="menu"] {
|
|
333
|
+
position: fixed !important;
|
|
334
|
+
left: 0 !important;
|
|
335
|
+
right: 0 !important;
|
|
336
|
+
bottom: 0 !important;
|
|
337
|
+
top: auto !important;
|
|
338
|
+
margin: 0 !important;
|
|
339
|
+
width: 100% !important;
|
|
340
|
+
min-width: 0 !important;
|
|
341
|
+
max-width: none !important;
|
|
342
|
+
max-height: min(70dvh, 520px) !important;
|
|
343
|
+
box-sizing: border-box !important;
|
|
344
|
+
border-radius: 16px 16px 0 0 !important;
|
|
345
|
+
border-bottom: none !important;
|
|
346
|
+
padding: 8px 8px calc(8px + var(--mnav-sab)) !important;
|
|
347
|
+
/* Above the model sheet it sits on (60, section 4), below the session-info
|
|
348
|
+
sheet's 70 (info.css.ts) — it is a child of the model sheet, not a peer
|
|
349
|
+
of the page's own layers. */
|
|
350
|
+
z-index: 61 !important;
|
|
351
|
+
box-shadow: 0 -8px 32px rgba(0, 0, 0, .18) !important;
|
|
352
|
+
}
|
|
353
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] [role="menu"] [role="menuitemradio"] {
|
|
354
|
+
min-height: 48px !important;
|
|
355
|
+
border-radius: 12px !important;
|
|
356
|
+
font-size: 15px !important;
|
|
357
|
+
}
|
|
358
|
+
|
|
219
359
|
/* --- 4b. the official scroll-to-bottom button must not poke through the
|
|
220
360
|
sheet (real-device follow-up, 2026-08-17) ---
|
|
221
361
|
ChatView's own "jump to latest" button (aria-label t("chat.toBottom"))
|
|
@@ -250,6 +390,17 @@ export const COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- *
|
|
|
250
390
|
[class$="_composerSeat"] {
|
|
251
391
|
--dsh-composer-text-max-height: 124px;
|
|
252
392
|
}
|
|
393
|
+
/* Pull the text in from the card's left border. Upstream insets the editing
|
|
394
|
+
host by 16px while the control row below it starts 8px in (row padding,
|
|
395
|
+
section 1), so the placeholder sat noticeably further out than the
|
|
396
|
+
buttons under it and the field read as over-padded on a phone (reported
|
|
397
|
+
2026-09-06). 12px squares it with the 12px of clear space on the right,
|
|
398
|
+
giving the text a symmetric box, and closes most of the gap with the
|
|
399
|
+
button row. Anchored on the editing host's own contract attribute rather
|
|
400
|
+
than a hashed class. */
|
|
401
|
+
[data-slot="conversation.composer.bar"] [data-composer-input] {
|
|
402
|
+
padding-left: 12px !important;
|
|
403
|
+
}
|
|
253
404
|
|
|
254
405
|
/* --- 6. no divider above OR below the message list ---
|
|
255
406
|
Instead of a rule the messages butt against, they fade near both edges.
|
|
@@ -341,7 +492,13 @@ export const COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- *
|
|
|
341
492
|
composer. Anchored on the official data-testid, a stable contract
|
|
342
493
|
marker rather than a hashed class name. */
|
|
343
494
|
[data-slot="conversation.input.dock"] > [data-testid="todo-panel"] {
|
|
344
|
-
|
|
495
|
+
/* A whole line to itself. It shared line 0 with the pills until the goal
|
|
496
|
+
bar turned up (2026-09-06): two full-width things on one nowrap line
|
|
497
|
+
meant the to-do header shrank to a stub and the goal bar ran off the
|
|
498
|
+
right edge. Both are status strips you read at a glance, so they stack
|
|
499
|
+
— see the line policy in 7c. */
|
|
500
|
+
flex: 1 0 100% !important;
|
|
501
|
+
order: 1 !important;
|
|
345
502
|
min-width: 0 !important;
|
|
346
503
|
max-width: none !important;
|
|
347
504
|
min-height: 0 !important;
|
|
@@ -368,7 +525,7 @@ export const COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- *
|
|
|
368
525
|
display: flex !important;
|
|
369
526
|
flex: 1 0 100% !important;
|
|
370
527
|
flex-wrap: wrap !important;
|
|
371
|
-
order:
|
|
528
|
+
order: 4 !important;
|
|
372
529
|
align-items: center !important;
|
|
373
530
|
gap: 6px !important;
|
|
374
531
|
max-width: none !important;
|
|
@@ -403,12 +560,12 @@ export const COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- *
|
|
|
403
560
|
sits closest to the input it will be sent from. The rail only wraps
|
|
404
561
|
while one of the two full-width entries exists, so a session with
|
|
405
562
|
neither keeps the original single scrolling chip line. */
|
|
406
|
-
[data-slot="conversation.input.dock"]:has(> [data-queue-dock], > [data-mobile-nav="attach-chips"]) {
|
|
563
|
+
[data-slot="conversation.input.dock"]:has(> [data-queue-dock], > [data-mobile-nav="attach-chips"], > [data-testid="todo-panel"], > [data-goal-bar]) {
|
|
407
564
|
flex-wrap: wrap !important;
|
|
408
565
|
}
|
|
409
566
|
[data-slot="conversation.input.dock"] > [data-queue-dock] {
|
|
410
567
|
flex: 1 0 100% !important;
|
|
411
|
-
order:
|
|
568
|
+
order: 3 !important;
|
|
412
569
|
max-width: none !important;
|
|
413
570
|
min-height: 0 !important;
|
|
414
571
|
max-height: none !important;
|
|
@@ -419,6 +576,24 @@ export const COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- *
|
|
|
419
576
|
font-size: 13px !important;
|
|
420
577
|
line-height: normal !important;
|
|
421
578
|
}
|
|
579
|
+
/* dsh-client-ui-goal's status strip (its own \`data-goal-bar\` marker; it
|
|
580
|
+
registers into this dock at order 10). Fourth thing here that is not a
|
|
581
|
+
pill: a row carrying the objective text plus pause / edit / delete
|
|
582
|
+
buttons, so the 26px cage clipped its buttons off the right edge and the
|
|
583
|
+
nowrap line pushed what was left off screen (reported 2026-09-06). Same
|
|
584
|
+
treatment the queue gets — a line of its own, official geometry back. */
|
|
585
|
+
[data-slot="conversation.input.dock"] > [data-goal-bar] {
|
|
586
|
+
flex: 1 0 100% !important;
|
|
587
|
+
order: 2 !important;
|
|
588
|
+
max-width: none !important;
|
|
589
|
+
min-height: 0 !important;
|
|
590
|
+
max-height: none !important;
|
|
591
|
+
border-radius: 12px !important;
|
|
592
|
+
overflow: visible !important;
|
|
593
|
+
font-size: 13px !important;
|
|
594
|
+
line-height: normal !important;
|
|
595
|
+
}
|
|
596
|
+
|
|
422
597
|
/* The panel is the dock's only child; upstream rounds its top corners only
|
|
423
598
|
and drops the bottom border, because there it is half-hidden behind the
|
|
424
599
|
card. On its own line it is a free-standing strip, so close it up. */
|
|
@@ -34,6 +34,27 @@ export const INFO_CSS = `/* ---------- session-info sheet (< 768px) ---------- *
|
|
|
34
34
|
display: block !important;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/* The sheet renders inside the header, and \`[data-phase] header\` is a
|
|
38
|
+
stacking context of its own (position: relative + z-index: 2, set in
|
|
39
|
+
header.css.ts so the header's fade strip paints over the scroller). A
|
|
40
|
+
child's z-index is scoped to that context, so the z:70 below competes not
|
|
41
|
+
with the page but with the header's own 2 — and the host's composer seat
|
|
42
|
+
(.wSkVaW_composerSeat, z:7, from dsh-client-ui-conversation) outranks
|
|
43
|
+
it. Measured live on DSH 0.1.2, 2026-09-04: with the sheet open, the
|
|
44
|
+
topmost element over the composer band was the composer's own trigger
|
|
45
|
+
button, i.e. the input row painted THROUGH the card.
|
|
46
|
+
Promote the header itself for exactly as long as the sheet is mounted —
|
|
47
|
+
MobileSessionInfo returns null when closed (\`if (!open) return null\`),
|
|
48
|
+
so the header drops back to 2 on close (verified: 70 while open, 2
|
|
49
|
+
after). Descendant :has(), never \`>\`: the utilities slot wrapper is
|
|
50
|
+
\`display: contents\` and the host owns the depth (AGENTS.md — do not
|
|
51
|
+
encode host DOM depth in a selector).
|
|
52
|
+
Not raising the seat's own z-index instead: it belongs to the host and
|
|
53
|
+
is shared with the scroll-to-bottom button and the composer sheets. */
|
|
54
|
+
[data-phase] header:has([data-mobile-nav="info-layer"]) {
|
|
55
|
+
z-index: 70;
|
|
56
|
+
}
|
|
57
|
+
|
|
37
58
|
[data-mobile-nav="info-layer"] {
|
|
38
59
|
position: fixed;
|
|
39
60
|
inset: 0;
|