dsh-zen-remote 1.0.0
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 +51 -0
- package/LICENSE +38 -0
- package/README.md +237 -0
- package/assets/home.png +0 -0
- package/assets/info.png +0 -0
- package/assets/pairing.png +0 -0
- package/assets/session.png +0 -0
- package/assets/sheet.png +0 -0
- package/cordis.patch.yml +17 -0
- package/cordis.patch.yml.example +16 -0
- package/docs/interface.md +200 -0
- package/docs/remote-access.en.md +335 -0
- package/docs/remote-access.md +422 -0
- package/dsh-push.mjs +213 -0
- package/lan-gate.mjs +78 -0
- package/lib/client.js +6615 -0
- package/lib/index.js +310 -0
- package/lib/index.js.map +1 -0
- package/lib/lan-gate-server.cjs +626 -0
- package/lib/types/client/MobileAttachButton.d.ts +25 -0
- package/lib/types/client/MobileAttachButton.d.ts.map +1 -0
- package/lib/types/client/MobileAttachChips.d.ts +32 -0
- package/lib/types/client/MobileAttachChips.d.ts.map +1 -0
- package/lib/types/client/MobileDrawerFooter.d.ts +21 -0
- package/lib/types/client/MobileDrawerFooter.d.ts.map +1 -0
- package/lib/types/client/MobileHome.d.ts +24 -0
- package/lib/types/client/MobileHome.d.ts.map +1 -0
- package/lib/types/client/MobileHomeChips.d.ts +76 -0
- package/lib/types/client/MobileHomeChips.d.ts.map +1 -0
- package/lib/types/client/MobileNavOverlay.d.ts +15 -0
- package/lib/types/client/MobileNavOverlay.d.ts.map +1 -0
- package/lib/types/client/MobileNavToggle.d.ts +18 -0
- package/lib/types/client/MobileNavToggle.d.ts.map +1 -0
- package/lib/types/client/MobileSessionHeader.d.ts +58 -0
- package/lib/types/client/MobileSessionHeader.d.ts.map +1 -0
- package/lib/types/client/MobileSessionInfo.d.ts +41 -0
- package/lib/types/client/MobileSessionInfo.d.ts.map +1 -0
- package/lib/types/client/attach-upload.d.ts +78 -0
- package/lib/types/client/attach-upload.d.ts.map +1 -0
- package/lib/types/client/chips-store.d.ts +13 -0
- package/lib/types/client/chips-store.d.ts.map +1 -0
- package/lib/types/client/debug.d.ts +9 -0
- package/lib/types/client/debug.d.ts.map +1 -0
- package/lib/types/client/effects/aionui-compat.d.ts +4 -0
- package/lib/types/client/effects/aionui-compat.d.ts.map +1 -0
- package/lib/types/client/effects/gestures.d.ts +7 -0
- package/lib/types/client/effects/gestures.d.ts.map +1 -0
- package/lib/types/client/effects/header-status.d.ts +15 -0
- package/lib/types/client/effects/header-status.d.ts.map +1 -0
- package/lib/types/client/effects/phone-chrome.d.ts +106 -0
- package/lib/types/client/effects/phone-chrome.d.ts.map +1 -0
- package/lib/types/client/effects/turn-fold.d.ts +25 -0
- package/lib/types/client/effects/turn-fold.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +18 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/locales.d.ts +58 -0
- package/lib/types/client/locales.d.ts.map +1 -0
- package/lib/types/client/nav-store.d.ts +73 -0
- package/lib/types/client/nav-store.d.ts.map +1 -0
- package/lib/types/client/session-dot.d.ts +12 -0
- package/lib/types/client/session-dot.d.ts.map +1 -0
- package/lib/types/client/styles/base.css.d.ts +2 -0
- package/lib/types/client/styles/base.css.d.ts.map +1 -0
- package/lib/types/client/styles/chips.css.d.ts +2 -0
- package/lib/types/client/styles/chips.css.d.ts.map +1 -0
- package/lib/types/client/styles/compat.css.d.ts +2 -0
- package/lib/types/client/styles/compat.css.d.ts.map +1 -0
- package/lib/types/client/styles/composer.css.d.ts +2 -0
- package/lib/types/client/styles/composer.css.d.ts.map +1 -0
- package/lib/types/client/styles/header.css.d.ts +2 -0
- package/lib/types/client/styles/header.css.d.ts.map +1 -0
- package/lib/types/client/styles/home.css.d.ts +2 -0
- package/lib/types/client/styles/home.css.d.ts.map +1 -0
- package/lib/types/client/styles/index.d.ts +15 -0
- package/lib/types/client/styles/index.d.ts.map +1 -0
- package/lib/types/client/styles/info.css.d.ts +2 -0
- package/lib/types/client/styles/info.css.d.ts.map +1 -0
- package/lib/types/client/styles/layout.css.d.ts +2 -0
- package/lib/types/client/styles/layout.css.d.ts.map +1 -0
- package/lib/types/client/styles/misc.css.d.ts +2 -0
- package/lib/types/client/styles/misc.css.d.ts.map +1 -0
- package/lib/types/client/styles/turn-fold.css.d.ts +2 -0
- package/lib/types/client/styles/turn-fold.css.d.ts.map +1 -0
- package/lib/types/index.d.ts +83 -0
- package/lib/types/index.d.ts.map +1 -0
- package/llms.txt +20 -0
- package/package.json +128 -0
- package/pwa/app.css +95 -0
- package/pwa/icons/icon-192.png +0 -0
- package/pwa/icons/icon-512.png +0 -0
- package/pwa/icons/icon-maskable-512.png +0 -0
- package/pwa/icons/icon-maskable.svg +1 -0
- package/pwa/icons/icon.svg +1 -0
- package/pwa/inject.js +190 -0
- package/pwa/manifest.json +29 -0
- package/pwa/offline.html +38 -0
- package/pwa/sw.js +176 -0
- package/pwa/touch-gestures.js +94 -0
- package/scripts/build-client.mjs +101 -0
- package/scripts/cdp-probe.mjs +64 -0
- package/scripts/check-attach-upload.mjs +99 -0
- package/scripts/check-sunk-viewport.mjs +33 -0
- package/scripts/check-upload-endpoint.mjs +168 -0
- package/src/client/MobileAttachButton.tsx +125 -0
- package/src/client/MobileAttachChips.tsx +85 -0
- package/src/client/MobileDrawerFooter.tsx +56 -0
- package/src/client/MobileHome.tsx +348 -0
- package/src/client/MobileHomeChips.tsx +426 -0
- package/src/client/MobileNavOverlay.tsx +279 -0
- package/src/client/MobileNavToggle.tsx +52 -0
- package/src/client/MobileSessionHeader.tsx +242 -0
- package/src/client/MobileSessionInfo.tsx +303 -0
- package/src/client/attach-upload.ts +140 -0
- package/src/client/chips-store.ts +41 -0
- package/src/client/debug.ts +253 -0
- package/src/client/effects/aionui-compat.ts +183 -0
- package/src/client/effects/gestures.ts +265 -0
- package/src/client/effects/header-status.ts +30 -0
- package/src/client/effects/phone-chrome.ts +265 -0
- package/src/client/effects/turn-fold.ts +258 -0
- package/src/client/index.tsx +227 -0
- package/src/client/locales.ts +109 -0
- package/src/client/nav-store.ts +78 -0
- package/src/client/session-dot.ts +17 -0
- package/src/client/styles/base.css.ts +167 -0
- package/src/client/styles/chips.css.ts +224 -0
- package/src/client/styles/compat.css.ts +681 -0
- package/src/client/styles/composer.css.ts +504 -0
- package/src/client/styles/header.css.ts +368 -0
- package/src/client/styles/home.css.ts +477 -0
- package/src/client/styles/index.ts +25 -0
- package/src/client/styles/info.css.ts +238 -0
- package/src/client/styles/layout.css.ts +501 -0
- package/src/client/styles/misc.css.ts +128 -0
- package/src/client/styles/turn-fold.css.ts +106 -0
- package/src/index.ts +343 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phone-chrome.d.ts","sourceRoot":"","sources":["../../../../src/client/effects/phone-chrome.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAE3E;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAkC3D;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE;IAClD,UAAU,EAAE,OAAO,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;CACf,GAAG,OAAO,CAIV;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CA6E5D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAyCzD"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
|
|
2
|
+
/**
|
|
3
|
+
* S8 — collapse a turn's process into one summary row (< 768px only).
|
|
4
|
+
*
|
|
5
|
+
* Route taken: DOM marking, not the `conversation.chat.node` keyed slot.
|
|
6
|
+
* That slot dispatches on node kind and a second registration at a key
|
|
7
|
+
* SHADOWS the official renderer (dsh-client-ui-slots index.d.ts:542 — the
|
|
8
|
+
* cell's lowest-priority live entry renders, there is no wrapping form and
|
|
9
|
+
* no `children` handle to the shadowed component). Taking `tool-call` would
|
|
10
|
+
* mean re-implementing every tool's presentation, its `t` seat comes from
|
|
11
|
+
* the entry's own declared locale namespace and its services from the
|
|
12
|
+
* registrant's own inject face — neither is reachable from here — and the
|
|
13
|
+
* registration is global, so the desktop no-op would be gone too.
|
|
14
|
+
*
|
|
15
|
+
* The DOM route needs no class names: ChatNodeSeat stamps every row with
|
|
16
|
+
* `data-chat-flow-kind` / `data-chat-flow-key` (lib/client.js:5228) and
|
|
17
|
+
* ReasoningRow stamps `data-variant="think"` — turn grouping is pure
|
|
18
|
+
* structure (the run of rows following one `user` row), never text.
|
|
19
|
+
*
|
|
20
|
+
* ponytail: one rAF-coalesced full rescan per DOM mutation batch, O(rows).
|
|
21
|
+
* If a very long session ever makes streaming feel heavy, narrow the
|
|
22
|
+
* observer to the flow column's own childList and diff instead.
|
|
23
|
+
*/
|
|
24
|
+
export declare function installTurnFold(ctx: ClientContext): void;
|
|
25
|
+
//# sourceMappingURL=turn-fold.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-fold.d.ts","sourceRoot":"","sources":["../../../../src/client/effects/turn-fold.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAqH3E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAsHxD"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
|
|
2
|
+
import type { MobileNavKey } from './locales.ts';
|
|
3
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
4
|
+
interface LocaleNamespaceMap {
|
|
5
|
+
/** Directory-drawer controls copy. */
|
|
6
|
+
'mobileNav': MobileNavKey;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/** Required services (cordis fiber inject — the loader passes all module exports as an object plugin). */
|
|
10
|
+
export declare const inject: string[];
|
|
11
|
+
/**
|
|
12
|
+
* Mobile-adaptive shell, browser half: injects the mobile stylesheet, then
|
|
13
|
+
* contributes the directory toggle to the session header and the backdrop +
|
|
14
|
+
* floating button to the shell overlay.
|
|
15
|
+
* @param ctx - client root context.
|
|
16
|
+
*/
|
|
17
|
+
export declare function apply(ctx: ClientContext): void;
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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;AAkBnG,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,CAqL9C"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** `mobileNav` namespace dictionaries: drawer controls. */
|
|
2
|
+
export declare const NS = "mobileNav";
|
|
3
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
4
|
+
export declare const zh: {
|
|
5
|
+
readonly open: "打开目录";
|
|
6
|
+
readonly close: "收起目录";
|
|
7
|
+
readonly backdrop: "点击关闭目录";
|
|
8
|
+
readonly sessionLog: "导出会话日志";
|
|
9
|
+
readonly files: "文件浏览";
|
|
10
|
+
readonly previewFullscreen: "全屏预览";
|
|
11
|
+
readonly previewExitFullscreen: "退出全屏";
|
|
12
|
+
readonly allWorkspaces: "全部";
|
|
13
|
+
readonly switchWorkspace: "切换工作区";
|
|
14
|
+
readonly newSession: "新建会话";
|
|
15
|
+
readonly noSessions: "还没有会话,点右下角加号开始";
|
|
16
|
+
readonly homeStatusOngoing: "运行中";
|
|
17
|
+
readonly homeStatusWarning: "待处理";
|
|
18
|
+
readonly homeStatusDone: "已完成";
|
|
19
|
+
readonly backToList: "返回会话列表";
|
|
20
|
+
readonly sessionInfo: "会话信息";
|
|
21
|
+
readonly workbench: "工作台";
|
|
22
|
+
readonly workbenchClose: "关闭工作台";
|
|
23
|
+
readonly switchView: "切换视图";
|
|
24
|
+
readonly attach: "添加附件";
|
|
25
|
+
readonly attachBusy: "上传中…";
|
|
26
|
+
readonly attachFailed: "上传失败,点按重试";
|
|
27
|
+
readonly attachRemove: "移除附件 {name}";
|
|
28
|
+
readonly infoClose: "关闭";
|
|
29
|
+
readonly infoMode: "模式";
|
|
30
|
+
readonly infoCwdFallback: "未知目录";
|
|
31
|
+
readonly infoSubagents: "{count} 个子代理";
|
|
32
|
+
readonly infoStatTurns: "轮次";
|
|
33
|
+
readonly infoStatSteps: "步骤";
|
|
34
|
+
readonly infoStatTtft: "首字延迟";
|
|
35
|
+
readonly infoStatLlm: "模型耗时";
|
|
36
|
+
readonly infoStatTool: "工具耗时";
|
|
37
|
+
readonly infoStatTokens: "Token";
|
|
38
|
+
readonly infoCacheHit: "缓存 {percent}%";
|
|
39
|
+
readonly infoExport: "导出日志";
|
|
40
|
+
readonly infoRename: "重命名";
|
|
41
|
+
readonly infoRenamePrompt: "会话新名称";
|
|
42
|
+
readonly infoFork: "Fork 会话";
|
|
43
|
+
readonly infoArchive: "归档";
|
|
44
|
+
readonly infoArchiveConfirm: "归档后将从会话列表隐藏,确定继续?";
|
|
45
|
+
readonly infoActionError: "操作失败:{message}";
|
|
46
|
+
readonly turnFold: "过程 · {count} 步";
|
|
47
|
+
readonly turnFoldRunning: "进行中 · {count} 步";
|
|
48
|
+
readonly settings: "设置";
|
|
49
|
+
readonly chipTaskboard: "任务看板";
|
|
50
|
+
readonly chipSsh: "SSH";
|
|
51
|
+
readonly chipUsage: "用量";
|
|
52
|
+
readonly chipCustomize: "自定义入口";
|
|
53
|
+
};
|
|
54
|
+
/** English dictionary, key-identical to the Chinese source of truth. */
|
|
55
|
+
export declare const en: Record<MobileNavKey, string>;
|
|
56
|
+
/** Key domain of the `mobileNav` namespace (zh is the source of truth). */
|
|
57
|
+
export type MobileNavKey = keyof typeof zh;
|
|
58
|
+
//# sourceMappingURL=locales.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,eAAO,MAAM,EAAE,cAAc,CAAA;AAE7B,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiDL,CAAA;AAEV,wEAAwE;AACxE,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAiD3C,CAAA;AAED,2EAA2E;AAC3E,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,EAAE,CAAA"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client';
|
|
2
|
+
/** The two levels of the phone page stack. */
|
|
3
|
+
export type MobileView = 'home' | 'session';
|
|
4
|
+
/**
|
|
5
|
+
* Session-list workspace filter: a pinned workspace, the explicit "all"
|
|
6
|
+
* choice, or `null` — the untouched default that follows the current
|
|
7
|
+
* session's workspace (resolved in the component, so a workspace that only
|
|
8
|
+
* appears after the first baseline still lands).
|
|
9
|
+
*/
|
|
10
|
+
export type WorkspaceFilter = WorkspaceId | 'all' | null;
|
|
11
|
+
/**
|
|
12
|
+
* `window` event a `conversation.session.header.*` slot (session scope)
|
|
13
|
+
* fires to move the ROOT-scope nav store back to `home` — see
|
|
14
|
+
* {@link GO_HOME_EVENT} below for why a store handle cannot cross this
|
|
15
|
+
* particular scope boundary directly.
|
|
16
|
+
*/
|
|
17
|
+
export declare const GO_HOME_EVENT = "dsh-mobile-nav:go-home";
|
|
18
|
+
/**
|
|
19
|
+
* `window` event the header's ⓘ button (`conversation.session.header.utilities`,
|
|
20
|
+
* session scope) fires to open the session-info sheet (S4, MobileSessionInfo.tsx)
|
|
21
|
+
* — a second, sibling entry on the SAME slot. Not a store handle for the same
|
|
22
|
+
* reason {@link GO_HOME_EVENT} isn't: two independent registrations sharing
|
|
23
|
+
* one scope could hold a common handle instead, but keeping the info sheet's
|
|
24
|
+
* open/closed state fully local (a plain `useState`) is simpler than adding a
|
|
25
|
+
* second store, and the event is one line either way.
|
|
26
|
+
*/
|
|
27
|
+
export declare const SESSION_INFO_EVENT = "dsh-mobile-nav:session-info";
|
|
28
|
+
/**
|
|
29
|
+
* Phone page-stack store (phone breakpoint only; the tablet/desktop layouts
|
|
30
|
+
* never read it). Deliberately NOT persisted: the spec's launch rule is
|
|
31
|
+
* "always land on the session list", so a reload must reset to `home`.
|
|
32
|
+
*
|
|
33
|
+
* Built by a factory instead of a module-level constant: a module-scope
|
|
34
|
+
* handle is a disguised singleton across plugin reloads (ui-slots docs).
|
|
35
|
+
*
|
|
36
|
+
* One handle IS shared by every registration of one apply() — but only
|
|
37
|
+
* within the SAME slot scope. This handle mounts at `shell.overlay`, a
|
|
38
|
+
* ROOT-scope slot (MobileHome.tsx); declaring the identical handle on a
|
|
39
|
+
* SESSION-scope slot (e.g. `conversation.session.header.actions`) throws at
|
|
40
|
+
* runtime ("store handle mounted under ... is already mounted under scope
|
|
41
|
+
* ...", confirmed 2026-08-17) — the framework creates one live instance per
|
|
42
|
+
* (handle, scope), and root/session are different scopes even for the same
|
|
43
|
+
* handle. A session-scope registration that needs to move the page stack
|
|
44
|
+
* (the S2 header back button) cannot hold `actions.show` directly; it
|
|
45
|
+
* dispatches {@link GO_HOME_EVENT} instead, and MobileHome — already
|
|
46
|
+
* mounted with this store — is the one that calls `actions.show('home')`.
|
|
47
|
+
* @returns a fresh store handle, shared by every SAME-SCOPE registration of
|
|
48
|
+
* one apply().
|
|
49
|
+
*/
|
|
50
|
+
export declare function createNavStore(): import("@deepseek-ai/dsh-client-runtime/client").EngineStoreHandle<{
|
|
51
|
+
view: MobileView;
|
|
52
|
+
workspace: WorkspaceFilter;
|
|
53
|
+
}, {
|
|
54
|
+
/**
|
|
55
|
+
* Move the page stack.
|
|
56
|
+
* @param draft - store draft.
|
|
57
|
+
* @param view - target level.
|
|
58
|
+
*/
|
|
59
|
+
show: (draft: {
|
|
60
|
+
view: MobileView;
|
|
61
|
+
workspace: WorkspaceFilter;
|
|
62
|
+
}, view: MobileView) => void;
|
|
63
|
+
/**
|
|
64
|
+
* Pin the session-list workspace filter.
|
|
65
|
+
* @param draft - store draft.
|
|
66
|
+
* @param workspace - workspace id, or 'all'.
|
|
67
|
+
*/
|
|
68
|
+
filter: (draft: {
|
|
69
|
+
view: MobileView;
|
|
70
|
+
workspace: WorkspaceFilter;
|
|
71
|
+
}, workspace: WorkspaceFilter) => void;
|
|
72
|
+
}>;
|
|
73
|
+
//# sourceMappingURL=nav-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-store.d.ts","sourceRoot":"","sources":["../../../src/client/nav-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,8CAA8C;AAC9C,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,CAAA;AAE3C;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,KAAK,GAAG,IAAI,CAAA;AAExD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,2BAA2B,CAAA;AAErD;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,gCAAgC,CAAA;AAE/D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc;UAEK,UAAU;eAAqB,eAAe;;IAE3E;;;;OAIG;;cAN0B,UAAU;mBAAqB,eAAe;aAOvD,UAAU;IAG9B;;;;OAIG;;cAd0B,UAAU;mBAAqB,eAAe;kBAehD,eAAe;GAK/C"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SessionSummary } from '@deepseek-ai/dsh-client-runtime/client';
|
|
2
|
+
import type { StateDotState } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
|
+
/**
|
|
4
|
+
* Status dot state of one session, matching the official sidebar semantics.
|
|
5
|
+
* Shared by the home-screen row dots (MobileHome.tsx) and the session
|
|
6
|
+
* header's running indicator (effects/header-status.ts), which reads it
|
|
7
|
+
* outside React — see that module for why.
|
|
8
|
+
* @param row - session summary.
|
|
9
|
+
* @returns the dot state, or undefined when the row needs no dot.
|
|
10
|
+
*/
|
|
11
|
+
export declare function dotState(row: SessionSummary): StateDotState | undefined;
|
|
12
|
+
//# sourceMappingURL=session-dot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-dot.d.ts","sourceRoot":"","sources":["../../../src/client/session-dot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAA;AAE1E;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,aAAa,GAAG,SAAS,CAKvE"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const BASE_CSS = "\n/* ---------- safe-area variables (S2.1, 2026-08-17) ----------\n Every safe-area use in this stylesheet reads --mnav-sat / --mnav-sab\n instead of env() directly. Same computed value by default, but the\n indirection gives one place to override: ?mobile-nav-inset=54 (client/\n debug.ts) writes a fake inset onto the root element, so a desktop CDP\n run can regress notch layout \u2014 env(safe-area-inset-*) is hard 0 in every\n desktop browser, which is exactly why the header and the workbench\n panel shipped broken to a real iPhone. */\n:root {\n --mnav-sat: env(safe-area-inset-top, 0px);\n --mnav-sab: env(safe-area-inset-bottom, 0px);\n}\n\n/* ---------- base control styles (rendered at any width, hidden where unused) ---------- */\n\n[data-mobile-nav=\"toggle\"],\n[data-mobile-nav=\"files\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n flex: none;\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 -webkit-tap-highlight-color: transparent;\n}\n[data-mobile-nav=\"toggle\"]:hover,\n[data-mobile-nav=\"files\"]:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n}\n[data-mobile-nav=\"toggle\"]:focus-visible,\n[data-mobile-nav=\"files\"]:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary, #4f6ef7);\n outline-offset: 1px;\n}\n\n/* Drawer footer actions: the relocated Session log download plus the Files\n action that opens the dsh-web-ui explorer sheet. */\n[data-mobile-nav=\"drawer-actions\"] {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n[data-mobile-nav=\"session-log\"],\n[data-mobile-nav=\"explorer\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 34px;\n padding: 0 12px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12));\n border-radius: 12px;\n background: transparent;\n color: var(--dsw-alias-label-primary, inherit);\n font-family: inherit;\n font-size: 13px;\n line-height: 20px;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n}\n[data-mobile-nav=\"session-log\"]:hover:not(:disabled),\n[data-mobile-nav=\"explorer\"]:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n}\n[data-mobile-nav=\"session-log\"]:disabled {\n color: var(--dsw-alias-label-dimmed, rgba(0, 0, 0, .35));\n cursor: default;\n}\n\n/* Floating fallback button (hero / blank phases without a session header).\n The top clears the camera band below the status bar; when the client has\n set viewport-fit=cover the safe-area inset moves it below the notch too. */\n[data-mobile-nav=\"fab\"] {\n position: absolute;\n top: calc(var(--mnav-sat) + 72px);\n left: 10px;\n z-index: 21;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 38px;\n height: 38px;\n padding: 0;\n border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12));\n border-radius: 50%;\n background: var(--dsw-alias-button-floating-fill, #ffffff);\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n box-shadow: 0 2px 12px rgba(0, 0, 0, .18);\n -webkit-tap-highlight-color: transparent;\n}\n[data-mobile-nav=\"fab\"]:hover {\n background: var(--dsw-alias-button-floating-hover, rgba(0, 0, 0, .08));\n}\n[data-mobile-nav=\"fab\"]:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary, #4f6ef7);\n outline-offset: 2px;\n}\n\n/* Dimmed backdrop under the open drawer; above every column, below the drawer. */\n[data-mobile-nav=\"backdrop\"] {\n position: absolute;\n inset: 0;\n z-index: 30;\n background: rgba(0, 0, 0, .45);\n cursor: pointer;\n animation: dsh-mobile-nav-fade .2s var(--ds-ease-in-out, ease-in-out);\n -webkit-tap-highlight-color: transparent;\n}\n@keyframes dsh-mobile-nav-fade {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n/* Settings sheet entrance: the official dialog mounts with no animation at\n all, so it snaps in. Fade + slight rise/scale reads as a proper sheet. */\n@keyframes dsh-mobile-nav-sheet-in {\n from {\n opacity: 0;\n transform: translateY(14px) scale(.98);\n }\n to {\n opacity: 1;\n transform: none;\n }\n}\n/* Preview sheet rise: the aionui preview column opens as a bottom sheet. */\n@keyframes dsh-mobile-nav-sheet-up {\n from {\n opacity: 0;\n transform: translateY(28px);\n }\n to {\n opacity: 1;\n transform: none;\n }\n}\n\n/* ---------- beta-notice suppression (ALL widths \u2014 deliberate exception) ----\n User-directed (2026-08-17): hide DSH's beta/preview notices everywhere,\n including desktop. This is the ONE place this plugin intentionally breaks\n its own \">=1024px strict no-op\" rule \u2014 do not \"fix\" it back.\n Selector notes:\n - The hero \"\u9884\u89C8\u7248\" badge only carries a hashed class; per repo convention\n the stable part is the suffix (_previewBadge), matched with [class$=].\n - The first-run \"\u5185\u6D4B\u58F0\u660E\" modal is hidden via its ARIA label. The label is\n LOCALIZED \u2014 zh is covered; if the UI runs in another language the dialog\n shows (harmless, dismiss by hand). Hiding targets the presentation\n wrapper via :has() so the modal's backdrop cannot linger invisibly and\n block taps; the app never requires acknowledging this dialog to work. */\n[class$=\"_previewBadge\"] {\n display: none !important;\n}\n[role=\"presentation\"]:has(> [role=\"dialog\"][aria-label=\"\u5185\u6D4B\u58F0\u660E\"]),\n[role=\"dialog\"][aria-label=\"\u5185\u6D4B\u58F0\u660E\"] {\n display: none !important;\n}\n";
|
|
2
|
+
//# sourceMappingURL=base.css.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/base.css.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,QAAQ,moLAmKpB,CAAA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const CHIPS_CSS = "/* ---------- chips row + settings entry (S5) ---------- */\n\n@media (max-width: 767px) {\n /* --- settings dialog / usage-stats panel / scheduled-tasks dialog portal fix ---\n dsh-client-ui-settings-general's SettingsRoot (trigger button +\n position:fixed;z-index:1000 modal), dsh-usage-stats' sidebar-footer\n badge (position:fixed panel), and @opendsh/dsh-plugin-scheduled-tasks'\n \"\u5B9A\u65F6\u4EFB\u52A1\" trigger (position:fixed;z-index:120 `.dshst-overlay`,\n verified against 0.2.0 \u2014 real-device report 2026-08-17: chip click\n produced no visible window) all mount as PLAIN CHILDREN of the sidebar\n tree \u2014 not a React portal to document.body \u2014 inside\n sidebar.settings / sidebar.footer.action\n (dsh-client-ui-sidebar's SidebarRoot: footArea > settingsArea /\n footerActions). home.css.ts sets the sidebar ROOT\n (`[data-mobile-nav=\"frame\"] > :first-child`) to display:none on the\n phone breakpoint (the app shell replaced the drawer) \u2014 and display:none\n removes the WHOLE subtree from the render tree, including\n position:fixed descendants, which cannot escape it the way they escape\n a merely-offscreen or opacity:0 ancestor. A `.click()` on the (still\n DOM-present) hidden trigger still flips its component-local `open`\n state and mounts the aria-modal dialog (synthetic dispatch bypasses\n hit-testing \u2014 S2.1 precedent), but nothing paints without this fix.\n\n Un-hiding the WHOLE sidebar would surface the session tree / new-session\n button underneath the dialog, so this is scoped to exactly the sidebar\n root plus its OWN direct children that are not on the path to\n footArea: display:contents on the root removes its own box (no\n duplicate page background/padding, no grid-column reflow) while\n letting footArea/footerActions/settingsArea resume their OWN official\n (non-none) display \u2014 logoRow / newSession / regionArea are hidden\n explicitly instead. :has() matches against the DOM tree regardless of\n the scope element's own computed display, so this reads correctly even\n though the whole subtree started at display:none (same precedent as\n every other :has()-gated rule in this stylesheet, e.g. the settings\n sheet adaptation below, which depends on exactly this becoming\n visible).\n\n `:first-child` is a layout-column WRAPPER\n (`pI_x6G_sidebarCol`, dsh-client-ui-layout's AppFrame grid cell), not\n SidebarRoot's own div \u2014 SidebarRoot (`hHd-Xa_root`) sits one more empty\n unstyled div below it (measured 2026-08-17: sidebarCol > div (no\n class) > div.hHd-Xa_root > logoRow/newSession/regionArea/footArea).\n display:contents on sidebarCol lets that whole chain resume its own\n official (non-none) display with no other rule needed, but the\n \"hide the other three\" selectors below must therefore be DESCENDANT\n (unscoped depth), not direct-child `>`, or they silently match\n nothing \u2014 confirmed live: an earlier `>`-only version left\n logoRow/newSession/regionArea at `display: flex`, which is only\n hidden from view by being fully underneath the usage-stats panel's\n opaque body, not by this rule. */\n [data-mobile-nav=\"frame\"] > :first-child:has([aria-modal=\"true\"], [data-usage-stats-panel], .dshst-overlay) {\n display: contents !important;\n }\n [data-mobile-nav=\"frame\"] > :first-child:has([aria-modal=\"true\"], [data-usage-stats-panel], .dshst-overlay) [class$=\"_logoRow\"],\n [data-mobile-nav=\"frame\"] > :first-child:has([aria-modal=\"true\"], [data-usage-stats-panel], .dshst-overlay) [class$=\"_newSession\"],\n [data-mobile-nav=\"frame\"] > :first-child:has([aria-modal=\"true\"], [data-usage-stats-panel], .dshst-overlay) [class$=\"_regionArea\"] {\n display: none !important;\n }\n\n /* Home-top settings gear: 44pt touch target, pushed to the row's end by\n its own auto margin (home-top itself stays a plain flex row). */\n [data-mobile-nav=\"home-settings\"] {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n margin-left: auto;\n padding: 0;\n border: none;\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=\"home-settings\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n border-radius: 50%;\n }\n\n /* Plugin-entry chips row: one horizontally-scrolling line of 34px pills\n between the workspace title and the session list (prototype spec). A\n thin scrollbar is the overflow affordance (AGENTS.md: an early\n no-scrollbar version silently cut off the last tab with no way to\n discover more content). */\n [data-mobile-nav=\"chip-row\"] {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 0 16px 10px;\n overflow-x: auto;\n overscroll-behavior: contain;\n -webkit-overflow-scrolling: touch;\n scrollbar-width: thin;\n }\n [data-mobile-nav=\"chip-row\"]::-webkit-scrollbar {\n height: 2px;\n }\n [data-mobile-nav=\"chip-row\"]::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-border-l2, rgba(0, 0, 0, .16));\n border-radius: 2px;\n }\n [data-mobile-nav=\"chip\"],\n [data-mobile-nav=\"chip-more\"] {\n flex: none;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 34px;\n padding: 0 14px;\n border: none;\n border-radius: 17px;\n background: var(--dsw-alias-bg-base, #ffffff);\n color: var(--dsw-alias-label-primary, inherit);\n font-family: inherit;\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n box-shadow: 0 1px 3px rgba(0, 0, 0, .06);\n }\n [data-mobile-nav=\"chip-more\"] {\n padding: 0 9px;\n }\n [data-mobile-nav=\"chip\"]:active,\n [data-mobile-nav=\"chip-more\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n\n /* Harvested chip icon (S5.1): the cloned <svg> keeps whatever width/height\n the source plugin gave it (18px for the one verified live example) \u2014\n force it down to the same 16px box every hand-registered chip icon\n uses, CSS wins over the presentational SVG attributes. */\n [data-mobile-nav=\"chip-harvest-icon\"] {\n display: inline-flex;\n flex: none;\n }\n [data-mobile-nav=\"chip-harvest-icon\"] svg {\n display: block;\n width: 16px;\n height: 16px;\n }\n\n /* Customize sheet: toggle rows inside the SAME home-sheet-layer/mask/sheet\n chrome the workspace switcher declares in home.css.ts \u2014 S6's\n drag-to-close and mask-click-close bind to\n `[data-mobile-nav=\"home-sheet\"]` generically (effects/gestures.ts), so\n this second use of the marker needs no new gesture wiring. */\n [data-mobile-nav=\"chip-toggle-row\"] {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n /* Real-device fix: without border-box this row's used width is\n 100% (of the sheet's own already-padded content box) PLUS the\n 12px+12px padding below, overflowing the sheet by 24px and\n shoving the flex:none toggle off its right edge (2026-08-17\n report: \"\u5F00\u5173\u8DD1\u5230\u5C4F\u5E55\u5916\"). home-sheet-item next door in\n home.css.ts has the identical width:100%+padding shape and the\n same latent bug, just with no trailing element to visibly clip. */\n box-sizing: border-box;\n min-height: 48px;\n padding: 0 12px;\n color: inherit;\n }\n [data-mobile-nav=\"chip-toggle-label\"] {\n flex: 1 1 auto;\n min-width: 0;\n font-size: 15px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n [data-mobile-nav=\"chip-toggle\"] {\n flex: none;\n position: relative;\n width: 42px;\n height: 26px;\n border: none;\n border-radius: 13px;\n background: var(--dsw-alias-border-l2, rgba(0, 0, 0, .16));\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n transition: background .15s var(--ds-ease-in-out, ease-in-out);\n }\n [data-mobile-nav=\"chip-toggle\"]::after {\n content: '';\n position: absolute;\n top: 2px;\n left: 2px;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 3px rgba(0, 0, 0, .3);\n transition: transform .15s var(--ds-ease-in-out, ease-in-out);\n }\n [data-mobile-nav=\"chip-toggle\"][aria-checked=\"true\"] {\n background: var(--dsw-alias-button-primary-fill, #1a1a1a);\n }\n [data-mobile-nav=\"chip-toggle\"][aria-checked=\"true\"]::after {\n transform: translateX(16px);\n }\n @media (prefers-reduced-motion: reduce) {\n [data-mobile-nav=\"chip-toggle\"],\n [data-mobile-nav=\"chip-toggle\"]::after {\n transition: none !important;\n }\n }\n}\n";
|
|
2
|
+
//# sourceMappingURL=chips.css.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chips.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/chips.css.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,SAAS,8gSAwNrB,CAAA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const COMPAT_CSS = " /* ---------- dsh-web-ui family compatibility ----------\n The linxin666 plugin suite extends the shell frame directly:\n - aionui-panel appends two trailing grid columns (explorer / preview)\n plus absolute drag handles to [data-dsh-frame]; its 5-track inline\n grid is already overridden above, but the handles and columns would\n still float over the main UI. On mobile the columns leave the grid\n as floating bottom sheets and keep their own visibility state \u2014\n the suite's collapse chevron / preview tabs still work, so no\n feature is lost. The task-board / ssh plugins inject sidebar\n entries and center-column takeover panels; the entries need\n spacing and the kanban needs scrollable columns. */\n\n /* Touch devices: the drag handles are useless \u2014 the floating expand\n button is the opener. */\n .aionui-explorer-handle,\n .aionui-preview-handle {\n display: none !important;\n }\n\n /* Shared base: both columns leave the grid as floating panels. The\n explorer is gated shut by default (its own persisted expanded state\n must never cover the mobile UI on load); the header Files action opens\n it via the frame marker below, and the sheet's own collapse chevron\n clears it. Preview stays owned by the suite (hidden while no tab is\n open). The per-column rules below override the geometry. */\n [data-aionui-explorer-col],\n [data-aionui-preview-col] {\n position: fixed !important;\n z-index: 55 !important;\n background: var(--aion-bg-base, #ffffff) !important;\n border-left: none !important;\n }\n /* Explorer (file tree) bottom sheet: bottom edge aligned exactly with\n the composer card's bottom line \u2014 the card sits 36px above the\n viewport bottom (8px composer padding + the 28px stats strip below\n the card), so the sheet uses the same 36px bottom offset. */\n [data-aionui-explorer-col] {\n visibility: hidden !important;\n left: 8px !important;\n right: 8px !important;\n top: auto !important;\n bottom: 36px !important;\n width: auto !important;\n height: min(55dvh, 460px) !important;\n max-height: calc(100dvh - 44px) !important;\n border-radius: 14px !important;\n overflow: hidden !important;\n box-shadow: 0 -4px 28px rgba(0, 0, 0, .18) !important;\n animation: dsh-mobile-nav-sheet-up .24s var(--ds-ease-out, ease-in-out) !important;\n }\n /* Preview (file content) bottom sheet. Gated shut by default: the suite\n persists open preview tabs in localStorage and restores them on load,\n which would pop the sheet over the fresh UI. The client only sets the\n frame marker after the user taps a file row in the explorer; the\n suite's own collapse chevron clears it via the visibility watcher. */\n [data-aionui-preview-col] {\n visibility: hidden !important;\n position: fixed !important;\n left: 8px !important;\n right: 8px !important;\n top: auto !important;\n bottom: 40px !important;\n width: auto !important;\n height: min(50dvh, 420px) !important;\n max-height: calc(100dvh - 48px) !important;\n border-radius: 14px !important;\n overflow: hidden !important;\n box-shadow: 0 -4px 28px rgba(0, 0, 0, .18) !important;\n z-index: 56 !important;\n animation: dsh-mobile-nav-sheet-up .24s var(--ds-ease-out, ease-in-out) !important;\n /* Fullscreen toggle (issue #8): animate the geometry change instead of\n snapping. visibility is deliberately not listed, so opening/closing\n the sheet stays instant; the open/close keyframes own transform. */\n transition:\n left .24s var(--ds-ease-out, ease-in-out),\n right .24s var(--ds-ease-out, ease-in-out),\n top .24s var(--ds-ease-out, ease-in-out),\n bottom .24s var(--ds-ease-out, ease-in-out),\n width .24s var(--ds-ease-out, ease-in-out),\n height .24s var(--ds-ease-out, ease-in-out),\n border-radius .24s var(--ds-ease-out, ease-in-out),\n box-shadow .24s var(--ds-ease-out, ease-in-out),\n padding-top .24s var(--ds-ease-out, ease-in-out) !important;\n }\n /* User-opened preview sheet (frame marker, set on file-row tap). */\n [data-mobile-nav=\"frame\"][data-aionui-preview-open] [data-aionui-preview-col] {\n visibility: visible !important;\n }\n /* The Files action opens the explorer sheet (frame marker). */\n [data-mobile-nav=\"frame\"][data-aionui-explorer-open] [data-aionui-explorer-col] {\n visibility: visible !important;\n }\n /* While the preview sheet is up, the explorer sheet yields (two stacked\n bottom sheets would read as one broken overlay). Closing the preview\n via its collapse chevron / tab close clears the marker, and the\n explorer sheet returns. Same specificity as the explorer-open rule, so\n this must stay AFTER it. */\n [data-mobile-nav=\"frame\"][data-aionui-preview-open] [data-aionui-explorer-col] {\n visibility: hidden !important;\n }\n /* The open drawer must never sit under a sheet: while the frame is in the\n narrow-expanded state both sheets yield (later in the file than the\n open marker rule, so it wins at equal specificity). The fullscreen\n toggle has its own drawer-open rule at the end of its section. */\n [data-mobile-nav=\"frame\"]:not([data-sidebar-collapsed]) [data-aionui-explorer-col],\n [data-mobile-nav=\"frame\"]:not([data-sidebar-collapsed]) [data-aionui-preview-col] {\n visibility: hidden !important;\n display: none !important;\n }\n /* The suite's own expand button reads the store state we bypass on\n mobile \u2014 hide it; the header Files action is the opener. */\n .aionui-floating-expand {\n display: none !important;\n }\n\n /* Preview sheet fullscreen toggle (issue #8): a fixed button parked in the\n sheet's titlebar row, just left of the suite's collapse chevron (24px at\n right:8px of the sheet, and the sheet spans 8px..(100vw-8px)). The top\n calc mirrors the sheet geometry above (bottom 40px + min(50dvh, 420px));\n when the frame carries \"data-mobile-preview-full\" the sheet goes\n fullscreen and the button moves to the viewport corner. */\n [data-mobile-nav=\"preview-full-toggle\"] {\n position: absolute !important;\n right: 36px !important;\n top: 8px !important;\n z-index: 57 !important;\n display: none !important;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n padding: 0;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: var(--aion-text-secondary, var(--dsw-alias-label-secondary, inherit));\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n /* Native look: same size/radius/hover language as the suite's tab-bar\n icon buttons (the 20px panelCollapse next to it). The button lives\n INSIDE the preview column, so it rides the sheet's own open\n animation and geometry transition \u2014 no curve matching needed. */\n transition: background-color .15s, top .24s var(--ds-ease-out, ease-in-out);\n }\n [data-mobile-nav=\"preview-full-toggle\"]:hover {\n background: var(--aion-bg-3, rgba(0, 0, 0, .22));\n }\n [data-mobile-nav=\"preview-full-toggle\"]:active {\n background: var(--aion-bg-active, rgba(0, 0, 0, .28));\n }\n [data-mobile-nav=\"preview-full-toggle\"]:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary, #4f6ef7);\n outline-offset: 2px;\n }\n [data-mobile-nav=\"preview-full-toggle\"] svg {\n width: 14px;\n height: 14px;\n }\n /* Keep the last tab (and the \"+\" URL-tab trigger) from sliding under the\n fullscreen toggle: reserve the right end of the preview tab row. */\n [data-aionui-preview-col] [class$=\"_tabScroll\"] {\n padding-right: 34px !important;\n }\n /* Visible only while the preview sheet is open. Visibility itself is\n inherited from the column, so the sheet's own hide rules (collapse,\n drawer open) cover the button too. */\n [data-mobile-nav=\"frame\"][data-aionui-preview-open] [data-aionui-preview-col] [data-mobile-nav=\"preview-full-toggle\"] {\n display: inline-flex !important;\n }\n /* Icon swap on the frame fullscreen marker. */\n [data-mobile-nav=\"preview-full-toggle\"] .dsh-mobile-nav-full-out {\n display: none !important;\n }\n [data-mobile-nav=\"frame\"][data-mobile-preview-full] [data-aionui-preview-col] [data-mobile-nav=\"preview-full-toggle\"] .dsh-mobile-nav-full-in {\n display: none !important;\n }\n [data-mobile-nav=\"frame\"][data-mobile-preview-full] [data-aionui-preview-col] [data-mobile-nav=\"preview-full-toggle\"] .dsh-mobile-nav-full-out {\n display: inline !important;\n }\n /* Fullscreen preview: the sheet fills the whole viewport (notch included);\n the safe-area padding drops the titlebar row below the status bar, and\n the toggle follows the titlebar into the top corner. */\n [data-mobile-nav=\"frame\"][data-aionui-preview-open][data-mobile-preview-full] [data-aionui-preview-col] {\n inset: 0 !important;\n left: 0 !important;\n right: 0 !important;\n top: 0 !important;\n bottom: 0 !important;\n width: 100% !important;\n height: 100dvh !important;\n max-height: none !important;\n box-sizing: border-box !important;\n padding-top: var(--mnav-sat) !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n z-index: 57 !important;\n animation: none !important;\n }\n /* Fullscreen: the column fills the viewport, so the button follows the\n titlebar row down below the notch. */\n [data-mobile-nav=\"frame\"][data-mobile-preview-full] [data-aionui-preview-col] [data-mobile-nav=\"preview-full-toggle\"] {\n top: calc(var(--mnav-sat) + 8px) !important;\n }\n @media (prefers-reduced-motion: reduce) {\n [data-aionui-preview-col],\n [data-mobile-nav=\"preview-full-toggle\"] {\n transition: none !important;\n animation: none !important;\n }\n }\n\n /* dsh-web-ui sidebar entries (task board / ssh) sit flush against each\n other \u2014 give the injected rows breathing room. */\n button[data-dsh-taskboard-entry],\n button[data-dsh-ssh-entry] {\n margin-bottom: 8px !important;\n }\n\n /* Task board: five kanban columns at minmax(0,1fr) crush into ~78px phone\n strips. Give every column a usable minimum and let the row scroll. */\n [data-dsh-taskboard-board] > [class$=\"_columns\"] {\n grid-template-columns: repeat(5, minmax(240px, 1fr)) !important;\n overflow-x: auto !important;\n }\n /* The floating button must not float over a takeover panel (task board /\n ssh own the center column while active). */\n html[data-dsh-taskboard-active] [data-mobile-nav=\"fab\"],\n html[data-dsh-ssh-active] [data-mobile-nav=\"fab\"],\n html[data-dsh-taskboard-active] [data-mobile-nav=\"backdrop\"],\n html[data-dsh-ssh-active] [data-mobile-nav=\"backdrop\"] {\n display: none !important;\n }\n /* Board header: let the search field take the slack instead of squeezing\n the action buttons. */\n [data-dsh-taskboard-board] > [class$=\"_boardHeader\"] [class$=\"_search\"] {\n flex: 1 1 auto !important;\n min-width: 80px !important;\n }\n\n /* ---------- dsh-web-ui polish: plugin market search ----------\n The market tab row (Discover / Themes / Installed + the plugin search\n box) is a no-wrap flex: at 390px the tabs plus the ~218px search box\n (~475px total) overflow the ~334px sheet and the search box runs off\n the right edge of the screen (it also forces a horizontal scrollbar on\n the sheet's options area). Let the row wrap: the tabs keep the first\n line and the search box gets its own full-width second line. */\n\n [aria-modal=\"true\"] [class$=\"_tabs\"] {\n flex-wrap: wrap !important;\n row-gap: 8px !important;\n }\n [aria-modal=\"true\"] [class$=\"_searchInline\"] {\n flex: 1 1 100% !important;\n width: 100% !important;\n max-width: 100% !important;\n }\n\n /* ---------- dsh-usage-stats polish: usage & balance panel ----------\n The panel's stats row shows three token counters side by side\n (today / month / total). The counters use tabular nowrap figures whose\n min-content width overflows the ~336px panel body on a phone: figures\n clip at the row's edges and the panel grows a horizontal scrollbar.\n Stack the three counters vertically \u2014 full-width rows, so the figures\n always fit. */\n\n [class*=\"usg_\"][class$=\"_statsRow\"] {\n flex-direction: column !important;\n }\n [class*=\"usg_\"][class$=\"_stat\"] {\n flex: 0 0 auto !important;\n width: 100% !important;\n min-width: 0 !important;\n }\n\n /* ---------- dsh-web-ui polish: settings sheet ----------\n The official dialog is a desktop two-column form; on a phone the\n label/control split leaves a huge dead gap and long descriptions wrap\n into tall stacks. Stack each row (text above, control full-width) and\n keep the nav tabs on ONE horizontally scrolling row. */\n\n /* Nav tabs: single scrolling row instead of the 3-per-row grid \u2014 seven\n categories wrap into three rows on a phone (~130px of sheet height);\n one row with a thin scrollbar keeps every tab reachable and returns\n that space to the options area (user feedback 2026-08-16). An earlier\n one-row attempt had no scroll affordance and silently cut the last\n tab off; the thin scrollbar IS the affordance. Scoped to the frame\n marker: the desktop dialog keeps its official vertical nav column. */\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) > :first-child [class$=\"_navList\"] {\n display: flex !important;\n flex-wrap: nowrap !important;\n overflow-x: auto !important;\n overflow-y: hidden !important;\n gap: 6px !important;\n width: 100% !important;\n scrollbar-width: thin !important;\n -webkit-overflow-scrolling: touch !important;\n }\n /* Hairline scrollbar for the tab row: the default WebKit scrollbar reads\n fat on a phone; 2px keeps the scroll affordance without the bulk. */\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_navList\"]::-webkit-scrollbar {\n height: 2px !important;\n }\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_navList\"]::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-border-l2, rgba(0, 0, 0, .22)) !important;\n border-radius: 1px !important;\n }\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_navList\"]::-webkit-scrollbar-track {\n background: transparent !important;\n }\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_navCell\"] {\n flex: 0 0 auto !important;\n white-space: nowrap !important;\n padding: 6px 8px !important;\n gap: 6px !important;\n font-size: 13px !important;\n justify-content: flex-start !important;\n }\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_navCell\"] svg {\n width: 14px !important;\n height: 14px !important;\n flex: none !important;\n }\n /* Content toolbar: the \"Open configuration file\" button is hidden on\n mobile \u2014 it is rarely needed on a phone and steals ~180px from the\n tab row's scroll area (user feedback 2026-08-16). Only the close \u2715\n stays, flush right in the nav row. Desktop untouched (frame scoped). */\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_header\"] [class$=\"_actions\"] {\n display: none !important;\n }\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_header\"] [class$=\"_actions\"] [class$=\"_action\"] {\n font-size: 13px !important;\n padding: 6px 12px !important;\n min-height: 0 !important;\n }\n /* Setting rows: text on top, control below at full width. */\n [aria-modal=\"true\"] [class$=\"_section\"] [class$=\"_row\"] {\n flex-direction: column !important;\n align-items: stretch !important;\n gap: 8px !important;\n }\n [aria-modal=\"true\"] [class$=\"_section\"] [class$=\"_row\"] > :first-child {\n width: 100% !important;\n max-width: none !important;\n }\n [aria-modal=\"true\"] [class$=\"_section\"] [class$=\"_row\"] > :last-child {\n width: 100% !important;\n max-width: none !important;\n }\n /* Appearance mode group: give the cube row a consistent bordered\n segmented look (the official borders differ per state). */\n [aria-modal=\"true\"] [class$=\"_cubeRow\"] > * {\n border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12)) !important;\n }\n\n /* ---------- dsh-web-ui polish: explorer sheet ----------\n The aionui explorer was designed for a desktop side column: compact the\n header, search box and tree rows so a phone shows more entries, and pad\n the scroll bottom so the last row never sits flush on the edge. */\n\n [data-aionui-explorer-col] [class$=\"_tabBar\"] {\n height: 36px !important;\n }\n [data-aionui-explorer-col] [class$=\"_tabBtn\"],\n [data-aionui-explorer-col] [class$=\"_tabBtnActive\"] {\n padding: 0 12px !important;\n font-size: 13px !important;\n }\n [data-aionui-explorer-col] [class$=\"_searchBox\"] {\n height: 32px !important;\n font-size: 13px !important;\n }\n [data-aionui-explorer-col] [class*=\"_treeRow\"] {\n height: 30px !important;\n font-size: 13px !important;\n }\n [data-aionui-explorer-col] [class*=\"_treeRow\"] svg {\n width: 14px !important;\n height: 14px !important;\n }\n [data-aionui-explorer-col] [class$=\"_scrollArea\"] {\n padding-bottom: 28px !important;\n }\n\n /* ---------- dsh-web-ui polish: drawer footer ----------\n The injected footer actions (Files + Session log) become two equal pill\n buttons instead of text-width capsules. */\n\n /* The official footerActions row also hosts the remote-web-ui entry\n row (two icon buttons); without wrapping the two groups squeeze each\n other on one line. Wrap so each group gets its own full-width row. */\n [data-mobile-nav=\"frame\"] [class$=\"_footerActions\"] {\n flex-wrap: wrap !important;\n gap: 6px !important;\n }\n [data-mobile-nav=\"drawer-actions\"] {\n width: 100% !important;\n }\n [data-mobile-nav=\"drawer-actions\"] > button {\n flex: 1 1 0 !important;\n padding: 0 8px !important;\n white-space: nowrap !important;\n }\n\n /* ---------- dsh-web-ui polish: floating pet ----------\n The whale-girl pet (dsh-pet) floats at the viewport corner with a\n persisted, draggable position. On phones the pet is scaled down so\n it does not dominate the screen; the plugin's own drag + persist\n still work (the position itself is left alone \u2014 the mobile default\n position is seeded via the pet API to just above the composer). */\n\n body > [class$=\"_float\"]:has([class$=\"_sprite\"][role=\"button\"]) {\n transform: scale(.66);\n transform-origin: bottom right;\n }\n /* While a modal dialog (settings sheet / export) owns the screen the pet\n floats ABOVE it and covers the dialog content; modal semantics say the\n background is inert, so hide the pet for the modal's lifetime. */\n body:has([aria-modal=\"true\"]) > [class$=\"_float\"]:has([class$=\"_sprite\"][role=\"button\"]) {\n display: none !important;\n }\n\n /* ---------- conversation stats line ----------\n The official session-status row (turns / steps / LLM time / TTFT /\n cache) is long. It is the single entry the official StatsLine puts in\n conversation.composer.dock, so the structural anchor below reaches it\n without any DOM marking (S3 deleted the text-matching effect that used\n to set [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"]). Layout: ONE fixed-height (28px) flex\n strip that scrolls horizontally \u2014 the full metrics stream stays\n reachable by swiping, the row never grows vertically, no ellipsis or\n fade, 12px gaps between metric groups, a 2px scrollbar as the swipe\n affordance. The phone breakpoint hides the strip outright instead\n (styles/composer.css.ts \u2014 its data moves into the session info card). */\n\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"] {\n display: flex !important;\n flex-flow: row nowrap !important;\n align-items: center !important;\n width: 100% !important;\n max-width: 100% !important;\n min-width: 0 !important;\n height: 28px !important;\n min-height: 28px !important;\n max-height: 28px !important;\n box-sizing: border-box !important;\n white-space: nowrap !important;\n overflow-x: auto !important;\n overflow-y: hidden !important;\n -webkit-overflow-scrolling: touch;\n overscroll-behavior-x: contain;\n scrollbar-width: thin !important;\n scrollbar-color: var(--dsw-alias-border-l1, rgba(0, 0, 0, .28)) transparent !important;\n padding: 0 0 4px !important;\n line-height: 20px !important;\n font-size: 12px !important;\n }\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"]::-webkit-scrollbar {\n height: 2px !important;\n }\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"]::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .3)) !important;\n border-radius: 2px !important;\n }\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"]::-webkit-scrollbar-track {\n background: transparent !important;\n }\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"] > * {\n display: flex !important;\n flex: 0 0 auto !important;\n flex-flow: row nowrap !important;\n align-items: center !important;\n width: max-content !important;\n min-width: max-content !important;\n max-width: none !important;\n white-space: nowrap !important;\n margin-right: 12px !important;\n padding: 0 !important;\n }\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"] > *:last-child {\n margin-right: 0 !important;\n }\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"] * {\n white-space: nowrap !important;\n }\n\n /* ---------- dsh-genui panel dock ----------\n The genui panel docks above the composer (conversation.input.dock,\n id genui-panel). On a phone its business-blue outline, generous chrome\n and single-line ellipsis read as an unfinished artifact: long titles\n truncate mid-word (\"\u2026default b\u00B7\u00B7\u00B7\") with the chevron glued to the\n ellipsis, and the pill crowds the composer. Mobile treatment: neutral\n card border matching the composer, tighter chrome so the full title\n fits, chevron with breathing room. Scoped to the mobile frame marker \u2014\n desktop keeps genui's own styling untouched. */\n\n [data-mobile-nav=\"frame\"] [data-genui-panel] {\n margin: 6px 12px 4px !important;\n border-color: var(--dsw-alias-border-l1, rgba(0, 0, 0, .12)) !important;\n border-radius: 12px !important;\n }\n [data-mobile-nav=\"frame\"] [data-genui-panel] [class*=\"_panelToggle\"] {\n padding: 7px 12px !important;\n gap: 8px !important;\n }\n [data-mobile-nav=\"frame\"] [data-genui-panel] [class*=\"_panelBadge\"] {\n padding: 0 7px !important;\n border-radius: 5px !important;\n font-size: 10.5px !important;\n line-height: 1.7 !important;\n }\n [data-mobile-nav=\"frame\"] [data-genui-panel] [class*=\"_panelTitle\"] {\n flex: 1 1 auto !important;\n min-width: 0 !important;\n font-size: 12.5px !important;\n line-height: 1.45 !important;\n }\n [data-mobile-nav=\"frame\"] [data-genui-panel] [class*=\"_panelChevron\"] {\n flex: none !important;\n margin-left: 0 !important;\n padding-left: 4px !important;\n }\n\n /* ---------- git-graph branch chip: inside the composer card ----------\n The branch chip (conversation.input.dock) floats between the dock rows\n and the input card; on a phone it reads as a stray capsule crowding the\n composer. A client effect (MobileNavOverlay) reparents the chip INTO\n the composer card; these rules pin it to the card's top-left and give\n the card a dedicated chip row. The card is position: relative by the\n official stylesheet, so the absolute anchor resolves against it. The\n plugin's own sheet sets all four offsets on the anchor, so right/bottom\n must be neutralized too. Scope is the frame marker + the anchor\n attribute (NOT the dock slot \u2014 the reparenting moves the chip out of\n the dock's subtree). Desktop untouched: the frame marker only exists\n below 1024px, and the effect restores the chip to the dock when the\n viewport widens. Chip row geometry (2026-08-16, user feedback): 48px\n padding left a 16px dead gap between the chip and the input line and\n made the composer read too tall; the row is now 40px = chip (24px) at\n top 12px + ~4px to the textarea \u2014 the chip sits slightly lower and\n the gap is compressed without touching the official height budget\n further. */\n\n [data-mobile-nav=\"frame\"] [data-gitgraph-chip-anchor] {\n position: absolute !important;\n top: 12px !important;\n left: 12px !important;\n right: auto !important;\n bottom: auto !important;\n z-index: 1 !important;\n }\n [data-mobile-nav=\"frame\"] [class$=\"_card\"]:has([data-gitgraph-chip-anchor]) {\n padding-top: 40px !important;\n }\n\n /* ---------- dsh-better-sidebar: safe area (S2.1, 2026-08-17) ----------\n THIRD-PARTY COMPAT RULE \u2014 dsh-better-sidebar (the workbench the session\n header's panel button opens). Its shell is viewport-fixed and starts at\n y=0: the panel at inset 0 (100vw drawer below 768px, a right column\n above it) and the toggle cluster at top:3px. Neither knows about\n env(safe-area-inset-*), so on a notched iPhone the whole tab strip \u2014\n including the one button that CLOSES the panel \u2014 sits behind the status\n bar and cannot be tapped: the user opened the workbench and was stuck\n there (real-device report, 2026-08-17).\n Applied across the plugin's whole mobile band, not just <768px: the\n panel is fixed at top:0 in the 768-1023px range too, so the same notch\n covers the same tab strip. Zero effect wherever the inset is 0 (every\n desktop browser, every non-notched device) \u2014 desktop is >=1024px and\n out of this media block entirely.\n Anchors: the plugin's own mount marker [data-dsh-better-sidebar]\n (index.tsx) plus class-suffix selectors, per this repo's hashed-class\n convention. --dsh-title-bar-strip is the plugin's own title-bar-compat\n offset (set only while that mode is on, 0px fallback otherwise): adding\n to it keeps both offsets rather than clobbering theirs.\n !important because their :global(body[...]) rules outrank a plain\n attribute selector.\n No box-sizing here on purpose: the panel is position:fixed with BOTH\n top and bottom set, so its used height already resolves to\n \"containing block - insets - padding - border\" (CSS 2.1 10.6.4) and the\n padding shrinks the content box without any help. Forcing border-box\n also folds their 1px left border into the inline width \u2014 measured as a\n 1px panel-width change at 768px, i.e. a regression outside this\n hotfix's remit. */\n [data-dsh-better-sidebar] [class$=\"_panel\"],\n [data-dsh-better-sidebar] [class$=\"_panelHidden\"] {\n padding-top: calc(var(--mnav-sat) + var(--dsh-title-bar-strip, 0px)) !important;\n }\n /* Only the tablet/desktop range still shows the cluster (see the hide\n rule below) \u2014 the notch offset is now dead weight below 768px, so it\n is scoped out rather than left applying invisibly. min-width:768px\n rather than the more common max-width pairing: this offset was\n already a harmless no-op at >=1024px before S3.1 (--mnav-sat is 0 on\n every desktop browser), so narrowing its floor to 768px changes\n nothing there either \u2014 it only stops evaluating on phone. */\n @media (min-width: 768px) {\n [data-dsh-better-sidebar] [class$=\"_toggleCluster\"] {\n top: calc(var(--mnav-sat) + var(--dsh-title-bar-strip, 0px) + 3px) !important;\n }\n }\n\n /* ---------- dsh-better-sidebar: hide the phone toggle cluster (S3.1, 2026-08-17) ----------\n Real-device round 2 feedback: this fixed top-right cluster duplicates\n \u2014 and visually overlaps \u2014 the session header's own workbench button\n (MobileSessionHeader.tsx), which already opens/closes the same panel\n by clicking this cluster's toggle button through. Hidden below 768px\n only; the 768-1023px tablet range has no workbench button (header.css.ts\n scopes that entire reflow to <768px) and still depends on this cluster\n as its only entry point, so it stays exactly as v1.0.0/S2.1 shipped it\n there. The cluster also holds the panel's own close affordance \u2014 see\n [data-mobile-nav=\"better-sidebar-close\"] below for the phone\n replacement, wired up in MobileSessionHeader.tsx. */\n @media (max-width: 767px) {\n [data-dsh-better-sidebar] [class$=\"_toggleCluster\"] {\n display: none !important;\n }\n }\n\n /* ---------- dsh-better-sidebar: phone close button (S3.1 follow-up, 2026-08-17) ----------\n Appended to document.body by MobileSessionHeader.tsx's\n MobileHeaderUtilities effect \u2014 never inside the panel's own subtree\n (the third party's React re-renders would wipe it) and never under any\n transformed/backdrop-filter ancestor (the S4 info-card WebKit lesson in\n AGENTS.md: position:fixed re-anchors to the nearest such ancestor\n instead of the viewport). Default hidden \u2014 belt-and-braces, same\n reasoning as header.css.ts's [data-mobile-nav=\"header-info\"] etc list:\n React does not know about media queries. Shown only below 768px AND\n only while the panel is actually open: the panel's own class name ends\n in \"_panel\" exclusively in the open state (the \"_panelHidden\" suffix is\n appended once closed, so the string no longer ends in \"_panel\") \u2014 a\n pure-CSS :has() open/closed read, no MutationObserver required.\n\n Bottom-center pill, not a top-right circle (real-device follow-up,\n 2026-08-17): a top-right position collided with the panel's own\n per-tab toolbar controls \u2014 measured live at 390px with the explorer\n tab open, the panel's Refresh button sits at x:354-382 y:93-121, and a\n 44px circle at top:8px+safe-area/right:8px lands at x:338-382\n y:(safe-area+8)-(safe-area+52), a direct overlap once the safe-area\n offset is small (or zero on non-notched phones). Every per-tab toolbar\n (explorer/git/tabBar) lives at the panel's TOP; nothing in the\n default explorer or git tabs reaches the bottom 90px of the viewport\n (checked live, both tabs, 2026-08-17), so bottom-center is clear\n regardless of which tab is open \u2014 one fixed position that does not\n need per-tab-type coordinates to dodge. */\n /* Default-hidden for this pill lives in header.css.ts, NOT here: this\n whole file sits inside the shared (max-width: 1023px) block, so a rule\n here can never hide anything at desktop widths (learned the hard way,\n 2026-08-17 desktop leak). */\n @media (max-width: 767px) {\n body:has([data-dsh-better-sidebar] [class$=\"_panel\"]) [data-mobile-nav=\"better-sidebar-close\"] {\n display: flex !important;\n position: fixed;\n left: 50%;\n bottom: calc(var(--mnav-sab) + 12px);\n transform: translateX(-50%);\n z-index: 70;\n align-items: center;\n gap: 6px;\n height: 44px;\n padding: 0 18px;\n border: none;\n border-radius: 999px;\n background: var(--dsw-alias-bg-base, #fff);\n box-shadow: 0 4px 16px rgba(0, 0, 0, .2);\n color: var(--dsw-alias-label-primary, inherit);\n font-family: inherit;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n body:has([data-dsh-better-sidebar] [class$=\"_panel\"]) [data-mobile-nav=\"better-sidebar-close\"] svg {\n width: 14px;\n height: 14px;\n flex: none;\n }\n body:has([data-dsh-better-sidebar] [class$=\"_panel\"]) [data-mobile-nav=\"better-sidebar-close\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n }\n";
|
|
2
|
+
//# sourceMappingURL=compat.css.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compat.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/compat.css.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,UAAU,u//BAqqBtB,CAAA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const COMPOSER_CSS = "/* ---------- phone composer (< 768px) ---------- */\n\n/* The file picker the attachment button drives (S7) \u2014 hidden at EVERY width,\n deliberately OUTSIDE the phone media block below. A bare <input type=\"file\">\n renders as a native \"Choose Files\" control, and the slot wrapper around it is\n `display: contents`, so an un-hidden one becomes a flex item of the official\n tool row. Scoping this to < 768px once put that control in the DESKTOP\n composer (caught on a live deploy, 2026-08-17): the button that drives it is\n phone-only, but the input it drives is in the DOM at all widths. */\n[data-mobile-nav=\"attach-picker\"] {\n display: none !important;\n}\n\n@media (max-width: 767px) {\n /* --- 1. flatten the two official groups ---\n `_tools` and `_trailing` only exist to cluster controls left/right.\n display:contents dissolves them into `_row`, so the six leaf controls\n become siblings in one flex line and `order` alone drives the layout:\n [attach \u00B7 + \u00B7 permission \u00B7 model] \u2026elastic gap\u2026 [context ring \u00B7 send]. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] {\n justify-content: flex-start !important;\n gap: 5px !important;\n padding: 2px 8px 8px !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] {\n display: contents !important;\n }\n\n /* --- 2. the running order ---\n The attachment button (S3 placeholder, S7 wires it up) sits leftmost;\n the official \"+\" command menu keeps its seat right next to it, where\n the two \"insert something\" affordances read as one cluster. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] [data-mobile-nav=\"attach\"] {\n order: 1 !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_add\"] {\n order: 2 !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] {\n order: 3 !important;\n flex: 0 1 auto !important;\n min-width: 0 !important;\n gap: 6px !important;\n }\n /* The model seat carries the elastic gap: everything after it is pushed to\n the right edge, so no spacer element is needed. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] {\n order: 4 !important;\n flex: 0 1 auto !important;\n min-width: 0 !important;\n margin-right: auto !important;\n }\n /* Third-party input.right entries park next to the ring rather than\n landing at order 0 (= far left) once the groups are flattened. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.right\"] > * {\n order: 5 !important;\n }\n /* ContextMeter \u2014 the only `span` ending in _root inside the row. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > span[class$=\"_root\"] {\n order: 6 !important;\n flex: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_primary\"] {\n order: 7 !important;\n }\n\n /* --- 3. permission + model as icon-only pills (real-device round 2, 2026-08-17) ---\n S3's icon-and-label capsules read as noise on an actual phone \u2014 there\n is no room to usefully show a preset name or a model id, so the label\n text is now hidden outright and both triggers collapse to a plain\n ~44x30 icon button. This AGREES with (rather than fights) the official\n container query (`@container (width <= 460px) { .trigger:has(.triggerIcon)\n .triggerLabel { display: none } }`) that S3 had to override \u2014 no need\n to override it back. Accessible name is unaffected: both official\n triggers already ship a descriptive aria-label independent of the\n visible text (PermissionSelect: t('input.accessMode', {name}); the\n model trigger: t('trigger.aria'/'trigger.ariaEffort')) \u2014 verified live\n in dsh-client-ui-conversation / dsh-client-ui-model-selection lib/\n client.js, 2026-08-17 \u2014 so hiding the label costs nothing for screen\n readers. The permission trigger only renders an icon for the\n \"read-only\" / \"workspace-write\" presets (permissionGlyphs in\n dsh-client-ui-conversation) \u2014 \"Full access\" and any host-configured\n preset name fall back to chevron-only, a known gap in the official\n markup this plugin cannot fill without inventing new icon meaning.\n The model trigger never renders an icon at all (only label + optional\n effort text + chevron), so its ::before below draws one from a\n primitives icon path (Sparkle \u2014 the closest existing \"model\" glyph) as\n a CSS-only pseudo-element: it survives React re-renders for free\n (unlike a DOM-injected node, which would need a MutationObserver, see\n the preview-full-toggle pitfall in AGENTS.md) and does not touch\n accessible-name computation (empty generated content). S3's\n rtl-ellipsis trick on the model label is simply inert under\n display:none now; left alone rather than unpicked. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [class$=\"_triggerLabel\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] > [class$=\"_triggerLabel\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] > [class$=\"_triggerEffort\"] {\n display: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] button[class$=\"_trigger\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] {\n background: var(--dsw-specific-selector, rgba(127, 127, 127, .12)) !important;\n width: 44px !important;\n height: 30px !important;\n max-width: none !important;\n min-width: 0 !important;\n padding: 0 !important;\n gap: 2px !important;\n justify-content: center !important;\n border-radius: 999px !important;\n touch-action: manipulation !important;\n }\n /* PermissionSelect wraps its trigger in the Menu primitive's root span,\n which must shrink to the icon button's fixed width. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] > span:has(> button[class$=\"_trigger\"]) {\n flex: 0 0 auto !important;\n min-width: 0 !important;\n }\n /* ic_ds_sparkle_16 (@deepseek-ai/dsh-client-ui-primitives IconSparkle16\n path, copied verbatim) as a mask so it inherits currentColor like every\n other icon in the row \u2014 the model trigger has no official icon slot to\n hook into. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"]::before {\n content: '';\n width: 16px;\n height: 16px;\n flex: none;\n background: currentColor;\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6.1 3.1Q6.6 7.8 11.3 8.3Q6.6 8.8 6.1 13.5Q5.6 8.8 0.9 8.3Q5.6 7.8 6.1 3.1Z'/%3E%3Cpath d='M11.9 1Q12.2 3.7 14.9 4Q12.2 4.3 11.9 7Q11.6 4.3 8.9 4Q11.6 3.7 11.9 1Z'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6.1 3.1Q6.6 7.8 11.3 8.3Q6.6 8.8 6.1 13.5Q5.6 8.8 0.9 8.3Q5.6 7.8 6.1 3.1Z'/%3E%3Cpath d='M11.9 1Q12.2 3.7 14.9 4Q12.2 4.3 11.9 7Q11.6 4.3 8.9 4Q11.6 3.7 11.9 1Z'/%3E%3C/svg%3E\");\n -webkit-mask-size: contain;\n mask-size: contain;\n -webkit-mask-repeat: no-repeat;\n mask-repeat: no-repeat;\n }\n\n /* --- 4. both menus become bottom sheets ---\n The permission menu is the Menu primitive (role=menu, absolute, side=top)\n and the model menu is ModelSelect's own `_menu` (absolute, bottom+right).\n Neither has a transformed ancestor between it and the viewport, so\n position:fixed re-anchors both to the screen edge. Only the shell moves \u2014\n the items, the two-level model panes and every selection handler stay\n official. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"] {\n position: fixed !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n top: auto !important;\n transform: none !important;\n width: 100% !important;\n min-width: 0 !important;\n max-width: none !important;\n max-height: min(70dvh, 520px) !important;\n box-sizing: border-box !important;\n border-radius: 16px 16px 0 0 !important;\n border-bottom: none !important;\n padding: 8px 8px calc(8px + var(--mnav-sab)) !important;\n z-index: 60 !important;\n box-shadow: 0 -8px 32px rgba(0, 0, 0, .18) !important;\n }\n /* 44pt+ rows in both sheets (Menu items, model options, and the model\n sheet's two root cells that drill into the model / effort panes). */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"] [role=\"menuitem\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"] [class$=\"_option\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"] [class$=\"_cell\"] {\n min-height: 48px !important;\n border-radius: 12px !important;\n font-size: 15px !important;\n }\n /* --- 4b. the official scroll-to-bottom button must not poke through the\n sheet (real-device follow-up, 2026-08-17) ---\n ChatView's own \"jump to latest\" button (aria-label t(\"chat.toBottom\"))\n is `position: sticky; z-index: 8` inside the message column, not\n `position: fixed` \u2014 it never escapes to the same top-level stacking\n context our sheets get promoted to, so raising the sheet's z-index\n further does nothing (measured live: it still rendered on top at\n z-index 60 vs 8). Rather than chase engine-specific stacking-context\n semantics (Chromium and WebKit do not always agree here \u2014 see AGENTS.md\n CDP/document.hidden lesson for another instance of that), this hides\n the button outright while either sheet is open and lets it reappear on\n close: a plain `display: none` behind a live `:has()` read is correct\n regardless of which stacking rules the engine happens to apply.\n Selector: no hashed classes (dsh-client-ui-conversation lib/client.js,\n verified 2026-08-17) \u2014 `data-chat-flow` is the one stable attribute on\n the message column, and the button's wrapper is its only sibling\n (ChatView only ever renders the column and, conditionally, this one\n slot), so the adjacent-sibling combinator pins it precisely. */\n body:has([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"]) [data-chat-flow] + div,\n body:has([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"]) [data-chat-flow] + div {\n display: none !important;\n }\n /* --- 5. input box: two lines minimum, five lines maximum ---\n The official autosizer drives the box off the hidden mirror's height, so\n a min-height on the mirror IS the min-height of the field; the scroll cap\n rides the official custom property. 52px = 2 x 24px line box + 4px pad,\n 124px = 5 lines. The hero card keeps its own one-line collapse (misc.css\n pins _scroll/_grow while the placeholder shows). */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] [class$=\"_mirror\"] {\n min-height: 52px !important;\n }\n [class$=\"_composerSeat\"] {\n --dsh-composer-text-max-height: 124px;\n }\n\n /* --- 6. no divider above OR below the message list ---\n Instead of a rule the messages butt against, the message scroller\n fades out over its last 26px (S3, no divider above the composer) AND\n fades in over its first 20px (S3.1 real-device round 2: the header's\n own bottom line is removed too, styles/header.css.ts's\n `header::after` rule). The mask lives on the scroll body (NOT on the\n header or the composer): a mask clips everything it paints, and both\n surfaces host position:fixed children (composer's permission/model\n sheets, any future header overlay) that must not be clipped.\n `mask-image` can only be declared once per element, so both fades are\n ONE linear-gradient rather than two separate declarations (the second\n would silently replace the first) \u2014 this is the merge of what used to\n be S3's bottom-only mask. */\n [class$=\"_scrollBody\"] {\n -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 20px, #000 calc(100% - 26px), transparent 100%);\n mask-image: linear-gradient(to bottom, transparent 0, #000 20px, #000 calc(100% - 26px), transparent 100%);\n }\n [class$=\"_composerSeat\"],\n [class$=\"_composerStack\"] {\n border-top: none !important;\n }\n\n /* --- 7. dock entries become mini chips above the input card ---\n conversation.input.dock is display:contents (inline style), so its\n entries are stacked rows of the composer column. Forcing the slot itself\n to flex turns them into ONE scrollable chip line outside and above the\n card. Written against the slot, not against any particular plugin's chip\n (the git branch chip and the todo panel are third-party and may not be\n installed at all). */\n [data-slot=\"conversation.input.dock\"] {\n display: flex !important;\n flex-flow: row nowrap !important;\n align-items: center !important;\n gap: 6px !important;\n min-width: 0 !important;\n margin: 0 16px !important;\n overflow-x: auto !important;\n overflow-y: hidden !important;\n scrollbar-width: none !important;\n }\n [data-slot=\"conversation.input.dock\"]::-webkit-scrollbar {\n display: none !important;\n }\n [data-slot=\"conversation.input.dock\"] > * {\n flex: 0 0 auto !important;\n max-width: 70% !important;\n min-height: 26px !important;\n max-height: 26px !important;\n border-radius: 999px !important;\n font-size: 11.5px !important;\n line-height: 18px !important;\n overflow: hidden !important;\n }\n /* The git-graph chip carries a 34px tablet target from misc.css; that\n selector is more specific, so restate it at the phone breakpoint. */\n [data-slot=\"conversation.input.dock\"] [data-gitgraph-chip-anchor] [data-gitgraph-chip] {\n min-height: 26px !important;\n padding: 0 10px !important;\n font-size: 11.5px !important;\n }\n\n /* --- 7b. attachment chips (S7.1) ---\n Our own dock entry opts OUT of the 26px pill cage above: an image chip is\n a 48px tile. The row sits tight under the card's top edge, so the chips\n read as part of the composer rather than as a floating strip. */\n [data-slot=\"conversation.input.dock\"] > [data-mobile-nav=\"attach-chips\"] {\n display: flex !important;\n align-items: center !important;\n gap: 6px !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n border-radius: 0 !important;\n overflow: visible !important;\n margin-bottom: -2px;\n }\n [data-mobile-nav=\"attach-chip\"] {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 auto;\n max-width: 62vw;\n background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12));\n color: var(--dsw-alias-label-primary, inherit);\n }\n [data-mobile-nav=\"attach-chip\"][data-kind=\"file\"] {\n height: 26px;\n padding: 0 4px 0 8px;\n border-radius: 999px;\n font-size: 11.5px;\n line-height: 18px;\n }\n /* An image chip is the thumbnail: no name line, the filename lives in the\n title attribute (and in the draft text right below). */\n [data-mobile-nav=\"attach-chip\"][data-kind=\"image\"] {\n width: 48px;\n height: 48px;\n padding: 0;\n border-radius: 10px;\n overflow: hidden;\n }\n [data-mobile-nav=\"attach-chip-art\"] {\n position: relative;\n display: grid;\n place-items: center;\n flex: none;\n overflow: hidden;\n }\n [data-kind=\"file\"] > [data-mobile-nav=\"attach-chip-art\"] {\n width: 16px;\n height: 16px;\n opacity: .7;\n }\n [data-kind=\"image\"] > [data-mobile-nav=\"attach-chip-art\"] {\n width: 100%;\n height: 100%;\n }\n /* The <img> is stacked over the paperclip, not swapped for it: a format the\n engine cannot decode (HEIC outside WebKit) simply paints nothing and the\n icon shows through \u2014 no onError state to carry. */\n [data-mobile-nav=\"attach-chip-art\"] img {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n background: var(--dsw-alias-bg-layer-2, rgba(127, 127, 127, .18));\n }\n [data-mobile-nav=\"attach-chip-name\"] {\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n }\n [data-mobile-nav=\"attach-chip-remove\"] {\n display: grid;\n place-items: center;\n flex: none;\n width: 18px;\n height: 18px;\n padding: 0;\n border: none;\n border-radius: 999px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n touch-action: manipulation;\n }\n /* On a tile the \u00D7 floats in the corner over the picture, so it needs its own\n ground to stay legible against an arbitrary photo. */\n [data-kind=\"image\"] > [data-mobile-nav=\"attach-chip-remove\"] {\n position: absolute;\n top: 2px;\n right: 2px;\n background: rgba(0, 0, 0, .55);\n color: #fff;\n }\n [data-mobile-nav=\"attach-chip-remove\"]:active {\n transform: scale(.9);\n }\n\n /* --- 8. the official stats strip leaves the composer ---\n Its data moves into the session info card (S4). The row is the composer\n dock's own `_root` entry; the slot itself stays live for later entries. */\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"] {\n display: none !important;\n }\n\n /* --- 9. the attachment button (S7; its file input is hidden at the top of\n this file, at every width) --- */\n [data-mobile-nav=\"attach\"] {\n position: relative;\n width: 28px !important;\n height: 28px !important;\n flex: none !important;\n display: grid !important;\n place-items: center !important;\n padding: 0 !important;\n border: none !important;\n border-radius: 999px !important;\n background: var(--dsw-specific-selector, rgba(127, 127, 127, .12));\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n }\n [data-mobile-nav=\"attach\"]:active {\n transform: scale(.94);\n transition: transform .12s;\n }\n /* Busy: a ring sweeps around the paperclip. Drawn with a conic gradient in\n a pseudo-element rather than a spinner node, so the official React tree\n around us has nothing extra to re-render (same reasoning as the model\n pill's mask icon, S3.1). */\n [data-mobile-nav=\"attach\"][data-busy]::after {\n content: \"\";\n position: absolute;\n inset: -2px;\n border-radius: 999px;\n background: conic-gradient(from 0deg, transparent 0 65%, currentColor 100%);\n mask: radial-gradient(closest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));\n -webkit-mask: radial-gradient(closest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));\n animation: mnav-attach-spin .9s linear infinite;\n pointer-events: none;\n }\n @keyframes mnav-attach-spin {\n to { transform: rotate(1turn); }\n }\n @media (prefers-reduced-motion: reduce) {\n [data-mobile-nav=\"attach\"][data-busy]::after {\n animation-duration: 3s;\n }\n }\n /* Failure note: one line above the button, tap-to-dismiss. Sits on the\n composer card (z 2) rather than in an overlay layer \u2014 nothing here needs\n to clear the permission/model sheets. */\n [data-mobile-nav=\"attach-error\"] {\n position: absolute;\n bottom: calc(100% + 6px);\n left: 0;\n z-index: 2;\n max-width: 62vw;\n padding: 5px 8px;\n border-radius: 8px;\n font-size: 12px;\n line-height: 1.3;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, .82));\n color: var(--dsw-alias-label-primary, #fff);\n box-shadow: 0 2px 8px rgba(0, 0, 0, .24);\n }\n\n /* --- 9. home-indicator clearance (S4.1, 2026-08-17) ---\n Owned HERE, not by the gateway half. That plugin used to carry\n [data-slot=\"conversation.composer\"] { padding-bottom: max(env(safe-area-inset-bottom), 8px) }\n in both pwa/app.css and the gateway's inline DEVICE_CSS. Both were\n INERT and had always been: that slot element is `display: contents`\n (measured 2026-08-17 \u2014 the slot wrapper generates no box, so padding on\n it is discarded), which is why raising it never moved anything. The rule\n is deleted on the PWA side and restated here on an element that actually\n lays out, reading --mnav-sab so ?mobile-nav-inset=54,34 can regress it\n off-device (env() is hard 0 on desktop \u2014 the whole reason S2.1 exists).\n\n Target: the card should look EQUALLY inset on all four sides. This same\n element carries the card's side inset (`padding: 0 16px` officially,\n measured 16px left and 16px right at 390px), so the bottom gap is simply\n capped at that same 16px. Clearing the FULL 34px inset \u2014 the naive\n max(sab, 8px) \u2014 is what read as \"too thick\": it is more than double the\n side margin, so the card looked shoved up off the bottom edge.\n\n clamp() says all three requirements at once:\n - floor 8px -> a device with no home indicator (sab: 0) keeps the\n official 8px exactly, so this is a strict no-op there;\n - track sab -> a shallower inset than 16px is honoured as-is;\n - cap 16px -> a full-size indicator (sab: 34) lands on 16px, equal\n to the side inset, which is the look being asked for.\n Keep the 16px in step with the side padding above if that ever changes;\n that equality IS the spec here, not a coincidence. */\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] {\n padding-bottom: clamp(8px, var(--mnav-sab), 16px) !important;\n }\n}\n\n/* The attachment button and its preview row only exist for the phone shell.\n Both render at every width (the slots are not breakpoint-aware), so both\n need hiding here \u2014 the dock-row rules that shape the chips live in the\n < 768px block and would otherwise leave a bare, unstyled chip line in the\n desktop composer. */\n@media (min-width: 768px) {\n [data-mobile-nav=\"attach\"],\n [data-mobile-nav=\"attach-chips\"] {\n display: none !important;\n }\n}\n";
|
|
2
|
+
//# sourceMappingURL=composer.css.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composer.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/composer.css.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,YAAY,8/vBAiexB,CAAA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const HEADER_CSS = "/* ---------- session header five-piece reflow (< 768px) ---------- */\n\n/* New elements render unconditionally (React does not know about media\n queries); default them to hidden so 768px+ never sees them, then\n re-enable inside the phone block below. Belt-and-braces alongside the\n scoped rules \u2014 mirrors the existing [data-mobile-nav=\"*\"] desktop no-op\n list in misc.css.ts. */\n[data-mobile-nav=\"header-back\"],\n[data-mobile-nav=\"header-viewrow\"],\n[data-mobile-nav=\"header-info\"],\n[data-mobile-nav=\"header-workbench\"],\n/* The workbench close pill's default-hidden used to live in compat.css.ts \u2014\n but EVERYTHING in layout/compat/misc sits inside one shared\n (max-width: 1023px) block (opened at the top of layout, closed at the end\n of misc), so that \"top-level\" rule silently never applied at >=1024 and\n the pill rendered as an unstyled button on desktop (2026-08-17 report).\n This file is appended after the shared block closes, so here it is truly\n global; the <=767px :has() show rule in compat overrides it with\n !important. */\n[data-mobile-nav=\"better-sidebar-close\"] {\n display: none;\n}\n\n@media (max-width: 767px) {\n /* The Files action targeted the dsh-web-ui-all (aionui) explorer sheet \u2014\n a different, unrelated suite from the workbench entry this slice adds\n (dsh-better-sidebar). Hidden here only; 768-1023px keeps it exactly as\n v1.0.0 shipped it. */\n [data-mobile-nav=\"files\"] {\n display: none !important;\n }\n\n /* --- three-column header: [back 92px] [title, truly centered] [utilities 92px] --- */\n /* The bottom padding is the view-switch row's seat: that row is\n position:absolute (see the bottom of this file \u2014 it renders two levels\n deep inside headerActions and can never be a grid item of titleRow), so\n without a reserve it would hang over the message list. 28px = its\n height. Unconditional rather than header:has([data-mobile-nav=\n \"header-viewrow\"]) on purpose: :has() is silently dropped by pre-105\n WebViews (see AGENTS.md) and a 28px overlap on the first message is a\n worse failure than 28px of empty band in the (never observed in\n practice) single-view session. Total header chrome: 48 + 28 = 76px.\n Left/right 8px (S2.1 report leftover, fixed in S4): the official header\n had zero horizontal padding, so the utilities button cluster's right\n edge sat flush against the screen edge (x=390 at 390px width, no\n margin). The 92px grid columns absorb this fine \u2014 they're fixed track\n widths, only the center 1fr column shrinks by 16px. */\n [data-phase] header {\n position: relative;\n padding: 0 8px 28px !important;\n }\n /* No header bottom line \u2014 fade instead (real-device round 2, 2026-08-17).\n The official header draws its border as a `::after` 1px bar (the\n `border-bottom` on the header itself is transparent, just a layout\n reserve \u2014 dsh-client-ui-conversation lib/client.js \".wSkVaW_header{\n border-bottom:1px solid #0000}.wSkVaW_header:after{...height:1px;\n position:absolute;bottom:1px...}\", verified live 2026-08-17), so\n hiding the pseudo-element is enough; the header's own border-bottom\n never had a visible color to begin with. The message scroller now\n fades in from under the header instead (styles/composer.css.ts, \"no\n divider above OR below the message list\" \u2014 the two edges share one\n mask-image on [class$=\"_scrollBody\"]). */\n [data-phase] header::after {\n display: none !important;\n }\n /* grid-row: 1 on every item is load-bearing, not decoration (S2.1 fix for\n the \"\u6807\u9898\u88AB\u6324\u4E0B\u53BB\" report). The three items are placed with explicit\n grid-column but arrive in DOM order crumbs(2) \u2192 headerActions(1) \u2192\n headerUtilities(3): sparse auto-placement never moves its cursor\n backwards, so headerActions and headerUtilities were pushed onto an\n implicit SECOND row. Measured at 390px: row1 = crumbs 28px, row2 =\n back button 44px, titleRow = 72px with the title stuck at the top\n instead of centered. Pinning the row makes it one 48px band again. */\n [data-phase] header [class$=\"_titleRow\"] {\n position: relative;\n display: grid;\n grid-template-columns: 92px 1fr 92px;\n grid-template-rows: minmax(48px, auto);\n align-items: center;\n min-height: 48px;\n padding: 0 !important;\n }\n /* Flatten the official cluster so its two children (crumbs, headerActions)\n become direct grid items of titleRow instead of a nested flex box. */\n [data-phase] header [class$=\"_titleCluster\"] {\n display: contents;\n }\n /* margin-left: 0 cancels layout.css.ts's shared <=1023px\n \"margin-left: auto !important\" on this exact selector (it pushed the\n Files button to the row's right edge in the old non-grid header;\n inside our grid column it instead shoves the back button 48px right of\n the column's start \u2014 the auto margin absorbs the leftover space\n between the button's content width and the 92px column). */\n [data-phase] header [class$=\"_headerActions\"] {\n grid-column: 1;\n grid-row: 1;\n justify-self: start;\n margin-left: 0 !important;\n display: flex;\n align-items: center;\n min-width: 0;\n }\n /* layout.css.ts's shared <=1023px block hides this element outright\n (header > :first-child > :last-child { display: none !important },\n specificity (0,3,1) \u2014 v1.0.0 relocated the desktop Session-log capsule\n to the drawer footer on every narrow width). That selector is repeated\n here at equal specificity so the later source order wins the display\n property; the class-suffix selector alone (0,2,1) would silently lose. */\n [data-phase] header [class$=\"_headerUtilities\"],\n [data-phase] header > :first-child > :last-child {\n grid-column: 3;\n grid-row: 1;\n justify-self: end;\n display: flex !important;\n align-items: center;\n gap: 4px;\n min-width: 0;\n }\n /* justify-self: stretch (the grid default) so this fills the whole 1fr\n column as a definite-width box \u2014 center/center-content happens inside\n it. justify-self: center (tried first) sizes the item to its content\n with no automatic column cap, and the obvious fix \u2014 max-width: 100% \u2014\n resolves against the WRONG box here (measured 93.6px at 390px, where\n the column is ~206px): the item's DOM parent (titleCluster) is\n display:contents, and percentage max-width does not reliably resolve\n through a boxless ancestor to the grid track. Stretching sidesteps the\n percentage entirely \u2014 but layout.css.ts's shared <=1023px block ALSO\n caps this exact selector at max-width: 24vw !important (93.6px at\n 390px \u2014 the value this rule used to silently inherit once its own\n max-width was dropped in favor of stretch), so that must be reset to\n none here too. */\n [data-phase] header [class$=\"_crumbs\"] {\n grid-column: 2;\n grid-row: 1;\n justify-self: stretch;\n max-width: none !important;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n overflow: hidden;\n }\n /* Only the current session's own name is the \"title\" \u2014 the parent chain\n (subagent breadcrumbs) is the \"\u591A\u4F59\u9879\" the design calls out to hide,\n while the current segment (and its running dot, see below) stays. */\n [data-phase] header [class$=\"_crumbSeg\"]:not(:last-child) {\n display: none !important;\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 [data-phase] header [class$=\"_crumbSeg\"]:last-child [class$=\"_crumbSep\"] {\n display: none !important;\n }\n /* BOTH selectors are required: [class$=] matches the whole class\n ATTRIBUTE's suffix, and the current-session title button's attribute is\n \"wSkVaW_crumb wSkVaW_crumbCurrent\" \u2014 it ends in _crumbCurrent, so the _crumb\n suffix selector alone silently missed the one element that matters\n (measured live: the title stayed 14px while the rule sat in the bundle,\n 2026-08-17). */\n [data-phase] header [class$=\"_crumb\"],\n [data-phase] header [class$=\"_crumbCurrent\"] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n max-width: 100%;\n /* Real-device feedback (2026-08-17): the session title read too small\n next to the enlarged header icons \u2014 19px sits just under the home\n screen's 22px workspace title, keeping the two-level hierarchy. */\n font-size: 19px;\n font-weight: 600;\n }\n\n /* Running-status dot: no official element to reposition (the header\n renders only the crumb title, the two action slots, and the tablist \u2014\n no status indicator), so this is the plan's documented fallback \u2014\n read the data ourselves and draw it (effects/header-status.ts stamps\n data-mobile-nav-dot on the frame). A ::after on the title crumb keeps\n it truly inline with the (centered, truncated) text instead of a\n separately positioned box that would fight the centering math. */\n [data-phase] header [class$=\"_crumbCurrent\"]::after {\n content: '';\n display: none;\n width: 6px;\n height: 6px;\n margin-left: 6px;\n border-radius: 50%;\n vertical-align: middle;\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot] header [class$=\"_crumbCurrent\"]::after {\n display: inline-block;\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"ongoing\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-business-primary, #4f6ef7);\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"warning\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-warn-primary, #d97706);\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"done\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-success-primary, #16a34a);\n }\n\n /* Everything else the official header puts in these two list slots \u2014 the\n agent-preset mode badge, the jobs/subagent trigger, the desktop\n Session-log download capsule, and this plugin's own legacy directory\n toggle \u2014 is the \"\u4E0D\u5E38\u9A7B\" set the design defers to the S4 info card.\n Scoped by the slots' own [data-slot] wrapper (a stable, non-hashed\n contract marker) rather than enumerating each registrant, so a future\n third-party header action is hidden by default too. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > *,\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > * {\n display: none !important;\n }\n /* The agent-preset mode badge specifically survives the blanket hide\n above: layout.css.ts's shared <=1023px block targets it directly\n (header [class$=\"_label\"]:has(> svg) { display: block !important }),\n specificity (0,3,1), higher than the [data-slot] wrapper rule (0,2,1) \u2014\n display wins on specificity before source order, so a same-selector\n override is needed here too. */\n [data-phase] header [class$=\"_label\"]:has(> svg) {\n display: none !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-back\"] {\n display: inline-flex !important;\n }\n /* header-viewrow is position:absolute (see below), so its re-shown\n display value must match the flex layout its own rule declares. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-viewrow\"] {\n display: flex !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"header-info\"],\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"header-workbench\"] {\n display: inline-flex !important;\n }\n\n /* Back button: 44pt touch target, leftmost column. */\n [data-mobile-nav=\"header-back\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-back\"]:active {\n opacity: .6;\n }\n\n /* Info + workbench utility buttons, rightmost column. */\n [data-mobile-nav=\"header-info\"],\n [data-mobile-nav=\"header-workbench\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 34px;\n height: 34px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-secondary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-info\"]:active,\n [data-mobile-nav=\"header-workbench\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n /* Icon family unification (real-device round 2, 2026-08-17): the \u24D8 text\n glyph is gone (MobileSessionHeader.tsx now renders IconInfoOutline16,\n a local 16px SVG built to match the primitives icon family) and the\n workbench button's IconPanelLeftOutline16 is mirrored into a\n panel-RIGHT glyph \u2014 there's no IconPanelRightOutline16 in primitives\n (checked lib/types/icons/index.d.ts), and the plugin's own right-side\n panel semantics are exactly the left icon flipped. Both buttons now\n carry a same-size (16px), same-stroke-weight icon. */\n [data-mobile-nav=\"header-workbench\"] svg {\n transform: scaleX(-1);\n }\n\n /* Official Chat/Trajectory tablist: removed from layout entirely (S2.1 \u2014\n visibility:hidden still held a 27px row, so the header carried the\n view-switch row's band TWICE: 72 + 27 + gap = 104px measured at 390px,\n and a real iPhone added ~54px of notch on top of that). The header's own\n padding-bottom above is now the row's seat.\n display:none does NOT break the view switch: HTMLElement.click()\n dispatches synthetically and fires React's handler regardless of\n rendering \u2014 only real pointer hit-testing needs a box, and this plugin\n never relies on it (MobileSessionHeader always calls .click()). */\n [data-phase] header [class$=\"_tabs\"][role=\"tablist\"] {\n display: none;\n }\n\n /* View-switch row: \"current view name + dots\", replacing the (still\n present, still clickable) official tablist visually. It renders inside\n headerActions/headerUtilities, but titleRow's own position:relative\n (set above) makes IT the containing block for absolute descendants \u2014\n display:contents on titleCluster does not break that search \u2014 so\n top:100% sits directly under the title regardless of the title row's\n actual height, with no hardcoded offset to keep in sync. */\n [data-mobile-nav=\"header-viewrow\"] {\n position: absolute;\n left: 0;\n right: 0;\n top: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 28px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-family: inherit;\n font-size: 13px;\n line-height: 18px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] > i {\n display: block;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: currentColor;\n opacity: .35;\n font-style: normal;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] > i[data-active] {\n opacity: 1;\n }\n}\n";
|
|
2
|
+
//# sourceMappingURL=header.css.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"header.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/header.css.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,UAAU,+7fA8VtB,CAAA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const HOME_CSS = "/* ---------- phone app shell (< 768px) ---------- */\n\n@media (max-width: 767px) {\n /* --- the document never scrolls (S1.1, 2026-08-17) ---\n Real-device symptom: the workspace title bar, the session header, the\n composer and the FAB all slid with the finger \u2014 every fixed surface\n \"followed the drag\". They are not misplaced; the whole DOCUMENT was\n rubber-banding under them (they are absolute/in-flow inside the frame,\n so a document-level bounce moves them all as one).\n\n After the S2.1 box-sizing fix the document has no overflow at all\n (scrollHeight === clientHeight, measured with ?mobile-nav-inset=54), so\n this is not scrolling \u2014 it is iOS's elastic overscroll, which happens on\n an unscrollable document too, and which an inner scroller chains into as\n soon as it hits its own end.\n\n overscroll-behavior: none on the viewport kills both halves at once: the\n document itself gets no bounce, and overscroll chained up from the\n message flow / session list is absorbed without moving anything.\n overflow: hidden then hard-locks the document scroller so future content\n can never reintroduce a real scroll. Set on html AND body: the spec\n propagates the viewport's value from html, but engines have historically\n read body, and neither is a scroll container we ever want.\n\n Deliberately NOT position: fixed on body \u2014 the app shell does not need\n it, and it is the variant that strands iOS's fixed elements behind the\n on-screen keyboard. With plain overflow: hidden, iOS still pans the\n visual viewport to reveal a focused textarea, so the composer stays\n visible while typing without any visualViewport JS. */\n html,\n body {\n overflow: hidden !important;\n overscroll-behavior: none !important;\n }\n\n /* The message flow is the one scroller that regularly hits its end under a\n finger. Root-level `none` already absorbs the chain, but declaring it\n at the source keeps the guarantee if the root rule is ever weakened\n (the home list and both sheets already declare it).\n\n `none`, NOT `contain` (S4.1 fix, 2026-08-17). The two differ in exactly\n the way that bit us: `contain` stops overscroll from CHAINING OUT to the\n document but deliberately KEEPS this element's own local elastic bounce,\n while `none` suppresses that local bounce too. S1.1 only reasoned about\n the chain, so it picked `contain` \u2014 and the composer went on twitching\n upward on every drag-past-the-end while the header and FAB stayed put.\n\n Why only the composer moved: the official composer seat\n (`[class$=\"_composerSeat\"]`) is `position: sticky; bottom: 0` and lives\n INSIDE this scroller (measured 2026-08-17: seat 0,710 390x134 inside\n _scrollBody 0,131 390x713, contains() === true). A sticky box is laid out\n against its scroll container's content, so the container's own rubber-band\n drags it along. The header/FAB are absolute on the frame, outside this\n scroller entirely \u2014 which is precisely why S1.1's document-level fix\n pinned them and left this one surface behind.\n\n Deliberately NOT re-pinning the seat with position: fixed: sticky inside\n the scroller is what keeps the focused textarea visible when iOS pans the\n visual viewport for the keyboard, and fixed is the variant that strands\n it behind the keyboard (same trap as the body rule above). Kill the\n bounce, keep the sticky. */\n [data-phase] [class$=\"_scrollBody\"] {\n overscroll-behavior: none !important;\n }\n\n /* The official sidebar is no longer a drawer on a phone \u2014 the home screen\n replaced it. Hidden outright (not translated off-screen) so it cannot\n capture taps or hold layout. */\n [data-mobile-nav=\"frame\"] > :first-child {\n display: none !important;\n }\n /* The header's drawer toggle opened that sidebar; with it gone the button\n has nothing to open. (S2 owns the rest of the header layout \u2014\n styles/header.css.ts \u2014 including the Files button's replacement.) */\n [data-mobile-nav=\"toggle\"] {\n display: none !important;\n }\n\n /* --- level 1: the session list ---\n Renders inside the shell overlay layer (absolute, pointer-events: none),\n so the page re-enables pointer events for itself. The layer's containing\n block is the frame's padding box, which still starts under the status\n bar \u2014 hence the safe-area padding here. */\n [data-mobile-nav=\"home\"] {\n position: absolute;\n inset: 0;\n z-index: 5;\n display: flex;\n flex-direction: column;\n pointer-events: auto;\n /* dsw-specific-sidebar-fill, not an alias bg-layer-* token (real-device\n round 2 follow-up, 2026-08-17): fetched and diffed the live theme\n CSS (both light/dark blocks in /assets/index-*.css) because computed\n values, not source-read guesses, are what actually matter here \u2014\n --dsw-alias-bg-base/-layer-1/-layer-2 all resolve to the exact same\n color in the LIGHT theme (neutral-bluish-00, i.e. plain white); they\n only diverge in dark mode. A layer-* token would have made the page\n and its cards indistinguishable in light mode specifically \u2014 the\n opposite of what was asked. --dsw-specific-sidebar-fill differs from\n bg-base in BOTH themes (bluish-50 vs -00 light, bluish-900 vs -950\n dark) and is the exact token dsh-better-sidebar's own panel already\n uses for this same \"secondary surface next to bg-base content\"\n role, so it is the correct reuse rather than a new hardcoded gray. */\n background: var(--dsw-specific-sidebar-fill, #f5f5f5);\n color: var(--dsw-alias-label-primary, inherit);\n padding-top: var(--mnav-sat);\n transform: translateX(0);\n opacity: 1;\n transition:\n transform .3s var(--ds-ease-in-out, ease-in-out),\n opacity .3s var(--ds-ease-in-out, ease-in-out);\n }\n /* Push transition: the list slides out to the left as the session takes\n the screen, and comes back from the left. It stays mounted (visibility,\n not display) so the slide has something to animate; the delayed\n visibility keeps it non-interactive the moment the transition ends. */\n [data-mobile-nav=\"home\"][data-view=\"session\"] {\n transform: translateX(-100%);\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n transition:\n transform .3s var(--ds-ease-in-out, ease-in-out),\n opacity .3s var(--ds-ease-in-out, ease-in-out),\n visibility 0s .3s;\n }\n @media (prefers-reduced-motion: reduce) {\n [data-mobile-nav=\"home\"],\n [data-mobile-nav=\"home\"][data-view=\"session\"] {\n transition: none !important;\n }\n }\n\n /* Title bar: the workspace name IS the switcher. */\n [data-mobile-nav=\"home-top\"] {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n min-height: 52px;\n padding: 4px 16px 6px;\n }\n /* Site logo (real-device round 2, 2026-08-17): rendered only when\n document.head actually has a <link rel=\"icon\"> (MobileHome.tsx never\n ships a placeholder box), so this rule only ever needs to size the\n image, not reserve space for its absence. */\n [data-mobile-nav=\"home-logo\"] {\n width: 24px;\n height: 24px;\n margin-right: 6px;\n flex: none;\n border-radius: 6px;\n object-fit: contain;\n }\n /* Dark theme (real-device report, 2026-08-17): the runtime favicon is a\n dark-on-transparent whale mark, illegible on the dark page. Force a\n white silhouette regardless of the source image's own colors \u2014\n brightness(0) collapses every opaque pixel to black first, invert(1)\n then flips that to white; transparent pixels stay transparent either\n way. Same body[data-ds-dark-theme] marker as the home-row hairline\n above (AGENTS.md: prefers-color-scheme never fires, the app switches\n themes via this attribute with color-scheme hardcoded to light). */\n body[data-ds-dark-theme] [data-mobile-nav=\"home-logo\"] {\n filter: brightness(0) invert(1);\n }\n [data-mobile-nav=\"ws-switch\"] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n min-height: 44px;\n max-width: 100%;\n padding: 0 6px 0 0;\n border: none;\n background: transparent;\n color: inherit;\n font-family: inherit;\n font-size: 22px;\n font-weight: 600;\n line-height: 1.2;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"ws-switch\"] > span {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n [data-mobile-nav=\"ws-switch\"] > svg {\n flex: none;\n opacity: .55;\n }\n [data-mobile-nav=\"ws-switch\"]:active {\n opacity: .6;\n }\n\n /* Session list: rounded cards, not bordered rows (real-device round 2\n follow-up, 2026-08-17 \u2014 reference: Claude Code mobile app's session\n list). `gap` on the flex column IS the inter-card spacing; no\n per-row margin bookkeeping. */\n [data-mobile-nav=\"home-list\"] {\n flex: 1 1 auto;\n min-height: 0;\n margin: 0;\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 16px calc(var(--mnav-sab) + 96px);\n list-style: none;\n overflow-y: auto;\n overscroll-behavior: contain;\n -webkit-overflow-scrolling: touch;\n }\n [data-mobile-nav=\"home-row\"] {\n display: flex;\n align-items: center;\n gap: 12px;\n width: 100%;\n min-height: 60px;\n padding: 14px 16px;\n border: none;\n border-radius: 22px;\n background: var(--dsw-alias-bg-base, #ffffff);\n color: inherit;\n font-family: inherit;\n text-align: left;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n /* Card edge definition (2026-08-17). Light theme only \u2014 see the dark\n override below. Two stacked shadows in the iOS idiom: a tight 1px\n contact shadow that draws the edge itself, plus a wide soft one that\n lifts the card off the page. Both are deliberately weak (.06/.05):\n the page sits on --dsw-specific-sidebar-fill and the card on\n --dsw-alias-bg-base, so there is already a slight tonal step here and\n the shadow only has to sharpen it, not carry it alone. */\n box-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 4px 12px rgba(0, 0, 0, .05);\n }\n [data-mobile-nav=\"home-row\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n /* Press = settle toward the page. Dropping the wide shadow and keeping a\n tighter contact one reads as the card pushing in, which is what the\n darkening background already implies; leaving the lifted shadow under\n a pressed card is the combination that looks wrong. */\n box-shadow: 0 1px 2px rgba(0, 0, 0, .05);\n }\n /* Dark theme: a black shadow on a near-black page is invisible, so the\n edge is drawn instead of cast. A 1px hairline at --dsw-alias-border-l2\n weight, delivered as an INSET box-shadow rather than a real border so\n the card's geometry is untouched (a real 1px border would grow every\n row by 2px unless box-sizing cooperated) and so it overrides the light\n shadow through the same property instead of fighting it.\n\n Chosen over \"brighten the card face\" because in dark theme the card\n (--dsw-alias-bg-base, bluish-950) is already DARKER than the page\n (--dsw-specific-sidebar-fill, bluish-900); lightening the card past the\n page would invert the layering that the light theme establishes, while\n a hairline keeps both themes reading as \"page behind, card in front\".\n\n Theme is read from body[data-ds-dark-theme], never prefers-color-scheme:\n the app hardcodes color-scheme: light on <html> and switches themes with\n this attribute, so a media query would never fire (AGENTS.md). This\n selector also outranks the :active rule above (0,2,1 vs 0,2,0), so one\n declaration covers the pressed state too. */\n body[data-ds-dark-theme] [data-mobile-nav=\"home-row\"] {\n box-shadow: inset 0 0 0 1px var(--dsw-alias-border-l2, rgba(255, 255, 255, .12));\n }\n [data-mobile-nav=\"home-row\"][data-current] [data-mobile-nav=\"home-row-title\"] {\n font-weight: 600;\n }\n /* Avatar: a running/warning/done session shows its existing StateDot\n (same component, same semantics as the old inline dot \u2014 just bigger\n and re-homed); otherwise the title's own first character stands in\n for it, so every row has a mark even when idle. interactive-bg-hover,\n not a bg-layer-* token: same reason as the page background above \u2014\n bg-layer-2 is IDENTICAL to the card's own bg-base in light theme\n (verified against the live theme CSS), so it would render invisible\n there. interactive-bg-hover is a translucent rgba tint rather than a\n solid layer color, so it always reads as \"a shade over the card\" in\n both themes regardless of what the card's base color resolves to. */\n [data-mobile-nav=\"home-row-avatar\"] {\n flex: none;\n display: grid;\n place-items: center;\n width: 40px;\n height: 40px;\n border-radius: 12px;\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .55));\n font-size: 16px;\n font-weight: 600;\n }\n [data-mobile-nav=\"home-row-body\"] {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n [data-mobile-nav=\"home-row-title\"] {\n font-size: 16px;\n line-height: 21px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n [data-mobile-nav=\"home-row-status\"] {\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-size: 12.5px;\n line-height: 17px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n [data-mobile-nav=\"home-row-time\"] {\n flex: none;\n align-self: flex-start;\n color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .4));\n font-size: 12.5px;\n line-height: 18px;\n }\n [data-mobile-nav=\"home-empty\"] {\n margin: 12px 16px;\n padding: 48px 24px;\n border-radius: 22px;\n background: var(--dsw-alias-bg-base, #ffffff);\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-size: 15px;\n text-align: center;\n }\n\n /* New-session FAB: a labeled pill (real-device round 2 follow-up), not a\n bare circle \u2014 tap starts in the shown workspace, long press picks\n one. Inverted-surface tokens (not a hardcoded accent color): the same\n pair the official git-commit button in dsh-better-sidebar uses for\n its own solid CTA. */\n [data-mobile-nav=\"home-fab\"] {\n position: absolute;\n right: 18px;\n bottom: calc(var(--mnav-sab) + 22px);\n z-index: 6;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 48px;\n padding: 0 20px 0 16px;\n border: none;\n border-radius: 999px;\n background: var(--dsw-alias-button-primary-fill, #1a1a1a);\n color: var(--dsw-alias-label-primary-inverted, #ffffff);\n font-family: inherit;\n font-size: 15px;\n font-weight: 600;\n cursor: pointer;\n box-shadow: 0 6px 20px rgba(0, 0, 0, .24);\n touch-action: manipulation;\n user-select: none;\n -webkit-user-select: none;\n -webkit-touch-callout: none;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"home-fab\"]:active {\n transform: scale(.96);\n }\n\n /* Workspace sheet (switcher and long-press New Session share it). */\n [data-mobile-nav=\"home-sheet-layer\"] {\n position: absolute;\n inset: 0;\n z-index: 7;\n }\n [data-mobile-nav=\"home-sheet-mask\"] {\n position: absolute;\n inset: 0;\n background: var(--dsw-alias-bg-mask-3, rgba(0, 0, 0, .45));\n border: none;\n animation: dsh-mobile-nav-fade .18s var(--ds-ease-out, ease-in-out);\n }\n /* Docked to the screen edge, matching the composer's permission/model\n sheets (styles/composer.css.ts section 4) rather than floating.\n\n It used to sit at `bottom: calc(var(--mnav-sab) + 8px)` with all four\n corners rounded, which left a 42px strip of bare mask under it at\n sab: 34 (measured). Docking removes the strip instead of dimming it, and\n the safe area moves INSIDE the card as bottom padding \u2014 so the white\n card body runs all the way to the physical screen edge and the last row\n still clears the home indicator. Same reason the corners go top-only:\n a rounded bottom corner on a card flush with the screen edge reads as a\n rendering mistake. */\n [data-mobile-nav=\"home-sheet\"] {\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n max-height: 70%;\n overflow-y: auto;\n overscroll-behavior: contain;\n box-sizing: border-box;\n padding: 6px 6px calc(var(--mnav-sab) + 14px);\n border-radius: 20px 20px 0 0;\n /* layer-2, not bg-base: in dark mode the sheet must lift off a page that\n shares bg-base, or only the shadow separates them. */\n background: var(--dsw-alias-bg-layer-2, #ffffff);\n box-shadow: 0 -8px 32px rgba(0, 0, 0, .28);\n animation: dsh-mobile-nav-sheet-up .22s var(--ds-ease-out, ease-in-out);\n }\n @media (prefers-reduced-motion: reduce) {\n [data-mobile-nav=\"home-sheet-mask\"],\n [data-mobile-nav=\"home-sheet\"] {\n animation: none !important;\n }\n }\n [data-mobile-nav=\"home-sheet-title\"] {\n padding: 10px 12px 6px;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-size: 13px;\n }\n [data-mobile-nav=\"home-sheet-item\"] {\n display: flex;\n align-items: center;\n width: 100%;\n min-height: 48px;\n padding: 0 12px;\n border: none;\n border-radius: 12px;\n background: transparent;\n color: inherit;\n font-family: inherit;\n font-size: 16px;\n text-align: left;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"home-sheet-item\"][data-selected] {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n font-weight: 600;\n }\n [data-mobile-nav=\"home-sheet-item\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n\n /* --- hero fallback back button ---\n The hero (new blank session) page renders no conversation.session.header,\n so the header-slot back button (S2) does not exist there. This floating\n fallback covers that page and hides itself as soon as the real header\n back mounts. */\n [data-mobile-nav=\"hero-back\"] {\n position: absolute;\n top: calc(var(--mnav-sat) + 8px);\n left: 8px;\n z-index: 6;\n width: 44px;\n height: 44px;\n display: grid;\n place-items: center;\n border: none;\n border-radius: 14px;\n background: transparent;\n color: var(--dsw-alias-label-secondary, inherit);\n pointer-events: auto;\n }\n [data-mobile-nav=\"hero-back\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n [data-mobile-nav=\"frame\"]:has([data-mobile-nav=\"header-back\"]) ~ [data-mobile-nav=\"hero-back\"],\n body:has([data-mobile-nav=\"header-back\"]) [data-mobile-nav=\"hero-back\"] {\n display: none;\n }\n}\n";
|
|
2
|
+
//# sourceMappingURL=home.css.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"home.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/home.css.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,QAAQ,4smBAqdpB,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All mobile styles, concatenated in the exact order of the original
|
|
3
|
+
* single-file stylesheet (base → layout → compat → misc, where misc keeps
|
|
4
|
+
* composer → tablet → desktop), followed by the phone app shell (home), the
|
|
5
|
+
* session-header reflow (header), the composer reflow (composer), and the
|
|
6
|
+
* session-info sheet (info, which must come last of all: it re-shows a
|
|
7
|
+
* header.utilities child header.css.ts hides by default, so its rule has to
|
|
8
|
+
* win that tie too), the turn-process fold (turn-fold, S8, which shares no
|
|
9
|
+
* selector with any of them), and finally the chips row / settings entry /
|
|
10
|
+
* portal fix (chips, S5) — all appended in this order so their <768px rules
|
|
11
|
+
* win ties against the shared <=1023px block. Injected as ONE
|
|
12
|
+
* <style data-plugin> tag — do not reorder.
|
|
13
|
+
*/
|
|
14
|
+
export declare const MOBILE_CSS: string;
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/index.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU,QAAkI,CAAA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const INFO_CSS = "/* ---------- session-info sheet (< 768px) ---------- */\n\n/* Unconditional render (React, not CSS, decides open/closed) \u2014 default\n hidden outside the phone breakpoint so a stray SESSION_INFO_EVENT at\n >=768px (the \u24D8 trigger itself is already CSS-hidden there) can never\n paint anything, mirroring header.css.ts's own belt-and-braces list. */\n[data-mobile-nav=\"info-layer\"] {\n display: none;\n}\n\n@media (max-width: 767px) {\n /* header.css.ts hides every [data-slot=\"conversation.session.header.utilities\"]\n direct child by default (its own \u24D8/workbench buttons are the two named\n exceptions) \u2014 this is a second, sibling entry on that same slot, so it\n needs the identical re-show override. Equal specificity\n ([data-slot=\"\u2026\"] > * vs this attribute selector, both one attribute\n selector + !important) means source order decides the tie; this file\n is concatenated after header.css.ts in styles/index.ts, so it wins. */\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"info-layer\"] {\n display: block !important;\n }\n\n [data-mobile-nav=\"info-layer\"] {\n position: fixed;\n inset: 0;\n /* Above the composer's own bottom sheets (permission/model menus sit at\n z:60, styles/composer.css.ts) and the shell.overlay layer's z:20\n ceiling (AGENTS.md stacking-context pitfall) \u2014 this sheet must cover\n both, which is only possible because it renders inside the header's\n own DOM instead of that capped layer. */\n z-index: 70;\n }\n [data-mobile-nav=\"info-mask\"] {\n position: absolute;\n inset: 0;\n border: none;\n background: var(--dsw-alias-bg-mask-3, rgba(0, 0, 0, .45));\n animation: dsh-mobile-nav-fade .18s var(--ds-ease-out, ease-in-out);\n }\n [data-mobile-nav=\"info-sheet\"] {\n position: absolute;\n left: 8px;\n right: 8px;\n bottom: calc(var(--mnav-sab) + 8px);\n max-height: 82dvh;\n overflow-y: auto;\n overscroll-behavior: contain;\n box-sizing: border-box;\n padding: 12px;\n border-radius: 16px;\n background: var(--dsw-alias-bg-layer-2, #ffffff);\n box-shadow: 0 8px 32px rgba(0, 0, 0, .28);\n animation: dsh-mobile-nav-sheet-up .22s var(--ds-ease-out, ease-in-out);\n }\n @media (prefers-reduced-motion: reduce) {\n [data-mobile-nav=\"info-mask\"],\n [data-mobile-nav=\"info-sheet\"] {\n animation: none !important;\n }\n }\n\n /* --- head: Chat/Trajectory segmented control + close --- */\n [data-mobile-nav=\"info-head\"] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n margin-bottom: 10px;\n }\n [data-mobile-nav=\"info-tabs\"] {\n display: flex;\n gap: 2px;\n padding: 2px;\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n border-radius: 12px;\n }\n [data-mobile-nav=\"info-tab\"] {\n min-height: 36px;\n padding: 0 14px;\n border: none;\n border-radius: 10px;\n background: transparent;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-family: inherit;\n font-size: 14px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"info-tab\"][data-selected] {\n background: var(--dsw-alias-bg-layer-2, #ffffff);\n color: var(--dsw-alias-label-primary, inherit);\n font-weight: 600;\n box-shadow: 0 1px 3px rgba(0, 0, 0, .12);\n }\n [data-mobile-nav=\"info-close\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 34px;\n height: 34px;\n flex: none;\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=\"info-close\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n\n /* --- badges: agent preset, subagent count, cwd --- */\n [data-mobile-nav=\"info-badges\"] {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n margin-bottom: 12px;\n }\n [data-mobile-nav=\"info-badge\"],\n [data-mobile-nav=\"info-badge-cwd\"] {\n display: inline-flex;\n align-items: center;\n max-width: 100%;\n padding: 4px 10px;\n border-radius: 999px;\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .6));\n font-size: 12px;\n line-height: 18px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n [data-mobile-nav=\"info-badge-cwd\"] {\n font-family: var(--dsw-font-mono, ui-monospace, monospace);\n }\n\n /* --- six-cell stats grid --- */\n [data-mobile-nav=\"info-stats\"] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 8px;\n margin-bottom: 12px;\n }\n [data-mobile-nav=\"info-stat\"] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n min-height: 48px;\n padding: 8px 4px;\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-1, rgba(0, 0, 0, .03));\n text-align: center;\n }\n [data-mobile-nav=\"info-stat-value\"] {\n color: var(--dsw-alias-label-primary, inherit);\n font-size: 17px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n line-height: 1.2;\n }\n [data-mobile-nav=\"info-stat-label\"] {\n color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .4));\n font-size: 11px;\n line-height: 1.3;\n }\n [data-mobile-nav=\"info-stat-sub\"] {\n color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .4));\n font-size: 10px;\n line-height: 1.2;\n }\n\n [data-mobile-nav=\"info-error\"] {\n margin-bottom: 8px;\n padding: 8px 10px;\n border-radius: 10px;\n background: var(--dsw-alias-state-warn-bg, rgba(217, 119, 6, .12));\n color: var(--dsw-alias-state-warn-primary, #d97706);\n font-size: 12px;\n line-height: 1.4;\n }\n\n /* --- action row: export / rename / fork / archive --- */\n [data-mobile-nav=\"info-actions\"] {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 8px;\n }\n [data-mobile-nav=\"info-action\"] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n min-height: 48px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12));\n border-radius: 12px;\n background: transparent;\n color: var(--dsw-alias-label-primary, inherit);\n font-family: inherit;\n font-size: 13px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"info-action\"]:active:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n [data-mobile-nav=\"info-action\"]:disabled {\n opacity: .5;\n cursor: default;\n }\n [data-mobile-nav=\"info-action\"][data-mobile-nav-danger] {\n border-color: var(--dsw-alias-state-warn-primary, #d97706);\n color: var(--dsw-alias-state-warn-primary, #d97706);\n }\n}\n";
|
|
2
|
+
//# sourceMappingURL=info.css.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"info.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/info.css.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,QAAQ,gqOA+NpB,CAAA"}
|