dsh-zen-remote 1.1.16 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/AGENTS.md +54 -9
  2. package/README.md +249 -190
  3. package/README.zh-CN.md +247 -201
  4. package/assets/home.png +0 -0
  5. package/assets/info.png +0 -0
  6. package/assets/pairing.png +0 -0
  7. package/assets/session.png +0 -0
  8. package/assets/sheet.png +0 -0
  9. package/cordis.patch.yml +12 -11
  10. package/cordis.patch.yml.example +24 -9
  11. package/docs/interface.md +57 -2
  12. package/docs/remote-access.en.md +99 -58
  13. package/docs/remote-access.md +99 -42
  14. package/dsh-push.mjs +67 -54
  15. package/lan-gate.mjs +88 -20
  16. package/lib/activity.js +168 -0
  17. package/lib/activity.js.map +1 -0
  18. package/lib/admin-routes.js +526 -0
  19. package/lib/admin-routes.js.map +1 -0
  20. package/lib/client-pairing.js +224 -0
  21. package/lib/client-pairing.js.map +1 -0
  22. package/lib/client-routes.js +674 -0
  23. package/lib/client-routes.js.map +1 -0
  24. package/lib/client.js +10051 -4777
  25. package/lib/config.js +346 -0
  26. package/lib/config.js.map +1 -0
  27. package/lib/fetch-route-intercept.js +418 -0
  28. package/lib/fetch-route-intercept.js.map +1 -0
  29. package/lib/fingerprint.js +363 -0
  30. package/lib/fingerprint.js.map +1 -0
  31. package/lib/http.js +69 -0
  32. package/lib/http.js.map +1 -0
  33. package/lib/index.js +675 -54
  34. package/lib/index.js.map +1 -1
  35. package/lib/intercept-shape.js +81 -0
  36. package/lib/intercept-shape.js.map +1 -0
  37. package/lib/intercept.js +3040 -0
  38. package/lib/intercept.js.map +1 -0
  39. package/lib/lan-gate-server.cjs +532 -121
  40. package/lib/merge-streams.js +1165 -0
  41. package/lib/merge-streams.js.map +1 -0
  42. package/lib/relay-access.js +496 -0
  43. package/lib/relay-access.js.map +1 -0
  44. package/lib/relay-client.js +1303 -0
  45. package/lib/relay-client.js.map +1 -0
  46. package/lib/relay-filter.js +282 -0
  47. package/lib/relay-filter.js.map +1 -0
  48. package/lib/relay-server.js +1792 -0
  49. package/lib/relay-server.js.map +1 -0
  50. package/lib/restart-fields.js +34 -0
  51. package/lib/restart-fields.js.map +1 -0
  52. package/lib/restart-watcher.js +106 -0
  53. package/lib/restart-watcher.js.map +1 -0
  54. package/lib/session-reference.js +157 -0
  55. package/lib/session-reference.js.map +1 -0
  56. package/lib/share-export.js +16 -1
  57. package/lib/share-export.js.map +1 -1
  58. package/lib/share-ops.js +86 -0
  59. package/lib/share-ops.js.map +1 -0
  60. package/lib/share-store.js +294 -0
  61. package/lib/share-store.js.map +1 -0
  62. package/lib/types/activity.d.ts +99 -0
  63. package/lib/types/activity.d.ts.map +1 -0
  64. package/lib/types/admin-routes.d.ts +129 -0
  65. package/lib/types/admin-routes.d.ts.map +1 -0
  66. package/lib/types/client/MobileDrawerFooter.d.ts.map +1 -1
  67. package/lib/types/client/MobileHome.d.ts +1 -1
  68. package/lib/types/client/MobileHome.d.ts.map +1 -1
  69. package/lib/types/client/MobileHomeChips.d.ts +1 -1
  70. package/lib/types/client/MobileHomeChips.d.ts.map +1 -1
  71. package/lib/types/client/MobileSessionHeader.d.ts +13 -2
  72. package/lib/types/client/MobileSessionHeader.d.ts.map +1 -1
  73. package/lib/types/client/MobileSessionInfo.d.ts +20 -3
  74. package/lib/types/client/MobileSessionInfo.d.ts.map +1 -1
  75. package/lib/types/client/RemoteComposerBanner.d.ts +42 -0
  76. package/lib/types/client/RemoteComposerBanner.d.ts.map +1 -0
  77. package/lib/types/client/RemoteHeaderIcon.d.ts +37 -0
  78. package/lib/types/client/RemoteHeaderIcon.d.ts.map +1 -0
  79. package/lib/types/client/RemoteShareMenu.d.ts +28 -0
  80. package/lib/types/client/RemoteShareMenu.d.ts.map +1 -0
  81. package/lib/types/client/RemoteStatusIcon.d.ts +36 -0
  82. package/lib/types/client/RemoteStatusIcon.d.ts.map +1 -0
  83. package/lib/types/client/compat/desktop.d.ts +21 -0
  84. package/lib/types/client/compat/desktop.d.ts.map +1 -0
  85. package/lib/types/client/compat/types.d.ts +57 -0
  86. package/lib/types/client/compat/types.d.ts.map +1 -1
  87. package/lib/types/client/current-session-report.d.ts +37 -0
  88. package/lib/types/client/current-session-report.d.ts.map +1 -0
  89. package/lib/types/client/effects/header-status.d.ts.map +1 -1
  90. package/lib/types/client/effects/keyboard-guard.d.ts.map +1 -1
  91. package/lib/types/client/effects/permission-sheet.d.ts +39 -0
  92. package/lib/types/client/effects/permission-sheet.d.ts.map +1 -0
  93. package/lib/types/client/effects/remote-session.d.ts +25 -0
  94. package/lib/types/client/effects/remote-session.d.ts.map +1 -0
  95. package/lib/types/client/effects/turn-fold.d.ts +9 -0
  96. package/lib/types/client/effects/turn-fold.d.ts.map +1 -1
  97. package/lib/types/client/index.d.ts.map +1 -1
  98. package/lib/types/client/locales.d.ts +187 -2
  99. package/lib/types/client/locales.d.ts.map +1 -1
  100. package/lib/types/client/remote-fetch.d.ts +51 -0
  101. package/lib/types/client/remote-fetch.d.ts.map +1 -0
  102. package/lib/types/client/remote-group-expansion.d.ts +41 -0
  103. package/lib/types/client/remote-group-expansion.d.ts.map +1 -0
  104. package/lib/types/client/remote-share-css.d.ts +19 -0
  105. package/lib/types/client/remote-share-css.d.ts.map +1 -0
  106. package/lib/types/client/remote-share-register.d.ts +113 -0
  107. package/lib/types/client/remote-share-register.d.ts.map +1 -0
  108. package/lib/types/client/remote-status-css.d.ts +15 -0
  109. package/lib/types/client/remote-status-css.d.ts.map +1 -0
  110. package/lib/types/client/remote-status-register.d.ts +46 -0
  111. package/lib/types/client/remote-status-register.d.ts.map +1 -0
  112. package/lib/types/client/settings/SettingsSection.d.ts +46 -0
  113. package/lib/types/client/settings/SettingsSection.d.ts.map +1 -0
  114. package/lib/types/client/settings/register-settings.d.ts +58 -0
  115. package/lib/types/client/settings/register-settings.d.ts.map +1 -0
  116. package/lib/types/client/settings/settings-css.d.ts +10 -0
  117. package/lib/types/client/settings/settings-css.d.ts.map +1 -0
  118. package/lib/types/client/share/share-preview.d.ts.map +1 -1
  119. package/lib/types/client/styles/composer.css.d.ts +1 -1
  120. package/lib/types/client/styles/composer.css.d.ts.map +1 -1
  121. package/lib/types/client/styles/header.css.d.ts +1 -1
  122. package/lib/types/client/styles/header.css.d.ts.map +1 -1
  123. package/lib/types/client/styles/info.css.d.ts +1 -1
  124. package/lib/types/client/styles/info.css.d.ts.map +1 -1
  125. package/lib/types/client/styles/remote-session.css.d.ts +2 -0
  126. package/lib/types/client/styles/remote-session.css.d.ts.map +1 -0
  127. package/lib/types/client-data/current-session.d.ts +41 -0
  128. package/lib/types/client-data/current-session.d.ts.map +1 -0
  129. package/lib/types/client-data/remote-group-expansion.d.ts +76 -0
  130. package/lib/types/client-data/remote-group-expansion.d.ts.map +1 -0
  131. package/lib/types/client-data/remote-status.d.ts +148 -0
  132. package/lib/types/client-data/remote-status.d.ts.map +1 -0
  133. package/lib/types/client-data/settings-form.d.ts +774 -0
  134. package/lib/types/client-data/settings-form.d.ts.map +1 -0
  135. package/lib/types/client-data/shares.d.ts +211 -0
  136. package/lib/types/client-data/shares.d.ts.map +1 -0
  137. package/lib/types/client-pairing.d.ts +114 -0
  138. package/lib/types/client-pairing.d.ts.map +1 -0
  139. package/lib/types/client-routes.d.ts +112 -0
  140. package/lib/types/client-routes.d.ts.map +1 -0
  141. package/lib/types/config.d.ts +200 -0
  142. package/lib/types/config.d.ts.map +1 -0
  143. package/lib/types/fetch-route-intercept.d.ts +156 -0
  144. package/lib/types/fetch-route-intercept.d.ts.map +1 -0
  145. package/lib/types/fingerprint.d.ts +164 -0
  146. package/lib/types/fingerprint.d.ts.map +1 -0
  147. package/lib/types/http.d.ts +41 -0
  148. package/lib/types/http.d.ts.map +1 -0
  149. package/lib/types/index.d.ts +56 -24
  150. package/lib/types/index.d.ts.map +1 -1
  151. package/lib/types/intercept-shape.d.ts +32 -0
  152. package/lib/types/intercept-shape.d.ts.map +1 -0
  153. package/lib/types/intercept.d.ts +432 -0
  154. package/lib/types/intercept.d.ts.map +1 -0
  155. package/lib/types/merge-streams.d.ts +393 -0
  156. package/lib/types/merge-streams.d.ts.map +1 -0
  157. package/lib/types/relay-access.d.ts +310 -0
  158. package/lib/types/relay-access.d.ts.map +1 -0
  159. package/lib/types/relay-client.d.ts +274 -0
  160. package/lib/types/relay-client.d.ts.map +1 -0
  161. package/lib/types/relay-filter.d.ts +103 -0
  162. package/lib/types/relay-filter.d.ts.map +1 -0
  163. package/lib/types/relay-server.d.ts +208 -0
  164. package/lib/types/relay-server.d.ts.map +1 -0
  165. package/lib/types/restart-fields.d.ts +22 -0
  166. package/lib/types/restart-fields.d.ts.map +1 -0
  167. package/lib/types/restart-watcher.d.ts +83 -0
  168. package/lib/types/restart-watcher.d.ts.map +1 -0
  169. package/lib/types/session-reference.d.ts +66 -0
  170. package/lib/types/session-reference.d.ts.map +1 -0
  171. package/lib/types/share-export.d.ts +14 -1
  172. package/lib/types/share-export.d.ts.map +1 -1
  173. package/lib/types/share-ops.d.ts +79 -0
  174. package/lib/types/share-ops.d.ts.map +1 -0
  175. package/lib/types/share-store.d.ts +87 -0
  176. package/lib/types/share-store.d.ts.map +1 -0
  177. package/lib/types/virtual-id.d.ts +38 -0
  178. package/lib/types/virtual-id.d.ts.map +1 -0
  179. package/lib/virtual-id.js +52 -0
  180. package/lib/virtual-id.js.map +1 -0
  181. package/llms.txt +17 -14
  182. package/package.json +60 -50
  183. package/scripts/build-client.mjs +5 -3
  184. package/scripts/check-host-shadow.mjs +19 -2
  185. package/scripts/check-settings-form.mjs +933 -0
  186. package/scripts/check-shares.mjs +397 -0
  187. package/scripts/check-upload-endpoint.mjs +59 -3
  188. package/scripts/sync-doc-version.mjs +8 -15
  189. package/scripts/verify-fingerprint-live.mjs +111 -0
  190. package/src/activity.ts +218 -0
  191. package/src/admin-routes.ts +627 -0
  192. package/src/client/MobileAttachButton.tsx +2 -2
  193. package/src/client/MobileAttachChips.tsx +3 -3
  194. package/src/client/MobileDrawerFooter.tsx +7 -4
  195. package/src/client/MobileHome.tsx +20 -16
  196. package/src/client/MobileHomeChips.tsx +16 -16
  197. package/src/client/MobileNavOverlay.tsx +2 -2
  198. package/src/client/MobileNavToggle.tsx +3 -3
  199. package/src/client/MobileSessionHeader.tsx +63 -42
  200. package/src/client/MobileSessionInfo.tsx +118 -17
  201. package/src/client/RemoteComposerBanner.tsx +76 -0
  202. package/src/client/RemoteHeaderIcon.tsx +103 -0
  203. package/src/client/RemoteShareMenu.tsx +57 -0
  204. package/src/client/RemoteStatusIcon.tsx +91 -0
  205. package/src/client/compat/desktop.ts +22 -0
  206. package/src/client/compat/types.ts +62 -0
  207. package/src/client/current-session-report.ts +164 -0
  208. package/src/client/effects/header-status.ts +21 -17
  209. package/src/client/effects/keyboard-guard.ts +65 -1
  210. package/src/client/effects/permission-sheet.ts +149 -0
  211. package/src/client/effects/remote-session.ts +68 -0
  212. package/src/client/effects/turn-fold.ts +52 -0
  213. package/src/client/index.tsx +151 -36
  214. package/src/client/locales.ts +396 -2
  215. package/src/client/remote-fetch.ts +125 -0
  216. package/src/client/remote-group-expansion.ts +190 -0
  217. package/src/client/remote-share-css.ts +54 -0
  218. package/src/client/remote-share-register.ts +244 -0
  219. package/src/client/remote-status-css.ts +64 -0
  220. package/src/client/remote-status-register.ts +107 -0
  221. package/src/client/settings/SettingsSection.tsx +1229 -0
  222. package/src/client/settings/register-settings.ts +116 -0
  223. package/src/client/settings/settings-css.ts +236 -0
  224. package/src/client/share/share-preview.tsx +6 -3
  225. package/src/client/share/share-sheet.tsx +3 -3
  226. package/src/client/styles/composer.css.ts +119 -26
  227. package/src/client/styles/header.css.ts +7 -7
  228. package/src/client/styles/info.css.ts +74 -0
  229. package/src/client/styles/remote-session.css.ts +251 -0
  230. package/src/client-data/current-session.ts +57 -0
  231. package/src/client-data/remote-group-expansion.ts +145 -0
  232. package/src/client-data/remote-status.ts +374 -0
  233. package/src/client-data/settings-form.ts +1371 -0
  234. package/src/client-data/shares.ts +467 -0
  235. package/src/client-pairing.ts +244 -0
  236. package/src/client-routes.ts +732 -0
  237. package/src/config.ts +447 -0
  238. package/src/fetch-route-intercept.ts +510 -0
  239. package/src/fingerprint.ts +408 -0
  240. package/src/http.ts +68 -0
  241. package/src/index.ts +719 -62
  242. package/src/intercept-shape.ts +87 -0
  243. package/src/intercept.ts +3362 -0
  244. package/src/merge-streams.ts +1272 -0
  245. package/src/relay-access.ts +669 -0
  246. package/src/relay-client.ts +1514 -0
  247. package/src/relay-filter.ts +291 -0
  248. package/src/relay-server.ts +2025 -0
  249. package/src/restart-fields.ts +36 -0
  250. package/src/restart-watcher.ts +140 -0
  251. package/src/session-reference.ts +156 -0
  252. package/src/share-export.ts +26 -1
  253. package/src/share-ops.ts +114 -0
  254. package/src/share-store.ts +373 -0
  255. package/src/virtual-id.ts +59 -0
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Settings-page registration for the plugin row — the work that must run
3
+ * BEFORE apply's desktop-shell gate, extracted so the Node-side desktop-gate
4
+ * test can drive it against a fake ctx (src/client/index.tsx is a .tsx: Node
5
+ * cannot import JSX, so apply itself is untestable there).
6
+ *
7
+ * Two constraints shape it:
8
+ * - `configForms` must never join the top-level `export const inject`: in a
9
+ * composition without the settings service (0.1.x web), a required service
10
+ * would keep the whole browser half from loading. The lazy
11
+ * `ctx.inject(['configForms'], …)` below simply never fires there.
12
+ * - The registration (and the dictionaries it needs) must survive the desktop
13
+ * gate: the desktop app IS the main server, so its Plugins page still shows
14
+ * this block while the phone shell stays switched off behind the gate.
15
+ *
16
+ * The `plugins.row.config` SlotMap entry and the `configForms` Context member
17
+ * are declared here as structural mirrors: this package does not depend on
18
+ * dsh-client-ui-plugin-manager / dsh-client-ui-settings, whose type-only
19
+ * merges normally provide them (same trick as the local SlotsCompat /
20
+ * UiWorkspaceLike faces elsewhere in this plugin).
21
+ */
22
+ import type { ClientContext } from '../compat/types.ts';
23
+ import type { SettingsSection } from './SettingsSection.tsx';
24
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
25
+ interface SlotMap {
26
+ /**
27
+ * The configuration of one row a bundle declares, keyed by
28
+ * `<package name>#<row id>` — structural mirror of the plugin-manager's
29
+ * real declaration (kind keyed, root scope, owner carries the view flag).
30
+ */
31
+ 'plugins.row.config': {
32
+ kind: 'keyed';
33
+ scope: 'root';
34
+ owner: {
35
+ readonly view: 'summary' | 'page';
36
+ };
37
+ };
38
+ }
39
+ }
40
+ declare module '@deepseek-ai/cordis' {
41
+ interface Context {
42
+ /** The shared per-entry configuration forms (ui-settings service). */
43
+ configForms: import('../../client-data/settings-form.ts').ConfigFormsLike;
44
+ }
45
+ }
46
+ /** Settings entry (= profile entry id = bundle row id) this page edits. */
47
+ export declare const SETTINGS_ENTRY_ID = "dsh-zen-remote";
48
+ /** `plugins.row.config` key: <package name>#<row id> as the bundle patch declares the row. */
49
+ export declare const SETTINGS_ROW_KEY = "dsh-zen-remote#dsh-zen-remote";
50
+ /**
51
+ * Register the dictionaries, the settings stylesheet, and (lazily, only where
52
+ * the settings service exists) the plugin-row configuration block.
53
+ * @param ctx - client root context.
54
+ * @param section - the block component; a parameter so this module stays
55
+ * JSX-free and importable under Node's type stripping.
56
+ */
57
+ export declare function registerSettingsPage(ctx: ClientContext, section: typeof SettingsSection): void;
58
+ //# sourceMappingURL=register-settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-settings.d.ts","sourceRoot":"","sources":["../../../../src/client/settings/register-settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAQvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAE5D,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,OAAO;QACf;;;;WAIG;QACH,oBAAoB,EAAE;YACpB,IAAI,EAAE,OAAO,CAAA;YACb,KAAK,EAAE,MAAM,CAAA;YACb,KAAK,EAAE;gBAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAAA;aAAE,CAAA;SAC7C,CAAA;KACF;CACF;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,sEAAsE;QACtE,WAAW,EAAE,OAAO,oCAAoC,EAAE,eAAe,CAAA;KAC1E;CACF;AAED,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB,mBAAmB,CAAA;AAEjD,8FAA8F;AAC9F,eAAO,MAAM,gBAAgB,kCAAkC,CAAA;AAE/D;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,eAAe,GAAG,IAAI,CAiD9F"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Settings-page CSS for the plugin-row block (T15), transcribed from the
3
+ * dsh-llm-verifier section styles so the page reads as native. Injected as
4
+ * one `<style data-plugin="dsh-zen-remote-settings">` tag; every selector is
5
+ * scoped under the block's own attribute so nothing can reach the phone-shell
6
+ * styles (the CSS-division rule in AGENTS.md — the shell keeps its own
7
+ * stylesheets, this surface owns its own).
8
+ */
9
+ export declare const SETTINGS_CSS = "\n[data-zen-remote=\"settings\"] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n max-width: 760px;\n}\n[data-zen-remote=\"settings\"] .zr-settings-summary {\n font-size: 13px;\n color: var(--dsw-alias-label-tertiary);\n margin: 0;\n}\n[data-zen-remote=\"settings\"] .zr-settings-notice {\n font-size: 13px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 8px 12px;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-secondary);\n margin: 0;\n}\n[data-zen-remote=\"settings\"] .zr-settings-card {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-2);\n padding: 4px 16px;\n}\n[data-zen-remote=\"settings\"] .zr-settings-card-title {\n font-size: 15px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n margin: 12px 0 0;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n[data-zen-remote=\"settings\"] .zr-settings-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n}\n[data-zen-remote=\"settings\"] .zr-settings-field + .zr-settings-field {\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n[data-zen-remote=\"settings\"] .zr-settings-head {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n/* The head's title text: real labels (they name the select/checkbox below)\n and the two .zr-settings-head-title spans whose text names no form control\n (the pairing code readout, the push-test button) share the look. */\n[data-zen-remote=\"settings\"] .zr-settings-head label,\n[data-zen-remote=\"settings\"] .zr-settings-head .zr-settings-head-title {\n flex: 1;\n font-size: 13px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n[data-zen-remote=\"settings\"] .zr-settings-badge {\n font-size: 11px;\n border-radius: 999px;\n padding: 2px 8px;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-secondary);\n}\n[data-zen-remote=\"settings\"] .zr-settings-badge[data-warn=\"true\"] {\n color: var(--dsw-alias-state-warning-primary, #b8860b);\n}\n[data-zen-remote=\"settings\"] .zr-settings-reset {\n font-size: 12px;\n border: none;\n background: none;\n padding: 0;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n}\n[data-zen-remote=\"settings\"] .zr-settings-reset:hover {\n color: var(--dsw-alias-label-primary);\n}\n[data-zen-remote=\"settings\"] input.zr-settings-input,\n[data-zen-remote=\"settings\"] select.zr-settings-input {\n height: 34px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n padding: 0 10px;\n}\n[data-zen-remote=\"settings\"] select.zr-settings-input {\n max-width: 260px;\n}\n[data-zen-remote=\"settings\"] .zr-settings-hint {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n margin: 0;\n}\n[data-zen-remote=\"settings\"] .zr-settings-hint[data-invalid=\"true\"] {\n color: var(--dsw-alias-state-error-primary, #d5304f);\n}\n[data-zen-remote=\"settings\"] .zr-settings-check-row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n[data-zen-remote=\"settings\"] .zr-settings-check-row input[type=\"checkbox\"] {\n width: 16px;\n height: 16px;\n accent-color: var(--dsw-alias-label-primary);\n}\n[data-zen-remote=\"settings\"] .zr-settings-row {\n display: flex;\n gap: 8px;\n align-items: center;\n}\n[data-zen-remote=\"settings\"] .zr-settings-status-line {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n margin: 0;\n padding: 10px 0 12px;\n}\n[data-zen-remote=\"settings\"] .zr-settings-status-line[data-down=\"true\"] {\n color: var(--dsw-alias-state-error-primary, #d5304f);\n}\n[data-zen-remote=\"settings\"] .zr-settings-role-cards {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px 0;\n}\n[data-zen-remote=\"settings\"] .zr-settings-role-card {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 10px 12px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n cursor: pointer;\n background: none;\n text-align: left;\n font: inherit;\n color: inherit;\n}\n[data-zen-remote=\"settings\"] .zr-settings-role-card[data-active=\"true\"] {\n border-color: var(--dsw-alias-label-primary);\n}\n[data-zen-remote=\"settings\"] .zr-settings-role-card-title {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n[data-zen-remote=\"settings\"] .zr-settings-role-card-title input[type=\"radio\"] {\n width: 15px;\n height: 15px;\n accent-color: var(--dsw-alias-label-primary);\n}\n[data-zen-remote=\"settings\"] .zr-settings-code-input {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n letter-spacing: 4px;\n text-transform: uppercase;\n max-width: 220px;\n}\n[data-zen-remote=\"settings\"] .zr-settings-pair-code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 28px;\n letter-spacing: 4px;\n color: var(--dsw-alias-label-primary);\n margin: 4px 0 0;\n}\n[data-zen-remote=\"settings\"] .zr-settings-device {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n}\n[data-zen-remote=\"settings\"] .zr-settings-device + .zr-settings-device {\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n[data-zen-remote=\"settings\"] .zr-settings-device-head {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n[data-zen-remote=\"settings\"] .zr-settings-device-name {\n font-size: 13px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n[data-zen-remote=\"settings\"] .zr-settings-device-actions {\n display: flex;\n gap: 8px;\n align-items: center;\n flex-wrap: wrap;\n}\n[data-zen-remote=\"settings\"] .zr-settings-device-actions select.zr-settings-input {\n height: 28px;\n max-width: 170px;\n}\n[data-zen-remote=\"settings\"] .zr-settings-share-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 0;\n}\n[data-zen-remote=\"settings\"] .zr-settings-share-row + .zr-settings-share-row {\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n[data-zen-remote=\"settings\"] .zr-settings-share-row .zr-settings-device-head {\n flex: 1;\n min-width: 0;\n}\n[data-zen-remote=\"settings\"] .zr-settings-badge[data-live=\"true\"] {\n color: var(--dsw-alias-state-success-primary, #16a34a);\n}\n[data-zen-remote=\"settings\"] .zr-settings-footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 0;\n}\n";
10
+ //# sourceMappingURL=settings-css.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-css.d.ts","sourceRoot":"","sources":["../../../../src/client/settings/settings-css.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,eAAO,MAAM,YAAY,qiNAkOxB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"share-preview.d.ts","sourceRoot":"","sources":["../../../../src/client/share/share-preview.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAEjF,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AASlC,+EAA+E;AAC/E,MAAM,MAAM,iBAAiB,GACzB,YAAY,CAAC,eAAe,CAAC,GAC7B,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;AAqH1B;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,EAAE,iBAAiB,sCA6HjE"}
1
+ {"version":3,"file":"share-preview.d.ts","sourceRoot":"","sources":["../../../../src/client/share/share-preview.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAEjF,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AASlC,+EAA+E;AAC/E,MAAM,MAAM,iBAAiB,GACzB,YAAY,CAAC,eAAe,CAAC,GAC7B,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;AAqH1B;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,EAAE,iBAAiB,sCAgIjE"}
@@ -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\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 display: contents !important;\n }\n\n /* --- 2. the running order ---\n The attachment button (S3 placeholder, S7 wires it up) sits leftmost;\n the official \"+\" command menu keeps its seat right next to it, where\n the two \"insert something\" affordances read as one cluster. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] [data-mobile-nav=\"attach\"] {\n order: 1 !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_add\"] {\n order: 2 !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] {\n order: 3 !important;\n /* 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\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] {\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\"] > [data-slot=\"conversation.input.right\"] > * {\n order: 5 !important;\n }\n /* ContextMeter \u2014 the only `span` ending in _root inside the row. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > span[class$=\"_root\"] {\n order: 6 !important;\n flex: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_primary\"] {\n order: 7 !important;\n }\n /* 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 Both get `margin-left: auto` and the last rule takes it back off the\n send button whenever a ring precedes it: two auto margins would SHARE\n the free space and open a second gap between the ring and send. A\n sibling combinator answers \"is there a ring before me\" without assuming\n anything about the host's nesting. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > span[class$=\"_root\"],\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$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > span[class$=\"_root\"] ~ [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\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] {\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\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [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\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] > [class$=\"_triggerEffort\"] {\n display: block !important;\n flex: none !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 :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][role=\"menu\"]) {\n position: fixed !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n top: auto !important;\n transform: none !important;\n width: 100% !important;\n min-width: 0 !important;\n max-width: none !important;\n max-height: min(70dvh, 520px) !important;\n box-sizing: border-box !important;\n border-radius: 16px 16px 0 0 !important;\n border-bottom: none !important;\n padding: 8px 8px calc(8px + var(--mnav-sab)) !important;\n /* 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 [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"] [role=\"menuitem\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][role=\"menu\"]) [class$=\"_option\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][role=\"menu\"]) [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 model sheet ---\n effects/model-sheet-extras.ts parks the `conversation.input.right`\n container inside the model menu while that menu is open and puts it back\n when it closes; these rules are the two halves of that trip.\n\n In the row: hidden, but ONLY once the effect has marked the container as\n one it manages. Keying the hide off the marker rather than off \"not\n currently parked\" is what makes the failure direction safe \u2014 if the\n effect declines to act (nothing in the slot but the vision toggle, so\n nothing worth moving) or never runs at all, the marker is absent, this\n rule does not match, and the controls stay exactly where the host put\n them. The inverse spelling hid them in the row whenever they were not in\n the sheet, which in those same cases left them reachable from neither\n place (caught in review, 2026-09-06).\n Hiding by ancestor rather than by a display rule on the container itself\n is what lets the parked copy style itself freely \u2014 same element, two\n homes, two rule sets. `!important` because the slot container carries an\n inline `display: contents`. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.right\"][data-zen-sheet-extras] {\n display: none !important;\n }\n /* Parked in the sheet: a column of full-width rows in the sheet's own\n language, so a relocated control reads as a sibling of \u6A21\u578B and \u63A8\u7406\u7B49\u7EA7\n rather than a chip that wandered in. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][role=\"menu\"]) > [data-zen-sheet-extras] {\n display: flex !important;\n flex-direction: column !important;\n gap: 2px !important;\n width: 100% !important;\n }\n /* Undo the row's compaction on every parked control: back to full width and\n the 48px the sheet's own cells use. `> * > *` reaches both shapes without\n naming either plugin \u2014 subscriptions wraps its trigger in a positioning\n div, vision-router puts its button straight in the slot. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][role=\"menu\"]) > [data-zen-sheet-extras] > *,\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][role=\"menu\"]) > [data-zen-sheet-extras] > * > button {\n width: 100% !important;\n max-width: none !important;\n min-width: 0 !important;\n justify-content: flex-start !important;\n }\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][role=\"menu\"]) > [data-zen-sheet-extras] button {\n box-sizing: border-box !important;\n min-height: 48px !important;\n height: auto !important;\n padding: 0 12px !important;\n gap: 10px !important;\n border-radius: 12px !important;\n border: none !important;\n background: transparent !important;\n font-size: 15px !important;\n /* Both plugins center their chip label, which is right for a pill in the\n row and wrong for a full-width row: the sheet's own cells start their\n label at the left edge. justify-content covers the flex triggers,\n text-align the ones that are not flex containers. */\n text-align: left !important;\n justify-content: flex-start !important;\n }\n /* Order comes from the row (compat.css.ts puts the vision toggle at 5 so it\n parks beside the context ring), and that rule still matches in here on\n \u2264 0.1.2 \u2014 the inline sheet is a DOM descendant of the composer bar even\n though it paints as a fixed layer (the 0.1.5 body portal is not, where\n this is a harmless no-op). Reset it so the sheet follows DOM order\n instead of inheriting a decision that was about a different layout. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][role=\"menu\"]) > [data-zen-sheet-extras] > * {\n order: 0 !important;\n }\n /* The vision toggle is squeezed to a 28px icon in the row (compat.css.ts);\n in the sheet it is a row, so its label comes back and the icon leads. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][role=\"menu\"]) > [data-zen-sheet-extras] [data-vision-router-mode-toggle] > span {\n display: inline !important;\n }\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][role=\"menu\"]) > [data-zen-sheet-extras] [data-vision-router-mode-toggle] > svg:first-of-type {\n width: 18px !important;\n height: 18px !important;\n }\n /* The speed chip opens a menu of its own, anchored `bottom: 100%` to its\n trigger. Inside the sheet \u2014 which is `overflow: hidden` so its rounded\n top clips its content \u2014 that menu would be cut off. Same escape section 4\n uses for the host's two menus: position: fixed leaves the clip behind\n (the sheet sets `transform: none`, so nothing between here and the\n viewport captures fixed positioning) and it lands as its own sheet on top\n of this one. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][role=\"menu\"]) > [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 model sheet it sits on (60, section 4), below the session-info\n sheet's 70 (info.css.ts) \u2014 it is a child of the model sheet, not a peer\n of the page's own layers. */\n z-index: 61 !important;\n box-shadow: 0 -8px 32px rgba(0, 0, 0, .18) !important;\n }\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][role=\"menu\"]) > [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\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"]) [data-chat-flow] + div,\n body:has(> [id$=\"-menu\"][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 /* --- 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 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\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;\n the official \"+\" command menu keeps its seat right next to it, where\n the two \"insert something\" affordances read as one cluster. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] [data-mobile-nav=\"attach\"] {\n order: 1 !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_add\"] {\n order: 2 !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] {\n order: 3 !important;\n /* 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\"] > [class$=\"_root\"] {\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\"] > [class$=\"_root\"] > [class$=\"_trigger\"] {\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\"] > [class$=\"_root\"] > [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\"] > [class$=\"_root\"] > [class$=\"_trigger\"] > [class$=\"_triggerEffort\"] {\n display: block !important;\n flex: none !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\"] > [class$=\"_root\"] > [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\"] > [class$=\"_root\"] > [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\"] > [class$=\"_root\"] > [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\"] > [class$=\"_root\"] > [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\"] > [class$=\"_root\"] > [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 model sheet ---\n effects/model-sheet-extras.ts parks the `conversation.input.right`\n container inside the model menu while that menu is open and puts it back\n when it closes; these rules are the two halves of that trip.\n\n In the row: hidden, but ONLY once the effect has marked the container as\n one it manages. Keying the hide off the marker rather than off \"not\n currently parked\" is what makes the failure direction safe \u2014 if the\n effect declines to act (nothing in the slot but the vision toggle, so\n nothing worth moving) or never runs at all, the marker is absent, this\n rule does not match, and the controls stay exactly where the host put\n them. The inverse spelling hid them in the row whenever they were not in\n the sheet, which in those same cases left them reachable from neither\n place (caught in review, 2026-09-06).\n Hiding by ancestor rather than by a display rule on the container itself\n is what lets the parked copy style itself freely \u2014 same element, two\n homes, two rule sets. `!important` because the slot container carries an\n inline `display: contents`. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.right\"][data-zen-sheet-extras] {\n display: none !important;\n }\n /* Parked in the sheet: a column of full-width rows in the sheet's own\n language, so a relocated control reads as a sibling of \u6A21\u578B and \u63A8\u7406\u7B49\u7EA7\n rather than a chip that wandered in. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] {\n display: flex !important;\n flex-direction: column !important;\n gap: 2px !important;\n width: 100% !important;\n }\n /* Undo the row's compaction on every parked control: back to full width and\n the 48px the sheet's own cells use. `> * > *` reaches both shapes without\n naming either plugin \u2014 subscriptions wraps its trigger in a positioning\n div, vision-router puts its button straight in the slot. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] > *,\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] > * > button {\n width: 100% !important;\n max-width: none !important;\n min-width: 0 !important;\n justify-content: flex-start !important;\n }\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] button {\n box-sizing: border-box !important;\n min-height: 48px !important;\n height: auto !important;\n padding: 0 12px !important;\n gap: 10px !important;\n border-radius: 12px !important;\n border: none !important;\n background: transparent !important;\n font-size: 15px !important;\n /* Both plugins center their chip label, which is right for a pill in the\n row and wrong for a full-width row: the sheet's own cells start their\n label at the left edge. justify-content covers the flex triggers,\n text-align the ones that are not flex containers. */\n text-align: left !important;\n justify-content: flex-start !important;\n }\n /* Order comes from the row (compat.css.ts puts the vision toggle at 5 so it\n parks beside the context ring), and that rule still matches in here on\n \u2264 0.1.2 \u2014 the inline sheet is a DOM descendant of the composer bar even\n though it paints as a fixed layer (the 0.1.5 body portal is not, where\n this is a harmless no-op). Reset it so the sheet follows DOM order\n instead of inheriting a decision that was about a different layout. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] > * {\n order: 0 !important;\n }\n /* The vision toggle is squeezed to a 28px icon in the row (compat.css.ts);\n in the sheet it is a row, so its label comes back and the icon leads. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] [data-vision-router-mode-toggle] > span {\n display: inline !important;\n }\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] [data-vision-router-mode-toggle] > svg:first-of-type {\n width: 18px !important;\n height: 18px !important;\n }\n /* The speed chip opens a menu of its own, anchored `bottom: 100%` to its\n trigger. Inside the sheet \u2014 which is `overflow: hidden` so its rounded\n top clips its content \u2014 that menu would be cut off. Same escape section 4\n uses for the host's two menus: position: fixed leaves the clip behind\n (the sheet sets `transform: none`, so nothing between here and the\n viewport captures fixed positioning) and it lands as its own sheet on top\n of this one. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [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 model sheet it sits on (60, section 4), below the session-info\n sheet's 70 (info.css.ts) \u2014 it is a child of the model sheet, not a peer\n of the page's own layers. */\n z-index: 61 !important;\n box-shadow: 0 -8px 32px rgba(0, 0, 0, .18) !important;\n }\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [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\"] > [class$=\"_root\"] > [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 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 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":"AAiCA,eAAO,MAAM,YAAY,w42CAqyBxB,CAAA"}
1
+ {"version":3,"file":"composer.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/composer.css.ts"],"names":[],"mappings":"AAkDA,eAAO,MAAM,YAAY,mniDAi3BxB,CAAA"}
@@ -1,2 +1,2 @@
1
- export declare const HEADER_CSS = "/* ---------- session header five-piece reflow (< 768px) ---------- */\n\n/* New elements render unconditionally (React does not know about media\n queries); default them to hidden so 768px+ never sees them, then\n re-enable inside the phone block below. Belt-and-braces alongside the\n scoped rules \u2014 mirrors the existing [data-mobile-nav=\"*\"] desktop no-op\n list in misc.css.ts. */\n[data-mobile-nav=\"header-back\"],\n[data-mobile-nav=\"header-viewrow\"],\n[data-mobile-nav=\"header-activity\"],\n[data-mobile-nav=\"header-info\"],\n[data-mobile-nav=\"header-workbench\"],\n/* The workbench close pill's default-hidden used to live in compat.css.ts \u2014\n but EVERYTHING in layout/compat/misc sits inside one shared\n (max-width: 1023px) block (opened at the top of layout, closed at the end\n of misc), so that \"top-level\" rule silently never applied at >=1024 and\n the pill rendered as an unstyled button on desktop (2026-08-17 report).\n This file is appended after the shared block closes, so here it is truly\n global; the <=767px :has() show rule in compat overrides it with\n !important. */\n[data-mobile-nav=\"better-sidebar-close\"] {\n display: none;\n}\n\n@media (max-width: 767px) {\n /* The Files action targeted the dsh-web-ui-all (aionui) explorer sheet \u2014\n a different, unrelated suite from the workbench entry this slice adds\n (dsh-better-sidebar). Hidden here only; 768-1023px keeps it exactly as\n v1.0.0 shipped it. */\n [data-mobile-nav=\"files\"] {\n display: none !important;\n }\n\n /* --- three-column header: [back 92px] [title, truly centered] [utilities 92px] --- */\n /* The bottom padding is the view-switch row's seat: that row is\n position:absolute (see the bottom of this file \u2014 it renders two levels\n deep inside headerActions and can never be a grid item of titleRow), so\n without a reserve it would hang over the message list. 28px = its\n height. Unconditional rather than header:has([data-mobile-nav=\n \"header-viewrow\"]) on purpose: :has() is silently dropped by pre-105\n WebViews (see AGENTS.md) and a 28px overlap on the first message is a\n worse failure than 28px of empty band in the (never observed in\n practice) single-view session. Total header chrome: 48 + 28 = 76px.\n Left/right 8px (S2.1 report leftover, fixed in S4): the official header\n had zero horizontal padding, so the utilities button cluster's right\n edge sat flush against the screen edge (x=390 at 390px width, no\n margin). The 92px grid columns absorb this fine \u2014 they're fixed track\n widths, only the center 1fr column shrinks by 16px. */\n [data-phase] header {\n position: relative;\n /* Above the scroller's plain and relative-positioned message content, so\n the ::after fade strip below actually paints over the messages sliding\n under it (positioned z-auto boxes otherwise win by DOM order \u2014 the\n scroller comes after the header). Deliberately tiny: the seat (7),\n scroll-to-bottom (8) and every overlay this plugin owns (30+) stay\n above. */\n z-index: 2;\n padding: 0 8px 28px !important;\n }\n /* No header bottom line \u2014 fade instead (real-device round 2, 2026-08-17).\n The official header draws its border as a `::after` 1px bar (the\n `border-bottom` on the header itself is transparent, just a layout\n reserve \u2014 dsh-client-ui-conversation lib/client.js \".wSkVaW_header{\n border-bottom:1px solid #0000}.wSkVaW_header:after{...height:1px;\n position:absolute;bottom:1px...}\", verified live 2026-08-17), so the\n bar is repurposed rather than hidden: restyled into a 20px strip\n hanging just below the header that fades the messages in \u2014 the same\n look the scroller's mask-image used to produce, without a mask on a\n scroll container (iOS WebKit renders that as fog over the header, see\n composer.css.ts \"no divider above OR below the message list\",\n 2026-08-23). color-mix instead of the transparent keyword: the host\n writes its own seat gradient that way, and plain `transparent` is\n rgba(0,0,0,0) \u2014 WebKit interpolates it through grey. */\n [data-phase] header::after {\n height: 20px !important;\n top: 100% !important;\n bottom: auto !important;\n background: linear-gradient(to bottom, var(--dsw-alias-bg-base), color-mix(in srgb, var(--dsw-alias-bg-base) 0%, transparent)) !important;\n }\n /* grid-row: 1 on every item is load-bearing, not decoration (S2.1 fix for\n the \"\u6807\u9898\u88AB\u6324\u4E0B\u53BB\" report). The three items are placed with explicit\n grid-column but arrive in DOM order crumbs(2) \u2192 headerActions(1) \u2192\n headerUtilities(3): sparse auto-placement never moves its cursor\n backwards, so headerActions and headerUtilities were pushed onto an\n implicit SECOND row. Measured at 390px: row1 = crumbs 28px, row2 =\n back button 44px, titleRow = 72px with the title stuck at the top\n instead of centered. Pinning the row makes it one 48px band again. */\n [data-phase] header [class$=\"_titleRow\"] {\n position: relative;\n display: grid;\n grid-template-columns: 92px 1fr 92px;\n grid-template-rows: minmax(48px, auto);\n align-items: center;\n min-height: 48px;\n padding: 0 !important;\n }\n /* Flatten the official cluster so its two children (crumbs, headerActions)\n become direct grid items of titleRow instead of a nested flex box. */\n [data-phase] header [class$=\"_titleCluster\"] {\n display: contents;\n }\n /* margin-left: 0 cancels layout.css.ts's shared <=1023px\n \"margin-left: auto !important\" on this exact selector (it pushed the\n Files button to the row's right edge in the old non-grid header;\n inside our grid column it instead shoves the back button 48px right of\n the column's start \u2014 the auto margin absorbs the leftover space\n between the button's content width and the 92px column). */\n [data-phase] header [class$=\"_headerActions\"] {\n grid-column: 1;\n grid-row: 1;\n justify-self: start;\n margin-left: 0 !important;\n display: flex;\n align-items: center;\n min-width: 0;\n }\n /* layout.css.ts's shared <=1023px block hides this element outright\n (header > :first-child > :last-child { display: none !important },\n specificity (0,3,1) \u2014 v1.0.0 relocated the desktop Session-log capsule\n to the drawer footer on every narrow width). That selector is repeated\n here at equal specificity so the later source order wins the display\n property; the class-suffix selector alone (0,2,1) would silently lose. */\n [data-phase] header [class$=\"_headerUtilities\"],\n [data-phase] header > :first-child > :last-child {\n grid-column: 3;\n grid-row: 1;\n justify-self: end;\n display: flex !important;\n align-items: center;\n gap: 4px;\n min-width: 0;\n }\n /* justify-self: stretch (the grid default) so this fills the whole 1fr\n column as a definite-width box \u2014 center/center-content happens inside\n it. justify-self: center (tried first) sizes the item to its content\n with no automatic column cap, and the obvious fix \u2014 max-width: 100% \u2014\n resolves against the WRONG box here (measured 93.6px at 390px, where\n the column is ~206px): the item's DOM parent (titleCluster) is\n display:contents, and percentage max-width does not reliably resolve\n through a boxless ancestor to the grid track. Stretching sidesteps the\n percentage entirely \u2014 but layout.css.ts's shared <=1023px block ALSO\n caps this exact selector at max-width: 24vw !important (93.6px at\n 390px \u2014 the value this rule used to silently inherit once its own\n max-width was dropped in favor of stretch), so that must be reset to\n none here too. */\n [data-phase] header [class$=\"_crumbs\"] {\n grid-column: 2;\n grid-row: 1;\n justify-self: stretch;\n max-width: none !important;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n overflow: hidden;\n }\n /* Subagent sessions keep ONE ancestor crumb (2026-08-18): the PC header\n lets you tap the parent session's title to switch back \u2014 that affordance\n was lost on phone when the whole parent chain was hidden as \"\u591A\u4F59\u9879\".\n Show only the immediate parent (for agent-teams member sessions that IS\n the captain/main session; deeper ancestors stay hidden \u2014 a 3-level chain\n cannot fit the ~206px center column at 390px). The parent crumb is the\n official breadcrumb <button> (dsh-client-ui-conversation renders\n open(parentId) as its onClick), so tapping needs no JS from this plugin,\n and main sessions (chain length 1) have no :nth-last-child(2) seg at\n all \u2014 the centered single title is pixel-identical to before. */\n [data-phase] header [class$=\"_crumbSeg\"]:not(:nth-last-child(-n+2)) {\n display: none !important;\n }\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) {\n display: inline-flex;\n align-items: center;\n flex: none;\n min-width: 0;\n max-width: 38%;\n }\n /* Ancestors BEFORE the parent seg are hidden, which would leave the\n parent seg's own leading \"/\" dangling at the start of the title. */\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) [class$=\"_crumbSep\"] {\n display: none !important;\n }\n /* Parent crumb: small tertiary text so the 19px current title stays the\n visual anchor; min-height keeps a finger-sized target inside the 48px\n row. Specificity (0,4,1) outranks the shared 19px _crumb rule below. */\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) [class$=\"_crumb\"] {\n font-size: 13px;\n font-weight: 400;\n color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .45));\n min-height: 44px;\n display: inline-flex;\n align-items: center;\n padding: 0 2px 0 6px;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-phase] header [class$=\"_crumbSeg\"]:last-child {\n display: flex;\n align-items: center;\n min-width: 0;\n max-width: 100%;\n }\n /* The last seg's own \"/\" is the divider between parent and current title \u2014\n it only exists when there IS an ancestor (index > 0), so it can simply\n stay visible now that the parent seg shows. (It used to be hidden here:\n with the parent hidden it dangled before the title.) */\n /* BOTH selectors are required: [class$=] matches the whole class\n ATTRIBUTE's suffix, and the current-session title button's attribute is\n \"wSkVaW_crumb wSkVaW_crumbCurrent\" \u2014 it ends in _crumbCurrent, so the _crumb\n suffix selector alone silently missed the one element that matters\n (measured live: the title stayed 14px while the rule sat in the bundle,\n 2026-08-17). */\n [data-phase] header [class$=\"_crumb\"],\n [data-phase] header [class$=\"_crumbCurrent\"] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n max-width: 100%;\n /* Real-device feedback (2026-08-17): the session title read too small\n next to the enlarged header icons \u2014 19px sits just under the home\n screen's 22px workspace title, keeping the two-level hierarchy. */\n font-size: 19px;\n font-weight: 600;\n }\n\n /* Running-status dot: no official element to reposition (the header\n renders only the crumb title, the two action slots, and the tablist \u2014\n no status indicator), so this is the plan's documented fallback \u2014\n read the data ourselves and draw it (effects/header-status.ts stamps\n data-mobile-nav-dot on the frame). A ::after on the title crumb keeps\n it truly inline with the (centered, truncated) text instead of a\n separately positioned box that would fight the centering math. */\n [data-phase] header [class$=\"_crumbCurrent\"]::after {\n content: '';\n display: none;\n width: 6px;\n height: 6px;\n margin-left: 6px;\n border-radius: 50%;\n vertical-align: middle;\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot] header [class$=\"_crumbCurrent\"]::after {\n display: inline-block;\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"ongoing\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-business-primary, #4f6ef7);\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"warning\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-warn-primary, #d97706);\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"done\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-success-primary, #16a34a);\n }\n\n /* Everything else the official header puts in these two list slots \u2014 the\n agent-preset mode badge, the jobs/subagent trigger, the desktop\n Session-log download capsule, and this plugin's own legacy directory\n toggle \u2014 is the \"\u4E0D\u5E38\u9A7B\" set the design defers to the S4 info card.\n Scoped by the slots' own [data-slot] wrapper (a stable, non-hashed\n contract marker) rather than enumerating each registrant, so a future\n third-party header action is hidden by default too. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > *,\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > * {\n display: none !important;\n }\n /* DSH 0.1.5's third titleRow child: the right sidebar's ExpandButton\n (dsh-client-ui-sidebar-right, slot conversation.session.header.corner\n inside div.headerCorner). Left alone it auto-placed into an implicit\n 4th grid column at the screen's top-right corner \u2014 a second panel-left\n icon next to our Workbench button, reading as \"the hidden official\n sidebar toggle is back\" (reported 2026-09-11). The button itself is the\n one our sidebar control forwards to whenever the host has this panel\n (sidebar-panels.ts, official first since 2026-09-21 \u2014 better-sidebar\n 0.19+ puts all its tabs in here), and a display:none button still\n receives synthetic .click() (the tablist precedent), so hiding it costs\n nothing. better-sidebar 0.19's own bottom-workbench toggle\n (`[data-dsh-bottom-toggle]`) registers into the utilities slot and is\n caught by the blanket hide above \u2014 intended: that surface is not for\n the phone.\n Anchors, in the specificity order they NEED (measured live, 2026-09-11):\n the corner div must be beaten with (0,3,1) \u2014 the utilities un-hide rule\n above spells header > :first-child > :last-child at exactly that\n weight, and with 0.1.5's third titleRow child the corner IS that\n :last-child now, so its flex !important outranks any lighter hide (a\n single-attribute hide loses at (0,2,1) \u2014 both spellings were tried\n live). Chaining BOTH of the corner's own anchors on one selector \u2014 the\n host's data-conversation-header-corner contract attribute AND the\n class suffix \u2014 reaches the tie, and the later source order here wins\n it. The slot wrapper rule below needs no help: nothing !important\n fights it, so its (0,2,1) settles the button. */\n [data-phase] header [data-conversation-header-corner][class$=\"_headerCorner\"],\n [data-phase] header [data-slot=\"conversation.session.header.corner\"] {\n display: none !important;\n }\n /* Native background-task / subagent entries (rewritten for DSH 0.1.1,\n 2026-08-21). Three upstream changes killed the old \"park the root as a\n zero-width anchor, hide the trigger, forward the tap\" design at once:\n\n - the subagent entry moved out of header.actions into the new\n conversation.session.header.lineage slot (inside the breadcrumb);\n - its root's class attribute is now \"ZKlsPq_root \" \u2014 with a TRAILING\n SPACE \u2014 so a [class$=\"_root\"] anchor matches nothing at all;\n - the trigger only reacts to TRUSTED input: .click() and synthetic\n pointer/mouse events leave aria-expanded untouched (measured on device\n with every plugin override stripped), so forwarding a tap in JS can\n never open it again.\n\n The trigger's geometry is therefore owned at runtime by\n effects/native-trigger-overlay.ts, which lays the REAL button\n transparently over the activity pill \u2014 a genuine finger tap lands on the\n official element. Nothing in this stylesheet may size, hide, or set\n pointer-events on the trigger: that is exactly what would take the tap\n target away again.\n\n CSS keeps only two chores. Dissolve the root so its box (and its\n stray separator, hidden below) stops occupying the breadcrumb, and let\n the trigger participate from wherever the overlay pins it. And no menu\n re-anchoring anymore: the 0.1.1 popover portals to <body> as\n position:fixed and positions itself from the trigger's viewport rect\n (measured 336px wide, fully on screen at 411px). */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > *:has(> button[class$=\"_trigger\"]),\n [data-phase] header [data-slot=\"conversation.session.header.lineage\"] > *:has(> button[class$=\"_trigger\"]) {\n display: contents !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] [class$=\"_separator\"],\n [data-phase] header [data-slot=\"conversation.session.header.lineage\"] [class$=\"_separator\"] {\n display: none !important;\n }\n /* The agent-preset mode badge specifically survives the blanket hide\n above: layout.css.ts's shared <=1023px block targets it directly\n (header [class$=\"_label\"]:has(> svg) { display: block !important }),\n specificity (0,3,1), higher than the [data-slot] wrapper rule (0,2,1) \u2014\n display wins on specificity before source order, so a same-selector\n override is needed here too. */\n [data-phase] header [class$=\"_label\"]:has(> svg) {\n display: none !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-back\"] {\n display: inline-flex !important;\n }\n /* header-viewrow is position:absolute (see below), so its re-shown\n display value must match the flex layout its own rule declares. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-viewrow\"] {\n display: flex !important;\n }\n /* Our own chip lives in the same slot, so the blanket hide above swallows\n it unless it is named here too (it was, on the first build: the chip\n rendered with the right counts and painted nothing). */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-activity\"] {\n display: inline-flex !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"header-info\"],\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"header-workbench\"] {\n display: inline-flex !important;\n }\n\n /* Back button: 44pt touch target, leftmost column. */\n [data-mobile-nav=\"header-back\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-back\"]:active {\n opacity: .6;\n }\n\n /* Info + workbench utility buttons, rightmost column. */\n [data-mobile-nav=\"header-info\"],\n [data-mobile-nav=\"header-workbench\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 34px;\n height: 34px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-secondary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-info\"]:active,\n [data-mobile-nav=\"header-workbench\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n /* Icon family unification (real-device round 2, 2026-08-17): the \u24D8 text\n glyph is gone (MobileSessionHeader.tsx now renders IconInfoOutline16,\n a local 16px SVG built to match the primitives icon family) and the\n workbench button's IconPanelLeftOutline16 is mirrored into a\n panel-RIGHT glyph \u2014 there's no IconPanelRightOutline16 in primitives\n (checked lib/types/icons/index.d.ts), and the plugin's own right-side\n panel semantics are exactly the left icon flipped. Both buttons now\n carry a same-size (16px), same-stroke-weight icon. */\n [data-mobile-nav=\"header-workbench\"] svg {\n transform: scaleX(-1);\n }\n\n /* Official Chat/Trajectory tablist: removed from layout entirely (S2.1 \u2014\n visibility:hidden still held a 27px row, so the header carried the\n view-switch row's band TWICE: 72 + 27 + gap = 104px measured at 390px,\n and a real iPhone added ~54px of notch on top of that). The header's own\n padding-bottom above is now the row's seat.\n display:none does NOT break the view switch: HTMLElement.click()\n dispatches synthetically and fires React's handler regardless of\n rendering \u2014 only real pointer hit-testing needs a box, and this plugin\n never relies on it (MobileSessionHeader always calls .click()). */\n [data-phase] header [class$=\"_tabs\"][role=\"tablist\"] {\n display: none;\n }\n\n /* View-switch row: \"current view name + dots\", replacing the (still\n present, still clickable) official tablist visually. It renders inside\n headerActions/headerUtilities, but titleRow's own position:relative\n (set above) makes IT the containing block for absolute descendants \u2014\n display:contents on titleCluster does not break that search \u2014 so\n top:100% sits directly under the title regardless of the title row's\n actual height, with no hardcoded offset to keep in sync. */\n /* Activity chip (subagents / background tasks) rides the right end of the\n same band. It is a SIBLING of the switch button, not a child \u2014 that\n button spans the full row, so nesting would make every tap on the chip\n switch views too \u2014 and it shares titleRow as its containing block, so\n top:100% lands both on the same 28px line. z-index puts it above the\n row it overlaps. */\n [data-mobile-nav=\"header-activity\"] {\n position: absolute;\n right: 0;\n top: 100%;\n z-index: 1;\n display: inline-flex;\n align-items: center;\n gap: 9px;\n height: 28px;\n padding: 0 2px 0 10px;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-family: inherit;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n /* pointer-events:none so the invisible official trigger laid over this pill\n (effects/native-trigger-overlay.ts) is what a tap reaches. The pill is\n visuals only. */\n [data-mobile-nav=\"activity-pill\"] {\n pointer-events: none;\n display: inline-flex;\n align-items: center;\n gap: 3px;\n height: 28px;\n padding: 0;\n border: none;\n background: transparent;\n color: inherit;\n font-family: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"activity-pill\"]:active {\n opacity: .55;\n }\n [data-mobile-nav=\"activity-count\"] {\n font-variant-numeric: tabular-nums;\n }\n /* Steady green = everything settled. */\n [data-mobile-nav=\"activity-dot\"] {\n display: block;\n width: 6px;\n height: 6px;\n margin-left: 1px;\n border-radius: 50%;\n background: var(--dsw-alias-state-success-primary, #16a34a);\n }\n /* A job that failed or was killed is not a success \u2014 never paint it green. */\n [data-activity-state=\"warning\"] [data-mobile-nav=\"activity-dot\"] {\n background: var(--dsw-alias-state-warn-primary, #d97706);\n }\n /* Pulsing blue = still running. Same state palette as the session dot. */\n [data-activity-state=\"running\"] [data-mobile-nav=\"activity-dot\"] {\n background: var(--dsw-alias-state-business-primary, #4f6ef7);\n animation: dsh-zen-activity-pulse 1.4s ease-in-out infinite;\n }\n @keyframes dsh-zen-activity-pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: .45; transform: scale(.72); }\n }\n @media (prefers-reduced-motion: reduce) {\n [data-activity-state=\"running\"] [data-mobile-nav=\"activity-dot\"] {\n animation: none;\n }\n }\n\n [data-mobile-nav=\"header-viewrow\"] {\n position: absolute;\n left: 0;\n right: 0;\n top: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 28px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-family: inherit;\n font-size: 13px;\n line-height: 18px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] > i {\n display: block;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: currentColor;\n opacity: .35;\n font-style: normal;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] > i[data-active] {\n opacity: 1;\n }\n}\n";
1
+ export declare const HEADER_CSS = "/* ---------- session header five-piece reflow (< 768px) ---------- */\n\n/* New elements render unconditionally (React does not know about media\n queries); default them to hidden so 768px+ never sees them, then\n re-enable inside the phone block below. Belt-and-braces alongside the\n scoped rules \u2014 mirrors the existing [data-mobile-nav=\"*\"] desktop no-op\n list in misc.css.ts. */\n[data-mobile-nav=\"header-back\"],\n[data-mobile-nav=\"header-viewrow\"],\n[data-mobile-nav=\"header-activity\"],\n[data-mobile-nav=\"header-info\"],\n[data-mobile-nav=\"header-workbench\"],\n/* The workbench close pill's default-hidden used to live in compat.css.ts \u2014\n but EVERYTHING in layout/compat/misc sits inside one shared\n (max-width: 1023px) block (opened at the top of layout, closed at the end\n of misc), so that \"top-level\" rule silently never applied at >=1024 and\n the pill rendered as an unstyled button on desktop (2026-08-17 report).\n This file is appended after the shared block closes, so here it is truly\n global; the <=767px :has() show rule in compat overrides it with\n !important. */\n[data-mobile-nav=\"better-sidebar-close\"] {\n display: none;\n}\n\n@media (max-width: 767px) {\n /* The Files action targeted the dsh-web-ui-all (aionui) explorer sheet \u2014\n a different, unrelated suite from the workbench entry this slice adds\n (dsh-better-sidebar). Hidden here only; 768-1023px keeps it exactly as\n v1.0.0 shipped it. */\n [data-mobile-nav=\"files\"] {\n display: none !important;\n }\n\n /* --- three-column header: [back 92px] [title, truly centered] [utilities 92px] --- */\n /* The bottom padding is the view-switch row's seat: that row is\n position:absolute (see the bottom of this file \u2014 it renders two levels\n deep inside headerActions and can never be a grid item of titleRow), so\n without a reserve it would hang over the message list. 28px = its\n height. Unconditional rather than header:has([data-mobile-nav=\n \"header-viewrow\"]) on purpose: :has() is silently dropped by pre-105\n WebViews (see AGENTS.md) and a 28px overlap on the first message is a\n worse failure than 28px of empty band in the (never observed in\n practice) single-view session. Total header chrome: 48 + 28 = 76px.\n Left/right 8px (S2.1 report leftover, fixed in S4): the official header\n had zero horizontal padding, so the utilities button cluster's right\n edge sat flush against the screen edge (x=390 at 390px width, no\n margin). The 92px grid columns absorb this fine \u2014 they're fixed track\n widths, only the center 1fr column shrinks by 16px. */\n [data-phase] header {\n position: relative;\n /* Above the scroller's plain and relative-positioned message content, so\n the ::after fade strip below actually paints over the messages sliding\n under it (positioned z-auto boxes otherwise win by DOM order \u2014 the\n scroller comes after the header). Deliberately tiny: the seat (7),\n scroll-to-bottom (8) and every overlay this plugin owns (30+) stay\n above. */\n z-index: 2;\n padding: 0 8px 28px !important;\n }\n /* No header bottom line \u2014 fade instead (real-device round 2, 2026-08-17).\n The official header draws its border as a `::after` 1px bar (the\n `border-bottom` on the header itself is transparent, just a layout\n reserve \u2014 dsh-client-ui-conversation lib/client.js \".wSkVaW_header{\n border-bottom:1px solid #0000}.wSkVaW_header:after{...height:1px;\n position:absolute;bottom:1px...}\", verified live 2026-08-17), so the\n bar is repurposed rather than hidden: restyled into a 20px strip\n hanging just below the header that fades the messages in \u2014 the same\n look the scroller's mask-image used to produce, without a mask on a\n scroll container (iOS WebKit renders that as fog over the header, see\n composer.css.ts \"no divider above OR below the message list\",\n 2026-08-23). color-mix instead of the transparent keyword: the host\n writes its own seat gradient that way, and plain `transparent` is\n rgba(0,0,0,0) \u2014 WebKit interpolates it through grey. */\n [data-phase] header::after {\n height: 20px !important;\n top: 100% !important;\n bottom: auto !important;\n background: linear-gradient(to bottom, var(--dsw-alias-bg-base), color-mix(in srgb, var(--dsw-alias-bg-base) 0%, transparent)) !important;\n }\n /* grid-row: 1 on every item is load-bearing, not decoration (S2.1 fix for\n the \"\u6807\u9898\u88AB\u6324\u4E0B\u53BB\" report). The three items are placed with explicit\n grid-column but arrive in DOM order crumbs(2) \u2192 headerActions(1) \u2192\n headerUtilities(3): sparse auto-placement never moves its cursor\n backwards, so headerActions and headerUtilities were pushed onto an\n implicit SECOND row. Measured at 390px: row1 = crumbs 28px, row2 =\n back button 44px, titleRow = 72px with the title stuck at the top\n instead of centered. Pinning the row makes it one 48px band again. */\n [data-phase] header [class$=\"_titleRow\"] {\n position: relative;\n display: grid;\n grid-template-columns: 92px 1fr 92px;\n grid-template-rows: minmax(48px, auto);\n align-items: center;\n min-height: 48px;\n padding: 0 !important;\n }\n /* Flatten the official cluster so its two children (crumbs, headerActions)\n become direct grid items of titleRow instead of a nested flex box. */\n [data-phase] header [class$=\"_titleCluster\"] {\n display: contents;\n }\n /* margin-left: 0 cancels layout.css.ts's shared <=1023px\n \"margin-left: auto !important\" on this exact selector (it pushed the\n Files button to the row's right edge in the old non-grid header;\n inside our grid column it instead shoves the back button 48px right of\n the column's start \u2014 the auto margin absorbs the leftover space\n between the button's content width and the 92px column). */\n [data-phase] header [class$=\"_headerActions\"] {\n grid-column: 1;\n grid-row: 1;\n justify-self: start;\n margin-left: 0 !important;\n display: flex;\n align-items: center;\n min-width: 0;\n }\n /* layout.css.ts's shared <=1023px block hides this element outright\n (header > :first-child > :last-child { display: none !important },\n specificity (0,3,1) \u2014 v1.0.0 relocated the desktop Session-log capsule\n to the drawer footer on every narrow width). That selector is repeated\n here at equal specificity so the later source order wins the display\n property; the class-suffix selector alone (0,2,1) would silently lose. */\n [data-phase] header [class$=\"_headerUtilities\"],\n [data-phase] header > :first-child > :last-child {\n grid-column: 3;\n grid-row: 1;\n justify-self: end;\n display: flex !important;\n align-items: center;\n gap: 4px;\n min-width: 0;\n }\n /* justify-self: stretch (the grid default) so this fills the whole 1fr\n column as a definite-width box \u2014 center/center-content happens inside\n it. justify-self: center (tried first) sizes the item to its content\n with no automatic column cap, and the obvious fix \u2014 max-width: 100% \u2014\n resolves against the WRONG box here (measured 93.6px at 390px, where\n the column is ~206px): the item's DOM parent (titleCluster) is\n display:contents, and percentage max-width does not reliably resolve\n through a boxless ancestor to the grid track. Stretching sidesteps the\n percentage entirely \u2014 but layout.css.ts's shared <=1023px block ALSO\n caps this exact selector at max-width: 24vw !important (93.6px at\n 390px \u2014 the value this rule used to silently inherit once its own\n max-width was dropped in favor of stretch), so that must be reset to\n none here too. */\n [data-phase] header [class$=\"_crumbs\"] {\n grid-column: 2;\n grid-row: 1;\n justify-self: stretch;\n max-width: none !important;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n overflow: hidden;\n }\n /* Subagent sessions keep ONE ancestor crumb (2026-08-18): the PC header\n lets you tap the parent session's title to switch back \u2014 that affordance\n was lost on phone when the whole parent chain was hidden as \"\u591A\u4F59\u9879\".\n Show only the immediate parent (for agent-teams member sessions that IS\n the captain/main session; deeper ancestors stay hidden \u2014 a 3-level chain\n cannot fit the ~206px center column at 390px). The parent crumb is the\n official breadcrumb <button> (dsh-client-ui-conversation renders\n open(parentId) as its onClick), so tapping needs no JS from this plugin,\n and main sessions (chain length 1) have no :nth-last-child(2) seg at\n all \u2014 the centered single title is pixel-identical to before. */\n [data-phase] header [class$=\"_crumbSeg\"]:not(:nth-last-child(-n+2)) {\n display: none !important;\n }\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) {\n display: inline-flex;\n align-items: center;\n flex: none;\n min-width: 0;\n max-width: 38%;\n }\n /* Ancestors BEFORE the parent seg are hidden, which would leave the\n parent seg's own leading \"/\" dangling at the start of the title. */\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) [class$=\"_crumbSep\"] {\n display: none !important;\n }\n /* Parent crumb: small tertiary text so the 19px current title stays the\n visual anchor; min-height keeps a finger-sized target inside the 48px\n row. Specificity (0,4,1) outranks the shared 19px _crumb rule below. */\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) [class$=\"_crumb\"] {\n font-size: 13px;\n font-weight: 400;\n color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .45));\n min-height: 44px;\n display: inline-flex;\n align-items: center;\n padding: 0 2px 0 6px;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-phase] header [class$=\"_crumbSeg\"]:last-child {\n display: flex;\n align-items: center;\n min-width: 0;\n max-width: 100%;\n }\n /* The last seg's own \"/\" is the divider between parent and current title \u2014\n it only exists when there IS an ancestor (index > 0), so it can simply\n stay visible now that the parent seg shows. (It used to be hidden here:\n with the parent hidden it dangled before the title.) */\n /* BOTH selectors are required: [class$=] matches the whole class\n ATTRIBUTE's suffix, and the current-session title button's attribute is\n \"wSkVaW_crumb wSkVaW_crumbCurrent\" \u2014 it ends in _crumbCurrent, so the _crumb\n suffix selector alone silently missed the one element that matters\n (measured live: the title stayed 14px while the rule sat in the bundle,\n 2026-08-17). */\n [data-phase] header [class$=\"_crumb\"],\n [data-phase] header [class$=\"_crumbCurrent\"] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n max-width: 100%;\n /* Real-device feedback (2026-08-17): the session title read too small\n next to the enlarged header icons \u2014 19px sits just under the home\n screen's 22px workspace title, keeping the two-level hierarchy. */\n font-size: 19px;\n font-weight: 600;\n }\n\n /* Running-status dot: no official element to reposition (the header\n renders only the crumb title, the two action slots, and the tablist \u2014\n no status indicator), so this is the plan's documented fallback \u2014\n read the data ourselves and draw it (effects/header-status.ts stamps\n data-mobile-nav-dot on the frame). A ::after on the title crumb keeps\n it truly inline with the (centered, truncated) text instead of a\n separately positioned box that would fight the centering math. */\n [data-phase] header [class$=\"_crumbCurrent\"]::after {\n content: '';\n display: none;\n width: 6px;\n height: 6px;\n margin-left: 6px;\n border-radius: 50%;\n vertical-align: middle;\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot] header [class$=\"_crumbCurrent\"]::after {\n display: inline-block;\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"ongoing\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-business-primary, #4f6ef7);\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"warning\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-warn-primary, #d97706);\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"done\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-success-primary, #16a34a);\n }\n\n /* Everything else the official header puts in these two list slots \u2014 the\n agent-preset mode badge, the jobs/subagent trigger, the desktop\n Session-log download capsule, and this plugin's own legacy directory\n toggle \u2014 is the \"\u4E0D\u5E38\u9A7B\" set the design defers to the S4 info card.\n Scoped by the slots' own [data-slot] wrapper (a stable, non-hashed\n contract marker) rather than enumerating each registrant, so a future\n third-party header action is hidden by default too. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > *,\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > * {\n display: none !important;\n }\n /* DSH 0.1.5's third titleRow child: the right sidebar's ExpandButton\n (dsh-client-ui-sidebar-right, slot conversation.session.header.corner\n inside div.headerCorner). Left alone it auto-placed into an implicit\n 4th grid column at the screen's top-right corner \u2014 a second panel-left\n icon next to our Workbench button, reading as \"the hidden official\n sidebar toggle is back\" (reported 2026-09-11). The button itself is the\n one our sidebar control forwards to whenever the host has this panel\n (sidebar-panels.ts, official first since 2026-09-21 \u2014 better-sidebar\n 0.19+ puts all its tabs in here), and a display:none button still\n receives synthetic .click() (the tablist precedent), so hiding it costs\n nothing. better-sidebar 0.19's own bottom-workbench toggle\n (`[data-dsh-bottom-toggle]`) registers into the utilities slot and is\n caught by the blanket hide above \u2014 intended: that surface is not for\n the phone.\n Anchors, in the specificity order they NEED (measured live, 2026-09-11):\n the corner div must be beaten with (0,3,1) \u2014 the utilities un-hide rule\n above spells header > :first-child > :last-child at exactly that\n weight, and with 0.1.5's third titleRow child the corner IS that\n :last-child now, so its flex !important outranks any lighter hide (a\n single-attribute hide loses at (0,2,1) \u2014 both spellings were tried\n live). Chaining BOTH of the corner's own anchors on one selector \u2014 the\n host's data-conversation-header-corner contract attribute AND the\n class suffix \u2014 reaches the tie, and the later source order here wins\n it. The slot wrapper rule below needs no help: nothing !important\n fights it, so its (0,2,1) settles the button. */\n [data-phase] header [data-conversation-header-corner][class$=\"_headerCorner\"],\n [data-phase] header [data-slot=\"conversation.session.header.corner\"] {\n display: none !important;\n }\n /* Native background-task / subagent entries (rewritten for DSH 0.1.1,\n 2026-08-21). Three upstream changes killed the old \"park the root as a\n zero-width anchor, hide the trigger, forward the tap\" design at once:\n\n - the subagent entry moved out of header.actions into the new\n conversation.session.header.lineage slot (inside the breadcrumb);\n - its root's class attribute is now \"ZKlsPq_root \" \u2014 with a TRAILING\n SPACE \u2014 so a [class$=\"_root\"] anchor matches nothing at all;\n - the trigger only reacts to TRUSTED input: .click() and synthetic\n pointer/mouse events leave aria-expanded untouched (measured on device\n with every plugin override stripped), so forwarding a tap in JS can\n never open it again.\n\n The trigger's geometry is therefore owned at runtime by\n effects/native-trigger-overlay.ts, which lays the REAL button\n transparently over the activity pill \u2014 a genuine finger tap lands on the\n official element. Nothing in this stylesheet may size, hide, or set\n pointer-events on the trigger: that is exactly what would take the tap\n target away again.\n\n CSS keeps only two chores. Dissolve the root so its box (and its\n stray separator, hidden below) stops occupying the breadcrumb, and let\n the trigger participate from wherever the overlay pins it. And no menu\n re-anchoring anymore: the 0.1.1 popover portals to <body> as\n position:fixed and positions itself from the trigger's viewport rect\n (measured 336px wide, fully on screen at 411px). */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > *:has(> button[class$=\"_trigger\"]),\n [data-phase] header [data-slot=\"conversation.session.header.lineage\"] > *:has(> button[class$=\"_trigger\"]) {\n display: contents !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] [class$=\"_separator\"],\n [data-phase] header [data-slot=\"conversation.session.header.lineage\"] [class$=\"_separator\"] {\n display: none !important;\n }\n /* The agent-preset mode badge specifically survives the blanket hide\n above: layout.css.ts's shared <=1023px block targets it directly\n (header [class$=\"_label\"]:has(> svg) { display: block !important }),\n specificity (0,3,1), higher than the [data-slot] wrapper rule (0,2,1) \u2014\n display wins on specificity before source order, so a same-selector\n override is needed here too. */\n [data-phase] header [class$=\"_label\"]:has(> svg) {\n display: none !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-back\"] {\n display: inline-flex !important;\n }\n /* header-viewrow is position:absolute (see below), so its re-shown\n display value must match the flex layout its own rule declares. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-viewrow\"] {\n display: flex !important;\n }\n /* Our own chip lives in the same slot, so the blanket hide above swallows\n it unless it is named here too (it was, on the first build: the chip\n rendered with the right counts and painted nothing). */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-activity\"] {\n display: inline-flex !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"header-info\"],\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"header-workbench\"] {\n display: inline-flex !important;\n }\n\n /* Back button: 44pt touch target, leftmost column. */\n [data-mobile-nav=\"header-back\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-back\"]:active {\n opacity: .6;\n }\n\n /* Info + workbench utility buttons, rightmost column. */\n [data-mobile-nav=\"header-info\"],\n [data-mobile-nav=\"header-workbench\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 34px;\n height: 34px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-secondary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-info\"]:active,\n [data-mobile-nav=\"header-workbench\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n /* Icon family unification (real-device round 2, 2026-08-17): the \u24D8 text\n glyph is gone (MobileSessionHeader.tsx renders the official\n IconInfoOutlineRegular since DSH 0.1.7) and the workbench button's\n IconPanelLeftOutlineRegular is mirrored into a panel-RIGHT glyph \u2014\n there's no panel-RIGHT glyph in primitives (re-checked against the\n 0.1.7-rc.2 exports), and the plugin's own right-side panel semantics\n are exactly the left icon flipped. Both buttons carry a same-size\n (16px) icon. */\n [data-mobile-nav=\"header-workbench\"] svg {\n transform: scaleX(-1);\n }\n\n /* Official Chat/Trajectory tablist: removed from layout entirely (S2.1 \u2014\n visibility:hidden still held a 27px row, so the header carried the\n view-switch row's band TWICE: 72 + 27 + gap = 104px measured at 390px,\n and a real iPhone added ~54px of notch on top of that). The header's own\n padding-bottom above is now the row's seat.\n display:none does NOT break the view switch: HTMLElement.click()\n dispatches synthetically and fires React's handler regardless of\n rendering \u2014 only real pointer hit-testing needs a box, and this plugin\n never relies on it (MobileSessionHeader always calls .click()). */\n [data-phase] header [class$=\"_tabs\"][role=\"tablist\"] {\n display: none;\n }\n\n /* View-switch row: \"current view name + dots\", replacing the (still\n present, still clickable) official tablist visually. It renders inside\n headerActions/headerUtilities, but titleRow's own position:relative\n (set above) makes IT the containing block for absolute descendants \u2014\n display:contents on titleCluster does not break that search \u2014 so\n top:100% sits directly under the title regardless of the title row's\n actual height, with no hardcoded offset to keep in sync. */\n /* Activity chip (subagents / background tasks) rides the right end of the\n same band. It is a SIBLING of the switch button, not a child \u2014 that\n button spans the full row, so nesting would make every tap on the chip\n switch views too \u2014 and it shares titleRow as its containing block, so\n top:100% lands both on the same 28px line. z-index puts it above the\n row it overlaps. */\n [data-mobile-nav=\"header-activity\"] {\n position: absolute;\n right: 0;\n top: 100%;\n z-index: 1;\n display: inline-flex;\n align-items: center;\n gap: 9px;\n height: 28px;\n padding: 0 2px 0 10px;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-family: inherit;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n /* pointer-events:none so the invisible official trigger laid over this pill\n (effects/native-trigger-overlay.ts) is what a tap reaches. The pill is\n visuals only. */\n [data-mobile-nav=\"activity-pill\"] {\n pointer-events: none;\n display: inline-flex;\n align-items: center;\n gap: 3px;\n height: 28px;\n padding: 0;\n border: none;\n background: transparent;\n color: inherit;\n font-family: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"activity-pill\"]:active {\n opacity: .55;\n }\n [data-mobile-nav=\"activity-count\"] {\n font-variant-numeric: tabular-nums;\n }\n /* Steady green = everything settled. */\n [data-mobile-nav=\"activity-dot\"] {\n display: block;\n width: 6px;\n height: 6px;\n margin-left: 1px;\n border-radius: 50%;\n background: var(--dsw-alias-state-success-primary, #16a34a);\n }\n /* A job that failed or was killed is not a success \u2014 never paint it green. */\n [data-activity-state=\"warning\"] [data-mobile-nav=\"activity-dot\"] {\n background: var(--dsw-alias-state-warn-primary, #d97706);\n }\n /* Pulsing blue = still running. Same state palette as the session dot. */\n [data-activity-state=\"running\"] [data-mobile-nav=\"activity-dot\"] {\n background: var(--dsw-alias-state-business-primary, #4f6ef7);\n animation: dsh-zen-activity-pulse 1.4s ease-in-out infinite;\n }\n @keyframes dsh-zen-activity-pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: .45; transform: scale(.72); }\n }\n @media (prefers-reduced-motion: reduce) {\n [data-activity-state=\"running\"] [data-mobile-nav=\"activity-dot\"] {\n animation: none;\n }\n }\n\n [data-mobile-nav=\"header-viewrow\"] {\n position: absolute;\n left: 0;\n right: 0;\n top: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 28px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-family: inherit;\n font-size: 13px;\n line-height: 18px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] > i {\n display: block;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: currentColor;\n opacity: .35;\n font-style: normal;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] > i[data-active] {\n opacity: 1;\n }\n}\n";
2
2
  //# sourceMappingURL=header.css.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"header.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/header.css.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,UAAU,8pzBAiiBtB,CAAA"}
