- Accordion
- Alert
- Alert Dialog
- Autocomplete
- Auth Surface
- Avatar
- Badge
- Browse Catalog Dialog
- Button
- Card
- Checkbox
- Checkbox Group
- Collapsible
- Combobox
- Command
- Connector Setup Dialog
- Cookie Banner
- Dialog
- Directory Card
- Directory Detail
- Directory Skeleton
- DrawerНовое
- Token Parts Input
- Empty
- Field
- Fieldset
- File Preview Modal
- File Preview Skeleton
- Form
- Frame
- Group
- Icon
- Input
- Input Group
- Kbd
- Label
- Legal Shell
- Menu
- Mermaid Diagram
- Mind Map Diagram
- Not Found Screen
- Onboarding Frame
- Popover
- PDF Thumbnail
- Personalization Landing
- Preview Card
- Pricing Page
- Progress
- Radio Group
- Ring Spinner
- Scroll Area
- Select
- Separator
- Settings Page
- Settings Skills
- Settings Connectors
- Settings Capabilities
- Settings Usage
- Settings Account
- Settings Billing
- Sheet
- Sidebar
- Skeleton
- Skill Create Dialog
- Slider
- Spinner
- Stat
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Tooltip
- Компоненты AI
- Chat Conversation
- Chat Message
- Chat Response
- Chat Suggestion
- Chat Prompt Input
- Slash Highlighted Textarea
- Chat Search Dialog
- Chat Skill Doc
- Chat Connector Detail
- Chat Attachments
- Chat File Card
- Chat Token Chip
- Chat Code Block
- Chat Image
- Chat Inline Citation
- Chat Sources
- Chat Web Search
- Chat Research
- Chat Source
- Chat Actions
- Chat Context
- Chat Loader
- Chat Compaction
- Chat Timeline
- Chat Snippet
- Chat Terminal
- Chat Stack Trace
- Chat Test Results
- Chat File Tree
- Chat Environment Variables
- Chat Audio Player
- Chat Transcription
- Chat Speech Input
- Chat Mic Selector
- Chat Voice Selector
- Chat Agent
- Chat Persona
- Chat Connection
- Chat Connector Suggestion
- Chat Queue
- Chat Checkpoint
- Chat Confirmation
- Chat Artifact
- Chat JSX Preview
- Chat Schema Display
- Chat Package Info
- Chat Commit
- Chat Plan
- Chat Open In Chat
- Chat Sandbox
- Chat Model Selector
- Chat Canvas
- Chat Node
- Chat Edge
Chat Artifact
Surface'ы артефактов чата — side-panel контейнер (header/actions) и borderless inline-хост живого артефакта прямо в потоке.
page.tsx — Cache Components
Сгенерировано ассистентом · ~32 строки · TSX
// app/products/[id]/page.tsx"use cache";
import { cacheTag } from "next/cache";
export default async function ProductPage({ params }) { const { id } = await params; cacheTag(`product:${id}`); const product = await db.product.get(id); return <Product data={product} />;}"use client";
import { IconDownload, IconExternalLink } from "@tabler/icons-react";
import { Button } from "@/components/ui/button";
import {
ChatArtifact,
ChatArtifactAction,
ChatArtifactActions,
ChatArtifactClose,
ChatArtifactContent,
ChatArtifactDescription,
ChatArtifactFooter,
ChatArtifactHeader,
ChatArtifactInfo,
ChatArtifactTitle,
} from "@/components/ui/chat-artifact";
import {
ChatCodeBlock,
ChatCodeBlockActions,
ChatCodeBlockCopyButton,
ChatCodeBlockFilename,
ChatCodeBlockHeader,
} from "@/components/ui/chat-code-block";
const code = `// app/products/[id]/page.tsx
"use cache";
import { cacheTag } from "next/cache";
export default async function ProductPage({ params }) {
const { id } = await params;
cacheTag(\`product:\${id}\`);
const product = await db.product.get(id);
return <Product data={product} />;
}`;
export default function Particle() {
return (
<div className="h-130 w-full max-w-2xl p-4">
<ChatArtifact>
<ChatArtifactHeader>
<ChatArtifactInfo>
<ChatArtifactTitle>page.tsx — Cache Components</ChatArtifactTitle>
<ChatArtifactDescription>
Сгенерировано ассистентом · ~32 строки · TSX
</ChatArtifactDescription>
</ChatArtifactInfo>
<ChatArtifactActions>
<ChatArtifactAction label="Скачать">
<IconDownload aria-hidden="true" stroke={1.5} />
</ChatArtifactAction>
<ChatArtifactAction label="Открыть в новой вкладке">
<IconExternalLink aria-hidden="true" stroke={1.5} />
</ChatArtifactAction>
<ChatArtifactClose />
</ChatArtifactActions>
</ChatArtifactHeader>
<ChatArtifactContent>
<div className="p-3">
<ChatCodeBlock code={code} language="tsx" showLineNumbers>
<ChatCodeBlockHeader>
<ChatCodeBlockFilename>page.tsx</ChatCodeBlockFilename>
<ChatCodeBlockActions>
<ChatCodeBlockCopyButton />
</ChatCodeBlockActions>
</ChatCodeBlockHeader>
</ChatCodeBlock>
</div>
</ChatArtifactContent>
<ChatArtifactFooter>
<span className="text-muted-foreground text-xs">
v3 · автосохранение 14:02
</span>
<Button size="sm">Применить патч</Button>
</ChatArtifactFooter>
</ChatArtifact>
</div>
);
}
Установка
pnpm dlx shadcn@latest add @oracul/chat-artifact
Использование
Panel — side-panel контейнер
import {
ChatArtifact,
ChatArtifactAction,
ChatArtifactActions,
ChatArtifactClose,
ChatArtifactContent,
ChatArtifactDescription,
ChatArtifactFooter,
ChatArtifactHeader,
ChatArtifactInfo,
ChatArtifactTitle,
} from "@/components/ui/chat-artifact";
<ChatArtifact className="h-[600px] w-[480px]">
<ChatArtifactHeader>
<ChatArtifactInfo>
<ChatArtifactTitle>Landing page draft</ChatArtifactTitle>
<ChatArtifactDescription>
Hero · Features · Pricing · Footer
</ChatArtifactDescription>
</ChatArtifactInfo>
<ChatArtifactActions>
<ChatArtifactAction label="Download">
<DownloadIcon aria-hidden="true" />
</ChatArtifactAction>
<ChatArtifactClose onClick={close} />
</ChatArtifactActions>
</ChatArtifactHeader>
<ChatArtifactContent>
{/* Code, web-preview, image, schema — что угодно */}
<ChatJsxPreview jsx={modelJsx} components={{ Button }} />
</ChatArtifactContent>
<ChatArtifactFooter>
<span className="text-muted-foreground text-xs">v3 · auto-saved</span>
<Button size="sm">Apply changes</Button>
</ChatArtifactFooter>
</ChatArtifact>Калькулятор тарифов
· React JSX
Командный тариф
$12 / место / месяц
"use client";
import {
IconCheck,
IconChevronDown,
IconCopy,
IconDownload,
IconExternalLink,
} from "@tabler/icons-react";
import { useRef, useState } from "react";
import { Button } from "@/components/ui/button";
import {
ChatArtifact,
ChatArtifactAction,
ChatArtifactActions,
ChatArtifactClose,
ChatArtifactContent,
ChatArtifactDescription,
ChatArtifactHeader,
ChatArtifactInfo,
ChatArtifactTitle,
} from "@/components/ui/chat-artifact";
import {
Menu,
MenuItem,
MenuPopup,
MenuTrigger,
} from "@/components/ui/menu";
export default function Particle() {
const [copied, setCopied] = useState(false);
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
const handleCopy = () => {
if (timer.current) clearTimeout(timer.current);
setCopied(true);
timer.current = setTimeout(() => setCopied(false), 1600);
};
return (
<div className="h-90 w-full max-w-2xl p-4">
<ChatArtifact>
<ChatArtifactHeader className="h-12 px-4">
<ChatArtifactInfo orientation="inline">
<ChatArtifactTitle className="truncate text-lg">
Калькулятор тарифов
</ChatArtifactTitle>
<ChatArtifactDescription className="shrink-0">
· React JSX
</ChatArtifactDescription>
</ChatArtifactInfo>
<ChatArtifactActions>
<ChatArtifactAction
label={copied ? "Скопировано" : "Копировать"}
onClick={handleCopy}
>
{copied ? (
<IconCheck aria-hidden="true" stroke={1.5} />
) : (
<IconCopy aria-hidden="true" stroke={1.5} />
)}
</ChatArtifactAction>
<Menu>
<MenuTrigger
render={
<ChatArtifactAction label="Параметры скачивания">
<IconChevronDown aria-hidden="true" stroke={1.5} />
</ChatArtifactAction>
}
/>
<MenuPopup align="end" className="min-w-44">
<MenuItem closeOnClick>
<IconDownload
aria-hidden="true"
className="size-4"
stroke={1.5}
/>
Скачать
</MenuItem>
<MenuItem closeOnClick>
<IconExternalLink
aria-hidden="true"
className="size-4"
stroke={1.5}
/>
Открыть в новой вкладке
</MenuItem>
</MenuPopup>
</Menu>
<ChatArtifactClose />
</ChatArtifactActions>
</ChatArtifactHeader>
<ChatArtifactContent className="grid place-items-center p-6">
<div className="w-full max-w-xs rounded-lg border bg-background p-4 shadow-sm">
<p className="font-semibold text-sm">Командный тариф</p>
<p className="mt-1 text-muted-foreground text-xs">
$12 / место / месяц
</p>
<Button className="mt-3 w-full" size="sm">
Выбрать тариф
</Button>
</div>
</ChatArtifactContent>
</ChatArtifact>
</div>
);
}
Claude-style однострочная шапка — ChatArtifactInfo orientation="inline" ставит заголовок и тип-тег на одну baseline-линию (Title · Type), а паттерн «скачать» собирается из Menu + MenuTrigger, отрендеренного как ChatArtifactAction.
Inline — живой артефакт в потоке
import {
ChatArtifactInline,
ChatArtifactInlineFallback,
ChatArtifactInlineMenu,
} from "@/components/ui/chat-artifact";
<ChatArtifactInline
host="auto"
status={status}
actions={
<ChatArtifactInlineMenu
onCopy={() => copyArtifactSource(artifact)}
onDownload={() => downloadArtifact(artifact)}
/>
}
>
{/* Артефакт сам рисует фон/рамку/шапку (iframe, kit-JSX и т.п.). */}
<ArtifactViewport artifact={artifact} variant="inline" />
</ChatArtifactInline>$ deploy --env production
→ сборка 3 сервисов…
✓ запущено за 4.2 с
Наведите курсор на артефакт, чтобы показать действия ⋯.
"use client";
import {
ChatArtifactInline,
ChatArtifactInlineMenu,
} from "@/components/ui/chat-artifact";
export default function Particle() {
return (
<div className="w-full max-w-xl p-4">
<ChatArtifactInline
actions={
<ChatArtifactInlineMenu
onCopy={() => {
/* consumer resolves artifact source + writes to clipboard */
}}
onDownload={() => {
/* consumer builds the download URL / blob */
}}
/>
}
host="auto"
>
{/* Stand-in for the rendered artifact (iframe / kit JSX). The artifact
paints its own surface; the host only clips it. */}
<div className="border bg-background">
<div className="flex items-center gap-2 border-b bg-muted px-3 py-2">
<span className="size-2 rounded-full bg-destructive/60" />
<span className="size-2 rounded-full bg-warning/60" />
<span className="size-2 rounded-full bg-success/60" />
<span className="ml-1 text-muted-foreground text-xs">
Консоль агента
</span>
</div>
<div className="space-y-1 p-4 font-mono text-foreground text-xs">
<p>$ deploy --env production</p>
<p className="text-muted-foreground">→ сборка 3 сервисов…</p>
<p className="text-success">✓ запущено за 4.2 с</p>
</div>
</div>
</ChatArtifactInline>
<p className="mt-2 text-muted-foreground text-xs">
Наведите курсор на артефакт, чтобы показать действия ⋯.
</p>
</div>
);
}
ChatArtifactInline — borderless, прозрачный, без тени и padding: подложку рисует САМ артефакт. Контролы (⋯) скрыты по умолчанию и проявляются по ховеру обёртки, остаются видимы при открытом меню. Во время status="streaming" контролы подавляются.
"use client";
import {
ChatArtifactInline,
ChatArtifactInlineFallback,
ChatArtifactInlineMenu,
} from "@/components/ui/chat-artifact";
import { Spinner } from "@/components/ui/spinner";
export default function Particle() {
return (
<div className="flex w-full max-w-xl flex-col gap-4 p-4">
{/* Streaming — controls are suppressed while the artifact is building. */}
<ChatArtifactInline
actions={
<ChatArtifactInlineMenu onCopy={() => {}} onDownload={() => {}} />
}
host="auto"
status="streaming"
>
<div className="flex min-h-45 items-center justify-center gap-2 border bg-background text-muted-foreground text-sm">
<Spinner className="size-4" />
Создание артефакта…
</div>
</ChatArtifactInline>
{/* Error — the consumer's error boundary renders this fallback. */}
<ChatArtifactInline host="auto">
<div className="border bg-background">
<ChatArtifactInlineFallback
onOpenInPanel={() => {
/* consumer opens the artifact in the side panel */
}}
/>
</div>
</ChatArtifactInline>
</div>
);
}
Error boundary — логика приложения; DS отдаёт только визуальный fallback ChatArtifactInlineFallback, который приложение передаёт boundary как fallback.
Квартальная выручка
· График
"use client";
import {
IconChevronDown,
IconDownload,
IconExternalLink,
} from "@tabler/icons-react";
import {
ChatArtifact,
ChatArtifactAction,
ChatArtifactActions,
ChatArtifactClose,
ChatArtifactContent,
ChatArtifactDescription,
ChatArtifactHeader,
ChatArtifactInfo,
ChatArtifactTitle,
} from "@/components/ui/chat-artifact";
import {
Menu,
MenuItem,
MenuPopup,
MenuTrigger,
} from "@/components/ui/menu";
export default function Particle() {
return (
<div className="h-90 w-full max-w-2xl border bg-background p-0">
{/* Full-bleed override: the live ArtifactPreviewPanel path. The chrome
drops its card surface so the hosting panel owns the framing. */}
<ChatArtifact className="rounded-none border-0 bg-transparent shadow-none">
<ChatArtifactHeader className="h-12 bg-transparent px-4">
<ChatArtifactInfo orientation="inline">
<ChatArtifactTitle className="truncate text-lg">
Квартальная выручка
</ChatArtifactTitle>
<ChatArtifactDescription className="shrink-0">
· График
</ChatArtifactDescription>
</ChatArtifactInfo>
<ChatArtifactActions>
<Menu>
<MenuTrigger
render={
<ChatArtifactAction label="Параметры скачивания">
<IconChevronDown aria-hidden="true" stroke={1.5} />
</ChatArtifactAction>
}
/>
<MenuPopup align="end" className="min-w-44">
<MenuItem closeOnClick>
<IconDownload
aria-hidden="true"
className="size-4"
stroke={1.5}
/>
Скачать
</MenuItem>
<MenuItem closeOnClick>
<IconExternalLink
aria-hidden="true"
className="size-4"
stroke={1.5}
/>
Открыть в новой вкладке
</MenuItem>
</MenuPopup>
</Menu>
<ChatArtifactClose />
</ChatArtifactActions>
</ChatArtifactHeader>
<ChatArtifactContent className="flex items-end justify-center gap-3 p-6">
{[42, 68, 55, 90].map((h, i) => (
<div
className="w-10 rounded-t-sm bg-brand"
key={`${h}-${i}`}
style={{ height: `${h}%` }}
/>
))}
</ChatArtifactContent>
</ChatArtifact>
</div>
);
}
Живая панель (ArtifactPreviewPanel) переопределяет стилевой дефолт ChatArtifact через className="rounded-none border-0 bg-transparent shadow-none" — оболочка снимает карточную поверхность, чтобы хостовая панель сама задавала рамки.
Card — артефакт-чип в ленте
import {
ChatArtifactCard,
ChatArtifactCardAction,
ChatArtifactCardActions,
ChatArtifactCardContent,
ChatArtifactCardIcon,
ChatArtifactCardSubtitle,
ChatArtifactCardTitle,
} from "@/components/ui/chat-artifact";
<ChatArtifactCard onOpen={() => openPanel(artifact)} open={isOpen}>
<ChatArtifactCardIcon>
<BarChart3Icon aria-hidden="true" />
</ChatArtifactCardIcon>
<ChatArtifactCardContent>
<ChatArtifactCardTitle title="Quarterly revenue">
Quarterly revenue
</ChatArtifactCardTitle>
<ChatArtifactCardSubtitle>Chart · Recharts</ChatArtifactCardSubtitle>
</ChatArtifactCardContent>
<ChatArtifactCardActions>
<ChatArtifactCardAction iconOnly label="Download chart">
<DownloadIcon aria-hidden="true" />
</ChatArtifactCardAction>
</ChatArtifactCardActions>
</ChatArtifactCard>"use client";
import { IconChartBar, IconCode, IconDownload } from "@tabler/icons-react";
import { useState } from "react";
import {
ChatArtifactCard,
ChatArtifactCardAction,
ChatArtifactCardActions,
ChatArtifactCardContent,
ChatArtifactCardIcon,
ChatArtifactCardSubtitle,
ChatArtifactCardTitle,
} from "@/components/ui/chat-artifact";
import {
ChatMessage,
ChatMessageAvatar,
ChatMessageContent,
} from "@/components/ui/chat-message";
export default function Particle() {
const [openId, setOpenId] = useState<string | null>("dashboard");
return (
<div className="flex w-full max-w-2xl flex-col gap-3 p-4">
<ChatMessage from="assistant">
<ChatMessageAvatar fallback="J" />
<ChatMessageContent>
<ChatArtifactCard
className="max-w-md"
onOpen={() => setOpenId("revenue")}
open={openId === "revenue"}
openLabel="График квартальной выручки"
>
<ChatArtifactCardIcon>
<IconChartBar aria-hidden="true" stroke={1.5} />
</ChatArtifactCardIcon>
<ChatArtifactCardContent>
<ChatArtifactCardTitle title="Квартальная выручка">
Квартальная выручка
</ChatArtifactCardTitle>
<ChatArtifactCardSubtitle>
График · Recharts
</ChatArtifactCardSubtitle>
</ChatArtifactCardContent>
<ChatArtifactCardActions>
<ChatArtifactCardAction iconOnly label="Скачать график">
<IconDownload aria-hidden="true" stroke={1.5} />
</ChatArtifactCardAction>
</ChatArtifactCardActions>
</ChatArtifactCard>
<ChatArtifactCard
className="max-w-md"
onOpen={() => setOpenId("dashboard")}
open={openId === "dashboard"}
openLabel="Аналитическая панель"
>
<ChatArtifactCardIcon>
<IconCode aria-hidden="true" stroke={1.5} />
</ChatArtifactCardIcon>
<ChatArtifactCardContent>
<ChatArtifactCardTitle title="Аналитическая панель">
Аналитическая панель
</ChatArtifactCardTitle>
<ChatArtifactCardSubtitle>Код · TSX</ChatArtifactCardSubtitle>
</ChatArtifactCardContent>
<ChatArtifactCardActions>
<ChatArtifactCardAction iconOnly label="Скачать исходный код">
<IconDownload aria-hidden="true" stroke={1.5} />
</ChatArtifactCardAction>
</ChatArtifactCardActions>
</ChatArtifactCard>
</ChatMessageContent>
</ChatMessage>
</div>
);
}
ChatArtifactCard — встраиваемый в ленту чип артефакта: document-tile «выглядывает» из левого слота и распрямляется по spring-ховеру, вся карточка кликабельна и открывает артефакт в панели через onOpen. Глиф-«вид» (Code2, BarChart3…) — child, подпись — строка Title · Type, действия скачать / открыть — ChatArtifactCardAction (link или onClick, всегда останавливают всплытие, чтобы не триггерить открытие). 1:1 с паттерном ChatFileCard.
import { IconCode } from "@tabler/icons-react";
import {
ChatArtifactCard,
ChatArtifactCardContent,
ChatArtifactCardIcon,
ChatArtifactCardSubtitle,
ChatArtifactCardTitle,
} from "@/components/ui/chat-artifact";
export default function Particle() {
return (
<div className="flex w-full max-w-2xl flex-col gap-3 p-4">
{/* loading — spinner replaces the glyph, opening is suppressed */}
<ChatArtifactCard className="max-w-md" status="loading">
<ChatArtifactCardIcon>
<IconCode aria-hidden="true" stroke={1.5} />
</ChatArtifactCardIcon>
<ChatArtifactCardContent>
<ChatArtifactCardTitle title="Сценарий онбординга">
Сценарий онбординга
</ChatArtifactCardTitle>
<ChatArtifactCardSubtitle>Генерация…</ChatArtifactCardSubtitle>
</ChatArtifactCardContent>
</ChatArtifactCard>
{/* error — destructive border + warning glyph */}
<ChatArtifactCard className="max-w-md" status="error">
<ChatArtifactCardIcon>
<IconCode aria-hidden="true" stroke={1.5} />
</ChatArtifactCardIcon>
<ChatArtifactCardContent>
<ChatArtifactCardTitle title="Сценарий онбординга">
Сценарий онбординга
</ChatArtifactCardTitle>
<ChatArtifactCardSubtitle>
Не удалось сгенерировать артефакт
</ChatArtifactCardSubtitle>
</ChatArtifactCardContent>
</ChatArtifactCard>
</div>
);
}
status ведёт жизненный цикл чипа: "loading" ставит aria-busy, блокирует открытие и меняет глиф на спиннер; "error" подсвечивает сбой генерации (destructive-рамка + warning-глиф).
API
Семейство Panel
| Компонент | Описание |
|---|---|
ChatArtifact | Корневой flex-column контейнер (rounded-lg border bg-card shadow-sm). Full-bleed — через className. |
ChatArtifactHeader | Border-bottom строка (bg-muted/50) с info-слотом и actions. |
ChatArtifactInfo | orientation: "stacked" | "inline" — колонка (по умолчанию) или однострочная baseline-строка. |
ChatArtifactTitle / ChatArtifactDescription | Типографские обёртки заголовка и подписи. |
ChatArtifactActions / ChatArtifactAction / ChatArtifactClose | Правый кластер ghost icon-кнопок (icon-sm). Action принимает label и render (для меню-триггера). |
ChatArtifactContent | Scrollable body — занимает оставшуюся высоту. |
ChatArtifactFooter | Опциональная footer-строка (bg-muted/50) для status / submit-кнопок. |
Семейство Inline
| Компонент | Описание |
|---|---|
ChatArtifactInline | Borderless/прозрачный хост: status, host (auto = rounded-[10px], fixed = h-[min(640px,70vh)]), actions. |
ChatArtifactInlineActions | Absolute top-right hover-reveal обёртка для контролов. |
ChatArtifactInlineMenu | Menu + ⋯-триггер (openOnHover) с Copy/Download: onCopy, onDownload, label-пропсы, children. |
ChatArtifactInlineFallback | Центрированный error-хост: onOpenInPanel, label, openLabel. |
Семейство Card
| Компонент | Описание |
|---|---|
ChatArtifactCard | Кликабельный чип-плашка артефакта в ленте: onOpen, open, interactive, status (idle | loading | error), disabled, openLabel. |
ChatArtifactCardIcon | Document-tile в левом слоте; child — глиф-«вид». По status показывает спиннер (loading) или warning (error). |
ChatArtifactCardContent | Колонка title/subtitle между иконкой и actions. |
ChatArtifactCardTitle / ChatArtifactCardSubtitle | Однострочные truncate-обёртки заголовка и подписи Title · Type. |
ChatArtifactCardActions / ChatArtifactCardAction | Правый кластер ghost-кнопок. Action — iconOnly, label, render/onClick; останавливает всплытие и гаснет на loading. |
Состояния
Panel
- default — стилевая карточка
rounded-lg border bg-card shadow-sm. - full-bleed —
rounded-none border-0 bg-transparent shadow-none(живая панель). - header: stacked / inline —
ChatArtifactInfo orientationпереключает заголовок над подписью ↔ однострочныйTitle · Type. - header: with-back / with-toggle — ведущая Back-кнопка и сегментированный Preview/Code-тоггл собираются в левый слот шапки потребителем.
- action: idle / copy → copied —
CopyIcon ↔ CheckIcon, возврат ~1.6 c; download-menu — ghost icon-кнопка какMenuTrigger.
Inline
- idle / hover — контролы скрыты (
opacity-0), проявляются поgroup-hover/inline. - menu-open —
⋯остаётся видим черезhas-[[data-popup-open]]. - streaming — контролы подавлены, пока артефакт собирается.
- host: auto / fixed — авто-высота с clip
rounded-[10px]↔ фикс-окно прокрутки. - error —
ChatArtifactInlineFallbackс опциональной ссылкой «Открыть в панели».
Card
- idle — хайрлайн-рамка (
border-foreground/15), document-tile выглядывает из левого слота. - hover — spring-распрямление тайла (
ease-[cubic-bezier(0,0.9,0.5,1.35)]),hover:border-foreground/30. - open — выбранная поверхность
border-foreground/30 bg-muted/30,aria-pressed. - loading —
aria-busy, спиннер вместо глифа, открытие заблокировано. - error —
border-destructive/40 bg-destructive/[0.03]+ warning-глиф.
Все цвета — семантические токены (bg-card/bg-muted/bg-popover, bg-secondary/hover:bg-accent, text-muted-foreground, border). rounded-[10px] и h-[min(640px,70vh)] — замеренная под claude геометрия inline-хоста. Разрешение копируемого текста, построение download-URL и error boundary остаются в приложении — DS принимает только onCopy/onDownload и визуальный fallback.