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,80 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { DemoDashboardArtifact } from "./demo-card-components.js";
4
+ const DEMO_ARTIFACT_RENDERERS = {
5
+ "demo-dashboard": () => /* @__PURE__ */ jsx(DemoDashboardArtifact, {})
6
+ };
7
+ const DEMO_DASHBOARD_ARTIFACT = {
8
+ id: "launch-dashboard",
9
+ title: "Launch dashboard",
10
+ kind: "demo-dashboard",
11
+ meta: "Dashboard",
12
+ icon: "space_dashboard"
13
+ };
14
+ const LAUNCH_PAGE_HTML = `<!doctype html>
15
+ <html lang="en">
16
+ <head>
17
+ <meta charset="utf-8">
18
+ <meta name="viewport" content="width=device-width, initial-scale=1">
19
+ <meta name="color-scheme" content="light dark">
20
+ <title>Acme Workspaces</title>
21
+ <style>
22
+ * { box-sizing: border-box; }
23
+ body { margin: 0; font-family: ui-sans-serif, system-ui, sans-serif; color: #14181f; background: #f6f4ef; line-height: 1.5; }
24
+ header { display: flex; align-items: center; justify-content: space-between; padding: 20px 32px; }
25
+ .brand { font-weight: 700; letter-spacing: -0.01em; }
26
+ nav { display: flex; gap: 20px; font-size: 14px; color: #55606e; }
27
+ .hero { padding: 56px 32px 40px; max-width: 720px; }
28
+ .eyebrow { font-size: 12px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: #7a5cff; }
29
+ h1 { margin: 12px 0 16px; font-size: clamp(32px, 6vw, 52px); line-height: 1.05; letter-spacing: -0.03em; }
30
+ .lede { margin: 0 0 28px; font-size: 18px; color: #414b58; max-width: 52ch; }
31
+ .cta { display: inline-block; padding: 12px 22px; border-radius: 999px; background: #14181f; color: #fff; font-weight: 600; font-size: 15px; }
32
+ .ghost { margin-left: 16px; font-size: 15px; font-weight: 600; color: #14181f; }
33
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 16px; padding: 8px 32px 48px; }
34
+ .card { padding: 20px; border-radius: 16px; background: #fff; border: 1px solid #e7e3da; }
35
+ .card h2 { margin: 0 0 6px; font-size: 16px; }
36
+ .card p { margin: 0; font-size: 14px; color: #55606e; }
37
+ footer { padding: 20px 32px 32px; font-size: 13px; color: #7b8592; }
38
+ @media (prefers-color-scheme: dark) {
39
+ body { color: #eef0f3; background: #101216; }
40
+ nav, .card p, footer { color: #98a1ad; }
41
+ .eyebrow { color: #a999ff; }
42
+ .lede { color: #c2c8d0; }
43
+ .cta { background: #eef0f3; color: #101216; }
44
+ .ghost { color: #eef0f3; }
45
+ .card { background: #181b21; border-color: #2a2e36; }
46
+ }
47
+ </style>
48
+ </head>
49
+ <body>
50
+ <header>
51
+ <span class="brand">Acme</span>
52
+ <nav><span>Product</span><span>Plans</span><span>Sign in</span></nav>
53
+ </header>
54
+ <section class="hero">
55
+ <span class="eyebrow">Launching this week</span>
56
+ <h1>One workspace for the whole team.</h1>
57
+ <p class="lede">Shared projects, guest seats and ninety days of version history, ready the moment your team joins.</p>
58
+ <span class="cta">Start the free trial</span><span class="ghost">See the plans</span>
59
+ </section>
60
+ <section class="grid">
61
+ <div class="card"><h2>Shared workspaces</h2><p>Every project in one place, open to the people who need it.</p></div>
62
+ <div class="card"><h2>Guest seats</h2><p>Bring in a client or a contractor without buying a full seat.</p></div>
63
+ <div class="card"><h2>Version history</h2><p>Ninety days of every change, one click from restored.</p></div>
64
+ </section>
65
+ <footer>Acme is a made-up company. This page is a demo artifact.</footer>
66
+ </body>
67
+ </html>`;
68
+ const DEMO_PAGE_ARTIFACT = {
69
+ id: "launch-page",
70
+ title: "Launch page",
71
+ kind: "html",
72
+ meta: "Web page",
73
+ icon: "web",
74
+ html: LAUNCH_PAGE_HTML
75
+ };
76
+ export {
77
+ DEMO_ARTIFACT_RENDERERS,
78
+ DEMO_DASHBOARD_ARTIFACT,
79
+ DEMO_PAGE_ARTIFACT
80
+ };
@@ -44,3 +44,5 @@ export declare function DemoStack({ card, renderers }: {
44
44
  card: DemoStackCard;
45
45
  renderers: ChatCardRenderers;
46
46
  }): import("react").JSX.Element;
47
+ /** The dashboard's rows ask the chat, like the welcome board's. */
48
+ export declare function DemoDashboardArtifact(): import("react").JSX.Element;
@@ -8,6 +8,8 @@ import { CodeBlock } from "../components/CodeBlock/CodeBlock.js";
8
8
  import { InterruptCard } from "../components/InterruptCard/InterruptCard.js";
