dsh-pocket-relay 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/LICENSE +344 -0
- package/README.en.md +219 -0
- package/README.md +258 -0
- package/bin/dsh-pocket-relay.mjs +216 -0
- package/client/api.js +88 -0
- package/client/build.mjs +47 -0
- package/client/client.js +3238 -0
- package/client/index.jsx +979 -0
- package/client/mobile/LICENSE.dsh-web-mobile +4 -0
- package/client/mobile/MobileDrawerFooter.tsx +56 -0
- package/client/mobile/MobileNavOverlay.tsx +290 -0
- package/client/mobile/MobileNavToggle.tsx +52 -0
- package/client/mobile/fileGuard.ts +200 -0
- package/client/mobile/layout-mode.mjs +46 -0
- package/client/mobile/locales.ts +23 -0
- package/client/mobile/mobile-apply.tsx +378 -0
- package/client/mobile/mobile.css.ts +1005 -0
- package/client/mobile/nav-targets.mjs +85 -0
- package/client/pocket-locales.js +331 -0
- package/cordis.patch.yml +11 -0
- package/lib/device-auth-http.mjs +338 -0
- package/lib/device-auth.mjs +485 -0
- package/lib/index.js +414 -0
- package/lib/ip.mjs +11 -0
- package/lib/proxy.mjs +1223 -0
- package/lib/relay-protocol.mjs +101 -0
- package/lib/relay.mjs +355 -0
- package/lib/restart.js +112 -0
- package/lib/service.mjs +578 -0
- package/lib/settings.mjs +392 -0
- package/lib/tunnel.mjs +621 -0
- package/lib/web-rpc.js +622 -0
- package/package.json +89 -0
|
@@ -0,0 +1,1005 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mobile stylesheet for the DSH web shell.
|
|
3
|
+
*
|
|
4
|
+
* Hooks are the stable framework attributes only — no hashed classes:
|
|
5
|
+
* - `[data-mobile-nav="frame"]` our marker on the AppFrame element (value-scoped so
|
|
6
|
+
* it never matches the plugin's own controls)
|
|
7
|
+
* - `[data-sidebar-collapsed]` AppFrame: sidebar is in the compact rail state
|
|
8
|
+
* - `[data-side="sidebar"|"details"]` AppFrame drag handles
|
|
9
|
+
* - `[data-shell-overlay]` AppFrame overlay layer (used to locate the frame)
|
|
10
|
+
* - `[data-phase]` conversation root phase (hero|active|settling)
|
|
11
|
+
*
|
|
12
|
+
* Below the official auto-collapse breakpoint (1024px) the rail is removed
|
|
13
|
+
* from the grid entirely; the sidebar column becomes an overlay drawer that
|
|
14
|
+
* slides in when the frame leaves the collapsed state (narrowExpanded).
|
|
15
|
+
*/
|
|
16
|
+
export const MOBILE_CSS = `
|
|
17
|
+
/* ---------- base control styles (rendered at any width, hidden where unused) ---------- */
|
|
18
|
+
|
|
19
|
+
[data-mobile-nav="toggle"],
|
|
20
|
+
[data-mobile-nav="files"] {
|
|
21
|
+
display: inline-flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
width: 28px;
|
|
25
|
+
height: 28px;
|
|
26
|
+
flex: none;
|
|
27
|
+
padding: 0;
|
|
28
|
+
border: none;
|
|
29
|
+
border-radius: 50%;
|
|
30
|
+
background: transparent;
|
|
31
|
+
color: var(--dsw-alias-label-secondary, inherit);
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
-webkit-tap-highlight-color: transparent;
|
|
34
|
+
}
|
|
35
|
+
[data-mobile-nav="toggle"]:hover,
|
|
36
|
+
[data-mobile-nav="files"]:hover {
|
|
37
|
+
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));
|
|
38
|
+
}
|
|
39
|
+
[data-mobile-nav="toggle"]:focus-visible,
|
|
40
|
+
[data-mobile-nav="files"]:focus-visible {
|
|
41
|
+
outline: 2px solid var(--dsw-alias-state-business-primary, #4f6ef7);
|
|
42
|
+
outline-offset: 1px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Drawer footer actions: the relocated Session log download plus the Files
|
|
46
|
+
action that opens the dsh-web-ui explorer sheet. */
|
|
47
|
+
[data-mobile-nav="drawer-actions"] {
|
|
48
|
+
display: inline-flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
gap: 8px;
|
|
51
|
+
}
|
|
52
|
+
/* 宿主没有 aionui explorer 列(官方 DSH 无 dsh-web-ui,issue #48)时隐藏
|
|
53
|
+
移动端「文件浏览」入口(header 图标 + drawer footer 项)——不然点了没反应。 */
|
|
54
|
+
[data-mobile-nav-explorer="0"] [data-mobile-nav="files"],
|
|
55
|
+
[data-mobile-nav-explorer="0"] [data-mobile-nav="explorer"] {
|
|
56
|
+
display: none !important;
|
|
57
|
+
}
|
|
58
|
+
[data-mobile-nav="session-log"],
|
|
59
|
+
[data-mobile-nav="explorer"] {
|
|
60
|
+
display: inline-flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
justify-content: center;
|
|
63
|
+
gap: 6px;
|
|
64
|
+
height: 34px;
|
|
65
|
+
padding: 0 12px;
|
|
66
|
+
border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12));
|
|
67
|
+
border-radius: 12px;
|
|
68
|
+
background: transparent;
|
|
69
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
70
|
+
font-family: inherit;
|
|
71
|
+
font-size: 13px;
|
|
72
|
+
line-height: 20px;
|
|
73
|
+
cursor: pointer;
|
|
74
|
+
-webkit-tap-highlight-color: transparent;
|
|
75
|
+
}
|
|
76
|
+
[data-mobile-nav="session-log"]:hover:not(:disabled),
|
|
77
|
+
[data-mobile-nav="explorer"]:hover {
|
|
78
|
+
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));
|
|
79
|
+
}
|
|
80
|
+
[data-mobile-nav="session-log"]:disabled {
|
|
81
|
+
color: var(--dsw-alias-label-dimmed, rgba(0, 0, 0, .35));
|
|
82
|
+
cursor: default;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Floating fallback button (hero / blank phases without a session header).
|
|
86
|
+
The top clears the camera band below the status bar; when the client has
|
|
87
|
+
set viewport-fit=cover the safe-area inset moves it below the notch too. */
|
|
88
|
+
[data-mobile-nav="fab"] {
|
|
89
|
+
position: absolute;
|
|
90
|
+
top: calc(env(safe-area-inset-top, 0px) + 72px);
|
|
91
|
+
left: 10px;
|
|
92
|
+
z-index: 21;
|
|
93
|
+
display: inline-flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
width: 38px;
|
|
97
|
+
height: 38px;
|
|
98
|
+
padding: 0;
|
|
99
|
+
border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12));
|
|
100
|
+
border-radius: 50%;
|
|
101
|
+
background: var(--dsw-alias-button-floating-fill, #ffffff);
|
|
102
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, .18);
|
|
105
|
+
-webkit-tap-highlight-color: transparent;
|
|
106
|
+
}
|
|
107
|
+
[data-mobile-nav="fab"]:hover {
|
|
108
|
+
background: var(--dsw-alias-button-floating-hover, rgba(0, 0, 0, .08));
|
|
109
|
+
}
|
|
110
|
+
[data-mobile-nav="fab"]:focus-visible {
|
|
111
|
+
outline: 2px solid var(--dsw-alias-state-business-primary, #4f6ef7);
|
|
112
|
+
outline-offset: 2px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Dimmed backdrop under the open drawer; above every column, below the drawer.
|
|
116
|
+
pointer-events: none —— 点击穿透(issue #38):backdrop 只负责视觉压暗,
|
|
117
|
+
不抢点击。关闭抽屉改由 MobileNavOverlay 的 document 级「抽屉外点击」监听处理
|
|
118
|
+
(等价于原来的点击遮罩关闭,且抽屉内点击不再被 backdrop 吃掉)。 */
|
|
119
|
+
[data-mobile-nav="backdrop"] {
|
|
120
|
+
position: absolute;
|
|
121
|
+
inset: 0;
|
|
122
|
+
z-index: 30;
|
|
123
|
+
background: rgba(0, 0, 0, .45);
|
|
124
|
+
pointer-events: none;
|
|
125
|
+
animation: dsh-mobile-nav-fade .2s var(--ds-ease-in-out, ease-in-out);
|
|
126
|
+
-webkit-tap-highlight-color: transparent;
|
|
127
|
+
}
|
|
128
|
+
@keyframes dsh-mobile-nav-fade {
|
|
129
|
+
from { opacity: 0; }
|
|
130
|
+
to { opacity: 1; }
|
|
131
|
+
}
|
|
132
|
+
/* Settings sheet entrance: the official dialog mounts with no animation at
|
|
133
|
+
all, so it snaps in. Fade + slight rise/scale reads as a proper sheet. */
|
|
134
|
+
@keyframes dsh-mobile-nav-sheet-in {
|
|
135
|
+
from {
|
|
136
|
+
opacity: 0;
|
|
137
|
+
transform: translateY(14px) scale(.98);
|
|
138
|
+
}
|
|
139
|
+
to {
|
|
140
|
+
opacity: 1;
|
|
141
|
+
transform: none;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/* Preview sheet rise: the aionui preview column opens as a bottom sheet. */
|
|
145
|
+
@keyframes dsh-mobile-nav-sheet-up {
|
|
146
|
+
from {
|
|
147
|
+
opacity: 0;
|
|
148
|
+
transform: translateY(28px);
|
|
149
|
+
}
|
|
150
|
+
to {
|
|
151
|
+
opacity: 1;
|
|
152
|
+
transform: none;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* ---------- mobile-only layout ---------- */
|
|
157
|
+
|
|
158
|
+
@media (max-width: 1023px) {
|
|
159
|
+
/* --- Phone chrome ---
|
|
160
|
+
The system status bar stays visible (no fullscreen). Two adjustments
|
|
161
|
+
make it behave:
|
|
162
|
+
- touch-action: manipulation kills double-tap-to-zoom (and the 300ms
|
|
163
|
+
tap delay) while keeping pan and pinch zoom; the client also
|
|
164
|
+
suppresses legacy-iOS gesturestart as a fallback.
|
|
165
|
+
- With the client's viewport-fit=cover, env(safe-area-inset-top) is the
|
|
166
|
+
status bar / notch height; the rules below push the app content below
|
|
167
|
+
it so the status bar never covers anything. Off notched phones (or in
|
|
168
|
+
a normal browser tab where the layout viewport already sits below the
|
|
169
|
+
status bar) the inset is 0 and nothing shifts. */
|
|
170
|
+
html,
|
|
171
|
+
body {
|
|
172
|
+
touch-action: manipulation !important;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* AppFrame: the drawer takes the sidebar column out of grid flow, so the
|
|
176
|
+
remaining in-flow items (center, details) land in tracks 1..2: give the
|
|
177
|
+
center every pixel and keep the details track at zero. The top padding
|
|
178
|
+
clears the status bar / notch for every in-flow surface (session header,
|
|
179
|
+
messages, composer); the absolutely-positioned drawer is unaffected (its
|
|
180
|
+
containing block is the frame's padding box, i.e. still the frame top). */
|
|
181
|
+
[data-mobile-nav="frame"] {
|
|
182
|
+
position: relative !important;
|
|
183
|
+
grid-template-columns: minmax(0, 1fr) 0 0 !important;
|
|
184
|
+
padding-top: env(safe-area-inset-top, 0px) !important;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* 主内容列(第 2 个网格子元素)在官方样式里有显式 grid-column: 2——
|
|
188
|
+
网格被压缩成 [1fr, 0, 0] 后它会落在 0px 的第 2 轨,整个主界面被挤出
|
|
189
|
+
视口(只剩背景图)。必须显式把它拉回第 1 轨(issue #5)。
|
|
190
|
+
第 3 列(details)保持 0 轨即可,无需处理。 */
|
|
191
|
+
[data-mobile-nav="frame"] > :nth-child(2) {
|
|
192
|
+
grid-column: 1 !important;
|
|
193
|
+
grid-row: 1 !important;
|
|
194
|
+
min-width: 0 !important;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* The sidebar column (first grid child) becomes a left drawer. The drawer
|
|
198
|
+
hugs the sidebar content exactly (the wide sidebar carries an inline
|
|
199
|
+
width, ~280px): a fixed 92vw box would leave a white strip where the
|
|
200
|
+
container background shows beside the content.
|
|
201
|
+
Closed state: translateX(-110%) — more than -100% of the max-content
|
|
202
|
+
width — guarantees the whole drawer (and its shadow, had it one) leaves
|
|
203
|
+
the viewport. A mere -100% leaves a sliver on screen; -105% (as used
|
|
204
|
+
before) left 14px of the drawer plus a long 32px-blur shadow gradient
|
|
205
|
+
visible along the left edge of the main UI. No box-shadow at all: the
|
|
206
|
+
dimmed backdrop already separates drawer from content.
|
|
207
|
+
Z-index note: the backdrop renders inside the shell's overlay layer
|
|
208
|
+
([data-shell-overlay]), which forms its own stacking context. Third-party
|
|
209
|
+
plugins can force that layer up with !important (dsh-update-checker sets
|
|
210
|
+
it to 500), and when the layer outranks the drawer, the backdrop paints
|
|
211
|
+
ABOVE the drawer and swallows every tap — the drawer opens but no row
|
|
212
|
+
can be pressed (every tap just closes it). The drawer must therefore
|
|
213
|
+
outrank any such raise.
|
|
214
|
+
1200 (was 600) clears the mobile layers shipped by
|
|
215
|
+
@linxin666/dsh-web-ui-all — its sidebar pane is z-index 1100, its
|
|
216
|
+
details pane 1000 and its full-screen frame ::after mask 1050 (issue
|
|
217
|
+
#67: that mask sat on top of the 600 drawer and ate every tap). Still
|
|
218
|
+
far under the fixed-position banners/toasts (z 9999) that float at the
|
|
219
|
+
viewport level. */
|
|
220
|
+
[data-mobile-nav="frame"] > :first-child {
|
|
221
|
+
position: absolute !important;
|
|
222
|
+
inset: 0 auto 0 0 !important;
|
|
223
|
+
width: max-content !important;
|
|
224
|
+
max-width: 92vw !important;
|
|
225
|
+
z-index: 1200 !important;
|
|
226
|
+
transform: translateX(-110%);
|
|
227
|
+
transition: transform .28s var(--ds-ease-in-out, ease-in-out);
|
|
228
|
+
background: var(--dsw-alias-bg-base, #ffffff);
|
|
229
|
+
/* Keep the drawer's own content below the status bar / notch: the drawer
|
|
230
|
+
spans the full frame height (its absolute containing block is the
|
|
231
|
+
frame's padding box, so the frame's own safe-area padding does NOT
|
|
232
|
+
reach it). The drawer background paints the status-bar strip, which
|
|
233
|
+
the client's theme-color meta matches, so the strip reads seamless. */
|
|
234
|
+
padding-top: env(safe-area-inset-top, 0px) !important;
|
|
235
|
+
/* Kill the official sidebarCol right border: with the backdrop the edge
|
|
236
|
+
reads cleanly, and the settings dialog (width:100% of this box) stays
|
|
237
|
+
pixel-flush with the drawer. */
|
|
238
|
+
border-right: none !important;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/* Expanded state (frame without data-sidebar-collapsed) slides the drawer in.
|
|
242
|
+
The open state must be transform:none — NOT translateX(0): an identity
|
|
243
|
+
transform still makes the drawer the containing block for fixed-position
|
|
244
|
+
descendants (the settings dialog's .VOzbGW_overlay is portaled into the
|
|
245
|
+
sidebar DOM). With the identity transform the wide settings sheet
|
|
246
|
+
(100vw-16) overflows the 280px drawer, the dialog's focus scrolls the
|
|
247
|
+
overflow:hidden drawer to scrollLeft=102, and every static child (plus the
|
|
248
|
+
fixed overlay) shifts 102px off-screen. With transform:none the overlay is
|
|
249
|
+
viewport-anchored: it dims the full screen and the sheet sits at left:8. */
|
|
250
|
+
[data-mobile-nav="frame"]:not([data-sidebar-collapsed]) > :first-child {
|
|
251
|
+
transform: none !important;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* Kill a competing full-screen mask (issue #67).
|
|
255
|
+
@linxin666/dsh-web-ui-all ships its own mobile drawer, and part of it is
|
|
256
|
+
|
|
257
|
+
[data-dsh-frame]:not([data-sidebar-collapsed])::after {
|
|
258
|
+
content: ""; position: fixed; inset: 0; z-index: 1050;
|
|
259
|
+
background: rgb(0 0 0 / 24%);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
The pseudo-element belongs to the frame we already mark, and the frame
|
|
263
|
+
carries only "position: relative" with z-index auto — no stacking context
|
|
264
|
+
— so this mask competes with the drawer in the parent stacking context
|
|
265
|
+
and, at 1050, paints over it. It covers the whole viewport, so every tap
|
|
266
|
+
on a session row lands on the mask instead: the drawer opens but nothing
|
|
267
|
+
inside it can be pressed, and the page behind cannot be scrolled.
|
|
268
|
+
Removing it is safe: the mobile stylesheet already renders its own
|
|
269
|
+
backdrop, and tapping outside the drawer is handled in JS.
|
|
270
|
+
|
|
271
|
+
The attribute selector is repeated on purpose. Their rule has the same
|
|
272
|
+
specificity (0,2,1) once ours is written the obvious way, and plugin
|
|
273
|
+
stylesheets are injected in load order, so a tie would be decided by
|
|
274
|
+
whichever plugin happened to load last. Doubling the attribute makes it
|
|
275
|
+
(0,3,1) and deterministic. */
|
|
276
|
+
[data-mobile-nav="frame"][data-mobile-nav="frame"]:not([data-sidebar-collapsed])::after {
|
|
277
|
+
content: none !important;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* Drag handles are useless on touch and would float over the drawer. */
|
|
281
|
+
[data-side="sidebar"],
|
|
282
|
+
[data-side="details"] {
|
|
283
|
+
display: none !important;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* --- Conversation text on mobile ---
|
|
287
|
+
The official message flow keeps desktop's 32px side gutters and 16px
|
|
288
|
+
type. On a phone: shrink the type a notch and widen the lines by
|
|
289
|
+
trimming the gutters (the sidebar drawer list keeps its size). The
|
|
290
|
+
flow's scroll container is the only _scroll element holding markdown
|
|
291
|
+
<p> paragraphs — the composer's own scroll (textarea) is excluded
|
|
292
|
+
via :has(p). */
|
|
293
|
+
/* The official main scroll body reserves scrollbar-gutter for desktop
|
|
294
|
+
scrollbars (8px), which shoves every column off-center on a phone.
|
|
295
|
+
Classic desktop scrollbars (Edge/Chrome) also occupy ~8-17px in a
|
|
296
|
+
phone-sized viewport, shifting the column further. Mobile scrolling
|
|
297
|
+
is touch/wheel, so remove the scrollbar entirely on phones: the
|
|
298
|
+
column is then exactly centered in every browser. */
|
|
299
|
+
[data-phase] [class$="_scrollBody"] {
|
|
300
|
+
scrollbar-gutter: auto !important;
|
|
301
|
+
scrollbar-width: none !important;
|
|
302
|
+
}
|
|
303
|
+
[data-phase] [class$="_scrollBody"]::-webkit-scrollbar {
|
|
304
|
+
display: none !important;
|
|
305
|
+
width: 0 !important;
|
|
306
|
+
height: 0 !important;
|
|
307
|
+
}
|
|
308
|
+
/* Message action rows (copy / run-time badges) can overflow the right
|
|
309
|
+
edge on narrow screens — keep them inside the message width. */
|
|
310
|
+
[data-phase] [class$="_actions"] {
|
|
311
|
+
overflow: hidden !important;
|
|
312
|
+
}
|
|
313
|
+
[data-phase] [class$="_actions"] [class$="_timeEnd"] {
|
|
314
|
+
flex: 0 1 auto !important;
|
|
315
|
+
min-width: 0 !important;
|
|
316
|
+
overflow: hidden !important;
|
|
317
|
+
text-overflow: ellipsis !important;
|
|
318
|
+
white-space: nowrap !important;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
[data-phase] [class$="_scroll"]:has(p) {
|
|
322
|
+
padding-left: 20px !important;
|
|
323
|
+
padding-right: 20px !important;
|
|
324
|
+
font-size: 15px !important;
|
|
325
|
+
}
|
|
326
|
+
/* The official markdown styles set an explicit 16px on paragraphs and
|
|
327
|
+
list items, so the container's inherited 15px is not enough. User
|
|
328
|
+
messages render their text in a div whose class carries _text_
|
|
329
|
+
(16px too) — cover it as well. */
|
|
330
|
+
[data-phase] [class$="_scroll"]:has(p) p,
|
|
331
|
+
[data-phase] [class$="_scroll"]:has(p) li,
|
|
332
|
+
[data-phase] [class$="_scroll"]:has(p) [class*="_text_"] {
|
|
333
|
+
font-size: 15px !important;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/* Keep DSH's own process disclosures and their expand/collapse behaviour,
|
|
337
|
+
but remove desktop-sized vertical breathing room between consecutive
|
|
338
|
+
context, Skill, and system-prompt entries. */
|
|
339
|
+
[data-phase] [data-turn-process] {
|
|
340
|
+
height: 28px !important;
|
|
341
|
+
padding-bottom: 4px !important;
|
|
342
|
+
margin-bottom: 4px !important;
|
|
343
|
+
}
|
|
344
|
+
[data-phase] [data-turn-process][data-open] {
|
|
345
|
+
margin-bottom: 4px !important;
|
|
346
|
+
}
|
|
347
|
+
[data-phase] [data-disclosure-row] {
|
|
348
|
+
min-height: 24px !important;
|
|
349
|
+
}
|
|
350
|
+
[data-phase] :is([data-context-injection-body], [data-system-prompt-body]) {
|
|
351
|
+
margin-top: 2px !important;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/* --- Composer bottom row on mobile ---
|
|
355
|
+
Keep add, permission, model, reasoning and send controls on one line at
|
|
356
|
+
the Honor 50's 360px CSS viewport. DSH's stable data-composer-card hook
|
|
357
|
+
survives the editor's textarea -> contenteditable migration. */
|
|
358
|
+
[data-phase] [data-composer-card="true"] > [class$="_row"] {
|
|
359
|
+
flex-wrap: nowrap !important;
|
|
360
|
+
gap: 6px !important;
|
|
361
|
+
}
|
|
362
|
+
[data-phase] [data-composer-card="true"] > [class$="_row"] > :first-child {
|
|
363
|
+
gap: 8px !important;
|
|
364
|
+
min-width: 0 !important;
|
|
365
|
+
}
|
|
366
|
+
[data-phase] [data-composer-card="true"] > [class$="_row"] > :first-child > :nth-child(2) {
|
|
367
|
+
flex: 0 1 auto !important;
|
|
368
|
+
min-width: 0 !important;
|
|
369
|
+
}
|
|
370
|
+
[data-phase] [data-composer-card="true"] > [class$="_row"] > :last-child {
|
|
371
|
+
flex: 1 1 0 !important;
|
|
372
|
+
min-width: 0 !important;
|
|
373
|
+
gap: 6px !important;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* --- Composer popups as bottom sheets on mobile ---
|
|
377
|
+
Two composer-anchored popups break on phones (field: bottom + side
|
|
378
|
+
cut, only part of the popup visible):
|
|
379
|
+
1. the model pill menu ([role=menu], max 360px, opens upward from the
|
|
380
|
+
pill inside the composer card);
|
|
381
|
+
2. the "/" command palette (max 320px card with the search box — it
|
|
382
|
+
hosts the /model popupSelect list: search + provider-grouped rows).
|
|
383
|
+
Both are position:absolute INSIDE the conversation scrollBody
|
|
384
|
+
(overflow:hidden) and the shell center column (overflow:hidden), so
|
|
385
|
+
the scroll containers clip them mid-list. Forensics showed neither
|
|
386
|
+
layer creates a containing block (no transform/contain/will-change),
|
|
387
|
+
so on mobile we snap whichever popup is open to a viewport-anchored
|
|
388
|
+
sheet: fixed positioning escapes the scroll clip entirely, width is
|
|
389
|
+
deterministic, safe-area keeps it off the gesture bar. A transient
|
|
390
|
+
picker covering the composer is standard mobile UX; selection or an
|
|
391
|
+
outside tap still dismisses it. */
|
|
392
|
+
[data-phase] [class$="_root"]:has(> [aria-haspopup="menu"]) > [role="menu"],
|
|
393
|
+
[data-phase] [class$="_card"]:has(> [class$="_search"]) {
|
|
394
|
+
position: fixed !important;
|
|
395
|
+
left: 12px !important;
|
|
396
|
+
right: 12px !important;
|
|
397
|
+
top: auto !important;
|
|
398
|
+
bottom: calc(env(safe-area-inset-bottom, 0px) + 12px) !important;
|
|
399
|
+
width: auto !important;
|
|
400
|
+
min-width: 0 !important;
|
|
401
|
+
max-width: none !important;
|
|
402
|
+
max-height: min(65vh, 480px) !important;
|
|
403
|
+
max-height: min(65dvh, 480px) !important;
|
|
404
|
+
z-index: 130 !important;
|
|
405
|
+
border-radius: 14px !important;
|
|
406
|
+
box-shadow: 0 -4px 28px rgba(0, 0, 0, .18) !important;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/* --- Session header on mobile ---
|
|
410
|
+
Layout goal: [toggle] [session title] [mode badge] in a row, with the
|
|
411
|
+
Session log capsule removed from the header (relocated to the drawer
|
|
412
|
+
footer). Stable structural hooks only:
|
|
413
|
+
[data-phase] header the session header element
|
|
414
|
+
header > :first-child titleRow (titleCluster + utilities)
|
|
415
|
+
header > :first-child > :last-child headerUtilities (Session log seat) */
|
|
416
|
+
[data-phase] header {
|
|
417
|
+
padding: 8px 12px 0 !important;
|
|
418
|
+
}
|
|
419
|
+
/* The directory and Files controls are absolutely positioned, so reserve
|
|
420
|
+
their lanes and let the title use the remaining width without squeezing. */
|
|
421
|
+
[data-phase] header > :first-child {
|
|
422
|
+
min-height: 36px !important;
|
|
423
|
+
padding: 0 32px !important;
|
|
424
|
+
}
|
|
425
|
+
[data-phase] header [class$="_titleCluster"],
|
|
426
|
+
[data-phase] header [class$="_crumbs"] {
|
|
427
|
+
min-width: 0 !important;
|
|
428
|
+
}
|
|
429
|
+
[data-phase] header button[class*="_crumb"] {
|
|
430
|
+
max-width: calc(100vw - 104px) !important;
|
|
431
|
+
padding-left: 0 !important;
|
|
432
|
+
padding-right: 0 !important;
|
|
433
|
+
}
|
|
434
|
+
/* The directory toggle sits at the far left of the header (the header
|
|
435
|
+
is position:relative; the data-slot wrappers are display:contents). */
|
|
436
|
+
[data-mobile-nav="toggle"] {
|
|
437
|
+
position: absolute !important;
|
|
438
|
+
left: 8px !important;
|
|
439
|
+
top: 12px !important;
|
|
440
|
+
z-index: 2 !important;
|
|
441
|
+
}
|
|
442
|
+
/* The Files action sits at the FAR RIGHT of the header so it reads as a
|
|
443
|
+
distinct control from the directory toggle on the left (which opens
|
|
444
|
+
the history sidebar). */
|
|
445
|
+
[data-mobile-nav="files"] {
|
|
446
|
+
position: absolute !important;
|
|
447
|
+
left: auto !important;
|
|
448
|
+
right: 8px !important;
|
|
449
|
+
top: 12px !important;
|
|
450
|
+
z-index: 2 !important;
|
|
451
|
+
}
|
|
452
|
+
/* Session log download: gone from the header row on mobile (the utilities
|
|
453
|
+
seat holds only the session-log-export capsule). */
|
|
454
|
+
[data-phase] header > :first-child > :last-child {
|
|
455
|
+
display: none !important;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/* --- Settings dialog on mobile ---
|
|
459
|
+
Desktop: 800px two-column flex (188px nav + content). Mobile: a
|
|
460
|
+
near-full-width sheet — nav tabs wrap into rows on top, option rows
|
|
461
|
+
stay horizontal (title+description left, control right). Structural
|
|
462
|
+
selectors are scoped to the unique aria-modal dialog; every
|
|
463
|
+
settings-specific rule is gated with
|
|
464
|
+
:has(> :first-child > :last-child > button) — the settings nav tab
|
|
465
|
+
list holds <button> tabs, so the transient export dialog (the same
|
|
466
|
+
primitives Modal, header(title+close)+description+body) keeps its
|
|
467
|
+
official centered card layout. Requires :has() support
|
|
468
|
+
(Chromium 105+, 2022). */
|
|
469
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) {
|
|
470
|
+
position: absolute !important;
|
|
471
|
+
left: 8px !important;
|
|
472
|
+
/* Fixed top (no translateY): a transform on the panel combined with the
|
|
473
|
+
panel overflowing the max-content drawer shifts the fixed overlay's
|
|
474
|
+
coordinate frame, dragging the whole sidebar content off-screen. The
|
|
475
|
+
safe-area inset keeps the sheet below the status bar / notch. */
|
|
476
|
+
top: calc(env(safe-area-inset-top, 0px) + 12px) !important;
|
|
477
|
+
width: calc(100vw - 16px) !important;
|
|
478
|
+
max-width: calc(100vw - 16px) !important;
|
|
479
|
+
/* Height follows the content (no dead space under a short page); it
|
|
480
|
+
caps at 100dvh-24 (less the safe-area top) and the options area
|
|
481
|
+
scrolls only then. */
|
|
482
|
+
height: auto !important;
|
|
483
|
+
max-height: min(800px, calc(100vh - 24px - env(safe-area-inset-top, 0px))) !important;
|
|
484
|
+
max-height: min(800px, calc(100dvh - 24px - env(safe-area-inset-top, 0px))) !important;
|
|
485
|
+
flex-direction: column !important;
|
|
486
|
+
border-radius: 14px !important;
|
|
487
|
+
animation: dsh-mobile-nav-sheet-in .22s var(--ds-ease-out, ease-in-out);
|
|
488
|
+
}
|
|
489
|
+
/* The settings sheet's dimmed mask fades in with the panel (the mask is
|
|
490
|
+
the first child of the overlay that directly contains the sheet). */
|
|
491
|
+
:has(> [aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"]))) > :first-child {
|
|
492
|
+
animation: dsh-mobile-nav-fade .18s var(--ds-ease-out, ease-in-out);
|
|
493
|
+
}
|
|
494
|
+
@media (prefers-reduced-motion: reduce) {
|
|
495
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])),
|
|
496
|
+
:has(> [aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"]))) > :first-child {
|
|
497
|
+
animation: none !important;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/* The export dialog (not the settings sheet) must never overflow the
|
|
501
|
+
viewport: the official centered card can be wider than 390px. */
|
|
502
|
+
[aria-modal="true"]:not(:has(> :first-child > :last-child > button)) {
|
|
503
|
+
max-width: calc(100vw - 32px) !important;
|
|
504
|
+
}
|
|
505
|
+
/* Nav bar: hide the "Settings" caption (redundant on a full-width sheet)
|
|
506
|
+
and wrap the tab list so every tab is visible — a horizontal scroll cut
|
|
507
|
+
the last tab ("Plugins") off with no affordance to scroll. */
|
|
508
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :first-child {
|
|
509
|
+
width: 100% !important;
|
|
510
|
+
flex-direction: row !important;
|
|
511
|
+
align-items: center !important;
|
|
512
|
+
gap: 6px !important;
|
|
513
|
+
padding: 10px 12px 8px !important;
|
|
514
|
+
}
|
|
515
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :first-child > :first-child {
|
|
516
|
+
display: none !important;
|
|
517
|
+
}
|
|
518
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :first-child > :last-child {
|
|
519
|
+
flex-direction: row !important;
|
|
520
|
+
flex-wrap: wrap !important;
|
|
521
|
+
width: 100% !important;
|
|
522
|
+
gap: 6px !important;
|
|
523
|
+
overflow: visible !important;
|
|
524
|
+
}
|
|
525
|
+
/* Content toolbar (Open configuration file + close): spread to the edges
|
|
526
|
+
instead of clustering right with a dead zone on the left. The toolbar
|
|
527
|
+
children carry official auto-margins that would defeat space-between,
|
|
528
|
+
so neutralize them. The close button gets a round tappable base so it
|
|
529
|
+
reads as its own control, not part of the outline button. */
|
|
530
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :last-child > :first-child {
|
|
531
|
+
justify-content: space-between !important;
|
|
532
|
+
align-items: center !important;
|
|
533
|
+
padding: 0 12px !important;
|
|
534
|
+
min-height: 40px !important;
|
|
535
|
+
}
|
|
536
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :last-child > :first-child > * {
|
|
537
|
+
margin-left: 0 !important;
|
|
538
|
+
margin-right: 0 !important;
|
|
539
|
+
}
|
|
540
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :last-child > :first-child > :last-child {
|
|
541
|
+
width: 32px !important;
|
|
542
|
+
height: 32px !important;
|
|
543
|
+
border-radius: 50% !important;
|
|
544
|
+
display: inline-flex !important;
|
|
545
|
+
align-items: center !important;
|
|
546
|
+
justify-content: center !important;
|
|
547
|
+
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06)) !important;
|
|
548
|
+
}
|
|
549
|
+
/* Appearance mode cards: the official cube row renders three tall
|
|
550
|
+
vertical cards (~268px) that eat half the sheet. Turn them into a
|
|
551
|
+
compact horizontal trio (icon + label inline, equal widths).
|
|
552
|
+
Relies on the official cube-row class name of this version. */
|
|
553
|
+
[aria-modal="true"] [class$="_cubeRow"] {
|
|
554
|
+
gap: 6px !important;
|
|
555
|
+
}
|
|
556
|
+
[aria-modal="true"] [class$="_cubeRow"] > * {
|
|
557
|
+
flex: 1 1 0 !important;
|
|
558
|
+
flex-direction: row !important;
|
|
559
|
+
align-items: center !important;
|
|
560
|
+
justify-content: center !important;
|
|
561
|
+
gap: 6px !important;
|
|
562
|
+
padding: 10px 8px !important;
|
|
563
|
+
min-height: 0 !important;
|
|
564
|
+
}
|
|
565
|
+
/* Content: the options scroll area gets bottom breathing room so the last
|
|
566
|
+
row never sits flush against the sheet's rounded corner. */
|
|
567
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :last-child {
|
|
568
|
+
flex: 1 1 auto !important;
|
|
569
|
+
min-height: 0 !important;
|
|
570
|
+
}
|
|
571
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :last-child > :last-child {
|
|
572
|
+
padding: 0 12px 24px !important;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/* ---------- dsh-web-ui family compatibility ----------
|
|
576
|
+
The linxin666 plugin suite extends the shell frame directly:
|
|
577
|
+
- aionui-panel appends two trailing grid columns (explorer / preview)
|
|
578
|
+
plus absolute drag handles to [data-dsh-frame]; its 5-track inline
|
|
579
|
+
grid is already overridden above, but the handles and columns would
|
|
580
|
+
still float over the main UI. On mobile the columns leave the grid
|
|
581
|
+
as floating bottom sheets and keep their own visibility state —
|
|
582
|
+
the suite's collapse chevron / preview tabs still work, so no
|
|
583
|
+
feature is lost. The task-board / ssh plugins inject sidebar
|
|
584
|
+
entries and center-column takeover panels; the entries need
|
|
585
|
+
spacing and the kanban needs scrollable columns. */
|
|
586
|
+
|
|
587
|
+
/* Touch devices: the drag handles are useless — the floating expand
|
|
588
|
+
button is the opener. */
|
|
589
|
+
.aionui-explorer-handle,
|
|
590
|
+
.aionui-preview-handle {
|
|
591
|
+
display: none !important;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/* Shared base: both columns leave the grid as floating panels. The
|
|
595
|
+
explorer is gated shut by default (its own persisted expanded state
|
|
596
|
+
must never cover the mobile UI on load); the header Files action opens
|
|
597
|
+
it via the frame marker below, and the sheet's own collapse chevron
|
|
598
|
+
clears it. Preview stays owned by the suite (hidden while no tab is
|
|
599
|
+
open). The per-column rules below override the geometry. */
|
|
600
|
+
[data-aionui-explorer-col],
|
|
601
|
+
[data-aionui-preview-col] {
|
|
602
|
+
position: fixed !important;
|
|
603
|
+
z-index: 55 !important;
|
|
604
|
+
background: var(--aion-bg-base, #ffffff) !important;
|
|
605
|
+
border-left: none !important;
|
|
606
|
+
}
|
|
607
|
+
/* Explorer (file tree) bottom sheet: bottom edge aligned exactly with
|
|
608
|
+
the composer card's bottom line — the card sits 36px above the
|
|
609
|
+
viewport bottom (8px composer padding + the 28px stats strip below
|
|
610
|
+
the card), so the sheet uses the same 36px bottom offset. */
|
|
611
|
+
[data-aionui-explorer-col] {
|
|
612
|
+
visibility: hidden !important;
|
|
613
|
+
left: 8px !important;
|
|
614
|
+
right: 8px !important;
|
|
615
|
+
top: auto !important;
|
|
616
|
+
bottom: 36px !important;
|
|
617
|
+
width: auto !important;
|
|
618
|
+
height: min(55dvh, 460px) !important;
|
|
619
|
+
max-height: calc(100dvh - 44px) !important;
|
|
620
|
+
border-radius: 14px !important;
|
|
621
|
+
overflow: hidden !important;
|
|
622
|
+
box-shadow: 0 -4px 28px rgba(0, 0, 0, .18) !important;
|
|
623
|
+
animation: dsh-mobile-nav-sheet-up .24s var(--ds-ease-out, ease-in-out) !important;
|
|
624
|
+
}
|
|
625
|
+
/* Preview (file content) bottom sheet. Gated shut by default: the suite
|
|
626
|
+
persists open preview tabs in localStorage and restores them on load,
|
|
627
|
+
which would pop the sheet over the fresh UI. The client only sets the
|
|
628
|
+
frame marker after the user taps a file row in the explorer; the
|
|
629
|
+
suite's own collapse chevron clears it via the visibility watcher. */
|
|
630
|
+
[data-aionui-preview-col] {
|
|
631
|
+
visibility: hidden !important;
|
|
632
|
+
position: fixed !important;
|
|
633
|
+
left: 8px !important;
|
|
634
|
+
right: 8px !important;
|
|
635
|
+
top: auto !important;
|
|
636
|
+
bottom: 40px !important;
|
|
637
|
+
width: auto !important;
|
|
638
|
+
height: min(50dvh, 420px) !important;
|
|
639
|
+
max-height: calc(100dvh - 48px) !important;
|
|
640
|
+
border-radius: 14px !important;
|
|
641
|
+
overflow: hidden !important;
|
|
642
|
+
box-shadow: 0 -4px 28px rgba(0, 0, 0, .18) !important;
|
|
643
|
+
z-index: 56 !important;
|
|
644
|
+
animation: dsh-mobile-nav-sheet-up .24s var(--ds-ease-out, ease-in-out) !important;
|
|
645
|
+
}
|
|
646
|
+
/* User-opened preview sheet (frame marker, set on file-row tap). */
|
|
647
|
+
[data-mobile-nav="frame"][data-aionui-preview-open] [data-aionui-preview-col] {
|
|
648
|
+
visibility: visible !important;
|
|
649
|
+
}
|
|
650
|
+
/* The Files action opens the explorer sheet (frame marker). */
|
|
651
|
+
[data-mobile-nav="frame"][data-aionui-explorer-open] [data-aionui-explorer-col] {
|
|
652
|
+
visibility: visible !important;
|
|
653
|
+
}
|
|
654
|
+
/* The open drawer must never sit under a sheet: while the frame is in the
|
|
655
|
+
narrow-expanded state both sheets yield (later in the file than the
|
|
656
|
+
open marker rule, so it wins at equal specificity). */
|
|
657
|
+
[data-mobile-nav="frame"]:not([data-sidebar-collapsed]) [data-aionui-explorer-col],
|
|
658
|
+
[data-mobile-nav="frame"]:not([data-sidebar-collapsed]) [data-aionui-preview-col] {
|
|
659
|
+
visibility: hidden !important;
|
|
660
|
+
}
|
|
661
|
+
/* The suite's own expand button reads the store state we bypass on
|
|
662
|
+
mobile — hide it; the header Files action is the opener. */
|
|
663
|
+
.aionui-floating-expand {
|
|
664
|
+
display: none !important;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/* dsh-web-ui sidebar entries (task board / ssh) sit flush against each
|
|
668
|
+
other — give the injected rows breathing room. */
|
|
669
|
+
button[data-dsh-taskboard-entry],
|
|
670
|
+
button[data-dsh-ssh-entry] {
|
|
671
|
+
margin-bottom: 8px !important;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/* Task board: five kanban columns at minmax(0,1fr) crush into ~78px phone
|
|
675
|
+
strips. Give every column a usable minimum and let the row scroll. */
|
|
676
|
+
[data-dsh-taskboard-board] > [class$="_columns"] {
|
|
677
|
+
grid-template-columns: repeat(5, minmax(240px, 1fr)) !important;
|
|
678
|
+
overflow-x: auto !important;
|
|
679
|
+
}
|
|
680
|
+
/* The floating button must not float over a takeover panel (task board /
|
|
681
|
+
ssh own the center column while active). */
|
|
682
|
+
html[data-dsh-taskboard-active] [data-mobile-nav="fab"],
|
|
683
|
+
html[data-dsh-ssh-active] [data-mobile-nav="fab"],
|
|
684
|
+
html[data-dsh-taskboard-active] [data-mobile-nav="backdrop"],
|
|
685
|
+
html[data-dsh-ssh-active] [data-mobile-nav="backdrop"] {
|
|
686
|
+
display: none !important;
|
|
687
|
+
}
|
|
688
|
+
/* Board header: let the search field take the slack instead of squeezing
|
|
689
|
+
the action buttons. */
|
|
690
|
+
[data-dsh-taskboard-board] > [class$="_boardHeader"] [class$="_search"] {
|
|
691
|
+
flex: 1 1 auto !important;
|
|
692
|
+
min-width: 80px !important;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/* ---------- dsh-web-ui polish: plugin market search ----------
|
|
696
|
+
The market tab row (Discover / Themes / Installed + the plugin search
|
|
697
|
+
box) is a no-wrap flex: at 390px the tabs plus the ~218px search box
|
|
698
|
+
(~475px total) overflow the ~334px sheet and the search box runs off
|
|
699
|
+
the right edge of the screen (it also forces a horizontal scrollbar on
|
|
700
|
+
the sheet's options area). Let the row wrap: the tabs keep the first
|
|
701
|
+
line and the search box gets its own full-width second line. */
|
|
702
|
+
|
|
703
|
+
[aria-modal="true"] [class$="_tabs"] {
|
|
704
|
+
flex-wrap: wrap !important;
|
|
705
|
+
row-gap: 8px !important;
|
|
706
|
+
}
|
|
707
|
+
[aria-modal="true"] [class$="_searchInline"] {
|
|
708
|
+
flex: 1 1 100% !important;
|
|
709
|
+
width: 100% !important;
|
|
710
|
+
max-width: 100% !important;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/* ---------- dsh-usage-stats polish: usage & balance panel ----------
|
|
714
|
+
The panel's stats row shows three token counters side by side
|
|
715
|
+
(today / month / total). The counters use tabular nowrap figures whose
|
|
716
|
+
min-content width overflows the ~336px panel body on a phone: figures
|
|
717
|
+
clip at the row's edges and the panel grows a horizontal scrollbar.
|
|
718
|
+
Stack the three counters vertically — full-width rows, so the figures
|
|
719
|
+
always fit. */
|
|
720
|
+
|
|
721
|
+
[class*="usg_"][class$="_statsRow"] {
|
|
722
|
+
flex-direction: column !important;
|
|
723
|
+
}
|
|
724
|
+
[class*="usg_"][class$="_stat"] {
|
|
725
|
+
flex: 0 0 auto !important;
|
|
726
|
+
width: 100% !important;
|
|
727
|
+
min-width: 0 !important;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/* ---------- dsh-web-ui polish: settings sheet ----------
|
|
731
|
+
The official dialog is a desktop two-column form; on a phone the
|
|
732
|
+
label/control split leaves a huge dead gap and long descriptions wrap
|
|
733
|
+
into tall stacks. Stack each row (text above, control full-width) and
|
|
734
|
+
compact the nav tabs into an even wrap. */
|
|
735
|
+
|
|
736
|
+
/* Nav tabs: a stable 3-per-row grid (two clean rows instead of a ragged
|
|
737
|
+
wrap) with tighter cells. */
|
|
738
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :first-child > :last-child {
|
|
739
|
+
display: grid !important;
|
|
740
|
+
grid-template-columns: repeat(3, 1fr) !important;
|
|
741
|
+
gap: 6px !important;
|
|
742
|
+
}
|
|
743
|
+
[aria-modal="true"] [class$="_navCell"] {
|
|
744
|
+
padding: 6px 8px !important;
|
|
745
|
+
gap: 6px !important;
|
|
746
|
+
font-size: 13px !important;
|
|
747
|
+
justify-content: flex-start !important;
|
|
748
|
+
}
|
|
749
|
+
[aria-modal="true"] [class$="_navCell"] svg {
|
|
750
|
+
width: 14px !important;
|
|
751
|
+
height: 14px !important;
|
|
752
|
+
flex: none !important;
|
|
753
|
+
}
|
|
754
|
+
/* Setting rows: text on top, control below at full width. */
|
|
755
|
+
[aria-modal="true"] [class$="_section"] [class$="_row"] {
|
|
756
|
+
flex-direction: column !important;
|
|
757
|
+
align-items: stretch !important;
|
|
758
|
+
gap: 8px !important;
|
|
759
|
+
}
|
|
760
|
+
[aria-modal="true"] [class$="_section"] [class$="_row"] > :first-child {
|
|
761
|
+
width: 100% !important;
|
|
762
|
+
max-width: none !important;
|
|
763
|
+
}
|
|
764
|
+
[aria-modal="true"] [class$="_section"] [class$="_row"] > :last-child {
|
|
765
|
+
width: 100% !important;
|
|
766
|
+
max-width: none !important;
|
|
767
|
+
}
|
|
768
|
+
/* Appearance mode group: give the cube row a consistent bordered
|
|
769
|
+
segmented look (the official borders differ per state). */
|
|
770
|
+
[aria-modal="true"] [class$="_cubeRow"] > * {
|
|
771
|
+
border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12)) !important;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/* ---------- dsh-web-ui polish: explorer sheet ----------
|
|
775
|
+
The aionui explorer was designed for a desktop side column: compact the
|
|
776
|
+
header, search box and tree rows so a phone shows more entries, and pad
|
|
777
|
+
the scroll bottom so the last row never sits flush on the edge. */
|
|
778
|
+
|
|
779
|
+
[data-aionui-explorer-col] [class$="_tabBar"] {
|
|
780
|
+
height: 36px !important;
|
|
781
|
+
}
|
|
782
|
+
[data-aionui-explorer-col] [class$="_tabBtn"],
|
|
783
|
+
[data-aionui-explorer-col] [class$="_tabBtnActive"] {
|
|
784
|
+
padding: 0 12px !important;
|
|
785
|
+
font-size: 13px !important;
|
|
786
|
+
}
|
|
787
|
+
[data-aionui-explorer-col] [class$="_searchBox"] {
|
|
788
|
+
height: 32px !important;
|
|
789
|
+
font-size: 13px !important;
|
|
790
|
+
}
|
|
791
|
+
[data-aionui-explorer-col] [class$="_treeRow"] {
|
|
792
|
+
height: 30px !important;
|
|
793
|
+
font-size: 13px !important;
|
|
794
|
+
}
|
|
795
|
+
[data-aionui-explorer-col] [class$="_treeRow"] svg {
|
|
796
|
+
width: 14px !important;
|
|
797
|
+
height: 14px !important;
|
|
798
|
+
}
|
|
799
|
+
[data-aionui-explorer-col] [class$="_scrollArea"] {
|
|
800
|
+
padding-bottom: 28px !important;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/* ---------- dsh-web-ui polish: drawer footer ----------
|
|
804
|
+
The injected footer actions (Files + Session log) become two equal pill
|
|
805
|
+
buttons instead of text-width capsules. */
|
|
806
|
+
|
|
807
|
+
/* The official footerActions row also hosts the remote-web-ui entry
|
|
808
|
+
row (two icon buttons); without wrapping the two groups squeeze each
|
|
809
|
+
other on one line. Wrap so each group gets its own full-width row. */
|
|
810
|
+
[data-mobile-nav="frame"] [class$="_footerActions"] {
|
|
811
|
+
flex-wrap: wrap !important;
|
|
812
|
+
gap: 6px !important;
|
|
813
|
+
}
|
|
814
|
+
[data-mobile-nav="drawer-actions"] {
|
|
815
|
+
width: 100% !important;
|
|
816
|
+
}
|
|
817
|
+
[data-mobile-nav="drawer-actions"] > button {
|
|
818
|
+
flex: 1 1 0 !important;
|
|
819
|
+
padding: 0 8px !important;
|
|
820
|
+
white-space: nowrap !important;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/* ---------- dsh-web-ui polish: floating pet ----------
|
|
824
|
+
The whale-girl pet (dsh-pet) floats at the viewport corner with a
|
|
825
|
+
persisted, draggable position. On phones the pet is scaled down so
|
|
826
|
+
it does not dominate the screen; the plugin's own drag + persist
|
|
827
|
+
still work (the position itself is left alone — the mobile default
|
|
828
|
+
position is seeded via the pet API to just above the composer). */
|
|
829
|
+
|
|
830
|
+
body > [class$="_float"]:has([class$="_sprite"][role="button"]) {
|
|
831
|
+
transform: scale(.66);
|
|
832
|
+
transform-origin: bottom right;
|
|
833
|
+
}
|
|
834
|
+
/* While a modal dialog (settings sheet / export) owns the screen the pet
|
|
835
|
+
floats ABOVE it and covers the dialog content; modal semantics say the
|
|
836
|
+
background is inert, so hide the pet for the modal's lifetime. */
|
|
837
|
+
body:has([aria-modal="true"]) > [class$="_float"]:has([class$="_sprite"][role="button"]) {
|
|
838
|
+
display: none !important;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/* ---------- dsh-web-ui polish: conversation stats line ----------
|
|
842
|
+
The official session-status row (turns / steps / LLM time / TTFT /
|
|
843
|
+
cache) is long. The client marks the exact row with
|
|
844
|
+
[data-mobile-nav="stats"] (text-anchored, hashed classes can't be
|
|
845
|
+
targeted). Layout: ONE fixed-height (28px) flex strip that scrolls
|
|
846
|
+
horizontally — the full metrics stream stays reachable by swiping,
|
|
847
|
+
the row never grows vertically, no ellipsis or fade, 12px gaps
|
|
848
|
+
between metric groups, a 2px scrollbar as the swipe affordance. */
|
|
849
|
+
|
|
850
|
+
[data-mobile-nav="stats"] {
|
|
851
|
+
display: flex !important;
|
|
852
|
+
flex-flow: row nowrap !important;
|
|
853
|
+
align-items: center !important;
|
|
854
|
+
width: 100% !important;
|
|
855
|
+
max-width: 100% !important;
|
|
856
|
+
min-width: 0 !important;
|
|
857
|
+
height: 28px !important;
|
|
858
|
+
min-height: 28px !important;
|
|
859
|
+
max-height: 28px !important;
|
|
860
|
+
box-sizing: border-box !important;
|
|
861
|
+
white-space: nowrap !important;
|
|
862
|
+
overflow-x: auto !important;
|
|
863
|
+
overflow-y: hidden !important;
|
|
864
|
+
-webkit-overflow-scrolling: touch;
|
|
865
|
+
overscroll-behavior-x: contain;
|
|
866
|
+
scrollbar-width: thin !important;
|
|
867
|
+
scrollbar-color: var(--dsw-alias-border-l1, rgba(0, 0, 0, .28)) transparent !important;
|
|
868
|
+
padding: 0 0 4px !important;
|
|
869
|
+
line-height: 20px !important;
|
|
870
|
+
font-size: 12px !important;
|
|
871
|
+
}
|
|
872
|
+
[data-mobile-nav="stats"]::-webkit-scrollbar {
|
|
873
|
+
height: 2px !important;
|
|
874
|
+
}
|
|
875
|
+
[data-mobile-nav="stats"]::-webkit-scrollbar-thumb {
|
|
876
|
+
background: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .3)) !important;
|
|
877
|
+
border-radius: 2px !important;
|
|
878
|
+
}
|
|
879
|
+
[data-mobile-nav="stats"]::-webkit-scrollbar-track {
|
|
880
|
+
background: transparent !important;
|
|
881
|
+
}
|
|
882
|
+
[data-mobile-nav="stats"] > * {
|
|
883
|
+
display: flex !important;
|
|
884
|
+
flex: 0 0 auto !important;
|
|
885
|
+
flex-flow: row nowrap !important;
|
|
886
|
+
align-items: center !important;
|
|
887
|
+
width: max-content !important;
|
|
888
|
+
min-width: max-content !important;
|
|
889
|
+
max-width: none !important;
|
|
890
|
+
white-space: nowrap !important;
|
|
891
|
+
margin-right: 12px !important;
|
|
892
|
+
padding: 0 !important;
|
|
893
|
+
}
|
|
894
|
+
[data-mobile-nav="stats"] > *:last-child {
|
|
895
|
+
margin-right: 0 !important;
|
|
896
|
+
}
|
|
897
|
+
[data-mobile-nav="stats"] * {
|
|
898
|
+
white-space: nowrap !important;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/* ---------- hero composer on mobile ----------
|
|
902
|
+
The official hero card carries a 2-line textarea plus a tall tool row,
|
|
903
|
+
which reads oversized on a phone. Tighten the empty-state rhythm: keep
|
|
904
|
+
the official centered hero, shrink the textarea line box, slim the card
|
|
905
|
+
padding and the tool row, and close the gap under the headline. */
|
|
906
|
+
|
|
907
|
+
[data-phase="hero"] [class$="_card"]:has(textarea) {
|
|
908
|
+
padding-top: 6px !important;
|
|
909
|
+
gap: 8px !important;
|
|
910
|
+
}
|
|
911
|
+
/* The official composer autosizes the textarea and writes an inline
|
|
912
|
+
height (2 lines on the hero empty state) on the textarea's scroll/grow
|
|
913
|
+
wrappers. :placeholder-shown lets us collapse the EMPTY state to one
|
|
914
|
+
line with !important; as soon as the user types, the pseudo-class no
|
|
915
|
+
longer matches and the autosizer's inline height takes over again — so
|
|
916
|
+
multi-line growth keeps working. */
|
|
917
|
+
[data-phase="hero"] textarea:placeholder-shown {
|
|
918
|
+
height: 28px !important;
|
|
919
|
+
}
|
|
920
|
+
[data-phase="hero"] [class$="_card"]:has(textarea:placeholder-shown) > [class$="_scroll"],
|
|
921
|
+
[data-phase="hero"] [class$="_card"]:has(textarea:placeholder-shown) [class$="_grow"] {
|
|
922
|
+
height: 28px !important;
|
|
923
|
+
}
|
|
924
|
+
[data-phase="hero"] [class$="_card"]:has(textarea) > [class$="_row"] {
|
|
925
|
+
padding-top: 2px !important;
|
|
926
|
+
}
|
|
927
|
+
[data-phase="hero"] [class$="_headline"] {
|
|
928
|
+
line-height: 1.15 !important;
|
|
929
|
+
margin-bottom: 0 !important;
|
|
930
|
+
}
|
|
931
|
+
[data-phase="hero"] [class$="_stack"] {
|
|
932
|
+
gap: 0 !important;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/* ---------- 隐藏「添加工作区」入口(手机上配工作区无意义,issue #17 修正) ----------
|
|
936
|
+
图标按钮的 aria-label 随语言变化(zh「添加工作区」/ en「Add workspace」),
|
|
937
|
+
两种都覆盖;下拉菜单里的「添加工作区…」项由 fileGuard.ts 的 MutationObserver
|
|
938
|
+
按文案兜底隐藏(CSS 选不到纯文本节点)。只在窄屏生效——桌面端照常保留。 */
|
|
939
|
+
button[aria-label="添加工作区"],
|
|
940
|
+
button[aria-label="添加工作区…"],
|
|
941
|
+
button[aria-label="Add workspace"],
|
|
942
|
+
button[aria-label="Add workspace…"] {
|
|
943
|
+
display: none !important;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/* ---------- 文件链接旁的「复制」按钮(issue #17:复制文件内容) ----------
|
|
947
|
+
挂在对话里的文件链接(<button>/<a>,文案即路径)紧邻位置,由 fileGuard.ts
|
|
948
|
+
注入。只屏内可见:桌面端不注入、不显示;这里再兜底一层,避免任何遗漏。
|
|
949
|
+
文件链接多为 inline,按钮用 inline-flex 紧跟其后即可。 */
|
|
950
|
+
[data-mobile-nav="copy-file"] {
|
|
951
|
+
display: inline-flex !important;
|
|
952
|
+
align-items: center;
|
|
953
|
+
justify-content: center;
|
|
954
|
+
margin-left: 6px !important;
|
|
955
|
+
vertical-align: baseline !important;
|
|
956
|
+
height: 22px !important;
|
|
957
|
+
padding: 0 8px !important;
|
|
958
|
+
border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .14)) !important;
|
|
959
|
+
border-radius: 6px !important;
|
|
960
|
+
background: var(--dsw-alias-bg-layer-1, #fff) !important;
|
|
961
|
+
color: var(--dsw-alias-label-primary, inherit) !important;
|
|
962
|
+
font-family: inherit !important;
|
|
963
|
+
font-size: 11px !important;
|
|
964
|
+
line-height: 1 !important;
|
|
965
|
+
cursor: pointer !important;
|
|
966
|
+
-webkit-tap-highlight-color: transparent !important;
|
|
967
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, .12) !important;
|
|
968
|
+
}
|
|
969
|
+
[data-mobile-nav="copy-file"]:active {
|
|
970
|
+
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06)) !important;
|
|
971
|
+
}
|
|
972
|
+
[data-mobile-nav="copy-file"][disabled] {
|
|
973
|
+
opacity: .55 !important;
|
|
974
|
+
cursor: default !important;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/* ---------- mobile: stop iOS Safari forced zoom on input focus ----------
|
|
979
|
+
* Inputs are rendered with inline fontSize 13-14px, below the 16px threshold
|
|
980
|
+
* that makes iOS Safari zoom the whole page on focus (and never recover).
|
|
981
|
+
* Force the safe 16px minimum on narrow viewports only, so desktop keeps its
|
|
982
|
+
* tighter metrics. !important is required to beat the inline styles. */
|
|
983
|
+
@media (max-width: 1024px) {
|
|
984
|
+
input,
|
|
985
|
+
textarea,
|
|
986
|
+
[contenteditable="true"] {
|
|
987
|
+
font-size: 16px !important;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/* ---------- desktop: the mobile controls must never appear ---------- */
|
|
992
|
+
|
|
993
|
+
@media (min-width: 1024px) {
|
|
994
|
+
[data-mobile-nav="toggle"],
|
|
995
|
+
[data-mobile-nav="files"],
|
|
996
|
+
[data-mobile-nav="fab"],
|
|
997
|
+
[data-mobile-nav="backdrop"],
|
|
998
|
+
[data-mobile-nav="session-log"],
|
|
999
|
+
[data-mobile-nav="explorer"],
|
|
1000
|
+
[data-mobile-nav="drawer-actions"] {
|
|
1001
|
+
display: none !important;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
`
|