1
+ {"version":3,"file":"header.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/header.css.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,UAAU,8mzBAiiBtB,CAAA"}
@@ -1,2 +1,2 @@
1
- export declare const INFO_CSS = "/* ---------- session-info sheet (< 768px) ---------- */\n\n/* Unconditional render (React, not CSS, decides open/closed) \u2014 default\n hidden outside the phone breakpoint so a stray SESSION_INFO_EVENT at\n >=768px (the \u24D8 trigger itself is already CSS-hidden there) can never\n paint anything, mirroring header.css.ts's own belt-and-braces list. */\n[data-mobile-nav=\"info-layer\"] {\n display: none;\n}\n\n@media (max-width: 767px) {\n /* header.css.ts hides every [data-slot=\"conversation.session.header.utilities\"]\n direct child by default (its own \u24D8/workbench buttons are the two named\n exceptions) \u2014 this is a second, sibling entry on that same slot, so it\n needs the identical re-show override. Equal specificity\n ([data-slot=\"\u2026\"] > * vs this attribute selector, both one attribute\n selector + !important) means source order decides the tie; this file\n is concatenated after header.css.ts in styles/index.ts, so it wins. */\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"info-layer\"] {\n display: block !important;\n }\n\n /* The sheet renders inside the header, and `[data-phase] header` is a\n stacking context of its own (position: relative + z-index: 2, set in\n header.css.ts so the header's fade strip paints over the scroller). A\n child's z-index is scoped to that context, so the z:70 below competes not\n with the page but with the header's own 2 \u2014 and the host's composer seat\n (.wSkVaW_composerSeat, z:7, from dsh-client-ui-conversation) outranks\n it. Measured live on DSH 0.1.2, 2026-09-04: with the sheet open, the\n topmost element over the composer band was the composer's own trigger\n button, i.e. the input row painted THROUGH the card.\n Promote the header itself for exactly as long as the sheet is mounted \u2014\n MobileSessionInfo returns null when closed (`if (!open) return null`),\n so the header drops back to 2 on close (verified: 70 while open, 2\n after). Descendant :has(), never `>`: the utilities slot wrapper is\n `display: contents` and the host owns the depth (AGENTS.md \u2014 do not\n encode host DOM depth in a selector).\n Not raising the seat's own z-index instead: it belongs to the host and\n is shared with the scroll-to-bottom button and the composer sheets. */\n [data-phase] header:has([data-mobile-nav=\"info-layer\"]) {\n z-index: 70;\n }\n\n [data-mobile-nav=\"info-layer\"] {\n position: fixed;\n inset: 0;\n /* Above the composer's own bottom sheets (permission/model menus sit at\n z:60, styles/composer.css.ts) and the shell.overlay layer's z:20\n ceiling (AGENTS.md stacking-context pitfall) \u2014 this sheet must cover\n both, which is only possible because it renders inside the header's\n own DOM instead of that capped layer. */\n z-index: 70;\n }\n [data-mobile-nav=\"info-mask\"] {\n position: absolute;\n inset: 0;\n border: none;\n background: var(--dsw-alias-bg-mask-3, rgba(0, 0, 0, .45));\n animation: dsh-mobile-nav-fade .18s var(--ds-ease-out, ease-in-out);\n }\n [data-mobile-nav=\"info-sheet\"] {\n position: absolute;\n left: 8px;\n right: 8px;\n bottom: calc(var(--mnav-sab) + 8px);\n max-height: 82dvh;\n overflow-y: auto;\n overscroll-behavior: contain;\n box-sizing: border-box;\n padding: 12px;\n border-radius: 16px;\n background: var(--dsw-alias-bg-layer-2, #ffffff);\n box-shadow: 0 8px 32px rgba(0, 0, 0, .28);\n animation: dsh-mobile-nav-sheet-up .22s var(--ds-ease-out, ease-in-out);\n }\n @media (prefers-reduced-motion: reduce) {\n [data-mobile-nav=\"info-mask\"],\n [data-mobile-nav=\"info-sheet\"] {\n animation: none !important;\n }\n }\n\n /* --- head: Chat/Trajectory segmented control + close --- */\n [data-mobile-nav=\"info-head\"] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n margin-bottom: 10px;\n }\n [data-mobile-nav=\"info-tabs\"] {\n display: flex;\n gap: 2px;\n padding: 2px;\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n border-radius: 12px;\n }\n [data-mobile-nav=\"info-tab\"] {\n min-height: 36px;\n padding: 0 14px;\n border: none;\n border-radius: 10px;\n background: transparent;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-family: inherit;\n font-size: 14px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"info-tab\"][data-selected] {\n background: var(--dsw-alias-bg-layer-2, #ffffff);\n color: var(--dsw-alias-label-primary, inherit);\n font-weight: 600;\n box-shadow: 0 1px 3px rgba(0, 0, 0, .12);\n }\n [data-mobile-nav=\"info-close\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 34px;\n height: 34px;\n flex: none;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-secondary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"info-close\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n\n /* --- badges: agent preset, subagent count, cwd --- */\n [data-mobile-nav=\"info-badges\"] {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n margin-bottom: 12px;\n }\n [data-mobile-nav=\"info-badge\"],\n [data-mobile-nav=\"info-badge-cwd\"] {\n display: inline-flex;\n align-items: center;\n max-width: 100%;\n padding: 4px 10px;\n border-radius: 999px;\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .6));\n font-size: 12px;\n line-height: 18px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n [data-mobile-nav=\"info-badge-cwd\"] {\n font-family: var(--dsw-font-mono, ui-monospace, monospace);\n }\n\n /* --- six-cell stats grid --- */\n [data-mobile-nav=\"info-stats\"] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 8px;\n margin-bottom: 12px;\n }\n [data-mobile-nav=\"info-stat\"] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n min-height: 48px;\n padding: 8px 4px;\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-1, rgba(0, 0, 0, .03));\n text-align: center;\n }\n [data-mobile-nav=\"info-stat-value\"] {\n color: var(--dsw-alias-label-primary, inherit);\n font-size: 17px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n line-height: 1.2;\n }\n [data-mobile-nav=\"info-stat-label\"] {\n color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .4));\n font-size: 11px;\n line-height: 1.3;\n }\n [data-mobile-nav=\"info-stat-sub\"] {\n color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .4));\n font-size: 10px;\n line-height: 1.2;\n }\n\n [data-mobile-nav=\"info-error\"] {\n margin-bottom: 8px;\n padding: 8px 10px;\n border-radius: 10px;\n background: var(--dsw-alias-state-warn-bg, rgba(217, 119, 6, .12));\n color: var(--dsw-alias-state-warn-primary, #d97706);\n font-size: 12px;\n line-height: 1.4;\n }\n\n /* --- action row: export / rename / fork / archive --- */\n [data-mobile-nav=\"info-actions\"] {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 8px;\n }\n [data-mobile-nav=\"info-action\"] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n min-height: 48px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12));\n border-radius: 12px;\n background: transparent;\n color: var(--dsw-alias-label-primary, inherit);\n font-family: inherit;\n font-size: 13px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"info-action\"]:active:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n [data-mobile-nav=\"info-action\"]:disabled {\n opacity: .5;\n cursor: default;\n }\n [data-mobile-nav=\"info-action\"][data-mobile-nav-danger] {\n border-color: var(--dsw-alias-state-warn-primary, #d97706);\n color: var(--dsw-alias-state-warn-primary, #d97706);\n }\n}\n";
1
+ export declare const INFO_CSS = "/* ---------- session-info sheet (< 768px) ---------- */\n\n/* Unconditional render (React, not CSS, decides open/closed) \u2014 default\n hidden outside the phone breakpoint so a stray SESSION_INFO_EVENT at\n >=768px (the \u24D8 trigger itself is already CSS-hidden there) can never\n paint anything, mirroring header.css.ts's own belt-and-braces list. */\n[data-mobile-nav=\"info-layer\"] {\n display: none;\n}\n\n@media (max-width: 767px) {\n /* header.css.ts hides every [data-slot=\"conversation.session.header.utilities\"]\n direct child by default (its own \u24D8/workbench buttons are the two named\n exceptions) \u2014 this is a second, sibling entry on that same slot, so it\n needs the identical re-show override. Equal specificity\n ([data-slot=\"\u2026\"] > * vs this attribute selector, both one attribute\n selector + !important) means source order decides the tie; this file\n is concatenated after header.css.ts in styles/index.ts, so it wins. */\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"info-layer\"] {\n display: block !important;\n }\n\n /* The sheet renders inside the header, and `[data-phase] header` is a\n stacking context of its own (position: relative + z-index: 2, set in\n header.css.ts so the header's fade strip paints over the scroller). A\n child's z-index is scoped to that context, so the z:70 below competes not\n with the page but with the header's own 2 \u2014 and the host's composer seat\n (.wSkVaW_composerSeat, z:7, from dsh-client-ui-conversation) outranks\n it. Measured live on DSH 0.1.2, 2026-09-04: with the sheet open, the\n topmost element over the composer band was the composer's own trigger\n button, i.e. the input row painted THROUGH the card.\n Promote the header itself for exactly as long as the sheet is mounted \u2014\n MobileSessionInfo returns null when closed (`if (!open) return null`),\n so the header drops back to 2 on close (verified: 70 while open, 2\n after). Descendant :has(), never `>`: the utilities slot wrapper is\n `display: contents` and the host owns the depth (AGENTS.md \u2014 do not\n encode host DOM depth in a selector).\n Not raising the seat's own z-index instead: it belongs to the host and\n is shared with the scroll-to-bottom button and the composer sheets. */\n [data-phase] header:has([data-mobile-nav=\"info-layer\"]) {\n z-index: 70;\n }\n\n [data-mobile-nav=\"info-layer\"] {\n position: fixed;\n inset: 0;\n /* Above the composer's own bottom sheets (permission/model menus sit at\n z:60, styles/composer.css.ts) and the shell.overlay layer's z:20\n ceiling (AGENTS.md stacking-context pitfall) \u2014 this sheet must cover\n both, which is only possible because it renders inside the header's\n own DOM instead of that capped layer. */\n z-index: 70;\n }\n [data-mobile-nav=\"info-mask\"] {\n position: absolute;\n inset: 0;\n border: none;\n background: var(--dsw-alias-bg-mask-3, rgba(0, 0, 0, .45));\n animation: dsh-mobile-nav-fade .18s var(--ds-ease-out, ease-in-out);\n }\n [data-mobile-nav=\"info-sheet\"] {\n position: absolute;\n left: 8px;\n right: 8px;\n bottom: calc(var(--mnav-sab) + 8px);\n max-height: 82dvh;\n overflow-y: auto;\n overscroll-behavior: contain;\n box-sizing: border-box;\n padding: 12px;\n border-radius: 16px;\n background: var(--dsw-alias-bg-layer-2, #ffffff);\n box-shadow: 0 8px 32px rgba(0, 0, 0, .28);\n animation: dsh-mobile-nav-sheet-up .22s var(--ds-ease-out, ease-in-out);\n }\n @media (prefers-reduced-motion: reduce) {\n [data-mobile-nav=\"info-mask\"],\n [data-mobile-nav=\"info-sheet\"] {\n animation: none !important;\n }\n }\n\n /* --- head: Chat/Trajectory segmented control + close --- */\n [data-mobile-nav=\"info-head\"] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n margin-bottom: 10px;\n }\n [data-mobile-nav=\"info-tabs\"] {\n display: flex;\n gap: 2px;\n padding: 2px;\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n border-radius: 12px;\n }\n [data-mobile-nav=\"info-tab\"] {\n min-height: 36px;\n padding: 0 14px;\n border: none;\n border-radius: 10px;\n background: transparent;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-family: inherit;\n font-size: 14px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"info-tab\"][data-selected] {\n background: var(--dsw-alias-bg-layer-2, #ffffff);\n color: var(--dsw-alias-label-primary, inherit);\n font-weight: 600;\n box-shadow: 0 1px 3px rgba(0, 0, 0, .12);\n }\n [data-mobile-nav=\"info-close\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 34px;\n height: 34px;\n flex: none;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-secondary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"info-close\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n\n /* --- badges: agent preset, subagent count, cwd --- */\n [data-mobile-nav=\"info-badges\"] {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n margin-bottom: 12px;\n }\n [data-mobile-nav=\"info-badge\"],\n [data-mobile-nav=\"info-badge-cwd\"] {\n display: inline-flex;\n align-items: center;\n max-width: 100%;\n padding: 4px 10px;\n border-radius: 999px;\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .6));\n font-size: 12px;\n line-height: 18px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n [data-mobile-nav=\"info-badge-cwd\"] {\n font-family: var(--dsw-font-mono, ui-monospace, monospace);\n }\n\n /* --- six-cell stats grid --- */\n [data-mobile-nav=\"info-stats\"] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 8px;\n margin-bottom: 12px;\n }\n [data-mobile-nav=\"info-stat\"] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n min-height: 48px;\n padding: 8px 4px;\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-1, rgba(0, 0, 0, .03));\n text-align: center;\n }\n [data-mobile-nav=\"info-stat-value\"] {\n color: var(--dsw-alias-label-primary, inherit);\n font-size: 17px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n line-height: 1.2;\n }\n [data-mobile-nav=\"info-stat-label\"] {\n color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .4));\n font-size: 11px;\n line-height: 1.3;\n }\n [data-mobile-nav=\"info-stat-sub\"] {\n color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .4));\n font-size: 10px;\n line-height: 1.2;\n }\n\n /* --- T67: the remote-access row (label + live description + switch) --- */\n [data-mobile-nav=\"info-remote\"] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n margin-bottom: 8px;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12));\n border-radius: 12px;\n }\n [data-mobile-nav=\"info-remote-text\"] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n }\n [data-mobile-nav=\"info-remote-label\"] {\n color: var(--dsw-alias-label-primary, inherit);\n font-size: 13px;\n line-height: 1.3;\n }\n [data-mobile-nav=\"info-remote-desc\"] {\n color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .4));\n font-size: 11px;\n line-height: 1.3;\n }\n /* The switch: a 44x26 track whose TRANSPARENT ::before stretches the\n tappable area to 52x44 (inset -9px vertical / -4px horizontal) \u2014 the\n 44x44 minimum touch target without moving the visible control; ::after\n is the thumb, so ::before carries the hot-zone extension. The horizontal\n reach leans toward the row edge, away from the description text. */\n [data-mobile-nav=\"info-remote-switch\"] {\n position: relative;\n flex: none;\n width: 44px;\n height: 26px;\n padding: 0;\n border: none;\n border-radius: 13px;\n background: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .4));\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n transition: background .15s ease;\n }\n [data-mobile-nav=\"info-remote-switch\"]::before {\n content: '';\n position: absolute;\n inset: -9px -4px;\n }\n [data-mobile-nav=\"info-remote-switch\"]::after {\n content: '';\n position: absolute;\n top: 3px;\n left: 3px;\n width: 20px;\n height: 20px;\n border-radius: 50%;\n background: #fff;\n box-shadow: 0 1px 2px rgba(0, 0, 0, .25);\n transition: transform .15s ease;\n }\n [data-mobile-nav=\"info-remote-switch\"][data-on] {\n background: var(--dsw-alias-state-success-primary, #16a34a);\n }\n [data-mobile-nav=\"info-remote-switch\"][data-on]::after {\n transform: translateX(18px);\n }\n [data-mobile-nav=\"info-remote-switch\"]:disabled {\n opacity: .5;\n cursor: default;\n }\n\n [data-mobile-nav=\"info-error\"] {\n margin-bottom: 8px;\n padding: 8px 10px;\n border-radius: 10px;\n background: var(--dsw-alias-state-warn-bg, rgba(217, 119, 6, .12));\n color: var(--dsw-alias-state-warn-primary, #d97706);\n font-size: 12px;\n line-height: 1.4;\n }\n\n /* --- action row: export / rename / fork / archive --- */\n [data-mobile-nav=\"info-actions\"] {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 8px;\n }\n [data-mobile-nav=\"info-action\"] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n min-height: 48px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12));\n border-radius: 12px;\n background: transparent;\n color: var(--dsw-alias-label-primary, inherit);\n font-family: inherit;\n font-size: 13px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"info-action\"]:active:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n [data-mobile-nav=\"info-action\"]:disabled {\n opacity: .5;\n cursor: default;\n }\n [data-mobile-nav=\"info-action\"][data-mobile-nav-danger] {\n border-color: var(--dsw-alias-state-warn-primary, #d97706);\n color: var(--dsw-alias-state-warn-primary, #d97706);\n }\n}\n";
2
2
  //# sourceMappingURL=info.css.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"info.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/info.css.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,QAAQ,m+QAoPpB,CAAA"}
1
+ {"version":3,"file":"info.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/info.css.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,QAAQ,gxVA8TpB,CAAA"}
@@ -0,0 +1,2 @@
1
+ export declare const REMOTE_SESSION_CSS = "/* ---------- remote session: hide the open-on-the-server-machine entries ----------\n A remote session's files live on the relay SERVER; every \"open\" control here\n would act on the machine the DSH process runs on, not the one the browser is\n on. Hidden while the current session is remote (html[data-zr-remote-session]):\n - [data-open-target] is dsh-client-ui-open-in-app's own mount marker (RT\n dsh-client-ui-open-in-app lib/client.js ~325: the split-button anchor\n React renders with data-open-target={kind}) \u2014 one selector covers the\n session-header \"Open In...\" button, the document-preview actions, AND the\n \"Open / Show in Finder / Choose an application\" buttons of the changes\n panel and the deliverable cards (RT dsh-client-ui-deliverables\n lib/client.js ~1702, ~1963: those cards render the open-in-app package's\n FileRouteAction through the deliverables.file.actions and\n deliverables.review.file.actions slots, the same OpenTargetButton anchor).\n The attribute is the component's own contract, not a generated class. */\nhtml[data-zr-remote-session=\"1\"] [data-open-target] {\n display: none !important;\n}\n\n/* ---------- remote session: hide dsh-better-sidebar's file surfaces (T53) ----------\n dsh-better-sidebar 0.24.x is the second big \"open the machine's files\" UI.\n Every tab it registers (files/editor, git changes, subagent/tasks, side\n chat, the markdown/html/code file viewers) builds its requests from a\n {sessionId, cwd} scope and POSTs them to the plugin's own /sidebar/api/*\n routes on whichever machine answers \u2014 in a remote session that is the\n SUB-CLIENT's machine (client.js call() ~3111, fetchUpload ~3162,\n encodeHtmlUrl /sidebar/html/ ~3069-3414), so the tree would show same-named\n LOCAL files as if they were the session's and an edit would WRITE locally.\n Per the T53 decision the interfaces are hidden, not proxied: the relay\n carries no /sidebar routes and none are added.\n\n Anchors, all the plugin's or the host's own rendered contracts (no hashed\n class names; the one suffix selector, _chipIcon, is unique among the\n profile's packages \u2014 checked dsh-vision-router / dsh-auto-approve /\n dsh-mem0 / dsh-llm-verifier / dsh-plugin-subscriptions /\n @liustack/modsearch / @wxg-prc-cpg/browser-skill-dsh-plugin (modsearch /\n browser-skill) / dshmarket and every @deepseek-ai sidebar package for it):\n\n HONEST LIMITS (T53-fix): everything here is display:none ONLY. The plugin's\n components stay mounted and keep silently POSTing READ-ONLY /sidebar/api\n requests to the local machine (session.cwd, changes.ops, the tree reads a\n mounted tab refreshes with) \u2014 the data never leaves that machine, and no\n write can happen because every write control is inside the hidden surfaces.\n Known boundary: a dsh-resource://file/** address opened through\n sidebarRight.openResource in a remote session (file links in chat,\n deliverables, @-references, skills) is CLAIMED by the plugin's editor\n registration (client.js ~21377: patterns [\"dsh-resource://file/**\"]) before\n the host preview sees it \u2014 the right sidebar expands, the rules below hide\n the tab's body and chip, and an EMPTY panel remains. Root fix needs\n zen-remote's own remote file tree; separate discussion.\n\n - [data-dsh-bottom-toggle] \u2014 the header-utilities button that expands the\n bottom workbench (client.js BottomDockToggle ~20863; slot registration\n ~21492-21498, id dsh-better-sidebar:bottom-toggle). Our own phone header\n button drives the OFFICIAL right sidebar (MobileSessionHeader.tsx), so\n hiding this does not strand the phone.\n - [data-dsh-panel-host] \u2014 the fixed viewport layer the plugin appends to\n document.body (client.js ~20763); the bottom workbench panel\n ([data-dsh-bottom-panel]) is its only child, so hiding the layer hides\n the workbench whole (tab strip, + menu, tree, editor and all).\n - --dsh-sidebar-height \u2014 the plugin's layout push: written INLINE on <html>\n while the workbench is open (client.js writeGeometry ~20482-20488) and\n spent by its own layout.css as the center column's margin-bottom\n (client.js layout css ~23009: #root [data-dsh-center-col] { margin-bottom:\n var(--dsh-sidebar-height, 0px) }). Hiding the panel alone would leave\n that reserved band as a blank strip under the conversation, so the remote\n html zeroes the variable. A stylesheet !important beats a non-important\n inline style, and the plugin's JS state is never touched: the moment the\n attribute is removed (back on a local session) its own value applies\n again, transitions included.\n - [data-dsh-native-tab-host] \u2014 the wrapper the plugin renders around EVERY\n tab body it contributes to the host's native right Sidebar\n (client.js NativeTabBody ~21074, marker ~21110/21120). The host's pane\n chrome is untouched: the strip keeps the \"+\" (guide) button and the\n collapse/fullscreen controls, so a tab that was already open leaves no\n dead panel \u2014 an empty body behind a strip that still collapses, and the\n \"+\" re-opens the guide page (whose plugin capsules these rules hide, see\n below). React keeps mounting into the display:none node: no errors, and\n the restore on returning to a local session is instant.\n - [data-dockkit-tab]:has([class$=\"_chipIcon\"]) \u2014 the native tab STRIP's\n chips for plugin tabs, so an already-open tab does not survive as a\n clickable ghost. The chip button is dockkit's ([data-dockkit-tab], dsh-web\n frontend bundle: role=tab, renderTabTitle inside a\n span[data-dockkit-tab-title]); its content is the plugin's\n NativeTabTitle (~21172-21197), which always leads with a\n [class$=\"_chipIcon\"] glyph span because every built-in descriptor ships an\n icon \u2014 a chip with no glyph (an external tab registered through the\n plugin's viewer service without an icon) keeps its chip; its body is\n still hidden by the rule above. Host tabs' chips never contain a\n _chipIcon (checked all four @deepseek-ai sidebar packages), so the\n remote-aware host files page survives alongside.\n - [data-floating-window]:has([data-window-body]) \u2014 the plugin's\n body-PORTALED floating windows (client.js FloatingWindow ~15078-15120):\n the task edit window (teams.taskCreate/Update with rootSessionId via\n /sidebar/api, writeTask ~3124-3140) and the job-output window (host jobs\n kill). Both open from the subagent/tasks tab; portal escapes the tab\n body's display:none, so they need their own rule. Anchored on the\n window's own root attribute plus its own inner body marker, so a foreign\n [data-floating-window] without that shape never matches.\n - guide capsules \u2014 the empty-sidebar doorway buttons the HOST renders from\n the plugin's registrations ([data-sidebar-right-guide-entry=\"<kind>\"], RT\n dsh-client-ui-sidebar-right lib/client.js:466): kinds git / subagent /\n sidechat exist only while this plugin registers them, so those selectors\n are inert without it. The \"files\" capsule is the one ambiguous kind:\n dsh-client-ui-sidebar-files registers the SAME kind (its filesDefinition,\n lib/client.js:14-31) and that page is REMOTE-AWARE (it injects the host's\n remote.workspaceFiles service). T53-fix discriminates on the capsule's\n own rendered contract instead of plugin presence: the host's EntryBox\n renders aria-keyshortcuts from the shortcut matching entry.commandId (RT\n sidebar-right lib/client.js:461-467) \u2014 the host files guide row carries\n commandId \"workspace.files\" (dsh-client-ui-sidebar-files\n lib/client.js:30), while better-sidebar's guide rows carry NO commandId\n (client.js registerNativeSurface ~21385-21400), so its capsule never\n renders the attribute. :not([aria-keyshortcuts]) therefore hides the\n plugin's takeover capsule and spares the host's wherever the host has a\n default binding. Boundary: that \"wherever\" is not everywhere \u2014 the host\n registers workspace.files defaults for desktop:macos/windows/linux and\n web:macos/windows only (dsh-client-ui-sidebar-files lib/client.js:949-977),\n so on any other platform the OFFICIAL capsule also renders without the\n attribute and this rule hides it too (cosmetic; the page stays reachable\n through its shortcut command path and the plugin's takeover is the\n \"files\" implementation on every deployment this plugin pairs with\n better-sidebar anyway). */\nhtml[data-zr-remote-session=\"1\"] [data-dsh-bottom-toggle] {\n display: none !important;\n}\nhtml[data-zr-remote-session=\"1\"] [data-dsh-panel-host] {\n display: none !important;\n}\nhtml[data-zr-remote-session=\"1\"] {\n --dsh-sidebar-height: 0px !important;\n}\nhtml[data-zr-remote-session=\"1\"] [data-dsh-native-tab-host] {\n display: none !important;\n}\nhtml[data-zr-remote-session=\"1\"] [data-dockkit-tab]:has([class$=\"_chipIcon\"]) {\n display: none !important;\n}\nhtml[data-zr-remote-session=\"1\"] [data-floating-window]:has([data-window-body]) {\n display: none !important;\n}\nhtml[data-zr-remote-session=\"1\"] [data-sidebar-right-guide-entry=\"git\"],\nhtml[data-zr-remote-session=\"1\"] [data-sidebar-right-guide-entry=\"subagent\"],\nhtml[data-zr-remote-session=\"1\"] [data-sidebar-right-guide-entry=\"sidechat\"] {\n display: none !important;\n}\nhtml[data-zr-remote-session=\"1\"] [data-sidebar-right-guide-entry=\"files\"]:not([aria-keyshortcuts]) {\n display: none !important;\n}\n\n/* ---------- T52: model-menu groups follow the session's side ----------\n The model catalog is GLOBAL (one shared load per page), so the merged\n catalog shows the server's groups everywhere; but a selection is only\n runnable on the side the open session lives on \u2014 the interceptor refuses\n the other side with a clear message. Hide what cannot run, by the one\n stable mark the menu carries:\n - the model menu renders one section[role=\"group\"] per provider group\n with aria-labelledby=\"<useId>-<group.id>\" (RT dsh-client-ui-model-\n selection lib/client.js:830-835), so a VIRTUAL group's section carries\n the \"zr~\" prefix in that attribute \u2014 an attribute the component itself\n writes, like data-open-target above;\n - the menu is a portal under <body>, but <html> is still its ancestor,\n so the html-level session attribute scopes it with no host-depth\n assumption and no :has();\n - the [role=\"menu\"] scope keeps the rule inside dropdown menus (where the\n model picker lives); if DSH ever drops the aria mark the rule simply\n stops matching \u2014 the interceptor's refusals remain the backstop.\n In a remote session only the server's groups stay; otherwise only the\n local ones (the home page and dialogs read as local, which is right:\n the model picker only mounts inside a session, and a blank local\n session cannot run server models either). */\nhtml[data-zr-remote-session=\"1\"] [role=\"menu\"] section[role=\"group\"]:not([aria-labelledby*=\"zr~\"]) {\n display: none !important;\n}\nhtml:not([data-zr-remote-session]) [role=\"menu\"] section[role=\"group\"][aria-labelledby*=\"zr~\"] {\n display: none !important;\n}\n\n/* ---------- T52-fix: hide the virtual groups in NATIVE <option> pickers ----\n The model menu above is React DOM, but dsh-vision-router's settings render\n its vision-backend picker as a native <select> of <option value={group.id}>\n rows, and its filter (dsh-vision-router lib/client.js:661-670\n filterVisionBackendGroups) drops only vision-http / vision-chain / *-vision\n \u2014 a merged virtual group (\"zr~<id>~\u2026\") survives into that picker even\n though a server model can never be a LOCAL vision backend (the router calls\n providers from the machine it runs on). Native <option> rows honor\n display:none in the dropdown list, so this hides them wherever they appear:\n - no scoping attribute on purpose: a virtual group is not a runnable\n backend in ANY local context, and the option element itself carries the\n value \u2014 no host-depth assumption, no :has();\n - when the vision router (or any other <option>-rendering surface) is not\n installed, nothing matches \u2014 dead rule, zero cost;\n - zen-remote's own surfaces are checked: our settings forms render fixed\n enum options (\"web\" / \"desktop-client\" / role kinds /\n configForm enum strings, src/client/settings/SettingsSection.tsx:641,690-701)\n and none can start with \"zr~\" (virtual group ids only ever exist inside\n model-catalog payloads and model-selection arguments). */\noption[value^=\"zr~\"] {\n display: none !important;\n}\n\n/* ---------- remote session: hide the session-export menu item (T51, T51-fix) ----------\n A remote session's log lives on the relay server and the backend refuses\n /api/session.export for a virtual id with 403 remote-unsupported\n (fetch-route-intercept.ts) \u2014 the item would only ever end in the \"Export\n failed\" modal, so it is hidden IN THE MENU (T51-fix: the anchor button\n stays, because it also opens the \u53CD\u9988 entry, which a remote session can\n still use). The DOM, verified against RT:\n - the Menu renders its anchor and its list as SIBLINGS inside its own\n span root (RT dsh-client-ui-primitives lib/index.js ~4265-4273:\n children = [anchor, portal ? createPortal(list) : list]), and the\n export Menu passes no portal flag (default false, ~3923) \u2014 so the list\n is the anchor's next sibling, matched by the general-sibling\n combinator below;\n - the list surface carries role=\"menu\" (Menu passes it at ~4243;\n MenuSurface spreads ...props onto its root div, ~3778-3795) and the\n items viewport inside it is div[role=\"presentation\"] (~4249-4252);\n - every data item is ONE div (the itemWrap wrapper, ~4143), so\n :first-child of the viewport is the FIRST item \u2014 and the export\n package puts \u4E0B\u8F7D/\u5BFC\u51FA first by construction (RT dsh-session-log-export\n lib/client.js 202-206, the optional feedback entry appended after at\n 207-211).\n The anchor button class suffix is matched the way compat.css.ts matches\n module classes (the hash prefix is build-local, \"_moreButton\" is the\n source name; unique in this slot \u2014 the only other moreButton in the RT\n tree, dsh-client-ui-sidebar-documentpreview lib/client.excel.js, lives in\n the sidebar, never under conversation.session.header.utilities).\n Known costs, accepted: with the feedback UI absent the menu's only item\n is the export, so the opened menu is EMPTY; and keyboard navigation still\n steps into the hidden item (focusable but invisible) until activated or\n escaped \u2014 it cannot be triggered through the visibility, only tabbed\n past blindly. */\nhtml[data-zr-remote-session=\"1\"] [data-slot=\"conversation.session.header.utilities\"] [class$=\"_moreButton\"] ~ [role=\"menu\"] > [role=\"presentation\"] > :first-child {\n display: none !important;\n}\n";
2
+ //# sourceMappingURL=remote-session.css.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-session.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/remote-session.css.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,kBAAkB,4+dA0O9B,CAAA"}