9
9
  import { MessageCard } from "../components/MessageCard/MessageCard.js";
10
10
  import { ToolCall } from "../components/ToolCall/ToolCall.js";
11
+ import { useChatWidget } from "./context.js";
12
+ import { DemoBoard } from "./DemoWelcome.js";
11
13
  const RETRY_RUN_MS = 1400;
12
14
  function DemoToolCall({ card }) {
13
15
  const [status, setStatus] = useState(card.status);
@@ -51,8 +53,13 @@ function DemoStack({ card, renderers }) {
51
53
  return render ? /* @__PURE__ */ jsx("div", { children: render(item.data) }, index) : null;
52
54
  }) });
53
55
  }
56
+ function DemoDashboardArtifact() {
57
+ const { send } = useChatWidget();
58
+ return /* @__PURE__ */ jsx("div", { className: "ds-chat-demo__artifact-board", children: /* @__PURE__ */ jsx(DemoBoard, { onAsk: (prompt) => send(prompt) }) });
59
+ }
54
60
  export {
55
61
  DemoChart,
62
+ DemoDashboardArtifact,
56
63
  DemoInterrupt,
57
64
  DemoStack,
58
65
  DemoToolCall
@@ -1,3 +1,5 @@
1
+ import { artifactCard } from "./artifacts.js";
2
+ import { DEMO_DASHBOARD_ARTIFACT, DEMO_PAGE_ARTIFACT } from "./demo-artifacts.js";
1
3
  import { scriptChat } from "./sim.js";
2
4
  const TRIAL_SIGNUPS = [
3
5
  { week: "W1", signups: 320 },
@@ -192,7 +194,27 @@ const DEMO_SCRIPT = scriptChat().user("How do I get started?").assistant(
192
194
  ]
193
195
  }),
194
196
  followups: ["Go ahead and invite the other three", "How is the trial going?", "How do I get started?"]
