@schlenkr/quassel 0.5.0 → 0.5.2

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/README.md CHANGED
@@ -4,7 +4,7 @@ Chat building blocks for React: a streaming transcript with thinking and tool st
4
4
  actions, an input card with attachments and steering, and a small event contract that works
5
5
  on the server as well. quassel is the chat of [RAgents](https://github.com/SchlenkR/RAgents),
6
6
  published as a library. It ships a compiled stylesheet, so hosts need no Tailwind, and every
7
- color, radius, font and font size is a `--qsl-*` variable, and the text size follows the host by default.
7
+ color, radius, font and font size is a `--qsl-*` variable, with compact defaults for text and tool steps.
8
8
 
9
9
  ![An agent chat with grouped steps, timestamps, Markdown and the input card](https://raw.githubusercontent.com/SchlenkR/quassel/main/docs/images/agent.png)
10
10
 
@@ -93,20 +93,28 @@ Any backend that emits the `@schlenkr/quassel/events` stream works with the same
93
93
 
94
94
  ## Text size
95
95
 
96
- quassel inherits the font size of the place it is mounted in. Chat text and the input use that
97
- size, meta lines, step rows and labels are derived from it as smaller steps, and nothing
98
- compounds when elements nest. One variable changes it for a chat:
96
+ quassel starts at 13px with a line height of 1.5, a 10px message gap and a 3px dense
97
+ step gap. Tool rows and timestamps use 88% of the text size. Expanded tool groups have a
98
+ 1px gap between rows; tool rows have 1px of vertical padding on each side.
99
99
 
100
- ```css
101
- .my-chat {
102
- --qsl-font-size: 15px;
103
- }
100
+ Every size derives from one base, so nested elements never compound. Set `--qsl-font-size`
101
+ on a wrapper, or `appearance.fontSize`, to change it. Use `1em` to follow the host's font size.
102
+ `appearance` also offers `toolFontScale`, `toolGroupGap` and `toolRowPadding`.
103
+
104
+ User message backgrounds fill the available row by default while text keeps its width and
105
+ alignment. `bubbleOptions={{ userBackgroundFullWidth: false }}` restores a fitted background.
106
+ Timestamps sit beside messages by default; their size follows the tool text unless overridden:
107
+
108
+ ```tsx
109
+ <ChatMessages
110
+ messages={messages}
111
+ showTimestamps
112
+ timestampOptions={{ position: "above", fontScale: 0.88 }}
113
+ />
104
114
  ```
105
115
 
106
- `--qsl-chat-font-size` and `--qsl-text-meta` (and `-trace`, `-label`, `-xs` to `-xl`) override
107
- single steps. Up to 0.4.9 the root was fixed at 14px and the chat text at 13px, so hosts had to
108
- override sizes by hand; from 0.5.0 on that is no longer needed. Details in the
109
- [guide](https://github.com/SchlenkR/quassel/blob/main/docs/guide.md#text-size).
116
+ `--qsl-text-trace`, `--qsl-timestamp-font-size` and the other `--qsl-text-*` variables override
117
+ individual sizes. The [guide](https://github.com/SchlenkR/quassel/blob/main/docs/guide.md#text-size) lists the full API.
110
118
 
111
119
  ## Development
112
120
 
package/chat.css CHANGED
@@ -43,15 +43,24 @@
43
43
  .qsl\:absolute {
44
44
  position: absolute;
45
45
  }
46
+ .qsl\:fixed {
47
+ position: fixed;
48
+ }
46
49
  .qsl\:relative {
47
50
  position: relative;
48
51
  }
52
+ .qsl\:inset-0 {
53
+ inset: 0px;
54
+ }
49
55
  .qsl\:inset-x-0 {
50
56
  inset-inline: 0px;
51
57
  }
52
58
  .qsl\:top-1 {
53
59
  top: var(--qsl-spacing);
54
60
  }
61
+ .qsl\:top-1\/2 {
62
+ top: calc(1 / 2 * 100%);
63
+ }
55
64
  .qsl\:right-1 {
56
65
  right: var(--qsl-spacing);
57
66
  }
@@ -61,6 +70,9 @@
61
70
  .qsl\:bottom-\[calc\(var\(--qsl-composer-height\,0px\)_\+_12px\)\] {
62
71
  bottom: calc(var(--qsl-composer-height,0px) + 12px);
63
72
  }
73
+ .qsl\:left-1\/2 {
74
+ left: calc(1 / 2 * 100%);
75
+ }
64
76
  .qsl\:isolate {
65
77
  isolation: isolate;
66
78
  }
@@ -76,6 +88,12 @@
76
88
  .qsl\:z-\[110\] {
77
89
  z-index: 110;
78
90
  }
91
+ .qsl\:z-\[120\] {
92
+ z-index: 120;
93
+ }
94
+ .qsl\:z-\[121\] {
95
+ z-index: 121;
96
+ }
79
97
  .qsl\:-mx-\[16px\] {
80
98
  margin-inline: calc(16px * -1);
81
99
  }
@@ -112,11 +130,11 @@
112
130
  .qsl\:mt-4 {
113
131
  margin-top: calc(var(--qsl-spacing) * 4);
114
132
  }
115
- .qsl\:mt-\[var\(--qsl-chat-dense-message-gap\,8px\)\] {
116
- margin-top: var(--qsl-chat-dense-message-gap,8px);
133
+ .qsl\:mt-\[var\(--qsl-chat-dense-message-gap\,3px\)\] {
134
+ margin-top: var(--qsl-chat-dense-message-gap,3px);
117
135
  }
118
- .qsl\:mt-\[var\(--qsl-chat-message-gap\,16px\)\] {
119
- margin-top: var(--qsl-chat-message-gap,16px);
136
+ .qsl\:mt-\[var\(--qsl-chat-message-gap\,10px\)\] {
137
+ margin-top: var(--qsl-chat-message-gap,10px);
120
138
  }
121
139
  .qsl\:mb-0\.5 {
122
140
  margin-bottom: calc(var(--qsl-spacing) * 0.5);
@@ -195,6 +213,9 @@
195
213
  .qsl\:max-h-\[240px\] {
196
214
  max-height: 240px;
197
215
  }
216
+ .qsl\:max-h-\[calc\(100vh-6rem\)\] {
217
+ max-height: calc(100vh - 6rem);
218
+ }
198
219
  .qsl\:max-h-\[calc\(var\(--qsl-panel-height\,100dvh\)_\/_2\)\] {
199
220
  max-height: calc(var(--qsl-panel-height,100dvh) / 2);
200
221
  }
@@ -222,6 +243,9 @@
222
243
  .qsl\:w-\[190px\] {
223
244
  width: 190px;
224
245
  }
246
+ .qsl\:w-\[320px\] {
247
+ width: 320px;
248
+ }
225
249
  .qsl\:w-\[calc\(100\%_-_2_\*_var\(--qsl-chat-horizontal-padding\,24px\)\)\] {
226
250
  width: calc(100% - 2 * var(--qsl-chat-horizontal-padding,24px));
227
251
  }
@@ -243,6 +267,9 @@
243
267
  .qsl\:max-w-\[86\%\] {
244
268
  max-width: 86%;
245
269
  }
270
+ .qsl\:max-w-\[94vw\] {
271
+ max-width: 94vw;
272
+ }
246
273
  .qsl\:max-w-\[220px\] {
247
274
  max-width: 220px;
248
275
  }
@@ -291,6 +318,14 @@
291
318
  .qsl\:origin-\(--transform-origin\) {
292
319
  transform-origin: var(--transform-origin);
293
320
  }
321
+ .qsl\:-translate-x-1\/2 {
322
+ --tw-translate-x: calc(calc(1 / 2 * 100%) * -1);
323
+ translate: var(--tw-translate-x) var(--tw-translate-y);
324
+ }
325
+ .qsl\:-translate-y-1\/2 {
326
+ --tw-translate-y: calc(calc(1 / 2 * 100%) * -1);
327
+ translate: var(--tw-translate-x) var(--tw-translate-y);
328
+ }
294
329
  .qsl\:rotate-90 {
295
330
  rotate: 90deg;
296
331
  }
@@ -306,6 +341,9 @@
306
341
  .qsl\:cursor-pointer {
307
342
  cursor: pointer;
308
343
  }
344
+ .qsl\:cursor-zoom-in {
345
+ cursor: zoom-in;
346
+ }
309
347
  .qsl\:resize-none {
310
348
  resize: none;
311
349
  }
@@ -336,6 +374,9 @@
336
374
  .qsl\:items-end {
337
375
  align-items: flex-end;
338
376
  }
377
+ .qsl\:items-stretch {
378
+ align-items: stretch;
379
+ }
339
380
  .qsl\:justify-between {
340
381
  justify-content: space-between;
341
382
  }
@@ -354,9 +395,6 @@
354
395
  .qsl\:gap-0 {
355
396
  gap: 0px;
356
397
  }
357
- .qsl\:gap-0\.5 {
358
- gap: calc(var(--qsl-spacing) * 0.5);
359
- }
360
398
  .qsl\:gap-1 {
361
399
  gap: var(--qsl-spacing);
362
400
  }
@@ -375,8 +413,11 @@
375
413
  .qsl\:gap-\[5px\] {
376
414
  gap: 5px;
377
415
  }
378
- .qsl\:gap-\[var\(--qsl-chat-dense-message-gap\,8px\)\] {
379
- gap: var(--qsl-chat-dense-message-gap,8px);
416
+ .qsl\:gap-\[var\(--qsl-chat-dense-message-gap\,3px\)\] {
417
+ gap: var(--qsl-chat-dense-message-gap,3px);
418
+ }
419
+ .qsl\:gap-\[var\(--qsl-tool-group-gap\,1px\)\] {
420
+ gap: var(--qsl-tool-group-gap,1px);
380
421
  }
381
422
  :where(.qsl\:space-y-0 > :not(:last-child)) {
382
423
  --tw-space-y-reverse: 0;
@@ -395,6 +436,9 @@
395
436
  .qsl\:self-baseline {
396
437
  align-self: baseline;
397
438
  }
439
+ .qsl\:self-start {
440
+ align-self: flex-start;
441
+ }
398
442
  .qsl\:overflow-auto {
399
443
  overflow: auto;
400
444
  }
@@ -506,6 +550,12 @@
506
550
  background-color: color-mix(in oklab, var(--qsl-color-black) 15%, transparent);
507
551
  }
508
552
  }
553
+ .qsl\:bg-black\/75 {
554
+ background-color: var(--qsl-color-black);
555
+ @supports (color: color-mix(in lab, red, red)) {
556
+ background-color: color-mix(in oklab, var(--qsl-color-black) 75%, transparent);
557
+ }
558
+ }
509
559
  .qsl\:bg-card {
510
560
  background-color: var(--qsl-card);
511
561
  }
@@ -530,8 +580,8 @@
530
580
  .qsl\:bg-\[linear-gradient\(to_right\,var\(--qsl-scroll-cover\,var\(--qsl-background\)\)_50\%\,transparent\)_left\/20px_100\%_no-repeat_local\,linear-gradient\(to_left\,var\(--qsl-scroll-cover\,var\(--qsl-background\)\)_50\%\,transparent\)_right\/20px_100\%_no-repeat_local\,linear-gradient\(to_right\,\#00000047\,transparent\)_left\/14px_100\%_no-repeat_scroll\,linear-gradient\(to_left\,\#00000047\,transparent\)_right\/14px_100\%_no-repeat_scroll\] {
531
581
  background-image: linear-gradient(to right,var(--qsl-scroll-cover,var(--qsl-background)) 50%,transparent) left/20px 100% no-repeat local,linear-gradient(to left,var(--qsl-scroll-cover,var(--qsl-background)) 50%,transparent) right/20px 100% no-repeat local,linear-gradient(to right,#00000047,transparent) left/14px 100% no-repeat scroll,linear-gradient(to left,#00000047,transparent) right/14px 100% no-repeat scroll;
532
582
  }
533
- .qsl\:\[mask-image\:linear-gradient\(to_bottom\,\#000_calc\(100\%_-_var\(--qsl-chat-message-gap\,16px\)\)\,transparent\)\] {
534
- mask-image: linear-gradient(to bottom,#000 calc(100% - var(--qsl-chat-message-gap,16px)),transparent);
583
+ .qsl\:\[mask-image\:linear-gradient\(to_bottom\,\#000_calc\(100\%_-_var\(--qsl-chat-message-gap\,10px\)\)\,transparent\)\] {
584
+ mask-image: linear-gradient(to bottom,#000 calc(100% - var(--qsl-chat-message-gap,10px)),transparent);
535
585
  }
536
586
  .qsl\:\[mask-image\:linear-gradient\(to_bottom\,\#000_calc\(var\(--qsl-fade-edge\)_-_40px\)\,transparent_calc\(var\(--qsl-fade-edge\)_\+_16px\)\)\] {
537
587
  mask-image: linear-gradient(to bottom,#000 calc(var(--qsl-fade-edge) - 40px),transparent calc(var(--qsl-fade-edge) + 16px));
@@ -608,6 +658,9 @@
608
658
  .qsl\:py-\[3px\] {
609
659
  padding-block: 3px;
610
660
  }
661
+ .qsl\:py-\[var\(--qsl-tool-row-padding\,1px\)\] {
662
+ padding-block: var(--qsl-tool-row-padding,1px);
663
+ }
611
664
  .qsl\:py-px {
612
665
  padding-block: 1px;
613
666
  }
@@ -635,8 +688,8 @@
635
688
  .qsl\:pb-\[calc\(var\(--qsl-composer-height\,0px\)_\+_max\(3\.25em\,40px\)\)\] {
636
689
  padding-bottom: calc(var(--qsl-composer-height,0px) + max(3.25em, 40px));
637
690
  }
638
- .qsl\:pb-\[var\(--qsl-chat-message-gap\,16px\)\] {
639
- padding-bottom: var(--qsl-chat-message-gap,16px);
691
+ .qsl\:pb-\[var\(--qsl-chat-message-gap\,10px\)\] {
692
+ padding-bottom: var(--qsl-chat-message-gap,10px);
640
693
  }
641
694
  .qsl\:pl-1 {
642
695
  padding-left: var(--qsl-spacing);
@@ -692,8 +745,11 @@
692
745
  .qsl\:text-meta {
693
746
  font-size: var(--qsl-text-meta, calc(var(--qsl-base) * 0.8));
694
747
  }
748
+ .qsl\:text-timestamp {
749
+ font-size: var(--qsl-timestamp-font-size, var(--qsl-text-trace, calc(var(--qsl-base) * var(--qsl-tool-font-scale, 0.88))));
750
+ }
695
751
  .qsl\:text-trace {
696
- font-size: var(--qsl-text-trace, calc(var(--qsl-base) * 0.85));
752
+ font-size: var(--qsl-text-trace, calc(var(--qsl-base) * var(--qsl-tool-font-scale, 0.88)));
697
753
  }
698
754
  .qsl\:leading-\[1\.3\] {
699
755
  --tw-leading: 1.3;
@@ -719,10 +775,6 @@
719
775
  --tw-leading: var(--qsl-chat-line-height,1.5);
720
776
  line-height: var(--qsl-chat-line-height,1.5);
721
777
  }
722
- .qsl\:leading-\[var\(--qsl-chat-line-height\,1\.625\)\] {
723
- --tw-leading: var(--qsl-chat-line-height,1.625);
724
- line-height: var(--qsl-chat-line-height,1.625);
725
- }
726
778
  .qsl\:leading-normal {
727
779
  --tw-leading: var(--qsl-leading-normal);
728
780
  line-height: var(--qsl-leading-normal);
@@ -811,6 +863,9 @@
811
863
  --tw-numeric-spacing: tabular-nums;
812
864
  font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
813
865
  }
866
+ .qsl\:underline {
867
+ text-decoration-line: underline;
868
+ }
814
869
  .qsl\:\[text-underline-offset\:2px\] {
815
870
  text-underline-offset: 2px;
816
871
  }
@@ -846,6 +901,10 @@
846
901
  --tw-shadow: var(--qsl-bar-shadow);
847
902
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
848
903
  }
904
+ .qsl\:shadow-lg {
905
+ --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
906
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
907
+ }
849
908
  .qsl\:shadow-md {
850
909
  --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
851
910
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -925,8 +984,8 @@
925
984
  .qsl\:\[--qsl-scroll-cover\:var\(--qsl-secondary\)\] {
926
985
  --qsl-scroll-cover: var(--qsl-secondary);
927
986
  }
928
- .qsl\:\[-webkit-mask-image\:linear-gradient\(to_bottom\,\#000_calc\(100\%_-_var\(--qsl-chat-message-gap\,16px\)\)\,transparent\)\] {
929
- -webkit-mask-image: linear-gradient(to bottom,#000 calc(100% - var(--qsl-chat-message-gap,16px)),transparent);
987
+ .qsl\:\[-webkit-mask-image\:linear-gradient\(to_bottom\,\#000_calc\(100\%_-_var\(--qsl-chat-message-gap\,10px\)\)\,transparent\)\] {
988
+ -webkit-mask-image: linear-gradient(to bottom,#000 calc(100% - var(--qsl-chat-message-gap,10px)),transparent);
930
989
  }
931
990
  .qsl\:\[-webkit-mask-image\:linear-gradient\(to_bottom\,\#000_calc\(var\(--qsl-fade-edge\)_-_40px\)\,transparent_calc\(var\(--qsl-fade-edge\)_\+_16px\)\)\] {
932
991
  -webkit-mask-image: linear-gradient(to bottom,#000 calc(var(--qsl-fade-edge) - 40px),transparent calc(var(--qsl-fade-edge) + 16px));
@@ -1119,9 +1178,6 @@
1119
1178
  :where([data-tone="material"]) .qsl\:in-data-\[tone\=material\]\:p-0 {
1120
1179
  padding: 0px;
1121
1180
  }
1122
- :where([data-tone="material"]) .qsl\:in-data-\[tone\=material\]\:text-foreground {
1123
- color: var(--qsl-foreground);
1124
- }
1125
1181
  :where([data-tone="on-color"]) .qsl\:in-data-\[tone\=on-color\]\:border-white\/35 {
1126
1182
  border-color: var(--qsl-color-white);
1127
1183
  @supports (color: color-mix(in lab, red, red)) {
@@ -1652,7 +1708,7 @@
1652
1708
  @property --qsl-base {
1653
1709
  syntax: "<length>";
1654
1710
  inherits: true;
1655
- initial-value: 14px;
1711
+ initial-value: 13px;
1656
1712
  }
1657
1713
  @layer theme {
1658
1714
  :where(:root, :host, [data-theme="light"], [data-theme="system"]) {
@@ -1735,7 +1791,7 @@
1735
1791
  font-family: var(--qsl-font);
1736
1792
  font-feature-settings: normal;
1737
1793
  font-variation-settings: normal;
1738
- font-size: var(--qsl-font-size, 1em);
1794
+ font-size: var(--qsl-font-size, 13px);
1739
1795
  --qsl-base: 1em;
1740
1796
  color: var(--qsl-foreground);
1741
1797
  -webkit-tap-highlight-color: transparent;
@@ -1850,6 +1906,21 @@
1850
1906
  display: none;
1851
1907
  }
1852
1908
  }
1909
+ @property --tw-translate-x {
1910
+ syntax: "*";
1911
+ inherits: false;
1912
+ initial-value: 0;
1913
+ }
1914
+ @property --tw-translate-y {
1915
+ syntax: "*";
1916
+ inherits: false;
1917
+ initial-value: 0;
1918
+ }
1919
+ @property --tw-translate-z {
1920
+ syntax: "*";
1921
+ inherits: false;
1922
+ initial-value: 0;
1923
+ }
1853
1924
  @property --tw-space-y-reverse {
1854
1925
  syntax: "*";
1855
1926
  inherits: false;
@@ -1966,21 +2037,6 @@
1966
2037
  syntax: "*";
1967
2038
  inherits: false;
1968
2039
  }
1969
- @property --tw-translate-x {
1970
- syntax: "*";
1971
- inherits: false;
1972
- initial-value: 0;
1973
- }
1974
- @property --tw-translate-y {
1975
- syntax: "*";
1976
- inherits: false;
1977
- initial-value: 0;
1978
- }
1979
- @property --tw-translate-z {
1980
- syntax: "*";
1981
- inherits: false;
1982
- initial-value: 0;
1983
- }
1984
2040
  @keyframes enter {
1985
2041
  from {
1986
2042
  opacity: var(--tw-enter-opacity,1);
@@ -2018,9 +2074,12 @@
2018
2074
  @layer properties {
2019
2075
  @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
2020
2076
  :root, :host {
2021
- --qsl-base: 14px;
2077
+ --qsl-base: 13px;
2022
2078
  }
2023
2079
  *, ::before, ::after, ::backdrop {
2080
+ --tw-translate-x: 0;
2081
+ --tw-translate-y: 0;
2082
+ --tw-translate-z: 0;
2024
2083
  --tw-space-y-reverse: 0;
2025
2084
  --tw-border-style: solid;
2026
2085
  --tw-leading: initial;
@@ -2047,9 +2106,6 @@
2047
2106
  --tw-ring-offset-shadow: 0 0 #0000;
2048
2107
  --tw-outline-style: solid;
2049
2108
  --tw-duration: initial;
2050
- --tw-translate-x: 0;
2051
- --tw-translate-y: 0;
2052
- --tw-translate-z: 0;
2053
2109
  --tw-animation-delay: 0s;
2054
2110
  --tw-animation-direction: normal;
2055
2111
  --tw-animation-duration: initial;