dsh-zen-remote 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/AGENTS.md +51 -0
  2. package/LICENSE +38 -0
  3. package/README.md +237 -0
  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 +17 -0
  10. package/cordis.patch.yml.example +16 -0
  11. package/docs/interface.md +200 -0
  12. package/docs/remote-access.en.md +335 -0
  13. package/docs/remote-access.md +422 -0
  14. package/dsh-push.mjs +213 -0
  15. package/lan-gate.mjs +78 -0
  16. package/lib/client.js +6615 -0
  17. package/lib/index.js +310 -0
  18. package/lib/index.js.map +1 -0
  19. package/lib/lan-gate-server.cjs +626 -0
  20. package/lib/types/client/MobileAttachButton.d.ts +25 -0
  21. package/lib/types/client/MobileAttachButton.d.ts.map +1 -0
  22. package/lib/types/client/MobileAttachChips.d.ts +32 -0
  23. package/lib/types/client/MobileAttachChips.d.ts.map +1 -0
  24. package/lib/types/client/MobileDrawerFooter.d.ts +21 -0
  25. package/lib/types/client/MobileDrawerFooter.d.ts.map +1 -0
  26. package/lib/types/client/MobileHome.d.ts +24 -0
  27. package/lib/types/client/MobileHome.d.ts.map +1 -0
  28. package/lib/types/client/MobileHomeChips.d.ts +76 -0
  29. package/lib/types/client/MobileHomeChips.d.ts.map +1 -0
  30. package/lib/types/client/MobileNavOverlay.d.ts +15 -0
  31. package/lib/types/client/MobileNavOverlay.d.ts.map +1 -0
  32. package/lib/types/client/MobileNavToggle.d.ts +18 -0
  33. package/lib/types/client/MobileNavToggle.d.ts.map +1 -0
  34. package/lib/types/client/MobileSessionHeader.d.ts +58 -0
  35. package/lib/types/client/MobileSessionHeader.d.ts.map +1 -0
  36. package/lib/types/client/MobileSessionInfo.d.ts +41 -0
  37. package/lib/types/client/MobileSessionInfo.d.ts.map +1 -0
  38. package/lib/types/client/attach-upload.d.ts +78 -0
  39. package/lib/types/client/attach-upload.d.ts.map +1 -0
  40. package/lib/types/client/chips-store.d.ts +13 -0
  41. package/lib/types/client/chips-store.d.ts.map +1 -0
  42. package/lib/types/client/debug.d.ts +9 -0
  43. package/lib/types/client/debug.d.ts.map +1 -0
  44. package/lib/types/client/effects/aionui-compat.d.ts +4 -0
  45. package/lib/types/client/effects/aionui-compat.d.ts.map +1 -0
  46. package/lib/types/client/effects/gestures.d.ts +7 -0
  47. package/lib/types/client/effects/gestures.d.ts.map +1 -0
  48. package/lib/types/client/effects/header-status.d.ts +15 -0
  49. package/lib/types/client/effects/header-status.d.ts.map +1 -0
  50. package/lib/types/client/effects/phone-chrome.d.ts +106 -0
  51. package/lib/types/client/effects/phone-chrome.d.ts.map +1 -0
  52. package/lib/types/client/effects/turn-fold.d.ts +25 -0
  53. package/lib/types/client/effects/turn-fold.d.ts.map +1 -0
  54. package/lib/types/client/index.d.ts +18 -0
  55. package/lib/types/client/index.d.ts.map +1 -0
  56. package/lib/types/client/locales.d.ts +58 -0
  57. package/lib/types/client/locales.d.ts.map +1 -0
  58. package/lib/types/client/nav-store.d.ts +73 -0
  59. package/lib/types/client/nav-store.d.ts.map +1 -0
  60. package/lib/types/client/session-dot.d.ts +12 -0
  61. package/lib/types/client/session-dot.d.ts.map +1 -0
  62. package/lib/types/client/styles/base.css.d.ts +2 -0
  63. package/lib/types/client/styles/base.css.d.ts.map +1 -0
  64. package/lib/types/client/styles/chips.css.d.ts +2 -0
  65. package/lib/types/client/styles/chips.css.d.ts.map +1 -0
  66. package/lib/types/client/styles/compat.css.d.ts +2 -0
  67. package/lib/types/client/styles/compat.css.d.ts.map +1 -0
  68. package/lib/types/client/styles/composer.css.d.ts +2 -0
  69. package/lib/types/client/styles/composer.css.d.ts.map +1 -0
  70. package/lib/types/client/styles/header.css.d.ts +2 -0
  71. package/lib/types/client/styles/header.css.d.ts.map +1 -0
  72. package/lib/types/client/styles/home.css.d.ts +2 -0
  73. package/lib/types/client/styles/home.css.d.ts.map +1 -0
  74. package/lib/types/client/styles/index.d.ts +15 -0
  75. package/lib/types/client/styles/index.d.ts.map +1 -0
  76. package/lib/types/client/styles/info.css.d.ts +2 -0
  77. package/lib/types/client/styles/info.css.d.ts.map +1 -0
  78. package/lib/types/client/styles/layout.css.d.ts +2 -0
  79. package/lib/types/client/styles/layout.css.d.ts.map +1 -0
  80. package/lib/types/client/styles/misc.css.d.ts +2 -0
  81. package/lib/types/client/styles/misc.css.d.ts.map +1 -0
  82. package/lib/types/client/styles/turn-fold.css.d.ts +2 -0
  83. package/lib/types/client/styles/turn-fold.css.d.ts.map +1 -0
  84. package/lib/types/index.d.ts +83 -0
  85. package/lib/types/index.d.ts.map +1 -0
  86. package/llms.txt +20 -0
  87. package/package.json +128 -0
  88. package/pwa/app.css +95 -0
  89. package/pwa/icons/icon-192.png +0 -0
  90. package/pwa/icons/icon-512.png +0 -0
  91. package/pwa/icons/icon-maskable-512.png +0 -0
  92. package/pwa/icons/icon-maskable.svg +1 -0
  93. package/pwa/icons/icon.svg +1 -0
  94. package/pwa/inject.js +190 -0
  95. package/pwa/manifest.json +29 -0
  96. package/pwa/offline.html +38 -0
  97. package/pwa/sw.js +176 -0
  98. package/pwa/touch-gestures.js +94 -0
  99. package/scripts/build-client.mjs +101 -0
  100. package/scripts/cdp-probe.mjs +64 -0
  101. package/scripts/check-attach-upload.mjs +99 -0
  102. package/scripts/check-sunk-viewport.mjs +33 -0
  103. package/scripts/check-upload-endpoint.mjs +168 -0
  104. package/src/client/MobileAttachButton.tsx +125 -0
  105. package/src/client/MobileAttachChips.tsx +85 -0
  106. package/src/client/MobileDrawerFooter.tsx +56 -0
  107. package/src/client/MobileHome.tsx +348 -0
  108. package/src/client/MobileHomeChips.tsx +426 -0
  109. package/src/client/MobileNavOverlay.tsx +279 -0
  110. package/src/client/MobileNavToggle.tsx +52 -0
  111. package/src/client/MobileSessionHeader.tsx +242 -0
  112. package/src/client/MobileSessionInfo.tsx +303 -0
  113. package/src/client/attach-upload.ts +140 -0
  114. package/src/client/chips-store.ts +41 -0
  115. package/src/client/debug.ts +253 -0
  116. package/src/client/effects/aionui-compat.ts +183 -0
  117. package/src/client/effects/gestures.ts +265 -0
  118. package/src/client/effects/header-status.ts +30 -0
  119. package/src/client/effects/phone-chrome.ts +265 -0
  120. package/src/client/effects/turn-fold.ts +258 -0
  121. package/src/client/index.tsx +227 -0
  122. package/src/client/locales.ts +109 -0
  123. package/src/client/nav-store.ts +78 -0
  124. package/src/client/session-dot.ts +17 -0
  125. package/src/client/styles/base.css.ts +167 -0
  126. package/src/client/styles/chips.css.ts +224 -0
  127. package/src/client/styles/compat.css.ts +681 -0
  128. package/src/client/styles/composer.css.ts +504 -0
  129. package/src/client/styles/header.css.ts +368 -0
  130. package/src/client/styles/home.css.ts +477 -0
  131. package/src/client/styles/index.ts +25 -0
  132. package/src/client/styles/info.css.ts +238 -0
  133. package/src/client/styles/layout.css.ts +501 -0
  134. package/src/client/styles/misc.css.ts +128 -0
  135. package/src/client/styles/turn-fold.css.ts +106 -0
  136. package/src/index.ts +343 -0
