@jcy2387/dsh-conversation-ui 0.1.0-rc.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +123 -0
  3. package/README.md +123 -0
  4. package/conversation-ui-off.yml +3 -0
  5. package/cordis.patch.yml +10 -0
  6. package/lib/client.js +3672 -0
  7. package/lib/client.js.map +1 -0
  8. package/lib/index.js +302 -0
  9. package/package.json +182 -0
  10. package/src/boot-config.ts +28 -0
  11. package/src/client/AnimatedDisclosure.tsx +89 -0
  12. package/src/client/ConversationCard.module.css +253 -0
  13. package/src/client/ConversationCard.tsx +122 -0
  14. package/src/client/DeliverablesCard.module.css +187 -0
  15. package/src/client/DeliverablesCard.tsx +142 -0
  16. package/src/client/FollowHost.tsx +25 -0
  17. package/src/client/ToolActivityGroup.tsx +341 -0
  18. package/src/client/TurnPreludeUserNodeView.tsx +258 -0
  19. package/src/client/TurnProcessMember.tsx +92 -0
  20. package/src/client/TypewriterAssistantNodeView.module.css +1260 -0
  21. package/src/client/TypewriterAssistantNodeView.tsx +390 -0
  22. package/src/client/TypewriterToolNodeView.tsx +916 -0
  23. package/src/client/conversation-ui-card-controller.ts +162 -0
  24. package/src/client/conversation-ui-settings-api.ts +65 -0
  25. package/src/client/deliverables.ts +263 -0
  26. package/src/client/index.ts +242 -0
  27. package/src/client/locales.ts +74 -0
  28. package/src/client/teleprompterGlide.ts +590 -0
  29. package/src/client/turnElapsed.ts +27 -0
  30. package/src/client/turnProcessFold.ts +224 -0
  31. package/src/client/useConversationContent.ts +471 -0
  32. package/src/client/useFpsGuard.ts +84 -0
  33. package/src/config.ts +44 -0
  34. package/src/css-modules.d.ts +9 -0
  35. package/src/index.ts +2 -0
  36. package/src/package-meta.ts +39 -0
  37. package/src/plugin.ts +155 -0
  38. package/src/profile-installation.ts +121 -0
  39. package/src/settings-api.ts +33 -0
  40. package/src/settings.ts +30 -0
