rift-ds 1.8.0 → 1.9.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.
Files changed (51) hide show
  1. package/README.md +3 -3
  2. package/chat/ChatAgentPane.d.ts +8 -5
  3. package/chat/ChatAgentPane.js +4 -1
  4. package/chat/ChatArtifactCard.d.ts +13 -0
  5. package/chat/ChatArtifactCard.js +21 -0
  6. package/chat/ChatArtifactPane.d.ts +18 -0
  7. package/chat/ChatArtifactPane.js +43 -0
  8. package/chat/ChatProvider.d.ts +4 -2
  9. package/chat/ChatProvider.js +15 -0
  10. package/chat/ChatWidget.css +208 -21
  11. package/chat/ChatWidget.d.ts +3 -1
  12. package/chat/ChatWidget.js +287 -73
  13. package/chat/TurnContent.d.ts +3 -1
  14. package/chat/TurnContent.js +9 -2
  15. package/chat/artifacts.d.ts +5 -0
  16. package/chat/artifacts.js +14 -0
  17. package/chat/context.d.ts +18 -1
  18. package/chat/context.js +2 -0
  19. package/chat/demo-artifacts.d.ts +6 -0
  20. package/chat/demo-artifacts.js +80 -0
  21. package/chat/demo-card-components.d.ts +2 -0
  22. package/chat/demo-card-components.js +7 -0
  23. package/chat/demo-script.js +24 -2
  24. package/chat/demo.css +6 -0
  25. package/chat/demo.d.ts +1 -0
  26. package/chat/demo.js +4 -0
  27. package/chat/index.d.ts +6 -3
  28. package/chat/index.js +12 -2
  29. package/chat/queries.d.ts +10 -1
  30. package/chat/queries.js +5 -1
  31. package/chat/registry.json.d.ts +6 -1
  32. package/chat/registry.json.js +1 -1
  33. package/chat/types.d.ts +36 -0
  34. package/chat/types.js +2 -0
  35. package/components/AgentRail/AgentRail.css +14 -5
  36. package/components/AgentRail/AgentRail.d.ts +2 -0
  37. package/components/AgentRail/AgentRail.js +3 -2
  38. package/components/ArtifactCard/ArtifactCard.css +106 -0
  39. package/components/ArtifactCard/ArtifactCard.d.ts +27 -0
  40. package/components/ArtifactCard/ArtifactCard.js +29 -0
  41. package/components/ArtifactPanel/ArtifactPanel.css +159 -0
  42. package/components/ArtifactPanel/ArtifactPanel.d.ts +70 -0
  43. package/components/ArtifactPanel/ArtifactPanel.js +131 -0
  44. package/components/ThreadPanel/ThreadPanel.css +87 -33
  45. package/components/ThreadPanel/ThreadPanel.js +21 -10
  46. package/components/registry.json +16 -0
  47. package/components/registry.json.d.ts +16 -0
  48. package/components/registry.json.js +1 -1
  49. package/index.d.ts +2 -0
  50. package/index.js +4 -0
  51. package/package.json +1 -1
