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,477 @@
1
+ // home — phone-only app shell (S1, 2026-08-17): the full-screen session list
2
+ // and the two-level page stack. Appended LAST so its rules win the ties
3
+ // against the shared <=1023px block in layout/compat/misc.
4
+ //
5
+ // Everything lives inside (max-width: 767px): the tablet range keeps the
6
+ // v1.0.0 drawer and the desktop stays a strict no-op.
7
+
8
+ export const HOME_CSS = `/* ---------- phone app shell (< 768px) ---------- */
9
+
10
+ @media (max-width: 767px) {
11
+ /* --- the document never scrolls (S1.1, 2026-08-17) ---
12
+ Real-device symptom: the workspace title bar, the session header, the
13
+ composer and the FAB all slid with the finger — every fixed surface
14
+ "followed the drag". They are not misplaced; the whole DOCUMENT was
15
+ rubber-banding under them (they are absolute/in-flow inside the frame,
16
+ so a document-level bounce moves them all as one).
17
+
18
+ After the S2.1 box-sizing fix the document has no overflow at all
19
+ (scrollHeight === clientHeight, measured with ?mobile-nav-inset=54), so
20
+ this is not scrolling — it is iOS's elastic overscroll, which happens on
21
+ an unscrollable document too, and which an inner scroller chains into as
22
+ soon as it hits its own end.
23
+
24
+ overscroll-behavior: none on the viewport kills both halves at once: the
25
+ document itself gets no bounce, and overscroll chained up from the
26
+ message flow / session list is absorbed without moving anything.
27
+ overflow: hidden then hard-locks the document scroller so future content
28
+ can never reintroduce a real scroll. Set on html AND body: the spec
29
+ propagates the viewport's value from html, but engines have historically
30
+ read body, and neither is a scroll container we ever want.
31
+
32
+ Deliberately NOT position: fixed on body — the app shell does not need
33
+ it, and it is the variant that strands iOS's fixed elements behind the
34
+ on-screen keyboard. With plain overflow: hidden, iOS still pans the
35
+ visual viewport to reveal a focused textarea, so the composer stays
36
+ visible while typing without any visualViewport JS. */
37
+ html,
38
+ body {
39
+ overflow: hidden !important;
40
+ overscroll-behavior: none !important;
41
+ }
42
+
43
+ /* The message flow is the one scroller that regularly hits its end under a
44
+ finger. Root-level \`none\` already absorbs the chain, but declaring it
45
+ at the source keeps the guarantee if the root rule is ever weakened
46
+ (the home list and both sheets already declare it).
47
+
48
+ \`none\`, NOT \`contain\` (S4.1 fix, 2026-08-17). The two differ in exactly
49
+ the way that bit us: \`contain\` stops overscroll from CHAINING OUT to the
50
+ document but deliberately KEEPS this element's own local elastic bounce,
51
+ while \`none\` suppresses that local bounce too. S1.1 only reasoned about
52
+ the chain, so it picked \`contain\` — and the composer went on twitching
53
+ upward on every drag-past-the-end while the header and FAB stayed put.
54
+
55
+ Why only the composer moved: the official composer seat
56
+ (\`[class$="_composerSeat"]\`) is \`position: sticky; bottom: 0\` and lives
57
+ INSIDE this scroller (measured 2026-08-17: seat 0,710 390x134 inside
58
+ _scrollBody 0,131 390x713, contains() === true). A sticky box is laid out
59
+ against its scroll container's content, so the container's own rubber-band
60
+ drags it along. The header/FAB are absolute on the frame, outside this
61
+ scroller entirely — which is precisely why S1.1's document-level fix
62
+ pinned them and left this one surface behind.
63
+
64
+ Deliberately NOT re-pinning the seat with position: fixed: sticky inside
65
+ the scroller is what keeps the focused textarea visible when iOS pans the
66
+ visual viewport for the keyboard, and fixed is the variant that strands
67
+ it behind the keyboard (same trap as the body rule above). Kill the
68
+ bounce, keep the sticky. */
69
+ [data-phase] [class$="_scrollBody"] {
70
+ overscroll-behavior: none !important;
71
+ }
72
+
73
+ /* The official sidebar is no longer a drawer on a phone — the home screen
74
+ replaced it. Hidden outright (not translated off-screen) so it cannot
75
+ capture taps or hold layout. */
76
+ [data-mobile-nav="frame"] > :first-child {
77
+ display: none !important;
78
+ }
79
+ /* The header's drawer toggle opened that sidebar; with it gone the button
80
+ has nothing to open. (S2 owns the rest of the header layout —
81
+ styles/header.css.ts — including the Files button's replacement.) */
82
+ [data-mobile-nav="toggle"] {
83
+ display: none !important;
84
+ }
85
+
86
+ /* --- level 1: the session list ---
87
+ Renders inside the shell overlay layer (absolute, pointer-events: none),
88
+ so the page re-enables pointer events for itself. The layer's containing
89
+ block is the frame's padding box, which still starts under the status
90
+ bar — hence the safe-area padding here. */
91
+ [data-mobile-nav="home"] {
92
+ position: absolute;
93
+ inset: 0;
94
+ z-index: 5;
95
+ display: flex;
96
+ flex-direction: column;
97
+ pointer-events: auto;
98
+ /* dsw-specific-sidebar-fill, not an alias bg-layer-* token (real-device
99
+ round 2 follow-up, 2026-08-17): fetched and diffed the live theme
100
+ CSS (both light/dark blocks in /assets/index-*.css) because computed
101
+ values, not source-read guesses, are what actually matter here —
102
+ --dsw-alias-bg-base/-layer-1/-layer-2 all resolve to the exact same
103
+ color in the LIGHT theme (neutral-bluish-00, i.e. plain white); they
104
+ only diverge in dark mode. A layer-* token would have made the page
105
+ and its cards indistinguishable in light mode specifically — the
106
+ opposite of what was asked. --dsw-specific-sidebar-fill differs from
107
+ bg-base in BOTH themes (bluish-50 vs -00 light, bluish-900 vs -950
108
+ dark) and is the exact token dsh-better-sidebar's own panel already
109
+ uses for this same "secondary surface next to bg-base content"
110
+ role, so it is the correct reuse rather than a new hardcoded gray. */
111
+ background: var(--dsw-specific-sidebar-fill, #f5f5f5);
112
+ color: var(--dsw-alias-label-primary, inherit);
113
+ padding-top: var(--mnav-sat);
114
+ transform: translateX(0);
115
+ opacity: 1;
116
+ transition:
117
+ transform .3s var(--ds-ease-in-out, ease-in-out),
118
+ opacity .3s var(--ds-ease-in-out, ease-in-out);
119
+ }
120
+ /* Push transition: the list slides out to the left as the session takes
121
+ the screen, and comes back from the left. It stays mounted (visibility,
122
+ not display) so the slide has something to animate; the delayed
123
+ visibility keeps it non-interactive the moment the transition ends. */
124
+ [data-mobile-nav="home"][data-view="session"] {
125
+ transform: translateX(-100%);
126
+ opacity: 0;
127
+ visibility: hidden;
128
+ pointer-events: none;
129
+ transition:
130
+ transform .3s var(--ds-ease-in-out, ease-in-out),
131
+ opacity .3s var(--ds-ease-in-out, ease-in-out),
132
+ visibility 0s .3s;
133
+ }
134
+ @media (prefers-reduced-motion: reduce) {
135
+ [data-mobile-nav="home"],
136
+ [data-mobile-nav="home"][data-view="session"] {
137
+ transition: none !important;
138
+ }
139
+ }
140
+
141
+ /* Title bar: the workspace name IS the switcher. */
142
+ [data-mobile-nav="home-top"] {
143
+ flex: 0 0 auto;
144
+ display: flex;
145
+ align-items: center;
146
+ min-height: 52px;
147
+ padding: 4px 16px 6px;
148
+ }
149
+ /* Site logo (real-device round 2, 2026-08-17): rendered only when
150
+ document.head actually has a <link rel="icon"> (MobileHome.tsx never
151
+ ships a placeholder box), so this rule only ever needs to size the
152
+ image, not reserve space for its absence. */
153
+ [data-mobile-nav="home-logo"] {
154
+ width: 24px;
155
+ height: 24px;
156
+ margin-right: 6px;
157
+ flex: none;
158
+ border-radius: 6px;
159
+ object-fit: contain;
160
+ }
161
+ /* Dark theme (real-device report, 2026-08-17): the runtime favicon is a
162
+ dark-on-transparent whale mark, illegible on the dark page. Force a
163
+ white silhouette regardless of the source image's own colors —
164
+ brightness(0) collapses every opaque pixel to black first, invert(1)
165
+ then flips that to white; transparent pixels stay transparent either
166
+ way. Same body[data-ds-dark-theme] marker as the home-row hairline
167
+ above (AGENTS.md: prefers-color-scheme never fires, the app switches
168
+ themes via this attribute with color-scheme hardcoded to light). */
169
+ body[data-ds-dark-theme] [data-mobile-nav="home-logo"] {
170
+ filter: brightness(0) invert(1);
171
+ }
172
+ [data-mobile-nav="ws-switch"] {
173
+ display: inline-flex;
174
+ align-items: center;
175
+ gap: 4px;
176
+ min-height: 44px;
177
+ max-width: 100%;
178
+ padding: 0 6px 0 0;
179
+ border: none;
180
+ background: transparent;
181
+ color: inherit;
182
+ font-family: inherit;
183
+ font-size: 22px;
184
+ font-weight: 600;
185
+ line-height: 1.2;
186
+ cursor: pointer;
187
+ touch-action: manipulation;
188
+ -webkit-tap-highlight-color: transparent;
189
+ }
190
+ [data-mobile-nav="ws-switch"] > span {
191
+ overflow: hidden;
192
+ text-overflow: ellipsis;
193
+ white-space: nowrap;
194
+ }
195
+ [data-mobile-nav="ws-switch"] > svg {
196
+ flex: none;
197
+ opacity: .55;
198
+ }
199
+ [data-mobile-nav="ws-switch"]:active {
200
+ opacity: .6;
201
+ }
202
+
203
+ /* Session list: rounded cards, not bordered rows (real-device round 2
204
+ follow-up, 2026-08-17 — reference: Claude Code mobile app's session
205
+ list). \`gap\` on the flex column IS the inter-card spacing; no
206
+ per-row margin bookkeeping. */
207
+ [data-mobile-nav="home-list"] {
208
+ flex: 1 1 auto;
209
+ min-height: 0;
210
+ margin: 0;
211
+ display: flex;
212
+ flex-direction: column;
213
+ gap: 10px;
214
+ padding: 12px 16px calc(var(--mnav-sab) + 96px);
215
+ list-style: none;
216
+ overflow-y: auto;
217
+ overscroll-behavior: contain;
218
+ -webkit-overflow-scrolling: touch;
219
+ }
220
+ [data-mobile-nav="home-row"] {
221
+ display: flex;
222
+ align-items: center;
223
+ gap: 12px;
224
+ width: 100%;
225
+ min-height: 60px;
226
+ padding: 14px 16px;
227
+ border: none;
228
+ border-radius: 22px;
229
+ background: var(--dsw-alias-bg-base, #ffffff);
230
+ color: inherit;
231
+ font-family: inherit;
232
+ text-align: left;
233
+ cursor: pointer;
234
+ touch-action: manipulation;
235
+ -webkit-tap-highlight-color: transparent;
236
+ /* Card edge definition (2026-08-17). Light theme only — see the dark
237
+ override below. Two stacked shadows in the iOS idiom: a tight 1px
238
+ contact shadow that draws the edge itself, plus a wide soft one that
239
+ lifts the card off the page. Both are deliberately weak (.06/.05):
240
+ the page sits on --dsw-specific-sidebar-fill and the card on
241
+ --dsw-alias-bg-base, so there is already a slight tonal step here and
242
+ the shadow only has to sharpen it, not carry it alone. */
243
+ box-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 4px 12px rgba(0, 0, 0, .05);
244
+ }
245
+ [data-mobile-nav="home-row"]:active {
246
+ background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));
247
+ /* Press = settle toward the page. Dropping the wide shadow and keeping a
248
+ tighter contact one reads as the card pushing in, which is what the
249
+ darkening background already implies; leaving the lifted shadow under
250
+ a pressed card is the combination that looks wrong. */
251
+ box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
252
+ }
253
+ /* Dark theme: a black shadow on a near-black page is invisible, so the
254
+ edge is drawn instead of cast. A 1px hairline at --dsw-alias-border-l2
255
+ weight, delivered as an INSET box-shadow rather than a real border so
256
+ the card's geometry is untouched (a real 1px border would grow every
257
+ row by 2px unless box-sizing cooperated) and so it overrides the light
258
+ shadow through the same property instead of fighting it.
259
+
260
+ Chosen over "brighten the card face" because in dark theme the card
261
+ (--dsw-alias-bg-base, bluish-950) is already DARKER than the page
262
+ (--dsw-specific-sidebar-fill, bluish-900); lightening the card past the
263
+ page would invert the layering that the light theme establishes, while
264
+ a hairline keeps both themes reading as "page behind, card in front".
265
+
266
+ Theme is read from body[data-ds-dark-theme], never prefers-color-scheme:
267
+ the app hardcodes color-scheme: light on <html> and switches themes with
268
+ this attribute, so a media query would never fire (AGENTS.md). This
269
+ selector also outranks the :active rule above (0,2,1 vs 0,2,0), so one
270
+ declaration covers the pressed state too. */
271
+ body[data-ds-dark-theme] [data-mobile-nav="home-row"] {
272
+ box-shadow: inset 0 0 0 1px var(--dsw-alias-border-l2, rgba(255, 255, 255, .12));
273
+ }
274
+ [data-mobile-nav="home-row"][data-current] [data-mobile-nav="home-row-title"] {
275
+ font-weight: 600;
276
+ }
277
+ /* Avatar: a running/warning/done session shows its existing StateDot
278
+ (same component, same semantics as the old inline dot — just bigger
279
+ and re-homed); otherwise the title's own first character stands in
280
+ for it, so every row has a mark even when idle. interactive-bg-hover,
281
+ not a bg-layer-* token: same reason as the page background above —
282
+ bg-layer-2 is IDENTICAL to the card's own bg-base in light theme
283
+ (verified against the live theme CSS), so it would render invisible
284
+ there. interactive-bg-hover is a translucent rgba tint rather than a
285
+ solid layer color, so it always reads as "a shade over the card" in
286
+ both themes regardless of what the card's base color resolves to. */
287
+ [data-mobile-nav="home-row-avatar"] {
288
+ flex: none;
289
+ display: grid;
290
+ place-items: center;
291
+ width: 40px;
292
+ height: 40px;
293
+ border-radius: 12px;
294
+ background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));
295
+ color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .55));
296
+ font-size: 16px;
297
+ font-weight: 600;
298
+ }
299
+ [data-mobile-nav="home-row-body"] {
300
+ flex: 1 1 auto;
301
+ min-width: 0;
302
+ display: flex;
303
+ flex-direction: column;
304
+ gap: 2px;
305
+ }
306
+ [data-mobile-nav="home-row-title"] {
307
+ font-size: 16px;
308
+ line-height: 21px;
309
+ overflow: hidden;
310
+ text-overflow: ellipsis;
311
+ white-space: nowrap;
312
+ }
313
+ [data-mobile-nav="home-row-status"] {
314
+ color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));
315
+ font-size: 12.5px;
316
+ line-height: 17px;
317
+ overflow: hidden;
318
+ text-overflow: ellipsis;
319
+ white-space: nowrap;
320
+ }
321
+ [data-mobile-nav="home-row-time"] {
322
+ flex: none;
323
+ align-self: flex-start;
324
+ color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .4));
325
+ font-size: 12.5px;
326
+ line-height: 18px;
327
+ }
328
+ [data-mobile-nav="home-empty"] {
329
+ margin: 12px 16px;
330
+ padding: 48px 24px;
331
+ border-radius: 22px;
332
+ background: var(--dsw-alias-bg-base, #ffffff);
333
+ color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));
334
+ font-size: 15px;
335
+ text-align: center;
336
+ }
337
+
338
+ /* New-session FAB: a labeled pill (real-device round 2 follow-up), not a
339
+ bare circle — tap starts in the shown workspace, long press picks
340
+ one. Inverted-surface tokens (not a hardcoded accent color): the same
341
+ pair the official git-commit button in dsh-better-sidebar uses for
342
+ its own solid CTA. */
343
+ [data-mobile-nav="home-fab"] {
344
+ position: absolute;
345
+ right: 18px;
346
+ bottom: calc(var(--mnav-sab) + 22px);
347
+ z-index: 6;
348
+ display: inline-flex;
349
+ align-items: center;
350
+ gap: 6px;
351
+ height: 48px;
352
+ padding: 0 20px 0 16px;
353
+ border: none;
354
+ border-radius: 999px;
355
+ background: var(--dsw-alias-button-primary-fill, #1a1a1a);
356
+ color: var(--dsw-alias-label-primary-inverted, #ffffff);
357
+ font-family: inherit;
358
+ font-size: 15px;
359
+ font-weight: 600;
360
+ cursor: pointer;
361
+ box-shadow: 0 6px 20px rgba(0, 0, 0, .24);
362
+ touch-action: manipulation;
363
+ user-select: none;
364
+ -webkit-user-select: none;
365
+ -webkit-touch-callout: none;
366
+ -webkit-tap-highlight-color: transparent;
367
+ }
368
+ [data-mobile-nav="home-fab"]:active {
369
+ transform: scale(.96);
370
+ }
371
+
372
+ /* Workspace sheet (switcher and long-press New Session share it). */
373
+ [data-mobile-nav="home-sheet-layer"] {
374
+ position: absolute;
375
+ inset: 0;
376
+ z-index: 7;
377
+ }
378
+ [data-mobile-nav="home-sheet-mask"] {
379
+ position: absolute;
380
+ inset: 0;
381
+ background: var(--dsw-alias-bg-mask-3, rgba(0, 0, 0, .45));
382
+ border: none;
383
+ animation: dsh-mobile-nav-fade .18s var(--ds-ease-out, ease-in-out);
384
+ }
385
+ /* Docked to the screen edge, matching the composer's permission/model
386
+ sheets (styles/composer.css.ts section 4) rather than floating.
387
+
388
+ It used to sit at \`bottom: calc(var(--mnav-sab) + 8px)\` with all four
389
+ corners rounded, which left a 42px strip of bare mask under it at
390
+ sab: 34 (measured). Docking removes the strip instead of dimming it, and
391
+ the safe area moves INSIDE the card as bottom padding — so the white
392
+ card body runs all the way to the physical screen edge and the last row
393
+ still clears the home indicator. Same reason the corners go top-only:
394
+ a rounded bottom corner on a card flush with the screen edge reads as a
395
+ rendering mistake. */
396
+ [data-mobile-nav="home-sheet"] {
397
+ position: absolute;
398
+ left: 0;
399
+ right: 0;
400
+ bottom: 0;
401
+ max-height: 70%;
402
+ overflow-y: auto;
403
+ overscroll-behavior: contain;
404
+ box-sizing: border-box;
405
+ padding: 6px 6px calc(var(--mnav-sab) + 14px);
406
+ border-radius: 20px 20px 0 0;
407
+ /* layer-2, not bg-base: in dark mode the sheet must lift off a page that
408
+ shares bg-base, or only the shadow separates them. */
409
+ background: var(--dsw-alias-bg-layer-2, #ffffff);
410
+ box-shadow: 0 -8px 32px rgba(0, 0, 0, .28);
411
+ animation: dsh-mobile-nav-sheet-up .22s var(--ds-ease-out, ease-in-out);
412
+ }
413
+ @media (prefers-reduced-motion: reduce) {
414
+ [data-mobile-nav="home-sheet-mask"],
415
+ [data-mobile-nav="home-sheet"] {
416
+ animation: none !important;
417
+ }
418
+ }
419
+ [data-mobile-nav="home-sheet-title"] {
420
+ padding: 10px 12px 6px;
421
+ color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));
422
+ font-size: 13px;
423
+ }
424
+ [data-mobile-nav="home-sheet-item"] {
425
+ display: flex;
426
+ align-items: center;
427
+ width: 100%;
428
+ min-height: 48px;
429
+ padding: 0 12px;
430
+ border: none;
431
+ border-radius: 12px;
432
+ background: transparent;
433
+ color: inherit;
434
+ font-family: inherit;
435
+ font-size: 16px;
436
+ text-align: left;
437
+ cursor: pointer;
438
+ touch-action: manipulation;
439
+ -webkit-tap-highlight-color: transparent;
440
+ }
441
+ [data-mobile-nav="home-sheet-item"][data-selected] {
442
+ background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));
443
+ font-weight: 600;
444
+ }
445
+ [data-mobile-nav="home-sheet-item"]:active {
446
+ background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));
447
+ }
448
+
449
+ /* --- hero fallback back button ---
450
+ The hero (new blank session) page renders no conversation.session.header,
451
+ so the header-slot back button (S2) does not exist there. This floating
452
+ fallback covers that page and hides itself as soon as the real header
453
+ back mounts. */
454
+ [data-mobile-nav="hero-back"] {
455
+ position: absolute;
456
+ top: calc(var(--mnav-sat) + 8px);
457
+ left: 8px;
458
+ z-index: 6;
459
+ width: 44px;
460
+ height: 44px;
461
+ display: grid;
462
+ place-items: center;
463
+ border: none;
464
+ border-radius: 14px;
465
+ background: transparent;
466
+ color: var(--dsw-alias-label-secondary, inherit);
467
+ pointer-events: auto;
468
+ }
469
+ [data-mobile-nav="hero-back"]:active {
470
+ background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));
471
+ }
472
+ [data-mobile-nav="frame"]:has([data-mobile-nav="header-back"]) ~ [data-mobile-nav="hero-back"],
473
+ body:has([data-mobile-nav="header-back"]) [data-mobile-nav="hero-back"] {
474
+ display: none;
475
+ }
476
+ }
477
+ `
@@ -0,0 +1,25 @@
1
+ import { BASE_CSS } from './base.css.ts'
2
+ import { LAYOUT_CSS } from './layout.css.ts'
3
+ import { COMPAT_CSS } from './compat.css.ts'
4
+ import { MISC_CSS } from './misc.css.ts'
5
+ import { HOME_CSS } from './home.css.ts'
6
+ import { HEADER_CSS } from './header.css.ts'
7
+ import { COMPOSER_CSS } from './composer.css.ts'
8
+ import { INFO_CSS } from './info.css.ts'
9
+ import { TURN_FOLD_CSS } from './turn-fold.css.ts'
10
+ import { CHIPS_CSS } from './chips.css.ts'
11
+
12
+ /**
13
+ * All mobile styles, concatenated in the exact order of the original
14
+ * single-file stylesheet (base → layout → compat → misc, where misc keeps
15
+ * composer → tablet → desktop), followed by the phone app shell (home), the
16
+ * session-header reflow (header), the composer reflow (composer), and the
17
+ * session-info sheet (info, which must come last of all: it re-shows a
18
+ * header.utilities child header.css.ts hides by default, so its rule has to
19
+ * win that tie too), the turn-process fold (turn-fold, S8, which shares no
20
+ * selector with any of them), and finally the chips row / settings entry /
21
+ * portal fix (chips, S5) — all appended in this order so their <768px rules
22
+ * win ties against the shared <=1023px block. Injected as ONE
23
+ * <style data-plugin> tag — do not reorder.
24
+ */
25
+ export const MOBILE_CSS = [BASE_CSS, LAYOUT_CSS, COMPAT_CSS, MISC_CSS, HOME_CSS, HEADER_CSS, COMPOSER_CSS, INFO_CSS, TURN_FOLD_CSS, CHIPS_CSS].join('\n')