@@ -0,0 +1,1260 @@
1
+ .root {
2
+ display: flex;
3
+ flex-direction: column;
4
+ min-width: 0;
5
+ color: var(--dsw-alias-label-primary);
6
+ font-size: 16px;
7
+ line-height: 28px;
8
+ animation: dsh-codex-stream-enter 140ms ease-out both;
9
+ }
10
+
11
+ .body {
12
+ display: flex;
13
+ flex-direction: column;
14
+ gap: 16px;
15
+ min-width: 0;
16
+ }
17
+
18
+ .markdownFlow {
19
+ min-width: 0;
20
+ }
21
+
22
+ /* Final-answer code blocks keep their source lines intact and scroll
23
+ * horizontally instead of breaking paths/commands at arbitrary characters.
24
+ * The bounded viewport prevents a long log from pushing the answer away. */
25
+ .root .markdownFlow :global(pre) {
26
+ max-height: min(60vh, 480px);
27
+ overflow: auto;
28
+ overflow-wrap: normal;
29
+ white-space: pre;
30
+ word-break: normal;
31
+ tab-size: 2;
32
+ }
33
+
34
+ .root .markdownFlow :global(pre) :global(code) {
35
+ white-space: inherit;
36
+ word-break: inherit;
37
+ }
38
+
39
+ /* MarkdownText owns spacing within one assistant text block. Its default
40
+ * adjacent 16px margins collapse, which leaves a paragraph visually attached
41
+ * to the final row of a long path list. Keep this boundary at one readable
42
+ * text-line rhythm without changing list nesting or other block pairs. */
43
+ .markdownFlow > div > :where(ul, ol) + p {
44
+ margin-block-start: 24px;
45
+ }
46
+
47
+ /* Codex keeps progress prose and the final answer in one calm document flow.
48
+ * Phase attributes preserve their different semantics without adding chat
49
+ * bubbles or labels that would compete with the answer itself. */
50
+ .root[data-stream-phase='streaming'],
51
+ .root[data-stream-phase='process'] {
52
+ color: var(--dsw-alias-label-primary);
53
+ }
54
+
55
+ /* Progress prose is not the final answer: keep its Markdown root at the same
56
+ * size as ordinary assistant text, while emphasis does not make a status
57
+ * sentence look heavier than the surrounding streamed text. */
58
+ .root[data-stream-phase='streaming'] .markdownFlow > :global(div),
59
+ .root[data-stream-phase='process'] .markdownFlow > :global(div) {
60
+ font-family: inherit;
61
+ font-size: 16px;
62
+ font-weight: 400;
63
+ line-height: 28px;
64
+ }
65
+
66
+ .root[data-stream-phase='streaming'] .markdownFlow :global(strong),
67
+ .root[data-stream-phase='process'] .markdownFlow :global(strong) {
68
+ /* MarkdownText's own `.markdown strong` rule is equally specific and can
69
+ * win when its stylesheet is injected after this module. Think/process
70
+ * emphasis is intentionally quiet, so make that contract order-independent. */
71
+ font-weight: 400 !important;
72
+ }
73
+
74
+ .root[data-stream-phase='final'] {
75
+ margin-block-start: 2px;
76
+ }
77
+
78
+ .turnFoldRow {
79
+ display: flex;
80
+ flex-direction: column;
81
+ align-items: stretch;
82
+ gap: 6px;
83
+ margin-block: 0 14px;
84
+ color: var(--dsw-alias-label-secondary);
85
+ font-size: 14px;
86
+ line-height: 24px;
87
+ }
88
+
89
+ .turnPrelude {
90
+ margin-block-start: 16px;
91
+ }
92
+
93
+ .turnWaiting {
94
+ width: fit-content;
95
+ margin-block-start: 0;
96
+ background: linear-gradient(
97
+ 105deg,
98
+ var(--dsw-alias-label-tertiary) 15%,
99
+ var(--dsw-alias-label-primary) 42%,
100
+ var(--dsw-alias-label-tertiary) 68%
101
+ );
102
+ background-clip: text;
103
+ background-size: 220% 100%;
104
+ color: transparent;
105
+ font-size: 14px;
106
+ line-height: 24px;
107
+ animation: dsh-turn-waiting-shimmer 1.8s linear infinite;
108
+ }
109
+
110
+ @keyframes dsh-turn-waiting-shimmer {
111
+ from { background-position: 130% 0; }
112
+ to { background-position: -90% 0; }
113
+ }
114
+
115
+ /* Light Stream owns the immediate Turn status. The native direct-child
116
+ * placeholder would otherwise duplicate the waiting signal at the flow tip. */
117
+ :global([data-chat-flow] > [role='status']) {
118
+ display: none !important;
119
+ }
120
+
121
+ .turnFoldRow::after {
122
+ width: 100%;
123
+ height: 1px;
124
+ flex: none;
125
+ background: var(--dsw-alias-border-l2);
126
+ content: '';
127
+ }
128
+
129
+ .turnFoldButton,
130
+ .turnFoldLabel {
131
+ display: inline-flex;
132
+ align-items: center;
133
+ align-self: flex-start;
134
+ gap: 6px;
135
+ flex: none;
136
+ }
137
+
138
+ .turnFoldButton {
139
+ padding: 2px 0;
140
+ border: 0;
141
+ border-radius: 4px;
142
+ background: transparent;
143
+ color: inherit;
144
+ cursor: pointer;
145
+ font: inherit;
146
+ }
147
+
148
+ .turnFoldButton:hover {
149
+ color: var(--dsw-alias-label-primary);
150
+ }
151
+
152
+ .turnFoldButton:focus-visible {
153
+ outline: 2px solid var(--dsw-alias-border-l3);
154
+ outline-offset: 3px;
155
+ }
156
+
157
+ .turnFoldButton > svg {
158
+ flex: none;
159
+ }
160
+
161
+ /* Author-level display rules on Assistant and Tool rows override the browser's
162
+ * built-in `[hidden]` stylesheet. Keep every registered process member truly
163
+ * out of layout while its Turn is collapsed. */
164
+ [data-turn-process][hidden] {
165
+ display: none !important;
166
+ }
167
+
168
+ /* ChatView keeps each flat node in its own flow row. Hiding only the inner
169
+ * process member would leave the parent row-gap behind, producing a large
170
+ * blank band. Preserve the boundary row and the mixed final-answer row, but
171
+ * remove every other collapsed process anchor from layout. */
172
+ :global([data-chat-anchor-key]:has([data-turn-process][hidden]):not(:has([data-turn-fold-row])):not(:has([data-stream-phase='final'])):not(:has([data-stream-tool-group-frame]))) {
173
+ display: none !important;
174
+ }
175
+
176
+ /* Portaled members live inside the first row's group frame. Remove their empty
177
+ * original Chat anchors so the host row-gap cannot leave phantom bands. */
178
+ :global([data-chat-anchor-key]:has([data-tool-group-proxy])) {
179
+ display: none !important;
180
+ }
181
+
182
+ /* A collapsed Turn still removes the group host itself; a collapsed activity
183
+ * group keeps its heading and only removes the command rows below it. */
184
+ :global([data-chat-anchor-key]:has([data-stream-tool-group-anchor][hidden])) {
185
+ display: none !important;
186
+ }
187
+
188
+ .root[data-stream-phase='interrupted'] {
189
+ color: var(--dsw-alias-label-secondary);
190
+ }
191
+
192
+ /* Every non-human Chat Node becomes one compact event row. Tool renderers
193
+ * keep their own icons, titles, summaries, and disclosure bodies; this layer
194
+ * only supplies the shared Codex timeline rhythm and entry transition. */
195
+ .eventRow {
196
+ min-width: 0;
197
+ color: var(--dsw-alias-label-secondary);
198
+ font-size: 14px;
199
+ line-height: 24px;
200
+ animation: dsh-codex-stream-enter 140ms ease-out both;
201
+ }
202
+
203
+ .eventRow[data-stream-node='tool-call'],
204
+ .eventRow[data-stream-node='workflow-run'],
205
+ .eventRow[data-stream-node='model-retry'],
206
+ .eventRow[data-stream-node='compaction'],
207
+ .eventRow[data-stream-node='manual-compaction'] {
208
+ margin-block: 2px;
209
+ }
210
+
211
+ .eventRow[data-stream-state='running'] {
212
+ color: var(--dsw-alias-label-tertiary);
213
+ }
214
+
215
+ /* Context compaction is a status marker, not a second disclosure surface.
216
+ * Keep the native icon/title/summary but remove the meaningless chevron and
217
+ * let the wrapper's click capture keep the row non-expandable. */
218
+ .eventRow[data-stream-node='compaction'] [data-compaction-disclosure],
219
+ .eventRow[data-stream-node='manual-compaction'] [data-compaction-disclosure] {
220
+ display: none !important;
221
+ }
222
+
223
+ .eventRow[data-stream-node='compaction'] :global(button:has([data-compaction-icon])),
224
+ .eventRow[data-stream-node='manual-compaction'] :global(button:has([data-compaction-icon])) {
225
+ cursor: default;
226
+ }
227
+
228
+ .eventRow[data-stream-node='compaction'] :global(button:has([data-compaction-icon])):hover,
229
+ .eventRow[data-stream-node='compaction'] :global(button:has([data-compaction-icon])):focus-visible,
230
+ .eventRow[data-stream-node='manual-compaction'] :global(button:has([data-compaction-icon])):hover,
231
+ .eventRow[data-stream-node='manual-compaction'] :global(button:has([data-compaction-icon])):focus-visible {
232
+ background: transparent !important;
233
+ }
234
+
235
+ .eventRow[data-stream-node='compaction'] [data-compaction-icon],
236
+ .eventRow[data-stream-node='manual-compaction'] [data-compaction-icon] {
237
+ color: var(--dsw-alias-label-secondary) !important;
238
+ opacity: 1 !important;
239
+ }
240
+
241
+ .eventRow[data-stream-node='compaction'] :global(button:has([data-compaction-icon])),
242
+ .eventRow[data-stream-node='manual-compaction'] :global(button:has([data-compaction-icon])) {
243
+ color: var(--dsw-alias-label-secondary) !important;
244
+ }
245
+
246
+ .eventRow[data-stream-node='compaction'] :global(button:has([data-compaction-icon])) > span,
247
+ .eventRow[data-stream-node='manual-compaction'] :global(button:has([data-compaction-icon])) > span {
248
+ color: var(--dsw-alias-label-secondary) !important;
249
+ }
250
+
251
+ /* The native compaction node can be mounted outside the overlay wrapper in
252
+ * paged/history surfaces. Match its unique data marker directly as well, so
253
+ * its title and summary use the same tone as the context-injection row. */
254
+ :global(button:has([data-compaction-icon])),
255
+ :global(button:has([data-compaction-icon])) > span {
256
+ color: var(--dsw-alias-label-secondary) !important;
257
+ opacity: 1 !important;
258
+ }
259
+
260
+ /* Consecutive same-kind tools are mounted into one shared activity frame. The
261
+ * frame owns one heading and one disclosure affordance; its members retain the
262
+ * native Harness Tool rows so command/file details remain independently useful. */
263
+ .toolGroupAnchor {
264
+ min-width: 0;
265
+ }
266
+
267
+ .toolGroupFrame {
268
+ min-width: 0;
269
+ margin-block: 2px 8px;
270
+ overflow: hidden;
271
+ background: transparent;
272
+ }
273
+
274
+ .toolGroupHeader {
275
+ position: relative;
276
+ display: flex;
277
+ width: 100%;
278
+ min-height: 28px;
279
+ align-items: center;
280
+ gap: 6px;
281
+ padding: 2px 8px 2px 22px;
282
+ border: 0;
283
+ background: transparent;
284
+ color: var(--dsw-alias-label-secondary);
285
+ cursor: pointer;
286
+ font: inherit;
287
+ font-size: 14px;
288
+ line-height: 24px;
289
+ text-align: start;
290
+ }
291
+
292
+ .toolGroupHeader:hover {
293
+ color: var(--dsw-alias-label-primary);
294
+ background: transparent;
295
+ }
296
+
297
+ .toolGroupHeader:focus-visible {
298
+ outline: none;
299
+ background: transparent;
300
+ }
301
+
302
+ .toolGroupHeaderIcon {
303
+ position: absolute;
304
+ inset-block-start: 50%;
305
+ inset-inline-start: 0;
306
+ display: inline-flex;
307
+ transform: translateY(-50%);
308
+ width: 14px;
309
+ height: 14px;
310
+ align-items: center;
311
+ justify-content: center;
312
+ flex: none;
313
+ color: var(--dsw-alias-label-tertiary);
314
+ }
315
+
316
+ .toolGroupHeaderGlyph {
317
+ display: inline-flex;
318
+ width: 14px;
319
+ height: 14px;
320
+ align-items: center;
321
+ justify-content: center;
322
+ }
323
+
324
+ .toolGroupHeaderLabel {
325
+ min-width: 0;
326
+ overflow: hidden;
327
+ text-overflow: ellipsis;
328
+ white-space: nowrap;
329
+ }
330
+
331
+ .toolGroupHeaderChevron {
332
+ display: inline-flex;
333
+ margin-inline-start: 0;
334
+ flex: none;
335
+ color: var(--dsw-alias-label-tertiary);
336
+ }
337
+
338
+ .editStats {
339
+ flex: none;
340
+ margin-inline-start: 4px;
341
+ color: var(--dsw-alias-label-tertiary);
342
+ font-size: 14px;
343
+ line-height: 24px;
344
+ white-space: nowrap;
345
+ }
346
+
347
+ .toolGroupFrame > .eventRow[data-stream-tool-group-member],
348
+ .toolGroupFrame > [data-tool-group-member] > .eventRow[data-stream-tool-group-member] {
349
+ margin-block: 0;
350
+ padding-inline: 8px 6px;
351
+ border: 0;
352
+ border-radius: 0;
353
+ background: transparent;
354
+ }
355
+
356
+ .toolGroupFrame .toolSemanticIcon,
357
+ .toolGroupFrame .streamSemanticIcon {
358
+ /* An expanded Bash row grows with its terminal output. Keep the semantic
359
+ * glyph anchored to the row header instead of centering it in the whole
360
+ * expanded card. */
361
+ inset-block-start: 4px;
362
+ transform: none;
363
+ }
364
+
365
+ .toolGroupFrame[data-stream-tool-group-collapsed]
366
+ > .eventRow[data-stream-tool-group-member] > :global([data-chat-call-id]),
367
+ .toolGroupFrame[data-stream-tool-group-collapsed]
368
+ > .eventRow[data-stream-tool-group-member] > * > :global([data-sample='bash']),
369
+ .toolGroupFrame[data-stream-tool-group-collapsed]
370
+ > [data-tool-group-member] > .eventRow[data-stream-tool-group-member] > :global([data-chat-call-id]),
371
+ .toolGroupFrame[data-stream-tool-group-collapsed]
372
+ > [data-tool-group-member] > .eventRow[data-stream-tool-group-member] > * > :global([data-sample='bash']) {
373
+ display: none !important;
374
+ }
375
+
376
+ .toolGroupFrame[data-stream-tool-group-collapsed]
377
+ > .eventRow[data-stream-tool-group-member] > .toolSemanticIcon,
378
+ .toolGroupFrame[data-stream-tool-group-collapsed]
379
+ > [data-tool-group-member] > .eventRow[data-stream-tool-group-member] > .toolSemanticIcon {
380
+ display: none;
381
+ }
382
+
383
+ .toolGroupFrame[data-stream-tool-group-collapsed]
384
+ > .eventRow[data-stream-tool-group-member] > :not(.toolSemanticIcon):not(.streamSemanticIcon),
385
+ .toolGroupFrame[data-stream-tool-group-collapsed]
386
+ > [data-tool-group-member] > .eventRow[data-stream-tool-group-member] > :not(.toolSemanticIcon):not(.streamSemanticIcon) {
387
+ display: none !important;
388
+ }
389
+
390
+ .eventRow :global([data-disclosure-row]) {
391
+ min-width: 0;
392
+ }
393
+
394
+ /* Codex-style Tool chrome: the semantic glyph is a stable leading landmark;
395
+ * disclosure state is a separate trailing affordance. The Harness ToolRow
396
+ * still owns click/keyboard state and the expanded body—we only rearrange its
397
+ * existing header geometry inside the root call. */
398
+ .eventRow[data-stream-tool-row] {
399
+ position: relative;
400
+ }
401
+
402
+ /* A Tool remains the visible activity handoff after its result arrives and
403
+ * until the Agent publishes the next row. Sweep only the root header so an
404
+ * expanded result card is never washed by the progress treatment. */
405
+ .eventRow[data-stream-tool-row][data-stream-progress='active']
406
+ > :global([data-chat-call-id])
407
+ > :global([data-slot='tool.call.toolview'])
408
+ > :global([data-tool])
409
+ > *
410
+ > :global([data-disclosure-row]),
411
+ .eventRow[data-stream-tool-row][data-stream-progress='active']
412
+ > :global([data-chat-call-id])
413
+ > :global([data-slot='tool.call.toolview'])
414
+ > *
415
+ > :global([data-sample='bash']) {
416
+ position: relative;
417
+ overflow: hidden;
418
+ }
419
+
420
+ .eventRow[data-stream-tool-row][data-stream-progress='active']
421
+ > :global([data-chat-call-id])
422
+ > :global([data-slot='tool.call.toolview'])
423
+ > :global([data-tool])
424
+ > *
425
+ > :global([data-disclosure-row])::after,
426
+ .eventRow[data-stream-tool-row][data-stream-progress='active']
427
+ > :global([data-chat-call-id])
428
+ > :global([data-slot='tool.call.toolview'])
429
+ > *
430
+ > :global([data-sample='bash'])::after {
431
+ position: absolute;
432
+ inset-block: 0;
433
+ left: -300px;
434
+ width: 300px;
435
+ background: linear-gradient(
436
+ 90deg,
437
+ transparent 0%,
438
+ color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%,
439
+ transparent 100%
440
+ );
441
+ animation: dsh-conversation-ui-think-sweep 2.6s ease-out infinite;
442
+ content: '';
443
+ pointer-events: none;
444
+ }
445
+
446
+ .toolSemanticIcon,
447
+ .streamSemanticIcon {
448
+ position: absolute;
449
+ z-index: 1;
450
+ inset-block-start: 4px;
451
+ inset-inline-start: 0;
452
+ display: inline-flex;
453
+ width: 14px;
454
+ height: 14px;
455
+ align-items: center;
456
+ justify-content: center;
457
+ color: var(--dsw-alias-label-tertiary);
458
+ pointer-events: none;
459
+ }
460
+
461
+ /* Harness-native rows (context injection, compaction, retry, and future
462
+ * disclosure-backed nodes) keep their semantic glyph leading while the
463
+ * disclosure affordance is always the final flex item. The core DisclosureRow
464
+ * swaps its leading icon for a chevron when open; an owned semantic overlay
465
+ * keeps context rows stable in both states. */
466
+ .eventRow[data-stream-semantic-icon-owned] {
467
+ position: relative;
468
+ }
469
+
470
+ .eventRow[data-stream-native-disclosure]
471
+ > :not(.streamSemanticIcon)
472
+ > :global([data-disclosure-row])[aria-expanded]
473
+ > :first-child {
474
+ display: contents;
475
+ }
476
+
477
+ .eventRow[data-stream-native-disclosure]
478
+ > :not(.streamSemanticIcon)
479
+ > :global([data-disclosure-row])[aria-expanded]
480
+ > :first-child
481
+ > :first-child:not(:last-child) {
482
+ opacity: 1;
483
+ }
484
+
485
+ .eventRow[data-stream-semantic-icon-owned]
486
+ > :not(.streamSemanticIcon)
487
+ > :global([data-disclosure-row])[aria-expanded] {
488
+ padding-inline-start: 22px;
489
+ }
490
+
491
+ .eventRow[data-stream-semantic-icon-owned]
492
+ > :not(.streamSemanticIcon)
493
+ > :global([data-disclosure-row])[aria-expanded]
494
+ > :first-child
495
+ > :first-child:not(:last-child) {
496
+ display: none;
497
+ }
498
+
499
+ .eventRow[data-stream-native-disclosure]
500
+ > :not(.streamSemanticIcon)
501
+ > :global([data-disclosure-row])[aria-expanded]
502
+ > :first-child
503
+ > svg {
504
+ position: static;
505
+ order: 4;
506
+ flex: none;
507
+ margin: 0 0 0 6px;
508
+ opacity: 0;
509
+ color: var(--dsw-alias-label-secondary);
510
+ transition: opacity 100ms ease, transform 120ms ease;
511
+ }
512
+
513
+ .eventRow[data-stream-native-disclosure]
514
+ > :not(.streamSemanticIcon)
515
+ > :global([data-disclosure-row])[aria-expanded]:hover
516
+ > :first-child
517
+ > svg,
518
+ .eventRow[data-stream-native-disclosure]
519
+ > :not(.streamSemanticIcon)
520
+ > :global([data-disclosure-row])[aria-expanded]:focus-visible
521
+ > :first-child
522
+ > svg {
523
+ opacity: 1;
524
+ }
525
+
526
+ .eventRow[data-stream-native-disclosure]
527
+ > :not(.streamSemanticIcon)
528
+ > :global([data-disclosure-row])[aria-expanded='false']
529
+ > :first-child
530
+ > svg {
531
+ transform: rotate(-90deg);
532
+ }
533
+
534
+ .eventRow[data-stream-native-disclosure]
535
+ > :not(.streamSemanticIcon)
536
+ > :global([data-disclosure-row])[aria-expanded='true']
537
+ > :first-child
538
+ > svg {
539
+ transform: rotate(0deg);
540
+ }
541
+
542
+ /* Root ToolCallBranch -> ToolRow -> DisclosureRow -> header. Keeping the
543
+ * direct-child chain prevents a parent tool from restyling nested subcalls. */
544
+ .eventRow[data-stream-tool-row]
545
+ > :global([data-chat-call-id])
546
+ > :global([data-slot='tool.call.toolview'])
547
+ > :global([data-tool])
548
+ > *
549
+ > :global([data-disclosure-row]) {
550
+ padding-inline-start: 22px;
551
+ }
552
+
553
+ /* A non-expandable row needs no disclosure control; its original leading
554
+ * glyph is hidden because the semantic overlay above now owns that position. */
555
+ .eventRow[data-stream-tool-row]
556
+ > :global([data-chat-call-id])
557
+ > :global([data-slot='tool.call.toolview'])
558
+ > :global([data-tool])
559
+ > *
560
+ > :global([data-disclosure-row]):not([aria-expanded])
561
+ > :first-child {
562
+ display: none;
563
+ }
564
+
565
+ /* For expandable rows, flatten the old leading slot into the header. Its
566
+ * first child is the hover-only idle icon; the last svg is the real Harness
567
+ * chevron, which moves behind title/summary as layout order 4. */
568
+ .eventRow[data-stream-tool-row]
569
+ > :global([data-chat-call-id])
570
+ > :global([data-slot='tool.call.toolview'])
571
+ > :global([data-tool])
572
+ > *
573
+ > :global([data-disclosure-row])[aria-expanded]
574
+ > :first-child {
575
+ display: contents;
576
+ }
577
+
578
+ .eventRow[data-stream-tool-row]
579
+ > :global([data-chat-call-id])
580
+ > :global([data-slot='tool.call.toolview'])
581
+ > :global([data-tool])
582
+ > *
583
+ > :global([data-disclosure-row])[aria-expanded]
584
+ > :first-child
585
+ > :first-child:not(:last-child) {
586
+ display: none;
587
+ }
588
+
589
+ .eventRow[data-stream-tool-row]
590
+ > :global([data-chat-call-id])
591
+ > :global([data-slot='tool.call.toolview'])
592
+ > :global([data-tool])
593
+ > *
594
+ > :global([data-disclosure-row])[aria-expanded]
595
+ > :nth-child(n + 3) {
596
+ flex-grow: 0;
597
+ }
598
+
599
+ .eventRow[data-stream-tool-row]
600
+ > :global([data-chat-call-id])
601
+ > :global([data-slot='tool.call.toolview'])
602
+ > :global([data-tool])
603
+ > *
604
+ > :global([data-disclosure-row])[aria-expanded]
605
+ > :first-child
606
+ > svg {
607
+ position: static;
608
+ order: 4;
609
+ flex: none;
610
+ margin: 0 0 0 6px;
611
+ opacity: 0;
612
+ color: var(--dsw-alias-label-secondary);
613
+ transition: opacity 100ms ease, transform 120ms ease;
614
+ }
615
+
616
+ .eventRow[data-stream-tool-row]
617
+ > :global([data-chat-call-id])
618
+ > :global([data-slot='tool.call.toolview'])
619
+ > :global([data-tool])
620
+ > *
621
+ > :global([data-disclosure-row])[aria-expanded]:hover
622
+ > :first-child
623
+ > svg,
624
+ .eventRow[data-stream-tool-row]
625
+ > :global([data-chat-call-id])
626
+ > :global([data-slot='tool.call.toolview'])
627
+ > :global([data-tool])
628
+ > *
629
+ > :global([data-disclosure-row])[aria-expanded]:focus-visible
630
+ > :first-child
631
+ > svg {
632
+ opacity: 1;
633
+ }
634
+
635
+ .eventRow[data-stream-tool-row]
636
+ > :global([data-chat-call-id])
637
+ > :global([data-slot='tool.call.toolview'])
638
+ > :global([data-tool])
639
+ > *
640
+ > :global([data-disclosure-row])[aria-expanded='false']
641
+ > :first-child
642
+ > svg {
643
+ transform: rotate(-90deg);
644
+ }
645
+
646
+ .eventRow[data-stream-tool-row]
647
+ > :global([data-chat-call-id])
648
+ > :global([data-slot='tool.call.toolview'])
649
+ > :global([data-tool])
650
+ > *
651
+ > :global([data-disclosure-row])[aria-expanded='true']
652
+ > :first-child
653
+ > svg {
654
+ transform: rotate(0deg);
655
+ }
656
+
657
+ /* Bash is the one built-in Tool view that owns its disclosure row directly
658
+ * instead of composing ToolRow/DisclosureRow. Apply the same leading/trailing
659
+ * contract to that row without changing its terminal card implementation. */
660
+ .eventRow[data-stream-tool-row]
661
+ > :global([data-chat-call-id])
662
+ > :global([data-slot='tool.call.toolview'])
663
+ > *
664
+ > :global([data-sample='bash']) {
665
+ padding-inline-start: 22px;
666
+ }
667
+
668
+ .eventRow[data-stream-tool-row]
669
+ > :global([data-chat-call-id])
670
+ > :global([data-slot='tool.call.toolview'])
671
+ > *
672
+ > :global([data-sample='bash']):not([aria-expanded])
673
+ > :first-child {
674
+ display: none;
675
+ }
676
+
677
+ .eventRow[data-stream-tool-row]
678
+ > :global([data-chat-call-id])
679
+ > :global([data-slot='tool.call.toolview'])
680
+ > *
681
+ > :global([data-sample='bash'])[aria-expanded]
682
+ > :first-child {
683
+ display: contents;
684
+ }
685
+
686
+ .eventRow[data-stream-tool-row]
687
+ > :global([data-chat-call-id])
688
+ > :global([data-slot='tool.call.toolview'])
689
+ > *
690
+ > :global([data-sample='bash'])[aria-expanded]
691
+ > :first-child
692
+ > :first-child:not(:last-child) {
693
+ display: none;
694
+ }
695
+
696
+ .eventRow[data-stream-tool-row]
697
+ > :global([data-chat-call-id])
698
+ > :global([data-slot='tool.call.toolview'])
699
+ > *
700
+ > :global([data-sample='bash'])[aria-expanded]
701
+ > :nth-child(n + 3) {
702
+ flex-grow: 0;
703
+ }
704
+
705
+ .eventRow[data-stream-tool-row]
706
+ > :global([data-chat-call-id])
707
+ > :global([data-slot='tool.call.toolview'])
708
+ > *
709
+ > :global([data-sample='bash'])[aria-expanded]
710
+ > :first-child
711
+ > svg {
712
+ position: static;
713
+ order: 4;
714
+ flex: none;
715
+ margin: 0 0 0 6px;
716
+ opacity: 0;
717
+ color: var(--dsw-alias-label-secondary);
718
+ transition: opacity 100ms ease, transform 120ms ease;
719
+ }
720
+
721
+ .eventRow[data-stream-tool-row]
722
+ > :global([data-chat-call-id])
723
+ > :global([data-slot='tool.call.toolview'])
724
+ > *
725
+ > :global([data-sample='bash'])[aria-expanded]:hover
726
+ > :first-child
727
+ > svg,
728
+ .eventRow[data-stream-tool-row]
729
+ > :global([data-chat-call-id])
730
+ > :global([data-slot='tool.call.toolview'])
731
+ > *
732
+ > :global([data-sample='bash'])[aria-expanded]:focus-visible
733
+ > :first-child
734
+ > svg {
735
+ opacity: 1;
736
+ }
737
+
738
+ .eventRow[data-stream-tool-row]
739
+ > :global([data-chat-call-id])
740
+ > :global([data-slot='tool.call.toolview'])
741
+ > *
742
+ > :global([data-sample='bash'])[aria-expanded='false']
743
+ > :first-child
744
+ > svg {
745
+ transform: rotate(-90deg);
746
+ }
747
+
748
+ .eventRow[data-stream-tool-row]
749
+ > :global([data-chat-call-id])
750
+ > :global([data-slot='tool.call.toolview'])
751
+ > *
752
+ > :global([data-sample='bash'])[aria-expanded='true']
753
+ > :first-child
754
+ > svg {
755
+ transform: rotate(0deg);
756
+ }
757
+
758
+ /* Nested ToolCallBranch rows live under the root call's `data-subcalls`
759
+ * container, so the root-only chain above deliberately cannot reach them.
760
+ * The plugin owns a stable semantic-icon portal in each nested header because
761
+ * Harness removes its native icon node when a DisclosureRow opens. */
762
+ .nestedToolSemanticIcon {
763
+ position: absolute;
764
+ z-index: 1;
765
+ inset-block-start: 5px;
766
+ inset-inline-start: 1px;
767
+ display: inline-flex;
768
+ width: 14px;
769
+ height: 14px;
770
+ align-items: center;
771
+ justify-content: center;
772
+ color: var(--dsw-alias-label-tertiary);
773
+ pointer-events: none;
774
+ }
775
+
776
+ .eventRow[data-stream-tool-row]
777
+ :global([data-subcalls])
778
+ > :global([data-chat-call-id])
779
+ > :global([data-slot='tool.call.toolview'])
780
+ > :global([data-tool])
781
+ > *
782
+ > :global([data-disclosure-row])[aria-expanded]:has(> .nestedToolSemanticIcon),
783
+ .eventRow[data-stream-tool-row]
784
+ :global([data-subcalls])
785
+ > :global([data-chat-call-id])
786
+ > :global([data-slot='tool.call.toolview'])
787
+ > *
788
+ > :global([data-sample='bash'])[aria-expanded]:has(> .nestedToolSemanticIcon) {
789
+ padding-inline-start: 22px;
790
+ }
791
+
792
+ .eventRow[data-stream-tool-row]
793
+ :global([data-subcalls])
794
+ > :global([data-chat-call-id])
795
+ > :global([data-slot='tool.call.toolview'])
796
+ > :global([data-tool])
797
+ > *
798
+ > :global([data-disclosure-row])[aria-expanded]:has(> .nestedToolSemanticIcon)
799
+ > :first-child
800
+ > :first-child:not(:last-child),
801
+ .eventRow[data-stream-tool-row]
802
+ :global([data-subcalls])
803
+ > :global([data-chat-call-id])
804
+ > :global([data-slot='tool.call.toolview'])
805
+ > *
806
+ > :global([data-sample='bash'])[aria-expanded]:has(> .nestedToolSemanticIcon)
807
+ > :first-child
808
+ > :first-child:not(:last-child) {
809
+ display: none;
810
+ }
811
+
812
+ .eventRow[data-stream-tool-row]
813
+ :global([data-subcalls])
814
+ > :global([data-chat-call-id])
815
+ > :global([data-slot='tool.call.toolview'])
816
+ > :global([data-tool])
817
+ > *
818
+ > :global([data-disclosure-row])[aria-expanded]
819
+ > :first-child,
820
+ .eventRow[data-stream-tool-row]
821
+ :global([data-subcalls])
822
+ > :global([data-chat-call-id])
823
+ > :global([data-slot='tool.call.toolview'])
824
+ > *
825
+ > :global([data-sample='bash'])[aria-expanded]
826
+ > :first-child {
827
+ display: contents;
828
+ }
829
+
830
+ .eventRow[data-stream-tool-row]
831
+ :global([data-subcalls])
832
+ > :global([data-chat-call-id])
833
+ > :global([data-slot='tool.call.toolview'])
834
+ > :global([data-tool])
835
+ > *
836
+ > :global([data-disclosure-row])[aria-expanded]
837
+ > :nth-child(n + 3),
838
+ .eventRow[data-stream-tool-row]
839
+ :global([data-subcalls])
840
+ > :global([data-chat-call-id])
841
+ > :global([data-slot='tool.call.toolview'])
842
+ > *
843
+ > :global([data-sample='bash'])[aria-expanded]
844
+ > :nth-child(n + 3) {
845
+ flex-grow: 0;
846
+ }
847
+
848
+ .eventRow[data-stream-tool-row]
849
+ :global([data-subcalls])
850
+ > :global([data-chat-call-id])
851
+ > :global([data-slot='tool.call.toolview'])
852
+ > :global([data-tool])
853
+ > *
854
+ > :global([data-disclosure-row])[aria-expanded]
855
+ > :first-child
856
+ > svg,
857
+ .eventRow[data-stream-tool-row]
858
+ :global([data-subcalls])
859
+ > :global([data-chat-call-id])
860
+ > :global([data-slot='tool.call.toolview'])
861
+ > *
862
+ > :global([data-sample='bash'])[aria-expanded]
863
+ > :first-child
864
+ > svg {
865
+ position: static;
866
+ order: 4;
867
+ flex: none;
868
+ margin: 0 0 0 6px;
869
+ opacity: 0;
870
+ color: var(--dsw-alias-label-secondary);
871
+ transition: opacity 100ms ease, transform 120ms ease;
872
+ }
873
+
874
+ .eventRow[data-stream-tool-row]
875
+ :global([data-subcalls])
876
+ > :global([data-chat-call-id])
877
+ > :global([data-slot='tool.call.toolview'])
878
+ > :global([data-tool])
879
+ > *
880
+ > :global([data-disclosure-row])[aria-expanded]:hover
881
+ > :first-child
882
+ > svg,
883
+ .eventRow[data-stream-tool-row]
884
+ :global([data-subcalls])
885
+ > :global([data-chat-call-id])
886
+ > :global([data-slot='tool.call.toolview'])
887
+ > :global([data-tool])
888
+ > *
889
+ > :global([data-disclosure-row])[aria-expanded]:focus-visible
890
+ > :first-child
891
+ > svg,
892
+ .eventRow[data-stream-tool-row]
893
+ :global([data-subcalls])
894
+ > :global([data-chat-call-id])
895
+ > :global([data-slot='tool.call.toolview'])
896
+ > *
897
+ > :global([data-sample='bash'])[aria-expanded]:hover
898
+ > :first-child
899
+ > svg,
900
+ .eventRow[data-stream-tool-row]
901
+ :global([data-subcalls])
902
+ > :global([data-chat-call-id])
903
+ > :global([data-slot='tool.call.toolview'])
904
+ > *
905
+ > :global([data-sample='bash'])[aria-expanded]:focus-visible
906
+ > :first-child
907
+ > svg {
908
+ opacity: 1;
909
+ }
910
+
911
+ .eventRow[data-stream-tool-row]
912
+ :global([data-subcalls])
913
+ > :global([data-chat-call-id])
914
+ > :global([data-slot='tool.call.toolview'])
915
+ > :global([data-tool])
916
+ > *
917
+ > :global([data-disclosure-row])[aria-expanded='false']
918
+ > :first-child
919
+ > svg,
920
+ .eventRow[data-stream-tool-row]
921
+ :global([data-subcalls])
922
+ > :global([data-chat-call-id])
923
+ > :global([data-slot='tool.call.toolview'])
924
+ > *
925
+ > :global([data-sample='bash'])[aria-expanded='false']
926
+ > :first-child
927
+ > svg {
928
+ transform: rotate(-90deg);
929
+ }
930
+
931
+ .eventRow[data-stream-tool-row]
932
+ :global([data-subcalls])
933
+ > :global([data-chat-call-id])
934
+ > :global([data-slot='tool.call.toolview'])
935
+ > :global([data-tool])
936
+ > *
937
+ > :global([data-disclosure-row])[aria-expanded='true']
938
+ > :first-child
939
+ > svg,
940
+ .eventRow[data-stream-tool-row]
941
+ :global([data-subcalls])
942
+ > :global([data-chat-call-id])
943
+ > :global([data-slot='tool.call.toolview'])
944
+ > *
945
+ > :global([data-sample='bash'])[aria-expanded='true']
946
+ > :first-child
947
+ > svg {
948
+ transform: rotate(0deg);
949
+ }
950
+
951
+ @keyframes dsh-codex-stream-enter {
952
+ from {
953
+ opacity: 0;
954
+ transform: translateY(4px);
955
+ }
956
+
957
+ to {
958
+ opacity: 1;
959
+ transform: translateY(0);
960
+ }
961
+ }
962
+
963
+ .reasoningText {
964
+ min-width: 0;
965
+ color: var(--dsw-alias-label-primary);
966
+ font-size: 14px;
967
+ font-weight: 400;
968
+ line-height: 24px;
969
+ }
970
+
971
+ /* MarkdownText applies its own 16px base shorthand on the inner root. Think
972
+ * should remain a quiet 14px/24px process stream, not inherit the answer's
973
+ * heavier markdown rhythm. */
974
+ .reasoningText > :global(div) {
975
+ font-family: inherit;
976
+ font-size: 14px;
977
+ font-weight: 400;
978
+ line-height: 24px;
979
+ }
980
+
981
+ .reasoningText :global(p),
982
+ .reasoningText :global(li),
983
+ .reasoningText :global(blockquote),
984
+ .reasoningText :global(strong),
985
+ .reasoningText :global(h1),
986
+ .reasoningText :global(h2),
987
+ .reasoningText :global(h3),
988
+ .reasoningText :global(h4),
989
+ .reasoningText :global(h5),
990
+ .reasoningText :global(h6) {
991
+ font-size: 14px;
992
+ font-weight: 400;
993
+ line-height: 24px;
994
+ }
995
+
996
+ .reasoningText :global(strong) {
997
+ font-weight: 400 !important;
998
+ }
999
+
1000
+ .reasoningText :global(h1),
1001
+ .reasoningText :global(h2),
1002
+ .reasoningText :global(h3),
1003
+ .reasoningText :global(h4),
1004
+ .reasoningText :global(h5),
1005
+ .reasoningText :global(h6) {
1006
+ margin-block: 8px;
1007
+ }
1008
+
1009
+ .reasoningText[data-stream-state='running'] {
1010
+ color: var(--dsw-alias-label-secondary);
1011
+ }
1012
+
1013
+ .think {
1014
+ display: flex;
1015
+ flex-direction: column;
1016
+ }
1017
+
1018
+ .thinkRow {
1019
+ position: relative;
1020
+ overflow: hidden;
1021
+ }
1022
+
1023
+ .think[data-state='running'] .thinkRow::after {
1024
+ content: '';
1025
+ position: absolute;
1026
+ inset-block: 0;
1027
+ left: 0;
1028
+ width: 300px;
1029
+ background: linear-gradient(
1030
+ 90deg,
1031
+ transparent 0%,
1032
+ color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%,
1033
+ transparent 100%
1034
+ );
1035
+ animation: dsh-conversation-ui-think-sweep 2.6s ease-out infinite;
1036
+ pointer-events: none;
1037
+ }
1038
+
1039
+ @keyframes dsh-conversation-ui-think-sweep {
1040
+ 0% { left: -300px; }
1041
+ 90%, 100% { left: 100%; }
1042
+ }
1043
+
1044
+ .thinkLeading {
1045
+ flex-shrink: 0;
1046
+ }
1047
+
1048
+ .thinkChevron {
1049
+ color: var(--dsw-alias-label-secondary);
1050
+ }
1051
+
1052
+ .thinkTitle {
1053
+ font-weight: 400;
1054
+ }
1055
+
1056
+ .thinkSeparator {
1057
+ flex: none;
1058
+ width: 2px;
1059
+ height: 2px;
1060
+ margin: 0 8px;
1061
+ border-radius: 1px;
1062
+ background: var(--dsw-alias-label-caption);
1063
+ }
1064
+
1065
+ .thinkSummary {
1066
+ min-width: 0;
1067
+ overflow: hidden;
1068
+ flex: 0 1 auto;
1069
+ color: var(--dsw-alias-label-tertiary);
1070
+ font-size: 14px;
1071
+ line-height: 24px;
1072
+ text-overflow: ellipsis;
1073
+ white-space: nowrap;
1074
+ }
1075
+
1076
+ .thinkSummary[data-follow-end] {
1077
+ text-overflow: clip;
1078
+ }
1079
+
1080
+ .thinkBody {
1081
+ padding: 4px 0 4px 22px;
1082
+ color: var(--dsw-alias-label-tertiary);
1083
+ font-size: 14px;
1084
+ line-height: 24px;
1085
+ white-space: pre-wrap;
1086
+ word-break: break-word;
1087
+ }
1088
+
1089
+ /*
1090
+ * AnimatedDisclosure chrome: keeps the semantic icon in the leading slot and
1091
+ * places the disclosure chevron after the title/summary, matching Tool rows.
1092
+ */
1093
+ .disclosureRoot {
1094
+ display: flex;
1095
+ flex-direction: column;
1096
+ width: 100%;
1097
+ min-width: 0;
1098
+ }
1099
+
1100
+ .disclosureRow {
1101
+ position: relative;
1102
+ overflow: hidden;
1103
+ display: flex;
1104
+ align-items: center;
1105
+ height: 24px;
1106
+ min-width: 0;
1107
+ cursor: pointer;
1108
+ }
1109
+
1110
+ .disclosureLeading {
1111
+ position: relative;
1112
+ flex: none;
1113
+ width: 16px;
1114
+ height: 16px;
1115
+ display: inline-flex;
1116
+ align-items: center;
1117
+ justify-content: center;
1118
+ margin-right: 6px;
1119
+ color: var(--dsw-alias-label-tertiary);
1120
+ }
1121
+
1122
+ .disclosureTitle {
1123
+ flex: none;
1124
+ font-size: 14px;
1125
+ line-height: 24px;
1126
+ color: var(--dsw-alias-label-secondary);
1127
+ }
1128
+
1129
+ .disclosureTrailing {
1130
+ display: inline-flex;
1131
+ flex: none;
1132
+ align-items: center;
1133
+ justify-content: center;
1134
+ width: 14px;
1135
+ height: 14px;
1136
+ margin-left: 6px;
1137
+ opacity: 0;
1138
+ color: var(--dsw-alias-label-tertiary);
1139
+ transition: opacity 100ms ease;
1140
+ }
1141
+
1142
+ .disclosureRow:hover .disclosureTrailing,
1143
+ .disclosureRow:focus-visible .disclosureTrailing {
1144
+ opacity: 1;
1145
+ }
1146
+
1147
+ /*
1148
+ * The animated body: one grid track transitions 0fr <-> 1fr, so the height
1149
+ * glides to the body's natural size with no measured pixel target. The direct
1150
+ * child clips to the track; `visibility` (not display) keeps a collapsed body
1151
+ * out of the accessibility tree and tab order while it still measures for the
1152
+ * animation - it flips visible immediately on expand and waits for the height
1153
+ * to collapse before hiding. Rides the deepsuite curve tokens with fallbacks
1154
+ * for standalone composition.
1155
+ */
1156
+ .disclosureContent {
1157
+ display: grid;
1158
+ grid-template-rows: 1fr;
1159
+ visibility: visible;
1160
+ transition:
1161
+ grid-template-rows var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, cubic-bezier(0.4, 0, 0.2, 1)),
1162
+ visibility 0s;
1163
+ }
1164
+
1165
+ .disclosureContent[data-collapsed] {
1166
+ grid-template-rows: 0fr;
1167
+ visibility: hidden;
1168
+ transition:
1169
+ grid-template-rows var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, cubic-bezier(0.4, 0, 0.2, 1)),
1170
+ visibility 0s var(--ds-transition-duration, 0.2s);
1171
+ }
1172
+
1173
+ .disclosureContent > * {
1174
+ min-height: 0;
1175
+ overflow: hidden;
1176
+ }
1177
+
1178
+ @media (prefers-reduced-motion: reduce) {
1179
+ .root,
1180
+ .eventRow {
1181
+ animation: none;
1182
+ }
1183
+
1184
+ .think[data-state='running'] .thinkRow::after {
1185
+ animation: none;
1186
+ }
1187
+
1188
+ .eventRow[data-stream-tool-row][data-stream-progress='active']
1189
+ > :global([data-chat-call-id])
1190
+ > :global([data-slot='tool.call.toolview'])
1191
+ > :global([data-tool])
1192
+ > *
1193
+ > :global([data-disclosure-row])::after,
1194
+ .eventRow[data-stream-tool-row][data-stream-progress='active']
1195
+ > :global([data-chat-call-id])
1196
+ > :global([data-slot='tool.call.toolview'])
1197
+ > *
1198
+ > :global([data-sample='bash'])::after {
1199
+ animation: none;
1200
+ }
1201
+
1202
+ .turnWaiting {
1203
+ background: none;
1204
+ color: var(--dsw-alias-label-secondary);
1205
+ animation: none;
1206
+ }
1207
+
1208
+ .disclosureContent,
1209
+ .disclosureContent[data-collapsed] {
1210
+ transition: none;
1211
+ }
1212
+ }
1213
+
1214
+ .stopped {
1215
+ align-self: flex-start;
1216
+ padding: 0 6px;
1217
+ border-radius: 6px;
1218
+ background: var(--dsw-alias-interactive-bg-hover);
1219
+ color: var(--dsw-alias-label-tertiary);
1220
+ font-size: 11px;
1221
+ line-height: 18px;
1222
+ }
1223
+
1224
+ .visuallyHidden {
1225
+ position: absolute;
1226
+ overflow: hidden;
1227
+ width: 1px;
1228
+ height: 1px;
1229
+ padding: 0;
1230
+ border: 0;
1231
+ margin: -1px;
1232
+ clip: rect(0 0 0 0);
1233
+ clip-path: inset(50%);
1234
+ white-space: nowrap;
1235
+ }
1236
+
1237
+ /*
1238
+ * Follow host: ordinary document flow. The last text block owns the
1239
+ * conversation scrollport's bottom-follow while the reply reveals
1240
+ * (see useConversationFollow).
1241
+ */
1242
+ .follow {
1243
+ min-width: 0;
1244
+ }
1245
+
1246
+ /*
1247
+ * Trim the half-leading above and below each text block so line boxes hug
1248
+ * the font's ascent/descent edges. `text` edges are used instead of
1249
+ * `cap alphabetic` because assistant text is CJK-heavy and cap-height
1250
+ * trimming would clip ideographic glyphs.
1251
+ */
1252
+ @supports (text-box-trim: trim-both) {
1253
+ /* Do not trim blocks containing inline code: a wrapped code chip can be
1254
+ * taller than its first line box, and trimming the parent lets the chip
1255
+ * escape its measured height and collide with the following content. */
1256
+ .root :is(p, h1, h2, h3, h4, h5, h6, blockquote):not(:has(code)) {
1257
+ text-box-trim: trim-both;
1258
+ text-box-edge: text;
1259
+ }
1260
+ }