@@ -0,0 +1,159 @@
1
+ /* ============================================
2
+ ARTIFACT PANEL COMPONENT
3
+ The surface a chat opens beside the conversation to show something it
4
+ made. A header names the artifact and carries its controls; the body is
5
+ the preview (live content, or a sandboxed frame) or the source.
6
+ Deliberately surface-less, like AgentRail and ThreadPanel — the host
7
+ paints the card it sits in, and decides where that card goes.
8
+
9
+ One scroll region only: the body. The header stays put, so the controls
10
+ never leave with the content.
11
+ ============================================ */
12
+
13
+ /* Base */
14
+
15
+ .ds-artifact-panel {
16
+ /* Width, height and padding combine on the root, so the component declares
17
+ its own sizing model rather than leaning on a host reset. */
18
+ box-sizing: border-box;
19
+ display: flex;
20
+ flex-direction: column;
21
+ width: 100%;
22
+ height: 100%;
23
+ min-width: 0;
24
+ min-height: 0;
25
+ overflow: hidden;
26
+ }
27
+
28
+ /* ============================================
29
+ HEADER — the artifact's name, then its controls
30
+ ============================================ */
31
+
32
+ /* The controls stand --padding-300 in from the top and trailing edges, the
33
+ inset AgentRail gives its own corner control, so the two panels that
34
+ share a chat's trailing seat put their close in the same place and a
35
+ rounded host card has room to turn its corner around it. */
36
+ .ds-artifact-panel__header {
37
+ display: flex;
38
+ flex: none;
39
+ align-items: center;
40
+ justify-content: space-between;
41
+ gap: var(--gap-300);
42
+ min-width: 0;
43
+ padding: var(--padding-300) var(--padding-300) var(--padding-300)
44
+ var(--padding-400);
45
+ border-bottom: var(--border-025) solid var(--color-bg-container-border);
46
+ }
47
+
48
+ .ds-artifact-panel__heading {
49
+ display: flex;
50
+ flex: 1;
51
+ align-items: baseline;
52
+ gap: var(--gap-200);
53
+ min-width: 0;
54
+ }
55
+
56
+ /* The mark sits on the row's centre line while the name and its meta share
57
+ a baseline. */
58
+ .ds-artifact-panel__icon {
59
+ --icon-size: var(--icon-size-500);
60
+ display: inline-flex;
61
+ flex: none;
62
+ align-self: center;
63
+ color: var(--color-icon-secondary);
64
+ }
65
+
66
+ /* ChatHeader's title face, so the two headers read as one line of type.
67
+ The name outranks its meta when the row runs short: the meta carries a
68
+ shrink factor large enough to give way entirely before the name loses a
69
+ letter. */
70
+ .ds-artifact-panel__title {
71
+ flex: 0 1 auto;
72
+ min-width: 0;
73
+ margin: 0;
74
+ overflow: hidden;
75
+ text-overflow: ellipsis;
76
+ white-space: nowrap;
77
+ font-family: var(--font-title-body-family);
78
+ font-size: var(--font-title-body-size);
79
+ font-weight: var(--font-title-body-weight);
80
+ line-height: var(--font-title-body-line-height);
81
+ color: var(--color-text-primary);
82
+ }
83
+
84
+ .ds-artifact-panel__meta {
85
+ flex: 0 1000 auto;
86
+ min-width: 0;
87
+ overflow: hidden;
88
+ text-overflow: ellipsis;
89
+ white-space: nowrap;
90
+ font-family: var(--font-caption-family);
91
+ font-size: var(--font-caption-size);
92
+ font-weight: var(--font-caption-weight);
93
+ line-height: var(--font-caption-line-height);
94
+ color: var(--color-text-tertiary);
95
+ }
96
+
97
+ .ds-artifact-panel__controls {
98
+ display: flex;
99
+ flex: none;
100
+ align-items: center;
101
+ /* The row is the buttons' height whatever it holds: the view switch
102
+ stands a hair taller than a button, and a header that grew with it
103
+ would move every control when an artifact with source took the stage. */
104
+ height: 40px;
105
+ gap: var(--gap-100);
106
+ min-width: 0;
107
+ }
108
+
109
+ /* The view switch stands a beat clear of the icon buttons it leads. */
110
+ .ds-artifact-panel__controls > .ds-segmented-control {
111
+ margin-inline-end: var(--gap-100);
112
+ }
113
+
114
+ /* ============================================
115
+ BODY — the preview or the source
116
+ ============================================ */
117
+
118
+ .ds-artifact-panel__body {
119
+ display: flex;
120
+ flex: 1;
121
+ flex-direction: column;
122
+ min-width: 0;
123
+ min-height: 0;
124
+ }
125
+
126
+ /* Live content scrolls inside the body and brings its own padding: a
127
+ dashboard wants an inset, a full-bleed page does not. */
128
+ .ds-artifact-panel__content,
129
+ .ds-artifact-panel__code {
130
+ flex: 1;
131
+ min-width: 0;
132
+ min-height: 0;
133
+ overflow: auto;
134
+ /* ThreadPanel's tucked-away thin scrollbar: no track fill, the thumb
135
+ showing only while the region is hovered or scrolled from the keyboard. */
136
+ scrollbar-width: thin;
137
+ scrollbar-color: transparent transparent;
138
+ }
139
+
140
+ .ds-artifact-panel__content:hover,
141
+ .ds-artifact-panel__content:focus-within,
142
+ .ds-artifact-panel__code:hover,
143
+ .ds-artifact-panel__code:focus-within {
144
+ scrollbar-color: var(--color-bg-container-border) transparent;
145
+ }
146
+
147
+ .ds-artifact-panel__code {
148
+ padding: var(--padding-400);
149
+ }
150
+
151
+ /* A framed document is its own page: it fills the body edge to edge and
152
+ owns its own scrolling. */
153
+ .ds-artifact-panel__frame {
154
+ display: block;
155
+ flex: 1;
156
+ width: 100%;
157
+ min-height: 0;
158
+ border: none;
159
+ }
@@ -0,0 +1,70 @@
1
+ import { default as React } from 'react';
2
+ /** Which face of the artifact is on stage. */
3
+ export type ArtifactPanelView = 'preview' | 'code';
4
+ /** Props owned by ArtifactPanel itself — everything else falls through to the root `<section>`. */
5
+ type ArtifactPanelOwnProps = {
6
+ /** The artifact's name, shown in the header and used as the panel's accessible name. Collides with the native `title` tooltip attribute on purpose: a panel has a heading, not a tooltip. */
7
+ title: React.ReactNode;
8
+ /** A quiet line beside the name: what kind of thing this is, or which version. */
9
+ meta?: React.ReactNode;
10
+ /** Leading mark in the header — Material Symbol name (string) or custom element (ReactNode). */
11
+ icon?: string | React.ReactNode;
12
+ /** The preview, as live React content. Wins over `html` and `src`. */
13
+ children?: React.ReactNode;
14
+ /** The preview, as a complete HTML document drawn in a sandboxed frame. Wins over `src`. A document that honours `prefers-color-scheme` follows the `color-scheme` of the page around the panel, so set that with your theme and the preview turns light or dark with it. */
15
+ html?: string;
16
+ /** The preview, as a URL drawn in a sandboxed frame: a deployed page, a site. */
17
+ src?: string;
18
+ /** The frame's `sandbox` tokens for `html` and `src`. The default lets a document run its own scripts and forms in an origin of its own, with no reach into the host page; widen it only for a source you trust. */
19
+ sandbox?: string;
20
+ /** Accessible name for the preview frame. Defaults to the title when that is a string. */
21
+ frameTitle?: string;
22
+ /** The artifact's source. Given, the header offers a Code view beside the preview (or shows the code alone when there is no preview). */
23
+ code?: string;
24
+ /** The source's language, shown on the code view. */
25
+ language?: string;
26
+ /** The view on stage, when the host owns the state. */
27
+ view?: ArtifactPanelView;
28
+ /** The view the panel opens on when it keeps its own state. */
29
+ defaultView?: ArtifactPanelView;
30
+ /** Fires with the chosen view. */
31
+ onViewChange?: (view: ArtifactPanelView) => void;
32
+ /** Whether the host is showing the panel at its expanded size. The panel never sizes itself: this only picks the toggle's icon and name. */
33
+ expanded?: boolean;
34
+ /** Fires with the next state when the expand toggle is pressed. The toggle renders only when this is given. */
35
+ onExpandedChange?: (expanded: boolean) => void;
36
+ /** Fires when the close button is pressed. The button renders only when this is given. */
37
+ onClose?: () => void;
38
+ /** Extra header controls, seated ahead of the expand and close buttons: a download, a share, a version menu. */
39
+ actions?: React.ReactNode;
40
+ /** Label for the preview segment of the view switch. */
41
+ previewLabel?: string;
42
+ /** Label for the code segment of the view switch. */
43
+ codeLabel?: string;
44
+ /** Accessible name for the view switch. */
45
+ viewLabel?: string;
46
+ /** Accessible name for the expand toggle while the panel is at its resting size. */
47
+ expandLabel?: string;
48
+ /** Accessible name for the expand toggle while the panel is expanded. */
49
+ collapseLabel?: string;
50
+ /** Accessible name for the close button. */
51
+ closeLabel?: string;
52
+ /** Additional CSS classes */
53
+ className?: string;
54
+ };
55
+ export interface ArtifactPanelProps extends ArtifactPanelOwnProps, Omit<React.ComponentPropsWithoutRef<'section'>, keyof ArtifactPanelOwnProps> {
56
+ }
57
+ /**
58
+ * ArtifactPanel is the surface a chat opens beside the conversation to show
59
+ * something it made: a dashboard, a document, a page, a site. A header names
60
+ * the artifact and carries its controls; the body is the preview, drawn from
61
+ * live React content, a sandboxed HTML document or a URL, with the source one
62
+ * switch away when there is any.
63
+ *
64
+ * Surface-less like AgentRail and ThreadPanel: it fills the box the host
65
+ * gives it and the host paints the card around it. Where it sits and how
66
+ * large it runs are the host's too, so `expanded` and `onClose` only report
67
+ * intent.
68
+ */
69
+ export declare const ArtifactPanel: React.ForwardRefExoticComponent<ArtifactPanelProps & React.RefAttributes<HTMLElement>>;
70
+ export {};
@@ -0,0 +1,131 @@
1
+ "use client";
2
+ import { jsxs, jsx } from "react/jsx-runtime";
3
+ import React, { useId, useState } from "react";
4
+ import { CircularButton } from "../CircularButton/CircularButton.js";
5
+ import { CodeBlock } from "../CodeBlock/CodeBlock.js";
6
+ import { SegmentedControl } from "../SegmentedControl/SegmentedControl.js";
7
+ import "./ArtifactPanel.css";
8
+ import "../../fonts/material-symbols.css";
9
+ const baseClass = "ds-artifact-panel";
10
+ const ArtifactPanel = React.forwardRef(
11
+ ({
12
+ title,
13
+ meta,
14
+ icon,
15
+ children,
16
+ html,
17
+ src,
18
+ sandbox = "allow-scripts allow-forms",
19
+ frameTitle,
20
+ code,
21
+ language,
22
+ view,
23
+ defaultView,
24
+ onViewChange,
25
+ expanded = false,
26
+ onExpandedChange,
27
+ onClose,
28
+ actions,
29
+ previewLabel = "Preview",
30
+ codeLabel = "Code",
31
+ viewLabel = "Artifact view",
32
+ expandLabel = "Expand the artifact",
33
+ collapseLabel = "Collapse the artifact",
34
+ closeLabel = "Close the artifact",
35
+ className = "",
36
+ ...rest
37
+ }, ref) => {
38
+ const titleId = useId();
39
+ const hasPreview = Boolean(children) || html !== void 0 || Boolean(src);
40
+ const hasCode = code !== void 0;
41
+ const [ownView, setOwnView] = useState(defaultView ?? "preview");
42
+ const wanted = view ?? ownView;
43
+ const current = wanted === "code" ? hasCode ? "code" : "preview" : hasPreview ? "preview" : "code";
44
+ const changeView = (next) => {
45
+ const chosen = next === "code" ? "code" : "preview";
46
+ setOwnView(chosen);
47
+ onViewChange?.(chosen);
48
+ };
49
+ const frameName = frameTitle ?? (typeof title === "string" ? title : previewLabel);
50
+ return /* @__PURE__ */ jsxs(
51
+ "section",
52
+ {
53
+ ref,
54
+ ...rest,
55
+ className: [baseClass, className].filter(Boolean).join(" "),
56
+ "aria-labelledby": titleId,
57
+ children: [
58
+ /* @__PURE__ */ jsxs("header", { className: `${baseClass}__header`, children: [
59
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__heading`, children: [
60
+ icon !== void 0 && (typeof icon === "string" ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon material-symbols-rounded`, "aria-hidden": "true", children: icon }) : /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon`, children: icon })),
61
+ /* @__PURE__ */ jsx("h2", { className: `${baseClass}__title`, id: titleId, children: title }),
62
+ meta && /* @__PURE__ */ jsx("span", { className: `${baseClass}__meta`, children: meta })
63
+ ] }),
64
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__controls`, children: [
65
+ hasPreview && hasCode && /* @__PURE__ */ jsx(
66
+ SegmentedControl,
67
+ {
68
+ size: "compact",
69
+ variant: "neutral",
70
+ ariaLabel: viewLabel,
71
+ segments: [
72
+ { value: "preview", label: previewLabel },
73
+ { value: "code", label: codeLabel }
74
+ ],
75
+ activeSegment: current,
76
+ onSegmentChange: changeView
77
+ }
78
+ ),
79
+ actions,
80
+ onExpandedChange && /* @__PURE__ */ jsx(
81
+ CircularButton,
82
+ {
83
+ icon: expanded ? "collapse_content" : "expand_content",
84
+ variant: "tertiary",
85
+ ariaLabel: expanded ? collapseLabel : expandLabel,
86
+ tooltipPosition: "bottom",
87
+ "aria-pressed": expanded,
88
+ onClick: () => onExpandedChange(!expanded)
89
+ }
90
+ ),
91
+ onClose && /* @__PURE__ */ jsx(
92
+ CircularButton,
93
+ {
94
+ icon: "close",
95
+ variant: "tertiary",
96
+ ariaLabel: closeLabel,
97
+ tooltipPosition: "bottom",
98
+ onClick: onClose
99
+ }
100
+ )
101
+ ] })
102
+ ] }),
103
+ /* @__PURE__ */ jsx("div", { className: `${baseClass}__body`, "data-view": current, children: current === "code" && hasCode ? /* @__PURE__ */ jsx("div", { className: `${baseClass}__code`, children: /* @__PURE__ */ jsx(CodeBlock, { code: code ?? "", language }) }) : children ? /* @__PURE__ */ jsx("div", { className: `${baseClass}__content`, children }) : html !== void 0 ? /* @__PURE__ */ jsx(
104
+ "iframe",
105
+ {
106
+ className: `${baseClass}__frame`,
107
+ title: frameName,
108
+ srcDoc: html,
109
+ sandbox,
110
+ referrerPolicy: "no-referrer"
111
+ }
112
+ ) : src ? /* @__PURE__ */ jsx(
113
+ "iframe",
114
+ {
115
+ className: `${baseClass}__frame`,
116
+ title: frameName,
117
+ src,
118
+ sandbox,
119
+ referrerPolicy: "no-referrer",
120
+ loading: "lazy"
121
+ }
122
+ ) : null })
123
+ ]
124
+ }
125
+ );
126
+ }
127
+ );
128
+ ArtifactPanel.displayName = "ArtifactPanel";
129
+ export {
130
+ ArtifactPanel
131
+ };
@@ -51,10 +51,9 @@
51
51
  HEADER — brand row and the collapse toggle
