mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-12 18:06:11 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Its UI is governed by a design system (`DESIGN.md` + the token layer in `ui/src/index.css`, merged in #9134) whose first principle is "one way to say each thing" — one Card, one Badge, one nav row > - After the token extraction landed, ~35 files still hand-rolled card containers, ~45 files hand-rolled pill spans, the sidebar agents section duplicated the nav-row chrome, and the inbox and tasks lists rendered the same task rows two subtly different ways > - Each divergence is a place where a future design change (radius, hover language, status vocabulary) silently misses surfaces, defeating the "edit tokens + run checks" model the design system exists for > - This pull request converges those surfaces onto the shared primitives, codifies the radius scale as the modern multiplicative shadcn ladder, and unifies the row/hover/tree-guide language across the inbox and tasks lists — every visible delta was human-reviewed screen-by-screen against a live instance across nine feedback rounds > - The benefit is that the app's look is now steerable from single knobs (one `--radius` anchor, one Card, one Badge, one row component), and the visual regression suite covers the result (514 snapshots including a new AgentDetail page story) ## Linked Issues or Issue Description No public GitHub issue exists for this work; describing per the feature template: - **Problem**: after the design-token foundation (#9134), component-level drift remained — hand-rolled cards/pills, duplicated sidebar row chrome, and two different renderings of task rows (inbox vs tasks list) meant design changes had to be applied per-surface and frequently missed spots (e.g. status glyphs rendered 16px in the inbox but 20px in the tasks list because a slot override silently beat the component default). - **Proposed behavior**: all card-shaped containers render via `Card`, all label pills via `Badge`, sidebar rows via `SidebarNavItem`, and both task-list surfaces via one `IssueRow` configuration; the radius scale is a single multiplicative ladder anchored at `--radius: 0.5rem`. - **Alternatives considered**: converting interactive `<button>`/`<Link>` cards to `Card` divs (rejected — breaks semantics; documented inline with `design-allow` comments instead); keeping the legacy additive radius ladder (rejected in favor of the standard shadcn multiplicative mapping). ## What Changed - `Card` adoption across ~35 files (settings pages, auth/board flows, dashboards, list containers, KPI tiles); non-adoptable sites (interactive cards, `<li>` rows, class-string props, chart tooltip) carry documented `design-allow(card-pattern)` comments - `Card` gains an `interactive` prop — one quiet hover affordance for clickable cards (cursor, border darken, shadow lift, focus ring), applied to skills tiles, artifact cards, and the company selector; cards carry no resting shadow - `Badge` adoption for 113 hand-rolled pill spans across ~45 files; `PropertyChip` wraps `Badge` internally; `StatusBadge`, external-object chips, and match chips stay bespoke by documented decision (WCAG-tuned status mechanics) - Radius ladder becomes the multiplicative shadcn mapping (`sm/md/lg/xl/2xl/3xl/4xl = 0.6/0.8/1.0/1.4/1.8/2.2/2.6 × --radius`, anchor `0.5rem`); every card surface unifies on `rounded-lg`; the orphaned 8px literal token is deleted - Sidebar: agent rows render via `SidebarNavItem` (new additive props: `iconNode`, `active`, `trailing`, `liveAccessory`); live dots use `--status-agent-running`; one row rhythm and inset rounded pill highlight; right-aligned trailing badges; every labeled section is collapsible - Inbox + tasks lists unified: md status glyphs, `accent/50` rounded row hovers, vertical tree guides under parent rows (opaque underlay so dark-mode translucent borders don't stack), no horizontal dividers under expanded parents; the swipe-to-archive reveal layer shows only mid-swipe; board toggle uses the `SquareKanban` glyph - Kanban: every column carries a status-hued tint; lanes default expanded (including empty); compact mode collapses empty lanes to labeled rails (fixes a clipped, label-less empty-column state) - Storybook: new AgentDetail page story (realistic fixtures, light+dark) joins the visual suite; suite captures with `reducedMotion: 'reduce'` and the ux-lab reasoning ticker honors `prefers-reduced-motion`; a stale lexical alias in `storybook/main.ts` is fixed (build was broken since the lexical 0.46 bump) - Keyboard navigation, from live review of the unified lists: inbox navigation keys work on every tab (archive keys stay scoped to the archivable tab); keyboard-driven scrolling no longer hands the selection to whatever row lands under the stationary cursor (hover selects only after real pointer movement); the tasks list view gains the same j/k / arrows / Enter selection model as the inbox; and the `g` then `i` go-to-inbox chord works app-wide instead of only on the issue detail page - Token gates restored to 3/3 CLEAN (tokenized a post-#9134 regression in the recovery card); decisions recorded in `doc/design/DECISION-SHEET.md` and `doc/design/COMPONENT-INVENTORY.md` (investigation verdicts: FileTree vs WorkspaceFileBrowser and the four entity pickers stay separate — evidence included) ## Verification - `pnpm check:token-gates` → 3/3 CLEAN - `pnpm typecheck` → green (all packages) - `cd ui && npx vitest run` → 2106/2106 (assertions updated in lockstep where they documented superseded decisions; new tests for the global go-to-inbox chord) - `pnpm --filter @paperclipai/ui build` → exit 0 - `pnpm build-storybook` → succeeds (also fixes the lexical-alias break on master) - Visual regression: 514-snapshot Playwright suite green against the updated baseline (zero diffs from the keyboard-navigation round — those changes are purely behavioral). Note: baselines live outside git per the suite design and the baseline-manifest archive is not yet published, so CI cannot run this suite — it was run locally throughout; every visible delta was reviewed screen-by-screen in a live instance across nine review rounds. Review evidence (before/after triplets) intentionally kept out of the repo for size; available on request. - Manual: exercised dashboard, tasks (list + board), inbox, agents, skills, costs, settings, and artifact surfaces in light and dark themes ## Risks - Wide but shallow visual surface: most changes are class-string substitutions with behavior preserved (props, handlers, roles, test ids). The riskiest areas — dnd-kit card refs (React 19 ref-as-prop), inbox swipe-to-archive, and sidebar overlays — are covered by existing unit tests (all green) and were manually exercised. - Intentional visual deltas (rounded cards, tinted kanban columns, md status glyphs, unified hovers) are design decisions recorded in `doc/design/DECISION-SHEET.md`; each maps to a re-baselined snapshot set locally. - The visual suite cannot yet run in CI (unpublished baseline archive — pre-existing condition from #9134); until that lands, snapshot coverage is local-only. ## Model Used - Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — agentic coding with tool use (file editing, test execution, Playwright screenshot verification); extended thinking enabled. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green (will confirm once CI runs) - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups (pending first review) - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
397 lines
18 KiB
TypeScript
397 lines
18 KiB
TypeScript
// token-extraction: allowlisted — intentional one-off decoration (DECISION-SHEET.md B1
|
|
// user ruling). The bg-[...gradient...] / shadow-[...] literals in this demo/UX-lab page
|
|
// are deliberate one-off decoration, reverted from --gradient-extract-*/--shadow-extract-*
|
|
// tokens; the file is on the check-token-gates allowlist in ui/src/index.css.
|
|
import { useEffect, useRef, useState, type ReactNode } from "react";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { IssueChatThread } from "../components/IssueChatThread";
|
|
import {
|
|
issueChatUxAgentMap,
|
|
issueChatUxFeedbackVotes,
|
|
issueChatUxLinkedRuns,
|
|
issueChatUxLiveComments,
|
|
issueChatUxLiveEvents,
|
|
issueChatUxLiveRuns,
|
|
issueChatUxMentions,
|
|
issueChatUxReassignOptions,
|
|
issueChatUxReviewComments,
|
|
issueChatUxReviewEvents,
|
|
issueChatUxSubmittingComments,
|
|
issueChatUxTranscriptsByRunId,
|
|
} from "../fixtures/issueChatUxFixtures";
|
|
import { cn } from "../lib/utils";
|
|
import { Bot, Brain, FlaskConical, Loader2, MessagesSquare, Route, Sparkles, WandSparkles } from "lucide-react";
|
|
|
|
const noop = async () => {};
|
|
|
|
const highlights = [
|
|
"Running assistant replies with streamed text, reasoning, tool cards, and background status notes",
|
|
"Historical issue events and linked runs rendered inline with the chat timeline",
|
|
"Queued user messages, settled assistant comments, and feedback controls",
|
|
"Submitting (pending) message bubble with Sending... label and reduced opacity",
|
|
"Empty and disabled-composer states without relying on live backend data",
|
|
];
|
|
|
|
function LabSection({
|
|
id,
|
|
eyebrow,
|
|
title,
|
|
description,
|
|
accentClassName,
|
|
children,
|
|
}: {
|
|
id?: string;
|
|
eyebrow: string;
|
|
title: string;
|
|
description: string;
|
|
accentClassName?: string;
|
|
children: ReactNode;
|
|
}) {
|
|
return (
|
|
<section
|
|
id={id}
|
|
className={cn(
|
|
"rounded-(--rad-28) border border-border/70 bg-background/80 p-4 shadow-[0_24px_60px_rgba(15,23,42,0.08)] sm:p-5",
|
|
accentClassName,
|
|
)}
|
|
>
|
|
<div className="mb-4 flex flex-wrap items-start justify-between gap-3">
|
|
<div className="min-w-0">
|
|
<div className="text-(length:--text-micro) font-semibold uppercase tracking-(--tracking-caps) text-muted-foreground">
|
|
{eyebrow}
|
|
</div>
|
|
<h2 className="mt-1 text-xl font-semibold tracking-tight">{title}</h2>
|
|
<p className="mt-2 max-w-3xl text-sm text-muted-foreground">{description}</p>
|
|
</div>
|
|
</div>
|
|
{children}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
const DEMO_REASONING_LINES = [
|
|
"Analyzing the user's request about the animation smoothness...",
|
|
"The current implementation unmounts the old span instantly, causing a flash...",
|
|
"Looking at the CSS keyframes for cot-line-slide-up...",
|
|
"We need a paired exit animation so the old line slides out while the new one slides in...",
|
|
"Implementing a two-span ticker: exiting line goes up and out, entering line comes up from below...",
|
|
"Testing the 280ms cubic-bezier transition timing...",
|
|
];
|
|
|
|
function RotatingReasoningDemo({ intervalMs = 2200 }: { intervalMs?: number }) {
|
|
const [index, setIndex] = useState(0);
|
|
const prevRef = useRef(DEMO_REASONING_LINES[0]);
|
|
const [ticker, setTicker] = useState<{
|
|
key: number;
|
|
current: string;
|
|
exiting: string | null;
|
|
}>({ key: 0, current: DEMO_REASONING_LINES[0], exiting: null });
|
|
|
|
useEffect(() => {
|
|
// Respect reduced motion (also makes the visual suite deterministic —
|
|
// it captures with reducedMotion: "reduce").
|
|
if (typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
return;
|
|
}
|
|
const timer = setInterval(() => {
|
|
setIndex((i) => (i + 1) % DEMO_REASONING_LINES.length);
|
|
}, intervalMs);
|
|
return () => clearInterval(timer);
|
|
}, [intervalMs]);
|
|
|
|
const currentLine = DEMO_REASONING_LINES[index];
|
|
|
|
useEffect(() => {
|
|
if (currentLine !== prevRef.current) {
|
|
const prev = prevRef.current;
|
|
prevRef.current = currentLine;
|
|
setTicker((t) => ({ key: t.key + 1, current: currentLine, exiting: prev }));
|
|
}
|
|
}, [currentLine]);
|
|
|
|
return (
|
|
<div className="flex gap-2 px-1">
|
|
<div className="flex flex-col items-center pt-0.5">
|
|
<Brain className="h-3.5 w-3.5 shrink-0 text-muted-foreground/50" />
|
|
</div>
|
|
<div className="relative h-5 min-w-0 flex-1 overflow-hidden">
|
|
{ticker.exiting !== null && (
|
|
<span
|
|
key={`out-${ticker.key}`}
|
|
className="cot-line-exit absolute inset-x-0 truncate text-(length:--text-compact) italic leading-5 text-muted-foreground/70"
|
|
onAnimationEnd={() => setTicker((t) => ({ ...t, exiting: null }))}
|
|
>
|
|
{ticker.exiting}
|
|
</span>
|
|
)}
|
|
<span
|
|
key={`in-${ticker.key}`}
|
|
className={cn(
|
|
"absolute inset-x-0 truncate text-(length:--text-compact) italic leading-5 text-muted-foreground/70",
|
|
ticker.key > 0 && "cot-line-enter",
|
|
)}
|
|
>
|
|
{ticker.current}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function IssueChatUxLab() {
|
|
const [showComposer, setShowComposer] = useState(true);
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="overflow-hidden rounded-(--rad-32) border border-border/70 bg-[linear-gradient(135deg,rgba(8,145,178,0.10),transparent_28%),linear-gradient(180deg,rgba(245,158,11,0.10),transparent_44%),var(--background)] shadow-[0_30px_80px_rgba(15,23,42,0.10)]">
|
|
<div className="grid gap-6 lg:grid-cols-(--gtc-39)">
|
|
<div className="p-6 sm:p-7">
|
|
<div className="inline-flex items-center gap-2 rounded-full border border-cyan-500/25 bg-cyan-500/[0.08] px-3 py-1 text-(length:--text-nano) font-semibold uppercase tracking-(--tracking-caps) text-cyan-700 dark:text-cyan-300">
|
|
<FlaskConical className="h-3.5 w-3.5" />
|
|
Chat UX Lab
|
|
</div>
|
|
<h1 className="mt-4 text-3xl font-semibold tracking-tight">Issue chat review surface</h1>
|
|
<p className="mt-3 max-w-3xl text-sm leading-6 text-muted-foreground">
|
|
This page exercises the real assistant-ui issue chat with fixture-backed messages. Use it to review
|
|
spacing, chronology, running states, tool rendering, activity rows, queueing, and composer behavior
|
|
without needing a live issue in progress.
|
|
</p>
|
|
|
|
<div className="mt-5 flex flex-wrap items-center gap-2">
|
|
<Badge variant="outline" className="rounded-full px-3 py-1 text-(length:--text-nano) uppercase tracking-(--tracking-caps)">
|
|
/tests/ux/chat
|
|
</Badge>
|
|
<Badge variant="outline" className="rounded-full px-3 py-1 text-(length:--text-nano) uppercase tracking-(--tracking-caps)">
|
|
assistant-ui thread
|
|
</Badge>
|
|
<Badge variant="outline" className="rounded-full px-3 py-1 text-(length:--text-nano) uppercase tracking-(--tracking-caps)">
|
|
fixture-backed live run
|
|
</Badge>
|
|
</div>
|
|
|
|
<div className="mt-6 flex flex-wrap items-center gap-3">
|
|
<Button variant="outline" size="sm" className="rounded-full" onClick={() => setShowComposer((value) => !value)}>
|
|
{showComposer ? "Hide composer in primary preview" : "Show composer in primary preview"}
|
|
</Button>
|
|
<a
|
|
href="#live-execution"
|
|
className="inline-flex items-center gap-2 rounded-full border border-border/70 bg-background/80 px-3 py-1.5 text-xs text-muted-foreground transition-colors hover:text-foreground"
|
|
>
|
|
<Route className="h-3.5 w-3.5" />
|
|
Jump to live execution preview
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<aside className="border-t border-border/60 bg-background/70 p-6 lg:border-l lg:border-t-0">
|
|
<div className="mb-4 flex items-center gap-2 text-(length:--text-micro) font-semibold uppercase tracking-(--tracking-caps) text-muted-foreground">
|
|
<WandSparkles className="h-4 w-4 text-cyan-700 dark:text-cyan-300" />
|
|
Covered states
|
|
</div>
|
|
<div className="space-y-3">
|
|
{highlights.map((highlight) => (
|
|
<div
|
|
key={highlight}
|
|
className="rounded-2xl border border-border/70 bg-background/85 px-4 py-3 text-sm text-muted-foreground"
|
|
>
|
|
{highlight}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</aside>
|
|
</div>
|
|
</div>
|
|
|
|
<LabSection
|
|
id="rotating-text"
|
|
eyebrow="Animation demo"
|
|
title="Rotating reasoning text"
|
|
description="Isolated ticker that cycles sample reasoning lines on a timer. The outgoing line slides up and fades out while the incoming line slides up from below. Runs in a loop so you can tune timing and easing without needing a live stream."
|
|
accentClassName="bg-[linear-gradient(180deg,rgba(168,85,247,0.06),transparent_28%),var(--background)]"
|
|
>
|
|
<div className="space-y-4">
|
|
<div className="rounded-xl border border-border/60 bg-accent/10 p-4">
|
|
<div className="mb-2 text-(length:--text-nano) font-semibold uppercase tracking-(--tracking-caps) text-muted-foreground">
|
|
Default interval (2.2s)
|
|
</div>
|
|
<RotatingReasoningDemo />
|
|
</div>
|
|
<div className="rounded-xl border border-border/60 bg-accent/10 p-4">
|
|
<div className="mb-2 text-(length:--text-nano) font-semibold uppercase tracking-(--tracking-caps) text-muted-foreground">
|
|
Fast interval (1s) — stress test
|
|
</div>
|
|
<RotatingReasoningDemo intervalMs={1000} />
|
|
</div>
|
|
</div>
|
|
</LabSection>
|
|
|
|
<LabSection
|
|
id="working-tokens"
|
|
eyebrow="Status tokens"
|
|
title="Working / Worked header verb"
|
|
description='The "Working" token uses the shimmer-text gradient sweep to signal an active run. Once the run completes it becomes the static "Worked" token.'
|
|
accentClassName="bg-[linear-gradient(180deg,rgba(16,185,129,0.06),transparent_28%),var(--background)]"
|
|
>
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<div className="rounded-xl border border-border/60 bg-accent/10 p-4">
|
|
<div className="mb-3 text-(length:--text-nano) font-semibold uppercase tracking-(--tracking-caps) text-muted-foreground">
|
|
Active run — shimmer
|
|
</div>
|
|
<div className="flex items-center gap-2.5 rounded-lg px-1 py-2">
|
|
<span className="inline-flex items-center gap-2 text-sm font-medium text-foreground/80">
|
|
<Loader2 className="h-4 w-4 shrink-0 animate-spin text-muted-foreground" />
|
|
<span className="shimmer-text">Working</span>
|
|
</span>
|
|
<span className="text-xs text-muted-foreground/60">for 12s</span>
|
|
</div>
|
|
</div>
|
|
<div className="rounded-xl border border-border/60 bg-accent/10 p-4">
|
|
<div className="mb-3 text-(length:--text-nano) font-semibold uppercase tracking-(--tracking-caps) text-muted-foreground">
|
|
Completed run — static
|
|
</div>
|
|
<div className="flex items-center gap-2.5 rounded-lg px-1 py-2">
|
|
<span className="inline-flex items-center gap-2 text-sm font-medium text-foreground/80">
|
|
<span className="flex h-4 w-4 shrink-0 items-center justify-center">
|
|
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500/70" />
|
|
</span>
|
|
Worked
|
|
</span>
|
|
<span className="text-xs text-muted-foreground/60">for 1 min 24s</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</LabSection>
|
|
|
|
<LabSection
|
|
id="live-execution"
|
|
eyebrow="Primary preview"
|
|
title="Live execution thread"
|
|
description="Shows the fully active state: timeline events, historical run marker, a running assistant reply with reasoning and tools, and a queued follow-up from the user."
|
|
accentClassName="bg-[linear-gradient(180deg,rgba(6,182,212,0.05),transparent_28%),var(--background)]"
|
|
>
|
|
<IssueChatThread
|
|
comments={issueChatUxLiveComments}
|
|
linkedRuns={issueChatUxLinkedRuns.slice(0, 1)}
|
|
timelineEvents={issueChatUxLiveEvents}
|
|
liveRuns={issueChatUxLiveRuns}
|
|
issueStatus="todo"
|
|
agentMap={issueChatUxAgentMap}
|
|
currentUserId="user-1"
|
|
onAdd={noop}
|
|
onVote={noop}
|
|
onCancelRun={noop}
|
|
onInterruptQueued={noop}
|
|
draftKey="issue-chat-ux-lab-primary"
|
|
enableReassign
|
|
reassignOptions={issueChatUxReassignOptions}
|
|
currentAssigneeValue="agent:agent-1"
|
|
suggestedAssigneeValue="agent:agent-2"
|
|
mentions={issueChatUxMentions}
|
|
showComposer={showComposer}
|
|
enableLiveTranscriptPolling={false}
|
|
transcriptsByRunId={issueChatUxTranscriptsByRunId}
|
|
hasOutputForRun={(runId) => issueChatUxTranscriptsByRunId.has(runId)}
|
|
/>
|
|
</LabSection>
|
|
|
|
<LabSection
|
|
eyebrow="Submitting state"
|
|
title="Pending message bubble"
|
|
description='When a user sends a message, the bubble briefly shows a "Sending..." label at reduced opacity until the server confirms receipt. This preview renders that transient state.'
|
|
accentClassName="bg-[linear-gradient(180deg,rgba(59,130,246,0.06),transparent_28%),var(--background)]"
|
|
>
|
|
<IssueChatThread
|
|
comments={issueChatUxSubmittingComments}
|
|
linkedRuns={[]}
|
|
timelineEvents={[]}
|
|
issueStatus="in_progress"
|
|
agentMap={issueChatUxAgentMap}
|
|
currentUserId="user-1"
|
|
onAdd={noop}
|
|
draftKey="issue-chat-ux-lab-submitting"
|
|
showComposer={false}
|
|
enableLiveTranscriptPolling={false}
|
|
/>
|
|
</LabSection>
|
|
|
|
<div className="grid gap-6 xl:grid-cols-2">
|
|
<LabSection
|
|
eyebrow="Settled review"
|
|
title="Durable comments and feedback"
|
|
description="Shows the post-run state: assistant comment feedback controls, historical run context, and timeline reassignment without any active stream."
|
|
accentClassName="bg-[linear-gradient(180deg,rgba(168,85,247,0.05),transparent_26%),var(--background)]"
|
|
>
|
|
<IssueChatThread
|
|
comments={issueChatUxReviewComments}
|
|
linkedRuns={issueChatUxLinkedRuns.slice(1)}
|
|
timelineEvents={issueChatUxReviewEvents}
|
|
feedbackVotes={issueChatUxFeedbackVotes}
|
|
feedbackTermsUrl="/feedback-terms"
|
|
issueStatus="in_review"
|
|
agentMap={issueChatUxAgentMap}
|
|
currentUserId="user-1"
|
|
onAdd={noop}
|
|
onVote={noop}
|
|
draftKey="issue-chat-ux-lab-review"
|
|
showComposer={false}
|
|
enableLiveTranscriptPolling={false}
|
|
/>
|
|
</LabSection>
|
|
|
|
<div className="space-y-6">
|
|
<LabSection
|
|
eyebrow="Empty thread"
|
|
title="Empty state and disabled composer"
|
|
description="Keeps the message area visible even when there is no thread yet, and replaces the composer with an explicit warning when replies are blocked."
|
|
accentClassName="bg-[linear-gradient(180deg,rgba(245,158,11,0.08),transparent_26%),var(--background)]"
|
|
>
|
|
<IssueChatThread
|
|
comments={[]}
|
|
linkedRuns={[]}
|
|
timelineEvents={[]}
|
|
issueStatus="done"
|
|
agentMap={issueChatUxAgentMap}
|
|
currentUserId="user-1"
|
|
onAdd={noop}
|
|
composerDisabledReason="This workspace is closed, so new chat replies are disabled until the issue is reopened."
|
|
draftKey="issue-chat-ux-lab-empty"
|
|
enableLiveTranscriptPolling={false}
|
|
/>
|
|
</LabSection>
|
|
|
|
<Card className="gap-4 border-border/70 bg-background/85 py-0">
|
|
<CardHeader className="px-5 pt-5 pb-0">
|
|
<div className="flex items-center gap-2 text-(length:--text-micro) font-semibold uppercase tracking-(--tracking-caps) text-muted-foreground">
|
|
<MessagesSquare className="h-4 w-4 text-cyan-700 dark:text-cyan-300" />
|
|
Review checklist
|
|
</div>
|
|
<CardTitle className="text-lg">What to evaluate on this page</CardTitle>
|
|
<CardDescription>
|
|
This route should be the fastest way to inspect the chat system before or after tweaks.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3 px-5 pb-5 pt-0 text-sm text-muted-foreground">
|
|
<div className="rounded-2xl border border-border/70 bg-background/80 px-4 py-3">
|
|
<div className="mb-1 flex items-center gap-2 font-medium text-foreground">
|
|
<Bot className="h-4 w-4 text-cyan-700 dark:text-cyan-300" />
|
|
Message hierarchy
|
|
</div>
|
|
Check that user, assistant, and system rows scan differently without feeling like separate products.
|
|
</div>
|
|
<div className="rounded-2xl border border-border/70 bg-background/80 px-4 py-3">
|
|
<div className="mb-1 flex items-center gap-2 font-medium text-foreground">
|
|
<Sparkles className="h-4 w-4 text-cyan-700 dark:text-cyan-300" />
|
|
Stream polish
|
|
</div>
|
|
Watch the live preview for reasoning density, tool expansion behavior, and queued follow-up readability.
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|