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,504 @@
|
|
|
1
|
+
// composer — phone-only composer reflow (S3, 2026-08-17).
|
|
2
|
+
//
|
|
3
|
+
// Appended after home/header so its < 768px rules win the ties against the
|
|
4
|
+
// shared <= 1023px block in layout/compat/misc (which keeps the v1.0.0
|
|
5
|
+
// composer treatment alive for the 768-1023px tablet range).
|
|
6
|
+
//
|
|
7
|
+
// Official structure this file reshapes (verified live at 390px, see the S3
|
|
8
|
+
// report): InputBar renders
|
|
9
|
+
// _card > _row > [_tools > (_add, _modes > PermissionSelect, input.left)]
|
|
10
|
+
// [_trailing > (input.right, input.model, ContextMeter, _primary)]
|
|
11
|
+
// with `_tools` / `_trailing` as inner flex groups. Flattening both to
|
|
12
|
+
// `display: contents` turns every control into a direct flex item of `_row`,
|
|
13
|
+
// which is what makes a pure `order` reflow possible without touching a
|
|
14
|
+
// single official component.
|
|
15
|
+
|
|
16
|
+
/** The composer input card's bottom control row (the only `_row` that is a direct card child — the ContextMeter panel also has `_row` descendants). */
|
|
17
|
+
const ROW = '[data-slot="conversation.composer.bar"] [class$="_card"] > [class$="_row"]'
|
|
18
|
+
/** The model seat's own root inside the trailing group (long form so it beats the <=1023px pill rules in layout.css.ts). */
|
|
19
|
+
const MODEL = `${ROW} > [class$="_trailing"] > [data-slot="conversation.input.model"] > [class$="_root"]`
|
|
20
|
+
/** The permission select's trigger button (structure only — the PermissionSelect hashed classes are never named). */
|
|
21
|
+
const PERM = `${ROW} > [class$="_tools"] > [class$="_modes"]`
|
|
22
|
+
|
|
23
|
+
export const COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- */
|
|
24
|
+
|
|
25
|
+
/* The file picker the attachment button drives (S7) — hidden at EVERY width,
|
|
26
|
+
deliberately OUTSIDE the phone media block below. A bare <input type="file">
|
|
27
|
+
renders as a native "Choose Files" control, and the slot wrapper around it is
|
|
28
|
+
\`display: contents\`, so an un-hidden one becomes a flex item of the official
|
|
29
|
+
tool row. Scoping this to < 768px once put that control in the DESKTOP
|
|
30
|
+
composer (caught on a live deploy, 2026-08-17): the button that drives it is
|
|
31
|
+
phone-only, but the input it drives is in the DOM at all widths. */
|
|
32
|
+
[data-mobile-nav="attach-picker"] {
|
|
33
|
+
display: none !important;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@media (max-width: 767px) {
|
|
37
|
+
/* --- 1. flatten the two official groups ---
|
|
38
|
+
\`_tools\` and \`_trailing\` only exist to cluster controls left/right.
|
|
39
|
+
display:contents dissolves them into \`_row\`, so the six leaf controls
|
|
40
|
+
become siblings in one flex line and \`order\` alone drives the layout:
|
|
41
|
+
[attach · + · permission · model] …elastic gap… [context ring · send]. */
|
|
42
|
+
${ROW} {
|
|
43
|
+
justify-content: flex-start !important;
|
|
44
|
+
gap: 5px !important;
|
|
45
|
+
padding: 2px 8px 8px !important;
|
|
46
|
+
}
|
|
47
|
+
${ROW} > [class$="_tools"],
|
|
48
|
+
${ROW} > [class$="_trailing"] {
|
|
49
|
+
display: contents !important;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* --- 2. the running order ---
|
|
53
|
+
The attachment button (S3 placeholder, S7 wires it up) sits leftmost;
|
|
54
|
+
the official "+" command menu keeps its seat right next to it, where
|
|
55
|
+
the two "insert something" affordances read as one cluster. */
|
|
56
|
+
${ROW} [data-mobile-nav="attach"] {
|
|
57
|
+
order: 1 !important;
|
|
58
|
+
}
|
|
59
|
+
${ROW} > [class$="_tools"] > [class$="_add"] {
|
|
60
|
+
order: 2 !important;
|
|
61
|
+
}
|
|
62
|
+
${ROW} > [class$="_tools"] > [class$="_modes"] {
|
|
63
|
+
order: 3 !important;
|
|
64
|
+
flex: 0 1 auto !important;
|
|
65
|
+
min-width: 0 !important;
|
|
66
|
+
gap: 6px !important;
|
|
67
|
+
}
|
|
68
|
+
/* The model seat carries the elastic gap: everything after it is pushed to
|
|
69
|
+
the right edge, so no spacer element is needed. */
|
|
70
|
+
${MODEL} {
|
|
71
|
+
order: 4 !important;
|
|
72
|
+
flex: 0 1 auto !important;
|
|
73
|
+
min-width: 0 !important;
|
|
74
|
+
margin-right: auto !important;
|
|
75
|
+
}
|
|
76
|
+
/* Third-party input.right entries park next to the ring rather than
|
|
77
|
+
landing at order 0 (= far left) once the groups are flattened. */
|
|
78
|
+
${ROW} > [class$="_trailing"] > [data-slot="conversation.input.right"] > * {
|
|
79
|
+
order: 5 !important;
|
|
80
|
+
}
|
|
81
|
+
/* ContextMeter — the only \`span\` ending in _root inside the row. */
|
|
82
|
+
${ROW} > [class$="_trailing"] > span[class$="_root"] {
|
|
83
|
+
order: 6 !important;
|
|
84
|
+
flex: none !important;
|
|
85
|
+
}
|
|
86
|
+
${ROW} > [class$="_trailing"] > [class$="_primary"] {
|
|
87
|
+
order: 7 !important;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* --- 3. permission + model as icon-only pills (real-device round 2, 2026-08-17) ---
|
|
91
|
+
S3's icon-and-label capsules read as noise on an actual phone — there
|
|
92
|
+
is no room to usefully show a preset name or a model id, so the label
|
|
93
|
+
text is now hidden outright and both triggers collapse to a plain
|
|
94
|
+
~44x30 icon button. This AGREES with (rather than fights) the official
|
|
95
|
+
container query (\`@container (width <= 460px) { .trigger:has(.triggerIcon)
|
|
96
|
+
.triggerLabel { display: none } }\`) that S3 had to override — no need
|
|
97
|
+
to override it back. Accessible name is unaffected: both official
|
|
98
|
+
triggers already ship a descriptive aria-label independent of the
|
|
99
|
+
visible text (PermissionSelect: t('input.accessMode', {name}); the
|
|
100
|
+
model trigger: t('trigger.aria'/'trigger.ariaEffort')) — verified live
|
|
101
|
+
in dsh-client-ui-conversation / dsh-client-ui-model-selection lib/
|
|
102
|
+
client.js, 2026-08-17 — so hiding the label costs nothing for screen
|
|
103
|
+
readers. The permission trigger only renders an icon for the
|
|
104
|
+
"read-only" / "workspace-write" presets (permissionGlyphs in
|
|
105
|
+
dsh-client-ui-conversation) — "Full access" and any host-configured
|
|
106
|
+
preset name fall back to chevron-only, a known gap in the official
|
|
107
|
+
markup this plugin cannot fill without inventing new icon meaning.
|
|
108
|
+
The model trigger never renders an icon at all (only label + optional
|
|
109
|
+
effort text + chevron), so its ::before below draws one from a
|
|
110
|
+
primitives icon path (Sparkle — the closest existing "model" glyph) as
|
|
111
|
+
a CSS-only pseudo-element: it survives React re-renders for free
|
|
112
|
+
(unlike a DOM-injected node, which would need a MutationObserver, see
|
|
113
|
+
the preview-full-toggle pitfall in AGENTS.md) and does not touch
|
|
114
|
+
accessible-name computation (empty generated content). S3's
|
|
115
|
+
rtl-ellipsis trick on the model label is simply inert under
|
|
116
|
+
display:none now; left alone rather than unpicked. */
|
|
117
|
+
${PERM} [class$="_triggerLabel"],
|
|
118
|
+
${MODEL} > [class$="_trigger"] > [class$="_triggerLabel"],
|
|
119
|
+
${MODEL} > [class$="_trigger"] > [class$="_triggerEffort"] {
|
|
120
|
+
display: none !important;
|
|
121
|
+
}
|
|
122
|
+
${PERM} button[class$="_trigger"],
|
|
123
|
+
${MODEL} > [class$="_trigger"] {
|
|
124
|
+
background: var(--dsw-specific-selector, rgba(127, 127, 127, .12)) !important;
|
|
125
|
+
width: 44px !important;
|
|
126
|
+
height: 30px !important;
|
|
127
|
+
max-width: none !important;
|
|
128
|
+
min-width: 0 !important;
|
|
129
|
+
padding: 0 !important;
|
|
130
|
+
gap: 2px !important;
|
|
131
|
+
justify-content: center !important;
|
|
132
|
+
border-radius: 999px !important;
|
|
133
|
+
touch-action: manipulation !important;
|
|
134
|
+
}
|
|
135
|
+
/* PermissionSelect wraps its trigger in the Menu primitive's root span,
|
|
136
|
+
which must shrink to the icon button's fixed width. */
|
|
137
|
+
${PERM} > span:has(> button[class$="_trigger"]) {
|
|
138
|
+
flex: 0 0 auto !important;
|
|
139
|
+
min-width: 0 !important;
|
|
140
|
+
}
|
|
141
|
+
/* ic_ds_sparkle_16 (@deepseek-ai/dsh-client-ui-primitives IconSparkle16
|
|
142
|
+
path, copied verbatim) as a mask so it inherits currentColor like every
|
|
143
|
+
other icon in the row — the model trigger has no official icon slot to
|
|
144
|
+
hook into. */
|
|
145
|
+
${MODEL} > [class$="_trigger"]::before {
|
|
146
|
+
content: '';
|
|
147
|
+
width: 16px;
|
|
148
|
+
height: 16px;
|
|
149
|
+
flex: none;
|
|
150
|
+
background: currentColor;
|
|
151
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6.1 3.1Q6.6 7.8 11.3 8.3Q6.6 8.8 6.1 13.5Q5.6 8.8 0.9 8.3Q5.6 7.8 6.1 3.1Z'/%3E%3Cpath d='M11.9 1Q12.2 3.7 14.9 4Q12.2 4.3 11.9 7Q11.6 4.3 8.9 4Q11.6 3.7 11.9 1Z'/%3E%3C/svg%3E");
|
|
152
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6.1 3.1Q6.6 7.8 11.3 8.3Q6.6 8.8 6.1 13.5Q5.6 8.8 0.9 8.3Q5.6 7.8 6.1 3.1Z'/%3E%3Cpath d='M11.9 1Q12.2 3.7 14.9 4Q12.2 4.3 11.9 7Q11.6 4.3 8.9 4Q11.6 3.7 11.9 1Z'/%3E%3C/svg%3E");
|
|
153
|
+
-webkit-mask-size: contain;
|
|
154
|
+
mask-size: contain;
|
|
155
|
+
-webkit-mask-repeat: no-repeat;
|
|
156
|
+
mask-repeat: no-repeat;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* --- 4. both menus become bottom sheets ---
|
|
160
|
+
The permission menu is the Menu primitive (role=menu, absolute, side=top)
|
|
161
|
+
and the model menu is ModelSelect's own \`_menu\` (absolute, bottom+right).
|
|
162
|
+
Neither has a transformed ancestor between it and the viewport, so
|
|
163
|
+
position:fixed re-anchors both to the screen edge. Only the shell moves —
|
|
164
|
+
the items, the two-level model panes and every selection handler stay
|
|
165
|
+
official. */
|
|
166
|
+
${PERM} [role="menu"],
|
|
167
|
+
${MODEL} > [class$="_menu"] {
|
|
168
|
+
position: fixed !important;
|
|
169
|
+
left: 0 !important;
|
|
170
|
+
right: 0 !important;
|
|
171
|
+
bottom: 0 !important;
|
|
172
|
+
top: auto !important;
|
|
173
|
+
transform: none !important;
|
|
174
|
+
width: 100% !important;
|
|
175
|
+
min-width: 0 !important;
|
|
176
|
+
max-width: none !important;
|
|
177
|
+
max-height: min(70dvh, 520px) !important;
|
|
178
|
+
box-sizing: border-box !important;
|
|
179
|
+
border-radius: 16px 16px 0 0 !important;
|
|
180
|
+
border-bottom: none !important;
|
|
181
|
+
padding: 8px 8px calc(8px + var(--mnav-sab)) !important;
|
|
182
|
+
z-index: 60 !important;
|
|
183
|
+
box-shadow: 0 -8px 32px rgba(0, 0, 0, .18) !important;
|
|
184
|
+
}
|
|
185
|
+
/* 44pt+ rows in both sheets (Menu items, model options, and the model
|
|
186
|
+
sheet's two root cells that drill into the model / effort panes). */
|
|
187
|
+
${PERM} [role="menu"] [role="menuitem"],
|
|
188
|
+
${MODEL} > [class$="_menu"] [class$="_option"],
|
|
189
|
+
${MODEL} > [class$="_menu"] [class$="_cell"] {
|
|
190
|
+
min-height: 48px !important;
|
|
191
|
+
border-radius: 12px !important;
|
|
192
|
+
font-size: 15px !important;
|
|
193
|
+
}
|
|
194
|
+
/* --- 4b. the official scroll-to-bottom button must not poke through the
|
|
195
|
+
sheet (real-device follow-up, 2026-08-17) ---
|
|
196
|
+
ChatView's own "jump to latest" button (aria-label t("chat.toBottom"))
|
|
197
|
+
is \`position: sticky; z-index: 8\` inside the message column, not
|
|
198
|
+
\`position: fixed\` — it never escapes to the same top-level stacking
|
|
199
|
+
context our sheets get promoted to, so raising the sheet's z-index
|
|
200
|
+
further does nothing (measured live: it still rendered on top at
|
|
201
|
+
z-index 60 vs 8). Rather than chase engine-specific stacking-context
|
|
202
|
+
semantics (Chromium and WebKit do not always agree here — see AGENTS.md
|
|
203
|
+
CDP/document.hidden lesson for another instance of that), this hides
|
|
204
|
+
the button outright while either sheet is open and lets it reappear on
|
|
205
|
+
close: a plain \`display: none\` behind a live \`:has()\` read is correct
|
|
206
|
+
regardless of which stacking rules the engine happens to apply.
|
|
207
|
+
Selector: no hashed classes (dsh-client-ui-conversation lib/client.js,
|
|
208
|
+
verified 2026-08-17) — \`data-chat-flow\` is the one stable attribute on
|
|
209
|
+
the message column, and the button's wrapper is its only sibling
|
|
210
|
+
(ChatView only ever renders the column and, conditionally, this one
|
|
211
|
+
slot), so the adjacent-sibling combinator pins it precisely. */
|
|
212
|
+
body:has(${PERM} [role="menu"]) [data-chat-flow] + div,
|
|
213
|
+
body:has(${MODEL} > [class$="_menu"]) [data-chat-flow] + div {
|
|
214
|
+
display: none !important;
|
|
215
|
+
}
|
|
216
|
+
/* --- 5. input box: two lines minimum, five lines maximum ---
|
|
217
|
+
The official autosizer drives the box off the hidden mirror's height, so
|
|
218
|
+
a min-height on the mirror IS the min-height of the field; the scroll cap
|
|
219
|
+
rides the official custom property. 52px = 2 x 24px line box + 4px pad,
|
|
220
|
+
124px = 5 lines. The hero card keeps its own one-line collapse (misc.css
|
|
221
|
+
pins _scroll/_grow while the placeholder shows). */
|
|
222
|
+
[data-slot="conversation.composer.bar"] [class$="_card"] [class$="_mirror"] {
|
|
223
|
+
min-height: 52px !important;
|
|
224
|
+
}
|
|
225
|
+
[class$="_composerSeat"] {
|
|
226
|
+
--dsh-composer-text-max-height: 124px;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* --- 6. no divider above OR below the message list ---
|
|
230
|
+
Instead of a rule the messages butt against, the message scroller
|
|
231
|
+
fades out over its last 26px (S3, no divider above the composer) AND
|
|
232
|
+
fades in over its first 20px (S3.1 real-device round 2: the header's
|
|
233
|
+
own bottom line is removed too, styles/header.css.ts's
|
|
234
|
+
\`header::after\` rule). The mask lives on the scroll body (NOT on the
|
|
235
|
+
header or the composer): a mask clips everything it paints, and both
|
|
236
|
+
surfaces host position:fixed children (composer's permission/model
|
|
237
|
+
sheets, any future header overlay) that must not be clipped.
|
|
238
|
+
\`mask-image\` can only be declared once per element, so both fades are
|
|
239
|
+
ONE linear-gradient rather than two separate declarations (the second
|
|
240
|
+
would silently replace the first) — this is the merge of what used to
|
|
241
|
+
be S3's bottom-only mask. */
|
|
242
|
+
[class$="_scrollBody"] {
|
|
243
|
+
-webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 20px, #000 calc(100% - 26px), transparent 100%);
|
|
244
|
+
mask-image: linear-gradient(to bottom, transparent 0, #000 20px, #000 calc(100% - 26px), transparent 100%);
|
|
245
|
+
}
|
|
246
|
+
[class$="_composerSeat"],
|
|
247
|
+
[class$="_composerStack"] {
|
|
248
|
+
border-top: none !important;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* --- 7. dock entries become mini chips above the input card ---
|
|
252
|
+
conversation.input.dock is display:contents (inline style), so its
|
|
253
|
+
entries are stacked rows of the composer column. Forcing the slot itself
|
|
254
|
+
to flex turns them into ONE scrollable chip line outside and above the
|
|
255
|
+
card. Written against the slot, not against any particular plugin's chip
|
|
256
|
+
(the git branch chip and the todo panel are third-party and may not be
|
|
257
|
+
installed at all). */
|
|
258
|
+
[data-slot="conversation.input.dock"] {
|
|
259
|
+
display: flex !important;
|
|
260
|
+
flex-flow: row nowrap !important;
|
|
261
|
+
align-items: center !important;
|
|
262
|
+
gap: 6px !important;
|
|
263
|
+
min-width: 0 !important;
|
|
264
|
+
margin: 0 16px !important;
|
|
265
|
+
overflow-x: auto !important;
|
|
266
|
+
overflow-y: hidden !important;
|
|
267
|
+
scrollbar-width: none !important;
|
|
268
|
+
}
|
|
269
|
+
[data-slot="conversation.input.dock"]::-webkit-scrollbar {
|
|
270
|
+
display: none !important;
|
|
271
|
+
}
|
|
272
|
+
[data-slot="conversation.input.dock"] > * {
|
|
273
|
+
flex: 0 0 auto !important;
|
|
274
|
+
max-width: 70% !important;
|
|
275
|
+
min-height: 26px !important;
|
|
276
|
+
max-height: 26px !important;
|
|
277
|
+
border-radius: 999px !important;
|
|
278
|
+
font-size: 11.5px !important;
|
|
279
|
+
line-height: 18px !important;
|
|
280
|
+
overflow: hidden !important;
|
|
281
|
+
}
|
|
282
|
+
/* The git-graph chip carries a 34px tablet target from misc.css; that
|
|
283
|
+
selector is more specific, so restate it at the phone breakpoint. */
|
|
284
|
+
[data-slot="conversation.input.dock"] [data-gitgraph-chip-anchor] [data-gitgraph-chip] {
|
|
285
|
+
min-height: 26px !important;
|
|
286
|
+
padding: 0 10px !important;
|
|
287
|
+
font-size: 11.5px !important;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* --- 7b. attachment chips (S7.1) ---
|
|
291
|
+
Our own dock entry opts OUT of the 26px pill cage above: an image chip is
|
|
292
|
+
a 48px tile. The row sits tight under the card's top edge, so the chips
|
|
293
|
+
read as part of the composer rather than as a floating strip. */
|
|
294
|
+
[data-slot="conversation.input.dock"] > [data-mobile-nav="attach-chips"] {
|
|
295
|
+
display: flex !important;
|
|
296
|
+
align-items: center !important;
|
|
297
|
+
gap: 6px !important;
|
|
298
|
+
max-width: none !important;
|
|
299
|
+
min-height: 0 !important;
|
|
300
|
+
max-height: none !important;
|
|
301
|
+
border-radius: 0 !important;
|
|
302
|
+
overflow: visible !important;
|
|
303
|
+
margin-bottom: -2px;
|
|
304
|
+
}
|
|
305
|
+
[data-mobile-nav="attach-chip"] {
|
|
306
|
+
position: relative;
|
|
307
|
+
display: inline-flex;
|
|
308
|
+
align-items: center;
|
|
309
|
+
gap: 6px;
|
|
310
|
+
flex: 0 0 auto;
|
|
311
|
+
max-width: 62vw;
|
|
312
|
+
background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12));
|
|
313
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
314
|
+
}
|
|
315
|
+
[data-mobile-nav="attach-chip"][data-kind="file"] {
|
|
316
|
+
height: 26px;
|
|
317
|
+
padding: 0 4px 0 8px;
|
|
318
|
+
border-radius: 999px;
|
|
319
|
+
font-size: 11.5px;
|
|
320
|
+
line-height: 18px;
|
|
321
|
+
}
|
|
322
|
+
/* An image chip is the thumbnail: no name line, the filename lives in the
|
|
323
|
+
title attribute (and in the draft text right below). */
|
|
324
|
+
[data-mobile-nav="attach-chip"][data-kind="image"] {
|
|
325
|
+
width: 48px;
|
|
326
|
+
height: 48px;
|
|
327
|
+
padding: 0;
|
|
328
|
+
border-radius: 10px;
|
|
329
|
+
overflow: hidden;
|
|
330
|
+
}
|
|
331
|
+
[data-mobile-nav="attach-chip-art"] {
|
|
332
|
+
position: relative;
|
|
333
|
+
display: grid;
|
|
334
|
+
place-items: center;
|
|
335
|
+
flex: none;
|
|
336
|
+
overflow: hidden;
|
|
337
|
+
}
|
|
338
|
+
[data-kind="file"] > [data-mobile-nav="attach-chip-art"] {
|
|
339
|
+
width: 16px;
|
|
340
|
+
height: 16px;
|
|
341
|
+
opacity: .7;
|
|
342
|
+
}
|
|
343
|
+
[data-kind="image"] > [data-mobile-nav="attach-chip-art"] {
|
|
344
|
+
width: 100%;
|
|
345
|
+
height: 100%;
|
|
346
|
+
}
|
|
347
|
+
/* The <img> is stacked over the paperclip, not swapped for it: a format the
|
|
348
|
+
engine cannot decode (HEIC outside WebKit) simply paints nothing and the
|
|
349
|
+
icon shows through — no onError state to carry. */
|
|
350
|
+
[data-mobile-nav="attach-chip-art"] img {
|
|
351
|
+
position: absolute;
|
|
352
|
+
inset: 0;
|
|
353
|
+
width: 100%;
|
|
354
|
+
height: 100%;
|
|
355
|
+
object-fit: cover;
|
|
356
|
+
background: var(--dsw-alias-bg-layer-2, rgba(127, 127, 127, .18));
|
|
357
|
+
}
|
|
358
|
+
[data-mobile-nav="attach-chip-name"] {
|
|
359
|
+
min-width: 0;
|
|
360
|
+
overflow: hidden;
|
|
361
|
+
white-space: nowrap;
|
|
362
|
+
}
|
|
363
|
+
[data-mobile-nav="attach-chip-remove"] {
|
|
364
|
+
display: grid;
|
|
365
|
+
place-items: center;
|
|
366
|
+
flex: none;
|
|
367
|
+
width: 18px;
|
|
368
|
+
height: 18px;
|
|
369
|
+
padding: 0;
|
|
370
|
+
border: none;
|
|
371
|
+
border-radius: 999px;
|
|
372
|
+
background: transparent;
|
|
373
|
+
color: inherit;
|
|
374
|
+
cursor: pointer;
|
|
375
|
+
touch-action: manipulation;
|
|
376
|
+
}
|
|
377
|
+
/* On a tile the × floats in the corner over the picture, so it needs its own
|
|
378
|
+
ground to stay legible against an arbitrary photo. */
|
|
379
|
+
[data-kind="image"] > [data-mobile-nav="attach-chip-remove"] {
|
|
380
|
+
position: absolute;
|
|
381
|
+
top: 2px;
|
|
382
|
+
right: 2px;
|
|
383
|
+
background: rgba(0, 0, 0, .55);
|
|
384
|
+
color: #fff;
|
|
385
|
+
}
|
|
386
|
+
[data-mobile-nav="attach-chip-remove"]:active {
|
|
387
|
+
transform: scale(.9);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/* --- 8. the official stats strip leaves the composer ---
|
|
391
|
+
Its data moves into the session info card (S4). The row is the composer
|
|
392
|
+
dock's own \`_root\` entry; the slot itself stays live for later entries. */
|
|
393
|
+
[data-slot="conversation.composer.dock"] > [class$="_root"] {
|
|
394
|
+
display: none !important;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* --- 9. the attachment button (S7; its file input is hidden at the top of
|
|
398
|
+
this file, at every width) --- */
|
|
399
|
+
[data-mobile-nav="attach"] {
|
|
400
|
+
position: relative;
|
|
401
|
+
width: 28px !important;
|
|
402
|
+
height: 28px !important;
|
|
403
|
+
flex: none !important;
|
|
404
|
+
display: grid !important;
|
|
405
|
+
place-items: center !important;
|
|
406
|
+
padding: 0 !important;
|
|
407
|
+
border: none !important;
|
|
408
|
+
border-radius: 999px !important;
|
|
409
|
+
background: var(--dsw-specific-selector, rgba(127, 127, 127, .12));
|
|
410
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
411
|
+
cursor: pointer;
|
|
412
|
+
touch-action: manipulation;
|
|
413
|
+
}
|
|
414
|
+
[data-mobile-nav="attach"]:active {
|
|
415
|
+
transform: scale(.94);
|
|
416
|
+
transition: transform .12s;
|
|
417
|
+
}
|
|
418
|
+
/* Busy: a ring sweeps around the paperclip. Drawn with a conic gradient in
|
|
419
|
+
a pseudo-element rather than a spinner node, so the official React tree
|
|
420
|
+
around us has nothing extra to re-render (same reasoning as the model
|
|
421
|
+
pill's mask icon, S3.1). */
|
|
422
|
+
[data-mobile-nav="attach"][data-busy]::after {
|
|
423
|
+
content: "";
|
|
424
|
+
position: absolute;
|
|
425
|
+
inset: -2px;
|
|
426
|
+
border-radius: 999px;
|
|
427
|
+
background: conic-gradient(from 0deg, transparent 0 65%, currentColor 100%);
|
|
428
|
+
mask: radial-gradient(closest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));
|
|
429
|
+
-webkit-mask: radial-gradient(closest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));
|
|
430
|
+
animation: mnav-attach-spin .9s linear infinite;
|
|
431
|
+
pointer-events: none;
|
|
432
|
+
}
|
|
433
|
+
@keyframes mnav-attach-spin {
|
|
434
|
+
to { transform: rotate(1turn); }
|
|
435
|
+
}
|
|
436
|
+
@media (prefers-reduced-motion: reduce) {
|
|
437
|
+
[data-mobile-nav="attach"][data-busy]::after {
|
|
438
|
+
animation-duration: 3s;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/* Failure note: one line above the button, tap-to-dismiss. Sits on the
|
|
442
|
+
composer card (z 2) rather than in an overlay layer — nothing here needs
|
|
443
|
+
to clear the permission/model sheets. */
|
|
444
|
+
[data-mobile-nav="attach-error"] {
|
|
445
|
+
position: absolute;
|
|
446
|
+
bottom: calc(100% + 6px);
|
|
447
|
+
left: 0;
|
|
448
|
+
z-index: 2;
|
|
449
|
+
max-width: 62vw;
|
|
450
|
+
padding: 5px 8px;
|
|
451
|
+
border-radius: 8px;
|
|
452
|
+
font-size: 12px;
|
|
453
|
+
line-height: 1.3;
|
|
454
|
+
white-space: nowrap;
|
|
455
|
+
overflow: hidden;
|
|
456
|
+
text-overflow: ellipsis;
|
|
457
|
+
background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, .82));
|
|
458
|
+
color: var(--dsw-alias-label-primary, #fff);
|
|
459
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, .24);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/* --- 9. home-indicator clearance (S4.1, 2026-08-17) ---
|
|
463
|
+
Owned HERE, not by the gateway half. That plugin used to carry
|
|
464
|
+
[data-slot="conversation.composer"] { padding-bottom: max(env(safe-area-inset-bottom), 8px) }
|
|
465
|
+
in both pwa/app.css and the gateway's inline DEVICE_CSS. Both were
|
|
466
|
+
INERT and had always been: that slot element is \`display: contents\`
|
|
467
|
+
(measured 2026-08-17 — the slot wrapper generates no box, so padding on
|
|
468
|
+
it is discarded), which is why raising it never moved anything. The rule
|
|
469
|
+
is deleted on the PWA side and restated here on an element that actually
|
|
470
|
+
lays out, reading --mnav-sab so ?mobile-nav-inset=54,34 can regress it
|
|
471
|
+
off-device (env() is hard 0 on desktop — the whole reason S2.1 exists).
|
|
472
|
+
|
|
473
|
+
Target: the card should look EQUALLY inset on all four sides. This same
|
|
474
|
+
element carries the card's side inset (\`padding: 0 16px\` officially,
|
|
475
|
+
measured 16px left and 16px right at 390px), so the bottom gap is simply
|
|
476
|
+
capped at that same 16px. Clearing the FULL 34px inset — the naive
|
|
477
|
+
max(sab, 8px) — is what read as "too thick": it is more than double the
|
|
478
|
+
side margin, so the card looked shoved up off the bottom edge.
|
|
479
|
+
|
|
480
|
+
clamp() says all three requirements at once:
|
|
481
|
+
- floor 8px -> a device with no home indicator (sab: 0) keeps the
|
|
482
|
+
official 8px exactly, so this is a strict no-op there;
|
|
483
|
+
- track sab -> a shallower inset than 16px is honoured as-is;
|
|
484
|
+
- cap 16px -> a full-size indicator (sab: 34) lands on 16px, equal
|
|
485
|
+
to the side inset, which is the look being asked for.
|
|
486
|
+
Keep the 16px in step with the side padding above if that ever changes;
|
|
487
|
+
that equality IS the spec here, not a coincidence. */
|
|
488
|
+
[data-slot="conversation.composer.bar"] > [class$="_root"] {
|
|
489
|
+
padding-bottom: clamp(8px, var(--mnav-sab), 16px) !important;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/* The attachment button and its preview row only exist for the phone shell.
|
|
494
|
+
Both render at every width (the slots are not breakpoint-aware), so both
|
|
495
|
+
need hiding here — the dock-row rules that shape the chips live in the
|
|
496
|
+
< 768px block and would otherwise leave a bare, unstyled chip line in the
|
|
497
|
+
desktop composer. */
|
|
498
|
+
@media (min-width: 768px) {
|
|
499
|
+
[data-mobile-nav="attach"],
|
|
500
|
+
[data-mobile-nav="attach-chips"] {
|
|
501
|
+
display: none !important;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
`
|