dsh-zen-remote 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +51 -0
- package/LICENSE +38 -0
- package/README.md +237 -0
- package/assets/home.png +0 -0
- package/assets/info.png +0 -0
- package/assets/pairing.png +0 -0
- package/assets/session.png +0 -0
- package/assets/sheet.png +0 -0
- package/cordis.patch.yml +17 -0
- package/cordis.patch.yml.example +16 -0
- package/docs/interface.md +200 -0
- package/docs/remote-access.en.md +335 -0
- package/docs/remote-access.md +422 -0
- package/dsh-push.mjs +213 -0
- package/lan-gate.mjs +78 -0
- package/lib/client.js +6615 -0
- package/lib/index.js +310 -0
- package/lib/index.js.map +1 -0
- package/lib/lan-gate-server.cjs +626 -0
- package/lib/types/client/MobileAttachButton.d.ts +25 -0
- package/lib/types/client/MobileAttachButton.d.ts.map +1 -0
- package/lib/types/client/MobileAttachChips.d.ts +32 -0
- package/lib/types/client/MobileAttachChips.d.ts.map +1 -0
- package/lib/types/client/MobileDrawerFooter.d.ts +21 -0
- package/lib/types/client/MobileDrawerFooter.d.ts.map +1 -0
- package/lib/types/client/MobileHome.d.ts +24 -0
- package/lib/types/client/MobileHome.d.ts.map +1 -0
- package/lib/types/client/MobileHomeChips.d.ts +76 -0
- package/lib/types/client/MobileHomeChips.d.ts.map +1 -0
- package/lib/types/client/MobileNavOverlay.d.ts +15 -0
- package/lib/types/client/MobileNavOverlay.d.ts.map +1 -0
- package/lib/types/client/MobileNavToggle.d.ts +18 -0
- package/lib/types/client/MobileNavToggle.d.ts.map +1 -0
- package/lib/types/client/MobileSessionHeader.d.ts +58 -0
- package/lib/types/client/MobileSessionHeader.d.ts.map +1 -0
- package/lib/types/client/MobileSessionInfo.d.ts +41 -0
- package/lib/types/client/MobileSessionInfo.d.ts.map +1 -0
- package/lib/types/client/attach-upload.d.ts +78 -0
- package/lib/types/client/attach-upload.d.ts.map +1 -0
- package/lib/types/client/chips-store.d.ts +13 -0
- package/lib/types/client/chips-store.d.ts.map +1 -0
- package/lib/types/client/debug.d.ts +9 -0
- package/lib/types/client/debug.d.ts.map +1 -0
- package/lib/types/client/effects/aionui-compat.d.ts +4 -0
- package/lib/types/client/effects/aionui-compat.d.ts.map +1 -0
- package/lib/types/client/effects/gestures.d.ts +7 -0
- package/lib/types/client/effects/gestures.d.ts.map +1 -0
- package/lib/types/client/effects/header-status.d.ts +15 -0
- package/lib/types/client/effects/header-status.d.ts.map +1 -0
- package/lib/types/client/effects/phone-chrome.d.ts +106 -0
- package/lib/types/client/effects/phone-chrome.d.ts.map +1 -0
- package/lib/types/client/effects/turn-fold.d.ts +25 -0
- package/lib/types/client/effects/turn-fold.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +18 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/locales.d.ts +58 -0
- package/lib/types/client/locales.d.ts.map +1 -0
- package/lib/types/client/nav-store.d.ts +73 -0
- package/lib/types/client/nav-store.d.ts.map +1 -0
- package/lib/types/client/session-dot.d.ts +12 -0
- package/lib/types/client/session-dot.d.ts.map +1 -0
- package/lib/types/client/styles/base.css.d.ts +2 -0
- package/lib/types/client/styles/base.css.d.ts.map +1 -0
- package/lib/types/client/styles/chips.css.d.ts +2 -0
- package/lib/types/client/styles/chips.css.d.ts.map +1 -0
- package/lib/types/client/styles/compat.css.d.ts +2 -0
- package/lib/types/client/styles/compat.css.d.ts.map +1 -0
- package/lib/types/client/styles/composer.css.d.ts +2 -0
- package/lib/types/client/styles/composer.css.d.ts.map +1 -0
- package/lib/types/client/styles/header.css.d.ts +2 -0
- package/lib/types/client/styles/header.css.d.ts.map +1 -0
- package/lib/types/client/styles/home.css.d.ts +2 -0
- package/lib/types/client/styles/home.css.d.ts.map +1 -0
- package/lib/types/client/styles/index.d.ts +15 -0
- package/lib/types/client/styles/index.d.ts.map +1 -0
- package/lib/types/client/styles/info.css.d.ts +2 -0
- package/lib/types/client/styles/info.css.d.ts.map +1 -0
- package/lib/types/client/styles/layout.css.d.ts +2 -0
- package/lib/types/client/styles/layout.css.d.ts.map +1 -0
- package/lib/types/client/styles/misc.css.d.ts +2 -0
- package/lib/types/client/styles/misc.css.d.ts.map +1 -0
- package/lib/types/client/styles/turn-fold.css.d.ts +2 -0
- package/lib/types/client/styles/turn-fold.css.d.ts.map +1 -0
- package/lib/types/index.d.ts +83 -0
- package/lib/types/index.d.ts.map +1 -0
- package/llms.txt +20 -0
- package/package.json +128 -0
- package/pwa/app.css +95 -0
- package/pwa/icons/icon-192.png +0 -0
- package/pwa/icons/icon-512.png +0 -0
- package/pwa/icons/icon-maskable-512.png +0 -0
- package/pwa/icons/icon-maskable.svg +1 -0
- package/pwa/icons/icon.svg +1 -0
- package/pwa/inject.js +190 -0
- package/pwa/manifest.json +29 -0
- package/pwa/offline.html +38 -0
- package/pwa/sw.js +176 -0
- package/pwa/touch-gestures.js +94 -0
- package/scripts/build-client.mjs +101 -0
- package/scripts/cdp-probe.mjs +64 -0
- package/scripts/check-attach-upload.mjs +99 -0
- package/scripts/check-sunk-viewport.mjs +33 -0
- package/scripts/check-upload-endpoint.mjs +168 -0
- package/src/client/MobileAttachButton.tsx +125 -0
- package/src/client/MobileAttachChips.tsx +85 -0
- package/src/client/MobileDrawerFooter.tsx +56 -0
- package/src/client/MobileHome.tsx +348 -0
- package/src/client/MobileHomeChips.tsx +426 -0
- package/src/client/MobileNavOverlay.tsx +279 -0
- package/src/client/MobileNavToggle.tsx +52 -0
- package/src/client/MobileSessionHeader.tsx +242 -0
- package/src/client/MobileSessionInfo.tsx +303 -0
- package/src/client/attach-upload.ts +140 -0
- package/src/client/chips-store.ts +41 -0
- package/src/client/debug.ts +253 -0
- package/src/client/effects/aionui-compat.ts +183 -0
- package/src/client/effects/gestures.ts +265 -0
- package/src/client/effects/header-status.ts +30 -0
- package/src/client/effects/phone-chrome.ts +265 -0
- package/src/client/effects/turn-fold.ts +258 -0
- package/src/client/index.tsx +227 -0
- package/src/client/locales.ts +109 -0
- package/src/client/nav-store.ts +78 -0
- package/src/client/session-dot.ts +17 -0
- package/src/client/styles/base.css.ts +167 -0
- package/src/client/styles/chips.css.ts +224 -0
- package/src/client/styles/compat.css.ts +681 -0
- package/src/client/styles/composer.css.ts +504 -0
- package/src/client/styles/header.css.ts +368 -0
- package/src/client/styles/home.css.ts +477 -0
- package/src/client/styles/index.ts +25 -0
- package/src/client/styles/info.css.ts +238 -0
- package/src/client/styles/layout.css.ts +501 -0
- package/src/client/styles/misc.css.ts +128 -0
- package/src/client/styles/turn-fold.css.ts +106 -0
- package/src/index.ts +343 -0
|
@@ -0,0 +1,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')
|