52
52
  ============================================ */
53
53
 
54
- /* Collapsed, --padding-200 centres the 24px mark on the rail's axis
55
- (12 + 8 = the rows' icon seat); expanded it deepens to the 28px optical
56
- seat with everything else, the padding gliding with the sweep. The
57
- margin sits the brand row evenly between the panel's top edge and the
54
+ /* --padding-200 centres the 24px mark on the rail's axis (12 + 8 = the
55
+ rows' icon seat) and holds it there expanded, so the logo never moves
56
+ as the rail sweeps. The margin sits the brand row evenly between the panel's top edge and the
58
57
  new-thread row: 6 + 12 + the panel's 4px gap below mirrors the 16px
59
58
  panel padding + 6 above. */
60
59
  .ds-thread-panel__header {
@@ -64,11 +63,6 @@
64
63
  min-width: 0;
65
64
  padding: var(--padding-150) var(--padding-200);
66
65
  margin-bottom: var(--gap-300);
67
- transition: padding var(--motion-duration-slow) var(--motion-ease-emphasized);
68
- }
69
-
70
- .ds-thread-panel--expanded .ds-thread-panel__header {
71
- padding: var(--padding-150) var(--padding-400);
72
66
  }
73
67
 
74
68
  /* A logo-less header collapsed to the rail: the empty brand slot would
@@ -151,11 +145,57 @@
151
145
  overflow: hidden;
152
146
  opacity: 0;
153
147
  transition: opacity var(--motion-duration-base) var(--motion-ease-standard),
154
- width var(--motion-duration-slow) var(--motion-ease-emphasized);
148
+ width var(--motion-duration-slow) var(--motion-ease-emphasized),
149
+ margin var(--motion-duration-slow) var(--motion-ease-emphasized);
150
+ }
151
+
152
+ /* The hover wash is a circle drawn around the 24px mark rather than a
153
+ bigger button, so the glyph keeps its place on the trailing rail. The
154
+ toggle clips while it folds, so the wash only exists once it is open. */
155
+ .ds-thread-panel__toggle::before {
156
+ content: '';
157
+ position: absolute;
158
+ inset: calc(-1 * var(--gap-100));
159
+ z-index: -1;
160
+ border-radius: var(--radius-pill);
161
+ background-color: transparent;
162
+ transition: background-color var(--motion-duration-fast)
163
+ var(--motion-ease-standard);
155
164
  }
