dsh-zen-remote 2.3.8 → 2.3.10
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/CHANGELOG.md +41 -0
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/docs/interface.md +1 -1
- package/docs/remote-access.en.md +1 -1
- package/docs/remote-access.md +1 -1
- package/lib/adapters/dsh-better-sidebar/index.js +21 -3
- package/lib/adapters/dsh-better-sidebar/index.js.map +1 -1
- package/lib/client.js +522 -89
- package/lib/types/adapters/dsh-better-sidebar/index.d.ts +1 -1
- package/lib/types/adapters/dsh-better-sidebar/index.d.ts.map +1 -1
- package/lib/types/client/MobileHome.d.ts.map +1 -1
- package/lib/types/client/MobileNavOverlay.d.ts.map +1 -1
- package/lib/types/client/MobileSessionHeader.d.ts.map +1 -1
- package/lib/types/client/effects/native-trigger-overlay.d.ts.map +1 -1
- package/lib/types/client/effects/permission-sheet.d.ts +51 -10
- package/lib/types/client/effects/permission-sheet.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +1 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/settings/SettingsSection.d.ts.map +1 -1
- package/lib/types/client/styles/chips.css.d.ts +1 -1
- package/lib/types/client/styles/chips.css.d.ts.map +1 -1
- package/lib/types/client/styles/composer.css.d.ts +1 -1
- package/lib/types/client/styles/composer.css.d.ts.map +1 -1
- package/lib/types/client/styles/header.css.d.ts +1 -1
- package/lib/types/client/styles/header.css.d.ts.map +1 -1
- package/lib/types/client/styles/home.css.d.ts +1 -1
- package/lib/types/client/styles/home.css.d.ts.map +1 -1
- package/lib/types/client/styles/layout.css.d.ts +1 -1
- package/lib/types/client/styles/layout.css.d.ts.map +1 -1
- package/lib/types/client/wizard/GatewayWizard.d.ts.map +1 -1
- package/lib/types/client-data/new-session.d.ts +26 -0
- package/lib/types/client-data/new-session.d.ts.map +1 -0
- package/lib/types/client-data/settings-form.d.ts +36 -0
- package/lib/types/client-data/settings-form.d.ts.map +1 -1
- package/package.json +1 -1
- package/scripts/check-settings-form.mjs +63 -0
- package/src/adapters/dsh-better-sidebar/index.ts +23 -3
- package/src/client/MobileHome.tsx +63 -8
- package/src/client/MobileNavOverlay.tsx +3 -1
- package/src/client/MobileSessionHeader.tsx +7 -1
- package/src/client/effects/native-trigger-overlay.ts +50 -11
- package/src/client/effects/permission-sheet.ts +199 -22
- package/src/client/locales.ts +2 -0
- package/src/client/settings/SettingsSection.tsx +43 -1
- package/src/client/styles/chips.css.ts +7 -2
- package/src/client/styles/composer.css.ts +24 -0
- package/src/client/styles/header.css.ts +58 -46
- package/src/client/styles/home.css.ts +6 -2
- package/src/client/styles/layout.css.ts +43 -2
- package/src/client/wizard/GatewayWizard.tsx +23 -1
- package/src/client-data/new-session.ts +30 -0
- package/src/client-data/settings-form.ts +57 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The dsh-better-sidebar adapter (PB-12), written against EXACTLY
|
|
3
|
-
* versions 0.25.0, 0.26.0, 0.26.1, 0.28.0-alpha.0, 0.28.0, 0.28.1
|
|
3
|
+
* versions 0.25.0, 0.26.0, 0.26.1, 0.28.0-alpha.0, 0.28.0, 0.28.1, 0.28.2 and 0.30.0 (PB-15 widened the list; 0.26.1 is a
|
|
4
4
|
* perf-only patch with the same wire surface as 0.26.0; 0.26.0 is a JSON-api
|
|
5
5
|
* superset of 0.25.0 — it grows fs.readPage and the sidechat dequeue/model/
|
|
6
6
|
* models methods, and moves fs.write's content onto the new raw-text
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapters/dsh-better-sidebar/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapters/dsh-better-sidebar/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAsBhD,eAAO,MAAM,uBAAuB,EAAE,aAmMrC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MobileHome.d.ts","sourceRoot":"","sources":["../../../src/client/MobileHome.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAA;AAU7F,OAAO,KAAK,EAAoB,SAAS,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACzG,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAMjC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"MobileHome.d.ts","sourceRoot":"","sources":["../../../src/client/MobileHome.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAA;AAU7F,OAAO,KAAK,EAAoB,SAAS,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACzG,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAMjC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAOpD,kEAAkE;AAClE,MAAM,MAAM,eAAe,GACvB,YAAY,CAAC,eAAe,CAAC,GAC7B,UAAU,CAAC,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,GAC7C,WAAW,CAAC,OAAO,EAAE,CAAC,GACtB;IACA,6CAA6C;IAC7C,WAAW,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IACpC,uDAAuD;IACvD,YAAY,EAAE,CAAC,WAAW,CAAC,EAAE,WAAW,KAAK,IAAI,CAAA;IACjD,2EAA2E;IAC3E,kBAAkB,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IAC3C,sEAAsE;IACtE,cAAc,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAChD;;;OAGG;IACH,4BAA4B,CAAC,EAAE,sBAAsB,CAAA;CACtD,CAAA;AAgNH;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,EACzB,WAAW,EACX,aAAa,EACb,4BAA4B,EAC5B,QAAQ,EACR,OAAO,EACP,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,CAAC,GACF,EAAE,eAAe,sCAqZjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MobileNavOverlay.d.ts","sourceRoot":"","sources":["../../../src/client/MobileNavOverlay.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAEjF,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAEjC,8CAA8C;AAC9C,MAAM,WAAW,qBAAsB,SAAQ,YAAY,CAAC,eAAe,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC;IAClG,wCAAwC;IACxC,aAAa,EAAE,MAAM,IAAI,CAAA;CAC1B;AAmCD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,qBAAqB,
|
|
1
|
+
{"version":3,"file":"MobileNavOverlay.d.ts","sourceRoot":"","sources":["../../../src/client/MobileNavOverlay.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAEjF,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAEjC,8CAA8C;AAC9C,MAAM,WAAW,qBAAsB,SAAQ,YAAY,CAAC,eAAe,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC;IAClG,wCAAwC;IACxC,aAAa,EAAE,MAAM,IAAI,CAAA;CAC1B;AAmCD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,qBAAqB,sCAsO3E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MobileSessionHeader.d.ts","sourceRoot":"","sources":["../../../src/client/MobileSessionHeader.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAEjF,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"MobileSessionHeader.d.ts","sourceRoot":"","sources":["../../../src/client/MobileSessionHeader.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAEjF,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAsHjC,yEAAyE;AACzE,MAAM,MAAM,wBAAwB,GAChC,YAAY,CAAC,qCAAqC,CAAC,GACnD,WAAW,CAAC,OAAO,EAAE,CAAC,GACtB;IACA;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAA;IAChB,kGAAkG;IAClG,SAAS,EAAE,CAAC,SAAS,EAAE,SAAS,KAAK,MAAM,IAAI,CAAA;CAChD,CAAA;AAEH,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,OAAO,CAAA;IACf,EAAE,EAAE,iBAAiB,CAAA;CACtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,IAAI,WAAW,EAAE,CAQ5C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,IAAI,WAAW,EAAE,CAe3C;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,SAAS,EACT,WAAW,EACX,gBAAgB,EAChB,OAAO,EACP,SAAS,EACT,CAAC,GACF,EAAE,wBAAwB,+BAyF1B;AAED,sEAAsE;AACtE,MAAM,MAAM,0BAA0B,GAClC,YAAY,CAAC,uCAAuC,CAAC,GACrD,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;AAuC1B;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,CAAC,EAAE,EAAE,0BAA0B,+BA4FtE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-trigger-overlay.d.ts","sourceRoot":"","sources":["../../../../src/client/effects/native-trigger-overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"native-trigger-overlay.d.ts","sourceRoot":"","sources":["../../../../src/client/effects/native-trigger-overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AA8EvD,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAuCpE"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { ClientContext } from '../compat/types.ts';
|
|
2
|
-
/** The DOM face the
|
|
3
|
-
*
|
|
2
|
+
/** The DOM face the pure decisions below read — structural, so the tests
|
|
3
|
+
* drive them with plain objects instead of a DOM. */
|
|
4
4
|
export interface PermissionSheetCandidate {
|
|
5
5
|
matches(selectors: string): boolean;
|
|
6
6
|
getAttribute(name: string): string | null;
|
|
7
|
-
/** A truthy `closest` result means the click started
|
|
7
|
+
/** A truthy `closest` result means the click started inside the seat. */
|
|
8
8
|
closest(selectors: string): unknown;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
@@ -13,13 +13,45 @@ export interface PermissionSheetCandidate {
|
|
|
13
13
|
* body-level role=menu shape, `getAttribute` the marker.
|
|
14
14
|
*/
|
|
15
15
|
export declare function isUnmarkedPortalMenu(node: PermissionSheetCandidate): boolean;
|
|
16
|
+
/** Which bottom sheet a trigger click arms. */
|
|
17
|
+
export type SheetKind = 'perm' | 'workspace';
|
|
16
18
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* the menus they open are
|
|
19
|
+
* Which sheet ONE click event's target arms: the phone shell only. The
|
|
20
|
+
* permission seat's trigger arms `perm` (T70); the hero page's two
|
|
21
|
+
* workspace triggers arm `workspace` (UX-3). Any other button — the
|
|
22
|
+
* sidebar's 「…」, a copy button — arms nothing, so the menus they open are
|
|
23
|
+
* never marked.
|
|
21
24
|
*/
|
|
22
|
-
export declare function
|
|
25
|
+
export declare function sheetKindForTrigger(target: PermissionSheetCandidate | null | undefined, phoneMatches: boolean): SheetKind | null;
|
|
26
|
+
/**
|
|
27
|
+
* UX-3: whether a pointerdown landed inside a WORKSPACE trigger — the only
|
|
28
|
+
* triggers whose open/close suffers the outside-pointerdown + toggle-click
|
|
29
|
+
* race (the Menu closes the portaled sheet on the outside pointerdown, and
|
|
30
|
+
* the trigger's own click would flip it straight back open). The permission
|
|
31
|
+
* trigger toggles by click alone and is never suppressed.
|
|
32
|
+
*/
|
|
33
|
+
export declare function pointerdownOnWorkspaceTrigger(target: PermissionSheetCandidate | null | undefined): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* UX-3: whether a click started on a workspace item inside the MARKED
|
|
36
|
+
* workspace sheet — the pick that starts the switch lock. Keyed on the
|
|
37
|
+
* `workspace` marker value, so a permission option can never lock the
|
|
38
|
+
* composer.
|
|
39
|
+
*/
|
|
40
|
+
export declare function menuitemClickStartsWorkspaceSwitch(target: PermissionSheetCandidate | null | undefined): boolean;
|
|
41
|
+
/** What the release check reads, injected so the tests stay DOM-free. */
|
|
42
|
+
export interface WorkspaceSwitchProbe {
|
|
43
|
+
/** The conversation root's current `data-conversation-session` value (null = none mounted). */
|
|
44
|
+
currentSessionValue(): string | null;
|
|
45
|
+
/** Whether the hero's "choose a workspace" placeholder card is mounted. */
|
|
46
|
+
placeholderOpen(): boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Whether the workspace switch has settled: the conversation root now names
|
|
50
|
+
* a DIFFERENT session than the one the lock was taken on, and the no-workspace
|
|
51
|
+
* placeholder card is gone (the optimistic `pendingWorkspaceId` display alone
|
|
52
|
+
* is not enough — the remount resets it).
|
|
53
|
+
*/
|
|
54
|
+
export declare function workspaceSwitchSettled(probe: WorkspaceSwitchProbe, lockedSessionValue: string | null): boolean;
|
|
23
55
|
/**
|
|
24
56
|
* T70: turn the portaled permission menu into the bottom sheet composer.css
|
|
25
57
|
* section 4 promises, on DSH 0.2.0. Up to 0.1.7 the permission menu rendered
|
|
@@ -28,12 +60,21 @@ export declare function clickArmsPermissionSheet(target: PermissionSheetCandidat
|
|
|
28
60
|
* selectors stopped matching, and the phone got a tiny floating popup. The
|
|
29
61
|
* host offers no stable attribute to key the portal off — the anchor is a
|
|
30
62
|
* per-instance useId, there is no id and no aria-controls — so this effect
|
|
31
|
-
* marks it: the trigger's click arms a window, the observer stamps the
|
|
32
|
-
* body-level menu with `data-zen-sheet
|
|
63
|
+
* marks it: the trigger's click arms a short window, the observer stamps the
|
|
64
|
+
* next body-level menu with `data-zen-sheet`, and the stylesheet (which
|
|
33
65
|
* only the phone shell loads) turns THAT into the sheet. The portal menu
|
|
34
66
|
* unmounts on close, so the marker's lifetime is exactly the menu's; the
|
|
35
67
|
* 0.1.7 inline structure keeps working through the untouched descendant
|
|
36
68
|
* selectors, and the desktop shell never loads this stylesheet at all.
|
|
69
|
+
*
|
|
70
|
+
* UX-3 extends the same machinery to the hero page's workspace menu:
|
|
71
|
+
* - the two workspace triggers arm a `data-zen-sheet="workspace"` sheet;
|
|
72
|
+
* - a pointerdown on a workspace trigger while a marked sheet is up
|
|
73
|
+
* suppresses the trigger's own click, so one tap reliably CLOSES (the
|
|
74
|
+
* host's outside-pointerdown close stands instead of being undone by the
|
|
75
|
+
* toggle click);
|
|
76
|
+
* - a pick inside the workspace sheet locks the composer for the length of
|
|
77
|
+
* the `selectWorkspace` remount (see SWITCH_MARKER above).
|
|
37
78
|
*/
|
|
38
79
|
export declare function installPermissionSheet(ctx: ClientContext): void;
|
|
39
80
|
//# sourceMappingURL=permission-sheet.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-sheet.d.ts","sourceRoot":"","sources":["../../../../src/client/effects/permission-sheet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"permission-sheet.d.ts","sourceRoot":"","sources":["../../../../src/client/effects/permission-sheet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAqEvD;qDACqD;AACrD,MAAM,WAAW,wBAAwB;IACvC,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAA;IACnC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACzC,yEAAyE;IACzE,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAA;CACpC;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,wBAAwB,GAAG,OAAO,CAE5E;AAED,+CAA+C;AAC/C,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,WAAW,CAAA;AAI5C;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,wBAAwB,GAAG,IAAI,GAAG,SAAS,EAAE,YAAY,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAKhI;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,wBAAwB,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAE1G;AAED;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,wBAAwB,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAE/G;AAED,yEAAyE;AACzE,MAAM,WAAW,oBAAoB;IACnC,+FAA+F;IAC/F,mBAAmB,IAAI,MAAM,GAAG,IAAI,CAAA;IACpC,2EAA2E;IAC3E,eAAe,IAAI,OAAO,CAAA;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAG9G;AAYD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAiJ/D"}
|
|
@@ -11,6 +11,7 @@ export declare const zh: {
|
|
|
11
11
|
readonly previewExitFullscreen: "退出全屏";
|
|
12
12
|
readonly allWorkspaces: "全部";
|
|
13
13
|
readonly switchWorkspace: "切换工作区";
|
|
14
|
+
readonly pickWorkspaceNew: "选择工作区开始";
|
|
14
15
|
readonly newSession: "新建会话";
|
|
15
16
|
readonly noSessions: "暂无会话,点击右下角加号新建";
|
|
16
17
|
readonly homeStatusOngoing: "运行中";
|
|
@@ -1 +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
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0gBL,CAAA;AAEV,wEAAwE;AACxE,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAsgB3C,CAAA;AAED,2EAA2E;AAC3E,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,EAAE,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SettingsSection.d.ts","sourceRoot":"","sources":["../../../../src/client/settings/SettingsSection.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;
|
|
1
|
+
{"version":3,"file":"SettingsSection.d.ts","sourceRoot":"","sources":["../../../../src/client/settings/SettingsSection.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AA6BjF,OAAO,KAAK,EAUV,qBAAqB,EACtB,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAGlC,OAAO,KAAK,EAAkB,WAAW,EAAE,MAAM,6BAA6B,CAAA;AAE9E,MAAM,WAAW,oBAAqB,SAAQ,YAAY,CAAC,oBAAoB,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC;IACtG,sDAAsD;IACtD,MAAM,EAAE,qBAAqB,CAAA;IAC7B;mDAC+C;IAC/C,MAAM,EAAE,WAAW,CAAA;CACpB;AAiGD,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,+BAW1D"}
|
|
@@ -1,2 +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).\n Scrollbar hidden on BOTH engines (2026-08-18 user decision): iOS\n overlay scrollbars never showed anyway, but desktop browsers painted a\n permanent 2px strip under the pills. This revisits the AGENTS.md\n \"silently cut off the last tab\" lesson deliberately \u2014 with 4+ chips the\n row now always overflows mid-chip (a partially visible pill is the\n scroll affordance), unlike the early incident where the chips fit\n flush and the hidden overflow was undiscoverable. */\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: none;\n }\n [data-mobile-nav=\"chip-row\"]::-webkit-scrollbar {\n display: none;\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 /* Same surface as the list rows: the home page is bg-base since the\n 2026-08-20 swap (see home.css.ts), so a bg-base chip would disappear\n into it. */\n background: var(--dsw-specific-sidebar-fill, #f5f5f5);\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";
|
|
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 [data-mobile-nav=\"frame\"] > [class$=\"_sidebarCol\"]:has([aria-modal=\"true\"], [data-usage-stats-panel], .dshst-overlay) {\n display: contents !important;\n visibility: visible !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 [data-mobile-nav=\"frame\"] > [class$=\"_sidebarCol\"]:has([aria-modal=\"true\"], [data-usage-stats-panel], .dshst-overlay) [class$=\"_logoRow\"],\n [data-mobile-nav=\"frame\"] > [class$=\"_sidebarCol\"]:has([aria-modal=\"true\"], [data-usage-stats-panel], .dshst-overlay) [class$=\"_newSession\"],\n [data-mobile-nav=\"frame\"] > [class$=\"_sidebarCol\"]: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).\n Scrollbar hidden on BOTH engines (2026-08-18 user decision): iOS\n overlay scrollbars never showed anyway, but desktop browsers painted a\n permanent 2px strip under the pills. This revisits the AGENTS.md\n \"silently cut off the last tab\" lesson deliberately \u2014 with 4+ chips the\n row now always overflows mid-chip (a partially visible pill is the\n scroll affordance), unlike the early incident where the chips fit\n flush and the hidden overflow was undiscoverable. */\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: none;\n }\n [data-mobile-nav=\"chip-row\"]::-webkit-scrollbar {\n display: none;\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 /* Same surface as the list rows: the home page is bg-base since the\n 2026-08-20 swap (see home.css.ts), so a bg-base chip would disappear\n into it. */\n background: var(--dsw-specific-sidebar-fill, #f5f5f5);\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
2
|
//# sourceMappingURL=chips.css.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chips.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/chips.css.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"chips.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/chips.css.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,SAAS,g+TAgOrB,CAAA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const COMPOSER_CSS = "/* ---------- phone composer (< 768px) ---------- */\n\n/* The file picker the attachment button drives (S7) \u2014 hidden at EVERY width,\n deliberately OUTSIDE the phone media block below. A bare <input type=\"file\">\n renders as a native \"Choose Files\" control, and the slot wrapper around it is\n `display: contents`, so an un-hidden one becomes a flex item of the official\n tool row. Scoping this to < 768px once put that control in the DESKTOP\n composer (caught on a live deploy, 2026-08-17): the button that drives it is\n phone-only, but the input it drives is in the DOM at all widths. */\n[data-mobile-nav=\"attach-picker\"] {\n display: none !important;\n}\n/* The composer-chips strip (effects/model-sheet-extras.ts) \u2014 same\n every-width default as the picker above: the phone block re-shows it\n inside the dock rail; the desktop shell keeps none. */\n[data-mobile-nav=\"composer-chips\"] {\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 /* One line, always: the model pill is the only shrinkable item (its\n label ellipsizes), so wrapping would only ever push the send button\n to a second row (real-device report, 2026-08-25). */\n flex-wrap: nowrap !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 [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] {\n display: contents !important;\n }\n\n /* --- 2. the running order ---\n The attachment button (S3 placeholder, S7 wires it up) sits leftmost and\n is a PERMANENT resident now (#31): on DSH 0.2.x the standalone paperclip\n moved into the \"+\" command menu while its hidden <input type=\"file\">\n stayed in _tools, so the old host-attach stand-down probe matched on\n every host and the phone composer was left with no attach affordance at\n all. 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 /* DSH \u2264 0.1.5 still renders that second _add button (the standalone\n paperclip) as a direct _tools child; hide it below 768px so the row\n keeps exactly one attach control \u2014 ours, which uploads into the session\n workspace the phone can actually reach. On 0.2.x the only button in\n _tools is the first of its type, so this never matches. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > button[class$=\"_add\"]:nth-of-type(2) {\n display: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] {\n order: 3 !important;\n /* Rigid on purpose. This seat holds the permission chip, whose own\n wrapper (the plugin's element inside _modes) is `flex: 0 0 auto` and\n will NOT follow the seat down \u2014 so a shrinkable seat does not make the\n chip narrower, it just slices the seat out from under a chip that\n stays 44px wide, and the overflow lands on top of the model pill.\n Measured 2026-09-04 on DSH 0.1.2 at 390px: seat 90-120 (29.7px) with\n the chip still 90-134, model starting at 125 \u2014 a 9px overlap, exactly\n the \"the two chips touch\" report. With the seat rigid: seat 90-134,\n model 139-246, overlap 0; and at 320px the model pill shrinks to 52px\n and still nothing overlaps.\n This also restores what section 1 says out loud \u2014 the model pill is\n the ONLY shrinkable item in the row. `min-width: 0` is gone with the\n shrink: it only ever mattered as the companion of `flex: 0 1`. */\n flex: 0 0 auto !important;\n gap: 6px !important;\n }\n /* The elastic gap used to live here as `margin-right: auto`, which broke\n the moment a session had no model pill: a subagent session has none, so\n the gap vanished and the stop / send buttons bunched up in the middle of\n the row instead of sitting at the right edge (reported 2026-09-06). The\n gap belongs to the RIGHT group, not to the last item of the left one \u2014\n see the two rules below the order list. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) {\n order: 4 !important;\n /* A definite flex-basis (not auto) breaks the circular sizing between\n this seat and the trigger's max-width:100%: the seat's width comes\n from flex resolution, the trigger fills up to it, the label\n ellipsizes inside. Under nowrap pressure this is the only\n min-width:0 seat, so it absorbs all shrink. */\n flex: 0 1 min(48vw, 200px) !important;\n min-width: 0 !important;\n max-width: min(48vw, 200px) !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\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.right\"] > * {\n order: 5 !important;\n }\n /* ContextMeter. 0.1.7 moved it out of the row into ROOT's `_dock`, a\n line UNDER the card \u2014 on the phone that dropped the ring to the very\n bottom of the screen (user report, 2026-09-28). Lifted back beside send\n without moving any official node: ROOT becomes a one-cell grid and the\n dock shares the card's cell, pinned to the card's bottom-right corner,\n over a placeholder the row reserves in front of send (row::before at\n order 6, below). Anchoring to the card \u2014 not to ROOT's bottom edge \u2014\n matters: ROOT's bottom padding follows the safe-area inset, so an\n absolute `bottom` offset measured in the emulator sat low on a real\n iPhone (user screenshot, 2026-09-29). Vertical: the row's 8px bottom\n pad, 34px tall = the send button's box. Horizontal: row pad 8 + send 34\n + gap 5. The percentage is dropped on the phone (user call): ring only. */\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"]:has(> [class$=\"_dock\"] button) {\n display: grid !important;\n grid-template-columns: minmax(0, 1fr) !important;\n justify-items: center !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"]:has(> [class$=\"_dock\"] button) > [class$=\"_card\"] {\n grid-area: 1 / 1 !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] > [class$=\"_dock\"] {\n grid-area: 1 / 1 !important;\n align-self: end !important;\n justify-self: end !important;\n position: relative !important;\n z-index: 1;\n height: 34px !important;\n margin: 0 47px 8px 0 !important;\n padding: 0 !important;\n gap: 0 !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] > [class$=\"_dock\"] button[class$=\"_trigger\"] {\n padding: 8px !important;\n gap: 0 !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] > [class$=\"_dock\"] button[class$=\"_trigger\"] > span {\n display: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"]:has(> [class$=\"_dock\"] button) > [class$=\"_card\"] > [class$=\"_row\"]::before {\n content: '';\n order: 6;\n flex: 0 0 30px;\n margin-left: auto;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_primary\"] {\n order: 7 !important;\n }\n /* The elastic gap. It sits in front of whichever of the two right-hand\n items comes first, so the group hugs the right edge no matter what the\n left side happens to contain \u2014 with a model pill, without one (subagent\n sessions), with or without the third-party entries at order 5.\n Send carries `margin-left: auto` on its own; when the lifted ring's\n placeholder (row::before, above) precedes it, the placeholder carries\n the auto margin instead and the rule below takes it back off send \u2014\n two auto margins would SHARE the free space and open a second gap\n between the ring and send. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_primary\"] {\n margin-left: auto !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"]:has(> [class$=\"_dock\"] button) > [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_primary\"] {\n margin-left: 0 !important;\n }\n\n /* --- 3. permission as icon-only pill; model as name+effort pill ---\n (real-device round 2, 2026-08-17; model pill reworked 2026-08-25:\n the user wants to SEE which model/effort is active, so only the\n permission trigger stays icon-only \u2014 its label is hidden and it\n collapses to a plain ~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 display: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] button[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 /* Model trigger: name + effort pill (2026-08-25 user request \u2014 the\n icon-only form hid which model was active). The effort text must stay\n whole; the model NAME is the elastic part and ellipsizes. max-width\n caps the pill so the row's elastic gap survives extreme model ids;\n the label rules below override the <=1023px grow rules in\n layout.css.ts (flex 1 1 auto) that would otherwise pad the pill. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_trigger\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > button {\n background: var(--dsw-specific-selector, rgba(127, 127, 127, .12)) !important;\n height: 30px !important;\n width: auto !important;\n max-width: 100% !important;\n min-width: 0 !important;\n padding: 0 10px !important;\n gap: 4px !important;\n justify-content: flex-start !important;\n border-radius: 999px !important;\n touch-action: manipulation !important;\n font-size: 12px !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_trigger\"] > [class$=\"_triggerLabel\"] {\n display: block !important;\n flex: 0 1 auto !important;\n min-width: 0 !important;\n overflow: hidden !important;\n text-overflow: ellipsis !important;\n white-space: nowrap !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_trigger\"] > [class$=\"_triggerEffort\"] {\n display: block !important;\n flex: none !important;\n white-space: nowrap !important;\n }\n /* Third-party model selectors (\u2026codex-ui) render a bare <button> with no\n _trigger classes and text straight inside it; give that button the same\n shrink+truncate contract the official label gets, so a long model id\n ellipsizes instead of stretching the row. The official trigger also\n matches this (harmless: it already ellipsizes its own label). */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > button {\n max-width: 100% !important;\n min-width: 0 !important;\n overflow: hidden !important;\n text-overflow: ellipsis !important;\n white-space: nowrap !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 /* (2026-08-25) The model trigger's CSS-only Sparkle ::before icon is gone:\n the pill now shows the model name + effort text itself, so an icon would\n just eat label width. */\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 under the pill on \u2264 0.1.2; a body portal on 0.1.5+ \u2014 see MODEL_MENU).\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 /* T70: the SAME permission menu, portaled to body on DSH 0.2.0 \u2014 the\n descendant selector above cannot reach it there. The marker is written\n by effects/permission-sheet.ts: the trigger's click arms a short window\n and the first body-level menu to land inside it is ours \u2014 the host has\n many other portal Menus (preset pickers, preference rows, the sidebar's\n \u2026, open-in-app, deliverables, \u2026), and none opens through this trigger\n while no marked menu is up. */\n body > div[role=\"menu\"][data-zen-sheet=\"perm\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) {\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 /* 60 for the body-portaled menu too: the host gives it 1100 for desktop\n stacking, but nothing of ours or the host's sits between 60 and the\n sheet on a phone (hit-tested at four points on 0.1.5-rc.2, 2026-09-21),\n and 60 keeps it under the session-info sheet's 70 like the PERM one. */\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 The model panes are matched by ROLE too: on DSH 0.2.0 the selected\n option's class list ends in `_selected` (`\u2026_option \u2026_selected`), so the\n class-suffix selector missed exactly the checked row \u2014 it fell back to\n the host's 34px pill while its siblings got 48px. The root cells are\n role=menuitem, the options role=menuitemradio; the class forms stay for\n older hosts. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"] [role=\"menuitem\"],\n body > div[role=\"menu\"][data-zen-sheet=\"perm\"] [role=\"menuitem\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [role=\"menuitem\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [role=\"menuitemradio\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [class$=\"_option\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [class$=\"_cell\"] {\n min-height: 48px !important;\n border-radius: 12px !important;\n font-size: 15px !important;\n }\n /* --- 4a. third-party composer entries live in the dock's chip strip ---\n effects/model-sheet-extras.ts collects the composer's secondary controls\n (input.right, input.left, and anything a third party parks directly into\n _tools / _standardControls / _trailing / _row) and moves the REAL nodes\n into [data-mobile-nav=\"composer-chips\"] \u2014 a 26px horizontally\n scrollable strip the effect mounts at the head of the dock rail above\n the card. Always visible, one tap, no sheet in between.\n\n In the row: hidden, but ONLY once the effect has marked them. Two opt-in\n markers, both written by the effect: the input.right CONTAINER gets\n data-zen-sheet-extras (so an emptied wrapper leaves no flex gap), and\n every collected node gets data-zen-composer-extra. Visibility comes back\n the moment a node is parked (=\"open\") or the effect releases it (marker\n removed) \u2014 a version of the effect that declines to act, or never runs\n at all, leaves every control exactly where the host put it; nothing can\n end up hidden from both places at once (the 2026-09-06 review's rule). */\n [data-slot=\"conversation.input.right\"][data-zen-sheet-extras],\n [data-zen-composer-extra]:not([data-zen-composer-extra=\"open\"]) {\n display: none !important;\n }\n /* The strip itself: one 26px line at the rail's head (order 0, document\n order first via the effect's prepend), scrolling sideways when the\n collected controls overflow. Scoped to the dock CHILD \u2014 the generic\n marker also sits on the emptied input.right container in the row, where\n nothing but the opt-in hide above may touch it. This specificity (0,2,0)\n is what beats the section 7 `> *` cage wherever they overlap (overflow,\n max-width, radius are all !important there too). */\n [data-slot=\"conversation.input.dock\"] > [data-mobile-nav=\"composer-chips\"] {\n display: flex !important;\n flex-flow: row nowrap !important;\n align-items: center !important;\n gap: 6px !important;\n order: 0 !important;\n flex: 0 1 auto !important;\n min-width: 0 !important;\n max-width: 100% !important;\n min-height: 26px !important;\n max-height: 26px !important;\n border-radius: 0 !important;\n overflow-x: auto !important;\n overflow-y: hidden !important;\n scrollbar-width: none !important;\n }\n [data-slot=\"conversation.input.dock\"] > [data-mobile-nav=\"composer-chips\"]::-webkit-scrollbar {\n display: none !important;\n }\n /* Parked controls shrink from whatever the row allowed them to the rail's\n 26px pill scale: auto width again (no full-width rows here), tight\n height, and the chip font the rest of the rail uses. */\n [data-mobile-nav=\"composer-chips\"] > * {\n flex: 0 0 auto !important;\n order: 0 !important;\n display: inline-flex !important;\n align-items: center !important;\n min-height: 26px !important;\n max-height: 26px !important;\n width: auto !important;\n max-width: none !important;\n }\n [data-mobile-nav=\"composer-chips\"] button {\n box-sizing: border-box !important;\n height: 26px !important;\n min-height: 26px !important;\n max-height: 26px !important;\n width: auto !important;\n padding: 0 10px !important;\n gap: 5px !important;\n border-radius: 999px !important;\n font-size: 11.5px !important;\n line-height: 18px !important;\n white-space: nowrap !important;\n }\n /* The vision toggle is squeezed to a 28px icon-only button in the row\n (compat.css.ts); in the strip it is a 14px icon + text pill, so its\n label comes back. */\n [data-mobile-nav=\"composer-chips\"] [data-vision-router-mode-toggle] > span {\n display: inline !important;\n }\n [data-mobile-nav=\"composer-chips\"] [data-vision-router-mode-toggle] > svg:first-of-type {\n width: 14px !important;\n height: 14px !important;\n }\n /* The speed chip opens a menu of its own, anchored `bottom: 100%` to its\n trigger. Inside the strip \u2014 which is `overflow-x: auto`, so its content\n clips at the strip's box \u2014 that menu would be cut off. Same escape\n section 4 uses for the host's two menus: position: fixed leaves the clip\n behind and it lands as its own bottom sheet over the composer. */\n [class$=\"_composerSeat\"]:has([data-zen-sheet-extras] [role=\"menu\"]) {\n z-index: 60 !important;\n }\n [data-zen-sheet-extras] [role=\"menu\"] {\n position: fixed !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n top: auto !important;\n margin: 0 !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 /* Above the page content behind the seat (the seat is raised to 60 by\n the rule above while a parked menu is open), below the session-info\n sheet's 70 (info.css.ts). */\n z-index: 61 !important;\n box-shadow: 0 -8px 32px rgba(0, 0, 0, .18) !important;\n }\n [data-zen-sheet-extras] [role=\"menu\"] [role=\"menuitemradio\"] {\n min-height: 48px !important;\n border-radius: 12px !important;\n font-size: 15px !important;\n }\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\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_menu\"]) [data-chat-flow] + div,\n body:has(> [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [data-chat-flow] + div,\n /* T70: the portaled permission menu (marked by effects/permission-sheet.ts). */\n body:has(> div[role=\"menu\"][data-zen-sheet=\"perm\"]) [data-chat-flow] + div,\n /* T-issue30: an inline menu a parked chip opened (escaped to a fixed\n sheet by 4a). */\n body:has([data-zen-sheet-extras] [role=\"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 /* Pull the text in from the card's left border. Upstream insets the editing\n host by 16px while the control row below it starts 8px in (row padding,\n section 1), so the placeholder sat noticeably further out than the\n buttons under it and the field read as over-padded on a phone (reported\n 2026-09-06). 12px squares it with the 12px of clear space on the right,\n giving the text a symmetric box, and closes most of the gap with the\n button row. Anchored on the editing host's own contract attribute rather\n than a hashed class. */\n [data-slot=\"conversation.composer.bar\"] [data-composer-input] {\n padding-left: 12px !important;\n }\n\n /* --- 6. no divider above OR below the message list ---\n Instead of a rule the messages butt against, they fade near both edges.\n This used to be ONE mask-image on the message scroller; iOS WebKit\n turned that into fog over the header (user report 2026-08-23): a mask\n on a scroll container is applied in CONTENT coordinates there (the fade\n bands scroll away with the messages) and, worse, it breaks the\n container's own overflow clipping \u2014 content scrolled out of view keeps\n painting, straight over the header and status bar. Engine bug with no\n iOS escape hatch (every iOS browser is WebKit), so the fades moved off\n the scroller entirely:\n\n - fade-IN under the header: the header's own ::after, restyled into a\n 20px gradient strip \u2014 styles/header.css.ts (\"no header bottom line\").\n - fade-OUT above the composer: nothing of ours. The official composer\n seat already carries `background: linear-gradient(transparent 0,\n bg 36px)` on its sticky self (dsh-client-ui-conversation lib/\n client.js .wSkVaW_composerSeat) and that is the entire visible\n effect; the mask's own bottom band landed inside the safe-area\n padding below the card and painted nothing. If the bottom edge ever\n reads harsh, hang a ::before strip off the seat the same way the\n header does \u2014 do NOT put a mask back on the scroller.\n\n Painted strips instead of a mask is a fair trade only because the page\n background is one flat color \u2014 which it is (--dsw-alias-bg-base). */\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 /* The dock is itself a flex ITEM of the composer stack, and the\n new-session hero pins that stack to a fixed height. overflow-x:auto\n above makes this a scroll container, which drops a flex item's\n automatic minimum size to 0 \u2014 so the hero squeezed the dock to ~5px\n and the attachment chips slid under the card drawn after it (only the\n top sliver of a thumbnail showed; reported from a real phone,\n 2026-08-26). Refuse the squeeze: natural height, the stack centers a\n few px lower instead. In-chat the column is not height-pinned and this\n is a no-op. */\n flex-shrink: 0 !important;\n }\n [data-slot=\"conversation.input.dock\"]::-webkit-scrollbar {\n display: none !important;\n }\n [data-slot=\"conversation.input.dock\"] > * {\n flex: 0 0 auto !important;\n max-width: 70% !important;\n min-height: 26px !important;\n max-height: 26px !important;\n border-radius: 999px !important;\n font-size: 11.5px !important;\n line-height: 18px !important;\n overflow: hidden !important;\n }\n /* The git-graph chip carries a 34px tablet target from misc.css; that\n selector is more specific, so restate it at the phone breakpoint. */\n [data-slot=\"conversation.input.dock\"] [data-gitgraph-chip-anchor] [data-gitgraph-chip] {\n min-height: 26px !important;\n padding: 0 10px !important;\n font-size: 11.5px !important;\n }\n /* --- 7a. the native to-do card opts OUT of the 26px pill cage ---\n conversation.input.dock is not only a chip rail: DSH's own TodoPanel\n (dsh-client-ui-conversation) registers here, and it is an expandable\n card, not a pill \u2014 tapping its header renders the to-do list INSIDE\n itself. The cage's max-height:26px + overflow:hidden left that list\n rendered but clipped to nothing, so on a phone the bar looked dead:\n aria-expanded flipped, 8 rows mounted at 216px, and the card still\n painted 26px tall (measured live, 2026-08-20). Give it the official\n geometry back \u2014 full row, natural height, 12px radius. Its list caps\n itself at 180px with its own scroller, so this cannot run into the\n composer. Anchored on the official data-testid, a stable contract\n marker rather than a hashed class name. */\n [data-slot=\"conversation.input.dock\"] > [data-testid=\"todo-panel\"] {\n /* A whole line to itself. It shared line 0 with the pills until the goal\n bar turned up (2026-09-06): two full-width things on one nowrap line\n meant the to-do header shrank to a stub and the goal bar ran off the\n right edge. Both are status strips you read at a glance, so they stack\n \u2014 see the line policy in 7c. */\n flex: 1 0 100% !important;\n order: 1 !important;\n min-width: 0 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n overflow: visible !important;\n border-radius: 12px !important;\n /* The cage shrinks dock text to 11.5px/18px; the card's rows inherit it\n and turn cramped. Restore the official 13px body scale. */\n font-size: 13px !important;\n line-height: normal !important;\n }\n\n /* --- 7b. attachment chips (S7.1) ---\n Our own dock entry opts OUT of the 26px pill cage above: an image chip is\n a 48px tile. The row sits tight under the card's top edge, so the chips\n read as part of the composer rather than as a floating strip.\n\n It also takes the LAST line of its own (line policy in 7c): a 48px tile\n beside the to-do chip on one nowrap line squeezed that chip down to an\n unreadable stub, with a long file name pushing the rest off-screen\n (reported 2026-08-22). Its own chips wrap rather than overflow, so\n several attachments stack instead of scrolling out of reach. */\n [data-slot=\"conversation.input.dock\"] > [data-mobile-nav=\"attach-chips\"] {\n display: flex !important;\n flex: 1 0 100% !important;\n flex-wrap: wrap !important;\n order: 4 !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\n /* --- 7b2. the T34 remote-status banner opts OUT of the cage ---\n Same class of entry as the to-do card (7a): a full-width status strip,\n not a pill. dsh-zen-remote's remote banner registers into this dock at\n order 15 when a remote session is offline or closed (T34) \u2014 the 26px\n cage would clip the copy to one unreadable sliver. A whole line of its\n own, natural height. */\n [data-slot=\"conversation.input.dock\"] > [data-zen-remote=\"remote-banner\"] {\n flex: 1 0 100% !important;\n order: 2 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n overflow: visible !important;\n border-radius: 12px !important;\n font-size: 12px !important;\n line-height: 18px !important;\n }\n\n /* --- 7c. the queue strip opts OUT of the cage, on a line of its own ---\n Third entry that is not a pill. conversation.input.dock is a `list` slot\n and DSH's own QueueDock registers into it at order 20 (id `queue`,\n dsh-client-ui-conversation) \u2014 a 36px row per queued message, or a\n collapsible \"N \u6761\u6392\u961F\u6D88\u606F\" header once there is more than one. The 26px\n cage above clipped it to a sliver: on a phone a queued message showed as\n a grey stub poking out from behind the input card, unreadable and with\n its edit / delete / send buttons cut off (reported 2026-08-22, same\n failure the to-do card hit in 7a).\n\n Two things are restored. Geometry: the official row height back, and the\n dock's own composer-column sizing dropped \u2014 its width/max-width are\n calc()s over the composer card variables and its bottom margin is\n negative by design (upstream tucks the strip UNDER the card), none of\n which survives being a flex item of our chip rail.\n\n Layout \u2014 the line policy for the whole rail, now that three things want\n room on it. The pills (the to-do card, a branch chip, whatever else\n registers) keep order 0 and share the top line, still scrolling\n horizontally. The queue takes the next line (order 1). The attachment\n chips take the last one (order 2, 7b), so what the user is about to send\n sits closest to the input it will be sent from. The rail only wraps\n while one of the two full-width entries exists, so a session with\n neither keeps the original single scrolling chip line. */\n [data-slot=\"conversation.input.dock\"]:has(> [data-queue-dock], > [data-mobile-nav=\"attach-chips\"], > [data-testid=\"todo-panel\"], > [data-goal-bar]) {\n flex-wrap: wrap !important;\n }\n [data-slot=\"conversation.input.dock\"] > [data-queue-dock] {\n flex: 1 0 100% !important;\n order: 3 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n margin: 0 !important;\n padding: 0 !important;\n border-radius: 0 !important;\n overflow: visible !important;\n font-size: 13px !important;\n line-height: normal !important;\n }\n /* dsh-client-ui-goal's status strip (its own `data-goal-bar` marker; it\n registers into this dock at order 10). Fourth thing here that is not a\n pill: a row carrying the objective text plus pause / edit / delete\n buttons, so the 26px cage clipped its buttons off the right edge and the\n nowrap line pushed what was left off screen (reported 2026-09-06). Same\n treatment the queue gets \u2014 a line of its own, official geometry back. */\n [data-slot=\"conversation.input.dock\"] > [data-goal-bar] {\n flex: 1 0 100% !important;\n order: 2 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n border-radius: 12px !important;\n overflow: visible !important;\n font-size: 13px !important;\n line-height: normal !important;\n }\n\n /* The panel is the dock's only child; upstream rounds its top corners only\n and drops the bottom border, because there it is half-hidden behind the\n card. On its own line it is a free-standing strip, so close it up. */\n [data-slot=\"conversation.input.dock\"] > [data-queue-dock] > * {\n border-radius: 12px !important;\n }\n [data-slot=\"conversation.input.dock\"] > [data-queue-dock] > *::after {\n border-bottom: 1px solid var(--dsw-alias-border-l1) !important;\n }\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 [data-slot=\"conversation.composer.dock\"] > [data-composer-stat] {\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 /* S10 keyboard avoid: effects/keyboard-avoid.ts mirrors the band of the\n layout viewport hidden behind the software keyboard into --mnav-kb-lift\n and stamps the html attribute while (and only while) it is non-zero \u2014\n a resting transform, even translateY(0), would silently become the\n containing block for any fixed-position descendant of the bar. In every\n environment where the browser's own focus-reveal works the attribute is\n simply never set.\n The target is the slot's _root CHILD, not the slot wrapper: the wrapper\n is display:contents \u2014 it generates no box, so a transform on it is a\n silent no-op (found live on-device via CDP, 2026-08-21; the variable and\n attribute were set and nothing moved). */\n html[data-mnav-kb] [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] {\n transform: translateY(calc(-1 * var(--mnav-kb-lift, 0px)));\n }\n}\n\n/* The attachment button and its preview row only exist for the phone shell.\n Both render at every width (the slots are not breakpoint-aware), so both\n need hiding here \u2014 the dock-row rules that shape the chips live in the\n < 768px block and would otherwise leave a bare, unstyled chip line in the\n desktop composer. */\n@media (min-width: 768px) {\n [data-mobile-nav=\"attach\"],\n [data-mobile-nav=\"attach-chips\"] {\n display: none !important;\n }\n}\n";
|
|
1
|
+
export declare const COMPOSER_CSS = "/* ---------- phone composer (< 768px) ---------- */\n\n/* The file picker the attachment button drives (S7) \u2014 hidden at EVERY width,\n deliberately OUTSIDE the phone media block below. A bare <input type=\"file\">\n renders as a native \"Choose Files\" control, and the slot wrapper around it is\n `display: contents`, so an un-hidden one becomes a flex item of the official\n tool row. Scoping this to < 768px once put that control in the DESKTOP\n composer (caught on a live deploy, 2026-08-17): the button that drives it is\n phone-only, but the input it drives is in the DOM at all widths. */\n[data-mobile-nav=\"attach-picker\"] {\n display: none !important;\n}\n/* The composer-chips strip (effects/model-sheet-extras.ts) \u2014 same\n every-width default as the picker above: the phone block re-shows it\n inside the dock rail; the desktop shell keeps none. */\n[data-mobile-nav=\"composer-chips\"] {\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 /* One line, always: the model pill is the only shrinkable item (its\n label ellipsizes), so wrapping would only ever push the send button\n to a second row (real-device report, 2026-08-25). */\n flex-wrap: nowrap !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 [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] {\n display: contents !important;\n }\n\n /* --- 2. the running order ---\n The attachment button (S3 placeholder, S7 wires it up) sits leftmost and\n is a PERMANENT resident now (#31): on DSH 0.2.x the standalone paperclip\n moved into the \"+\" command menu while its hidden <input type=\"file\">\n stayed in _tools, so the old host-attach stand-down probe matched on\n every host and the phone composer was left with no attach affordance at\n all. 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 /* DSH \u2264 0.1.5 still renders that second _add button (the standalone\n paperclip) as a direct _tools child; hide it below 768px so the row\n keeps exactly one attach control \u2014 ours, which uploads into the session\n workspace the phone can actually reach. On 0.2.x the only button in\n _tools is the first of its type, so this never matches. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > button[class$=\"_add\"]:nth-of-type(2) {\n display: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] {\n order: 3 !important;\n /* Rigid on purpose. This seat holds the permission chip, whose own\n wrapper (the plugin's element inside _modes) is `flex: 0 0 auto` and\n will NOT follow the seat down \u2014 so a shrinkable seat does not make the\n chip narrower, it just slices the seat out from under a chip that\n stays 44px wide, and the overflow lands on top of the model pill.\n Measured 2026-09-04 on DSH 0.1.2 at 390px: seat 90-120 (29.7px) with\n the chip still 90-134, model starting at 125 \u2014 a 9px overlap, exactly\n the \"the two chips touch\" report. With the seat rigid: seat 90-134,\n model 139-246, overlap 0; and at 320px the model pill shrinks to 52px\n and still nothing overlaps.\n This also restores what section 1 says out loud \u2014 the model pill is\n the ONLY shrinkable item in the row. `min-width: 0` is gone with the\n shrink: it only ever mattered as the companion of `flex: 0 1`. */\n flex: 0 0 auto !important;\n gap: 6px !important;\n }\n /* The elastic gap used to live here as `margin-right: auto`, which broke\n the moment a session had no model pill: a subagent session has none, so\n the gap vanished and the stop / send buttons bunched up in the middle of\n the row instead of sitting at the right edge (reported 2026-09-06). The\n gap belongs to the RIGHT group, not to the last item of the left one \u2014\n see the two rules below the order list. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) {\n order: 4 !important;\n /* A definite flex-basis (not auto) breaks the circular sizing between\n this seat and the trigger's max-width:100%: the seat's width comes\n from flex resolution, the trigger fills up to it, the label\n ellipsizes inside. Under nowrap pressure this is the only\n min-width:0 seat, so it absorbs all shrink. */\n flex: 0 1 min(48vw, 200px) !important;\n min-width: 0 !important;\n max-width: min(48vw, 200px) !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\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.right\"] > * {\n order: 5 !important;\n }\n /* ContextMeter. 0.1.7 moved it out of the row into ROOT's `_dock`, a\n line UNDER the card \u2014 on the phone that dropped the ring to the very\n bottom of the screen (user report, 2026-09-28). Lifted back beside send\n without moving any official node: ROOT becomes a one-cell grid and the\n dock shares the card's cell, pinned to the card's bottom-right corner,\n over a placeholder the row reserves in front of send (row::before at\n order 6, below). Anchoring to the card \u2014 not to ROOT's bottom edge \u2014\n matters: ROOT's bottom padding follows the safe-area inset, so an\n absolute `bottom` offset measured in the emulator sat low on a real\n iPhone (user screenshot, 2026-09-29). Vertical: the row's 8px bottom\n pad, 34px tall = the send button's box. Horizontal: row pad 8 + send 34\n + gap 5. The percentage is dropped on the phone (user call): ring only. */\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"]:has(> [class$=\"_dock\"] button) {\n display: grid !important;\n grid-template-columns: minmax(0, 1fr) !important;\n justify-items: center !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"]:has(> [class$=\"_dock\"] button) > [class$=\"_card\"] {\n grid-area: 1 / 1 !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] > [class$=\"_dock\"] {\n grid-area: 1 / 1 !important;\n align-self: end !important;\n justify-self: end !important;\n position: relative !important;\n z-index: 1;\n height: 34px !important;\n margin: 0 47px 8px 0 !important;\n padding: 0 !important;\n gap: 0 !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] > [class$=\"_dock\"] button[class$=\"_trigger\"] {\n padding: 8px !important;\n gap: 0 !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] > [class$=\"_dock\"] button[class$=\"_trigger\"] > span {\n display: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"]:has(> [class$=\"_dock\"] button) > [class$=\"_card\"] > [class$=\"_row\"]::before {\n content: '';\n order: 6;\n flex: 0 0 30px;\n margin-left: auto;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_primary\"] {\n order: 7 !important;\n }\n /* The elastic gap. It sits in front of whichever of the two right-hand\n items comes first, so the group hugs the right edge no matter what the\n left side happens to contain \u2014 with a model pill, without one (subagent\n sessions), with or without the third-party entries at order 5.\n Send carries `margin-left: auto` on its own; when the lifted ring's\n placeholder (row::before, above) precedes it, the placeholder carries\n the auto margin instead and the rule below takes it back off send \u2014\n two auto margins would SHARE the free space and open a second gap\n between the ring and send. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_primary\"] {\n margin-left: auto !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"]:has(> [class$=\"_dock\"] button) > [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_primary\"] {\n margin-left: 0 !important;\n }\n\n /* --- 3. permission as icon-only pill; model as name+effort pill ---\n (real-device round 2, 2026-08-17; model pill reworked 2026-08-25:\n the user wants to SEE which model/effort is active, so only the\n permission trigger stays icon-only \u2014 its label is hidden and it\n collapses to a plain ~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 display: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] button[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 /* Model trigger: name + effort pill (2026-08-25 user request \u2014 the\n icon-only form hid which model was active). The effort text must stay\n whole; the model NAME is the elastic part and ellipsizes. max-width\n caps the pill so the row's elastic gap survives extreme model ids;\n the label rules below override the <=1023px grow rules in\n layout.css.ts (flex 1 1 auto) that would otherwise pad the pill. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_trigger\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > button {\n background: var(--dsw-specific-selector, rgba(127, 127, 127, .12)) !important;\n height: 30px !important;\n width: auto !important;\n max-width: 100% !important;\n min-width: 0 !important;\n padding: 0 10px !important;\n gap: 4px !important;\n justify-content: flex-start !important;\n border-radius: 999px !important;\n touch-action: manipulation !important;\n font-size: 12px !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_trigger\"] > [class$=\"_triggerLabel\"] {\n display: block !important;\n flex: 0 1 auto !important;\n min-width: 0 !important;\n overflow: hidden !important;\n text-overflow: ellipsis !important;\n white-space: nowrap !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_trigger\"] > [class$=\"_triggerEffort\"] {\n display: block !important;\n flex: none !important;\n white-space: nowrap !important;\n }\n /* Third-party model selectors (\u2026codex-ui) render a bare <button> with no\n _trigger classes and text straight inside it; give that button the same\n shrink+truncate contract the official label gets, so a long model id\n ellipsizes instead of stretching the row. The official trigger also\n matches this (harmless: it already ellipsizes its own label). */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > button {\n max-width: 100% !important;\n min-width: 0 !important;\n overflow: hidden !important;\n text-overflow: ellipsis !important;\n white-space: nowrap !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 /* (2026-08-25) The model trigger's CSS-only Sparkle ::before icon is gone:\n the pill now shows the model name + effort text itself, so an icon would\n just eat label width. */\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 under the pill on \u2264 0.1.2; a body portal on 0.1.5+ \u2014 see MODEL_MENU).\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 /* T70: the SAME permission menu, portaled to body on DSH 0.2.0 \u2014 the\n descendant selector above cannot reach it there. The marker is written\n by effects/permission-sheet.ts: the trigger's click arms a short window\n and the first body-level menu to land inside it is ours \u2014 the host has\n many other portal Menus (preset pickers, preference rows, the sidebar's\n \u2026, open-in-app, deliverables, \u2026), and none opens through this trigger\n while no marked menu is up. */\n body > div[role=\"menu\"][data-zen-sheet=\"perm\"],\n /* UX-3: the hero page's workspace menu, marked by the same effect through\n its own two triggers (the WorkspaceChip and the placeholder card) with\n the workspace value \u2014 a distinct marker so the pick that starts the\n workspace switch (effects/permission-sheet.ts) can key on THIS sheet\n without ever touching permission options. */\n body > div[role=\"menu\"][data-zen-sheet=\"workspace\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) {\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 /* 60 for the body-portaled menu too: the host gives it 1100 for desktop\n stacking, but nothing of ours or the host's sits between 60 and the\n sheet on a phone (hit-tested at four points on 0.1.5-rc.2, 2026-09-21),\n and 60 keeps it under the session-info sheet's 70 like the PERM one. */\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 The model panes are matched by ROLE too: on DSH 0.2.0 the selected\n option's class list ends in `_selected` (`\u2026_option \u2026_selected`), so the\n class-suffix selector missed exactly the checked row \u2014 it fell back to\n the host's 34px pill while its siblings got 48px. The root cells are\n role=menuitem, the options role=menuitemradio; the class forms stay for\n older hosts. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"] [role=\"menuitem\"],\n body > div[role=\"menu\"][data-zen-sheet=\"perm\"] [role=\"menuitem\"],\n body > div[role=\"menu\"][data-zen-sheet=\"workspace\"] [role=\"menuitem\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [role=\"menuitem\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [role=\"menuitemradio\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [class$=\"_option\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [class$=\"_cell\"] {\n min-height: 48px !important;\n border-radius: 12px !important;\n font-size: 15px !important;\n }\n /* --- 4a. third-party composer entries live in the dock's chip strip ---\n effects/model-sheet-extras.ts collects the composer's secondary controls\n (input.right, input.left, and anything a third party parks directly into\n _tools / _standardControls / _trailing / _row) and moves the REAL nodes\n into [data-mobile-nav=\"composer-chips\"] \u2014 a 26px horizontally\n scrollable strip the effect mounts at the head of the dock rail above\n the card. Always visible, one tap, no sheet in between.\n\n In the row: hidden, but ONLY once the effect has marked them. Two opt-in\n markers, both written by the effect: the input.right CONTAINER gets\n data-zen-sheet-extras (so an emptied wrapper leaves no flex gap), and\n every collected node gets data-zen-composer-extra. Visibility comes back\n the moment a node is parked (=\"open\") or the effect releases it (marker\n removed) \u2014 a version of the effect that declines to act, or never runs\n at all, leaves every control exactly where the host put it; nothing can\n end up hidden from both places at once (the 2026-09-06 review's rule). */\n [data-slot=\"conversation.input.right\"][data-zen-sheet-extras],\n [data-zen-composer-extra]:not([data-zen-composer-extra=\"open\"]) {\n display: none !important;\n }\n /* The strip itself: one 26px line at the rail's head (order 0, document\n order first via the effect's prepend), scrolling sideways when the\n collected controls overflow. Scoped to the dock CHILD \u2014 the generic\n marker also sits on the emptied input.right container in the row, where\n nothing but the opt-in hide above may touch it. This specificity (0,2,0)\n is what beats the section 7 `> *` cage wherever they overlap (overflow,\n max-width, radius are all !important there too). */\n [data-slot=\"conversation.input.dock\"] > [data-mobile-nav=\"composer-chips\"] {\n display: flex !important;\n flex-flow: row nowrap !important;\n align-items: center !important;\n gap: 6px !important;\n order: 0 !important;\n flex: 0 1 auto !important;\n min-width: 0 !important;\n max-width: 100% !important;\n min-height: 26px !important;\n max-height: 26px !important;\n border-radius: 0 !important;\n overflow-x: auto !important;\n overflow-y: hidden !important;\n scrollbar-width: none !important;\n }\n [data-slot=\"conversation.input.dock\"] > [data-mobile-nav=\"composer-chips\"]::-webkit-scrollbar {\n display: none !important;\n }\n /* Parked controls shrink from whatever the row allowed them to the rail's\n 26px pill scale: auto width again (no full-width rows here), tight\n height, and the chip font the rest of the rail uses. */\n [data-mobile-nav=\"composer-chips\"] > * {\n flex: 0 0 auto !important;\n order: 0 !important;\n display: inline-flex !important;\n align-items: center !important;\n min-height: 26px !important;\n max-height: 26px !important;\n width: auto !important;\n max-width: none !important;\n }\n [data-mobile-nav=\"composer-chips\"] button {\n box-sizing: border-box !important;\n height: 26px !important;\n min-height: 26px !important;\n max-height: 26px !important;\n width: auto !important;\n padding: 0 10px !important;\n gap: 5px !important;\n border-radius: 999px !important;\n font-size: 11.5px !important;\n line-height: 18px !important;\n white-space: nowrap !important;\n }\n /* The vision toggle is squeezed to a 28px icon-only button in the row\n (compat.css.ts); in the strip it is a 14px icon + text pill, so its\n label comes back. */\n [data-mobile-nav=\"composer-chips\"] [data-vision-router-mode-toggle] > span {\n display: inline !important;\n }\n [data-mobile-nav=\"composer-chips\"] [data-vision-router-mode-toggle] > svg:first-of-type {\n width: 14px !important;\n height: 14px !important;\n }\n /* The speed chip opens a menu of its own, anchored `bottom: 100%` to its\n trigger. Inside the strip \u2014 which is `overflow-x: auto`, so its content\n clips at the strip's box \u2014 that menu would be cut off. Same escape\n section 4 uses for the host's two menus: position: fixed leaves the clip\n behind and it lands as its own bottom sheet over the composer. */\n [class$=\"_composerSeat\"]:has([data-zen-sheet-extras] [role=\"menu\"]) {\n z-index: 60 !important;\n }\n [data-zen-sheet-extras] [role=\"menu\"] {\n position: fixed !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n top: auto !important;\n margin: 0 !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 /* Above the page content behind the seat (the seat is raised to 60 by\n the rule above while a parked menu is open), below the session-info\n sheet's 70 (info.css.ts). */\n z-index: 61 !important;\n box-shadow: 0 -8px 32px rgba(0, 0, 0, .18) !important;\n }\n [data-zen-sheet-extras] [role=\"menu\"] [role=\"menuitemradio\"] {\n min-height: 48px !important;\n border-radius: 12px !important;\n font-size: 15px !important;\n }\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\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > *:not([data-mobile-nav]) > [class$=\"_menu\"]) [data-chat-flow] + div,\n body:has(> [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [data-chat-flow] + div,\n /* T70: the portaled permission menu (marked by effects/permission-sheet.ts). */\n body:has(> div[role=\"menu\"][data-zen-sheet=\"perm\"]) [data-chat-flow] + div,\n /* UX-3: the portaled hero workspace menu, same treatment. */\n body:has(> div[role=\"menu\"][data-zen-sheet=\"workspace\"]) [data-chat-flow] + div,\n /* T-issue30: an inline menu a parked chip opened (escaped to a fixed\n sheet by 4a). */\n body:has([data-zen-sheet-extras] [role=\"menu\"]) [data-chat-flow] + div {\n display: none !important;\n }\n /* --- 4c. UX-3: the workspace switch lock ---\n Between picking a workspace in the hero sheet and the new blank session\n landing (selectWorkspace \u2192 POST /api/session/create \u2192 replaceMain\n remounts the conversation), the composer still belongs to the OLD blank\n session: a message sent in that window lands there and is then wiped by\n the remount, bouncing the UI back to the \"choose a workspace\" state.\n effects/permission-sheet.ts stamps data-zen-ws-switching on <html> for\n exactly that window \u2014 the lock releases when the conversation root's\n data-conversation-session has moved and the placeholder card is gone,\n with a 1.5 s cap \u2014 and the dim (not just pointer-events) makes the\n freeze visible instead of reading as a dead input. */\n html[data-zen-ws-switching] [data-slot=\"conversation.composer.bar\"] {\n pointer-events: none !important;\n opacity: .6 !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 /* Pull the text in from the card's left border. Upstream insets the editing\n host by 16px while the control row below it starts 8px in (row padding,\n section 1), so the placeholder sat noticeably further out than the\n buttons under it and the field read as over-padded on a phone (reported\n 2026-09-06). 12px squares it with the 12px of clear space on the right,\n giving the text a symmetric box, and closes most of the gap with the\n button row. Anchored on the editing host's own contract attribute rather\n than a hashed class. */\n [data-slot=\"conversation.composer.bar\"] [data-composer-input] {\n padding-left: 12px !important;\n }\n\n /* --- 6. no divider above OR below the message list ---\n Instead of a rule the messages butt against, they fade near both edges.\n This used to be ONE mask-image on the message scroller; iOS WebKit\n turned that into fog over the header (user report 2026-08-23): a mask\n on a scroll container is applied in CONTENT coordinates there (the fade\n bands scroll away with the messages) and, worse, it breaks the\n container's own overflow clipping \u2014 content scrolled out of view keeps\n painting, straight over the header and status bar. Engine bug with no\n iOS escape hatch (every iOS browser is WebKit), so the fades moved off\n the scroller entirely:\n\n - fade-IN under the header: the header's own ::after, restyled into a\n 20px gradient strip \u2014 styles/header.css.ts (\"no header bottom line\").\n - fade-OUT above the composer: nothing of ours. The official composer\n seat already carries `background: linear-gradient(transparent 0,\n bg 36px)` on its sticky self (dsh-client-ui-conversation lib/\n client.js .wSkVaW_composerSeat) and that is the entire visible\n effect; the mask's own bottom band landed inside the safe-area\n padding below the card and painted nothing. If the bottom edge ever\n reads harsh, hang a ::before strip off the seat the same way the\n header does \u2014 do NOT put a mask back on the scroller.\n\n Painted strips instead of a mask is a fair trade only because the page\n background is one flat color \u2014 which it is (--dsw-alias-bg-base). */\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 /* The dock is itself a flex ITEM of the composer stack, and the\n new-session hero pins that stack to a fixed height. overflow-x:auto\n above makes this a scroll container, which drops a flex item's\n automatic minimum size to 0 \u2014 so the hero squeezed the dock to ~5px\n and the attachment chips slid under the card drawn after it (only the\n top sliver of a thumbnail showed; reported from a real phone,\n 2026-08-26). Refuse the squeeze: natural height, the stack centers a\n few px lower instead. In-chat the column is not height-pinned and this\n is a no-op. */\n flex-shrink: 0 !important;\n }\n [data-slot=\"conversation.input.dock\"]::-webkit-scrollbar {\n display: none !important;\n }\n [data-slot=\"conversation.input.dock\"] > * {\n flex: 0 0 auto !important;\n max-width: 70% !important;\n min-height: 26px !important;\n max-height: 26px !important;\n border-radius: 999px !important;\n font-size: 11.5px !important;\n line-height: 18px !important;\n overflow: hidden !important;\n }\n /* The git-graph chip carries a 34px tablet target from misc.css; that\n selector is more specific, so restate it at the phone breakpoint. */\n [data-slot=\"conversation.input.dock\"] [data-gitgraph-chip-anchor] [data-gitgraph-chip] {\n min-height: 26px !important;\n padding: 0 10px !important;\n font-size: 11.5px !important;\n }\n /* --- 7a. the native to-do card opts OUT of the 26px pill cage ---\n conversation.input.dock is not only a chip rail: DSH's own TodoPanel\n (dsh-client-ui-conversation) registers here, and it is an expandable\n card, not a pill \u2014 tapping its header renders the to-do list INSIDE\n itself. The cage's max-height:26px + overflow:hidden left that list\n rendered but clipped to nothing, so on a phone the bar looked dead:\n aria-expanded flipped, 8 rows mounted at 216px, and the card still\n painted 26px tall (measured live, 2026-08-20). Give it the official\n geometry back \u2014 full row, natural height, 12px radius. Its list caps\n itself at 180px with its own scroller, so this cannot run into the\n composer. Anchored on the official data-testid, a stable contract\n marker rather than a hashed class name. */\n [data-slot=\"conversation.input.dock\"] > [data-testid=\"todo-panel\"] {\n /* A whole line to itself. It shared line 0 with the pills until the goal\n bar turned up (2026-09-06): two full-width things on one nowrap line\n meant the to-do header shrank to a stub and the goal bar ran off the\n right edge. Both are status strips you read at a glance, so they stack\n \u2014 see the line policy in 7c. */\n flex: 1 0 100% !important;\n order: 1 !important;\n min-width: 0 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n overflow: visible !important;\n border-radius: 12px !important;\n /* The cage shrinks dock text to 11.5px/18px; the card's rows inherit it\n and turn cramped. Restore the official 13px body scale. */\n font-size: 13px !important;\n line-height: normal !important;\n }\n\n /* --- 7b. attachment chips (S7.1) ---\n Our own dock entry opts OUT of the 26px pill cage above: an image chip is\n a 48px tile. The row sits tight under the card's top edge, so the chips\n read as part of the composer rather than as a floating strip.\n\n It also takes the LAST line of its own (line policy in 7c): a 48px tile\n beside the to-do chip on one nowrap line squeezed that chip down to an\n unreadable stub, with a long file name pushing the rest off-screen\n (reported 2026-08-22). Its own chips wrap rather than overflow, so\n several attachments stack instead of scrolling out of reach. */\n [data-slot=\"conversation.input.dock\"] > [data-mobile-nav=\"attach-chips\"] {\n display: flex !important;\n flex: 1 0 100% !important;\n flex-wrap: wrap !important;\n order: 4 !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\n /* --- 7b2. the T34 remote-status banner opts OUT of the cage ---\n Same class of entry as the to-do card (7a): a full-width status strip,\n not a pill. dsh-zen-remote's remote banner registers into this dock at\n order 15 when a remote session is offline or closed (T34) \u2014 the 26px\n cage would clip the copy to one unreadable sliver. A whole line of its\n own, natural height. */\n [data-slot=\"conversation.input.dock\"] > [data-zen-remote=\"remote-banner\"] {\n flex: 1 0 100% !important;\n order: 2 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n overflow: visible !important;\n border-radius: 12px !important;\n font-size: 12px !important;\n line-height: 18px !important;\n }\n\n /* --- 7c. the queue strip opts OUT of the cage, on a line of its own ---\n Third entry that is not a pill. conversation.input.dock is a `list` slot\n and DSH's own QueueDock registers into it at order 20 (id `queue`,\n dsh-client-ui-conversation) \u2014 a 36px row per queued message, or a\n collapsible \"N \u6761\u6392\u961F\u6D88\u606F\" header once there is more than one. The 26px\n cage above clipped it to a sliver: on a phone a queued message showed as\n a grey stub poking out from behind the input card, unreadable and with\n its edit / delete / send buttons cut off (reported 2026-08-22, same\n failure the to-do card hit in 7a).\n\n Two things are restored. Geometry: the official row height back, and the\n dock's own composer-column sizing dropped \u2014 its width/max-width are\n calc()s over the composer card variables and its bottom margin is\n negative by design (upstream tucks the strip UNDER the card), none of\n which survives being a flex item of our chip rail.\n\n Layout \u2014 the line policy for the whole rail, now that three things want\n room on it. The pills (the to-do card, a branch chip, whatever else\n registers) keep order 0 and share the top line, still scrolling\n horizontally. The queue takes the next line (order 1). The attachment\n chips take the last one (order 2, 7b), so what the user is about to send\n sits closest to the input it will be sent from. The rail only wraps\n while one of the two full-width entries exists, so a session with\n neither keeps the original single scrolling chip line. */\n [data-slot=\"conversation.input.dock\"]:has(> [data-queue-dock], > [data-mobile-nav=\"attach-chips\"], > [data-testid=\"todo-panel\"], > [data-goal-bar]) {\n flex-wrap: wrap !important;\n }\n [data-slot=\"conversation.input.dock\"] > [data-queue-dock] {\n flex: 1 0 100% !important;\n order: 3 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n margin: 0 !important;\n padding: 0 !important;\n border-radius: 0 !important;\n overflow: visible !important;\n font-size: 13px !important;\n line-height: normal !important;\n }\n /* dsh-client-ui-goal's status strip (its own `data-goal-bar` marker; it\n registers into this dock at order 10). Fourth thing here that is not a\n pill: a row carrying the objective text plus pause / edit / delete\n buttons, so the 26px cage clipped its buttons off the right edge and the\n nowrap line pushed what was left off screen (reported 2026-09-06). Same\n treatment the queue gets \u2014 a line of its own, official geometry back. */\n [data-slot=\"conversation.input.dock\"] > [data-goal-bar] {\n flex: 1 0 100% !important;\n order: 2 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n border-radius: 12px !important;\n overflow: visible !important;\n font-size: 13px !important;\n line-height: normal !important;\n }\n\n /* The panel is the dock's only child; upstream rounds its top corners only\n and drops the bottom border, because there it is half-hidden behind the\n card. On its own line it is a free-standing strip, so close it up. */\n [data-slot=\"conversation.input.dock\"] > [data-queue-dock] > * {\n border-radius: 12px !important;\n }\n [data-slot=\"conversation.input.dock\"] > [data-queue-dock] > *::after {\n border-bottom: 1px solid var(--dsw-alias-border-l1) !important;\n }\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 [data-slot=\"conversation.composer.dock\"] > [data-composer-stat] {\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 /* S10 keyboard avoid: effects/keyboard-avoid.ts mirrors the band of the\n layout viewport hidden behind the software keyboard into --mnav-kb-lift\n and stamps the html attribute while (and only while) it is non-zero \u2014\n a resting transform, even translateY(0), would silently become the\n containing block for any fixed-position descendant of the bar. In every\n environment where the browser's own focus-reveal works the attribute is\n simply never set.\n The target is the slot's _root CHILD, not the slot wrapper: the wrapper\n is display:contents \u2014 it generates no box, so a transform on it is a\n silent no-op (found live on-device via CDP, 2026-08-21; the variable and\n attribute were set and nothing moved). */\n html[data-mnav-kb] [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] {\n transform: translateY(calc(-1 * var(--mnav-kb-lift, 0px)));\n }\n}\n\n/* The attachment button and its preview row only exist for the phone shell.\n Both render at every width (the slots are not breakpoint-aware), so both\n need hiding here \u2014 the dock-row rules that shape the chips live in the\n < 768px block and would otherwise leave a bare, unstyled chip line in the\n desktop composer. */\n@media (min-width: 768px) {\n [data-mobile-nav=\"attach\"],\n [data-mobile-nav=\"attach-chips\"] {\n display: none !important;\n }\n}\n";
|
|
2
2
|
//# sourceMappingURL=composer.css.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composer.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/composer.css.ts"],"names":[],"mappings":"AAoEA,eAAO,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"composer.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/composer.css.ts"],"names":[],"mappings":"AAoEA,eAAO,MAAM,YAAY,uwkDAm7BxB,CAAA"}
|