195
- }).fallback(
197
+ }).user("Build me a launch dashboard").assistant(
198
+ "Here is the launch on one board: what is running now, the trial signups, the projects and what is waiting on you. The rows that ask a question will ask it here.",
199
+ {
200
+ steps: [
201
+ { status: "Gathering the launch numbers", point: "Signups, seats and open work all belong on one board." },
202
+ { status: "Laying out the dashboard", point: "Too much for a card; build it as an artifact." }
203
+ ],
204
+ card: artifactCard(DEMO_DASHBOARD_ARTIFACT),
205
+ followups: ["Draft the launch page", "How is the trial going?", "Which plan fits a team of five?"]
206
+ }
207
+ ).user("Draft the launch page").assistant(
208
+ "A first draft of the launch page is up beside the chat: the headline, the three things the Team plan adds, and one call to action. The source is behind the Code view.",
209
+ {
210
+ steps: [
211
+ { status: "Reading the launch plan", point: "The page should lead with the workspace, then the plan." },
212
+ { status: "Writing the page", point: "One document, its own styles, no scripts." }
213
+ ],
214
+ card: artifactCard(DEMO_PAGE_ARTIFACT),
215
+ followups: ["Build me a launch dashboard", "What do the plans include?", "How is the trial going?"]
216
+ }
217
+ ).fallback(
196
218
  "The launch is on track overall. Three of the four readiness tracks are green, and the one item I would watch is the **legal review**, since it gates the pricing rollout.",
197
219
  {
198
220
  steps: [
@@ -203,7 +225,7 @@ const DEMO_SCRIPT = scriptChat().user("How do I get started?").assistant(
203
225
  followups: ["Which track is behind?", "Who owns the legal review?", "What happens if pricing slips?"]
204
226
  }
205
227
  ).fallback(
206
- "This chat is a scripted preview of the widget, so free text gets one of a few written replies. The suggestion chips lead through the parts of the story that show off the rich content: a plan, a chart, a diff, a tool call and an approval.",
228
+ "This chat is a scripted preview of the widget, so free text gets one of a few written replies. The suggestion chips lead through the parts of the story that show off the rich content: a plan, a chart, a diff, a tool call, an approval and an artifact.",
207
229
  {
208
230
  steps: [{ status: "Reading the question", point: "Nothing scripted matches this one." }],
209
231
  followups: ["How do I get started?", "What do the plans include?", "How is the trial going?"]
package/chat/demo.css CHANGED
@@ -44,3 +44,9 @@
44
44
  .ds-chat-demo__logo--three {
45
45
  background-color: var(--color-chart-series-3);
46
46
  }
47
+
48
+ /* The dashboard artifact: the welcome board, given the inset a panel's
49
+ live content brings for itself. */
50
+ .ds-chat-demo__artifact-board {
51
+ padding: var(--padding-400);
52
+ }
package/chat/demo.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { SimTiming } from './sim';
2
2
  import { ChatTransport } from './types';
3
3
  export { DEMO_RENDERERS, type DemoProductCard, type DemoChartCard, type DemoSourcesCard, type DemoToolCallCard, type DemoDiffCard, type DemoPlanCard, type DemoInterruptCard, type DemoStackCard, } from './demo-cards';
4
+ export { DEMO_ARTIFACT_RENDERERS, DEMO_DASHBOARD_ARTIFACT, DEMO_PAGE_ARTIFACT, } from './demo-artifacts';
4
5
  export { DEMO_SCRIPT, DEMO_STARTERS } from './demo-script';
5
6
  export { DEMO_MENTIONS, DEMO_MODELS, DEMO_THREAD_GROUPS, DEMO_PROJECTS, DEMO_THREAD_NAMES, DEMO_THREADS, DEMO_WELCOME_OPTIONS, type DemoWelcomeVariant, } from './demo-fixtures';
6
7
  export { demoAgentTabs } from './demo-agent';
package/chat/demo.js CHANGED
@@ -2,13 +2,17 @@ import { createSimTransport } from "./sim.js";
2
2
  import { DEMO_SCRIPT } from "./demo-script.js";
3
3
  import { DEMO_STARTERS } from "./demo-script.js";
4
4
  import { DEMO_RENDERERS } from "./demo-cards.js";
5
+ import { DEMO_ARTIFACT_RENDERERS, DEMO_DASHBOARD_ARTIFACT, DEMO_PAGE_ARTIFACT } from "./demo-artifacts.js";
5
6
  import { DEMO_MENTIONS, DEMO_MODELS, DEMO_PROJECTS, DEMO_THREADS, DEMO_THREAD_GROUPS, DEMO_THREAD_NAMES, DEMO_WELCOME_OPTIONS } from "./demo-fixtures.js";
6
7
  import { demoAgentTabs } from "./demo-agent.js";
7
8
  import { DemoBoard, DemoWelcome } from "./DemoWelcome.js";
8
9
  const createDemoTransport = (timing) => createSimTransport(DEMO_SCRIPT, timing);
9
10
  export {
11
+ DEMO_ARTIFACT_RENDERERS,
12
+ DEMO_DASHBOARD_ARTIFACT,
10
13
  DEMO_MENTIONS,
11
14
  DEMO_MODELS,
15
+ DEMO_PAGE_ARTIFACT,
12
16
  DEMO_PROJECTS,
13
17
  DEMO_RENDERERS,
14
18
  DEMO_SCRIPT,
package/chat/index.d.ts CHANGED
@@ -19,8 +19,10 @@ export { ChatLauncher, CHAT_PANEL_ID, type ChatLauncherProps } from './ChatLaunc
19
19
  export { ChatThreads, type ChatThreadsProps } from './ChatThreads';
20
20
  export { ChatTabs, type ChatTabsProps } from './ChatTabs';
21
21
  export { ChatAgentPane, type ChatAgentPaneProps } from './ChatAgentPane';
22
- export { useChatWidget, isInternalPath, createFeedbackPoster, type ChatView, type ChatCardRenderers, type ChatLinkRenderer, type ChatWidgetContextValue, type FeedbackSink, } from './context';
23
- export { DOCK_QUERY, TAKEOVER_QUERY, THREADS_RAIL_MIN_WIDTH, ASIDE_RAIL_MIN_WIDTH, useTakeoverViewport, } from './queries';
22
+ export { ChatArtifactPane, type ChatArtifactPaneProps } from './ChatArtifactPane';
23
+ export { ChatArtifactCard, type ChatArtifactCardProps } from './ChatArtifactCard';
24
+ export { useChatWidget, isInternalPath, createFeedbackPoster, type ChatView, type ChatArtifactRenderers, type ChatCardRenderers, type ChatLinkRenderer, type ChatWidgetContextValue, type FeedbackSink, } from './context';
25
+ export { DOCK_QUERY, TAKEOVER_QUERY, THREADS_RAIL_MIN_WIDTH, ASIDE_RAIL_MIN_WIDTH, ARTIFACT_MIN_COLUMN_WIDTH, ASIDE_WITH_THREADS_MIN_WIDTH, useTakeoverViewport, } from './queries';
24
26
  export { useChat, DEFAULT_PLACEHOLDER_COPY, type ChatState, type ChatPlaceholderCopy, } from './useChat';
25
27
  export { useThreads, THREAD_NAMING_DELAY_MS, type ThreadsOptions, type ThreadsState } from './threads';
26
28
  export { createFetchTransport, DEFAULT_FETCH_COPY, FIRST_BYTE_TIMEOUT_MS, MAX_TRANSPORT_TURNS, type FetchTransportOptions, type FetchTransportCopy, } from './transport';
@@ -30,5 +32,6 @@ export { SUGGESTION_MAX_CHARS, fitsChip } from './suggestions';
30
32
  export { STARTER_COUNT, pickStarters, namedStarter, hashString, mulberry32, } from './starters';
31
33
  export { greetingFor, readGreeting, serverGreeting, subscribeClock, DEFAULT_GREETING_COPY, type GreetingCopy, } from './greeting';
32
34
  export { CHAT_PLACEMENTS, chatPlacement, DOCK_MIN_WIDTH, DOCK_MAX_WIDTH, FLOAT_MAX_SCALE, FLOAT_EDGE_GAP, dockWidthFromDrag, isFloatDragStart, clampFloatPosition, type ChatPlacement, type ChatSide, } from './placement';
33
- export { CHAT_EVENT_KINDS, type ChatCard, type ChatEvent, type ChatTransport, type ChatTransportMessage, type ChatTurn, type ChatModelOption, type ChatFeedbackVerdict, type LivePhase, type LiveResponse, type Starter, } from './types';
35
+ export { artifactCard, cardArtifact } from './artifacts';
36
+ export { ARTIFACT_CARD_KIND, CHAT_EVENT_KINDS, type ChatArtifact, type ChatCard, type ChatEvent, type ChatTransport, type ChatTransportMessage, type ChatTurn, type ChatModelOption, type ChatFeedbackVerdict, type LivePhase, type LiveResponse, type Starter, } from './types';
34
37
  export { chatWidget, chatWidgetRegistry, WIDGET_COUNT, type ChatWidgetMeta } from './registry';
package/chat/index.js CHANGED
@@ -4,8 +4,10 @@ import { CHAT_PANEL_ID, ChatLauncher } from "./ChatLauncher.js";
4
4
  import { ChatThreads } from "./ChatThreads.js";
5
5
  import { ChatTabs } from "./ChatTabs.js";
6
6
  import { ChatAgentPane } from "./ChatAgentPane.js";
7
+ import { ChatArtifactPane } from "./ChatArtifactPane.js";
8
+ import { ChatArtifactCard } from "./ChatArtifactCard.js";
7
9
  import { createFeedbackPoster, isInternalPath, useChatWidget } from "./context.js";
8
- import { ASIDE_RAIL_MIN_WIDTH, DOCK_QUERY, TAKEOVER_QUERY, THREADS_RAIL_MIN_WIDTH, useTakeoverViewport } from "./queries.js";
10
+ import { ARTIFACT_MIN_COLUMN_WIDTH, ASIDE_RAIL_MIN_WIDTH, ASIDE_WITH_THREADS_MIN_WIDTH, DOCK_QUERY, TAKEOVER_QUERY, THREADS_RAIL_MIN_WIDTH, useTakeoverViewport } from "./queries.js";
9
11
  import { DEFAULT_PLACEHOLDER_COPY, useChat } from "./useChat.js";
10
12
  import { THREAD_NAMING_DELAY_MS, useThreads } from "./threads.js";
11
13
  import { DEFAULT_FETCH_COPY, FIRST_BYTE_TIMEOUT_MS, MAX_TRANSPORT_TURNS, createFetchTransport } from "./transport.js";
@@ -15,14 +17,20 @@ import { SUGGESTION_MAX_CHARS, fitsChip } from "./suggestions.js";
15
17
  import { STARTER_COUNT, hashString, mulberry32, namedStarter, pickStarters } from "./starters.js";
16
18
  import { DEFAULT_GREETING_COPY, greetingFor, readGreeting, serverGreeting, subscribeClock } from "./greeting.js";
17
19
  import { CHAT_PLACEMENTS, DOCK_MAX_WIDTH, DOCK_MIN_WIDTH, FLOAT_EDGE_GAP, FLOAT_MAX_SCALE, chatPlacement, clampFloatPosition, dockWidthFromDrag, isFloatDragStart } from "./placement.js";
18
- import { CHAT_EVENT_KINDS } from "./types.js";
20
+ import { artifactCard, cardArtifact } from "./artifacts.js";
21
+ import { ARTIFACT_CARD_KIND, CHAT_EVENT_KINDS } from "./types.js";
19
22
  import { WIDGET_COUNT, chatWidget, chatWidgetRegistry } from "./registry.js";
20
23
  export {
24
+ ARTIFACT_CARD_KIND,
25
+ ARTIFACT_MIN_COLUMN_WIDTH,
21
26
  ASIDE_RAIL_MIN_WIDTH,
27
+ ASIDE_WITH_THREADS_MIN_WIDTH,
22
28
  CHAT_EVENT_KINDS,
23
29
  CHAT_PANEL_ID,
24
30
  CHAT_PLACEMENTS,
25
31
  ChatAgentPane,
32
+ ChatArtifactCard,
33
+ ChatArtifactPane,
26
34
  ChatLauncher,
27
35
  ChatProvider,
28
36
  ChatTabs,
@@ -49,6 +57,8 @@ export {
49
57
  THREADS_RAIL_MIN_WIDTH,
50
58
  THREAD_NAMING_DELAY_MS,
51
59
  WIDGET_COUNT,
60
+ artifactCard,
61
+ cardArtifact,
52
62
  chatPlacement,
53
63
  chatWidget,
54
64
  chatWidgetRegistry,
package/chat/queries.d.ts CHANGED
@@ -2,7 +2,16 @@ export declare const DOCK_QUERY = "(min-width: 1440px)";
2
2
  export declare const TAKEOVER_QUERY = "(max-width: 719px)";
3
3
  /** The widget's own width thresholds for seating its rails inline. */
4
4
  export declare const THREADS_RAIL_MIN_WIDTH = 720;
5
- export declare const ASIDE_RAIL_MIN_WIDTH = 860;
5
+ /** The trailing seat's threshold, one number for both of its tenants: at
6
+ this width and up the agent pane and the artifact panel sit beside the
7
+ conversation, below it each rises as a bottom sheet. */
8
+ export declare const ASIDE_RAIL_MIN_WIDTH = 760;
9
+ /** Below this the inline threads rail yields its room to a trailing panel
10
+ seated inline, and goes back behind the header's hamburger. */
11
+ export declare const ASIDE_WITH_THREADS_MIN_WIDTH = 1040;
12
+ /** The floor either column keeps while an artifact is dragged wider or
13
+ narrower: the conversation never loses more than this, nor the panel. */
14
+ export declare const ARTIFACT_MIN_COLUMN_WIDTH = 320;
6
15
  /** Whether the viewport is a phone width (TAKEOVER_QUERY), false on the
7
16
  server and for the first client render. Shared by the launcher and any
8
17
  host stage, which both hand the widget its phone layout. */
package/chat/queries.js CHANGED
@@ -3,7 +3,9 @@ import { useSyncExternalStore } from "react";
3
3
  const DOCK_QUERY = "(min-width: 1440px)";
4
4
  const TAKEOVER_QUERY = "(max-width: 719px)";
5
5
  const THREADS_RAIL_MIN_WIDTH = 720;
6
- const ASIDE_RAIL_MIN_WIDTH = 860;
6
+ const ASIDE_RAIL_MIN_WIDTH = 760;
7
+ const ASIDE_WITH_THREADS_MIN_WIDTH = 1040;
8
+ const ARTIFACT_MIN_COLUMN_WIDTH = 320;
7
9
  const subscribeTakeover = (onChange) => {
8
10
  const media = window.matchMedia(TAKEOVER_QUERY);
9
11
  media.addEventListener("change", onChange);
@@ -14,7 +16,9 @@ function useTakeoverViewport() {
14
16
  return useSyncExternalStore(subscribeTakeover, readTakeover, () => false);
15
17
  }
16
18
  export {
19
+ ARTIFACT_MIN_COLUMN_WIDTH,
17
20
  ASIDE_RAIL_MIN_WIDTH,
21
+ ASIDE_WITH_THREADS_MIN_WIDTH,
18
22
  DOCK_QUERY,
19
23
  TAKEOVER_QUERY,
20
24
  THREADS_RAIL_MIN_WIDTH,
@@ -35,6 +35,8 @@ declare const _default: {
35
35
  "AgentRail",
36
36
  "AgentStatus",
37
37
  "AiButton",
38
+ "ArtifactCard",
39
+ "ArtifactPanel",
38
40
  "AttachmentDropZone",
39
41
  "AttachmentGroup",
40
42
  "ChatHeader",
@@ -44,6 +46,7 @@ declare const _default: {
44
46
  "CodeBlock",
45
47
  "Composer",
46
48
  "DropdownMenu",
49
+ "EmptyState",
47
50
  "Mention",
48
51
  "ModelPicker",
49
52
  "PixelAvatar",
@@ -59,7 +62,9 @@ declare const _default: {
59
62
  "ChatLauncher",
60
63
  "ChatThreads",
61
64
  "ChatTabs",
62
- "ChatAgentPane"
65
+ "ChatAgentPane",
66
+ "ChatArtifactPane",
67
+ "ChatArtifactCard"
63
68
  ]
64
69
  }
65
70
  ]
@@ -1,5 +1,5 @@
1
1
  const $comment = "The widget registry: the fourth kind of thing the package ships, between the components and the templates. One member by design. `parts` names the library components the widget composes (held to src/components/registry.json by scripts/validate-chat-widget.mjs), `subpaths` the package exports it occupies (held to scripts/package-manifest.mjs), `category` the component category whose section and sidebar group its page joins (held to src/components/registry.json), `peers` the optional peer dependencies each subpath needs (held to package.json). The widget's page under /components, the /docs/chat page, the chat corpus and the MCP tools read this file; never type its facts elsewhere.";
2
- const widgets = [{ "name": "ChatWidget", "slug": "ai-chat", "label": "AI chat", "category": "ai", "description": "The whole AI chat, assembled: the panel, the streaming replies, the history, the cards a reply can carry and the server handlers, set up with props.", "command": "init chat", "subpaths": { "./chat": "The widget, the provider, the launcher and the engine under them.", "./chat/server": "The streaming chat handler and its follow-ups and feedback siblings, for any web-standard route.", "./chat/upstash": "Rate limits, a daily budget and the exchange log over Upstash Redis, as the handlers' guardrails.", "./chat/demo": "A scripted conversation with rich cards and fixtures, for a preview that needs no key.", "./chat/chat.css": "The widget's stylesheet." }, "peers": { "./chat": ["react-markdown", "remark-gfm"], "./chat/server": ["@anthropic-ai/sdk"], "./chat/upstash": ["@upstash/ratelimit", "@upstash/redis"], "./chat/demo": ["recharts"] }, "parts": ["AgentRail", "AgentStatus", "AiButton", "AttachmentDropZone", "AttachmentGroup", "ChatHeader", "ChatMessage", "ChatThread", "CircularButton", "CodeBlock", "Composer", "DropdownMenu", "Mention", "ModelPicker", "PixelAvatar", "PromptSuggestions", "Prose", "Reasoning", "ThreadPanel", "ThreadTabs"], "documented": ["ChatWidget", "ChatProvider", "ChatLauncher", "ChatThreads", "ChatTabs", "ChatAgentPane"] }];
2
+ const widgets = [{ "name": "ChatWidget", "slug": "ai-chat", "label": "AI chat", "category": "ai", "description": "The whole AI chat, assembled: the panel, the streaming replies, the history, the cards a reply can carry and the server handlers, set up with props.", "command": "init chat", "subpaths": { "./chat": "The widget, the provider, the launcher and the engine under them.", "./chat/server": "The streaming chat handler and its follow-ups and feedback siblings, for any web-standard route.", "./chat/upstash": "Rate limits, a daily budget and the exchange log over Upstash Redis, as the handlers' guardrails.", "./chat/demo": "A scripted conversation with rich cards and fixtures, for a preview that needs no key.", "./chat/chat.css": "The widget's stylesheet." }, "peers": { "./chat": ["react-markdown", "remark-gfm"], "./chat/server": ["@anthropic-ai/sdk"], "./chat/upstash": ["@upstash/ratelimit", "@upstash/redis"], "./chat/demo": ["recharts"] }, "parts": ["AgentRail", "AgentStatus", "AiButton", "ArtifactCard", "ArtifactPanel", "AttachmentDropZone", "AttachmentGroup", "ChatHeader", "ChatMessage", "ChatThread", "CircularButton", "CodeBlock", "Composer", "DropdownMenu", "EmptyState", "Mention", "ModelPicker", "PixelAvatar", "PromptSuggestions", "Prose", "Reasoning", "ThreadPanel", "ThreadTabs"], "documented": ["ChatWidget", "ChatProvider", "ChatLauncher", "ChatThreads", "ChatTabs", "ChatAgentPane", "ChatArtifactPane", "ChatArtifactCard"] }];
3
3
  const registry = {
4
4
  $comment,
5
5
  widgets
package/chat/types.d.ts CHANGED
@@ -12,6 +12,42 @@ export interface ChatCard {
12
12
  /** Whatever the renderer for `kind` expects. Must survive JSON. */
13
13
  data: unknown;
14
14
  }
15
+ /**
16
+ * Something the chat made that is worth more room than a turn can give it: a
17
+ * dashboard, a document, a page, a site. It travels as an ordinary card of
18
+ * kind `ARTIFACT_CARD_KIND`, so any transport that can send a card can send
19
+ * one; the thread draws it as a tile, and the tile opens the artifact panel
20
+ * beside the conversation.
21
+ */
22
+ export interface ChatArtifact {
23
+ /** Stable identity: the same id reopened is the same artifact. */
24
+ id: string;
25
+ /** The artifact's name, on the tile and in the panel's header. */
26
+ title: string;
27
+ /**
28
+ * How to draw it. `html` frames the `html` document, `url` frames the
29
+ * `url`, `code` shows the `code` alone; any other kind is drawn by the
30
+ * provider's `artifactRenderers`. An unknown kind with no renderer falls
31
+ * back to whichever of `html`, `url` and `code` it carries.
32
+ */
33
+ kind: string;
34
+ /** A quiet line beside the name: what it is, or which version. */
35
+ meta?: string;
36
+ /** A Material Symbol name for the tile and the header. */
37
+ icon?: string;
38
+ /** A complete HTML document, drawn in a sandboxed frame. */
39
+ html?: string;
40
+ /** A page to frame. Only http(s) URLs and same-site paths are framed. */
41
+ url?: string;
42
+ /** The source, offered as the panel's Code view. An `html` artifact offers its document when this is absent. */
43
+ code?: string;
44
+ /** The source's language. */
45
+ language?: string;
46
+ /** Whatever a custom renderer for `kind` expects. Must survive JSON. */
47
+ data?: unknown;
48
+ }
49
+ /** The card kind an artifact travels as. Its `data` is a `ChatArtifact`. */
50
+ export declare const ARTIFACT_CARD_KIND = "artifact";
15
51
  export type ChatEvent =
16
52
  /** A live status step: the AgentStatus label, with an optional reasoning trace point. */
17
53
  {
package/chat/types.js CHANGED
@@ -1,3 +1,4 @@
1
+ const ARTIFACT_CARD_KIND = "artifact";
1
2
  const CHAT_EVENT_KINDS = [
2
3
  "status",
3
4
  "model",
@@ -12,5 +13,6 @@ const CHAT_EVENT_KINDS = [
12
13
  "error"
13
14
  ];
14
15
  export {
16
+ ARTIFACT_CARD_KIND,
15
17
  CHAT_EVENT_KINDS
16
18
  };
@@ -38,11 +38,12 @@
38
38
  }
39
39
 
40
40
  /* ============================================
41
- COLLAPSE — the way out, pinned to the LEADING corner. The trailing one
42
- belongs to the host: a rail seated at a card's edge puts the card's own
43
- controls above it, and the collapse would sit under them. A chevron,
44
- never a cross, for the same reason: two crosses a few pixels apart read
45
- as two ways to dismiss the same thing.
41
+ COLLAPSE — the way out. By default a chevron pinned to the LEADING
42
+ corner: the trailing one usually belongs to the host, since a rail seated
43
+ at a card's edge puts the card's own controls above it, and two crosses a
44
+ few pixels apart read as two ways to dismiss the same thing. A host that
45
+ has weighed that and wants the way out where a panel's close is looked
46
+ for takes the close variant below: a cross in the trailing corner.
46
47
  ============================================ */
47
48
 
48
49
  .ds-agent-rail__collapse {
@@ -72,6 +73,14 @@
72
73
  transition: background-color var(--motion-duration-base) var(--motion-ease-standard);
73
74
  }
74
75
 
76
+ /* The close variant: a cross in the trailing corner, where a panel's own
77
+ way out usually sits. The host that asks for it has decided its controls
78
+ and this one do not collide. */
79
+ .ds-agent-rail__collapse--close {
80
+ left: auto;
81
+ right: var(--padding-300);
82
+ }
83
+
75
84
  .ds-agent-rail__collapse .material-symbols-rounded {
76
85
  --icon-size: var(--icon-size-600);
77
86
  }
@@ -49,6 +49,8 @@ type AgentRailOwnProps = {
49
49
  onCollapse?: () => void;
50
50
  /** Accessible name for the collapse affordance. */
51
51
  collapseLabel?: string;
52
+ /** How the collapse affordance is drawn. `chevron` (the default) pins a chevron to the leading corner, clear of a host's own controls. `close` pins a cross to the trailing corner, for a rail that is its card's only way out or whose host wants the way out where a panel's close usually is. */
53
+ collapseVariant?: 'chevron' | 'close';
52
54
  /** Rendered under the pane, outside its scroll — a sign-out row, a quiet hint. */
53
55
  footer?: React.ReactNode;
54
56
  /** Additional CSS classes */
@@ -15,6 +15,7 @@ const AgentRail = React.forwardRef(
15
15
  ariaLabel,
16
16
  onCollapse,
17
17
  collapseLabel = "Collapse the agent panel",
18
+ collapseVariant = "chevron",
18
19
  footer,
19
20
  className = "",
20
21
  ...rest
@@ -26,10 +27,10 @@ const AgentRail = React.forwardRef(
26
27
  "button",
27
28
  {
28
29
  type: "button",
29
- className: `${baseClass}__collapse`,
30
+ className: `${baseClass}__collapse${collapseVariant === "close" ? ` ${baseClass}__collapse--close` : ""}`,
30
31
  "aria-label": collapseLabel,
31
32
  onClick: onCollapse,
32
- children: /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: "chevron_right" })
33
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: collapseVariant === "close" ? "close" : "chevron_right" })
33
34
  }
34
35
  ),
35
36
  profile && /* @__PURE__ */ jsxs("header", { className: `${baseClass}__header`, children: [
@@ -0,0 +1,106 @@
1
+ /* ============================================
2
+ ARTIFACT CARD COMPONENT
3
+ An artifact's place in a conversation: a mark, the name over its kind,
4
+ and a cue for what a press does. One row, the width of a card, and a
5
+ toggle button: pressed while its artifact is on stage in the panel.
6
+ ============================================ */
7
+
8
+ /* Base */
9
+
10
+ .ds-artifact-card {
11
+ box-sizing: border-box;
12
+ display: flex;
13
+ align-items: center;
14
+ gap: var(--gap-300);
15
+ width: 100%;
16
+ max-width: 360px;
17
+ padding: var(--padding-300);
18
+ border: var(--border-025) solid var(--color-bg-container-border);
19
+ border-radius: var(--radius-600);
20
+ background-color: var(--color-bg-container-primary);
21
+ cursor: pointer;
22
+ text-align: left;
23
+ transition:
24
+ background-color var(--motion-duration-fast) var(--motion-ease-standard),
25
+ border-color var(--motion-duration-fast) var(--motion-ease-standard);
26
+ }
27
+
28
+ .ds-artifact-card:hover {
29
+ background-color: var(--color-action-passive-bg-hover);
30
+ }
31
+
32
+ .ds-artifact-card:focus-visible {
33
+ outline: 2px solid var(--color-action-primary-bg);
34
+ outline-offset: 2px;
35
+ }
36
+
37
+ /* States */
38
+
39
+ /* On stage, the tile holds the "on stage" fill ThreadPanel's active row and
40
+ ThreadTabs' active tab wear. */
41
+ .ds-artifact-card--open {
42
+ background-color: var(--color-bg-container-tertiary);
43
+ }
44
+
45
+ /* The mark steps to the card's resting fill while the card holds the
46
+ on-stage one, so it stays a tile on the card instead of dissolving into
47
+ a fill of nearly its own value. */
48
+ .ds-artifact-card--open .ds-artifact-card__mark {
49
+ background-color: var(--color-bg-container-primary);
50
+ }
51
+
52
+ .ds-artifact-card__mark {
53
+ --icon-size: var(--icon-size-600);
54
+ display: flex;
55
+ flex: none;
56
+ align-items: center;
57
+ justify-content: center;
58
+ width: 40px;
59
+ height: 40px;
60
+ /* Concentric with the card: its radius less the padding between them,
61
+ computed from the live tokens so the corner holds under any theme's
62
+ radius scale, and squares off with a squared theme. */
63
+ border-radius: max(0px, calc(var(--radius-600) - var(--padding-300)));
64
+ background-color: var(--color-bg-container-secondary);
65
+ color: var(--color-icon-primary);
66
+ }
67
+
68
+ .ds-artifact-card__text {
69
+ display: flex;
70
+ flex: 1;
71
+ flex-direction: column;
72
+ min-width: 0;
73
+ }
74
+
75
+ .ds-artifact-card__title {
76
+ overflow: hidden;
77
+ text-overflow: ellipsis;
78
+ white-space: nowrap;
79
+ font-family: var(--font-paragraph-sm-emphasis-family);
80
+ font-size: var(--font-paragraph-sm-emphasis-size);
81
+ font-weight: var(--font-paragraph-sm-emphasis-weight);
82
+ line-height: var(--font-paragraph-sm-emphasis-line-height);
83
+ color: var(--color-text-primary);
84
+ }
85
+
86
+ .ds-artifact-card__meta {
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-card__cue {
98
+ --icon-size: var(--icon-size-500);
99
+ flex: none;
100
+ color: var(--color-icon-secondary);
101
+ }
102
+
103
+ .ds-artifact-card:disabled {
104
+ opacity: 0.4;
105
+ cursor: not-allowed;
106
+ }
@@ -0,0 +1,27 @@
1
+ import { default as React } from 'react';
2
+ /** Props owned by ArtifactCard itself — everything else falls through to the root `<button>`. */
3
+ type ArtifactCardOwnProps = {
4
+ /** The artifact's name. Collides with the native `title` tooltip attribute on purpose: the card shows its name, it does not hide it in a tooltip. */
5
+ title: React.ReactNode;
6
+ /** A quiet line under the name: what kind of thing this is, or which version. */
7
+ meta?: React.ReactNode;
8
+ /** The leading mark — Material Symbol name (string) or custom element (ReactNode). */
9
+ icon?: string | React.ReactNode;
10
+ /** Whether this card's artifact is on stage in its panel. The card holds the on-stage fill, announces itself pressed, and its trailing cue turns from open to close. */
11
+ open?: boolean;
12
+ /** Additional CSS classes */
13
+ className?: string;
14
+ };
15
+ export interface ArtifactCardProps extends ArtifactCardOwnProps, Omit<React.ComponentPropsWithoutRef<'button'>, keyof ArtifactCardOwnProps | 'type'> {
16
+ }
17
+ /**
18
+ * ArtifactCard is an artifact's place in a conversation: one row naming
19
+ * something the chat made, which opens it in an ArtifactPanel. It is a
20
+ * toggle button, so `open` is the whole state and `onClick` the whole
21
+ * behaviour; the host decides what opening means.
22
+ *
23
+ * Hook-free and stateless, so it renders from a Server Component until it
24
+ * is given a handler.
25
+ */
26
+ export declare const ArtifactCard: React.ForwardRefExoticComponent<ArtifactCardProps & React.RefAttributes<HTMLButtonElement>>;
27
+ export {};
@@ -0,0 +1,29 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import "./ArtifactCard.css";
4
+ import "../../fonts/material-symbols.css";
5
+ const baseClass = "ds-artifact-card";
6
+ const ArtifactCard = React.forwardRef(
7
+ ({ title, meta, icon = "draft", open = false, className = "", ...rest }, ref) => /* @__PURE__ */ jsxs(
8
+ "button",
9
+ {
10
+ ref,
11
+ ...rest,
12
+ type: "button",
13
+ className: [baseClass, open && `${baseClass}--open`, className].filter(Boolean).join(" "),
14
+ "aria-pressed": open,
15
+ children: [
16
+ typeof icon === "string" ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__mark material-symbols-rounded`, "aria-hidden": "true", children: icon }) : /* @__PURE__ */ jsx("span", { className: `${baseClass}__mark`, children: icon }),
17
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__text`, children: [
18
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__title`, children: title }),
19
+ meta && /* @__PURE__ */ jsx("span", { className: `${baseClass}__meta`, children: meta })
20
+ ] }),
21
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__cue material-symbols-rounded`, "aria-hidden": "true", children: open ? "close" : "open_in_new_down" })
22
+ ]
23
+ }
24
+ )
25
+ );
26
+ ArtifactCard.displayName = "ArtifactCard";
27
+ export {
28
+ ArtifactCard
29
+ };