156
165
 
166
+ .ds-thread-panel--expanded .ds-thread-panel__toggle,
167
+ .ds-thread-panel__toggle--standalone {
168
+ position: relative;
169
+ z-index: 0;
170
+ overflow: visible;
171
+ }
172
+
173
+ .ds-thread-panel__toggle:hover::before {
174
+ background-color: var(--color-action-passive-bg-hover);
175
+ }
176
+
177
+ .ds-thread-panel__toggle:active::before {
178
+ background-color: var(--color-action-passive-bg-active);
179
+ }
180
+
181
+ /* Collapsed, the rail is too narrow for a tooltip and clips it; the
182
+ native title carries the name there instead. */
183
+ .ds-thread-panel__toggle-tip {
184
+ flex: none;
185
+ }
186
+
187
+ .ds-thread-panel:not(.ds-thread-panel--expanded)
188
+ .ds-thread-panel__toggle-tip
189
+ .ds-tooltip__panel {
190
+ display: none;
191
+ }
192
+
193
+ /* The glyph's ink stops a bearing short of its 24px box, so the box is
194
+ pulled out by that much: the mark's right edge lands on the rail the
195
+ rows' meta captions and the profile's caption end on. */
157
196
  .ds-thread-panel--expanded .ds-thread-panel__toggle {
158
197
  width: 24px;
198
+ margin-inline-end: calc(-1 * var(--gap-100));
159
199
  opacity: 1;
160
200
  }
