@sunshaobei/dsh-session-manager 0.0.0-stage → 0.6.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/client.css ADDED
@@ -0,0 +1,1613 @@
1
+ /*
2
+ * dsh-session-recycle-bin — plugin stylesheet (client.css)
3
+ *
4
+ * Served by the host at GET /api/session-trash/client.css and injected by
5
+ * client.js as a <link rel="stylesheet">. All colors are CSS custom
6
+ * properties (design tokens) declared on :root; the dark palette applies
7
+ * under `body[data-ds-dark-theme]` (the resolved DSH appearance: light /
8
+ * dark / system, set by the host right after <body> opens) OR under
9
+ * `@media (prefers-color-scheme: dark)` (OS-level fallback), while an
10
+ * explicit light appearance restores the light palette via
11
+ * `body:not([data-ds-dark-theme])` — so inside DSH the app preference is
12
+ * authoritative, and the OS media query remains the fallback without it.
13
+ *
14
+ * Naming: components use the `dsh-trash-` prefix; markdown blocks are styled
15
+ * with element selectors scoped under `.dsh-trash-md`.
16
+ */
17
+
18
+ /* ------------------------------------------------------------------ */
19
+ /* Design tokens (light default; dark = app theme || OS fallback) */
20
+ /* ------------------------------------------------------------------ */
21
+
22
+ :root {
23
+ /* FONT STACKS ARE RESOLVED AT THE POINT OF USE, NOT HERE.
24
+ *
25
+ * These two tokens are the last-resort fallbacks for hosts that do not
26
+ * publish the shell's own font tokens (older DSH builds, the standalone
27
+ * test harness). They are deliberately CONCRETE stacks: a custom property
28
+ * whose value contains var() is substituted at computed-value time on the
29
+ * element that DECLARES it, and that computed value is what inherits. So
30
+ * :root { --dstrb-font: var(--dsw-font-family); }
31
+ * would freeze the :root-scoped stock stack and would NOT follow the
32
+ * body-level `--dsw-font-family` override that dsh-ui-font installs for the
33
+ * whole app — the plugin page would keep rendering in the default face.
34
+ * The effective font is therefore routed at the use sites (see the
35
+ * "Font routing" block below), where the inherited DSH tokens are visible.
36
+ */
37
+ --dstrb-font-fallback: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Arial, sans-serif;
38
+ --dstrb-mono-fallback: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
39
+
40
+ /* Text */
41
+ --dstrb-text-strong: #1a202c;
42
+ --dstrb-text: #2d3748;
43
+ --dstrb-text-secondary: #4a5568;
44
+ /* #718096 measured 4.02:1 on the card surface and 3.83:1 on the subtle
45
+ surface (badges) — under the 4.5:1 text bar for the small labels that use
46
+ it. #5f6b7a clears it everywhere it lands: 5.43 on surface, 5.18 on
47
+ surface-subtle, 4.82 on surface-hover (the hovered badge), while staying
48
+ visibly weaker than --dstrb-text (11.99) and --dstrb-text-secondary (7.53). */
49
+ --dstrb-text-muted: #5f6b7a;
50
+ --dstrb-text-faint: #a0aec0;
51
+
52
+ /* Accents */
53
+ --dstrb-accent: #3182ce;
54
+ --dstrb-accent-veil: rgba(49, 130, 206, 0.15);
55
+ --dstrb-danger: #e53e3e;
56
+ --dstrb-danger-strong: #c53030;
57
+ --dstrb-danger-border: #feb2b2;
58
+ --dstrb-danger-veil: rgba(229, 62, 62, 0.08);
59
+ --dstrb-on-danger: #ffffff;
60
+
61
+ /* Surfaces */
62
+ --dstrb-surface: #ffffff; /* cards, modals, menus */
63
+ --dstrb-surface-subtle: #f7fafc; /* inputs, badges, checked rows */
64
+ --dstrb-surface-hover: #edf2f7; /* cancel button, user bubble base */
65
+
66
+ /* Borders */
67
+ --dstrb-border: #e2e8f0;
68
+ --dstrb-border-light: #edf2f7;
69
+ --dstrb-border-hairline: #f0f4f8;
70
+
71
+ /* Structural lines that must stay IDENTIFIABLE, not decorative: WCAG 1.4.11
72
+ wants >= 3:1 from the boundary that carries the hierarchy — the default
73
+ borders sit at ~1.1:1, so the nesting cue uses this token instead.
74
+ #64748b on the light card surface (#ffffff) = 4.76:1. */
75
+ --dstrb-structure: #64748b;
76
+
77
+ /* Veils & overlays */
78
+ --dstrb-veil: rgba(0, 0, 0, 0.06);
79
+ --dstrb-overlay: rgba(0, 0, 0, 0.45);
80
+
81
+ /* Shadows */
82
+ --dstrb-shadow-card: 0 2px 8px rgba(0, 0, 0, 0.03);
83
+ --dstrb-shadow-menu: 0 4px 16px rgba(0, 0, 0, 0.12);
84
+ --dstrb-shadow-modal: 0 20px 40px rgba(0, 0, 0, 0.2);
85
+ --dstrb-shadow-modal-sm: 0 10px 25px rgba(0, 0, 0, 0.2);
86
+ --dstrb-shadow-tooltip: 0 8px 24px rgba(0, 0, 0, 0.2);
87
+ --dstrb-shadow-toast: 0 4px 12px rgba(0, 0, 0, 0.15);
88
+
89
+ /* Inverted tooltip */
90
+ --dstrb-tooltip-bg: #1a202c;
91
+ --dstrb-tooltip-text: #ffffff;
92
+ --dstrb-tooltip-muted: #a0aec0;
93
+
94
+ /* Toast (also inverted) */
95
+ --dstrb-toast-bg: #2d3748;
96
+ --dstrb-toast-text: #ffffff;
97
+ --dstrb-toast-btn-bg: #4a5568;
98
+ --dstrb-toast-btn-text: #63b3ed;
99
+
100
+ /* Markdown */
101
+ --dstrb-code-inline-bg: rgba(0, 0, 0, 0.07);
102
+ --dstrb-code-inline-text: #c53030;
103
+ --dstrb-quote-bg: rgba(0, 0, 0, 0.02);
104
+ --dstrb-quote-border: #cbd5e0;
105
+ --dstrb-bubble-user: #edf2f7;
106
+ /* Code blocks stay dark in both modes (editor-style) */
107
+ --dstrb-codeblock-bg: #1e293b;
108
+ --dstrb-codeblock-text: #e2e8f0;
109
+ --dstrb-codeblock-lang: #94a3b8;
110
+ }
111
+
112
+ @media (prefers-color-scheme: dark) {
113
+ :root {
114
+ --dstrb-text-strong: #edf2f7;
115
+ --dstrb-text: #e2e8f0;
116
+ --dstrb-text-secondary: #cbd5e0;
117
+ --dstrb-text-muted: #a0aec0;
118
+ --dstrb-text-faint: #718096;
119
+
120
+ --dstrb-accent: #63b3ed;
121
+ --dstrb-accent-veil: rgba(99, 179, 237, 0.2);
122
+ --dstrb-danger: #fc8181;
123
+ --dstrb-danger-strong: #feb2b2;
124
+ --dstrb-danger-border: #9b2c2c;
125
+ --dstrb-danger-veil: rgba(252, 129, 129, 0.12);
126
+ --dstrb-on-danger: #1a202c;
127
+
128
+ --dstrb-surface: #1f2530;
129
+ --dstrb-surface-subtle: #262e3d;
130
+ --dstrb-surface-hover: #2c3444;
131
+
132
+ --dstrb-border: #3a4356;
133
+ --dstrb-border-light: #333c4e;
134
+ --dstrb-border-hairline: #2c3444;
135
+
136
+ /* Same >=3:1 rule on the dark card surface (#1f2530): #8a97a8 = 5.18:1. */
137
+ --dstrb-structure: #8a97a8;
138
+
139
+ --dstrb-veil: rgba(255, 255, 255, 0.08);
140
+ --dstrb-overlay: rgba(0, 0, 0, 0.65);
141
+
142
+ --dstrb-shadow-card: 0 2px 8px rgba(0, 0, 0, 0.35);
143
+ --dstrb-shadow-menu: 0 4px 16px rgba(0, 0, 0, 0.5);
144
+ --dstrb-shadow-modal: 0 20px 40px rgba(0, 0, 0, 0.55);
145
+ --dstrb-shadow-modal-sm: 0 10px 25px rgba(0, 0, 0, 0.5);
146
+ --dstrb-shadow-tooltip: 0 8px 24px rgba(0, 0, 0, 0.55);
147
+ --dstrb-shadow-toast: 0 4px 12px rgba(0, 0, 0, 0.4);
148
+
149
+ --dstrb-tooltip-bg: #e2e8f0;
150
+ --dstrb-tooltip-text: #1a202c;
151
+ --dstrb-tooltip-muted: #4a5568;
152
+
153
+ --dstrb-toast-bg: #e2e8f0;
154
+ --dstrb-toast-text: #1a202c;
155
+ --dstrb-toast-btn-bg: #cbd5e0;
156
+ --dstrb-toast-btn-text: #1a202c;
157
+
158
+ --dstrb-code-inline-bg: rgba(255, 255, 255, 0.12);
159
+ --dstrb-code-inline-text: #fc8181;
160
+ --dstrb-quote-bg: rgba(255, 255, 255, 0.04);
161
+ --dstrb-quote-border: #4a5568;
162
+ --dstrb-bubble-user: #2c3444;
163
+ }
164
+ }
165
+
166
+ /* ------------------------------------------------------------------ */
167
+ /* DSH app theme — body[data-ds-dark-theme] */
168
+ /* ------------------------------------------------------------------ */
169
+ /* The host app resolves its appearance preference (light / dark / */
170
+ /* system) onto body[data-ds-dark-theme] as an inline script right */
171
+ /* after <body> opens. This block ORs with the OS media query above: */
172
+ /* either match resolves the dark palette. */
173
+ /* ------------------------------------------------------------------ */
174
+
175
+ body[data-ds-dark-theme] {
176
+ --dstrb-text-strong: #edf2f7;
177
+ --dstrb-text: #e2e8f0;
178
+ --dstrb-text-secondary: #cbd5e0;
179
+ --dstrb-text-muted: #a0aec0;
180
+ --dstrb-text-faint: #718096;
181
+
182
+ --dstrb-accent: #63b3ed;
183
+ --dstrb-accent-veil: rgba(99, 179, 237, 0.2);
184
+ --dstrb-danger: #fc8181;
185
+ --dstrb-danger-strong: #feb2b2;
186
+ --dstrb-danger-border: #9b2c2c;
187
+ --dstrb-danger-veil: rgba(252, 129, 129, 0.12);
188
+ --dstrb-on-danger: #1a202c;
189
+
190
+ --dstrb-surface: #1f2530;
191
+ --dstrb-surface-subtle: #262e3d;
192
+ --dstrb-surface-hover: #2c3444;
193
+
194
+ --dstrb-border: #3a4356;
195
+ --dstrb-border-light: #333c4e;
196
+ --dstrb-border-hairline: #2c3444;
197
+
198
+ /* Same >=3:1 rule on the dark card surface (#1f2530): #8a97a8 = 5.18:1. */
199
+ --dstrb-structure: #8a97a8;
200
+
201
+ --dstrb-veil: rgba(255, 255, 255, 0.08);
202
+ --dstrb-overlay: rgba(0, 0, 0, 0.65);
203
+
204
+ --dstrb-shadow-card: 0 2px 8px rgba(0, 0, 0, 0.35);
205
+ --dstrb-shadow-menu: 0 4px 16px rgba(0, 0, 0, 0.5);
206
+ --dstrb-shadow-modal: 0 20px 40px rgba(0, 0, 0, 0.55);
207
+ --dstrb-shadow-modal-sm: 0 10px 25px rgba(0, 0, 0, 0.5);
208
+ --dstrb-shadow-tooltip: 0 8px 24px rgba(0, 0, 0, 0.55);
209
+ --dstrb-shadow-toast: 0 4px 12px rgba(0, 0, 0, 0.4);
210
+
211
+ --dstrb-tooltip-bg: #e2e8f0;
212
+ --dstrb-tooltip-text: #1a202c;
213
+ --dstrb-tooltip-muted: #4a5568;
214
+
215
+ --dstrb-toast-bg: #e2e8f0;
216
+ --dstrb-toast-text: #1a202c;
217
+ --dstrb-toast-btn-bg: #cbd5e0;
218
+ --dstrb-toast-btn-text: #1a202c;
219
+
220
+ --dstrb-code-inline-bg: rgba(255, 255, 255, 0.12);
221
+ --dstrb-code-inline-text: #fc8181;
222
+ --dstrb-quote-bg: rgba(255, 255, 255, 0.04);
223
+ --dstrb-quote-border: #4a5568;
224
+ --dstrb-bubble-user: #2c3444;
225
+ }
226
+
227
+ /* An explicit light preference restores the light palette even under */
228
+ /* a dark OS, so the app preference stays authoritative inside DSH */
229
+ /* (fixes near-white titles on a light page). */
230
+
231
+ body:not([data-ds-dark-theme]) {
232
+ /* Fonts are NOT palette state: they are routed once, at the point of use,
233
+ in the "Font routing" block above — do not re-declare them here, or the
234
+ explicit-light branch would pin the plugin back to the stock stack. */
235
+
236
+ /* Text */
237
+ --dstrb-text-strong: #1a202c;
238
+ --dstrb-text: #2d3748;
239
+ --dstrb-text-secondary: #4a5568;
240
+ /* #718096 measured 4.02:1 on the card surface and 3.83:1 on the subtle
241
+ surface (badges) — under the 4.5:1 text bar for the small labels that use
242
+ it. #5f6b7a clears it everywhere it lands: 5.43 on surface, 5.18 on
243
+ surface-subtle, 4.82 on surface-hover (the hovered badge), while staying
244
+ visibly weaker than --dstrb-text (11.99) and --dstrb-text-secondary (7.53). */
245
+ --dstrb-text-muted: #5f6b7a;
246
+ --dstrb-text-faint: #a0aec0;
247
+
248
+ /* Accents */
249
+ --dstrb-accent: #3182ce;
250
+ --dstrb-accent-veil: rgba(49, 130, 206, 0.15);
251
+ --dstrb-danger: #e53e3e;
252
+ --dstrb-danger-strong: #c53030;
253
+ --dstrb-danger-border: #feb2b2;
254
+ --dstrb-danger-veil: rgba(229, 62, 62, 0.08);
255
+ --dstrb-on-danger: #ffffff;
256
+
257
+ /* Surfaces */
258
+ --dstrb-surface: #ffffff; /* cards, modals, menus */
259
+ --dstrb-surface-subtle: #f7fafc; /* inputs, badges, checked rows */
260
+ --dstrb-surface-hover: #edf2f7; /* cancel button, user bubble base */
261
+
262
+ /* Borders */
263
+ --dstrb-border: #e2e8f0;
264
+ --dstrb-border-light: #edf2f7;
265
+ --dstrb-border-hairline: #f0f4f8;
266
+
267
+ /* Structural lines that must stay IDENTIFIABLE, not decorative: WCAG 1.4.11
268
+ wants >= 3:1 from the boundary that carries the hierarchy — the default
269
+ borders sit at ~1.1:1, so the nesting cue uses this token instead.
270
+ #64748b on the light card surface (#ffffff) = 4.76:1. */
271
+ --dstrb-structure: #64748b;
272
+
273
+ /* Veils & overlays */
274
+ --dstrb-veil: rgba(0, 0, 0, 0.06);
275
+ --dstrb-overlay: rgba(0, 0, 0, 0.45);
276
+
277
+ /* Shadows */
278
+ --dstrb-shadow-card: 0 2px 8px rgba(0, 0, 0, 0.03);
279
+ --dstrb-shadow-menu: 0 4px 16px rgba(0, 0, 0, 0.12);
280
+ --dstrb-shadow-modal: 0 20px 40px rgba(0, 0, 0, 0.2);
281
+ --dstrb-shadow-modal-sm: 0 10px 25px rgba(0, 0, 0, 0.2);
282
+ --dstrb-shadow-tooltip: 0 8px 24px rgba(0, 0, 0, 0.2);
283
+ --dstrb-shadow-toast: 0 4px 12px rgba(0, 0, 0, 0.15);
284
+
285
+ /* Inverted tooltip */
286
+ --dstrb-tooltip-bg: #1a202c;
287
+ --dstrb-tooltip-text: #ffffff;
288
+ --dstrb-tooltip-muted: #a0aec0;
289
+
290
+ /* Toast (also inverted) */
291
+ --dstrb-toast-bg: #2d3748;
292
+ --dstrb-toast-text: #ffffff;
293
+ --dstrb-toast-btn-bg: #4a5568;
294
+ --dstrb-toast-btn-text: #63b3ed;
295
+
296
+ /* Markdown */
297
+ --dstrb-code-inline-bg: rgba(0, 0, 0, 0.07);
298
+ --dstrb-code-inline-text: #c53030;
299
+ --dstrb-quote-bg: rgba(0, 0, 0, 0.02);
300
+ --dstrb-quote-border: #cbd5e0;
301
+ --dstrb-bubble-user: #edf2f7;
302
+ /* Code blocks stay dark in both modes (editor-style) */
303
+ --dstrb-codeblock-bg: #1e293b;
304
+ --dstrb-codeblock-text: #e2e8f0;
305
+ --dstrb-codeblock-lang: #94a3b8;
306
+ }
307
+
308
+ /* ------------------------------------------------------------------ */
309
+ /* Font routing — follow the DSH shell, stay overridable */
310
+ /* ------------------------------------------------------------------ */
311
+ /*
312
+ * The shell paints `<body>` with `font-family: var(--dsw-font-family, …)`
313
+ * and dsh-ui-font swaps that token at the body level to apply the user's
314
+ * Settings → 字体 choice (and `--ds-font-family-code` for code faces)
315
+ * across the whole app. Referencing those host tokens HERE — on the plugin
316
+ * roots, where the inherited values are already in scope — is what makes
317
+ * the settings page and the portaled toast track the app font, live.
318
+ *
319
+ * --dstrb-font / --dstrb-mono remain available as optional override hooks:
320
+ * a theme may still declare either one higher up the tree and it wins,
321
+ * because the fallback chain is evaluated at the use site.
322
+ */
323
+ .dsh-trash-container,
324
+ .dsh-trash-toast {
325
+ font-family: var(--dstrb-font, var(--dsw-font-family, var(--dstrb-font-fallback)));
326
+ }
327
+
328
+ /* ------------------------------------------------------------------ */
329
+ /* Page container — width follows the window ratio (no fixed px cap) */
330
+ /* ------------------------------------------------------------------ */
331
+
332
+ .dsh-trash-container {
333
+ width: 90%;
334
+ padding: 24px;
335
+ margin: 0 auto;
336
+ color: var(--dstrb-text);
337
+ color-scheme: light dark; /* native select/checkbox follow the resolved mode */
338
+ }
339
+
340
+ /* App-theme-scoped overrides: native controls (select, checkbox,
341
+ scrollbars) follow the resolved plugin mode, matching the painted
342
+ surfaces even when the OS media query disagrees with the DSH
343
+ appearance preference. */
344
+ body[data-ds-dark-theme] .dsh-trash-container { color-scheme: dark; }
345
+ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
346
+
347
+ .dsh-trash-container,
348
+ .dsh-trash-container * {
349
+ box-sizing: border-box;
350
+ }
351
+
352
+ /* ------------------------------------------------------------------ */
353
+ /* Header */
354
+ /* ------------------------------------------------------------------ */
355
+
356
+ .dsh-trash-header {
357
+ display: flex;
358
+ justify-content: space-between;
359
+ align-items: center;
360
+ margin-bottom: 20px;
361
+ }
362
+
363
+ .dsh-trash-header-left {
364
+ display: flex;
365
+ flex-direction: column;
366
+ gap: 4px;
367
+ }
368
+
369
+ .dsh-trash-title {
370
+ margin: 0;
371
+ font-size: 20px;
372
+ font-weight: 600;
373
+ color: var(--dstrb-text-strong);
374
+ display: flex;
375
+ align-items: center;
376
+ }
377
+
378
+ .dsh-trash-subtitle {
379
+ margin: 0;
380
+ font-size: 13px;
381
+ color: var(--dstrb-text-muted);
382
+ }
383
+
384
+ /* Icons (size/margin/color moved out of JS inline styles) */
385
+ .dsh-trash-icon-title { width: 20px; height: 20px; margin-right: 8px; color: var(--dstrb-text-secondary); flex: none; }
386
+ .dsh-trash-icon-btn { width: 14px; height: 14px; margin-right: 6px; }
387
+ .dsh-trash-icon-menu { width: 13px; height: 13px; margin-right: 6px; }
388
+ .dsh-trash-icon-folder { width: 18px; height: 18px; color: var(--dstrb-text-muted); flex: none; }
389
+ .dsh-trash-icon-badge { width: 13px; height: 13px; margin-right: 4px; color: var(--dstrb-text-muted); }
390
+ .dsh-trash-icon-sm { width: 12px; height: 12px; margin-right: 3px; }
391
+
392
+ /* ------------------------------------------------------------------ */
393
+ /* Search bar */
394
+ /* ------------------------------------------------------------------ */
395
+
396
+ .dsh-trash-search {
397
+ position: relative;
398
+ margin-bottom: 12px;
399
+ width: 100%;
400
+ }
401
+
402
+ .dsh-trash-search-icon {
403
+ position: absolute;
404
+ top: 50%;
405
+ transform: translateY(-50%);
406
+ left: 14px;
407
+ color: var(--dstrb-text-faint);
408
+ pointer-events: none;
409
+ }
410
+
411
+ .dsh-trash-search-input {
412
+ width: 100%;
413
+ padding: 10px 14px 10px 40px;
414
+ border-radius: 24px;
415
+ border: 1px solid var(--dstrb-border);
416
+ background-color: var(--dstrb-surface-subtle);
417
+ font-size: 14px;
418
+ font-family: inherit;
419
+ color: var(--dstrb-text);
420
+ outline: none;
421
+ transition: all 0.15s ease;
422
+ }
423
+
424
+ .dsh-trash-search-input::placeholder {
425
+ color: var(--dstrb-text-muted);
426
+ }
427
+
428
+ .dsh-trash-search-input:focus {
429
+ border-color: var(--dstrb-accent);
430
+ box-shadow: 0 0 0 3px var(--dstrb-accent-veil);
431
+ background-color: var(--dstrb-surface);
432
+ }
433
+
434
+ /* ------------------------------------------------------------------ */
435
+ /* Filter toolbar */
436
+ /* ------------------------------------------------------------------ */
437
+
438
+ /* Flex-wrap rather than `1fr 1fr auto`: a `1fr` grid track refuses to shrink
439
+ below its longest option, so at a 320px settings column the toolbar pushed
440
+ itself 6px past the panel. The controls now wrap instead of spilling. */
441
+ .dsh-trash-toolbar {
442
+ display: flex;
443
+ flex-wrap: wrap;
444
+ gap: 12px;
445
+ align-items: center;
446
+ margin-bottom: 20px;
447
+ }
448
+
449
+ .dsh-trash-toolbar > .dsh-trash-select {
450
+ flex: 1 1 120px;
451
+ min-width: 0;
452
+ }
453
+
454
+ .dsh-trash-toolbar > .dsh-trash-btn-danger-outline {
455
+ flex: 0 0 auto;
456
+ }
457
+
458
+ .dsh-trash-select {
459
+ width: 100%;
460
+ height: 36px;
461
+ padding: 0 32px 0 14px;
462
+ border-radius: 18px;
463
+ border: 1px solid var(--dstrb-border);
464
+ background-color: var(--dstrb-surface);
465
+ font-size: 13px;
466
+ font-family: inherit;
467
+ color: var(--dstrb-text-secondary);
468
+ cursor: pointer;
469
+ outline: none;
470
+ appearance: none;
471
+ background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a0aec0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
472
+ background-repeat: no-repeat;
473
+ background-position: right 10px center;
474
+ background-size: 14px;
475
+ }
476
+
477
+ .dsh-trash-btn-danger-outline {
478
+ height: 36px;
479
+ background-color: var(--dstrb-surface);
480
+ color: var(--dstrb-danger);
481
+ border: 1px solid var(--dstrb-danger-border);
482
+ padding: 0 16px;
483
+ border-radius: 18px;
484
+ font-size: 13px;
485
+ font-weight: 500;
486
+ font-family: inherit;
487
+ display: inline-flex;
488
+ align-items: center;
489
+ justify-content: center;
490
+ cursor: pointer;
491
+ transition: all 0.15s ease;
492
+ white-space: nowrap;
493
+ }
494
+
495
+ .dsh-trash-btn-danger-outline:hover {
496
+ background-color: var(--dstrb-danger-veil);
497
+ }
498
+
499
+ /* ------------------------------------------------------------------ */
500
+ /* States (loading / error / empty) */
501
+ /* ------------------------------------------------------------------ */
502
+
503
+ .dsh-trash-state {
504
+ padding: 40px 0;
505
+ text-align: center;
506
+ color: var(--dstrb-text-muted);
507
+ font-size: 14px;
508
+ }
509
+
510
+ .dsh-trash-state--error {
511
+ color: var(--dstrb-danger);
512
+ }
513
+
514
+ /* ------------------------------------------------------------------ */
515
+ /* Workspace groups */
516
+ /* ------------------------------------------------------------------ */
517
+
518
+ .dsh-trash-groups {
519
+ display: flex;
520
+ flex-direction: column;
521
+ gap: 24px;
522
+ }
523
+
524
+ .dsh-trash-group {
525
+ display: flex;
526
+ flex-direction: column;
527
+ gap: 10px;
528
+ }
529
+
530
+ .dsh-trash-group-header {
531
+ display: flex;
532
+ justify-content: space-between;
533
+ align-items: center;
534
+ padding: 4px 2px;
535
+ }
536
+
537
+ .dsh-trash-group-title-group {
538
+ display: flex;
539
+ align-items: center;
540
+ gap: 8px;
541
+ min-width: 0;
542
+ flex: 1;
543
+ margin-right: 12px;
544
+ }
545
+
546
+ .dsh-trash-group-title {
547
+ font-size: 15px;
548
+ font-weight: 600;
549
+ color: var(--dstrb-text-strong);
550
+ white-space: nowrap;
551
+ }
552
+
553
+ .dsh-trash-group-path {
554
+ font-size: 11px;
555
+ color: var(--dstrb-text-muted);
556
+ font-family: var(--dstrb-mono, var(--ds-font-family-code, var(--dstrb-mono-fallback)));
557
+ margin-left: 4px;
558
+ overflow: hidden;
559
+ text-overflow: ellipsis;
560
+ white-space: nowrap;
561
+ max-width: 260px;
562
+ }
563
+
564
+ .dsh-trash-group-right {
565
+ display: flex;
566
+ align-items: center;
567
+ gap: 12px;
568
+ flex-shrink: 0;
569
+ }
570
+
571
+ .dsh-trash-group-count {
572
+ font-size: 12px;
573
+ color: var(--dstrb-text-muted);
574
+ }
575
+
576
+ .dsh-trash-more-wrap {
577
+ position: relative;
578
+ }
579
+
580
+ .dsh-trash-more-btn {
581
+ background: none;
582
+ border: none;
583
+ color: var(--dstrb-text-muted);
584
+ font-size: 14px;
585
+ cursor: pointer;
586
+ padding: 2px 6px;
587
+ border-radius: 4px;
588
+ transition: background 0.12s ease, color 0.12s ease;
589
+ }
590
+
591
+ .dsh-trash-more-btn:hover {
592
+ background: var(--dstrb-veil);
593
+ color: var(--dstrb-text-muted);
594
+ }
595
+
596
+ .dsh-trash-menu {
597
+ position: absolute;
598
+ right: 0;
599
+ top: 24px;
600
+ background-color: var(--dstrb-surface);
601
+ box-shadow: var(--dstrb-shadow-menu);
602
+ border-radius: 8px;
603
+ padding: 6px 0;
604
+ z-index: 100;
605
+ min-width: 180px;
606
+ border: 1px solid var(--dstrb-border-light);
607
+ }
608
+
609
+ .dsh-trash-menu-item--danger {
610
+ width: 100%;
611
+ text-align: left;
612
+ padding: 8px 14px;
613
+ background-color: transparent;
614
+ border: none;
615
+ color: var(--dstrb-danger);
616
+ font-size: 13px;
617
+ font-family: inherit;
618
+ display: flex;
619
+ align-items: center;
620
+ cursor: pointer;
621
+ }
622
+
623
+ .dsh-trash-menu-item--danger:hover {
624
+ background-color: var(--dstrb-danger-veil);
625
+ }
626
+
627
+ /* ------------------------------------------------------------------ */
628
+ /* Session cards */
629
+ /* ------------------------------------------------------------------ */
630
+
631
+ .dsh-trash-cards {
632
+ display: flex;
633
+ flex-direction: column;
634
+ background-color: var(--dstrb-surface);
635
+ border-radius: 16px;
636
+ border: 1px solid var(--dstrb-border-light);
637
+ overflow: hidden;
638
+ box-shadow: var(--dstrb-shadow-card);
639
+ /* Query container for the narrow-panel rules below: the panel sits inside
640
+ the settings column, so its own width — not the viewport — decides what
641
+ the meta row can still show. */
642
+ container-type: inline-size;
643
+ }
644
+
645
+ /* ONE geometry at every width. A per-width padding/gap (plus a label that
646
+ reappears once the container grows) makes the title NARROWER on a wider
647
+ panel — a 400px list showed a 97px title while a 310px one showed 178px.
648
+ With the row's fixed costs constant, the title grows 1:1 with the panel. */
649
+ .dsh-trash-card {
650
+ display: flex;
651
+ justify-content: space-between;
652
+ align-items: center;
653
+ padding: 12px 10px;
654
+ border-bottom: 1px solid var(--dstrb-border-hairline);
655
+ transition: background-color 0.15s ease;
656
+ gap: 4px;
657
+ }
658
+
659
+ .dsh-trash-card--checked {
660
+ background-color: var(--dstrb-surface-subtle);
661
+ }
662
+
663
+ .dsh-trash-checkbox {
664
+ width: 16px;
665
+ height: 16px;
666
+ border-radius: 4px;
667
+ border: 1px solid var(--dstrb-border);
668
+ cursor: pointer;
669
+ accent-color: var(--dstrb-accent);
670
+ margin-right: 8px;
671
+ flex-shrink: 0;
672
+ }
673
+
674
+ /* The row gap already separates the card checkbox; an extra margin would come
675
+ straight out of the title's width. Declared AFTER the shared rule: same
676
+ specificity, so source order decides. */
677
+ .dsh-trash-checkbox--card {
678
+ margin-right: 0;
679
+ }
680
+
681
+ .dsh-trash-card-main {
682
+ display: flex;
683
+ flex-direction: column;
684
+ gap: 6px;
685
+ min-width: 0;
686
+ flex: 1 1 0%;
687
+ }
688
+
689
+ .dsh-trash-card-title {
690
+ font-size: 14px;
691
+ font-weight: 600;
692
+ color: var(--dstrb-text);
693
+ overflow: hidden;
694
+ text-overflow: ellipsis;
695
+ white-space: nowrap;
696
+ line-height: 1.3;
697
+ }
698
+
699
+ .dsh-trash-card-meta {
700
+ display: flex;
701
+ align-items: center;
702
+ flex-wrap: wrap;
703
+ gap: 14px;
704
+ font-size: 12px;
705
+ color: var(--dstrb-text-muted);
706
+ line-height: 1;
707
+ }
708
+
709
+ .dsh-trash-card-meta-item {
710
+ white-space: nowrap;
711
+ /* An item wider than the wrapping line must yield, not spill: without this a
712
+ squeezed card clips the timestamp by a couple of pixels inside its own
713
+ column (scrollWidth > clientWidth). */
714
+ min-width: 0;
715
+ overflow: hidden;
716
+ text-overflow: ellipsis;
717
+ }
718
+
719
+ .dsh-trash-badge {
720
+ background-color: var(--dstrb-surface-subtle);
721
+ padding: 2px 8px;
722
+ border-radius: 6px;
723
+ font-size: 11px;
724
+ color: var(--dstrb-text-muted);
725
+ border: 1px solid var(--dstrb-border-light);
726
+ white-space: nowrap;
727
+ display: inline-flex;
728
+ align-items: center;
729
+ }
730
+
731
+ /* Sub-session badge: same box as the size badge, but it discloses the inline
732
+ list, so it is a real button rather than a label. No font-size of its own —
733
+ it inherits the badge's, which keeps both badges the same size. */
734
+ .dsh-trash-badge--children {
735
+ font-family: inherit;
736
+ cursor: pointer;
737
+ transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease;
738
+ }
739
+
740
+ .dsh-trash-badge--children:hover {
741
+ background-color: var(--dstrb-surface-hover);
742
+ border-color: var(--dstrb-text-faint);
743
+ }
744
+
745
+ /* The open state is carried by the accent border and icon; the COUNT is 11px
746
+ text, and --dstrb-accent on the accent veil measures 3.22:1 — under the
747
+ 4.5:1 text bar this count already had to meet. */
748
+ .dsh-trash-badge--children[aria-expanded="true"] {
749
+ background-color: var(--dstrb-accent-veil);
750
+ border-color: var(--dstrb-accent);
751
+ color: var(--dstrb-text-strong);
752
+ }
753
+
754
+ .dsh-trash-badge--children[aria-expanded="true"] .dsh-trash-icon-badge {
755
+ color: var(--dstrb-accent);
756
+ }
757
+
758
+ .dsh-trash-badge--children:focus-visible {
759
+ outline: 2px solid var(--dstrb-accent);
760
+ outline-offset: 1px;
761
+ }
762
+
763
+ /* A reserved slot with tabular figures: the count is backfilled asynchronously,
764
+ so its digits must not resize the badge and shift the row under it. */
765
+ .dsh-trash-badge-num {
766
+ min-width: 1.6em;
767
+ text-align: center;
768
+ font-variant-numeric: tabular-nums;
769
+ }
770
+
771
+ .dsh-trash-card-actions {
772
+ display: flex;
773
+ align-items: center;
774
+ /* Icon-only buttons need less air than labelled pills; the width they give up
775
+ belongs to the title. */
776
+ gap: 4px;
777
+ flex-shrink: 0;
778
+ }
779
+
780
+ /* The action LABELS are hidden at every width, not just on a narrow panel.
781
+ Re-showing them once the container grows is what made a wider panel show a
782
+ narrower title: a label costs ~123px, so the title would drop by that much
783
+ the moment they come back. The icons keep a full accessible name
784
+ (aria-label + title), which is the state P0-4 asks for on a narrow panel.
785
+ To trade this back, show .dsh-trash-btn-label under an @container rule and
786
+ accept that the title then steps DOWN at that width. */
787
+ .dsh-trash-btn-label {
788
+ display: none;
789
+ }
790
+
791
+ .dsh-trash-btn-ghost .dsh-trash-icon-sm,
792
+ .dsh-trash-btn-danger-text .dsh-trash-icon-sm {
793
+ margin-right: 0;
794
+ }
795
+
796
+ .dsh-trash-btn-ghost {
797
+ background-color: var(--dstrb-surface-subtle);
798
+ color: var(--dstrb-text-secondary);
799
+ border: 1px solid var(--dstrb-border);
800
+ padding: 4px 5px;
801
+ border-radius: 12px;
802
+ font-size: 11px;
803
+ font-weight: 500;
804
+ font-family: inherit;
805
+ display: inline-flex;
806
+ align-items: center;
807
+ /* WCAG 2.5.8: an icon-only action keeps a >=24x24 target. */
808
+ min-width: 24px;
809
+ min-height: 24px;
810
+ justify-content: center;
811
+ cursor: pointer;
812
+ transition: all 0.12s ease;
813
+ white-space: nowrap;
814
+ }
815
+
816
+ .dsh-trash-btn-ghost:hover {
817
+ background-color: var(--dstrb-surface-hover);
818
+ border-color: var(--dstrb-text-faint);
819
+ }
820
+
821
+ .dsh-trash-btn-danger-text {
822
+ background-color: transparent;
823
+ color: var(--dstrb-danger);
824
+ border: none;
825
+ padding: 4px 5px;
826
+ border-radius: 12px;
827
+ font-size: 11px;
828
+ font-weight: 500;
829
+ font-family: inherit;
830
+ display: inline-flex;
831
+ align-items: center;
832
+ /* WCAG 2.5.8: an icon-only action keeps a >=24x24 target. */
833
+ min-width: 24px;
834
+ min-height: 24px;
835
+ justify-content: center;
836
+ cursor: pointer;
837
+ transition: all 0.12s ease;
838
+ white-space: nowrap;
839
+ }
840
+
841
+ .dsh-trash-btn-danger-text:hover {
842
+ background-color: var(--dstrb-danger-veil);
843
+ }
844
+
845
+ /* ------------------------------------------------------------------ */
846
+ /* Sub-session rows — full-width inline list under a card */
847
+ /* ------------------------------------------------------------------ */
848
+
849
+ /* Sits between two cards of the list, so it owns the hairline separator the
850
+ card would otherwise paint. The nesting cue is the RAIL, not a background
851
+ tint: a tint cannot carry a boundary (a subtle fill is ~1.05:1 against the
852
+ card by construction), so the hierarchy hangs on a line that meets the
853
+ >=3:1 non-text contrast bar in both themes — see --dstrb-structure.
854
+ 24px = the card's 10px padding + the 2px rail + the 12px indent. */
855
+ .dsh-trash-children {
856
+ position: relative;
857
+ padding: 6px 10px 10px 24px;
858
+ background-color: transparent;
859
+ border-bottom: 1px solid var(--dstrb-border-hairline);
860
+ }
861
+
862
+ .dsh-trash-children::before {
863
+ content: '';
864
+ position: absolute;
865
+ left: 10px;
866
+ top: 6px;
867
+ bottom: 10px;
868
+ width: 2px;
869
+ border-radius: 1px;
870
+ background-color: var(--dstrb-structure);
871
+ }
872
+
873
+ .dsh-trash-child {
874
+ display: flex;
875
+ align-items: center;
876
+ gap: 6px;
877
+ height: 28px;
878
+ font-size: 0.92em;
879
+ color: var(--dstrb-text-secondary);
880
+ }
881
+
882
+ .dsh-trash-child--live {
883
+ /* No row-level opacity: fading the whole row drags the TITLE down with it
884
+ (2.81:1, below the 4.5:1 text bar). The running state is carried by the
885
+ pill plus a danger tint, and only the periphery is dimmed — below. */
886
+ background-color: var(--dstrb-danger-veil);
887
+ border-radius: 6px;
888
+ cursor: not-allowed;
889
+ }
890
+
891
+ .dsh-trash-child--live .dsh-trash-child-check,
892
+ .dsh-trash-child--live .dsh-trash-child-meta,
893
+ .dsh-trash-child--live .dsh-trash-child-delete {
894
+ opacity: 0.55;
895
+ }
896
+
897
+ .dsh-trash-child-check {
898
+ /* Smaller and muted next to the card's own checkbox: this one scopes a
899
+ cascade delete, that one picks sessions for a batch action. */
900
+ width: 13px;
901
+ height: 13px;
902
+ margin-right: 0;
903
+ accent-color: var(--dstrb-text-faint);
904
+ }
905
+
906
+ /* The title is the preview control (a real button), so it needs button
907
+ resets; it also has to outrank the meta line for the row's width. */
908
+ .dsh-trash-child-title {
909
+ flex: 1 1 auto;
910
+ min-width: 0;
911
+ overflow: hidden;
912
+ text-overflow: ellipsis;
913
+ white-space: nowrap;
914
+ padding: 0;
915
+ border: none;
916
+ background: none;
917
+ color: inherit;
918
+ font: inherit;
919
+ text-align: left;
920
+ cursor: pointer;
921
+ }
922
+
923
+ .dsh-trash-child-title:hover {
924
+ color: var(--dstrb-text);
925
+ text-decoration: underline;
926
+ }
927
+
928
+ .dsh-trash-child-title:focus-visible {
929
+ outline: 2px solid var(--dstrb-accent);
930
+ outline-offset: 1px;
931
+ border-radius: 3px;
932
+ }
933
+
934
+ .dsh-trash-child-meta {
935
+ /* Shrinkable, so the title keeps its room: this is the first thing to give
936
+ way when the panel narrows. */
937
+ flex: 0 1 auto;
938
+ min-width: 0;
939
+ overflow: hidden;
940
+ text-overflow: ellipsis;
941
+ white-space: nowrap;
942
+ color: var(--dstrb-text-muted);
943
+ font-variant-numeric: tabular-nums;
944
+ }
945
+
946
+ .dsh-trash-child-live {
947
+ flex: none;
948
+ padding: 0 5px;
949
+ border-radius: 4px;
950
+ border: 1px solid var(--dstrb-danger-border);
951
+ background-color: var(--dstrb-surface);
952
+ color: var(--dstrb-danger);
953
+ line-height: 1.6;
954
+ white-space: nowrap;
955
+ }
956
+
957
+ /* A 24x24 icon button, always visible: a hover-only affordance does not exist
958
+ on touch, and WCAG 2.5.8 asks for a >=24px target. */
959
+ .dsh-trash-child-delete {
960
+ flex: none;
961
+ display: inline-flex;
962
+ align-items: center;
963
+ justify-content: center;
964
+ width: 24px;
965
+ height: 24px;
966
+ padding: 0;
967
+ border: none;
968
+ border-radius: 6px;
969
+ background: none;
970
+ color: var(--dstrb-danger);
971
+ cursor: pointer;
972
+ transition: background-color 0.12s ease;
973
+ }
974
+
975
+ .dsh-trash-child-delete:hover {
976
+ background-color: var(--dstrb-danger-veil);
977
+ }
978
+
979
+ .dsh-trash-child-delete:focus-visible {
980
+ outline: 2px solid var(--dstrb-accent);
981
+ outline-offset: 1px;
982
+ }
983
+
984
+ .dsh-trash-child-delete:disabled {
985
+ cursor: not-allowed;
986
+ }
987
+
988
+ .dsh-trash-child-delete-icon {
989
+ width: 14px;
990
+ height: 14px;
991
+ margin-right: 0;
992
+ }
993
+
994
+ /* Two placeholder rows: the grouping is fetched after the list paints, so an
995
+ expanded card holds its height instead of jumping when the rows arrive. */
996
+ .dsh-trash-child--skeleton {
997
+ border-radius: 6px;
998
+ background-color: var(--dstrb-veil);
999
+ margin: 3px 0;
1000
+ }
1001
+
1002
+ .dsh-trash-children-more {
1003
+ display: block;
1004
+ width: 100%;
1005
+ padding: 4px 0 0;
1006
+ border: none;
1007
+ background: none;
1008
+ color: var(--dstrb-accent);
1009
+ font-family: inherit;
1010
+ font-size: inherit;
1011
+ text-align: left;
1012
+ cursor: pointer;
1013
+ }
1014
+
1015
+ .dsh-trash-children-empty {
1016
+ padding: 4px 0;
1017
+ color: var(--dstrb-text-muted);
1018
+ }
1019
+
1020
+ /* ── Narrow panel ───────────────────────────────────────────────────
1021
+ The container is the card list, so these fire on the panel's own width.
1022
+ Priority is fixed: the turn count goes first, the timestamp second, then the
1023
+ action LABELS (the icons stay — they keep their aria-label/title), and the
1024
+ sub-session age last. The size badge and the sub-session badge are never
1025
+ hidden: they are counts the row exists to show. */
1026
+ @container (max-width: 360px) {
1027
+ .dsh-trash-card-meta-item--turns {
1028
+ display: none;
1029
+ }
1030
+
1031
+ /* Title first: the age gives way instead of squeezing the name. The row's
1032
+ size and age stay in the TITLE BUTTON's accessible name, which is never
1033
+ hidden — `display: none` here would drop them out of the accessibility
1034
+ tree entirely, and a tooltip is not readable by keyboard or screen
1035
+ reader. */
1036
+ .dsh-trash-child-meta {
1037
+ display: none;
1038
+ }
1039
+ }
1040
+
1041
+ @container (max-width: 320px) {
1042
+ .dsh-trash-card-meta-item--time {
1043
+ display: none;
1044
+ }
1045
+ }
1046
+
1047
+ /* Kept sub-sessions of a main session that is already gone: no restore is
1048
+ offered for them, so the row reads as a bin shell, not as a card. */
1049
+ .dsh-trash-card--orphan {
1050
+ background-color: var(--dstrb-surface-subtle);
1051
+ }
1052
+
1053
+ .dsh-trash-tag-kept {
1054
+ padding: 0 5px;
1055
+ border-radius: 4px;
1056
+ border: 1px solid var(--dstrb-border);
1057
+ background-color: var(--dstrb-surface);
1058
+ color: var(--dstrb-text-muted);
1059
+ white-space: nowrap;
1060
+ }
1061
+
1062
+ /* ------------------------------------------------------------------ */
1063
+ /* Hover info card (left/top set inline by JS, from the hovered TITLE) */
1064
+ /* ------------------------------------------------------------------ */
1065
+ /*
1066
+ * Deliberately NOT the host tooltip language. `.dsh-trash-tooltip-host` is a
1067
+ * short hint ON a button and follows the app's own tooltip tokens
1068
+ * (--dsw-alias-tooltip-*), which are a DARK bubble in both themes — the host's
1069
+ * choice for one-line hints. This card is a small PANEL of the recycle bin that
1070
+ * the user reads and selects text from, so it is painted with the plugin's own
1071
+ * theme surface: white in the light theme, #1f2530 in the dark one, like every
1072
+ * other surface of this panel. The boundary uses the structure token
1073
+ * (4.76:1 light / 5.18:1 dark), not a hairline.
1074
+ */
1075
+ .dsh-trash-tooltip {
1076
+ position: fixed;
1077
+ z-index: 20000;
1078
+ display: flex;
1079
+ flex-direction: column;
1080
+ gap: 6px;
1081
+ max-width: min(380px, 92vw);
1082
+ padding: 10px 12px;
1083
+ border-radius: 8px;
1084
+ background: var(--dstrb-surface);
1085
+ color: var(--dstrb-text-strong);
1086
+ /* The boundary is a real boundary, so it uses the structure token (>=3:1)
1087
+ rather than a hairline that only reads as a smudge; the elevation shadow
1088
+ underneath carries the same message. */
1089
+ border: 1px solid var(--dstrb-structure);
1090
+ box-shadow: var(--dstrb-shadow-tooltip);
1091
+ /* Readable and selectable: the card is content, not a decoration. */
1092
+ pointer-events: auto;
1093
+ user-select: text;
1094
+ -webkit-user-select: text;
1095
+ }
1096
+
1097
+ /* Placement is decided in JS from the measured box; the transform only flips
1098
+ the card to the row's other side. */
1099
+ .dsh-trash-tooltip--above {
1100
+ transform: translateY(-100%);
1101
+ }
1102
+
1103
+ .dsh-trash-tooltip--below {
1104
+ transform: none;
1105
+ }
1106
+
1107
+ .dsh-trash-tooltip-title {
1108
+ font-size: 1em;
1109
+ font-weight: 600;
1110
+ color: inherit;
1111
+ word-break: break-all;
1112
+ }
1113
+
1114
+ .dsh-trash-tooltip-sub {
1115
+ font-size: 0.86em;
1116
+ color: var(--dstrb-text-muted);
1117
+ font-family: var(--dstrb-mono, var(--ds-font-family-code, var(--dstrb-mono-fallback)));
1118
+ word-break: break-all;
1119
+ }
1120
+
1121
+ /* ------------------------------------------------------------------ */
1122
+ /* Footer */
1123
+ /* ------------------------------------------------------------------ */
1124
+
1125
+ .dsh-trash-footer {
1126
+ margin-top: 24px;
1127
+ font-size: 12px;
1128
+ color: var(--dstrb-text-muted);
1129
+ text-align: right;
1130
+ }
1131
+
1132
+ /* ------------------------------------------------------------------ */
1133
+ /* Modals */
1134
+ /* ------------------------------------------------------------------ */
1135
+
1136
+ .dsh-trash-overlay {
1137
+ position: fixed;
1138
+ top: 0;
1139
+ left: 0;
1140
+ right: 0;
1141
+ bottom: 0;
1142
+ background-color: var(--dstrb-overlay);
1143
+ display: flex;
1144
+ justify-content: center;
1145
+ align-items: center;
1146
+ z-index: 10000;
1147
+ }
1148
+
1149
+ .dsh-trash-preview {
1150
+ background-color: var(--dstrb-surface);
1151
+ border-radius: 12px;
1152
+ width: 680px;
1153
+ max-width: 92vw;
1154
+ max-height: 85vh;
1155
+ display: flex;
1156
+ flex-direction: column;
1157
+ box-shadow: var(--dstrb-shadow-modal);
1158
+ overflow: hidden;
1159
+ }
1160
+
1161
+ .dsh-trash-preview-header {
1162
+ padding: 16px 24px;
1163
+ border-bottom: 1px solid var(--dstrb-border-light);
1164
+ display: flex;
1165
+ justify-content: space-between;
1166
+ align-items: center;
1167
+ }
1168
+
1169
+ .dsh-trash-preview-title {
1170
+ font-size: 16px;
1171
+ font-weight: 600;
1172
+ color: var(--dstrb-text-strong);
1173
+ overflow: hidden;
1174
+ text-overflow: ellipsis;
1175
+ white-space: nowrap;
1176
+ }
1177
+
1178
+ .dsh-trash-close {
1179
+ background: none;
1180
+ border: none;
1181
+ font-size: 18px;
1182
+ color: var(--dstrb-text-muted);
1183
+ cursor: pointer;
1184
+ padding: 4px 8px;
1185
+ }
1186
+
1187
+ .dsh-trash-close:hover {
1188
+ color: var(--dstrb-text-muted);
1189
+ }
1190
+
1191
+ .dsh-trash-preview-body {
1192
+ padding: 20px 24px;
1193
+ overflow-y: auto;
1194
+ display: flex;
1195
+ flex-direction: column;
1196
+ gap: 16px;
1197
+ max-height: 70vh;
1198
+ background-color: var(--dstrb-surface-subtle);
1199
+ }
1200
+
1201
+ .dsh-trash-preview-state {
1202
+ text-align: center;
1203
+ padding: 30px;
1204
+ color: var(--dstrb-text-muted);
1205
+ }
1206
+
1207
+ .dsh-trash-modal {
1208
+ background-color: var(--dstrb-surface);
1209
+ border-radius: 12px;
1210
+ padding: 24px;
1211
+ width: 420px;
1212
+ max-width: 92vw;
1213
+ box-shadow: var(--dstrb-shadow-modal-sm);
1214
+ }
1215
+
1216
+ .dsh-trash-modal-title {
1217
+ margin: 0 0 12px 0;
1218
+ font-size: 16px;
1219
+ font-weight: 600;
1220
+ color: var(--dstrb-text-strong);
1221
+ }
1222
+
1223
+ .dsh-trash-modal-body {
1224
+ margin: 0 0 20px 0;
1225
+ font-size: 14px;
1226
+ color: var(--dstrb-text-secondary);
1227
+ line-height: 1.5;
1228
+ }
1229
+
1230
+ /* The cascade dialog names every sub-session it will erase; past a handful the
1231
+ list scrolls inside the dialog rather than growing it. */
1232
+ .dsh-trash-modal-list {
1233
+ margin: -8px 0 20px 0;
1234
+ padding: 6px 10px;
1235
+ max-height: 40vh;
1236
+ overflow-y: auto;
1237
+ border: 1px solid var(--dstrb-border-light);
1238
+ border-radius: 8px;
1239
+ background-color: var(--dstrb-surface-subtle);
1240
+ }
1241
+
1242
+ .dsh-trash-modal-list-item {
1243
+ color: var(--dstrb-text-secondary);
1244
+ line-height: 1.8;
1245
+ overflow: hidden;
1246
+ text-overflow: ellipsis;
1247
+ white-space: nowrap;
1248
+ }
1249
+
1250
+ .dsh-trash-modal-actions {
1251
+ display: flex;
1252
+ justify-content: flex-end;
1253
+ gap: 12px;
1254
+ }
1255
+
1256
+ .dsh-trash-btn-cancel {
1257
+ background-color: var(--dstrb-surface-hover);
1258
+ color: var(--dstrb-text-secondary);
1259
+ border: none;
1260
+ padding: 8px 16px;
1261
+ border-radius: 6px;
1262
+ font-size: 14px;
1263
+ font-family: inherit;
1264
+ cursor: pointer;
1265
+ }
1266
+
1267
+ .dsh-trash-btn-cancel:hover {
1268
+ background-color: var(--dstrb-border);
1269
+ }
1270
+
1271
+ .dsh-trash-btn-confirm {
1272
+ background-color: var(--dstrb-danger);
1273
+ color: var(--dstrb-on-danger);
1274
+ border: none;
1275
+ padding: 8px 16px;
1276
+ border-radius: 6px;
1277
+ font-size: 14px;
1278
+ font-family: inherit;
1279
+ cursor: pointer;
1280
+ }
1281
+
1282
+ .dsh-trash-btn-confirm:hover {
1283
+ background-color: var(--dstrb-danger-strong);
1284
+ }
1285
+
1286
+ /* ------------------------------------------------------------------ */
1287
+ /* Message bubbles (preview modal) */
1288
+ /* ------------------------------------------------------------------ */
1289
+
1290
+ .dsh-trash-message {
1291
+ display: flex;
1292
+ flex-direction: column;
1293
+ gap: 6px;
1294
+ }
1295
+
1296
+ .dsh-trash-message-header {
1297
+ display: flex;
1298
+ align-items: center;
1299
+ gap: 10px;
1300
+ font-size: 12px;
1301
+ }
1302
+
1303
+ .dsh-trash-message-role {
1304
+ font-weight: 600;
1305
+ color: var(--dstrb-text-secondary);
1306
+ }
1307
+
1308
+ .dsh-trash-message-time {
1309
+ color: var(--dstrb-text-muted);
1310
+ }
1311
+
1312
+ .dsh-trash-bubble-user {
1313
+ background-color: var(--dstrb-bubble-user);
1314
+ color: var(--dstrb-text-strong);
1315
+ padding: 12px 16px;
1316
+ border-radius: 12px;
1317
+ border-top-left-radius: 2px;
1318
+ font-size: 14px;
1319
+ line-height: 1.6;
1320
+ white-space: pre-wrap;
1321
+ word-break: break-word;
1322
+ }
1323
+
1324
+ .dsh-trash-bubble-assistant {
1325
+ background-color: var(--dstrb-surface);
1326
+ color: var(--dstrb-text);
1327
+ padding: 14px 18px;
1328
+ border-radius: 12px;
1329
+ border-top-left-radius: 2px;
1330
+ border: 1px solid var(--dstrb-border);
1331
+ font-size: 14px;
1332
+ line-height: 1.6;
1333
+ white-space: pre-wrap;
1334
+ word-break: break-word;
1335
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
1336
+ }
1337
+
1338
+ /* ------------------------------------------------------------------ */
1339
+ /* Lightweight markdown renderer */
1340
+ /* ------------------------------------------------------------------ */
1341
+
1342
+ .dsh-trash-md {
1343
+ white-space: normal;
1344
+ word-break: break-word;
1345
+ }
1346
+
1347
+ .dsh-trash-md p {
1348
+ margin: 0 0 10px 0;
1349
+ line-height: 1.65;
1350
+ }
1351
+
1352
+ .dsh-trash-md h1 { font-size: 20px; font-weight: 600; margin: 16px 0 8px; line-height: 1.4; color: var(--dstrb-text-strong); }
1353
+ .dsh-trash-md h2 { font-size: 18px; font-weight: 600; margin: 14px 0 8px; line-height: 1.4; color: var(--dstrb-text-strong); }
1354
+ .dsh-trash-md h3 { font-size: 16px; font-weight: 600; margin: 12px 0 6px; line-height: 1.4; color: var(--dstrb-text-strong); }
1355
+ .dsh-trash-md h4 { font-size: 15px; font-weight: 600; margin: 12px 0 6px; line-height: 1.4; color: var(--dstrb-text-strong); }
1356
+ .dsh-trash-md h5 { font-size: 14px; font-weight: 600; margin: 10px 0 4px; line-height: 1.4; color: var(--dstrb-text-strong); }
1357
+ .dsh-trash-md h6 { font-size: 13px; font-weight: 600; margin: 10px 0 4px; line-height: 1.4; color: var(--dstrb-text-strong); }
1358
+
1359
+ .dsh-trash-md ul,
1360
+ .dsh-trash-md ol {
1361
+ margin: 6px 0 10px;
1362
+ padding-left: 22px;
1363
+ }
1364
+
1365
+ .dsh-trash-md li {
1366
+ margin: 3px 0;
1367
+ line-height: 1.6;
1368
+ }
1369
+
1370
+ .dsh-trash-md blockquote {
1371
+ border-left: 3px solid var(--dstrb-quote-border);
1372
+ margin: 8px 0;
1373
+ padding: 4px 12px;
1374
+ color: var(--dstrb-text-secondary);
1375
+ background-color: var(--dstrb-quote-bg);
1376
+ }
1377
+
1378
+ .dsh-trash-md hr {
1379
+ border: none;
1380
+ border-top: 1px solid var(--dstrb-border);
1381
+ margin: 14px 0;
1382
+ }
1383
+
1384
+ .dsh-trash-md-code-inline {
1385
+ background-color: var(--dstrb-code-inline-bg);
1386
+ padding: 2px 5px;
1387
+ border-radius: 4px;
1388
+ font-family: var(--dstrb-mono, var(--ds-font-family-code, var(--dstrb-mono-fallback)));
1389
+ font-size: 0.9em;
1390
+ color: var(--dstrb-code-inline-text);
1391
+ }
1392
+
1393
+ .dsh-trash-md-codeblock {
1394
+ background-color: var(--dstrb-codeblock-bg);
1395
+ color: var(--dstrb-codeblock-text);
1396
+ padding: 12px 14px;
1397
+ border-radius: 8px;
1398
+ overflow-x: auto;
1399
+ margin: 10px 0;
1400
+ font-family: var(--dstrb-mono, var(--ds-font-family-code, var(--dstrb-mono-fallback)));
1401
+ font-size: 13px;
1402
+ line-height: 1.5;
1403
+ white-space: pre-wrap;
1404
+ word-break: break-word;
1405
+ }
1406
+
1407
+ .dsh-trash-md-codeblock-content {
1408
+ font-family: inherit;
1409
+ font-size: inherit;
1410
+ white-space: pre-wrap;
1411
+ word-break: break-word;
1412
+ }
1413
+
1414
+ .dsh-trash-md-lang {
1415
+ font-size: 11px;
1416
+ color: var(--dstrb-codeblock-lang);
1417
+ text-transform: uppercase;
1418
+ letter-spacing: 0.5px;
1419
+ margin-bottom: 6px;
1420
+ }
1421
+
1422
+ .dsh-trash-md-link {
1423
+ color: var(--dstrb-accent);
1424
+ text-decoration: underline;
1425
+ }
1426
+
1427
+ .dsh-trash-md-table-wrap {
1428
+ overflow-x: auto;
1429
+ margin: 10px 0;
1430
+ }
1431
+
1432
+ .dsh-trash-md-table {
1433
+ border-collapse: collapse;
1434
+ width: 100%;
1435
+ font-size: 13px;
1436
+ }
1437
+
1438
+ .dsh-trash-md-th {
1439
+ border: 1px solid var(--dstrb-border);
1440
+ padding: 6px 10px;
1441
+ background-color: var(--dstrb-surface-subtle);
1442
+ font-weight: 600;
1443
+ text-align: left;
1444
+ }
1445
+
1446
+ .dsh-trash-md-td {
1447
+ border: 1px solid var(--dstrb-border);
1448
+ padding: 6px 10px;
1449
+ }
1450
+
1451
+ /* ------------------------------------------------------------------ */
1452
+ /* Toast layer — host-matched surface */
1453
+ /* ------------------------------------------------------------------ */
1454
+ /*
1455
+ * Ported from @deepseek-ai/dsh-client-ui-primitives Toast.module.css. This
1456
+ * hand-written bundle cannot import that package: primitives declares no
1457
+ * `dsh.client`, so Vite inlines it into each consumer and it is neither a
1458
+ * platform seed word nor a registered module-row factory — `require()` would
1459
+ * miss the module table. Porting the spec is the only way to match it.
1460
+ *
1461
+ * Two things changed beyond styling:
1462
+ * - Placement is TOP-CENTRE, not a bottom-right card.
1463
+ * - The surface is dark in BOTH themes. The old --dstrb-toast-* pair flipped
1464
+ * to a LIGHT panel in dark mode, i.e. the exact opposite of the host.
1465
+ * The host tokens lead with the plugin's own as fallbacks so older cohorts,
1466
+ * which publish no --dsw-* palette, still render a readable toast.
1467
+ */
1468
+
1469
+ .dsh-trash-toast-container {
1470
+ position: fixed;
1471
+ top: 40px;
1472
+ left: 50%;
1473
+ transform: translateX(-50%);
1474
+ /* Above the 1000 the image lightbox backdrop uses, as the host does. */
1475
+ z-index: 1100;
1476
+ display: flex;
1477
+ flex-direction: column;
1478
+ align-items: center;
1479
+ gap: 8px;
1480
+ /* Announcements never intercept clicks; only the action text does. */
1481
+ pointer-events: none;
1482
+ }
1483
+
1484
+ .dsh-trash-toast {
1485
+ display: flex;
1486
+ align-items: center;
1487
+ gap: 10px;
1488
+ width: max-content;
1489
+ max-width: min(640px, calc(100vw - 48px));
1490
+ padding: 12px 16px;
1491
+ border-radius: var(--dsw-radius-lg, 12px);
1492
+ background: var(--dsw-alias-toast-bg, var(--dstrb-toast-bg));
1493
+ color: var(--dsw-alias-toast-label, var(--dstrb-toast-text));
1494
+ font-size: 14px;
1495
+ line-height: 22px;
1496
+ box-shadow: var(--dsw-shadow-lv3, var(--dstrb-shadow-toast));
1497
+ pointer-events: none;
1498
+ /* One value drives both the fade delay and the unmount timer below. */
1499
+ animation:
1500
+ dsh-trash-toast-in 160ms ease-out,
1501
+ dsh-trash-toast-fade 1000ms ease var(--dsh-toast-hold, 3000ms) forwards;
1502
+ }
1503
+
1504
+ @keyframes dsh-trash-toast-in {
1505
+ from {
1506
+ opacity: 0;
1507
+ transform: translateY(-6px);
1508
+ }
1509
+
1510
+ to {
1511
+ opacity: 1;
1512
+ transform: translateY(0);
1513
+ }
1514
+ }
1515
+
1516
+ @keyframes dsh-trash-toast-fade {
1517
+ to {
1518
+ opacity: 0;
1519
+ visibility: hidden;
1520
+ }
1521
+ }
1522
+
1523
+ /* Reduced motion drops the slide-in; the delayed fade still ends the banner
1524
+ before the timed unmount. */
1525
+ @media (prefers-reduced-motion: reduce) {
1526
+ .dsh-trash-toast {
1527
+ animation: dsh-trash-toast-fade 1000ms ease var(--dsh-toast-hold, 3000ms) forwards;
1528
+ }
1529
+ }
1530
+
1531
+ .dsh-trash-toast-undo {
1532
+ display: inline;
1533
+ padding: 0;
1534
+ border: 0;
1535
+ background: none;
1536
+ /* deepseek-400 reads as the action against the always-dark surface. */
1537
+ color: var(--dsw-static-deepseek-400, var(--dstrb-toast-btn-text));
1538
+ font: inherit;
1539
+ cursor: pointer;
1540
+ margin-inline: 3px;
1541
+ /* The banner stays click-through; only this takes the pointer. */
1542
+ pointer-events: auto;
1543
+ }
1544
+
1545
+ .dsh-trash-toast-undo:hover {
1546
+ opacity: 0.8;
1547
+ }
1548
+
1549
+ /* ------------------------------------------------------------------ */
1550
+ /* Conversation-header delete action */
1551
+ /* ------------------------------------------------------------------ */
1552
+
1553
+ .dsh-trash-header-action {
1554
+ display: inline-flex;
1555
+ align-items: center;
1556
+ justify-content: center;
1557
+ background: none;
1558
+ border: none;
1559
+ padding: 5px;
1560
+ width: 26px;
1561
+ height: 26px;
1562
+ border-radius: 7px;
1563
+ line-height: 1;
1564
+ cursor: pointer;
1565
+ color: var(--dsw-alias-label-tertiary, var(--dstrb-text-muted));
1566
+ opacity: 0.85;
1567
+ transition: color 0.12s ease, background 0.12s ease, opacity 0.12s ease;
1568
+ }
1569
+
1570
+ .dsh-trash-header-action:hover {
1571
+ color: var(--dstrb-danger);
1572
+ background: var(--dstrb-veil);
1573
+ opacity: 1;
1574
+ }
1575
+
1576
+ /* One-line label tooltip for the header action, replacing the native `title`
1577
+ attribute: an OS-drawn bubble next to the host's themed tooltips read as
1578
+ "unstyled plugin", and it cannot follow the palette or the motion setting.
1579
+ Ported from @deepseek-ai/dsh-client-ui-primitives Tooltip.module.css for the
1580
+ same reason the toast is (the package is inlined by Vite, so it cannot be
1581
+ required from a hand-written bundle).
1582
+ `translateX(-100%)` is the host's `side: bottom` + `align: end` transform,
1583
+ so `left` carries the anchor's RIGHT edge. */
1584
+ .dsh-trash-tooltip-host {
1585
+ position: fixed;
1586
+ z-index: 100;
1587
+ width: max-content;
1588
+ max-width: 50vw;
1589
+ padding: 3px 7px;
1590
+ border-radius: var(--dsw-radius-sm, 4px);
1591
+ background: var(--dsw-alias-tooltip-bg, var(--dstrb-tooltip-bg));
1592
+ color: var(--dsw-static-neutral-bluish-00, var(--dstrb-tooltip-text));
1593
+ font-size: 13px;
1594
+ line-height: 20px;
1595
+ white-space: pre-line;
1596
+ /* Unbreakable tokens (paths, URLs) must not push past max-width. */
1597
+ overflow-wrap: break-word;
1598
+ pointer-events: none;
1599
+ transform: translateX(-100%);
1600
+ animation: dsh-trash-tooltip-in 150ms var(--ds-ease-in-out, ease-in-out);
1601
+ }
1602
+
1603
+ @keyframes dsh-trash-tooltip-in {
1604
+ from {
1605
+ opacity: 0;
1606
+ }
1607
+ }
1608
+
1609
+ @media (prefers-reduced-motion: reduce) {
1610
+ .dsh-trash-tooltip-host {
1611
+ animation: none;
1612
+ }
1613
+ }