@@ -0,0 +1,681 @@
1
+ // compat — split from src/client/mobile.css.ts (2026-08-16), order preserved.
2
+ // Do not reorder: styles/index.ts concatenates in this exact order.
3
+
4
+ export const COMPAT_CSS = ` /* ---------- dsh-web-ui family compatibility ----------
5
+ The linxin666 plugin suite extends the shell frame directly:
6
+ - aionui-panel appends two trailing grid columns (explorer / preview)
7
+ plus absolute drag handles to [data-dsh-frame]; its 5-track inline
8
+ grid is already overridden above, but the handles and columns would
9
+ still float over the main UI. On mobile the columns leave the grid
10
+ as floating bottom sheets and keep their own visibility state —
11
+ the suite's collapse chevron / preview tabs still work, so no
12
+ feature is lost. The task-board / ssh plugins inject sidebar
13
+ entries and center-column takeover panels; the entries need
14
+ spacing and the kanban needs scrollable columns. */
15
+
16
+ /* Touch devices: the drag handles are useless — the floating expand
17
+ button is the opener. */
18
+ .aionui-explorer-handle,
19
+ .aionui-preview-handle {
20
+ display: none !important;
21
+ }
22
+
23
+ /* Shared base: both columns leave the grid as floating panels. The
24
+ explorer is gated shut by default (its own persisted expanded state
25
+ must never cover the mobile UI on load); the header Files action opens
26
+ it via the frame marker below, and the sheet's own collapse chevron
27
+ clears it. Preview stays owned by the suite (hidden while no tab is
28
+ open). The per-column rules below override the geometry. */
29
+ [data-aionui-explorer-col],
30
+ [data-aionui-preview-col] {
31
+ position: fixed !important;
32
+ z-index: 55 !important;
33
+ background: var(--aion-bg-base, #ffffff) !important;
34
+ border-left: none !important;
35
+ }
36
+ /* Explorer (file tree) bottom sheet: bottom edge aligned exactly with
37
+ the composer card's bottom line — the card sits 36px above the
38
+ viewport bottom (8px composer padding + the 28px stats strip below
39
+ the card), so the sheet uses the same 36px bottom offset. */
40
+ [data-aionui-explorer-col] {
41
+ visibility: hidden !important;
42
+ left: 8px !important;
43
+ right: 8px !important;
44
+ top: auto !important;
45
+ bottom: 36px !important;
46
+ width: auto !important;
47
+ height: min(55dvh, 460px) !important;
48
+ max-height: calc(100dvh - 44px) !important;
49
+ border-radius: 14px !important;
50
+ overflow: hidden !important;
51
+ box-shadow: 0 -4px 28px rgba(0, 0, 0, .18) !important;
52
+ animation: dsh-mobile-nav-sheet-up .24s var(--ds-ease-out, ease-in-out) !important;
53
+ }
54
+ /* Preview (file content) bottom sheet. Gated shut by default: the suite
55
+ persists open preview tabs in localStorage and restores them on load,
56
+ which would pop the sheet over the fresh UI. The client only sets the
57
+ frame marker after the user taps a file row in the explorer; the
58
+ suite's own collapse chevron clears it via the visibility watcher. */
59
+ [data-aionui-preview-col] {
60
+ visibility: hidden !important;
61
+ position: fixed !important;
62
+ left: 8px !important;
63
+ right: 8px !important;
64
+ top: auto !important;
65
+ bottom: 40px !important;
66
+ width: auto !important;
67
+ height: min(50dvh, 420px) !important;
68
+ max-height: calc(100dvh - 48px) !important;
69
+ border-radius: 14px !important;
70
+ overflow: hidden !important;
71
+ box-shadow: 0 -4px 28px rgba(0, 0, 0, .18) !important;
72
+ z-index: 56 !important;
73
+ animation: dsh-mobile-nav-sheet-up .24s var(--ds-ease-out, ease-in-out) !important;
74
+ /* Fullscreen toggle (issue #8): animate the geometry change instead of
75
+ snapping. visibility is deliberately not listed, so opening/closing
76
+ the sheet stays instant; the open/close keyframes own transform. */
77
+ transition:
78
+ left .24s var(--ds-ease-out, ease-in-out),
79
+ right .24s var(--ds-ease-out, ease-in-out),
80
+ top .24s var(--ds-ease-out, ease-in-out),
81
+ bottom .24s var(--ds-ease-out, ease-in-out),
82
+ width .24s var(--ds-ease-out, ease-in-out),
83
+ height .24s var(--ds-ease-out, ease-in-out),
84
+ border-radius .24s var(--ds-ease-out, ease-in-out),
85
+ box-shadow .24s var(--ds-ease-out, ease-in-out),
86
+ padding-top .24s var(--ds-ease-out, ease-in-out) !important;
87
+ }
88
+ /* User-opened preview sheet (frame marker, set on file-row tap). */
89
+ [data-mobile-nav="frame"][data-aionui-preview-open] [data-aionui-preview-col] {
90
+ visibility: visible !important;
91
+ }
92
+ /* The Files action opens the explorer sheet (frame marker). */
93
+ [data-mobile-nav="frame"][data-aionui-explorer-open] [data-aionui-explorer-col] {
94
+ visibility: visible !important;
95
+ }
96
+ /* While the preview sheet is up, the explorer sheet yields (two stacked
97
+ bottom sheets would read as one broken overlay). Closing the preview
98
+ via its collapse chevron / tab close clears the marker, and the
99
+ explorer sheet returns. Same specificity as the explorer-open rule, so
100
+ this must stay AFTER it. */
101
+ [data-mobile-nav="frame"][data-aionui-preview-open] [data-aionui-explorer-col] {
102
+ visibility: hidden !important;
103
+ }
104
+ /* The open drawer must never sit under a sheet: while the frame is in the
105
+ narrow-expanded state both sheets yield (later in the file than the
106
+ open marker rule, so it wins at equal specificity). The fullscreen
107
+ toggle has its own drawer-open rule at the end of its section. */
108
+ [data-mobile-nav="frame"]:not([data-sidebar-collapsed]) [data-aionui-explorer-col],
109
+ [data-mobile-nav="frame"]:not([data-sidebar-collapsed]) [data-aionui-preview-col] {
110
+ visibility: hidden !important;
111
+ display: none !important;
112
+ }
113
+ /* The suite's own expand button reads the store state we bypass on
114
+ mobile — hide it; the header Files action is the opener. */
115
+ .aionui-floating-expand {
116
+ display: none !important;
117
+ }
118
+
119
+ /* Preview sheet fullscreen toggle (issue #8): a fixed button parked in the
120
+ sheet's titlebar row, just left of the suite's collapse chevron (24px at
121
+ right:8px of the sheet, and the sheet spans 8px..(100vw-8px)). The top
122
+ calc mirrors the sheet geometry above (bottom 40px + min(50dvh, 420px));
123
+ when the frame carries "data-mobile-preview-full" the sheet goes
124
+ fullscreen and the button moves to the viewport corner. */
125
+ [data-mobile-nav="preview-full-toggle"] {
126
+ position: absolute !important;
127
+ right: 36px !important;
128
+ top: 8px !important;
129
+ z-index: 57 !important;
130
+ display: none !important;
131
+ align-items: center;
132
+ justify-content: center;
133
+ width: 20px;
134
+ height: 20px;
135
+ padding: 0;
136
+ border: none;
137
+ border-radius: 4px;
138
+ background: transparent;
139
+ color: var(--aion-text-secondary, var(--dsw-alias-label-secondary, inherit));
140
+ cursor: pointer;
141
+ -webkit-tap-highlight-color: transparent;
142
+ /* Native look: same size/radius/hover language as the suite's tab-bar
143
+ icon buttons (the 20px panelCollapse next to it). The button lives
144
+ INSIDE the preview column, so it rides the sheet's own open
145
+ animation and geometry transition — no curve matching needed. */
146
+ transition: background-color .15s, top .24s var(--ds-ease-out, ease-in-out);
147
+ }
148
+ [data-mobile-nav="preview-full-toggle"]:hover {
149
+ background: var(--aion-bg-3, rgba(0, 0, 0, .22));
150
+ }
151
+ [data-mobile-nav="preview-full-toggle"]:active {
152
+ background: var(--aion-bg-active, rgba(0, 0, 0, .28));
153
+ }
154
+ [data-mobile-nav="preview-full-toggle"]:focus-visible {
155
+ outline: 2px solid var(--dsw-alias-state-business-primary, #4f6ef7);
156
+ outline-offset: 2px;
157
+ }
158
+ [data-mobile-nav="preview-full-toggle"] svg {
159
+ width: 14px;
160
+ height: 14px;
161
+ }
162
+ /* Keep the last tab (and the "+" URL-tab trigger) from sliding under the
163
+ fullscreen toggle: reserve the right end of the preview tab row. */
164
+ [data-aionui-preview-col] [class$="_tabScroll"] {
165
+ padding-right: 34px !important;
166
+ }
167
+ /* Visible only while the preview sheet is open. Visibility itself is
168
+ inherited from the column, so the sheet's own hide rules (collapse,
169
+ drawer open) cover the button too. */
170
+ [data-mobile-nav="frame"][data-aionui-preview-open] [data-aionui-preview-col] [data-mobile-nav="preview-full-toggle"] {
171
+ display: inline-flex !important;
172
+ }
173
+ /* Icon swap on the frame fullscreen marker. */
174
+ [data-mobile-nav="preview-full-toggle"] .dsh-mobile-nav-full-out {
175
+ display: none !important;
176
+ }
177
+ [data-mobile-nav="frame"][data-mobile-preview-full] [data-aionui-preview-col] [data-mobile-nav="preview-full-toggle"] .dsh-mobile-nav-full-in {
178
+ display: none !important;
179
+ }
180
+ [data-mobile-nav="frame"][data-mobile-preview-full] [data-aionui-preview-col] [data-mobile-nav="preview-full-toggle"] .dsh-mobile-nav-full-out {
181
+ display: inline !important;
182
+ }
183
+ /* Fullscreen preview: the sheet fills the whole viewport (notch included);
184
+ the safe-area padding drops the titlebar row below the status bar, and
185
+ the toggle follows the titlebar into the top corner. */
186
+ [data-mobile-nav="frame"][data-aionui-preview-open][data-mobile-preview-full] [data-aionui-preview-col] {
187
+ inset: 0 !important;
188
+ left: 0 !important;
189
+ right: 0 !important;
190
+ top: 0 !important;
191
+ bottom: 0 !important;
192
+ width: 100% !important;
193
+ height: 100dvh !important;
194
+ max-height: none !important;
195
+ box-sizing: border-box !important;
196
+ padding-top: var(--mnav-sat) !important;
197
+ border-radius: 0 !important;
198
+ box-shadow: none !important;
199
+ z-index: 57 !important;
200
+ animation: none !important;
201
+ }
202
+ /* Fullscreen: the column fills the viewport, so the button follows the
203
+ titlebar row down below the notch. */
204
+ [data-mobile-nav="frame"][data-mobile-preview-full] [data-aionui-preview-col] [data-mobile-nav="preview-full-toggle"] {
205
+ top: calc(var(--mnav-sat) + 8px) !important;
206
+ }
207
+ @media (prefers-reduced-motion: reduce) {
208
+ [data-aionui-preview-col],
209
+ [data-mobile-nav="preview-full-toggle"] {
210
+ transition: none !important;
211
+ animation: none !important;
212
+ }
213
+ }
214
+
215
+ /* dsh-web-ui sidebar entries (task board / ssh) sit flush against each
216
+ other — give the injected rows breathing room. */
217
+ button[data-dsh-taskboard-entry],
218
+ button[data-dsh-ssh-entry] {
219
+ margin-bottom: 8px !important;
220
+ }
221
+
222
+ /* Task board: five kanban columns at minmax(0,1fr) crush into ~78px phone
223
+ strips. Give every column a usable minimum and let the row scroll. */
224
+ [data-dsh-taskboard-board] > [class$="_columns"] {
225
+ grid-template-columns: repeat(5, minmax(240px, 1fr)) !important;
226
+ overflow-x: auto !important;
227
+ }
228
+ /* The floating button must not float over a takeover panel (task board /
229
+ ssh own the center column while active). */
230
+ html[data-dsh-taskboard-active] [data-mobile-nav="fab"],
231
+ html[data-dsh-ssh-active] [data-mobile-nav="fab"],
232
+ html[data-dsh-taskboard-active] [data-mobile-nav="backdrop"],
233
+ html[data-dsh-ssh-active] [data-mobile-nav="backdrop"] {
234
+ display: none !important;
235
+ }
236
+ /* Board header: let the search field take the slack instead of squeezing
237
+ the action buttons. */
238
+ [data-dsh-taskboard-board] > [class$="_boardHeader"] [class$="_search"] {
239
+ flex: 1 1 auto !important;
240
+ min-width: 80px !important;
241
+ }
242
+
243
+ /* ---------- dsh-web-ui polish: plugin market search ----------
244
+ The market tab row (Discover / Themes / Installed + the plugin search
245
+ box) is a no-wrap flex: at 390px the tabs plus the ~218px search box
246
+ (~475px total) overflow the ~334px sheet and the search box runs off
247
+ the right edge of the screen (it also forces a horizontal scrollbar on
248
+ the sheet's options area). Let the row wrap: the tabs keep the first
249
+ line and the search box gets its own full-width second line. */
250
+
251
+ [aria-modal="true"] [class$="_tabs"] {
252
+ flex-wrap: wrap !important;
253
+ row-gap: 8px !important;
254
+ }
255
+ [aria-modal="true"] [class$="_searchInline"] {
256
+ flex: 1 1 100% !important;
257
+ width: 100% !important;
258
+ max-width: 100% !important;
259
+ }
260
+
261
+ /* ---------- dsh-usage-stats polish: usage & balance panel ----------
262
+ The panel's stats row shows three token counters side by side
263
+ (today / month / total). The counters use tabular nowrap figures whose
264
+ min-content width overflows the ~336px panel body on a phone: figures
265
+ clip at the row's edges and the panel grows a horizontal scrollbar.
266
+ Stack the three counters vertically — full-width rows, so the figures
267
+ always fit. */
268
+
269
+ [class*="usg_"][class$="_statsRow"] {
270
+ flex-direction: column !important;
271
+ }
272
+ [class*="usg_"][class$="_stat"] {
273
+ flex: 0 0 auto !important;
274
+ width: 100% !important;
275
+ min-width: 0 !important;
276
+ }
277
+
278
+ /* ---------- dsh-web-ui polish: settings sheet ----------
279
+ The official dialog is a desktop two-column form; on a phone the
280
+ label/control split leaves a huge dead gap and long descriptions wrap
281
+ into tall stacks. Stack each row (text above, control full-width) and
282
+ keep the nav tabs on ONE horizontally scrolling row. */
283
+
284
+ /* Nav tabs: single scrolling row instead of the 3-per-row grid — seven
285
+ categories wrap into three rows on a phone (~130px of sheet height);
286
+ one row with a thin scrollbar keeps every tab reachable and returns
287
+ that space to the options area (user feedback 2026-08-16). An earlier
288
+ one-row attempt had no scroll affordance and silently cut the last
289
+ tab off; the thin scrollbar IS the affordance. Scoped to the frame
290
+ marker: the desktop dialog keeps its official vertical nav column. */
291
+ [data-mobile-nav="frame"] [aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])):not(:has([class*="ZuhsRW"])) > :first-child [class$="_navList"] {
292
+ display: flex !important;
293
+ flex-wrap: nowrap !important;
294
+ overflow-x: auto !important;
295
+ overflow-y: hidden !important;
296
+ gap: 6px !important;
297
+ width: 100% !important;
298
+ scrollbar-width: thin !important;
299
+ -webkit-overflow-scrolling: touch !important;
300
+ }
301
+ /* Hairline scrollbar for the tab row: the default WebKit scrollbar reads
302
+ fat on a phone; 2px keeps the scroll affordance without the bulk. */
303
+ [data-mobile-nav="frame"] [aria-modal="true"] [class$="_navList"]::-webkit-scrollbar {
304
+ height: 2px !important;
305
+ }
306
+ [data-mobile-nav="frame"] [aria-modal="true"] [class$="_navList"]::-webkit-scrollbar-thumb {
307
+ background: var(--dsw-alias-border-l2, rgba(0, 0, 0, .22)) !important;
308
+ border-radius: 1px !important;
309
+ }
310
+ [data-mobile-nav="frame"] [aria-modal="true"] [class$="_navList"]::-webkit-scrollbar-track {
311
+ background: transparent !important;
312
+ }
313
+ [data-mobile-nav="frame"] [aria-modal="true"] [class$="_navCell"] {
314
+ flex: 0 0 auto !important;
315
+ white-space: nowrap !important;
316
+ padding: 6px 8px !important;
317
+ gap: 6px !important;
318
+ font-size: 13px !important;
319
+ justify-content: flex-start !important;
320
+ }
321
+ [data-mobile-nav="frame"] [aria-modal="true"] [class$="_navCell"] svg {
322
+ width: 14px !important;
323
+ height: 14px !important;
324
+ flex: none !important;
325
+ }
326
+ /* Content toolbar: the "Open configuration file" button is hidden on
327
+ mobile — it is rarely needed on a phone and steals ~180px from the
328
+ tab row's scroll area (user feedback 2026-08-16). Only the close ✕
329
+ stays, flush right in the nav row. Desktop untouched (frame scoped). */
330
+ [data-mobile-nav="frame"] [aria-modal="true"] [class$="_header"] [class$="_actions"] {
331
+ display: none !important;
332
+ }
333
+ [data-mobile-nav="frame"] [aria-modal="true"] [class$="_header"] [class$="_actions"] [class$="_action"] {
334
+ font-size: 13px !important;
335
+ padding: 6px 12px !important;
336
+ min-height: 0 !important;
337
+ }
338
+ /* Setting rows: text on top, control below at full width. */
339
+ [aria-modal="true"] [class$="_section"] [class$="_row"] {
340
+ flex-direction: column !important;
341
+ align-items: stretch !important;
342
+ gap: 8px !important;
343
+ }
344
+ [aria-modal="true"] [class$="_section"] [class$="_row"] > :first-child {
345
+ width: 100% !important;
346
+ max-width: none !important;
347
+ }
348
+ [aria-modal="true"] [class$="_section"] [class$="_row"] > :last-child {
349
+ width: 100% !important;
350
+ max-width: none !important;
351
+ }
352
+ /* Appearance mode group: give the cube row a consistent bordered
353
+ segmented look (the official borders differ per state). */
354
+ [aria-modal="true"] [class$="_cubeRow"] > * {
355
+ border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12)) !important;
356
+ }
357
+
358
+ /* ---------- dsh-web-ui polish: explorer sheet ----------
359
+ The aionui explorer was designed for a desktop side column: compact the
360
+ header, search box and tree rows so a phone shows more entries, and pad
361
+ the scroll bottom so the last row never sits flush on the edge. */
362
+
363
+ [data-aionui-explorer-col] [class$="_tabBar"] {
364
+ height: 36px !important;
365
+ }
366
+ [data-aionui-explorer-col] [class$="_tabBtn"],
367
+ [data-aionui-explorer-col] [class$="_tabBtnActive"] {
368
+ padding: 0 12px !important;
369
+ font-size: 13px !important;
370
+ }
371
+ [data-aionui-explorer-col] [class$="_searchBox"] {
372
+ height: 32px !important;
373
+ font-size: 13px !important;
374
+ }
375
+ [data-aionui-explorer-col] [class*="_treeRow"] {
376
+ height: 30px !important;
377
+ font-size: 13px !important;
378
+ }
379
+ [data-aionui-explorer-col] [class*="_treeRow"] svg {
380
+ width: 14px !important;
381
+ height: 14px !important;
382
+ }
383
+ [data-aionui-explorer-col] [class$="_scrollArea"] {
384
+ padding-bottom: 28px !important;
385
+ }
386
+
387
+ /* ---------- dsh-web-ui polish: drawer footer ----------
388
+ The injected footer actions (Files + Session log) become two equal pill
389
+ buttons instead of text-width capsules. */
390
+
391
+ /* The official footerActions row also hosts the remote-web-ui entry
392
+ row (two icon buttons); without wrapping the two groups squeeze each
393
+ other on one line. Wrap so each group gets its own full-width row. */
394
+ [data-mobile-nav="frame"] [class$="_footerActions"] {
395
+ flex-wrap: wrap !important;
396
+ gap: 6px !important;
397
+ }
398
+ [data-mobile-nav="drawer-actions"] {
399
+ width: 100% !important;
400
+ }
401
+ [data-mobile-nav="drawer-actions"] > button {
402
+ flex: 1 1 0 !important;
403
+ padding: 0 8px !important;
404
+ white-space: nowrap !important;
405
+ }
406
+
407
+ /* ---------- dsh-web-ui polish: floating pet ----------
408
+ The whale-girl pet (dsh-pet) floats at the viewport corner with a
409
+ persisted, draggable position. On phones the pet is scaled down so
410
+ it does not dominate the screen; the plugin's own drag + persist
411
+ still work (the position itself is left alone — the mobile default
412
+ position is seeded via the pet API to just above the composer). */
413
+
414
+ body > [class$="_float"]:has([class$="_sprite"][role="button"]) {
415
+ transform: scale(.66);
416
+ transform-origin: bottom right;
417
+ }
418
+ /* While a modal dialog (settings sheet / export) owns the screen the pet
419
+ floats ABOVE it and covers the dialog content; modal semantics say the
420
+ background is inert, so hide the pet for the modal's lifetime. */
421
+ body:has([aria-modal="true"]) > [class$="_float"]:has([class$="_sprite"][role="button"]) {
422
+ display: none !important;
423
+ }
424
+
425
+ /* ---------- conversation stats line ----------
426
+ The official session-status row (turns / steps / LLM time / TTFT /
427
+ cache) is long. It is the single entry the official StatsLine puts in
428
+ conversation.composer.dock, so the structural anchor below reaches it
429
+ without any DOM marking (S3 deleted the text-matching effect that used
430
+ to set [data-slot="conversation.composer.dock"] > [class$="_root"]). Layout: ONE fixed-height (28px) flex
431
+ strip that scrolls horizontally — the full metrics stream stays
432
+ reachable by swiping, the row never grows vertically, no ellipsis or
433
+ fade, 12px gaps between metric groups, a 2px scrollbar as the swipe
434
+ affordance. The phone breakpoint hides the strip outright instead
435
+ (styles/composer.css.ts — its data moves into the session info card). */
436
+
437
+ [data-slot="conversation.composer.dock"] > [class$="_root"] {
438
+ display: flex !important;
439
+ flex-flow: row nowrap !important;
440
+ align-items: center !important;
441
+ width: 100% !important;
442
+ max-width: 100% !important;
443
+ min-width: 0 !important;
444
+ height: 28px !important;
445
+ min-height: 28px !important;
446
+ max-height: 28px !important;
447
+ box-sizing: border-box !important;
448
+ white-space: nowrap !important;
449
+ overflow-x: auto !important;
450
+ overflow-y: hidden !important;
451
+ -webkit-overflow-scrolling: touch;
452
+ overscroll-behavior-x: contain;
453
+ scrollbar-width: thin !important;
454
+ scrollbar-color: var(--dsw-alias-border-l1, rgba(0, 0, 0, .28)) transparent !important;
455
+ padding: 0 0 4px !important;
456
+ line-height: 20px !important;
457
+ font-size: 12px !important;
458
+ }
459
+ [data-slot="conversation.composer.dock"] > [class$="_root"]::-webkit-scrollbar {
460
+ height: 2px !important;
461
+ }
462
+ [data-slot="conversation.composer.dock"] > [class$="_root"]::-webkit-scrollbar-thumb {
463
+ background: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .3)) !important;
464
+ border-radius: 2px !important;
465
+ }
466
+ [data-slot="conversation.composer.dock"] > [class$="_root"]::-webkit-scrollbar-track {
467
+ background: transparent !important;
468
+ }
469
+ [data-slot="conversation.composer.dock"] > [class$="_root"] > * {
470
+ display: flex !important;
471
+ flex: 0 0 auto !important;
472
+ flex-flow: row nowrap !important;
473
+ align-items: center !important;
474
+ width: max-content !important;
475
+ min-width: max-content !important;
476
+ max-width: none !important;
477
+ white-space: nowrap !important;
478
+ margin-right: 12px !important;
479
+ padding: 0 !important;
480
+ }
481
+ [data-slot="conversation.composer.dock"] > [class$="_root"] > *:last-child {
482
+ margin-right: 0 !important;
483
+ }
484
+ [data-slot="conversation.composer.dock"] > [class$="_root"] * {
485
+ white-space: nowrap !important;
486
+ }
487
+
488
+ /* ---------- dsh-genui panel dock ----------
489
+ The genui panel docks above the composer (conversation.input.dock,
490
+ id genui-panel). On a phone its business-blue outline, generous chrome
491
+ and single-line ellipsis read as an unfinished artifact: long titles
492
+ truncate mid-word ("…default b···") with the chevron glued to the
493
+ ellipsis, and the pill crowds the composer. Mobile treatment: neutral
494
+ card border matching the composer, tighter chrome so the full title
495
+ fits, chevron with breathing room. Scoped to the mobile frame marker —
496
+ desktop keeps genui's own styling untouched. */
497
+
498
+ [data-mobile-nav="frame"] [data-genui-panel] {
499
+ margin: 6px 12px 4px !important;
500
+ border-color: var(--dsw-alias-border-l1, rgba(0, 0, 0, .12)) !important;
501
+ border-radius: 12px !important;
502
+ }
503
+ [data-mobile-nav="frame"] [data-genui-panel] [class*="_panelToggle"] {
504
+ padding: 7px 12px !important;
505
+ gap: 8px !important;
506
+ }
507
+ [data-mobile-nav="frame"] [data-genui-panel] [class*="_panelBadge"] {
508
+ padding: 0 7px !important;
509
+ border-radius: 5px !important;
510
+ font-size: 10.5px !important;
511
+ line-height: 1.7 !important;
512
+ }
513
+ [data-mobile-nav="frame"] [data-genui-panel] [class*="_panelTitle"] {
514
+ flex: 1 1 auto !important;
515
+ min-width: 0 !important;
516
+ font-size: 12.5px !important;
517
+ line-height: 1.45 !important;
518
+ }
519
+ [data-mobile-nav="frame"] [data-genui-panel] [class*="_panelChevron"] {
520
+ flex: none !important;
521
+ margin-left: 0 !important;
522
+ padding-left: 4px !important;
523
+ }
524
+
525
+ /* ---------- git-graph branch chip: inside the composer card ----------
526
+ The branch chip (conversation.input.dock) floats between the dock rows
527
+ and the input card; on a phone it reads as a stray capsule crowding the
528
+ composer. A client effect (MobileNavOverlay) reparents the chip INTO
529
+ the composer card; these rules pin it to the card's top-left and give
530
+ the card a dedicated chip row. The card is position: relative by the
531
+ official stylesheet, so the absolute anchor resolves against it. The
532
+ plugin's own sheet sets all four offsets on the anchor, so right/bottom
533
+ must be neutralized too. Scope is the frame marker + the anchor
534
+ attribute (NOT the dock slot — the reparenting moves the chip out of
535
+ the dock's subtree). Desktop untouched: the frame marker only exists
536
+ below 1024px, and the effect restores the chip to the dock when the
537
+ viewport widens. Chip row geometry (2026-08-16, user feedback): 48px
538
+ padding left a 16px dead gap between the chip and the input line and
539
+ made the composer read too tall; the row is now 40px = chip (24px) at
540
+ top 12px + ~4px to the textarea — the chip sits slightly lower and
541
+ the gap is compressed without touching the official height budget
542
+ further. */
543
+
544
+ [data-mobile-nav="frame"] [data-gitgraph-chip-anchor] {
545
+ position: absolute !important;
546
+ top: 12px !important;
547
+ left: 12px !important;
548
+ right: auto !important;
549
+ bottom: auto !important;
550
+ z-index: 1 !important;
551
+ }
552
+ [data-mobile-nav="frame"] [class$="_card"]:has([data-gitgraph-chip-anchor]) {
553
+ padding-top: 40px !important;
554
+ }
555
+
556
+ /* ---------- dsh-better-sidebar: safe area (S2.1, 2026-08-17) ----------
557
+ THIRD-PARTY COMPAT RULE — dsh-better-sidebar (the workbench the session
558
+ header's panel button opens). Its shell is viewport-fixed and starts at
559
+ y=0: the panel at inset 0 (100vw drawer below 768px, a right column
560
+ above it) and the toggle cluster at top:3px. Neither knows about
561
+ env(safe-area-inset-*), so on a notched iPhone the whole tab strip —
562
+ including the one button that CLOSES the panel — sits behind the status
563
+ bar and cannot be tapped: the user opened the workbench and was stuck
564
+ there (real-device report, 2026-08-17).
565
+ Applied across the plugin's whole mobile band, not just <768px: the
566
+ panel is fixed at top:0 in the 768-1023px range too, so the same notch
567
+ covers the same tab strip. Zero effect wherever the inset is 0 (every
568
+ desktop browser, every non-notched device) — desktop is >=1024px and
569
+ out of this media block entirely.
570
+ Anchors: the plugin's own mount marker [data-dsh-better-sidebar]
571
+ (index.tsx) plus class-suffix selectors, per this repo's hashed-class
572
+ convention. --dsh-title-bar-strip is the plugin's own title-bar-compat
573
+ offset (set only while that mode is on, 0px fallback otherwise): adding
574
+ to it keeps both offsets rather than clobbering theirs.
575
+ !important because their :global(body[...]) rules outrank a plain
576
+ attribute selector.
577
+ No box-sizing here on purpose: the panel is position:fixed with BOTH
578
+ top and bottom set, so its used height already resolves to
579
+ "containing block - insets - padding - border" (CSS 2.1 10.6.4) and the
580
+ padding shrinks the content box without any help. Forcing border-box
581
+ also folds their 1px left border into the inline width — measured as a
582
+ 1px panel-width change at 768px, i.e. a regression outside this
583
+ hotfix's remit. */
584
+ [data-dsh-better-sidebar] [class$="_panel"],
585
+ [data-dsh-better-sidebar] [class$="_panelHidden"] {
586
+ padding-top: calc(var(--mnav-sat) + var(--dsh-title-bar-strip, 0px)) !important;
587
+ }
588
+ /* Only the tablet/desktop range still shows the cluster (see the hide
589
+ rule below) — the notch offset is now dead weight below 768px, so it
590
+ is scoped out rather than left applying invisibly. min-width:768px
591
+ rather than the more common max-width pairing: this offset was
592
+ already a harmless no-op at >=1024px before S3.1 (--mnav-sat is 0 on
593
+ every desktop browser), so narrowing its floor to 768px changes
594
+ nothing there either — it only stops evaluating on phone. */
595
+ @media (min-width: 768px) {
596
+ [data-dsh-better-sidebar] [class$="_toggleCluster"] {
597
+ top: calc(var(--mnav-sat) + var(--dsh-title-bar-strip, 0px) + 3px) !important;
598
+ }
599
+ }
600
+
601
+ /* ---------- dsh-better-sidebar: hide the phone toggle cluster (S3.1, 2026-08-17) ----------
602
+ Real-device round 2 feedback: this fixed top-right cluster duplicates
603
+ — and visually overlaps — the session header's own workbench button
604
+ (MobileSessionHeader.tsx), which already opens/closes the same panel
605
+ by clicking this cluster's toggle button through. Hidden below 768px
606
+ only; the 768-1023px tablet range has no workbench button (header.css.ts
607
+ scopes that entire reflow to <768px) and still depends on this cluster
608
+ as its only entry point, so it stays exactly as v1.0.0/S2.1 shipped it
609
+ there. The cluster also holds the panel's own close affordance — see
610
+ [data-mobile-nav="better-sidebar-close"] below for the phone
611
+ replacement, wired up in MobileSessionHeader.tsx. */
612
+ @media (max-width: 767px) {
613
+ [data-dsh-better-sidebar] [class$="_toggleCluster"] {
614
+ display: none !important;
615
+ }
616
+ }
617
+
618
+ /* ---------- dsh-better-sidebar: phone close button (S3.1 follow-up, 2026-08-17) ----------
619
+ Appended to document.body by MobileSessionHeader.tsx's
620
+ MobileHeaderUtilities effect — never inside the panel's own subtree
621
+ (the third party's React re-renders would wipe it) and never under any
622
+ transformed/backdrop-filter ancestor (the S4 info-card WebKit lesson in
623
+ AGENTS.md: position:fixed re-anchors to the nearest such ancestor
624
+ instead of the viewport). Default hidden — belt-and-braces, same
625
+ reasoning as header.css.ts's [data-mobile-nav="header-info"] etc list:
626
+ React does not know about media queries. Shown only below 768px AND
627
+ only while the panel is actually open: the panel's own class name ends
628
+ in "_panel" exclusively in the open state (the "_panelHidden" suffix is
629
+ appended once closed, so the string no longer ends in "_panel") — a
630
+ pure-CSS :has() open/closed read, no MutationObserver required.
631
+
632
+ Bottom-center pill, not a top-right circle (real-device follow-up,
633
+ 2026-08-17): a top-right position collided with the panel's own
634
+ per-tab toolbar controls — measured live at 390px with the explorer
635
+ tab open, the panel's Refresh button sits at x:354-382 y:93-121, and a
636
+ 44px circle at top:8px+safe-area/right:8px lands at x:338-382
637
+ y:(safe-area+8)-(safe-area+52), a direct overlap once the safe-area
638
+ offset is small (or zero on non-notched phones). Every per-tab toolbar
639
+ (explorer/git/tabBar) lives at the panel's TOP; nothing in the
640
+ default explorer or git tabs reaches the bottom 90px of the viewport
641
+ (checked live, both tabs, 2026-08-17), so bottom-center is clear
642
+ regardless of which tab is open — one fixed position that does not
643
+ need per-tab-type coordinates to dodge. */
644
+ /* Default-hidden for this pill lives in header.css.ts, NOT here: this
645
+ whole file sits inside the shared (max-width: 1023px) block, so a rule
646
+ here can never hide anything at desktop widths (learned the hard way,
647
+ 2026-08-17 desktop leak). */
648
+ @media (max-width: 767px) {
649
+ body:has([data-dsh-better-sidebar] [class$="_panel"]) [data-mobile-nav="better-sidebar-close"] {
650
+ display: flex !important;
651
+ position: fixed;
652
+ left: 50%;
653
+ bottom: calc(var(--mnav-sab) + 12px);
654
+ transform: translateX(-50%);
655
+ z-index: 70;
656
+ align-items: center;
657
+ gap: 6px;
658
+ height: 44px;
659
+ padding: 0 18px;
660
+ border: none;
661
+ border-radius: 999px;
662
+ background: var(--dsw-alias-bg-base, #fff);
663
+ box-shadow: 0 4px 16px rgba(0, 0, 0, .2);
664
+ color: var(--dsw-alias-label-primary, inherit);
665
+ font-family: inherit;
666
+ font-size: 14px;
667
+ font-weight: 600;
668
+ cursor: pointer;
669
+ touch-action: manipulation;
670
+ -webkit-tap-highlight-color: transparent;
671
+ }
672
+ body:has([data-dsh-better-sidebar] [class$="_panel"]) [data-mobile-nav="better-sidebar-close"] svg {
673
+ width: 14px;
674
+ height: 14px;
675
+ flex: none;
676
+ }
677
+ body:has([data-dsh-better-sidebar] [class$="_panel"]) [data-mobile-nav="better-sidebar-close"]:active {
678
+ background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));
679
+ }
680
+ }
681
+ `