161
201
 
@@ -199,20 +239,13 @@
199
239
  text-align: left;
200
240
  text-decoration: none;
201
241
  transition: background-color var(--motion-duration-fast)
202
- var(--motion-ease-standard),
203
- padding var(--motion-duration-slow) var(--motion-ease-emphasized);
242
+ var(--motion-ease-standard);
204
243
  }
205
244
 
206
- /* Expanded, the rows breathe: --padding-400 inline sets the 28px optical
207
- seat (12 + 16) that everything in the panel aligns to — the pen icon's
208
- box is the guide. Collapsed returns to --padding-200, which is what the
209
- 40px circle's math needs; the padding glides with the rail's sweep. */
210
- .ds-thread-panel--expanded .ds-thread-panel__new,
211
- .ds-thread-panel--expanded .ds-thread-panel__control,
212
- .ds-thread-panel--expanded .ds-thread-panel__project,
213
- .ds-thread-panel--expanded .ds-thread-panel__thread {
214
- padding: var(--padding-200) var(--padding-400);
215
- }
245
+ /* One seat in both states: the 20px inset (12 + 8) the collapsed circle's
246
+ math needs is also where the expanded panel's marks sit, so no mark
247
+ shifts as the rail sweeps. Text-led content sits a bearing deeper — see
248
+ TEXT SEAT at the end of the file. */
216
249
 
217
250
  .ds-thread-panel__new:hover,
218
251
  .ds-thread-panel__control:hover,
@@ -335,8 +368,7 @@
335
368
  align-items: center;
336
369
  gap: var(--gap-200);
337
370
  min-width: 0;
338
- padding: var(--padding-150) var(--padding-200) var(--padding-150)
339
- var(--padding-400);
371
+ padding: var(--padding-150) var(--padding-200);
340
372
  opacity: 0;
341
373
  visibility: hidden;
342
374
  transition: opacity var(--motion-duration-base) var(--motion-ease-standard),
@@ -356,7 +388,7 @@
356
388
  overflow: hidden;
357
389
  white-space: nowrap;
358
390
  font-family: var(--font-overline-family);
359
- font-size: var(--font-overline-size);
391
+ font-size: var(--font-caption-size);
360
392
  font-weight: var(--font-overline-weight);
361
393
  line-height: var(--font-overline-line-height);
362
394
  letter-spacing: var(--font-overline-letter-spacing);
@@ -477,16 +509,19 @@
477
509
  gap: var(--gap-050);
478
510
  }
479
511
 
480
- /* Aligned with the expanded rows' 28px optical seat. The vertical padding
512
+ /* The overline face set one step down, at the caption's size: the labels
513
+ sit among caption-sized meta and second lines, and the full overline
514
+ outweighed the thread titles it files. The overline's own line-height
515
+ stays, so the rhythm below is unchanged. Aligned with the text seat. The vertical padding
481
516
  is the rows' own inset restated — as if the label sat inside a row —
482
517
  so the overline keeps the pills' rhythm instead of hugging the
483
518
  new-thread row above and the first thread below. */
484
519
  .ds-thread-panel__group-label {
485
- padding: var(--padding-200) var(--padding-400);
520
+ padding: var(--padding-200);
486
521
  overflow: hidden;
487
522
  white-space: nowrap;
488
523
  font-family: var(--font-overline-family);
489
- font-size: var(--font-overline-size);
524
+ font-size: var(--font-caption-size);
490
525
  font-weight: var(--font-overline-weight);
491
526
  line-height: var(--font-overline-line-height);
492
527
  letter-spacing: var(--font-overline-letter-spacing);
@@ -760,7 +795,7 @@
760
795
  width: 100%;
761
796
  height: 40px;
762
797
  min-width: 0;
763
- padding: 0 var(--padding-400);
798
+ padding: 0 var(--padding-200);
764
799
  border: var(--border-025) solid var(--color-action-primary-bg);
765
800
  border-radius: var(--radius-pill);
766
801
  background-color: var(--color-bg-container-primary);
@@ -778,7 +813,7 @@
778
813
 
779
814
  .ds-thread-panel__more {
780
815
  align-self: flex-start;
781
- padding: var(--padding-150) var(--padding-400);
816
+ padding: var(--padding-150) var(--padding-200);
782
817
  border: none;
783
818
  border-radius: var(--radius-pill);
784
819
  background: transparent;
@@ -826,8 +861,9 @@
826
861
  pointer-events: auto;
827
862
  }
828
863
 
829
- /* Same per-state seat as the header: collapsed, --padding-100 centres the
830
- 32px avatar on the rail's axis (12 + 4); expanded joins the 28px seat. */
864
+ /* --padding-100 centres the 32px avatar on the rail's axis (12 + 4) in
865
+ both states, so it holds still like the header's mark; expanded, only
866
+ the trailing inset deepens, landing the caption on the rows' meta rail. */
831
867
  .ds-thread-panel__profile {
832
868
  display: flex;
833
869
  align-items: center;
@@ -838,7 +874,8 @@
838
874
  }
839
875
 
840
876
  .ds-thread-panel--expanded .ds-thread-panel__profile {
841
- padding: var(--padding-100) var(--padding-400);
877
+ padding: var(--padding-100) var(--padding-200) var(--padding-100)
878
+ var(--padding-100);
842
879
  }
843
880
 
844
881
  .ds-thread-panel__profile-name {
@@ -875,3 +912,20 @@
875
912
  .ds-thread-panel--expanded .ds-thread-panel__profile-meta {
876
913
  opacity: 1;
877
914
  }
915
+
916
+ /* ============================================
917
+ TEXT SEAT — last, so it wins over each rule's padding shorthand
918
+ ============================================ */
919
+
920
+ /* Text-led content — group labels, thread rows, the more row, the rename
921
+ field — sits a bearing deeper than the icon seat: a glyph's ink starts
922
+ that far inside its 24px box, so this is what lands the labels and status
923
+ dots under the pen's visible edge while the glyph itself stays centred
924
+ under the logo. None of it shows collapsed, so nothing moves. */
925
+ .ds-thread-panel__thread,
926
+ .ds-thread-panel__group-label,
927
+ .ds-thread-panel__projects-header,
928
+ .ds-thread-panel__more,
929
+ .ds-thread-panel__rename {
930
+ padding-inline-start: var(--padding-300);
931
+ }
@@ -5,6 +5,7 @@ import { DropdownMenu } from "../DropdownMenu/DropdownMenu.js";
5
5
  import { Kbd } from "../Kbd/Kbd.js";
6
6
  import { PixelAvatar } from "../PixelAvatar/PixelAvatar.js";
7
7
  import { StatusDot } from "../StatusDot/StatusDot.js";
8
+ import { Tooltip } from "../Tooltip/Tooltip.js";
8
9
  import "./ThreadPanel.css";
9
10
  import "../../fonts/material-symbols.css";
10
11
  import { distinctPixelInks } from "../PixelAvatar/pixelInks.js";
@@ -163,18 +164,28 @@ const ThreadPanel = React.forwardRef(
163
164
  const showNew = Boolean(newThreadLabel || onNewThread || newThreadHref);
164
165
  const showFooter = Boolean(footerSlot || profile);
165
166
  const logoExpands = Boolean(logo && !expanded && onExpandedChange);
167
+ const toggleLabel = expanded ? collapseLabel : expandLabel;
166
168
  const toggle = onExpandedChange && /* @__PURE__ */ jsx(
167
- "button",
169
+ Tooltip,
168
170
  {
169
- type: "button",
170
- className: [
171
- `${baseClass}__toggle`,
172
- !logo && `${baseClass}__toggle--standalone`
173
- ].filter(Boolean).join(" "),
174
- "aria-label": expanded ? collapseLabel : expandLabel,
175
- "aria-expanded": expanded,
176
- onClick: () => onExpandedChange(!expanded),
177
- children: /* @__PURE__ */ jsx("span", { className: iconClass, "aria-hidden": "true", children: expanded ? "left_panel_close" : "left_panel_open" })
171
+ content: toggleLabel,
172
+ position: "left",
173
+ className: `${baseClass}__toggle-tip`,
174
+ children: /* @__PURE__ */ jsx(
175
+ "button",
176
+ {
177
+ type: "button",
178
+ className: [
179
+ `${baseClass}__toggle`,
180
+ !logo && `${baseClass}__toggle--standalone`
181
+ ].filter(Boolean).join(" "),
182
+ "aria-label": toggleLabel,
183
+ title: expanded ? void 0 : toggleLabel,
184
+ "aria-expanded": expanded,
185
+ onClick: () => onExpandedChange(!expanded),
186
+ children: /* @__PURE__ */ jsx("span", { className: iconClass, "aria-hidden": "true", children: expanded ? "left_panel_close" : "left_panel_open" })
187
+ }
188
+ )
178
189
  }
179
190
  );
180
191
  const expandOnClick = !expanded && onExpandedChange ? (e) => {
@@ -161,6 +161,22 @@
161
161
  "folder": "Chart",
162
162
  "recharts": true
163
163
  },
164
+ {
165
+ "name": "ArtifactCard",
166
+ "label": "Artifact card",
167
+ "slug": "artifact-card",
168
+ "description": "The tile a chat leaves in the thread for something it made, a toggle that opens the artifact in its panel.",
169
+ "category": "ai",
170
+ "client": false
171
+ },
172
+ {
173
+ "name": "ArtifactPanel",
174
+ "label": "Artifact panel",
175
+ "slug": "artifact-panel",
176
+ "description": "The panel a chat opens beside the conversation to preview what it made: live content, a sandboxed page or a site, with the source a switch away.",
177
+ "category": "ai",
178
+ "client": true
179
+ },
164
180
  {
165
181
  "name": "AttachmentDropZone",
166
182
  "label": "Attachment drop zone",
@@ -161,6 +161,22 @@ declare const _default: {
161
161
  "folder": "Chart",
162
162
  "recharts": true
163
163
  },
164
+ {
165
+ "name": "ArtifactCard",
166
+ "label": "Artifact card",
167
+ "slug": "artifact-card",
168
+ "description": "The tile a chat leaves in the thread for something it made, a toggle that opens the artifact in its panel.",
169
+ "category": "ai",
170
+ "client": false
171
+ },
172
+ {
173
+ "name": "ArtifactPanel",
174
+ "label": "Artifact panel",
175
+ "slug": "artifact-panel",
176
+ "description": "The panel a chat opens beside the conversation to preview what it made: live content, a sandboxed page or a site, with the source a switch away.",
177
+ "category": "ai",
178
+ "client": true
179
+ },
164
180
  {
165
181
  "name": "AttachmentDropZone",
166
182
  "label": "Attachment drop zone",