# Tattva parts: all the source in one file

For AI builders (Lovable, v0, Bolt and others) that cannot unzip or install packages. Each section is one file; recreate them under `src/tattva/` with the same names, then import from `./tattva` (the `index.ts` section lists what is exported). Also add the colours from https://lkb00.github.io/tattva/downloads/tokens.css to the project's global stylesheet, and set `data-palette` on the html element for a colour theme.

Read https://lkb00.github.io/tattva/llms.txt for how to use each part. Pro parts are not included.

## tattva/index.ts

```ts
// Tattva free kit: the free parts. Import everything from here: import { Button } from "./tattva";
export * from "./atoms/AIBadge";
export * from "./atoms/AIDisclosure";
export * from "./atoms/AIMark";
export * from "./atoms/AIPresence";
export * from "./atoms/AgentStatusIcon";
export * from "./atoms/ApprovedByMark";
export * from "./atoms/AttentionDot";
export * from "./atoms/Avatar";
export * from "./atoms/Badge";
export * from "./atoms/Button";
export * from "./atoms/Checkbox";
export * from "./atoms/CitationMarker";
export * from "./atoms/Cluster";
export * from "./atoms/Container";
export * from "./atoms/DataFreshness";
export * from "./atoms/EditScopeChip";
export * from "./atoms/FrameScrubber";
export * from "./atoms/GhostText";
export * from "./atoms/Grid";
export * from "./atoms/HeroHeading";
export * from "./atoms/Illustration";
export * from "./atoms/Kbd";
export * from "./atoms/Meter";
export * from "./atoms/MicButton";
export * from "./atoms/Money";
export * from "./atoms/NumberField";
export * from "./atoms/Pictogram";
export * from "./atoms/Popover";
export * from "./atoms/Price";
export * from "./atoms/PriceChange";
export * from "./atoms/Reveal";
export * from "./atoms/ScrollEdge";
export * from "./atoms/SectionLabel";
export * from "./atoms/SegmentedControl";
export * from "./atoms/Select";
export * from "./atoms/ShimmerText";
export * from "./atoms/Skeleton";
export * from "./atoms/Slider";
export * from "./atoms/Spinner";
export * from "./atoms/Stack";
export * from "./atoms/StatusLine";
export * from "./atoms/StatusTicker";
export * from "./atoms/Switch";
export * from "./atoms/TextField";
export * from "./atoms/Tooltip";
export * from "./atoms/TypingIndicator";
export * from "./atoms/Waveform";
export * from "./atoms/WorkingEdge";
export * from "./molecules/AILabel";
export * from "./molecules/AttachmentChip";
export * from "./molecules/AudioPlayer";
export * from "./molecules/AutonomyLevel";
export * from "./molecules/AvatarStack";
export * from "./molecules/BackgroundRunChip";
export * from "./molecules/Callout";
export * from "./molecules/CameraMovePicker";
export * from "./molecules/CardRow";
export * from "./molecules/ChartFrame";
export * from "./molecules/ChartRangeBrush";
export * from "./molecules/ChecklistRow";
export * from "./molecules/CitationHoverCard";
export * from "./molecules/ClaimCheck";
export * from "./molecules/ClipRangeSelector";
export * from "./molecules/CodeBlock";
export * from "./molecules/Collapsible";
export * from "./molecules/Combobox";
export * from "./molecules/ConfidenceIndicator";
export * from "./molecules/ConnectorPermission";
export * from "./molecules/ContextChip";
export * from "./molecules/ContextMeter";
export * from "./molecules/ContinueNotice";
export * from "./molecules/Countdown";
export * from "./molecules/DataTable";
export * from "./molecules/DatePicker";
export * from "./molecules/DiagramFigure";
export * from "./molecules/DiffFileRow";
export * from "./molecules/DraftPreview";
export * from "./molecules/EditableTurn";
export * from "./molecules/EmptyState";
export * from "./molecules/EnhanceCompare";
export * from "./molecules/ErrorState";
export * from "./molecules/FactList";
export * from "./molecules/FeedbackBar";
export * from "./molecules/Field";
export * from "./molecules/FileDropZone";
export * from "./molecules/FileResultCard";
export * from "./molecules/GenerationProgress";
export * from "./molecules/InlineConfirm";
export * from "./molecules/InlineEdit";
export * from "./molecules/JsonView";
export * from "./molecules/JumpToLatest";
export * from "./molecules/KeyTakeaway";
export * from "./molecules/LatencyBreakdown";
export * from "./molecules/MediaFrame";
export * from "./molecules/MemoryNotice";
export * from "./molecules/Menu";
export * from "./molecules/Message";
export * from "./molecules/MessageActions";
export * from "./molecules/ModelPicker";
export * from "./molecules/PermissionModeSwitcher";
export * from "./molecules/PrivateChatBanner";
export * from "./molecules/ProvenancePopover";
export * from "./molecules/RadioGroup";
export * from "./molecules/RateLimitNotice";
export * from "./molecules/Receipt";
export * from "./molecules/RefusalNotice";
export * from "./molecules/ResultCard";
export * from "./molecules/RevertToggle";
export * from "./molecules/SceneIllustration";
export * from "./molecules/SelectionToolbar";
export * from "./molecules/SensitiveDataGuard";
export * from "./molecules/SlideStage";
export * from "./molecules/SourceCard";
export * from "./molecules/SpeakerNotes";
export * from "./molecules/Splitter";
export * from "./molecules/StatTile";
export * from "./molecules/StreamingReveal";
export * from "./molecules/SuggestionCard";
export * from "./molecules/SuggestionChips";
export * from "./molecules/Tabs";
export * from "./molecules/TakeCard";
export * from "./molecules/TakeVersions";
export * from "./molecules/TakeoverBar";
export * from "./molecules/ThinkingBlock";
export * from "./molecules/TickerTape";
export * from "./molecules/Timeline";
export * from "./molecules/Toast";
export * from "./molecules/ToolCallCard";
export * from "./molecules/ToolChip";
export * from "./molecules/TrayRow";
export * from "./molecules/UsageMeter";
export * from "./molecules/VariantTile";
export * from "./molecules/VariationActions";
export * from "./molecules/VersionPager";
export * from "./molecules/VideoPlayer";
export * from "./molecules/VoiceCard";
export * from "./organisms/ActionDraft";
export * from "./organisms/ActionLog";
export * from "./organisms/ActivityTrace";
export * from "./organisms/AgentQuestionCard";
export * from "./organisms/AgentSessionList";
export * from "./organisms/ApprovalInbox";
export * from "./organisms/ApprovalPrompt";
export * from "./organisms/BarChart";
export * from "./organisms/BudgetControl";
export * from "./organisms/CallRecord";
export * from "./organisms/CandlestickChart";
export * from "./organisms/CanvasPanel";
export * from "./organisms/CatchUpSummary";
export * from "./organisms/ChartLegend";
export * from "./organisms/Checklist";
export * from "./organisms/CheckpointList";
export * from "./organisms/CommandPalette";
export * from "./organisms/ComparisonTable";
export * from "./organisms/Composer";
export * from "./organisms/ContextPanel";
export * from "./organisms/ConversationList";
export * from "./organisms/DeckBuildProgress";
export * from "./organisms/DeckChangeReview";
export * from "./organisms/Dialog";
export * from "./organisms/DiffView";
export * from "./organisms/DotPlot";
export * from "./organisms/ExportSheet";
export * from "./organisms/GenUIRenderer";
export * from "./organisms/GeneratedSurface";
export * from "./organisms/GlancePanel";
export * from "./organisms/Heatmap";
export * from "./organisms/LayoutAlternatives";
export * from "./organisms/LikenessConsent";
export * from "./organisms/LineChart";
export * from "./organisms/LiveCaptions";
export * from "./organisms/LiveLog";
export * from "./organisms/Markdown";
export * from "./organisms/MediaGrid";
export * from "./organisms/MemoryList";
export * from "./organisms/MessageList";
export * from "./organisms/MessageQueue";
export * from "./organisms/OptionChain";
export * from "./organisms/OrderTicket";
export * from "./organisms/OutlineEditor";
export * from "./organisms/ParameterPanel";
export * from "./organisms/PayoffChart";
export * from "./organisms/PermissionPrompt";
export * from "./organisms/PlanCard";
export * from "./organisms/ProcessDiagram";
export * from "./organisms/ProjectPanel";
export * from "./organisms/PromptBox";
export * from "./organisms/PromptIndex";
export * from "./organisms/QuestionSet";
export * from "./organisms/ResearchRun";
export * from "./organisms/ResponseCompare";
export * from "./organisms/RiskLimits";
export * from "./organisms/SandboxedFrame";
export * from "./organisms/SceneStrip";
export * from "./organisms/SectionLane";
export * from "./organisms/Sections";
export * from "./organisms/SharePanel";
export * from "./organisms/Sheet";
export * from "./organisms/ShotSettings";
export * from "./organisms/SlideRail";
export * from "./organisms/SourceList";
export * from "./organisms/Sparkline";
export * from "./organisms/StarterGallery";
export * from "./organisms/StemMixer";
export * from "./organisms/StepTimeline";
export * from "./organisms/SuggestionBar";
export * from "./organisms/SummaryCard";
export * from "./organisms/TakeGrid";
export * from "./organisms/TakePicker";
export * from "./organisms/TaskTree";
export * from "./organisms/ToolMenu";
export * from "./organisms/TranscriptAudioEditor";
export * from "./organisms/TranscriptSync";
export * from "./organisms/Tray";
export * from "./organisms/VariantGrid";
export * from "./organisms/VersionHistory";
export * from "./organisms/VoiceConsentCheck";
export * from "./organisms/VoicePanel";
export * from "./organisms/VoicePicker";
export * from "./pages/HomePage";
export * from "./pages/StudioPage";
export * from "./pages/ThreadPage";
export * from "./pages/WorkflowPage";
export * from "./templates/AppShell";
export * from "./templates/HomeTemplate";
export * from "./templates/SplitCanvasTemplate";
export * from "./templates/ThreadTemplate";
export * from "./hooks";
export * from "./lib/icons";
export { cn } from "./lib/cn";
```

## tattva/atoms/AIBadge.tsx

```tsx
import { SparkleIcon } from "../lib/icons";
import { Badge } from "./Badge";

/** Label for any content produced by AI. Use on generated images, summaries, and drafts. */
/** Set `solid` when the badge sits on an image or another busy background. */
export function AIBadge({ label = "AI-generated", solid = false }: { label?: string; solid?: boolean }) {
  return <Badge tone={solid ? "lime" : "accent"}><SparkleIcon width={10} height={10} />{label}</Badge>;
}
```

## tattva/atoms/AIDisclosure.tsx

```tsx
import type { ReactNode } from "react";

export function AIDisclosure({ children }: { children?: ReactNode }) {
  return <p className="text-center text-small leading-4 text-fg-subtle">{children ?? "AI can make mistakes. Check important information."}</p>;
}
```

## tattva/atoms/AIMark.tsx

```tsx
import type { SVGProps } from "react";

export interface AIMarkProps extends Omit<SVGProps<SVGSVGElement>, "width" | "height" | "title"> {
  /** Pixel size. Use 16 beside 12px text, 18 beside 14px text, 22 beside 16px text, 24 and 32 for standalone use. */
  size?: 16 | 18 | 22 | 24 | 32;
  /** Accessible name. Omit for a decorative mark that sits next to text. When set, the mark becomes an image with this name. */
  title?: string;
}

/**
 * The Tattva AI mark: a four-point shape with arms of slightly different length, so it reads as drawn
 * and does not collide with the generic sparkle other products use. It fills with currentColor.
 * The standard sparkle is ambiguous on its own, so pair the mark with text when the action is specific.
 */
export function AIMark({ size = 18, title, ...rest }: AIMarkProps) {
  return (
    <svg
      width={size} height={size} viewBox="0 0 24 24" fill="currentColor" stroke="none"
      role={title ? "img" : undefined} aria-label={title} aria-hidden={title ? undefined : true} focusable="false"
      {...rest}
    >
      <path d="M12.4 1.5Q14.2 9.8 21.5 11.4Q13.8 14.4 11.4 22.5Q9.6 14 2.5 12.6Q9.8 9.8 12.4 1.5Z" />
    </svg>
  );
}
```

## tattva/atoms/AIPresence.tsx

```tsx
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { cn } from "../lib/cn";

export type AIPresenceState = "idle" | "listening" | "thinking" | "speaking" | "error";
export type AIPresenceSize = "sm" | "md" | "lg";

export interface AIPresenceProps {
  /** What the assistant is doing. Changes are announced once in a polite status region. */
  state?: AIPresenceState;
  size?: AIPresenceSize;
  /**
   * Optional audio amplitude from 0 to 1, for example from a microphone analyser.
   * The component smooths it over several frames, so pass the raw value. It is ignored under reduced motion.
   */
  level?: number;
  /** Show the state as visible text beside the mark. The status region for assistive technology is always present. */
  showLabel?: boolean;
  /** Override the text for each state, for localisation. */
  labels?: Partial<Record<AIPresenceState, string>>;
  className?: string;
}

const DEFAULT_LABELS: Record<AIPresenceState, string> = {
  idle: "Ready", listening: "Listening", thinking: "Thinking", speaking: "Speaking", error: "Something went wrong",
};

/**
 * One continuous parameter set. Each state is a row of targets the animation eases toward, so moving between
 * states never restarts anything: the mark just changes how it moves.
 */
interface Params {
  breathHz: number; breathAmp: number; // slow swell of the halo
  halo: number;      // how much of the soft halo shows
  ripple: number;    // opacity of the rings that spread while listening
  arc: number;       // opacity of the arc that circles while thinking
  turn: number;      // how fast the arc turns, degrees per second
  gain: number;      // how much the audio level moves things
}
const TARGETS: Record<AIPresenceState, Params> = {
  idle:      { breathHz: 0.25, breathAmp: 0.04, halo: 1,   ripple: 0, arc: 0, turn: 0,   gain: 0 },
  listening: { breathHz: 0.6,  breathAmp: 0.03, halo: 1,   ripple: 1, arc: 0, turn: 0,   gain: 0.5 },
  thinking:  { breathHz: 0.4,  breathAmp: 0.02, halo: 1,   ripple: 0, arc: 1, turn: 220, gain: 0 },
  speaking:  { breathHz: 1.4,  breathAmp: 0.05, halo: 1,   ripple: 0, arc: 0, turn: 0,   gain: 1 },
  error:     { breathHz: 0,    breathAmp: 0,    halo: 0,   ripple: 0, arc: 0, turn: 0,   gain: 0 },
};

const DIM: Record<AIPresenceSize, string> = { sm: "size-8", md: "size-16", lg: "size-28" };

function usePrefersReducedMotion() {
  const [reduced, setReduced] = useState(() => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches);
  useEffect(() => {
    if (typeof window.matchMedia !== "function") return;
    const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
    const on = () => setReduced(mq.matches);
    mq.addEventListener("change", on);
    return () => mq.removeEventListener("change", on);
  }, []);
  return reduced;
}

const DISC = 27;  // the AI-colour disc, in a 100 x 100 box
const HALO = 39;  // the soft halo around it
const ARC = 33;   // the thinking arc runs in the halo, between the disc and the halo's edge
const MARK = "M12.4 1.5Q14.2 9.8 21.5 11.4Q13.8 14.4 11.4 22.5Q9.6 14 2.5 12.6Q9.8 9.8 12.4 1.5Z";

/**
 * The assistant's presence: the Tattva AI mark on a disc in the theme's AI colour, inside a soft halo. Flat, like
 * the rest of the system: no glass, glow or blur, only theme colours. Idle breathes slowly, listening sends rings
 * outward, thinking turns a thin arc round the disc, speaking swells with the audio level, and an error turns the
 * disc to the danger colours and stops. One frame loop eases a set of parameters toward the current state, so
 * changing state never restarts an animation. Under prefers-reduced-motion no frames run: each state is a still
 * drawing plus its label. Place a labelled Stop button beside it. The disc is not a control.
 */
export function AIPresence({ state = "idle", size = "md", level = 0, showLabel = true, labels, className }: AIPresenceProps) {
  const reduced = usePrefersReducedMotion();
  const text = { ...DEFAULT_LABELS, ...labels }[state];
  const halo = useRef<SVGCircleElement>(null);
  const disc = useRef<SVGGElement>(null);
  const arc = useRef<SVGGElement>(null);
  const rip1 = useRef<SVGCircleElement>(null);
  const rip2 = useRef<SVGCircleElement>(null);
  const cur = useRef<Params>({ ...TARGETS[state] });
  const target = useRef<Params>(TARGETS[state]);
  const lvlIn = useRef(0);
  lvlIn.current = Math.min(1, Math.max(0, level));

  const draw = (t: number, lvl: number, angle: number) => {
    const c = cur.current;
    const breath = Math.sin(2 * Math.PI * c.breathHz * t) * c.breathAmp;
    const push = lvl * c.gain;
    halo.current?.setAttribute("r", String(HALO * (1 + breath + push * 0.08)));
    halo.current?.setAttribute("opacity", String(c.halo));
    disc.current?.setAttribute("transform", `translate(50 50) scale(${1 + push * 0.1}) translate(-50 -50)`);
    arc.current?.setAttribute("opacity", String(c.arc));
    arc.current?.setAttribute("transform", `rotate(${angle} 50 50)`);
    // Two rings spread out from the disc and fade, half a cycle apart.
    const p1 = (t * 0.55) % 1, p2 = (t * 0.55 + 0.5) % 1;
    rip1.current?.setAttribute("r", String(DISC + p1 * (HALO + 9 - DISC)));
    rip1.current?.setAttribute("opacity", String(c.ripple * (1 - p1)));
    rip2.current?.setAttribute("r", String(DISC + p2 * (HALO + 9 - DISC)));
    rip2.current?.setAttribute("opacity", String(c.ripple * (1 - p2)));
  };

  useLayoutEffect(() => {
    target.current = TARGETS[state];
    if (reduced) { cur.current = { ...TARGETS[state] }; draw(0, 0, 40); }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [state, reduced]);

  useEffect(() => {
    if (reduced) return;
    let raf = 0, last = performance.now(), t = 0, angle = 0, lvl = 0;
    const loop = (now: number) => {
      const dt = Math.max(0, Math.min(0.05, (now - last) / 1000)); last = now; t += dt;
      const ease = 1 - Math.exp(-dt / 0.18);
      const c = cur.current, g = target.current;
      (Object.keys(g) as (keyof Params)[]).forEach((k) => { c[k] += (g[k] - c[k]) * ease; });
      const rate = lvlIn.current > lvl ? 0.35 : 0.12; // fast attack, slow release, per 60fps frame
      lvl += (lvlIn.current - lvl) * (1 - Math.pow(1 - rate, dt * 60));
      angle = (angle + c.turn * dt) % 360;
      draw(t, lvl, angle);
      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [reduced]);

  const init = TARGETS[state];
  const err = state === "error";
  return (
    <div className={cn("inline-flex items-center gap-3", className)}>
      <svg viewBox="0 0 100 100" aria-hidden="true" focusable="false" className={cn("shrink-0 overflow-visible", DIM[size])}>
        {/* The soft halo, and the rings that spread while listening. */}
        <circle ref={halo} cx="50" cy="50" r={HALO} opacity={init.halo} style={{ fill: "var(--lime-soft)", transition: "opacity var(--dur-base)" }} />
        <circle ref={rip1} cx="50" cy="50" r={DISC} fill="none" strokeWidth="2" opacity="0" style={{ stroke: "var(--lime)" }} />
        <circle ref={rip2} cx="50" cy="50" r={DISC} fill="none" strokeWidth="2" opacity="0" style={{ stroke: "var(--lime)" }} />
        {/* The arc that turns in the halo, round the disc, while thinking. Text colour, so it reads in light and dark. */}
        <g ref={arc} opacity={init.arc}>
          <path d={`M50 ${50 - ARC} A ${ARC} ${ARC} 0 0 1 ${50 + ARC} 50`} fill="none" strokeWidth="2.5" strokeLinecap="round" style={{ stroke: "var(--fg)" }} />
        </g>
        {/* The disc in the AI colour, and the AI mark at its centre. */}
        <g ref={disc}>
          <circle cx="50" cy="50" r={DISC} style={{ fill: err ? "var(--danger-soft)" : "var(--lime)", transition: "fill var(--dur-base)" }} />
          <path d={MARK} transform="translate(50 50) scale(1.5) translate(-12 -12)" style={{ fill: err ? "var(--danger-fg)" : "var(--on-lime)", transition: "fill var(--dur-base)" }} />
        </g>
      </svg>
      {showLabel && <span aria-hidden="true" className={cn("text-small leading-4", err ? "text-danger-fg" : "text-fg-muted")}>{text}</span>}
      <span role="status" className="sr-only">{text}</span>
    </div>
  );
}
```

## tattva/atoms/AgentStatusIcon.tsx

```tsx
import { cn } from "../lib/cn";

export type AgentStatus = "working" | "needs-input" | "idle" | "completed" | "failed" | "stopped";

const defaultLabels: Record<AgentStatus, string> = {
  working: "Working",
  "needs-input": "Needs input",
  idle: "Idle",
  completed: "Completed",
  failed: "Failed",
  stopped: "Stopped",
};

const tone: Record<AgentStatus, string> = {
  working: "text-fg-muted",
  "needs-input": "text-attention",
  idle: "text-fg-subtle",
  completed: "text-success",
  failed: "text-danger",
  stopped: "text-fg-subtle",
};

export interface AgentStatusIconProps {
  status: AgentStatus;
  /** Glyph size in px. */
  size?: number;
  /** Override the accessible text for this status, for example for another language. */
  label?: string;
  /** Show the label as visible text beside the glyph. The glyph is then hidden from assistive tech so the text is read once. */
  showLabel?: boolean;
  className?: string;
}

/**
 * Status glyph for an agent session. Each status has its own shape (working arc, question mark,
 * dash, check, cross, square), so it reads without color. Needs input uses the attention color
 * because a person must act. The working arc spins and is static under prefers-reduced-motion.
 */
export function AgentStatusIcon({ status, size = 16, label, showLabel = false, className }: AgentStatusIconProps) {
  const text = label ?? defaultLabels[status];
  const svg = (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round"
      aria-hidden={showLabel ? true : undefined} role={showLabel ? undefined : "img"} aria-label={showLabel ? undefined : text}
      className={cn("shrink-0", tone[status], status === "working" && "animate-spin", !showLabel && className)}>
      {status === "working" && (<><circle cx="12" cy="12" r="9" strokeOpacity="0.25" /><path d="M21 12a9 9 0 0 0-9-9" /></>)}
      {status === "needs-input" && (<><circle cx="12" cy="12" r="9" fill="currentColor" stroke="none" /><path d="M9.8 9.6a2.3 2.3 0 1 1 3.3 2.1c-.7.4-1.1.8-1.1 1.6M12 16.6h.01" stroke="var(--surface)" strokeWidth={2} /></>)}
      {status === "idle" && (<><circle cx="12" cy="12" r="9" /><path d="M8.5 12h7" /></>)}
      {status === "completed" && (<><circle cx="12" cy="12" r="9" /><path d="M8 12.5l3 3 5-6" /></>)}
      {status === "failed" && (<><circle cx="12" cy="12" r="9" /><path d="M9 9l6 6M15 9l-6 6" /></>)}
      {status === "stopped" && <rect x="5" y="5" width="14" height="14" rx="3" />}
    </svg>
  );
  if (!showLabel) return svg;
  return (
    <span className={cn("inline-flex items-center gap-1.5 text-small leading-4 font-medium text-fg", className)}>
      {svg}
      {text}
    </span>
  );
}
```

## tattva/atoms/ApprovedByMark.tsx

```tsx
import type { HTMLAttributes, ReactNode } from "react";
import { cn } from "../lib/cn";
import { ApproveIcon, BookmarkIcon, LoopIcon, ShieldIcon } from "../lib/icons";

export type ApprovedBy = "you" | "check" | "rule" | "none";

export interface ApprovedByMarkProps extends Omit<HTMLAttributes<HTMLSpanElement>, "children"> {
  /** Who said yes: the person, an automatic safety check, a saved rule, or nobody (auto mode). */
  by: ApprovedBy;
  /** Replace the words for one kind. */
  labels?: Partial<Record<ApprovedBy, string>>;
  /** Extra plain detail, such as the name of the saved rule. Added to the tooltip and the screen reader text. */
  detail?: string;
}

const defaults: Record<ApprovedBy, string> = {
  you: "You approved",
  check: "Automatic check approved",
  rule: "Allowed by a saved rule",
  none: "Nobody asked (auto mode)",
};
const icons: Record<ApprovedBy, ReactNode> = {
  you: <ApproveIcon width={13} height={13} />,
  check: <ShieldIcon width={13} height={13} />,
  rule: <BookmarkIcon width={13} height={13} />,
  none: <LoopIcon width={13} height={13} />,
};

/** Small mark that says who approved an action. Always an icon plus words, never color alone. */
export function ApprovedByMark({ by, labels, detail, className, title, ...rest }: ApprovedByMarkProps) {
  const text = labels?.[by] ?? defaults[by];
  return (
    <span title={title ?? detail} className={cn("inline-flex min-w-0 items-center gap-1 text-small leading-4 text-fg-muted", className)} {...rest}>
      <span className="shrink-0">{icons[by]}</span>
      <span>{text}</span>
      {detail && <span className="sr-only">. {detail}</span>}
    </span>
  );
}
```

## tattva/atoms/AttentionDot.tsx

```tsx
/** Amber dot. The only signal that something needs a person; it always travels with text. */
export function AttentionDot({ label = "Needs attention" }: { label?: string }) {
  return <span role="img" aria-label={label} className="size-1.5 shrink-0 rounded-full bg-attention" />;
}
```

## tattva/atoms/Avatar.tsx

```tsx
import { cn } from "../lib/cn";
import { SparkleIcon } from "../lib/icons";

export interface AvatarProps {
  kind: "user" | "ai";
  name?: string;
  size?: "sm" | "md";
  className?: string;
}

/** The assistant is an ink disc with a lime spark: recognisable at 24px, and the only place lime fills a circle. */
export function Avatar({ kind, name = "You", size = "md", className }: AvatarProps) {
  const dims = size === "sm" ? "size-6 text-nano" : "size-8 text-small leading-4";
  return (
    <span
      role="img"
      aria-label={kind === "ai" ? "Assistant" : name}
      className={cn("inline-flex shrink-0 items-center justify-center rounded-full",
        kind === "ai" ? "bg-code text-lime border border-line-strong" : "border border-line bg-sunken font-serif text-fg-muted", dims, className)}
    >
      {kind === "ai" ? <SparkleIcon width={size === "sm" ? 11 : 14} height={size === "sm" ? 11 : 14} /> : name.slice(0, 1).toUpperCase()}
    </span>
  );
}
```

## tattva/atoms/Badge.tsx

```tsx
import type { HTMLAttributes } from "react";
import { cn } from "../lib/cn";

export type Tone = "neutral" | "accent" | "lime" | "success" | "warning" | "danger" | "info" | "unsure" | "celebrate";

const tones: Record<Tone, string> = {
  neutral: "bg-sunken text-fg-muted border-line",
  accent: "bg-lime-soft text-fg border-transparent",
  /** Opaque lime for use over images and other busy backgrounds. */
  lime: "bg-lime text-on-lime border-transparent",
  success: "bg-success-soft text-success-fg border-transparent",
  warning: "bg-warning-soft text-warning-fg border-transparent",
  danger: "bg-danger-soft text-danger-fg border-transparent",
  info: "bg-info-soft text-info-fg border-transparent",
  /** Not fully sure, or partly done. Calm, not a warning. */
  unsure: "bg-unsure-soft text-unsure-fg border-transparent",
  /** A goal fully reached. Reserved: one place per product. */
  celebrate: "bg-celebrate-soft text-celebrate-fg border-transparent",
};

export function Badge({ tone = "neutral", className, ...rest }: HTMLAttributes<HTMLSpanElement> & { tone?: Tone }) {
  return <span data-slot="badge" data-tone={tone} className={cn("inline-flex items-center gap-1 rounded-control border px-2 py-0.5 text-micro leading-4 font-medium", tones[tone], className)} {...rest} />;
}
```

## tattva/atoms/Button.tsx

```tsx
import type { ButtonHTMLAttributes, MouseEvent, ReactNode, Ref } from "react";
import { cn } from "../lib/cn";

export type ButtonVariant = "primary" | "lime" | "secondary" | "ghost" | "danger";
export type ButtonSize = "sm" | "md" | "lg";

const variants: Record<ButtonVariant, string> = {
  primary: "bg-accent text-on-accent hover:bg-accent-hover disabled:bg-disabled disabled:text-disabled-fg",
  lime: "bg-lime text-on-lime hover:bg-lime-hover disabled:bg-disabled disabled:text-disabled-fg",
  secondary: "bg-surface text-fg border border-line hover:bg-bg hover:border-line-strong active:bg-pressed disabled:bg-disabled disabled:text-disabled-fg disabled:border-disabled-line",
  ghost: "text-fg-muted hover:bg-hover hover:text-fg active:bg-pressed disabled:bg-transparent disabled:text-disabled-fg",
  danger: "bg-danger text-on-danger hover:opacity-90 disabled:bg-disabled disabled:text-disabled-fg disabled:opacity-100",
};
const sizes: Record<ButtonSize, string> = {
  sm: "h-8 px-3 text-small leading-4 gap-1.5",
  md: "h-9 px-4 text-compact gap-2",
  lg: "h-11 px-6 text-body leading-5 gap-2",
};

export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  /** The button element, for moving focus to it. */
  ref?: Ref<HTMLButtonElement>;
  variant?: ButtonVariant;
  size?: ButtonSize;
  leading?: ReactNode;
  trailing?: ReactNode;
  /** Work is running after a press. The label gives way to a spinner without changing the width, presses are ignored, and the button keeps focus and stays in the Tab order. */
  busy?: boolean;
  /** Read by screen readers while busy, for example "Saving". Defaults to "Working". */
  busyLabel?: string;
}

/** Press feedback shared by every button: a quick sink on press and a slower release. Off under reduced motion. */
export const pressable = "transition-[color,background-color,border-color,opacity,scale] duration-(--dur-fast) ease-(--ease-out) motion-safe:active:scale-(--press-scale) motion-safe:active:duration-(--dur-instant)";

function BusyMark() {
  return (
    <svg aria-hidden viewBox="0 0 24 24" className="size-4 animate-spin">
      <circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" strokeOpacity="0.25" strokeWidth="3" />
      <path d="M21 12a9 9 0 0 0-9-9" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" />
    </svg>
  );
}

export function Button({ variant = "primary", size = "md", leading, trailing, busy = false, busyLabel = "Working", className, children, type = "button", onClick, ...rest }: ButtonProps) {
  return (
    <button
      type={type}
      data-slot="button"
      data-variant={variant}
      data-size={size}
      aria-busy={busy || undefined}
      onClick={(e: MouseEvent<HTMLButtonElement>) => { if (busy) { e.preventDefault(); return; } onClick?.(e); }}
      className={cn(
        "relative inline-flex items-center justify-center rounded-control font-medium whitespace-nowrap select-none cursor-pointer",
        pressable, "disabled:pointer-events-none",
        busy && "cursor-progress motion-safe:active:scale-100",
        variants[variant], sizes[size], className,
      )}
      {...rest}
    >
      <span className={cn("inline-flex items-center justify-center gap-[inherit] transition-opacity duration-(--dur-fast)", busy && "opacity-0")}>{leading}{children}{trailing}</span>
      {busy && <span className="absolute inset-0 flex items-center justify-center motion-safe:animate-fade"><BusyMark /><span className="sr-only">{busyLabel}</span></span>}
    </button>
  );
}

export interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  /** The button element, for moving focus to it. */
  ref?: Ref<HTMLButtonElement>;
  /** Required: icon-only controls must have an accessible name. */
  label: string;
  variant?: "ghost" | "secondary" | "primary" | "lime";
  size?: "sm" | "md";
  active?: boolean;
}

export function IconButton({ label, variant = "ghost", size = "md", active, className, children, type = "button", ...rest }: IconButtonProps) {
  const v = { ghost: "text-fg-subtle hover:bg-hover hover:text-fg active:bg-pressed disabled:bg-transparent disabled:text-disabled-fg", secondary: "border border-line bg-surface text-fg-muted hover:bg-bg hover:text-fg active:bg-pressed disabled:bg-disabled disabled:text-disabled-fg disabled:border-disabled-line", primary: "bg-accent text-on-accent hover:bg-accent-hover disabled:bg-disabled disabled:text-disabled-fg", lime: "bg-lime text-on-lime hover:bg-lime-hover disabled:bg-disabled disabled:text-disabled-fg" }[variant];
  return (
    <button
      type={type}
      data-slot="icon-button"
      data-variant={variant}
      data-size={size}
      data-active={active || undefined}
      aria-label={label}
      title={label}
      aria-pressed={active}
      className={cn(
        "inline-flex shrink-0 items-center justify-center rounded-control cursor-pointer disabled:pointer-events-none", pressable,
        size === "sm" ? "size-7" : "size-9", v, active && "bg-accent-soft text-accent-fg", className,
      )}
      {...rest}
    >
      {children}
    </button>
  );
}
```

## tattva/atoms/Checkbox.tsx

```tsx
import { useEffect, useId, useRef } from "react";
import type { InputHTMLAttributes, ReactNode } from "react";
import { cn } from "../lib/cn";
import { CheckIcon } from "../lib/icons";

export interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "checked" | "onChange" | "size" | "children"> {
  checked: boolean;
  onChange: (checked: boolean) => void;
  /** Visible label. Clicking it toggles the box. Also the accessible name. */
  label?: ReactNode;
  /** Visible hint under the label. Linked with aria-describedby. */
  description?: string;
  /** Shows a dash instead of a check, for a group that is partly selected. Pressing it still calls onChange. */
  indeterminate?: boolean;
  /** Marks the box as having an error (red border, aria-invalid). Show the error text yourself. */
  invalid?: boolean;
  /** Accessible name when there is no visible label. */
  ariaLabel?: string;
  className?: string;
}

/** Controlled checkbox. A native input, redrawn as a 20px box. The whole row is the click target, and at least 44px tall on coarse pointers. */
export function Checkbox({ checked, onChange, label, description, indeterminate, invalid, disabled, ariaLabel, id, className, ...rest }: CheckboxProps) {
  const ref = useRef<HTMLInputElement>(null);
  const labelId = useId();
  const descId = useId();
  // `indeterminate` is a DOM property only; there is no attribute for it.
  useEffect(() => { if (ref.current) ref.current.indeterminate = !!indeterminate; }, [indeterminate]);
  return (
    <label className={cn("group/cb inline-flex items-start gap-3 text-body leading-5 text-fg pointer-coarse:min-h-11 pointer-coarse:items-center", disabled ? "cursor-not-allowed opacity-(--disabled-opacity)" : "cursor-pointer", className)}>
      <span className="relative mt-0.5 inline-grid size-5 shrink-0 place-items-center pointer-coarse:mt-0">
        <input {...rest} ref={ref} type="checkbox" id={id} checked={checked} disabled={disabled}
          aria-checked={indeterminate ? "mixed" : checked}
          aria-invalid={invalid || undefined}
          aria-label={label ? undefined : ariaLabel}
          aria-labelledby={label ? labelId : undefined}
          aria-describedby={description ? descId : undefined}
          onChange={(e) => onChange(e.target.checked)}
          className={cn("peer size-5 shrink-0 appearance-none rounded-md border bg-surface transition-colors duration-[var(--dur-fast)] checked:border-accent checked:bg-accent indeterminate:border-accent indeterminate:bg-accent forced-colors:border-[ButtonText] forced-colors:checked:border-[Highlight] forced-colors:checked:bg-[Highlight] forced-colors:indeterminate:border-[Highlight] forced-colors:indeterminate:bg-[Highlight]",
            invalid ? "border-danger" : "border-line-strong", disabled ? "cursor-not-allowed" : "cursor-pointer")} />
        <CheckIcon aria-hidden className="pointer-events-none absolute size-3.5 text-on-accent opacity-0 peer-checked:opacity-100 peer-indeterminate:opacity-0 forced-colors:text-[HighlightText]" strokeWidth={3} />
        <svg aria-hidden viewBox="0 0 24 24" className="pointer-events-none absolute size-3.5 text-on-accent opacity-0 peer-indeterminate:opacity-100 forced-colors:text-[HighlightText]" fill="none" stroke="currentColor" strokeWidth={3} strokeLinecap="round">
          <path d="M6 12h12" />
        </svg>
      </span>
      {(label || description) && (
        <span className="flex min-w-0 flex-col">
          {label && <span id={labelId}>{label}</span>}
          {description && <span id={descId} className="text-fg-muted">{description}</span>}
        </span>
      )}
    </label>
  );
}
```

## tattva/atoms/CitationMarker.tsx

```tsx

/** Inline numbered marker, e.g. "…grew 12%[1]". Links to the source and is keyboard reachable. */
export function CitationMarker({ n, href, title }: { n: number; href?: string; title?: string }) {
  return (
    <a href={href ?? `#source-${n}`} title={title} aria-label={`Source ${n}${title ? `: ${title}` : ""}`}
      className="mx-0.5 inline-flex animate-pop h-4 min-w-4 -translate-y-0.5 items-center justify-center rounded bg-accent-soft px-1 text-nano font-semibold text-accent-fg no-underline hover:bg-accent hover:text-on-accent">
      {n}
    </a>
  );
}
```

## tattva/atoms/Cluster.tsx

```tsx
import { createElement, type CSSProperties, type HTMLAttributes, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { spaceValue, type Space } from "./Stack";

const align = { start: "items-start", center: "items-center", end: "items-end", baseline: "items-baseline" } as const;
const justify = { start: "justify-start", center: "justify-center", end: "justify-end", between: "justify-between" } as const;

export interface ClusterProps extends HTMLAttributes<HTMLElement> {
  /** Gap step from 1 to 8 on the 4px grid, scaled by --density. */
  gap?: Space;
  /** Cross-axis alignment. */
  align?: keyof typeof align;
  /** Main-axis distribution. */
  justify?: keyof typeof justify;
  as?: "div" | "ul" | "ol" | "nav" | "section" | "header" | "footer";
  children?: ReactNode;
}

/** Wrapping horizontal group for chips, actions and metadata. Items drop to the next line instead of overflowing. */
export function Cluster({ gap = 2, align: a = "center", justify: j = "start", as = "div", className, style, children, ...rest }: ClusterProps) {
  const s: CSSProperties = { gap: spaceValue(gap), ...style };
  // Safari stops calling a list a list once its markers are removed, so say it is one.
  const isList = as === "ul" || as === "ol";
  return createElement(as, { ...(isList ? { role: "list" } : {}), ...rest, className: cn("flex flex-wrap", align[a], justify[j], isList ? "list-none p-0 m-0" : "", className), style: s }, children);
}
```

## tattva/atoms/Container.tsx

```tsx
import { createElement, type HTMLAttributes, type ReactNode } from "react";
import { cn } from "../lib/cn";

const widths = {
  chat: "max-w-[var(--w-chat)]",
  home: "max-w-[var(--w-home)]",
  page: "max-w-[var(--w-page)]",
  full: "max-w-none",
} as const;

export interface ContainerProps extends HTMLAttributes<HTMLElement> {
  /** Max-width preset. chat 768px, home 680px, page 1120px (proposed), full has no cap. */
  size?: keyof typeof widths;
  /** Remove the side margins, for content that bleeds to the edge. */
  flush?: boolean;
  as?: "div" | "main" | "section" | "article" | "header" | "footer" | "nav";
  children?: ReactNode;
}

/** Centered column with a width preset and side margins from --margin (16, 24 and 32px by window class). */
export function Container({ size = "page", flush, as = "div", className, children, ...rest }: ContainerProps) {
  return createElement(as, { ...rest, className: cn("mx-auto w-full", widths[size], !flush && "px-[var(--margin)]", className) }, children);
}
```

## tattva/atoms/DataFreshness.tsx

```tsx
import { useEffect, useRef, useState } from "react";
import { cn } from "../lib/cn";

export type FreshnessStatus = "live" | "delayed" | "stale" | "simulated" | "closed";

export interface DataFreshnessProps {
  /** If left out, it is worked out from asOf and now. Delayed, simulated and closed are only used when passed. */
  status?: FreshnessStatus;
  /** When the data was last updated. ISO time. */
  asOf?: string;
  /** The current time, ISO. For docs and tests. By default the real time, refreshed every second. */
  now?: string;
  /** Shown in the delayed label, such as "Delayed 15 min". */
  delayMinutes?: number;
  /** Seconds after which data counts as stale. */
  staleAfter?: number;
  className?: string;
}

const LIVE_WITHIN = 10;

function ago(seconds: number): string {
  if (seconds < 60) return `${Math.max(0, Math.floor(seconds))} sec ago`;
  if (seconds < 3600) return `${Math.floor(seconds / 60)} min ago`;
  if (seconds < 86400) return `${Math.floor(seconds / 3600)} hr ago`;
  return `${Math.floor(seconds / 86400)} d ago`;
}

function Icon({ status }: { status: FreshnessStatus }) {
  const common = { "aria-hidden": true, viewBox: "0 0 16 16", className: "size-3.5 shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" } as const;
  if (status === "delayed" || status === "stale") return <svg {...common}><circle cx="8" cy="8" r="6" /><path d="M8 4.5V8l2.2 1.4" /></svg>;
  if (status === "simulated") return <svg {...common}><path d="M2 11l3-4 3 2 3-5 3 3" /><path d="M2 14h12" strokeDasharray="2 2" /></svg>;
  return <svg {...common}><circle cx="8" cy="8" r="6" /><path d="M5.5 8h5" /></svg>;
}

/** Says how fresh the numbers are, in words and an icon. Calm tones only: a late feed is not an error. */
export function DataFreshness({ status, asOf, now, delayMinutes = 15, staleAfter = 60, className }: DataFreshnessProps) {
  const computed = status === undefined;
  const [tickNow, setTickNow] = useState(() => Date.now());
  // Only a computed status with no fixed `now` needs a clock.
  useEffect(() => {
    if (!computed || now !== undefined || !asOf) return;
    const id = setInterval(() => setTickNow(Date.now()), 1000);
    return () => clearInterval(id);
  }, [computed, now, asOf]);

  const nowMs = now !== undefined ? Date.parse(now) : tickNow;
  const asOfMs = asOf !== undefined ? Date.parse(asOf) : NaN;
  const seconds = Number.isFinite(nowMs) && Number.isFinite(asOfMs) ? Math.max(0, (nowMs - asOfMs) / 1000) : NaN;

  let resolved: FreshnessStatus;
  if (status) resolved = status;
  else if (!Number.isFinite(seconds)) resolved = "stale";
  else resolved = seconds < LIVE_WITHIN ? "live" : seconds > staleAfter ? "stale" : "live";

  // A short gap between 10s and staleAfter still counts as live, so the label does not flicker.
  const label =
    resolved === "live" ? "Live"
    : resolved === "delayed" ? `Delayed ${delayMinutes} min`
    : resolved === "simulated" ? "Simulated data"
    : resolved === "closed" ? "Market closed"
    : Number.isFinite(seconds) ? `Stale: last update ${ago(seconds)}` : "Stale";

  // Announce once, only on the change into stale.
  const prevStatus = useRef(resolved);
  const [alert, setAlert] = useState("");
  useEffect(() => {
    if (prevStatus.current !== "stale" && resolved === "stale") setAlert("Data is stale");
    else if (resolved !== "stale") setAlert("");
    prevStatus.current = resolved;
  }, [resolved]);

  const tone = resolved === "delayed" || resolved === "stale" ? "bg-unsure-soft text-unsure-fg border-transparent" : "bg-sunken text-fg-muted border-line";
  return (
    <span className={cn("inline-flex items-center gap-1.5 rounded-control border px-2 py-0.5 text-micro leading-4 font-medium whitespace-nowrap forced-colors:border-[CanvasText]", tone, className)}>
      {resolved === "live" ? <span aria-hidden className="size-2 shrink-0 rounded-full bg-up motion-safe:animate-pulse-dot forced-colors:bg-[CanvasText]" /> : <Icon status={resolved} />}
      <span>{label}</span>
      <span aria-live="polite" className="sr-only">{alert}</span>
    </span>
  );
}
```

## tattva/atoms/EditScopeChip.tsx

```tsx
import { useEffect, useRef, useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { CheckIcon, ChevronIcon, EditIcon, FileIcon, LayersIcon } from "../lib/icons";
import { AIMark } from "./AIMark";
import { Popover } from "./Popover";

/** What an AI edit will change. Slide ids are your own; give a label when the default ("Slide 3", "3 slides") is not right. */
export type EditScope =
  | { kind: "element"; label: string }
  | { kind: "slides"; ids: string[]; label?: string }
  | { kind: "deck"; label?: string };

export interface EditScopeChipProps {
  /** The scope the next AI request will use. */
  scope: EditScope;
  /** The scopes the person can switch to. With one option or none, the chip is a plain label with no menu. */
  options: EditScope[];
  onChange: (scope: EditScope) => void;
  /** Cost or reach of a whole-deck edit, worked out by your app, such as "All 12 slides, about 40 credits". Shown in the menu and beside the chip while the whole deck is chosen. */
  deckCost?: ReactNode;
  /** Locks the scope, for example while the AI is already editing. Say why in disabledReason. */
  disabled?: boolean;
  /** Said beside a locked chip, such as "Scope is fixed while the AI edits". */
  disabledReason?: string;
  /** Word before the scope, in the chip and its accessible name. Default "Edit". */
  prefix?: string;
  className?: string;
}

/** The words for a scope: "Slide 3", "Slides 3 to 5", "4 slides", "Whole deck" or the element's own label. */
export function scopeLabel(s: EditScope): string {
  if (s.label) return s.label;
  if (s.kind === "deck") return "Whole deck";
  if (s.kind === "slides") {
    if (s.ids.length === 1) return `Slide ${s.ids[0]}`;
    const n = s.ids.map(Number);
    const run = n.every((v, i) => Number.isInteger(v) && (i === 0 || v === n[i - 1] + 1));
    return run && n.length > 1 ? `Slides ${s.ids[0]} to ${s.ids[s.ids.length - 1]}` : `${s.ids.length} slides`;
  }
  return "This element";
}

const key = (s: EditScope) => (s.kind === "slides" ? `slides:${s.ids.join(",")}` : s.kind === "element" ? `element:${s.label}` : "deck");
const ICON = { element: EditIcon, slides: FileIcon, deck: LayersIcon } as const;

/**
 * Shows exactly what the next AI request will change, and lets the person narrow or widen it before sending.
 * The chip stays neutral; only the small AI mark uses the AI colour.
 */
export function EditScopeChip({ scope, options, onChange, deckCost, disabled = false, disabledReason, prefix = "Edit", className }: EditScopeChipProps) {
  const [open, setOpen] = useState(false);
  const list = useRef<HTMLDivElement>(null);
  // The menu is drawn on the page (portal) so a clipped parent cannot cut it off. It becomes visible only after it is
  // placed, so focus moves to the checked scope one frame later.
  useEffect(() => {
    if (!open) return;
    const f = requestAnimationFrame(() => {
      const el = list.current;
      if (!el || el.contains(document.activeElement)) return;
      (el.querySelector<HTMLElement>('[aria-checked="true"]') ?? el.querySelector<HTMLElement>("[role=menuitemradio]"))?.focus();
    });
    return () => cancelAnimationFrame(f);
  }, [open]);
  const current = scopeLabel(scope);
  const Icon = ICON[scope.kind];
  const pickable = !disabled && options.length > 1;
  const name = `${prefix} scope: ${current}`;

  const face = (
    <>
      <span aria-hidden className="inline-flex size-4 shrink-0 items-center justify-center rounded-full bg-lime text-on-lime"><AIMark size={16} /></span>
      <span className="text-fg-muted">{prefix}<span className="sr-only"> scope:</span></span>
      <Icon aria-hidden width={13} height={13} className="shrink-0 text-fg-subtle" />
      <span className="min-w-0 truncate">{current}</span>
    </>
  );
  const chip = "inline-flex h-8 max-w-full items-center gap-1.5 rounded-control border border-line bg-surface px-2.5 text-small leading-4 font-medium text-fg tap";

  return (
    <span className={cn("inline-flex max-w-full flex-wrap items-center gap-x-2 gap-y-1", className)}>
      {pickable ? (
        <Popover open={open} onOpenChange={setOpen} role="menu" label="What the AI edit will change" side="top" portal panelClassName="w-72 max-w-[calc(100vw-2rem)]"
          trigger={({ triggerProps }) => (
            <button type="button" {...triggerProps} aria-label={name} onClick={() => setOpen(!open)}
              onKeyDown={(e) => { if (!open && (e.key === "ArrowDown" || e.key === "ArrowUp")) { e.preventDefault(); setOpen(true); } }}
              className={cn(chip, "cursor-pointer transition-colors duration-(--dur-fast) hover:border-line-strong hover:bg-hover")}>
              {face}
              <ChevronIcon aria-hidden width={12} height={12} className={cn("shrink-0 text-fg-subtle transition-transform duration-(--dur-fast)", open ? "-rotate-90" : "rotate-90")} />
            </button>
          )}>
          {({ close }) => (
            <div ref={list}>
              <p role="presentation" className="px-2.5 pt-1.5 pb-1 text-small leading-4 font-medium text-fg-subtle">The AI will change</p>
              {options.map((o) => {
                const checked = key(o) === key(scope);
                const OIcon = ICON[o.kind];
                return (
                  <button key={key(o)} type="button" role="menuitemradio" aria-checked={checked} tabIndex={-1}
                    onClick={() => { if (!checked) onChange(o); close(); }}
                    className="flex min-h-9 w-full cursor-pointer items-start gap-2.5 rounded-control px-2.5 py-2 text-left text-body text-fg hover:bg-hover focus-visible:bg-hover focus-visible:outline-offset-[-2px]">
                    <OIcon aria-hidden width={15} height={15} className="mt-0.5 shrink-0 text-fg-subtle" />
                    <span className="min-w-0 flex-1">
                      <span className="block">{scopeLabel(o)}</span>
                      {o.kind === "deck" && deckCost && <span className="block text-small text-fg-muted">{deckCost}</span>}
                    </span>
                    <span aria-hidden className="flex size-4 shrink-0 items-center justify-center pt-0.5">{checked && <CheckIcon width={14} height={14} />}</span>
                  </button>
                );
              })}
            </div>
          )}
        </Popover>
      ) : (
        <span className={cn(chip, disabled && "border-dashed text-fg-muted")}>
          {face}
        </span>
      )}
      {scope.kind === "deck" && deckCost && !disabled && <span className="text-small text-fg-muted">{deckCost}</span>}
      {disabled && disabledReason && <span className="text-small text-fg-muted">{disabledReason}</span>}
    </span>
  );
}
```

## tattva/atoms/FrameScrubber.tsx

```tsx
import { useRef, useState } from "react";
import type { HTMLAttributes, KeyboardEvent, PointerEvent, ReactNode } from "react";
import { cn } from "../lib/cn";
import { formatMediaTime, speakMediaTime } from "./Waveform";

export interface FrameScrubberProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
  /** Length of the clip in seconds. 0 or less shows a disabled, empty track (length not known yet). */
  duration: number;
  /** Playhead in seconds. The parent owns it. */
  value: number;
  /** Called with the new time after a drag, a tap or a key press. */
  onChange: (seconds: number) => void;
  /** Name of the slider, such as "Seek, take 3". */
  label: string;
  /** Frames per second. Turns on frame steps (comma and period, or Shift with an arrow, while the scrubber has focus) and snaps every move to a whole frame. */
  fps?: number;
  /** Seconds moved by an arrow key. */
  step?: number;
  /** Still images spread evenly over the clip. The nearest one shows above the playhead while dragging or pointing. */
  thumbnails?: string[];
  /** Draws the preview above the playhead for a time, when there are no image thumbnails (for example a drawn frame). */
  renderThumbnail?: (seconds: number) => ReactNode;
  /** Called when a drag starts and ends, so a player can pause while scrubbing. */
  onScrubbingChange?: (scrubbing: boolean) => void;
  /** Blocks seeking. The slider stays focusable and says it is unavailable. */
  disabled?: boolean;
  className?: string;
}

const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));

/** "3.2 seconds" under a minute, where tenths matter; "1 minute 12 seconds" past it. */
export function speakClipTime(seconds: number): string {
  const t = Math.max(0, seconds || 0);
  if (t >= 60) return speakMediaTime(t);
  const r = Math.round(t * 10) / 10;
  return `${Number.isInteger(r) ? r : r.toFixed(1)} second${r === 1 ? "" : "s"}`;
}

/** The frame number counted from 1, as people say it. */
export const frameAt = (seconds: number, fps: number) => Math.floor(Math.max(0, seconds) * fps + 1e-6) + 1;

/**
 * The seek slider for a video clip. A thin track with a 44px hit area, the played part in ink (never the AI colour),
 * value text in words, and frame steps on comma and period that work only while the scrubber itself has focus.
 */
export function FrameScrubber({
  duration, value, onChange, label, fps, step = 1, thumbnails, renderThumbnail, onScrubbingChange, disabled = false, className, ...rest
}: FrameScrubberProps) {
  const track = useRef<HTMLDivElement>(null);
  const drag = useRef<number | null>(null);
  const [dragAt, setDragAt] = useState<number | null>(null);
  const [hoverAt, setHoverAt] = useState<number | null>(null);

  const dur = Math.max(0, duration || 0);
  const live = !disabled && dur > 0;
  const frame = fps ? 1 / fps : 0;
  const snap = (t: number) => clamp(frame ? Math.round(t / frame) * frame : t, 0, dur);
  const pos = clamp(dragAt ?? value, 0, dur);
  const pct = (s: number) => `${dur ? (clamp(s, 0, dur) / dur) * 100 : 0}%`;
  const totalFrames = fps ? Math.max(1, Math.round(dur * fps)) : 0;

  const timeAt = (clientX: number) => {
    const r = track.current?.getBoundingClientRect();
    return r && r.width ? snap(clamp((clientX - r.left) / r.width, 0, 1) * dur) : 0;
  };
  const down = (ev: PointerEvent<HTMLDivElement>) => {
    if (!live || (ev.pointerType === "mouse" && ev.button !== 0)) return;
    ev.currentTarget.setPointerCapture(ev.pointerId);
    ev.currentTarget.focus();
    drag.current = ev.pointerId;
    const t = timeAt(ev.clientX);
    setDragAt(t);
    onScrubbingChange?.(true);
    onChange(t);
  };
  const move = (ev: PointerEvent<HTMLDivElement>) => {
    if (drag.current === ev.pointerId) { const t = timeAt(ev.clientX); setDragAt(t); onChange(t); }
    else if (live && ev.pointerType === "mouse") setHoverAt(timeAt(ev.clientX));
  };
  const up = (ev: PointerEvent<HTMLDivElement>) => {
    if (drag.current !== ev.pointerId) return;
    drag.current = null;
    setDragAt(null);
    onScrubbingChange?.(false);
  };
  const key = (ev: KeyboardEvent<HTMLDivElement>) => {
    if (!live || ev.altKey || ev.ctrlKey || ev.metaKey) return;
    const k = ev.key;
    const one = frame || 0.1;
    let to: number;
    // Frame steps: comma and period, or Shift with an arrow. They only reach here while this slider has focus.
    if (k === "," || (ev.shiftKey && (k === "ArrowLeft" || k === "ArrowDown"))) to = value - one;
    else if (k === "." || (ev.shiftKey && (k === "ArrowRight" || k === "ArrowUp"))) to = value + one;
    else if (k === "ArrowRight" || k === "ArrowUp") to = value + step;
    else if (k === "ArrowLeft" || k === "ArrowDown") to = value - step;
    else if (k === "PageUp") to = value + step * 10;
    else if (k === "PageDown") to = value - step * 10;
    else if (k === "Home") to = 0;
    else if (k === "End") to = dur;
    else return;
    ev.preventDefault();
    ev.stopPropagation();
    onChange(snap(to));
  };

  const valueText = dur > 0
    ? `${speakClipTime(value)} of ${speakClipTime(dur)}${fps ? `, frame ${Math.min(frameAt(value, fps), totalFrames)} of ${totalFrames}` : ""}`
    : "Length not known yet";

  const peekAt = dragAt ?? hoverAt;
  const thumbFor = (t: number) => {
    if (thumbnails?.length) {
      const i = clamp(Math.round((t / (dur || 1)) * (thumbnails.length - 1)), 0, thumbnails.length - 1);
      return <img src={thumbnails[i]} alt="" className="size-full object-cover" />;
    }
    return renderThumbnail?.(t);
  };
  const hasThumbs = !!thumbnails?.length || !!renderThumbnail;

  return (
    <div className={cn("relative min-w-0", className)} {...rest}>
      <div ref={track} role="slider" tabIndex={0} aria-label={label}
        aria-valuemin={0} aria-valuemax={Math.round(dur * 10) / 10} aria-valuenow={Math.round(clamp(value, 0, dur) * 10) / 10}
        aria-valuetext={valueText} aria-orientation="horizontal" aria-disabled={!live || undefined}
        onKeyDown={key} onPointerDown={down} onPointerMove={move} onPointerUp={up} onPointerCancel={up} onPointerLeave={() => setHoverAt(null)}
        className={cn("group relative flex min-h-11 touch-none items-center rounded-sm select-none", live ? "cursor-pointer" : "cursor-default")}>
        {/* The drawn track is 4px; the slider box around it is the 44px hit area. */}
        <span aria-hidden className="relative block h-1 w-full rounded-full bg-line-strong forced-colors:bg-[GrayText]">
          <span className="absolute inset-y-0 left-0 rounded-full bg-fg forced-colors:bg-[CanvasText]" style={{ width: pct(pos) }} />
        </span>
        {live && (
          <span aria-hidden className="absolute top-1/2 size-3.5 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-surface bg-fg shadow-sm transition-transform duration-(--dur-fast) group-hover:scale-110 group-focus-visible:scale-110 forced-colors:bg-[CanvasText] motion-reduce:transition-none"
            style={{ left: pct(pos) }} />
        )}
      </div>
      {live && peekAt !== null && (
        <span aria-hidden className="pointer-events-none absolute bottom-full z-10 mb-1 flex -translate-x-1/2 flex-col items-center gap-1"
          style={{ left: `clamp(var(--spacing) * 10, ${pct(peekAt)}, 100% - var(--spacing) * 10)` }}>
          {hasThumbs && <span className="block h-11 w-20 overflow-hidden rounded-sm border border-line bg-sunken shadow-md">{thumbFor(peekAt)}</span>}
          <span className="rounded-control bg-inverse px-1.5 py-0.5 text-small text-inverse-fg tabular-nums shadow-sm">
            {formatMediaTime(peekAt)}{fps ? ` · frame ${frameAt(peekAt, fps)}` : ""}
          </span>
        </span>
      )}
    </div>
  );
}
```

## tattva/atoms/GhostText.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { XIcon } from "../lib/icons";
import { Kbd } from "./Kbd";

export interface GhostTextProps {
  /** The suggested continuation, shown dimmed. */
  children: string;
  /** Text the person already typed. Rendered in normal ink before the suggestion. */
  typed?: ReactNode;
  /** Id for the visually hidden description. Point the field's aria-describedby at it. */
  id?: string;
  /** Key that accepts the whole suggestion. Shown in the hint. */
  acceptKey?: string;
  /** Key that dismisses the suggestion. Announced to assistive tech. */
  dismissKey?: string;
  /** Text for a partial accept path, for example "Ctrl + Right accepts one word". Omit when partial accept is not offered. */
  partialHint?: string;
  /** Override the text read by assistive tech. The default names the suggestion, the accept key and the dismiss key. */
  description?: string;
  /** Called when the hint is activated. The consumer wires the real key (usually Tab) to the same handler. */
  onAccept?: () => void;
  /** Called when the dismiss control is activated. The consumer wires Escape to the same handler. */
  onDismiss?: () => void;
  /** Accessible names for the two controls. */
  acceptLabel?: string;
  dismissLabel?: string;
  className?: string;
}

/**
 * Dimmed inline suggestion shown after typed text. The visible suggestion is hidden from the accessibility tree and
 * a visually hidden description carries the same text, so it is read as a pending suggestion and not as typed text.
 * It never uses aria-live. The consumer owns key handling.
 */
export function GhostText({
  children, typed, id, acceptKey = "Tab", dismissKey = "Esc", partialHint, description,
  onAccept, onDismiss, acceptLabel = "Accept suggestion", dismissLabel = "Dismiss suggestion", className,
}: GhostTextProps) {
  const hint = <Kbd>{acceptKey}</Kbd>;
  return (
    <span className={cn("inline", className)}>
      {typed}
      <span id={id} className="sr-only">
        {description ?? `Suggestion available: ${children}. Press ${acceptKey} to accept, ${dismissKey} to dismiss.${partialHint ? ` ${partialHint}.` : ""}`}
      </span>
      <span aria-hidden="true" data-ghost className="rounded-sm bg-sunken text-fg-subtle underline decoration-line-strong decoration-dotted underline-offset-4">{children}</span>
      <span className="ml-1.5 inline-flex items-center gap-1 align-middle">
        {onAccept
          ? <button type="button" aria-label={`${acceptLabel}, ${acceptKey}`} onClick={onAccept} className="cursor-pointer rounded-md">{hint}</button>
          : <span aria-hidden="true">{hint}</span>}
        {partialHint && <span aria-hidden="true" className="text-micro text-fg-subtle">{partialHint}</span>}
        {onDismiss && (
          <button type="button" aria-label={dismissLabel} title={dismissLabel} onClick={onDismiss}
            className="inline-flex size-5 cursor-pointer items-center justify-center rounded-control text-fg-subtle hover:bg-hover hover:text-fg">
            <XIcon width={12} height={12} />
          </button>
        )}
      </span>
    </span>
  );
}
```

## tattva/atoms/Grid.tsx

```tsx
import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
import { cn } from "../lib/cn";
import { spaceValue, type Space } from "./Stack";

export type Cols = 1 | 2 | 3 | 4 | 6 | 8 | 12;

const base: Record<Cols, string> = { 1: "grid-cols-1", 2: "grid-cols-2", 3: "grid-cols-3", 4: "grid-cols-4", 6: "grid-cols-6", 8: "grid-cols-8", 12: "grid-cols-12" };
const md: Record<Cols, string> = { 1: "md:grid-cols-1", 2: "md:grid-cols-2", 3: "md:grid-cols-3", 4: "md:grid-cols-4", 6: "md:grid-cols-6", 8: "md:grid-cols-8", 12: "md:grid-cols-12" };
const lg: Record<Cols, string> = { 1: "lg:grid-cols-1", 2: "lg:grid-cols-2", 3: "lg:grid-cols-3", 4: "lg:grid-cols-4", 6: "lg:grid-cols-6", 8: "lg:grid-cols-8", 12: "lg:grid-cols-12" };

export interface GridCols {
  /** Columns below md. Default 4. */
  base?: Cols;
  /** Columns from md (840px). Default 8. */
  md?: Cols;
  /** Columns from lg (1200px). Default 12. */
  lg?: Cols;
}

export interface GridProps extends HTMLAttributes<HTMLDivElement> {
  /** Column counts per window class. Defaults follow the 4, 8, 12 column system. */
  cols?: GridCols;
  /** Minimum item width as a CSS length such as "16rem". When set, the grid auto-fits and `cols` is ignored. */
  min?: string;
  /** Gap step from 1 to 8, scaled by --density. Omit to use the --gutter token. */
  gap?: Space;
  children?: ReactNode;
}

/** Responsive grid. Items span columns with Tailwind col-span utilities. */
export function Grid({ cols, min, gap, className, style, children, ...rest }: GridProps) {
  const c = { base: 4 as Cols, md: 8 as Cols, lg: 12 as Cols, ...cols };
  const s: CSSProperties = {
    gap: gap ? spaceValue(gap) : "var(--gutter)",
    ...(min ? { gridTemplateColumns: `repeat(auto-fit, minmax(min(100%, ${min}), 1fr))` } : null),
    ...style,
  };
  return <div {...rest} className={cn("grid", !min && [base[c.base], md[c.md], lg[c.lg]].join(" "), className)} style={s}>{children}</div>;
}
```

## tattva/atoms/HeroHeading.tsx

```tsx
import { useEffect, useState } from "react";

/** Serif headline that types itself in once. Static for reduced-motion users and screen readers. */
export function HeroHeading({ text, type = true }: { text: string; type?: boolean }) {
  const reduce = typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches;
  const [n, setN] = useState(type && !reduce ? 0 : text.length);
  useEffect(() => {
    if (!type || reduce) return;
    setN(0);
    const t = window.setInterval(() => setN((v) => { if (v >= text.length) { window.clearInterval(t); return v; } return v + 1; }), 28);
    return () => window.clearInterval(t);
  }, [text, type, reduce]);
  return (
    <h2 aria-label={text} className="text-balance text-center text-title-md leading-9 tracking-snug text-fg sm:text-head-sm sm:leading-headline">
      <span aria-hidden>{text.slice(0, n)}</span>
    </h2>
  );
}
```

## tattva/atoms/Illustration.tsx

```tsx
import type { ReactNode } from "react";

/** Names of the spot illustrations. Each one shows a single concept. */
export type IllustrationName =
  | "empty-chat"
  | "no-results"
  | "error"
  | "permission"
  | "offline"
  | "rate-limit"
  | "success"
  | "upload"
  | "first-run";

export type IllustrationSize = 64 | 96 | 160;

/** Gallery data: every name with a short plain description of what is drawn. */
export const spotIllustrations: { name: IllustrationName; label: string }[] = [
  { name: "empty-chat", label: "Two empty speech bubbles" },
  { name: "no-results", label: "A magnifier over a sheet of lines" },
  { name: "error", label: "A sheet with an exclamation badge" },
  { name: "permission", label: "A closed lock" },
  { name: "offline", label: "A plug apart from its socket" },
  { name: "rate-limit", label: "An hourglass" },
  { name: "success", label: "A sheet with a check badge" },
  { name: "upload", label: "A tray with a sheet and an up arrow" },
  { name: "first-run", label: "A dotted path leading to a flag" },
];

export interface IllustrationProps {
  /** Which spot illustration to draw. */
  name: IllustrationName;
  /** Rendered size in px. Stroke weight follows it: 64 is 1.5px, 96 is 2px, 160 is 2.5px. */
  size?: IllustrationSize;
  /** Accessible name. Leave it out for decorative art, which is hidden from assistive tech. */
  title?: string;
  className?: string;
}

const strokeFor: Record<IllustrationSize, number> = { 64: 1.5, 96: 2, 160: 2.5 };

/* Art is drawn on a 160 unit grid. Ink is stroke-fg, paper is fill-surface, sand is fill-sunken,
   and each piece uses at most one lime fill. No detail is smaller than 4 units. */
const art: Record<IllustrationName, ReactNode> = {
  "empty-chat": (
    <>
      <path className="fill-sunken" d="M36 30H86a12 12 0 0 1 12 12V68a12 12 0 0 1-12 12H52L40 92V80H36a12 12 0 0 1-12-12V42a12 12 0 0 1 12-12Z" />
      <path className="fill-surface" d="M70 62H124a12 12 0 0 1 12 12V104a12 12 0 0 1-12 12H116V132L100 116H70a12 12 0 0 1-12-12V74a12 12 0 0 1 12-12Z" />
      <path d="M74 78H120M74 90H106" />
      <rect className="fill-lime" x="74" y="99" width="18" height="6" rx="3" />
    </>
  ),
  "no-results": (
    <>
      <rect className="fill-surface" x="30" y="26" width="64" height="86" rx="8" />
      <path d="M44 46H80M44 58H80M44 70H64" />
      <circle className="fill-sunken" cx="94" cy="90" r="26" />
      <path d="M82 84a14 14 0 0 1 10-10" />
      <rect className="fill-lime" x="0" y="0" width="28" height="10" rx="5" transform="translate(113 109) rotate(45)" />
    </>
  ),
  error: (
    <>
      <path className="fill-surface" d="M42 24H98L120 46V132H42Z" />
      <path d="M98 24V46H120M56 62H90M56 74H80M56 86H70" />
      <circle className="fill-lime" cx="110" cy="112" r="20" />
      <path d="M110 102V114M110 121V122" />
    </>
  ),
  permission: (
    <>
      <path d="M58 70V52a22 22 0 0 1 44 0V70" />
      <rect className="fill-surface" x="42" y="70" width="76" height="60" rx="12" />
      <circle className="fill-lime" cx="80" cy="97" r="8" />
      <path d="M80 105V115" />
    </>
  ),
  offline: (
    <>
      <path d="M22 82C8 82 8 108 28 120" />
      <rect className="fill-lime" x="22" y="64" width="42" height="36" rx="10" />
      <rect className="fill-sunken" x="64" y="70" width="14" height="6" rx="3" />
      <rect className="fill-sunken" x="64" y="88" width="14" height="6" rx="3" />
      <rect className="fill-surface" x="104" y="56" width="40" height="52" rx="10" />
      <path d="M114 72H126M114 92H126" />
      <path d="M12 134H148" />
    </>
  ),
  "rate-limit": (
    <>
      <path className="fill-surface" d="M52 32V46C52 64 80 66 80 80C80 94 52 96 52 114V128H108V114C108 96 80 94 80 80C80 66 108 64 108 46V32Z" />
      <path className="fill-sunken" d="M64 46H96C96 54 88 58 80 62C72 58 64 54 64 46Z" />
      <path className="fill-lime" d="M64 122H96C96 112 88 106 80 102C72 106 64 112 64 122Z" />
      <rect className="fill-surface" x="42" y="22" width="76" height="10" rx="5" />
      <rect className="fill-surface" x="42" y="128" width="76" height="10" rx="5" />
    </>
  ),
  success: (
    <>
      <rect className="fill-surface" x="36" y="26" width="64" height="88" rx="8" />
      <path d="M50 46H86M50 58H86M50 70H72" />
      <circle className="fill-lime" cx="104" cy="104" r="28" />
      <path d="M92 104L101 113L117 93" />
    </>
  ),
  upload: (
    <>
      <rect className="fill-surface" x="48" y="26" width="64" height="80" rx="8" />
      <rect className="fill-lime" x="62" y="38" width="36" height="6" rx="3" />
      <path d="M80 82V56M68 66L80 54L92 66" />
      <path className="fill-sunken" d="M22 98H52L58 110H102L108 98H138V122A10 10 0 0 1 128 132H32A10 10 0 0 1 22 122Z" />
    </>
  ),
  "first-run": (
    <>
      <path d="M26 122C54 122 50 98 76 102C96 105 98 118 112 106" strokeDasharray="0.1 10" />
      <circle className="fill-surface" cx="26" cy="122" r="6" />
      <ellipse className="fill-sunken" cx="116" cy="108" rx="20" ry="5" />
      <path d="M116 28V108" />
      <path className="fill-lime" d="M116 28H146L134 42L146 56H116Z" />
    </>
  ),
};

/**
 * Spot illustration for empty, error and success states. One concept, monochrome ink on paper,
 * at most one lime accent, drawn only with theme tokens so it follows light and dark mode.
 * Decorative by default. Pass `title` when the art carries meaning that the page text does not.
 */
export function Illustration({ name, size = 96, title, className }: IllustrationProps) {
  const units = (strokeFor[size] * 160) / size;
  return (
    <svg
      viewBox="0 0 160 160"
      width={size}
      height={size}
      className={className}
      role={title ? "img" : undefined}
      aria-label={title}
      aria-hidden={title ? undefined : true}
      focusable="false"
    >
      <g className="fill-none stroke-fg" strokeWidth={units} strokeLinecap="round" strokeLinejoin="round">{art[name]}</g>
    </svg>
  );
}
```

## tattva/atoms/Kbd.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";

/** Keyboard hint pill such as Tab or Esc. Pair it with the action it triggers, and keep the action reachable without the key. */
export function Kbd({ children, className }: { children: ReactNode; className?: string }) {
  return <kbd className={cn("inline-flex h-5 min-w-5 items-center justify-center rounded-md border border-line-strong bg-surface px-1.5 font-mono text-nano text-fg-muted", className)}>{children}</kbd>;
}

/** @deprecated Renamed to Kbd in 0.29.0, to match the name other design systems use. The old name goes in 0.31.0. */
export const KeyHint = Kbd;
```

## tattva/atoms/Meter.tsx

```tsx
import { cn } from "../lib/cn";

export interface MeterProps {
  value: number;
  max?: number;
  /** Accessible name, e.g. "Context used". */
  label: string;
  /** Switch to the attention color at or above this fraction of max (0 to 1). Use only when the person should act. */
  warnAt?: number;
  /** Text read out with the value, e.g. "68% of context". */
  valueText?: string;
  className?: string;
}

/** Thin progress bar exposed as a meter. Ink by default, amber past the warn threshold. */
export function Meter({ value, max = 100, label, warnAt, valueText, className }: MeterProps) {
  const pct = Math.max(0, Math.min(100, (value / max) * 100));
  const warn = warnAt !== undefined && value / max >= warnAt;
  return (
    <div role="meter" aria-label={label} aria-valuemin={0} aria-valuemax={max} aria-valuenow={value} aria-valuetext={valueText}
      className={cn("h-1.5 w-full overflow-hidden rounded-full bg-line", className)}>
      <div className={cn("h-full origin-left animate-grow-x rounded-full transition-[width] duration-[var(--dur-base)]", warn ? "bg-attention" : "bg-fg")} style={{ width: `${pct}%` }} />
    </div>
  );
}

/** @deprecated Renamed to Meter in 0.29.0, to match the name other design systems use. The old name goes in 0.31.0. */
export const MeterBar = Meter;
/** @deprecated Renamed to MeterProps in 0.29.0. */
export type MeterBarProps = MeterProps;
```

## tattva/atoms/MicButton.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import type { KeyboardEvent, MouseEvent, PointerEvent } from "react";
import { cn } from "../lib/cn";
import { LockIcon, MicIcon, MicOffIcon, StopIcon } from "../lib/icons";
import { useReducedMotion } from "../hooks/useReducedMotion";
import { AttentionDot } from "./AttentionDot";
import { Spinner } from "./Spinner";

export type MicState = "idle" | "requesting" | "recording" | "locked" | "processing" | "blocked" | "unavailable";

export interface MicButtonProps {
  /** Where the mic is. The app owns it: set it from the real permission, recorder and transcriber. */
  state: MicState;
  /** tap: press once to start and once to stop. hold: hold to talk, slide up to lock, slide away to cancel. */
  mode?: "tap" | "hold";
  /** Input level from 0 to 1, read from the mic. Drives the three bars while recording. */
  level?: number;
  /** The person asked to start. Ask for the mic, then set state to requesting, recording or blocked. */
  onStart: () => void;
  /** The person asked to stop. Keep what was said and move to processing or idle. */
  onStop: () => void;
  /** The person slid away and let go (hold), or pressed Cancel while locked. Throw the recording away. */
  onCancel?: () => void;
  /** The person slid up while holding (hold mode only). Set state to locked; recording carries on hands-free. */
  onLock?: () => void;
  /** Pressed while blocked. Open the steps for turning the mic back on. */
  onHelp?: () => void;
  /** What the button does when idle, such as "Dictate" or "Start voice". */
  label?: string;
  /** Show the label next to the button when idle. Other states always show their word. */
  showLabel?: boolean;
  /** md is 44px, lg is 56px (the main control on a voice screen). */
  size?: "md" | "lg";
  /** A short buzz on start and stop where the device supports it. */
  haptics?: boolean;
  className?: string;
}

/** How far up (px) a held press must slide to lock. */
const LOCK_AT = 56;
/** How far outside the button (px) a held press must slide to arm cancel. */
const CANCEL_AT = 32;

const stateWord: Record<Exclude<MicState, "idle">, string> = {
  requesting: "Allow the mic to start",
  recording: "Recording",
  locked: "Recording, locked",
  processing: "Writing it down",
  blocked: "Mic blocked",
  unavailable: "No mic found",
};

const buzz = (on: boolean) => { if (on && typeof navigator !== "undefined" && "vibrate" in navigator) navigator.vibrate?.(12); };

/** Three bars that follow the input level. Heights ease over --dur-fast, which smooths the raw level. Neutral, never lime: it is the person's voice. */
function Bars({ level }: { level: number }) {
  const l = Math.max(0, Math.min(1, level));
  return (
    <span aria-hidden className="flex h-4 items-center gap-0.5">
      {[0.65, 1, 0.8].map((w, i) => (
        <span key={i} style={{ height: `calc(var(--spacing) * ${1 + Math.round(l * w * 30) / 10})` }}
          className="w-1 rounded-full bg-current transition-[height] duration-(--dur-fast) ease-(--ease-out)" />
      ))}
    </span>
  );
}

/**
 * The one mic control for dictation and for starting voice. It shows every mic state in words beside the button:
 * waiting for permission, recording, locked, writing it down, blocked and no mic. In hold mode the person holds to
 * talk, slides up to lock and slides away to cancel; keyboard people hold Space, or press Enter to start and stop.
 */
export function MicButton({
  state, mode = "tap", level = 0, onStart, onStop, onCancel, onLock, onHelp,
  label = "Dictate", showLabel = false, size = "md", haptics = true, className,
}: MicButtonProps) {
  const reduced = useReducedMotion();
  const hintId = useId();
  const live = state === "recording" || state === "locked";
  const busy = state === "requesting" || state === "processing";
  const inert = busy || state === "unavailable";

  // A held press in progress (pointer or Space), and whether letting go now cancels.
  const [holding, setHolding] = useState(false);
  const [cancelArmed, setCancelArmed] = useState(false);
  const origin = useRef<{ x: number; y: number } | null>(null);
  // The click that follows the end of a held press must not toggle again.
  const pressed = useRef(false);
  const skipClick = useRef(false);
  const swallowNextClick = () => { skipClick.current = true; window.setTimeout(() => { skipClick.current = false; }, 0); };

  // The host left recording while a hold was in progress (an error, a lock): forget the hold.
  if (holding && state !== "idle" && state !== "recording" && state !== "requesting") { setHolding(false); setCancelArmed(false); }

  // Announce only "Recording" and "Stopped", once each, in a polite region that is always in the page.
  const [said, setSaid] = useState("");
  const wasLive = useRef(live);
  useEffect(() => {
    if (live && !wasLive.current) setSaid("Recording");
    else if (!live && wasLive.current) setSaid("Stopped");
    wasLive.current = live;
  }, [live]);

  const start = () => { buzz(haptics); onStart(); };
  const stop = () => { buzz(haptics); onStop(); };

  const onClick = (e: MouseEvent<HTMLButtonElement>) => {
    if (skipClick.current) { skipClick.current = false; return; }
    if (state === "blocked") { onHelp?.(); return; }
    if (inert) return;
    // Tap mode, a locked hold, or Enter on a hold button (detail 0 is a keyboard click): start and stop like a toggle.
    if (mode === "tap" || state === "locked" || e.detail === 0) {
      if (live) stop(); else if (state === "idle") start();
    }
  };

  const endHold = (cancel: boolean) => {
    origin.current = null;
    setHolding(false);
    setCancelArmed(false);
    if (cancel && onCancel) { buzz(haptics); onCancel(); } else stop();
  };

  const onPointerDown = (e: PointerEvent<HTMLButtonElement>) => {
    if (mode !== "hold" || state !== "idle" || e.button !== 0) return;
    e.currentTarget.setPointerCapture(e.pointerId);
    origin.current = { x: e.clientX, y: e.clientY };
    pressed.current = true;
    setHolding(true);
    start();
  };
  const onPointerMove = (e: PointerEvent<HTMLButtonElement>) => {
    if (!holding || !origin.current) return;
    const dy = e.clientY - origin.current.y;
    if (onLock && dy < -LOCK_AT) { origin.current = null; setHolding(false); setCancelArmed(false); onLock(); return; }
    const r = e.currentTarget.getBoundingClientRect();
    const out = e.clientX < r.left - CANCEL_AT || e.clientX > r.right + CANCEL_AT || e.clientY > r.bottom + CANCEL_AT;
    setCancelArmed(out && !!onCancel);
  };
  const onPointerUp = () => {
    if (!pressed.current) return;
    pressed.current = false;
    swallowNextClick();
    if (holding && origin.current) endHold(cancelArmed);
  };

  const onKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {
    if (mode !== "hold" || e.key !== " ") return;
    e.preventDefault();
    if (e.repeat || state !== "idle") return;
    pressed.current = true;
    origin.current = { x: 0, y: 0 };
    setHolding(true);
    start();
  };
  const onKeyUp = (e: KeyboardEvent<HTMLButtonElement>) => {
    if (mode !== "hold" || e.key !== " ") return;
    e.preventDefault();
    if (!pressed.current) return;
    pressed.current = false;
    swallowNextClick();
    if (holding) endHold(false);
  };

  const name =
    live ? "Stop recording"
    : state === "requesting" ? "Waiting for mic permission"
    : state === "processing" ? "Writing it down"
    : state === "blocked" ? "Mic blocked. Show how to turn it on"
    : state === "unavailable" ? "No mic found"
    : label;

  const word =
    holding && cancelArmed ? "Let go to cancel"
    : state === "idle" ? (showLabel ? label : "")
    : stateWord[state];
  const hint = holding && !cancelArmed ? [onLock && "Slide up to lock", onCancel && "slide away to cancel"].filter(Boolean).join(", ") : "";

  return (
    <span className={cn("inline-flex items-center gap-2", className)}>
      <button
        type="button"
        aria-label={name}
        aria-pressed={live}
        aria-disabled={inert || undefined}
        aria-describedby={mode === "hold" && state === "idle" ? hintId : undefined}
        onClick={onClick}
        onPointerDown={onPointerDown}
        onPointerMove={onPointerMove}
        onPointerUp={onPointerUp}
        onPointerCancel={() => { pressed.current = false; if (holding) endHold(false); }}
        onKeyDown={onKeyDown}
        onKeyUp={onKeyUp}
        onContextMenu={mode === "hold" ? (e) => e.preventDefault() : undefined}
        className={cn(
          "relative inline-flex shrink-0 select-none items-center justify-center rounded-full",
          "transition-[color,background-color,border-color,scale] duration-(--dur-fast) ease-(--ease-out)",
          "motion-safe:active:scale-96 motion-safe:active:duration-(--dur-instant)",
          mode === "hold" && "touch-none [-webkit-touch-callout:none]",
          size === "lg" ? "size-14" : "size-11",
          live
            ? cancelArmed ? "border border-line-strong bg-sunken text-fg-muted" : "bg-accent text-on-accent hover:bg-accent-hover"
            : "border border-line bg-surface text-fg hover:border-line-strong hover:bg-hover",
          inert ? "cursor-default text-fg-muted motion-safe:active:scale-100 hover:border-line hover:bg-surface" : "cursor-pointer",
          state === "unavailable" && "opacity-60",
        )}
      >
        {live && !reduced && !cancelArmed && state === "recording" && <Bars level={level} />}
        {live && (reduced || cancelArmed) && state === "recording" && <StopIcon width={size === "lg" ? 20 : 16} height={size === "lg" ? 20 : 16} />}
        {state === "locked" && <StopIcon width={size === "lg" ? 20 : 16} height={size === "lg" ? 20 : 16} />}
        {state === "processing" && <span aria-hidden className="motion-reduce:[&_svg]:animate-none"><Spinner size={size === "lg" ? 20 : 16} label="" /></span>}
        {(state === "idle" || state === "requesting") && <MicIcon width={size === "lg" ? 22 : 18} height={size === "lg" ? 22 : 18} />}
        {(state === "blocked" || state === "unavailable") && <MicOffIcon width={size === "lg" ? 22 : 18} height={size === "lg" ? 22 : 18} />}
        {state === "blocked" && (
          <span aria-hidden className="absolute right-1 top-1 flex rounded-full bg-surface p-0.5"><AttentionDot /></span>
        )}
        {state === "requesting" && (
          <span aria-hidden className="absolute inset-0 rounded-full border-2 border-transparent border-t-fg-subtle motion-safe:animate-spin" />
        )}
      </button>

      {(word || hint) && (
        <span className="flex min-w-0 flex-col leading-tight">
          {word && (
            <span className={cn("inline-flex items-center gap-1.5 text-small font-medium", state === "idle" ? "text-fg-muted" : "text-fg")}>
              {live && !cancelArmed && <span aria-hidden className="size-1.5 shrink-0 rounded-full bg-danger" />}
              {state === "locked" && <LockIcon aria-hidden width={12} height={12} />}
              {word}
            </span>
          )}
          {hint && <span className="text-micro text-fg-muted">{hint}</span>}
        </span>
      )}

      {state === "locked" && onCancel && (
        <button type="button" onClick={() => { buzz(haptics); onCancel(); }}
          className="tap inline-flex h-8 cursor-pointer items-center rounded-control px-3 text-small font-medium text-fg-muted hover:bg-hover hover:text-fg">
          Cancel
        </button>
      )}

      {mode === "hold" && <span id={hintId} hidden>Hold to record and let go to finish. Or press Enter to start and Enter again to stop.</span>}
      <span role="status" className="sr-only">{said}</span>
    </span>
  );
}
```

## tattva/atoms/Money.tsx

```tsx
import { useId } from "react";
import type { ReactNode } from "react";
import { cn } from "../lib/cn";

export interface MoneyProps {
  value: number;
  /** ISO 4217 code. */
  currency?: string;
  locale?: string;
  /** Short form. INR uses lakh and crore (1.2 L, 3.4 Cr). Other currencies use K, M and so on. */
  compact?: boolean;
  /** Prefix + for a positive number. A negative number always shows the true minus sign. */
  showSign?: boolean;
  /** Adds an arrow and the up or down colour for a number that is not zero. The sign is always shown with it. */
  direction?: boolean;
  /** Marks the number as worked out by code. Pass a string to replace the default screen reader phrase. */
  calculated?: boolean | string;
  className?: string;
}

const MONO = "font-mono tabular-nums";

export interface MonoProps {
  children: ReactNode;
  className?: string;
}

/** Mono text with tabular figures, for a date or id that code worked out. */
export function Mono({ children, className }: MonoProps) {
  return <span className={cn(MONO, className)}>{children}</span>;
}

function format(value: number, currency: string, locale: string, compact: boolean): string {
  const abs = Math.abs(value);
  const full = new Intl.NumberFormat(locale, { style: "currency", currency, minimumFractionDigits: Number.isInteger(abs) ? 0 : 2, maximumFractionDigits: 2 });
  if (!compact) return full.format(abs);
  if (currency === "INR") {
    const symbol = full.formatToParts(0).find((p) => p.type === "currency")?.value ?? "₹";
    const one = new Intl.NumberFormat(locale, { maximumFractionDigits: 1 });
    if (abs >= 1e7) return `${symbol}${one.format(abs / 1e7)} Cr`;
    if (abs >= 1e5) return `${symbol}${one.format(abs / 1e5)} L`;
    return full.format(abs);
  }
  return new Intl.NumberFormat(locale, { style: "currency", currency, notation: "compact", maximumFractionDigits: 1 }).format(abs);
}

/** An amount that code calculated, in mono with tabular figures, so it reads as worked out and not guessed. */
export function Money({ value, currency = "INR", locale = "en-IN", compact = false, showSign = false, direction = false, calculated, className }: MoneyProps) {
  const noteId = useId();
  if (!Number.isFinite(value)) return <span className={cn(MONO, className)}>{"—"}</span>;
  const dir = direction && value !== 0 ? (value > 0 ? "up" : "down") : null;
  const sign = value < 0 ? "−" : (showSign || dir) && value > 0 ? "+" : "";
  let text: string;
  try { text = format(value, currency, locale, compact); } catch { text = `${Math.abs(value)} ${currency}`; }
  const note = calculated ? (typeof calculated === "string" ? calculated : "Calculated by code") : null;
  return (
    <span aria-describedby={note ? noteId : undefined}
      className={cn(MONO, dir === "up" && "text-up-fg", dir === "down" && "text-down-fg", note && "underline decoration-fg-subtle decoration-dotted underline-offset-4", className)}>
      {dir && <span className="sr-only">{dir === "up" ? "Up " : "Down "}</span>}
      {dir && <span aria-hidden className="mr-1 inline-block text-[0.7em] align-[0.1em]">{dir === "up" ? "▲" : "▼"}</span>}
      {sign}{text}
      {note && <span id={noteId} className="sr-only">. {note}</span>}
    </span>
  );
}
```

## tattva/atoms/NumberField.tsx

```tsx
import { useState } from "react";
import type { KeyboardEvent } from "react";
import { cn } from "../lib/cn";
import { PlusIcon } from "../lib/icons";

export interface NumberFieldProps {
  value: number | null;
  onChange: (value: number | null) => void;
  /** Accessible name. The buttons are named from it too. */
  label: string;
  min?: number;
  max?: number;
  /** Amount for the buttons and Arrow keys. */
  step?: number;
  /** Decimals shown and kept. Defaults to the decimals in step. */
  precision?: number;
  /** Text after the number, such as "lots". */
  unit?: string;
  invalid?: boolean;
  disabled?: boolean;
  /** md is 40px tall, sm is 32px. Both are 44px on coarse pointers. */
  size?: "sm" | "md";
  id?: string;
  "aria-describedby"?: string;
  /** Also marks the field invalid. Field passes this when it has an error. */
  "aria-invalid"?: boolean;
  required?: boolean;
  className?: string;
}

const decimals = (n: number) => (String(n).split(".")[1] ?? "").length;

/** A number input with minus and plus buttons. Typing is free; the value is checked on blur and Enter. */
export function NumberField({ value, onChange, label, min, max, step = 1, precision, unit, invalid, disabled, size = "md", id, required, className, "aria-invalid": ariaInvalid, ...rest }: NumberFieldProps) {
  const bad = invalid || ariaInvalid;
  const places = precision ?? decimals(step);
  const [draft, setDraft] = useState<string | null>(null);
  const clamp = (n: number) => Number(Math.min(max ?? Infinity, Math.max(min ?? -Infinity, n)).toFixed(places));
  const parse = (t: string): number | null | undefined => {
    const s = t.trim().replace(/,/g, "");
    if (s === "") return null;
    const n = Number(s);
    return Number.isFinite(n) ? clamp(n) : undefined; // undefined: not a number, keep the old value
  };
  const commit = () => {
    if (draft === null) return;
    const next = parse(draft);
    setDraft(null);
    if (next !== undefined && next !== value) onChange(next);
  };
  const current = draft !== null ? (parse(draft) ?? value) : value;
  const move = (dir: 1 | -1, mult = 1) => {
    setDraft(null);
    const next = current == null ? clamp(min ?? 0) : clamp(current + dir * step * mult);
    if (next !== value) onChange(next);
  };
  const jump = (n: number) => { setDraft(null); if (n !== value) onChange(clamp(n)); };
  const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
    const big = e.shiftKey ? 10 : 1;
    if (e.key === "ArrowUp") move(1, big);
    else if (e.key === "ArrowDown") move(-1, big);
    else if (e.key === "PageUp") move(1, 10);
    else if (e.key === "PageDown") move(-1, 10);
    else if (e.key === "Home" && min !== undefined) jump(min);
    else if (e.key === "End" && max !== undefined) jump(max);
    else if (e.key === "Enter") commit();
    else return;
    if (e.key !== "Enter") e.preventDefault();
  };
  const shown = draft ?? (value === null ? "" : value.toFixed(places));
  const atMin = current != null && min !== undefined && current <= min;
  const atMax = current != null && max !== undefined && current >= max;
  const btn = cn(
    "flex shrink-0 cursor-pointer items-center justify-center self-stretch rounded-lg px-2 text-fg-muted transition-colors duration-[var(--dur-fast)] hover:bg-hover hover:text-fg",
    "disabled:cursor-not-allowed disabled:text-disabled-fg disabled:hover:bg-transparent pointer-coarse:min-w-11",
  );
  return (
    <span className={cn(
      "inline-flex items-center gap-1 rounded-field border px-1 text-body leading-5 text-fg transition-colors duration-[var(--dur-fast)]",
      size === "sm" ? "min-h-8" : "min-h-10", "pointer-coarse:min-h-11",
      "has-[input:focus-visible]:outline-(length:--focus-width) has-[input:focus-visible]:outline-solid has-[input:focus-visible]:outline-offset-2 has-[input:focus-visible]:outline-[color:var(--focus-ring)]",
      bad ? "border-danger" : "border-line hover:border-line-strong",
      disabled ? "cursor-not-allowed border-disabled-line bg-disabled text-disabled-fg [&_input]:text-disabled-fg" : "bg-surface",
      className,
    )}>
      <button type="button" tabIndex={-1} aria-label={`Decrease ${label}`} disabled={disabled || atMin} onClick={() => move(-1)} className={btn}>
        <svg aria-hidden viewBox="0 0 24 24" width={16} height={16} fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round"><path d="M5 12h14" /></svg>
      </button>
      <input {...rest} id={id} type="text" inputMode={min !== undefined && min >= 0 ? "decimal" : "text"} role="spinbutton" autoComplete="off" aria-label={label}
        aria-valuenow={current ?? undefined} aria-valuemin={min} aria-valuemax={max}
        aria-valuetext={current == null ? undefined : `${current.toFixed(places)}${unit ? ` ${unit}` : ""}`}
        aria-invalid={bad || undefined} required={required} disabled={disabled} value={shown}
        onChange={(e) => setDraft(e.target.value)} onBlur={commit} onKeyDown={onKeyDown}
        className="w-16 min-w-0 flex-1 bg-transparent py-1 text-center tabular-nums outline-none focus-visible:outline-none disabled:cursor-not-allowed" />
      {unit && <span className="shrink-0 text-fg-muted">{unit}</span>}
      <button type="button" tabIndex={-1} aria-label={`Increase ${label}`} disabled={disabled || atMax} onClick={() => move(1)} className={btn}>
        <PlusIcon />
      </button>
    </span>
  );
}
```

## tattva/atoms/Pictogram.tsx

```tsx
import type { ReactNode } from "react";

export type PictogramName =
  | "chat" | "agent" | "document" | "image" | "code" | "data"
  | "search" | "shield" | "memory" | "voice" | "plan" | "tools";

export type PictogramSize = 32 | 48 | 64;

/** Gallery data: every pictogram name. */
export const pictogramNames: PictogramName[] = [
  "chat", "agent", "document", "image", "code", "data", "search", "shield", "memory", "voice", "plan", "tools",
];

export interface PictogramProps {
  name: PictogramName;
  /** Rendered size in px. */
  size?: PictogramSize;
  /** Accessible name. Leave it out for decorative use. */
  title?: string;
  className?: string;
}

/**
 * Drawn on a 32 x 32 grid with 1px of padding, so art stays inside 30 x 30.
 * Every shape is a stroke, never a filled outline. Color is currentColor.
 */
const art: Record<PictogramName, ReactNode> = {
  chat: <><path d="M5 5H27a2 2 0 0 1 2 2V20a2 2 0 0 1-2 2H15L9 28V22H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2Z" /><path d="M9 11H23M9 16H18" /></>,
  agent: <><circle cx="8" cy="8" r="4" /><rect x="20" y="20" width="8" height="8" rx="2" /><path d="M12 8H20a4 4 0 0 1 4 4V20" /></>,
  document: <><path d="M7 3H19L25 9V29H7Z" /><path d="M19 3V9H25M11 16H21M11 21H21M11 26H16" /></>,
  image: <><rect x="3" y="5" width="26" height="22" rx="3" /><circle cx="11" cy="12" r="3" /><path d="M3 23L11 17L17 22L22 18L29 24" /></>,
  code: <><path d="M10 9L3 16L10 23M22 9L29 16L22 23M18 6L14 26" /></>,
  data: <><path d="M3 28H29" /><rect x="5" y="16" width="5" height="12" rx="1" /><rect x="13" y="9" width="5" height="19" rx="1" /><rect x="21" y="4" width="5" height="24" rx="1" /></>,
  search: <><circle cx="14" cy="14" r="10" /><path d="M21 21L29 29" /></>,
  shield: <><path d="M16 3L27 7V15C27 22 22 27 16 29C10 27 5 22 5 15V7Z" /><path d="M11 16L15 20L22 12" /></>,
  memory: <><path d="M3 10L16 4L29 10L16 16Z" /><path d="M3 16L16 22L29 16M3 22L16 28L29 22" /></>,
  voice: <><rect x="11" y="3" width="10" height="16" rx="5" /><path d="M6 15a10 10 0 0 0 20 0M16 25V29M11 29H21" /></>,
  plan: <><path d="M4 8L7 11L12 5M4 17L7 20L12 14" /><path d="M17 8H28M17 17H28M17 26H25" /><circle cx="8" cy="26" r="3" /></>,
  tools: <><path d="M20 4a7 7 0 0 0-6 10L4 24a2.8 2.8 0 0 0 4 4L18 18a7 7 0 0 0 10-6L23 17L19 16L18 12L23 8Z" /></>,
};

/**
 * Single-color pictogram for headings, feature lists and small empty areas. It is a stroke drawing on a 32 grid
 * and takes its color from the surrounding text color.
 *
 * Stroke rule: the stroke is 1.5 units in the 32 unit artwork and scales with the artwork, so it renders
 * 1.5px at 32, 2.25px at 48 and 3px at 64. Proportions stay the same at every size.
 * Decorative by default. Pass `title` to give it an accessible name.
 */
export function Pictogram({ name, size = 32, title, className }: PictogramProps) {
  return (
    <svg
      viewBox="0 0 32 32"
      width={size}
      height={size}
      fill="none"
      stroke="currentColor"
      strokeWidth={1.5}
      strokeLinecap="round"
      strokeLinejoin="round"
      className={className}
      role={title ? "img" : undefined}
      aria-label={title}
      aria-hidden={title ? undefined : true}
      focusable="false"
    >
      {art[name]}
    </svg>
  );
}
```

## tattva/atoms/Popover.tsx

```tsx
import { createPortal } from "react-dom";
import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState, type KeyboardEvent, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { usePresence } from "../hooks/usePresence";

export interface PopoverTriggerApi {
  open: boolean;
  toggle: () => void;
  /** Spread onto the trigger element: aria-expanded, aria-haspopup, aria-controls. */
  triggerProps: { "aria-expanded": boolean; "aria-haspopup": "dialog" | "menu" | "listbox"; "aria-controls": string };
}

export interface PopoverProps {
  trigger: (api: PopoverTriggerApi) => ReactNode;
  /** Panel content. A function receives `close` so items can dismiss the panel. */
  children: ReactNode | ((api: { close: () => void }) => ReactNode);
  /** Accessible name for the panel. */
  label: string;
  align?: "start" | "end";
  side?: "bottom" | "top";
  role?: "dialog" | "menu" | "listbox";
  /** Controlled mode. Omit both to let the popover manage itself. */
  open?: boolean;
  onOpenChange?: (open: boolean) => void;
  /** Move focus into the panel when it opens. Set false when the opener manages focus itself. */
  autoFocus?: boolean;
  /** Open on the opposite side when the preferred side would overflow the viewport, and keep the panel inside the viewport edges. */
  flip?: boolean;
  /** Draw the panel on the page itself (fixed position, outside the parent) so a scrolling or clipped parent can never cut it off. Use it for menus inside lists. */
  portal?: boolean;
  /** On a phone (narrow touch screen) draw the panel as a bottom sheet with a backdrop, within thumb reach. On by default for menus. */
  phoneSheet?: boolean;
  className?: string;
  panelClassName?: string;
}

const PHONE = "(max-width: 639px) and (pointer: coarse)";
function usePhone(on: boolean) {
  const [phone, setPhone] = useState(() => on && typeof window !== "undefined" && window.matchMedia(PHONE).matches);
  useEffect(() => {
    if (!on) { setPhone(false); return; }
    const m = window.matchMedia(PHONE);
    const f = () => setPhone(m.matches);
    f(); m.addEventListener("change", f);
    return () => m.removeEventListener("change", f);
  }, [on]);
  return phone;
}

const GAP = 8;
const EDGE = 8;
const ITEM = '[role="menuitem"],[role="menuitemcheckbox"],[role="menuitemradio"],[role="option"]';
const CHECKED = '[role="menuitemradio"][aria-checked="true"],[role="option"][aria-selected="true"],[role="radio"][aria-checked="true"]';
const FOCUSABLE = 'a[href],button,input,select,textarea,summary,[contenteditable="true"],[tabindex]';

/**
 * Anchored panel for menus, pickers and explainers. Focus moves into the panel on open and returns to the
 * trigger on close. Menu and listbox panels add arrow-key movement, Home, End and Tab-to-leave. Placement
 * starts from CSS, then flips to the opposite side and clamps to the viewport edges when the panel would overflow.
 */
export function Popover({
  trigger, children, label, align = "start", side = "bottom", role = "dialog", open: controlled, onOpenChange,
  autoFocus = true, flip = true, portal = false, phoneSheet, className, panelClassName,
}: PopoverProps) {
  const [inner, setInner] = useState(false);
  const open = controlled ?? inner;
  const sheet = usePhone(phoneSheet ?? role === "menu");
  // Closing is a quick plain fade; the panel cannot be pressed while it goes.
  const { present, leaving } = usePresence(open);
  const root = useRef<HTMLDivElement>(null);
  const panel = useRef<HTMLDivElement>(null);
  const id = useId();
  const [place, setPlace] = useState({ flipped: false, dx: 0 });
  const [fixed, setFixed] = useState<{ top: number; left: number } | null>(null);
  const skipRestore = useRef(false);
  const wasOpen = useRef(false);
  const mounted = useRef(false);
  const set = (v: boolean) => { if (controlled === undefined) setInner(v); onOpenChange?.(v); };

  const triggerEl = useCallback(
    () => root.current?.querySelector<HTMLElement>(`[aria-controls="${CSS.escape(id)}"]`) ?? root.current?.querySelector<HTMLElement>("[aria-haspopup]") ?? null,
    [id],
  );
  const items = () => Array.from(panel.current?.querySelectorAll<HTMLElement>(ITEM) ?? []).filter((el) => !(el as HTMLButtonElement).disabled && el.getAttribute("aria-disabled") !== "true");

  useEffect(() => {
    if (!open) return;
    const onDoc = (e: MouseEvent) => {
      const t = e.target as Element | null;
      if (!root.current || !t || root.current.contains(t) || panel.current?.contains(t)) return;
      // A click on another focusable element keeps focus there. A click on empty space returns focus to the trigger.
      if (t.closest?.(FOCUSABLE)) skipRestore.current = true;
      set(false);
    };
    const onKey = (e: globalThis.KeyboardEvent) => { if (e.key === "Escape") set(false); };
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => { document.removeEventListener("mousedown", onDoc); document.removeEventListener("keydown", onKey); };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open]);

  // Placement: measure after mount, flip if the preferred side overflows, clamp horizontally.
  useLayoutEffect(() => {
    // Keep the last fixed spot while the panel fades out; it is measured again on the next open.
    if (!open) { setPlace((p) => (p.flipped || p.dx ? { flipped: false, dx: 0 } : p)); return; }
    if (sheet) return;
    if (portal) {
      // Fixed placement from the trigger's box, kept in step while the page scrolls or resizes.
      const place = () => {
        const p = panel.current; const r = root.current;
        if (!p || !r) return;
        const rect = r.getBoundingClientRect();
        const w = p.offsetWidth; const h = p.offsetHeight;
        const vw = document.documentElement.clientWidth; const vh = window.innerHeight;
        const below = vh - rect.bottom - GAP; const above = rect.top - GAP;
        const wantBelow = side === "bottom" ? !(below < h + GAP && above > below) : (above < h + GAP && below > above);
        const top = wantBelow ? rect.bottom + GAP : Math.max(EDGE, rect.top - GAP - h);
        const natural = align === "end" ? rect.right - w : rect.left;
        const left = w > vw - EDGE * 2 ? EDGE : Math.min(Math.max(natural, EDGE), vw - EDGE - w);
        setFixed((prev) => (prev && prev.top === Math.round(top) && prev.left === Math.round(left) ? prev : { top: Math.round(top), left: Math.round(left) }));
      };
      place();
      window.addEventListener("resize", place); window.addEventListener("scroll", place, true);
      return () => { window.removeEventListener("resize", place); window.removeEventListener("scroll", place, true); };
    }
    if (!flip) return;
    const measure = () => {
      const p = panel.current; const r = root.current;
      if (!p || !r) return;
      const rect = r.getBoundingClientRect();
      const w = p.offsetWidth; const h = p.offsetHeight;
      const vw = document.documentElement.clientWidth; const vh = window.innerHeight;
      const below = vh - rect.bottom - GAP; const above = rect.top - GAP;
      const room = side === "bottom" ? below : above;
      const other = side === "bottom" ? above : below;
      const flipped = room < h + GAP && other > room;
      const natural = align === "end" ? rect.right - w : rect.left;
      const clamped = w > vw - EDGE * 2 ? EDGE : Math.min(Math.max(natural, EDGE), vw - EDGE - w);
      const dx = Math.round(clamped - natural);
      setPlace((prev) => (prev.flipped === flipped && prev.dx === dx ? prev : { flipped, dx }));
    };
    measure();
    window.addEventListener("resize", measure);
    return () => window.removeEventListener("resize", measure);
  }, [open, flip, side, align, portal, sheet]);

  // A panel drawn on the page (portal) stays hidden until it has been placed, and a hidden element cannot take focus,
  // so focusing waits for the placement.
  const placed = !portal || sheet || !!fixed;
  const pendingFocus = useRef(false);
  // Focus in on open, back to the trigger on close.
  useEffect(() => {
    const first = !mounted.current;
    mounted.current = true;
    if (open) {
      if (!wasOpen.current) pendingFocus.current = autoFocus && !first;
      wasOpen.current = true;
      if (!pendingFocus.current || !placed) return;
      pendingFocus.current = false;
      const p = panel.current;
      if (!p) return;
      let target: HTMLElement | null | undefined;
      if (role === "dialog") {
        target = Array.from(p.querySelectorAll<HTMLElement>(FOCUSABLE)).find((el) => el.tabIndex >= 0 && !(el as HTMLButtonElement).disabled && el.getAttribute("aria-disabled") !== "true");
      } else {
        target = p.querySelector<HTMLElement>(CHECKED) ?? items()[0];
        if (target && ((target as HTMLButtonElement).disabled || target.getAttribute("aria-disabled") === "true")) target = items()[0];
      }
      (target ?? p).focus();
      return;
    }
    pendingFocus.current = false;
    if (!wasOpen.current) return;
    wasOpen.current = false;
    if (skipRestore.current) { skipRestore.current = false; return; }
    const a = document.activeElement;
    if (!a || a === document.body || root.current?.contains(a) || panel.current?.contains(a)) triggerEl()?.focus();
  }, [open, autoFocus, role, triggerEl, placed]);

  const onPanelKey = (e: KeyboardEvent<HTMLDivElement>) => {
    if (role === "dialog" || e.defaultPrevented) return;
    if (e.key === "Tab") {
      // Standard menu behavior: leave the menu and let focus continue from the trigger.
      skipRestore.current = true;
      triggerEl()?.focus();
      set(false);
      return;
    }
    const list = items();
    if (!list.length) return;
    const i = list.findIndex((el) => el === document.activeElement || el.contains(document.activeElement));
    let next = -1;
    if (e.key === "ArrowDown") next = i < 0 ? 0 : (i + 1) % list.length;
    else if (e.key === "ArrowUp") next = i < 0 ? list.length - 1 : (i - 1 + list.length) % list.length;
    else if (e.key === "Home") next = 0;
    else if (e.key === "End") next = list.length - 1;
    else return;
    e.preventDefault();
    list[next].focus();
  };

  const eff = place.flipped ? (side === "bottom" ? "top" : "bottom") : side;
  const style = place.dx ? (align === "end" ? { right: -place.dx } : { left: place.dx }) : undefined;

  const panelEl = present && (
    <div id={id} ref={panel} role={role} aria-label={label} tabIndex={-1} onKeyDown={onPanelKey}
      style={sheet ? undefined : portal ? { position: "fixed", top: fixed?.top ?? 0, left: fixed?.left ?? 0, visibility: fixed ? "visible" : "hidden" } : style}
      className={cn(sheet
        ? "fixed inset-x-0 bottom-0 z-(--z-menu) max-h-[80dvh] overflow-y-auto overscroll-contain rounded-t-overlay border border-b-0 border-line bg-raised p-2 pb-[max(0.5rem,var(--safe-bottom))] shadow-lg outline-none motion-safe:animate-sheet"
        : cn("min-w-56 rounded-overlay border border-line bg-raised p-1.5 shadow-lg outline-none animate-rise",
          portal ? "z-(--z-menu)" : cn("z-30 absolute", eff === "bottom" ? "top-full mt-2" : "bottom-full mb-2", align === "end" ? "right-0" : "left-0"), panelClassName), leaving && "pointer-events-none motion-safe:animate-fade-out!")}>
      {sheet && <span aria-hidden className="mx-auto mt-0.5 mb-1.5 block h-1 w-10 rounded-full bg-line-strong forced-colors:bg-[CanvasText]" />}
      {typeof children === "function" ? children({ close: () => set(false) }) : children}
    </div>
  );
  return (
    <div ref={root} className={cn("relative inline-block", className)}>
      {trigger({ open, toggle: () => set(!open), triggerProps: { "aria-expanded": open, "aria-haspopup": role, "aria-controls": id } })}
      {sheet && panelEl
        ? createPortal(<><div aria-hidden className={cn("fixed inset-0 z-(--z-menu) bg-(--scrim)", leaving ? "motion-safe:animate-fade-out" : "motion-safe:animate-fade")} />{panelEl}</>, root.current?.closest("dialog") ?? document.body)
        : portal && panelEl ? createPortal(panelEl, root.current?.closest("dialog") ?? document.body) : panelEl}
    </div>
  );
}
```

## tattva/atoms/Price.tsx

```tsx
import { useEffect, useRef, useState } from "react";
import { cn } from "../lib/cn";

export interface PriceProps {
  value: number;
  /** ISO 4217 code. */
  currency?: string;
  locale?: string;
  decimals?: number;
  /** A small arrow before the number. It shows the direction of the last change and stays until the next change. */
  showDirection?: boolean;
  /** Tints the background briefly when the value changes. Only with motion allowed. */
  flash?: boolean;
  /** Announces the new value in a hidden polite live region, at most once every 5 seconds. */
  announce?: boolean;
  /** Accessible name, such as "Reliance price". Read before the number. */
  label?: string;
  className?: string;
}

type Dir = "up" | "down" | null;

const FLASH_MS = 600;
const ANNOUNCE_MS = 5000;

/** Formats a price. Falls back to a plain number when the currency or locale is not valid. */
export function formatPrice(value: number, currency: string, locale: string, decimals: number): string {
  if (!Number.isFinite(value)) return "—";
  try {
    return new Intl.NumberFormat(locale, { style: "currency", currency, minimumFractionDigits: decimals, maximumFractionDigits: decimals }).format(value);
  } catch {
    return `${value.toFixed(decimals)} ${currency}`;
  }
}

/** A live number. It tints briefly and shows an arrow for the last change. The arrow is the cue, the tint is extra. */
export function Price({ value, currency = "INR", locale = "en-IN", decimals = 2, showDirection = true, flash = true, announce = false, label, className }: PriceProps) {
  const [prev, setPrev] = useState(value);
  const [dir, setDir] = useState<Dir>(null);
  const [flashing, setFlashing] = useState(false);
  const [tick, setTick] = useState(0);

  // Compare during render so the first paint of a new value already carries its direction.
  if (value !== prev) {
    setPrev(value);
    if (Number.isFinite(value) && Number.isFinite(prev) && value !== prev) {
      setDir(value > prev ? "up" : "down");
      setFlashing(true);
      setTick((t) => t + 1);
    }
  }

  useEffect(() => {
    if (!tick) return;
    const t = setTimeout(() => setFlashing(false), FLASH_MS);
    return () => clearTimeout(t);
  }, [tick]);

  const text = formatPrice(value, currency, locale, decimals);

  const [spoken, setSpoken] = useState("");
  const lastSpoken = useRef(0);
  useEffect(() => {
    if (!announce || !tick) return;
    const wait = Math.max(0, lastSpoken.current + ANNOUNCE_MS - Date.now());
    const t = setTimeout(() => { lastSpoken.current = Date.now(); setSpoken(text); }, wait);
    return () => clearTimeout(t);
  }, [announce, tick, text]);

  const arrow = showDirection && dir;
  return (
    <span className={cn(
      "inline-flex items-baseline gap-1 rounded-sm whitespace-nowrap font-mono tabular-nums transition-colors duration-[var(--dur-slow)]",
      flash && flashing && dir === "up" && "motion-safe:bg-up-soft",
      flash && flashing && dir === "down" && "motion-safe:bg-down-soft",
      className,
    )}>
      {label && <span className="sr-only">{label}: </span>}
      {arrow && (
        <svg aria-hidden viewBox="0 0 10 10" className={cn("size-[0.6em] shrink-0 self-center", arrow === "up" ? "text-up-fg" : "text-down-fg")} fill="currentColor">
          {arrow === "up" ? <path d="M5 1l4 7H1z" /> : <path d="M5 9L1 2h8z" />}
        </svg>
      )}
      {arrow && <span className="sr-only">{arrow === "up" ? "Up " : "Down "}</span>}
      <span>{text}</span>
      {announce && <span aria-live="polite" aria-atomic="true" className="sr-only">{spoken}</span>}
    </span>
  );
}
```

## tattva/atoms/PriceChange.tsx

```tsx
import { cn } from "../lib/cn";

export interface PriceChangeProps {
  /** The change as an absolute amount, such as 32.5. The sign sets the direction. */
  change: number;
  /** The change as a percent, such as 1.14. */
  percent?: number;
  currency?: string;
  locale?: string;
  decimals?: number;
  /** What to show: the amount and the percent, only the percent, or only the amount. */
  format?: "both" | "percent" | "amount";
  /** Shown instead of the numbers when both are zero. */
  flatLabel?: string;
  className?: string;
}

type Dir = "up" | "down" | "flat";

function direction(change: number, percent?: number): Dir {
  const n = change !== 0 && Number.isFinite(change) ? change : percent ?? 0;
  return n > 0 ? "up" : n < 0 ? "down" : "flat";
}

function num(abs: number, locale: string, decimals: number, style: "currency" | "percent" | "decimal", currency: string): string {
  try {
    if (style === "percent") return `${new Intl.NumberFormat(locale, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }).format(abs)}%`;
    return new Intl.NumberFormat(locale, { style: "currency", currency, minimumFractionDigits: decimals, maximumFractionDigits: decimals }).format(abs);
  } catch {
    return abs.toFixed(decimals);
  }
}

/** A signed change with an arrow. Direction is shown by the arrow and the sign, never by colour alone. */
export function PriceChange({ change, percent, currency = "INR", locale = "en-IN", decimals = 2, format = "both", flatLabel, className }: PriceChangeProps) {
  const amountOk = Number.isFinite(change);
  const percentOk = percent !== undefined && Number.isFinite(percent);
  const wantAmount = format !== "percent";
  const wantPercent = format !== "amount";
  const usable = (wantAmount && amountOk) || (wantPercent && percentOk);
  if (!usable) return <span className={cn("font-mono tabular-nums whitespace-nowrap", className)}>{"—"}</span>;

  const dir = direction(amountOk ? change : 0, percentOk ? percent : undefined);
  const sign = dir === "up" ? "+" : dir === "down" ? "−" : "";
  const parts: string[] = [];
  if (wantAmount && amountOk) parts.push(`${sign}${num(Math.abs(change), locale, decimals, "currency", currency)}`);
  if (wantPercent && percentOk) parts.push(`${sign}${num(Math.abs(percent), locale, decimals, "percent", currency)}`);
  const text = parts.length === 2 ? `${parts[0]} (${parts[1]})` : parts[0];
  const showFlat = dir === "flat" && flatLabel;

  return (
    <span className={cn("inline-flex items-center gap-1 whitespace-nowrap font-mono tabular-nums", dir === "up" && "text-up-fg", dir === "down" && "text-down-fg", dir === "flat" && "text-fg-muted", className)}>
      <span className="sr-only">{dir === "up" ? "Up " : dir === "down" ? "Down " : "Unchanged "}</span>
      <svg aria-hidden viewBox="0 0 10 10" className="size-[0.6em] shrink-0" fill={dir === "flat" ? "none" : "currentColor"} stroke={dir === "flat" ? "currentColor" : "none"} strokeWidth={1.5} strokeLinecap="round">
        {dir === "up" && <path d="M5 1l4 7H1z" />}
        {dir === "down" && <path d="M5 9L1 2h8z" />}
        {dir === "flat" && <path d="M1.5 5h7" />}
      </svg>
      <span>{showFlat ? flatLabel : text}</span>
    </span>
  );
}
```

## tattva/atoms/Reveal.tsx

```tsx
import { createElement, type CSSProperties, type HTMLAttributes, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { useReducedMotion } from "../hooks/useReducedMotion";

/** Stagger step in ms. Proposal. */
export const REVEAL_STEP = 40;
/** Items beyond this index share the last delay, so the whole group settles inside 400ms. Proposal. */
export const REVEAL_CAP = 5;

export interface RevealProps extends HTMLAttributes<HTMLElement> {
  /** rise moves 6px up while fading in (220ms). fade is opacity only (150ms). */
  variant?: "rise" | "fade";
  /** Position in a group. Delay is index x 40ms, held at the cap so the group stays under 400ms. */
  index?: number;
  as?: "div" | "li" | "section" | "article" | "span" | "p";
  children?: ReactNode;
}

/**
 * Enter animation wrapper using animate-rise or animate-fade.
 * Under reduced motion it renders its children with no animation.
 * Stagger numbers (40ms step, 5 items) are proposals.
 */
export function Reveal({ variant = "rise", index = 0, as = "div", className, style, children, ...rest }: RevealProps) {
  const reduced = useReducedMotion();
  const delay = Math.min(Math.max(index, 0), REVEAL_CAP - 1) * REVEAL_STEP;
  const s: CSSProperties | undefined = reduced ? style : { animationDelay: `${delay}ms`, ...style };
  return createElement(as, { ...rest, className: cn(!reduced && (variant === "rise" ? "animate-rise" : "animate-fade"), className), style: s }, children);
}
```

## tattva/atoms/ScrollEdge.tsx

```tsx
import type { CSSProperties, ReactNode } from "react";
import { cn } from "../lib/cn";

export interface ScrollEdgeProps {
  /** Which edge softens. */
  edge?: "top" | "bottom" | "both";
  /** Height of the fade zone, in px. */
  size?: number;
  /** The content that scrolls. */
  children?: ReactNode;
  /** Accessible name of the scrolling region. */
  label?: string;
  /** Classes for the outer box. Give it a height, or place it in a flex or grid cell that has one. */
  className?: string;
  /** Classes for the element that scrolls. */
  contentClassName?: string;
}

// Stops run from 0% (the content side) to 100% (the screen edge) because --se-dir points towards the edge.
// Each blur layer owns an overlapping slice, so the strength ramps up instead of showing as a band.
// Blur doubles: 0.75, 1.5, 3, 6, 12px.
const LAYERS = [
  "[backdrop-filter:blur(0.75px)] [-webkit-backdrop-filter:blur(0.75px)] [mask-image:linear-gradient(var(--se-dir),transparent_0%,black_20%,black_40%,transparent_60%)] [-webkit-mask-image:linear-gradient(var(--se-dir),transparent_0%,black_20%,black_40%,transparent_60%)]",
  "[backdrop-filter:blur(1.5px)] [-webkit-backdrop-filter:blur(1.5px)] [mask-image:linear-gradient(var(--se-dir),transparent_20%,black_40%,black_60%,transparent_80%)] [-webkit-mask-image:linear-gradient(var(--se-dir),transparent_20%,black_40%,black_60%,transparent_80%)]",
  "[backdrop-filter:blur(3px)] [-webkit-backdrop-filter:blur(3px)] [mask-image:linear-gradient(var(--se-dir),transparent_40%,black_60%,black_80%,transparent_100%)] [-webkit-mask-image:linear-gradient(var(--se-dir),transparent_40%,black_60%,black_80%,transparent_100%)]",
  "[backdrop-filter:blur(6px)] [-webkit-backdrop-filter:blur(6px)] [mask-image:linear-gradient(var(--se-dir),transparent_60%,black_80%,black_100%)] [-webkit-mask-image:linear-gradient(var(--se-dir),transparent_60%,black_80%,black_100%)]",
  "[backdrop-filter:blur(12px)] [-webkit-backdrop-filter:blur(12px)] [mask-image:linear-gradient(var(--se-dir),transparent_80%,black_100%)] [-webkit-mask-image:linear-gradient(var(--se-dir),transparent_80%,black_100%)]",
];

const TINT = "[background-image:linear-gradient(var(--se-dir),transparent_0%,color-mix(in_srgb,var(--bg)_35%,transparent)_55%,color-mix(in_srgb,var(--bg)_90%,transparent)_100%)]";
const SOLID = "[background-image:linear-gradient(var(--se-dir),transparent_0%,var(--bg)_100%)]";

function Edge({ side, size }: { side: "top" | "bottom"; size: number }) {
  const style = { height: size, "--se-dir": side === "top" ? "to top" : "to bottom" } as CSSProperties;
  return (
    <div aria-hidden style={style} className={cn("pointer-events-none absolute inset-x-0", side === "top" ? "top-0" : "bottom-0")}>
      {/* Plain gradient: where backdrop-filter is missing. */}
      <div className={cn("absolute inset-0 supports-[backdrop-filter]:hidden", SOLID)} />
      {/* Plain gradient: when the person asked for less transparency. */}
      <div className={cn("absolute inset-0 hidden [@media(prefers-reduced-transparency:reduce)]:block", SOLID)} />
      <div className="absolute inset-0 hidden supports-[backdrop-filter]:block">
        <div className="absolute inset-0 [@media(prefers-reduced-transparency:reduce)]:hidden">
          {LAYERS.map((l, i) => <div key={i} className={cn("pointer-events-none absolute inset-0", l)} />)}
          <div className={cn("absolute inset-0", TINT)} />
        </div>
      </div>
    </div>
  );
}

/**
 * A scrolling area whose top and/or bottom edge softens progressively, so content passing under it fades out
 * instead of meeting a hard panel line. The edge layers are decorative, ignore the pointer and never take focus.
 * The scroller is a focusable region, so keyboard people can scroll it with the arrow keys.
 */
export function ScrollEdge({ edge = "both", size = 48, children, label = "Scrollable content", className, contentClassName }: ScrollEdgeProps) {
  return (
    <div className={cn("relative min-h-0 overflow-hidden", className)}>
      <div role="region" aria-label={label} tabIndex={0}
        className={cn("h-full overflow-y-auto overscroll-contain focus-visible:[outline-offset:-2px]", contentClassName)}>
        {children}
      </div>
      {(edge === "top" || edge === "both") && <Edge side="top" size={size} />}
      {(edge === "bottom" || edge === "both") && <Edge side="bottom" size={size} />}
    </div>
  );
}
```

## tattva/atoms/SectionLabel.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";

export interface SectionLabelProps {
  children: ReactNode;
  action?: ReactNode;
  /** Element to render. A label is plain text by default. Use a heading level only when it should appear in the page outline. */
  as?: "p" | "h2" | "h3" | "h4";
  className?: string;
}

/** Small uppercase section label, used in trays, rails and lists. */
export function SectionLabel({ children, action, as: Tag = "p", className }: SectionLabelProps) {
  return (
    <div className={cn("flex items-center justify-between gap-2 px-1", className)}>
      <Tag className="font-sans! text-nano font-normal uppercase tracking-caps text-fg-muted">{children}</Tag>
      {action && <span className="text-small leading-4 text-fg-muted">{action}</span>}
    </div>
  );
}
```

## tattva/atoms/SegmentedControl.tsx

```tsx
import { useRef, type KeyboardEvent } from "react";
import { cn } from "../lib/cn";
import { useIndicator } from "../hooks/useIndicator";

export interface SegmentOption<T extends string> { value: T; label: string; description?: string }

export interface SegmentedControlProps<T extends string> {
  options: SegmentOption<T>[];
  value: T;
  onChange: (value: T) => void;
  /** Accessible name for the group. */
  label: string;
  size?: "sm" | "md";
  className?: string;
}

/** Single-choice pill group. Arrow keys move and select, Home and End jump, and only the selected option is a tab stop. */
export function SegmentedControl<T extends string>({ options, value, onChange, label, size = "md", className }: SegmentedControlProps<T>) {
  const refs = useRef<(HTMLButtonElement | null)[]>([]);
  const group = useRef<HTMLDivElement>(null);
  const mark = useIndicator(group, '[role="radio"][aria-checked="true"]', value);
  // If value matches nothing, keep the first option reachable so the group is never skipped by Tab.
  const hasMatch = options.some((o) => o.value === value);
  const move = (i: number, e: KeyboardEvent) => {
    const n = options.length;
    let next = i;
    if (e.key === "ArrowRight" || e.key === "ArrowDown") next = (i + 1) % n;
    else if (e.key === "ArrowLeft" || e.key === "ArrowUp") next = (i - 1 + n) % n;
    else if (e.key === "Home") next = 0;
    else if (e.key === "End") next = n - 1;
    else return;
    e.preventDefault();
    onChange(options[next].value);
    refs.current[next]?.focus();
  };
  return (
    <div ref={group} role="radiogroup" aria-label={label} className={cn("relative inline-flex max-w-full overflow-x-auto rounded-control border border-line bg-sunken p-0.5 [scrollbar-width:none]", className)}>
      {mark && <span aria-hidden style={mark} className="pointer-events-none rounded-control bg-surface shadow-sm forced-colors:hidden" />}
      {options.map((o, i) => {
        const on = o.value === value;
        return (
          <button key={o.value} ref={(el) => { refs.current[i] = el; }} type="button" role="radio" aria-checked={on} title={o.description}
            tabIndex={on || (!hasMatch && i === 0) ? 0 : -1} onClick={() => onChange(o.value)} onKeyDown={(e) => move(i, e)}
            className={cn("relative cursor-pointer rounded-control font-medium whitespace-nowrap transition-colors duration-(--dur-fast)", size === "sm" ? "px-2.5 py-1 text-micro" : "px-3.5 py-1.5 text-small leading-4",
              on ? cn(!mark && "bg-surface shadow-sm", "text-fg forced-colors:border forced-colors:border-[Highlight]") : "text-fg-muted hover:text-fg")}>
            {o.label}
          </button>
        );
      })}
    </div>
  );
}
```

## tattva/atoms/Select.tsx

```tsx
import type { ComponentProps } from "react";
import { cn } from "../lib/cn";

export interface SelectOption {
  value: string;
  label: string;
  disabled?: boolean;
}

export interface SelectProps extends Omit<ComponentProps<"select">, "size"> {
  /** The choices. Use this or pass <option> elements as children. */
  options?: SelectOption[];
  /** Shown while value is empty. It is a disabled first option, so it cannot be picked again. */
  placeholder?: string;
  invalid?: boolean;
  /** md is 40px tall, sm is 32px. Both are 44px on coarse pointers. */
  size?: "sm" | "md";
  /** Classes for the wrapper around the select. */
  className?: string;
}

/** A native select with a drawn chevron. The menu stays the browser's own, so it works with every screen reader and phone. */
export function Select({ options, placeholder, invalid, size = "md", className, children, value, defaultValue, ...rest }: SelectProps) {
  const empty = value === "" || (value === undefined && (defaultValue === undefined || defaultValue === ""));
  return (
    <span className={cn("relative block w-full", className)}>
      <select {...rest} value={value} defaultValue={value === undefined ? (defaultValue ?? (placeholder ? "" : undefined)) : undefined}
        aria-invalid={invalid || rest["aria-invalid"] || undefined}
        className={cn(
          "w-full appearance-none rounded-field border bg-surface pr-9 pl-3 text-body leading-5 transition-colors duration-[var(--dur-fast)] cursor-pointer",
          size === "sm" ? "h-8" : "h-10", "pointer-coarse:h-11",
          empty && placeholder ? "text-fg-subtle" : "text-fg",
          invalid || rest["aria-invalid"] ? "border-danger" : "border-line hover:border-line-strong",
          "disabled:cursor-not-allowed disabled:border-disabled-line disabled:bg-disabled disabled:text-disabled-fg",
        )}>
        {placeholder && <option value="" disabled hidden>{placeholder}</option>}
        {options?.map((o) => <option key={o.value} value={o.value} disabled={o.disabled} className="text-fg">{o.label}</option>)}
        {children}
      </select>
      <svg aria-hidden viewBox="0 0 24 24" width={16} height={16} fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round"
        className="pointer-events-none absolute top-1/2 right-3 -translate-y-1/2 text-fg-muted"><path d="M6 9l6 6 6-6" /></svg>
    </span>
  );
}
```

## tattva/atoms/ShimmerText.tsx

```tsx
import { cn } from "../lib/cn";

/** Shimmering status text such as "Searching the web…". */
export function ShimmerText({ children, className }: { children: string; className?: string }) {
  return (
    <span className={cn("bg-[linear-gradient(90deg,var(--fg-subtle)_40%,var(--fg)_50%,var(--fg-subtle)_60%)] bg-[length:200%_100%] bg-clip-text text-transparent animate-shimmer text-body leading-5", className)}>
      {children}
    </span>
  );
}
```

## tattva/atoms/Skeleton.tsx

```tsx
import type { HTMLAttributes } from "react";
import { cn } from "../lib/cn";

export function Skeleton({ className, ...rest }: HTMLAttributes<HTMLDivElement>) {
  return (
    <div
      aria-hidden
      className={cn("rounded-md bg-[linear-gradient(90deg,var(--surface-sunken)_25%,var(--border)_50%,var(--surface-sunken)_75%)] bg-[length:200%_100%] animate-shimmer", className)}
      {...rest}
    />
  );
}
```

## tattva/atoms/Slider.tsx

```tsx
import { useId } from "react";
import type { CSSProperties } from "react";
import { cn } from "../lib/cn";

export interface SliderProps {
  value: number;
  onChange: (value: number) => void;
  min?: number;
  max?: number;
  step?: number;
  /** Accessible name. */
  label: string;
  /** Shows the current value as text to the right. */
  showValue?: boolean;
  /** Turns the number into text, for example "40%". Used for the visible value and for aria-valuetext. */
  formatValue?: (value: number) => string;
  disabled?: boolean;
  id?: string;
  className?: string;
}

// Class strings are written out in full so Tailwind can see them. Both engines get the same drawing:
// the track paints the filled part up to --fill, and the thumb is a 20px ring.
const webkit = "[&::-webkit-slider-runnable-track]:h-1.5 [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:border-0 [&::-webkit-slider-runnable-track]:bg-[linear-gradient(to_right,var(--color-accent)_var(--fill),var(--color-line-strong)_var(--fill))] [&::-webkit-slider-runnable-track]:forced-colors:bg-none [&::-webkit-slider-runnable-track]:forced-colors:bg-[ButtonText] [&::-webkit-slider-thumb]:-mt-1.75 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-5 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-accent [&::-webkit-slider-thumb]:bg-surface [&::-webkit-slider-thumb]:shadow-sm [&::-webkit-slider-thumb]:forced-colors:border-[ButtonText] [&::-webkit-slider-thumb]:forced-colors:bg-[Canvas]";
const moz = "[&::-moz-range-track]:h-1.5 [&::-moz-range-track]:rounded-full [&::-moz-range-track]:border-0 [&::-moz-range-track]:bg-[linear-gradient(to_right,var(--color-accent)_var(--fill),var(--color-line-strong)_var(--fill))] [&::-moz-range-track]:forced-colors:bg-none [&::-moz-range-track]:forced-colors:bg-[ButtonText] [&::-moz-range-thumb]:size-5 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-accent [&::-moz-range-thumb]:bg-surface [&::-moz-range-thumb]:shadow-sm [&::-moz-range-thumb]:forced-colors:border-[ButtonText] [&::-moz-range-thumb]:forced-colors:bg-[Canvas]";

/** Controlled single-thumb slider on a native range input. Arrow keys, Home, End and Page keys come from the browser. */
export function Slider({ value, onChange, min = 0, max = 100, step = 1, label, showValue, formatValue, disabled, id, className }: SliderProps) {
  const auto = useId();
  const inputId = id ?? auto;
  const text = formatValue ? formatValue(value) : String(value);
  const fill = max > min ? Math.min(100, Math.max(0, ((value - min) / (max - min)) * 100)) : 0;
  return (
    <div className={cn("flex items-center gap-3", disabled && "opacity-(--disabled-opacity)", className)}>
      <input id={inputId} type="range" min={min} max={max} step={step} value={value} disabled={disabled}
        aria-label={label} aria-valuetext={formatValue ? text : undefined}
        onChange={(e) => onChange(Number(e.target.value))}
        style={{ "--fill": `${fill}%` } as CSSProperties}
        className={cn("m-0 h-5 min-w-0 flex-1 cursor-pointer appearance-none bg-transparent pointer-coarse:h-11 disabled:cursor-not-allowed",
          webkit, moz)} />
      {showValue && <output htmlFor={inputId} className="min-w-[3ch] text-right text-body leading-5 text-fg-muted tabular-nums">{text}</output>}
    </div>
  );
}
```

## tattva/atoms/Spinner.tsx

```tsx
export function Spinner({ size = 16, label = "Loading" }: { size?: number; label?: string }) {
  return (
    <svg role="status" aria-label={label} width={size} height={size} viewBox="0 0 24 24" className="animate-spin text-fg-muted">
      <circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" strokeOpacity="0.2" strokeWidth="3" />
      <path d="M21 12a9 9 0 0 0-9-9" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" />
    </svg>
  );
}
```

## tattva/atoms/Stack.tsx

```tsx
import { createElement, type CSSProperties, type HTMLAttributes, type ReactNode } from "react";
import { cn } from "../lib/cn";

/** Step on the 4px grid. 1 is 4px, 8 is 32px. Multiplied by --density. */
export type Space = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
export type StackTag = "div" | "section" | "article" | "ul" | "ol" | "nav" | "header" | "footer" | "main" | "aside";

/** CSS length for a space step, scaled by the density token. */
export const spaceValue = (n: Space) => `calc(var(--density) * ${n * 4}px)`;

export interface StackProps extends HTMLAttributes<HTMLElement> {
  /** Gap step from 1 to 8 on the 4px grid, scaled by --density. */
  gap?: Space;
  /** Element to render. Use ul or ol with li children for lists. */
  as?: StackTag;
  children?: ReactNode;
}

/** Vertical flow with one gap between children. Prefer it over margins between siblings. */
export function Stack({ gap = 4, as = "div", className, style, children, ...rest }: StackProps) {
  const s: CSSProperties = { gap: spaceValue(gap), ...style };
  // Safari stops calling a list a list once its markers are removed, so say it is one.
  const isList = as === "ul" || as === "ol";
  return createElement(as, { ...(isList ? { role: "list" } : {}), ...rest, className: cn("flex flex-col", isList ? "list-none p-0 m-0" : "", className), style: s }, children);
}
```

## tattva/atoms/StatusLine.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";

export interface StatusLineProps {
  /** The pieces of the line, in order. A middle dot is drawn between them. */
  items: ReactNode[];
  /** Index of the item that is the current state. It keeps the normal ink and turns medium weight. The others are muted. */
  current?: number;
  /** Accessible name of the group. */
  label?: string;
  className?: string;
}

/** One quiet line for the top of a screen, such as "Case · Flipkart · ₹2,340 · Waiting". It truncates instead of wrapping. */
export function StatusLine({ items, current, label = "Status", className }: StatusLineProps) {
  return (
    <div role="group" aria-label={label} className={cn("min-w-0 truncate text-body leading-6 text-fg", className)}>
      <span className="sr-only">Status: </span>
      {items.map((item, i) => (
        <span key={i}>
          {i > 0 && <span aria-hidden className="mx-1.5 text-fg-subtle">{"·"}</span>}
          <span aria-current={i === current ? "true" : undefined} className={current === undefined ? undefined : i === current ? "font-medium text-fg" : "text-fg-muted"}>{item}</span>
        </span>
      ))}
    </div>
  );
}
```

## tattva/atoms/StatusTicker.tsx

```tsx
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { cn } from "../lib/cn";
import { useReducedMotion } from "../hooks/useReducedMotion";
import { ShimmerText } from "./ShimmerText";

export interface StatusTickerProps {
  /** The step the AI run is on right now, such as "Reading 3 files". The app passes the real step; when done, pass the final line, such as "Done in 12s". */
  current: string;
  /** Every step text the run may show. Used only to reserve the width of the widest one, so the box never grows. */
  steps?: string[];
  /** The run has finished. The line stops shimmering, aria-busy is removed and the line is announced at once. */
  done?: boolean;
  /** Accessible name of the line, such as "Assistant status". */
  label?: string;
  /** A text whose width the box keeps from the start, such as the longest step you expect. */
  reserve?: string;
  className?: string;
}

/** Screen readers hear a new step at most this often. The latest step wins. */
const ANNOUNCE_GAP = 2000;

/** One line that shows the real current step of an AI run. It keeps the width of the widest text it has shown, so nothing beside it moves. */
export function StatusTicker({ current, steps, done = false, label = "AI status", reserve, className }: StatusTickerProps) {
  const reduced = useReducedMotion();
  const [prev, setPrev] = useState<{ text: string; key: number } | null>(null);
  const [shown, setShown] = useState({ text: current, key: 0 });
  const [minWidth, setMinWidth] = useState(0);
  const [spoken, setSpoken] = useState(current);
  const textRef = useRef<HTMLSpanElement>(null);
  const lastSpoke = useRef(0);
  const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);

  // Swap the line during render so the old text and the new one are drawn in the same frame.
  if (current !== shown.text) {
    setPrev(reduced ? null : { text: shown.text, key: shown.key });
    setShown({ text: current, key: shown.key + 1 });
  }

  // Remember the widest text shown so far; the box never shrinks below it.
  useLayoutEffect(() => {
    const w = textRef.current?.getBoundingClientRect().width ?? 0;
    setMinWidth((m) => (w > m ? w : m));
  }, [shown.key]);

  // Throttle the live region: speak at once if the last announcement was long enough ago, else speak the latest step when the gap ends.
  useEffect(() => {
    clearTimeout(timer.current);
    const speak = () => { lastSpoke.current = Date.now(); setSpoken(current); };
    const wait = ANNOUNCE_GAP - (Date.now() - lastSpoke.current);
    if (done || wait <= 0) speak();
    else timer.current = setTimeout(speak, wait);
    return () => clearTimeout(timer.current);
  }, [current, done]);

  const ghosts = [reserve, ...(steps ?? [])].filter((s): s is string => !!s);
  const working = !done && !reduced;

  return (
    <span className={cn("inline-flex max-w-full min-w-0 align-middle", className)}>
      <span role="group" aria-label={label} aria-busy={!done} style={{ minWidth: `min(${minWidth}px, 100%)` }} className="relative inline-grid min-w-0 max-w-full text-body leading-6 text-fg-muted">
        {ghosts.map((g, i) => (
          <span key={i} aria-hidden className="invisible col-start-1 row-start-1 truncate">{g}</span>
        ))}
        {prev && (
          <span key={`p${prev.key}`} aria-hidden onAnimationEnd={() => setPrev(null)}
            className="pointer-events-none absolute inset-0 truncate animate-fade-out">{prev.text}</span>
        )}
        <span key={shown.key} ref={textRef} className={cn("col-start-1 row-start-1 w-fit max-w-full truncate", !reduced && shown.key > 0 && "animate-blur-in", done && "text-fg")}>
          {working ? <ShimmerText className="leading-6">{shown.text}</ShimmerText> : shown.text}
        </span>
      </span>
      <span role="status" aria-live="polite" className="sr-only">{spoken}</span>
    </span>
  );
}
```

## tattva/atoms/Switch.tsx

```tsx
import { useId } from "react";
import type { ReactNode } from "react";
import { cn } from "../lib/cn";

export interface SwitchProps {
  checked: boolean;
  onChange: (v: boolean) => void;
  /** Accessible name. Used as aria-label only when there are no children. */
  label: string;
  /** Visible label beside the switch. Clicking it toggles the switch, and it becomes the accessible name. */
  children?: ReactNode;
  /** Which side of the switch the children sit on. */
  labelPosition?: "start" | "end";
  /** Blocks toggling and dims the switch. */
  disabled?: boolean;
  /** Extra context announced after the name through aria-describedby. Rendered as screen reader text. */
  description?: string;
  id?: string;
  className?: string;
}

/** Controlled on/off toggle. The track is 20px tall. The button around it is at least 24px tall, and 44px on coarse pointers. */
export function Switch({ checked, onChange, label, children, labelPosition = "end", disabled, description, id, className }: SwitchProps) {
  const descId = useId();
  const textId = useId();
  const hasText = children != null && children !== false && children !== "";
  const control = (
    <button type="button" role="switch" id={id} aria-checked={checked} aria-label={hasText ? undefined : label} aria-labelledby={hasText ? textId : undefined} disabled={disabled}
      aria-describedby={description ? descId : undefined} onClick={() => { if (!disabled) onChange(!checked); }}
      className={cn("inline-flex min-h-6 min-w-9 shrink-0 cursor-pointer items-center justify-center rounded-full disabled:cursor-not-allowed pointer-coarse:min-h-11 pointer-coarse:min-w-11", !hasText && className)}>
      <span aria-hidden className={cn("relative block h-5 w-9 rounded-full border border-transparent forced-colors:border-[CanvasText] transition-colors duration-[var(--dur-fast)]", disabled ? "bg-line" : checked ? "bg-accent" : "bg-line-strong")}>
        <span className={cn("absolute top-px left-px size-4 rounded-full bg-thumb shadow-sm forced-colors:bg-[CanvasText] transition-transform duration-[var(--dur-fast)]", checked && "translate-x-4")} />
      </span>
      {description && <span id={descId} className="sr-only">{description}</span>}
    </button>
  );
  if (!hasText) return control;
  const text = <span id={textId} className={cn("text-body leading-5", disabled ? "text-disabled-fg" : "text-fg")}>{children}</span>;
  return (
    <label className={cn("inline-flex items-center gap-3", disabled ? "cursor-not-allowed" : "cursor-pointer", className)}>
      {labelPosition === "start" && text}
      {control}
      {labelPosition === "end" && text}
    </label>
  );
}
```

## tattva/atoms/TextField.tsx

```tsx
import type { ComponentProps, ReactNode } from "react";
import { cn } from "../lib/cn";

export interface TextFieldProps extends Omit<ComponentProps<"input">, "size" | "type"> {
  type?: "text" | "email" | "password" | "search" | "url" | "tel";
  /** Marks the field as invalid: danger border and aria-invalid. Say what is wrong in text too (see Field). */
  invalid?: boolean;
  /** Content before the text, such as an icon. Decorative: it is not read out. */
  leading?: ReactNode;
  /** Content after the text, such as a unit like "₹". */
  trailing?: ReactNode;
  /** md is 40px tall, sm is 32px. Both are 44px on coarse pointers. */
  size?: "sm" | "md";
  /** Classes for the outer box, so width and margins apply to it. */
  className?: string;
}

/**
 * A single-line text input in a bordered box. The focus ring is drawn on the box (not the bare input) so it wraps the
 * icon and unit slots too. It uses the global focus colour and shows for keyboard focus only.
 */
export function TextField({ type = "text", invalid, leading, trailing, size = "md", className, disabled, readOnly, ...rest }: TextFieldProps) {
  return (
    <span className={cn(
      "flex w-full items-center gap-2 rounded-field border px-3 text-body leading-5 text-fg transition-colors duration-[var(--dur-fast)]",
      size === "sm" ? "min-h-8" : "min-h-10", "pointer-coarse:min-h-11",
      "has-[input:focus-visible]:outline-(length:--focus-width) has-[input:focus-visible]:outline-solid has-[input:focus-visible]:outline-offset-2 has-[input:focus-visible]:outline-[color:var(--focus-ring)]",
      invalid || rest["aria-invalid"] ? "border-danger" : "border-line hover:border-line-strong",
      disabled ? "cursor-not-allowed border-disabled-line bg-disabled text-disabled-fg [&_input]:text-disabled-fg" : readOnly ? "bg-sunken" : "bg-surface",
      className,
    )}>
      {leading && <span aria-hidden className="flex shrink-0 text-fg-muted">{leading}</span>}
      <input {...rest} type={type} disabled={disabled} readOnly={readOnly} aria-invalid={invalid || rest["aria-invalid"] || undefined}
        className="min-w-0 flex-1 bg-transparent py-1 text-fg outline-none placeholder:text-fg-subtle focus-visible:outline-none disabled:cursor-not-allowed" />
      {trailing && <span className="flex shrink-0 text-fg-muted">{trailing}</span>}
    </span>
  );
}
```

## tattva/atoms/Tooltip.tsx

```tsx
import { cloneElement, useEffect, useId, useRef, useState, type ReactElement } from "react";
import { cn } from "../lib/cn";

export interface TooltipProps {
  /** Plain text only. Never put essential information or anything clickable here. */
  content: string;
  /** The trigger. It must be focusable (a button or a link) and accept aria-describedby. */
  children: ReactElement<{ "aria-describedby"?: string }>;
  side?: "top" | "bottom" | "left" | "right";
  /** Milliseconds to wait before a hover shows it. Focus shows it at once. */
  delay?: number;
  className?: string;
}

const place = {
  top: "bottom-full left-1/2 -translate-x-1/2 pb-1.5",
  bottom: "top-full left-1/2 -translate-x-1/2 pt-1.5",
  left: "right-full top-1/2 -translate-y-1/2 pr-1.5",
  right: "left-full top-1/2 -translate-y-1/2 pl-1.5",
};

/** A short text label for a control, shown on hover and on keyboard focus. Touch users never see it from a tap. */
export function Tooltip({ content, children, side = "top", delay = 400, className }: TooltipProps) {
  const id = useId();
  const [open, setOpen] = useState(false);
  const timer = useRef<number | undefined>(undefined);
  const clear = () => window.clearTimeout(timer.current);
  useEffect(() => clear, []);

  useEffect(() => {
    if (!open) return;
    // Escape closes the tooltip and leaves focus where it is. While focus is inside, the wrapper below also stops the key
    // so it does not close a dialog or page around the tooltip; this listener covers a tooltip opened by hover.
    const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [open]);

  const enter = () => {
    // Touch emulates mouseenter on tap; coarse pointers must not depend on hover.
    if (window.matchMedia("(pointer: coarse)").matches) return;
    clear();
    timer.current = window.setTimeout(() => setOpen(true), delay);
  };
  const hide = () => { clear(); setOpen(false); };
  const existing = children.props["aria-describedby"];

  return (
    <span className="relative inline-flex" onMouseEnter={enter} onMouseLeave={hide} onFocus={() => { clear(); setOpen(true); }} onBlur={hide}
      onKeyDown={(e) => { if (e.key === "Escape" && open) { e.stopPropagation(); setOpen(false); } }}>
      {cloneElement(children, { "aria-describedby": existing ? `${existing} ${id}` : id })}
      {/* The outer span carries transparent padding as the gap, so the pointer can move onto the tip without it closing. */}
      <span hidden={!open} className={cn("absolute z-40", place[side])}>
        <span id={id} role="tooltip"
          className={cn("block w-max max-w-64 rounded-md bg-inverse px-2 py-1 text-small leading-4 text-inverse-fg shadow-md motion-safe:animate-fade forced-colors:border forced-colors:border-[CanvasText]", className)}>
          {content}
        </span>
      </span>
    </span>
  );
}
```

## tattva/atoms/TypingIndicator.tsx

```tsx
/** Three-dot "assistant is composing" indicator, shown before the first token arrives. */
export function TypingIndicator({ label = "Assistant is thinking" }: { label?: string }) {
  return (
    <span role="status" aria-label={label} className="inline-flex items-center gap-1 py-2">
      {[0, 1, 2].map((i) => (
        <span key={i} className="size-1.5 rounded-full bg-fg-subtle animate-pulse-dot" style={{ animationDelay: `${i * 160}ms` }} />
      ))}
    </span>
  );
}
```

## tattva/atoms/Waveform.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import type { HTMLAttributes, KeyboardEvent, PointerEvent } from "react";
import { cn } from "../lib/cn";
import { useReducedMotion } from "../hooks/useReducedMotion";

/** What a stretch of the clip is. `ai` = made or changed by AI, `person` = made by a person, `stale` = needs a person to regenerate or check, `selected` = the person's selection, `highlight` = a neutral mark such as the line being read. */
export type WaveformRegionKind = "ai" | "person" | "stale" | "selected" | "highlight";

export interface WaveformRegion {
  /** Start, in seconds. */
  start: number;
  /** End, in seconds. */
  end: number;
  kind: WaveformRegionKind;
  /** Short words for the stretch, such as "Chorus rewritten by AI". Shown under the waveform and read out. */
  label?: string;
}

export interface WaveformMarker {
  /** Where it sits, in seconds. */
  at: number;
  /** What happened, such as "Tool call: look up order". */
  label: string;
  /** `review` is drawn as an amber diamond and only for a moment a person must check. */
  tone?: "neutral" | "review";
}

export type WaveformState = "drawing" | "ready" | "error";

export interface WaveformProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
  /** Loudness per slice, each 0 to 1. `null` while the peaks are still being worked out (the drawing state). In live mode, the newest level is last. */
  peaks: number[] | null;
  /** Length of the clip in seconds. In live mode, the seconds the visible window covers. */
  duration: number;
  /** Playhead, in seconds. Everything before it is drawn as played. */
  position?: number;
  /** Makes it a seek control (one slider). Leave out for a drawing only, which is hidden from screen readers. */
  onSeek?: (seconds: number) => void;
  /** Name of the seek slider, such as "Seek, call with Maya Okafor". Required with onSeek. */
  label?: string;
  /** Marked stretches: AI-made, person-made, stale, selected or highlighted. */
  regions?: WaveformRegion[];
  /** Moments on the timeline, such as a tool call or an interruption. */
  markers?: WaveformMarker[];
  /** From this second to the end the clip is still being made. Drawn as a dashed track. */
  pendingFrom?: number;
  /** `static` draws a whole clip. `live` draws the most recent levels from a microphone or a stream, newest on the right, with no playhead or seeking. */
  mode?: "static" | "live";
  /** In live mode, how many bars to show. */
  liveBars?: number;
  /** Shows a flat line and this message instead of the peaks. Seeking still works on the plain bar. */
  error?: string;
  /** Draws a plain progress bar instead of peaks. It also switches to this when the waveform is narrower than 256px. */
  simple?: boolean;
  /** Height of the drawing. The hit area is at least 44px tall when it is a seek control. */
  height?: 32 | 48 | 72;
  /** Clock text for the time bubble shown while dragging. Defaults to formatMediaTime. */
  formatTime?: (seconds: number) => string;
  className?: string;
}

const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));

/** "1:12", or "1:02:05" past an hour. Use it for visible times. */
export function formatMediaTime(seconds: number): string {
  const t = Math.max(0, Math.floor(seconds || 0));
  const h = Math.floor(t / 3600);
  const m = Math.floor((t % 3600) / 60);
  const s = String(t % 60).padStart(2, "0");
  return h ? `${h}:${String(m).padStart(2, "0")}:${s}` : `${m}:${s}`;
}

/** "1 minute 12 seconds", in words for screen readers. */
export function speakMediaTime(seconds: number): string {
  const t = Math.max(0, Math.round(seconds || 0));
  const h = Math.floor(t / 3600);
  const m = Math.floor((t % 3600) / 60);
  const s = t % 60;
  const unit = (n: number, w: string) => `${n} ${w}${n === 1 ? "" : "s"}`;
  const parts = [h && unit(h, "hour"), m && unit(m, "minute"), (s || (!h && !m)) && unit(s, "second")].filter(Boolean);
  return parts.join(" ");
}

const HEIGHT = { 32: "h-8", 48: "h-12", 72: "h-18" } as const;
const KIND_WORD: Record<WaveformRegionKind, string> = { ai: "Made by AI", person: "Made by a person", stale: "Needs regenerating", selected: "Selected", highlight: "Marked" };

/** Keeps the shown value from changing more than once per `ms` when `on` is true. */
function useThrottled<T>(value: T, ms: number, on: boolean): T {
  const [shown, setShown] = useState(value);
  const last = useRef(0);
  useEffect(() => {
    if (!on) return;
    const wait = Math.max(0, last.current + ms - Date.now());
    const t = window.setTimeout(() => { last.current = Date.now(); setShown(value); }, wait);
    return () => window.clearTimeout(t);
  }, [value, ms, on]);
  return on ? shown : value;
}

/**
 * Draws the loudness of one clip as bars, with the played part filled. With onSeek it is also the seek slider.
 * It never uses the AI colour for playback: played bars are ink, the rest are the strong line colour.
 * Only an `ai` region gets a faint AI wash, and it also has a dotted top edge and words, so colour is never the only signal.
 */
export function Waveform({
  peaks, duration, position = 0, onSeek, label, regions = [], markers = [], pendingFrom, mode = "static", liveBars = 64,
  error, simple = false, height = 48, formatTime = formatMediaTime, className, ...rest
}: WaveformProps) {
  const reduced = useReducedMotion();
  const descId = useId();
  const trackRef = useRef<HTMLDivElement>(null);
  const drag = useRef<number | null>(null);
  const [dragAt, setDragAt] = useState<number | null>(null);

  const live = mode === "live";
  const dur = Math.max(0, duration || 0);
  const state: WaveformState = error ? "error" : peaks === null ? "drawing" : "ready";
  const seekable = !!onSeek && !live && dur > 0;
  // With reduced motion the playhead moves at most four times a second.
  const shownPos = useThrottled(clamp(position, 0, dur), 250, reduced && !live && dragAt === null);
  const pos = dragAt ?? shownPos;
  const pct = (s: number) => `${dur ? (clamp(s, 0, dur) / dur) * 100 : 0}%`;

  // With reduced motion a live drawing changes at most once a second, so it reads as a still level, not a moving wave.
  const livePeaks = useThrottled(peaks, 1000, reduced && live);
  const bars = live ? (livePeaks ?? []).slice(-liveBars) : peaks ?? [];
  const n = live ? liveBars : bars.length;
  const offset = live ? liveBars - bars.length : 0;
  const playedBars = !live && dur ? (pos / dur) * n : 0;

  const timeAt = (clientX: number) => {
    const r = trackRef.current?.getBoundingClientRect();
    return r && r.width ? clamp((clientX - r.left) / r.width, 0, 1) * dur : 0;
  };
  const down = (ev: PointerEvent<HTMLDivElement>) => {
    if (!seekable || (ev.pointerType === "mouse" && ev.button !== 0)) return;
    ev.currentTarget.setPointerCapture(ev.pointerId);
    ev.currentTarget.focus();
    drag.current = ev.pointerId;
    const t = timeAt(ev.clientX);
    setDragAt(t);
    onSeek?.(t);
  };
  const move = (ev: PointerEvent<HTMLDivElement>) => {
    if (drag.current !== ev.pointerId) return;
    const t = timeAt(ev.clientX);
    setDragAt(t);
    onSeek?.(t);
  };
  const up = (ev: PointerEvent<HTMLDivElement>) => {
    if (drag.current !== ev.pointerId) return;
    drag.current = null;
    setDragAt(null);
  };
  const key = (ev: KeyboardEvent<HTMLDivElement>) => {
    if (!seekable) return;
    const big = ev.shiftKey ? 15 : 5;
    const k = ev.key;
    let to: number;
    if (k === "ArrowRight" || k === "ArrowUp") to = position + big;
    else if (k === "ArrowLeft" || k === "ArrowDown") to = position - big;
    else if (k === "PageUp") to = position + 30;
    else if (k === "PageDown") to = position - 30;
    else if (k === "Home") to = 0;
    else if (k === "End") to = dur;
    else return;
    ev.preventDefault();
    onSeek?.(clamp(to, 0, dur));
  };

  const labelled = regions.filter((r) => r.label);
  const description = [
    state === "drawing" ? "Waveform still drawing." : state === "error" ? error : "",
    pendingFrom !== undefined && pendingFrom < dur ? `Still being made after ${speakMediaTime(pendingFrom)}.` : "",
    ...regions.map((r) => `${r.label ?? KIND_WORD[r.kind]}, ${speakMediaTime(r.start)} to ${speakMediaTime(r.end)}.`),
    ...markers.map((m) => `${m.tone === "review" ? "Needs review: " : ""}${m.label} at ${speakMediaTime(m.at)}.`),
  ].filter(Boolean).join(" ");

  const showBars = state === "ready" && !simple;
  const drawing = (
    <div aria-hidden className={cn("relative w-full", HEIGHT[height])}>
      {/* Regions sit behind the bars. */}
      {regions.map((r, i) => (
        <span key={i} className={cn("absolute inset-y-0",
          r.kind === "ai" && "border-t-2 border-dotted border-fg-muted bg-lime-soft forced-colors:border-[CanvasText]",
          r.kind === "person" && "border-t-2 border-fg-subtle",
          r.kind === "stale" && "border-t-2 border-dashed border-attention",
          r.kind === "selected" && "rounded-sm bg-selected ring-1 ring-fg forced-colors:outline forced-colors:outline-1",
          r.kind === "highlight" && "rounded-sm bg-hover",
        )}
          style={{
            left: pct(r.start), width: `calc(${pct(r.end)} - ${pct(r.start)})`,
            backgroundImage: r.kind === "stale" ? "repeating-linear-gradient(135deg, color-mix(in oklab, var(--attention) 28%, transparent) 0 calc(var(--spacing) * 0.5), transparent 0 calc(var(--spacing) * 1.75))" : undefined,
          }} />
      ))}
      {showBars ? (
        <svg viewBox={`0 0 ${Math.max(1, n)} 100`} preserveAspectRatio="none" focusable="false"
          className={cn("absolute inset-0 size-full", !live && "motion-safe:animate-fade")}>
          {bars.map((p, i) => {
            const h = Math.max(4, clamp(p, 0, 1) * 96);
            const x = i + offset;
            const played = !live && x + 0.5 <= playedBars;
            const pending = pendingFrom !== undefined && dur && (x / n) * dur >= pendingFrom;
            return (
              <rect key={i} x={x + 0.18} width={0.64} y={50 - h / 2} height={h}
                fill={live ? "var(--fg-muted)" : played ? "var(--fg)" : "var(--border-strong)"}
                fillOpacity={pending ? 0.45 : 1} />
            );
          })}
        </svg>
      ) : (
        // Plain bar: drawing, error, simple, or a narrow space.
        <span className="absolute inset-x-0 top-1/2 h-1 -translate-y-1/2 rounded-full bg-line-strong forced-colors:bg-[GrayText]">
          {!live && state !== "drawing" && <span className="absolute inset-y-0 left-0 rounded-full bg-fg forced-colors:bg-[CanvasText]" style={{ width: pct(pos) }} />}
        </span>
      )}
      {/* Narrow containers swap the bars for the plain bar. */}
      {showBars && !live && (
        <span className="absolute inset-0 hidden bg-surface @max-3xs:block">
          <span className="absolute inset-x-0 top-1/2 h-1 -translate-y-1/2 rounded-full bg-line-strong">
            {!live && <span className="absolute inset-y-0 left-0 rounded-full bg-fg" style={{ width: pct(pos) }} />}
          </span>
        </span>
      )}
      {pendingFrom !== undefined && pendingFrom < dur && (
        <span className="absolute inset-y-0 right-0 border-l border-dashed border-fg-subtle" style={{ left: pct(pendingFrom) }} />
      )}
      {markers.map((m, i) => (
        <span key={i} title={m.label} className="absolute inset-y-0 w-0" style={{ left: pct(m.at) }}>
          <span className={cn("absolute inset-y-0 left-0 w-px -translate-x-1/2", m.tone === "review" ? "bg-attention" : "bg-fg-subtle")} />
          <span className={cn("absolute -top-1 left-0 size-2 -translate-x-1/2", m.tone === "review" ? "rotate-45 bg-attention" : "rounded-full bg-fg-subtle")} />
        </span>
      ))}
      {!live && state === "ready" && (
        <span className="absolute -inset-y-1 w-0.5 -translate-x-1/2 rounded-full bg-fg forced-colors:bg-[CanvasText]" style={{ left: pct(pos) }} />
      )}
      {state === "drawing" && <span className="absolute inset-0 flex items-center justify-center"><span className="bg-surface px-2 text-small text-fg-muted">Drawing waveform</span></span>}
    </div>
  );

  return (
    <div className={cn("@container min-w-0", className)} {...rest}>
      {seekable ? (
        <div ref={trackRef} role="slider" tabIndex={0} aria-label={label ?? "Seek"}
          aria-valuemin={0} aria-valuemax={Math.round(dur)} aria-valuenow={Math.round(position)}
          aria-valuetext={`${speakMediaTime(position)} of ${speakMediaTime(dur)}`} aria-orientation="horizontal"
          aria-describedby={description ? descId : undefined}
          onKeyDown={key} onPointerDown={down} onPointerMove={move} onPointerUp={up} onPointerCancel={up}
          className="relative flex min-h-11 cursor-pointer touch-none items-center rounded-sm select-none">
          {drawing}
          {dragAt !== null && (
            <span aria-hidden className="pointer-events-none absolute -top-7 -translate-x-1/2 rounded-control bg-inverse px-1.5 py-0.5 text-small text-inverse-fg tabular-nums shadow-sm" style={{ left: pct(dragAt) }}>
              {formatTime(dragAt)}
            </span>
          )}
        </div>
      ) : (
        <div ref={trackRef} aria-hidden className="relative">{drawing}</div>
      )}
      {description && seekable && <span id={descId} hidden>{description}</span>}
      {state === "error" && <p className="mt-1 text-small text-fg-muted">{error}</p>}
      {labelled.length > 0 && (
        <ul aria-hidden={seekable || undefined} className="mt-1.5 flex flex-wrap gap-x-4 gap-y-1 text-small text-fg-muted">
          {labelled.map((r, i) => (
            <li key={i} className="flex min-w-0 items-center gap-1.5">
              <span className={cn("inline-block size-2 shrink-0",
                r.kind === "ai" && "rounded-full bg-lime ring-1 ring-fg-muted",
                r.kind === "stale" && "rotate-45 bg-attention",
                r.kind === "person" && "rounded-full bg-fg-subtle",
                (r.kind === "selected" || r.kind === "highlight") && "rounded-sm ring-1 ring-fg")} />
              <span className="min-w-0">{r.label}, <span className="tabular-nums">{formatTime(r.start)} to {formatTime(r.end)}</span></span>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}
```

## tattva/atoms/WorkingEdge.tsx

```tsx
import type { HTMLAttributes, ReactNode } from "react";
import { cn } from "../lib/cn";

export type WorkingEdgeRadius = "card" | "overlay" | "control" | "field" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "full";

export interface WorkingEdgeProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
  /** True only while the AI is really working on this surface. The ring shows while true. */
  active: boolean;
  /** The surface the ring goes around, such as a card or a field. */
  children: ReactNode;
  /** Said once by screen readers when active turns true. */
  label?: string;
  /** Set false when the caller announces the change itself, and the surface must not hold a status region (such as a listbox option). */
  announce?: boolean;
  /** Corner radius. Match the corner of the surface inside. "card" (the default) follows the shape setting like every card. */
  radius?: WorkingEdgeRadius;
  className?: string;
}

const RADIUS: Record<WorkingEdgeRadius, string> = {
  card: "rounded-card", overlay: "rounded-overlay", control: "rounded-control", field: "rounded-field",
  sm: "rounded-sm", md: "rounded-md", lg: "rounded-lg", xl: "rounded-xl", "2xl": "rounded-2xl", "3xl": "rounded-3xl", "4xl": "rounded-4xl", full: "rounded-full",
};

// The turning highlight needs a registered angle, which a class cannot declare. React hoists and dedupes this sheet by href.
// One slow turn is decorative and loops only while real work runs. Reduced motion gets a still ring; forced colours a plain border.
const CSS = `
@property --sd-edge-angle { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
@keyframes sd-edge-turn { to { --sd-edge-angle: 360deg; } }
.sd-edge-ring {
  position: absolute; inset: 0; border-radius: inherit; pointer-events: none; padding: var(--focus-width);
  background: conic-gradient(from var(--sd-edge-angle), color-mix(in oklab, var(--lime) 30%, transparent), var(--lime) 18%, color-mix(in oklab, var(--lime) 30%, transparent) 36%, color-mix(in oklab, var(--lime) 30%, transparent));
  -webkit-mask: linear-gradient(var(--fg) 0 0) content-box, linear-gradient(var(--fg) 0 0); -webkit-mask-composite: xor;
  mask: linear-gradient(var(--fg) 0 0) content-box exclude, linear-gradient(var(--fg) 0 0);
  animation: sd-edge-turn var(--dur-ambient, 2.4s) linear infinite;
}
.sd-edge-ring[data-active="false"] { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { .sd-edge-ring { animation: none; background: var(--lime); } }
@media (forced-colors: active) { .sd-edge-ring { animation: none; background: none; -webkit-mask: none; mask: none; padding: 0; border: var(--focus-width) solid CanvasText; } }
`;

/** Wraps a surface and draws a calm moving ring in the AI colour around it, only while the AI is working on it. */
export function WorkingEdge({ active, children, label = "AI is working on this", radius = "card", announce = true, className, ...rest }: WorkingEdgeProps) {
  return (
    <div {...rest} className={cn("relative", RADIUS[radius], className)}>
      <style href="tattva-working-edge" precedence="default">{CSS}</style>
      {children}
      <span aria-hidden data-active={active}
        className={cn("sd-edge-ring transition-opacity", active ? "opacity-100 duration-[var(--dur-base)] ease-[var(--ease-arrive)]" : "opacity-0 duration-[var(--dur-fast)] ease-[var(--ease-in)]")} />
      {announce && <span role="status" aria-live="polite" className="sr-only">{active ? label : ""}</span>}
    </div>
  );
}
```

## tattva/hooks/index.ts

```ts
export * from "./useReducedMotion";
export * from "./useStickToBottom";
export * from "./useStreamingText";
export { useScrollEdges } from "./useScrollEdges";
export { useIndicator } from "./useIndicator";
export { usePresence } from "./usePresence";
```

## tattva/hooks/useIndicator.ts

```ts
import { useLayoutEffect, useState, type CSSProperties, type RefObject } from "react";

/**
 * Position of a sliding selection indicator inside `ref`, following the child that matches `selector`
 * (for example '[aria-selected="true"]'). Returns a style for an absolutely positioned element; the container must be
 * `relative`. The first placement does not animate, so the indicator never slides in from the corner. Pass `key` so it
 * re-measures when the selection changes.
 */
export function useIndicator(ref: RefObject<HTMLElement | null>, selector: string, key: unknown): CSSProperties | null {
  const [box, setBox] = useState<{ x: number; y: number; w: number; h: number; ready: boolean } | null>(null);
  useLayoutEffect(() => {
    const root = ref.current;
    if (!root) return;
    const read = () => {
      const el = root.querySelector<HTMLElement>(selector);
      if (!el) { setBox(null); return; }
      const next = { x: el.offsetLeft, y: el.offsetTop, w: el.offsetWidth, h: el.offsetHeight };
      setBox((p) => (p && p.x === next.x && p.y === next.y && p.w === next.w && p.h === next.h ? p : { ...next, ready: !!p }));
    };
    read();
    const ro = new ResizeObserver(read);
    ro.observe(root);
    root.querySelectorAll("*").forEach((c) => ro.observe(c));
    return () => ro.disconnect();
  }, [ref, selector, key]);
  if (!box) return null;
  return {
    position: "absolute", left: 0, top: box.y, width: box.w, height: box.h,
    transform: `translateX(${box.x}px)`,
    transitionProperty: box.ready ? "transform, width" : "none",
    transitionDuration: "var(--dur-base)", transitionTimingFunction: "var(--ease-arrive)",
  };
}
```

## tattva/hooks/usePresence.ts

```ts
import { useEffect, useState } from "react";
import { useReducedMotion } from "./useReducedMotion";

/**
 * Keeps something mounted for a short exit after `open` turns false, so it can fade out instead of vanishing.
 * `leaving` is true during that time. Under reduced motion there is no delay. The default matches --dur-fast.
 */
export function usePresence(open: boolean, exitMs = 150): { present: boolean; leaving: boolean } {
  const reduce = useReducedMotion();
  const [present, setPresent] = useState(open);
  useEffect(() => {
    if (open) { setPresent(true); return; }
    if (!present) return;
    const t = window.setTimeout(() => setPresent(false), reduce ? 0 : exitMs);
    return () => window.clearTimeout(t);
  }, [open, present, reduce, exitMs]);
  return { present: open || present, leaving: !open && present };
}
```

## tattva/hooks/useReducedMotion.ts

```ts
import { useSyncExternalStore } from "react";

const QUERY = "(prefers-reduced-motion: reduce)";

function subscribe(cb: () => void) {
  if (typeof window === "undefined" || !window.matchMedia) return () => {};
  const mq = window.matchMedia(QUERY);
  mq.addEventListener("change", cb);
  return () => mq.removeEventListener("change", cb);
}
const snapshot = () => typeof window !== "undefined" && !!window.matchMedia && window.matchMedia(QUERY).matches;

/**
 * True when the person has asked the system to reduce motion.
 * Updates live when the setting changes. Returns false where matchMedia is unavailable.
 */
export function useReducedMotion(): boolean {
  return useSyncExternalStore(subscribe, snapshot, () => false);
}
```

## tattva/hooks/useScrollEdges.ts

```ts
import { useEffect, useState, type CSSProperties, type RefObject } from "react";

/**
 * For a row that scrolls sideways. Returns a style that softly fades the edge that has more content past it, so a phone user
 * can see there is more to reach. Nothing fades when everything fits. Spread it on the scrolling element.
 * Pass remeasureOn when the element mounts later than the component, so it is measured once it is there.
 */
export function useScrollEdges(ref: RefObject<HTMLElement | null>, remeasureOn?: unknown): CSSProperties {
  const [edge, setEdge] = useState({ start: false, end: false });
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const read = () => {
      const start = el.scrollLeft > 1;
      const end = el.scrollLeft + el.clientWidth < el.scrollWidth - 1;
      setEdge((p) => (p.start === start && p.end === end ? p : { start, end }));
    };
    read();
    el.addEventListener("scroll", read, { passive: true });
    const ro = new ResizeObserver(read);
    ro.observe(el);
    return () => { el.removeEventListener("scroll", read); ro.disconnect(); };
  }, [ref, remeasureOn]);
  if (!edge.start && !edge.end) return {};
  const mask = `linear-gradient(to right, ${edge.start ? "transparent 0, black var(--edge-fade)" : "black 0"}, ${edge.end ? "black calc(100% - var(--edge-fade)), transparent 100%" : "black 100%"})`;
  return { maskImage: mask, WebkitMaskImage: mask };
}
```

## tattva/hooks/useStickToBottom.ts

```ts
import { useCallback, useEffect, useRef, useState, type RefObject } from "react";
import { useReducedMotion } from "./useReducedMotion";

export interface UseStickToBottomOptions {
  /** Distance in px from the bottom that still counts as "at the bottom". Default 80. */
  threshold?: number;
}

export interface UseStickToBottom<T extends HTMLElement> {
  /** Attach to the scrolling container. Put the content in one wrapper child so growth can be observed. */
  ref: RefObject<T | null>;
  /** True while the reader is within the threshold of the bottom. */
  atBottom: boolean;
  /** Messages that arrived while the reader was scrolled up. Reset when they reach the bottom. */
  unseen: number;
  /** Call when a new message arrives. Pins when the reader is at the bottom, otherwise raises `unseen`. */
  markUnseen: (count?: number) => void;
  /** Smooth scroll to the bottom, instant under reduced motion. Re-pins and clears `unseen`. */
  scrollToBottom: () => void;
}

/**
 * Keeps a scroll container pinned to the bottom only while the reader is near it.
 * Scrolling up releases the pin. Growth of the content and resizes of the container re-pin when pinned.
 * Pass the container as `ref`. Proposal: threshold 80px, from community and implementer sources.
 */
export function useStickToBottom<T extends HTMLElement = HTMLDivElement>({ threshold = 80 }: UseStickToBottomOptions = {}): UseStickToBottom<T> {
  const ref = useRef<T>(null);
  const pinned = useRef(true);
  const gliding = useRef(false);
  const reduced = useReducedMotion();
  const [atBottom, setAtBottom] = useState(true);
  const [unseen, setUnseen] = useState(0);

  const jump = useCallback(() => {
    const el = ref.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, []);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const onScroll = () => {
      const dist = el.scrollHeight - el.scrollTop - el.clientHeight;
      if (gliding.current) {
        if (dist <= 1) { gliding.current = false; pinned.current = true; setAtBottom(true); setUnseen(0); }
        return;
      }
      const near = dist <= threshold;
      pinned.current = near;
      setAtBottom(near);
      if (near) setUnseen(0);
    };
    const cancelGlide = () => { gliding.current = false; };
    el.addEventListener("scroll", onScroll, { passive: true });
    el.addEventListener("wheel", cancelGlide, { passive: true });
    el.addEventListener("touchstart", cancelGlide, { passive: true });
    const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => { if (pinned.current && !gliding.current) jump(); }) : null;
    ro?.observe(el);
    if (el.firstElementChild) ro?.observe(el.firstElementChild);
    jump();
    return () => {
      el.removeEventListener("scroll", onScroll);
      el.removeEventListener("wheel", cancelGlide);
      el.removeEventListener("touchstart", cancelGlide);
      ro?.disconnect();
    };
  }, [threshold, jump]);

  const scrollToBottom = useCallback(() => {
    const el = ref.current;
    if (!el) return;
    pinned.current = true;
    setUnseen(0);
    if (reduced) { gliding.current = false; jump(); setAtBottom(true); return; }
    gliding.current = true;
    el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
  }, [reduced, jump]);

  const markUnseen = useCallback((count = 1) => {
    if (pinned.current) { jump(); return; }
    setUnseen((n) => n + count);
  }, [jump]);

  return { ref, atBottom, unseen, markUnseen, scrollToBottom };
}
```

## tattva/hooks/useStreamingText.ts

```ts
import { useCallback, useEffect, useRef, useState } from "react";

/** Simulates token streaming for demos and tests. Swap for your real stream. */
export function useStreamingText(full: string, { speed = 18, auto = true }: { speed?: number; auto?: boolean } = {}) {
  const [text, setText] = useState("");
  const [streaming, setStreaming] = useState(false);
  const timer = useRef<number | undefined>(undefined);

  const stop = useCallback(() => { window.clearInterval(timer.current); setStreaming(false); }, []);
  const start = useCallback(() => {
    window.clearInterval(timer.current);
    setText(""); setStreaming(true);
    let i = 0;
    timer.current = window.setInterval(() => {
      i += 2;
      setText(full.slice(0, i));
      if (i >= full.length) { window.clearInterval(timer.current); setStreaming(false); }
    }, speed);
  }, [full, speed]);

  useEffect(() => { if (auto) start(); return () => window.clearInterval(timer.current); }, [auto, start]);
  return { text, streaming, start, stop };
}
```

## tattva/lib/cn.ts

```ts
export type ClassValue = string | false | null | undefined;

/** Join class names, dropping falsy values. */
export function cn(...parts: ClassValue[]): string {
  return parts.filter(Boolean).join(" ");
}
```

## tattva/lib/embedded.ts

```ts
import { createContext } from "react";

/**
 * True inside documentation previews. Page-level components such as AppShell render plain
 * containers instead of landmarks (main, aside) there, so a docs page keeps one main landmark.
 */
export const EmbeddedContext = createContext(false);
```

## tattva/lib/focus.ts

```ts
import { useEffect, useRef, useState, type RefObject } from "react";

/**
 * Keeping keyboard focus and screen reader users in step when a part changes under them: a chip or card that removes
 * itself, buttons that are replaced by a result, options that arrive later. Parts use these so the app does not have to.
 */

const FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]):not([type="hidden"]), textarea:not([disabled]), select:not([disabled]), [contenteditable="true"], [tabindex]:not([tabindex="-1"])';
const TYPING = 'textarea:not([disabled]), input:not([disabled]):not([type]), input[type="text"]:not([disabled]), input[type="search"]:not([disabled]), [contenteditable="true"], [role="textbox"]';

const first = (root: Element | null, selector = FOCUSABLE) =>
  root instanceof HTMLElement ? (root.matches(selector) ? root : root.querySelector<HTMLElement>(selector)) : null;

/**
 * Where focus should go when `el` (a chip, a card, a row) is about to go away: the same kind of control in the next
 * item, else in the previous one, else the nearest place to type (the message box or the document), searching
 * outwards from `el`.
 */
export function focusTargetAfter(el: HTMLElement): HTMLElement | null {
  for (const sib of [el.nextElementSibling, el.previousElementSibling]) {
    const t = first(sib);
    if (t) return t;
  }
  for (let p = el.parentElement, i = 0; p && i < 8; p = p.parentElement, i++) {
    const t = [...p.querySelectorAll<HTMLElement>(TYPING)].find((x) => !el.contains(x));
    if (t) return t;
  }
  return null;
}

/** The nearest place to type outside `el`, such as the message box beside a starter gallery. */
export function nearestTyping(el: HTMLElement | null): HTMLElement | null {
  for (let p = el?.parentElement, i = 0; p && i < 8; p = p.parentElement, i++) {
    const t = [...p.querySelectorAll<HTMLElement>(TYPING)].find((x) => !el!.contains(x));
    if (t) return t;
  }
  return null;
}

const lost = () => { const a = document.activeElement; return !a || a === document.body || !a.isConnected; };

/**
 * Call from a handler that may make `el`, or the control just pressed, go away. Once the app has updated, if focus
 * fell to the page, it moves to `target()` when given, else to the first control left in `el`, else to
 * `focusTargetAfter(el)` worked out before anything changed. Focus that landed somewhere real is left alone.
 */
export function keepFocus(el: HTMLElement | null, target?: () => HTMLElement | null | undefined) {
  if (!el) return;
  const fallback = focusTargetAfter(el);
  requestAnimationFrame(() => {
    if (!lost()) return;
    const t = target?.() ?? (el.isConnected ? first(el) : null) ?? fallback;
    if (t?.isConnected) t.focus({ preventScroll: true });
  });
}

/**
 * When `key` changes (a status, the current version) and focus was last inside `root` but has fallen to the page
 * because the control it was on went away, move it to `target()`.
 */
export function useFocusReturn(key: unknown, root: RefObject<HTMLElement | null>, target: () => HTMLElement | null | undefined) {
  const inside = useRef(false);
  const pick = useRef(target);
  pick.current = target;
  useEffect(() => {
    // Focus moving anywhere in the document says whether it is inside the part. A control that is removed while
    // focused moves focus nowhere, so the last answer stands.
    const on = (e: FocusEvent) => { inside.current = !!root.current?.contains(e.target as Node); };
    document.addEventListener("focusin", on);
    return () => document.removeEventListener("focusin", on);
  }, [root]);
  useEffect(() => {
    if (!inside.current || !lost()) return;
    pick.current()?.focus({ preventScroll: true });
  }, [key]);
}

/**
 * A message for a polite live region that is set only when `value` changes after the first render, so nothing is
 * read out when the part appears. `say` returns the words, or "" to stay quiet.
 */
export function useAnnounceChange<T>(value: T, say: (now: T, before: T) => string): string {
  const before = useRef(value);
  const [message, setMessage] = useState("");
  useEffect(() => {
    if (Object.is(before.current, value)) return;
    const m = say(value, before.current);
    before.current = value;
    setMessage(m);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [value]);
  return message;
}
```

## tattva/lib/icons.tsx

```tsx
import type { SVGProps } from "react";

type P = SVGProps<SVGSVGElement>;
const base = (p: P) => ({
  width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor",
  strokeWidth: 1.5, strokeLinecap: "round" as const, strokeLinejoin: "round" as const,
  "aria-hidden": true, ...p,
});

export const SendIcon = (p: P) => <svg {...base(p)}><path d="M12 19V5M5 12l7-7 7 7" /></svg>;
export const StopIcon = (p: P) => <svg {...base(p)} fill="currentColor" stroke="none"><rect x="6" y="6" width="12" height="12" rx="2" /></svg>;
export const CopyIcon = (p: P) => <svg {...base(p)}><rect x="9" y="9" width="11" height="11" rx="2" /><path d="M5 15V6a2 2 0 0 1 2-2h9" /></svg>;
export const CheckIcon = (p: P) => <svg {...base(p)}><path d="M5 12l5 5L20 7" /></svg>;
export const XIcon = (p: P) => <svg {...base(p)}><path d="M6 6l12 12M18 6L6 18" /></svg>;
export const ThumbUpIcon = (p: P) => <svg {...base(p)}><path d="M7 11v9H4v-9h3zM7 11l4-7a2 2 0 0 1 2 2v4h5.5a2 2 0 0 1 2 2.3l-1 6A2 2 0 0 1 17.5 20H7" /></svg>;
export const ThumbDownIcon = (p: P) => <svg {...base(p)}><path d="M17 13V4h3v9h-3zM17 13l-4 7a2 2 0 0 1-2-2v-4H5.5a2 2 0 0 1-2-2.3l1-6A2 2 0 0 1 6.5 4H17" /></svg>;
export const RefreshIcon = (p: P) => <svg {...base(p)}><path d="M20 11a8 8 0 0 0-14-4M4 4v4h4M4 13a8 8 0 0 0 14 4M20 20v-4h-4" /></svg>;
export const SparkleIcon = (p: P) => <svg {...base(p)} fill="currentColor" stroke="none"><path d="M12 2l1.9 5.6L19.5 9.5l-5.6 1.9L12 17l-1.9-5.6L4.5 9.5l5.6-1.9L12 2zM19 15l.9 2.1L22 18l-2.1.9L19 21l-.9-2.1L16 18l2.1-.9L19 15z" /></svg>;
export const ChevronIcon = (p: P) => <svg {...base(p)}><path d="M9 6l6 6-6 6" /></svg>;
export const PaperclipIcon = (p: P) => <svg {...base(p)}><path d="M21 12l-8.5 8.5a5 5 0 0 1-7-7L14 5a3.5 3.5 0 0 1 5 5l-8.5 8.5a2 2 0 0 1-3-3L15 8" /></svg>;
export const AlertIcon = (p: P) => <svg {...base(p)}><path d="M12 3l10 18H2L12 3zM12 10v5M12 18h.01" /></svg>;
export const InfoIcon = (p: P) => <svg {...base(p)}><circle cx="12" cy="12" r="9" /><path d="M12 11v5M12 8h.01" /></svg>;
export const ToolIcon = (p: P) => <svg {...base(p)}><path d="M14.5 6.5a4 4 0 0 0-5 5L3 18l3 3 6.5-6.5a4 4 0 0 0 5-5l-2.5 2.5-2.5-.5-.5-2.5 2.5-2.5z" /></svg>;
export const GlobeIcon = (p: P) => <svg {...base(p)}><circle cx="12" cy="12" r="9" /><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18" /></svg>;
export const PlusIcon = (p: P) => <svg {...base(p)}><path d="M12 5v14M5 12h14" /></svg>;
export const ImageIcon = (p: P) => <svg {...base(p)}><rect x="3" y="4" width="18" height="16" rx="2" /><circle cx="9" cy="10" r="2" /><path d="M21 16l-5-5-8 9" /></svg>;
export const ShieldIcon = (p: P) => <svg {...base(p)}><path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6l8-3z" /></svg>;
export const MoonIcon = (p: P) => <svg {...base(p)}><path d="M20 14.5A8 8 0 0 1 9.5 4 8 8 0 1 0 20 14.5z" /></svg>;
export const SunIcon = (p: P) => <svg {...base(p)}><circle cx="12" cy="12" r="4" /><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" /></svg>;
export const FileIcon = (p: P) => <svg {...base(p)}><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8l-5-5zM14 3v5h5" /></svg>;
export const SearchIcon = (p: P) => <svg {...base(p)}><circle cx="11" cy="11" r="7" /><path d="M21 21l-4.3-4.3" /></svg>;
export const UsersIcon = (p: P) => <svg {...base(p)}><circle cx="9" cy="8" r="3.5" /><path d="M2.5 20a6.5 6.5 0 0 1 13 0M16 4.5a3.5 3.5 0 0 1 0 7M18 14a6.5 6.5 0 0 1 3.5 6" /></svg>;

/* ---- AI product icons, added for the visual identity slice. Same grid, stroke and caps as above. ---- */
const spark = (d: string) => <path d={d} fill="currentColor" stroke="none" />;

// Actions
export const RegenerateIcon = (p: P) => <svg {...base(p)}><path d="M20 12a8 8 0 1 1-2.6-5.9M20 4v4h-4" />{spark("M12 8.8l1 2.2 2.2 1-2.2 1-1 2.2-1-2.2-2.2-1 2.2-1z")}</svg>;
export const RetryIcon = (p: P) => <svg {...base(p)}><path d="M5 12a7 7 0 1 0 2.2-5.1M5 4.5v4h4" /></svg>;
export const EditIcon = (p: P) => <svg {...base(p)}><path d="M4 20l.9-3.6L16.4 4.9a2 2 0 0 1 2.8 2.8L7.6 19.1 4 20zM14.5 6.8l2.7 2.7" /></svg>;
export const UndoIcon = (p: P) => <svg {...base(p)}><path d="M9 14L4 9l5-5M4 9h10.5a5.5 5.5 0 0 1 0 11H11" /></svg>;
export const RedoIcon = (p: P) => <svg {...base(p)}><path d="M15 14l5-5-5-5M20 9H9.5a5.5 5.5 0 0 0 0 11H13" /></svg>;
export const ShareIcon = (p: P) => <svg {...base(p)}><path d="M12 15V4M8 8l4-4 4 4M5 12v6a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-6" /></svg>;
export const DownloadIcon = (p: P) => <svg {...base(p)}><path d="M12 4v11M8 11l4 4 4-4M5 20h14" /></svg>;
export const PinIcon = (p: P) => <svg {...base(p)}><path d="M9 4h6M10 4l-.5 6L6.5 13h11L14.5 10 14 4M12 13v7" /></svg>;
export const TagIcon = (p: P) => <svg {...base(p)}><path d="M3.5 12.2V4.5a1 1 0 0 1 1-1h7.7a1 1 0 0 1 .7.3l7.3 7.3a1 1 0 0 1 0 1.4l-7.7 7.7a1 1 0 0 1-1.4 0L3.8 12.9a1 1 0 0 1-.3-.7z" /><circle cx="8" cy="8" r="1.2" fill="currentColor" stroke="none" /></svg>;
export const BookmarkIcon = (p: P) => <svg {...base(p)}><path d="M7 4h10a1 1 0 0 1 1 1v15l-6-4-6 4V5a1 1 0 0 1 1-1z" /></svg>;
export const SettingsIcon = (p: P) => <svg {...base(p)}><path d="M4 7h9M17 7h3M4 17h3M11 17h9" /><circle cx="15" cy="7" r="2" /><circle cx="9" cy="17" r="2" /></svg>;
export const MoreIcon = (p: P) => <svg {...base(p)} fill="currentColor" stroke="none"><circle cx="5.5" cy="12" r="1.5" /><circle cx="12" cy="12" r="1.5" /><circle cx="18.5" cy="12" r="1.5" /></svg>;
export const SyncIcon = (p: P) => <svg {...base(p)}><path d="M4 8h14M14 4l4 4-4 4M20 16H6M10 12l-4 4 4 4" /></svg>;
export const LoopIcon = (p: P) => <svg {...base(p)}><path d="M17 2l4 4-4 4M3 11V9a3 3 0 0 1 3-3h15M7 22l-4-4 4-4M21 13v2a3 3 0 0 1-3 3H3" /></svg>;

// Media and input
export const MicIcon = (p: P) => <svg {...base(p)}><rect x="9" y="3" width="6" height="11" rx="3" /><path d="M5 11a7 7 0 0 0 14 0M12 18v3" /></svg>;
export const MicOffIcon = (p: P) => <svg {...base(p)}><path d="M4 4l16 16M9 9v2a3 3 0 0 0 5 2M15 9V6a3 3 0 0 0-5.5-1.7M5 11a7 7 0 0 0 11 5.7M12 18v3" /></svg>;
export const CameraIcon = (p: P) => <svg {...base(p)}><path d="M4 8h3l1.5-2.5h7L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z" /><circle cx="12" cy="13.5" r="3.5" /></svg>;
export const EyeIcon = (p: P) => <svg {...base(p)}><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z" /><circle cx="12" cy="12" r="3" /></svg>;
export const LockIcon = (p: P) => <svg {...base(p)}><rect x="5" y="11" width="14" height="10" rx="2" /><path d="M8 11V8a4 4 0 0 1 8 0v3" /></svg>;

// Agent and context
export const MemoryIcon = (p: P) => <svg {...base(p)}><rect x="5" y="3" width="14" height="18" rx="2" /><path d="M9 3v18M12.5 8h3M12.5 12h3" /></svg>;
export const NotebookIcon = (p: P) => <svg {...base(p)}><path d="M12 6c-2-1.5-5-2-8-1.5v13c3-.5 6 0 8 1.5 2-1.5 5-2 8-1.5v-13C17 4 14 4.5 12 6zM12 6v13" /></svg>;
export const PlanIcon = (p: P) => <svg {...base(p)}><path d="M3.5 6.5L5 8l3-3M11 6.5h9M3.5 12.5L5 14l3-3M11 12.5h9M5.5 19h.01M11 19h9" /></svg>;
export const CheckpointIcon = (p: P) => <svg {...base(p)}><path d="M5 21V4M5 4h11l-2 4 2 4H5" /></svg>;
export const BranchIcon = (p: P) => <svg {...base(p)}><circle cx="6" cy="5" r="2" /><circle cx="6" cy="19" r="2" /><circle cx="18" cy="8" r="2" /><path d="M6 7v10M18 10c0 4-3 5-7 5H6" /></svg>;
export const ContextIcon = (p: P) => <svg {...base(p)}><rect x="5" y="10" width="14" height="10" rx="2" /><path d="M8 6.5h8M10.5 3h3" /></svg>;
export const LayersIcon = (p: P) => <svg {...base(p)}><path d="M12 3l9 4.5-9 4.5-9-4.5L12 3zM3 12l9 4.5 9-4.5M3 16.5L12 21l9-4.5" /></svg>;
export const HistoryIcon = (p: P) => <svg {...base(p)}><path d="M3 12a9 9 0 1 0 3-6.7M3 4v5h5M12 8v4l3 2" /></svg>;
export const ThoughtIcon = (p: P) => <svg {...base(p)}><path d="M7.5 16a4 4 0 0 1-.8-7.9 5 5 0 0 1 9.6-.7A3.8 3.8 0 0 1 17 16H7.5z" /><circle cx="6.5" cy="19.2" r="1" /><circle cx="4" cy="22" r=".5" /></svg>;

// Trust and review
export const CitationIcon = (p: P) => <svg {...base(p)}><path d="M9.5 7C6.5 7.5 5 9.5 5 12.5V17h4.5v-4.5H5M19 7c-3 .5-4.5 2.5-4.5 5.5V17H19v-4.5h-4.5" /></svg>;
export const SourceIcon = (p: P) => <svg {...base(p)}><path d="M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1M14 10a4 4 0 0 0-5.7 0l-3 3A4 4 0 0 0 11 18.7l1-1" /></svg>;
export const ApproveIcon = (p: P) => <svg {...base(p)}><circle cx="12" cy="12" r="9" /><path d="M8 12.5l3 3 5-6" /></svg>;
export const NeedsReviewIcon = (p: P) => <svg {...base(p)}><path d="M13 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h4M13 3l5 5v2M13 3v5h5" /><circle cx="16" cy="16" r="3" /><path d="M18.2 18.2L21 21" /></svg>;

// AI actions
export const AIEditIcon = (p: P) => <svg {...base(p)}><path d="M4 20l.8-3.2L14.5 7a2 2 0 0 1 2.8 2.8L7.6 19.5 4 20zM13 8.5l2.5 2.5" />{spark("M19 2.5l.8 1.8 1.8.8-1.8.8-.8 1.8-.8-1.8-1.8-.8 1.8-.8z")}</svg>;
export const AISummarizeIcon = (p: P) => <svg {...base(p)}><path d="M4 5h16M4 10h10M4 15h5M4 20h5" />{spark("M17 11l1.3 3.7L22 16l-3.7 1.3L17 21l-1.3-3.7L12 16l3.7-1.3z")}</svg>;
export const AIEnhanceIcon = (p: P) => <svg {...base(p)}><path d="M4 20L14 10" />{spark("M17.5 3l1 2.5 2.5 1-2.5 1-1 2.5-1-2.5-2.5-1 2.5-1zM7 4l.6 1.4L9 6l-1.4.6L7 8l-.6-1.4L5 6l1.4-.6z")}</svg>;
export const AISuggestIcon = (p: P) => <svg {...base(p)} fill="currentColor" stroke="none"><path d="M9 8l1.8 4.2L15 14l-4.2 1.8L9 20l-1.8-4.2L3 14l4.2-1.8zM18 3l1 2.3 2.3 1-2.3 1-1 2.3-1-2.3-2.3-1 2.3-1z" /></svg>;
export const RevertAIIcon = (p: P) => <svg {...base(p)}><path d="M9 14L4 9l5-5M4 9h10.5a5.5 5.5 0 0 1 0 11H11" />{spark("M14.5 12l.8 1.7 1.7.8-1.7.8-.8 1.7-.8-1.7-1.7-.8 1.7-.8z")}</svg>;
export const ArrowRightIcon = (p: P) => <svg {...base(p)}><path d="M5 12h14M13 6l6 6-6 6" /></svg>;
export const PaletteIcon = (p: P) => <svg {...base(p)}><path d="M12 3a9 9 0 1 0 0 18c1.4 0 2-.9 2-1.8 0-1.2-1-1.5-1-2.7 0-1 .8-1.5 1.8-1.5H17a4 4 0 0 0 4-4C21 6.6 17 3 12 3z" /><circle cx="7.5" cy="11" r="1" fill="currentColor" stroke="none" /><circle cx="10" cy="7" r="1" fill="currentColor" stroke="none" /><circle cx="15" cy="7.5" r="1" fill="currentColor" stroke="none" /></svg>;
```

## tattva/lib/sensitive.ts

```ts
export type SensitiveKind = "card" | "key" | "password" | "email" | "phone";

export interface SensitiveFinding {
  id: string;
  kind: SensitiveKind;
  /** Where it is in the text, so it can be taken out. */
  start: number;
  end: number;
  /** A safe preview, such as "•••• 4242". */
  preview: string;
}

// A real card number passes the Luhn check, so a long order number does not count. A phone number is 10 digits, or
// 10 to 13 written the way phone numbers are (a + or spaces), so an order number is not one either.
const luhn = (digits: string) => {
  let sum = 0;
  for (let i = 0; i < digits.length; i++) {
    let d = Number(digits[digits.length - 1 - i]);
    if (i % 2 === 1) { d *= 2; if (d > 9) d -= 9; }
    sum += d;
  }
  return sum % 10 === 0;
};

const RULES: { kind: SensitiveKind; re: RegExp; ok?: (m: string) => boolean; preview: (m: string) => string }[] = [
  { kind: "card", re: /\b(?:\d[ -]?){13,19}\b/g, ok: (m) => luhn(m.replace(/\D/g, "")), preview: (m) => `•••• ${m.replace(/\D/g, "").slice(-4)}` },
  { kind: "key", re: /\b(?:sk|pk|rk|ghp|gho|xox[abp]|AKIA)[-_A-Za-z0-9]{12,}\b/g, preview: (m) => `${m.slice(0, 4)}••••` },
  { kind: "password", re: /\b(?:password|passcode|pwd)\s*(?:is|:|=)\s*\S+/gi, preview: () => "••••••" },
  { kind: "email", re: /\b[\w.+-]+@[\w-]+\.[\w.-]+\b/g, preview: (m) => `${m[0]}•••@${m.split("@")[1]}` },
  { kind: "phone", re: /(?<!\d)\+?\d[\d ()-]{8,14}\d(?!\d)/g, ok: (m) => { const n = m.replace(/\D/g, "").length; return n >= 10 && n <= 13 && (n === 10 || /[+ ()-]/.test(m)); }, preview: (m) => `•••• ${m.replace(/\D/g, "").slice(-3)}` },
];

/**
 * Finds private details in a message before it is sent: card numbers (Luhn checked), API keys, passwords written
 * as "password: …", email addresses and phone numbers. It runs on the person's device; nothing is sent anywhere.
 * Overlapping matches keep the first, most specific kind (a card number is not also a phone number).
 */
export function findSensitive(text: string, kinds: SensitiveKind[] = ["card", "key", "password", "email", "phone"]): SensitiveFinding[] {
  const out: SensitiveFinding[] = [];
  for (const r of RULES) {
    if (!kinds.includes(r.kind)) continue;
    for (const m of text.matchAll(r.re)) {
      const raw = m[0].replace(/[ -]+$/, ""); // a match never ends in the space after a number
      const start = m.index ?? 0, end = start + raw.length;
      if (r.ok && !r.ok(raw)) continue;
      if (out.some((f) => start < f.end && end > f.start)) continue;
      out.push({ id: `${r.kind}-${start}`, kind: r.kind, start, end, preview: r.preview(raw) });
    }
  }
  return out.sort((a, b) => a.start - b.start);
}

/** The text with the given findings taken out. */
export function removeSensitive(text: string, findings: SensitiveFinding[]): string {
  let out = text;
  for (const f of [...findings].sort((a, b) => b.start - a.start)) out = out.slice(0, f.start) + out.slice(f.end);
  return out.replace(/[ \t]{2,}/g, " ").replace(/\s+([,.;:!?])/g, "$1").trim();
}
```

## tattva/lib/useFocusAfter.ts

```ts
import { useEffect, useRef } from "react";

/**
 * Keeps keyboard focus in place when a part swaps the control someone just pressed for something else
 * (Keep becomes a "Kept" badge, Confirm becomes a summary). Call `arm()` in the handler; when `key`
 * next changes and focus has fallen to the page body, focus moves to what `target()` returns.
 */
export function useFocusAfter(key: unknown, target: () => HTMLElement | null | undefined) {
  const armed = useRef(false);
  const pick = useRef(target);
  pick.current = target;
  useEffect(() => {
    if (!armed.current) return;
    armed.current = false;
    const a = document.activeElement;
    if (a && a !== document.body && a.isConnected) return;
    pick.current()?.focus({ preventScroll: true });
  }, [key]);
  return () => { armed.current = true; };
}

export type HeadingLevel = 2 | 3 | 4 | 5 | 6;
/** The heading tag for a level, and the one below it for inner headings (never deeper than h6). */
export const headingTag = (level: HeadingLevel) => `h${level}` as "h2" | "h3" | "h4" | "h5" | "h6";
export const subHeadingTag = (level: HeadingLevel) => headingTag(Math.min(level + 1, 6) as HeadingLevel);
```

## tattva/molecules/AILabel.tsx

```tsx
import { useState, type ReactNode } from "react";
import { AIMark } from "../atoms/AIMark";
import { Popover } from "../atoms/Popover";
import { cn } from "../lib/cn";
import { RevertAIIcon } from "../lib/icons";

export type AILabelLevel = "feature" | "message" | "field" | "image";
export type AILabelSize = 16 | 18 | 22;

export interface AILabelProps {
  /** Where the label sits. Sets the placement classes when `placed` is true and the side the explainer opens toward. */
  level?: AILabelLevel;
  /** Pixel size. Match the text beside it: 16 for 12px text, 18 for 14px, 22 for 16px. Use one size smaller next to ghost icon buttons. */
  size?: AILabelSize;
  /** Text in the pill. */
  text?: string;
  /** Apply absolute placement for the level. The parent needs `relative`. Leave false to place the label yourself, for example inline in a row. */
  placed?: boolean;
  /** Toggletip section: a short statement of what the AI did here. */
  overview?: ReactNode;
  /** Toggletip section: model, inputs, limits or anything specific to this instance. */
  details?: ReactNode;
  /** Toggletip section: links to documentation or sources. */
  resources?: ReactNode;
  /** Toggletip section: controls such as Learn more or Turn off. The label never triggers an AI action itself. */
  actions?: ReactNode;
  /** True once a person has edited the content. The tint is dropped and an icon-only revert button replaces the pill. */
  edited?: boolean;
  /** Called when the revert button is pressed. The app restores the AI version and sets `edited` back to false. */
  onRevert?: () => void;
  /** Accessible name for the revert button. */
  revertLabel?: string;
  /** Accessible name for the toggletip panel. */
  popoverLabel?: string;
  className?: string;
}

const PILL: Record<AILabelSize, string> = {
  16: "h-4 gap-0.5 px-1.5 text-nano leading-4",
  18: "h-4.5 gap-1 px-2 text-micro leading-chip",
  22: "h-5.5 gap-1 px-2.5 text-small leading-chip-lg",
};
const MARK: Record<AILabelSize, 16 | 18 | 22> = { 16: 16, 18: 16, 22: 16 };
const PLACE: Record<AILabelLevel, string> = {
  feature: "absolute top-3 right-3",
  message: "",
  field: "absolute top-1/2 right-3 -translate-y-1/2",
  image: "absolute bottom-2 left-2",
};

/**
 * Marks content as produced by AI and opens a toggletip that explains it. The pill is a lime fill with charcoal text.
 * It labels and explains. It never starts an AI action. After a person edits the content the pill is replaced by an
 * icon-only revert button, and reverting brings the pill back.
 */
export function AILabel({
  level = "feature", size = 18, text = "AI", placed = false, overview, details, resources, actions,
  edited = false, onRevert, revertLabel = "Revert to AI version", popoverLabel = "About this AI content", className,
}: AILabelProps) {
  const [open, setOpen] = useState(false);
  const place = placed ? PLACE[level] : "";

  if (edited) {
    return (
      <button type="button" aria-label={revertLabel} title={revertLabel} onClick={onRevert}
        className={cn("inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-control border border-line bg-surface text-fg-muted hover:bg-hover hover:text-fg pointer-coarse:size-11", place, className)}>
        <RevertAIIcon width={size === 16 ? 14 : 16} height={size === 16 ? 14 : 16} />
      </button>
    );
  }

  const sections: [string, ReactNode][] = ([["Overview", overview], ["Details", details], ["Resources", resources], ["Actions", actions]] as [string, ReactNode][]).filter(([, v]) => v !== undefined && v !== null && v !== false);

  return (
    <span className={cn("inline-flex align-middle", place, className)} onBlur={(e) => { if (open && !e.currentTarget.contains(e.relatedTarget as Node | null)) setOpen(false); }}>
      <Popover open={open} onOpenChange={setOpen} label={popoverLabel} align={level === "image" || level === "message" ? "start" : "end"} side={level === "image" ? "top" : "bottom"}
        panelClassName="w-72 max-w-[calc(100vw-2rem)] p-4"
        trigger={({ triggerProps, toggle }) => (
          <button type="button" onClick={toggle} {...triggerProps} aria-label={`${text}. About this AI content`}
            className="group inline-flex min-h-6 shrink-0 cursor-pointer items-center rounded-control tap">
            <span className={cn("inline-flex animate-pop items-center rounded-control border border-transparent bg-lime font-medium text-on-lime group-hover:bg-lime-hover", PILL[size])}>
              <AIMark size={MARK[size]} />{text}
            </span>
          </button>
        )}>
        {sections.length === 0 ? <p className="text-compact leading-5 text-fg-muted">This content was produced by AI.</p> : (
          <div className="space-y-3 text-compact leading-5 text-fg-muted">
            {sections.map(([name, body]) => (
              <section key={name} aria-label={name}>
                <h4 className="font-sans! mb-1 text-nano font-normal uppercase tracking-caps text-fg-subtle">{name}</h4>
                <div className={name === "Actions" ? "flex flex-wrap gap-2" : undefined}>{body}</div>
              </section>
            ))}
          </div>
        )}
      </Popover>
    </span>
  );
}
```

## tattva/molecules/AttachmentChip.tsx

```tsx
import { useRef, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { keepFocus, useAnnounceChange } from "../lib/focus";
import { FileIcon, ImageIcon, XIcon } from "../lib/icons";
import { Button, IconButton } from "../atoms/Button";
import { Meter } from "../atoms/Meter";

export type AttachmentStatus = "ready" | "uploading" | "error";

export interface AttachmentChipProps {
  /** File name. */
  name: string;
  /** Display size such as "2.4 MB". */
  size?: string;
  /** Picks the default icon when no thumbnail is given. */
  kind?: "file" | "image";
  /** Replaces the default icon, for example an img element. Keep it decorative. */
  thumbnail?: ReactNode;
  status?: AttachmentStatus;
  /** Upload progress from 0 to 100. Used while status is "uploading". */
  progress?: number;
  /** Text shown when status is "error". */
  errorMessage?: string;
  /** Shows a Retry button in the error state. */
  onRetry?: () => void;
  /** Shows the remove button. The button is named after the file. */
  onRemove?: () => void;
  /** Where keyboard focus goes after Remove. By default the next attachment, else the previous one, else the message box. */
  focusAfterRemove?: () => HTMLElement | null;
  /** What screen readers hear when the upload finishes. Defaults to "(name) uploaded". */
  readyMessage?: string;
  className?: string;
}

/** File or image chip for the composer. Shows upload progress as a meter and a retry action when the upload fails. */
export function AttachmentChip({ name, size, kind = "file", thumbnail, status = "ready", progress = 0, errorMessage = "Upload failed", onRetry, onRemove, focusAfterRemove, readyMessage, className }: AttachmentChipProps) {
  const ref = useRef<HTMLDivElement>(null);
  // Uploads finishing or failing are read out; the chip appearing is not.
  const said = useAnnounceChange(status, (now) => (now === "ready" ? readyMessage ?? `${name} uploaded` : now === "error" ? `${name}: ${errorMessage}` : ""));
  const pct = Math.round(Math.max(0, Math.min(100, progress)));
  const Icon = kind === "image" ? ImageIcon : FileIcon;
  return (
    <div ref={ref} className={cn("flex w-64 max-w-full items-center gap-2.5 rounded-card border bg-surface p-2", status === "error" ? "border-danger-line" : "border-line", className)}>
      <span className="flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-sunken text-fg-muted">
        {thumbnail ?? <Icon width={18} height={18} />}
      </span>
      <span className="min-w-0 flex-1">
        <span className="block truncate text-compact font-medium text-fg" title={name}>{name}</span>
        {status === "ready" && size && <span className="block text-small leading-4 text-fg-muted">{size}</span>}
        {status === "uploading" && (
          <span className="mt-1 block">
            <Meter value={pct} label={`Uploading ${name}`} valueText={`${pct}% uploaded`} />
            <span className="mt-1 block text-small leading-4 text-fg-muted tabular-nums">Uploading {pct}%{size ? ` of ${size}` : ""}</span>
          </span>
        )}
        {status === "error" && <span className="block truncate text-small leading-4 text-danger-fg">{errorMessage}</span>}
      </span>
      {status === "error" && onRetry && <Button size="sm" variant="secondary" onClick={onRetry} aria-label={`Retry upload of ${name}`}>Retry</Button>}
      {onRemove && (
        <IconButton label={`Remove ${name}`} size="sm" onClick={() => { onRemove(); keepFocus(ref.current, focusAfterRemove); }}><XIcon width={14} height={14} /></IconButton>
      )}
      <span role="status" aria-live="polite" className="sr-only">{said}</span>
    </div>
  );
}
```

## tattva/molecules/AudioPlayer.tsx

```tsx
import { useCallback, useEffect, useId, useRef, useState } from "react";
import type { KeyboardEvent, ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, DownloadIcon, RetryIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { Select } from "../atoms/Select";
import { Spinner } from "../atoms/Spinner";
import { Waveform, formatMediaTime } from "../atoms/Waveform";
import type { WaveformMarker, WaveformRegion } from "../atoms/Waveform";
import { useReducedMotion } from "../hooks/useReducedMotion";
import { AILabel } from "./AILabel";

/**
 * loading: getting the file. streaming: playable while the end is still being made. ready: loaded, not started.
 * playing, paused, buffering (waiting for data), ended, error (could not load), unavailable (the app will not play it, with a reason).
 */
export type PlayerState = "loading" | "streaming" | "ready" | "playing" | "paused" | "buffering" | "ended" | "error" | "unavailable";

export interface AudioPlayerProps {
  /** Title of the clip. Also names the player region. */
  title: string;
  /** A second line, such as "Support call, 3 October, 14:02". */
  subtitle?: string;
  /** A real audio file. Leave out to simulate playback from `duration` (for previews, tests and docs). */
  src?: string;
  /** Length in seconds. Needed without `src`; with `src` it is read from the file when left out. */
  duration?: number;
  /** Loudness per slice, each 0 to 1, worked out ahead of time. `null` shows the waveform still drawing. Leave out for a plain bar. */
  peaks?: number[] | null;
  /** Forces a state the player cannot find out on its own: loading, streaming, buffering, error or unavailable. Ready, playing, paused and ended are always tracked by the player. */
  state?: PlayerState;
  /** Position in seconds, when the parent keeps it (for example to sync a transcript). A change of more than a quarter second seeks. */
  current?: number;
  /** Called as the position moves, from playback or a seek. */
  onTimeChange?: (seconds: number) => void;
  /** Called when playback starts or stops. */
  onPlayingChange?: (playing: boolean) => void;
  /** Moments on the timeline, such as tool calls or interruptions. Use tone `review` only for one a person must check. */
  markers?: WaveformMarker[];
  /** Marked stretches, such as an AI-made section. */
  regions?: WaveformRegion[];
  /** In the streaming state, the seconds made so far. The rest of the track is drawn dashed. */
  madeUpTo?: number;
  /** Speeds offered. The first time it plays at 1. */
  speeds?: number[];
  /** The clip was made by AI (a generated voice, song or effect). Adds an AI label; the player itself stays neutral. */
  aiGenerated?: boolean;
  /** What the AI label explains, such as the model and voice used. */
  aiNote?: ReactNode;
  /** Shows a Download button. */
  onDownload?: () => void;
  /** Short note beside Download, such as "2 downloads left this month". */
  downloadNote?: string;
  /** Why it cannot be played, shown in the unavailable state, such as "Recordings are kept for 30 days." */
  unavailableReason?: string;
  /** Message in the error state. */
  errorText?: string;
  /** Shows Try again in the error state. */
  onRetry?: () => void;
  /** Lock-screen details while playing (Media Session). */
  mediaSession?: { artist?: string; artwork?: string };
  /** One row, 56px tall, for lists. No waveform, speed or download. */
  compact?: boolean;
  className?: string;
}

const STATE_WORD: Record<PlayerState, string> = {
  loading: "Loading", streaming: "Still making the end", ready: "Ready", playing: "Playing", paused: "Paused",
  buffering: "Buffering", ended: "Ended", error: "Could not play", unavailable: "Not available",
};
const PLAY_EVENT = "tattva-audio-play";

const PlayGlyph = () => <svg aria-hidden viewBox="0 0 24 24" className="size-5" fill="currentColor"><path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.5-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5z" /></svg>;
const PauseGlyph = () => <svg aria-hidden viewBox="0 0 24 24" className="size-5" fill="currentColor"><rect x="6.5" y="5" width="4" height="14" rx="1" /><rect x="13.5" y="5" width="4" height="14" rx="1" /></svg>;
const ReplayGlyph = () => <RetryIcon width={20} height={20} aria-hidden />;

const isTyping = (el: EventTarget | null) => el instanceof HTMLElement && (el.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName));

/**
 * Plays one clip: a call recording, a generated voice or a song. Play and pause, a waveform you can seek, time, speed and download.
 * It works without a file by simulating playback from `duration`, and plays a real file when `src` is set.
 * Nothing plays until the person presses Play, and starting one player pauses any other on the page.
 */
export function AudioPlayer({
  title, subtitle, src, duration, peaks, state: forced, current, onTimeChange, onPlayingChange, markers, regions, madeUpTo,
  speeds = [0.75, 1, 1.25, 1.5, 2], aiGenerated, aiNote, onDownload, downloadNote, unavailableReason, errorText = "This audio could not be loaded.",
  onRetry, mediaSession, compact = false, className,
}: AudioPlayerProps) {
  const id = useId();
  const titleId = `${id}-title`;
  const reduced = useReducedMotion();
  const audio = useRef<HTMLAudioElement>(null);
  const [time, setTime] = useState(current ?? 0);
  const [playing, setPlaying] = useState(false);
  const [ended, setEnded] = useState(false);
  const [rate, setRate] = useState(1);
  const [fileDur, setFileDur] = useState<number | null>(null);
  const [fileState, setFileState] = useState<"loading" | "ok" | "buffering" | "error">(src ? "loading" : "ok");
  const [slowBuffer, setSlowBuffer] = useState(false);
  const timeRef = useRef(time);
  timeRef.current = time;

  const dur = duration ?? fileDur ?? 0;
  const blocked = forced === "loading" || forced === "error" || forced === "unavailable";
  const derived: PlayerState = src && fileState === "loading" ? "loading" : src && fileState === "error" ? "error"
    : playing && (forced === "buffering" || fileState === "buffering") ? "buffering"
    : playing ? "playing" : ended ? "ended" : time > 0 ? "paused" : "ready";
  // The app can force the states the player cannot see for itself. The rest are tracked here.
  const state: PlayerState = blocked || forced === "buffering" ? forced! : forced === "streaming" && !playing && !ended ? "streaming" : derived;
  const canPlay = state !== "loading" && state !== "error" && state !== "unavailable" && dur > 0;

  const report = useCallback((t: number) => { setTime(t); onTimeChange?.(t); }, [onTimeChange]);

  const setPlay = useCallback((on: boolean) => {
    setPlaying(on);
    onPlayingChange?.(on);
    if (on) window.dispatchEvent(new CustomEvent(PLAY_EVENT, { detail: id }));
  }, [id, onPlayingChange]);

  const seek = useCallback((t: number) => {
    const to = Math.min(Math.max(0, t), dur || 0);
    if (audio.current) audio.current.currentTime = to;
    setEnded(false);
    report(to);
  }, [dur, report]);

  // The parent moved the position (for example a transcript line was pressed).
  useEffect(() => {
    if (current !== undefined && Math.abs(current - timeRef.current) > 0.25) seek(current);
  }, [current, seek]);

  // Only one player plays at a time.
  useEffect(() => {
    const other = (e: Event) => { if ((e as CustomEvent).detail !== id) { setPlaying(false); audio.current?.pause(); } };
    window.addEventListener(PLAY_EVENT, other);
    return () => window.removeEventListener(PLAY_EVENT, other);
  }, [id]);

  // A real file plays through the audio element.
  useEffect(() => {
    const a = audio.current;
    if (!a || !src) return;
    a.playbackRate = rate;
    if (playing && !blocked) a.play().catch(() => { setPlay(false); });
    else a.pause();
  }, [playing, rate, src, blocked, setPlay]);

  // Without a file, a clock moves the position. It ticks less often with reduced motion.
  useEffect(() => {
    if (src || !playing || blocked || forced === "buffering") return;
    const step = reduced ? 250 : 100;
    let last = performance.now();
    const t = window.setInterval(() => {
      const now = performance.now();
      const next = timeRef.current + ((now - last) / 1000) * rate;
      last = now;
      if (next >= dur) { report(dur); setPlay(false); setEnded(true); } else report(next);
    }, step);
    return () => window.clearInterval(t);
  }, [src, playing, blocked, forced, reduced, rate, dur, report, setPlay]);

  // Buffering shows a spinner only after 300ms, so short waits do not flicker.
  const buffering = state === "buffering";
  useEffect(() => {
    if (!buffering) { setSlowBuffer(false); return; }
    const t = window.setTimeout(() => setSlowBuffer(true), 300);
    return () => window.clearTimeout(t);
  }, [buffering]);

  // Lock-screen controls while this clip plays.
  useEffect(() => {
    const ms = typeof navigator !== "undefined" ? navigator.mediaSession : undefined;
    if (!ms || !playing || typeof MediaMetadata === "undefined") return;
    ms.metadata = new MediaMetadata({ title, artist: mediaSession?.artist, artwork: mediaSession?.artwork ? [{ src: mediaSession.artwork }] : undefined });
    ms.setActionHandler("play", () => setPlay(true));
    ms.setActionHandler("pause", () => setPlay(false));
    ms.setActionHandler("seekto", (d) => { if (d.seekTime !== undefined) seek(d.seekTime); });
    return () => { ms.setActionHandler("play", null); ms.setActionHandler("pause", null); ms.setActionHandler("seekto", null); };
  }, [playing, title, mediaSession?.artist, mediaSession?.artwork, setPlay, seek]);

  const toggle = () => {
    if (!canPlay) return;
    if (playing) { setPlay(false); return; }
    if (ended || time >= dur) { seek(0); }
    setEnded(false);
    setPlay(true);
  };

  // Space and K play or pause while focus is in the player, but never while typing or on a control that uses Space itself.
  const onKey = (e: KeyboardEvent<HTMLElement>) => {
    if (e.altKey || e.ctrlKey || e.metaKey || isTyping(e.target)) return;
    const onButton = e.target instanceof HTMLElement && e.target.tagName === "BUTTON";
    if ((e.key === " " && !onButton) || e.key === "k" || e.key === "K") { e.preventDefault(); toggle(); }
  };

  const glyph = playing ? <PauseGlyph /> : state === "ended" ? <ReplayGlyph /> : <PlayGlyph />;
  const notice = state === "error" || state === "unavailable" || state === "buffering" || state === "ended" || state === "streaming";
  const timeText = <span className="tabular-nums">{formatMediaTime(time)} / {dur ? formatMediaTime(dur) : "–:––"}</span>;

  const playButton = (
    <button type="button" onClick={toggle} aria-label="Play" aria-pressed={playing} aria-disabled={!canPlay || undefined}
      className={cn("relative inline-flex size-11 shrink-0 cursor-pointer items-center justify-center rounded-full bg-accent text-on-accent transition-colors duration-(--dur-instant) hover:bg-accent-hover",
        !canPlay && "cursor-default bg-sunken text-fg-subtle hover:bg-sunken")}>
      {state === "loading" ? <Spinner size={18} label="Loading" /> : glyph}
      {slowBuffer && <span className="absolute -right-1 -bottom-1 rounded-full bg-surface p-0.5"><Spinner size={14} label="Buffering" /></span>}
    </button>
  );

  const heading = (
    <div className="min-w-0 flex-1">
      <div className="flex min-w-0 items-center gap-2">
        <span id={titleId} className="truncate text-compact font-medium text-fg">{title}</span>
        {aiGenerated && <AILabel level="message" size={16} text="AI" overview={aiNote ?? "This audio was made by an AI voice or music model."} popoverLabel={`About the AI in ${title}`} />}
      </div>
      <p className="truncate text-small text-fg-muted">
        {compact ? <>{timeText} · {STATE_WORD[state]}</> : subtitle ?? STATE_WORD[state]}
      </p>
    </div>
  );

  return (
    <section aria-labelledby={titleId} onKeyDown={onKey}
      className={cn("min-w-0 rounded-card border border-line bg-surface", compact ? "flex min-h-14 items-center gap-3 px-3 py-1.5" : "flex flex-col gap-2 p-3 sm:p-4", className)}>
      {src && (
        <audio ref={audio} src={src} preload="metadata" className="hidden"
          onLoadedMetadata={(e) => { setFileDur(e.currentTarget.duration); setFileState("ok"); }}
          onCanPlay={() => setFileState("ok")} onWaiting={() => setFileState("buffering")} onPlaying={() => setFileState("ok")}
          onError={() => { setFileState("error"); setPlay(false); }}
          onTimeUpdate={(e) => report(e.currentTarget.currentTime)}
          onEnded={() => { setPlay(false); setEnded(true); }} />
      )}
      {compact ? (<>{playButton}{heading}</>) : (
        <>
          <div className="flex items-center gap-3">
            {playButton}
            {heading}
          </div>
          {state === "error" ? (
            <div className="flex flex-wrap items-center gap-2 rounded-field bg-sunken px-3 py-2 text-compact text-fg">
              <AlertIcon width={16} height={16} aria-hidden className="shrink-0 text-danger-fg" />
              <span className="min-w-40 flex-1">{errorText}</span>
              {onRetry && <Button size="sm" variant="secondary" onClick={onRetry} leading={<RetryIcon width={14} height={14} />} className="pointer-coarse:h-11">Try again</Button>}
            </div>
          ) : (
            <Waveform peaks={peaks === undefined ? (state === "loading" ? null : []) : state === "loading" ? null : peaks}
              simple={peaks === undefined} duration={dur} position={time} onSeek={canPlay ? seek : undefined}
              label={`Seek, ${title}`} markers={markers} regions={regions} height={48}
              pendingFrom={forced === "streaming" ? (madeUpTo ?? dur) : undefined} />
          )}
          <div className="flex flex-wrap items-center gap-x-3 gap-y-2 text-small text-fg-muted">
            {timeText}
            <span className="flex-1" />
            {speeds.length > 1 && (
              <span className="w-24 shrink-0"><Select size="sm" aria-label="Playback speed" value={String(rate)} disabled={!canPlay}
                onChange={(e) => setRate(Number(e.target.value))}
                options={speeds.map((s) => ({ value: String(s), label: `${s}×` }))} /></span>
            )}
            {state === "unavailable" ? (
              <span className="text-fg-muted">{unavailableReason ?? "This audio cannot be played or downloaded."}</span>
            ) : onDownload && (
              <span className="flex items-center gap-2">
                {downloadNote && <span>{downloadNote}</span>}
                <Button size="sm" variant="secondary" onClick={onDownload} leading={<DownloadIcon width={14} height={14} />} className="pointer-coarse:h-11">Download</Button>
              </span>
            )}
          </div>
        </>
      )}
      {/* State changes worth hearing; not the clock, and not play or pause, which the button already says. */}
      <span role="status" className="sr-only">{notice ? `${title}: ${STATE_WORD[state]}.${state === "error" ? ` ${errorText}` : ""}${state === "unavailable" && unavailableReason ? ` ${unavailableReason}` : ""}` : ""}</span>
    </section>
  );
}
```

## tattva/molecules/AutonomyLevel.tsx

```tsx
import { useId, useState } from "react";
import { cn } from "../lib/cn";
import { SegmentedControl } from "../atoms/SegmentedControl";

export type Altitude = "summary" | "steps" | "everything";

export interface AutonomyLevelProps {
  /** Selected view (controlled). */
  value?: Altitude;
  /** Starting view when uncontrolled. */
  defaultValue?: Altitude;
  onChange?: (value: Altitude) => void;
  /** Replace the names of the views. */
  labels?: Partial<Record<Altitude, string>>;
  /** Replace the line that explains each view. */
  hints?: Partial<Record<Altitude, string>>;
  /** Accessible name for the group. */
  label?: string;
  className?: string;
}

const defaultLabels: Record<Altitude, string> = { summary: "Summary", steps: "Steps", everything: "Everything" };
const defaultHints: Record<Altitude, string> = {
  summary: "A short account of the result and anything that needs you.",
  steps: "Each thing the assistant did, one line at a time.",
  everything: "Every detail, including what it read and tried.",
};
const order: Altitude[] = ["summary", "steps", "everything"];

/** Switch between three levels of detail about what an agent did. Arrow keys move and select. */
export function AutonomyLevel({ value, defaultValue = "summary", onChange, labels, hints, label = "How much detail", className }: AutonomyLevelProps) {
  const [inner, setInner] = useState<Altitude>(defaultValue);
  const current = value ?? inner;
  const uid = useId();
  const l = { ...defaultLabels, ...labels };
  const h = { ...defaultHints, ...hints };
  return (
    <div className={cn("min-w-0", className)}>
      <div className="max-w-full overflow-x-auto">
        <SegmentedControl label={label} value={current} options={order.map((k) => ({ value: k, label: l[k] }))}
          onChange={(v) => { if (value === undefined) setInner(v); onChange?.(v); }} />
      </div>
      <p id={`${uid}-hint`} className="mt-1.5 text-small leading-4 text-fg-muted">{h[current]}</p>
    </div>
  );
}

/** @deprecated Renamed to AutonomyLevel in 0.29.0, to match the name other design systems use. The old name goes in 0.31.0. */
export const AltitudeToggle = AutonomyLevel;
/** @deprecated Renamed to AutonomyLevelProps in 0.29.0. */
export type AltitudeToggleProps = AutonomyLevelProps;
```

## tattva/molecules/AvatarStack.tsx

```tsx
import { cn } from "../lib/cn";
import { PlusIcon } from "../lib/icons";
import { Avatar } from "../atoms/Avatar";

export interface AvatarStackPerson {
  name: string;
  kind?: "user" | "ai";
}

export interface AvatarStackProps {
  /** People to show, most relevant first. Only the first `max` are drawn. */
  people: AvatarStackPerson[];
  /** Everyone counted, including the ones drawn. The number shown is the rest: `total - drawn`. Defaults to `people.length`. */
  total?: number;
  /** How many avatars to draw before the plus and the number. */
  max?: number;
  /** Word after the number, e.g. "others" or "more viewers". */
  label?: string;
  size?: "sm" | "md";
  /** Show large numbers short, e.g. 1.2k instead of 1,248. */
  compact?: boolean;
  className?: string;
}

/**
 * A few overlapping avatars, a plus, then how many more people there are.
 * The picture is decoration; screen readers hear one plain sentence with the names and the count.
 */
export function AvatarStack({ people, total = people.length, max = 3, label = "others", size = "md", compact = false, className }: AvatarStackProps) {
  const shown = people.slice(0, Math.max(0, max));
  const rest = Math.max(0, total - shown.length);
  const fmt = new Intl.NumberFormat(undefined, compact ? { notation: "compact", maximumFractionDigits: 1 } : undefined);
  const names = shown.map((p) => (p.kind === "ai" ? "Assistant" : p.name));
  const sentence = names.length === 0
    ? `${fmt.format(rest)} ${label}`
    : rest > 0 ? `${names.join(", ")} and ${fmt.format(rest)} ${label}` : names.length > 1 ? `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}` : names[0];
  const ring = "ring-2 ring-[var(--bg)]";
  const disc = size === "sm" ? "size-6" : "size-8";
  return (
    <span className={cn("inline-flex items-center gap-2", className)}>
      <span aria-hidden className="flex items-center">
        {shown.map((p, i) => (
          <Avatar key={`${p.name}-${i}`} kind={p.kind ?? "user"} name={p.name} size={size} className={cn(ring, i > 0 && (size === "sm" ? "-ml-1.5" : "-ml-2.5"))} />
        ))}
        {rest > 0 && (
          <span className={cn("inline-flex shrink-0 items-center justify-center rounded-full border border-line bg-surface text-fg-muted", disc, ring, shown.length > 0 && (size === "sm" ? "-ml-1.5" : "-ml-2.5"))}>
            <PlusIcon width={size === "sm" ? 11 : 14} height={size === "sm" ? 11 : 14} />
          </span>
        )}
      </span>
      {rest > 0 && (
        <span aria-hidden className={cn("tabular-nums text-fg", size === "sm" ? "text-small leading-4" : "text-body leading-5")}>
          <span className="font-semibold">{fmt.format(rest)}</span> <span className="text-fg-muted">{label}</span>
        </span>
      )}
      <span className="sr-only">{sentence}</span>
    </span>
  );
}
```

## tattva/molecules/BackgroundRunChip.tsx

```tsx
import type { ButtonHTMLAttributes } from "react";
import { cn } from "../lib/cn";
import { AgentStatusIcon, type AgentStatus } from "../atoms/AgentStatusIcon";

export interface BackgroundRunChipProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
  /** Name of the run, such as "Trip plan". */
  name: string;
  status: AgentStatus;
  /** Short plain status, such as "Booking hotels". Defaults to the status name. */
  statusText?: string;
  /** How long the run has been going, such as "4 min". */
  elapsed?: string;
  /** Pass true while the details panel this chip opens is showing. */
  expanded?: boolean;
}

const defaults: Record<AgentStatus, string> = {
  working: "Working", "needs-input": "Needs you", idle: "Waiting", completed: "Done", failed: "Failed", stopped: "Stopped",
};

/**
 * Compact chip for a run in the background. It is a button that opens details or a peek. A run that
 * needs a person gets the amber style; every other state stays neutral. The state icon, the words and
 * the time are read together as the button name.
 */
export function BackgroundRunChip({ name, status, statusText, elapsed, expanded, className, type = "button", ...rest }: BackgroundRunChipProps) {
  const needs = status === "needs-input";
  return (
    <button type={type} aria-expanded={expanded} {...rest}
      className={cn("inline-flex max-w-full min-w-0 cursor-pointer items-center gap-2 rounded-control border px-3 py-1.5 text-left text-small leading-4 transition-colors duration-[var(--dur-fast)]",
        needs ? "border-transparent bg-attention-soft text-attention-fg hover:opacity-90" : "border-line bg-surface text-fg hover:border-line-strong hover:bg-bg", className)}>
      <AgentStatusIcon status={status} size={14} className={needs ? "text-attention-fg" : undefined} />
      <span className="truncate font-medium">{name}</span>
      <span className={cn("truncate", needs ? "" : "text-fg-muted")}>{statusText ?? defaults[status]}</span>
      {elapsed && <span className={cn("shrink-0 tabular-nums", needs ? "" : "text-fg-subtle")}>{elapsed}</span>}
    </button>
  );
}
```

## tattva/molecules/Callout.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, InfoIcon } from "../lib/icons";
import type { Tone } from "../atoms/Badge";

const calloutTones: Record<Exclude<Tone, "neutral" | "accent" | "lime" | "unsure" | "celebrate">, string> = {
  info: "bg-info-soft text-info-fg",
  success: "bg-success-soft text-success-fg",
  warning: "bg-warning-soft text-warning-fg",
  danger: "bg-danger-soft text-danger-fg",
};

export function Callout({ tone = "info", title, children, action }: { tone?: keyof typeof calloutTones; title?: string; children?: ReactNode; action?: ReactNode }) {
  const Icon = tone === "info" || tone === "success" ? InfoIcon : AlertIcon;
  return (
    <div role={tone === "danger" ? "alert" : "status"} className={cn("flex gap-3 rounded-card p-4 text-body leading-5 max-sm:flex-wrap", calloutTones[tone])}>
      <Icon className="mt-0.5 shrink-0" />
      <div className="min-w-0 flex-1">
        {title && <p className="font-medium">{title}</p>}
        {children && <div className={cn(title && "mt-0.5")}>{children}</div>}
      </div>
      {action && <div className="shrink-0 max-sm:basis-full max-sm:pl-8">{action}</div>}
    </div>
  );
}
```

## tattva/molecules/CameraMovePicker.tsx

```tsx
import { useId, useRef, type KeyboardEvent, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { CheckIcon } from "../lib/icons";
import { SegmentedControl } from "../atoms/SegmentedControl";

export type CameraMove =
  | "static" | "pushIn" | "pullOut" | "panLeft" | "panRight" | "tiltUp" | "tiltDown"
  | "truckLeft" | "truckRight" | "orbit" | "crane" | "tracking" | "handheld";

export type CameraStrength = "subtle" | "strong";

/** Plain names and one-line meanings, so nobody has to learn camera jargon. */
export const cameraMoveInfo: Record<CameraMove, { name: string; meaning: string }> = {
  static: { name: "Static", meaning: "The camera does not move." },
  pushIn: { name: "Push in", meaning: "The camera moves closer to the subject." },
  pullOut: { name: "Pull out", meaning: "The camera moves away from the subject." },
  panLeft: { name: "Pan left", meaning: "The camera turns to the left." },
  panRight: { name: "Pan right", meaning: "The camera turns to the right." },
  tiltUp: { name: "Tilt up", meaning: "The camera tips upward." },
  tiltDown: { name: "Tilt down", meaning: "The camera tips downward." },
  truckLeft: { name: "Truck left", meaning: "The camera slides sideways to the left." },
  truckRight: { name: "Truck right", meaning: "The camera slides sideways to the right." },
  orbit: { name: "Orbit", meaning: "The camera circles around the subject." },
  crane: { name: "Crane up", meaning: "The camera rises above the scene." },
  tracking: { name: "Tracking", meaning: "The camera follows a moving subject." },
  handheld: { name: "Handheld", meaning: "A slight, natural shake, as if held by hand." },
};

export const allCameraMoves = Object.keys(cameraMoveInfo) as CameraMove[];

export interface CameraMovePickerProps {
  /** Chosen moves, in the order they were picked. Static is never combined with other moves. */
  value: CameraMove[];
  onChange: (value: CameraMove[]) => void;
  /** Visible name of the group. */
  label?: string;
  /** Moves to offer, in order. Defaults to all of them. */
  moves?: CameraMove[];
  /** How many moves can be combined. 1 makes it a single choice. */
  max?: number;
  /** Moves the chosen model can make. Others stay visible but cannot be picked. Leave out when all are supported. */
  supported?: CameraMove[];
  /** Why the other moves cannot be picked, for example "Not available with Reel Lite". */
  unsupportedReason?: string;
  /** How strong the move is. Shown only with onStrengthChange and a moving choice. */
  strength?: CameraStrength;
  onStrengthChange?: (strength: CameraStrength) => void;
  /** Turns the whole picker off, for example while a take is generating. */
  disabled?: boolean;
  className?: string;
}

/* ---------- diagrams ----------
   Each diagram is a frame with a subject and a fixed arrow for the move. On hover or keyboard focus the subject moves
   once, as it would appear in the shot, and holds. It never loops. Under reduced motion nothing moves: the arrow and the
   still subject already say what the move is. */

const still = "transition-transform duration-(--dur-slow) ease-(--ease-out) transform-fill origin-center motion-reduce:transition-none motion-reduce:translate-none! motion-reduce:scale-none! motion-reduce:rotate-none!";

/** Where the subject goes during the preview, written as utilities that only apply while the tile is hovered or focused. */
const subjectMotion: Record<CameraMove, string> = {
  static: "",
  pushIn: "group-hover:scale-150 group-focus-visible:scale-150",
  pullOut: "scale-150 group-hover:scale-100 group-focus-visible:scale-100",
  panLeft: "group-hover:translate-x-1.5 group-focus-visible:translate-x-1.5",
  panRight: "group-hover:-translate-x-1.5 group-focus-visible:-translate-x-1.5",
  tiltUp: "group-hover:translate-y-1 group-focus-visible:translate-y-1",
  tiltDown: "group-hover:-translate-y-1 group-focus-visible:-translate-y-1",
  truckLeft: "group-hover:translate-x-2 group-focus-visible:translate-x-2",
  truckRight: "group-hover:-translate-x-2 group-focus-visible:-translate-x-2",
  orbit: "group-hover:scale-x-50 group-focus-visible:scale-x-50",
  crane: "group-hover:translate-y-1 group-hover:scale-90 group-focus-visible:translate-y-1 group-focus-visible:scale-90",
  tracking: "",
  handheld: "group-hover:translate-x-0.5 group-hover:rotate-6 group-focus-visible:translate-x-0.5 group-focus-visible:rotate-6",
};

/** Background marks that move instead of the subject (the subject stays centred while the world slides by). */
const groundMotion: Partial<Record<CameraMove, string>> = {
  tracking: "group-hover:-translate-x-2 group-focus-visible:-translate-x-2",
};

/** The fixed arrow (or mark) for each move, drawn in a 32 by 24 box. */
const arrows: Record<CameraMove, ReactNode> = {
  static: <path d="M13 21h6M16 18v3" />,
  pushIn: <path d="M4 5l3 3M28 5l-3 3M4 19l3-3M28 19l-3-3" />,
  pullOut: <path d="M7 8L4 5M25 8l3-3M7 16l-3 3M25 16l3 3M4 7V5h2M28 7V5h-2M4 17v2h2M28 17v2h-2" />,
  panLeft: <path d="M11 21H5M7 19l-2 2 2 2" />,
  panRight: <path d="M21 21h6M25 19l2 2-2 2" />,
  tiltUp: <path d="M28 15V7M26 9l2-2 2 2" />,
  tiltDown: <path d="M28 9v8M26 15l2 2 2-2" />,
  truckLeft: <path d="M22 21H5M7 19l-2 2 2 2" />,
  truckRight: <path d="M10 21h17M25 19l2 2-2 2" />,
  orbit: <path d="M7 19c3 3 15 3 18 0M23 17l2 2-2.5 1" />,
  crane: <path d="M28 18V5M26 7l2-2 2 2" />,
  tracking: <path d="M21 4h6M25 2l2 2-2 2" />,
  handheld: <path d="M5 21l2-1.5 2 1.5 2-1.5 2 1.5" />,
};

function MoveDiagram({ move }: { move: CameraMove }) {
  return (
    <svg aria-hidden viewBox="0 0 32 24" className="h-6 w-8 shrink-0 overflow-visible" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
      <rect x="2" y="1.5" width="28" height="21" rx="2.5" strokeOpacity={0.35} />
      <g className={cn(still, groundMotion[move])} strokeOpacity={0.45}>
        <path d="M6 17h20" />
        {move === "tracking" && <path d="M9 15v4M15 15v4M21 15v4" />}
      </g>
      <g className={cn(still, subjectMotion[move])}>
        <circle cx="16" cy="9" r="2.2" fill="currentColor" stroke="none" />
        <path d="M13 15c.5-2.2 1.6-3.2 3-3.2s2.5 1 3 3.2" />
      </g>
      <g strokeWidth={1.75}>{arrows[move]}</g>
    </svg>
  );
}

/**
 * Pick a camera move by name, with a small diagram for each one. Moves can be combined up to `max`. Moves the model
 * cannot make stay visible, cannot be picked, and say why in words. The diagram moves once on hover or focus and never
 * under reduced motion.
 */
export function CameraMovePicker({
  value, onChange, label = "Camera move", moves = allCameraMoves, max = 1, supported, unsupportedReason = "Not available with this model",
  strength, onStrengthChange, disabled = false, className,
}: CameraMovePickerProps) {
  const uid = useId();
  const refs = useRef<(HTMLButtonElement | null)[]>([]);
  const single = max <= 1;
  const full = !single && value.length >= max;
  const isSupported = (m: CameraMove) => !supported || supported.includes(m);
  const unsupported = moves.filter((m) => !isSupported(m));

  // Why a tile cannot be pressed right now, or null when it can.
  const blockedWhy = (m: CameraMove): string | null => {
    if (disabled) return null;
    if (!isSupported(m)) return unsupportedReason;
    if (full && !value.includes(m) && m !== "static") return `Up to ${max} moves. Remove one to pick another.`;
    return null;
  };

  const toggle = (m: CameraMove) => {
    if (disabled || blockedWhy(m)) return;
    const on = value.includes(m);
    if (single) { onChange(on && m !== "static" ? ["static"] : [m]); return; }
    if (m === "static") { onChange(["static"]); return; }
    const rest = value.filter((x) => x !== "static");
    const next = on ? rest.filter((x) => x !== m) : [...rest, m];
    onChange(next.length ? next : ["static"]);
  };

  // One tab stop: the first chosen tile, or the first tile. Arrow keys move between tiles, Home and End jump.
  const firstChosen = Math.max(0, moves.findIndex((m) => value.includes(m)));
  const onKey = (i: number, e: KeyboardEvent<HTMLButtonElement>) => {
    const n = moves.length;
    let next = i;
    if (e.key === "ArrowRight" || e.key === "ArrowDown") next = (i + 1) % n;
    else if (e.key === "ArrowLeft" || e.key === "ArrowUp") next = (i - 1 + n) % n;
    else if (e.key === "Home") next = 0;
    else if (e.key === "End") next = n - 1;
    else return;
    e.preventDefault();
    refs.current[next]?.focus();
  };

  const chosen = value.filter((m) => moves.includes(m)).map((m) => cameraMoveInfo[m].name);
  const summary = chosen.length === 0 ? "No move chosen" : chosen.length === 1 ? chosen[0] : `${chosen.slice(0, -1).join(", ")}, then ${chosen[chosen.length - 1].toLowerCase()}`;
  const moving = value.some((m) => m !== "static");
  const reasonId = `${uid}-reason`;
  const limitId = `${uid}-limit`;

  return (
    <div className={cn("min-w-0 space-y-2", className)}>
      <div className="flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1">
        <span id={`${uid}-label`} className="text-small leading-4 font-medium text-fg">{label}</span>
        <span className="text-small leading-4.5 text-fg-muted">
          {single ? "Pick one" : `Combine up to ${max}`}<span aria-hidden> · </span><span className="text-fg">{summary}</span>
        </span>
      </div>
      <div role="group" aria-labelledby={`${uid}-label`} aria-describedby={!single ? limitId : undefined}
        className={cn("grid grid-cols-3 gap-1.5 sm:grid-cols-4", disabled && "opacity-(--disabled-opacity)")}>
        {moves.map((m, i) => {
          const on = value.includes(m);
          const why = blockedWhy(m);
          const off = disabled || !!why;
          const info = cameraMoveInfo[m];
          return (
            <button key={m} ref={(el) => { refs.current[i] = el; }} type="button" aria-pressed={on} aria-disabled={off || undefined}
              aria-describedby={!isSupported(m) ? reasonId : undefined} title={why ?? info.meaning}
              tabIndex={i === firstChosen ? 0 : -1} onClick={() => toggle(m)} onKeyDown={(e) => onKey(i, e)}
              className={cn("group tap relative flex min-w-0 flex-col items-center gap-1 rounded-card border px-1.5 pt-2 pb-1.5 text-small leading-4.5 font-medium",
                "transition-colors duration-(--dur-fast)",
                on ? "border-fg bg-surface text-fg shadow-sm" : "border-line text-fg-muted",
                off ? "cursor-not-allowed opacity-(--disabled-opacity)" : "cursor-pointer hover:bg-hover hover:text-fg")}>
              {on && <CheckIcon width={12} height={12} className="absolute top-1 right-1" />}
              <MoveDiagram move={m} />
              <span className="max-w-full truncate">{info.name}</span>
              {!isSupported(m) && <span className="sr-only">, not available</span>}
            </button>
          );
        })}
      </div>
      {!single && <p id={limitId} className={cn("text-small leading-4.5", full ? "text-fg" : "text-fg-muted")}>
        {full ? `${max} of ${max} moves chosen. Remove one to pick another.` : "Static cannot be combined with other moves."}
      </p>}
      {unsupported.length > 0 && (
        <p id={reasonId} className="text-small leading-4.5 text-fg-muted">
          {unsupported.map((m) => cameraMoveInfo[m].name).join(", ")}: {unsupportedReason.charAt(0).toLowerCase() + unsupportedReason.slice(1)}.
        </p>
      )}
      {onStrengthChange && moving && (
        <div className="flex flex-wrap items-center gap-2 pt-1">
          <span className="text-small leading-4.5 text-fg-muted">Strength</span>
          <SegmentedControl size="sm" label="Camera move strength" value={strength ?? "subtle"} onChange={onStrengthChange}
            options={[{ value: "subtle", label: "Subtle" }, { value: "strong", label: "Strong" }]} />
        </div>
      )}
    </div>
  );
}
```

## tattva/molecules/CardRow.tsx

```tsx
import { Children, type ReactNode } from "react";
import { cn } from "../lib/cn";

export interface CardRowProps {
  /** Names the group for screen readers: "Hotels in Paris". */
  label: string;
  /** Two or more ResultCards with the same fields. */
  children: ReactNode;
  /** Narrowest a card may get on wide screens before the row wraps, in the spacing scale (60 is 240px). */
  minCardWidth?: 52 | 60 | 72;
  className?: string;
}

const MIN: Record<NonNullable<CardRowProps["minCardWidth"]>, string> = {
  52: "sm:grid-cols-[repeat(auto-fill,minmax(calc(var(--spacing)*52),1fr))]",
  60: "sm:grid-cols-[repeat(auto-fill,minmax(calc(var(--spacing)*60),1fr))]",
  72: "sm:grid-cols-[repeat(auto-fill,minmax(calc(var(--spacing)*72),1fr))]",
};

/**
 * Two or more results of the same kind side by side: a grid on wide screens and a row that swipes sideways on a
 * phone, with the next card peeking in so people can tell there is more. A list, so its length is announced.
 */
export function CardRow({ label, children, minCardWidth = 60, className }: CardRowProps) {
  const items = Children.toArray(children);
  return (
    <ul aria-label={label} tabIndex={0}
      className={cn(
        "m-0 flex list-none snap-x snap-mandatory gap-3 overflow-x-auto p-0 pb-2 [scrollbar-width:thin] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--focus-ring)]",
        "sm:grid sm:snap-none sm:overflow-visible sm:pb-0", MIN[minCardWidth], className,
      )}>
      {/* On a phone the next card peeks in to show there is more; a single card takes the full width. */}
      {items.map((child, i) => (
        <li key={i} className={cn("flex shrink-0 snap-start sm:w-auto", items.length > 1 ? "w-[85%]" : "w-full")}>{child}</li>
      ))}
    </ul>
  );
}
```

## tattva/molecules/ChartFrame.tsx

```tsx
import { useEffect, useId, useRef, useState, type ReactNode, type RefObject } from "react";
import { cn } from "../lib/cn";
import { Button } from "../atoms/Button";
import { Skeleton } from "../atoms/Skeleton";
import { ErrorState } from "./ErrorState";
import { DataTable, type DataTableProps } from "./DataTable";

/* ---------- shared chart helpers (used by the chart organisms) ---------- */

/** Measures the content width of an element and keeps it current with a ResizeObserver. Returns 0 until measured. */
export function useElementWidth<T extends HTMLElement>(): [RefObject<T | null>, number] {
  const ref = useRef<T | null>(null);
  const [w, setW] = useState(0);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    setW(Math.floor(el.getBoundingClientRect().width));
    if (typeof ResizeObserver === "undefined") return;
    const ro = new ResizeObserver((entries) => {
      const next = Math.floor(entries[0].contentRect.width);
      setW((p) => (p === next ? p : next));
    });
    ro.observe(el);
    return () => ro.disconnect();
  }, []);
  return [ref, w];
}

/** True when the person asked the system to reduce motion. Charts skip draw-in animation when it is true. */
export function usePrefersReducedMotion(): boolean {
  const [r, setR] = useState(() => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches);
  useEffect(() => {
    if (typeof window.matchMedia !== "function") return;
    const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
    const on = () => setR(mq.matches);
    mq.addEventListener("change", on);
    return () => mq.removeEventListener("change", on);
  }, []);
  return r;
}

const round = (n: number) => Math.round(n * 1e9) / 1e9;

/** Round tick values that cover min to max, never more than 6. */
export function niceTicks(min: number, max: number, target = 5): number[] {
  if (!(max > min)) max = min + 1;
  let raw = (max - min) / Math.max(1, target - 1);
  for (let guard = 0; guard < 8; guard++) {
    const mag = 10 ** Math.floor(Math.log10(raw));
    const n = raw / mag;
    const step = (n <= 1 ? 1 : n <= 2 ? 2 : n <= 2.5 ? 2.5 : n <= 5 ? 5 : 10) * mag;
    const lo = Math.floor(min / step + 1e-9) * step;
    const hi = Math.ceil(max / step - 1e-9) * step;
    const out: number[] = [];
    for (let v = lo; v <= hi + step / 1e6; v += step) out.push(round(v));
    if (out.length <= 6) return out;
    raw = step * 1.01;
  }
  return [min, max];
}

/** Default number formatter: thousands separators and at most two decimals. */
export function formatNumber(n: number): string {
  return n.toLocaleString("en-US", { maximumFractionDigits: 2 });
}

/* ---------- ChartFrame ---------- */

export interface ChartEmptyState {
  /** What is missing, for example "No requests in this window." */
  what: string;
  /** Why it is empty, for example "Filters exclude every model." */
  why: string;
  /** The action that fixes it, such as a Button. */
  action?: ReactNode;
}

export interface ChartFrameProps {
  /** Chart title. Set in the serif. */
  title: string;
  /** States the unit and the time window, for example "p95 latency in ms, last 7 days". */
  subtitle?: string;
  /** Plain-language reading of the chart, shown above it in body text. Say what the data shows. */
  summary?: ReactNode;
  /** The chart. Receives the full width of the frame. */
  children?: ReactNode;
  /** Data for the table view. Without it the toggle is hidden. */
  table?: DataTableProps;
  /** Source line, for example "Source: gateway logs". */
  source?: string;
  /** Caveat line, for example "Latest point is still streaming." */
  note?: string;
  /** Rendered above the chart, usually a ChartLegend. */
  legend?: ReactNode;
  /** Data state. Anything other than "ready" replaces the chart. */
  state?: "ready" | "loading" | "empty" | "error";
  /** Content for the empty state. */
  empty?: ChartEmptyState;
  /** Message for the error state. */
  errorMessage?: string;
  /** Called by the retry button in the error state. */
  onRetry?: () => void;
  /** Shown instead of the chart when the frame is narrower than minWidth, usually a Sparkline. */
  fallback?: ReactNode;
  /** Narrowest width in px at which the chart is drawn. */
  minWidth?: number;
  /** Start in the table view. */
  defaultView?: "chart" | "table";
  /** Adds a Download data button and calls this when pressed. */
  onDownload?: () => void;
  /** Height in px reserved for loading and empty states so the layout does not jump. */
  stateHeight?: number;
  className?: string;
}

/**
 * Frame for every chart: serif title, unit and time window, a visible text summary, the chart, a source line
 * and a "View as table" toggle. It also owns the loading, empty and error states and the collapse-below-minimum fallback.
 */
export function ChartFrame({
  title, subtitle, summary, children, table, source, note, legend, state = "ready", empty, errorMessage, onRetry,
  fallback, minWidth = 280, defaultView = "chart", onDownload, stateHeight = 200, className,
}: ChartFrameProps) {
  const [view, setView] = useState<"chart" | "table">(defaultView);
  const [bodyRef, width] = useElementWidth<HTMLDivElement>();
  const id = useId();
  const showTable = view === "table" && !!table;
  const collapsed = !showTable && !!fallback && width > 0 && width < minWidth;

  let body: ReactNode;
  if (state === "loading") {
    body = (
      <div role="status" aria-label="Loading chart" style={{ height: stateHeight }} className="relative flex items-end gap-3 border-b border-l border-[var(--chart-axis)] px-4 pb-0">
        <span className="sr-only">Loading chart data</span>
        {[0.45, 0.7, 0.55, 0.85, 0.6, 0.75].map((h, i) => <Skeleton key={i} className="flex-1 rounded-b-none" style={{ height: `${h * 100}%` }} />)}
      </div>
    );
  } else if (state === "empty") {
    body = (
      <div style={{ minHeight: stateHeight }} className="flex flex-col items-start justify-center gap-2 rounded-card border border-dashed border-line-strong px-5 py-6 text-compact leading-5">
        <p className="font-medium text-fg">{empty?.what ?? "No data to show."}</p>
        {empty?.why && <p className="text-fg-muted">{empty.why}</p>}
        {empty?.action}
      </div>
    );
  } else if (state === "error") {
    body = <div style={{ minHeight: stateHeight }}><ErrorState title="This chart did not load" message={errorMessage ?? "The data could not be fetched. Nothing you entered was lost."} onRetry={onRetry} /></div>;
  } else if (showTable && table) {
    body = <DataTable {...table} maxHeight={table.maxHeight ?? 320} stickyHeader={table.stickyHeader ?? true} />;
  } else if (collapsed) {
    body = (
      <div className="flex flex-wrap items-center gap-3">
        {fallback}
        <p className="text-small leading-4 text-fg-muted">Too narrow for the full chart. Use the table view for every value.</p>
      </div>
    );
  } else {
    body = <>{legend}{children}</>;
  }

  return (
    <figure className={cn("m-0 min-w-0 rounded-card border border-line bg-surface p-4 sm:p-5", className)} aria-busy={state === "loading" || undefined}>
      <figcaption>
        <div className="flex flex-wrap items-start justify-between gap-x-4 gap-y-2">
          <div className="min-w-0">
            <h3 className="font-serif! text-lead-lg leading-6 [overflow-wrap:anywhere]">{title}</h3>
            {subtitle && <p className="mt-0.5 text-small leading-5 text-fg-muted">{subtitle}</p>}
          </div>
          {state === "ready" && (table || onDownload) && (
            <div className="flex shrink-0 flex-wrap gap-2">
              {table && (
                <Button size="sm" variant="secondary" aria-controls={`${id}-body`} onClick={() => setView(showTable ? "chart" : "table")}>
                  {showTable ? "View as chart" : "View as table"}
                </Button>
              )}
              {onDownload && <Button size="sm" variant="ghost" onClick={onDownload}>Download data</Button>}
            </div>
          )}
        </div>
        {summary && state === "ready" && <p className="mt-3 max-w-[68ch] text-compact leading-6 text-fg-muted">{summary}</p>}
      </figcaption>
      <div ref={bodyRef} id={`${id}-body`} className="mt-3 min-w-0">{body}</div>
      {(source || note) && state === "ready" && (
        <p className="mt-3 text-micro leading-4 text-fg-subtle">{[source, note].filter(Boolean).join(" ")}</p>
      )}
    </figure>
  );
}
```

## tattva/molecules/ChartRangeBrush.tsx

```tsx
import { useId, useRef } from "react";
import type { HTMLAttributes, KeyboardEvent, PointerEvent } from "react";
import { cn } from "../lib/cn";
import { RefreshIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { useElementWidth } from "./ChartFrame";

export interface ChartRangeBrushProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
  /** The overview series, one value per position. Drawn as a small line. */
  values: number[];
  /** One label per value, for example dates. Used in the range text and read out by the handles. */
  labels?: string[];
  /** Start and end index, both inclusive. The parent owns it. */
  range: [number, number];
  /** Called with the new start and end index after a drag, a key press or Reset. */
  onRangeChange: (range: [number, number]) => void;
  /** The fewest points the window may hold. */
  minSpan?: number;
  /** Name of the control, for example "Date range". */
  label: string;
  /** Turns an index into the text shown and read out. Defaults to labels[index], or the position counted from 1. */
  formatLabel?: (index: number) => string;
  /** Turns the number of points in the window into words, for example (n) => `${n} days`. */
  formatCount?: (count: number) => string;
  className?: string;
}

type Part = "start" | "end" | "window";

const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));
const STRIP_H = 48;

/**
 * A strip under a long chart that narrows it to a range. Two slider handles and a draggable window sit on a small overview line.
 * Fully controlled: it never holds the range itself. Drags move instantly, with no easing, because they are direct manipulation.
 */
export function ChartRangeBrush({ values, labels, range, onRangeChange, minSpan = 2, label, formatLabel, formatCount, className, ...rest }: ChartRangeBrushProps) {
  const labelId = useId();
  const [stripRef, measured] = useElementWidth<HTMLDivElement>();
  const startRef = useRef<HTMLDivElement | null>(null);
  const endRef = useRef<HTMLDivElement | null>(null);
  const drag = useRef<{ part: Part; s0: number; e0: number; f0: number; id: number } | null>(null);

  const n = values.length;
  const last = Math.max(0, n - 1);
  // gap is the smallest allowed end minus start; minSpan counts points, so it is one less.
  const gap = clamp(Math.round(minSpan) - 1, 0, last);
  let s = clamp(Math.round(range[0]), 0, last);
  let e = clamp(Math.round(range[1]), 0, last);
  if (s > e) [s, e] = [e, s];
  if (e - s < gap) { e = Math.min(last, s + gap); s = Math.max(0, e - gap); }
  const latest = useRef<[number, number]>([s, e]);
  latest.current = [s, e];

  const name = (i: number) => (formatLabel ? formatLabel(i) : labels?.[i] ?? String(i + 1));
  const count = e - s + 1;
  const countText = formatCount ? formatCount(count) : `${count} ${count === 1 ? "point" : "points"}`;
  const pageStep = Math.max(1, Math.round(last * 0.1));
  const full = s === 0 && e === last;

  const emit = (ns: number, ne: number) => {
    const [cs, ce] = latest.current;
    if (ns !== cs || ne !== ce) onRangeChange([ns, ne]);
  };
  const moveStart = (i: number) => emit(clamp(i, 0, e - gap), e);
  const moveEnd = (i: number) => emit(s, clamp(i, s + gap, last));
  const moveWindow = (i: number) => { const ns = clamp(i, 0, last - (e - s)); emit(ns, ns + (e - s)); };

  const onKey = (part: Part) => (ev: KeyboardEvent<HTMLDivElement>) => {
    const cur = part === "end" ? e : s;
    const k = ev.key;
    let to: number;
    if (k === "ArrowRight" || k === "ArrowUp") to = cur + 1;
    else if (k === "ArrowLeft" || k === "ArrowDown") to = cur - 1;
    else if (k === "PageUp") to = cur + pageStep;
    else if (k === "PageDown") to = cur - pageStep;
    else if (k === "Home") to = 0;
    else if (k === "End") to = last;
    else return;
    ev.preventDefault();
    if (part === "start") moveStart(to);
    else if (part === "end") moveEnd(to);
    else moveWindow(to);
  };

  const fracAt = (clientX: number) => {
    const r = stripRef.current?.getBoundingClientRect();
    return r && r.width ? clamp((clientX - r.left) / r.width, 0, 1) : 0;
  };

  const down = (ev: PointerEvent<HTMLDivElement>) => {
    if ((ev.pointerType === "mouse" && ev.button !== 0) || last < 1) return;
    const hit = (ev.target as Element).closest("[data-part]")?.getAttribute("data-part") as Part | null;
    let part: Part;
    let [s0, e0] = [s, e];
    if (hit) part = hit;
    else {
      // A press on the bare track brings the nearer handle to that point and keeps dragging it.
      const i = Math.round(fracAt(ev.clientX) * last);
      part = Math.abs(i - s) <= Math.abs(i - e) ? "start" : "end";
      if (part === "start") s0 = clamp(i, 0, e - gap); else e0 = clamp(i, s + gap, last);
      emit(s0, e0);
      (part === "start" ? startRef : endRef).current?.focus();
    }
    ev.currentTarget.setPointerCapture(ev.pointerId);
    drag.current = { part, s0, e0, f0: fracAt(ev.clientX), id: ev.pointerId };
  };

  const move = (ev: PointerEvent<HTMLDivElement>) => {
    const d = drag.current;
    if (!d || d.id !== ev.pointerId) return;
    const f = fracAt(ev.clientX);
    const i = Math.round(f * last);
    if (d.part === "start") emit(clamp(i, 0, d.e0 - gap), d.e0);
    else if (d.part === "end") emit(d.s0, clamp(i, d.s0 + gap, last));
    else {
      const span = d.e0 - d.s0;
      const ns = clamp(d.s0 + Math.round((f - d.f0) * last), 0, last - span);
      emit(ns, ns + span);
    }
  };
  const up = (ev: PointerEvent<HTMLDivElement>) => { if (drag.current?.id === ev.pointerId) drag.current = null; };

  // Overview drawing, sized to the measured strip like LineChart.
  const w = measured || 320;
  const lo = n ? Math.min(...values) : 0;
  const hi = n ? Math.max(...values) : 1;
  const px = (i: number) => (last ? (i / last) * w : w / 2);
  const py = (v: number) => STRIP_H - 6 - ((v - lo) / (hi - lo || 1)) * (STRIP_H - 12);
  const d = values.map((v, i) => `${i ? "L" : "M"}${px(i).toFixed(1)} ${py(v).toFixed(1)}`).join("");
  const pct = (i: number) => `${last ? (i / last) * 100 : 0}%`;

  const handle = (part: "start" | "end") => {
    const isStart = part === "start";
    const i = isStart ? s : e;
    const word = isStart ? "Start" : "End";
    return (
      <div ref={isStart ? startRef : endRef} data-part={part} role="slider" tabIndex={0}
        aria-label={`${label}, ${word.toLowerCase()}`}
        aria-valuemin={isStart ? 0 : s + gap} aria-valuemax={isStart ? e - gap : last} aria-valuenow={i}
        aria-valuetext={`${word}, ${name(i)}`} aria-orientation="horizontal" onKeyDown={onKey(part)}
        // Each hit box sits mostly outside the window, so the two handles never cover each other.
        className={cn("group absolute inset-y-0 z-10 w-6 cursor-ew-resize rounded-sm pointer-coarse:w-11", isStart ? "ml-1.5 -translate-x-full" : "-ml-1.5")}
        style={{ left: pct(i) }}>
        <span aria-hidden className={cn("absolute inset-y-0 w-0.5 bg-fg forced-colors:bg-[CanvasText]", isStart ? "right-1.5 translate-x-1/2" : "left-1.5 -translate-x-1/2")} />
        <span aria-hidden className={cn("absolute top-1/2 h-5 w-2.5 -translate-y-1/2 rounded-full border border-line-strong bg-surface shadow-sm transition-colors duration-[var(--dur-fast)] group-hover:border-fg forced-colors:border-[CanvasText]", isStart ? "right-1.5 translate-x-1/2" : "left-1.5 -translate-x-1/2")} />
      </div>
    );
  };

  return (
    <div className={cn("flex min-w-0 flex-col gap-2", className)} {...rest}>
      <div className="flex flex-wrap items-center justify-between gap-x-3 gap-y-1">
        <p className="min-w-0 text-small">
          <span id={labelId} className="block font-medium text-fg">{label}</span>
          <span className="block text-fg-muted tabular-nums">{name(s)} to {name(e)}, {countText}</span>
        </p>
        <Button variant="secondary" size="sm" aria-disabled={full} leading={<RefreshIcon width={14} height={14} />}
          onClick={() => { if (!full) emit(0, last); }}
          className={cn("pointer-coarse:h-11", full && "cursor-default opacity-50")}>
          Reset
        </Button>
      </div>
      <div className="px-4.5 pointer-coarse:px-9.5">
        <div ref={stripRef} role="group" aria-labelledby={labelId} onPointerDown={down} onPointerMove={move} onPointerUp={up} onPointerCancel={up}
          className="relative h-12 touch-none select-none">
          <div aria-hidden className="absolute inset-0 overflow-hidden rounded-md bg-sunken ring-1 ring-line forced-colors:outline forced-colors:outline-1">
            <svg width={w} height={STRIP_H} viewBox={`0 0 ${w} ${STRIP_H}`} className="block max-w-full" focusable="false">
              <path d={d} fill="none" stroke="var(--chart-1)" strokeWidth={1.5} strokeLinejoin="round" strokeLinecap="round" />
              {/* Outside the window the line stays visible but is washed back with the strip colour. */}
              <rect x={0} y={0} width={px(s)} height={STRIP_H} fill="var(--surface-sunken)" fillOpacity={0.72} />
              <rect x={px(e)} y={0} width={Math.max(0, w - px(e))} height={STRIP_H} fill="var(--surface-sunken)" fillOpacity={0.72} />
            </svg>
          </div>
          {handle("start")}
          <div data-part="window" role="slider" tabIndex={0} aria-label={`${label}, whole window`}
            aria-valuemin={0} aria-valuemax={last - (e - s)} aria-valuenow={s} aria-valuetext={`${name(s)} to ${name(e)}`} aria-orientation="horizontal"
            onKeyDown={onKey("window")}
            className="absolute inset-y-0 cursor-grab border-y-2 border-chart-1 active:cursor-grabbing forced-colors:border-[Highlight]"
            style={{ left: pct(s), width: `calc(${pct(e)} - ${pct(s)})`, backgroundColor: "color-mix(in oklab, var(--chart-1) 12%, transparent)" }} />
          {handle("end")}
        </div>
      </div>
    </div>
  );
}
```

## tattva/molecules/ChecklistRow.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { AttentionDot } from "../atoms/AttentionDot";

export interface ChecklistItem {
  id: string;
  title: ReactNode;
  badge?: ReactNode;
  status?: string;
  /** Needs a person: amber dot, status in the foreground colour. */
  attention?: boolean;
}

export function ChecklistRow({ item: i }: { item: ChecklistItem }) {
  return (
    <li className="flex items-center gap-3 border-b border-line px-1 py-3 text-body-lg">
      <span className="flex min-w-0 flex-1 items-center gap-2.5">{i.title}{i.badge}</span>
      {i.status && (
        <span className={cn("flex items-center gap-1.5 text-small leading-4", i.attention ? "text-fg" : "text-fg-subtle")}>
          {i.attention && <AttentionDot />}{i.status}
        </span>
      )}
    </li>
  );
}
```

## tattva/molecules/CitationHoverCard.tsx

```tsx
import { useEffect, useId, useRef, useState, type ReactNode } from "react";
import { GlobeIcon } from "../lib/icons";
import { Badge } from "../atoms/Badge";

export interface CitationHoverCardProps {
  /** Citation number shown in the marker. */
  n: number;
  title: string;
  domain: string;
  /** Where "Open source" goes. */
  url: string;
  snippet?: string;
  /** Trust label as text, for example "Government" or "Academic". */
  trust?: string;
  /** Favicon or any small mark. Falls back to the first letter of the domain. */
  favicon?: ReactNode;
  openLabel?: string;
  className?: string;
}

/**
 * Numbered citation that opens a source card on hover, on keyboard focus and on click or tap.
 * Click pins the card so it stays open on touch. Escape closes it and returns focus to the marker.
 */
export function CitationHoverCard({ n, title, domain, url, snippet, trust, favicon, openLabel = "Open source", className }: CitationHoverCardProps) {
  const [open, setOpen] = useState(false);
  const [pinned, setPinned] = useState(false);
  const root = useRef<HTMLSpanElement>(null);
  const btn = useRef<HTMLButtonElement>(null);
  const id = useId();
  const close = () => { setOpen(false); setPinned(false); };

  useEffect(() => {
    if (!open) return;
    const onDoc = (e: MouseEvent) => { if (root.current && !root.current.contains(e.target as Node)) close(); };
    const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") { close(); btn.current?.focus(); } };
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => { document.removeEventListener("mousedown", onDoc); document.removeEventListener("keydown", onKey); };
  }, [open]);

  return (
    <span ref={root} className={`relative mx-0.5 inline-block align-baseline ${className ?? ""}`}
      onMouseEnter={() => setOpen(true)}
      onMouseLeave={() => { if (!pinned) setOpen(false); }}
      onFocus={() => setOpen(true)}
      onBlur={(e) => { if (!root.current?.contains(e.relatedTarget as Node)) close(); }}>
      <button ref={btn} type="button" aria-label={`Source ${n}: ${domain}`} aria-expanded={open} aria-controls={id}
        onClick={() => { if (open && pinned) close(); else { setOpen(true); setPinned(true); } }}
        className="inline-flex h-4 min-w-4 -translate-y-0.5 cursor-pointer items-center justify-center rounded bg-accent-soft px-1 text-nano font-semibold text-accent-fg hover:bg-accent hover:text-on-accent">
        {n}
      </button>
      {open && (
        <span id={id} role="group" aria-label={`Source ${n}: ${title}`} className="absolute bottom-full left-1/2 z-30 block w-72 max-w-[calc(100vw-2rem)] -translate-x-1/2 pb-2 max-sm:fixed max-sm:inset-x-4 max-sm:bottom-[max(1rem,var(--safe-bottom))] max-sm:left-4 max-sm:w-auto max-sm:max-w-none max-sm:translate-x-0 max-sm:pb-0">
          <span className="block animate-rise rounded-overlay border border-line bg-raised p-3 text-left text-body leading-5 font-normal shadow-lg">
            <span className="flex items-center gap-2 text-small leading-4 text-fg-subtle">
              <span className="flex size-5 shrink-0 items-center justify-center rounded-full bg-sunken text-nano font-semibold text-fg-muted" aria-hidden="true">
                {favicon ?? (domain ? domain.charAt(0).toUpperCase() : <GlobeIcon width={11} height={11} />)}
              </span>
              <span className="min-w-0 flex-1 truncate">{domain}</span>
              {trust && <Badge>{trust}</Badge>}
            </span>
            <span className="mt-2 block font-medium text-fg">{title}</span>
            {snippet && <span className="mt-1 line-clamp-3 block text-small leading-4 text-fg-muted">{snippet}</span>}
            <a href={url} target="_blank" rel="noreferrer noopener" className="mt-2.5 inline-block text-small leading-4 font-medium text-fg underline underline-offset-2">{openLabel}</a>
          </span>
        </span>
      )}
    </span>
  );
}
```

## tattva/molecules/ClaimCheck.tsx

```tsx
import { useId, useState } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, SourceIcon } from "../lib/icons";
import { Button } from "../atoms/Button";

export interface Claim {
  id: string;
  text: string;
  /** unsupported: no source backs it up. disputed: a source says otherwise. */
  status: "unsupported" | "disputed";
  /** Why it was flagged, in plain words. */
  note?: string;
}

/** Plain text, or a claim the check flagged. */
export type ClaimSegment = string | Claim;

export interface ClaimCheckLabels {
  /** Receives how many claims were flagged. */
  summary?: (n: number) => string;
  unsupported?: string;
  disputed?: string;
  askSource?: string;
  remove?: string;
  close?: string;
  /** Prefix of each mark's name, before the claim: "Why it is flagged". */
  why?: string;
}

export interface ClaimCheckProps {
  /** The reply, as plain text and flagged claims in reading order. */
  segments: ClaimSegment[];
  /** Asks the assistant to back the claim up. The button is hidden when omitted. */
  onAskSource?: (id: string) => void;
  /** Takes the claim out of the reply. The button is hidden when omitted. */
  onRemove?: (id: string) => void;
  labels?: ClaimCheckLabels;
  className?: string;
}

/**
 * A reply with the sentences no source backs up marked in place. The warning mark after each one is a button that
 * opens a short note under the reply: why it was flagged, and what to do (ask for a source, or take it out). A line above the
 * reply says how many were flagged, so nobody has to scan for the marks. Marks are a dotted underline plus a small
 * icon, never colour alone.
 */
export function ClaimCheck({ segments, onAskSource, onRemove, labels, className }: ClaimCheckProps) {
  const l = {
    summary: (n: number) => (n === 1 ? "1 claim has no source. Check it before you rely on it." : `${n} claims have no source. Check them before you rely on them.`),
    unsupported: "No source backs this up.", disputed: "A source says otherwise.", askSource: "Ask for a source", remove: "Take it out", close: "Close", why: "Why it is flagged", ...labels,
  };
  const [open, setOpen] = useState<string | null>(null);
  const uid = useId();
  const claims = segments.filter((s): s is Claim => typeof s !== "string");
  const cut = (t: string) => { const x = t.trimEnd(); const i = x.lastIndexOf(" "); return i < 0 ? 0 : i + 1; };
  const head = (t: string) => t.trimEnd().slice(0, cut(t));
  const tail = (t: string) => t.trimEnd().slice(cut(t));
  const mark = (c: Claim) => cn("rounded-sm underline decoration-dotted decoration-2 underline-offset-4", c.status === "disputed" ? "decoration-danger" : "decoration-attention", open === c.id && "bg-attention-soft");
  const current = claims.find((c) => c.id === open);
  return (
    <div className={cn("space-y-3", className)}>
      {claims.length > 0 && (
        <p className="m-0 flex items-start gap-2 text-small leading-5 text-attention-fg">
          <AlertIcon aria-hidden width={16} height={16} className="mt-0.5 shrink-0" />{l.summary(claims.length)}
        </p>
      )}
      <p className="m-0 text-body-lg leading-7 text-fg">
        {segments.map((s, i) => typeof s === "string" ? <span key={i}>{s}</span> : (
          // The claim stays ordinary text so it wraps like the words around it; the warning mark after it is the
          // button (a button cannot break across lines).
          <span key={s.id}>
            <span className={mark(s)}>{head(s.text)}</span>
            {/* The last word and the mark stay together, so the mark never starts a line on its own. */}
            <span className="whitespace-nowrap"><span className={mark(s)}>{tail(s.text)}</span><button type="button" aria-expanded={open === s.id} aria-controls={`${uid}-note`} onClick={() => setOpen(open === s.id ? null : s.id)}
              aria-label={`${s.status === "disputed" ? l.disputed : l.unsupported} ${l.why}: ${s.text}`}
              className={cn("-my-1 ml-0.5 inline-flex size-6 cursor-pointer items-center justify-center rounded-full align-middle transition-colors duration-(--dur-fast) hover:bg-attention-soft motion-reduce:transition-none",
                s.status === "disputed" ? "text-danger-fg" : "text-attention-fg", open === s.id && "bg-attention-soft")}>
              <AlertIcon aria-hidden width={14} height={14} />
            </button></span>
          </span>
        ))}
      </p>
      <div id={`${uid}-note`} aria-live="polite">
        {current && (
          <div className="rounded-control border border-attention-line bg-attention-soft p-3">
            <p className="m-0 text-small leading-5 font-medium text-fg">{current.status === "disputed" ? l.disputed : l.unsupported}</p>
            <p className="m-0 mt-1 text-small leading-5 text-fg-muted">“{current.text.trim()}”{current.note ? ` ${current.note}` : ""}</p>
            <div className="mt-3 flex flex-wrap gap-2">
              {onAskSource && <Button size="sm" variant="secondary" leading={<SourceIcon aria-hidden width={14} height={14} />} onClick={() => onAskSource(current.id)}>{l.askSource}</Button>}
              {onRemove && <Button size="sm" variant="ghost" onClick={() => { onRemove(current.id); setOpen(null); }}>{l.remove}</Button>}
              <Button size="sm" variant="ghost" onClick={() => setOpen(null)}>{l.close}</Button>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}
```

## tattva/molecules/ClipRangeSelector.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import type { HTMLAttributes, KeyboardEvent, PointerEvent, ReactNode } from "react";
import { cn } from "../lib/cn";
import { Button } from "../atoms/Button";
import { NumberField } from "../atoms/NumberField";
import { formatMediaTime } from "../atoms/Waveform";
import { speakClipTime } from "../atoms/FrameScrubber";

/** editing: the person can move the range. processing: the AI is working on it, so it is locked. applied: done, shown with a note. */
export type ClipRangeStatus = "editing" | "processing" | "applied";

export interface ClipRangeSelectorProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
  /** Length of the clip in seconds. */
  durationSec: number;
  /** Start and end, in seconds. The parent owns it. */
  range: [number, number];
  /** Called with the new start and end after a drag, a key press, a typed number or a playhead button. */
  onRangeChange: (range: [number, number]) => void;
  /** Name of the control, such as "Part to extend from". Also names the two handles. */
  label: string;
  /** Frames per second. Handles snap to whole frames and the arrow keys move one frame. */
  fps?: number;
  /** The longest range allowed, such as 10. The window cannot be stretched past it. */
  maxSpanSec?: number;
  /** The shortest range allowed. */
  minSpanSec?: number;
  /** Still images spread evenly over the clip, drawn as a filmstrip. */
  thumbnails?: string[];
  /** Draws one filmstrip tile for a time, when there are no image thumbnails. */
  renderThumbnail?: (seconds: number) => ReactNode;
  /** The player's position. Shows a playhead line and the "Set start here" and "Set end here" buttons. */
  playhead?: number;
  /** editing (default), processing (locked while the AI works) or applied. */
  status?: ClipRangeStatus;
  /** Words for the processing or applied status, such as "Extending from this part" or "Applied. Saved as version 3". */
  statusNote?: string;
  className?: string;
}

const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));
const TILES = 8;
type Part = "start" | "end" | "window";

/**
 * Picks the part of a clip to edit, extend from, loop or keep. Two named slider handles over a filmstrip, with the
 * longest allowed length built in: the window cannot pass it, so the person never meets a "too long" error later.
 * Number fields and "Set at playhead" buttons give a way to set it without dragging.
 */
export function ClipRangeSelector({
  durationSec, range, onRangeChange, label, fps, maxSpanSec, minSpanSec = 1, thumbnails, renderThumbnail, playhead, status = "editing",
  statusNote, className, ...rest
}: ClipRangeSelectorProps) {
  const labelId = useId();
  const strip = useRef<HTMLDivElement>(null);
  const drag = useRef<{ part: Part; s0: number; e0: number; t0: number; id: number } | null>(null);
  const [announce, setAnnounce] = useState("");
  const keyTimer = useRef<number | undefined>(undefined);

  const dur = Math.max(0, durationSec || 0);
  const unit = fps ? 1 / fps : 0.1;
  const snap = (t: number) => Math.round(t / unit) * unit;
  const maxSpan = Math.min(dur, maxSpanSec ?? dur);
  const minSpan = clamp(minSpanSec, 0, maxSpan);
  const locked = status === "processing";

  // Normalise what the parent passed. A range longer than the maximum keeps its start and snaps the end back.
  let s = clamp(Math.min(range[0], range[1]), 0, dur);
  let e = clamp(Math.max(range[0], range[1]), 0, dur);
  const rawSpan = e - s;
  const snapped = rawSpan > maxSpan + 1e-6;
  if (snapped) e = s + maxSpan;
  if (e - s < minSpan) { e = Math.min(dur, s + minSpan); s = Math.max(0, e - minSpan); }
  if (e > dur) { e = dur; s = Math.max(0, dur - (e - s)); }
  const span = e - s;
  const atMax = maxSpanSec !== undefined && span >= maxSpan - unit / 2;
  const latest = useRef<[number, number]>([s, e]);
  latest.current = [s, e];

  const spanWords = (a: number, b: number) => `${speakClipTime(a)} to ${speakClipTime(b)}, ${speakClipTime(b - a)} long`;
  const say = () => {
    const [a, b] = latest.current;
    setAnnounce(`${label}: ${spanWords(a, b)}${maxSpanSec !== undefined && b - a >= maxSpan - unit / 2 ? ", the most allowed" : ""}.`);
  };
  useEffect(() => () => window.clearTimeout(keyTimer.current), []);

  const emit = (ns: number, ne: number) => {
    const [cs, ce] = latest.current;
    const a = Number(snap(ns).toFixed(4));
    const b = Number(snap(ne).toFixed(4));
    if (Math.abs(a - cs) > 1e-6 || Math.abs(b - ce) > 1e-6) { latest.current = [a, b]; onRangeChange([a, b]); }
  };
  // Each handle stays within the min and max length of the other; it stops rather than pushing the other handle.
  const moveStart = (t: number, end = latest.current[1]) => emit(clamp(t, Math.max(0, end - maxSpan), end - minSpan), end);
  const moveEnd = (t: number, start = latest.current[0]) => emit(start, clamp(t, start + minSpan, Math.min(dur, start + maxSpan)));
  const moveWindow = (t: number, len = latest.current[1] - latest.current[0]) => { const ns = clamp(t, 0, dur - len); emit(ns, ns + len); };

  const onKey = (part: Part) => (ev: KeyboardEvent<HTMLDivElement>) => {
    if (locked || ev.altKey || ev.ctrlKey || ev.metaKey) return;
    const [cs, ce] = latest.current;
    const cur = part === "end" ? ce : cs;
    const big = ev.shiftKey ? 1 : unit;
    const k = ev.key;
    let to: number;
    if (k === "ArrowRight" || k === "ArrowUp") to = cur + big;
    else if (k === "ArrowLeft" || k === "ArrowDown") to = cur - big;
    else if (k === "PageUp") to = cur + 1;
    else if (k === "PageDown") to = cur - 1;
    else if (k === "Home") to = 0;
    else if (k === "End") to = dur;
    else return;
    ev.preventDefault();
    if (part === "start") moveStart(to);
    else if (part === "end") moveEnd(to);
    else moveWindow(to);
    // The length is said once the keys stop, not on every step.
    window.clearTimeout(keyTimer.current);
    keyTimer.current = window.setTimeout(say, 700);
  };

  const timeAt = (clientX: number) => {
    const r = strip.current?.getBoundingClientRect();
    return r && r.width ? clamp((clientX - r.left) / r.width, 0, 1) * dur : 0;
  };
  const down = (ev: PointerEvent<HTMLDivElement>) => {
    if (locked || dur <= 0 || (ev.pointerType === "mouse" && ev.button !== 0)) return;
    const hit = (ev.target as Element).closest("[data-part]")?.getAttribute("data-part") as Part | null;
    const t = timeAt(ev.clientX);
    const part: Part = hit ?? (Math.abs(t - s) <= Math.abs(t - e) ? "start" : "end");
    if (!hit) {
      if (part === "start") moveStart(t); else moveEnd(t);
      (ev.currentTarget.querySelector(`[data-part="${part}"]`) as HTMLElement | null)?.focus();
    }
    ev.currentTarget.setPointerCapture(ev.pointerId);
    drag.current = { part, s0: latest.current[0], e0: latest.current[1], t0: t, id: ev.pointerId };
  };
  const move = (ev: PointerEvent<HTMLDivElement>) => {
    const d = drag.current;
    if (!d || d.id !== ev.pointerId) return;
    const t = timeAt(ev.clientX);
    if (d.part === "start") moveStart(t, d.e0);
    else if (d.part === "end") moveEnd(t, d.s0);
    else moveWindow(d.s0 + (t - d.t0), d.e0 - d.s0);
  };
  const up = (ev: PointerEvent<HTMLDivElement>) => {
    if (drag.current?.id !== ev.pointerId) return;
    drag.current = null;
    say();
  };

  const pct = (t: number) => `${dur ? (clamp(t, 0, dur) / dur) * 100 : 0}%`;
  // "0:04.5": the clock with tenths, because generated clips are short.
  const fmt = (t: number) => { const r = Math.round(t * 10) / 10; return `${formatMediaTime(Math.floor(r))}.${Math.round((r - Math.floor(r)) * 10)}`; };

  const tile = (i: number) => {
    const t = ((i + 0.5) / TILES) * dur;
    if (thumbnails?.length) {
      const j = clamp(Math.round((i / (TILES - 1)) * (thumbnails.length - 1)), 0, thumbnails.length - 1);
      return <img src={thumbnails[j]} alt="" className="size-full object-cover" />;
    }
    return renderThumbnail?.(t);
  };

  const handle = (part: "start" | "end") => {
    const isStart = part === "start";
    const t = isStart ? s : e;
    const word = isStart ? "start" : "end";
    const lo = isStart ? Math.max(0, e - maxSpan) : s + minSpan;
    const hi = isStart ? e - minSpan : Math.min(dur, s + maxSpan);
    return (
      <div data-part={part} role="slider" tabIndex={0} aria-label={`${label}, ${word}`}
        aria-valuemin={Math.round(lo * 10) / 10} aria-valuemax={Math.round(hi * 10) / 10} aria-valuenow={Math.round(t * 10) / 10}
        aria-valuetext={`${isStart ? "Starts" : "Ends"} at ${speakClipTime(t)}`} aria-orientation="horizontal" aria-disabled={locked || undefined}
        onKeyDown={onKey(part)}
        // The hit box sits outside the window, so it reaches past the clip edge and never covers the other handle.
        className={cn("group absolute -inset-y-1 z-10 w-6 rounded-sm pointer-coarse:w-11", isStart ? "-translate-x-full" : "", locked ? "cursor-default" : "cursor-ew-resize")}
        style={{ left: pct(t) }}>
        <span aria-hidden className={cn("absolute inset-y-0 flex w-3 items-center justify-center bg-fg forced-colors:bg-[CanvasText]",
          isStart ? "right-0 rounded-l-sm" : "left-0 rounded-r-sm")}>
          <span className="h-4 w-0.5 rounded-full bg-bg" />
        </span>
      </div>
    );
  };

  const showPlayhead = playhead !== undefined && dur > 0;
  return (
    <div className={cn("flex min-w-0 flex-col gap-3", className)} {...rest}>
      <div className="flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1">
        <span id={labelId} className="text-small font-medium text-fg">{label}</span>
        <span className="text-small text-fg-muted tabular-nums">
          {fmt(s)} to {fmt(e)} · {span.toFixed(1)}{maxSpanSec !== undefined ? ` of ${maxSpan} s max` : " s"}
          {atMax && <span className="ml-1.5 rounded-control border border-line px-1.5 text-micro text-fg">At the limit</span>}
        </span>
      </div>

      <div className="px-6 pointer-coarse:px-11">
        <div ref={strip} role="group" aria-labelledby={labelId} onPointerDown={down} onPointerMove={move} onPointerUp={up} onPointerCancel={up}
          className={cn("relative h-14 touch-none select-none", locked && "opacity-(--disabled-opacity)")}>
          <div aria-hidden className="absolute inset-0 flex overflow-hidden rounded-sm bg-sunken ring-1 ring-line forced-colors:outline forced-colors:outline-1">
            {(thumbnails?.length || renderThumbnail) && Array.from({ length: TILES }, (_, i) => (
              <span key={i} className="h-full min-w-0 flex-1 overflow-hidden border-r border-glass last:border-r-0">{tile(i)}</span>
            ))}
            {/* Outside the window is washed back, so the chosen part reads at a glance. */}
            <span className="absolute inset-y-0 left-0 bg-glass" style={{ width: pct(s) }} />
            <span className="absolute inset-y-0 right-0 bg-glass" style={{ left: pct(e) }} />
          </div>
          {handle("start")}
          <div data-part="window" role="slider" tabIndex={0} aria-label={`${label}, whole part`}
            aria-valuemin={0} aria-valuemax={Math.round((dur - span) * 10) / 10} aria-valuenow={Math.round(s * 10) / 10}
            aria-valuetext={spanWords(s, e)} aria-orientation="horizontal" aria-disabled={locked || undefined}
            onKeyDown={onKey("window")}
            className={cn("absolute inset-y-0 border-y-2 border-fg forced-colors:border-[Highlight]", locked ? "cursor-default" : "cursor-grab active:cursor-grabbing")}
            style={{ left: pct(s), width: `calc(${pct(e)} - ${pct(s)})` }} />
          {handle("end")}
          {showPlayhead && (
            <span aria-hidden className="pointer-events-none absolute -inset-y-1.5 z-20 w-0.5 -translate-x-1/2 rounded-full bg-fg-muted ring-1 ring-surface" style={{ left: pct(playhead!) }} />
          )}
        </div>
      </div>

      {status !== "editing" ? (
        <p className="flex items-center gap-2 text-small text-fg">
          <span aria-hidden className={cn("inline-block size-2 shrink-0 rounded-full", status === "processing" ? "bg-lime ring-1 ring-fg-muted motion-safe:animate-pulse" : "bg-success")} />
          {status === "processing" ? (statusNote ?? "Working on this part. The range is locked until it finishes.") : (statusNote ?? "Applied.")}
        </p>
      ) : snapped ? (
        <p className="text-small text-fg-muted">That was longer than {maxSpan} seconds, so the end was moved to {fmt(e)}.</p>
      ) : null}

      <div className="flex flex-wrap items-end gap-3">
        <div className="flex min-w-32 flex-1 flex-col gap-1 text-small text-fg-muted">
          <span aria-hidden>Start, seconds</span>
          <NumberField size="sm" label={`${label}, start in seconds`} value={Number(s.toFixed(1))} step={0.1} precision={1}
            min={0} max={Number((e - minSpan).toFixed(1))} disabled={locked}
            onChange={(v) => { if (v !== null) { moveStart(v); say(); } }} />
        </div>
        <div className="flex min-w-32 flex-1 flex-col gap-1 text-small text-fg-muted">
          <span aria-hidden>End, seconds</span>
          <NumberField size="sm" label={`${label}, end in seconds`} value={Number(e.toFixed(1))} step={0.1} precision={1}
            min={Number((s + minSpan).toFixed(1))} max={Number(Math.min(dur, s + maxSpan).toFixed(1))} disabled={locked}
            onChange={(v) => { if (v !== null) { moveEnd(v); say(); } }} />
        </div>
        {showPlayhead && (
          <div className="flex flex-wrap gap-2">
            <Button size="sm" variant="secondary" disabled={locked} className="pointer-coarse:h-11"
              onClick={() => { const [, ce] = latest.current; if (playhead! < ce) moveStart(playhead!); else moveWindow(playhead!); say(); }}>
              Set start at {formatMediaTime(playhead!)}
            </Button>
            <Button size="sm" variant="secondary" disabled={locked} className="pointer-coarse:h-11"
              onClick={() => { const [cs, ce] = latest.current; if (playhead! > cs) moveEnd(playhead!); else moveWindow(playhead! - (ce - cs)); say(); }}>
              Set end at {formatMediaTime(playhead!)}
            </Button>
          </div>
        )}
      </div>

      <span role="status" className="sr-only">{announce}</span>
    </div>
  );
}
```

## tattva/molecules/CodeBlock.tsx

```tsx
import { useState } from "react";
import { CheckIcon, CopyIcon } from "../lib/icons";

export function CodeBlock({ code, language = "text" }: { code: string; language?: string }) {
  const [copied, setCopied] = useState(false);
  const copy = async () => {
    try { await navigator.clipboard.writeText(code); setCopied(true); setTimeout(() => setCopied(false), 1500); } catch { /* clipboard unavailable */ }
  };
  return (
    <figure className="overflow-hidden rounded-card border border-line bg-code text-code-fg">
      <figcaption className="flex items-center justify-between border-b border-code-line px-3 py-1.5 text-small leading-4 text-code-muted">
        <span className="font-mono">{language}</span>
        <button type="button" onClick={copy} className="inline-flex items-center gap-1 rounded px-1.5 py-0.5 hover:text-code-fg">
          {copied ? <CheckIcon width={12} height={12} /> : <CopyIcon width={12} height={12} />}
          <span aria-live="polite">{copied ? "Copied" : "Copy"}</span>
        </button>
      </figcaption>
      <pre tabIndex={0} className="overflow-x-auto p-3 font-mono text-compact leading-6"><code>{code}</code></pre>
    </figure>
  );
}
```

## tattva/molecules/Collapsible.tsx

```tsx
import { useEffect, useId, useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { ChevronIcon } from "../lib/icons";
import { usePresence } from "../hooks/usePresence";
import { headingTag, type HeadingLevel } from "../lib/useFocusAfter";

export interface CollapsibleProps {
  header: ReactNode;
  children: ReactNode;
  defaultOpen?: boolean;
  /** Controls whether it is open. Use with `onOpenChange`; without it the part keeps its own state. */
  open?: boolean;
  /** Called with the new value when the header is pressed. */
  onOpenChange?: (open: boolean) => void;
  /** Wraps the header button in a heading of this level, so screen reader users can jump between sections. */
  headingLevel?: HeadingLevel;
  className?: string;
}

/**
 * A header that shows and hides what is under it. The content grows open in --dur-base and closes faster in --dur-fast.
 * Closed content is hidden from everyone, not just from sight. Under reduced motion it simply appears.
 */
export function Collapsible({ header, children, defaultOpen = false, open: openProp, onOpenChange, headingLevel, className }: CollapsibleProps) {
  const [openState, setOpenState] = useState(openProp ?? defaultOpen);
  const open = openProp ?? openState;
  const setOpen = (v: boolean) => { if (openProp === undefined) setOpenState(v); onOpenChange?.(v); };
  const id = useId();
  const { present } = usePresence(open);
  // `shown` turns on one frame after the content mounts, so the height has a closed state to grow from.
  const [shown, setShown] = useState(open);
  // Clipping is only needed while the height moves; once open, focus rings inside are not cut off.
  const [settled, setSettled] = useState(open);
  useEffect(() => {
    setSettled(false);
    if (!open) { setShown(false); return; }
    const r = requestAnimationFrame(() => setShown(true));
    return () => cancelAnimationFrame(r);
  }, [open]);
  return (
    <div className={className}>
      {(() => {
        const button = (
          <button type="button" aria-expanded={open} aria-controls={id} onClick={() => setOpen(!open)}
            className="flex w-full items-center gap-2 text-left">
            <ChevronIcon className={cn("shrink-0 text-fg-subtle transition-transform duration-[var(--dur-fast)]", open && "rotate-90")} />
            {header}
          </button>
        );
        if (!headingLevel) return button;
        const H = headingTag(headingLevel);
        return <H className="m-0 font-[inherit] text-[length:inherit] leading-[inherit]">{button}</H>;
      })()}
      <div id={id} hidden={!present} onTransitionEnd={(e) => { if (e.target === e.currentTarget && open) setSettled(true); }}
        className={cn("grid transition-[grid-template-rows,opacity] ease-(--ease-arrive)", shown ? "grid-rows-[1fr] opacity-100 duration-(--dur-base)" : "grid-rows-[0fr] opacity-0 duration-(--dur-fast)")}>
        <div className={cn("min-h-0", !(shown && settled) && "overflow-hidden")}>{children}</div>
      </div>
    </div>
  );
}
```

## tattva/molecules/Combobox.tsx

```tsx
import { useEffect, useId, useMemo, useRef, useState } from "react";
import type { KeyboardEvent } from "react";
import { TextField } from "../atoms/TextField";
import { cn } from "../lib/cn";
import { SearchIcon } from "../lib/icons";

export interface ComboboxOption {
  value: string;
  label: string;
  /** Small text on the right of the row, such as a category. */
  hint?: string;
}

export interface ComboboxProps {
  /** The text in the box. Always the text, never an id. */
  value: string;
  /** Called on every keystroke, and with the option's label when a suggestion is picked. */
  onChange: (value: string) => void;
  options: ComboboxOption[];
  /** Labels shown before typing. A label that matches an option uses that option's value. */
  popular?: string[];
  /** Called when a suggestion is picked ("option") or the typed text is accepted ("typed"). */
  onSelect?: (value: string, how: "option" | "typed") => void;
  /** Accessible name of the box. */
  label: string;
  placeholder?: string;
  /** Text of the last row while typing, which keeps exactly what was typed. */
  useTypedLabel?: (typed: string) => string;
  /** Line under the popular names, saying any name can be typed. */
  emptyHint?: string;
  invalid?: boolean;
  disabled?: boolean;
  /** Set by Field. */
  id?: string;
  "aria-describedby"?: string;
  "aria-invalid"?: boolean;
  required?: boolean;
  className?: string;
}

type Row = { key: string; value: string; label: string; hint?: string; how: "option" | "typed" };
const MAX = 6;

/**
 * A search box with suggestions that is never a closed list. Whatever is typed can be used. WAI-ARIA combobox with list
 * autocomplete: focus stays in the input and the active option is named with aria-activedescendant.
 */
export function Combobox({ value, onChange, options, popular, onSelect, label, placeholder, useTypedLabel, emptyHint = "Not here? Type its name above.", invalid, disabled, id, className, ...aria }: ComboboxProps) {
  const uid = useId();
  const listId = `${uid}-list`;
  const [focused, setFocused] = useState(false);
  const [dismissed, setDismissed] = useState(false);
  const [active, setActive] = useState(-1);
  const list = useRef<HTMLUListElement>(null);

  const typed = value.trim();
  const q = typed.toLowerCase();
  const rows = useMemo<Row[]>(() => {
    if (!typed) {
      return (popular ?? []).map((p) => {
        const o = options.find((x) => x.label.toLowerCase() === p.toLowerCase());
        return { key: `p-${p}`, value: o?.value ?? p, label: o?.label ?? p, hint: o?.hint, how: "option" as const };
      });
    }
    const starts = options.filter((o) => o.label.toLowerCase().startsWith(q));
    const contains = options.filter((o) => !o.label.toLowerCase().startsWith(q) && o.label.toLowerCase().includes(q));
    const found: Row[] = [...starts, ...contains].slice(0, MAX).map((o) => ({ key: `o-${o.value}`, value: o.value, label: o.label, hint: o.hint, how: "option" as const }));
    // An exact match is already in the list, so a second row saying the same thing would only confuse.
    if (!found.some((r) => r.label.toLowerCase() === q)) found.push({ key: "typed", value: typed, label: useTypedLabel ? useTypedLabel(typed) : `Use “${typed}”`, how: "typed" });
    return found;
  }, [typed, q, options, popular, useTypedLabel]);

  const open = focused && !dismissed && !disabled && (typed ? true : rows.length > 0);
  const suggestions = rows.filter((r) => r.how === "option");

  useEffect(() => {
    if (active >= 0) list.current?.children[active]?.scrollIntoView?.({ block: "nearest" });
  }, [active]);

  const pick = (r: Row) => {
    if (r.how === "option") { onChange(r.label); onSelect?.(r.value, "option"); }
    else { if (r.value !== value) onChange(r.value); onSelect?.(r.value, "typed"); }
    setDismissed(true);
    setActive(-1);
  };

  const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
    if (e.key === "ArrowDown" || e.key === "ArrowUp") {
      if (!rows.length) return;
      e.preventDefault();
      if (!open) { setDismissed(false); setActive(-1); return; }
      const n = rows.length;
      setActive(e.key === "ArrowDown" ? (active + 1) % n : active <= 0 ? n - 1 : active - 1);
    } else if (e.key === "Enter") {
      if (!typed) return;
      e.preventDefault();
      const top = suggestions[0];
      if (open && active >= 0) pick(rows[active]);
      // "ama" then Enter means Amazon: a typed prefix of the top suggestion picks it.
      else if (top && top.label.toLowerCase().startsWith(q)) pick(top);
      else pick({ key: "typed", value: typed, label: typed, how: "typed" });
    } else if (e.key === "Escape" && open) {
      e.preventDefault();
      e.stopPropagation();
      setDismissed(true);
      setActive(-1);
    }
  };

  const status = !open ? "" : typed ? (suggestions.length ? `${suggestions.length} ${suggestions.length === 1 ? "suggestion" : "suggestions"}. Use the arrow keys, or press Enter to use what you typed.` : "No suggestions. Press Enter to use what you typed.") : `${rows.length} popular. Or type any name.`;

  return (
    <div className={cn("relative w-full", className)}>
      <TextField
        {...aria} id={id} type="text" role="combobox" autoComplete="off" aria-label={label} aria-expanded={open} aria-controls={listId} aria-autocomplete="list"
        aria-activedescendant={open && active >= 0 ? `${uid}-o${active}` : undefined}
        leading={<SearchIcon />} placeholder={placeholder} value={value} invalid={invalid} disabled={disabled}
        onChange={(e) => { onChange(e.target.value); setDismissed(false); setActive(-1); }}
        onFocus={() => setFocused(true)} onBlur={() => { setFocused(false); setDismissed(false); setActive(-1); }} onKeyDown={onKeyDown} />
      <div className={cn("absolute inset-x-0 top-full z-20 mt-1 overflow-hidden rounded-overlay border border-line bg-raised py-1 shadow-md", !open && "hidden")}>
        <ul ref={list} id={listId} role="listbox" aria-label={`${label} suggestions`} className="m-0 list-none p-0">
          {open && rows.map((r, i) => (
            // mousedown is stopped so the input keeps focus and the list does not close before the click lands.
            <li key={r.key} id={`${uid}-o${i}`} role="option" aria-selected={i === active} onMouseDown={(e) => e.preventDefault()} onClick={() => pick(r)}
              className={cn(
                "flex min-h-9 cursor-pointer items-center justify-between gap-3 px-3 py-1.5 text-body leading-5 text-fg pointer-coarse:min-h-11",
                r.how === "typed" && suggestions.length > 0 && "mt-1 border-t border-line pt-2",
                i === active ? "bg-hover shadow-[inset_3px_0_0_var(--fg)] forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]" : "hover:bg-hover",
              )}>
              <span className={r.how === "typed" ? "font-medium" : undefined}>{r.label}</span>
              {r.hint && <span className="shrink-0 text-small leading-4 text-fg-muted">{r.hint}</span>}
            </li>
          ))}
        </ul>
        {open && !typed && <p className="px-3 pt-1.5 pb-1 text-small leading-4 text-fg-muted">{emptyHint}</p>}
      </div>
      <p role="status" aria-live="polite" className="sr-only">{status}</p>
    </div>
  );
}
```

## tattva/molecules/ConfidenceIndicator.tsx

```tsx
import { cn } from "../lib/cn";
import { Badge } from "../atoms/Badge";

export type ConfidenceLevel = "low" | "medium" | "high";
/** Amber marks low confidence because a person should verify. Medium is neutral: it asks for nothing. */
const map: Record<ConfidenceLevel, { tone: "neutral" | "warning" | "success"; text: string; bars: number }> = {
  low: { tone: "warning", text: "Low confidence. Verify before relying on this.", bars: 1 },
  medium: { tone: "neutral", text: "Moderate confidence. Spot-check key facts.", bars: 2 },
  high: { tone: "success", text: "High confidence, supported by multiple sources.", bars: 3 },
};

/** Never convey confidence by colour alone: bars + text label are always present. */
export function ConfidenceIndicator({ level, showText = true }: { level: ConfidenceLevel; showText?: boolean }) {
  const m = map[level];
  return (
    <span className="inline-flex items-center gap-2" title={m.text}>
      <span className="flex items-end gap-0.5" aria-hidden>
        {[1, 2, 3].map((b) => (
          <span key={b} className={cn("w-1 origin-bottom animate-grow-y rounded-sm", b <= m.bars ? { low: "bg-attention", medium: "bg-fg-subtle", high: "bg-success" }[level] : "bg-line-strong")} style={{ height: 6 + b * 3, animationDelay: `${b * 60}ms` }} />
        ))}
      </span>
      <Badge tone={m.tone}>{level[0].toUpperCase() + level.slice(1)} confidence</Badge>
      {showText && <span className="sr-only">{m.text}</span>}
    </span>
  );
}
```

## tattva/molecules/ConnectorPermission.tsx

```tsx
import { useId, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { headingTag, subHeadingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { AlertIcon, EditIcon, EyeIcon, SendIcon } from "../lib/icons";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { InlineConfirm } from "./InlineConfirm";

export type AccessLevel = "read" | "write" | "send" | "delete";

export interface ConnectorAccess {
  id: string;
  /** What it can do, in plain words: "Read your email". */
  label: string;
  level: AccessLevel;
}

export interface ConnectorPermissionLabels {
  status?: Partial<Record<"connected" | "off" | "expired", string>>;
  canHeading?: string;
  levels?: Partial<Record<AccessLevel, string>>;
  connect?: string;
  reconnect?: string;
  disconnect?: string;
  /** Receives the app name. */
  disconnectQuestion?: (app: string) => string;
  keep?: string;
  /** Receives when it was last used. */
  lastUsed?: (when: string) => string;
}

export interface ConnectorPermissionProps {
  /** The app's name, such as Gmail. */
  app: string;
  /** The app's logo, about 24px. */
  icon?: ReactNode;
  /** Which account it is connected to. */
  account?: string;
  status: "connected" | "off" | "expired";
  /** What the assistant can do with it, one line each. */
  access: ConnectorAccess[];
  /** When the assistant last used it, such as "2 hours ago". */
  lastUsed?: string;
  onConnect?: () => void;
  onDisconnect?: () => void;
  labels?: ConnectorPermissionLabels;
  /** Level of the part's heading, so it fits the page outline. Default 3. */
  headingLevel?: HeadingLevel;
  className?: string;
}

const LEVEL_ICON: Record<AccessLevel, typeof EyeIcon> = { read: EyeIcon, write: EditIcon, send: SendIcon, delete: AlertIcon };

/**
 * One connected app, and exactly what the assistant can do with it. Each line says the action in plain words with a
 * mark for how far it reaches (see, change, send, delete); deleting stands out. Disconnecting asks once, in place.
 * An expired connection says so and offers to reconnect, instead of failing quietly.
 */
export function ConnectorPermission({ app, icon, account, status, access, lastUsed, onConnect, onDisconnect, labels, headingLevel = 3, className }: ConnectorPermissionProps) {
  const l = {
    canHeading: "The assistant can", connect: "Connect", reconnect: "Reconnect", disconnect: "Disconnect", keep: "Keep it",
    disconnectQuestion: (a: string) => `Disconnect ${a}?`, lastUsed: (w: string) => `Last used ${w}`, ...labels,
    status: { connected: "Connected", off: "Not connected", expired: "Needs you to sign in again", ...labels?.status },
    levels: { read: "See", write: "Change", send: "Send", delete: "Delete", ...labels?.levels },
  };
  const uid = useId();
  const H = headingTag(headingLevel);
  const H2 = subHeadingTag(headingLevel);
  const tone = status === "connected" ? "success" : status === "expired" ? "warning" : "neutral";
  return (
    <section aria-labelledby={`${uid}-h`} className={cn("rounded-card border border-line bg-surface p-4", className)}>
      <div className="flex flex-wrap items-center gap-x-3 gap-y-2">
        {icon && <span aria-hidden className="grid size-9 shrink-0 place-items-center rounded-control border border-line bg-bg">{icon}</span>}
        <div className="min-w-0 flex-1">
          <H id={`${uid}-h`} className="m-0 text-body leading-5 font-semibold text-fg">{app}</H>
          {account && <p className="m-0 truncate text-small leading-5 text-fg-muted">{account}</p>}
        </div>
        <Badge tone={tone}>{l.status[status]}</Badge>
      </div>
      {status !== "off" && access.length > 0 && (
        <div className="mt-4">
          <H2 className="m-0 text-small leading-4 font-medium text-fg-muted">{l.canHeading}</H2>
          <ul className="mt-2 space-y-2">
            {access.map((a) => {
              const Icon = LEVEL_ICON[a.level];
              return (
                <li key={a.id} className="flex items-start gap-2.5">
                  <Icon aria-hidden width={16} height={16} className={cn("mt-0.5 shrink-0", a.level === "delete" ? "text-danger-fg" : "text-fg-muted")} />
                  <span className="min-w-0 flex-1 text-body leading-6 text-fg">{a.label}</span>
                  <span className={cn("shrink-0 text-small leading-6", a.level === "delete" ? "font-medium text-danger-fg" : "text-fg-muted")}>{l.levels[a.level]}</span>
                </li>
              );
            })}
          </ul>
        </div>
      )}
      {lastUsed && status === "connected" && <p className="m-0 mt-4 text-small leading-5 text-fg-muted">{l.lastUsed(lastUsed)}</p>}
      <div className="mt-3 flex flex-wrap items-center gap-x-3 gap-y-2">
        {status === "connected" && onDisconnect && (
          <InlineConfirm onConfirm={onDisconnect} confirmLabel={l.disconnect} cancelLabel={l.keep} question={l.disconnectQuestion(app)} tone="danger">{l.disconnect}</InlineConfirm>
        )}
        {status !== "connected" && onConnect && <Button size="sm" onClick={onConnect}>{status === "expired" ? l.reconnect : l.connect}</Button>}
      </div>
    </section>
  );
}
```

## tattva/molecules/ContextChip.tsx

```tsx
import { useRef, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { keepFocus } from "../lib/focus";
import { ChevronIcon, XIcon } from "../lib/icons";

/**
 * Scope control that lives inside the composer: "what is this chat about?"
 * With onClick it opens a picker (shows a chevron). With onClear it is a removable label; after clearing, focus moves
 * to what is left of the pill, else the next control, else the message box (or to `focusAfterClear`).
 */
export function ContextChip({ icon, children, onClick, onClear, clearLabel = "Clear context", focusAfterClear, className }: { icon?: ReactNode; children: ReactNode; onClick?: () => void; onClear?: () => void; clearLabel?: string; focusAfterClear?: () => HTMLElement | null; className?: string }) {
  const ref = useRef<HTMLSpanElement>(null);
  const body = (
    <>
      {icon && <span className="text-fg-subtle">{icon}</span>}
      <span>{children}</span>
      {onClick && <ChevronIcon className="size-3 rotate-90 text-fg-subtle" />}
    </>
  );
  const base = cn("inline-flex h-7 items-center gap-1.5 rounded-control border border-line bg-surface px-3 text-small leading-4 font-medium text-fg", className);
  return (
    <span ref={ref} className={cn("inline-flex items-center", onClear && "rounded-control bg-hover pr-1")}>
      {onClick
        ? <button type="button" onClick={onClick} className={cn(base, "cursor-pointer hover:bg-bg")}>{body}</button>
        : <span className={cn(base, onClear && "border-transparent bg-transparent pr-1")}>{body}</span>}
      {onClear && <button type="button" aria-label={clearLabel} onClick={() => { onClear(); keepFocus(ref.current, focusAfterClear); }} className="flex size-5 items-center justify-center rounded-control text-fg-subtle hover:bg-line cursor-pointer"><XIcon width={12} height={12} /></button>}
    </span>
  );
}

/** @deprecated Renamed to ContextChip in 0.29.0, to match the name other design systems use. The old name goes in 0.31.0. */
export const ContextPill = ContextChip;
```

## tattva/molecules/ContextMeter.tsx

```tsx
import { CheckIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { Meter } from "../atoms/Meter";
import { cn } from "../lib/cn";

export interface ContextMeterProps {
  /** Share of the context window in use, 0 to 100. */
  percent: number;
  /** Text beside the bar. Defaults to "68% of context". */
  valueText?: string;
  /** At or above this percent the bar turns amber and Summarize appears. */
  summarizeAt?: number;
  /** Called when the person chooses Summarize. The action stays hidden when omitted. */
  onSummarize?: () => void;
  /** Disables Summarize while a summary is being made. */
  summarizing?: boolean;
  /** Shows a compaction marker with this text, for example "Earlier messages summarized". */
  compactionNote?: string;
  /** Reason shown beside the Summarize action. */
  warnText?: string;
  summarizeLabel?: string;
  /** Accessible name for the meter. */
  label?: string;
  className?: string;
}

/** Context-window usage. Stays neutral until the limit is near, then says why and offers to summarize. */
export function ContextMeter({ percent, valueText, summarizeAt = 85, onSummarize, summarizing, compactionNote, warnText = "Close to the limit.", summarizeLabel = "Summarize", label = "Context used", className }: ContextMeterProps) {
  const pct = Math.round(Math.max(0, Math.min(100, percent)));
  const text = valueText ?? `${pct}% of context`;
  const near = pct >= summarizeAt;
  return (
    <div className={cn("space-y-2", className)}>
      <div className="flex items-center justify-between gap-3 text-small leading-4">
        <span className="text-fg-muted">{text}</span>
        {near && <span className="text-attention-fg">{warnText}</span>}
      </div>
      <Meter label={label} value={pct} warnAt={summarizeAt / 100} valueText={text} />
      {(near && onSummarize) && (
        <Button size="sm" variant="secondary" disabled={summarizing} onClick={onSummarize}>{summarizing ? "Summarizing" : summarizeLabel}</Button>
      )}
      {compactionNote && (
        <p className="flex items-center gap-1.5 border-t border-line pt-2 text-small leading-4 text-fg-muted"><CheckIcon width={12} height={12} />{compactionNote}</p>
      )}
    </div>
  );
}
```

## tattva/molecules/ContinueNotice.tsx

```tsx
import { cn } from "../lib/cn";
import { InfoIcon, AlertIcon } from "../lib/icons";
import { Button } from "../atoms/Button";

export interface ContinueNoticeLabels {
  length?: string;
  stopped?: string;
  lost?: string;
  continue?: string;
  retry?: string;
}

export interface ContinueNoticeProps {
  /** Why the reply ended early: it hit the length limit, the person pressed Stop, or the connection dropped. */
  reason: "length" | "stopped" | "lost";
  /** Picks up where the reply ended (or, after a lost connection, tries again). */
  onContinue: () => void;
  /** True while the continuation is starting. The button keeps its place and ignores presses. */
  busy?: boolean;
  labels?: ContinueNoticeLabels;
  className?: string;
}

/**
 * One quiet line under a reply that ended early, saying why in plain words and offering to carry on. A reply cut
 * off by the length limit otherwise just stops mid-sentence, and people cannot tell whether it finished.
 */
export function ContinueNotice({ reason, onContinue, busy, labels, className }: ContinueNoticeProps) {
  const l = {
    length: "The reply stopped at the length limit.", stopped: "You stopped this reply.", lost: "The connection dropped before the reply finished.",
    continue: "Continue", retry: "Try again", ...labels,
  };
  const Icon = reason === "lost" ? AlertIcon : InfoIcon;
  return (
    <div className={cn("flex flex-wrap items-center gap-x-3 gap-y-2", className)}>
      <p className="m-0 inline-flex items-center gap-1.5 text-small leading-5 text-fg-muted">
        <Icon aria-hidden width={14} height={14} className="shrink-0" />{l[reason]}
      </p>
      <Button size="sm" variant="secondary" busy={busy} busyLabel={reason === "lost" ? l.retry : l.continue} onClick={onContinue}>{reason === "lost" ? l.retry : l.continue}</Button>
    </div>
  );
}
```

## tattva/molecules/Countdown.tsx

```tsx
import { cn } from "../lib/cn";
import { AlertIcon } from "../lib/icons";
import { Meter } from "../atoms/Meter";

export interface CountdownProps {
  /** ISO date, such as 2026-10-27. Only the calendar day is used. */
  deadline: string;
  /** ISO date to count from. Defaults to today on this device. Pass it in docs and tests. */
  today?: string;
  /** What the date is, such as "They must reply by". */
  label: string;
  /** ISO date the clock started. Turns on the time bar. */
  start?: string;
  /** Only days for now. */
  unit?: "days";
  /** Shown with an icon once the deadline has passed. */
  missedLabel?: string;
  /** Turns an ISO date into the large text. Defaults to a long en-IN date. */
  formatDate?: (iso: string) => string;
  /** Amber treatment for a deadline that is due or missed and needs a person to act. Never on by default. */
  needsAction?: boolean;
  /** Text of the amber chip. */
  actionText?: string;
  className?: string;
}

const DAY = 86_400_000;

/** Whole days since 1970 for the calendar day in an ISO string. Plain UTC maths, so time zones and daylight saving cannot shift it. */
function dayNumber(iso: string): number {
  const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
  if (!m) return NaN;
  return Math.round(Date.UTC(+m[1], +m[2] - 1, +m[3]) / DAY);
}

function localToday(): string {
  const d = new Date();
  const p = (n: number) => String(n).padStart(2, "0");
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
}

const longDate = (iso: string) => {
  const n = dayNumber(iso);
  return Number.isNaN(n) ? "—" : new Date(n * DAY).toLocaleDateString("en-IN", { day: "numeric", month: "long", year: "numeric", timeZone: "UTC" });
};

const plural = (n: number) => `${n} ${n === 1 ? "day" : "days"}`;

/** The deadline, first and biggest: the date in large text, the days left in words, and an optional bar of time used. */
export function Countdown({ deadline, today, label, start, missedLabel = "Deadline passed", formatDate = longDate, needsAction, actionText = "Needs action", className }: CountdownProps) {
  const end = dayNumber(deadline);
  const now = dayNumber(today ?? localToday());
  const left = end - now;
  const valid = !Number.isNaN(left);
  const missed = valid && left < 0;
  const begin = start ? dayNumber(start) : NaN;
  const total = end - begin;
  const showBar = valid && total > 0;
  const used = showBar ? Math.max(0, Math.min(total, now - begin)) : 0;
  const attention = needsAction && valid && left <= 0;
  const words = !valid ? "" : missed ? `${plural(-left)} past` : left === 0 ? "Due today" : `${plural(left)} left`;
  return (
    <div role="group" aria-label={label} className={cn("min-w-0 rounded-card border bg-surface p-4", attention ? "border-attention" : "border-line", className)}>
      <div className="flex items-start justify-between gap-2">
        <p className="text-small leading-5 text-fg-muted">{label}</p>
        {needsAction && <span className="inline-flex shrink-0 items-center gap-1 rounded-control bg-attention-soft px-2 py-0.5 text-micro leading-4 font-medium text-attention-fg"><AlertIcon width={12} height={12} />{actionText}</span>}
      </div>
      <p className="mt-1 text-title-lg leading-9 font-semibold tracking-snug tabular-nums text-fg [overflow-wrap:anywhere]">
        {valid ? <time dateTime={deadline.slice(0, 10)}>{formatDate(deadline.slice(0, 10))}</time> : "—"}
      </p>
      {valid && (
        <p className="mt-1 flex flex-wrap items-center gap-x-2 text-body leading-6 text-fg">
          {missed && <span className="inline-flex items-center gap-1 font-medium"><AlertIcon aria-hidden width={14} height={14} className="shrink-0" />{missedLabel}</span>}
          <span className={cn("tabular-nums", missed ? "text-fg-muted" : "font-medium")}>{missed && <span aria-hidden>{"· "}</span>}{words}</span>
        </p>
      )}
      {showBar && (
        <Meter className="mt-3" label="Time used" value={used} max={total} warnAt={attention ? 0 : undefined}
          valueText={`${used} of ${total} days used`} />
      )}
    </div>
  );
}
```

## tattva/molecules/DataTable.tsx

```tsx
import { useId, useMemo, useState } from "react";
import type { ReactNode } from "react";
import { cn } from "../lib/cn";

export type DataRow = Record<string, ReactNode>;
export type SortDirection = "asc" | "desc";
export interface SortState { key: string; direction: SortDirection }
export type Direction = "up" | "down" | "flat";

export interface DataTableColumn {
  /** Key into each row object. */
  key: string;
  /** Visible column header. Include the unit, for example "p95 latency (ms)". */
  header: string;
  /** Right-align and use tabular figures. Set on every numeric column. */
  numeric?: boolean;
  /** Lets people sort by this column. The cell text is used unless sortValue is given. */
  sortable?: boolean;
  /** The raw number or text to sort by, for cells that show formatted text such as "₹1,200" or "+2.1%". */
  sortValue?: (row: DataRow) => number | string;
  /** Colours the cell as up or down and puts an arrow before it. Return undefined for no marker. The text must still carry its own sign. */
  direction?: (row: DataRow) => Direction | undefined;
}

export interface DataTableProps {
  /** Column definitions in display order. The first column is the row header. */
  columns: DataTableColumn[];
  /** One object per row, keyed by column key. Values are already formatted. */
  rows: DataRow[];
  /** Names the table for assistive technology and states what it holds. */
  caption: string;
  /** Keep the header row visible while the body scrolls. Pair with maxHeight. */
  stickyHeader?: boolean;
  /** Maximum body height in px before the table scrolls vertically. */
  maxHeight?: number;
  /** Row spacing. Dense fits more rows, for screens people scan all day. */
  density?: "comfortable" | "dense";
  /** Current sort, when your code keeps it. */
  sort?: SortState | null;
  /** Starting sort, when the table keeps it. */
  defaultSort?: SortState | null;
  /** Called when a sortable heading is pressed. Needed when you pass sort. */
  onSortChange?: (sort: SortState | null) => void;
  /** Called when a row is chosen. The row name becomes a button, so it works with the keyboard. */
  onRowClick?: (row: DataRow, index: number) => void;
  /** Key of the row to mark as the current one. Use with rowKey. */
  activeRowKey?: string;
  /** Gives each row a stable key. Defaults to the row's id value, then its position. */
  rowKey?: (row: DataRow, index: number) => string;
  /** What a narrow container does with the table. "scroll" keeps the columns and scrolls sideways. "stack" turns each row into a card, with the column name before each value, so nothing scrolls. */
  responsive?: "scroll" | "stack";
  /** Shown instead of the body when there are no rows. */
  emptyText?: string;
  className?: string;
}

const text = (v: ReactNode) => (typeof v === "string" || typeof v === "number" ? v : "");

function compare(a: number | string, b: number | string) {
  if (typeof a === "number" && typeof b === "number") return a - b;
  return String(a).localeCompare(String(b), undefined, { numeric: true, sensitivity: "base" });
}

function Arrow({ dir }: { dir: Direction }) {
  if (dir === "flat") return <span aria-hidden className="inline-block w-[0.7em] text-center">–</span>;
  return (
    <svg aria-hidden viewBox="0 0 10 10" className={cn("inline-block size-[0.7em] fill-current", dir === "down" && "rotate-180")}>
      <path d="M5 1.5 9 8.5H1Z" />
    </svg>
  );
}

function SortMark({ state }: { state: "none" | SortDirection }) {
  return (
    <svg aria-hidden viewBox="0 0 10 14" className="size-3 shrink-0">
      <path d="M5 1 8.5 5.5h-7Z" className={state === "asc" ? "fill-fg" : "fill-fg-subtle opacity-50"} />
      <path d="M5 13 1.5 8.5h7Z" className={state === "desc" ? "fill-fg" : "fill-fg-subtle opacity-50"} />
    </svg>
  );
}

/**
 * Semantic table counterpart to a chart, and a working data grid for screens of numbers. Numbers are right-aligned with
 * tabular figures, headings can sort, cells can carry an up or down marker, and the table scrolls horizontally in its own
 * region on narrow screens.
 */
export function DataTable({
  columns, rows, caption, stickyHeader, maxHeight, density = "comfortable", sort, defaultSort = null, onSortChange,
  onRowClick, activeRowKey, rowKey, responsive = "scroll", emptyText, className,
}: DataTableProps) {
  const [inner, setInner] = useState<SortState | null>(defaultSort);
  const controlled = sort !== undefined;
  const current = controlled ? sort : inner;
  const live = useId();

  const keyOf = (r: DataRow, i: number) => rowKey?.(r, i) ?? (typeof r.id === "string" || typeof r.id === "number" ? String(r.id) : String(i));

  const shown = useMemo(() => {
    if (!current) return rows.map((r, i) => ({ r, i }));
    const col = columns.find((c) => c.key === current.key);
    if (!col) return rows.map((r, i) => ({ r, i }));
    const val = (r: DataRow) => (col.sortValue ? col.sortValue(r) : (text(r[col.key]) as number | string));
    const dir = current.direction === "asc" ? 1 : -1;
    return rows.map((r, i) => ({ r, i })).sort((x, y) => dir * compare(val(x.r), val(y.r)) || x.i - y.i);
  }, [rows, columns, current]);

  const press = (key: string) => {
    // none -> ascending -> descending -> none
    const next: SortState | null = current?.key !== key ? { key, direction: "asc" } : current.direction === "asc" ? { key, direction: "desc" } : null;
    if (!controlled) setInner(next);
    onSortChange?.(next);
  };

  const pad = density === "dense" ? "px-3 py-1" : "px-3 py-2";
  const stack = responsive === "stack";
  const sortedCol = current ? columns.find((c) => c.key === current.key) : undefined;

  return (
    <div role="region" aria-label={caption} tabIndex={0}
      className={cn(stack && "@container", "relative overflow-auto rounded-card border border-line bg-surface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--focus-ring)]", className)}
      style={maxHeight ? { maxHeight } : undefined}>
      <table role={stack ? "table" : undefined} className={cn("w-full border-collapse text-left text-compact leading-5", stack && "@max-md:block")}>
        <caption className="sr-only">{caption}</caption>
        <thead role={stack ? "rowgroup" : undefined} className={cn(stack && "@max-md:sr-only")}>
          <tr role={stack ? "row" : undefined}>
            {columns.map((c) => {
              const state = current?.key === c.key ? current.direction : "none";
              return (
                <th key={c.key} scope="col" role={stack ? "columnheader" : undefined} aria-sort={c.sortable ? (state === "asc" ? "ascending" : state === "desc" ? "descending" : "none") : undefined}
                  className={cn("bg-sunken text-micro font-medium whitespace-nowrap text-fg-muted", c.sortable ? "p-0" : pad, c.numeric && "text-right", stickyHeader && "sticky top-0 z-10")}>
                  {c.sortable ? (
                    <button type="button" onClick={() => press(c.key)}
                      className={cn("flex w-full items-center gap-1.5 px-3 py-2 text-micro font-medium text-fg-muted hover:bg-hover hover:text-fg pointer-coarse:min-h-11", c.numeric ? "justify-end" : "justify-start", density === "dense" && "py-1")}>
                      <span>{c.header}</span>
                      <SortMark state={state} />
                    </button>
                  ) : c.header}
                </th>
              );
            })}
          </tr>
        </thead>
        <tbody role={stack ? "rowgroup" : undefined} className={cn(stack && "@max-md:block")}>
          {shown.length === 0 && (
            <tr><td colSpan={columns.length} className="px-3 py-6 text-center text-fg-muted">{emptyText ?? "No rows."}</td></tr>
          )}
          {shown.map(({ r, i }) => {
            const k = keyOf(r, i);
            const active = activeRowKey !== undefined && activeRowKey === k;
            return (
              <tr key={k} role={stack ? "row" : undefined} aria-current={active ? "true" : undefined} onClick={onRowClick ? () => onRowClick(r, i) : undefined}
                className={cn("border-t border-line", stack && "@max-md:block @max-md:px-3 @max-md:py-2", onRowClick && "cursor-pointer hover:bg-hover", active && "bg-selected")}>
                {columns.map((c, j) => {
                  const dir = c.direction?.(r);
                  const content = dir ? (
                    <span className={cn("inline-flex items-center gap-1", dir === "up" && "text-up-fg", dir === "down" && "text-down-fg")}>
                      <Arrow dir={dir} /><span className="sr-only">{dir === "up" ? "Up " : dir === "down" ? "Down " : "Unchanged "}</span>{r[c.key]}
                    </span>
                  ) : r[c.key];
                  return j === 0 ? (
                    <th key={c.key} scope="row" role={stack ? "rowheader" : undefined} className={cn(pad, "font-medium whitespace-nowrap text-fg", c.numeric && "text-right [font-variant-numeric:tabular-nums]", stack && "@max-md:block @max-md:px-0 @max-md:text-left @max-md:text-body")}>
                      {onRowClick ? (
                        <button type="button" onClick={(e) => { e.stopPropagation(); onRowClick(r, i); }}
                          className="-mx-1 rounded px-1 text-left font-medium text-fg underline-offset-4 hover:underline pointer-coarse:min-h-11">{content}</button>
                      ) : content}
                    </th>
                  ) : (
                    <td key={c.key} role={stack ? "cell" : undefined} data-label={stack ? c.header : undefined} className={cn(pad, "whitespace-nowrap text-fg-muted", c.numeric && "text-right [font-variant-numeric:tabular-nums]", stack && "@max-md:flex @max-md:justify-between @max-md:gap-4 @max-md:px-0 @max-md:text-right @max-md:before:text-left @max-md:before:text-small @max-md:before:text-fg-muted @max-md:before:content-[attr(data-label)]")}>{content}</td>
                  );
                })}
              </tr>
            );
          })}
        </tbody>
      </table>
      <p id={live} role="status" aria-live="polite" className="sr-only">
        {current && sortedCol ? `Sorted by ${sortedCol.header}, ${current.direction === "asc" ? "ascending" : "descending"}.` : ""}
      </p>
    </div>
  );
}
```

## tattva/molecules/DatePicker.tsx

```tsx
import { useEffect, useMemo, useRef, useState } from "react";
import type { KeyboardEvent } from "react";
import { Button } from "../atoms/Button";
import { cn } from "../lib/cn";
import { ChevronIcon } from "../lib/icons";

export interface DatePickerProps {
  /** The chosen day as an ISO date (yyyy-mm-dd), or null when nothing is chosen. */
  value: string | null;
  onChange: (value: string) => void;
  /** Earliest day that can be picked (ISO). Earlier days are shown but disabled. */
  min?: string;
  /** Latest day that can be picked (ISO). Later days are shown but disabled. Defaults to today. */
  max?: string;
  /** Today as an ISO date. Set it for tests and docs. Defaults to the real date on this device. */
  today?: string;
  /** Accessible name of the calendar grid, such as "Date it happened". */
  label: string;
  /** BCP 47 locale for month, weekday and day names. */
  locale?: string;
  className?: string;
}

// All date maths is in UTC on yyyy-mm-dd strings, so a day never shifts with the time zone.
const DAY = 86_400_000;
const toUTC = (iso: string) => { const [y, m, d] = iso.split("-").map(Number); return Date.UTC(y, m - 1, d); };
const toISO = (t: number) => new Date(t).toISOString().slice(0, 10);
const addDays = (iso: string, n: number) => toISO(toUTC(iso) + n * DAY);
const daysIn = (y: number, m: number) => new Date(Date.UTC(y, m + 1, 0)).getUTCDate();
const monthKey = (iso: string) => iso.slice(0, 7);
// Clamps the day so 31 Jan plus one month is 28 or 29 Feb, not 3 March.
function addMonths(iso: string, n: number) {
  const [y, m, d] = iso.split("-").map(Number);
  const t = new Date(Date.UTC(y, m - 1 + n, 1));
  return toISO(Date.UTC(t.getUTCFullYear(), t.getUTCMonth(), Math.min(d, daysIn(t.getUTCFullYear(), t.getUTCMonth()))));
}
const localToday = () => { const n = new Date(); return toISO(Date.UTC(n.getFullYear(), n.getMonth(), n.getDate())); };
const weekdayIndex = (iso: string) => (new Date(toUTC(iso)).getUTCDay() + 6) % 7; // Monday is 0

function monthCells(y: number, m: number): (string | null)[] {
  const first = toISO(Date.UTC(y, m, 1));
  const cells: (string | null)[] = Array(weekdayIndex(first)).fill(null);
  for (let d = 1; d <= daysIn(y, m); d++) cells.push(toISO(Date.UTC(y, m, d)));
  while (cells.length % 7) cells.push(null);
  return cells;
}

/** "Sat, 20 Sep 2026 · 6 days ago". Today and yesterday get their own words. */
function describe(iso: string, today: string, fmt: (iso: string, o: Intl.DateTimeFormatOptions) => string) {
  const label = `${fmt(iso, { weekday: "short" })}, ${fmt(iso, { day: "numeric" })} ${fmt(iso, { month: "short" })} ${fmt(iso, { year: "numeric" })}`;
  const ago = Math.round((toUTC(today) - toUTC(iso)) / DAY);
  const when = ago === 0 ? "Today" : ago === 1 ? "Yesterday" : ago > 1 ? `${ago} days ago` : ago === -1 ? "Tomorrow" : `in ${-ago} days`;
  return `${label} · ${when}`;
}

/**
 * An inline month calendar for dates that are almost always recent. It is not a pop-up. Days outside min and max are shown
 * but cannot be picked. It follows the WAI-ARIA date picker grid: one tab stop, arrow keys, Home, End and Page keys.
 */
export function DatePicker({ value, onChange, min, max, today: todayProp, label, locale = "en-IN", className }: DatePickerProps) {
  const today = todayProp ?? localToday();
  const latest = max ?? today;
  const start = value ?? today;
  const [view, setView] = useState(() => ({ y: Number(start.slice(0, 4)), m: Number(start.slice(5, 7)) - 1 }));
  const [focus, setFocus] = useState(start);
  const grid = useRef<HTMLDivElement>(null);
  const moved = useRef(false);

  const fmt = useMemo(() => {
    const cache = new Map<string, Intl.DateTimeFormat>();
    return (iso: string, o: Intl.DateTimeFormatOptions) => {
      const k = JSON.stringify(o);
      let f = cache.get(k);
      if (!f) { f = new Intl.DateTimeFormat(locale, { ...o, timeZone: "UTC" }); cache.set(k, f); }
      return f.format(toUTC(iso));
    };
  }, [locale]);

  const allowed = (iso: string) => iso <= latest && (!min || iso >= min);
  const cells = useMemo(() => monthCells(view.y, view.m), [view]);
  const rows = Array.from({ length: cells.length / 7 }, (_, i) => cells.slice(i * 7, i * 7 + 7));
  const viewKey = `${view.y}-${String(view.m + 1).padStart(2, "0")}`;
  const firstOfView = `${viewKey}-01`;
  const prevDisabled = !!min && addDays(firstOfView, -1) < min;
  const nextDisabled = addMonths(firstOfView, 1) > latest;
  // The one day in the tab order: where focus was, else the chosen day, else the first allowed day of the month.
  const tabDay = [focus, value].find((d) => d && monthKey(d) === viewKey && allowed(d)) ?? cells.find((c) => c && allowed(c)) ?? null;

  // After a key press moves focus to a day (possibly in another month), focus its button once it has rendered.
  useEffect(() => {
    if (!moved.current) return;
    moved.current = false;
    grid.current?.querySelector<HTMLElement>(`[data-day="${focus}"]`)?.focus();
  }, [focus, view]);

  // Follow a value changed from outside, such as the parent's own shortcut buttons.
  useEffect(() => {
    if (!value) return;
    setFocus(value);
    setView({ y: Number(value.slice(0, 4)), m: Number(value.slice(5, 7)) - 1 });
  }, [value]);

  const show = (iso: string) => setView({ y: Number(iso.slice(0, 4)), m: Number(iso.slice(5, 7)) - 1 });
  const shiftMonth = (by: number) => { const n = addMonths(firstOfView, by); show(n); };
  const pick = (iso: string) => { if (!allowed(iso)) return; setFocus(iso); onChange(iso); };

  const onKey = (e: KeyboardEvent<HTMLDivElement>) => {
    if (e.altKey || e.ctrlKey || e.metaKey) return;
    const from = (e.target as HTMLElement).closest<HTMLElement>("[data-day]")?.dataset.day;
    if (!from) return;
    let next: string;
    switch (e.key) {
      case "ArrowLeft": next = addDays(from, -1); break;
      case "ArrowRight": next = addDays(from, 1); break;
      case "ArrowUp": next = addDays(from, -7); break;
      case "ArrowDown": next = addDays(from, 7); break;
      case "Home": next = addDays(from, -weekdayIndex(from)); break;
      case "End": next = addDays(from, 6 - weekdayIndex(from)); break;
      case "PageUp": next = addMonths(from, e.shiftKey ? -12 : -1); break;
      case "PageDown": next = addMonths(from, e.shiftKey ? 12 : 1); break;
      default: return;
    }
    e.preventDefault();
    // A day outside the range is skipped to the nearest allowed day. If that is where we already are, nothing moves.
    if (min && next < min) next = min;
    if (next > latest) next = latest;
    if (next === from) return;
    moved.current = true;
    setFocus(next);
    show(next);
  };

  const quick = [{ text: "Today", iso: today }, { text: "Yesterday", iso: addDays(today, -1) }].filter((q) => allowed(q.iso));
  const heads = Array.from({ length: 7 }, (_, i) => {
    const iso = addDays("2024-01-01", i); // a Monday
    return { short: fmt(iso, { weekday: "narrow" }), long: fmt(iso, { weekday: "long" }) };
  });

  return (
    <div className={cn("w-fit max-w-full rounded-card border border-line bg-surface p-3", className)}>
      {quick.length > 0 && (
        <div className="flex gap-2 pb-3">
          {quick.map((q) => <Button key={q.text} variant="secondary" size="sm" className="pointer-coarse:h-11" onClick={() => pick(q.iso)}>{q.text}</Button>)}
        </div>
      )}
      <div className="flex items-center justify-between gap-2 pb-2">
        <p aria-live="polite" className="px-1 text-body leading-5 font-semibold text-fg">{fmt(firstOfView, { month: "long", year: "numeric" })}</p>
        <div className="flex gap-1">
          {([["Previous month", -1, prevDisabled], ["Next month", 1, nextDisabled]] as const).map(([name, by, off]) => (
            <button key={name} type="button" aria-label={name} disabled={off} onClick={() => shiftMonth(by)}
              className="inline-flex size-9 cursor-pointer items-center justify-center rounded-control text-fg transition-colors duration-[var(--dur-fast)] hover:bg-hover disabled:cursor-not-allowed disabled:text-disabled-fg disabled:hover:bg-transparent pointer-coarse:size-11">
              <ChevronIcon className={by < 0 ? "rotate-180" : undefined} />
            </button>
          ))}
        </div>
      </div>
      <div ref={grid} role="grid" aria-label={label} onKeyDown={onKey}>
        <div role="row" className="grid grid-cols-7">
          {heads.map((h) => (
            <div key={h.long} role="columnheader" className="flex h-8 items-center justify-center text-small leading-4 font-medium text-fg-muted">
              <abbr title={h.long} aria-hidden className="no-underline">{h.short}</abbr>
              <span className="sr-only">{h.long}</span>
            </div>
          ))}
        </div>
        {rows.map((row, r) => (
          <div key={r} role="row" className="grid grid-cols-7">
            {row.map((iso, c) => {
              if (!iso) return <div key={c} role="gridcell" aria-hidden className="size-10 pointer-coarse:size-11" />;
              const ok = allowed(iso), chosen = iso === value, isToday = iso === today;
              return (
                <div key={iso} role="gridcell" aria-selected={chosen} className="flex items-center justify-center">
                  <button type="button" data-day={iso} tabIndex={iso === tabDay ? 0 : -1} aria-disabled={!ok || undefined} aria-current={isToday ? "date" : undefined}
                    aria-label={`${fmt(iso, { weekday: "long" })}, ${fmt(iso, { day: "numeric" })} ${fmt(iso, { month: "long" })} ${fmt(iso, { year: "numeric" })}`}
                    onClick={() => pick(iso)}
                    className={cn(
                      "flex size-10 items-center justify-center rounded-full border-2 text-body leading-5 tabular-nums transition-colors duration-[var(--dur-fast)] pointer-coarse:size-11",
                      isToday ? "border-fg" : "border-transparent",
                      chosen ? "bg-accent font-semibold text-on-accent forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]"
                        : ok ? "cursor-pointer text-fg hover:bg-hover" : "cursor-not-allowed text-fg-subtle opacity-50",
                    )}>
                    {Number(iso.slice(8))}
                  </button>
                </div>
              );
            })}
          </div>
        ))}
      </div>
      <p aria-live="polite" className={cn("px-1 pt-3 text-body leading-5", value ? "font-medium text-fg" : "text-fg-muted")}>
        {value ? describe(value, today, fmt) : "Pick a date"}
      </p>
    </div>
  );
}
```

## tattva/molecules/DiagramFigure.tsx

```tsx
import { useId, useState, type ReactNode } from "react";
import { Button } from "../atoms/Button";
import { CodeBlock } from "./CodeBlock";

export interface DiagramFigureProps {
  /** Short alt. Names the diagram and says where the long description is. Becomes the figure's aria-label. */
  label: string;
  /** Visible caption. */
  caption: string;
  /** Long description, shown on the page for everyone. */
  description?: ReactNode;
  /** Text alternative shown under the diagram, such as an ordered list of steps. */
  alternative?: ReactNode;
  /** Textual definition of the diagram. Enables the view-source toggle. */
  source?: string;
  /** Language label for the source block. */
  sourceLanguage?: string;
  /** The diagram itself. */
  children: ReactNode;
}

/**
 * Accessible wrapper for a complex image. It renders a figure with a short aria-label, a caption,
 * a visible long description, an optional text alternative and a view-source toggle with copy.
 */
export function DiagramFigure({ label, caption, description, alternative, source, sourceLanguage = "text", children }: DiagramFigureProps) {
  const [open, setOpen] = useState(false);
  const uid = useId();
  const desc = `${uid}-desc`;
  const src = `${uid}-src`;
  return (
    <figure aria-label={label} className="m-0 min-w-0 rounded-card border border-line bg-surface p-4 sm:p-5">
      <div tabIndex={0} className="overflow-x-auto">{children}</div>
      <figcaption className="mt-4 border-t border-line pt-4">
        <p className="text-compact font-medium text-fg">{caption}</p>
        {description && <div id={desc} className="mt-1 max-w-[68ch] text-compact leading-6 text-fg-muted">{description}</div>}
      </figcaption>
      {alternative && <div className="mt-3 text-compact leading-6 text-fg-muted">{alternative}</div>}
      {source && (
        <div className="mt-3">
          <Button variant="secondary" size="sm" aria-expanded={open} aria-controls={src} onClick={() => setOpen((o) => !o)}>{open ? "Hide source" : "View source"}</Button>
          {open && <div id={src} className="mt-3"><CodeBlock code={source} language={sourceLanguage} /></div>}
        </div>
      )}
    </figure>
  );
}
```

## tattva/molecules/DiffFileRow.tsx

```tsx
import { useRef } from "react";
import { cn } from "../lib/cn";
import { useFocusAfter } from "../lib/useFocusAfter";
import { CheckIcon, FileIcon, XIcon } from "../lib/icons";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";

export type DiffHunkState = "pending" | "kept" | "rejected";
/** A file is "partial" when some hunks are kept and others are rejected or still pending. */
export type DiffReviewState = DiffHunkState | "partial";

export interface DiffFileRowProps {
  /** File path. The directory is shown muted and the file name in full weight. */
  path: string;
  /** Number of added lines. */
  added: number;
  /** Number of removed lines. */
  removed: number;
  state?: DiffReviewState;
  /** Marks the file whose hunks are currently shown. */
  selected?: boolean;
  onSelect?: () => void;
  onKeep?: () => void;
  onReject?: () => void;
  /** Return a kept or rejected file to pending. When omitted, decided rows show no undo. */
  onReset?: () => void;
  labels?: { keep?: string; reject?: string; kept?: string; rejected?: string; partial?: string; undo?: string; /** Spoken in place of the +/- counts. */ lines?: (added: number, removed: number) => string };
  className?: string;
}

/** Half-filled circle for a partly kept file. */
function PartialGlyph() {
  return (
    <svg width={11} height={11} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} aria-hidden>
      <circle cx="12" cy="12" r="9" /><path d="M12 3a9 9 0 0 1 0 18z" fill="currentColor" />
    </svg>
  );
}

/** One file in a change review: name, +/- counts and a per-file Keep and Reject. Decided files show a badge with an icon and text, and a partly kept file shows its badge beside the buttons. */
export function DiffFileRow({ path, added, removed, state = "pending", selected, onSelect, onKeep, onReject, onReset, labels, className }: DiffFileRowProps) {
  const l = { keep: "Keep", reject: "Reject", kept: "Kept", rejected: "Rejected", partial: "Partly kept", undo: "Undo", lines: (a: number, r: number) => `${a} ${a === 1 ? "line" : "lines"} added, ${r} removed`, ...labels };
  const slash = path.lastIndexOf("/");
  const dir = slash >= 0 ? path.slice(0, slash + 1) : "";
  const name = slash >= 0 ? path.slice(slash + 1) : path;
  const nameBtn = useRef<HTMLButtonElement>(null);
  // Keep, Reject and Undo disappear once pressed. Focus then lands on the file name, which says the new state.
  const arm = useFocusAfter(state, () => nameBtn.current);
  const act = (f?: () => void) => () => { arm(); f?.(); };
  const stateWord = state === "kept" ? l.kept : state === "rejected" ? l.rejected : state === "partial" ? l.partial : "";
  return (
    <div className={cn("flex flex-wrap items-center gap-x-3 gap-y-1.5 rounded-card px-3 py-2", selected ? "bg-selected" : "hover:bg-hover", className)}>
      <button ref={nameBtn} type="button" onClick={onSelect} aria-current={selected ? "true" : undefined}
        className="flex min-w-36 flex-1 cursor-pointer items-center gap-2 rounded-control text-left">
        <FileIcon width={14} height={14} className="shrink-0 text-fg-subtle" />
        <span className="min-w-0 truncate text-compact">
          <span className={selected ? "text-fg-muted" : "text-fg-subtle"}>{dir}</span>
          <span className={cn("font-medium", state === "rejected" && "line-through text-fg-muted")}>{name}</span>
          {stateWord && <span className="sr-only">, {stateWord}</span>}
        </span>
      </button>
      <span className="font-mono text-small leading-4 tabular-nums">
        <span aria-hidden className="text-success-fg">+{added}</span>{" "}
        <span aria-hidden className="text-danger-fg">&minus;{removed}</span>
        <span className="sr-only">{l.lines(added, removed)}</span>
      </span>
      {state === "pending" || state === "partial" ? (
        <span key="open" className="flex flex-wrap items-center gap-1.5">
          {state === "partial" && (
            <Badge tone="neutral"><PartialGlyph />{l.partial}</Badge>
          )}
          <Button size="sm" variant="secondary" onClick={act(onKeep)} aria-label={`${l.keep} ${path}`}>{l.keep}</Button>
          <Button size="sm" variant="ghost" onClick={act(onReject)} aria-label={`${l.reject} ${path}`}>{l.reject}</Button>
          {state === "partial" && onReset && <Button size="sm" variant="ghost" onClick={act(onReset)} aria-label={`${l.undo}: ${path}`}>{l.undo}</Button>}
        </span>
      ) : (
        <span key="decided" className="flex items-center gap-1.5">
          <Badge tone={state === "kept" ? "success" : "danger"}>
            {state === "kept" ? <CheckIcon width={11} height={11} /> : <XIcon width={11} height={11} />}
            {state === "kept" ? l.kept : l.rejected}
          </Badge>
          {onReset && <Button size="sm" variant="ghost" onClick={act(onReset)} aria-label={`${l.undo}: ${path}`}>{l.undo}</Button>}
        </span>
      )}
    </div>
  );
}
```

## tattva/molecules/DraftPreview.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { ChevronIcon } from "../lib/icons";

export interface DraftHeader {
  label: string;
  value: ReactNode;
}

export interface DraftPreviewProps {
  /** Rows like To, From and Subject, drawn as a labelled list above the body. */
  headers?: DraftHeader[];
  /** The body text. */
  children: ReactNode;
  /** Shown in the expand button, such as "Read all 95 words". */
  wordCount?: number;
  /** How many lines show before the text fades out. */
  previewLines?: number;
  defaultOpen?: boolean;
  /** Controlled open state. Pair with onOpenChange. */
  open?: boolean;
  onOpenChange?: (open: boolean) => void;
  /** Text of the expand button. */
  expandLabel?: (words: number | undefined) => string;
  /** Text of the collapse button. */
  collapseLabel?: string;
  /** A line under the message, such as "Nothing goes out without your tap." */
  footer?: ReactNode;
  className?: string;
}

const LINE = 1.625;
const defaultExpand = (words: number | undefined) => (words === undefined ? "Read all" : `Read all ${words} words`);

/** A drafted message shown as what it is, with headers and a body, so no one approves text they have not read. It never opens by itself. */
export function DraftPreview({ headers, children, wordCount, previewLines = 4, defaultOpen = false, open, onOpenChange, expandLabel = defaultExpand, collapseLabel = "Show less", footer, className }: DraftPreviewProps) {
  const [inner, setInner] = useState(defaultOpen);
  const isOpen = open ?? inner;
  const [overflows, setOverflows] = useState(true);
  const bodyRef = useRef<HTMLDivElement>(null);
  const id = useId();

  useEffect(() => {
    const el = bodyRef.current;
    if (!el || isOpen) return;
    const measure = () => setOverflows(el.scrollHeight > el.clientHeight + 1);
    measure();
    const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(measure);
    ro?.observe(el);
    return () => ro?.disconnect();
  }, [isOpen, previewLines, children]);

  const toggle = () => { const next = !isOpen; if (open === undefined) setInner(next); onOpenChange?.(next); };
  const clamped = !isOpen && overflows;
  return (
    <section className={cn("min-w-0 overflow-hidden rounded-card border border-line bg-surface forced-colors:border-[CanvasText]", className)}>
      {headers && headers.length > 0 && (
        <dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 border-b border-line bg-sunken px-4 py-3 text-body leading-5">
          {headers.map((h) => (
            <div key={h.label} className="col-span-2 grid grid-cols-subgrid items-baseline">
              <dt className="text-fg-muted">{h.label}</dt>
              <dd className="m-0 min-w-0 text-fg [overflow-wrap:anywhere]">{h.value}</dd>
            </div>
          ))}
        </dl>
      )}
      <div className="px-4 py-3">
        <div className="relative">
          <div ref={bodyRef} id={id} className={cn("text-body leading-5 text-fg [overflow-wrap:anywhere]", !isOpen && "overflow-hidden")}
            style={{ lineHeight: LINE, maxHeight: isOpen ? undefined : `${previewLines * LINE}em` }}>{children}</div>
          {clamped && <div aria-hidden className="pointer-events-none absolute inset-x-0 bottom-0 h-10 bg-linear-to-t from-surface to-transparent" />}
        </div>
        {(overflows || isOpen) && (
          <button type="button" aria-expanded={isOpen} aria-controls={id} onClick={toggle}
            className="mt-2 inline-flex min-h-6 cursor-pointer items-center gap-1 rounded-md text-body leading-5 font-medium text-fg underline-offset-4 hover:underline pointer-coarse:min-h-11">
            <ChevronIcon aria-hidden width={14} height={14} className={cn("shrink-0 text-fg-subtle transition-transform duration-[var(--dur-fast)]", isOpen ? "-rotate-90" : "rotate-90")} />
            {isOpen ? collapseLabel : expandLabel(wordCount)}
          </button>
        )}
      </div>
      {footer != null && <div className="border-t border-line px-4 py-2.5 text-small leading-4 text-fg-muted">{footer}</div>}
    </section>
  );
}
```

## tattva/molecules/EditableTurn.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import { cn } from "../lib/cn";
import { CopyIcon, EditIcon } from "../lib/icons";
import { Button, IconButton } from "../atoms/Button";
import { VersionPager } from "./VersionPager";

export interface EditableTurnLabels {
  edit?: string;
  copy?: string;
  copied?: string;
  send?: string;
  cancel?: string;
  /** Name of the edit box for screen readers. */
  field?: string;
  /** One line under the edit box. */
  note?: string;
}

export interface EditableTurnProps {
  /** Every version of this message the person has sent, oldest first. */
  versions: string[];
  /** Which version is showing, 1-based. Defaults to the newest. */
  current?: number;
  /** Shows another version (and the replies that followed it). */
  onChangeVersion?: (version: number) => void;
  /** Sends the edited text as a new version. The replies after this message are written again. */
  onResend: (text: string) => void;
  /** Copies the message. The button is hidden when omitted. */
  onCopy?: (text: string) => void;
  labels?: EditableTurnLabels;
  className?: string;
}

/**
 * A message the person sent, which they can edit and send again. Sending an edit starts a new branch: the earlier
 * versions stay, and a small pager (◀ 2 of 2 ▶) moves between them. The Edit and Copy buttons sit under the
 * bubble and are always there, not only on hover, so touch and keyboard users can reach them.
 */
export function EditableTurn({ versions, current, onChangeVersion, onResend, onCopy, labels, className }: EditableTurnProps) {
  const l = {
    edit: "Edit", copy: "Copy", copied: "Copied", send: "Send", cancel: "Cancel", field: "Edit your message",
    note: "Sending this writes the replies after it again. Your earlier version stays.", ...labels,
  };
  const at = Math.min(Math.max(current ?? versions.length, 1), versions.length);
  const text = versions[at - 1] ?? "";
  const [editing, setEditing] = useState(false);
  const [draft, setDraft] = useState(text);
  const [copied, setCopied] = useState(false);
  const box = useRef<HTMLTextAreaElement>(null);
  const editBtn = useRef<HTMLButtonElement>(null);
  const uid = useId();
  const back = useRef(false);
  useEffect(() => {
    if (editing) { const el = box.current; el?.focus(); el?.setSelectionRange(el.value.length, el.value.length); }
    else if (back.current) { back.current = false; editBtn.current?.focus(); }
  }, [editing]);
  const start = () => { setDraft(text); setEditing(true); };
  const stop = () => { back.current = true; setEditing(false); };
  const send = () => { const t = draft.trim(); if (!t) return; back.current = true; setEditing(false); if (t !== text.trim()) onResend(t); };
  if (editing) {
    return (
      <div className={cn("flex justify-end", className)}>
        <div className="w-full max-w-[80%] max-sm:max-w-full">
          <label htmlFor={`${uid}-box`} className="sr-only">{l.field}</label>
          <textarea id={`${uid}-box`} ref={box} value={draft} onChange={(e) => setDraft(e.target.value)} rows={3} aria-describedby={`${uid}-note`}
            onKeyDown={(e) => { if (e.key === "Escape") { e.preventDefault(); stop(); } if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); send(); } }}
            className="block w-full resize-y rounded-card border border-line-strong bg-surface px-4 py-3 text-body-lg leading-relaxed text-fg" />
          <p id={`${uid}-note`} className="m-0 mt-2 text-small leading-5 text-fg-muted">{l.note}</p>
          <div className="mt-3 flex justify-end gap-2">
            <Button size="sm" variant="ghost" onClick={stop}>{l.cancel}</Button>
            <Button size="sm" onClick={send} disabled={!draft.trim()}>{l.send}</Button>
          </div>
        </div>
      </div>
    );
  }
  return (
    <div className={cn("flex flex-col items-end gap-1.5", className)}>
      <div className="max-w-[80%] rounded-card rounded-br-[min(var(--radius-lg),var(--shape-card))] bg-sunken px-5 py-3 text-body-lg leading-relaxed whitespace-pre-wrap text-fg">{text}</div>
      <div className="flex items-center gap-0.5">
        {versions.length > 1 && <VersionPager total={versions.length} current={at} onChange={onChangeVersion} edited={at > 1} label="Versions of your message" />}
        {onCopy && (
          <IconButton size="sm" label={copied ? l.copied : l.copy} onClick={() => { onCopy(text); setCopied(true); window.setTimeout(() => setCopied(false), 1500); }}>
            <CopyIcon width={14} height={14} />
          </IconButton>
        )}
        <IconButton ref={editBtn} size="sm" label={l.edit} onClick={start}><EditIcon width={14} height={14} /></IconButton>
      </div>
    </div>
  );
}
```

## tattva/molecules/EmptyState.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon } from "../lib/icons";
import { Illustration, type IllustrationName } from "../atoms/Illustration";

export type EmptyStateVariant = "first-use" | "no-results" | "cleared" | "error" | "permission" | "offline" | "rate-limit";

interface Preset {
  art: IllustrationName;
  title: string;
  body: string;
  /** True when a person must act, which is the only time amber appears. */
  attention: boolean;
  /** Whether the region announces itself when it appears. */
  live: boolean;
}

/** Default copy follows "cause, then next step". Every string can be overridden. */
const presets: Record<EmptyStateVariant, Preset> = {
  "first-use": { art: "empty-chat", title: "Start your first chat", body: "Nothing is here yet. Ask a question or add a file to begin.", attention: false, live: false },
  "no-results": { art: "no-results", title: "No results for these filters", body: "Try a shorter search or clear the filters.", attention: false, live: true },
  cleared: { art: "success", title: "Everything here is done", body: "New items will show up as they arrive.", attention: false, live: false },
  error: { art: "error", title: "We could not load this", body: "The request failed on our side and your work is safe. Try again.", attention: false, live: false },
  permission: { art: "permission", title: "You need access to see this", body: "This project is private. Ask an owner to add you.", attention: true, live: false },
  offline: { art: "offline", title: "You are offline", body: "Changes are saved on this device and will sync when you reconnect.", attention: false, live: true },
  "rate-limit": { art: "rate-limit", title: "You reached the usage limit", body: "Wait for the limit to reset or upgrade your plan to continue.", attention: true, live: false },
};

export interface EmptyStateProps {
  /** Preset that sets the default art, copy, tone and live behavior. */
  variant?: EmptyStateVariant;
  /** Art slot. Omit for the variant's illustration, pass `null` for none, or pass an Illustration or Pictogram. */
  art?: ReactNode | null;
  /** Names the cause or invites the first step. */
  title?: string;
  /** One short line that gives the next step. */
  children?: ReactNode;
  /** The one primary action, usually a Button. */
  action?: ReactNode;
  /** Optional secondary link or ghost button. */
  secondary?: ReactNode;
  /** `center` suits full panels. `start` left-aligns text for small areas. */
  layout?: "center" | "start";
  /** Drops the art for dense contexts such as tables and menus. */
  compact?: boolean;
  /** Heading level of the title. */
  headingLevel?: 2 | 3 | 4;
  /** Overrides whether the region has role="status". Defaults per variant: no-results and offline only. */
  live?: boolean;
  /** Label of the amber chip shown for permission and rate-limit. */
  attentionLabel?: string;
  className?: string;
}

/**
 * Message for an area with nothing to show. It names the cause, gives one next step and optionally shows art.
 * Presets cover first use, no results, cleared, error, permission, offline and rate limit.
 */
export function EmptyState({
  variant = "first-use", art, title, children, action, secondary, layout = "center", compact = false,
  headingLevel = 3, live, attentionLabel = "Action needed", className,
}: EmptyStateProps) {
  const p = presets[variant];
  const Heading = `h${headingLevel}` as "h3";
  const center = layout === "center";
  const artNode = compact ? null : art === undefined ? <Illustration name={p.art} size={center ? 96 : 64} /> : art;
  const isLive = live ?? p.live;
  return (
    <div
      role={isLive ? "status" : undefined}
      className={cn("flex flex-col gap-4 rounded-card border border-line bg-surface p-6", center ? "items-center text-center" : "items-start text-left", compact && "gap-3 p-4", className)}
    >
      {artNode && <div aria-hidden={art === undefined ? true : undefined} className="shrink-0">{artNode}</div>}
      <div className={cn("min-w-0 max-w-[44ch]", center && "mx-auto")}>
        {p.attention && (
          <p className={cn("mb-2 inline-flex items-center gap-1.5 rounded-control bg-attention-soft px-2.5 py-0.5 text-micro font-medium text-attention-fg")}>
            <AlertIcon width={12} height={12} />{attentionLabel}
          </p>
        )}
        <Heading className="text-lead-lg leading-6 [overflow-wrap:anywhere]">{title ?? p.title}</Heading>
        <p className="mt-1.5 text-body leading-6 text-fg-muted">{children ?? p.body}</p>
      </div>
      {(action || secondary) && (
        <div className={cn("flex flex-wrap items-center gap-2", center && "justify-center")}>
          {action}{secondary}
        </div>
      )}
    </div>
  );
}
```

## tattva/molecules/EnhanceCompare.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import type { KeyboardEvent } from "react";
import { cn } from "../lib/cn";
import { type HeadingLevel, headingTag } from "../lib/useFocusAfter";
import { AlertIcon, RetryIcon, SparkleIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { Checkbox } from "../atoms/Checkbox";
import { Kbd } from "../atoms/Kbd";
import { SegmentedControl } from "../atoms/SegmentedControl";
import { Slider } from "../atoms/Slider";
import { AudioPlayer } from "./AudioPlayer";

/** idle: not cleaned up yet. processing: the AI is cleaning it up. ready: Before and After can be compared. failed: the clean-up did not work. */
export type EnhanceStatus = "idle" | "processing" | "ready" | "failed";
export type EnhanceListening = "before" | "after";

export interface EnhanceKeepPart {
  id: string;
  /** What to keep, such as "Keep laughter" or "Keep room sound". */
  label: string;
  on: boolean;
}

export interface EnhanceCompareProps {
  /** Name of the recording. Also names the player. */
  title: string;
  /** Length in seconds. */
  duration: number;
  /** Cleanup strength, 0 (Natural) to 100 (Clean). */
  strength: number;
  onStrengthChange: (n: number) => void;
  /** Which version is playing. Switching keeps the position. */
  listening: EnhanceListening;
  onListeningChange: (v: EnhanceListening) => void;
  status: EnhanceStatus;
  /** Starts or repeats the clean-up with the current settings. Shows the Clean up button. */
  onProcess?: () => void;
  /** Cost shown beside Clean up, such as "Free on your plan" or "about 2 credits". */
  costNote?: string;
  /** Sounds the person can choose to keep. */
  keepParts?: EnhanceKeepPart[];
  onKeepPartChange?: (id: string, on: boolean) => void;
  /** Loudness per slice of the original. */
  beforePeaks?: number[];
  /** Loudness per slice of the cleaned version. */
  afterPeaks?: number[];
  /** The original file. Leave both files out to simulate playback. */
  beforeSrc?: string;
  /** The cleaned file. */
  afterSrc?: string;
  /** Message in the failed state. */
  errorText?: string;
  /** Heading of the panel. */
  heading?: string;
  headingLevel?: HeadingLevel;
  className?: string;
}

const isTyping = (el: EventTarget | null) => el instanceof HTMLElement && (el.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName));
const strengthWords = (n: number) => `${n} percent, ${n < 34 ? "closer to Natural" : n > 66 ? "closer to Clean" : "between Natural and Clean"}`;
const settingsKey = (strength: number, parts?: EnhanceKeepPart[]) => `${strength}|${(parts ?? []).filter((p) => p.on).map((p) => p.id).join(",")}`;

/**
 * Cleans up a recording and lets the person hear the difference. One strength slider runs from Natural to Clean,
 * a few check boxes keep sounds worth keeping, and a Before and After switch changes version at the same moment,
 * so the change is heard straight away instead of after a new render. The After version carries the AI label; the original does not.
 */
export function EnhanceCompare({
  title, duration, strength, onStrengthChange, listening, onListeningChange, status, onProcess, costNote, keepParts, onKeepPartChange,
  beforePeaks, afterPeaks, beforeSrc, afterSrc, errorText = "The clean-up did not finish. Your recording is unchanged.", heading = "Clean up sound", headingLevel = 3, className,
}: EnhanceCompareProps) {
  const id = useId();
  const H = headingTag(headingLevel);
  const [pos, setPos] = useState(0);
  const [current, setCurrent] = useState<number | undefined>(undefined);
  const lastPos = useRef(0);
  const switchedAt = useRef(0);
  const nudge = useRef(0);
  const applied = useRef<string | null>(null);

  const ready = status === "ready";
  const showing: EnhanceListening = ready ? listening : "before";
  const key = settingsKey(strength, keepParts);
  // Remember the settings the After version was made with, so a later change can say it is not applied yet.
  useEffect(() => { if (ready && applied.current === null) applied.current = key; if (status === "processing") applied.current = null; }, [ready, status, key]);
  const changed = ready && applied.current !== null && applied.current !== key;

  const setListening = (v: EnhanceListening) => {
    if (v === listening || !ready) return;
    lastPos.current = pos;
    switchedAt.current = performance.now();
    onListeningChange(v);
  };

  // A real file restarts at zero when it changes. Put it back where the person was, so the switch is a true A/B.
  const onTime = (t: number) => {
    if (performance.now() - switchedAt.current < 1500 && t < lastPos.current - 0.5) {
      nudge.current = (nudge.current + 1) % 2;
      setCurrent(lastPos.current + nudge.current * 0.001);
      return;
    }
    setPos(t);
  };

  // B switches Before and After while focus is inside, but never while typing or on the slider.
  const onKey = (e: KeyboardEvent<HTMLElement>) => {
    if (e.altKey || e.ctrlKey || e.metaKey || isTyping(e.target)) return;
    if ((e.key === "b" || e.key === "B") && ready) { e.preventDefault(); setListening(listening === "before" ? "after" : "before"); }
  };

  const statusText = status === "processing" ? "Cleaning up. You can keep listening to the original."
    : status === "ready" ? "Clean-up ready. Switch between Before and After to compare."
    : status === "failed" ? `Clean-up failed. ${errorText}` : "";

  return (
    <section aria-labelledby={`${id}-h`} onKeyDown={onKey} className={cn("@container flex min-w-0 flex-col gap-4 rounded-card border border-line bg-surface p-3 sm:p-4", className)}>
      <header className="flex flex-wrap items-center justify-between gap-2">
        <H id={`${id}-h`} className="text-body leading-5 font-semibold text-fg">{heading}</H>
        {ready && (
          <SegmentedControl<EnhanceListening> label="Listen to" value={listening} onChange={setListening}
            options={[{ value: "before", label: "Before" }, { value: "after", label: "After" }]} className="pointer-coarse:[&_button]:min-h-11" />
        )}
      </header>

      <AudioPlayer title={title} duration={duration} speeds={[1]}
        subtitle={showing === "after" ? "Listening to After, cleaned up" : ready ? "Listening to Before, the original" : "The original"}
        peaks={showing === "after" ? afterPeaks ?? beforePeaks : beforePeaks}
        src={showing === "after" ? afterSrc : beforeSrc}
        aiGenerated={showing === "after"} aiNote="The After version was cleaned up by AI. The original is kept as it was."
        current={current} onTimeChange={onTime} />

      <div className="flex flex-col gap-1">
        <span aria-hidden className="text-compact font-medium text-fg">Strength</span>
        <Slider label="Cleanup strength" value={strength} onChange={onStrengthChange} min={0} max={100} step={5}
          formatValue={strengthWords} disabled={status === "processing"} />
        <div aria-hidden className="flex justify-between text-small text-fg-muted">
          <span>Natural</span><span>Clean</span>
        </div>
      </div>

      {keepParts && keepParts.length > 0 && (
        <fieldset className="flex flex-col gap-1" disabled={status === "processing"}>
          <legend className="mb-1 text-compact font-medium text-fg">Sounds to keep</legend>
          <div className="flex flex-wrap gap-x-5 gap-y-1">
            {keepParts.map((p) => (
              <Checkbox key={p.id} checked={p.on} label={p.label} disabled={status === "processing"} onChange={(on) => onKeepPartChange?.(p.id, on)} />
            ))}
          </div>
        </fieldset>
      )}

      {status === "failed" && (
        <div className="flex flex-wrap items-center gap-2 rounded-field bg-sunken px-3 py-2 text-compact text-fg">
          <AlertIcon width={16} height={16} aria-hidden className="shrink-0 text-danger-fg" />
          <span className="min-w-40 flex-1">{errorText}</span>
        </div>
      )}

      <footer className="flex flex-wrap items-center gap-x-3 gap-y-2 border-t border-line pt-3">
        <p className="flex min-w-40 flex-1 items-center gap-2 text-small text-fg-muted">
          {status === "processing" && <span aria-hidden className="size-2 shrink-0 rounded-full bg-lime ring-1 ring-fg-muted motion-safe:animate-pulse-dot" />}
          {status === "idle" && "Not cleaned up yet. Press Clean up, then compare Before and After."}
          {status === "processing" && "Cleaning up. You can keep listening to the original."}
          {ready && !changed && <span className="flex flex-wrap items-center gap-1.5">Cleaned up. <span className="pointer-coarse:hidden">Press <Kbd>B</Kbd> to switch.</span></span>}
          {changed && "Settings changed. Apply them to hear the new After."}
          {status === "failed" && "Nothing was changed."}
        </p>
        {onProcess && status !== "processing" && (status !== "ready" || changed) && (
          <span className="flex flex-wrap items-center gap-2">
            {costNote && <span className="text-small text-fg-muted">{costNote}</span>}
            <Button size="sm" variant={status === "failed" ? "secondary" : "lime"} onClick={onProcess} className="pointer-coarse:h-11"
              leading={status === "failed" ? <RetryIcon width={14} height={14} /> : <SparkleIcon width={14} height={14} />}>
              {status === "failed" ? "Try again" : changed ? "Apply" : "Clean up"}
            </Button>
          </span>
        )}
      </footer>

      <span role="status" className="sr-only">{statusText}</span>
    </section>
  );
}
```

## tattva/molecules/ErrorState.tsx

```tsx
import type { ReactNode } from "react";
import { Button } from "../atoms/Button";
import { Callout } from "./Callout";

/** Recoverable failure: say what happened, whether the user's input is safe, and offer a next step. */
export function ErrorState({ title = "Something went wrong", message, onRetry }: { title?: string; message: ReactNode; onRetry?: () => void }) {
  return <Callout tone="danger" title={title} action={onRetry && <Button size="sm" variant="secondary" onClick={onRetry}>Try again</Button>}>{message}</Callout>;
}
```

## tattva/molecules/FactList.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { Skeleton } from "../atoms/Skeleton";

export interface Fact {
  /** What the value is: "Check-in", "Plan". */
  label: string;
  /** The value itself: "3pm", "Pro". Text or a small part such as a Badge. */
  value: ReactNode;
}

export interface FactListProps {
  facts: Fact[];
  /** "split" puts the label and the value side by side; "stacked" puts the label above the value, for narrow places. */
  layout?: "split" | "stacked";
  /** On wide screens, lays the facts out in two columns. */
  columns?: 1 | 2;
  /** Shows placeholder rows while the facts are still arriving. */
  loading?: boolean;
  /** How many placeholder rows to show while loading. */
  loadingRows?: number;
  /** Said when there are no facts. Nothing is drawn when omitted. */
  emptyLabel?: string;
  className?: string;
}

/**
 * Short label and value pairs, such as "Check-in: 3pm" or "Plan: Pro". A description list, so each value is read
 * with its label. Light enough to sit in an answer or a card, and easy to scan on a phone.
 */
export function FactList({ facts, layout = "split", columns = 1, loading = false, loadingRows = 3, emptyLabel, className }: FactListProps) {
  if (loading) {
    return (
      <div aria-busy="true" className={cn("grid gap-2", className)}>
        {Array.from({ length: loadingRows }, (_, i) => (
          <div key={i} className="flex items-center justify-between gap-4"><Skeleton className="h-4 w-24" /><Skeleton className="h-4 w-16" /></div>
        ))}
      </div>
    );
  }
  if (facts.length === 0) return emptyLabel ? <p className={cn("m-0 text-body leading-5 text-fg-muted", className)}>{emptyLabel}</p> : null;
  return (
    <dl className={cn("m-0 grid gap-x-8", columns === 2 && "sm:grid-cols-2", className)}>
      {facts.map((f, i) => (
        <div key={`${f.label}-${i}`}
          className={cn("min-w-0 py-1.5", layout === "split" ? "flex items-baseline justify-between gap-4" : "flex flex-col gap-0.5")}>
          <dt className="shrink-0 text-body leading-5 text-fg-muted">{f.label}</dt>
          <dd className={cn("m-0 min-w-0 text-body leading-5 font-medium text-fg [overflow-wrap:anywhere]", layout === "split" && "text-right")}>{f.value}</dd>
        </div>
      ))}
    </dl>
  );
}
```

## tattva/molecules/FeedbackBar.tsx

```tsx
import { useEffect, useRef, useState } from "react";
import { ThumbDownIcon, ThumbUpIcon } from "../lib/icons";
import { Button, IconButton } from "../atoms/Button";

export type Rating = "up" | "down" | null;
const reasons = ["Inaccurate", "Not helpful", "Unsafe", "Too long", "Other"];

export interface FeedbackBarProps {
  /** Called once per message. Thumbs up sends the rating. Thumbs down sends the rating with a reason, or alone when the person chooses Skip. */
  onSubmit: (rating: Exclude<Rating, null>, reason?: string) => void;
  /** Any value. When it changes, for example to the message id, the bar returns to its starting state. */
  resetKey?: unknown;
}

/** Thumbs up and down for one response. After submitting, the chosen thumb stays pressed and a thank-you line sits beside it. */
export function FeedbackBar({ onSubmit, resetKey }: FeedbackBarProps) {
  const [rating, setRating] = useState<Rating>(null);
  const [done, setDone] = useState(false);
  const first = useRef(true);

  useEffect(() => {
    if (first.current) { first.current = false; return; }
    setRating(null); setDone(false);
  }, [resetKey]);

  const submit = (r: Exclude<Rating, null>, reason?: string) => { setRating(r); setDone(true); if (reason === undefined) onSubmit(r); else onSubmit(r, reason); };

  return (
    <div className="space-y-2">
      <div className="-ml-1.5 flex items-center gap-0.5">
        <IconButton size="sm" label="Good response" active={rating === "up"} onClick={() => { if (!done) submit("up"); }}><ThumbUpIcon /></IconButton>
        <IconButton size="sm" label="Bad response" active={rating === "down"} onClick={() => { if (!done) setRating("down"); }}><ThumbDownIcon /></IconButton>
        <p className="ml-1.5 text-small leading-4 text-fg-muted" role="status">{done ? "Thanks, your feedback helps improve responses." : ""}</p>
      </div>
      {rating === "down" && !done && (
        <div className="flex flex-wrap items-center gap-1.5" role="group" aria-label="What went wrong?">
          <span className="text-small leading-4 text-fg-muted">What went wrong?</span>
          {reasons.map((r) => <Button key={r} size="sm" variant="secondary" className="h-7 px-2.5 text-small leading-4" onClick={() => submit("down", r)}>{r}</Button>)}
          <Button size="sm" variant="ghost" className="h-7 px-2.5 text-small leading-4" onClick={() => submit("down")}>Skip</Button>
        </div>
      )}
    </div>
  );
}
```

## tattva/molecules/Field.tsx

```tsx
import { useId } from "react";
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon } from "../lib/icons";

export interface FieldControlProps {
  id: string;
  "aria-describedby"?: string;
  "aria-invalid"?: boolean;
  required?: boolean;
}

export interface FieldProps {
  /** Visible label, joined to the control with htmlFor. */
  label: string;
  /** Help text under the control. */
  hint?: string;
  /** Error message. Setting it marks the control invalid. */
  error?: string;
  required?: boolean;
  /** Shows "(optional)" after the label. */
  optional?: boolean;
  /** Receives the id and aria props to spread onto the control. */
  children: (control: FieldControlProps) => ReactNode;
  className?: string;
}

/** Label, control, hint and error in one wrapper, with the ids wired for you. */
export function Field({ label, hint, error, required, optional, children, className }: FieldProps) {
  const uid = useId();
  const id = `${uid}-control`, hintId = `${uid}-hint`, errorId = `${uid}-error`;
  const describedBy = [hint && hintId, error && errorId].filter(Boolean).join(" ") || undefined;
  return (
    <div className={cn("flex flex-col gap-1.5", className)}>
      <label htmlFor={id} className="text-body leading-5 font-medium text-fg">
        {label}
        {required && <span aria-hidden className="ml-0.5 text-fg-muted">*</span>}
        {optional && !required && <span className="ml-1 font-normal text-fg-muted">(optional)</span>}
      </label>
      {children({ id, "aria-describedby": describedBy, "aria-invalid": error ? true : undefined, required: required || undefined })}
      {hint && <p id={hintId} className="text-small leading-4 text-fg-muted">{hint}</p>}
      {/* Always mounted so screen readers pick up the message when it appears. */}
      <div id={errorId} aria-live="polite" className="empty:-mt-1.5">
        {error && <p className="flex items-start gap-1.5 text-small leading-4 text-danger-fg"><AlertIcon className="mt-px shrink-0" width={14} height={14} />{error}</p>}
      </div>
    </div>
  );
}
```

## tattva/molecules/FileDropZone.tsx

```tsx
import { useId, useRef, useState } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, PaperclipIcon } from "../lib/icons";
import { Button } from "../atoms/Button";

export interface FileDropZoneLabels {
  title?: string;
  browse?: string;
  drop?: string;
  /** Receives the accepted kinds and the size limit, already in words. */
  hint?: (kinds: string, size: string) => string;
  /** Receives the file name. */
  wrongKind?: (name: string) => string;
  /** Receives the file name and the limit. */
  tooBig?: (name: string, size: string) => string;
}

export interface FileDropZoneProps {
  /** Called with the files that passed the checks. */
  onFiles: (files: File[]) => void;
  /** Accepted extensions, such as ["pdf", "docx", "png"]. Any file when omitted. */
  accept?: string[];
  /** Largest file in megabytes. */
  maxSizeMb?: number;
  multiple?: boolean;
  disabled?: boolean;
  labels?: FileDropZoneLabels;
  className?: string;
}

/**
 * Where people add files for the AI to read: drop them on the area or press Browse. It says up front which kinds
 * and sizes work, and when a file does not, it says which one and why, instead of failing quietly. Browse is a real
 * button, so keyboard and screen reader users never need to drag.
 */
export function FileDropZone({ onFiles, accept, maxSizeMb, multiple = true, disabled, labels, className }: FileDropZoneProps) {
  const l = {
    title: "Add files", browse: "Browse files", drop: "Drop to add",
    hint: (k: string, s: string) => [k, s].filter(Boolean).join(". ") + (k || s ? "." : ""),
    wrongKind: (n: string) => `${n} is a kind of file that cannot be read here.`,
    tooBig: (n: string, s: string) => `${n} is larger than ${s}.`, ...labels,
  };
  const [over, setOver] = useState(false);
  const [errors, setErrors] = useState<string[]>([]);
  const input = useRef<HTMLInputElement>(null);
  const uid = useId();
  const sizeText = maxSizeMb ? `${maxSizeMb} MB` : "";
  const kindsText = accept?.length ? `${accept.map((a) => a.toUpperCase()).join(", ")}` : "";
  const take = (list: FileList | null) => {
    if (!list || disabled) return;
    const ok: File[] = [], bad: string[] = [];
    for (const f of [...list].slice(0, multiple ? undefined : 1)) {
      const ext = f.name.split(".").pop()?.toLowerCase() ?? "";
      if (accept?.length && !accept.map((a) => a.toLowerCase()).includes(ext)) bad.push(l.wrongKind(f.name));
      else if (maxSizeMb && f.size > maxSizeMb * 1024 * 1024) bad.push(l.tooBig(f.name, sizeText));
      else ok.push(f);
    }
    setErrors(bad);
    if (ok.length) onFiles(ok);
  };
  return (
    <div className={cn("space-y-2", className)}>
      <div
        onDragOver={(e) => { if (disabled) return; e.preventDefault(); setOver(true); }}
        onDragLeave={() => setOver(false)}
        onDrop={(e) => { e.preventDefault(); setOver(false); take(e.dataTransfer.files); }}
        className={cn("flex flex-col items-center gap-3 rounded-card border-2 border-dashed px-4 py-8 text-center transition-colors duration-(--dur-fast) motion-reduce:transition-none",
          over ? "border-selected-line bg-selected" : "border-line-strong bg-bg", disabled && "opacity-(--disabled-opacity)")}>
        <PaperclipIcon aria-hidden width={22} height={22} className="text-fg-muted" />
        <p className="m-0 text-body leading-5 font-medium text-fg">{over ? l.drop : l.title}</p>
        {(kindsText || sizeText) && <p id={`${uid}-hint`} className="m-0 text-small leading-5 text-fg-muted">{l.hint(kindsText, sizeText ? `Up to ${sizeText}` : "")}</p>}
        <Button size="sm" variant="secondary" disabled={disabled} onClick={() => input.current?.click()} aria-describedby={kindsText || sizeText ? `${uid}-hint` : undefined}>{l.browse}</Button>
        <input ref={input} type="file" multiple={multiple} hidden tabIndex={-1} accept={accept?.map((a) => `.${a}`).join(",")}
          onChange={(e) => { take(e.target.files); e.target.value = ""; }} />
      </div>
      <div role="alert">
        {errors.map((e) => (
          <p key={e} className="m-0 flex items-start gap-1.5 text-small leading-5 text-danger-fg"><AlertIcon aria-hidden width={14} height={14} className="mt-0.5 shrink-0" />{e}</p>
        ))}
      </div>
    </div>
  );
}
```

## tattva/molecules/FileResultCard.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, DownloadIcon, FileIcon, ImageIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { Spinner } from "../atoms/Spinner";

export type FileKind = "document" | "sheet" | "slides" | "pdf" | "image" | "code" | "file";

export interface FileResultCardLabels {
  kinds?: Partial<Record<FileKind, string>>;
  open?: string;
  download?: string;
  retry?: string;
  /** Receives the file name. */
  making?: (name: string) => string;
  failed?: string;
}

export interface FileResultCardProps {
  /** The file's name, with its extension: "Q3 report.docx". */
  name: string;
  /** What kind of file it is. Worked out from the extension when omitted. */
  kind?: FileKind;
  /** Size or length, such as "24 KB" or "8 slides". */
  detail?: string;
  /** A small picture of the first page or the image itself. */
  preview?: ReactNode;
  status?: "ready" | "making" | "failed";
  /** Opens the file in the app (a canvas or a viewer). The button is hidden when omitted. */
  onOpen?: () => void;
  /** Downloads the file. The button is hidden when omitted. */
  onDownload?: () => void;
  /** Tries again after a failure. */
  onRetry?: () => void;
  labels?: FileResultCardLabels;
  className?: string;
}

const BY_EXT: Record<string, FileKind> = {
  doc: "document", docx: "document", md: "document", txt: "document", rtf: "document", odt: "document",
  xls: "sheet", xlsx: "sheet", csv: "sheet", ods: "sheet",
  ppt: "slides", pptx: "slides", key: "slides", odp: "slides",
  pdf: "pdf",
  png: "image", jpg: "image", jpeg: "image", gif: "image", webp: "image", svg: "image",
  js: "code", ts: "code", tsx: "code", py: "code", json: "code", html: "code", css: "code", sql: "code",
};
const KIND: Record<FileKind, string> = { document: "Document", sheet: "Spreadsheet", slides: "Slides", pdf: "PDF", image: "Image", code: "Code", file: "File" };

/** What kind of file a name is, from its extension. */
export const fileKindOf = (name: string): FileKind => BY_EXT[name.split(".").pop()?.toLowerCase() ?? ""] ?? "file";

/**
 * A file the AI made: a report, a spreadsheet, slides or an image. It shows what kind of file it is in words, a
 * small preview when there is one, and Open and Download. While the file is being made it says so; if making it
 * failed, it says so and offers to try again, instead of a broken link.
 */
export function FileResultCard({ name, kind, detail, preview, status = "ready", onOpen, onDownload, onRetry, labels, className }: FileResultCardProps) {
  const l = {
    open: "Open", download: "Download", retry: "Try again", making: (n: string) => `Making ${n}`, failed: "Could not make this file.", ...labels,
    kinds: { ...KIND, ...labels?.kinds },
  };
  const k = kind ?? fileKindOf(name);
  const Icon = k === "image" ? ImageIcon : FileIcon;
  return (
    <div className={cn("flex w-full max-w-md items-center gap-3 rounded-card border border-line bg-surface p-3", className)} aria-busy={status === "making" || undefined}>
      <div aria-hidden className="grid size-14 shrink-0 place-items-center overflow-hidden rounded-control border border-line bg-bg text-fg-muted">
        {status === "making" ? <Spinner label="" /> : preview ?? <Icon width={22} height={22} />}
      </div>
      <div className="min-w-0 flex-1">
        <p className="m-0 truncate text-body leading-5 font-medium text-fg" title={name}>{name}</p>
        <p className={cn("m-0 mt-0.5 text-small leading-5", status === "failed" ? "text-danger-fg" : "text-fg-muted")} role={status === "making" || status === "failed" ? "status" : undefined}>
          {status === "making" ? l.making(name) : status === "failed"
            ? <span className="inline-flex items-center gap-1"><AlertIcon aria-hidden width={13} height={13} />{l.failed}</span>
            : [l.kinds[k], detail].filter(Boolean).join(" · ")}
        </p>
      </div>
      {status === "ready" && (onOpen || onDownload) && (
        <div className="flex shrink-0 flex-wrap justify-end gap-1.5">
          {onOpen && <Button size="sm" variant="secondary" onClick={onOpen} aria-label={`${l.open} ${name}`}>{l.open}</Button>}
          {onDownload && <Button size="sm" variant="ghost" onClick={onDownload} aria-label={`${l.download} ${name}`} leading={<DownloadIcon aria-hidden width={14} height={14} />}>{l.download}</Button>}
        </div>
      )}
      {status === "failed" && onRetry && <Button size="sm" variant="secondary" onClick={onRetry} className="shrink-0">{l.retry}</Button>}
    </div>
  );
}
```

## tattva/molecules/GenerationProgress.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, CheckIcon, XIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { Meter } from "../atoms/Meter";
import { Skeleton } from "../atoms/Skeleton";

/**
 * queued: waiting in line. generating: being made. streaming: playable already while the rest is made (audio).
 * finalising: almost done. done. failed. cancelled: the person stopped it.
 */
export type GenerationStatus = "queued" | "generating" | "streaming" | "finalising" | "done" | "failed" | "cancelled";

export interface GenerationProgressProps {
  status: GenerationStatus;
  /** 0 to 100. Shown as a bar while generating or streaming. Omit for an indeterminate state. */
  percent?: number;
  /** Place in the queue while queued. */
  queuePosition?: number;
  /** Time estimate such as "About 30 seconds left". */
  eta?: string;
  /** Shown while failed. */
  error?: string;
  onCancel?: () => void;
  onRetry?: () => void;
  /** While streaming, shows a Listen now button. Nothing plays until the person presses it. */
  onListen?: () => void;
  /** Text for the Listen now button. */
  listenLabel?: string;
  /** Result shown when done, and while streaming when it can already be played. Before then a placeholder of the same shape stands in. */
  preview?: ReactNode;
  /** Shape of the placeholder. `media` is a plain block (16:9 unless previewClassName says otherwise). `audio` is a flat waveform with a label. */
  previewShape?: "media" | "audio";
  /** Words in the audio placeholder, such as "Song, about 2 minutes". */
  previewLabel?: string;
  /** Classes for the preview area, to set its aspect ratio or height. */
  previewClassName?: string;
  /** What happens to the credits, such as "Cancelling now returns your 40 credits." Shown next to Cancel and after cancelling. Pass it only when you know it is true. */
  refundNote?: string;
  /** Replaces the text for each status. Also used for the announcement. */
  labels?: Partial<Record<GenerationStatus, string>>;
  className?: string;
}

const DEFAULT_LABELS: Record<GenerationStatus, string> = {
  queued: "Queued", generating: "Generating", streaming: "Ready to listen, still finishing", finalising: "Finalising",
  done: "Done", failed: "Generation failed", cancelled: "Cancelled",
};

/** A flat waveform: even, low bars, so it reads as "audio will be here" without pretending to show the sound. */
function AudioPlaceholder({ label, moving }: { label: string; moving: boolean }) {
  return (
    <div className="relative flex h-full min-h-16 items-center gap-3 bg-sunken px-3">
      <span aria-hidden className="absolute inset-x-3 inset-y-2">
        <svg viewBox="0 0 48 100" preserveAspectRatio="none" focusable="false" className="size-full">
          {Array.from({ length: 48 }, (_, i) => <rect key={i} x={i + 0.15} width={0.7} y={42} height={16} fill="var(--border-strong)" />)}
        </svg>
      </span>
      {moving && <span aria-hidden className="absolute inset-0 bg-[linear-gradient(90deg,transparent_25%,color-mix(in_oklab,var(--surface)_60%,transparent)_50%,transparent_75%)] bg-[length:200%_100%] motion-safe:animate-shimmer" />}
      <span className="relative rounded-control bg-surface px-2 py-0.5 text-small text-fg-muted">{label}</span>
    </div>
  );
}

/**
 * Status for slow generation. The visible text can change often, but screen readers only hear status changes.
 * Streaming audio can be listened to before it is finished; Listen now never starts playback on its own.
 */
export function GenerationProgress({
  status, percent, queuePosition, eta, error, onCancel, onRetry, onListen, listenLabel = "Listen now", preview, previewShape = "media",
  previewLabel, previewClassName, refundNote, labels, className,
}: GenerationProgressProps) {
  const text = { ...DEFAULT_LABELS, ...labels };
  const active = status === "queued" || status === "generating" || status === "streaming" || status === "finalising";
  const determinate = (status === "generating" || status === "streaming") && percent !== undefined;
  const announcement = status === "failed" && error ? `${text.failed}. ${error}`
    : status === "cancelled" && refundNote ? `${text.cancelled}. ${refundNote}` : text[status];
  const pct = percent === undefined ? 0 : Math.round(Math.max(0, Math.min(100, percent)));
  const audio = previewShape === "audio";
  const showPreview = !!preview && (status === "done" || status === "streaming");
  const stopped = status === "failed" || status === "cancelled";
  const placeholderLabel = stopped ? (status === "cancelled" ? "Not made" : "No audio") : previewLabel ?? "The audio will appear here";

  return (
    <div className={cn("space-y-3 rounded-card border border-line bg-surface p-3", className)}>
      <p role="status" aria-live="polite" className="sr-only">{announcement}</p>
      <div className={cn("overflow-hidden rounded-card", previewClassName ?? (audio ? "" : "aspect-video"))}>
        {showPreview ? preview
          : audio ? <AudioPlaceholder label={placeholderLabel} moving={active} />
          : <Skeleton className={cn("h-full w-full", stopped && "animate-none")} />}
      </div>
      <div className="flex flex-wrap items-center justify-between gap-2">
        <p className="flex items-center gap-1.5 text-body leading-5 font-medium" aria-hidden>
          {status === "done" && <CheckIcon width={14} height={14} />}
          {status === "failed" && <AlertIcon width={14} height={14} className="text-danger-fg" />}
          {status === "cancelled" && <XIcon width={14} height={14} className="text-fg-muted" />}
          <span className={status === "failed" ? "text-danger-fg" : undefined}>{text[status]}</span>
          {status === "queued" && queuePosition !== undefined && <span className="font-normal text-fg-muted">Position {queuePosition}</span>}
          {determinate && <span className="font-normal text-fg-muted">{pct}%</span>}
        </p>
        <div className="flex flex-wrap items-center gap-2">
          {active && eta && <span className="text-small leading-4 text-fg-muted">{eta}</span>}
          {status === "streaming" && onListen && <Button size="sm" variant="secondary" onClick={onListen} className="pointer-coarse:h-11">{listenLabel}</Button>}
          {active && onCancel && <Button size="sm" variant="secondary" onClick={onCancel} className="pointer-coarse:h-11">Cancel</Button>}
          {stopped && onRetry && <Button size="sm" variant="secondary" onClick={onRetry} className="pointer-coarse:h-11">{status === "cancelled" ? "Start again" : "Retry"}</Button>}
        </div>
      </div>
      {determinate && <Meter label="Generation progress" value={pct} valueText={`${pct}%`} />}
      {active && !determinate && status !== "queued" && <div aria-hidden className="h-1.5 w-full rounded-full bg-[linear-gradient(90deg,var(--border)_25%,var(--fg-subtle)_50%,var(--border)_75%)] bg-[length:200%_100%] animate-shimmer" />}
      {status === "failed" && error && <p className="text-small leading-4 text-fg-muted">{error}</p>}
      {refundNote && (active && onCancel || status === "cancelled") && <p className="text-small leading-4 text-fg-muted">{refundNote}</p>}
    </div>
  );
}
```

## tattva/molecules/InlineConfirm.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import type { FocusEvent, KeyboardEvent, PointerEvent, ReactNode } from "react";
import { Button } from "../atoms/Button";
import { cn } from "../lib/cn";

export interface InlineConfirmProps {
  /** The first button's label, for example "Delete chat". */
  children: ReactNode;
  /** Label of the button that does the action. */
  confirmLabel?: string;
  /** Label of the safe button that goes back. It gets focus when the question shows. */
  cancelLabel?: string;
  /** The question shown in place of the button. Defaults to the confirm label plus " this?". */
  question?: string;
  /** Called once when the confirm button is pressed. */
  onConfirm: () => void;
  /** danger draws the confirm button in the danger colour. */
  tone?: "danger" | "default";
  /** Milliseconds before the question quietly goes back to the first button. 0 turns this off. */
  timeout?: number;
  className?: string;
}

type Pause = "hover" | "focus" | "touch" | "hidden";

/** A button that asks "Are you sure?" in its own place instead of opening a dialog. */
export function InlineConfirm({ children, confirmLabel = "Delete", cancelLabel = "Keep it", question, onConfirm, tone = "default", timeout = 6000, className }: InlineConfirmProps) {
  const [asking, setAsking] = useState(false);
  const questionId = useId();
  const root = useRef<HTMLSpanElement>(null);
  const trigger = useRef<HTMLButtonElement>(null);
  const cancel = useRef<HTMLButtonElement>(null);
  // "trigger" or "cancel": where focus goes after the next swap. Visibility must change before focus can land.
  const focusNext = useRef<"trigger" | "cancel" | null>(null);

  const pauses = useRef(new Set<Pause>());
  const timer = useRef<ReturnType<typeof setTimeout>>(undefined);
  const left = useRef(timeout);
  const startedAt = useRef(0);

  const close = (focusTrigger: boolean) => {
    clearTimeout(timer.current);
    timer.current = undefined;
    // Hover and touch are still true after a close; they are tracked all the time.
    pauses.current.delete("focus");
    pauses.current.delete("hidden");
    focusNext.current = focusTrigger ? "trigger" : null;
    setAsking(false);
  };

  // Runs the timer only while nothing pauses it, and keeps the time left across pauses.
  const sync = () => {
    if (!asking || timeout <= 0) return;
    if (pauses.current.size === 0 && timer.current === undefined) {
      startedAt.current = Date.now();
      timer.current = setTimeout(() => {
        timer.current = undefined;
        // Only take focus back if it was inside, so a timeout never pulls focus from elsewhere.
        close(!!root.current?.contains(document.activeElement));
      }, left.current);
    } else if (pauses.current.size > 0 && timer.current !== undefined) {
      clearTimeout(timer.current);
      timer.current = undefined;
      left.current -= Date.now() - startedAt.current;
    }
  };
  const pause = (p: Pause, on: boolean) => {
    if (on) pauses.current.add(p); else pauses.current.delete(p);
    sync();
  };

  useEffect(() => {
    if (asking) left.current = timeout;
    const target = focusNext.current === "cancel" ? cancel.current : focusNext.current === "trigger" ? trigger.current : null;
    focusNext.current = null;
    target?.focus();
    if (!asking) return;
    const onVis = () => pause("hidden", document.visibilityState === "hidden");
    onVis();
    sync();
    document.addEventListener("visibilitychange", onVis);
    return () => { document.removeEventListener("visibilitychange", onVis); clearTimeout(timer.current); timer.current = undefined; };
  }, [asking]);

  const ask = () => { focusNext.current = "cancel"; setAsking(true); };
  const confirm = () => { close(true); onConfirm(); };

  const onKeyDown = (e: KeyboardEvent) => {
    if (asking && e.key === "Escape") { e.preventDefault(); e.stopPropagation(); close(true); }
  };
  // Keyboard focus pauses the timer. Focus moved there after a mouse press does not, so the question still times out.
  const onFocusIn = (e: FocusEvent) => { if (asking) pause("focus", (e.target as HTMLElement).matches(":focus-visible")); };
  const onFocusOut = (e: FocusEvent) => { if (!root.current?.contains(e.relatedTarget as Node | null)) pause("focus", false); };
  const onEnter = (e: PointerEvent) => { if (e.pointerType === "mouse") pause("hover", true); };
  const onLeave = (e: PointerEvent) => { if (e.pointerType === "mouse") pause("hover", false); };

  const q = question ?? `${confirmLabel} this?`;

  return (
    <span
      ref={root}
      onKeyDown={onKeyDown}
      onFocus={onFocusIn}
      onBlur={onFocusOut}
      onPointerEnter={onEnter}
      onPointerLeave={onLeave}
      onTouchStart={() => pause("touch", true)}
      onTouchEnd={() => pause("touch", false)}
      onTouchCancel={() => pause("touch", false)}
      // Two columns: the first holds the button and, stacked on it, the question and Cancel. Confirm sits in the
      // second, so it can never appear under the pointer that pressed the first button.
      className={cn("inline-grid grid-cols-[auto_auto] items-center gap-x-2 align-middle", className)}
    >
      <span className={cn("col-start-1 row-start-1 justify-self-start", asking && "invisible")}>
        <Button ref={trigger} variant="secondary" size="sm" onClick={ask}>{children}</Button>
      </span>
      <span className={cn("col-start-1 row-start-1 flex items-center gap-2", asking ? "motion-safe:animate-fade" : "invisible")}>
        <span id={questionId} className="text-compact text-fg">{q}</span>
        <Button ref={cancel} variant="secondary" size="sm" onClick={() => close(true)}>{cancelLabel}</Button>
      </span>
      <span className={cn("col-start-2 row-start-1", asking ? "motion-safe:animate-fade" : "invisible")}>
        <Button variant={tone === "danger" ? "danger" : "primary"} size="sm" onClick={confirm} aria-describedby={questionId}>{confirmLabel}</Button>
      </span>
      <span role="status" aria-live="polite" className="sr-only">{asking ? q : ""}</span>
    </span>
  );
}
```

## tattva/molecules/InlineEdit.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import type { KeyboardEvent, ReactNode } from "react";
import { Button } from "../atoms/Button";
import { TextField } from "../atoms/TextField";
import { cn } from "../lib/cn";
import { AlertIcon, EditIcon } from "../lib/icons";

export interface InlineEditProps {
  /** Visible label, small and muted. Also the accessible name of the input. */
  label: string;
  value: string;
  /** Called with the new text when the edit is saved. */
  onChange: (value: string) => void;
  /** How to show the value when not editing, such as a Money. Defaults to the text. */
  display?: ReactNode;
  /** Forces the "Edited" tag on or off. When left out, the part compares value with originalValue. */
  edited?: boolean;
  /** The value before anyone changed it. Defaults to the value this part first received. */
  originalValue?: string;
  inputMode?: "text" | "numeric" | "decimal" | "tel" | "email" | "url" | "search";
  /** Returns an error message, or null when the value is fine. On an error the edit stays open. */
  validate?: (value: string) => string | null;
  /** Accessible name of the Edit button. */
  editLabel?: string;
  className?: string;
}

/**
 * "Verify, don't fill in". A value reads as plain text with a visible Edit button. Pressing it turns the value into an
 * input in place. Enter or Done saves, Escape cancels, and a saved change gets an "Edited" tag.
 */
export function InlineEdit({ label, value, onChange, display, edited, originalValue, inputMode, validate, editLabel, className }: InlineEditProps) {
  const uid = useId();
  const labelId = `${uid}-label`, errorId = `${uid}-error`;
  const first = useRef(value);
  const [editing, setEditing] = useState(false);
  const [draft, setDraft] = useState(value);
  const [error, setError] = useState<string | null>(null);
  const [saved, setSaved] = useState("");
  const editBtn = useRef<HTMLButtonElement>(null);
  const input = useRef<HTMLInputElement>(null);
  const back = useRef(false);

  useEffect(() => { if (editing) { input.current?.focus(); input.current?.select(); } }, [editing]);
  // The input is gone after save or cancel, so focus goes back to the Edit button that opened it.
  useEffect(() => { if (!editing && back.current) { back.current = false; editBtn.current?.focus(); } }, [editing]);

  const isEdited = edited ?? value !== (originalValue ?? first.current);

  const start = () => { setDraft(value); setError(null); setSaved(""); setEditing(true); };
  const cancel = () => { back.current = true; setError(null); setEditing(false); };
  const save = () => {
    const err = validate?.(draft) ?? null;
    if (err) { setError(err); input.current?.focus(); return; }
    if (draft !== value) { onChange(draft); setSaved("Saved"); }
    back.current = true;
    setEditing(false);
  };
  const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
    if (e.key === "Enter") { e.preventDefault(); save(); }
    else if (e.key === "Escape") { e.preventDefault(); e.stopPropagation(); cancel(); }
  };

  return (
    <div className={cn("border-b border-line py-3 last:border-b-0", className)}>
      <div className="flex items-center gap-2">
        <span id={labelId} className="text-small leading-4 text-fg-muted">{label}</span>
        {isEdited && <span className="rounded-control bg-sunken px-2 py-0.5 text-small leading-4 font-medium text-fg-muted forced-colors:border forced-colors:border-[CanvasText]">Edited</span>}
      </div>
      {editing ? (
        <div className="mt-1.5 flex flex-col gap-1.5">
          <div className="flex items-center gap-2">
            <TextField ref={input} className="flex-1" aria-labelledby={labelId} aria-describedby={error ? errorId : undefined} invalid={!!error} inputMode={inputMode} value={draft}
              onChange={(e) => { setDraft(e.target.value); if (error) setError(null); }} onKeyDown={onKeyDown} />
            <Button size="sm" className="pointer-coarse:h-11" onClick={save}>Done</Button>
            <Button size="sm" variant="ghost" className="pointer-coarse:h-11" onClick={cancel}>Cancel</Button>
          </div>
          <div id={errorId} aria-live="polite" className="empty:hidden">
            {error && <p className="flex items-start gap-1.5 text-small leading-4 text-danger-fg"><AlertIcon className="mt-px shrink-0" width={14} height={14} />{error}</p>}
          </div>
        </div>
      ) : (
        <div className="mt-0.5 flex items-center justify-between gap-3">
          <div className="min-w-0 text-lead leading-6 font-medium text-fg">{display ?? (value || <span className="font-normal text-fg-muted">Not set</span>)}</div>
          <button ref={editBtn} type="button" aria-label={editLabel ?? `Edit ${label}`} onClick={start}
            className="inline-flex min-h-8 shrink-0 cursor-pointer items-center gap-1.5 rounded-control px-3 text-body leading-5 font-medium text-fg underline underline-offset-4 transition-colors duration-[var(--dur-fast)] hover:bg-hover tap">
            <EditIcon width={14} height={14} />Edit
          </button>
        </div>
      )}
      <p role="status" aria-live="polite" className="sr-only">{saved}</p>
    </div>
  );
}
```

## tattva/molecules/JsonView.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import type { CSSProperties, KeyboardEvent } from "react";
import { Button } from "../atoms/Button";
import { cn } from "../lib/cn";
import { CheckIcon, ChevronIcon, CopyIcon } from "../lib/icons";

export interface JsonViewProps {
  /** The value to show, for example a tool call's input or output. */
  value: unknown;
  /** Accessible name of the tree. Also shown as the heading of the box. */
  label: string;
  /** How many levels start open. 1 opens the top level only, 0 starts fully closed. */
  defaultExpandDepth?: number;
  /** Strings longer than this are cut short with a "Show all" choice. */
  maxStringLength?: number;
  /** Replaces the built-in clipboard copy. Gets the value as indented JSON. Throw or reject to report a failure. */
  onCopy?: (text: string) => void | Promise<void>;
  className?: string;
}

type Kind = "object" | "array" | "string" | "number" | "boolean" | "null" | "other";

interface Row {
  id: string;
  parent: string | null;
  depth: number;
  name: string | number | null;
  value: unknown;
  kind: Kind;
  /** Number of children, 0 for leaves and empty containers. */
  count: number;
  pos: number;
  size: number;
  circular: boolean;
}

const kindOf = (v: unknown): Kind => {
  if (v === null) return "null";
  if (Array.isArray(v)) return "array";
  const t = typeof v;
  if (t === "object") return "object";
  if (t === "string" || t === "boolean") return t;
  if (t === "number" || t === "bigint") return "number";
  return "other";
};

const childrenOf = (v: unknown, kind: Kind): [string | number, unknown][] =>
  kind === "array" ? (v as unknown[]).map((x, i) => [i, x]) : kind === "object" ? Object.entries(v as object) : [];

const parentOf = (id: string) => (id === "$" ? null : id.slice(0, id.lastIndexOf("/")));

const typeWord: Record<Kind, string> = { object: "object", array: "array", string: "string", number: "number", boolean: "boolean", null: "null", other: "value" };
const tint: Record<Kind, string> = {
  string: "text-success-fg",
  number: "text-info-fg",
  boolean: "text-accent-fg",
  null: "text-fg-subtle italic",
  other: "text-fg-subtle italic",
  object: "text-fg-muted",
  array: "text-fg-muted",
};

const plural = (n: number, one: string) => `${n} ${one}${n === 1 ? "" : "s"}`;

/** Shows a JSON value as a tree that opens level by level, following the WAI-ARIA tree view pattern. */
export function JsonView({ value, label, defaultExpandDepth = 1, maxStringLength = 160, onCopy, className }: JsonViewProps) {
  const labelId = useId();
  const [toggled, setToggled] = useState<Record<string, boolean>>({});
  const [full, setFull] = useState<Record<string, boolean>>({});
  const [active, setActive] = useState("$");
  const [status, setStatus] = useState<"" | "copied" | "failed">("");
  const items = useRef(new Map<string, HTMLDivElement>());
  const resetTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
  useEffect(() => () => clearTimeout(resetTimer.current), []);

  const isOpen = (id: string, depth: number) => toggled[id] ?? depth < defaultExpandDepth;

  // Only open branches are walked, so a large or self-referencing value costs nothing until someone opens it.
  const rows: Row[] = [];
  const visit = (v: unknown, id: string, parent: string | null, depth: number, name: string | number | null, pos: number, size: number, seen: unknown[]) => {
    const circular = typeof v === "object" && v !== null && seen.includes(v);
    const kind = circular ? "other" : kindOf(v);
    const kids = childrenOf(v, kind);
    rows.push({ id, parent, depth, name, value: v, kind, count: kids.length, pos, size, circular });
    if (kids.length && isOpen(id, depth)) {
      kids.forEach(([k, c], i) => visit(c, `${id}/${encodeURIComponent(String(k))}`, id, depth + 1, k, i + 1, kids.length, [...seen, v]));
    }
  };
  visit(value, "$", null, 0, null, 1, 1, []);

  const ids = new Set(rows.map((r) => r.id));
  let current: string | null = active;
  while (current && !ids.has(current)) current = parentOf(current);
  const activeId = current ?? "$";

  const isLong = (r: Row) => r.kind === "string" && (r.value as string).length > maxStringLength;
  const toggle = (r: Row) => setToggled((t) => ({ ...t, [r.id]: !isOpen(r.id, r.depth) }));
  const flipFull = (id: string) => setFull((f) => ({ ...f, [id]: !f[id] }));
  const move = (id: string) => { setActive(id); items.current.get(id)?.focus(); };

  const activate = (r: Row) => {
    if (r.count) toggle(r);
    else if (isLong(r)) flipFull(r.id);
  };

  const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
    const i = rows.findIndex((r) => r.id === activeId);
    const r = rows[i];
    if (!r) return;
    const open = r.count > 0 && isOpen(r.id, r.depth);
    let next: string | undefined;
    switch (e.key) {
      case "ArrowDown": next = rows[i + 1]?.id; break;
      case "ArrowUp": next = rows[i - 1]?.id; break;
      case "Home": next = rows[0].id; break;
      case "End": next = rows[rows.length - 1].id; break;
      case "ArrowRight": if (r.count) { if (open) next = rows[i + 1]?.id; else toggle(r); } break;
      case "ArrowLeft": if (open) toggle(r); else if (r.parent) next = r.parent; break;
      case "Enter": case " ": activate(r); break;
      default: return;
    }
    e.preventDefault();
    if (next) move(next);
  };

  const copy = async () => {
    clearTimeout(resetTimer.current);
    try {
      const text = JSON.stringify(value, null, 2) ?? String(value);
      if (onCopy) await onCopy(text);
      else await navigator.clipboard.writeText(text);
      setStatus("copied");
    } catch {
      // The failure is shown and announced instead of being dropped.
      setStatus("failed");
    }
    resetTimer.current = setTimeout(() => setStatus(""), 2000);
  };

  const describe = (r: Row) => {
    const v = r.value;
    if (r.circular) return { text: "[circular]", spoken: "circular reference" };
    if (r.kind === "object") return r.count ? { text: `{ ${plural(r.count, "key")} }`, spoken: plural(r.count, "key") } : { text: "{ }", spoken: "empty" };
    if (r.kind === "array") return r.count ? { text: `[ ${plural(r.count, "item")} ]`, spoken: plural(r.count, "item") } : { text: "[ ]", spoken: "empty" };
    if (r.kind === "string") {
      const s = v as string;
      const cut = isLong(r) && !full[r.id];
      const shown = cut ? s.slice(0, maxStringLength) : s;
      return { text: JSON.stringify(shown).slice(0, -1) + (cut ? "…\"" : "\""), spoken: shown + (cut ? ", shortened, press Enter to show all" : isLong(r) ? ", press Enter to shorten" : "") };
    }
    const t = String(v);
    return { text: t, spoken: t };
  };

  return (
    <div className={cn("overflow-hidden rounded-card border border-line bg-sunken", className)}>
      <div className="flex items-center gap-2 border-b border-line py-1 ps-3 pe-1">
        <span id={labelId} className="min-w-0 flex-1 truncate text-small font-medium text-fg-muted">{label}</span>
        <span role="status" aria-live="polite" className={cn("text-small", status === "failed" ? "text-danger-fg" : "text-fg-muted")}>
          {status === "copied" ? "Copied" : status === "failed" ? "Could not copy" : ""}
        </span>
        <Button variant="ghost" size="sm" onClick={copy} leading={status === "copied" ? <CheckIcon width={14} height={14} /> : <CopyIcon width={14} height={14} />} className="pointer-coarse:min-h-11">
          Copy
        </Button>
      </div>
      <div role="region" aria-labelledby={labelId} className="overflow-x-auto p-1 font-mono text-compact leading-5">
        <div role="tree" aria-labelledby={labelId} onKeyDown={onKeyDown} className="min-w-full">
          {rows.map((r) => {
            const open = r.count > 0 && isOpen(r.id, r.depth);
            const d = describe(r);
            const name = r.name === null ? "" : `${r.name}, `;
            return (
              <div
                key={r.id}
                ref={(el) => { if (el) items.current.set(r.id, el); else items.current.delete(r.id); }}
                role="treeitem"
                aria-level={r.depth + 1}
                aria-posinset={r.pos}
                aria-setsize={r.size}
                aria-expanded={r.count ? open : undefined}
                aria-label={`${name}${typeWord[r.kind]}, ${d.spoken}`}
                tabIndex={r.id === activeId ? 0 : -1}
                onFocus={() => setActive(r.id)}
                onClick={() => { move(r.id); activate(r); }}
                style={{ "--depth": r.depth } as CSSProperties}
                className={cn(
                  "flex items-start gap-1 rounded-md py-0.5 pe-2 hover:bg-hover pointer-coarse:min-h-11 pointer-coarse:items-center motion-safe:animate-fade",
                  "ps-[calc(var(--depth)*var(--spacing)*2.5+var(--spacing))] sm:ps-[calc(var(--depth)*var(--spacing)*4+var(--spacing))]",
                  (r.count > 0 || isLong(r)) && "cursor-pointer",
                )}
              >
                <span aria-hidden className="flex h-5 w-4 shrink-0 items-center justify-center text-fg-subtle">
                  {r.count > 0 && <ChevronIcon width={12} height={12} className={cn("motion-safe:transition-transform motion-safe:duration-[var(--dur-fast)]", open && "rotate-90")} />}
                </span>
                <span aria-hidden className="min-w-32 flex-1 wrap-anywhere">
                  {r.name !== null && (
                    <><span className={typeof r.name === "number" ? "text-fg-subtle" : "text-fg"}>{r.name}</span><span className="text-fg-subtle">: </span></>
                  )}
                  <span className={tint[r.kind]}>{d.text}</span>
                  {isLong(r) && <span className="ms-2 font-sans text-small text-accent-fg underline underline-offset-2">{full[r.id] ? "Show less" : "Show all"}</span>}
                </span>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}
```

## tattva/molecules/JumpToLatest.tsx

```tsx
import { cn } from "../lib/cn";
import { ChevronIcon } from "../lib/icons";
import { useReducedMotion } from "../hooks/useReducedMotion";

export interface JumpToLatestProps {
  /** Show the pill. Usually `!atBottom` from useStickToBottom. */
  visible: boolean;
  /** Messages that arrived while the reader was scrolled up. Shown with the "New messages" label when above zero. */
  count?: number;
  /** Scrolls the thread. Usually `scrollToBottom` from useStickToBottom. */
  onClick: () => void;
  /** Label with no unseen messages. */
  label?: string;
  /** Label with unseen messages. */
  newLabel?: string;
  className?: string;
}

/**
 * Floating pill that returns the reader to the newest message. Position it inside a relatively positioned thread.
 * It rises in over 220ms, or appears with no motion when the person prefers reduced motion.
 */
export function JumpToLatest({ visible, count = 0, onClick, label = "Jump to latest", newLabel = "New messages", className }: JumpToLatestProps) {
  const reduced = useReducedMotion();
  if (!visible) return null;
  return (
    <div className={cn("pointer-events-none absolute inset-x-0 bottom-3 z-10 flex justify-center", className)}>
      <button type="button" onClick={onClick}
        className={cn("pointer-events-auto inline-flex h-9 cursor-pointer tap items-center gap-2 rounded-control border border-line-strong bg-surface-raised px-4 text-compact text-fg shadow-md hover:bg-hover", !reduced && "animate-rise")}>
        <ChevronIcon className="rotate-90" width={14} height={14} />
        <span>{count > 0 ? newLabel : label}</span>
        {count > 0 && <span className="rounded-control bg-lime px-1.5 text-micro font-medium text-on-lime tabular-nums"><span className="sr-only">, </span>{count}</span>}
      </button>
    </div>
  );
}
```

## tattva/molecules/KeyTakeaway.tsx

```tsx
import { useId, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { SparkleIcon } from "../lib/icons";

export interface KeyTakeawayProps {
  /** Visible label. It names the region. */
  label?: string;
  /** One to three sentences that close the answer. */
  children: ReactNode;
  className?: string;
}

/** Closing summary of an answer. An icon and a text label name the region, so it never relies on color. */
export function KeyTakeaway({ label = "Key takeaway", children, className }: KeyTakeawayProps) {
  const id = useId();
  return (
    <section aria-labelledby={id} className={cn("rounded-card border border-line bg-sunken p-4", className)}>
      <p id={id} className="flex items-center gap-2 text-micro font-bold uppercase tracking-caps text-fg-muted">
        <span className="flex size-5 items-center justify-center rounded-full bg-lime text-on-lime"><SparkleIcon width={11} height={11} /></span>
        {label}
      </p>
      <div className="mt-2 text-body-lg leading-7 text-fg">{children}</div>
    </section>
  );
}
```

## tattva/molecules/LatencyBreakdown.tsx

```tsx
import { useId } from "react";
import { cn } from "../lib/cn";

export type LatencyStageKey = "endpointing" | "stt" | "llm" | "tools" | "tts" | "transport" | "s2s";

export interface LatencyStage {
  key: LatencyStageKey;
  /** Time spent in this stage, in milliseconds. */
  ms: number;
}

export interface LatencyTurn {
  id: string;
  /** Shown in the list, for example "Turn 4" or "Turn 4, 00:52". Defaults to "Turn" and its position. */
  label?: string;
  totalMs: number;
  stages: LatencyStage[];
}

export interface LatencyBreakdownProps {
  /** One reply (turn), or the whole call (stages are medians across turns). */
  mode: "turn" | "call";
  stages: LatencyStage[];
  /** Total time from the end of the person's speech to the first sound of the reply. */
  totalMs: number;
  /** Call mode: spread of turn times. */
  percentiles?: { p50: number; p90: number; p99?: number };
  /** A goal your team set, for example 800. Shown as your team's target, never as a rule. */
  targetMs?: number;
  /** Call mode: every turn. Turns over the target are listed. */
  turns?: LatencyTurn[];
  /** Called when a slow turn is chosen, for example to seek the recording to it. Turns become buttons when given. */
  onTurnSelect?: (id: string) => void;
  /** Heading. Defaults to "Response time" for a turn and "Response time, whole call" for a call. */
  title?: string;
  /** Override stage names, for localisation or your own vocabulary. */
  stageLabels?: Partial<Record<LatencyStageKey, string>>;
  className?: string;
}

const NAMES: Record<LatencyStageKey, string> = {
  endpointing: "End of speech",
  stt: "Speech to text",
  llm: "Model",
  tools: "Tools",
  tts: "Voice",
  transport: "Network",
  s2s: "Speech model",
};

/** Fixed colour per stage, so the same stage looks the same everywhere. Never lime: this is about time, not the AI. */
const SWATCH: Record<LatencyStageKey, string> = {
  endpointing: "bg-(--chart-neutral)",
  stt: "bg-(--chart-1)",
  llm: "bg-(--chart-2)",
  tools: "bg-(--chart-3)",
  tts: "bg-(--chart-4)",
  transport: "bg-(--chart-5)",
  s2s: "bg-(--chart-seq-4)",
};

const fmt = new Intl.NumberFormat("en-GB");
const ms = (n: number) => `${fmt.format(Math.round(n))} ms`;

const slowestOf = (stages: LatencyStage[]) => stages.reduce<LatencyStage | null>((a, s) => (!a || s.ms > a.ms ? s : a), null);

/**
 * Where the time went in a voice turn or a whole call. A stacked bar (decorative) names the slowest stage in words,
 * and a real table follows with every stage in ms. In call mode, turns over the team's target are listed and can
 * seek the recording. On a phone the bar gives way to the table, whose rows carry their own small bars.
 */
export function LatencyBreakdown({ mode, stages, totalMs, percentiles, targetMs, turns, onTurnSelect, title, stageLabels, className }: LatencyBreakdownProps) {
  const id = useId();
  const names = { ...NAMES, ...stageLabels };
  const sum = stages.reduce((a, s) => a + Math.max(0, s.ms), 0) || 1;
  const slowest = slowestOf(stages);
  const over = targetMs !== undefined && totalMs > targetMs;
  const slowTurns = targetMs !== undefined && turns ? turns.filter((t) => t.totalMs > targetMs) : [];
  const heading = title ?? (mode === "turn" ? "Response time" : "Response time, whole call");
  const totalLabel = mode === "turn" ? "This reply" : "Median reply";

  return (
    <section aria-labelledby={`${id}-h`} className={cn("w-full space-y-4 rounded-card border border-line bg-surface p-5 max-sm:p-4", className)}>
      <header className="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1">
        <h3 id={`${id}-h`} className="text-body leading-5 font-semibold text-fg">{heading}</h3>
        {targetMs !== undefined && <p className="text-small leading-4 text-fg-muted">Your team's target: {ms(targetMs)}</p>}
      </header>

      <div className="flex flex-wrap items-end gap-x-6 gap-y-3">
        <div>
          <p className="text-small leading-4 text-fg-muted">{totalLabel}</p>
          <p className="font-mono text-title-sm tabular-nums text-fg">{ms(totalMs)}</p>
          {targetMs !== undefined && (
            <p className={cn("text-small leading-4", over ? "text-danger-fg" : "text-fg-muted")}>
              {over ? `Over target by ${ms(totalMs - targetMs)}` : `Within target, ${ms(targetMs - totalMs)} to spare`}
            </p>
          )}
        </div>
        {percentiles && (
          <dl className="flex gap-5 text-small leading-4">
            {([["P50", percentiles.p50], ["P90", percentiles.p90], ["P99", percentiles.p99]] as const).map(([k, v]) => v !== undefined && (
              <div key={k}>
                <dt className="text-fg-muted"><abbr title={`${k.slice(1)} percent of replies were this fast or faster`} className="no-underline">{k}</abbr></dt>
                <dd className="font-mono text-body leading-5 tabular-nums text-fg">{ms(v)}</dd>
              </div>
            ))}
          </dl>
        )}
      </div>

      {slowest && (
        <p className="text-body leading-5 text-fg">
          Most time: <span className="font-medium">{names[slowest.key].toLowerCase()}</span>, <span className="font-mono tabular-nums">{ms(slowest.ms)}</span>
          <span className="text-fg-muted"> ({Math.round((slowest.ms / sum) * 100)}% of the reply)</span>
        </p>
      )}

      {/* Decorative: the table below carries every number. Hidden on a phone, where the table rows hold the bars. */}
      <div aria-hidden className="max-sm:hidden">
        <div className="flex h-4 w-full overflow-hidden rounded-control bg-sunken">
          {stages.map((s) => (
            <span key={s.key} style={{ width: `${(Math.max(0, s.ms) / sum) * 100}%` }}
              className={cn("h-full border-r border-surface last:border-r-0 motion-safe:animate-grow-x origin-left", SWATCH[s.key], slowest && s.key !== slowest.key && "opacity-60")} />
          ))}
        </div>
      </div>

      <table className="w-full text-compact">
        <caption className="sr-only">{heading}: time per stage in milliseconds</caption>
        <thead>
          <tr className="border-b border-line text-left text-small leading-4 text-fg-muted">
            <th scope="col" className="py-1.5 pr-2 font-medium">Stage</th>
            <th scope="col" className="py-1.5 pr-2 text-right font-medium">Time</th>
            <th scope="col" className="py-1.5 text-right font-medium sm:w-2/5">Share</th>
          </tr>
        </thead>
        <tbody>
          {stages.map((s) => {
            const pct = Math.round((Math.max(0, s.ms) / sum) * 100);
            const top = slowest?.key === s.key;
            return (
              <tr key={s.key} className="border-b border-line last:border-b-0">
                <th scope="row" className={cn("py-2 pr-2 text-left", top ? "font-semibold text-fg" : "font-normal text-fg")}>
                  <span className="flex items-center gap-2">
                    <span aria-hidden className={cn("size-2.5 shrink-0 rounded-full", SWATCH[s.key])} />
                    {names[s.key]}{top && <span className="sr-only"> (slowest)</span>}
                  </span>
                </th>
                <td className="whitespace-nowrap py-2 pr-2 text-right font-mono tabular-nums">{ms(s.ms)}</td>
                <td className="py-2">
                  <span className="flex items-center justify-end gap-2">
                    <span aria-hidden className="h-1.5 w-full max-w-24 min-w-6 overflow-hidden rounded-full bg-sunken sm:hidden">
                      <span className={cn("block h-full", SWATCH[s.key])} style={{ width: `${pct}%` }} />
                    </span>
                    <span className="w-10 text-right font-mono tabular-nums text-fg-muted">{pct}%</span>
                  </span>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>

      {mode === "call" && targetMs !== undefined && turns && (
        <div className="space-y-2 border-t border-line pt-4">
          <h4 className="text-small leading-4 font-semibold text-fg">
            {slowTurns.length === 0 ? `No turns over ${ms(targetMs)}` : `${slowTurns.length} of ${turns.length} turns over ${ms(targetMs)}`}
          </h4>
          {slowTurns.length > 0 && (
            <ul className="space-y-1">
              {slowTurns.map((t) => {
                const s = slowestOf(t.stages);
                const name = t.label ?? `Turn ${turns.indexOf(t) + 1}`;
                const body = (
                  <>
                    <span className="font-medium text-fg">{name}</span>
                    <span className="font-mono tabular-nums text-fg">{ms(t.totalMs)}</span>
                    {s && <span className="text-fg-muted">most time: {names[s.key].toLowerCase()}, {ms(s.ms)}</span>}
                  </>
                );
                return (
                  <li key={t.id}>
                    {onTurnSelect ? (
                      <button type="button" onClick={() => onTurnSelect(t.id)}
                        className="flex min-h-9 w-full cursor-pointer flex-wrap items-center gap-x-3 gap-y-0.5 rounded-control px-2 py-1.5 text-left text-compact hover:bg-hover pointer-coarse:min-h-11">
                        {body}
                        <span className="ml-auto text-small leading-4 text-fg-muted">Play from here</span>
                      </button>
                    ) : (
                      <p className="flex flex-wrap items-center gap-x-3 gap-y-0.5 px-2 py-1.5 text-compact">{body}</p>
                    )}
                  </li>
                );
              })}
            </ul>
          )}
        </div>
      )}
    </section>
  );
}
```

## tattva/molecules/MediaFrame.tsx

```tsx
import { useState } from "react";
import { cn } from "../lib/cn";
import { ImageIcon } from "../lib/icons";
import { AIBadge } from "../atoms/AIBadge";
import { Button } from "../atoms/Button";
import { Skeleton } from "../atoms/Skeleton";
import { ProvenancePopover, type ProvenancePopoverProps } from "./ProvenancePopover";

export type MediaRatio = "1:1" | "4:3" | "16:9" | "3:2";

const ratioClass: Record<MediaRatio, string> = {
  "1:1": "aspect-square",
  "4:3": "aspect-[4/3]",
  "16:9": "aspect-video",
  "3:2": "aspect-[3/2]",
};

export interface MediaFrameProps {
  /** Image URL. */
  src: string;
  /** Describes what the image shows, not how it was made. Required. Ignored when `decorative` is set. */
  alt: string;
  /** Marks the image as decoration. Renders alt="". */
  decorative?: boolean;
  /** Fixed ratio. The frame reserves this space before the image loads, so layout does not shift. */
  ratio?: MediaRatio;
  /** Average color of the image as any CSS color, such as a token `var(--lime-soft)`. Used as the placeholder. When omitted, a shimmer shows while loading. */
  dominantColor?: string;
  /** Caption below the image. Wraps the frame in figure and figcaption. */
  caption?: string;
  /** Shows the AI-generated label on the image. */
  aiGenerated?: boolean;
  /** Label text for the AI badge. */
  aiLabel?: string;
  /** Provenance details. When set, the label opens a popover with them. */
  provenance?: Omit<ProvenancePopoverProps, "label">;
  /** Called when Retry is pressed after a load failure. */
  onRetry?: () => void;
  /** Forces a state. Use it for previews and for controlled loading. */
  status?: "loading" | "error";
  /** Copy for the error tile. */
  errorLabel?: string;
  retryLabel?: string;
  /** Edge to edge inside a card: no corners or border of its own, so the card's corners clip the picture. */
  flush?: boolean;
  className?: string;
}

/**
 * Image with a fixed ratio, a token radius and a reserved box. It shows a placeholder while loading,
 * a fallback tile with Retry when the image fails, an optional caption and an optional AI label.
 */
export function MediaFrame({
  src, alt, decorative, ratio = "4:3", dominantColor, caption, aiGenerated, aiLabel = "AI-generated", provenance, onRetry,
  status, errorLabel = "Image could not load", retryLabel = "Retry", flush = false, className,
}: MediaFrameProps) {
  const [loaded, setLoaded] = useState(false);
  const [failed, setFailed] = useState(false);
  const [attempt, setAttempt] = useState(0);
  const error = status === "error" || (status === undefined && failed);
  const showImage = status === undefined && !failed;
  const loading = status === "loading" || (showImage && !loaded);

  const retry = () => { setFailed(false); setLoaded(false); setAttempt((n) => n + 1); onRetry?.(); };

  const frame = (
    <div className={cn("relative", className)}>
      <div className={cn("relative overflow-hidden bg-sunken", !flush && "rounded-card border border-line", ratioClass[ratio])} style={dominantColor && !error ? { backgroundColor: dominantColor } : undefined}>
        {loading && !dominantColor && <Skeleton className="absolute inset-0 rounded-none" />}
        {showImage && (
          <img
            key={attempt}
            src={src}
            alt={decorative ? "" : alt}
            onLoad={() => setLoaded(true)}
            onError={() => setFailed(true)}
            className={cn("absolute inset-0 size-full object-cover transition-opacity duration-[var(--dur-base)] motion-reduce:transition-none", loaded ? "opacity-100" : "opacity-0")}
          />
        )}
        {error && (
          <div className="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-sunken p-3 text-center">
            <ImageIcon width={20} height={20} className="text-fg-subtle" />
            <p className="text-small leading-4 text-fg-muted">{errorLabel}</p>
            <Button size="sm" variant="secondary" onClick={retry}>{retryLabel}</Button>
          </div>
        )}
      </div>
      {aiGenerated && (
        <div className="absolute bottom-2 left-2 z-10 rounded-control bg-surface p-0.5">
          {provenance ? <ProvenancePopover label={aiLabel} side="top" {...provenance} /> : <AIBadge label={aiLabel} solid />}
        </div>
      )}
    </div>
  );

  if (!caption) return frame;
  return (
    <figure className="m-0">
      {frame}
      <figcaption className="mt-2 text-small leading-5 text-fg-muted">{caption}</figcaption>
    </figure>
  );
}
```

## tattva/molecules/MemoryNotice.tsx

```tsx
import type { ReactNode } from "react";
import { SparkleIcon, XIcon } from "../lib/icons";
import { Button, IconButton } from "../atoms/Button";
import { cn } from "../lib/cn";

export interface MemoryNoticeProps {
  /** Heading. */
  title?: string;
  /** What was saved, in the person's terms. */
  saved: ReactNode;
  /** Opens the memory manager. The button is hidden when omitted. */
  onManage?: () => void;
  /** Removes the saved item. The button is hidden when omitted. */
  onUndo?: () => void;
  /** Dismisses the notice. The close button is hidden when omitted. */
  onDismiss?: () => void;
  manageLabel?: string;
  undoLabel?: string;
  dismissLabel?: string;
  className?: string;
}

/** Inline notice shown at the moment something is saved to memory. It names what was saved and offers Manage and Undo. */
export function MemoryNotice({ title = "Memory updated", saved, onManage, onUndo, onDismiss, manageLabel = "Manage", undoLabel = "Undo", dismissLabel = "Dismiss memory notice", className }: MemoryNoticeProps) {
  return (
    <div role="status" className={cn("flex items-start gap-3 rounded-card border border-line bg-sunken px-4 py-3 text-body leading-5", className)}>
      <SparkleIcon className="mt-0.5 shrink-0 text-fg-muted" width={14} height={14} />
      <div className="min-w-0 flex-1">
        <p className="font-medium text-fg">{title}</p>
        <p className="mt-0.5 text-fg-muted">{saved}</p>
        {(onManage || onUndo) && (
          <div className="mt-2 flex flex-wrap gap-2">
            {onManage && <Button size="sm" variant="secondary" onClick={onManage}>{manageLabel}</Button>}
            {onUndo && <Button size="sm" variant="ghost" onClick={onUndo}>{undoLabel}</Button>}
          </div>
        )}
      </div>
      {onDismiss && <IconButton size="sm" label={dismissLabel} onClick={onDismiss}><XIcon /></IconButton>}
    </div>
  );
}
```

## tattva/molecules/Menu.tsx

```tsx
import { useEffect, useRef, useState, type KeyboardEvent, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { Kbd } from "../atoms/Kbd";
import { Popover } from "../atoms/Popover";

export type MenuEntry =
  | { type?: "item"; id: string; label: string; icon?: ReactNode; shortcut?: string; disabled?: boolean; danger?: boolean; onSelect: () => void }
  | { type: "separator" }
  | { type: "label"; label: string };

export interface MenuProps {
  /** Content of the trigger button. For an icon alone, also set triggerLabel. */
  trigger: ReactNode;
  /** Accessible name of the trigger button. Required when the trigger is only an icon. */
  triggerLabel?: string;
  /** Accessible name of the menu itself. Defaults to triggerLabel, then "Menu". */
  label?: string;
  items: MenuEntry[];
  align?: "start" | "end";
  side?: "bottom" | "top";
  /** Draw the menu on the page itself so a scrolling or clipped parent cannot cut it off. Use inside lists and side bars. */
  portal?: boolean;
  /** On a phone the menu opens as a bottom sheet within thumb reach. Set false to keep a small floating menu there too. */
  phoneSheet?: boolean;
  className?: string;
}

const ENABLED = '[role="menuitem"]:not([aria-disabled="true"])';

/** Action menu opened from a button, following the WAI-ARIA menu button pattern. Built on Popover. */
export function Menu({ trigger, triggerLabel, label, items, align = "start", side = "bottom", portal, phoneSheet, className }: MenuProps) {
  const [open, setOpen] = useState(false);
  const list = useRef<HTMLDivElement>(null);
  const openAt = useRef<"first" | "last">("first");
  const buffer = useRef({ text: "", timer: 0 });

  // Popover focuses the first item. ArrowUp from the trigger asks for the last one instead.
  useEffect(() => {
    if (!open) return;
    if (openAt.current === "last") {
      const all = list.current?.querySelectorAll<HTMLElement>(ENABLED);
      all?.[all.length - 1]?.focus();
    }
    openAt.current = "first";
  }, [open]);
  useEffect(() => () => window.clearTimeout(buffer.current.timer), []);

  const typeAhead = (e: KeyboardEvent<HTMLDivElement>) => {
    if (e.key.length !== 1 || e.ctrlKey || e.metaKey || e.altKey || (e.key === " " && !buffer.current.text)) return;
    const all = Array.from(list.current?.querySelectorAll<HTMLElement>(ENABLED) ?? []);
    if (!all.length) return;
    e.preventDefault();
    const b = buffer.current;
    window.clearTimeout(b.timer);
    b.text += e.key.toLowerCase();
    b.timer = window.setTimeout(() => { b.text = ""; }, 600);
    const cur = all.findIndex((el) => el === document.activeElement);
    // One repeated letter cycles through matches; a longer word keeps the current match if it still fits.
    const start = b.text.length === 1 ? cur + 1 : Math.max(cur, 0);
    for (let k = 0; k < all.length; k++) {
      const el = all[(start + k) % all.length];
      if ((el.dataset.label ?? "").toLowerCase().startsWith(b.text)) { el.focus(); return; }
    }
  };

  return (
    <Popover label={label ?? triggerLabel ?? "Menu"} role="menu" align={align} side={side} portal={portal} phoneSheet={phoneSheet} open={open} onOpenChange={setOpen} className={className} panelClassName="min-w-48"
      trigger={({ triggerProps }) => (
        <button type="button" aria-label={triggerLabel} {...triggerProps}
          onClick={() => setOpen(!open)}
          onKeyDown={(e) => {
            if (open || (e.key !== "ArrowDown" && e.key !== "ArrowUp")) return;
            e.preventDefault();
            openAt.current = e.key === "ArrowUp" ? "last" : "first";
            setOpen(true);
          }}
          className="inline-flex min-h-9 min-w-9 cursor-pointer items-center justify-center gap-2 rounded-control border border-line bg-surface px-2.5 text-body font-medium text-fg transition-colors duration-[var(--dur-fast)] hover:bg-hover pointer-coarse:min-h-11 pointer-coarse:min-w-11">
          {trigger}
        </button>
      )}>
      {({ close }) => (
        <div ref={list} onKeyDown={typeAhead}>
          {items.map((it, i) => {
            if (it.type === "separator") return <div key={`s${i}`} role="separator" className="my-1 h-px bg-line forced-colors:bg-[CanvasText]" />;
            if (it.type === "label") return <div key={`l${i}`} role="presentation" className="px-2.5 pt-2 pb-1 text-small leading-4 font-medium text-fg-subtle">{it.label}</div>;
            return (
              <button key={it.id} type="button" role="menuitem" tabIndex={-1} data-label={it.label} aria-disabled={it.disabled || undefined}
                onClick={() => { if (it.disabled) return; it.onSelect(); close(); }}
                className={cn(
                  "flex min-h-9 w-full items-center gap-2.5 rounded-lg px-2.5 text-left text-body focus-visible:outline-offset-[-2px] pointer-coarse:min-h-11",
                  it.disabled ? "cursor-not-allowed opacity-(--disabled-opacity)" : cn("cursor-pointer hover:bg-hover focus-visible:bg-hover", it.danger ? "text-danger-fg" : "text-fg"),
                )}>
                {it.icon && <span aria-hidden className="flex size-4 shrink-0 items-center justify-center">{it.icon}</span>}
                <span className="flex-1">{it.label}</span>
                {it.shortcut && <Kbd>{it.shortcut}</Kbd>}
              </button>
            );
          })}
        </div>
      )}
    </Popover>
  );
}
```

## tattva/molecules/Message.tsx

```tsx
import { useEffect, useRef, useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { SparkleIcon } from "../lib/icons";

export interface MessageProps {
  role: "user" | "assistant";
  /** True while tokens are still arriving. Shows the caret, sets aria-busy and keeps the streaming text out of live announcements. */
  streaming?: boolean;
  /** Announced once, politely, when streaming ends. */
  completeLabel?: string;
  /** How the person's own turn looks. bubble is a right-aligned bubble. plain is left-aligned serif text with no box, like a line in a letter. Only changes user messages. */
  variant?: "bubble" | "plain";
  /**
   * The AI mark on an assistant reply. none (the default) shows no mark: the reply uses the full width and its place in
   * the thread says who wrote it, as in most chat products. top shows a small mark and the name above the reply: use it
   * once, on the first reply of a thread, or on every reply when more than one assistant or agent speaks.
   */
  mark?: "none" | "top";
  /** The name shown with the mark and read by screen readers before the reply. Defaults to "Assistant". */
  name?: string;
  /** @deprecated Use mark. true shows the mark above the reply; the mark no longer sits beside it. */
  showAvatar?: boolean;
  children: ReactNode;
  /** Slot under the bubble: MessageActions, FeedbackBar, SourceList... */
  footer?: ReactNode;
  className?: string;
}

/**
 * User messages are right-aligned bubbles; assistant messages are unboxed prose across the full width, so long answers
 * stay readable and short ones do not leave a column of marks down the thread. Screen readers hear the name first.
 */
export function Message({ role, streaming, completeLabel = "Response complete", variant = "bubble", mark, name = "Assistant", showAvatar, children, footer, className }: MessageProps) {
  const top = (mark ?? (showAvatar ? "top" : "none")) === "top";
  const [announce, setAnnounce] = useState("");
  const was = useRef(false);
  useEffect(() => {
    if (streaming) setAnnounce("");
    else if (was.current) setAnnounce(completeLabel);
    was.current = !!streaming;
  }, [streaming, completeLabel]);

  if (role === "user" && variant === "plain") {
    return <div className={cn("animate-rise font-serif text-turn text-fg", className)}>{children}</div>;
  }
  if (role === "user") {
    return (
      <div className={cn("flex justify-end animate-rise", className)}>
        <div className="max-w-[80%] rounded-card rounded-br-[min(var(--radius-lg),var(--shape-card))] bg-sunken px-5 py-3 text-body-lg leading-relaxed">{children}</div>
      </div>
    );
  }
  return (
    <div className={cn("animate-rise min-w-0 space-y-3", className)} aria-busy={streaming || undefined}>
      {top && (
        <p className="flex items-center gap-1.5 text-compact font-medium text-fg-muted">
          <span aria-hidden className="grid size-5 place-items-center rounded-full bg-lime text-on-lime"><SparkleIcon width={11} height={11} /></span>{name}
        </p>
      )}
      {/* aria-live is off while tokens arrive, so screen readers are not interrupted per token. */}
      <div aria-live={streaming ? "off" : undefined} className="space-y-4 text-body-lg leading-7 text-fg">
        {!top && <span className="sr-only">{name}: </span>}
        {children}
        {streaming && <span aria-hidden className="ml-0.5 inline-block h-4 w-0.5 translate-y-0.5 bg-fg animate-caret" />}
      </div>
      {footer}
      <span role="status" className="sr-only">{announce}</span>
    </div>
  );
}
```

## tattva/molecules/MessageActions.tsx

```tsx
import { useState } from "react";
import { CheckIcon, CopyIcon, RefreshIcon } from "../lib/icons";
import { IconButton } from "../atoms/Button";

export function MessageActions({ text, onRegenerate }: { text: string; onRegenerate?: () => void }) {
  const [copied, setCopied] = useState(false);
  return (
    <div className="-ml-1.5 flex items-center gap-0.5">
      <IconButton size="sm" label={copied ? "Copied" : "Copy response"} onClick={async () => {
        try { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 1500); } catch { /* noop */ }
      }}>{copied ? <CheckIcon /> : <CopyIcon />}</IconButton>
      {onRegenerate && <IconButton size="sm" label="Regenerate response" onClick={onRegenerate}><RefreshIcon /></IconButton>}
    </div>
  );
}
```

## tattva/molecules/ModelPicker.tsx

```tsx
import { useRef, useState, type KeyboardEvent, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { CheckIcon, ChevronIcon, InfoIcon } from "../lib/icons";
import { Badge } from "../atoms/Badge";
import { Popover } from "../atoms/Popover";
import { SegmentedControl, type SegmentOption } from "../atoms/SegmentedControl";

export interface ModelOption {
  id: string;
  name: string;
  /** One line describing when to use it. */
  description?: string;
  /** Short badge text such as "Auto". */
  badge?: string;
  /** Marks the option that routes to another model by itself. Pair with `routedId` on the picker. */
  auto?: boolean;
  disabled?: boolean;
}

export interface ModelLimit {
  /** Reason line shown above the list, for example "Weekly limit reached. Resets Friday." */
  reason: string;
  /** Options that cannot be chosen until the limit resets. */
  limitedIds: string[];
  /** Option the conversation continues on. Its name appears in the fallback line. */
  fallbackId: string;
}

export interface ModelPickerProps {
  models: ModelOption[];
  /** Selected model id. Omit for uncontrolled use. */
  value?: string;
  defaultValue?: string;
  onChange?: (id: string) => void;
  /** Accessible name for the list and the panel. */
  label?: string;
  /** Model that actually answered when an auto option is selected. Shown in the panel and on the trigger. */
  routedId?: string;
  /** Builds the line that names the answering model. */
  routedText?: (modelName: string) => string;
  /** Reasoning effort choices, for example Standard and Extended. Omit to hide the control. */
  effortOptions?: SegmentOption<string>[];
  effortValue?: string;
  defaultEffort?: string;
  onEffortChange?: (value: string) => void;
  effortLabel?: string;
  /** Puts the picker in its limit-reached state. */
  limit?: ModelLimit;
  /** Builds the line that names the fallback model. */
  fallbackText?: (modelName: string) => string;
  align?: "start" | "end";
  side?: "bottom" | "top";
  /** Replaces the trigger chip's trailing content. */
  triggerExtra?: ReactNode;
}

/** Chip plus popover for choosing a model or mode. Options form a labelled radio list with arrow-key movement, and an optional effort control sits below. */
export function ModelPicker({
  models, value, defaultValue, onChange, label = "Model", routedId, routedText = (n) => `Answered by ${n}`,
  effortOptions, effortValue, defaultEffort, onEffortChange, effortLabel = "Thinking time",
  limit, fallbackText = (n) => `Continuing on ${n}.`, align = "start", side = "bottom", triggerExtra,
}: ModelPickerProps) {
  const [innerValue, setInnerValue] = useState(defaultValue ?? models[0]?.id ?? "");
  const [innerEffort, setInnerEffort] = useState(defaultEffort ?? effortOptions?.[0]?.value ?? "");
  const current = value ?? innerValue;
  const effort = effortValue ?? innerEffort;
  const refs = useRef<(HTMLButtonElement | null)[]>([]);

  const isOff = (m: ModelOption) => m.disabled || !!limit?.limitedIds.includes(m.id);
  const selected = models.find((m) => m.id === current);
  const routed = selected?.auto && routedId ? models.find((m) => m.id === routedId) : undefined;
  const fallback = limit ? models.find((m) => m.id === limit.fallbackId) : undefined;

  const pick = (id: string) => { if (value === undefined) setInnerValue(id); onChange?.(id); };
  const setEffort = (v: string) => { if (effortValue === undefined) setInnerEffort(v); onEffortChange?.(v); };

  const move = (i: number, e: KeyboardEvent) => {
    const dir = e.key === "ArrowDown" || e.key === "ArrowRight" ? 1 : e.key === "ArrowUp" || e.key === "ArrowLeft" ? -1 : 0;
    const edge = e.key === "Home" ? 0 : e.key === "End" ? models.length - 1 : -1;
    if (!dir && edge < 0) return;
    e.preventDefault();
    const n = models.length;
    let next = i;
    if (edge >= 0) { next = edge; let guard = 0; while (isOff(models[next]) && guard++ < n) next = (next + (edge === 0 ? 1 : -1) + n) % n; }
    else { let guard = 0; do { next = (next + dir + n) % n; } while (isOff(models[next]) && guard++ < n); }
    if (isOff(models[next])) return;
    pick(models[next].id);
    refs.current[next]?.focus();
  };

  // Roving tab stop: the checked enabled option, else the first enabled one.
  const stopId = selected && !isOff(selected) ? selected.id : models.find((m) => !isOff(m))?.id;

  return (
    <Popover phoneSheet label={label} align={align} side={side} panelClassName="w-80 max-w-[calc(100vw-2rem)]"
      trigger={({ toggle, triggerProps }) => (
        <button type="button" onClick={toggle} {...triggerProps}
          className="inline-flex h-8 max-w-full cursor-pointer items-center gap-1.5 rounded-control border border-line bg-surface pl-3 pr-2 text-small leading-4 font-medium text-fg transition-colors hover:bg-hover">
          <span className="sr-only">{label}: </span>
          <span className="truncate">{selected?.name ?? label}</span>
          {routed && <span className="truncate font-normal text-fg-muted">via {routed.name}</span>}
          {triggerExtra}
          <ChevronIcon width={14} height={14} className="shrink-0 rotate-90 text-fg-subtle" />
        </button>
      )}>
      {({ close }) => (
        <div>
          {limit && (
            <p className="mx-1 mb-1 flex gap-2 rounded-xl bg-sunken px-3 py-2 text-small leading-4 text-fg-muted">
              <InfoIcon width={14} height={14} className="mt-0.5 shrink-0" />
              <span>{limit.reason}{fallback ? ` ${fallbackText(fallback.name)}` : ""}</span>
            </p>
          )}
          <div role="radiogroup" aria-label={label}>
            {models.map((m, i) => {
              const on = m.id === current;
              const off = isOff(m);
              return (
                <button key={m.id} ref={(el) => { refs.current[i] = el; }} type="button" role="radio" aria-checked={on} aria-disabled={off || undefined}
                  tabIndex={m.id === stopId ? 0 : -1}
                  onClick={() => { if (off) return; pick(m.id); close(); }} onKeyDown={(e) => move(i, e)}
                  className={cn("flex w-full items-start gap-3 rounded-xl px-3 py-2 text-left transition-colors", off ? "cursor-not-allowed opacity-(--disabled-opacity)" : "cursor-pointer hover:bg-hover", on && "bg-selected")}>
                  <span className="min-w-0 flex-1">
                    <span className="flex items-center gap-2 text-compact font-medium text-fg">
                      <span className="truncate">{m.name}</span>
                      {m.badge && <Badge>{m.badge}</Badge>}
                    </span>
                    {m.description && <span className="block text-small leading-4 text-fg-muted">{m.description}</span>}
                  </span>
                  <span className="mt-0.5 flex size-4 shrink-0 items-center justify-center text-fg">{on && <CheckIcon width={16} height={16} />}</span>
                </button>
              );
            })}
          </div>
          {routed && (
            <p className="mx-1 mt-1 rounded-xl bg-sunken px-3 py-2 text-small leading-4 text-fg-muted">{routedText(routed.name)}</p>
          )}
          {effortOptions && (
            <div className="mt-1 flex items-center justify-between gap-3 border-t border-line px-3 pt-2.5 pb-1.5">
              <span className="text-small leading-4 text-fg-muted">{effortLabel}</span>
              <SegmentedControl size="sm" label={effortLabel} options={effortOptions} value={effort} onChange={setEffort} />
            </div>
          )}
        </div>
      )}
    </Popover>
  );
}
```

## tattva/molecules/PermissionModeSwitcher.tsx

```tsx
import { useId, useRef, useState, type KeyboardEvent } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, CheckIcon, ChevronIcon, InfoIcon, ShieldIcon } from "../lib/icons";
import { Popover } from "../atoms/Popover";
import { Kbd } from "../atoms/Kbd";

export type ModeRisk = "low" | "medium" | "high";

export interface PermissionMode {
  id: string;
  /** Short name shown on the trigger, such as "Manual". */
  label: string;
  /** Plain description of what the agent may do without asking. */
  description: string;
  risk: ModeRisk;
  /** Warning line shown under the description. Use it on the riskiest mode. */
  warning?: string;
  /** When set, the mode cannot be chosen and this reason is shown, for example an admin policy. */
  lockedReason?: string;
}

export const defaultPermissionModes: PermissionMode[] = [
  { id: "manual", label: "Manual", description: "Asks before every edit and command. Read-only commands run without asking.", risk: "low" },
  { id: "plan", label: "Plan (read-only)", description: "Reads and explores, then proposes a plan. Nothing is edited until you approve it.", risk: "low" },
  { id: "accept-edits", label: "Accept edits", description: "Edits files without asking. Commands still ask.", risk: "medium" },
  { id: "auto", label: "Auto", description: "A classifier reviews each action instead of you.", risk: "medium" },
  { id: "bypass", label: "Bypass", description: "Skips all permission prompts, including protected paths.", risk: "high", warning: "Use only in an isolated environment." },
];

const riskText: Record<ModeRisk, string> = { low: "Low risk", medium: "Medium risk", high: "High risk" };
const RiskIcon = { low: ShieldIcon, medium: InfoIcon, high: AlertIcon } as const;

export interface PermissionModeSwitcherProps {
  /** Modes in display order. Defaults to Manual, Plan, Accept edits, Auto and Bypass. */
  modes?: PermissionMode[];
  /** Selected mode id (controlled). */
  value?: string;
  /** Initial mode id when uncontrolled. Defaults to the first mode. */
  defaultValue?: string;
  onChange?: (id: string) => void;
  /** Accessible name for the mode list and the popover. */
  label?: string;
  /** Keyboard shortcut shown at the foot of the panel, such as "Shift+Tab". Only show it if the app binds it. */
  shortcut?: string;
  /** Text beside the shortcut. */
  shortcutLabel?: string;
  /** Text used in the live announcement, followed by the mode name. */
  announcePrefix?: string;
  className?: string;
}

/**
 * Always-visible trigger that names the current autonomy mode, with a popover to change it.
 * Each mode carries a risk label written out and shown with its own icon shape.
 */
export function PermissionModeSwitcher({
  modes = defaultPermissionModes, value, defaultValue, onChange, label = "Permission mode",
  shortcut, shortcutLabel = "to cycle modes", announcePrefix = "Mode changed to", className,
}: PermissionModeSwitcherProps) {
  const [inner, setInner] = useState(defaultValue ?? modes[0]?.id);
  const [announce, setAnnounce] = useState("");
  const current = value ?? inner;
  const active = modes.find((m) => m.id === current) ?? modes[0];
  const refs = useRef<(HTMLButtonElement | null)[]>([]);
  const listId = useId();

  const choose = (m: PermissionMode, close: () => void) => {
    if (m.lockedReason) return;
    if (value === undefined) setInner(m.id);
    onChange?.(m.id);
    setAnnounce(`${announcePrefix} ${m.label}`);
    close();
  };
  const move = (i: number, e: KeyboardEvent) => {
    const n = modes.length;
    const dir = e.key === "ArrowDown" ? 1 : e.key === "ArrowUp" ? -1 : 0;
    if (!dir) return;
    e.preventDefault();
    refs.current[(i + dir + n) % n]?.focus();
  };

  if (!active) return null;
  const ActiveIcon = RiskIcon[active.risk];

  return (
    <div className={cn("inline-block", className)}>
      <Popover phoneSheet label={label} align="start" side="top" panelClassName="w-80 max-w-[calc(100vw-2rem)]"
        trigger={({ toggle, triggerProps }) => (
          <button type="button" onClick={toggle} {...triggerProps}
            className="inline-flex h-8 cursor-pointer items-center gap-2 rounded-control border border-line bg-surface px-3 text-small leading-4 font-medium text-fg hover:border-line-strong">
            <ActiveIcon width={14} height={14} className="text-fg-muted" />
            <span className="text-fg-subtle">{label}:</span>
            <span>{active.label}</span>
            <span className="sr-only">, {riskText[active.risk]}</span>
            <ChevronIcon width={12} height={12} className="rotate-90 text-fg-subtle" />
          </button>
        )}>
        {({ close }) => (
          <div>
            <ul role="radiogroup" aria-label={label} id={listId} className="space-y-0.5">
              {modes.map((m, i) => {
                const on = m.id === active.id;
                const locked = !!m.lockedReason;
                const Icon = RiskIcon[m.risk];
                return (
                  <li key={m.id} role="presentation">
                    <button ref={(el) => { refs.current[i] = el; }} type="button" role="radio" aria-checked={on} aria-disabled={locked || undefined}
                      tabIndex={on ? 0 : -1} onKeyDown={(e) => move(i, e)} onClick={() => choose(m, close)}
                      className={cn("flex w-full items-start gap-2.5 rounded-xl p-2.5 text-left transition-colors",
                        locked ? "cursor-not-allowed opacity-(--disabled-opacity)" : "cursor-pointer hover:bg-hover", on && "bg-selected")}>
                      <span className="mt-0.5 flex size-4 shrink-0 items-center justify-center text-fg">{on && <CheckIcon width={14} height={14} />}</span>
                      <span className="min-w-0 flex-1">
                        <span className="flex flex-wrap items-center gap-x-2 text-compact font-medium text-fg">
                          {m.label}
                          <span className={cn("inline-flex items-center gap-1 text-micro font-normal", m.risk === "high" ? "text-danger-fg" : "text-fg-muted")}>
                            <Icon width={12} height={12} />{riskText[m.risk]}
                          </span>
                        </span>
                        <span className="mt-0.5 block text-small leading-4 text-fg-muted">{m.description}</span>
                        {m.warning && (
                          <span className="mt-1 flex items-start gap-1 text-small leading-4 font-medium text-danger-fg">
                            <AlertIcon width={12} height={12} className="mt-0.5 shrink-0" />{m.warning}
                          </span>
                        )}
                        {locked && <span className="mt-1 block text-small leading-4 text-fg-subtle">Locked: {m.lockedReason}</span>}
                      </span>
                    </button>
                  </li>
                );
              })}
            </ul>
            {shortcut && (
              <p className="mt-1 border-t border-line px-2.5 pt-2 pb-1 text-micro text-fg-subtle">
                <Kbd>{shortcut}</Kbd> {shortcutLabel}
              </p>
            )}
          </div>
        )}
      </Popover>
      <span role="status" aria-live="polite" className="sr-only">{announce}</span>
    </div>
  );
}
```

## tattva/molecules/PrivateChatBanner.tsx

```tsx
import type { ReactNode } from "react";
import { XIcon } from "../lib/icons";
import { Button, IconButton } from "../atoms/Button";
import { cn } from "../lib/cn";

/** Private inline glyph: a ghost, 24px grid, 1.5 stroke. */
function GhostIcon({ className }: { className?: string }) {
  return (
    <svg width={16} height={16} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className={className}>
      <path d="M5 21V11a7 7 0 0 1 14 0v10l-3.5-2.5L12 21l-3.5-2.5L5 21z" /><path d="M9.5 11h.01M14.5 11h.01" />
    </svg>
  );
}

export interface PrivateChatBannerProps {
  /** "banner" is a full-width strip with retention copy. "compact" is a pill for headers. */
  variant?: "banner" | "compact";
  /** Name of the mode. */
  label?: string;
  /** Plain retention copy. State the real retention period for your product. */
  description?: ReactNode;
  /** Leaves the private chat. */
  onExit?: () => void;
  exitLabel?: string;
  className?: string;
}

/** Persistent indicator that the current chat is private. Neutral on purpose, because nothing needs fixing. */
export function PrivateChatBanner({ variant = "banner", label = "Private chat", description = "Not saved to your history or memory and not used for training. A copy may be kept for 30 days.", onExit, exitLabel = "Exit", className }: PrivateChatBannerProps) {
  if (variant === "compact") {
    return (
      <div className={cn("inline-flex items-center gap-1.5 rounded-control border border-line-strong bg-sunken py-0.5 pr-0.5 pl-2.5 text-small leading-4 font-medium text-fg", className)}>
        <GhostIcon className="text-fg-muted" />
        <span>{label}</span>
        {onExit ? <IconButton size="sm" label={`Exit ${label.toLowerCase()}`} onClick={onExit}><XIcon /></IconButton> : <span className="w-2" />}
      </div>
    );
  }
  return (
    <section aria-label={label} className={cn("flex flex-wrap items-center gap-x-4 gap-y-2 rounded-card border border-line-strong bg-sunken px-4 py-3", className)}>
      <GhostIcon className="shrink-0 text-fg-muted" />
      <div className="min-w-0 flex-1 basis-56">
        <p className="text-body leading-5 font-medium text-fg">{label}</p>
        <p className="text-small leading-4 text-fg-muted">{description}</p>
      </div>
      {onExit && <Button size="sm" variant="secondary" onClick={onExit}>{exitLabel}</Button>}
    </section>
  );
}
```

## tattva/molecules/ProvenancePopover.tsx

```tsx
import { SparkleIcon, ChevronIcon } from "../lib/icons";
import { Popover } from "../atoms/Popover";

export interface ProvenancePopoverProps {
  /** Text of the AI label. It is the accessible name of the trigger. */
  label?: string;
  /** Tool or model that produced the content. */
  madeWith?: string;
  /** When it was made, as display text. */
  date?: string;
  /** What the AI did, one short phrase each. */
  actions?: string[];
  /** Whether a person changed the content after it was generated. Omit to leave the row out. */
  editedByPerson?: boolean;
  /** One short line answering "why am I seeing this". */
  why?: string;
  /** Link to a fuller record. */
  detailsHref?: string;
  detailsLabel?: string;
  /** Heading inside the panel. */
  title?: string;
  /** Row labels, so product copy can be localised. */
  rowLabels?: { madeWith: string; date: string; actions: string; edited: string; yes: string; no: string };
  /** Controlled mode. Omit both to let it manage itself. */
  open?: boolean;
  onOpenChange?: (open: boolean) => void;
  align?: "start" | "end";
  side?: "bottom" | "top";
}

const defaultRows = { madeWith: "Made with", date: "Date", actions: "What AI did", edited: "Edited by a person", yes: "Yes", no: "No" };

/**
 * AI label that opens an explanation on demand. The label is always visible text, the details sit one click away.
 * Built on Popover, so it opens with Enter, Space or a tap, closes on Escape and returns focus to the label.
 */
export function ProvenancePopover({
  label = "AI-generated", madeWith, date, actions, editedByPerson, why, detailsHref, detailsLabel = "View full details",
  title = "About this content", rowLabels = defaultRows, open, onOpenChange, align = "start", side = "bottom",
}: ProvenancePopoverProps) {
  const r = { ...defaultRows, ...rowLabels };
  const rows: [string, string][] = [];
  if (madeWith) rows.push([r.madeWith, madeWith]);
  if (date) rows.push([r.date, date]);
  if (editedByPerson !== undefined) rows.push([r.edited, editedByPerson ? r.yes : r.no]);
  return (
    <Popover phoneSheet label={title} open={open} onOpenChange={onOpenChange} align={align} side={side} panelClassName="w-72 max-w-[calc(100vw-2rem)] p-3.5"
      trigger={({ open: isOpen, toggle, triggerProps }) => (
        <button type="button" onClick={toggle} {...triggerProps}
          className="inline-flex cursor-pointer items-center gap-1 rounded-control border border-transparent bg-lime-soft px-2 py-0.5 text-micro leading-4 font-medium text-fg transition-colors hover:bg-lime-soft-hover">
          <SparkleIcon width={10} height={10} />{label}
          <ChevronIcon width={10} height={10} className={isOpen ? "-rotate-90" : "rotate-90"} />
        </button>
      )}>
      <div className="space-y-3 text-compact">
        <h4 className="font-sans! text-body leading-5 font-medium text-fg">{title}</h4>
        {rows.length > 0 && (
          <dl className="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1.5">
            {rows.map(([k, v]) => (<div key={k} className="contents"><dt className="text-fg-muted">{k}</dt><dd className="text-fg">{v}</dd></div>))}
          </dl>
        )}
        {actions && actions.length > 0 && (
          <div>
            <p className="text-fg-muted">{r.actions}</p>
            <ul className="mt-1 list-disc space-y-0.5 pl-4 text-fg">{actions.map((a) => <li key={a}>{a}</li>)}</ul>
          </div>
        )}
        {why && <p className="rounded-xl bg-sunken px-3 py-2 text-small leading-4 text-fg-muted">{why}</p>}
        {detailsHref && <a href={detailsHref} className="inline-block text-small leading-4 font-medium text-fg underline underline-offset-2">{detailsLabel}</a>}
      </div>
    </Popover>
  );
}
```

## tattva/molecules/RadioGroup.tsx

```tsx
import { useId } from "react";
import type { ReactNode } from "react";
import { cn } from "../lib/cn";

export interface RadioOption {
  value: string;
  label: ReactNode;
  /** Visible hint under the label. */
  description?: string;
  disabled?: boolean;
}

export interface RadioGroupProps {
  options: RadioOption[];
  value: string;
  onChange: (value: string) => void;
  /** Name of the group, shown as the legend. */
  label: string;
  /** Hides the legend visually. Screen readers still hear it. */
  hideLabel?: boolean;
  orientation?: "vertical" | "horizontal";
  /** Marks every radio as having an error (red border, aria-invalid). Show the error text yourself. */
  invalid?: boolean;
  disabled?: boolean;
  /** Shared by all radios, which is what gives the browser its arrow-key behaviour. Defaults to a generated one. */
  name?: string;
  className?: string;
}

/** Controlled group of native radios in a fieldset. The browser handles Tab and the arrow keys. */
export function RadioGroup({ options, value, onChange, label, hideLabel, orientation = "vertical", invalid, disabled, name, className }: RadioGroupProps) {
  const auto = useId();
  const groupName = name ?? auto;
  return (
    <fieldset disabled={disabled} className={cn("min-w-0 border-0 p-0 m-0", className)}>
      <legend className={cn("mb-2 p-0 text-body leading-5 font-medium text-fg", hideLabel && "sr-only")}>{label}</legend>
      <div className={cn("flex gap-x-6 gap-y-2", orientation === "vertical" ? "flex-col" : "flex-row flex-wrap")}>
        {options.map((o) => <Radio key={o.value} name={groupName} option={o} checked={o.value === value} invalid={invalid} onSelect={onChange} />)}
      </div>
    </fieldset>
  );
}

function Radio({ name, option, checked, invalid, onSelect }: { name: string; option: RadioOption; checked: boolean; invalid?: boolean; onSelect: (v: string) => void }) {
  const labelId = useId();
  const descId = useId();
  return (
    <label className={cn("inline-flex items-start gap-3 text-body leading-5 text-fg pointer-coarse:min-h-11 pointer-coarse:items-center", option.disabled ? "cursor-not-allowed opacity-(--disabled-opacity)" : "cursor-pointer")}>
      <span className="relative mt-0.5 inline-grid size-5 shrink-0 place-items-center pointer-coarse:mt-0">
        <input type="radio" name={name} value={option.value} checked={checked} disabled={option.disabled}
          aria-invalid={invalid || undefined} aria-labelledby={labelId} aria-describedby={option.description ? descId : undefined}
          onChange={() => onSelect(option.value)}
          className={cn("peer size-5 shrink-0 appearance-none rounded-full border bg-surface transition-colors duration-[var(--dur-fast)] checked:border-accent forced-colors:border-[ButtonText] forced-colors:checked:border-[Highlight]",
            invalid ? "border-danger" : "border-line-strong", option.disabled ? "cursor-not-allowed" : "cursor-pointer")} />
        <span aria-hidden className="pointer-events-none absolute size-2.5 scale-0 rounded-full bg-accent transition-transform duration-[var(--dur-fast)] peer-checked:scale-100 forced-colors:bg-[Highlight]" />
      </span>
      <span className="flex min-w-0 flex-col">
        <span id={labelId}>{option.label}</span>
        {option.description && <span id={descId} className="text-fg-muted">{option.description}</span>}
      </span>
    </label>
  );
}
```

## tattva/molecules/RateLimitNotice.tsx

```tsx
import { Button } from "../atoms/Button";
import { Callout } from "./Callout";


export function RateLimitNotice({ resetsIn, onUpgrade }: { resetsIn: string; onUpgrade?: () => void }) {
  return <Callout tone="warning" title="You've reached the usage limit" action={onUpgrade && <Button size="sm" onClick={onUpgrade}>Upgrade</Button>}>Limits reset in {resetsIn}.</Callout>;
}
```

## tattva/molecules/Receipt.tsx

```tsx
import { useId, useState } from "react";
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { CheckIcon, ChevronIcon } from "../lib/icons";

export interface ReceiptProps {
  /** What was settled, such as "Confirmed the facts". */
  title: string;
  /** Right side of the row, such as a date or an amount. */
  meta?: ReactNode;
  /** Replaces the default check. Decorative. */
  icon?: ReactNode;
  /** The full card, shown when opened. */
  children: ReactNode;
  defaultOpen?: boolean;
  /** Controlled open state. */
  open?: boolean;
  onOpenChange?: (open: boolean) => void;
  /** Mount the children even while closed (hidden), for example to keep form state. */
  keepMounted?: boolean;
  className?: string;
}

/**
 * A settled card folded into one line so the thread stays short. Every decision card in a thread becomes a
 * Receipt once the person has acted. Opening it shows the original card in place; nothing traps focus.
 */
export function Receipt({ title, meta, icon, children, defaultOpen = false, open, onOpenChange, keepMounted, className }: ReceiptProps) {
  const uid = useId();
  const [inner, setInner] = useState(defaultOpen);
  const isOpen = open ?? inner;
  const toggle = () => { const v = !isOpen; if (open === undefined) setInner(v); onOpenChange?.(v); };
  return (
    <div className={cn("rounded-card border border-line bg-surface", className)}>
      <button type="button" id={`${uid}-b`} aria-expanded={isOpen} aria-controls={`${uid}-r`} onClick={toggle}
        className="flex min-h-10 w-full cursor-pointer items-center gap-3 rounded-card px-4 py-2 text-left text-body leading-5 hover:bg-hover pointer-coarse:min-h-11">
        <span aria-hidden className="flex shrink-0 text-success-fg">{icon ?? <CheckIcon width={16} height={16} />}</span>
        <span className="min-w-0 flex-1 break-words font-medium">{title}</span>
        {meta && <span className="shrink-0 text-small leading-4 text-fg-muted">{meta}</span>}
        <ChevronIcon aria-hidden className={cn("shrink-0 text-fg-subtle transition-transform duration-[var(--dur-fast)]", isOpen && "rotate-90")} />
      </button>
      <div id={`${uid}-r`} role="region" aria-labelledby={`${uid}-b`} hidden={!isOpen} className="border-t border-line p-3">
        {(isOpen || keepMounted) && children}
      </div>
    </div>
  );
}
```

## tattva/molecules/RefusalNotice.tsx

```tsx
import type { ReactNode } from "react";
import { Callout } from "./Callout";

/** Refusals are not errors: neutral tone, explain the boundary, offer an alternative. */
export function RefusalNotice({ reason, alternative }: { reason: string; alternative?: ReactNode }) {
  return (
    <Callout tone="info" title="I can't help with that">
      <p>{reason}</p>
      {alternative && <p className="mt-1">{alternative}</p>}
    </Callout>
  );
}
```

## tattva/molecules/ResultCard.tsx

```tsx
import { cn } from "../lib/cn";
import { headingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { Badge, type Tone } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { Skeleton } from "../atoms/Skeleton";
import { formatPrice } from "../atoms/Price";
import { FactList, type Fact } from "./FactList";
import { MediaFrame } from "./MediaFrame";

export interface ResultCardPrice {
  value: number;
  /** ISO currency code, such as "EUR". */
  currency?: string;
  locale?: string;
  decimals?: number;
  /** What the price is for: "per night", "a month". */
  note?: string;
}

export interface ResultCardProps {
  /** The thing's name: a hotel, product, place or person. */
  title: string;
  /** One short line under the name: an area, a brand, a role. */
  subtitle?: string;
  /** A picture of the thing. `alt` says what it shows. */
  image?: { src: string; alt: string };
  /** Two to four facts that help choose between results. */
  facts?: Fact[];
  price?: ResultCardPrice;
  /** One short tag, such as "Free cancellation" or "Best match". */
  tag?: { label: string; tone?: Tone };
  /** The one thing to do with this result. */
  action?: { label: string; onClick: () => void };
  /** The answer is still arriving: the card is shown, but its action waits until the answer is complete. */
  streaming?: boolean;
  /** Draws placeholders while the result is loading. */
  loading?: boolean;
  /** Level of the name's heading. Defaults to 3, under an answer's own heading. */
  headingLevel?: HeadingLevel;
  className?: string;
}

/**
 * One thing in an answer, such as a hotel, a product, a place or a person: a picture, its name, a few facts, an
 * optional price and tag, and one action. Lay several out with CardRow, all with the same fields.
 */
export function ResultCard({ title, subtitle, image, facts, price, tag, action, streaming = false, loading = false, headingLevel = 3, className }: ResultCardProps) {
  const H = headingTag(headingLevel);
  if (loading) {
    return (
      <div aria-busy="true" className={cn("flex h-full w-full flex-col gap-3 rounded-card border border-line bg-surface p-3", className)}>
        <Skeleton className="aspect-video w-full rounded-[max(2px,calc(var(--radius-card)-12px))]" />
        <Skeleton className="h-5 w-2/3" />
        <Skeleton className="h-4 w-1/2" />
        <Skeleton className="h-4 w-full" />
      </div>
    );
  }
  return (
    <article className={cn("flex h-full w-full flex-col overflow-hidden rounded-card border border-line bg-surface", className)} aria-busy={streaming || undefined}>
      {image && (
        <div className="relative">
          <MediaFrame src={image.src} alt={image.alt} ratio="16:9" flush />
          {/* On a picture the tag sits on its corner, so the name keeps the full width. */}
          {tag && <Badge tone={tag.tone ?? "neutral"} className="absolute top-3 left-3">{tag.label}</Badge>}
        </div>
      )}
      <div className="flex flex-1 flex-col gap-3 p-4">
        <div className="flex items-start justify-between gap-3">
          <div className="min-w-0">
            <H className="m-0 font-sans! text-lead leading-6 font-semibold text-fg [overflow-wrap:anywhere]">{title}</H>
            {subtitle && <p className="m-0 mt-0.5 text-body leading-5 text-fg-muted">{subtitle}</p>}
          </div>
          {tag && !image && <Badge tone={tag.tone ?? "neutral"} className="shrink-0">{tag.label}</Badge>}
        </div>
        {facts && facts.length > 0 && <FactList facts={facts} className="-my-1.5" />}
        {(price || action) && (
          <div className="mt-auto flex flex-wrap items-end justify-between gap-3 pt-1">
            {price ? (
              <p className="m-0 min-w-0">
                <span className="text-lead-lg leading-6 font-semibold text-fg tabular-nums">{formatPrice(price.value, price.currency ?? "USD", price.locale ?? "en-US", price.decimals ?? 0)}</span>
                {price.note && <span className="ml-1 text-body leading-5 text-fg-muted">{price.note}</span>}
              </p>
            ) : <span />}
            {action && (
              <Button size="sm" variant="secondary" onClick={action.onClick} disabled={streaming} aria-label={`${action.label}: ${title}`}>{action.label}</Button>
            )}
          </div>
        )}
      </div>
    </article>
  );
}
```

## tattva/molecules/RevertToggle.tsx

```tsx
import { useState, type ReactNode } from "react";
import { Button } from "../atoms/Button";
import { SegmentedControl } from "../atoms/SegmentedControl";
import { cn } from "../lib/cn";

export type RevertVersion = "ai" | "user";

export interface RevertToggleProps {
  /** The original AI suggestion. */
  aiValue: ReactNode;
  /** The person's edited version. When absent, only the AI value shows and the controls are hidden. */
  userValue?: ReactNode;
  /** Controlled active version. Omit to let the toggle manage itself. */
  value?: RevertVersion;
  /** Starting version in uncontrolled mode. Defaults to "user" when an edit exists. */
  defaultValue?: RevertVersion;
  onChange?: (value: RevertVersion) => void;
  /** Called when the person chooses Revert to original. The app should drop the edit. */
  onRevert?: () => void;
  aiLabel?: string;
  userLabel?: string;
  revertLabel?: string;
  /** Accessible name for the version switch. */
  label?: string;
  className?: string;
}

/** Switches a value between the AI suggestion and the person's edit, with a way back to the original. */
export function RevertToggle({ aiValue, userValue, value, defaultValue, onChange, onRevert, aiLabel = "AI suggestion", userLabel = "Your edit", revertLabel = "Revert to original", label = "Version", className }: RevertToggleProps) {
  const hasEdit = userValue !== undefined && userValue !== null;
  const [inner, setInner] = useState<RevertVersion>(defaultValue ?? (hasEdit ? "user" : "ai"));
  const [message, setMessage] = useState("");
  const active: RevertVersion = hasEdit ? (value ?? inner) : "ai";
  const names = { ai: aiLabel, user: userLabel };
  const set = (v: RevertVersion) => { if (value === undefined) setInner(v); onChange?.(v); setMessage(`Showing ${names[v]}`); };
  return (
    <div className={cn("space-y-3", className)}>
      {hasEdit && (
        <div className="flex flex-wrap items-center gap-2">
          <SegmentedControl size="sm" label={label} value={active} onChange={set} options={[{ value: "ai", label: aiLabel }, { value: "user", label: userLabel }]} />
          <Button size="sm" variant="ghost" onClick={() => { set("ai"); onRevert?.(); }}>{revertLabel}</Button>
        </div>
      )}
      <div className="rounded-card border border-line bg-surface px-4 py-3 text-body leading-5 text-fg">{active === "ai" ? aiValue : userValue}</div>
      <span role="status" className="sr-only">{message}</span>
    </div>
  );
}
```

## tattva/molecules/SceneIllustration.tsx

```tsx
import { useEffect, useRef, useState } from "react";
import type { AnimationItem } from "lottie-web";
import { cn } from "../lib/cn";

/** The 35 Tattva scenes. Each is a Lottie file at `scenes/<name>.json` on the site and in the npm package. */
export const sceneNames = [
  "first-use", "no-results", "error", "permission", "offline", "rate-limit", "success", "upload", "working",
  "sources", "approval", "confidence", "refusal", "memory", "private", "undo", "sensitive",
  "plan", "takeover", "needs-you", "background",
  "variants", "voice", "video", "slides", "music",
  "chart", "generated", "first-run", "download", "connect", "lost", "whats-new", "accessible", "mobile",
] as const;
export type SceneName = (typeof sceneNames)[number];

/** A plain title for each scene, for galleries and as a fallback accessible name. */
export const sceneTitles: Record<SceneName, string> = {
  "first-use": "First message", "no-results": "Nothing found", error: "Something broke", permission: "Asking for access",
  offline: "No connection", "rate-limit": "Slow down", success: "Done", upload: "Add a file", working: "Working on it",
  sources: "Show the sources", approval: "Ask before acting", confidence: "How sure it is", refusal: "A polite no",
  memory: "What it remembers", private: "Private chat", undo: "Take it back", sensitive: "Hide private details",
  plan: "The plan", takeover: "Take over", "needs-you": "Waiting for you", background: "Working while you are away",
  variants: "Pick a version", voice: "Speak to it", video: "Take after take", slides: "Build a deck", music: "Make music",
  chart: "Read the numbers", generated: "Built as it goes", "first-run": "Get started", download: "Download",
  connect: "Connect", lost: "Page not found", "whats-new": "What is new", accessible: "Works for everyone", mobile: "On a phone",
};

export type SceneMotion = "loop" | "once" | "still";
export type SceneSize = "sm" | "md" | "lg" | "fill";

export interface SceneIllustrationProps {
  /** The scene's Lottie data. Pass this or `src`. */
  data?: object;
  /** Address of the scene's Lottie file, for example `${base}scenes/approval.json`. Used when `data` is not given. */
  src?: string;
  /** Accessible name. Leave it out when nearby words already say what the scene shows; the scene is then hidden from assistive tech. */
  label?: string;
  /** `loop` plays the entrance, then a calm loop. `once` plays the entrance and stops. `still` shows the finished picture. Reduced motion always shows the still picture. */
  motion?: SceneMotion;
  /** Width: 12rem, 18rem or 30rem. `fill` takes the width of its container. Height follows at 4:3. */
  size?: SceneSize;
  className?: string;
}

const sizes: Record<SceneSize, string> = { sm: "w-48", md: "w-72", lg: "w-120", fill: "w-full" };

/* Scenes are drawn with three key colours. The theme decides what they become, so scenes follow light and dark mode. */
const themed =
  "[&_[fill='rgb(17,17,17)']]:fill-fg [&_[stroke='rgb(17,17,17)']]:stroke-fg " +
  "[&_[fill='rgb(255,255,255)']]:fill-surface [&_[stroke='rgb(255,255,255)']]:stroke-surface " +
  "[&_[fill='rgb(200,240,74)']]:fill-lime [&_[stroke='rgb(200,240,74)']]:stroke-lime";

interface Marker { tm: number; cm: string; dr: number }

/** Where the entrance ends and the loop runs, read from the file's "intro" and "loop" markers. */
function segments(data: { markers?: Marker[]; op?: number }) {
  const intro = data.markers?.find((m) => m.cm === "intro");
  const loop = data.markers?.find((m) => m.cm === "loop");
  const end = data.op ?? 0;
  const introEnd = intro ? intro.tm + intro.dr : end;
  return { introEnd, loop: loop ? [loop.tm, loop.tm + loop.dr] as [number, number] : null };
}

/**
 * A Tattva scene: a small story told with objects and a lime shape, played from a Lottie file.
 * It plays a short entrance, then a calm loop that carries the meaning, and pauses while off screen.
 */
export function SceneIllustration({ data, src, label, motion = "loop", size = "md", className }: SceneIllustrationProps) {
  const box = useRef<HTMLDivElement>(null);
  const [ready, setReady] = useState(false);

  useEffect(() => {
    let anim: AnimationItem | null = null;
    let seen: IntersectionObserver | null = null;
    let cancelled = false;
    const still = motion === "still" || window.matchMedia("(prefers-reduced-motion: reduce)").matches;

    (async () => {
      const json = (data ?? (src ? await fetch(src).then((r) => r.json()) : null)) as { markers?: Marker[]; op?: number } | null;
      if (!json || cancelled || !box.current) return;
      const lottie = (await import("lottie-web/build/player/lottie_light")).default;
      if (cancelled || !box.current) return;
      const { introEnd, loop } = segments(json);
      anim = lottie.loadAnimation({
        container: box.current, renderer: "svg", loop: false, autoplay: false,
        animationData: structuredClone(json), rendererSettings: { preserveAspectRatio: "xMidYMid meet" },
      });
      anim.addEventListener("DOMLoaded", () => {
        setReady(true);
        if (still) { anim?.goToAndStop(introEnd, true); return; }
        anim?.playSegments([0, introEnd], true);
      });
      anim.addEventListener("complete", () => {
        if (motion === "loop" && loop && anim) { anim.loop = true; anim.playSegments(loop, true); }
      });
      if (!still) {
        let hidden = false;
        seen = new IntersectionObserver(([e]) => {
          if (!anim) return;
          if (!e.isIntersecting) { hidden = true; anim.pause(); } else if (hidden) { hidden = false; anim.play(); }
        });
        seen.observe(box.current);
      }
    })();

    return () => { cancelled = true; seen?.disconnect(); anim?.destroy(); };
  }, [data, src, motion]);

  return (
    <div
      ref={box}
      role={label ? "img" : undefined}
      aria-label={label}
      aria-hidden={label ? undefined : true}
      aria-busy={ready ? undefined : true}
      className={cn("aspect-[4/3] max-w-full shrink-0", sizes[size], themed, className)}
    />
  );
}
```

## tattva/molecules/SelectionToolbar.tsx

```tsx
import { useEffect, useRef, useState, type KeyboardEvent, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { SendIcon, SparkleIcon } from "../lib/icons";
import { Popover } from "../atoms/Popover";
import { IconButton } from "../atoms/Button";

export interface SelectionAction { id: string; label: string; icon?: ReactNode }

export interface SelectionToolbarProps {
  /** Preset chips shown in the bar. */
  presets?: SelectionAction[];
  /** Items in the overflow menu. Pass an empty array to hide the menu. */
  moreActions?: SelectionAction[];
  /** Called with the action id for a preset or overflow item. */
  onAction?: (id: string) => void;
  /** Called with the instruction typed in the free-text field. */
  onAsk?: (instruction: string) => void;
  /** Called on Escape. Hide the bar and return focus to the editor. */
  onClose?: () => void;
  /** Accessible name for the toolbar. */
  label?: string;
  moreLabel?: string;
  askLabel?: string;
  askPlaceholder?: string;
  sendLabel?: string;
  className?: string;
}

const defaultPresets: SelectionAction[] = [{ id: "rephrase", label: "Rephrase" }, { id: "shorten", label: "Shorten" }, { id: "tone", label: "Change tone" }];
const defaultMore: SelectionAction[] = [
  { id: "elaborate", label: "Elaborate" }, { id: "formal", label: "More formal" }, { id: "casual", label: "More casual" },
  { id: "bullets", label: "Bulletize" }, { id: "summarize", label: "Summarize" },
];

const chip = "inline-flex h-8 shrink-0 cursor-pointer items-center gap-1.5 rounded-control border border-line bg-surface px-3 text-small leading-4 font-medium text-fg transition-colors hover:bg-hover";

/**
 * Floating action bar for highlighted text. It renders inline and does no measuring, so the consumer positions it.
 * It is a toolbar with one tab stop: arrow keys, Home and End move between controls, Escape calls onClose.
 * Real apps should also offer the same actions from a context menu and a keyboard shortcut.
 */
export function SelectionToolbar({
  presets = defaultPresets, moreActions = defaultMore, onAction, onAsk, onClose, label = "Edit selected text",
  moreLabel = "More options", askLabel = "Ask AI to edit", askPlaceholder = "Ask AI to edit", sendLabel = "Send instruction", className,
}: SelectionToolbarProps) {
  const root = useRef<HTMLDivElement>(null);
  const [active, setActive] = useState(0);
  const [moreOpen, setMoreOpen] = useState(false);
  const [text, setText] = useState("");
  const focusFirstItem = useRef(false);
  const items = () => Array.from(root.current?.querySelectorAll<HTMLElement>("[data-roving]") ?? []);
  const tab = (i: number) => (i === active ? 0 : -1);

  useEffect(() => {
    if (moreOpen && focusFirstItem.current) { root.current?.querySelector<HTMLElement>('[role="menuitem"]')?.focus(); }
    focusFirstItem.current = false;
  }, [moreOpen]);

  const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
    if (e.key === "Escape") { if (!moreOpen) onClose?.(); return; }
    const list = items();
    const i = list.indexOf(document.activeElement as HTMLElement);
    if (i < 0) return;
    const el = list[i];
    if (el instanceof HTMLInputElement) {
      const atStart = el.selectionStart === 0 && el.selectionEnd === 0;
      const atEnd = el.selectionStart === el.value.length && el.selectionEnd === el.value.length;
      if ((e.key === "ArrowLeft" && !atStart) || (e.key === "ArrowRight" && !atEnd) || e.key === "Home" || e.key === "End") return;
    }
    let next = i;
    if (e.key === "ArrowRight") next = Math.min(i + 1, list.length - 1);
    else if (e.key === "ArrowLeft") next = Math.max(i - 1, 0);
    else if (e.key === "Home") next = 0;
    else if (e.key === "End") next = list.length - 1;
    else return;
    e.preventDefault();
    list[next].focus();
  };

  const onMenuKey = (e: KeyboardEvent<HTMLDivElement>) => {
    const m = Array.from(e.currentTarget.querySelectorAll<HTMLElement>('[role="menuitem"]'));
    const i = m.indexOf(document.activeElement as HTMLElement);
    if (e.key === "ArrowDown") m[(i + 1) % m.length]?.focus();
    else if (e.key === "ArrowUp") m[(i - 1 + m.length) % m.length]?.focus();
    else return;
    e.preventDefault();
    e.stopPropagation();
  };

  const submit = () => { const t = text.trim(); if (!t) return; onAsk?.(t); setText(""); };

  return (
    <div ref={root} role="toolbar" aria-label={label} aria-orientation="horizontal" onKeyDown={onKeyDown}
      className={cn("inline-flex max-w-full flex-wrap items-center gap-1.5 rounded-overlay border border-line bg-raised p-1.5 shadow-md", className)}>
      {presets.map((p, i) => (
        <button key={p.id} type="button" data-roving tabIndex={tab(i)} onFocus={() => setActive(i)} onClick={() => onAction?.(p.id)} className={chip}>{p.icon}{p.label}</button>
      ))}
      {moreActions.length > 0 && (
        <Popover role="menu" label={moreLabel} open={moreOpen} onOpenChange={setMoreOpen} panelClassName="min-w-44"
          trigger={({ toggle, triggerProps }) => (
            <button type="button" data-roving tabIndex={tab(presets.length)} onFocus={() => setActive(presets.length)} {...triggerProps}
              onClick={toggle} aria-label={moreLabel} title={moreLabel}
              onKeyDown={(e) => { if (e.key === "ArrowDown" || e.key === "ArrowUp") { e.preventDefault(); e.stopPropagation(); focusFirstItem.current = true; setMoreOpen(true); } }}
              className={cn(chip, "px-2.5")}>
              <span aria-hidden="true" className="tracking-widest">...</span>
            </button>
          )}>
          {({ close }) => (
            <div onKeyDown={onMenuKey} className="flex flex-col">
              {moreActions.map((a) => (
                <button key={a.id} type="button" role="menuitem" tabIndex={-1}
                  onClick={() => { onAction?.(a.id); close(); }}
                  className="flex cursor-pointer items-center gap-2 rounded-xl px-3 py-2 text-left text-compact text-fg hover:bg-hover">{a.icon}{a.label}</button>
              ))}
            </div>
          )}
        </Popover>
      )}
      <form onSubmit={(e) => { e.preventDefault(); submit(); }} className="flex min-w-48 flex-1 items-center gap-1 rounded-field border border-line bg-sunken pl-3 pr-0.5 has-[input:focus-visible]:outline-2 has-[input:focus-visible]:outline-offset-2 has-[input:focus-visible]:outline-[var(--focus-ring)]">
        <SparkleIcon width={12} height={12} className="shrink-0 text-fg-subtle" />
        <input data-roving tabIndex={tab(presets.length + (moreActions.length > 0 ? 1 : 0))}
          onFocus={() => setActive(presets.length + (moreActions.length > 0 ? 1 : 0))}
          value={text} onChange={(e) => setText(e.target.value)} aria-label={askLabel} placeholder={askPlaceholder}
          className="h-8 min-w-0 flex-1 bg-transparent text-small leading-4 text-fg placeholder:text-fg-subtle focus:outline-none focus-visible:outline-none" />
        <IconButton type="submit" size="sm" label={sendLabel} variant="primary" disabled={!text.trim()}><SendIcon width={13} height={13} /></IconButton>
      </form>
    </div>
  );
}
```

## tattva/molecules/SensitiveDataGuard.tsx

```tsx
import { useId } from "react";
import { cn } from "../lib/cn";
import { headingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { LockIcon } from "../lib/icons";
import type { SensitiveFinding, SensitiveKind } from "../lib/sensitive";
import { Button } from "../atoms/Button";

export interface SensitiveDataGuardLabels {
  /** Receives how many details were found. */
  heading?: (n: number) => string;
  body?: string;
  kinds?: Partial<Record<SensitiveKind, string>>;
  remove?: string;
  removeAll?: string;
  sendAnyway?: string;
}

export interface SensitiveDataGuardProps {
  /** What was found, from findSensitive(text). Nothing shows when the list is empty. */
  findings: SensitiveFinding[];
  /** Takes one detail out of the message. */
  onRemove: (id: string) => void;
  /** Takes every detail out. */
  onRemoveAll: () => void;
  /** Sends the message as it is. */
  onSendAnyway: () => void;
  labels?: SensitiveDataGuardLabels;
  /** Level of the part's heading, so it fits the page outline. Default 3. */
  headingLevel?: HeadingLevel;
  className?: string;
}

const KIND: Record<SensitiveKind, string> = { card: "Card number", key: "API key", password: "Password", email: "Email address", phone: "Phone number" };

/**
 * Sits just above the message box when a message about to be sent holds a private detail: a card number, an API key,
 * a password, an email address or a phone number. It names each one with a safe preview, offers to take it out, and
 * still lets the person send as it is. Taking it out is the main action. Use findSensitive() to fill it.
 */
export function SensitiveDataGuard({ findings, onRemove, onRemoveAll, onSendAnyway, labels, headingLevel = 3, className }: SensitiveDataGuardProps) {
  const l = {
    heading: (n: number) => (n === 1 ? "This message has a private detail" : `This message has ${n} private details`),
    body: "The assistant does not need these to help. Take them out, or send as it is.",
    remove: "Take out", removeAll: "Take them out", sendAnyway: "Send anyway", ...labels,
    kinds: { ...KIND, ...labels?.kinds },
  };
  const uid = useId();
  const H = headingTag(headingLevel);
  if (!findings.length) return null;
  return (
    <section role="status" aria-labelledby={`${uid}-h`} className={cn("rounded-card border border-attention-line bg-attention-soft p-4", className)}>
      <div className="flex items-start gap-2.5">
        <LockIcon aria-hidden width={18} height={18} className="mt-0.5 shrink-0 text-attention-fg" />
        <div className="min-w-0 flex-1">
          <H id={`${uid}-h`} className="m-0 text-body leading-5 font-semibold text-fg">{l.heading(findings.length)}</H>
          <p className="m-0 mt-1 text-small leading-5 text-fg-muted">{l.body}</p>
          <ul className="mt-3 space-y-1.5">
            {findings.map((f) => (
              <li key={f.id} className="flex flex-wrap items-center gap-x-3 gap-y-1">
                <span className="text-small leading-5 text-fg">{l.kinds[f.kind]}</span>
                <span className="font-mono text-small leading-5 text-fg-muted">{f.preview}</span>
                {findings.length > 1 && <Button size="sm" variant="ghost" onClick={() => onRemove(f.id)} aria-label={`${l.remove}: ${l.kinds[f.kind]} ${f.preview}`}>{l.remove}</Button>}
              </li>
            ))}
          </ul>
          <div className="mt-4 flex flex-wrap gap-2">
            <Button size="sm" onClick={findings.length > 1 ? onRemoveAll : () => onRemove(findings[0].id)}>{findings.length > 1 ? l.removeAll : l.remove}</Button>
            <Button size="sm" variant="ghost" onClick={onSendAnyway}>{l.sendAnyway}</Button>
          </div>
        </div>
      </div>
    </section>
  );
}
```

## tattva/molecules/SlideStage.tsx

```tsx
import { useEffect, useId, useLayoutEffect, useRef, useState, type CSSProperties, type KeyboardEvent, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, EditIcon, EyeIcon, InfoIcon, RetryIcon } from "../lib/icons";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { Skeleton } from "../atoms/Skeleton";
import { WorkingEdge } from "../atoms/WorkingEdge";
import { AILabel } from "./AILabel";

export type SlideRatio = "16:9" | "4:3" | "1:1" | "4:5" | "9:16" | "fluid";
export type StageState = "viewing" | "editing" | "working" | "previewing" | "loading" | "partial" | "failed";
export type StageIssueKind = "overflow" | "contrast" | "missingAlt" | "noSource";

export interface StageIssue {
  id: string;
  kind: StageIssueKind;
  /** Plain words, such as "The third point does not fit. Shorten it or split the slide." */
  message: string;
  /** id of the element on the slide this is about. Choosing the issue focuses it. */
  targetId?: string;
  /** True when it stops the person presenting or exporting. Only then the chip turns amber. */
  blocking?: boolean;
}

export interface StageAIChange {
  /** What the AI changed on this slide, in one or two plain sentences. */
  summary: string;
  /** Shown as Undo this change when given. */
  onUndo?: () => void;
}

/** Width and height, in design pixels, of a slide at each ratio. Content is laid out at this size and then scaled. */
const DESIGN: Record<Exclude<SlideRatio, "fluid">, [number, number]> = {
  "16:9": [960, 540], "4:3": [960, 720], "1:1": [960, 960], "4:5": [960, 1200], "9:16": [540, 960],
};

/* ---------------- SlideCanvas ---------------- */

export interface SlideCanvasProps {
  /** Slide shape. "fluid" grows with its content and is not scaled. */
  ratio?: SlideRatio;
  /** The slide, laid out for the design size (960 wide for landscape, 540 wide for 9:16). */
  children: ReactNode;
  className?: string;
  /** Extra styles on the frame, for example a max width. */
  style?: CSSProperties;
}

/**
 * Draws a slide at its design size and scales the whole thing to the width it is given, so nothing reflows.
 * What the person sees here is what will export. SlideStage and SlideRail thumbnails both use it.
 */
export function SlideCanvas({ ratio = "16:9", children, className, style }: SlideCanvasProps) {
  const box = useRef<HTMLDivElement>(null);
  const [scale, setScale] = useState(0);
  const size = ratio === "fluid" ? null : DESIGN[ratio];

  useLayoutEffect(() => {
    const el = box.current;
    if (!el || !size) return;
    const fit = () => setScale(el.clientWidth / size[0]);
    fit();
    if (typeof ResizeObserver === "undefined") return;
    const ro = new ResizeObserver(fit);
    ro.observe(el);
    return () => ro.disconnect();
  }, [size?.[0]]);

  if (!size) return <div className={cn("relative w-full overflow-hidden", className)} style={style}>{children}</div>;
  return (
    <div ref={box} className={cn("relative w-full overflow-hidden", className)} style={{ aspectRatio: `${size[0]} / ${size[1]}`, ...style }}>
      {/* Hidden until measured, so a full-size slide never flashes before it is scaled. Scaling never animates. */}
      <div className="absolute top-0 left-0 origin-top-left" style={{ width: size[0], height: size[1], transform: `scale(${scale})`, visibility: scale ? undefined : "hidden" }}>
        {children}
      </div>
    </div>
  );
}

/* ---------------- SlideStage ---------------- */

export interface SlideStageProps {
  /** Names the stage for screen readers and in the header, such as "Slide 3, Pricing". */
  label: string;
  /** Slide shape. The frame keeps it at every width. */
  ratio?: SlideRatio;
  /** What is happening to this slide. */
  state?: StageState;
  /** The slide, laid out at its design size. */
  children?: ReactNode;
  /** Things to check before presenting, such as text that does not fit. */
  issues?: StageIssue[];
  /** Called when the person picks an issue. Without it, focus moves to the element named by targetId. */
  onIssueSelect?: (issue: StageIssue) => void;
  /** Words for the preview banner. */
  previewLabel?: string;
  /** Shows Exit preview in the banner. Escape inside the stage does the same. */
  onExitPreview?: () => void;
  /** What the AI is doing while state is "working", such as "Rewriting the title and points". */
  workingLabel?: string;
  /** What is missing while state is "partial", such as "The chart could not be made. The text is ready." */
  partialNote?: string;
  /** Why it failed, while state is "failed". Say whether anything was kept. */
  errorMessage?: string;
  /** Shows Try again while state is "failed" or "partial". */
  onRetry?: () => void;
  /** A caption under the slide saying what the AI changed. */
  aiChange?: StageAIChange;
  /** Tallest the frame may be, as any CSS length. Portrait slides use it to stay on screen. */
  maxHeight?: string;
  className?: string;
}

const STATE_WORDS: Record<StageState, string> = {
  viewing: "", editing: "Editing", working: "AI is working on this slide", previewing: "Preview",
  loading: "Loading slide", partial: "Partly built", failed: "Could not build this slide",
};

const KIND_WORDS: Record<StageIssueKind, string> = {
  overflow: "Text does not fit", contrast: "Hard to read", missingAlt: "Image has no description", noSource: "Number has no source",
};

function aspect(ratio: SlideRatio) {
  if (ratio === "fluid") return undefined;
  const [w, h] = DESIGN[ratio];
  return w / h;
}

/**
 * The current slide at its true shape, scaled to fit, with the warnings a person needs before presenting.
 * It covers viewing, editing, AI working, previewing a change, loading, partly built and failed, each in words.
 */
export function SlideStage({
  label, ratio = "16:9", state = "viewing", children, issues = [], onIssueSelect, previewLabel = "Previewing a change. Nothing has changed yet.",
  onExitPreview, workingLabel, partialNote, errorMessage, onRetry, aiChange, maxHeight = "70dvh", className,
}: SlideStageProps) {
  const [showIssues, setShowIssues] = useState(false);
  const issuesId = useId();
  const blocking = issues.filter((i) => i.blocking).length;
  const ar = aspect(ratio);
  // Width is capped so the frame's height never passes maxHeight: a 9:16 slide stays on screen.
  const frameStyle: CSSProperties | undefined = ar ? { maxWidth: `min(100%, calc(${maxHeight} * ${ar}))` } : undefined;

  useEffect(() => { if (!issues.length) setShowIssues(false); }, [issues.length]);

  const pick = (issue: StageIssue) => {
    if (onIssueSelect) onIssueSelect(issue);
    else if (issue.targetId) {
      const el = document.getElementById(issue.targetId);
      if (el) {
        if (!el.hasAttribute("tabindex") && !el.matches("a, button, input, select, textarea")) el.setAttribute("tabindex", "-1");
        el.focus();
      }
    }
  };

  const onKeyDown = (e: KeyboardEvent<HTMLElement>) => {
    if (e.key === "Escape" && state === "previewing" && onExitPreview) { e.preventDefault(); onExitPreview(); }
  };

  const words = state === "working" && workingLabel ? workingLabel : STATE_WORDS[state];
  const issueWords = blocking
    ? `${blocking} ${blocking === 1 ? "thing" : "things"} to fix before you present`
    : `${issues.length} ${issues.length === 1 ? "thing" : "things"} to check`;

  let body: ReactNode;
  if (state === "loading") {
    body = (
      <div className="absolute inset-0 grid content-center gap-3 p-6">
        <Skeleton className="h-6 w-3/5 rounded-control" />
        <Skeleton className="h-3 w-4/5 rounded-control" />
        <Skeleton className="h-3 w-2/3 rounded-control" />
      </div>
    );
  } else if (state === "failed") {
    body = (
      <div role="alert" className="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-sunken p-4 text-center">
        <AlertIcon width={20} height={20} className="text-danger-fg" />
        <p className="text-body font-medium text-fg">Could not build this slide</p>
        {errorMessage && <p className="max-w-narrow text-compact text-fg-muted">{errorMessage}</p>}
        {onRetry && <Button size="sm" variant="secondary" leading={<RetryIcon width={14} height={14} />} onClick={onRetry} className="mt-1">Try again</Button>}
      </div>
    );
  } else {
    body = (
      <div key={state === "previewing" ? "preview" : "slide"} className={cn("size-full", "motion-safe:animate-fade")}>
        <SlideCanvas ratio={ratio}>{children}</SlideCanvas>
      </div>
    );
  }

  const frame = (
    <div className={cn(
      "relative w-full overflow-hidden rounded-card border bg-surface",
      state === "editing" ? "border-fg" : "border-line",
      state === "working" && "opacity-90",
    )} style={ar && (state === "loading" || state === "failed") ? { aspectRatio: String(ar) } : undefined}>
      {body}
    </div>
  );

  return (
    <section role="region" aria-label={label} onKeyDown={onKeyDown} className={cn("grid w-full min-w-0 gap-2", className)}>
      {/* Top line: the state in words on the left, things to check on the right. */}
      <div className="flex min-h-8 flex-wrap items-center justify-between gap-2">
        <p className="flex min-w-0 items-center gap-1.5 text-compact text-fg-muted">
          {state === "editing" && <EditIcon width={14} height={14} aria-hidden />}
          {state === "previewing" && <EyeIcon width={14} height={14} aria-hidden />}
          {state === "failed" && <AlertIcon width={14} height={14} aria-hidden className="text-danger-fg" />}
          <span className="truncate font-medium text-fg">{label}</span>
          {words && <span aria-hidden className="text-fg-subtle">·</span>}
          {words && <span className="truncate">{words}</span>}
        </p>
        {issues.length > 0 && (
          <button type="button" aria-expanded={showIssues} aria-controls={issuesId} onClick={() => setShowIssues((v) => !v)}
            className={cn(
              "tap inline-flex h-7 cursor-pointer items-center gap-1.5 rounded-control border px-2.5 text-small font-medium transition-colors duration-(--dur-fast)",
              blocking ? "border-transparent bg-attention-soft text-attention-fg hover:opacity-90" : "border-line bg-surface text-fg-muted hover:bg-hover hover:text-fg",
            )}>
            {blocking ? <AlertIcon width={14} height={14} aria-hidden /> : <InfoIcon width={14} height={14} aria-hidden />}
            {issueWords}
          </button>
        )}
      </div>

      {state === "previewing" && (
        <div role="status" className="flex flex-wrap items-center justify-between gap-2 rounded-card border border-line bg-sunken px-3 py-2 motion-safe:animate-fade">
          <p className="flex items-center gap-2 text-compact text-fg"><EyeIcon width={16} height={16} aria-hidden className="shrink-0 text-fg-muted" />{previewLabel}</p>
          {onExitPreview && <Button size="sm" variant="secondary" onClick={onExitPreview}>Exit preview</Button>}
        </div>
      )}

      {showIssues && issues.length > 0 && (
        <ul id={issuesId} aria-label="Things to check on this slide" className="grid gap-1 rounded-card border border-line bg-surface p-1.5">
          {issues.map((i) => (
            <li key={i.id}>
              <button type="button" onClick={() => pick(i)}
                className="flex min-h-9 w-full cursor-pointer items-start gap-2 rounded-control px-2 py-1.5 text-left hover:bg-hover pointer-coarse:min-h-11">
                {i.blocking
                  ? <Badge className="mt-0.5 shrink-0 border-transparent bg-attention-soft text-attention-fg">Fix first</Badge>
                  : <Badge className="mt-0.5 shrink-0">Check</Badge>}
                <span className="min-w-0 text-compact leading-5">
                  <span className="font-medium text-fg">{KIND_WORDS[i.kind]}. </span>
                  <span className="text-fg-muted">{i.message}</span>
                  {i.targetId && <span className="text-fg-subtle"> Go to it.</span>}
                </span>
              </button>
            </li>
          ))}
        </ul>
      )}

      <div className="mx-auto w-full" style={frameStyle}>
        <WorkingEdge active={state === "working"} label={workingLabel ?? "AI is working on this slide"} radius="card">
          {frame}
        </WorkingEdge>
      </div>

      {state === "partial" && (
        <div role="status" className="flex flex-wrap items-center gap-2 text-compact text-fg-muted">
          <Badge tone="unsure">Partly built</Badge>
          <span className="min-w-0 flex-1">{partialNote ?? "Some parts of this slide are not ready yet."}</span>
          {onRetry && <Button size="sm" variant="secondary" leading={<RetryIcon width={14} height={14} />} onClick={onRetry}>Try the rest again</Button>}
        </div>
      )}

      {aiChange && state !== "loading" && state !== "failed" && (
        <div className="flex flex-wrap items-start gap-2 text-compact leading-5 text-fg-muted">
          <AILabel level="message" size={18} text="AI edit" overview="The AI changed this slide at your request. Your theme and the rest of the deck stayed the same." />
          <p className="min-w-0 flex-1 basis-48">{aiChange.summary}</p>
          {aiChange.onUndo && <Button size="sm" variant="ghost" onClick={aiChange.onUndo}>Undo this change</Button>}
        </div>
      )}
    </section>
  );
}
```

## tattva/molecules/SourceCard.tsx

```tsx
import { GlobeIcon } from "../lib/icons";

export interface Source { id: number; title: string; url: string; domain: string; snippet?: string }

/** One numbered source. Render inside an <ol>; the id anchors inline citation markers. */
export function SourceCard({ source: s }: { source: Source }) {
  return (
    <li id={`source-${s.id}`}>
      <a href={s.url} target="_blank" rel="noreferrer noopener"
        className="flex h-full animate-rise gap-2.5 rounded-card border border-line bg-surface p-3 text-body leading-5 no-underline transition-colors hover:border-line-strong hover:bg-sunken">
        <span className="flex size-5 shrink-0 items-center justify-center rounded-full bg-lime-soft text-nano font-semibold text-fg">{s.id}</span>
        <span className="min-w-0">
          <span className="line-clamp-1 font-medium text-fg">{s.title}</span>
          <span className="flex items-center gap-1 text-small leading-4 text-fg-subtle"><GlobeIcon width={11} height={11} />{s.domain}</span>
          {s.snippet && <span className="mt-1 line-clamp-2 block text-small leading-4 text-fg-muted">{s.snippet}</span>}
        </span>
      </a>
    </li>
  );
}
```

## tattva/molecules/SpeakerNotes.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, ChevronIcon, EditIcon, ImageIcon, RegenerateIcon, StopIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { WorkingEdge } from "../atoms/WorkingEdge";
import { AILabel } from "./AILabel";
import { InlineConfirm } from "./InlineConfirm";

export type NotesState = "empty" | "generating" | "proposed" | "kept" | "edited" | "failed";

export interface SpeakerNotesProps {
  /** Which slide these notes are for, such as "Slide 4: Pricing plans". Names the notes box. */
  slideLabel: string;
  /** The notes the person has now. Nothing the AI writes lands here until they press Keep. */
  value: string;
  /** Called on every keystroke. Your app sets state to "edited" once the person changes AI-written notes. */
  onChange: (value: string) => void;
  /**
   * empty: no notes yet. generating: the AI is drafting (show the text so far in `proposed`). proposed: a draft waits for Keep or Discard.
   * kept: notes are the AI draft the person kept. edited: the person has written or changed them. failed: the draft did not arrive.
   */
  state: NotesState;
  /** The AI draft, shown apart from the person's notes while generating or proposed. */
  proposed?: string;
  /** Asks the AI to draft notes for this slide. When the person has edited the notes it asks first. */
  onGenerate?: () => void;
  onKeep?: () => void;
  onDiscard?: () => void;
  /** Stops a draft that is still being written. */
  onStop?: () => void;
  /** Puts the AI draft back after the person edited it. Shows a small revert button beside "Edited by you". */
  onRevert?: () => void;
  /** Speaking pace used to turn words into time. The sum is done here in code, never by the model. */
  wordsPerMinute?: number;
  /** Lines about images on this slide, such as "Image on this slide: made by AI". */
  imageCredits?: string[];
  /** Why the draft failed, in plain words. */
  error?: string;
  /** Folds the notes under a button, for a phone where they sit below the slide. */
  collapsible?: boolean;
  defaultOpen?: boolean;
  className?: string;
}

const countWords = (t: string) => (t.trim() ? t.trim().split(/\s+/).length : 0);

/** "About 45 seconds" or "About 2 minutes" from a word count and a pace. Rounded to the nearest 5 seconds under two minutes. */
function speakingTime(words: number, wpm: number) {
  if (!words) return "";
  const secs = (words / wpm) * 60;
  if (secs < 10) return "under 10 seconds";
  if (secs < 120) return `about ${Math.round(secs / 5) * 5} seconds`;
  const mins = Math.round(secs / 30) / 2;
  return `about ${mins % 1 ? mins.toFixed(1) : mins} minutes`;
}

const measure = (text: string, wpm: number) => {
  const n = countWords(text);
  return n ? `${n} ${n === 1 ? "word" : "words"}, ${speakingTime(n, wpm)} to say` : "No notes yet";
};

const ANNOUNCE: Partial<Record<NotesState, string>> = {
  generating: "Drafting notes", proposed: "Draft ready. Keep it or discard it.", kept: "Notes kept", failed: "Notes could not be drafted",
};

/**
 * Speaker notes for one slide. An AI draft is held apart until the person keeps it; kept notes carry an AI label that
 * turns into "Edited by you" once they type. The word count and speaking time are worked out here from the text.
 * Drafting again over the person's own words asks first.
 */
export function SpeakerNotes({
  slideLabel, value, onChange, state, proposed, onGenerate, onKeep, onDiscard, onStop, onRevert, wordsPerMinute = 130,
  imageCredits, error, collapsible = false, defaultOpen = true, className,
}: SpeakerNotesProps) {
  const id = useId();
  const [open, setOpen] = useState(defaultOpen || !collapsible);
  const showBody = !collapsible || open;
  const draft = state === "generating" || state === "proposed";

  // Announce changes of state only, never the streaming words.
  const [spoken, setSpoken] = useState("");
  const prev = useRef(state);
  useEffect(() => {
    if (prev.current === state) return;
    const from = prev.current;
    prev.current = state;
    if (from === "proposed" && (state === "empty" || state === "edited")) setSpoken("Draft discarded");
    else if (state !== "edited") setSpoken(ANNOUNCE[state] ?? "");
  }, [state]);

  const origin =
    state === "kept" ? <AILabel level="field" size={16} text="AI draft" overview="The AI wrote these notes and you kept them. Change anything; it becomes yours." /> :
    state === "edited" && value.trim() ? (
      <span className="inline-flex items-center gap-1.5">
        <span className="inline-flex items-center gap-1 text-small leading-4 text-fg-muted"><EditIcon aria-hidden width={12} height={12} />Edited by you</span>
        {onRevert && <AILabel edited onRevert={onRevert} revertLabel="Put back the AI draft" size={16} />}
      </span>
    ) : null;

  const draftLabel = value.trim() ? "Draft new notes" : "Draft notes with AI";
  const generate = onGenerate && !draft && (
    state === "edited" && value.trim() ? (
      <InlineConfirm question="Draft over your edits?" confirmLabel="Draft new notes" cancelLabel="Keep mine" onConfirm={onGenerate}>
        <RegenerateIcon aria-hidden width={14} height={14} />{draftLabel}
      </InlineConfirm>
    ) : (
      <Button size="sm" variant="secondary" leading={<RegenerateIcon aria-hidden width={14} height={14} />} onClick={onGenerate}>{draftLabel}</Button>
    )
  );

  const header = (
    <div className="flex flex-wrap items-center gap-x-3 gap-y-1">
      {collapsible ? (
        <button type="button" aria-expanded={open} aria-controls={`${id}-body`} onClick={() => setOpen(!open)}
          className="tap -ml-1 inline-flex min-h-8 cursor-pointer items-center gap-1.5 rounded-control px-1 text-body leading-5 font-semibold text-fg hover:bg-hover">
          <ChevronIcon aria-hidden width={14} height={14} className={cn("transition-transform duration-(--dur-fast)", open && "rotate-90")} />
          Speaker notes
        </button>
      ) : (
        <label htmlFor={`${id}-notes`} className="text-body leading-5 font-semibold text-fg">Speaker notes</label>
      )}
      {origin}
      <span className="ml-auto text-small leading-4 text-fg-muted tabular-nums">{measure(value, wordsPerMinute)}</span>
    </div>
  );

  return (
    <section aria-label={`Speaker notes, ${slideLabel}`} className={cn("w-full space-y-2", className)}>
      <p role="status" aria-live="polite" className="sr-only">{spoken}</p>
      {header}
      {showBody && (
        <div id={`${id}-body`} className="space-y-2">
          <textarea id={`${id}-notes`} aria-label={collapsible ? `Speaker notes for ${slideLabel}` : undefined} aria-describedby={`${id}-for`}
            value={value} onChange={(e) => onChange(e.target.value)} rows={4}
            placeholder={state === "empty" ? "What you plan to say on this slide. Only you see these while presenting." : undefined}
            className="block w-full resize-y rounded-field border border-line bg-surface px-3 py-2 text-body leading-relaxed text-fg placeholder:text-fg-subtle hover:border-line-strong pointer-coarse:text-lead" />
          <p id={`${id}-for`} className="sr-only">For {slideLabel}.</p>

          {draft && (
            <WorkingEdge active={state === "generating"} label="AI is drafting notes" radius="card">
              <div className="space-y-2 rounded-card border border-line bg-sunken p-3">
                <div className="flex flex-wrap items-center gap-2">
                  <AILabel size={16} text={state === "generating" ? "Drafting" : "AI draft"} overview="A draft from the AI. It does not replace your notes until you press Keep." />
                  <span className="text-small leading-4 text-fg-muted">{state === "generating" ? "Writing a draft. Your notes above stay as they are." : measure(proposed ?? "", wordsPerMinute)}</span>
                </div>
                <p className="text-compact leading-relaxed whitespace-pre-wrap text-fg">{proposed || <span className="text-fg-subtle">Starting…</span>}</p>
                <div className="flex flex-wrap items-center gap-2">
                  {state === "proposed" && onKeep && <Button size="sm" onClick={onKeep}>{value.trim() ? "Keep, replacing my notes" : "Keep"}</Button>}
                  {state === "proposed" && onDiscard && <Button size="sm" variant="ghost" onClick={onDiscard}>Discard</Button>}
                  {state === "generating" && onStop && <Button size="sm" variant="ghost" leading={<StopIcon aria-hidden width={12} height={12} />} onClick={onStop}>Stop drafting</Button>}
                </div>
              </div>
            </WorkingEdge>
          )}

          {state === "failed" && (
            <p className="flex items-start gap-1.5 text-small leading-4 text-danger-fg"><AlertIcon aria-hidden width={14} height={14} className="mt-px shrink-0" />{error ?? "The draft did not arrive. Your notes are unchanged."}</p>
          )}

          {(generate || (state === "failed" && onGenerate)) && (
            <div className="flex flex-wrap items-center gap-2">
              {state === "failed" && onGenerate ? <Button size="sm" variant="secondary" leading={<RegenerateIcon aria-hidden width={14} height={14} />} onClick={onGenerate}>Try drafting again</Button> : generate}
            </div>
          )}

          {imageCredits && imageCredits.length > 0 && (
            <ul aria-label="Images on this slide" className="space-y-1 border-t border-line pt-2">
              {imageCredits.map((c) => <li key={c} className="flex items-start gap-1.5 text-small leading-4 text-fg-muted"><ImageIcon aria-hidden width={12} height={12} className="mt-0.5 shrink-0" />{c}</li>)}
            </ul>
          )}
        </div>
      )}
    </section>
  );
}
```

## tattva/molecules/Splitter.tsx

```tsx
import { useRef, useState, type KeyboardEvent, type PointerEvent } from "react";
import { cn } from "../lib/cn";

export interface SplitterProps {
  /** horizontal draws a vertical bar between left and right panes. vertical draws a horizontal bar between top and bottom panes. */
  direction: "horizontal" | "vertical";
  /** Size of the first pane in px. */
  value: number;
  onChange: (px: number) => void;
  min: number;
  max: number;
  /** Accessible name, for example "Resize sidebar". */
  label: string;
  /** Called on double click and on Enter. Use it to collapse or restore the first pane. */
  onToggle?: () => void;
  /** Keyboard step in px. Shift multiplies it by 4. */
  step?: number;
  className?: string;
}

/**
 * Only the draggable bar. Your layout draws the two panes and sizes the first one from `value`.
 * The bar is 12px across (44px on touch screens, through an invisible larger hit area).
 */
export function Splitter({ direction, value, onChange, min, max, label, onToggle, step = 16, className }: SplitterProps) {
  const [dragging, setDragging] = useState(false);
  const start = useRef({ pos: 0, size: 0 });
  const horizontal = direction === "horizontal";
  const clamp = (n: number) => Math.round(Math.min(max, Math.max(min, n)));

  const down = (e: PointerEvent<HTMLDivElement>) => {
    if (e.button !== 0) return;
    e.currentTarget.setPointerCapture(e.pointerId);
    start.current = { pos: horizontal ? e.clientX : e.clientY, size: value };
    setDragging(true);
  };
  const move = (e: PointerEvent<HTMLDivElement>) => {
    if (!dragging) return;
    const delta = (horizontal ? e.clientX : e.clientY) - start.current.pos;
    const next = clamp(start.current.size + delta);
    if (next !== value) onChange(next);
  };
  const up = (e: PointerEvent<HTMLDivElement>) => {
    if (e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId);
    setDragging(false);
  };

  const key = (e: KeyboardEvent<HTMLDivElement>) => {
    const s = step * (e.shiftKey ? 4 : 1);
    const grow = horizontal ? "ArrowRight" : "ArrowDown";
    const shrink = horizontal ? "ArrowLeft" : "ArrowUp";
    let next: number | undefined;
    if (e.key === grow) next = value + s;
    else if (e.key === shrink) next = value - s;
    else if (e.key === "Home") next = min;
    else if (e.key === "End") next = max;
    else if (e.key === "Enter" && onToggle) { e.preventDefault(); onToggle(); return; }
    if (next === undefined) return;
    e.preventDefault();
    onChange(clamp(next));
  };

  return (
    <div role="separator" tabIndex={0} aria-label={label} aria-orientation={horizontal ? "vertical" : "horizontal"}
      aria-valuenow={clamp(value)} aria-valuemin={min} aria-valuemax={max}
      onPointerDown={down} onPointerMove={move} onPointerUp={up} onPointerCancel={up} onDoubleClick={onToggle} onKeyDown={key}
      className={cn(
        "group relative shrink-0 touch-none select-none",
        horizontal ? "h-full w-3 cursor-col-resize" : "h-3 w-full cursor-row-resize",
        className,
      )}>
      {/* Larger invisible target on touch screens. */}
      <span aria-hidden className={cn("absolute hidden pointer-coarse:block", horizontal ? "inset-y-0 -inset-x-4" : "inset-x-0 -inset-y-4")} />
      <span aria-hidden className={cn(
        "absolute transition-colors duration-[var(--dur-fast)] forced-colors:bg-[CanvasText]",
        horizontal ? cn("inset-y-0 left-1/2 -translate-x-1/2", dragging ? "w-0.5" : "w-px") : cn("inset-x-0 top-1/2 -translate-y-1/2", dragging ? "h-0.5" : "h-px"),
        dragging ? "bg-accent" : "bg-line group-hover:bg-line-strong group-focus-visible:bg-line-strong",
      )} />
    </div>
  );
}
```

## tattva/molecules/StatTile.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { Meter } from "../atoms/Meter";
import { AlertIcon } from "../lib/icons";

export type DeltaDirection = "up" | "down" | "flat";

export interface StatDelta {
  /** Signed text with the unit, for example "+4.2%" or "-120 ms". */
  text: string;
  /** Which way the number moved. Drives the arrow. */
  direction: DeltaDirection;
  /** The comparison period, for example "vs previous 7 days". */
  versus?: string;
}

export interface StatThreshold {
  /** Current value on the same scale as max. */
  value: number;
  /** Upper end of the scale. */
  max: number;
  /** Where the threshold tick sits on the scale. */
  at: number;
  /** Visible name for the tick, for example "Target 800 ms". */
  label: string;
}

export interface StatTileProps {
  /** Metric name in sentence case, without a trailing colon. */
  label: string;
  /** Formatted value, for example "1.24 s" or "$4.2K". */
  value: string;
  /** Set the value in the serif. */
  serif?: boolean;
  /** Change since the comparison period. */
  delta?: StatDelta;
  /** Which direction of change is an improvement. Use "down" for latency, cost and error rate. */
  goodDirection?: "up" | "down" | "neutral";
  /** A visible detail line under the value, such as an interval or a sample size. */
  detail?: string;
  /** Slot for a Sparkline. Pass it with decorative set, since the value and delta already say the numbers. */
  spark?: ReactNode;
  /** Draws a thin scale with a tick at the threshold. */
  threshold?: StatThreshold;
  /** A person has to act on this metric. Shows an amber chip with text. Leave off for ordinary good or bad movement. */
  needsAction?: boolean;
  /** Text of the amber chip. */
  actionText?: string;
  className?: string;
}

const Arrow = ({ d }: { d: DeltaDirection }) => (
  <svg width={14} height={14} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden>
    {d === "up" ? <path d="M12 19V5M5 12l7-7 7 7" /> : d === "down" ? <path d="M12 5v14M5 12l7 7 7-7" /> : <path d="M5 12h14" />}
  </svg>
);

/**
 * One headline number with its change. The delta carries an arrow and text, and a visible "better" or "worse" word,
 * so the meaning never rests on color. goodDirection decides which way counts as better, so a drop in latency reads as good.
 */
export function StatTile({ label, value, serif, delta, goodDirection = "up", detail, spark, threshold, needsAction, actionText = "Needs review", className }: StatTileProps) {
  const verdict = !delta || delta.direction === "flat" || goodDirection === "neutral" ? "none" : delta.direction === goodDirection ? "better" : "worse";
  const tone = verdict === "better" ? "text-success-fg" : verdict === "worse" ? "text-danger-fg" : "text-fg-muted";
  const dirWord = delta ? { up: "up", down: "down", flat: "unchanged" }[delta.direction] : "";
  return (
    <div role="group" aria-label={label} className={cn("min-w-0 rounded-card border border-line bg-surface p-4", className)}>
      <div className="flex items-start justify-between gap-2">
        <p className="text-small leading-5 text-fg-muted">{label}</p>
        {needsAction && <span className="inline-flex shrink-0 items-center gap-1 rounded-control bg-attention-soft px-2 py-0.5 text-micro leading-4 font-medium text-attention-fg"><AlertIcon width={12} height={12} />{actionText}</span>}
      </div>
      <div className="mt-1 flex items-end justify-between gap-3">
        <p className={cn("min-w-0 text-title-lg leading-9 font-medium tracking-snug text-fg [overflow-wrap:anywhere]", serif && "font-serif font-normal")}>{value}</p>
        {spark && <div className="shrink-0 pb-1">{spark}</div>}
      </div>
      {delta && (
        <p className="mt-1 flex flex-wrap items-center gap-x-1.5 text-small leading-5">
          <span className={cn("inline-flex items-center gap-1 font-medium", tone)}>
            <Arrow d={delta.direction} />
            <span className="sr-only">{dirWord}, </span>{delta.text}
            {verdict !== "none" && <span className="font-normal">{verdict}</span>}
          </span>
          {delta.versus && <span className="text-fg-subtle">{delta.versus}</span>}
        </p>
      )}
      {detail && <p className="mt-1 text-small leading-5 text-fg-muted">{detail}</p>}
      {threshold && (
        <div className="mt-3">
          <div className="relative">
            <Meter label={label} value={threshold.value} max={threshold.max} warnAt={needsAction ? threshold.at / threshold.max : undefined}
              valueText={`${value}. ${threshold.label}.`} />
            <span aria-hidden className="absolute -top-1 h-3.5 w-0.5 -translate-x-1/2 rounded-full bg-fg" style={{ left: `${Math.max(0, Math.min(100, (threshold.at / threshold.max) * 100))}%` }} />
          </div>
          <p className="mt-1.5 text-micro leading-4 text-fg-subtle">{threshold.label}</p>
        </div>
      )}
    </div>
  );
}
```

## tattva/molecules/StreamingReveal.tsx

```tsx
import { Fragment, useRef } from "react";
import { cn } from "../lib/cn";
import { useReducedMotion } from "../hooks/useReducedMotion";

export interface StreamingRevealProps {
  /** The text received so far. Pass the `text` from useStreamingText or your own buffer. */
  text: string;
  /** True while more text may arrive. The last partial word is held back until it ends, so words land whole. */
  streaming?: boolean;
  className?: string;
}

/**
 * Renders streamed text so each new word fades in once from a faint blur (--dur-base, --ease-arrive).
 * Words that have settled keep their element and never re-animate. New words append in flow, so nothing moves.
 * Under reduced motion the text appears instantly. Words present at first render do not animate.
 */
export function StreamingReveal({ text, streaming = false, className }: StreamingRevealProps) {
  const reduced = useReducedMotion();
  let tokens = text.split(/(\s+)/).filter((t) => t !== "");
  if (streaming && tokens.length > 0 && !/\s$/.test(text)) tokens = tokens.slice(0, -1);
  const settled = useRef<number | null>(null);
  if (settled.current === null) settled.current = tokens.length;
  if (tokens.length < settled.current) settled.current = 0;
  const initial = settled.current;

  if (reduced) return <span className={className}>{tokens.join("")}</span>;
  return (
    <span className={className}>
      {tokens.map((t, i) =>
        /^\s+$/.test(t) ? <Fragment key={i}>{t}</Fragment> : <span key={i} className={cn(i >= initial && "animate-word-in")}>{t}</span>,
      )}
    </span>
  );
}
```

## tattva/molecules/SuggestionCard.tsx

```tsx
import { useRef, type ReactNode } from "react";
import { keepFocus } from "../lib/focus";
import { AlertIcon, SearchIcon, SparkleIcon, UsersIcon } from "../lib/icons";
import { Badge, type Tone } from "../atoms/Badge";
import { Button } from "../atoms/Button";

export type SuggestionCategory = "correctness" | "clarity" | "engagement" | "tone";

const categories: Record<SuggestionCategory, { label: string; icon: ReactNode; tone: Tone }> = {
  correctness: { label: "Correctness", icon: <AlertIcon width={11} height={11} />, tone: "danger" },
  clarity: { label: "Clarity", icon: <SearchIcon width={11} height={11} />, tone: "info" },
  engagement: { label: "Engagement", icon: <SparkleIcon width={11} height={11} />, tone: "success" },
  tone: { label: "Tone", icon: <UsersIcon width={11} height={11} />, tone: "neutral" },
};

export interface SuggestionCardProps {
  category?: SuggestionCategory;
  /** Override the category text, for document-specific categories. */
  categoryLabel?: string;
  /** Override the category icon. */
  categoryIcon?: ReactNode;
  /** Text as written. Shown struck through. */
  original: string;
  /** Proposed replacement. Shown highlighted. Leave empty for a deletion. */
  suggestion: string;
  /** Short reason for the change. */
  explanation?: string;
  onAccept?: () => void;
  onDismiss?: () => void;
  acceptLabel?: string;
  dismissLabel?: string;
  /** Where keyboard focus goes after Accept or Dismiss. By default the next suggestion, else the previous one, else the text. */
  focusAfter?: () => HTMLElement | null;
  className?: string;
}

/** One writing suggestion with a category label, an original-versus-suggested diff, a reason, Accept and Dismiss. Category is always text plus an icon, never color alone. */
export function SuggestionCard({
  category = "correctness", categoryLabel, categoryIcon, original, suggestion, explanation, onAccept, onDismiss,
  acceptLabel = "Accept", dismissLabel = "Dismiss", focusAfter, className,
}: SuggestionCardProps) {
  const ref = useRef<HTMLElement>(null);
  const act = (f?: () => void) => () => { f?.(); keepFocus(ref.current, focusAfter); };
  const c = categories[category];
  const label = categoryLabel ?? c.label;
  return (
    <article ref={ref} aria-label={`${label} suggestion`} className={`w-full max-w-sm space-y-3 rounded-card border border-line bg-surface p-4 text-body leading-5 shadow-sm ${className ?? ""}`}>
      <Badge tone={c.tone}>{categoryIcon ?? c.icon}{label}</Badge>
      <p className="leading-relaxed text-fg">
        <span className="sr-only">Original: </span>
        <del className="text-fg-muted decoration-danger decoration-2">{original}</del>
        {" "}
        <span className="sr-only">Suggested: </span>
        {suggestion ? <ins className="rounded-sm bg-lime-soft px-0.5 text-fg no-underline">{suggestion}</ins> : <span className="text-fg-muted">(remove)</span>}
      </p>
      {explanation && <p className="text-small leading-4 text-fg-muted">{explanation}</p>}
      <div className="flex gap-2">
        <Button size="sm" variant="lime" onClick={act(onAccept)}>{acceptLabel}</Button>
        <Button size="sm" variant="ghost" onClick={act(onDismiss)}>{dismissLabel}</Button>
      </div>
    </article>
  );
}
```

## tattva/molecules/SuggestionChips.tsx

```tsx
export function SuggestionChips({ suggestions, onPick }: { suggestions: string[]; onPick: (s: string) => void }) {
  return (
    <ul className="flex flex-wrap gap-2" aria-label="Suggested prompts">
      {suggestions.map((s) => (
        <li key={s}>
          <button type="button" onClick={() => onPick(s)}
            className="rounded-control border border-line bg-surface px-3.5 py-1.5 text-compact text-fg-muted transition-colors hover:border-line-strong hover:bg-sunken hover:text-fg">
            {s}
          </button>
        </li>
      ))}
    </ul>
  );
}
```

## tattva/molecules/Tabs.tsx

```tsx
import { useEffect, useId, useRef, type KeyboardEvent, type ReactNode } from "react";
import { useScrollEdges } from "../hooks/useScrollEdges";
import { useIndicator } from "../hooks/useIndicator";
import { cn } from "../lib/cn";

export interface TabItem {
  id: string;
  label: ReactNode;
  /** A number shown after the label, such as unread items. */
  count?: number;
  disabled?: boolean;
  /** Content for this tab. Only the active tab's panel is rendered. */
  panel?: ReactNode;
}

export interface TabsProps {
  tabs: TabItem[];
  /** Id of the active tab. */
  value: string;
  onChange: (id: string) => void;
  /** Accessible name of the tab list. */
  label: string;
  variant?: "underline" | "pill";
  /** auto selects a tab when arrow keys reach it. manual only moves focus; Enter or Space selects. */
  activation?: "auto" | "manual";
  /** Tabs share the full width instead of sizing to their label. */
  fill?: boolean;
  className?: string;
}

/**
 * WAI-ARIA tabs. One tab stop (the active tab), arrow keys, Home and End move between tabs. On narrow screens the
 * list scrolls sideways with no visible scrollbar and keeps the active tab in view. Only the active panel is mounted.
 */
export function Tabs({ tabs, value, onChange, label, variant = "underline", activation = "auto", fill, className }: TabsProps) {
  const uid = useId();
  const list = useRef<HTMLDivElement>(null);
  const tabId = (id: string) => `${uid}-tab-${id}`;
  const panelId = (id: string) => `${uid}-panel-${id}`;
  const edges = useScrollEdges(list);
  const mark = useIndicator(list, '[role="tab"][aria-selected="true"]', value);
  const enabled = tabs.filter((t) => !t.disabled);
  const active = tabs.find((t) => t.id === value);
  // If value matches nothing, the first enabled tab takes the tab stop so the list stays reachable.
  const stop = active && !active.disabled ? active.id : enabled[0]?.id;

  useEffect(() => {
    const l = list.current;
    const el = l?.querySelector<HTMLElement>('[aria-selected="true"]');
    if (!l || !el) return;
    const a = l.getBoundingClientRect(); const b = el.getBoundingClientRect();
    if (b.left < a.left) l.scrollLeft += b.left - a.left;
    else if (b.right > a.right) l.scrollLeft += b.right - a.right;
  }, [value]);

  const onKey = (e: KeyboardEvent<HTMLDivElement>) => {
    if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key) || !enabled.length) return;
    const cur = enabled.findIndex((t) => tabId(t.id) === (document.activeElement as HTMLElement | null)?.id);
    const n = enabled.length;
    const j = e.key === "Home" ? 0 : e.key === "End" ? n - 1 : e.key === "ArrowRight" ? (cur + 1) % n : (cur < 0 ? 0 : (cur - 1 + n) % n);
    e.preventDefault();
    const next = enabled[j];
    document.getElementById(tabId(next.id))?.focus();
    if (activation === "auto") onChange(next.id);
  };

  return (
    <div className={className}>
      <div ref={list} role="tablist" aria-label={label} onKeyDown={onKey} style={edges}
        className={cn("relative flex overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", variant === "underline" ? "gap-1 border-b border-line sm:gap-4" : "gap-1")}>
        {mark && (
          <span aria-hidden style={variant === "underline" ? { ...mark, top: undefined, bottom: 0, height: 2 } : mark}
            className={cn("pointer-events-none forced-colors:hidden", variant === "underline" ? "rounded-full bg-fg" : "rounded-control bg-accent")} />
        )}
        {tabs.map((t) => {
          const on = t.id === value;
          return (
            <button key={t.id} type="button" role="tab" id={tabId(t.id)} aria-selected={on} disabled={t.disabled}
              aria-controls={on && t.panel !== undefined ? panelId(t.id) : undefined} tabIndex={t.id === stop ? 0 : -1}
              onClick={() => { if (!t.disabled) onChange(t.id); }}
              className={cn(
                "relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap text-body font-medium transition-colors duration-[var(--dur-fast)] focus-visible:outline-offset-[-2px] disabled:cursor-not-allowed disabled:text-disabled-fg pointer-coarse:h-11",
                fill && "flex-1",
                variant === "underline"
                  ? cn("h-10 border-b-2 px-3", on ? cn(mark ? "border-transparent" : "border-fg", "text-fg forced-colors:border-[Highlight] forced-colors:text-[Highlight]") : "border-transparent text-fg-muted hover:text-fg forced-colors:border-transparent")
                  : cn("h-9 rounded-control px-4", on ? cn(!mark && "bg-accent", "text-on-accent forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]") : "text-fg-muted hover:bg-hover hover:text-fg"),
              )}>
              {t.label}
              {t.count !== undefined && <span className="text-small leading-4 tabular-nums opacity-80">{t.count}</span>}
            </button>
          );
        })}
      </div>
      {active?.panel !== undefined && (
        <div key={active.id} role="tabpanel" id={panelId(active.id)} aria-labelledby={tabId(active.id)} tabIndex={0} className="animate-panel-in pt-4">{active.panel}</div>
      )}
    </div>
  );
}
```

## tattva/molecules/TakeCard.tsx

```tsx
import { useId, type CSSProperties, type KeyboardEvent, type MouseEvent, type Ref } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, BranchIcon, CheckIcon, HistoryIcon, InfoIcon, MoreIcon, ShieldIcon, SparkleIcon, XIcon } from "../lib/icons";
import { headingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { Meter } from "../atoms/Meter";
import { WorkingEdge } from "../atoms/WorkingEdge";
import { AILabel } from "./AILabel";
import { Menu, type MenuEntry } from "./Menu";

/* ---------- shared vocabulary for video takes (also used by TakeGrid and TakeVersions) ---------- */

export type TakeStatus = "queued" | "generating" | "finalising" | "done" | "failed" | "blocked" | "cancelled" | "rateLimited";
export type TakeRatio = "16:9" | "9:16" | "1:1" | "4:3" | "21:9";
/** A drawn stand-in frame, made only from colour tokens. Use it in previews and while you have no poster image. */
export type TakeScene = "harbour" | "dusk" | "forest" | "city" | "studio";
export type TakeChargeState = "estimated" | "charged" | "refunded" | "notCharged";

export interface TakeCharge {
  /** Credits (or the unit you pass). Code works it out; never let the model write it. */
  amount: number;
  /** estimated: before or during the run. charged: taken. refunded: taken, then given back. notCharged: never taken. */
  state: TakeChargeState;
  /** Plural unit. Defaults to "credits". */
  unit?: string;
}

export interface TakeBlockedReason {
  /** input: the prompt or a reference was stopped before generating. output: the finished video was stopped by the safety check. */
  where: "input" | "output";
  /** What the policy check said, in plain words. */
  message: string;
  /** One way to rephrase or change the request. */
  suggestion?: string;
}

export interface TakeProvenance {
  /** Model or tool that made the take. Defaults to `model`. */
  madeWith?: string;
  /** When it was made, as display text. */
  made?: string;
  /** What the file carries, one item each, such as "Invisible watermark" or "C2PA Content Credentials". */
  carries?: string[];
  /** What can remove the marks. Defaults to a plain warning that re-uploads can strip metadata. */
  caveat?: string;
}

export const TAKE_STATUS_WORD: Record<TakeStatus, string> = {
  queued: "Queued", generating: "Generating", finalising: "Finalising", done: "Ready", failed: "Failed",
  blocked: "Blocked by content policy", cancelled: "Cancelled", rateLimited: "Paused, too many requests",
};

/** True while a model is really working on the take. Drives the lime working edge and aria-busy. */
export const isTakeWorking = (s: TakeStatus) => s === "queued" || s === "generating" || s === "finalising";

const RATIO: Record<TakeRatio, string> = {
  "16:9": "aspect-video", "9:16": "aspect-[9/16]", "1:1": "aspect-square", "4:3": "aspect-[4/3]", "21:9": "aspect-[21/9]",
};
export const takeRatioClass = (r: TakeRatio) => RATIO[r];

/** "0:08" for the eye. */
export function formatClock(sec: number) {
  const s = Math.max(0, Math.round(sec));
  return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
}
/** "8 seconds" or "1 minute 4 seconds" for the ear. */
export function formatSpokenDuration(sec: number) {
  const s = Math.max(0, Math.round(sec));
  const m = Math.floor(s / 60);
  const r = s % 60;
  const part = (n: number, w: string) => `${n} ${w}${n === 1 ? "" : "s"}`;
  if (!m) return part(r, "second");
  return r ? `${part(m, "minute")} ${part(r, "second")}` : part(m, "minute");
}

/** "20 credits", "1 credit". */
export function formatCredits(amount: number, unit = "credits") {
  const n = new Intl.NumberFormat("en", { maximumFractionDigits: 2 }).format(amount);
  return `${n} ${amount === 1 && unit.endsWith("s") ? unit.slice(0, -1) : unit}`;
}

/** The charge in words. Every ending says whether credits were taken. */
export function chargeText(c: TakeCharge) {
  const amt = formatCredits(c.amount, c.unit);
  switch (c.state) {
    case "estimated": return `Estimated ${amt}`;
    case "charged": return `Charged ${amt}`;
    case "refunded": return `${amt} refunded`;
    case "notCharged": return "Not charged";
  }
}

/* ---------- drawn poster ---------- */

const mix = (a: string, pct: number, b: string) => `color-mix(in oklab, ${a} ${pct}%, ${b})`;

const SCENES: Record<TakeScene, { sky: [string, string]; sun: string; far: string; near: string; city?: boolean }> = {
  harbour: { sky: ["var(--chart-seq-1)", mix("var(--chart-seq-2)", 70, "var(--surface)")], sun: mix("var(--chart-2)", 70, "var(--surface)"), far: "var(--chart-seq-3)", near: "var(--chart-seq-5)" },
  dusk: { sky: [mix("var(--chart-5)", 45, "var(--surface)"), mix("var(--chart-2)", 50, "var(--surface)")], sun: mix("var(--chart-2)", 80, "var(--surface)"), far: mix("var(--chart-5)", 70, "var(--fg)"), near: mix("var(--chart-5)", 35, "var(--fg)") },
  forest: { sky: [mix("var(--chart-3)", 30, "var(--surface)"), mix("var(--chart-4)", 25, "var(--surface)")], sun: mix("var(--chart-4)", 35, "var(--surface)"), far: mix("var(--chart-4)", 80, "var(--surface)"), near: mix("var(--chart-4)", 55, "var(--fg)") },
  city: { sky: [mix("var(--chart-seq-2)", 45, "var(--surface)"), mix("var(--chart-seq-1)", 30, "var(--surface)")], sun: mix("var(--chart-2)", 55, "var(--surface)"), far: "var(--chart-seq-3)", near: "var(--chart-seq-5)", city: true },
  studio: { sky: ["var(--surface-sunken)", "var(--border)"], sun: mix("var(--chart-3)", 60, "var(--surface)"), far: "var(--border-strong)", near: "var(--fg-subtle)" },
};

export interface TakePosterProps {
  /** Poster image URL. Wins over `scene`. */
  src?: string;
  /** Drawn stand-in frame from colour tokens. Defaults to "harbour". */
  scene?: TakeScene;
  className?: string;
}

/** A still frame for a take: the poster image, or a small drawn scene in token colours. Always decorative; the card carries the words. */
export function TakePoster({ src, scene = "harbour", className }: TakePosterProps) {
  const id = useId();
  if (src) return <img src={src} alt="" className={cn("h-full w-full object-cover", className)} />;
  const s = SCENES[scene];
  const fill = (c: string): CSSProperties => ({ fill: c });
  return (
    <svg aria-hidden viewBox="0 0 160 90" preserveAspectRatio="xMidYMid slice" className={cn("block h-full w-full", className)}>
      <defs>
        <linearGradient id={`${id}sky`} x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" style={{ stopColor: s.sky[0] }} />
          <stop offset="1" style={{ stopColor: s.sky[1] }} />
        </linearGradient>
      </defs>
      <rect width="160" height="90" fill={`url(#${id}sky)`} />
      <circle cx="100" cy="32" r="11" style={fill(s.sun)} />
      {s.city ? (
        <g style={fill(s.far)}>
          <rect x="6" y="40" width="16" height="50" /><rect x="26" y="30" width="12" height="60" /><rect x="42" y="46" width="20" height="44" />
          <rect x="68" y="24" width="14" height="66" /><rect x="88" y="42" width="18" height="48" /><rect x="112" y="34" width="12" height="56" /><rect x="130" y="48" width="24" height="42" />
        </g>
      ) : (
        <path d="M0 60 Q30 44 60 56 T120 52 T160 50 V90 H0Z" style={fill(s.far)} />
      )}
      <path d="M0 74 Q40 62 80 72 T160 68 V90 H0Z" style={fill(s.near)} />
    </svg>
  );
}

/* ---------- card ---------- */

export interface TakeCardProps {
  id: string;
  /** Name of the take, such as "Take 2". It names the card for screen readers. */
  label: string;
  status: TakeStatus;
  /** The box keeps this ratio from the moment the request is sent, so nothing moves when the result lands. */
  ratio?: TakeRatio;
  /** Poster image URL, shown when done. */
  poster?: string;
  /** Drawn stand-in poster when there is no image. */
  scene?: TakeScene;
  /** Length in seconds. Shown as 0:08 and read as "8 seconds". */
  durationSec?: number;
  /** Model that made it, shown on the card. */
  model?: string;
  /** What was asked for. Shown under the title. It is not a description of the result. */
  prompt?: string;
  /** "draft" adds a Draft tag, so a low-resolution preview is never taken for the finished take. */
  quality?: "draft" | "final";
  /** Place in the queue while queued. */
  queuePosition?: number;
  /** Why the person is waiting, such as "Busy period". Never an upgrade pitch. */
  queueReason?: string;
  /** Plain time estimate such as "About 2 minutes". Only if the service gives one. */
  eta?: string;
  /** 0 to 100, only if the service really reports it. Omit for no bar. */
  percent?: number;
  /** The charge for this take and whether it was taken. */
  charge?: TakeCharge;
  /** Why a blocked take was stopped. Shown as a calm note, never a red error. */
  blockedReason?: TakeBlockedReason;
  /** What went wrong, shown when failed. */
  error?: string;
  /** When the rate limit lets this take start again, such as "Starts again in about a minute". */
  retryNote?: string;
  /** Says whether cancelling returns credits, such as "Cancel now and the 20 credits come back." Shown while working. */
  cancelNote?: string;
  /** Where this take came from, such as "Extended from take 2". */
  lineage?: string;
  /** What the file carries and what can strip it. Fills the AI label's explainer. */
  provenance?: TakeProvenance;
  /** Opens the take in your player or lightbox. Makes the poster a Play button when done. */
  onOpen?: () => void;
  onCancel?: () => void;
  /** Shown as Try again on failed, cancelled and rate-limited takes. */
  onRetry?: () => void;
  /** Shown as Edit prompt on blocked takes. */
  onEditPrompt?: () => void;
  /** Extra actions for a finished take (Extend, Remix, Download), in an overflow menu. */
  menu?: MenuEntry[];
  /** Turns on the select control. "multi" is a checkbox, "single" picks one take. */
  selectMode?: "single" | "multi";
  selected?: boolean;
  onSelect?: () => void;
  /** Speak status changes from this card. TakeGrid turns this off and speaks for all its cards at once. */
  announce?: boolean;
  /** Heading level of the take name. */
  headingLevel?: HeadingLevel;
  /** Makes the whole card a focus stop. TakeGrid sets this for its roving focus. */
  tabIndex?: number;
  onKeyDown?: (e: KeyboardEvent<HTMLElement>) => void;
  /** Extra id for aria-describedby, such as a keyboard hint. */
  describedBy?: string;
  /** Hides the prompt and the lineage line, for small tiles. The name, status and charge stay. */
  compact?: boolean;
  ref?: Ref<HTMLElement>;
  className?: string;
}

const STATUS_ICON: Record<TakeStatus, typeof CheckIcon> = {
  queued: HistoryIcon, generating: SparkleIcon, finalising: SparkleIcon, done: CheckIcon, failed: AlertIcon,
  blocked: ShieldIcon, cancelled: XIcon, rateLimited: HistoryIcon,
};

/** The sentence spoken for a status change. No percentages. */
export function takeAnnouncement(label: string, status: TakeStatus) {
  return `${label}: ${TAKE_STATUS_WORD[status].toLowerCase()}`;
}

/**
 * One generated video take, from the queue to the finished clip. The box holds its ratio from the start, a calm lime
 * edge shows only while a model is working, and every ending (failed, blocked, cancelled, rate limited) says in words
 * whether credits were taken. Status is always a word with an icon, never colour alone.
 */
export function TakeCard({
  id, label, status, ratio = "16:9", poster, scene, durationSec, model, prompt, quality, queuePosition, queueReason, eta, percent,
  charge, blockedReason, error, retryNote, cancelNote, lineage, provenance, onOpen, onCancel, onRetry, onEditPrompt, menu,
  selectMode, selected = false, onSelect, announce = true, headingLevel = 3, tabIndex, onKeyDown, describedBy, compact = false, ref, className,
}: TakeCardProps) {
  const uid = useId();
  const H = headingTag(headingLevel);
  const working = isTakeWorking(status);
  const done = status === "done";
  const Icon = STATUS_ICON[status];
  const word = TAKE_STATUS_WORD[status];
  const pct = percent === undefined ? undefined : Math.round(Math.max(0, Math.min(100, percent)));
  const spokenLength = durationSec !== undefined ? formatSpokenDuration(durationSec) : undefined;
  const madeWith = provenance?.madeWith ?? model;
  const stop = (e: MouseEvent) => e.stopPropagation();

  return (
    <article
      ref={ref}
      data-take-id={id}
      aria-labelledby={`${uid}t`}
      aria-describedby={`${uid}s ${uid}c${describedBy ? ` ${describedBy}` : ""}`}
      aria-busy={working || undefined}
      tabIndex={tabIndex}
      onKeyDown={onKeyDown}
      className={cn("group/take min-w-0 rounded-card", className)}
    >
      <WorkingEdge active={working} label="" className="h-full">
        <div className={cn(
          "flex h-full flex-col rounded-card border bg-surface transition-[border-color,box-shadow] duration-(--dur-fast) ease-(--ease-out)",
          selected ? "border-fg shadow-md" : "border-line",
        )}>
          {/* Media box: holds the ratio in every state. */}
          <div className={cn("relative rounded-t-card bg-sunken", RATIO[ratio])}>
            {done ? (
              <>
                <div className="absolute inset-0 overflow-hidden rounded-t-card motion-safe:animate-[fade_var(--dur-base)_var(--ease-out)_both]"><TakePoster src={poster} scene={scene} /></div>
                {onOpen ? (
                  <button type="button" onClick={onOpen} aria-label={`Play ${label}${spokenLength ? `, ${spokenLength}` : ""}`}
                    className="group/play absolute inset-0 flex cursor-pointer items-center justify-center rounded-t-card">
                    <span aria-hidden className="flex size-11 items-center justify-center rounded-full bg-overlay text-inverse-fg shadow-md transition-transform duration-(--dur-fast) ease-(--ease-out) motion-safe:group-hover/play:scale-105">
                      <svg viewBox="0 0 24 24" className="ml-0.5 size-5" fill="currentColor"><path d="M8 5.5v13l10.5-6.5z" /></svg>
                    </span>
                  </button>
                ) : null}
                {durationSec !== undefined && (
                  <span className="pointer-events-none absolute right-2 bottom-2 rounded-control bg-overlay px-1.5 py-0.5 font-mono text-micro leading-4 text-inverse-fg tabular-nums" aria-hidden>{formatClock(durationSec)}</span>
                )}
                <span className="absolute bottom-2 left-2">
                  <AILabel level="message" size={16} text="AI" popoverLabel={`About ${label}`}
                    overview={`This video was generated by AI${madeWith ? ` with ${madeWith}` : ""}${provenance?.made ? `, ${provenance.made}` : ""}.`}
                    details={provenance?.carries?.length ? <><p>The file carries:</p><ul className="mt-1 list-disc pl-4">{provenance.carries.map((c) => <li key={c}>{c}</li>)}</ul></> : undefined}
                    resources={<p>{provenance?.caveat ?? "These marks can be lost when the file is edited elsewhere or re-uploaded, so a missing label does not prove a video is real."}</p>} />
                </span>
              </>
            ) : (
              <div className="absolute inset-0 flex flex-col items-center justify-center gap-1.5 p-3 text-center">
                <Icon width={18} height={18} aria-hidden className={cn(status === "failed" ? "text-danger-fg" : "text-fg-muted")} />
                <p key={status} className={cn("text-small font-medium motion-safe:animate-fade", status === "failed" ? "text-danger-fg" : "text-fg")} aria-hidden>{word}</p>
                {status === "queued" && queuePosition !== undefined && <p className="text-small leading-4 text-fg-muted" aria-hidden>Position {queuePosition} in the queue</p>}
                {status === "rateLimited" && retryNote && <p className="text-small leading-4 text-fg-muted" aria-hidden>{retryNote}</p>}
                {status === "generating" && pct !== undefined && (
                  <Meter label={`${label} progress`} value={pct} valueText={`${pct}%`} className="mt-1 w-2/3 max-w-40" />
                )}
              </div>
            )}
            {quality === "draft" && (
              <span className="pointer-events-none absolute top-2 left-2"><Badge className="bg-glass">Draft</Badge></span>
            )}
            {selectMode && onSelect && (
              <button type="button" onClick={(e) => { stop(e); onSelect(); }} tabIndex={tabIndex === undefined ? undefined : -1}
                {...(selectMode === "multi" ? { role: "checkbox", "aria-checked": selected } : { "aria-pressed": selected })}
                aria-label={`${selectMode === "multi" ? "Select" : "Pick"} ${label}`}
                className={cn(
                  "absolute top-2 right-2 flex size-7 cursor-pointer items-center justify-center rounded-full border shadow-sm",
                  selected ? "border-transparent bg-inverse text-inverse-fg" : "border-line-strong bg-glass text-transparent hover:text-fg-subtle",
                )}>
                <CheckIcon width={14} height={14} />
              </button>
            )}
          </div>

          {/* Words: name, status, meta, notes, charge, actions. */}
          <div className="flex flex-1 flex-col gap-2 p-3">
            <div className="flex min-w-0 flex-wrap items-start justify-between gap-x-2 gap-y-0.5">
              <H id={`${uid}t`} className="min-w-0 truncate text-compact font-semibold text-fg">{label}</H>
              <p id={`${uid}s`} className={cn("flex shrink-0 items-center gap-1 text-small leading-4 font-medium", status === "failed" ? "text-danger-fg" : done ? "text-fg" : "text-fg-muted")}>
                <Icon width={12} height={12} aria-hidden />
                <span key={status} className="motion-safe:animate-fade">{word}{selected ? <span className="sr-only">, selected</span> : null}</span>
              </p>
            </div>

            <p className="flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-small leading-4 text-fg-muted">
              {durationSec !== undefined && <><span aria-hidden className="font-mono tabular-nums">{formatClock(durationSec)}</span><span className="sr-only">{spokenLength}</span></>}
              {durationSec !== undefined && (model || ratio) && <span aria-hidden>·</span>}
              {model && <span>{model}</span>}
              {model && <span aria-hidden>·</span>}
              <span><span className="sr-only">Ratio </span>{ratio}</span>
              {quality === "draft" && <><span aria-hidden>·</span><span>Draft quality</span></>}
            </p>

            {prompt && !compact && <p className="line-clamp-2 text-small text-fg-muted"><span className="sr-only">Prompt: </span>{prompt}</p>}

            {lineage && !compact && (
              <p className="flex items-center gap-1 text-small leading-4 text-fg-muted"><BranchIcon width={12} height={12} aria-hidden />{lineage}</p>
            )}

            {status === "queued" && (queueReason || eta) && (
              <p className="text-small leading-4 text-fg-muted">{[queueReason, eta].filter(Boolean).join(". ")}</p>
            )}
            {(status === "generating" || status === "finalising") && eta && <p className="text-small leading-4 text-fg-muted">{eta}</p>}

            {status === "blocked" && (
              <div className="rounded-card border border-line bg-sunken p-2.5 text-small">
                <p className="flex items-center gap-1.5 font-medium text-fg"><ShieldIcon width={14} height={14} aria-hidden />
                  {blockedReason?.where === "input" ? "Your request was stopped before generating" : "The finished video was stopped by the safety check"}
                </p>
                {blockedReason?.where === "output" && <p className="mt-1 text-fg-muted">The check looked at what the model made, not at you.</p>}
                {blockedReason?.message && <p className="mt-1 text-fg-muted">{blockedReason.message}</p>}
                {blockedReason?.suggestion && <p className="mt-1 text-fg-muted"><span className="font-medium text-fg">Try: </span>{blockedReason.suggestion}</p>}
              </div>
            )}
            {status === "failed" && error && (
              <p className="flex items-start gap-1.5 text-small text-fg-muted"><AlertIcon width={14} height={14} aria-hidden className="mt-0.5 shrink-0 text-danger-fg" />{error}</p>
            )}
            {status === "rateLimited" && (
              <p className="flex items-start gap-1.5 text-small text-fg-muted"><InfoIcon width={14} height={14} aria-hidden className="mt-0.5 shrink-0" />
                Too many takes were requested at once, so this one is waiting. {retryNote ?? "It starts again on its own."}
              </p>
            )}

            <div className="mt-auto flex flex-wrap items-center justify-between gap-2 pt-1">
              <p id={`${uid}c`} className="flex items-center gap-1 text-small leading-4 text-fg-muted">
                {charge ? <>
                  {charge.state === "refunded" || charge.state === "notCharged" ? <CheckIcon width={12} height={12} aria-hidden /> : null}
                  <span className={cn(charge.state !== "notCharged" && "font-mono tabular-nums")}>{chargeText(charge)}</span>
                </> : null}
              </p>
              <div className="flex flex-wrap items-center gap-1.5">
                {working && onCancel && <Button size="sm" variant="secondary" onClick={onCancel} aria-label={`Cancel ${label}`}>Cancel</Button>}
                {(status === "failed" || status === "cancelled" || status === "rateLimited") && onRetry && (
                  <Button size="sm" variant="secondary" onClick={onRetry} aria-label={`Try ${label} again`}>Try again</Button>
                )}
                {status === "blocked" && onEditPrompt && <Button size="sm" variant="secondary" onClick={onEditPrompt}>Edit prompt</Button>}
                {done && menu && menu.length > 0 && (
                  <Menu portal align="end" triggerLabel={`More actions for ${label}`} items={menu}
                    trigger={<MoreIcon width={16} height={16} />} />
                )}
              </div>
            </div>
            {working && cancelNote && onCancel && <p className="text-small leading-4 text-fg-subtle">{cancelNote}</p>}
          </div>
        </div>
      </WorkingEdge>
      {announce && <p role="status" aria-live="polite" className="sr-only">{takeAnnouncement(label, status)}</p>}
    </article>
  );
}
```

## tattva/molecules/TakeVersions.tsx

```tsx
import { useId, useRef, useState, useSyncExternalStore, type KeyboardEvent } from "react";
import { cn } from "../lib/cn";
import { BranchIcon, SparkleIcon, UsersIcon } from "../lib/icons";
import { headingTag, useFocusAfter, type HeadingLevel } from "../lib/useFocusAfter";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { TakePoster, TAKE_STATUS_WORD, formatClock, formatSpokenDuration, type TakeScene, type TakeStatus } from "./TakeCard";
import { VersionPager } from "./VersionPager";

export type TakeOp = "generated" | "extended" | "edited" | "remixed" | "upscaled" | "recut" | "uploaded";

export interface TakeVersion {
  id: string;
  /** Overrides the automatic name "v1", "v2"... */
  name?: string;
  /** What made this version. */
  op: TakeOp;
  /** Id of the version it came from. */
  from?: string;
  /** The prompt behind this version. */
  prompt?: string;
  /** Seconds of the clip that were edited or re-cut, as [start, end]. */
  segment?: [number, number];
  /** Short extra fact, such as "4K" for an upscale or "sunset lighting" for an edit. */
  detail?: string;
  durationSec: number;
  author: "ai" | "person";
  /** Person name, or the model name for AI. */
  authorName?: string;
  /** Display time such as "5 min ago". */
  time: string;
  status: TakeStatus;
  poster?: string;
  scene?: TakeScene;
}

export interface TakeVersionsProps {
  /** Oldest first. Versions are numbered from the start, so the first is v1. The list shows the newest at the top. */
  versions: TakeVersion[];
  currentId: string;
  /** Called when a version is shown for a look, without changing anything. Showing the current one ends the preview. */
  onPreview: (id: string) => void;
  /** Makes a version the current one. Nothing is deleted, and the notice that follows offers Undo. */
  onMakeCurrent: (id: string) => void;
  /** Starts a new line of edits from this version. */
  onBranch?: (id: string) => void;
  /** Controlled preview id. Omit to let the part keep it. */
  previewId?: string;
  /** list: every version in a column. pager: one at a time with Previous and Next. auto: pager on narrow screens. */
  layout?: "auto" | "list" | "pager";
  title?: string;
  headingLevel?: HeadingLevel;
  className?: string;
}

const NARROW = "(max-width: 639px)";
const subscribe = (cb: () => void) => {
  if (typeof window === "undefined" || !window.matchMedia) return () => {};
  const mq = window.matchMedia(NARROW);
  mq.addEventListener("change", cb);
  return () => mq.removeEventListener("change", cb);
};
const useNarrow = () => useSyncExternalStore(subscribe, () => !!window.matchMedia?.(NARROW).matches, () => false);

const OP_WORD: Record<TakeOp, string> = {
  generated: "Generated", extended: "Extended", edited: "Edited", remixed: "Remixed", upscaled: "Upscaled", recut: "Re-cut", uploaded: "Uploaded",
};

/** The lineage line, such as "Extended +4 s from v2" or "Edited 0:02 to 0:06 from v2: sunset lighting". */
export function takeOpLabel(v: TakeVersion, all: TakeVersion[]) {
  const nameOf = (x: TakeVersion) => x.name ?? `v${all.indexOf(x) + 1}`;
  const src = v.from ? all.find((x) => x.id === v.from) : undefined;
  const from = src ? ` from ${nameOf(src)}` : "";
  const seg = v.segment ? ` ${formatClock(v.segment[0])} to ${formatClock(v.segment[1])}` : "";
  switch (v.op) {
    case "extended": {
      const added = src ? Math.round(v.durationSec - src.durationSec) : 0;
      return `Extended${added > 0 ? ` +${added} s` : ""}${from}`;
    }
    case "edited": return `Edited${seg}${from}${v.detail ? `: ${v.detail}` : ""}`;
    case "recut": return `Re-cut${seg}${from}`;
    case "upscaled": return `Upscaled${v.detail ? ` to ${v.detail}` : ""}${from}`;
    case "remixed": return `Remixed${from}${v.detail ? `: ${v.detail}` : ""}`;
    default: return `${OP_WORD[v.op]}${v.detail ? `: ${v.detail}` : ""}`;
  }
}

/** Where in the clip the change landed, as start and end fractions. Extends mark the added tail. */
function markOf(v: TakeVersion, all: TakeVersion[]): [number, number] | undefined {
  if (!v.durationSec) return undefined;
  if (v.segment && (v.op === "edited" || v.op === "recut")) return [v.segment[0] / v.durationSec, v.segment[1] / v.durationSec];
  if (v.op === "extended") {
    const src = all.find((x) => x.id === v.from);
    if (src && src.durationSec < v.durationSec) return [src.durationSec / v.durationSec, 1];
  }
  return undefined;
}

/** A tiny strip of frames with the changed part outlined. Drawn for the eye; the words carry the same fact. */
function Filmstrip({ mark }: { mark: [number, number] }) {
  return (
    <span aria-hidden className="relative mt-1.5 flex h-2.5 w-full max-w-48 gap-px overflow-hidden rounded-control">
      {Array.from({ length: 8 }, (_, i) => <span key={i} className="h-full flex-1 bg-line" />)}
      <span className="absolute inset-y-0 rounded-control border-2 border-fg bg-selected" style={{ left: `${mark[0] * 100}%`, width: `${Math.max(4, (mark[1] - mark[0]) * 100)}%` }} />
    </span>
  );
}

/**
 * The versions of one video take: every extend, edit, remix and upscale, each saying what it came from. Making a
 * version current never deletes anything, and an Undo follows. Up and Down move between versions.
 */
export function TakeVersions({
  versions, currentId, onPreview, onMakeCurrent, onBranch, previewId: controlledPreview, layout = "auto", title = "Versions", headingLevel = 3, className,
}: TakeVersionsProps) {
  const narrow = useNarrow();
  const pager = layout === "pager" || (layout === "auto" && narrow);
  const H = headingTag(headingLevel);
  const uid = useId();
  const listRef = useRef<HTMLOListElement>(null);
  const [innerPreview, setInnerPreview] = useState<string | undefined>(undefined);
  const previewId = controlledPreview ?? innerPreview;
  const [focusId, setFocusId] = useState<string | undefined>(undefined);
  const [undo, setUndo] = useState<{ from: string; to: string } | null>(null);
  const [notice, setNotice] = useState("");
  const [changes, setChanges] = useState(0);
  const [pagerId, setPagerId] = useState<string | undefined>(undefined);

  const nameOf = (v: TakeVersion) => v.name ?? `v${versions.indexOf(v) + 1}`;
  const byId = (id?: string) => versions.find((v) => v.id === id);
  const newestFirst = [...versions].reverse();
  const stopId = byId(focusId)?.id ?? currentId;
  const armFocus = useFocusAfter(changes, () => listRef.current?.querySelector<HTMLElement>(`[data-version="${currentId}"]`));

  const preview = (id: string) => {
    const next = id === currentId ? undefined : id;
    if (controlledPreview === undefined) setInnerPreview(next);
    onPreview(id);
  };
  const makeCurrent = (id: string) => {
    const v = byId(id);
    if (!v) return;
    armFocus();
    setUndo({ from: currentId, to: id });
    setChanges((n) => n + 1);
    setNotice(`${nameOf(v)} is now the current version. Nothing was deleted.`);
    if (controlledPreview === undefined) setInnerPreview(undefined);
    onMakeCurrent(id);
  };
  const undoLast = () => {
    if (!undo) return;
    const back = byId(undo.from);
    armFocus();
    onMakeCurrent(undo.from);
    setUndo(null);
    setChanges((n) => n + 1);
    setNotice(back ? `Back to ${nameOf(back)}.` : "Undone.");
  };

  const onListKey = (e: KeyboardEvent<HTMLOListElement>) => {
    if (e.altKey || e.ctrlKey || e.metaKey) return;
    const all = Array.from(listRef.current?.querySelectorAll<HTMLElement>("[data-version]") ?? []);
    const from = all.indexOf(document.activeElement as HTMLElement);
    if (from < 0) return;
    let to = from;
    if (e.key === "ArrowDown") to = Math.min(from + 1, all.length - 1);
    else if (e.key === "ArrowUp") to = Math.max(from - 1, 0);
    else if (e.key === "Home") to = 0;
    else if (e.key === "End") to = all.length - 1;
    else return;
    e.preventDefault();
    all[to]?.focus();
  };

  const row = (v: TakeVersion, roving: boolean) => {
    const name = nameOf(v);
    const isCurrent = v.id === currentId;
    const isPreview = v.id === previewId && !isCurrent;
    const op = takeOpLabel(v, versions);
    const mark = markOf(v, versions);
    const ready = v.status === "done";
    return (
      <div className={cn("flex flex-wrap items-start gap-x-3 gap-y-2 rounded-card p-2", isPreview ? "bg-sunken" : isCurrent ? "bg-surface" : "")}>
        <button type="button" data-version={v.id} tabIndex={roving ? (v.id === stopId ? 0 : -1) : undefined}
          aria-pressed={isPreview} aria-label={`Preview ${name}. ${op}${isCurrent ? ". Current version" : ""}`}
          onClick={() => preview(v.id)} onFocus={() => setFocusId(v.id)}
          className="flex min-w-0 flex-1 basis-56 cursor-pointer items-start gap-3 rounded-control text-left">
          <span className={cn("relative aspect-video w-20 shrink-0 overflow-hidden rounded-card border", isCurrent ? "border-fg" : "border-line")}>
            {ready ? <TakePoster src={v.poster} scene={v.scene} /> : <span className="block h-full w-full bg-sunken" />}
            <span className="absolute right-1 bottom-1 rounded-control bg-overlay px-1 font-mono text-nano leading-4 text-inverse-fg tabular-nums">{formatClock(v.durationSec)}</span>
          </span>
          <span className="min-w-0 flex-1">
            <span className="flex flex-wrap items-center gap-1.5 text-compact font-semibold text-fg">
              {name}
              {isCurrent && <Badge>Current</Badge>}
              {isPreview && <Badge>Previewing</Badge>}
              {!ready && <Badge>{TAKE_STATUS_WORD[v.status]}</Badge>}
            </span>
            <span className="mt-0.5 block text-small text-fg">{op}</span>
            {v.prompt && <span className="mt-0.5 line-clamp-2 block text-small leading-4 text-fg-muted">“{v.prompt}”</span>}
            <span className="mt-0.5 flex flex-wrap items-center gap-1.5 text-small leading-4 text-fg-muted">
              <span className="inline-flex items-center gap-1">
                {v.author === "ai" ? <SparkleIcon width={11} height={11} aria-hidden /> : <UsersIcon width={11} height={11} aria-hidden />}
                {v.author === "ai" ? "AI" : "Person"}{v.authorName ? `, ${v.authorName}` : ""}
              </span>
              <span aria-hidden>·</span><span>{v.time}</span>
              <span className="sr-only">, {formatSpokenDuration(v.durationSec)}</span>
            </span>
            {mark && <Filmstrip mark={mark} />}
          </span>
        </button>
        <span className="flex shrink-0 flex-wrap gap-1.5 max-sm:basis-full max-sm:pl-23">
          {!isCurrent && ready && <Button size="sm" variant="secondary" onClick={() => makeCurrent(v.id)} aria-label={`Make ${name} current`}>Make current</Button>}
          {onBranch && ready && <Button size="sm" variant="ghost" leading={<BranchIcon width={14} height={14} />} onClick={() => onBranch(v.id)} aria-label={`Branch from ${name}`}>Branch</Button>}
        </span>
      </div>
    );
  };

  const shownId = byId(pagerId)?.id ?? previewId ?? currentId;
  const shownIndex = Math.max(0, versions.findIndex((v) => v.id === shownId));

  return (
    <section aria-labelledby={`${uid}h`} className={cn("space-y-2 rounded-card border border-line bg-surface p-3", className)}>
      <div className="flex flex-wrap items-baseline justify-between gap-2 px-1">
        <H id={`${uid}h`} className="text-body font-semibold text-fg">{title}</H>
        <p className="text-small leading-4 text-fg-muted">{versions.length} {versions.length === 1 ? "version" : "versions"}. Nothing is ever overwritten.</p>
      </div>

      <div className={cn("flex flex-wrap items-center gap-2 rounded-card text-small", notice ? "border border-line bg-sunken px-3 py-2" : "sr-only")}>
        <p role="status" aria-live="polite" className="min-w-0 flex-1">{notice}</p>
        {undo && <Button size="sm" variant="secondary" onClick={undoLast}>Undo</Button>}
      </div>

      {pager ? (
        <div className="space-y-2">
          <VersionPager total={versions.length} current={shownIndex + 1} label={`${title} pager`}
            announce={(c, t) => `${nameOf(versions[c - 1])}, ${c} of ${t}`}
            onChange={(n) => { const v = versions[n - 1]; setPagerId(v.id); preview(v.id); }} />
          {row(versions[shownIndex], false)}
        </div>
      ) : (
        <ol ref={listRef} onKeyDown={onListKey} aria-label={`${title}, newest first`} className="m-0 list-none space-y-1 p-0">
          {newestFirst.map((v) => (
            <li key={v.id} aria-current={v.id === currentId ? "true" : undefined}>{row(v, true)}</li>
          ))}
        </ol>
      )}
      {!pager && <p className="px-1 text-small leading-4 text-fg-subtle">Up and Down move between versions. Preview does not change the current version.</p>}
    </section>
  );
}
```

## tattva/molecules/TakeoverBar.tsx

```tsx
import { useRef, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { useFocusAfter } from "../lib/useFocusAfter";
import { EyeIcon } from "../lib/icons";
import { AgentStatusIcon } from "../atoms/AgentStatusIcon";
import { Button } from "../atoms/Button";

export type TakeoverState = "you-control" | "agent-working" | "supervise";

export interface TakeoverBarLabels {
  youControl?: string;
  youControlHint?: string;
  agentWorking?: string;
  supervise?: string;
  /** Receives the step the person is asked to watch. */
  superviseHint?: (step: string) => ReactNode;
  takeOver?: string;
  resume?: string;
  handBack?: string;
}

export interface TakeoverBarProps {
  state: TakeoverState;
  /** The step that needs watching. Shown in the supervise state. */
  step?: string;
  /** Person takes control from the agent. Shown in the working and supervise states. */
  onTakeOver?: () => void;
  /** Agent continues from where it paused. */
  onResume?: () => void;
  /** Person gives control back, and the agent continues with what the person changed. */
  onHandBack?: () => void;
  labels?: TakeoverBarLabels;
  className?: string;
}

/**
 * Full-width bar that says who is in control right now. The status sentence sits in one polite
 * live region so a screen reader says it once when the state changes. Only the supervise state uses
 * amber, because only then a person must act.
 */
export function TakeoverBar({ state, step, onTakeOver, onResume, onHandBack, labels, className }: TakeoverBarProps) {
  const l = {
    youControl: "You have control", youControlHint: "The agent is paused and is not doing anything.",
    agentWorking: "The agent is working", supervise: "This step needs you to watch",
    superviseHint: (s: string) => <>The agent will wait while you watch: {s}</>,
    takeOver: "Take over", resume: "Resume", handBack: "Hand back", ...labels,
  };
  const title = state === "you-control" ? l.youControl : state === "agent-working" ? l.agentWorking : l.supervise;
  const actions = useRef<HTMLDivElement>(null);
  // Every button is swapped when control changes hands. Focus moves to the first button of the new state.
  const arm = useFocusAfter(state, () => actions.current?.querySelector("button"));
  const act = (f?: () => void) => () => { arm(); f?.(); };
  const hint = state === "you-control" ? l.youControlHint : state === "supervise" ? (step ? l.superviseHint(step) : null) : null;
  return (
    <div className={cn("flex flex-wrap items-center gap-x-4 gap-y-2 rounded-card border px-4 py-3",
      state === "supervise" ? "border-transparent bg-attention-soft text-attention-fg" : "border-line bg-surface text-fg", className)}>
      <div className="flex min-w-0 flex-1 basis-56 items-start gap-2.5">
        <span className="mt-0.5 shrink-0" aria-hidden>
          {state === "agent-working" ? <AgentStatusIcon status="working" /> : state === "you-control" ? <AgentStatusIcon status="idle" /> : <EyeIcon />}
        </span>
        <div role="status" aria-live="polite" className="min-w-0">
          <p className="text-body leading-5 font-medium">{title}</p>
          {hint && <p className={cn("text-small leading-4", state === "supervise" ? "text-attention-fg" : "text-fg-muted")}>{hint}</p>}
        </div>
      </div>
      <div ref={actions} className="ml-auto flex flex-wrap justify-end gap-2">
        {state === "agent-working" && <Button variant="secondary" onClick={act(onTakeOver)}>{l.takeOver}</Button>}
        {state === "supervise" && (<><Button variant="secondary" onClick={act(onTakeOver)}>{l.takeOver}</Button><Button onClick={act(onResume)}>{l.resume}</Button></>)}
        {state === "you-control" && (<><Button variant="secondary" onClick={act(onHandBack)}>{l.handBack}</Button><Button onClick={act(onResume)}>{l.resume}</Button></>)}
      </div>
    </div>
  );
}
```

## tattva/molecules/ThinkingBlock.tsx

```tsx
import type { ReactNode } from "react";
import { ShimmerText } from "../atoms/ShimmerText";
import { Collapsible } from "./Collapsible";

/** Collapsed-by-default reasoning. Shimmers while active, shows duration when done. */
export function ThinkingBlock({ active, seconds, children }: { active?: boolean; seconds?: number; children: ReactNode }) {
  return (
    <Collapsible
      defaultOpen={false}
      header={active ? <ShimmerText>Thinking…</ShimmerText> : <span className="text-body leading-5 text-fg-muted">Thought for {seconds ?? 0}s</span>}
    >
      <div className="mt-2 ml-2 border-l-2 border-line pl-4 text-body leading-relaxed text-fg-muted">{children}</div>
    </Collapsible>
  );
}
```

## tattva/molecules/TickerTape.tsx

```tsx
import { useEffect, useRef, useState } from "react";
import type { Ref } from "react";
import { cn } from "../lib/cn";
import { useReducedMotion } from "../hooks/useReducedMotion";
import { PriceChange } from "../atoms/PriceChange";

export interface TickerItem {
  symbol: string;
  price: number;
  /** Absolute change. */
  change: number;
  /** Change in percent. */
  percent: number;
}

export interface TickerTapeProps {
  items: TickerItem[];
  /** Accessible name of the strip, such as "Market ticker". */
  label: string;
  /** Scroll speed in pixels per second. */
  speed?: number;
  /** Controlled pause state. Leave out to let the strip manage it. */
  paused?: boolean;
  onPausedChange?: (paused: boolean) => void;
  className?: string;
}

function plain(n: number): string {
  if (!Number.isFinite(n)) return "—";
  return new Intl.NumberFormat("en-IN", { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(n);
}

function Row({ items, hidden, rowRef }: { items: TickerItem[]; hidden?: boolean; rowRef?: Ref<HTMLUListElement> }) {
  return (
    <ul ref={rowRef} aria-hidden={hidden || undefined} inert={hidden} className="m-0 flex shrink-0 list-none items-center p-0">
      {items.map((it) => (
        <li key={it.symbol} className="flex items-baseline gap-2 border-r border-line px-4 py-2 text-body leading-5 whitespace-nowrap">
          <span className="font-medium text-fg">{it.symbol}</span>
          <span className="font-mono tabular-nums text-fg">{plain(it.price)}</span>
          <PriceChange change={it.change} percent={it.percent} format="both" className="text-small leading-4" />
        </li>
      ))}
    </ul>
  );
}

function PauseIcon({ paused }: { paused: boolean }) {
  return (
    <svg aria-hidden viewBox="0 0 16 16" className="size-4" fill="currentColor">
      {paused ? <path d="M5 3l8 5-8 5z" /> : <path d="M4.5 3h2.5v10H4.5zM9 3h2.5v10H9z" />}
    </svg>
  );
}

/** A looping strip of prices. It stops when paused, hovered or focused, and does not move at all with reduced motion. */
export function TickerTape({ items, label, speed = 40, paused, onPausedChange, className }: TickerTapeProps) {
  const reduced = useReducedMotion();
  const [inner, setInner] = useState(false);
  const isControlled = paused !== undefined;
  const pausedNow = isControlled ? paused : inner;
  const [hover, setHover] = useState(false);
  const [focus, setFocus] = useState(false);
  const trackRef = useRef<HTMLDivElement>(null);
  const copyRef = useRef<HTMLUListElement>(null);
  const offset = useRef(0);

  const moving = !reduced && !pausedNow && !hover && !focus && items.length > 0;
  const loops = !reduced && items.length > 0;

  useEffect(() => {
    if (!moving) return;
    let raf = 0;
    let last = performance.now();
    const step = (t: number) => {
      const width = copyRef.current?.offsetWidth ?? 0;
      const dt = Math.min(t - last, 100) / 1000;
      last = t;
      if (width > 0) {
        offset.current = (offset.current + speed * dt) % width;
        if (trackRef.current) trackRef.current.style.transform = `translateX(${-offset.current}px)`;
      }
      raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [moving, speed]);

  // Reduced motion: drop the transform so the strip can be scrolled by hand.
  useEffect(() => {
    if (reduced && trackRef.current) { trackRef.current.style.transform = ""; offset.current = 0; }
  }, [reduced]);

  const toggle = () => { if (!isControlled) setInner(!pausedNow); onPausedChange?.(!pausedNow); };
  const name = pausedNow ? "Play ticker" : "Pause ticker";

  return (
    <div role="region" aria-label={label} className={cn("flex items-stretch overflow-hidden rounded-card border border-line bg-surface", className)}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      onFocus={() => setFocus(true)} onBlur={(e) => { if (!e.currentTarget.contains(e.relatedTarget)) setFocus(false); }}>
      <div tabIndex={reduced ? 0 : undefined} className={cn("min-w-0 flex-1", reduced ? "overflow-x-auto" : "overflow-hidden")}>
        <div ref={trackRef} className="flex w-max will-change-transform">
          <Row items={items} rowRef={copyRef} />
          {loops && <Row items={items} hidden />}
        </div>
      </div>
      <button type="button" onClick={toggle} aria-label={name} title={name}
        className="inline-flex min-h-9 min-w-9 shrink-0 cursor-pointer items-center justify-center gap-1.5 border-l border-line px-3 text-small leading-4 font-medium text-fg-muted transition-colors duration-[var(--dur-fast)] hover:bg-hover hover:text-fg pointer-coarse:min-h-11 pointer-coarse:min-w-11">
        <PauseIcon paused={pausedNow} />
        <span className="hidden sm:inline">{pausedNow ? "Play" : "Pause"}</span>
      </button>
    </div>
  );
}
```

## tattva/molecules/Timeline.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { CheckIcon, XIcon } from "../lib/icons";

export type TimelineState = "done" | "current" | "upcoming" | "failed";

export interface TimelineEvent {
  id: string;
  /** Display text for the time, such as "09:40" or "3 Oct". */
  time: string;
  /** Machine-readable value for the time element, such as "2026-10-03T09:40". */
  dateTime?: string;
  title: string;
  description?: ReactNode;
  /** Optional state. Shown as a marker shape and as text. */
  state?: TimelineState;
}

export interface TimelineProps {
  events: TimelineEvent[];
  /** Text for each state label, for localisation. */
  stateLabels?: Record<TimelineState, string>;
  className?: string;
}

const defaultLabels: Record<TimelineState, string> = { done: "Done", current: "In progress", upcoming: "Upcoming", failed: "Failed" };

/** Vertical timeline as an ordered list. Each event has a time element, a title and an optional state. */
export function Timeline({ events, stateLabels = defaultLabels, className }: TimelineProps) {
  return (
    <ol className={cn("m-0 list-none p-0", className)}>
      {events.map((e, i) => (
        <li key={e.id} className="relative flex gap-4 pb-6 last:pb-0">
          {i < events.length - 1 && <span aria-hidden className="absolute top-6 bottom-0 left-2.75 w-px bg-line-strong" />}
          <span aria-hidden className={cn("relative z-(--z-above) flex size-6 shrink-0 items-center justify-center rounded-full border",
            e.state === "done" && "border-transparent bg-lime text-on-lime",
            e.state === "current" && "border-fg bg-surface",
            e.state === "failed" && "border-transparent bg-danger-soft text-danger-fg",
            (!e.state || e.state === "upcoming") && "border-line-strong bg-surface")}>
            {e.state === "done" && <CheckIcon width={12} height={12} />}
            {e.state === "current" && <span className="size-2 rounded-full bg-fg" />}
            {e.state === "failed" && <XIcon width={12} height={12} />}
          </span>
          <div className="min-w-0 flex-1 pt-0.5">
            <p className="flex flex-wrap items-baseline gap-x-3 text-compact">
              <time dateTime={e.dateTime} className="font-mono text-small text-fg-subtle tabular-nums">{e.time}</time>
              <span className="font-medium text-fg">{e.title}</span>
              {e.state && <span className="text-micro text-fg-muted">{stateLabels[e.state]}</span>}
            </p>
            {e.description && <div className="mt-1 text-compact leading-6 text-fg-muted">{e.description}</div>}
          </div>
        </li>
      ))}
    </ol>
  );
}
```

## tattva/molecules/Toast.tsx

```tsx
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type HTMLAttributes, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, CheckIcon, InfoIcon, XIcon } from "../lib/icons";
import { IconButton } from "../atoms/Button";
import { useReducedMotion } from "../hooks/useReducedMotion";

export type ToastTone = "neutral" | "success" | "danger" | "info";

export interface ToastOptions {
  title: string;
  description?: string;
  tone?: ToastTone;
  action?: { label: string; onClick: () => void };
  /** Milliseconds before it closes. 0 keeps it until dismissed. By default it follows the content: 4s for a title, 6s with a description, 8s with an action. */
  duration?: number;
}

export interface ToastApi {
  show: (options: ToastOptions) => string;
  dismiss: (id: string) => void;
  /** Changes a toast in place, for example "Saving" to "Saved". Its timer starts again with the new content. */
  update: (id: string, options: Partial<ToastOptions>) => void;
}

/** How long a toast stays when no duration is given: long enough to read, longest when there is something to press. */
export const toastDuration = (o: ToastOptions) => o.duration ?? (o.action ? 8000 : o.description ? 6000 : 4000);

const toneIcon = {
  neutral: null,
  success: <CheckIcon aria-hidden width={18} height={18} className="mt-0.5 shrink-0 text-success-fg" />,
  danger: <AlertIcon aria-hidden width={18} height={18} className="mt-0.5 shrink-0 text-danger-fg" />,
  info: <InfoIcon aria-hidden width={18} height={18} className="mt-0.5 shrink-0 text-info-fg" />,
} satisfies Record<ToastTone, ReactNode>;

export interface ToastProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
  title: string;
  description?: string;
  tone?: ToastTone;
  action?: { label: string; onClick: () => void };
  /** Shows the close button. Without it the toast has no close button, so pass it whenever the toast can go away. */
  onDismiss?: () => void;
  /** Shows how much time is left, as a thin bar along the bottom. Pass the length in ms; `paused` holds it. */
  timeLeft?: { duration: number; paused: boolean };
}

/** The toast itself, without any timing. Use it to show a toast in docs or to build your own host. A danger toast has role="alert". */
export function Toast({ title, description, tone = "neutral", action, onDismiss, timeLeft, className, onKeyDown, ...rest }: ToastProps) {
  return (
    <div role={tone === "danger" ? "alert" : undefined} {...rest}
      onKeyDown={(e) => { onKeyDown?.(e); if (e.key === "Escape" && onDismiss && !e.defaultPrevented) { e.stopPropagation(); onDismiss(); } }}
      className={cn("pointer-events-auto relative flex w-full max-w-sm items-start gap-3 overflow-hidden rounded-overlay border border-line bg-raised p-3 pl-4 text-body leading-5 text-fg shadow-lg forced-colors:border-[CanvasText]", className)}>
      {toneIcon[tone]}
      <div className="min-w-0 flex-1 py-0.5">
        <p className="font-medium">{title}</p>
        {description && <p className="mt-0.5 text-fg-muted">{description}</p>}
        {action && (
          <button type="button" onClick={action.onClick}
            className="-ml-2 mt-1.5 inline-flex h-8 cursor-pointer items-center rounded-md px-2 text-compact font-medium text-accent-fg hover:bg-hover pointer-coarse:min-h-11">
            {action.label}
          </button>
        )}
      </div>
      {onDismiss && (
        <IconButton label={`Close notification: ${title}`} size="sm" onClick={onDismiss} className="-mt-0.5 -mr-1 shrink-0 pointer-coarse:size-11">
          <XIcon aria-hidden width={14} height={14} />
        </IconButton>
      )}
      {timeLeft && timeLeft.duration > 0 && (
        <span aria-hidden className="absolute inset-x-0 bottom-0 h-0.5 origin-left bg-line-strong motion-reduce:hidden"
          style={{ animation: `shrink-x ${timeLeft.duration}ms linear both`, animationPlayState: timeLeft.paused ? "paused" : "running" }} />
      )}
    </div>
  );
}

const ToastContext = createContext<ToastApi | null>(null);

/** Returns `{ show, dismiss, update }`. Must be used under a ToastProvider. */
export function useToast(): ToastApi {
  const api = useContext(ToastContext);
  if (!api) throw new Error("useToast must be used inside a ToastProvider.");
  return api;
}

interface Item extends ToastOptions { id: string; version: number; leaving?: boolean }
const MAX_VISIBLE = 3;
const EXIT_MS = 150; // --dur-fast

function ToastItem({ item, onDismiss }: { item: Item; onDismiss: (id: string) => void }) {
  const [hover, setHover] = useState(false);
  const [focus, setFocus] = useState(false);
  const [touch, setTouch] = useState(false);
  // A hidden tab cannot be read, so the clock stops there too.
  const [away, setAway] = useState(() => typeof document !== "undefined" && document.visibilityState === "hidden");
  useEffect(() => {
    const f = () => setAway(document.visibilityState === "hidden");
    document.addEventListener("visibilitychange", f);
    return () => document.removeEventListener("visibilitychange", f);
  }, []);
  const paused = hover || focus || touch || away;
  const duration = toastDuration(item);
  const remaining = useRef(duration);
  const dismiss = useRef(onDismiss);
  dismiss.current = onDismiss;

  // Time left carries over a pause, so hovering does not reset the countdown.
  useEffect(() => {
    if (!duration || paused) return;
    const start = Date.now();
    const t = setTimeout(() => dismiss.current(item.id), remaining.current);
    return () => { clearTimeout(t); remaining.current -= Date.now() - start; };
  }, [paused, duration, item.id]);

  return (
    <li className={cn("pointer-events-none flex w-full justify-center md:justify-end", item.leaving ? "motion-safe:animate-fade-out" : "motion-safe:animate-rise")}>
      <Toast title={item.title} description={item.description} tone={item.tone} action={item.action && { label: item.action.label, onClick: () => { item.action!.onClick(); onDismiss(item.id); } }}
        onDismiss={() => onDismiss(item.id)} timeLeft={item.action && duration ? { duration, paused } : undefined}
        onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
        onTouchStart={() => setTouch(true)} onTouchEnd={() => setTouch(false)} onTouchCancel={() => setTouch(false)}
        onFocus={() => setFocus(true)} onBlur={(e) => { if (!e.currentTarget.contains(e.relatedTarget)) setFocus(false); }} />
    </li>
  );
}

/**
 * Hosts the toasts. Put it once near the root. The region and its live list stay mounted so screen readers
 * hear new toasts. At most three show at once, newest on top; older ones wait and appear when there is room.
 */
export function ToastProvider({ children, className }: { children: ReactNode; className?: string }) {
  const [items, setItems] = useState<Item[]>([]);
  const counter = useRef(0);
  const reduce = useReducedMotion();
  // A toast fades out (faster than it arrived) before it leaves the list.
  const dismiss = useCallback((id: string) => {
    if (reduce) { setItems((l) => l.filter((t) => t.id !== id)); return; }
    setItems((l) => l.map((t) => (t.id === id ? { ...t, leaving: true } : t)));
    window.setTimeout(() => setItems((l) => l.filter((t) => t.id !== id)), EXIT_MS);
  }, [reduce]);
  const show = useCallback((options: ToastOptions) => {
    counter.current += 1;
    const id = `toast-${counter.current}`;
    setItems((l) => [...l, { ...options, id, version: 0 }]);
    return id;
  }, []);
  const update = useCallback((id: string, options: Partial<ToastOptions>) => {
    setItems((l) => l.map((t) => (t.id === id ? { ...t, ...options, version: t.version + 1 } : t)));
  }, []);
  const api = useMemo(() => ({ show, dismiss, update }), [show, dismiss, update]);
  const visible = [...items].reverse().slice(0, MAX_VISIBLE);

  return (
    <ToastContext.Provider value={api}>
      {children}
      <section role="region" aria-label="Notifications" className={cn("pointer-events-none fixed inset-x-0 bottom-0 z-50 p-4 pb-[max(1rem,var(--safe-bottom))]", className)}>
        <ol aria-live="polite" aria-relevant="additions text" className="m-0 flex list-none flex-col gap-2 p-0">
          {visible.map((t) => <ToastItem key={`${t.id}-${t.version}`} item={t} onDismiss={dismiss} />)}
        </ol>
      </section>
    </ToastContext.Provider>
  );
}
```

## tattva/molecules/ToolCallCard.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, CheckIcon, ToolIcon } from "../lib/icons";
import { Badge, type Tone } from "../atoms/Badge";
import { Spinner } from "../atoms/Spinner";
import { Collapsible } from "./Collapsible";

export type ToolStatus = "pending" | "running" | "success" | "error";

const status: Record<ToolStatus, { tone: Tone; label: string }> = {
  pending: { tone: "neutral", label: "Queued" },
  running: { tone: "accent", label: "Running" },
  success: { tone: "success", label: "Done" },
  error: { tone: "danger", label: "Failed" },
};

export interface ToolCallCardProps {
  name: string;
  status: ToolStatus;
  /** One-line human summary of what the tool is doing, e.g. 'Searching "q3 revenue"'. */
  summary?: string;
  input?: unknown;
  output?: ReactNode;
  durationMs?: number;
}

export function ToolCallCard({ name, status: s, summary, input, output, durationMs }: ToolCallCardProps) {
  const { tone, label } = status[s];
  return (
    <div className={cn("rounded-card border bg-surface p-3", s === "error" ? "border-danger-line" : "border-line")}>
      <Collapsible
        header={
          <span className="flex min-w-0 flex-1 items-center gap-2">
            <span className="flex size-6 shrink-0 items-center justify-center rounded-md bg-sunken text-fg-muted">
              {s === "running" ? <Spinner size={14} /> : s === "success" ? <CheckIcon className="text-success" /> : s === "error" ? <AlertIcon className="text-danger" /> : <ToolIcon />}
            </span>
            <span className="min-w-0 flex-1">
              <span className="block truncate font-mono text-compact font-medium">{name}</span>
              {summary && <span className="block truncate text-small leading-4 text-fg-muted">{summary}</span>}
            </span>
            {durationMs != null && s !== "running" && <span className="text-small leading-4 text-fg-subtle tabular-nums">{(durationMs / 1000).toFixed(1)}s</span>}
            <Badge tone={tone}>{label}</Badge>
          </span>
        }
      >
        <div className="mt-3 space-y-2 text-small leading-4">
          {input !== undefined && (
            <div>
              <p className="mb-1 font-medium text-fg-subtle">Input</p>
              <pre tabIndex={0} className="overflow-x-auto rounded-md bg-sunken p-2 font-mono leading-5">{JSON.stringify(input, null, 2)}</pre>
            </div>
          )}
          {output != null && (
            <div>
              <p className="mb-1 font-medium text-fg-subtle">Output</p>
              <div className="overflow-x-auto rounded-md bg-sunken p-2 font-mono leading-5 [overflow-wrap:anywhere]">{output}</div>
            </div>
          )}
        </div>
      </Collapsible>
    </div>
  );
}
```

## tattva/molecules/ToolChip.tsx

```tsx
import { useRef, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { keepFocus } from "../lib/focus";
import { XIcon } from "../lib/icons";

export interface ToolChipProps {
  /** Tool name, for example "Web search". Also names the remove button. */
  label: string;
  /** Leading icon. Decorative, so it needs no name of its own. */
  icon?: ReactNode;
  /** Called when the remove button is pressed. Omit to render a fixed chip with no remove button. */
  onRemove?: () => void;
  /** Overrides the remove button name. Defaults to `Remove ${label}`. */
  removeLabel?: string;
  /** Where keyboard focus goes after Remove. By default the next tag, else the previous one, else the message box. */
  focusAfterRemove?: () => HTMLElement | null;
  className?: string;
}

/** Pill shown in the composer for a tool that is switched on. The remove button is named after the tool. */
export function ToolChip({ label, icon, onRemove, removeLabel, focusAfterRemove, className }: ToolChipProps) {
  const ref = useRef<HTMLSpanElement>(null);
  return (
    <span ref={ref} className={cn("inline-flex min-h-8 max-w-full items-center gap-1.5 rounded-control border border-line bg-sunken pl-3 text-small leading-4 font-medium text-fg", onRemove ? "pr-1" : "pr-3", className)}>
      {icon && <span className="flex shrink-0 items-center text-fg-muted">{icon}</span>}
      <span className="truncate">{label}</span>
      {onRemove && (
        <button type="button" onClick={() => { onRemove(); keepFocus(ref.current, focusAfterRemove); }} aria-label={removeLabel ?? `Remove ${label}`} title={removeLabel ?? `Remove ${label}`}
          className="flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-control text-fg-subtle transition-colors hover:bg-hover hover:text-fg">
          <XIcon width={12} height={12} />
        </button>
      )}
    </span>
  );
}
```

## tattva/molecules/TrayRow.tsx

```tsx
import type { ReactNode } from "react";
import { AttentionDot } from "../atoms/AttentionDot";

export interface TrayRowProps {
  title: string;
  detail?: string;
  /** Amber dot + detail in the foreground colour: this one needs you. */
  attention?: boolean;
  icon?: ReactNode;
  trailing?: ReactNode;
  onClick?: () => void;
}

export function TrayRow({ title, detail, attention, icon, trailing, onClick }: TrayRowProps) {
  return (
    <li>
      <button type="button" onClick={onClick}
        className="group flex w-full items-center gap-2.5 rounded-card px-3 py-2 text-left sm:rounded-control sm:py-1.5 tap text-compact transition-colors hover:bg-hover cursor-pointer">
        {attention ? <AttentionDot /> : icon && <span className="text-fg-subtle">{icon}</span>}
        <span className="flex min-w-0 flex-col sm:flex-row sm:items-baseline sm:gap-2.5">
          <span className="font-medium text-fg">{title}</span>
          {detail && <span className="min-w-0 text-fg-subtle sm:truncate">{detail}</span>}
        </span>
        <span className="flex-1" />
        {trailing && <span className="text-small leading-4 text-fg-subtle shrink-0 opacity-0 transition-opacity pointer-coarse:opacity-100 group-hover:opacity-100 group-focus-visible:opacity-100">{trailing}</span>}
      </button>
    </li>
  );
}
```

## tattva/molecules/UsageMeter.tsx

```tsx
import type { ReactNode } from "react";
import { Meter } from "../atoms/Meter";
import { cn } from "../lib/cn";

export interface UsageMeterProps {
  /** Credits or quota left. */
  remaining: number;
  /** Full allowance for the period. */
  total: number;
  /** Plural unit name. */
  unit?: string;
  /** Cost of the action about to run, in the same unit. Shows "This will use about N credits". */
  estimate?: number;
  /** Reset time, for example "Resets Nov 1". */
  resetText?: string;
  /** Fraction of the allowance used at which the meter warns, 0 to 1. */
  warnAt?: number;
  /** Slot for an upgrade action. Shown only in the warn state. */
  upgrade?: ReactNode;
  /** Reason shown in the warn state. */
  warnText?: string;
  /** Accessible name for the meter. */
  label?: string;
  className?: string;
}

/** Remaining credits or quota with an optional cost estimate. Warns only when the person may need to act. */
export function UsageMeter({ remaining, total, unit = "credits", estimate, resetText, warnAt = 0.8, upgrade, warnText, label = "Usage", className }: UsageMeterProps) {
  const used = Math.max(0, total - remaining);
  const short = estimate !== undefined && estimate > remaining;
  const low = total > 0 && used / total >= warnAt;
  const warn = low || short;
  const reason = warnText ?? (short ? `Not enough ${unit} left for this.` : `Running low on ${unit}.`);
  const text = `${remaining} of ${total} ${unit} left`;
  return (
    <div className={cn("space-y-2", className)}>
      <div className="flex items-center justify-between gap-3 text-small leading-4">
        <span className="font-medium text-fg">{text}</span>
        {resetText && <span className="text-fg-subtle">{resetText}</span>}
      </div>
      <Meter label={label} value={used} max={total} warnAt={warnAt} valueText={text} className={short && !low ? "[&>div]:bg-attention" : undefined} />
      {estimate !== undefined && <p className="text-small leading-4 text-fg-muted">This will use about {estimate} {unit}.</p>}
      {warn && (
        <div className="flex flex-wrap items-center gap-2 text-small leading-4">
          <span className="text-attention-fg">{reason}</span>
          {upgrade}
        </div>
      )}
    </div>
  );
}
```

## tattva/molecules/VariantTile.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { CheckIcon } from "../lib/icons";
import { AIBadge } from "../atoms/AIBadge";

export interface Variant { id: string; node: ReactNode; label: string }

/** One generated option. Selection shows a check mark, not only a colour change. Arrow key movement between tiles comes from VariantGrid. */
export function VariantTile({ variant: v, selected, onSelect, tabIndex }: {
  variant: Variant; selected: boolean; onSelect: (id: string) => void;
  /** Set to -1 on tiles that are not the tab stop of a roving group. VariantGrid does this for you. Leave unset for a standalone tile. */
  tabIndex?: number;
}) {
  return (
    <button type="button" role="radio" aria-checked={selected} aria-label={v.label} tabIndex={tabIndex} onClick={() => onSelect(v.id)}
      className={cn("group relative aspect-square cursor-pointer overflow-hidden rounded-card border-2 text-left transition-all animate-rise", selected ? "border-fg shadow-md" : "border-line hover:border-line-strong")}>
      {v.node}
      <span className="absolute top-2 left-2"><AIBadge label="AI" solid /></span>
      {selected && <span className="absolute right-2 bottom-2 flex size-5 items-center justify-center rounded-full bg-inverse text-inverse-fg"><CheckIcon width={12} height={12} /></span>}
    </button>
  );
}
```

## tattva/molecules/VariationActions.tsx

```tsx
import { useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { Button } from "../atoms/Button";
import { SegmentedControl, type SegmentOption } from "../atoms/SegmentedControl";

export interface VariationAction {
  id: string;
  label: string;
  /** Cost hint shown beside the action, such as "About 4 credits". */
  cost?: ReactNode;
  /** Set false for actions with no strength choice. Default true. */
  strength?: boolean;
}

export const DEFAULT_VARIATION_ACTIONS: VariationAction[] = [
  { id: "vary", label: "Vary" },
  { id: "upscale", label: "Upscale" },
  { id: "remix", label: "Remix" },
  { id: "extend", label: "Extend", strength: false },
];

export interface VariationActionsProps {
  /** Actions in display order. Defaults to Vary, Upscale, Remix and Extend. */
  actions?: VariationAction[];
  /** Called with the action id and the chosen strength value. */
  onAction: (actionId: string, strength: string) => void;
  /** Strength choices shared by every action that has one. */
  strengths?: SegmentOption<string>[];
  /** Strength selected at first for each action. Defaults to the first strength. */
  defaultStrength?: string;
  /** Lineage line, for example "Made from Variant B". */
  lineage?: ReactNode;
  disabled?: boolean;
  className?: string;
}

const STRENGTHS: SegmentOption<string>[] = [{ value: "subtle", label: "Subtle" }, { value: "strong", label: "Strong" }];

/** Action row for one generated item. Each action keeps its own strength choice and can show what it costs. */
export function VariationActions({ actions = DEFAULT_VARIATION_ACTIONS, onAction, strengths = STRENGTHS, defaultStrength, lineage, disabled, className }: VariationActionsProps) {
  const first = defaultStrength ?? strengths[0]?.value ?? "";
  const [chosen, setChosen] = useState<Record<string, string>>({});
  return (
    <div className={cn("space-y-2", className)}>
      {lineage && <p className="text-small leading-4 text-fg-muted">{lineage}</p>}
      <ul className="space-y-1.5">
        {actions.map((a) => {
          const s = chosen[a.id] ?? first;
          const hasStrength = a.strength !== false;
          return (
            <li key={a.id} className="flex flex-wrap items-center gap-2">
              <Button variant="secondary" size="sm" disabled={disabled} onClick={() => onAction(a.id, hasStrength ? s : "")}>{a.label}</Button>
              {hasStrength && <SegmentedControl size="sm" label={`${a.label} strength`} options={strengths} value={s} onChange={(v) => setChosen((c) => ({ ...c, [a.id]: v }))} />}
              {a.cost && <span className="text-small leading-4 text-fg-subtle">{a.cost}</span>}
            </li>
          );
        })}
      </ul>
    </div>
  );
}
```

## tattva/molecules/VersionPager.tsx

```tsx
import { useState, type ReactNode } from "react";
import { ChevronIcon } from "../lib/icons";
import { IconButton } from "../atoms/Button";
import { cn } from "../lib/cn";

export interface VersionPagerProps {
  /** Number of sibling versions, from edits and regenerations. */
  total: number;
  /** Controlled current version, 1-based. Omit to let the pager manage itself. */
  current?: number;
  /** Starting version in uncontrolled mode. Defaults to the last version. */
  defaultCurrent?: number;
  /** Called with the new 1-based version when the person steps. */
  onChange?: (version: number) => void;
  /** Shows a small "edited" label when the current version came from an edit. */
  edited?: boolean;
  /** Text for the edited label. */
  editedLabel?: string;
  /** Slot for an action such as a "Branch from here" button. */
  branchAction?: ReactNode;
  /** Builds the polite announcement. Defaults to "Version 2 of 3". */
  announce?: (current: number, total: number) => string;
  /** Accessible name for the group. */
  label?: string;
  className?: string;
}

/** Pager for sibling versions of one message. Announces each change through a separate polite status region. */
export function VersionPager({ total, current, defaultCurrent, onChange, edited, editedLabel = "Edited", branchAction, announce = (c, t) => `Version ${c} of ${t}`, label = "Message versions", className }: VersionPagerProps) {
  const [inner, setInner] = useState(defaultCurrent ?? total);
  const [message, setMessage] = useState("");
  const value = Math.min(Math.max(current ?? inner, 1), Math.max(total, 1));
  const go = (next: number) => {
    if (next < 1 || next > total) return;
    if (current === undefined) setInner(next);
    onChange?.(next);
    setMessage(announce(next, total));
  };
  return (
    <div role="group" aria-label={label} className={cn("inline-flex max-w-full flex-wrap items-center gap-1.5 text-small leading-4 text-fg-muted", className)}>
      <IconButton size="sm" label="Previous version" disabled={value <= 1} onClick={() => go(value - 1)}><ChevronIcon className="rotate-180" /></IconButton>
      <span className="min-w-8 text-center tabular-nums" aria-hidden="true">{value}/{total}</span>
      <IconButton size="sm" label="Next version" disabled={value >= total} onClick={() => go(value + 1)}><ChevronIcon /></IconButton>
      {edited && <span className="rounded-control border border-line bg-sunken px-2 py-0.5 text-micro leading-4">{editedLabel}</span>}
      {branchAction}
      <span role="status" className="sr-only">{message}</span>
    </div>
  );
}
```

## tattva/molecules/VideoPlayer.tsx

```tsx
import { useCallback, useEffect, useId, useRef, useState } from "react";
import type { KeyboardEvent, PointerEvent, ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, LoopIcon, RetryIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { FrameScrubber, frameAt } from "../atoms/FrameScrubber";
import { Kbd } from "../atoms/Kbd";
import { Spinner } from "../atoms/Spinner";
import { Switch } from "../atoms/Switch";
import { WorkingEdge } from "../atoms/WorkingEdge";
import { formatMediaTime } from "../atoms/Waveform";
import { useReducedMotion } from "../hooks/useReducedMotion";
import { AILabel } from "./AILabel";
import { Collapsible } from "./Collapsible";
import { ProvenancePopover } from "./ProvenancePopover";
import type { ProvenancePopoverProps } from "./ProvenancePopover";

export type VideoRatio = "16:9" | "9:16" | "1:1";
/** A drawn stand-in for a clip, used when there is no `src` (previews, tests and docs). */
export type VideoSceneName = "harbour" | "kite";
/**
 * generating: the AI is still making the clip; the poster shows and nothing can play. loading: getting the file.
 * ready, playing, paused, buffering, ended: tracked by the player. error: the clip could not be loaded.
 */
export type VideoPlayerState = "generating" | "loading" | "ready" | "playing" | "paused" | "buffering" | "ended" | "error";

export interface VideoFrameAction {
  id: string;
  /** Short verb phrase, such as "Use as start frame" or "Extend from here". */
  label: string;
  icon?: ReactNode;
}

export interface VideoCue {
  start: number;
  end: number;
  text: string;
}

export interface VideoPlayerProps {
  /** Names the player, such as "Take 3, harbour at dawn". Tells clips apart. */
  label: string;
  /** A second line, such as "8 seconds, made 3 October". Without it the line shows the state in words. */
  subtitle?: string;
  /** A real video file. Leave out to simulate playback with a drawn scene. */
  src?: string;
  /** Length in seconds. Needed without `src`; with `src` it is read from the file when left out. */
  duration?: number;
  /** The drawn scene used without `src`. */
  scene?: VideoSceneName;
  /** Still image shown before playing and while generating. Without it the first drawn frame is used. */
  poster?: string;
  /** Shape of the frame. The space is reserved before anything loads. */
  ratio?: VideoRatio;
  /** Frames per second. Turns on frame step buttons and keys, and frame numbers. */
  fps?: number;
  /** Forces a state the player cannot see for itself: generating, loading or error. */
  state?: "generating" | "loading" | "error";
  /** Extra words beside the generating or error state, such as "Second in the queue, busy period". */
  statusNote?: string;
  /** Shows Try again in the error state. */
  onRetry?: () => void;
  /** Starts with loop on. The person can change it. */
  loop?: boolean;
  /** Starts muted. The person can change it. Nothing ever plays, with or without sound, until the person presses Play. */
  muted?: boolean;
  /** Set to false when the clip has no sound. The mute button then says so and does nothing. */
  hasAudio?: boolean;
  /** Caption files for a real `src`, played through native tracks. */
  captions?: { src: string; lang: string; label: string }[];
  /** Caption lines drawn over the frame. Works with or without `src`. */
  cues?: VideoCue[];
  /** The words spoken in the clip, or a description of what it shows. Shown in a collapsed Transcript section. */
  transcript?: ReactNode;
  /** Actions on the paused frame, such as "Use as start frame". They show only while paused. */
  frameActions?: VideoFrameAction[];
  /** Called with the action id, the time in seconds and the frame number (when `fps` is set). */
  onFrameAction?: (id: string, seconds: number, frame?: number) => void;
  /** Single-key shortcuts (K, M, C, comma, period) on at first. They only work while focus is inside the player, and the person can turn them off. */
  shortcuts?: boolean;
  /** Adds an AI label beside the title. The player itself stays neutral. */
  aiGenerated?: boolean;
  /** What the AI label explains, used when there is no `provenance`. */
  aiNote?: ReactNode;
  /** Opens full provenance details from the label: model, date, what the file carries. Say what can strip it. */
  provenance?: Omit<ProvenancePopoverProps, "open" | "onOpenChange">;
  /** Position in seconds, when the parent keeps it. A change of more than a frame seeks. */
  current?: number;
  onTimeChange?: (seconds: number) => void;
  onPlayingChange?: (playing: boolean) => void;
  className?: string;
}

const STATE_WORD: Record<VideoPlayerState, string> = {
  generating: "Generating", loading: "Loading", ready: "Ready", playing: "Playing", paused: "Paused",
  buffering: "Buffering", ended: "Ended", error: "Could not play",
};
const RATIO: Record<VideoRatio, string> = { "16:9": "aspect-video", "9:16": "aspect-[9/16] max-w-64", "1:1": "aspect-square max-w-md" };
const VIEW: Record<VideoRatio, string> = { "16:9": "0 35 160 90", "9:16": "35 0 90 160", "1:1": "0 0 160 160" };
// Shared with AudioPlayer, so starting a video pauses any audio on the page and the reverse.
const PLAY_EVENT = "tattva-audio-play";
const DOUBLE_TAP_SECONDS = 2;

const isTyping = (el: EventTarget | null) => el instanceof HTMLElement && (el.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName));
const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));

/* ---------- glyphs ---------- */
const G = ({ children, className = "size-5" }: { children: ReactNode; className?: string }) => (
  <svg aria-hidden viewBox="0 0 24 24" className={className} fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round">{children}</svg>
);
const PlayGlyph = ({ className }: { className?: string }) => <G className={className}><path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.5-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5z" fill="currentColor" stroke="none" /></G>;
const PauseGlyph = () => <G><rect x="6.5" y="5" width="4" height="14" rx="1" fill="currentColor" stroke="none" /><rect x="13.5" y="5" width="4" height="14" rx="1" fill="currentColor" stroke="none" /></G>;
const FrameBackGlyph = () => <G><path d="M6 5v14" /><path d="M18 6l-8 6 8 6z" fill="currentColor" stroke="none" /></G>;
const FrameFwdGlyph = () => <G><path d="M18 5v14" /><path d="M6 6l8 6-8 6z" fill="currentColor" stroke="none" /></G>;
const SoundGlyph = ({ off }: { off: boolean }) => (
  <G><path d="M4 9.5h3.5L12 6v12l-4.5-3.5H4z" />{off ? <path d="M16 9.5l5 5M21 9.5l-5 5" /> : <path d="M15.5 9a4 4 0 0 1 0 6M18 6.5a7.5 7.5 0 0 1 0 11" />}</G>
);
const CaptionsGlyph = () => <G><rect x="3" y="5.5" width="18" height="13" rx="2" /><path d="M10.5 10.5a2 2 0 1 0 0 3M17 10.5a2 2 0 1 0 0 3" /></G>;
const FullGlyph = () => <G><path d="M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5" /></G>;

/* ---------- drawn scenes ---------- */

/**
 * One frame of a drawn stand-in clip, worked out from the time alone, so any frame can be drawn again (for the scrubber
 * preview, a filmstrip or a frame action). Colours come from tokens. Use it where a real file is not needed.
 */
export function VideoScene({ scene = "harbour", time, duration, ratio = "16:9", className }: { scene?: VideoSceneName; time: number; duration: number; ratio?: VideoRatio; className?: string }) {
  const p = duration > 0 ? clamp(time / duration, 0, 1) : 0;
  const gid = useId().replace(/:/g, "");
  const paper = "var(--ref-paper)";
  const ink = "var(--ref-charcoal)";
  if (scene === "kite") {
    const kx = 80 + Math.sin(p * Math.PI * 4) * 28;
    const ky = 52 + Math.sin(p * Math.PI * 8) * 10 - p * 8;
    const cloud = (x: number, y: number, s: number) => (
      <g transform={`translate(${((x + p * 60) % 200) - 20} ${y}) scale(${s})`} fill={paper} fillOpacity={0.85}>
        <ellipse cx="0" cy="0" rx="12" ry="5" /><ellipse cx="7" cy="-3" rx="8" ry="5" /><ellipse cx="-6" cy="-2" rx="6" ry="4" />
      </g>
    );
    return (
      <svg viewBox={VIEW[ratio]} preserveAspectRatio="xMidYMid slice" className={cn("block size-full", className)} focusable="false" aria-hidden>
        <defs>
          <linearGradient id={`${gid}k`} x1="0" y1="0" x2="0" y2="1">
            <stop offset="0" style={{ stopColor: "var(--chart-seq-3)" }} />
            <stop offset="1" style={{ stopColor: "var(--chart-seq-1)" }} />
          </linearGradient>
        </defs>
        <rect width="160" height="160" fill={`url(#${gid}k)`} />
        {cloud(20, 30, 1)}{cloud(110, 45, 0.8)}{cloud(70, 18, 0.6)}
        <path d="M0 118 Q40 96 85 108 T160 102 V160 H0z" style={{ fill: "color-mix(in oklab, var(--chart-4) 70%, var(--ref-charcoal))" }} />
        <path d="M0 128 Q50 112 100 124 T160 118 V160 H0z" fill="var(--chart-4)" />
        {/* A child on the hill holding the line. */}
        <g transform="translate(58 112)" fill={ink}><circle cx="0" cy="-9" r="2.2" /><path d="M-2 -7h4l1 7h-6z" /><path d="M-2 0v5M2 0v5" stroke={ink} strokeWidth="1.4" /></g>
        <path d={`M60 106 Q${(kx + 60) / 2} ${ky + 30} ${kx} ${ky + 8}`} fill="none" stroke={ink} strokeWidth="0.5" />
        <g transform={`translate(${kx} ${ky}) rotate(${Math.sin(p * Math.PI * 8) * 12})`}>
          <path d="M0 -8 L6 0 L0 10 L-6 0z" fill="var(--chart-2)" stroke={ink} strokeWidth="0.6" />
          <path d="M0 10 q3 5 0 9 q-3 4 0 8" fill="none" stroke="var(--chart-2)" strokeWidth="1" />
        </g>
      </svg>
    );
  }
  const sx = 34 + p * 70;
  const sy = 104 - Math.sin(p * Math.PI * 0.85) * 46;
  const boat = 8 + p * 112;
  const wave = (y: number, o: number) => {
    const x0 = -20 + ((p * 40 + o) % 20);
    return <path d={`M${x0} ${y} q5 -2 10 0 t10 0 t10 0 t10 0 t10 0 t10 0 t10 0 t10 0 t10 0 t10 0 t10 0 t10 0 t10 0 t10 0 t10 0 t10 0 t10 0 t10 0`} fill="none" stroke={paper} strokeOpacity={0.35} strokeWidth="0.7" />;
  };
  return (
    <svg viewBox={VIEW[ratio]} preserveAspectRatio="xMidYMid slice" className={cn("block size-full", className)} focusable="false" aria-hidden>
      <defs>
        <linearGradient id={`${gid}s`} x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" style={{ stopColor: "var(--chart-seq-5)" }} />
          <stop offset="0.7" style={{ stopColor: "var(--chart-seq-2)" }} />
          <stop offset="1" style={{ stopColor: "color-mix(in oklab, var(--chart-2) 45%, var(--ref-paper))" }} />
        </linearGradient>
      </defs>
      <rect width="160" height="160" fill={`url(#${gid}s)`} />
      <circle cx={sx} cy={sy} r="9" style={{ fill: "color-mix(in oklab, var(--chart-2) 40%, var(--ref-paper))" }} />
      <path d="M0 100 L18 90 L30 95 L48 84 L64 96 L160 96 V100z" fill="var(--chart-seq-5)" />
      <rect y="100" width="160" height="60" fill="var(--chart-seq-4)" />
      <ellipse cx={sx} cy={112} rx="6" ry="14" style={{ fill: "color-mix(in oklab, var(--chart-2) 40%, var(--ref-paper))" }} fillOpacity={0.35} />
      {wave(106, 0)}{wave(114, 7)}{wave(124, 13)}{wave(138, 4)}
      {/* Lighthouse on the right. */}
      <g transform="translate(136 72)">
        <path d="M-3 0h6l2 28h-10z" fill={paper} />
        <rect x="-4" y="8" width="8" height="4" fill="var(--chart-2)" /><rect x="-4.6" y="18" width="9.2" height="4" fill="var(--chart-2)" />
        <rect x="-3.5" y="-5" width="7" height="5" fill={ink} />
        <path d="M-20 28h40v4h-40z" fill="var(--chart-seq-5)" />
      </g>
      <g transform={`translate(${boat} 101)`}>
        <path d="M-9 0h18l-3 4h-12z" fill={ink} />
        <path d="M0 -1V-15L8 -2z" fill={paper} />
        <path d="M-1 -1V-11L-7 -2z" fill={paper} fillOpacity={0.8} />
      </g>
    </svg>
  );
}

/* ---------- player ---------- */

/**
 * Plays one generated clip and lets the person act on the exact frame they paused on. Play and pause, a frame-accurate
 * scrubber, frame steps, loop, mute, captions and full screen. Nothing plays until the person presses Play.
 * Without `src` it simulates playback with a drawn scene, so previews and docs need no file.
 */
export function VideoPlayer({
  label, subtitle, src, duration, scene = "harbour", poster, ratio = "16:9", fps, state: forced, statusNote, onRetry,
  loop: loopStart = false, muted: mutedStart = false, hasAudio = true, captions, cues, transcript, frameActions, onFrameAction,
  shortcuts = true, aiGenerated, aiNote, provenance, current, onTimeChange, onPlayingChange, className,
}: VideoPlayerProps) {
  const id = useId();
  const titleId = `${id}-title`;
  const reduced = useReducedMotion();
  const video = useRef<HTMLVideoElement>(null);
  const root = useRef<HTMLElement>(null);
  const [time, setTime] = useState(current ?? 0);
  const [playing, setPlaying] = useState(false);
  const [ended, setEnded] = useState(false);
  const [loop, setLoop] = useState(loopStart);
  const [muted, setMuted] = useState(mutedStart || !hasAudio);
  const [captionsOn, setCaptionsOn] = useState(true);
  const [keysOn, setKeysOn] = useState(shortcuts);
  const [fileDur, setFileDur] = useState<number | null>(null);
  const [fileState, setFileState] = useState<"loading" | "ok" | "buffering" | "error">(src ? "loading" : "ok");
  const [said, setSaid] = useState("");
  const timeRef = useRef(time);
  timeRef.current = time;

  const dur = duration ?? fileDur ?? 0;
  const blocked = forced === "generating" || forced === "loading" || forced === "error";
  const state: VideoPlayerState = blocked ? forced!
    : src && fileState === "loading" ? "loading" : src && fileState === "error" ? "error"
    : playing && fileState === "buffering" ? "buffering" : playing ? "playing" : ended ? "ended" : time > 0 ? "paused" : "ready";
  const canPlay = !blocked && state !== "loading" && state !== "error" && dur > 0;
  const hasCaptions = !!cues?.length || !!captions?.length;
  const frame = fps ? 1 / fps : 0;
  const frameNo = fps ? Math.min(frameAt(time, fps), Math.max(1, Math.round(dur * fps))) : undefined;
  const totalFrames = fps ? Math.max(1, Math.round(dur * fps)) : 0;

  const report = useCallback((t: number) => { setTime(t); onTimeChange?.(t); }, [onTimeChange]);
  const setPlay = useCallback((on: boolean) => {
    setPlaying(on);
    onPlayingChange?.(on);
    if (on) window.dispatchEvent(new CustomEvent(PLAY_EVENT, { detail: id }));
  }, [id, onPlayingChange]);

  const seek = useCallback((t: number) => {
    const to = clamp(t, 0, dur || 0);
    if (video.current) video.current.currentTime = to;
    setEnded(false);
    report(to);
  }, [dur, report]);

  // The parent moved the position.
  useEffect(() => {
    if (current !== undefined && Math.abs(current - timeRef.current) > Math.max(frame, 0.05)) seek(current);
  }, [current, seek, frame]);

  // Only one player plays at a time, across audio and video.
  useEffect(() => {
    const other = (e: Event) => { if ((e as CustomEvent).detail !== id) { setPlaying(false); video.current?.pause(); } };
    window.addEventListener(PLAY_EVENT, other);
    return () => window.removeEventListener(PLAY_EVENT, other);
  }, [id]);

  // Stop when the clip becomes unplayable (for example it goes back to generating).
  useEffect(() => { if (blocked && playing) setPlay(false); }, [blocked, playing, setPlay]);

  // A real file plays through the video element.
  useEffect(() => {
    const v = video.current;
    if (!v || !src) return;
    v.muted = muted;
    v.loop = loop;
    if (playing && !blocked) v.play().catch(() => setPlay(false));
    else v.pause();
  }, [playing, muted, loop, src, blocked, setPlay]);

  useEffect(() => {
    const tracks = video.current?.textTracks;
    if (!tracks) return;
    for (let i = 0; i < tracks.length; i++) tracks[i].mode = captionsOn ? "showing" : "hidden";
  }, [captionsOn, src]);

  // Without a file, a clock moves the playhead. It ticks less often with reduced motion.
  useEffect(() => {
    if (src || !playing || blocked) return;
    const step = reduced ? 250 : 1000 / Math.min(fps ?? 30, 30);
    let last = performance.now();
    const t = window.setInterval(() => {
      const now = performance.now();
      const next = timeRef.current + (now - last) / 1000;
      last = now;
      if (next >= dur) {
        if (loop) report(0);
        else { report(dur); setPlay(false); setEnded(true); }
      } else report(next);
    }, step);
    return () => window.clearInterval(t);
  }, [src, playing, blocked, reduced, dur, loop, fps, report, setPlay]);

  // Say state changes once: ready after generating (WorkingEdge already says generating), ended, error. Never the clock or play and pause.
  const prevForced = useRef(forced);
  useEffect(() => {
    if (prevForced.current === "generating" && forced !== "generating") setSaid(`${label} is ready to play.`);
    else if (state === "generating") setSaid(statusNote ?? "");
    else if (state === "ended") setSaid(`${label}: ended.`);
    else if (state === "error") setSaid(`${label}: could not play.${statusNote ? ` ${statusNote}` : ""}`);
    else if (state === "buffering") setSaid(`${label}: buffering.`);
    prevForced.current = forced;
  }, [state, forced, label, statusNote]);

  const toggle = () => {
    if (!canPlay) return;
    if (playing) { setPlay(false); return; }
    if (ended || time >= dur) seek(0);
    setEnded(false);
    setPlay(true);
  };
  const stepFrame = (dir: 1 | -1) => {
    if (!canPlay) return;
    if (playing) setPlay(false);
    seek(Math.round((timeRef.current + dir * (frame || 0.1)) / (frame || 0.1)) * (frame || 0.1));
  };
  const toggleMute = () => { if (hasAudio) setMuted((m) => !m); };
  const fullscreen = () => {
    const el = root.current;
    if (!el) return;
    if (document.fullscreenElement) void document.exitFullscreen?.();
    else void el.requestFullscreen?.().catch(() => {});
  };
  const canFull = typeof document !== "undefined" && !!document.fullscreenEnabled;

  // Keys work only while focus is inside the player, never while typing. Single-character keys can be turned off.
  const onKey = (e: KeyboardEvent<HTMLElement>) => {
    if (e.altKey || e.ctrlKey || e.metaKey || isTyping(e.target)) return;
    const el = e.target instanceof HTMLElement ? e.target : null;
    const onButton = el?.tagName === "BUTTON";
    const k = e.key;
    let done = true;
    if (k === " " && !onButton) toggle();
    else if (keysOn && (k === "k" || k === "K")) toggle();
    else if (keysOn && (k === "m" || k === "M")) toggleMute();
    else if (keysOn && (k === "c" || k === "C") && hasCaptions) setCaptionsOn((c) => !c);
    else if (keysOn && k === ",") stepFrame(-1);
    else if (keysOn && k === ".") stepFrame(1);
    else if (k === "ArrowLeft" || k === "ArrowRight") {
      const dir = k === "ArrowLeft" ? -1 : 1;
      if (e.shiftKey) stepFrame(dir); else if (canPlay) seek(timeRef.current + dir);
    } else if (k === "Home" && canPlay) seek(0);
    else if (k === "End" && canPlay) seek(dur);
    else done = false;
    if (done) e.preventDefault();
  };

  // Touch: one tap plays or pauses; a quick second tap on the left or right side seeks back or forward.
  const tap = useRef<{ at: number; timer: number } | null>(null);
  const onSurfaceUp = (e: PointerEvent<HTMLButtonElement>) => {
    if (e.pointerType !== "touch" || !canPlay) return;
    const r = e.currentTarget.getBoundingClientRect();
    const side = (e.clientX - r.left) / r.width;
    if (tap.current && performance.now() - tap.current.at < 300) {
      window.clearTimeout(tap.current.timer);
      tap.current = null;
      if (side < 0.4) seek(timeRef.current - DOUBLE_TAP_SECONDS);
      else if (side > 0.6) seek(timeRef.current + DOUBLE_TAP_SECONDS);
      return;
    }
    const timer = window.setTimeout(() => { tap.current = null; toggle(); }, 260);
    tap.current = { at: performance.now(), timer };
  };
  useEffect(() => () => { if (tap.current) window.clearTimeout(tap.current.timer); }, []);
  const lastPointer = useRef("mouse");

  const cue = captionsOn && cues?.find((c) => time >= c.start && time < c.end);
  const showFrameActions = !!frameActions?.length && canPlay && !playing;
  const showPoster = !src && (state === "generating" || state === "loading") ? poster : undefined;
  const timeText = <span className="tabular-nums">{formatMediaTime(time)} / {dur ? formatMediaTime(dur) : "–:––"}</span>;

  const iconBtn = "inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-control text-fg-muted transition-colors duration-(--dur-fast) hover:bg-hover hover:text-fg aria-disabled:cursor-default aria-disabled:text-disabled-fg aria-disabled:hover:bg-transparent aria-pressed:text-fg aria-pressed:bg-sunken pointer-coarse:size-11";

  return (
    <section ref={root} aria-labelledby={titleId} onKeyDown={onKey}
      className={cn("flex min-w-0 flex-col gap-2 rounded-card border border-line bg-surface p-3 sm:p-4 [&:fullscreen]:justify-center [&:fullscreen]:overflow-auto", className)}>
      <div className="flex min-w-0 items-center gap-2">
        <div className="min-w-0 flex-1">
          <div className="flex min-w-0 items-center gap-2">
            <h3 id={titleId} className="truncate text-compact font-medium text-fg">{label}</h3>
            {provenance ? <ProvenancePopover label="AI-generated" {...provenance} />
              : aiGenerated && <AILabel level="message" size={16} text="AI" overview={aiNote ?? "This clip was made by an AI video model."} popoverLabel={`About the AI in ${label}`} />}
          </div>
          <p className="truncate text-small text-fg-muted">{subtitle ?? STATE_WORD[state]}{subtitle && state !== "ready" && state !== "paused" ? ` · ${STATE_WORD[state]}` : ""}</p>
        </div>
      </div>

      <WorkingEdge active={state === "generating"} label={`Generating ${label}`} className={cn("mx-auto w-full", RATIO[ratio])}>
        <div className="relative size-full overflow-hidden rounded-card bg-sunken">
          {src ? (
            <video ref={video} src={src} poster={poster} preload="metadata" playsInline muted={muted} loop={loop}
              aria-label={label} className="absolute inset-0 size-full object-contain"
              onLoadedMetadata={(e) => { setFileDur(e.currentTarget.duration); setFileState("ok"); }}
              onCanPlay={() => setFileState("ok")} onWaiting={() => setFileState("buffering")} onPlaying={() => setFileState("ok")}
              onError={() => { setFileState("error"); setPlay(false); }}
              onTimeUpdate={(e) => report(e.currentTarget.currentTime)}
              onEnded={() => { setPlay(false); setEnded(true); }}>
              {captions?.map((c) => <track key={c.src} kind="captions" src={c.src} srcLang={c.lang} label={c.label} default={captionsOn} />)}
            </video>
          ) : showPoster ? (
            <img src={showPoster} alt="" className="absolute inset-0 size-full object-cover" />
          ) : (
            <VideoScene scene={scene} time={state === "generating" ? 0 : time} duration={dur || 1} ratio={ratio}
              className={cn("absolute inset-0", (state === "generating" || state === "loading") && "opacity-50 saturate-50")} />
          )}

          {/* Pointer convenience only: the Play button below is the keyboard and screen reader control. */}
          {canPlay && (
            <button type="button" tabIndex={-1} aria-hidden data-compact
              onPointerDown={(e) => { lastPointer.current = e.pointerType; }}
              onPointerUp={onSurfaceUp}
              onClick={() => { if (lastPointer.current !== "touch") toggle(); }}
              className="group absolute inset-0 flex cursor-pointer items-center justify-center">
              <span className={cn("flex size-14 items-center justify-center rounded-full bg-overlay text-inverse-fg shadow-md transition-opacity duration-(--dur-fast) motion-reduce:transition-none",
                playing ? "opacity-0" : "opacity-100")}>
                {state === "ended" ? <RetryIcon width={24} height={24} /> : <PlayGlyph className="size-7" />}
              </span>
            </button>
          )}

          {cue && (
            <p aria-hidden className="pointer-events-none absolute inset-x-3 bottom-3 text-center">
              <span className="box-decoration-clone rounded-sm bg-overlay px-1.5 py-0.5 text-compact leading-relaxed text-inverse-fg">{cue.text}</span>
            </p>
          )}

          {state === "generating" && (
            <div className="absolute inset-0 flex items-center justify-center p-3">
              <p className="flex max-w-full items-center gap-2 rounded-control bg-glass px-3 py-1.5 text-small text-fg shadow-sm">
                <span className="min-w-0 text-center">Generating{statusNote ? `. ${statusNote}` : ""}</span>
              </p>
            </div>
          )}
          {state === "loading" && (
            <div className="absolute inset-0 flex items-center justify-center"><Spinner size={22} label="Loading" /></div>
          )}
          {state === "buffering" && (
            <div className="absolute top-2 right-2 rounded-full bg-glass p-1"><Spinner size={16} label="Buffering" /></div>
          )}
          {state === "error" && (
            <div className="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-sunken p-3 text-center">
              <AlertIcon width={20} height={20} aria-hidden className="text-danger-fg" />
              <p className="text-compact text-fg">This clip could not be loaded.{statusNote ? ` ${statusNote}` : ""}</p>
              {onRetry && <Button size="sm" variant="secondary" onClick={onRetry} leading={<RetryIcon width={14} height={14} />} className="pointer-coarse:h-11">Try again</Button>}
            </div>
          )}
        </div>
      </WorkingEdge>

      <FrameScrubber duration={canPlay ? dur : 0} value={time} onChange={(t) => seek(t)} fps={fps}
        label={`Seek, ${label}`} disabled={!canPlay}
        onScrubbingChange={(on) => { if (on && playing) setPlay(false); }}
        renderThumbnail={src ? undefined : (t) => <VideoScene scene={scene} time={t} duration={dur || 1} ratio="16:9" />} />

      <div className="flex flex-wrap items-center gap-x-1 gap-y-1 text-small text-fg-muted">
        <button type="button" onClick={toggle} aria-label="Play" aria-pressed={playing} aria-disabled={!canPlay || undefined}
          className={cn("inline-flex size-11 shrink-0 cursor-pointer items-center justify-center rounded-full bg-accent text-on-accent transition-colors duration-(--dur-instant) hover:bg-accent-hover",
            !canPlay && "cursor-default bg-sunken text-fg-subtle hover:bg-sunken")}>
          {playing ? <PauseGlyph /> : state === "ended" ? <RetryIcon width={20} height={20} aria-hidden /> : <PlayGlyph />}
        </button>
        {fps && (
          <>
            <button type="button" className={iconBtn} onClick={() => stepFrame(-1)} aria-disabled={!canPlay || frameNo === 1 || undefined}
              aria-label={canPlay && frameNo && frameNo > 1 ? `Back one frame, to frame ${frameNo - 1}` : "Back one frame"}><FrameBackGlyph /></button>
            <button type="button" className={iconBtn} onClick={() => stepFrame(1)} aria-disabled={!canPlay || frameNo === totalFrames || undefined}
              aria-label={canPlay && frameNo && frameNo < totalFrames ? `Forward one frame, to frame ${frameNo + 1}` : "Forward one frame"}><FrameFwdGlyph /></button>
          </>
        )}
        <span className="ml-1 flex-1 whitespace-nowrap">{timeText}{fps && canPlay && frameNo ? <span className="hidden sm:inline"> · frame {frameNo}</span> : null}</span>
        {/* The secondary toggles wrap together onto their own line when space runs out. */}
        <span className="ml-auto flex shrink-0 items-center gap-1">
          <button type="button" className={iconBtn} onClick={() => setLoop((l) => !l)} aria-pressed={loop} aria-label="Loop"><LoopIcon width={18} height={18} aria-hidden /></button>
          <button type="button" className={iconBtn} onClick={toggleMute} aria-pressed={hasAudio ? muted : undefined} aria-disabled={!hasAudio || undefined}
            aria-label={hasAudio ? "Mute" : "No sound in this clip"} title={hasAudio ? undefined : "No sound in this clip"}><SoundGlyph off={muted} /></button>
          {hasCaptions && (
            <button type="button" className={iconBtn} onClick={() => setCaptionsOn((c) => !c)} aria-pressed={captionsOn} aria-label="Captions"><CaptionsGlyph /></button>
          )}
          {canFull && <button type="button" className={iconBtn} onClick={fullscreen} aria-label="Full screen"><FullGlyph /></button>}
        </span>
      </div>

      {showFrameActions && (
        <div role="group" aria-label={`Actions for the paused frame${frameNo ? `, frame ${frameNo}` : ""}`}
          className="flex flex-wrap items-center gap-2 rounded-field bg-sunken px-3 py-2 motion-safe:animate-fade">
          <span className="min-w-0 flex-1 basis-full text-small text-fg-muted tabular-nums sm:basis-auto">
            Paused at {formatMediaTime(time)}{frameNo ? `, frame ${frameNo}` : ""}
          </span>
          {frameActions!.map((a) => (
            <Button key={a.id} size="sm" variant="secondary" leading={a.icon} className="pointer-coarse:h-11"
              onClick={() => onFrameAction?.(a.id, timeRef.current, fps ? frameAt(timeRef.current, fps) : undefined)}>
              {a.label}
            </Button>
          ))}
        </div>
      )}

      {transcript && (
        <Collapsible header={<span className="text-small font-medium text-fg">Transcript</span>} className="border-t border-line pt-2">
          <div className="pt-2 pl-6 text-compact text-fg">{transcript}</div>
        </Collapsible>
      )}
      <Collapsible header={<span className="text-small font-medium text-fg">Keyboard shortcuts</span>} className={cn(!transcript && "border-t border-line pt-2")}>
        <div className="flex flex-col gap-2 pt-2 pl-6 text-small text-fg-muted">
          <p>These work while focus is inside the player, and never while typing.</p>
          <ul className="grid gap-1 sm:grid-cols-2">
            <li className="flex items-center gap-2"><Kbd>Space</Kbd><Kbd>K</Kbd> Play or pause</li>
            <li className="flex items-center gap-2"><Kbd>←</Kbd><Kbd>→</Kbd> Back or forward 1 second</li>
            {fps && <li className="flex items-center gap-2"><Kbd>,</Kbd><Kbd>.</Kbd> or Shift with an arrow: one frame</li>}
            <li className="flex items-center gap-2"><Kbd>Home</Kbd><Kbd>End</Kbd> Start or end</li>
            <li className="flex items-center gap-2"><Kbd>M</Kbd> Mute</li>
            {hasCaptions && <li className="flex items-center gap-2"><Kbd>C</Kbd> Captions</li>}
          </ul>
          <Switch checked={keysOn} onChange={setKeysOn} label="Single-key shortcuts">Single-key shortcuts (K, M, C, comma and period)</Switch>
        </div>
      </Collapsible>

      <span role="status" className="sr-only">{said}</span>
    </section>
  );
}
```

## tattva/molecules/VoiceCard.tsx

```tsx
import { useCallback, useEffect, useId, useRef, useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, CheckIcon, HistoryIcon, LockIcon, ShieldIcon, StopIcon } from "../lib/icons";
import { Badge } from "../atoms/Badge";

export type VoiceSource = "builtIn" | "designed" | "cloned" | "library";
export type VoiceConsent = "self-verified" | "licensed" | "none";
export type VoiceStatus = "available" | "withdrawing" | "withdrawn" | "locked";
export type PreviewState = "idle" | "loading" | "playing" | "error";

export interface VoicePreview {
  state: PreviewState;
  /** Starts the sample, or stops it when it is loading or playing. */
  onToggle: () => void;
  /** Seconds played so far, for the readout while playing. */
  elapsed?: number;
  /** Length of the sample in seconds. */
  duration?: number;
}

export interface VoiceCardProps {
  name: string;
  /** One short line about how it sounds, for example "Warm, unhurried, good for long reads". */
  description?: string;
  /** Short tags such as accent, age range or use case. */
  tags?: string[];
  /** Where the voice came from. Built in, designed from a description, cloned from a recording, or shared in a library. */
  source: VoiceSource;
  /** Person the voice belongs to. For a cloned voice, leave it out when it is the person's own voice. */
  owner?: string;
  /** How the owner agreed. "none" on a cloned voice asks the person to verify it. */
  consent?: VoiceConsent;
  /** The sample player. Never starts by itself. */
  preview?: VoicePreview;
  status?: VoiceStatus;
  /** Date the voice stops working, already written out by your code, for example "3 March 2027". Shown when withdrawing. */
  withdrawsOn?: string;
  /** Why a locked voice cannot be chosen, for example "Needs the Studio plan". */
  lockedReason?: string;
  /** Shows the card as chosen. Only with onSelect. */
  selected?: boolean;
  /** Makes the card a radio. Leave out for a card that only plays its sample. */
  onSelect?: () => void;
  /** Radio group name. VoicePicker sets it. */
  groupName?: string;
  className?: string;
}

const sourceText: Record<VoiceSource, string> = { builtIn: "Built in", designed: "Designed", cloned: "Cloned", library: "From the library" };

/** "0:07" for seven seconds. */
export const formatSeconds = (s: number) => {
  const t = Math.max(0, Math.floor(s));
  return `${Math.floor(t / 60)}:${String(t % 60).padStart(2, "0")}`;
};

function ownershipLine(source: VoiceSource, owner?: string, consent?: VoiceConsent): { text: string; needsYou?: boolean } | null {
  if (source === "cloned") {
    if (consent === "none") return { text: owner ? `Cloned from ${owner}. Not verified yet` : "Not verified yet. Verify it to use it", needsYou: true };
    if (!owner) return { text: "Your voice, verified" };
    return { text: consent === "licensed" ? `${owner}'s voice, licensed` : `${owner}'s voice, verified` };
  }
  if (source === "library" && owner) return { text: consent === "licensed" ? `Shared by ${owner}, licensed` : `Shared by ${owner}` };
  if (owner) return { text: `Made by ${owner}` };
  return null;
}

/** Three small bars that move only while the sample plays. Under reduced motion they hold still at mid height. */
function PreviewBars({ playing, tick }: { playing: boolean; tick: number }) {
  const shape = [0.5, 0.9, 0.65, 0.8];
  return (
    <span aria-hidden className="flex h-4 items-center gap-0.5">
      {shape.map((h, i) => {
        const live = playing ? 0.35 + (((tick + i * 3) * 37) % 65) / 100 : h * 0.5;
        return (
          <span key={i} style={{ ["--lvl" as string]: String(live) }}
            className="h-4 w-0.5 origin-center scale-y-50 rounded-full bg-fg-muted motion-safe:scale-y-(--lvl) motion-safe:transition-transform motion-safe:duration-(--dur-fast)" />
        );
      })}
    </span>
  );
}

const PlayGlyph = (p: { className?: string }) => (
  <svg aria-hidden width={16} height={16} viewBox="0 0 24 24" fill="currentColor" className={p.className}><path d="M8 5.5v13a1 1 0 0 0 1.5.9l10.2-6.5a1 1 0 0 0 0-1.7L9.5 4.6A1 1 0 0 0 8 5.5z" /></svg>
);

/**
 * One voice: its name, how it sounds, who it belongs to and whether it can be used, with a sample button.
 * Choosing the voice and playing its sample are separate controls. Give it onSelect to make it a radio inside VoicePicker.
 */
export function VoiceCard({
  name, description, tags, source, owner, consent, preview, status = "available", withdrawsOn,
  lockedReason = "Not on your plan", selected = false, onSelect, groupName, className,
}: VoiceCardProps) {
  const id = useId();
  const own = ownershipLine(source, owner, consent);
  const withdrawn = status === "withdrawn";
  const locked = status === "locked";
  const blocked = withdrawn || locked;
  const pState = preview?.state ?? "idle";
  const busy = pState === "loading" || pState === "playing";
  const tick = Math.round((preview?.elapsed ?? 0) * 4);

  const previewLabel = pState === "playing" ? `Stop sample of ${name}` : pState === "loading" ? `Stop loading sample of ${name}`
    : pState === "error" ? `Try the sample of ${name} again` : `Play sample of ${name}`;
  // Said once per change of state, never per second.
  const announce = pState === "playing" ? `Playing sample of ${name}` : pState === "loading" ? `Loading sample of ${name}`
    : pState === "error" ? `The sample of ${name} did not load` : "";

  const statusLine: ReactNode =
    status === "withdrawing" ? <><HistoryIcon width={14} height={14} className="shrink-0" />{withdrawsOn ? `Available until ${withdrawsOn}` : "Being withdrawn by its owner"}</>
    : withdrawn ? <><XCircle />Withdrawn by its owner. No longer available</>
    : locked ? <><LockIcon width={14} height={14} className="shrink-0" />{lockedReason}</>
    : null;

  const heading = onSelect
    ? <label htmlFor={`${id}-r`} className={cn("text-body leading-5 font-medium text-fg after:absolute after:inset-0 after:rounded-card after:content-['']", blocked ? "cursor-not-allowed" : "cursor-pointer")}>{name}</label>
    : <p className="text-body leading-5 font-medium text-fg">{name}</p>;

  return (
    <div className={cn(
      "relative flex items-start gap-3 rounded-card border bg-surface p-3 transition-colors duration-(--dur-fast) ease-(--ease-out)",
      selected ? "border-fg" : "border-line", onSelect && !blocked && !selected && "hover:border-line-strong",
      "has-[input:focus-visible]:outline-2 has-[input:focus-visible]:outline-offset-2 has-[input:focus-visible]:outline-accent",
      className,
    )}>
      {onSelect && (
        <span className="relative mt-0.5 inline-grid size-5 shrink-0 place-items-center">
          <input id={`${id}-r`} type="radio" name={groupName} checked={selected} disabled={blocked}
            aria-describedby={`${id}-d`} onChange={() => onSelect()}
            className={cn("peer size-5 appearance-none rounded-full border bg-surface outline-none transition-colors duration-(--dur-fast) checked:border-accent forced-colors:border-[ButtonText] forced-colors:checked:border-[Highlight]",
              "border-line-strong", blocked ? "cursor-not-allowed opacity-(--disabled-opacity)" : "cursor-pointer")} />
          <span aria-hidden className="pointer-events-none absolute size-2.5 scale-0 rounded-full bg-accent transition-transform duration-(--dur-fast) peer-checked:scale-100 forced-colors:bg-[Highlight]" />
        </span>
      )}

      <div className="min-w-0 flex-1">
        <div className="flex flex-wrap items-center gap-x-2 gap-y-1">
          {heading}
          {selected && <span className="inline-flex items-center gap-1 text-small leading-4.5 text-fg"><CheckIcon width={12} height={12} />Selected</span>}
        </div>
        <div id={`${id}-d`} className={cn("mt-0.5 space-y-1", blocked && "text-fg-subtle")}>
          {description && <p className="text-compact text-fg-muted">{description}</p>}
          <p className="flex flex-wrap items-center gap-1">
            <Badge>{sourceText[source]}</Badge>
            {tags?.map((t) => <Badge key={t}>{t}</Badge>)}
          </p>
          {own && (
            <p className={cn("flex items-center gap-1 text-small leading-4.5", own.needsYou ? "text-warning-fg" : "text-fg-muted")}>
              {own.needsYou ? <AlertIcon width={14} height={14} className="shrink-0" /> : <ShieldIcon width={14} height={14} className="shrink-0" />}{own.text}
            </p>
          )}
          {statusLine && <p className="flex items-center gap-1 text-small leading-4.5 text-fg-muted">{statusLine}</p>}
          {withdrawn && selected && (
            <p className="rounded-control bg-warning-soft px-2 py-1 text-small leading-4.5 text-warning-fg">You are still on this voice. Pick another one.</p>
          )}
          {preview && pState !== "idle" && (
            <div className="flex items-center gap-2 pt-0.5 text-small leading-4.5 text-fg-muted" aria-hidden>
              {pState === "loading" && <span>Loading sample…</span>}
              {pState === "error" && <span className="flex items-center gap-1 text-danger-fg"><AlertIcon width={14} height={14} />Sample did not load</span>}
              {pState === "playing" && (
                <>
                  <PreviewBars playing tick={tick} />
                  <span className="tabular-nums">Playing {formatSeconds(preview.elapsed ?? 0)}{preview.duration ? ` of ${formatSeconds(preview.duration)}` : ""}</span>
                  {preview.duration ? (
                    <span className="h-1 min-w-8 flex-1 overflow-hidden rounded-full bg-line">
                      <span className="block h-full origin-left rounded-full bg-fg" style={{ transform: `scaleX(${Math.min(1, (preview.elapsed ?? 0) / preview.duration)})` }} />
                    </span>
                  ) : null}
                </>
              )}
            </div>
          )}
        </div>
      </div>

      {preview && !withdrawn && (
        <button type="button" onClick={preview.onToggle} aria-label={previewLabel} title={previewLabel}
          className={cn("relative z-10 inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full border transition-colors duration-(--dur-fast) pointer-coarse:size-11",
            busy ? "border-inverse bg-inverse text-inverse-fg" : "border-line bg-surface text-fg hover:bg-hover")}>
          {pState === "playing" ? <StopIcon width={14} height={14} />
            : pState === "loading" ? <svg aria-hidden viewBox="0 0 24 24" className="size-4 motion-safe:animate-spin"><circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" strokeOpacity="0.3" strokeWidth="3" /><path d="M21 12a9 9 0 0 0-9-9" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" /></svg>
            : pState === "error" ? <AlertIcon width={16} height={16} />
            : <PlayGlyph />}
        </button>
      )}
      <span role="status" className="sr-only">{announce}</span>
    </div>
  );
}

const XCircle = () => (
  <svg aria-hidden width={14} height={14} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" className="shrink-0"><circle cx="12" cy="12" r="9" /><path d="M9 9l6 6M15 9l-6 6" /></svg>
);

/* ---------- simulated sample playback ---------- */

export interface SampleSpec {
  /** Length of the sample in seconds. */
  seconds: number;
  /** Makes the sample fail to load, to show the error state. */
  fails?: boolean;
}

export interface SamplePlayback {
  /** Voice whose sample is loading, playing or failed. Null when nothing is. */
  id: string | null;
  state: PreviewState;
  elapsed: number;
  /** Plays the sample for this id, or stops it if it is the one already going. Starting one stops any other. */
  toggle: (id: string, spec: SampleSpec) => void;
  stop: () => void;
  /** The preview prop for one card. */
  previewFor: (id: string, spec: SampleSpec) => VoicePreview;
}

/**
 * Stand-in for real audio: a short load, then a timer that counts up to the sample length. Only one sample plays at a time.
 * Swap it for your own player and pass the same preview shape to each card.
 */
export function useSimulatedSamples({ loadMs = 600, tickMs = 250 }: { loadMs?: number; tickMs?: number } = {}): SamplePlayback {
  const [id, setId] = useState<string | null>(null);
  const [state, setState] = useState<PreviewState>("idle");
  const [elapsed, setElapsed] = useState(0);
  const timers = useRef<number[]>([]);
  const clear = () => { timers.current.forEach((t) => window.clearTimeout(t)); timers.current.forEach((t) => window.clearInterval(t)); timers.current = []; };
  useEffect(() => clear, []);

  const stop = useCallback(() => { clear(); setId(null); setState("idle"); setElapsed(0); }, []);

  const toggle = useCallback((next: string, spec: SampleSpec) => {
    if (next === id && (state === "loading" || state === "playing")) { stop(); return; }
    clear();
    setId(next); setState("loading"); setElapsed(0);
    timers.current.push(window.setTimeout(() => {
      if (spec.fails) { setState("error"); return; }
      setState("playing");
      let t = 0;
      const iv = window.setInterval(() => {
        t += tickMs / 1000;
        if (t >= spec.seconds) { clear(); setId(null); setState("idle"); setElapsed(0); return; }
        setElapsed(t);
      }, tickMs);
      timers.current.push(iv);
    }, loadMs));
  }, [id, state, stop, loadMs, tickMs]);

  const previewFor = (v: string, spec: SampleSpec): VoicePreview => ({
    state: v === id ? state : "idle",
    elapsed: v === id ? elapsed : 0,
    duration: spec.seconds,
    onToggle: () => toggle(v, spec),
  });

  return { id, state, elapsed, toggle, stop, previewFor };
}
```

## tattva/organisms/ActionDraft.tsx

```tsx
import { useId, useRef } from "react";
import type { ReactNode } from "react";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { Spinner } from "../atoms/Spinner";
import { InlineEdit } from "../molecules/InlineEdit";
import { cn } from "../lib/cn";
import { useFocusAfter } from "../lib/useFocusAfter";
import { AlertIcon, CheckIcon, RefreshIcon, XIcon } from "../lib/icons";

export interface ActionDraftField {
  id: string;
  label: string;
  value: string;
  kind?: "text" | "number";
  editable?: boolean;
  /** Shown after the value, such as "shares". */
  unit?: string;
  /** A message in words. While any field has one, Confirm is disabled. */
  error?: string;
}
export interface ActionDraftStep {
  id: string;
  label: string;
  state: "pending" | "running" | "done" | "failed";
  detail?: string;
}
export type ActionDraftStatus = "draft" | "confirming" | "working" | "done" | "failed";

export interface ActionDraftProps {
  status: ActionDraftStatus;
  /** What will happen, in one line, such as "Buy 10 RELIANCE at market". */
  title: string;
  summary?: string;
  fields: ActionDraftField[];
  onFieldChange: (id: string, value: string) => void;
  /** Facts the app worked out, such as margin and charges. Read only. */
  rows?: { label: string; value: ReactNode }[];
  /** Progress once confirmed. */
  steps?: ActionDraftStep[];
  /** Shown when status is done. */
  result?: ReactNode;
  /** Shown in words when status is failed. */
  error?: string;
  onConfirm: () => void;
  onDiscard: () => void;
  confirmLabel?: string;
  discardLabel?: string;
  className?: string;
}

const STATUS: Record<ActionDraftStatus, { text: string; tone: "neutral" | "unsure" | "success" | "danger" }> = {
  draft: { text: "Draft", tone: "neutral" },
  confirming: { text: "Confirming", tone: "unsure" },
  working: { text: "In progress", tone: "unsure" },
  done: { text: "Done", tone: "success" },
  failed: { text: "Failed", tone: "danger" },
};

function StepIcon({ state }: { state: ActionDraftStep["state"] }) {
  const words = { pending: "Waiting", running: "In progress", done: "Done", failed: "Failed" }[state];
  return (
    <span className="mt-0.5 flex size-5 shrink-0 items-center justify-center" >
      {state === "pending" && <span aria-hidden className="size-3.5 rounded-full border border-line-strong forced-colors:border-[CanvasText]" />}
      {state === "running" && <RefreshIcon aria-hidden width={16} height={16} className="text-fg-muted motion-safe:animate-spin" />}
      {state === "done" && <CheckIcon aria-hidden width={16} height={16} className="text-success-fg" />}
      {state === "failed" && <XIcon aria-hidden width={16} height={16} className="text-danger-fg" />}
      <span className="sr-only">{words}. </span>
    </span>
  );
}

/**
 * The draft an AI proposes. The person reads it, edits what is wrong in place and confirms. The same card then shows
 * progress and the result. Nothing runs until Confirm is pressed, and this part never confirms by itself.
 */
export function ActionDraft({ status, title, summary, fields, onFieldChange, rows, steps, result, error, onConfirm, onDiscard, confirmLabel = "Confirm", discardLabel = "Not now", className }: ActionDraftProps) {
  const uid = useId();
  const titleId = `${uid}-title`, noteId = `${uid}-note`;
  const heading = useRef<HTMLHeadingElement>(null);
  const originals = useRef<Record<string, string>>({});
  for (const f of fields) if (!(f.id in originals.current)) originals.current[f.id] = f.value;
  const arm = useFocusAfter(status, () => heading.current);

  const isDraft = status === "draft";
  const blocked = fields.some((f) => !!f.error);
  const s = STATUS[status];
  const running = steps?.find((x) => x.state === "running");
  const live = status === "working" ? `In progress${running ? `: ${running.label}` : ""}.` : status === "done" ? "Done." : status === "confirming" ? "Confirming." : "";

  return (
    <section aria-labelledby={titleId} className={cn("flex w-full max-w-phone flex-col gap-3 rounded-card border border-line bg-surface p-4", className)}>
      <div className="flex items-start justify-between gap-3">
        <h3 id={titleId} ref={heading} tabIndex={-1} className="text-lead leading-6 font-semibold text-fg">{title}</h3>
        <Badge tone={s.tone} className="shrink-0">
          {status === "done" && <CheckIcon aria-hidden width={12} height={12} />}
          {status === "failed" && <XIcon aria-hidden width={12} height={12} />}
          {(status === "working" || status === "confirming") && <RefreshIcon aria-hidden width={12} height={12} className="motion-safe:animate-spin" />}
          {s.text}
        </Badge>
      </div>
      {summary && <p className="text-body leading-5 text-fg-muted">{summary}</p>}

      <div>
        {fields.map((f) => {
          const shown = f.unit ? `${f.value} ${f.unit}` : f.value;
          const edited = f.value !== originals.current[f.id];
          return (
            <div key={f.id} className="border-b border-line last:border-b-0">
              {isDraft && f.editable ? (
                <InlineEdit label={f.label} value={f.value} display={shown} originalValue={originals.current[f.id]} inputMode={f.kind === "number" ? "decimal" : "text"}
                  onChange={(v) => onFieldChange(f.id, v)} className="border-b-0" />
              ) : (
                <div className="py-3">
                  <div className="flex items-center gap-2">
                    <span className="text-small leading-4 text-fg-muted">{f.label}</span>
                    {edited && <span className="rounded-control bg-sunken px-2 py-0.5 text-small leading-4 font-medium text-fg-muted forced-colors:border forced-colors:border-[CanvasText]">Edited</span>}
                  </div>
                  <p className="mt-0.5 text-lead leading-6 font-medium text-fg">{shown || <span className="font-normal text-fg-muted">Not set</span>}</p>
                </div>
              )}
              {f.error && <p className="-mt-1 pb-3 flex items-start gap-1.5 text-small leading-4 text-danger-fg"><AlertIcon className="mt-px shrink-0" width={14} height={14} />{f.error}</p>}
            </div>
          );
        })}
      </div>

      {rows && rows.length > 0 && (
        <dl className="m-0 flex flex-col gap-1.5 rounded-card bg-sunken p-3 text-body leading-5">
          {rows.map((r) => (
            <div key={r.label} className="flex items-baseline justify-between gap-3">
              <dt className="text-fg-muted">{r.label}</dt>
              <dd className="m-0 font-medium text-fg tabular-nums">{r.value}</dd>
            </div>
          ))}
        </dl>
      )}

      {!isDraft && steps && steps.length > 0 && (
        <ol className="m-0 flex list-none flex-col gap-2 p-0">
          {steps.map((x) => (
            <li key={x.id} className="flex items-start gap-2.5 text-body leading-5">
              <StepIcon state={x.state} />
              <span className="flex min-w-0 flex-col">
                <span className={cn("text-fg", x.state === "pending" && "text-fg-muted")}>{x.label}</span>
                {x.detail && <span className="text-small leading-4 text-fg-muted">{x.detail}</span>}
              </span>
            </li>
          ))}
        </ol>
      )}

      {status === "done" && result != null && (
        <div className="flex items-start gap-2 rounded-card bg-success-soft p-3 text-body leading-5 text-success-fg">
          <CheckIcon aria-hidden className="mt-0.5 shrink-0" width={16} height={16} />
          <div className="min-w-0">{result}</div>
        </div>
      )}
      {status === "failed" && (
        <div role="alert" className="flex items-start gap-2 rounded-card bg-danger-soft p-3 text-body leading-5 text-danger-fg">
          <AlertIcon aria-hidden className="mt-0.5 shrink-0" width={16} height={16} />
          <p className="min-w-0">{error ?? "Something went wrong."}</p>
        </div>
      )}

      {(isDraft || status === "confirming") && (
        <div className="flex flex-col gap-2">
          <p id={noteId} className="text-body leading-5 text-fg-muted">
            {status === "confirming" ? "Confirming. Please wait." : blocked ? "Fix the fields marked above to continue." : "Nothing happens until you confirm."}
          </p>
          <div className="flex gap-2">
            <Button size="lg" className="flex-1 pointer-coarse:h-12" aria-describedby={noteId} disabled={!isDraft || blocked}
              leading={status === "confirming" ? <Spinner size={16} label="Confirming" /> : undefined} onClick={() => { arm(); onConfirm(); }}>
              {confirmLabel}
            </Button>
            <Button size="lg" variant="secondary" className="flex-1 pointer-coarse:h-12" disabled={!isDraft} onClick={() => { arm(); onDiscard(); }}>
              {discardLabel}
            </Button>
          </div>
        </div>
      )}
      <p role="status" aria-live="polite" className="sr-only">{live}</p>
    </section>
  );
}
```

## tattva/organisms/ActionLog.tsx

```tsx
import { useId, useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, CheckIcon, HistoryIcon, UndoIcon, XIcon } from "../lib/icons";
import { ApprovedByMark, type ApprovedBy } from "../atoms/ApprovedByMark";
import { Badge, type Tone } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { SegmentedControl } from "../atoms/SegmentedControl";

export type ActionStatus = "done" | "failed" | "undone" | "waiting";

export interface ActionLogItem {
  id: string;
  /** What was done, in plain words, such as "Moved 12 files to Archive". */
  action: string;
  /** Who did it, such as "Travel helper". */
  actor: string;
  status: ActionStatus;
  /** When, as text, such as "10:42" or "2 minutes ago". */
  time: string;
  approvedBy?: ApprovedBy;
  /** Minutes left to undo. Omit when it was never undoable and give notUndoableReason instead. 0 means the time is over. */
  undoMinutes?: number;
  /** Why this cannot be undone, such as "The email was already sent." */
  notUndoableReason?: string;
}

export interface ActionLogLabels {
  filter?: string;
  all?: string;
  done?: string;
  failed?: string;
  undone?: string;
  waiting?: string;
  undo?: string;
  empty?: string;
  cannotUndo?: string;
  undoEnded?: string;
  /** Receives the minutes left. */
  undoFor?: (minutes: number) => string;
}

export interface ActionLogProps {
  items: ActionLogItem[];
  /** Called when the person presses Undo. The row then shows Undone. */
  onUndo?: (id: string) => void;
  /** Starting filter. */
  defaultFilter?: ActionStatus | "all";
  labels?: ActionLogLabels;
  className?: string;
}

const tone: Record<ActionStatus, Tone> = { done: "success", failed: "danger", undone: "neutral", waiting: "info" };
const statusIcon: Record<ActionStatus, ReactNode> = {
  done: <CheckIcon width={11} height={11} />, failed: <XIcon width={11} height={11} />,
  undone: <UndoIcon width={11} height={11} />, waiting: <HistoryIcon width={11} height={11} />,
};
const statuses: ActionStatus[] = ["done", "failed", "undone", "waiting"];

/** Lasting record of what an agent did. Each row shows who did it, how it ended, who approved it, and Undo while time remains. */
export function ActionLog({ items, onUndo, defaultFilter = "all", labels, className }: ActionLogProps) {
  const l = {
    filter: "Show actions", all: "All", done: "Done", failed: "Failed", undone: "Undone", waiting: "Waiting", undo: "Undo",
    empty: "No actions match this filter.", cannotUndo: "Can't be undone", undoEnded: "Undo time is over",
    undoFor: (m: number) => `Undo for ${m} more ${m === 1 ? "minute" : "minutes"}`, ...labels,
  };
  const [filter, setFilter] = useState<ActionStatus | "all">(defaultFilter);
  const [undoneIds, setUndoneIds] = useState<string[]>([]);
  const [message, setMessage] = useState("");
  const uid = useId();
  const rows = items.map((i) => (undoneIds.includes(i.id) ? { ...i, status: "undone" as const } : i));
  const count = (s: ActionStatus) => rows.filter((r) => r.status === s).length;
  const shown = filter === "all" ? rows : rows.filter((r) => r.status === filter);
  const summary = `${rows.length} ${rows.length === 1 ? "action" : "actions"}: ${statuses.filter((s) => count(s) > 0).map((s) => `${count(s)} ${l[s].toLowerCase()}`).join(", ") || "none yet"}`;
  const name = (s: ActionStatus) => l[s];

  const undo = (r: ActionLogItem) => {
    setUndoneIds((x) => [...x, r.id]);
    setMessage(`Undone: ${r.action}`);
    onUndo?.(r.id);
  };
  const choose = (v: ActionStatus | "all") => {
    setFilter(v);
    const n = v === "all" ? rows.length : rows.filter((r) => r.status === v).length;
    setMessage(`${n} ${n === 1 ? "action" : "actions"} shown`);
  };

  return (
    <section aria-label="Action log" className={cn("rounded-card border border-line bg-surface p-4", className)}>
      <div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-2">
        <p className="min-w-0 text-body leading-5 font-medium">{summary}</p>
        <div className="max-w-full overflow-x-auto">
          <SegmentedControl size="sm" label={l.filter} value={filter} onChange={choose}
            options={[{ value: "all", label: `${l.all} ${rows.length}` }, ...statuses.map((s) => ({ value: s, label: `${l[s]} ${count(s)}` }))]} />
        </div>
      </div>
      {shown.length === 0 ? (
        <p className="mt-3 text-body leading-5 text-fg-muted">{l.empty}</p>
      ) : (
        <ul className="mt-3 space-y-1.5">
          {shown.map((r) => (
            <li key={r.id} className="flex flex-wrap items-start gap-x-3 gap-y-2 rounded-card border border-line px-3 py-2.5">
              <div className="min-w-0 flex-1 basis-48">
                <p className="text-body leading-5 break-words">{r.action}</p>
                <p className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-small leading-4 text-fg-muted">
                  <span>{r.actor}</span><span aria-hidden>·</span><span>{r.time}</span>
                </p>
                {r.approvedBy && <ApprovedByMark by={r.approvedBy} className="mt-1" />}
              </div>
              <div className="ml-auto flex flex-wrap items-center justify-end gap-2">
                <Badge tone={tone[r.status]}>{statusIcon[r.status]}{name(r.status)}</Badge>
                {r.status === "done" && (
                  r.notUndoableReason ? (
                    <p id={`${uid}-${r.id}`} className="flex max-w-56 items-start gap-1 text-small leading-4 text-fg-muted">
                      <AlertIcon width={13} height={13} className="mt-0.5 shrink-0" /><span><span className="font-medium text-fg">{l.cannotUndo}.</span> {r.notUndoableReason}</span>
                    </p>
                  ) : r.undoMinutes && r.undoMinutes > 0 ? (
                    <Button size="sm" variant="secondary" leading={<UndoIcon width={13} height={13} />} onClick={() => undo(r)} aria-label={`${l.undo}: ${r.action}. ${l.undoFor(r.undoMinutes)}`}>{l.undoFor(r.undoMinutes)}</Button>
                  ) : r.undoMinutes === 0 ? (
                    <span className="text-small leading-4 text-fg-muted">{l.undoEnded}</span>
                  ) : null
                )}
              </div>
            </li>
          ))}
        </ul>
      )}
      <p role="status" aria-live="polite" className="sr-only">{message}</p>
    </section>
  );
}
```

## tattva/organisms/ActivityTrace.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { CheckIcon, ChevronIcon, XIcon } from "../lib/icons";
import { Badge } from "../atoms/Badge";

export type TraceActor = "ai" | "rule" | "calculated" | "check" | "you";
export type TraceStatus = "pending" | "running" | "done" | "failed";

export interface TraceStep {
  id: string;
  actor: TraceActor;
  title: string;
  detail?: ReactNode;
  /** Default "done". */
  status?: TraceStatus;
}

export interface ActivityTraceProps {
  steps: TraceStep[];
  /** True while steps are still arriving. The list stays open and each new row is announced. */
  streaming?: boolean;
  /** The one line shown when folded, such as "Read your message · 8 steps". */
  summary: string;
  defaultOpen?: boolean;
  /** Controlled open state. */
  open?: boolean;
  onOpenChange?: (open: boolean) => void;
  /** Rename the tags, for another language or wording. */
  actorLabels?: Partial<Record<TraceActor, string>>;
  className?: string;
}

const defaultLabels: Record<TraceActor, string> = { ai: "AI", rule: "Rule sheet", calculated: "Calculated", check: "Safety check", you: "You" };
const statusWords: Record<TraceStatus, string> = { pending: "Waiting", running: "Running", done: "Done", failed: "Failed" };

function StatusIcon({ status }: { status: TraceStatus }) {
  return (
    <span className="mt-0.5 flex size-5 shrink-0 items-center justify-center">
      {status === "running" ? (
        <svg aria-hidden width={16} height={16} viewBox="0 0 24 24" className="text-fg-muted motion-safe:animate-spin">
          <circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" strokeOpacity="0.25" strokeWidth="3" />
          <path d="M21 12a9 9 0 0 0-9-9" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" />
        </svg>
      ) : status === "done" ? <CheckIcon aria-hidden width={16} height={16} className="text-success-fg" />
        : status === "failed" ? <XIcon aria-hidden width={16} height={16} className="text-danger-fg" />
        : <span aria-hidden className="size-2.5 rounded-full border border-line-strong forced-colors:border-[CanvasText]" />}
      <span className="sr-only">{statusWords[status]}</span>
    </span>
  );
}

/**
 * The agent's work shown step by step, then folded into one line. Each step carries a tag that says who did it:
 * AI (a judgment that can be wrong), Rule sheet (looked up and checked), Calculated (done by code), Safety check
 * (a guardrail), or You. While streaming the list is open; when the work is done the parent sets streaming to false and the person can fold it.
 */
export function ActivityTrace({ steps, streaming, summary, defaultOpen = false, open, onOpenChange, actorLabels, className }: ActivityTraceProps) {
  const uid = useId();
  const [inner, setInner] = useState(defaultOpen);
  const isOpen = streaming || (open ?? inner);
  const labels = { ...defaultLabels, ...actorLabels };
  const toggle = () => { if (streaming) return; const v = !isOpen; if (open === undefined) setInner(v); onOpenChange?.(v); };

  // Announce each new step once. The first render is not announced, so a trace that mounts full stays quiet.
  const seen = useRef(steps.length);
  const [said, setSaid] = useState("");
  useEffect(() => {
    if (steps.length > seen.current && streaming) { const s = steps[steps.length - 1]; setSaid(`${labels[s.actor]}: ${s.title}`); }
    seen.current = steps.length;
    // labels is rebuilt each render; steps.length is the real trigger.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [steps.length, streaming]);

  return (
    <div className={cn("rounded-card border border-line bg-surface", className)}>
      <button type="button" aria-expanded={isOpen} aria-controls={`${uid}-list`} aria-disabled={streaming || undefined} onClick={toggle}
        className={cn("flex min-h-10 w-full items-center gap-2 rounded-card px-4 py-2 text-left text-body leading-5 text-fg-muted pointer-coarse:min-h-11", streaming ? "cursor-default" : "cursor-pointer hover:bg-hover hover:text-fg")}>
        <ChevronIcon aria-hidden className={cn("shrink-0 text-fg-subtle transition-transform duration-[var(--dur-fast)]", isOpen && "rotate-90")} />
        <span className="min-w-0 flex-1 break-words">{summary}</span>
      </button>
      {isOpen && (
        <ol id={`${uid}-list`} className="m-0 list-none space-y-3 border-t border-line px-4 py-3">
          {steps.map((s) => {
            const status = s.status ?? "done";
            return (
              <li key={s.id} className={cn("flex gap-3", streaming && "motion-safe:animate-rise")}>
                <StatusIcon status={status} />
                <div className="min-w-0 flex-1">
                  <p className="flex flex-wrap items-center gap-x-2 gap-y-1 text-body leading-5">
                    <span className={cn("font-medium break-words", status === "pending" && "text-fg-muted", status === "failed" && "text-danger-fg")}>{s.title}</span>
                    <Badge tone={s.actor === "ai" ? "accent" : "neutral"}>{labels[s.actor]}</Badge>
                    {status === "failed" && <span className="text-small leading-4 text-danger-fg">Failed</span>}
                  </p>
                  {s.detail && <div className="mt-0.5 text-small leading-4 text-fg-muted break-words">{s.detail}</div>}
                </div>
              </li>
            );
          })}
        </ol>
      )}
      <p role="status" aria-live="polite" className="sr-only">{said}</p>
    </div>
  );
}
```

## tattva/organisms/AgentQuestionCard.tsx

```tsx
import { useEffect, useId, useRef, useState, type FormEvent } from "react";
import { cn } from "../lib/cn";
import { useFocusAfter } from "../lib/useFocusAfter";
import { CheckIcon, LockIcon } from "../lib/icons";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";

export type QuestionReason = "approach" | "information" | "sign-in" | "risky-step";
export type QuestionAnswerKind = "option" | "text" | "agent-decides" | "skipped";

export interface QuestionOption {
  id: string;
  label: string;
  /** One short line that tells the options apart. */
  description?: string;
}

export interface QuestionAnswer {
  kind: QuestionAnswerKind;
  /** Option id for "option", the typed text for "text", empty otherwise. */
  value: string;
  /** What the summary shows, such as the option label. */
  label: string;
}

export interface AgentQuestionCardLabels {
  reasons?: Partial<Record<QuestionReason, string>>;
  textLabel?: string;
  textPlaceholder?: string;
  send?: string;
  letAgentDecide?: string;
  skip?: string;
  signInHint?: string;
  answeredHeading?: string;
  agentDecided?: string;
  skipped?: string;
  change?: string;
}

export interface AgentQuestionCardProps {
  question: string;
  reason: QuestionReason;
  /** Two to four choices. More are cut to four. */
  options?: QuestionOption[];
  /** Show the free-text answer. Default true, except for sign-in questions, which never ask for a secret in text. */
  allowText?: boolean;
  /** Answer (controlled). Omit while open. */
  answer?: QuestionAnswer | null;
  onAnswer?: (answer: QuestionAnswer) => void;
  /** Called when the person chooses Change on the summary. */
  onReopen?: () => void;
  /** Name of the run that asked. */
  runName?: string;
  labels?: AgentQuestionCardLabels;
  className?: string;
}

const reasonDefaults: Record<QuestionReason, string> = {
  approach: "Choose an approach", information: "Needs information", "sign-in": "Needs a sign-in or secret", "risky-step": "Confirm a risky step",
};

/**
 * The agent asks a question in the middle of a task. A reason chip says why it is asking, option
 * buttons give quick answers, and a text box allows any other answer. "Let the agent decide" and
 * "Skip" are always there. After an answer the card shrinks to a one-line summary. This is a place where
 * amber is allowed, because the agent is waiting for a person.
 */
export function AgentQuestionCard({ question, reason, options = [], allowText, answer, onAnswer, onReopen, runName, labels, className }: AgentQuestionCardProps) {
  const l = {
    textLabel: "Or type your own answer", textPlaceholder: "Type an answer", send: "Send", letAgentDecide: "Let the agent decide", skip: "Skip",
    signInHint: "Never type a password or code here. Sign in in the window the agent opened, then choose an option.",
    answeredHeading: "You answered", agentDecided: "You let the agent decide", skipped: "You skipped this question", change: "Change", ...labels,
  };
  const reasonText = { ...reasonDefaults, ...labels?.reasons }[reason];
  const [inner, setInner] = useState<QuestionAnswer | null>(null);
  const [text, setText] = useState("");
  const uid = useId();
  const current = answer !== undefined ? answer : inner;
  const showText = allowText ?? reason !== "sign-in";
  const shown = options.slice(0, 4);

  const box = useRef<HTMLElement>(null);
  const byUs = useRef(false);
  // The buttons pressed here are replaced (answer becomes a summary, Change brings the question back), so focus moves to the card.
  const arm = useFocusAfter(current, () => box.current);
  useEffect(() => { if (!current) byUs.current = false; }, [current]);
  const give = (a: QuestionAnswer) => { byUs.current = true; arm(); if (answer === undefined) setInner(a); onAnswer?.(a); };
  const reopen = () => { arm(); if (answer === undefined) setInner(null); onReopen?.(); };
  const submit = (e: FormEvent) => { e.preventDefault(); const t = text.trim(); if (t) give({ kind: "text", value: t, label: t }); };

  const summary = !current ? "" : current.kind === "agent-decides" ? l.agentDecided : current.kind === "skipped" ? l.skipped : `${l.answeredHeading}: ${current.label}`;
  // One live region that stays mounted, so an answer given elsewhere is announced. An answer given here is not
  // announced twice: focus moves to the summary instead.
  const live = <p role="status" aria-live="polite" className="sr-only">{current && !byUs.current ? summary : ""}</p>;

  if (current) {
    return (
      <>
        <section ref={box} tabIndex={-1} aria-label={`${question}: ${summary}`} className={cn("flex flex-wrap items-center gap-x-3 gap-y-1 rounded-card border border-line bg-surface px-4 py-3", className)}>
          <CheckIcon className="shrink-0 text-success" />
          <div className="min-w-0 flex-1 basis-48">
            <p className="truncate text-small leading-4 text-fg-muted">{question}</p>
            <p className="text-body leading-5 font-medium break-words">{summary}</p>
          </div>
          <Button variant="ghost" size="sm" onClick={reopen}>{l.change}</Button>
        </section>
        {live}
      </>
    );
  }

  return (
    <>
    <section ref={box} tabIndex={-1} aria-labelledby={`${uid}-q`} className={cn("rounded-card border border-attention bg-surface p-4", className)}>
      <div className="flex flex-wrap items-center gap-2">
        <Badge tone="warning">{reason === "sign-in" && <LockIcon width={11} height={11} />}{reasonText}</Badge>
        {runName && <span className="text-small leading-4 text-fg-muted">{runName}</span>}
      </div>
      <p id={`${uid}-q`} className="mt-2 text-body leading-5 font-medium break-words">{question}</p>
      {reason === "sign-in" && <p className="mt-1 text-small leading-4 text-fg-muted">{l.signInHint}</p>}

      {shown.length > 0 && (
        <ul className="mt-3 grid gap-2 sm:grid-cols-2">
          {shown.map((o) => (
            <li key={o.id}>
              <button type="button" onClick={() => give({ kind: "option", value: o.id, label: o.label })}
                className="h-full w-full cursor-pointer rounded-card border border-line bg-surface px-3 py-2 text-left hover:border-line-strong hover:bg-bg">
                <span className="block text-body leading-5 font-medium">{o.label}</span>
                {o.description && <span className="mt-0.5 block text-small leading-4 text-fg-muted">{o.description}</span>}
              </button>
            </li>
          ))}
        </ul>
      )}

      {showText && (
        <form onSubmit={submit} className="mt-3 flex flex-wrap items-center gap-2">
          <label htmlFor={`${uid}-t`} className="w-full text-small leading-4 font-medium text-fg-muted">{l.textLabel}</label>
          <input id={`${uid}-t`} value={text} onChange={(e) => setText(e.target.value)} placeholder={l.textPlaceholder}
            className="h-9 min-w-0 flex-1 basis-40 rounded-field border border-line bg-surface px-4 text-body leading-5 placeholder:text-fg-subtle" />
          <Button type="submit" disabled={!text.trim()}>{l.send}</Button>
        </form>
      )}

      <div className="mt-3 flex flex-wrap gap-2">
        <Button variant="ghost" size="sm" onClick={() => give({ kind: "agent-decides", value: "", label: l.letAgentDecide })}>{l.letAgentDecide}</Button>
        <Button variant="ghost" size="sm" onClick={() => give({ kind: "skipped", value: "", label: l.skip })}>{l.skip}</Button>
      </div>
    </section>
    {live}
    </>
  );
}
```

## tattva/organisms/AgentSessionList.tsx

```tsx
import { useId, useState } from "react";
import { cn } from "../lib/cn";
import { headingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { ChevronIcon, SendIcon } from "../lib/icons";
import { AgentStatusIcon, type AgentStatus } from "../atoms/AgentStatusIcon";
import { AIBadge } from "../atoms/AIBadge";
import { Badge } from "../atoms/Badge";
import { Button, IconButton } from "../atoms/Button";

export type SessionGroup = "needs-input" | "working" | "review" | "completed";

export interface SessionPeek {
  kind: "output" | "question";
  /** The latest output or the question the agent is waiting on. */
  text: string;
  /** How long the session has waited for a reply, such as "Waiting 12 minutes". */
  waiting?: string;
}

export interface AgentSession {
  id: string;
  name: string;
  status: AgentStatus;
  /** One-line status summary. It is shown with an AI-generated badge, so pass model-written text. */
  summary: string;
  /** Age text, such as "3m". */
  age: string;
  /** Linked pull request label, such as "#1234". */
  pr?: string;
  peek?: SessionPeek;
  /** Group override. By default needs-input, working and completed map to their own group, and idle, failed and stopped map to review. */
  group?: SessionGroup;
}

export interface AgentSessionListLabels {
  groups?: Partial<Record<SessionGroup, string>>;
  peek?: string;
  hidePeek?: string;
  reply?: string;
  send?: string;
  aiSummary?: string;
}

export interface AgentSessionListProps {
  sessions: AgentSession[];
  /** Called when the person sends a reply from the peek panel. */
  onReply?: (id: string, text: string) => void;
  labels?: AgentSessionListLabels;
  /** Level of the part's own heading, so it fits the page outline. Default 3. */
  headingLevel?: HeadingLevel;
  className?: string;
}

const order: SessionGroup[] = ["needs-input", "working", "review", "completed"];
const groupNames: Record<SessionGroup, string> = { "needs-input": "Needs input", working: "Working", review: "Ready for review", completed: "Completed" };
const defaultGroup: Record<AgentStatus, SessionGroup> = { "needs-input": "needs-input", working: "working", completed: "completed", idle: "review", failed: "review", stopped: "review" };

/**
 * Agent sessions grouped by what they need from the person. Each row shows a status icon, the name,
 * an AI-generated one-line summary, the age and an optional PR label. Peek expands the latest output
 * or question, how long it has waited, and a reply field.
 */
export function AgentSessionList({ sessions, onReply, labels, headingLevel = 3, className }: AgentSessionListProps) {
  const l = { peek: "Peek", hidePeek: "Close peek", reply: "Reply", send: "Send reply", aiSummary: "AI-generated", ...labels };
  const names = { ...groupNames, ...labels?.groups };
  const uid = useId();
  const H = headingTag(headingLevel);
  const [open, setOpen] = useState<string | null>(null);
  const [drafts, setDrafts] = useState<Record<string, string>>({});
  const [announce, setAnnounce] = useState("");

  const send = (s: AgentSession) => {
    const text = (drafts[s.id] ?? "").trim();
    if (!text) return;
    onReply?.(s.id, text);
    setDrafts({ ...drafts, [s.id]: "" });
    setAnnounce(`Reply sent to ${s.name}`);
  };

  return (
    <div className={cn("space-y-5", className)}>
      {order.map((g) => {
        const rows = sessions.filter((s) => (s.group ?? defaultGroup[s.status]) === g);
        if (!rows.length) return null;
        const hid = `${uid}-group-${g}`;
        return (
          <section key={g} aria-labelledby={hid}>
            <H id={hid} className="mb-1.5 flex items-center gap-2 px-1 font-sans text-small leading-4 font-medium tracking-wide text-fg-muted uppercase">
              {names[g]}
              <Badge>{rows.length}<span className="sr-only"> {rows.length === 1 ? "session" : "sessions"}</span></Badge>
            </H>
            <ul className="divide-y divide-line overflow-hidden rounded-card border border-line bg-surface">
              {rows.map((s) => {
                const isOpen = open === s.id;
                const panel = `${uid}-peek-${s.id}`;
                return (
                  <li key={s.id} className="px-4 py-3">
                    <div className="flex flex-wrap items-center gap-x-3 gap-y-1">
                      <AgentStatusIcon status={s.status} />
                      <span className="min-w-0 text-body leading-5 font-medium">{s.name}</span>
                      {s.pr && <Badge>{s.pr}</Badge>}
                      <span className="ml-auto text-small leading-4 text-fg-subtle tabular-nums">{s.age}</span>
                      {s.peek && (
                        <Button size="sm" variant="ghost" aria-expanded={isOpen} aria-controls={panel} onClick={() => setOpen(isOpen ? null : s.id)}
                          trailing={<ChevronIcon width={12} height={12} className={cn("transition-transform", isOpen && "rotate-90")} />}>
                          {isOpen ? l.hidePeek : l.peek}<span className="sr-only">: {s.name}</span>
                        </Button>
                      )}
                    </div>
                    <p className="mt-1 flex flex-wrap items-center gap-2 pl-7 text-small leading-4 text-fg-muted">
                      <span>{s.summary}</span>
                      <AIBadge label={l.aiSummary} />
                    </p>
                    {s.peek && isOpen && (
                      <div id={panel} className="mt-3 ml-7 space-y-2 rounded-card bg-sunken p-3">
                        <p className="text-small leading-4 font-medium text-fg-subtle">
                          {s.peek.kind === "question" ? "Question" : "Latest output"}
                          {s.peek.waiting && <span className="font-normal"> · {s.peek.waiting}</span>}
                        </p>
                        <p className={cn("text-body leading-5", s.peek.kind === "output" && "font-mono text-small leading-5")}>{s.peek.text}</p>
                        <form className="flex items-center gap-2" onSubmit={(e) => { e.preventDefault(); send(s); }}>
                          <label className="sr-only" htmlFor={`${uid}-reply-${s.id}`}>{l.reply}: {s.name}</label>
                          <input id={`${uid}-reply-${s.id}`} value={drafts[s.id] ?? ""} onChange={(e) => setDrafts({ ...drafts, [s.id]: e.target.value })} placeholder={`${l.reply}…`}
                            className="h-9 min-w-0 flex-1 rounded-field border border-line bg-surface px-4 text-body leading-5 placeholder:text-fg-subtle" />
                          <IconButton type="submit" variant="primary" label={`${l.send}: ${s.name}`}><SendIcon width={14} height={14} /></IconButton>
                        </form>
                      </div>
                    )}
                  </li>
                );
              })}
            </ul>
          </section>
        );
      })}
      <p role="status" aria-live="polite" className="sr-only">{announce}</p>
    </div>
  );
}
```

## tattva/organisms/ApprovalInbox.tsx

```tsx
import { useId, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, NeedsReviewIcon, ShieldIcon } from "../lib/icons";
import { AgentStatusIcon } from "../atoms/AgentStatusIcon";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { EmptyState } from "../molecules/EmptyState";

export type InboxKind = "question" | "approval" | "failure" | "review";
export type InboxUrgency = "now" | "soon" | "later";

export interface InboxItem {
  id: string;
  /** Name of the run that is waiting. */
  run: string;
  kind: InboxKind;
  urgency: InboxUrgency;
  /** What is needed, in plain words, such as "Pick a hotel". */
  need: string;
  /** How long it has waited, such as "12 min". */
  waited: string;
  /** Label of the action button, such as "Answer". */
  actionLabel: string;
}

export interface ApprovalInboxLabels {
  title?: string;
  groups?: Partial<Record<InboxUrgency, string>>;
  kinds?: Partial<Record<InboxKind, string>>;
  waiting?: (time: string) => string;
  emptyTitle?: string;
  emptyBody?: string;
}

export interface ApprovalInboxProps {
  items: InboxItem[];
  /** Called with the item when its action button is pressed. */
  onAction?: (item: InboxItem) => void;
  labels?: ApprovalInboxLabels;
  /** Heading level. Default 2. */
  headingLevel?: 2 | 3 | 4;
  className?: string;
}

const groupOrder: InboxUrgency[] = ["now", "soon", "later"];
const groupDefaults: Record<InboxUrgency, string> = { now: "Needs you now", soon: "Needs you soon", later: "When you have time" };
const kindDefaults: Record<InboxKind, string> = { question: "Question", approval: "Approval", failure: "Failed", review: "Ready for review" };
const kindIcon: Record<InboxKind, ReactNode> = {
  question: <AgentStatusIcon status="needs-input" size={14} />,
  approval: <ShieldIcon width={14} height={14} />,
  failure: <AlertIcon width={14} height={14} />,
  review: <NeedsReviewIcon width={14} height={14} />,
};

/**
 * One list of everything that is waiting on a person, across all runs. Items are grouped by how
 * urgent they are, and each row says which run, what is needed, how long it has waited and has one
 * action. Rows wrap on a narrow screen. Amber marks only the "now" group, where a person must act.
 */
export function ApprovalInbox({ items, onAction, labels, headingLevel = 2, className }: ApprovalInboxProps) {
  const l = {
    title: "Waiting for you", emptyTitle: "Nothing is waiting for you", emptyBody: "When a run needs an answer, an approval or a look, it will show up here.",
    waiting: (t: string) => `Waiting ${t}`, ...labels,
  };
  const groups = { ...groupDefaults, ...labels?.groups };
  const kinds = { ...kindDefaults, ...labels?.kinds };
  const uid = useId();
  const H = `h${headingLevel}` as "h2";
  const H2 = `h${Math.min(headingLevel + 1, 6)}` as "h3";
  const total = items.length;
  return (
    <section aria-labelledby={`${uid}-title`} className={cn("rounded-card border border-line bg-surface p-4 sm:p-5", className)}>
      <div className="flex items-center gap-2">
        <H id={`${uid}-title`} className="text-body leading-5 font-medium">{l.title}</H>
        {total > 0 && <Badge tone="warning" aria-label={`${total} items`}>{total}</Badge>}
      </div>
      {total === 0 ? (
        <div className="mt-3"><EmptyState variant="cleared" title={l.emptyTitle} compact layout="start" headingLevel={Math.min(headingLevel + 1, 4) as 3 | 4}>{l.emptyBody}</EmptyState></div>
      ) : (
        <div className="mt-3 space-y-4">
          {groupOrder.map((g) => {
            const rows = items.filter((i) => i.urgency === g);
            if (!rows.length) return null;
            return (
              <div key={g}>
                <H2 className="mb-1.5 flex items-center gap-2 text-small leading-4 font-medium text-fg-muted">
                  {g === "now" && <AlertIcon width={12} height={12} className="text-attention" />}{groups[g]}<span className="text-fg-subtle">{rows.length}</span>
                </H2>
                <ul className="space-y-1.5">
                  {rows.map((it) => (
                    <li key={it.id} className={cn("flex flex-wrap items-center gap-x-3 gap-y-2 rounded-card border px-3 py-2.5", g === "now" ? "border-attention" : "border-line")}>
                      <div className="min-w-0 flex-1 basis-52">
                        <div className="flex flex-wrap items-center gap-x-2 gap-y-1">
                          <span className="truncate text-body leading-5 font-medium">{it.run}</span>
                          <Badge tone={it.kind === "failure" ? "danger" : "neutral"}>{kindIcon[it.kind]}{kinds[it.kind]}</Badge>
                        </div>
                        <p className="mt-0.5 text-body leading-5 break-words text-fg-muted">{it.need}</p>
                        <p className="mt-0.5 text-small leading-4 text-fg-subtle">{l.waiting(it.waited)}</p>
                      </div>
                      <div className="ml-auto flex flex-wrap justify-end gap-2">
                        <Button variant={g === "now" ? "primary" : "secondary"} size="sm" onClick={() => onAction?.(it)}
                          aria-label={`${it.actionLabel}: ${it.run}, ${it.need}`}>{it.actionLabel}</Button>
                      </div>
                    </li>
                  ))}
                </ul>
              </div>
            );
          })}
        </div>
      )}
    </section>
  );
}

/** @deprecated Renamed to ApprovalInbox in 0.29.0, to match the name other design systems use. The old name goes in 0.31.0. */
export const NeedsYouInbox = ApprovalInbox;
/** @deprecated Renamed to ApprovalInboxProps in 0.29.0. */
export type NeedsYouInboxProps = ApprovalInboxProps;
```

## tattva/organisms/ApprovalPrompt.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { ShieldIcon } from "../lib/icons";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";

export type Risk = "low" | "medium" | "high";

export interface ApprovalPromptProps {
  action: string;
  /** Exactly what will happen, in plain language. Show real values, not placeholders. */
  detail: ReactNode;
  risk?: Risk;
  onApprove: () => void;
  onDeny: () => void;
  onAlways?: () => void;
}

/**
 * Human-in-the-loop gate for consequential agent actions. Deny is always as easy as approve.
 * Amber means a person must act, so low risk uses the neutral sunken surface, medium uses the attention surface,
 * and high uses the attention surface with a danger badge. The risk is always written out as text.
 */
export function ApprovalPrompt({ action, detail, risk = "medium", onApprove, onDeny, onAlways }: ApprovalPromptProps) {
  return (
    <div role="alertdialog" aria-label={`Approval needed: ${action}`} className={cn("rounded-card border p-4", risk === "low" ? "border-line bg-sunken" : "border-warning-line bg-warning-soft")}>
      <div className="flex items-center gap-2">
        <ShieldIcon className={risk === "low" ? "text-fg-muted" : "text-warning-fg"} />
        <p className="flex-1 text-body leading-5 font-medium">{action}</p>
        <Badge tone={risk === "high" ? "danger" : risk === "medium" ? "warning" : "neutral"}>{risk} risk</Badge>
      </div>
      <div className="mt-2 text-body leading-5 text-fg-muted">{detail}</div>
      <div className="mt-3 flex flex-wrap gap-2">
        <Button size="sm" onClick={onApprove}>Approve</Button>
        <Button size="sm" variant="secondary" onClick={onDeny}>Deny</Button>
        {onAlways && risk === "low" && <Button size="sm" variant="ghost" onClick={onAlways}>Always allow</Button>}
      </div>
    </div>
  );
}
```

## tattva/organisms/BarChart.tsx

```tsx
import { useRef, useState, type KeyboardEvent, type PointerEvent } from "react";
import { cn } from "../lib/cn";
import type { DataTableProps } from "../molecules/DataTable";
import { formatNumber, niceTicks, useElementWidth } from "../molecules/ChartFrame";
import { ChartLegend, seriesStyle } from "./ChartLegend";

export interface BarDatum {
  /** Category name. */
  label: string;
  /** Value for a single series chart. Must be zero or more. */
  value?: number;
  /** One value per stack segment, in stack order, for a stacked chart. Must be zero or more. */
  values?: number[];
}

export interface BarSeries {
  id: string;
  /** Segment name shown in the legend, tooltip and table. */
  label: string;
}

export interface BarChartProps {
  /** One entry per category, in display order. */
  data: BarDatum[];
  /** Horizontal bars read best for long labels and many categories. */
  orientation?: "horizontal" | "vertical";
  /** Stack segment definitions, at most four. When set the chart is stacked and each datum uses `values`. */
  series?: BarSeries[];
  /** Label of the one category to emphasize. It draws in the first chart color and the rest in the neutral. */
  highlight?: string;
  /** Name of the measure for single series charts, used in announcements and the tooltip, for example "Cost". */
  measure?: string;
  /** Formats values on the axis, in labels and in the tooltip. */
  formatValue?: (n: number) => string;
  /** Unit word read after each value by screen readers, for example "dollars". */
  unit?: string;
  /** Total height in px. Vertical charts default to 240. Horizontal charts size themselves to their rows. */
  height?: number;
  /** Name for the chart region, for example "Spend by model". */
  ariaLabel?: string;
  className?: string;
}

const MAX_STACK = 4;
const T = 24; // maximum bar thickness

/** Bar from a to b along the value axis, c to c+t across it, with the data end (b) rounded to 4px. */
function barPath(hor: boolean, a: number, b: number, c: number, t: number, round: boolean): string {
  const len = Math.abs(b - a);
  const r = round ? Math.min(4, len, t / 2) : 0;
  if (hor) {
    const x1 = b;
    return r ? `M${a} ${c}H${x1 - r}Q${x1} ${c} ${x1} ${c + r}V${c + t - r}Q${x1} ${c + t} ${x1 - r} ${c + t}H${a}Z` : `M${a} ${c}H${x1}V${c + t}H${a}Z`;
  }
  const y1 = b;
  return r ? `M${c} ${a}V${y1 + r}Q${c} ${y1} ${c + r} ${y1}H${c + t - r}Q${c + t} ${y1} ${c + t} ${y1 + r}V${a}Z` : `M${c} ${a}V${y1}H${c + t}V${a}Z`;
}

/**
 * Bar chart drawn in SVG. Bars start at zero with a 4px rounded data end and a 2px surface gap between neighbors.
 * Single series charts can emphasize one bar. Stacked charts carry up to four segments and a legend in stack order.
 * The plot takes focus: arrow keys step through bars and each one is announced as "Series, category, value, unit".
 */
export function BarChart({ data, orientation = "horizontal", series, highlight, measure = "Value", formatValue = formatNumber, unit = "", height, ariaLabel = "Bar chart", className }: BarChartProps) {
  const stack = series && series.length ? series.slice(0, MAX_STACK) : undefined;
  const hor = orientation === "horizontal";
  const [ref, measured] = useElementWidth<HTMLDivElement>();
  const wrapRef = useRef<HTMLDivElement | null>(null);
  const svgRef = useRef<SVGSVGElement | null>(null);
  const [active, setActive] = useState<{ c: number; s: number } | null>(null);
  const [said, setSaid] = useState("");

  const w = measured || 640;
  const n = data.length;
  const segs = (d: BarDatum) => (stack ? stack.map((_, i) => Math.max(0, d.values?.[i] ?? 0)) : [Math.max(0, d.value ?? 0)]);
  const totals = data.map((d) => segs(d).reduce((a, b) => a + b, 0));
  const max = Math.max(0, ...totals);
  const ticks = niceTicks(0, max || 1);
  const hi = ticks[ticks.length - 1] || 1;
  const tickLabels = ticks.map((t) => formatValue(t));
  const tickW = Math.max(...tickLabels.map((t) => t.length)) * 6.6;
  const valW = Math.max(0, ...totals.map((t) => formatValue(t).length)) * 6.6;

  // geometry
  const catChars = Math.max(0, ...data.map((d) => d.label.length));
  const left = hor ? Math.min(w * 0.4, catChars * 6.4 + 14) : tickW + 14;
  const right = hor ? valW + 14 : 10;
  const plotTop = hor ? 4 : 20;
  const band = hor ? (height ? Math.max(20, (height - 32) / Math.max(1, n)) : 32) : Math.max(10, (w - left - right) / Math.max(1, n));
  const h = hor ? Math.round(n * band + 32) : Math.max(160, height ?? 240);
  const plotBottom = h - 28;
  const pw = Math.max(40, w - left - right);
  const ph = plotBottom - plotTop;
  const thick = Math.min(T, band * (hor ? 0.62 : 0.6));
  const vs = (v: number) => (hor ? left + (v / hi) * pw : plotBottom - (v / hi) * ph);
  const cross = (c: number) => (hor ? plotTop + c * band + (band - thick) / 2 : left + c * band + (band - thick) / 2);
  const fit = (s: string, px: number) => { const k = Math.max(1, Math.floor(px / 6.4)); return s.length > k ? `${s.slice(0, Math.max(1, k - 1))}…` : s; };

  const tickStep = Math.max(1, Math.ceil((tickW + 12) / Math.max(1, hor ? pw / Math.max(1, ticks.length - 1) : 1)));
  const colorOf = (c: number, s: number) => (stack ? seriesStyle(s).color : highlight ? (data[c].label === highlight ? "var(--chart-1)" : "var(--chart-neutral)") : "var(--chart-1)");
  const nameOf = (s: number) => (stack ? stack[s].label : measure);
  const readout = (c: number, s: number) => `${nameOf(s)}, ${data[c].label}, ${formatValue(segs(data[c])[s])}${unit ? ` ${unit}` : ""}`;

  const move = (e: KeyboardEvent<HTMLDivElement>) => {
    if (!n) return;
    const cur = active ?? { c: 0, s: 0 };
    let { c, s } = cur;
    const catKeys = hor ? ["ArrowDown", "ArrowUp"] : ["ArrowRight", "ArrowLeft"];
    const segKeys = hor ? ["ArrowRight", "ArrowLeft"] : ["ArrowUp", "ArrowDown"];
    const nextC = (d: number) => Math.max(0, Math.min(n - 1, c + d));
    if (e.key === catKeys[0]) c = nextC(1);
    else if (e.key === catKeys[1]) c = nextC(-1);
    else if (stack && e.key === segKeys[0]) s = Math.min(stack.length - 1, s + 1);
    else if (stack && e.key === segKeys[1]) s = Math.max(0, s - 1);
    else if (!stack && e.key === segKeys[0]) c = nextC(1);
    else if (!stack && e.key === segKeys[1]) c = nextC(-1);
    else if (e.key === "Home") c = 0;
    else if (e.key === "End") c = n - 1;
    else if (e.key === "Escape") { setActive(null); setSaid(""); return; }
    else return;
    e.preventDefault();
    setActive({ c, s });
    setSaid(readout(c, s));
  };

  const pointAt = (c: number, e: PointerEvent<SVGRectElement>) => {
    let s = 0;
    const r = svgRef.current?.getBoundingClientRect();
    if (stack && r) {
      const pos = hor ? e.clientX - r.left : e.clientY - r.top;
      const v = hor ? ((pos - left) / pw) * hi : ((plotBottom - pos) / ph) * hi;
      let cum = 0;
      const vals = segs(data[c]);
      s = vals.length - 1;
      for (let i = 0; i < vals.length; i++) { cum += vals[i]; if (v <= cum) { s = i; break; } }
    }
    setActive({ c, s });
  };

  const ac = active?.c ?? -1;
  const acTotal = ac >= 0 ? totals[ac] : 0;
  const tipAnchor = ac >= 0 ? vs(acTotal) : 0;
  const tipStyle = ac >= 0
    ? hor
      ? { top: plotTop + ac * band, left: tipAnchor, transform: tipAnchor > w * 0.55 ? "translateX(calc(-100% - 12px))" : "translateX(12px)" }
      : { top: tipAnchor, left: left + ac * band + band / 2, transform: tipAnchor > 90 ? "translate(-50%, calc(-100% - 8px))" : "translate(-50%, 8px)" }
    : undefined;
  const lastNonZero = (vals: number[]) => { for (let i = vals.length - 1; i >= 0; i--) if (vals[i] > 0) return i; return -1; };

  return (
    <div ref={ref} className={cn("relative min-w-0", className)}>
      {stack && stack.length >= 2 && <ChartLegend kind="bar" label="Legend, in stack order" items={stack.map((s) => ({ id: s.id, label: s.label }))} />}
      <div ref={wrapRef} tabIndex={0} role="group" aria-roledescription="bar chart"
        aria-label={`${ariaLabel}. ${n} categories${stack ? `, ${stack.length} segments` : ""}. Use arrow keys to step through bars.`}
        onKeyDown={move} onFocus={() => { if (n) setActive((p) => p ?? { c: 0, s: 0 }); }} onBlur={() => { setActive(null); setSaid(""); }}
        className="relative rounded-lg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--focus-ring)]">
        <svg ref={svgRef} width={w} height={h} viewBox={`0 0 ${w} ${h}`} className="block max-w-full touch-pan-y select-none" aria-hidden focusable="false" style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-small)" }}>
          {ac >= 0 && (hor
            ? <rect x={left} y={plotTop + ac * band} width={pw} height={band} fill="var(--surface-hover)" />
            : <rect x={left + ac * band} y={plotTop} width={band} height={ph} fill="var(--surface-hover)" />)}
          {ticks.map((t, i) => {
            const p = vs(t);
            const base = i === 0;
            return (
              <g key={t}>
                {hor
                  ? <line x1={p} x2={p} y1={plotTop} y2={plotBottom} stroke={base ? "var(--chart-axis)" : "var(--chart-grid)"} strokeWidth={base ? 1 : 0.5} shapeRendering="crispEdges" />
                  : <line x1={left} x2={left + pw} y1={p} y2={p} stroke={base ? "var(--chart-axis)" : "var(--chart-grid)"} strokeWidth={base ? 1 : 0.5} shapeRendering="crispEdges" />}
                {hor
                  ? (i % tickStep === 0 || i === ticks.length - 1) && <text x={p} y={plotBottom + 18} textAnchor="middle" fill="var(--fg-muted)" style={{ fontVariantNumeric: "tabular-nums" }}>{tickLabels[i]}</text>
                  : <text x={left - 8} y={p} textAnchor="end" dominantBaseline="central" fill="var(--fg-muted)" style={{ fontVariantNumeric: "tabular-nums" }}>{tickLabels[i]}</text>}
              </g>
            );
          })}
          {data.map((d, c) => {
            const vals = segs(d);
            const last = lastNonZero(vals);
            let cum = 0;
            const c0 = cross(c);
            return (
              <g key={d.label}>
                {hor
                  ? <text x={left - 8} y={plotTop + c * band + band / 2} textAnchor="end" dominantBaseline="central" fill="var(--fg)">{fit(d.label, left - 12)}</text>
                  : <text x={left + c * band + band / 2} y={plotBottom + 18} textAnchor="middle" fill="var(--fg-muted)">{fit(d.label, band - 4)}</text>}
                {vals.map((v, s) => {
                  if (v <= 0) { return null; }
                  const a = vs(cum) + (s > 0 ? (hor ? 1 : -1) : 0);
                  cum += v;
                  const b = vs(cum) + (s < last ? (hor ? -1 : 1) : 0);
                  if (Math.abs(b - a) < 1) return null;
                  return <path key={s} d={barPath(hor, a, b, c0, thick, s === last)} fill={colorOf(c, s)} />;
                })}
                {(() => {
                  const tot = totals[c];
                  const txt = formatValue(tot);
                  if (hor) return <text x={vs(tot) + 8} y={plotTop + c * band + band / 2} dominantBaseline="central" fill="var(--fg)" style={{ fontVariantNumeric: "tabular-nums" }}>{txt}</text>;
                  return band >= txt.length * 6.6 + 4 ? <text x={left + c * band + band / 2} y={vs(tot) - 6} textAnchor="middle" fill="var(--fg)" style={{ fontVariantNumeric: "tabular-nums" }}>{txt}</text> : null;
                })()}
              </g>
            );
          })}
          {data.map((d, c) => (
            <rect key={d.label} x={hor ? 0 : left + c * band} y={hor ? plotTop + c * band : 0} width={hor ? w : band} height={hor ? band : h} fill="transparent"
              onPointerMove={(e) => pointAt(c, e)} onPointerDown={(e) => pointAt(c, e)}
              onPointerLeave={(e) => { if (e.pointerType === "mouse" && document.activeElement !== wrapRef.current) setActive(null); }} />
          ))}
        </svg>
        {ac >= 0 && (
          <div aria-hidden className="pointer-events-none absolute z-10 min-w-36 max-w-64 rounded-overlay border border-line bg-surface px-3 py-2 text-small leading-4 shadow-md" style={tipStyle}>
            <p className="mb-1 text-fg-muted">{data[ac].label}</p>
            {stack ? (
              <>
                {segs(data[ac]).map((v, s) => (
                  <p key={stack[s].id} className="flex items-center gap-2 leading-5">
                    <span className="inline-block h-2.5 w-2.5 rounded-2xs" style={{ background: seriesStyle(s).color }} />
                    <span className={cn("flex-1 text-fg-muted", active?.s === s && "font-medium text-fg")}>{stack[s].label}</span>
                    <span className="font-semibold text-fg [font-variant-numeric:tabular-nums]">{formatValue(v)}</span>
                  </p>
                ))}
                <p className="mt-1 flex justify-between gap-2 border-t border-line pt-1 leading-5"><span className="text-fg-muted">Total</span><span className="font-semibold text-fg [font-variant-numeric:tabular-nums]">{formatValue(acTotal)}</span></p>
              </>
            ) : (
              <p className="flex items-center gap-2 leading-5"><span className="text-fg-muted">{measure}</span><span className="font-semibold text-fg [font-variant-numeric:tabular-nums]">{formatValue(acTotal)}</span></p>
            )}
          </div>
        )}
      </div>
      <div className="sr-only" aria-live="polite" role="status">{said}</div>
    </div>
  );
}

/** Table data for ChartFrame's table view. Stacked charts get one column per segment and a total. */
export function barChartTable(data: BarDatum[], opts: { labelHeader: string; caption: string; series?: BarSeries[]; measure?: string; formatValue?: (n: number) => string; unit?: string }): DataTableProps {
  const f = opts.formatValue ?? formatNumber;
  const st = opts.series?.slice(0, MAX_STACK);
  const u = opts.unit ? ` (${opts.unit})` : "";
  if (!st) {
    return { caption: opts.caption, columns: [{ key: "l", header: opts.labelHeader }, { key: "v", header: `${opts.measure ?? "Value"}${u}`, numeric: true }], rows: data.map((d) => ({ l: d.label, v: f(d.value ?? 0) })) };
  }
  return {
    caption: opts.caption,
    columns: [{ key: "l", header: opts.labelHeader }, ...st.map((s) => ({ key: s.id, header: `${s.label}${u}`, numeric: true })), { key: "total", header: `Total${u}`, numeric: true }],
    rows: data.map((d) => {
      const row: Record<string, string> = { l: d.label };
      let tot = 0;
      st.forEach((s, i) => { const v = d.values?.[i] ?? 0; tot += v; row[s.id] = f(v); });
      row.total = f(tot);
      return row;
    }),
  };
}
```

## tattva/organisms/BudgetControl.tsx

```tsx
import { useId, useState } from "react";
import { cn } from "../lib/cn";
import { headingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { AlertIcon } from "../lib/icons";
import { Meter } from "../atoms/Meter";

export type LimitAction = "stop-and-ask" | "stop" | "ask-for-more";

export interface BudgetLimit {
  id: string;
  /** Plain name, such as "Time", "Spend" or "Steps". */
  label: string;
  used: number;
  cap: number;
  /** Unit shown beside the number, such as "minutes", "credits", "$" or "steps". */
  unit: string;
}

export interface BudgetControlLabels {
  heading?: string;
  whenReached?: string;
  stopAndAsk?: string;
  stop?: string;
  askForMore?: string;
  nearLimit?: string;
  limitReached?: string;
  /** Receives the limit name. */
  capLabel?: (label: string) => string;
}

export interface BudgetControlProps {
  limits: BudgetLimit[];
  /** Called with the limit id and the new cap when the person edits a number. */
  onCapChange?: (id: string, cap: number) => void;
  /** What happens at a limit (controlled). */
  limitAction?: LimitAction;
  defaultLimitAction?: LimitAction;
  onLimitActionChange?: (action: LimitAction) => void;
  /** Fraction of a cap at which a limit counts as close, 0 to 1. */
  warnAt?: number;
  labels?: BudgetControlLabels;
  /** Level of the part's own heading, so it fits the page outline. Default 3. */
  headingLevel?: HeadingLevel;
  className?: string;
}

/** Shows how much of each limit a run has used (time, spend, steps), lets the person change the caps, and sets what happens at a limit. */
export function BudgetControl({ limits, onCapChange, limitAction, defaultLimitAction = "stop-and-ask", onLimitActionChange, warnAt = 0.8, labels, headingLevel = 3, className }: BudgetControlProps) {
  const l = {
    heading: "Limits for this run", whenReached: "When a limit is reached", stopAndAsk: "Stop and ask me", stop: "Stop", askForMore: "Ask for more",
    nearLimit: "Close to the limit", limitReached: "Limit reached", capLabel: (n: string) => `${n} limit`, ...labels,
  };
  const [inner, setInner] = useState<LimitAction>(defaultLimitAction);
  const [caps, setCaps] = useState<Record<string, string>>({});
  const current = limitAction ?? inner;
  const uid = useId();
  const H = headingTag(headingLevel);
  const options: { value: LimitAction; label: string }[] = [
    { value: "stop-and-ask", label: l.stopAndAsk }, { value: "stop", label: l.stop }, { value: "ask-for-more", label: l.askForMore },
  ];
  const state = (x: BudgetLimit) => (x.cap > 0 && x.used >= x.cap ? "reached" : x.cap > 0 && x.used / x.cap >= warnAt ? "near" : "ok");
  const alerts = limits.filter((x) => state(x) !== "ok").map((x) => `${x.label}: ${state(x) === "reached" ? l.limitReached : l.nearLimit}`).join(". ");

  const edit = (x: BudgetLimit, text: string) => {
    setCaps((c) => ({ ...c, [x.id]: text }));
    const n = Number(text);
    if (text.trim() !== "" && Number.isFinite(n) && n >= 0) onCapChange?.(x.id, n);
  };

  return (
    <section aria-label={l.heading} className={cn("rounded-card border border-line bg-surface p-4", className)}>
      <H className="text-body leading-5 font-medium">{l.heading}</H>
      <ul className="mt-3 space-y-3">
        {limits.map((x) => {
          const s = state(x);
          return (
            <li key={x.id} className="rounded-card border border-line p-3">
              <div className="flex flex-wrap items-center justify-between gap-x-3 gap-y-2">
                <p className="min-w-0 text-body leading-5 font-medium">{x.label}</p>
                <label className="flex items-center gap-2 text-small leading-4 text-fg-muted">
                  <span className="sr-only">{l.capLabel(x.label)}</span>
                  <input type="number" min={0} inputMode="decimal" value={caps[x.id] ?? String(x.cap)} onChange={(e) => edit(x, e.target.value)}
                    className="h-9 w-24 rounded-field border border-line bg-surface px-3 text-body leading-5 text-fg" />
                  <span>{x.unit}</span>
                </label>
              </div>
              <Meter className="mt-2.5" label={x.label} value={Math.min(x.used, x.cap || x.used)} max={x.cap || 1} warnAt={warnAt} valueText={`${x.used} of ${x.cap} ${x.unit} used`} />
              <p className="mt-1.5 flex flex-wrap items-center gap-x-2 text-small leading-4 text-fg-muted">
                <span>{x.used} of {x.cap} {x.unit} used</span>
                {s !== "ok" && <span className="inline-flex items-center gap-1 font-medium text-fg"><AlertIcon width={12} height={12} />{s === "reached" ? l.limitReached : l.nearLimit}</span>}
              </p>
            </li>
          );
        })}
      </ul>
      <fieldset className="mt-4">
        <legend className="text-body leading-5 font-medium">{l.whenReached}</legend>
        <div className="mt-1.5 space-y-1">
          {options.map((o) => (
            <label key={o.value} className={cn("flex cursor-pointer items-center gap-2.5 rounded-card border px-3 py-2 text-body leading-5 pointer-coarse:min-h-11 has-[:focus-visible]:outline has-[:focus-visible]:outline-(length:--focus-width)", current === o.value ? "border-selected-line bg-selected" : "border-transparent hover:bg-hover")}>
              <input type="radio" name={`${uid}-action`} value={o.value} checked={current === o.value} className="size-4 shrink-0 accent-[var(--accent)]"
                onChange={() => { if (limitAction === undefined) setInner(o.value); onLimitActionChange?.(o.value); }} />
              <span>{o.label}</span>
            </label>
          ))}
        </div>
      </fieldset>
      <p role="status" aria-live="polite" className="sr-only">{alerts}</p>
    </section>
  );
}
```

## tattva/organisms/CallRecord.tsx

```tsx
import { useId, useRef, useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, CheckIcon, XIcon } from "../lib/icons";
import { AIBadge } from "../atoms/AIBadge";
import { Badge, type Tone } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { Money } from "../atoms/Money";
import { SegmentedControl } from "../atoms/SegmentedControl";

export type CallStatus = "live" | "completed" | "failed" | "voicemail" | "transferred";
export type CallVerdict = "success" | "failure" | "unknown";

export interface CallCostItem {
  /** "Speech to text", "Model", "Voice", "Platform", "Phone line", or your own. */
  label: string;
  /** Amount in the record's currency. */
  amount: number;
  /** What was used, for example "4 min 12 s" or "3,180 tokens". */
  quantity?: string;
}

export interface CallTranscriptLine {
  id: string;
  speaker: "agent" | "caller";
  text: string;
  /** Offset into the call, for example "01:12". */
  time?: string;
}

export interface CallRecordProps {
  status: CallStatus;
  /** Why the call ended, in plain words: "Caller hung up", "Transferred to Priya". */
  endedReason: string;
  duration: string;
  startedAt: string;
  /** Heading, for example the caller or the call id. */
  title?: string;
  /** The AI's judgement of the call. Shown with an AI label and its reason. */
  outcome?: { verdict: CallVerdict; rationale: string; evidenceLineIds?: string[] };
  /** What the call cost. Amounts are numbers in `currency`. */
  cost?: { total: number; items: CallCostItem[] };
  /** ISO 4217 code for every amount. */
  currency?: string;
  locale?: string;
  /** Did the agent say it is an AI? false shows an amber notice: someone must fix the agent. */
  disclosureGiven?: boolean;
  /** Was recording consent captured? false shows an amber notice. */
  consentRecorded?: boolean;
  /** Fields the agent took from the call, for example order number. */
  data?: Record<string, string>;
  /** A simple transcript. Evidence lines are marked and Show in transcript jumps to them. */
  transcript?: CallTranscriptLine[];
  /** Names for the two speakers. */
  speakerLabels?: { agent: string; caller: string };
  /** Called by Show in transcript, for example to seek your audio player. The built-in transcript also scrolls. */
  onShowEvidence?: (lineIds: string[]) => void;
  /** Extra actions in the header, for example Open recording. */
  actions?: ReactNode;
  className?: string;
}

const STATUS: Record<CallStatus, { label: string; tone: Tone }> = {
  live: { label: "Live", tone: "danger" },
  completed: { label: "Completed", tone: "neutral" },
  failed: { label: "Failed", tone: "danger" },
  voicemail: { label: "Went to voicemail", tone: "neutral" },
  transferred: { label: "Transferred to a person", tone: "info" },
};

const VERDICT: Record<CallVerdict, { label: string; tone: Tone; icon: ReactNode }> = {
  success: { label: "Goal met", tone: "success", icon: <CheckIcon width={12} height={12} /> },
  failure: { label: "Goal not met", tone: "danger", icon: <XIcon width={12} height={12} /> },
  unknown: { label: "Not sure", tone: "neutral", icon: <span aria-hidden>?</span> },
};

function Section({ title, children, aside }: { title: string; children: ReactNode; aside?: ReactNode }) {
  return (
    <section className="space-y-2 border-t border-line pt-4">
      <div className="flex flex-wrap items-center justify-between gap-2">
        <h4 className="text-small leading-4 font-semibold text-fg">{title}</h4>
        {aside}
      </div>
      {children}
    </section>
  );
}

/**
 * One call at a glance, for the people who run a voice agent: status, why it ended, the AI's verdict with its
 * reason and evidence, the cost, and what to check. A missing AI disclosure or recording consent is amber, because
 * a person must fix the agent. An unknown verdict is neutral.
 */
export function CallRecord({
  status, endedReason, duration, startedAt, title = "Call", outcome, cost, currency = "GBP", locale = "en-GB",
  disclosureGiven, consentRecorded, data, transcript, speakerLabels = { agent: "Agent", caller: "Caller" },
  onShowEvidence, actions, className,
}: CallRecordProps) {
  const id = useId();
  const [view, setView] = useState<"money" | "quantity">("money");
  const [shown, setShown] = useState<string[]>([]);
  const lines = useRef<Map<string, HTMLLIElement>>(new Map());
  const live = status === "live";
  const s = STATUS[status];
  const evidence = outcome?.evidenceLineIds ?? [];
  const hasQuantity = !!cost?.items.some((i) => i.quantity);

  const showEvidence = () => {
    setShown(evidence);
    onShowEvidence?.(evidence);
    const first = lines.current.get(evidence[0]);
    if (first) {
      first.scrollIntoView({ block: "nearest", behavior: window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth" });
      first.focus({ preventScroll: true });
    }
  };

  const checks: { ok: boolean; good: string; bad: string; fix: string }[] = [];
  if (disclosureGiven !== undefined) checks.push({ ok: disclosureGiven, good: "Said it is an AI", bad: "The agent did not say it is an AI", fix: "Add the AI line to the start of the agent's greeting." });
  if (consentRecorded !== undefined) checks.push({ ok: consentRecorded, good: "Recording consent captured", bad: "No recording consent was captured", fix: "Make the agent ask before recording, and check this call's recording can be kept." });

  return (
    <article aria-labelledby={`${id}-h`} className={cn("w-full space-y-4 rounded-card border border-line bg-surface p-5 max-sm:p-4", className)}>
      <header className="space-y-2">
        <div className="flex flex-wrap items-center gap-2">
          <h3 id={`${id}-h`} className="mr-auto text-body leading-5 font-semibold text-fg">{title}</h3>
          <Badge tone={s.tone}>
            {live && <span aria-hidden className="size-1.5 rounded-full bg-danger motion-safe:animate-pulse-dot" />}
            {status === "failed" && <AlertIcon width={11} height={11} />}
            {s.label}
          </Badge>
          {actions}
        </div>
        <dl className="grid grid-cols-2 gap-x-4 gap-y-2 text-compact sm:grid-cols-3">
          <div><dt className="text-small leading-4 text-fg-muted">Started</dt><dd className="font-mono tabular-nums text-fg">{startedAt}</dd></div>
          <div><dt className="text-small leading-4 text-fg-muted">{live ? "So far" : "Duration"}</dt><dd className="font-mono tabular-nums text-fg">{duration}</dd></div>
          <div className="col-span-2 sm:col-span-1"><dt className="text-small leading-4 text-fg-muted">{live ? "Status" : "Ended because"}</dt><dd className="text-fg">{live ? "Still talking" : endedReason}</dd></div>
        </dl>
      </header>

      {checks.some((c) => !c.ok) && (
        <div className="space-y-2">
          {checks.filter((c) => !c.ok).map((c) => (
            <div key={c.bad} className="flex gap-3 rounded-card bg-warning-soft p-3 text-body leading-5 text-warning-fg">
              <AlertIcon className="mt-0.5 shrink-0" />
              <div><p className="font-medium">Needs you: {c.bad.charAt(0).toLowerCase() + c.bad.slice(1)}</p><p className="mt-0.5">{c.fix}</p></div>
            </div>
          ))}
        </div>
      )}
      {checks.some((c) => c.ok) && (
        <ul className="flex flex-wrap gap-x-4 gap-y-1 text-small leading-4 text-fg-muted">
          {checks.filter((c) => c.ok).map((c) => (
            <li key={c.good} className="inline-flex items-center gap-1"><CheckIcon width={12} height={12} className="text-success-fg" />{c.good}</li>
          ))}
        </ul>
      )}

      <Section title="Outcome" aside={outcome && <AIBadge label="AI review" />}>
        {outcome ? (
          <div className="space-y-2">
            <Badge tone={VERDICT[outcome.verdict].tone}>{VERDICT[outcome.verdict].icon}{VERDICT[outcome.verdict].label}</Badge>
            <p className="text-body leading-5 text-fg">{outcome.rationale}</p>
            {evidence.length > 0 && (transcript?.length || onShowEvidence) && (
              <Button size="sm" variant="ghost" onClick={showEvidence} aria-controls={transcript?.length ? `${id}-t` : undefined} className="-ml-3">
                Show in transcript
              </Button>
            )}
          </div>
        ) : (
          <p className="text-body leading-5 text-fg-muted">{live ? "The review appears when the call ends." : "No review for this call."}</p>
        )}
      </Section>

      {data && Object.keys(data).length > 0 && (
        <Section title="Taken from the call">
          <dl className="grid grid-cols-1 gap-x-4 gap-y-2 text-compact sm:grid-cols-2">
            {Object.entries(data).map(([k, v]) => (
              <div key={k}><dt className="text-small leading-4 text-fg-muted">{k}</dt><dd className="text-fg">{v}</dd></div>
            ))}
          </dl>
        </Section>
      )}

      {cost && (
        <Section title={live ? "Cost so far" : "Cost"}
          aside={hasQuantity && (
            <SegmentedControl size="sm" label="Show cost as" value={view} onChange={setView}
              options={[{ value: "money", label: "Money" }, { value: "quantity", label: "Usage" }]} />
          )}>
          <table className="w-full text-compact">
            <caption className="sr-only">{view === "money" ? "Cost by part of the call" : "Usage by part of the call"}</caption>
            <tbody>
              {cost.items.map((i) => (
                <tr key={i.label} className="border-b border-line">
                  <th scope="row" className="py-1.5 pr-2 text-left font-normal text-fg">{i.label}</th>
                  <td className="py-1.5 text-right text-fg">
                    {view === "money" ? <Money value={i.amount} currency={currency} locale={locale} /> : <span className="font-mono tabular-nums">{i.quantity ?? "Not counted"}</span>}
                  </td>
                </tr>
              ))}
              <tr>
                <th scope="row" className="pt-2 pr-2 text-left font-semibold text-fg">Total</th>
                <td className="pt-2 text-right font-semibold text-fg"><Money value={cost.total} currency={currency} locale={locale} calculated /></td>
              </tr>
            </tbody>
          </table>
        </Section>
      )}

      {transcript && transcript.length > 0 && (
        <Section title="Transcript">
          <ol id={`${id}-t`} className="max-h-72 space-y-1 overflow-y-auto rounded-md pr-1 text-compact" tabIndex={0} aria-label="Call transcript">
            {transcript.map((l) => {
              const isEvidence = shown.includes(l.id);
              return (
                <li key={l.id} ref={(el) => { if (el) lines.current.set(l.id, el); else lines.current.delete(l.id); }} tabIndex={-1}
                  className={cn("rounded-control border-l-2 px-3 py-1.5 outline-none transition-colors duration-(--dur-base) focus-visible:ring-2 focus-visible:ring-fg",
                    isEvidence ? "border-fg bg-sunken" : "border-transparent")}>
                  <p className="flex flex-wrap items-baseline gap-x-2 text-small leading-4 text-fg-muted">
                    <span className="font-medium text-fg">{l.speaker === "agent" ? speakerLabels.agent : speakerLabels.caller}</span>
                    {l.time && <span className="font-mono tabular-nums">{l.time}</span>}
                    {isEvidence && <span className="font-medium text-fg">Evidence for the outcome</span>}
                  </p>
                  <p className="text-fg">{l.text}</p>
                </li>
              );
            })}
          </ol>
        </Section>
      )}
    </article>
  );
}
```

## tattva/organisms/CandlestickChart.tsx

```tsx
import { useId, useRef, useState, type KeyboardEvent, type PointerEvent } from "react";
import { cn } from "../lib/cn";
import type { DataTableProps } from "../molecules/DataTable";
import { formatNumber, niceTicks, useElementWidth } from "../molecules/ChartFrame";

export interface Candle {
  /** Label for this candle, already formatted, for example "12 Mar" or "10:15". */
  t: string;
  /** Open price. */
  o: number;
  /** Highest price. */
  h: number;
  /** Lowest price. */
  l: number;
  /** Close price. */
  c: number;
  /** Volume traded. Needed for the volume strip. */
  v?: number;
}

export interface CandlestickChartProps {
  /** One entry per candle, oldest first. */
  data: Candle[];
  /** Plot height in px, including the volume strip and the x axis band. Minimum 160. */
  height?: number;
  /** Adds a thin volume strip under the prices, on the same x scale. Needs v on the candles. */
  volume?: boolean;
  /** Formats prices on the axis, in the tooltip and in the table. */
  formatValue?: (n: number) => string;
  /** Name for the chart region, for example "Daily price of Acme, last 40 days". */
  ariaLabel: string;
  /** The last candle may still change. It is drawn with a dashed outline and called provisional. */
  streaming?: boolean;
  className?: string;
}

const dirOf = (d: Candle): "up" | "down" => (d.c >= d.o ? "up" : "down");
const pctOf = (d: Candle) => (d.o !== 0 ? ((d.c - d.o) / d.o) * 100 : 0);

/** The change in words, for example "up 1.5 (1.2 percent)". Used by the tooltip, the live region and the table. */
function changeWords(d: Candle, f: (n: number) => string): string {
  return `${dirOf(d)} ${f(Math.abs(d.c - d.o))} (${Math.abs(pctOf(d)).toFixed(2)} percent)`;
}

/**
 * Candlestick chart drawn in SVG at real pixel size. Up candles (close at or above open) are hollow with an up outline.
 * Down candles are filled with the down colour. Direction is also said in words in the tooltip, the live region and the table.
 * The plot takes focus: Left and Right step through candles, Home and End jump, Escape clears.
 */
export function CandlestickChart({ data, height = 280, volume, formatValue = formatNumber, ariaLabel, streaming, className }: CandlestickChartProps) {
  const [ref, measured] = useElementWidth<HTMLDivElement>();
  const svgRef = useRef<SVGSVGElement | null>(null);
  const wrapRef = useRef<HTMLDivElement | null>(null);
  const [active, setActive] = useState<number | null>(null);
  const [said, setSaid] = useState("");
  const uid = useId();

  const w = measured || 640;
  const h = Math.max(160, height);
  const n = data.length;
  const showVol = !!volume && data.some((d) => typeof d.v === "number");
  const volH = showVol ? Math.min(56, Math.max(32, h * 0.18)) : 0;

  const lows = data.map((d) => d.l);
  const highs = data.map((d) => d.h);
  const dMin = n ? Math.min(...lows) : 0;
  const dMax = n ? Math.max(...highs) : 1;
  const pad = (dMax - dMin || Math.abs(dMax) || 1) * 0.06;
  const ticks = niceTicks(dMin - pad, dMax + pad);
  const lo = ticks[0];
  const hi = ticks[ticks.length - 1];
  const tickLabels = ticks.map((t) => formatValue(t));
  const left = Math.max(28, Math.max(...tickLabels.map((t) => t.length)) * 6.6 + 14);
  const right = 12;
  const pw = Math.max(40, w - left - right);

  const plotTop = 12;
  const axisBand = 28;
  const volGap = showVol ? 8 : 0;
  const priceBottom = h - axisBand - volH - volGap;
  const volTop = priceBottom + volGap;
  const volBottom = volTop + volH;
  const py = (v: number) => priceBottom - ((v - lo) / (hi - lo || 1)) * (priceBottom - plotTop);
  const slot = pw / Math.max(1, n);
  const px = (i: number) => left + (i + 0.5) * slot;
  const bodyW = Math.max(2, Math.min(14, slot * 0.6));
  const maxVol = Math.max(1, ...data.map((d) => d.v ?? 0));

  const xw = Math.max(0, ...data.map((d) => d.t.length)) * 6.4 + 14;
  const stepK = Math.max(1, Math.ceil(xw / slot));

  const readout = (i: number) => {
    const d = data[i];
    const prov = streaming && i === n - 1 ? ", provisional" : "";
    const vol = typeof d.v === "number" ? `, volume ${formatNumber(d.v)}` : "";
    return `${d.t}, open ${formatValue(d.o)}, high ${formatValue(d.h)}, low ${formatValue(d.l)}, close ${formatValue(d.c)}, ${changeWords(d, formatValue)}${vol}${prov}`;
  };

  const step = (e: KeyboardEvent<HTMLDivElement>) => {
    if (!n) return;
    let i = active ?? n - 1;
    if (e.key === "ArrowRight") i = Math.min(n - 1, i + 1);
    else if (e.key === "ArrowLeft") i = Math.max(0, i - 1);
    else if (e.key === "Home") i = 0;
    else if (e.key === "End") i = n - 1;
    else if (e.key === "Escape") { setActive(null); setSaid(""); return; }
    else return;
    e.preventDefault();
    setActive(i);
    setSaid(readout(i));
  };

  const point = (e: PointerEvent<SVGRectElement>) => {
    const r = svgRef.current?.getBoundingClientRect();
    if (!r || !n) return;
    setActive(Math.max(0, Math.min(n - 1, Math.floor((e.clientX - r.left - left) / slot))));
  };

  const ai = active !== null && active < n ? active : -1;
  const tipRight = ai >= 0 && px(ai) > w / 2;
  const ad = ai >= 0 ? data[ai] : null;

  return (
    <div ref={ref} className={cn("relative min-w-0", className)}>
      <div ref={wrapRef} tabIndex={0} role="group" aria-roledescription="candlestick chart"
        aria-label={`${ariaLabel}. ${n} candles. Use left and right arrow keys to step through candles.`}
        onKeyDown={step} onFocus={() => { if (n) setActive((p) => p ?? n - 1); }}
        onBlur={() => { setActive(null); setSaid(""); }}
        className="relative rounded-lg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--focus-ring)]">
        <svg ref={svgRef} width={w} height={h} viewBox={`0 0 ${w} ${h}`} className="block max-w-full touch-pan-y select-none" aria-hidden focusable="false"
          style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-small)" }}>
          <defs>
            <clipPath id={`${uid}-clip`}><rect x={left} y={0} width={pw} height={priceBottom + 2} /></clipPath>
          </defs>
          {ticks.map((t, i) => (
            <g key={t}>
              <line x1={left} x2={left + pw} y1={py(t)} y2={py(t)} stroke={i === 0 ? "var(--chart-axis)" : "var(--chart-grid)"} strokeWidth={i === 0 ? 1 : 0.5} shapeRendering="crispEdges" />
              <text x={left - 8} y={py(t)} textAnchor="end" dominantBaseline="central" fill="var(--fg-muted)" style={{ fontVariantNumeric: "tabular-nums" }}>{tickLabels[i]}</text>
            </g>
          ))}
          {showVol && <line x1={left} x2={left + pw} y1={volBottom} y2={volBottom} stroke="var(--chart-axis)" strokeWidth={1} shapeRendering="crispEdges" />}
          {data.map((d, i) => {
            if (i % stepK !== 0) return null;
            const half = (d.t.length * 6.4) / 2;
            const cx = px(i);
            const anchor = cx - half < 0 ? "start" : cx + half > w ? "end" : "middle";
            return <text key={i} x={anchor === "start" ? Math.max(0, cx - half) : cx} y={h - 10} textAnchor={anchor} fill="var(--fg-muted)">{d.t}</text>;
          })}
          {ai >= 0 && <line x1={px(ai)} x2={px(ai)} y1={plotTop} y2={showVol ? volBottom : priceBottom} stroke="var(--chart-axis)" strokeWidth={1} shapeRendering="crispEdges" />}
          <g clipPath={`url(#${uid}-clip)`}>
            {data.map((d, i) => {
              const up = dirOf(d) === "up";
              const color = up ? "var(--up)" : "var(--down)";
              const x = px(i);
              const top = py(Math.max(d.o, d.c));
              const bh = Math.max(1.5, Math.abs(py(d.o) - py(d.c)));
              const prov = streaming && i === n - 1;
              return (
                <g key={i}>
                  <line x1={x} x2={x} y1={py(d.h)} y2={py(d.l)} stroke={color} strokeWidth={1} shapeRendering="crispEdges" />
                  <rect x={x - bodyW / 2} y={top} width={bodyW} height={bh} fill={up ? "var(--surface)" : color} stroke={color}
                    strokeWidth={1.25} strokeDasharray={prov ? "2 2" : undefined} shapeRendering="crispEdges" />
                </g>
              );
            })}
          </g>
          {showVol && data.map((d, i) => {
            const vh = ((d.v ?? 0) / maxVol) * volH;
            return <rect key={i} x={px(i) - bodyW / 2} y={volBottom - vh} width={bodyW} height={vh} fill="var(--chart-axis)" fillOpacity={ai === i ? 0.9 : 0.45} stroke="none" />;
          })}
          {ad && <circle cx={px(ai)} cy={py(ad.c)} r={3.5} fill="var(--surface)" stroke="var(--fg)" strokeWidth={1.5} />}
          <rect x={left} y={0} width={pw + right} height={h - axisBand + 4} fill="transparent" onPointerMove={point} onPointerDown={point}
            onPointerLeave={(e) => { if (e.pointerType === "mouse" && document.activeElement !== wrapRef.current) setActive(null); }} />
        </svg>
        {ad && (
          <div aria-hidden className="pointer-events-none absolute z-10 min-w-40 max-w-64 rounded-overlay border border-line bg-surface px-3 py-2 text-small leading-4 shadow-md"
            style={{ top: plotTop, left: px(ai), transform: `translateX(${tipRight ? "calc(-100% - 12px)" : "12px"})` }}>
            <p className="mb-1 text-fg-muted">{ad.t}{streaming && ai === n - 1 ? ", provisional" : ""}</p>
            {([["Open", ad.o], ["High", ad.h], ["Low", ad.l], ["Close", ad.c]] as const).map(([k, v]) => (
              <p key={k} className="flex items-center justify-between gap-4 leading-5">
                <span className="text-fg-muted">{k}</span>
                <span className="font-semibold text-fg [font-variant-numeric:tabular-nums]">{formatValue(v)}</span>
              </p>
            ))}
            <p className="mt-1 flex items-center gap-2 border-t border-line pt-1 leading-5">
              <svg width={10} height={14} viewBox="0 0 10 14" aria-hidden focusable="false">
                <line x1={5} x2={5} y1={0} y2={14} stroke={dirOf(ad) === "up" ? "var(--up)" : "var(--down)"} strokeWidth={1} />
                <rect x={1.5} y={4} width={7} height={6} fill={dirOf(ad) === "up" ? "var(--surface)" : "var(--down)"} stroke={dirOf(ad) === "up" ? "var(--up)" : "var(--down)"} strokeWidth={1.25} />
              </svg>
              <span className={cn("font-medium", dirOf(ad) === "up" ? "text-up-fg" : "text-down-fg")}>{changeWords(ad, formatValue)}</span>
            </p>
            {typeof ad.v === "number" && (
              <p className="flex items-center justify-between gap-4 leading-5">
                <span className="text-fg-muted">Volume</span>
                <span className="font-semibold text-fg [font-variant-numeric:tabular-nums]">{formatNumber(ad.v)}</span>
              </p>
            )}
          </div>
        )}
      </div>
      <div className="sr-only" aria-live="polite" role="status">{said}</div>
    </div>
  );
}

/** Table data for ChartFrame's table view. The Change column says "up" or "down" in words. */
export function candlestickTable(data: Candle[], formatValue: (n: number) => string = formatNumber, caption = "Open, high, low and close for each candle"): DataTableProps {
  return {
    caption,
    columns: [
      { key: "t", header: "Time" },
      { key: "o", header: "Open", numeric: true },
      { key: "h", header: "High", numeric: true },
      { key: "l", header: "Low", numeric: true },
      { key: "c", header: "Close", numeric: true },
      { key: "chg", header: "Change", numeric: true, direction: (r) => (String(r.chg).startsWith("Up") ? "up" : "down") },
      { key: "v", header: "Volume", numeric: true },
    ],
    rows: data.map((d) => ({
      t: d.t, o: formatValue(d.o), h: formatValue(d.h), l: formatValue(d.l), c: formatValue(d.c),
      chg: `${dirOf(d) === "up" ? "Up" : "Down"} ${formatValue(Math.abs(d.c - d.o))} (${Math.abs(pctOf(d)).toFixed(2)}%)`,
      v: typeof d.v === "number" ? formatNumber(d.v) : "No data",
    })),
  };
}
```

## tattva/organisms/CanvasPanel.tsx

```tsx
import { useEffect, useRef, type ReactNode } from "react";
import { keepFocus, useAnnounceChange } from "../lib/focus";
import { RefreshIcon, XIcon } from "../lib/icons";
import { Button, IconButton } from "../atoms/Button";

/**
 * Side panel for generated artifacts (docs, code, designs) that outlive a single chat message. Version changes are
 * read out, the content can be scrolled from the keyboard, and Close returns focus to what opened the panel.
 */
export function CanvasPanel({ title, version, versions = 1, onVersionChange, onClose, onRegenerate, children, actions, versionMessage = (v, n) => `Version ${v} of ${n}`, focusAfterClose }: {
  title: string; version?: number; versions?: number; onVersionChange?: (v: number) => void; onClose?: () => void; onRegenerate?: () => void; children: ReactNode; actions?: ReactNode;
  /** What screen readers hear when the version changes. */
  versionMessage?: (version: number, versions: number) => string;
  /** Where keyboard focus goes after Close. By default the control that had focus when the panel opened. */
  focusAfterClose?: () => HTMLElement | null;
}) {
  const ref = useRef<HTMLElement>(null);
  // Remember what opened the panel, so Close can go back to it.
  const opener = useRef<HTMLElement | null>(null);
  useEffect(() => { const a = document.activeElement; if (a instanceof HTMLElement && a !== document.body && !ref.current?.contains(a)) opener.current = a; }, []);
  const said = useAnnounceChange(version, (v) => (v != null ? versionMessage(v, versions) : ""));
  return (
    <aside ref={ref} aria-label={title} className="flex h-full flex-col overflow-hidden rounded-card border border-line bg-surface shadow-md">
      <header className="flex flex-wrap items-center gap-x-2 gap-y-1 border-b border-line px-3 py-2">
        <h2 className="min-w-32 flex-1 truncate text-body leading-5 font-medium">{title}</h2>
        {versions > 1 && version != null && (
          <div className="flex items-center gap-1 text-small leading-4 text-fg-muted" role="group" aria-label="Version history">
            <Button size="sm" variant="ghost" disabled={version <= 1} onClick={() => onVersionChange?.(version - 1)} aria-label="Previous version">‹</Button>
            <span className="tabular-nums">v{version} of {versions}</span>
            <Button size="sm" variant="ghost" disabled={version >= versions} onClick={() => onVersionChange?.(version + 1)} aria-label="Next version">›</Button>
          </div>
        )}
        {onRegenerate && <IconButton size="sm" label="Regenerate" onClick={onRegenerate}><RefreshIcon /></IconButton>}
        {actions}
        {onClose && <IconButton size="sm" label="Close panel" onClick={() => { onClose(); keepFocus(ref.current, () => focusAfterClose?.() ?? (opener.current?.isConnected ? opener.current : null)); }}><XIcon /></IconButton>}
      </header>
      <p role="status" aria-live="polite" className="sr-only">{said}</p>
      {/* Reachable from the keyboard, so long content can be scrolled with the arrow keys. */}
      <div tabIndex={0} role="region" aria-label={`${title}, content`} className="min-h-0 flex-1 overflow-auto p-4 outline-offset-[-2px]">{children}</div>
    </aside>
  );
}
```

## tattva/organisms/CatchUpSummary.tsx

```tsx
import { useId, useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { headingTag, subHeadingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { ChevronIcon, CheckIcon, HistoryIcon } from "../lib/icons";
import { AttentionDot } from "../atoms/AttentionDot";
import { Button } from "../atoms/Button";

export interface RecapNeed {
  id: string;
  /** What needs the person, in plain words. */
  text: string;
  actionLabel: string;
  onAction?: () => void;
}

export interface RecapChange {
  label: string;
  count: number;
}

export interface CatchUpSummaryLabels {
  heading?: string;
  /** Receives the time away, such as "2 hours". */
  away?: (time: string) => string;
  finished?: string;
  needsYou?: string;
  changed?: string;
  nothingFinished?: string;
  nothingNeeded?: string;
  showLog?: string;
}

export interface CatchUpSummaryProps {
  /** How long the person was away, such as "2 hours". */
  awayFor: string;
  /** Things that finished while the person was away. */
  finished?: string[];
  /** Things that now wait on the person. */
  needsYou?: RecapNeed[];
  /** What changed, as counts: "Files edited 4". */
  changes?: RecapChange[];
  /** Opens the full log. The button is hidden when omitted. */
  onShowLog?: () => void;
  defaultOpen?: boolean;
  labels?: CatchUpSummaryLabels;
  /** Level of the part's own heading, so it fits the page outline. Default 3; the inner group headings are one level below. */
  headingLevel?: HeadingLevel;
  className?: string;
}

/**
 * "While you were away" card shown when a person comes back to a run. It lists what finished, what
 * needs the person (with actions), what changed as counts, and a way to open the full log. The
 * whole card folds down to its header.
 */
export function CatchUpSummary({ awayFor, finished = [], needsYou = [], changes = [], onShowLog, defaultOpen = true, labels, headingLevel = 3, className }: CatchUpSummaryProps) {
  const l = {
    heading: "While you were away", away: (t: string) => `You were away for ${t}`, finished: "Finished", needsYou: "Needs you", changed: "What changed",
    nothingFinished: "Nothing has finished yet.", nothingNeeded: "Nothing needs you.", showLog: "Show full log", ...labels,
  };
  const [open, setOpen] = useState(defaultOpen);
  const uid = useId();
  const H = headingTag(headingLevel);
  const H2 = subHeadingTag(headingLevel);
  const Section = ({ title, children }: { title: string; children: ReactNode }) => (
    <div><H2 className="mb-1.5 text-small leading-4 font-medium text-fg-muted">{title}</H2>{children}</div>
  );
  return (
    <section aria-labelledby={`${uid}-h`} className={cn("rounded-card border border-line bg-surface", className)}>
      <H id={`${uid}-h`} className="m-0">
        <button type="button" aria-expanded={open} aria-controls={`${uid}-body`} onClick={() => setOpen(!open)}
          className="flex w-full cursor-pointer items-center gap-2.5 rounded-card px-4 py-3 text-left">
          <HistoryIcon className="shrink-0 text-fg-muted" />
          <span className="min-w-0 flex-1">
            <span className="block text-body leading-5 font-medium">{l.heading}</span>
            <span className="block text-small leading-4 font-normal text-fg-muted">{l.away(awayFor)}</span>
          </span>
          <ChevronIcon className={cn("shrink-0 text-fg-subtle transition-transform duration-[var(--dur-fast)] motion-reduce:transition-none", open && "rotate-90")} />
        </button>
      </H>
      <div id={`${uid}-body`} hidden={!open} className="space-y-4 border-t border-line px-4 py-3">
        <Section title={l.finished}>
          {finished.length ? (
            <ul className="space-y-1">{finished.map((f) => (
              <li key={f} className="flex items-start gap-2 text-body leading-5"><CheckIcon className="mt-0.5 shrink-0 text-success" /><span className="min-w-0 break-words">{f}</span></li>
            ))}</ul>
          ) : <p className="text-body leading-5 text-fg-muted">{l.nothingFinished}</p>}
        </Section>
        <Section title={l.needsYou}>
          {needsYou.length ? (
            <ul className="space-y-1.5">{needsYou.map((n) => (
              <li key={n.id} className="flex flex-wrap items-center gap-x-3 gap-y-2 rounded-card border border-attention px-3 py-2">
                <span className="flex min-w-0 flex-1 basis-48 items-start gap-2 text-body leading-5"><span className="mt-1.5"><AttentionDot /></span><span className="min-w-0 break-words">{n.text}</span></span>
                <div className="ml-auto flex flex-wrap justify-end gap-2"><Button size="sm" onClick={n.onAction}>{n.actionLabel}</Button></div>
              </li>
            ))}</ul>
          ) : <p className="text-body leading-5 text-fg-muted">{l.nothingNeeded}</p>}
        </Section>
        {changes.length > 0 && (
          <Section title={l.changed}>
            <ul className="flex flex-wrap gap-2">{changes.map((c) => (
              <li key={c.label} className="rounded-card bg-sunken px-3 py-1.5 text-small leading-4 text-fg-muted"><span className="text-body leading-5 font-medium tabular-nums text-fg">{c.count}</span> {c.label}</li>
            ))}</ul>
          </Section>
        )}
        {onShowLog && <Button variant="ghost" size="sm" onClick={onShowLog}>{l.showLog}</Button>}
      </div>
    </section>
  );
}

/** @deprecated Renamed to CatchUpSummary in 0.29.0, to match the name other design systems use. The old name goes in 0.31.0. */
export const ReEntryRecap = CatchUpSummary;
/** @deprecated Renamed to CatchUpSummaryProps in 0.29.0. */
export type ReEntryRecapProps = CatchUpSummaryProps;
```

## tattva/organisms/ChartLegend.tsx

```tsx
import { cn } from "../lib/cn";

export type MarkerShape = "circle" | "square" | "triangle" | "diamond";

export interface SeriesStyle {
  /** CSS color value, a chart token such as var(--chart-1). */
  color: string;
  /** Marker drawn at line ends and in the legend. */
  shape: MarkerShape;
  /** SVG stroke-dasharray for lines. Undefined means solid. */
  dash?: string;
}

/**
 * Fixed series order. Colors are assigned by position and never cycled. From the third series on, a dash pattern
 * and a marker shape repeat the color so identity never rests on hue alone. Dotted lines are kept for projections.
 */
export const SERIES_STYLES: SeriesStyle[] = [
  { color: "var(--chart-1)", shape: "circle" },
  { color: "var(--chart-2)", shape: "circle" },
  { color: "var(--chart-3)", shape: "square", dash: "9 4" },
  { color: "var(--chart-4)", shape: "triangle", dash: "9 4 2 4" },
  { color: "var(--chart-5)", shape: "diamond", dash: "14 4" },
];
/** Color used for "Other" and context series. */
export const NEUTRAL_STYLE: SeriesStyle = { color: "var(--chart-neutral)", shape: "circle" };

/** Style for series at index i. Index 5 and beyond is not defined: fold the tail into Other. */
export function seriesStyle(i: number): SeriesStyle {
  return SERIES_STYLES[Math.min(i, SERIES_STYLES.length - 1)];
}

/** Path for a marker of the given shape centred on cx, cy. r is the half size (use 4 or more for an 8px marker). */
export function markerPath(shape: MarkerShape, cx: number, cy: number, r: number): string {
  switch (shape) {
    case "square": return `M${cx - r} ${cy - r}h${2 * r}v${2 * r}h${-2 * r}z`;
    case "triangle": return `M${cx} ${cy - r * 1.15}L${cx + r * 1.1} ${cy + r * 0.85}L${cx - r * 1.1} ${cy + r * 0.85}z`;
    case "diamond": return `M${cx} ${cy - r * 1.25}L${cx + r * 1.25} ${cy}L${cx} ${cy + r * 1.25}L${cx - r * 1.25} ${cy}z`;
    default: return `M${cx - r} ${cy}a${r} ${r} 0 1 0 ${2 * r} 0a${r} ${r} 0 1 0 ${-2 * r} 0z`;
  }
}

export interface SeriesMarkProps {
  shape: MarkerShape;
  cx: number;
  cy: number;
  color: string;
  /** Half size in px. Default 4.5, which gives a 9px marker. */
  r?: number;
  /** Hollow marker: surface fill with a colored outline. Used for provisional values. */
  hollow?: boolean;
}

/** Marker with a 2px surface ring so it stays legible where it crosses a line. Use inside an svg. */
export function SeriesMark({ shape, cx, cy, color, r = 4.5, hollow }: SeriesMarkProps) {
  const d = markerPath(shape, cx, cy, r);
  if (hollow) {
    return (<g><path d={d} fill="var(--surface)" stroke="var(--surface)" strokeWidth={5} strokeLinejoin="round" /><path d={d} fill="var(--surface)" stroke={color} strokeWidth={2} strokeLinejoin="round" /></g>);
  }
  return <path d={d} fill={color} stroke="var(--surface)" strokeWidth={2} paintOrder="stroke" strokeLinejoin="round" />;
}

export interface LegendItem {
  id: string;
  label: string;
  /** Overrides the style taken from the item's position. */
  style?: SeriesStyle;
}

export interface ChartLegendProps {
  /** Items in the same order as the chart's series or stack. */
  items: LegendItem[];
  /** "line" shows the dash and marker. "bar" shows a rounded swatch. */
  kind?: "line" | "bar";
  /** Ids currently hidden. Only used when onToggle is set. */
  hidden?: string[];
  /** Makes items toggle buttons. Pressed means the series is shown. */
  onToggle?: (id: string) => void;
  /** Accessible name for the group. */
  label?: string;
  className?: string;
}

function Key({ style, kind }: { style: SeriesStyle; kind: "line" | "bar" }) {
  if (kind === "bar") return <span aria-hidden className="inline-block h-2.5 w-4 shrink-0 rounded-xs" style={{ background: style.color }} />;
  return (
    <svg aria-hidden width={26} height={12} viewBox="0 0 26 12" className="shrink-0">
      <line x1={1} y1={6} x2={25} y2={6} stroke={style.color} strokeWidth={2} strokeDasharray={style.dash} strokeLinecap="butt" />
      <SeriesMark shape={style.shape} cx={13} cy={6} color={style.color} r={4} />
    </svg>
  );
}

/**
 * Legend that defines every visual property: color, dash and marker for lines, color for bars. Order matches the
 * chart. Items become toggle buttons (aria-pressed) when onToggle is given. Render it for two or more series only.
 */
export function ChartLegend({ items, kind = "line", hidden = [], onToggle, label = "Legend", className }: ChartLegendProps) {
  return (
    <ul aria-label={label} className={cn("m-0 mb-2 flex list-none flex-wrap gap-x-4 gap-y-1 p-0", className)}>
      {items.map((it, i) => {
        const st = it.style ?? seriesStyle(i);
        const off = hidden.includes(it.id);
        const inner = (<><Key style={st} kind={kind} /><span className={cn("text-small leading-5", off ? "text-fg-subtle line-through" : "text-fg-muted")}>{it.label}</span></>);
        return (
          <li key={it.id}>
            {onToggle ? (
              <button type="button" aria-pressed={!off} onClick={() => onToggle(it.id)}
                className={cn("inline-flex min-h-6 cursor-pointer items-center gap-1.5 rounded-control px-1.5 hover:bg-hover tap", off && "opacity-70")}>
                {inner}<span className="sr-only">{off ? "hidden" : "shown"}</span>
              </button>
            ) : (
              <span className="inline-flex min-h-6 items-center gap-1.5">{inner}</span>
            )}
          </li>
        );
      })}
    </ul>
  );
}
```

## tattva/organisms/Checklist.tsx

```tsx
import { cn } from "../lib/cn";
import { CheckIcon } from "../lib/icons";
import { type ChecklistItem, ChecklistRow } from "../molecules/ChecklistRow";

export type { ChecklistItem };

/** Hairline-ruled progress list for a running task. Open items first, finished work folded into one line. */
export function Checklist({ items, doneSummary, className }: { items: ChecklistItem[]; doneSummary?: string; className?: string }) {
  return (
    <ul className={cn("border-t border-line", className)}>
      {items.map((i) => <ChecklistRow key={i.id} item={i} />)}
      {doneSummary && (
        <li className="flex items-center gap-2.5 border-b border-line px-1 py-3 text-body leading-5 text-fg-muted">
          <span className="flex size-4 items-center justify-center rounded-full border border-line-strong"><CheckIcon width={10} height={10} /></span>{doneSummary}
        </li>
      )}
    </ul>
  );
}
```

## tattva/organisms/CheckpointList.tsx

```tsx
import { useRef, useState, type ReactNode } from "react";
import { useFocusReturn } from "../lib/focus";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { cn } from "../lib/cn";

export type RestoreMode = "everything" | "conversation" | "work";

export interface Checkpoint {
  id: string;
  /** The prompt that created this checkpoint. */
  prompt: string;
  /** Relative time, for example "12 min ago". */
  time: string;
  /** Number of files changed since the previous checkpoint. */
  files: number;
}

export interface CheckpointListProps {
  /** Newest first or oldest first, rendered in the order given. */
  checkpoints: Checkpoint[];
  /** Id of the checkpoint the work is at now. It is marked and has no actions. */
  currentId?: string;
  /** Called after the person confirms a restore. */
  onRestore?: (id: string, mode: RestoreMode) => void;
  /** Called when Summarize from here is chosen. It runs without a confirm step. */
  onSummarize?: (id: string) => void;
  /** Slot for what the checkpoints do not cover, such as untracked changes. */
  limitations?: ReactNode;
  /** Overrides for any visible label. */
  labels?: Partial<Record<RestoreMode | "summarize" | "confirm" | "cancel" | "current" | "rewind", string>>;
  /** Words for the change count, for example `(n) => `${n} edits``. Defaults to a file count. */
  changeLabel?: (count: number) => string;
  className?: string;
}

const defaults = { everything: "Restore everything", conversation: "Restore conversation only", work: "Restore work only", summarize: "Summarize from here", confirm: "Confirm restore", cancel: "Cancel", current: "Current", rewind: "Rewind" };
const warnings: Record<RestoreMode, string> = {
  everything: "This replaces your work and the conversation with how they were at this point. Later changes are removed.",
  conversation: "This removes the messages after this point. Your work stays as it is.",
  work: "This replaces your work with how it was at this point. The conversation stays as it is.",
};

/** Rewind list with one row per earlier prompt. A destructive restore needs an inline confirm. */
export function CheckpointList({ checkpoints, currentId, onRestore, onSummarize, limitations, labels, changeLabel, className }: CheckpointListProps) {
  const t = { ...defaults, ...labels };
  const [open, setOpen] = useState<string | null>(null);
  const [pending, setPending] = useState<RestoreMode | null>(null);
  const close = () => { setOpen(null); setPending(null); };
  const modes: RestoreMode[] = ["everything", "conversation", "work"];
  // Focus follows the choices: into the confirm step when a restore is picked, back to that choice on Cancel, and
  // back to the row's Rewind button (or the row, if it became the current one) after a restore or a summary.
  const root = useRef<HTMLDivElement>(null);
  const rows = useRef(new Map<string, HTMLLIElement>());
  const last = useRef<{ id: string; mode: RestoreMode | null } | null>(null);
  useFocusReturn(`${open}|${pending}|${currentId}`, root, () => {
    const at = last.current; if (!at) return null;
    const row = rows.current.get(at.id);
    if (open === at.id && pending) return row?.querySelector<HTMLElement>('[role="group"]');
    if (open === at.id && at.mode) return row?.querySelector<HTMLElement>(`[data-mode="${at.mode}"]`);
    return row?.querySelector<HTMLElement>("button[aria-expanded]") ?? row;
  });

  return (
    <div ref={root} className={cn("space-y-3", className)}>
      <ol className="divide-y divide-line rounded-card border border-line bg-surface">
        {checkpoints.map((c) => {
          const current = c.id === currentId;
          const expanded = open === c.id;
          return (
            <li key={c.id} ref={(n) => { if (n) rows.current.set(c.id, n); else rows.current.delete(c.id); }} tabIndex={-1} className="px-4 py-3 outline-offset-[-2px]" aria-current={current ? "step" : undefined}>
              <div className="flex items-start gap-3">
                <div className="min-w-0 flex-1">
                  <p title={typeof c.prompt === "string" ? c.prompt : undefined} className="truncate text-body leading-5 text-fg">{c.prompt}</p>
                  <p className="mt-0.5 text-small leading-4 text-fg-muted">{c.time} · {changeLabel ? changeLabel(c.files) : c.files === 1 ? "1 file changed" : `${c.files} files changed`}</p>
                </div>
                {current ? <Badge tone="accent">{t.current}</Badge> : (
                  <Button size="sm" variant="secondary" aria-expanded={expanded} onClick={() => (expanded ? close() : (setOpen(c.id), setPending(null)))}>{t.rewind}</Button>
                )}
              </div>
              {expanded && !current && (
                <div className="mt-3 space-y-3 rounded-card bg-sunken p-3">
                  {pending === null ? (
                    <div className="flex flex-wrap gap-2">
                      {modes.map((m) => (
                        <Button key={m} size="sm" variant="secondary" data-mode={m} disabled={m === "work" && c.files === 0} onClick={() => { last.current = { id: c.id, mode: m }; setPending(m); }}>{t[m]}</Button>
                      ))}
                      <Button size="sm" variant="ghost" onClick={() => { last.current = { id: c.id, mode: null }; onSummarize?.(c.id); close(); }}>{t.summarize}</Button>
                    </div>
                  ) : (
                    <div role="group" tabIndex={-1} aria-label={t[pending]} className="space-y-2 outline-offset-4">
                      <p className="text-body leading-5 text-fg">{t[pending]}? <span className="text-fg-muted">{warnings[pending]}</span></p>
                      <div className="flex gap-2">
                        <Button size="sm" variant="danger" onClick={() => { last.current = { id: c.id, mode: null }; onRestore?.(c.id, pending); close(); }}>{t.confirm}</Button>
                        <Button size="sm" variant="ghost" onClick={() => setPending(null)}>{t.cancel}</Button>
                      </div>
                    </div>
                  )}
                </div>
              )}
            </li>
          );
        })}
      </ol>
      {limitations && <div className="text-small leading-4 text-fg-muted">{limitations}</div>}
    </div>
  );
}
```

## tattva/organisms/CommandPalette.tsx

```tsx
import { useEffect, useId, useMemo, useRef, useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { SearchIcon } from "../lib/icons";
import { Kbd } from "../atoms/Kbd";

export interface PaletteCommand {
  id: string;
  label: string;
  /** Heading the command is listed under. Commands without one sit above the headings. */
  group?: string;
  /** Short helper text under the label. */
  hint?: string;
  icon?: ReactNode;
  /** Shown as a key hint. Display only: your app wires the real shortcut. */
  shortcut?: string;
  /** Extra words that should find this command. */
  keywords?: string[];
  disabled?: boolean;
  onSelect: () => void;
}

export interface CommandPaletteProps {
  open: boolean;
  onClose: () => void;
  commands: PaletteCommand[];
  placeholder?: string;
  /** Shown when nothing matches. */
  emptyText?: string;
  /** Required. Accessible name for the dialog and the search field. */
  label: string;
  className?: string;
}

/** Lower is better. -1 means no match. Subsequence: the letters appear in order, not necessarily next to each other. */
function rank(c: PaletteCommand, q: string): number {
  const label = c.label.toLowerCase();
  if (label.startsWith(q)) return 0;
  if (label.includes(q)) return 1;
  const sub = (s: string) => { let i = 0; for (const ch of s) if (ch === q[i]) i += 1; return i === q.length; };
  if (sub(label)) return 2;
  const rest = [c.group ?? "", ...(c.keywords ?? [])].join(" ").toLowerCase();
  if (rest.includes(q)) return 3;
  return sub(`${label} ${rest}`) ? 4 : -1;
}

function filter(commands: PaletteCommand[], raw: string): PaletteCommand[] {
  const q = raw.trim().toLowerCase();
  if (!q) return commands;
  return commands.map((c, i) => ({ c, i, r: rank(c, q) })).filter((x) => x.r >= 0).sort((a, b) => a.r - b.r || a.i - b.i).map((x) => x.c);
}

/** Groups in order of first appearance. Commands with no group come first, without a heading. */
function groupUp(list: PaletteCommand[]): { name: string; items: PaletteCommand[] }[] {
  const m = new Map<string, PaletteCommand[]>();
  for (const c of list) m.set(c.group ?? "", [...(m.get(c.group ?? "") ?? []), c]);
  return [...m].map(([name, items]) => ({ name, items })).sort((a, b) => Number(a.name !== "") - Number(b.name !== ""));
}

/**
 * Searchable list of actions in a modal window (native <dialog>). The search field is a combobox and focus stays
 * in it while the arrow keys move through the options. It adds no keyboard shortcut of its own: your app decides how it opens.
 */
export function CommandPalette({ open, onClose, commands, placeholder = "Type a command…", emptyText = "No matching commands", label, className }: CommandPaletteProps) {
  const ref = useRef<HTMLDialogElement>(null);
  const opener = useRef<HTMLElement | null>(null);
  const openRef = useRef(open);
  openRef.current = open;

  useEffect(() => {
    const d = ref.current;
    if (!d || !open) return;
    opener.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
    if (!d.open) d.showModal();
    const root = document.documentElement;
    const prevOverflow = root.style.overflow;
    root.style.overflow = "hidden";
    d.querySelector<HTMLElement>('[role="combobox"]')?.focus({ preventScroll: true });
    return () => {
      root.style.overflow = prevOverflow;
      if (d.open) d.close();
      const back = opener.current;
      if (back && back.isConnected) back.focus({ preventScroll: true });
      opener.current = null;
    };
  }, [open]);

  return (
    <dialog ref={ref} aria-label={label}
      onCancel={(e) => { e.preventDefault(); onClose(); }}
      onClose={() => { if (openRef.current) onClose(); }}
      onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
      className="fixed inset-0 m-0 h-full max-h-none w-full max-w-none items-start justify-center bg-transparent p-0 text-fg backdrop:bg-(--scrim) open:flex">
      {/* Mounted only while open, so the search text and the active option start fresh each time. */}
      {open && <Body commands={commands} placeholder={placeholder} emptyText={emptyText} label={label} onClose={onClose} className={className} />}
    </dialog>
  );
}

function Body({ commands, placeholder, emptyText, label, onClose, className }: Required<Pick<CommandPaletteProps, "commands" | "placeholder" | "emptyText" | "label" | "onClose">> & { className?: string }) {
  const [q, setQ] = useState("");
  const [activeId, setActiveId] = useState<string | null>(null);
  const uid = useId();
  const list = useRef<HTMLDivElement>(null);
  const flatGroups = useMemo(() => groupUp(filter(commands, q)), [commands, q]);
  const flat = useMemo(() => flatGroups.flatMap((g) => g.items), [flatGroups]);
  const enabled = flat.filter((c) => !c.disabled);
  const active = enabled.find((c) => c.id === activeId) ?? enabled[0];
  const optId = (c: PaletteCommand) => `${uid}-o${flat.indexOf(c)}`;

  useEffect(() => {
    // Scroll the list itself rather than calling scrollIntoView, which can also scroll the page.
    const box = list.current;
    const el = active ? box?.querySelector<HTMLElement>(`[id="${optId(active)}"]`) : null;
    if (!box || !el) return;
    if (el.offsetTop - 32 < box.scrollTop) box.scrollTop = Math.max(0, el.offsetTop - 32);
    else if (el.offsetTop + el.offsetHeight > box.scrollTop + box.clientHeight) box.scrollTop = el.offsetTop + el.offsetHeight - box.clientHeight + 8;
  });

  const run = (c: PaletteCommand) => { if (c.disabled) return; onClose(); c.onSelect(); };
  const move = (to: "next" | "prev" | "first" | "last") => {
    if (!enabled.length) return;
    const i = active ? enabled.indexOf(active) : 0;
    const n = to === "first" ? 0 : to === "last" ? enabled.length - 1 : to === "next" ? (i + 1) % enabled.length : (i - 1 + enabled.length) % enabled.length;
    setActiveId(enabled[n].id);
  };
  const onKey = (e: React.KeyboardEvent) => {
    const map: Record<string, "next" | "prev" | "first" | "last"> = { ArrowDown: "next", ArrowUp: "prev", Home: "first", End: "last" };
    if (map[e.key]) { e.preventDefault(); move(map[e.key]); }
    else if (e.key === "Enter" && active) { e.preventDefault(); run(active); }
  };

  const count = flat.length;
  return (
    <div className={cn("mx-3 mt-3 flex max-h-[calc(100dvh-1.5rem)] w-full max-w-xl flex-col overflow-hidden rounded-overlay border border-line bg-raised shadow-lg motion-safe:animate-rise sm:mt-[10dvh] sm:max-h-[min(30rem,80dvh)] forced-colors:border-[CanvasText]", className)}>
      <div className="flex items-center gap-3 border-b border-line px-4">
        <SearchIcon aria-hidden width={16} height={16} className="shrink-0 text-fg-subtle" />
        <input value={q} onChange={(e) => setQ(e.target.value)} onKeyDown={onKey} placeholder={placeholder} aria-label={label}
          role="combobox" aria-expanded={count > 0} aria-controls={`${uid}-list`} aria-autocomplete="list" aria-activedescendant={active ? optId(active) : undefined}
          autoComplete="off" spellCheck={false}
          className="h-14 min-w-0 flex-1 bg-transparent text-lead leading-6 text-fg placeholder:text-fg-subtle focus-visible:outline-none" />
        <Kbd className="shrink-0 max-sm:hidden"><span aria-hidden>Esc</span></Kbd>
      </div>
      <p role="status" aria-live="polite" className="sr-only">{q.trim() ? `${count} result${count === 1 ? "" : "s"}` : ""}</p>
      <div ref={list} id={`${uid}-list`} role="listbox" aria-label={label} className="relative min-h-0 flex-1 overflow-y-auto overscroll-contain p-2">
        {count === 0 && <p className="px-3 py-8 text-center text-body leading-5 text-fg-muted">{emptyText}</p>}
        {flatGroups.map((g, gi) => {
          const options = g.items.map((c) => (
            <div key={c.id} id={optId(c)} role="option" aria-selected={c === active} aria-disabled={c.disabled || undefined}
              onMouseDown={(e) => e.preventDefault()}
              onMouseMove={() => { if (!c.disabled && c !== active) setActiveId(c.id); }}
              onClick={() => run(c)}
              className={cn("flex min-h-9 cursor-pointer items-center gap-3 rounded-lg px-3 py-1.5 text-body leading-5 pointer-coarse:min-h-11 forced-colors:border forced-colors:border-transparent",
                c === active && "bg-hover forced-colors:border-[Highlight]", c.disabled && "cursor-not-allowed opacity-(--disabled-opacity)")}>
              {c.icon && <span aria-hidden className="shrink-0 text-fg-subtle">{c.icon}</span>}
              <span className="min-w-0 flex-1">
                <span className="block truncate">{c.label}</span>
                {c.hint && <span className="block truncate text-small leading-4 text-fg-muted">{c.hint}</span>}
              </span>
              {c.shortcut && <Kbd className="shrink-0 max-sm:hidden">{c.shortcut}</Kbd>}
            </div>
          ));
          if (!g.name) return <div key="__none" className={cn(gi > 0 && "mt-1")}>{options}</div>;
          return (
            <div key={g.name} role="group" aria-labelledby={`${uid}-g${gi}`} className={cn(gi > 0 && "mt-1")}>
              <p id={`${uid}-g${gi}`} className="px-3 pt-2 pb-1 text-small leading-4 font-medium text-fg-muted">{g.name}</p>
              {options}
            </div>
          );
        })}
      </div>
    </div>
  );
}
```

## tattva/organisms/ComparisonTable.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { Badge } from "../atoms/Badge";

export interface ComparisonItem {
  id: string;
  name: string;
  /** Short line under the name. */
  subtitle?: string;
  /** Marks this item with a visible text badge and a lime fill. */
  badge?: string;
}

export interface ComparisonRow {
  id: string;
  /** Attribute name. */
  label: string;
  /** One value per item, in the same order as `items`. */
  values: ReactNode[];
}

export interface ComparisonTableProps {
  /** Two to five items. Extras are ignored. */
  items: ComparisonItem[];
  rows: ComparisonRow[];
  /** Names the table. Visible to screen readers on the table and used as the heading of the stacked cards. */
  caption: string;
  /** Header text of the attribute column. */
  attributeLabel?: string;
  className?: string;
}

/**
 * Semantic comparison table for 2 to 5 items. Column headers are th scope=col, row headers are th scope=row.
 * Below the sm breakpoint (600px) it switches to one stacked card per item. Only one form is exposed at a time.
 */
export function ComparisonTable({ items, rows, caption, attributeLabel = "Attribute", className }: ComparisonTableProps) {
  const list = items.slice(0, 5);
  return (
    <div className={className}>
      <div tabIndex={0} className="hidden overflow-x-auto rounded-card border border-line bg-surface sm:block">
        <table className="w-full min-w-120 border-collapse text-left text-compact">
          <caption className="sr-only">{caption}</caption>
          <thead>
            <tr className="bg-sunken">
              <th scope="col" className="px-4 py-3 text-nano font-normal uppercase tracking-caps text-fg-muted">{attributeLabel}</th>
              {list.map((it) => (
                <th key={it.id} scope="col" className={cn("px-4 py-3 align-bottom font-medium text-fg", it.badge && "bg-lime-faint")}>
                  <span className="block">{it.name}</span>
                  {it.subtitle && <span className="block text-small font-normal text-fg-muted">{it.subtitle}</span>}
                  {it.badge && <Badge tone="accent" className="mt-1">{it.badge}</Badge>}
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            {rows.map((r) => (
              <tr key={r.id} className="border-t border-line align-top">
                <th scope="row" className="px-4 py-3 font-medium text-fg">{r.label}</th>
                {list.map((it, i) => <td key={it.id} className={cn("px-4 py-3 leading-5 text-fg-muted", it.badge && "bg-lime-faint")}>{r.values[i]}</td>)}
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      <div className="sm:hidden">
        <h3 className="font-sans! mb-2 text-compact font-bold">{caption}</h3>
        <ul className="m-0 list-none space-y-3 p-0">
          {list.map((it, i) => (
            <li key={it.id} className="rounded-card border border-line bg-surface p-4">
              <p className="flex flex-wrap items-center gap-2 text-body font-medium">{it.name}{it.badge && <Badge tone="accent">{it.badge}</Badge>}</p>
              {it.subtitle && <p className="text-small text-fg-muted">{it.subtitle}</p>}
              <dl className="mt-3 space-y-2 text-compact">
                {rows.map((r) => (
                  <div key={r.id} className="grid grid-cols-[minmax(0,2fr)_minmax(0,3fr)] gap-3 border-t border-line pt-2">
                    <dt className="text-fg-subtle">{r.label}</dt>
                    <dd className="m-0 text-fg-muted">{r.values[i]}</dd>
                  </div>
                ))}
              </dl>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}
```

## tattva/organisms/Composer.tsx

```tsx
import { useEffect, useId, useRef, useState, type KeyboardEvent, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { PaperclipIcon, SendIcon, StopIcon } from "../lib/icons";
import { IconButton } from "../atoms/Button";
import { useReducedMotion } from "../hooks/useReducedMotion";

export interface ComposerProps {
  onSend: (text: string) => void;
  /** While true the send button becomes Stop and calls onStop. */
  generating?: boolean;
  onStop?: () => void;
  placeholder?: string;
  /** Example messages that take turns as the placeholder while the box is empty. With reduced motion the first one stays. Overrides placeholder. */
  placeholders?: string[];
  /** Milliseconds each example stays. Default 4000. */
  placeholderInterval?: number;
  maxLength?: number;
  /** Called when Attach file is pressed. The attach button renders only when this is provided. */
  onAttach?: () => void;
  /** Extra controls (model picker, tools toggle) rendered left of the send button. */
  tools?: ReactNode;
  disabled?: boolean;
}

export function Composer({ onSend, generating, onStop, onAttach, placeholder = "Message the assistant…", placeholders, placeholderInterval = 4000, maxLength = 4000, tools, disabled }: ComposerProps) {
  const [value, setValue] = useState("");
  const [turn, setTurn] = useState(0);
  const reduced = useReducedMotion();
  const rotating = !!placeholders && placeholders.length > 1 && !reduced && value === "";
  useEffect(() => {
    if (!rotating) return;
    const t = window.setInterval(() => setTurn((n) => n + 1), placeholderInterval);
    return () => window.clearInterval(t);
  }, [rotating, placeholderInterval]);
  const shownPlaceholder = placeholders?.length ? placeholders[(rotating ? turn : 0) % placeholders.length] : placeholder;
  const ref = useRef<HTMLTextAreaElement>(null);
  const inputId = useId();
  const canSend = value.trim().length > 0 && !disabled;

  const submit = () => {
    if (!canSend || generating) return;
    onSend(value.trim());
    setValue("");
    if (ref.current) ref.current.style.height = "auto";
  };
  const onKey = (e: KeyboardEvent<HTMLTextAreaElement>) => {
    // Enter sends, Shift+Enter newline. Never send mid IME composition.
    if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) { e.preventDefault(); submit(); }
  };
  const grow = (el: HTMLTextAreaElement) => { el.style.height = "auto"; el.style.height = `${Math.min(el.scrollHeight, 200)}px`; };
  const nearLimit = value.length > maxLength * 0.9;

  return (
    <div className={cn("rounded-field border border-line-strong bg-surface shadow-sm transition-shadow focus-within:border-fg-subtle focus-within:shadow-md has-[textarea:focus-visible]:outline-2 has-[textarea:focus-visible]:outline-offset-2 has-[textarea:focus-visible]:outline-[var(--focus-ring)]", disabled && "opacity-(--disabled-opacity)")}>
      <label htmlFor={inputId} className="sr-only">Message</label>
      <textarea enterKeyHint="send"
        id={inputId} ref={ref} rows={1} value={value} maxLength={maxLength} disabled={disabled}
        placeholder={shownPlaceholder}
        onChange={(e) => { setValue(e.target.value); grow(e.target); }}
        onKeyDown={onKey}
        className="block w-full resize-none bg-transparent px-5 pt-4 pb-1 text-body-lg leading-relaxed outline-none [field-sizing:content] placeholder:text-fg-subtle"
      />
      <div className="flex flex-wrap items-center gap-1.5 px-2.5 pb-2.5">
        {onAttach && <IconButton label="Attach file" size="sm" variant="secondary" onClick={onAttach}><PaperclipIcon /></IconButton>}
        {tools}
        <span className="ml-auto flex items-center gap-1.5">
        {nearLimit && <span className="mr-2 text-small leading-4 text-warning-fg" aria-live="polite">{value.length}/{maxLength}</span>}
        {generating
          ? <IconButton label="Stop generating" variant="secondary" onClick={onStop}><StopIcon /></IconButton>
          : <IconButton label="Send message" variant="lime" disabled={!canSend} onClick={submit}><SendIcon /></IconButton>}
        </span>
      </div>
    </div>
  );
}
```

## tattva/organisms/ContextPanel.tsx

```tsx
import { useId, useRef } from "react";
import { cn } from "../lib/cn";
import { headingTag, subHeadingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { ContextIcon, FileIcon, GlobeIcon, MemoryIcon, NotebookIcon, PlusIcon, ToolIcon, XIcon } from "../lib/icons";
import { Button, IconButton } from "../atoms/Button";
import { Meter } from "../atoms/Meter";

export type ContextKind = "file" | "app" | "memory" | "page" | "instruction";

export interface ContextItem {
  id: string;
  kind: ContextKind;
  /** Its name, such as "Q3 report.pdf" or "Gmail". */
  label: string;
  /** One short line, such as "12 pages" or "Read only". */
  detail?: string;
  /** Items that cannot be taken out here (an app's own instructions). */
  fixed?: boolean;
}

export interface ContextPanelLabels {
  heading?: string;
  /** Receives how many items there are. */
  count?: (n: number) => string;
  groups?: Partial<Record<ContextKind, string>>;
  /** Receives the item's name. */
  remove?: (label: string) => string;
  /** Read out after an item is taken out. Receives its name. */
  removed?: (label: string) => string;
  add?: string;
  empty?: string;
  /** Receives used and total, already formatted. */
  room?: (used: string, total: string) => string;
  /** Name of the bar for screen readers. */
  roomLabel?: string;
}

export interface ContextPanelProps {
  items: ContextItem[];
  /** Takes an item out of what the assistant can see. */
  onRemove: (id: string) => void;
  /** Opens a way to add files or apps. The button is hidden when omitted. */
  onAdd?: () => void;
  /** How much of the assistant's working memory the items fill, as words or tokens. */
  room?: { used: number; total: number };
  labels?: ContextPanelLabels;
  /** Level of the part's heading, so it fits the page outline. Default 3. */
  headingLevel?: HeadingLevel;
  className?: string;
}

const ORDER: ContextKind[] = ["instruction", "page", "file", "app", "memory"];
const ICON: Record<ContextKind, typeof FileIcon> = { file: FileIcon, app: ToolIcon, memory: MemoryIcon, page: GlobeIcon, instruction: NotebookIcon };
const fmt = (n: number) => n.toLocaleString("en-US");

/**
 * Everything the assistant can see right now, in one place: instructions, the open page, files, connected apps and
 * memories, grouped by kind. Each can be taken out with one press, and focus moves to the next item so a keyboard user
 * never lands on nothing. An optional bar shows how full the assistant's working memory is.
 */
export function ContextPanel({ items, onRemove, onAdd, room, labels, headingLevel = 3, className }: ContextPanelProps) {
  const l = {
    heading: "What the assistant can see", count: (n: number) => (n === 1 ? "1 item" : `${n} items`), remove: (x: string) => `Take out ${x}`,
    removed: (x: string) => `${x} taken out.`, add: "Add", empty: "Nothing is shared yet. The assistant only sees this chat.",
    room: (u: string, t: string) => `Uses ${u} of ${t} tokens`, roomLabel: "Working memory used", ...labels,
    groups: { instruction: "Instructions", page: "Open page", file: "Files", app: "Connected apps", memory: "Memories", ...labels?.groups },
  };
  const uid = useId();
  const H = headingTag(headingLevel);
  const H2 = subHeadingTag(headingLevel);
  const list = useRef<HTMLDivElement>(null);
  const said = useRef<HTMLParagraphElement>(null);
  const take = (item: ContextItem) => {
    // Move focus to the next remove button (or the heading) before the row goes, then say what happened.
    const buttons = [...(list.current?.querySelectorAll<HTMLButtonElement>("[data-remove]") ?? [])];
    const at = buttons.findIndex((b) => b.dataset.remove === item.id);
    const next = buttons[at + 1] ?? buttons[at - 1];
    onRemove(item.id);
    if (said.current) said.current.textContent = l.removed(item.label);
    requestAnimationFrame(() => (next && next.isConnected ? next : document.getElementById(`${uid}-h`))?.focus());
  };
  const groups = ORDER.map((k) => ({ kind: k, items: items.filter((i) => i.kind === k) })).filter((g) => g.items.length);
  return (
    <section aria-labelledby={`${uid}-h`} className={cn("rounded-card border border-line bg-surface p-4", className)}>
      <div className="flex items-center gap-2.5">
        <ContextIcon aria-hidden width={18} height={18} className="shrink-0 text-fg-muted" />
        <H id={`${uid}-h`} tabIndex={-1} className="m-0 min-w-0 flex-1 text-body leading-5 font-semibold text-fg outline-none">{l.heading}</H>
        <span className="text-small leading-5 text-fg-muted">{l.count(items.length)}</span>
      </div>
      <div ref={list} className="mt-3 space-y-4">
        {groups.length === 0 && <p className="m-0 text-small leading-5 text-fg-muted">{l.empty}</p>}
        {groups.map((g) => {
          const Icon = ICON[g.kind];
          return (
            <div key={g.kind}>
              <H2 className="m-0 text-small leading-4 font-medium text-fg-muted">{l.groups[g.kind]}</H2>
              <ul className="mt-1.5 space-y-1">
                {g.items.map((i) => (
                  <li key={i.id} className="flex min-h-10 items-center gap-2.5 rounded-control px-2 py-1 hover:bg-hover">
                    <Icon aria-hidden width={16} height={16} className="shrink-0 text-fg-muted" />
                    <span className="min-w-0 flex-1">
                      <span className="line-clamp-2 block text-body leading-5 text-fg">{i.label}</span>
                      {i.detail && <span className="block truncate text-small leading-4 text-fg-muted">{i.detail}</span>}
                    </span>
                    {!i.fixed && (
                      <IconButton size="sm" label={l.remove(i.label)} data-remove={i.id} onClick={() => take(i)}><XIcon width={14} height={14} /></IconButton>
                    )}
                  </li>
                ))}
              </ul>
            </div>
          );
        })}
      </div>
      {(room || onAdd) && (
        <div className="mt-4 flex flex-wrap items-center gap-x-4 gap-y-3">
          {room && (
            <div className="min-w-0 flex-1 basis-48">
              <p className="m-0 mb-1.5 text-small leading-4 text-fg-muted">{l.room(fmt(room.used), fmt(room.total))}</p>
              <Meter value={room.used} max={room.total} warnAt={0.9} label={l.roomLabel} valueText={l.room(fmt(room.used), fmt(room.total))} />
            </div>
          )}
          {onAdd && <Button size="sm" variant="secondary" leading={<PlusIcon aria-hidden width={14} height={14} />} onClick={onAdd}>{l.add}</Button>}
        </div>
      )}
      <p ref={said} role="status" className="sr-only" />
    </section>
  );
}
```

## tattva/organisms/ConversationList.tsx

```tsx
import { useEffect, useRef, useState } from "react";
import { cn } from "../lib/cn";
import { EditIcon, MoreIcon, PlusIcon, XIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { Menu } from "../molecules/Menu";

export interface ConversationItem { id: string; title: string; group?: string }

export interface ConversationListProps {
  items: ConversationItem[];
  activeId?: string;
  onSelect: (id: string) => void;
  onNew: () => void;
  /** When given, each row gets a "More" menu with Rename. Your code asks for the new name. */
  onRename?: (id: string) => void;
  /** When given, each row gets Delete in its menu. Pressing it asks once, in place, with Keep as easy to press as Delete. */
  onDelete?: (id: string) => void;
  /** Words for the in-place confirmation. */
  labels?: { delete?: string; keep?: string; confirm?: (title: string) => string };
}

/** The history of past chats for a side bar, with an optional row menu and an in-place delete confirmation. */
export function ConversationList({ items, activeId, onSelect, onNew, onRename, onDelete, labels }: ConversationListProps) {
  const groups = [...new Set(items.map((i) => i.group ?? "Recent"))];
  const [asking, setAsking] = useState<string | null>(null);
  const keep = useRef<HTMLButtonElement>(null);
  const L = { delete: labels?.delete ?? "Delete", keep: labels?.keep ?? "Keep it", confirm: labels?.confirm ?? ((t: string) => `Delete “${t}”?`) };
  // Focus lands on the safe choice, so a stray Enter keeps the chat.
  useEffect(() => { if (asking) keep.current?.focus(); }, [asking]);
  const rowMenu = (i: ConversationItem) => {
    const menu = [
      ...(onRename ? [{ id: "rename", label: "Rename", icon: <EditIcon aria-hidden width={14} height={14} />, onSelect: () => onRename(i.id) }] : []),
      ...(onDelete ? [{ id: "delete", label: L.delete, icon: <XIcon aria-hidden width={14} height={14} />, danger: true, onSelect: () => setAsking(i.id) }] : []),
    ];
    if (!menu.length) return null;
    return <Menu portal align="end" triggerLabel={`More for ${i.title}`} trigger={<MoreIcon aria-hidden width={16} height={16} />} items={menu}
      className="shrink-0 [&>button]:size-8 [&>button]:rounded-control [&>button]:text-fg-subtle [&>button]:hover:bg-hover [&>button]:hover:text-fg pointer-coarse:[&>button]:size-11" />;
  };
  return (
    <nav aria-label="Conversations" className="flex flex-col gap-4">
      <Button variant="secondary" leading={<PlusIcon />} onClick={onNew} className="w-full justify-start">New chat</Button>
      {groups.map((g) => (
        <div key={g}>
          <p className="px-2 pb-1 text-nano font-medium uppercase tracking-caps text-fg-subtle">{g}</p>
          <ul>
            {items.filter((i) => (i.group ?? "Recent") === g).map((i) => (
              <li key={i.id}>
                {asking === i.id ? (
                  <div role="group" aria-label={L.confirm(i.title)} onKeyDown={(e) => { if (e.key === "Escape") { e.stopPropagation(); setAsking(null); } }}
                    className="rounded-card border border-line-strong bg-sunken p-2.5">
                    <p className="px-1 pb-2 text-small leading-4 [overflow-wrap:anywhere] text-fg">{L.confirm(i.title)}</p>
                    <div className="flex gap-2">
                      <Button ref={keep} variant="secondary" size="sm" className="flex-1" onClick={() => setAsking(null)}>{L.keep}</Button>
                      <Button variant="secondary" size="sm" className="flex-1" onClick={() => { setAsking(null); onDelete?.(i.id); }}>{L.delete}</Button>
                    </div>
                  </div>
                ) : (
                  <div className="flex items-center gap-1">
                    <button type="button" aria-current={i.id === activeId ? "page" : undefined} onClick={() => onSelect(i.id)}
                      className={cn("min-w-0 flex-1 rounded-control [overflow-wrap:anywhere] px-3 py-1.5 text-left text-small leading-4 hover:bg-hover", i.id === activeId ? "bg-hover text-fg" : "text-fg-muted")}>
                      {i.title}
                    </button>
                    {rowMenu(i)}
                  </div>
                )}
              </li>
            ))}
          </ul>
        </div>
      ))}
    </nav>
  );
}
```

## tattva/organisms/DeckBuildProgress.tsx

```tsx
import { useEffect, useRef, useState } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, CheckIcon, ChevronIcon, RetryIcon, StopIcon } from "../lib/icons";
import { headingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { Button } from "../atoms/Button";

export type DeckBuildState = "queued" | "building" | "partial" | "failed" | "cancelled" | "done";
export type SlideBuildStatus = "waiting" | "building" | "ready" | "failed";
/** What the little drawing of a ready slide looks like. Only a hint for the thumbnail. */
export type SlideThumbKind = "title" | "chart" | "quote" | "columns" | "text";

export interface DeckBuildSlide {
  id: string;
  /** The slide title from the outline, such as "Why churn went up". */
  title: string;
  status: SlideBuildStatus;
  /** Shape of the thumbnail once the slide is ready. Defaults to "text". */
  kind?: SlideThumbKind;
}

export interface DeckBuildProgressProps {
  /** Where the whole job is. Use "partial" when the job ended with some slides made and some failed; "failed" only when nothing usable exists. */
  state: DeckBuildState;
  /** Every slide in outline order. Ready, failed and waiting counts come from here, so the numbers are always real. */
  slides: DeckBuildSlide[];
  /** The real step the job is on, written by your app, such as "Making the chart for slide 4". Never a made-up percentage. */
  currentStep?: string;
  /** Place in the queue while queued. */
  queuePosition?: number;
  /** The job runs on the server. Says "You can leave this page" instead of asking the person to keep it open. */
  background?: boolean;
  /** Why the whole build failed, in plain words. Shown in the failed state. */
  error?: string;
  /** Stops the build and keeps every ready slide. The button says how many it keeps. */
  onStop?: () => void;
  /** Retries one failed slide. */
  onRetrySlide?: (slideId: string) => void;
  /** Retries every failed slide, or the whole build in the failed state. */
  onRetryFailed?: () => void;
  /** Builds the slides that were not made after a stop. */
  onResume?: () => void;
  /** Opens the deck, or one ready slide when an id is given. */
  onOpen?: (slideId?: string) => void;
  /** One row for a phone: status, count, bar and the main action, with the slide list behind a Show slides button. */
  compact?: boolean;
  /** Name of the deck, used in the heading, such as "Q3 churn review". */
  deckTitle?: string;
  headingLevel?: HeadingLevel;
  className?: string;
}

const plural = (n: number, one: string, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;

const STATE_WORD: Record<DeckBuildState, string> = {
  queued: "Waiting to start", building: "Building", partial: "Partly ready", failed: "Build failed", cancelled: "Stopped", done: "Ready",
};

/** Small drawing of a slide made from tokens, so a ready slide is recognisable at a glance. */
function SlideThumb({ kind = "text", status }: { kind?: SlideThumbKind; status: SlideBuildStatus }) {
  const frame = "relative aspect-video w-14 shrink-0 overflow-hidden rounded-control border";
  if (status === "waiting") return <span aria-hidden className={cn(frame, "border-dashed border-line-strong bg-transparent")} />;
  if (status === "failed") {
    return <span aria-hidden className={cn(frame, "grid place-items-center border-line bg-danger-soft text-danger-fg")}><AlertIcon width={14} height={14} /></span>;
  }
  if (status === "building") {
    return (
      <span aria-hidden className={cn(frame, "border-lime bg-sunken")}>
        <span className="absolute inset-x-2 top-2 h-1 rounded-full bg-line-strong" />
        <span className="absolute inset-x-2 top-4 h-1 w-1/2 rounded-full bg-line" />
        <span className="absolute right-1 bottom-1 size-1.5 rounded-full bg-lime motion-safe:animate-pulse-dot" />
      </span>
    );
  }
  const bar = "absolute rounded-full bg-fg-subtle";
  return (
    <span aria-hidden className={cn(frame, "border-line bg-surface")}>
      {kind === "title" && <><span className={cn(bar, "inset-x-3 top-3 h-1.5 bg-fg")} /><span className={cn(bar, "inset-x-5 top-6 h-0.5")} /></>}
      {kind === "chart" && (
        <>
          <span className={cn(bar, "top-1.5 left-1.5 h-0.5 w-6 bg-fg")} />
          <span className="absolute inset-x-2 bottom-1.5 flex h-4 items-end gap-0.5">
            {["h-1/3", "h-1/2", "h-2/3", "h-full", "h-3/4"].map((h, i) => <span key={i} className={cn("flex-1 bg-fg-muted", h)} />)}
          </span>
        </>
      )}
      {kind === "quote" && <><span className="absolute top-0.5 left-1.5 text-small leading-none font-semibold text-fg-muted">“</span><span className={cn(bar, "inset-x-3 top-3 h-0.5")} /><span className={cn(bar, "top-4.5 right-5 left-3 h-0.5")} /><span className={cn(bar, "right-3 bottom-1.5 h-0.5 w-3 bg-fg-muted")} /></>}
      {kind === "columns" && (
        <>
          <span className={cn(bar, "top-1.5 left-1.5 h-0.5 w-6 bg-fg")} />
          <span className="absolute inset-x-1.5 top-3.5 bottom-1.5 flex gap-1">
            {[0, 1, 2].map((i) => <span key={i} className="flex-1 bg-sunken" />)}
          </span>
        </>
      )}
      {kind === "text" && <><span className={cn(bar, "top-1.5 left-1.5 h-0.5 w-6 bg-fg")} />{["top-3.5", "top-5", "top-6.5"].map((t) => <span key={t} className={cn(bar, "left-1.5 h-0.5 w-9", t)} />)}</>}
    </span>
  );
}

const SLIDE_WORD: Record<SlideBuildStatus, string> = { waiting: "Waiting", building: "Building", ready: "Ready", failed: "Failed" };

/**
 * Progress for a deck built slide by slide. It counts slides ready out of the total, shows the real current step,
 * lists every slide with its status, and keeps what is done when the person stops. A failed slide gets its own Retry.
 * The live region speaks when the job changes state or a slide fails, never for each step.
 */
export function DeckBuildProgress({
  state, slides, currentStep, queuePosition, background, error, onStop, onRetrySlide, onRetryFailed, onResume, onOpen,
  compact = false, deckTitle, headingLevel = 3, className,
}: DeckBuildProgressProps) {
  const H = headingTag(headingLevel);
  const total = slides.length;
  const ready = slides.filter((s) => s.status === "ready").length;
  const failed = slides.filter((s) => s.status === "failed");
  const notMade = total - ready - failed.length;
  const active = state === "queued" || state === "building";
  const [listOpen, setListOpen] = useState(!compact);

  const countText = `${ready} of ${plural(total, "slide")} ready`;
  const failText = failed.length ? `, ${failed.length} failed` : "";

  // Announce only changes of job state and new failures. Steps and each ready slide stay silent.
  const announcement =
    state === "queued" ? "Deck build is waiting to start" :
    state === "building" ? (failed.length ? `Slide ${slides.indexOf(failed[failed.length - 1]) + 1} failed. Still building.` : `Building ${plural(total, "slide")}`) :
    state === "partial" ? `Build finished. ${countText}${failText}` :
    state === "failed" ? `Build failed${error ? `. ${error}` : ""}` :
    state === "cancelled" ? `Stopped. ${plural(ready, "slide")} kept` :
    `Deck ready. ${plural(total, "slide")}`;
  const [spoken, setSpoken] = useState("");
  const first = useRef(true);
  useEffect(() => {
    // Skip the first render so a page that opens mid-build does not shout; the heading already says where things are.
    if (first.current) { first.current = false; return; }
    setSpoken(announcement);
  }, [announcement]);

  const headline =
    state === "queued" ? (queuePosition ? `Waiting to start. Number ${queuePosition} in the queue.` : "Waiting to start.") :
    state === "building" ? `${countText}${failText}` :
    state === "partial" ? `${countText}. ${plural(failed.length, "slide")} did not build.` :
    state === "failed" ? (ready ? `${countText}.` : "No slides were made.") :
    state === "cancelled" ? `Stopped. ${plural(ready, "slide")} kept${notMade ? `, ${notMade} not built` : ""}.` :
    `All ${plural(total, "slide")} are ready.`;

  const stopLabel = ready ? `Stop and keep ${plural(ready, "slide")}` : "Stop building";

  const icon =
    state === "done" ? <CheckIcon width={14} height={14} className="text-success-fg" /> :
    state === "failed" ? <AlertIcon width={14} height={14} className="text-danger-fg" /> :
    state === "partial" ? <AlertIcon width={14} height={14} className="text-attention-fg" /> :
    state === "cancelled" ? <StopIcon width={12} height={12} className="text-fg-muted" /> :
    <span className={cn("size-2 rounded-full", state === "building" ? "bg-lime motion-safe:animate-pulse-dot" : "border border-fg-subtle")} />;

  const bar = (
    <div role="progressbar" aria-label="Slides ready" aria-valuemin={0} aria-valuemax={total} aria-valuenow={ready}
      aria-valuetext={`${countText}${failText}`} className="flex h-1.5 w-full gap-0.5">
      {slides.map((s) => (
        <span key={s.id} className={cn("h-full min-w-0 flex-1 rounded-full transition-colors duration-(--dur-base) ease-(--ease-out)",
          s.status === "ready" ? "bg-fg" : s.status === "failed" ? "bg-danger" : s.status === "building" ? "bg-lime" : "bg-line",
          state === "cancelled" && s.status !== "ready" && "bg-line opacity-50")} />
      ))}
    </div>
  );

  const actions = (
    <div className="flex flex-wrap items-center gap-2">
      {active && onStop && <Button size="sm" variant="secondary" leading={<StopIcon width={12} height={12} aria-hidden />} onClick={onStop}>{stopLabel}</Button>}
      {(state === "partial" || (state === "building" && failed.length > 0)) && onRetryFailed && failed.length > 0 && (
        <Button size="sm" variant="secondary" leading={<RetryIcon width={14} height={14} aria-hidden />} onClick={onRetryFailed}>
          {failed.length === 1 ? "Retry the failed slide" : `Retry ${failed.length} failed slides`}
        </Button>
      )}
      {state === "failed" && onRetryFailed && <Button size="sm" variant="secondary" leading={<RetryIcon width={14} height={14} aria-hidden />} onClick={onRetryFailed}>Try the build again</Button>}
      {state === "cancelled" && onResume && notMade + failed.length > 0 && (
        <Button size="sm" variant="secondary" onClick={onResume}>Build the other {plural(notMade + failed.length, "slide")}</Button>
      )}
      {(state === "done" || state === "partial" || state === "cancelled") && ready > 0 && onOpen && <Button size="sm" onClick={() => onOpen()}>Open deck</Button>}
    </div>
  );

  const list = (
    <ol aria-label="Slides" className="divide-y divide-line">
      {slides.map((s, i) => {
        const shownStatus: SlideBuildStatus | "not built" = state === "cancelled" && (s.status === "waiting" || s.status === "building") ? "not built" : s.status;
        return (
          <li key={s.id} className="flex min-h-12 items-center gap-3 py-1.5">
            <SlideThumb kind={s.kind} status={s.status === "building" && !active ? "waiting" : s.status} />
            <span className="min-w-0 flex-1">
              <span className="block truncate text-compact text-fg"><span className="text-fg-muted tabular-nums">{i + 1}.</span> {s.title}</span>
              <span className={cn("flex items-center gap-1 text-small leading-4",
                shownStatus === "failed" ? "text-danger-fg" : shownStatus === "ready" ? "text-fg-muted" : "text-fg-subtle")}>
                {shownStatus === "ready" && <CheckIcon aria-hidden width={12} height={12} />}
                {shownStatus === "failed" && <AlertIcon aria-hidden width={12} height={12} />}
                {shownStatus === "not built" ? "Not built" : SLIDE_WORD[shownStatus]}
              </span>
            </span>
            {s.status === "failed" && onRetrySlide && (
              <Button size="sm" variant="ghost" className="shrink-0" aria-label={`Retry slide ${i + 1}, ${s.title}`} onClick={() => onRetrySlide(s.id)}>Retry</Button>
            )}
            {s.status === "ready" && onOpen && (
              <Button size="sm" variant="ghost" className="shrink-0" aria-label={`Open slide ${i + 1}, ${s.title}`} onClick={() => onOpen(s.id)}>Open</Button>
            )}
          </li>
        );
      })}
    </ol>
  );

  const step = active && (currentStep || background) && (
    <div className="space-y-1">
      {currentStep && state === "building" && <p className="flex min-w-0 items-center gap-2 text-compact text-fg-muted"><span className="truncate">{currentStep}</span></p>}
      {background && <p className="text-small leading-4 text-fg-subtle">You can leave this page. The deck keeps building and we will tell you when it is ready.</p>}
    </div>
  );

  if (compact) {
    return (
      <section aria-label={deckTitle ? `Building ${deckTitle}` : "Deck build"} className={cn("w-full space-y-2 rounded-card border border-line bg-surface p-3", className)}>
        <p role="status" aria-live="polite" className="sr-only">{spoken}</p>
        <div className="flex items-center gap-2">
          <span aria-hidden className="flex size-4 shrink-0 items-center justify-center">{icon}</span>
          <p className="min-w-0 flex-1 truncate text-compact"><span className="font-medium">{STATE_WORD[state]}</span><span className="text-fg-muted"> · {state === "queued" ? "Not started" : `${ready} of ${total} ready`}{failed.length ? `, ${failed.length} failed` : ""}</span></p>
          <button type="button" aria-expanded={listOpen} onClick={() => setListOpen(!listOpen)}
            className="tap inline-flex h-8 shrink-0 cursor-pointer items-center gap-1 rounded-control px-2 text-small leading-4 text-fg-muted hover:bg-hover hover:text-fg">
            {listOpen ? "Hide slides" : "Show slides"}
            <ChevronIcon aria-hidden width={12} height={12} className={cn("transition-transform duration-(--dur-fast)", listOpen ? "-rotate-90" : "rotate-90")} />
          </button>
        </div>
        {bar}
        {state === "building" && currentStep && <p className="truncate text-small leading-4 text-fg-muted">{currentStep}</p>}
        {state === "failed" && error && <p className="text-small leading-4 text-danger-fg">{error}</p>}
        {actions}
        {listOpen && list}
      </section>
    );
  }

  return (
    <section aria-label={deckTitle ? `Building ${deckTitle}` : "Deck build"} className={cn("w-full space-y-3 rounded-card border border-line bg-surface p-4", className)}>
      <p role="status" aria-live="polite" className="sr-only">{spoken}</p>
      <div className="space-y-1">
        <H className="flex items-center gap-2 text-body leading-5 font-semibold text-fg">
          <span aria-hidden className="flex size-4 shrink-0 items-center justify-center">{icon}</span>
          <span className="min-w-0">{STATE_WORD[state]}{deckTitle && <span className="font-normal text-fg-muted">: {deckTitle}</span>}</span>
        </H>
        <p className="text-compact text-fg">{headline}</p>
        {state === "failed" && error && <p className="text-compact text-danger-fg">{error}</p>}
        {state === "partial" && <p className="text-small leading-4 text-attention-fg">Needs you: retry or remove the failed {failed.length === 1 ? "slide" : "slides"} before you present.</p>}
      </div>
      {bar}
      {step}
      {actions}
      {total > 0 && (
        <div>
          <button type="button" aria-expanded={listOpen} onClick={() => setListOpen(!listOpen)}
            className="tap inline-flex h-8 cursor-pointer items-center gap-1 rounded-control text-small leading-4 text-fg-muted hover:text-fg">
            <ChevronIcon aria-hidden width={12} height={12} className={cn("transition-transform duration-(--dur-fast)", listOpen && "rotate-90")} />
            {listOpen ? "Hide slides" : `Show all ${plural(total, "slide")}`}
          </button>
          {listOpen && list}
        </div>
      )}
    </section>
  );
}
```

## tattva/organisms/DeckChangeReview.tsx

```tsx
import { useEffect, useId, useRef, useState, type ReactNode, type RefObject } from "react";
import { cn } from "../lib/cn";
import { type HeadingLevel, headingTag, subHeadingTag } from "../lib/useFocusAfter";
import { ChevronIcon, CheckIcon, HistoryIcon, NeedsReviewIcon, UndoIcon } from "../lib/icons";
import { AIMark } from "../atoms/AIMark";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { SegmentedControl } from "../atoms/SegmentedControl";

/** proposed: waiting for the person. kept: the change will go into the deck. rejected: the slide stays as it was ("Undone" on screen). */
export type DeckChangeState = "proposed" | "kept" | "rejected";

export interface DeckChange {
  slideId: string;
  /** Position in the deck, for example 3. Shown as "Slide 3". */
  slideNumber: number;
  /** The slide's title. */
  title: string;
  /** A drawing of the slide before the AI edit. */
  before: ReactNode;
  /** A drawing of the slide after the AI edit. */
  after: ReactNode;
  /** What changed, in one plain line, for example "Turned the bullet list into a bar chart". */
  summary: string;
  state: DeckChangeState;
}

/** reviewing: decisions open. applying: Done was pressed and the deck is saving. applied: saved as one version. */
export type DeckReviewStatus = "reviewing" | "applying" | "applied";

export interface DeckChangeReviewProps {
  /** What the person asked the AI to do, for example "Make it more visual". */
  request: string;
  changes: DeckChange[];
  /** Called when the person keeps or undoes one slide's change. */
  onChange: (slideId: string, state: DeckChangeState) => void;
  onKeepAll: () => void;
  onRejectAll: () => void;
  /** Applies the kept changes to the deck as one version. Only offered once every change has a decision. */
  onDone: () => void;
  status?: DeckReviewStatus;
  /** side-by-side: Before and After next to each other. toggle: one picture with a Before and After switch. Narrow screens always use toggle. */
  view?: "side-by-side" | "toggle";
  /** list: every slide in a column. pager: one slide at a time with Previous and Next. auto: pager when the part is narrower than a phone. */
  layout?: "auto" | "list" | "pager";
  /** In the applied state, opens version history so the whole edit can be restored. */
  onOpenHistory?: () => void;
  /** Heading level of the panel title. Each slide is one level below. */
  headingLevel?: HeadingLevel;
  className?: string;
}

const plural = (n: number, one: string, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
const SAID: Record<DeckChangeState, string> = { proposed: "back to needs review", kept: "kept", rejected: "undone" };

/** Counts for the header, in words: "2 of 6 kept, 1 undone, 3 left to review". */
export function deckReviewSummary(changes: { state: DeckChangeState }[]) {
  const n = (s: DeckChangeState) => changes.filter((c) => c.state === s).length;
  const parts = [`${n("kept")} of ${plural(changes.length, "slide")} kept`];
  if (n("rejected")) parts.push(`${n("rejected")} undone`);
  parts.push(n("proposed") ? `${n("proposed")} left to review` : "all reviewed");
  return parts.join(", ");
}

function StateBadge({ state }: { state: DeckChangeState }) {
  if (state === "kept") return <Badge tone="success"><CheckIcon aria-hidden width={10} height={10} />Kept</Badge>;
  if (state === "rejected") return <Badge><UndoIcon aria-hidden width={10} height={10} />Undone</Badge>;
  return <Badge tone="warning"><NeedsReviewIcon aria-hidden width={10} height={10} />Needs review</Badge>;
}

function Frame({ label, ai, children }: { label: string; ai?: boolean; children: ReactNode }) {
  return (
    <figure className="m-0 min-w-0">
      <figcaption className="mb-1 flex items-center gap-1 text-micro font-medium text-fg-muted">
        {ai && <span aria-hidden className="inline-flex size-4 items-center justify-center rounded-control bg-lime text-on-lime"><AIMark size={16} className="size-3" /></span>}
        {label}
      </figcaption>
      <div aria-hidden className="relative aspect-video overflow-hidden rounded-xs border border-line bg-bg">{children}</div>
    </figure>
  );
}

/** Narrower than this, the part shows one slide at a time with the toggle view. */
const PAGER_BELOW = 480;

function useNarrow(ref: RefObject<HTMLElement | null>, on: boolean) {
  const [narrow, setNarrow] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!on || !el || typeof ResizeObserver === "undefined") return;
    const ro = new ResizeObserver(([e]) => setNarrow(e.contentRect.width < PAGER_BELOW));
    ro.observe(el);
    return () => ro.disconnect();
  }, [ref, on]);
  return on && narrow;
}

/**
 * Review an AI edit that touched several slides, one slide at a time. Each slide shows what changed in one line, the
 * slide before and after, and Keep or Undo. Nothing reaches the deck until Done, which saves every kept change as one
 * version that can be restored as a whole. Undecided slides are the amber "needs you" state.
 */
export function DeckChangeReview({
  request, changes, onChange, onKeepAll, onRejectAll, onDone, status = "reviewing", view = "side-by-side", layout = "auto",
  onOpenHistory, headingLevel = 3, className,
}: DeckChangeReviewProps) {
  const H = headingTag(headingLevel);
  const SubH = subHeadingTag(headingLevel);
  const id = useId();
  const root = useRef<HTMLElement>(null);
  const narrow = useNarrow(root, layout === "auto");
  const pager = layout === "pager" || narrow;
  const mode = pager ? "toggle" : view;
  const [page, setPage] = useState(0);
  const [shown, setShown] = useState<Record<string, "before" | "after">>({});
  const [said, setSaid] = useState("");
  const [doneHint, setDoneHint] = useState(false);

  const left = changes.filter((c) => c.state === "proposed").length;
  const kept = changes.filter((c) => c.state === "kept").length;
  const locked = status !== "reviewing";
  const at = Math.min(page, Math.max(changes.length - 1, 0));
  const visible = pager ? changes.slice(at, at + 1) : changes;

  useEffect(() => { if (left === 0) setDoneHint(false); }, [left]);
  const lastStatus = useRef(status);
  useEffect(() => {
    if (status === lastStatus.current) return;
    lastStatus.current = status;
    if (status === "applying") setSaid("Saving changes to the deck.");
    if (status === "applied") setSaid(`Saved. ${plural(kept, "slide")} changed as one version.`);
  }, [status, kept]);

  const decide = (c: DeckChange, s: DeckChangeState) => {
    if (locked || c.state === s) return;
    onChange(c.slideId, s);
    const after = left - (c.state === "proposed" ? 1 : 0) + (s === "proposed" ? 1 : 0);
    setSaid(`Slide ${c.slideNumber} ${SAID[s]}. ${after ? `${after} left to review.` : "All slides reviewed."}`);
  };

  if (status === "applied") {
    return (
      <section ref={root} aria-labelledby={`${id}-title`} className={cn("min-w-0 rounded-card border border-line bg-surface p-4", className)}>
        <H id={`${id}-title`} className="flex items-center gap-2 text-body leading-5 font-semibold text-fg">
          <CheckIcon aria-hidden width={16} height={16} className="text-success-fg" />Changes saved
        </H>
        <p className="mt-1 text-small leading-4 text-fg-muted">
          {kept ? <>Saved as one version: <span className="text-fg">AI: {request}, {plural(kept, "slide")}</span>. You can restore the whole edit from version history.</> : "No changes were kept. The deck is as it was."}
        </p>
        {kept > 0 && onOpenHistory && (
          <Button size="sm" variant="secondary" className="mt-3" leading={<HistoryIcon aria-hidden width={14} height={14} />} onClick={onOpenHistory}>Version history</Button>
        )}
        <p role="status" aria-live="polite" className="sr-only">{said}</p>
      </section>
    );
  }

  return (
    <section ref={root} aria-labelledby={`${id}-title`} className={cn("min-w-0 space-y-3", className)}>
      <div className="space-y-2">
        <H id={`${id}-title`} className="flex items-center gap-2 text-body leading-5 font-semibold text-fg">
          <span aria-hidden className="inline-flex size-6 shrink-0 items-center justify-center rounded-control bg-lime text-on-lime"><AIMark size={16} /></span>
          AI changed {plural(changes.length, "slide")}
        </H>
        <p className="text-small leading-4 text-fg-muted">You asked: <q className="text-fg">{request}</q>. Nothing reaches the deck until you press Done.</p>
        <p className={cn("flex flex-wrap items-center gap-2 rounded-card border px-3 py-2 text-small leading-4",
          left ? "border-attention bg-attention-soft text-attention-fg" : "border-line bg-surface text-fg")}>
          {left ? <NeedsReviewIcon aria-hidden width={14} height={14} className="shrink-0" /> : <CheckIcon aria-hidden width={14} height={14} className="shrink-0 text-success-fg" />}
          <span className="font-medium">{left ? "Needs your review." : "All reviewed."}</span>
          <span>{deckReviewSummary(changes)}.</span>
        </p>
      </div>

      <ol className="m-0 list-none space-y-3 p-0" aria-label="Changed slides">
        {visible.map((c) => {
          const face = shown[c.slideId] ?? "after";
          return (
            <li key={c.slideId} className={cn("min-w-0 rounded-card border bg-surface p-3 transition-colors duration-(--dur-fast)",
              c.state === "proposed" ? "border-attention" : "border-line")}>
              <div className="flex flex-wrap items-start gap-2">
                <div className="min-w-0 flex-1">
                  <SubH className="text-body leading-5 font-medium text-fg"><span className="text-fg-muted">Slide {c.slideNumber}</span> {c.title}</SubH>
                  <p className="mt-0.5 text-small leading-4 text-fg-muted">{c.summary}</p>
                </div>
                <StateBadge state={c.state} />
              </div>

              {mode === "side-by-side" ? (
                <div className={cn("mt-3 grid grid-cols-2 gap-3", c.state === "rejected" && "[&_figure:last-child]:opacity-60")}>
                  <Frame label="Before">{c.before}</Frame>
                  <Frame label="After" ai>{c.after}</Frame>
                </div>
              ) : (
                <div className="mt-3 space-y-2">
                  <SegmentedControl size="sm" label={`Slide ${c.slideNumber}: show`} value={face}
                    onChange={(v) => setShown((m) => ({ ...m, [c.slideId]: v }))}
                    options={[{ value: "before", label: "Before" }, { value: "after", label: "After" }]} />
                  <div className="max-w-md">{face === "after" ? <Frame label="After" ai>{c.after}</Frame> : <Frame label="Before">{c.before}</Frame>}</div>
                </div>
              )}

              <div role="group" aria-label={`Slide ${c.slideNumber}, ${c.title}`} className="mt-3 flex flex-wrap gap-2">
                <Button size="sm" variant={c.state === "kept" ? "primary" : "secondary"} aria-pressed={c.state === "kept"} disabled={locked}
                  leading={<CheckIcon aria-hidden width={14} height={14} />} onClick={() => decide(c, c.state === "kept" ? "proposed" : "kept")}>
                  Keep
                </Button>
                <Button size="sm" variant={c.state === "rejected" ? "primary" : "secondary"} aria-pressed={c.state === "rejected"} disabled={locked}
                  leading={<UndoIcon aria-hidden width={14} height={14} />} onClick={() => decide(c, c.state === "rejected" ? "proposed" : "rejected")}>
                  Undo
                </Button>
              </div>
            </li>
          );
        })}
      </ol>

      {pager && changes.length > 1 && (
        <nav aria-label="Changed slides" className="flex items-center justify-between gap-2">
          <Button size="sm" variant="ghost" disabled={at === 0} onClick={() => setPage(at - 1)}
            leading={<ChevronIcon aria-hidden width={14} height={14} className="rotate-180" />}>Previous</Button>
          <span className="text-small leading-4 text-fg-muted">{at + 1} of {changes.length}</span>
          <Button size="sm" variant="ghost" disabled={at >= changes.length - 1} onClick={() => setPage(at + 1)}
            trailing={<ChevronIcon aria-hidden width={14} height={14} />}>Next</Button>
        </nav>
      )}

      <div className="flex flex-wrap items-center gap-2 border-t border-line pt-3">
        <Button size="sm" variant="secondary" disabled={locked} onClick={() => { onKeepAll(); setSaid(`All ${plural(changes.length, "slide")} kept.`); }}>Keep all</Button>
        <Button size="sm" variant="secondary" disabled={locked} onClick={() => { onRejectAll(); setSaid(`All ${plural(changes.length, "slide")} undone.`); }}>Undo all</Button>
        <span className="flex-1" />
        <Button size="sm" busy={status === "applying"} busyLabel="Saving changes" aria-disabled={left > 0 || undefined}
          aria-describedby={left > 0 ? `${id}-hint` : undefined}
          onClick={() => { if (left > 0) { setDoneHint(true); return; } onDone(); }}
          className={cn(left > 0 && "opacity-60")}>
          {kept ? `Done, apply ${plural(kept, "change")}` : "Done"}
        </Button>
        <p id={`${id}-hint`} className={cn("w-full text-micro", doneHint ? "text-attention-fg" : "text-fg-muted", left === 0 && "sr-only")}>
          {left > 0 ? `Keep or undo ${plural(left, "more slide")} first, or use Keep all.` : ""}
        </p>
      </div>

      <p role="status" aria-live="polite" className="sr-only">{said}</p>
    </section>
  );
}
```

## tattva/organisms/Dialog.tsx

```tsx
import { useEffect, useId, useRef, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, XIcon } from "../lib/icons";
import { IconButton } from "../atoms/Button";
import { usePresence } from "../hooks/usePresence";

export type DialogSize = "sm" | "md" | "lg";
export type DialogTone = "default" | "destructive";

export interface DialogProps {
  open: boolean;
  /** Called when the dialog asks to close: Escape, a backdrop click or the Close button. Set `open` to false in response. */
  onClose: () => void;
  /** Required. Rendered as the heading and used as the dialog's name. */
  title: string;
  /** Extra context announced after the title. Rendered visibly under it. */
  description?: string;
  /** The body. It scrolls when it is taller than the screen. */
  children?: ReactNode;
  /** Usually buttons. The main action goes last. */
  footer?: ReactNode;
  size?: DialogSize;
  /** When false, Escape and a backdrop click do nothing, the Close button is hidden and only your footer buttons can close it. */
  dismissible?: boolean;
  /** "destructive" adds a danger icon and the word "Destructive" to the title row and sets role="alertdialog". */
  tone?: DialogTone;
  className?: string;
}

const widths: Record<DialogSize, string> = { sm: "sm:max-w-sm", md: "sm:max-w-lg", lg: "sm:max-w-2xl" };
const FOCUSABLE = 'a[href],button:not([disabled]),input:not([disabled]):not([type="hidden"]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';

/**
 * Modal window on the native <dialog> element. showModal() makes the page behind inert and keeps Tab inside.
 * Focus goes to the first control in the body or footer (or the dialog), and returns to the opener on close.
 * Under the sm breakpoint it becomes a bottom sheet.
 */
export function Dialog({ open, onClose, title, description, children, footer, size = "md", dismissible = true, tone = "default", className }: DialogProps) {
  const ref = useRef<HTMLDialogElement>(null);
  const opener = useRef<HTMLElement | null>(null);
  const openRef = useRef(open);
  openRef.current = open;
  const id = useId();
  const titleId = `${id}-title`;
  const descId = `${id}-desc`;
  const destructive = tone === "destructive";
  // Closing is a quick fade (faster than opening); the dialog stays modal until it has gone.
  const { present, leaving } = usePresence(open);

  useEffect(() => {
    const d = ref.current;
    if (!d || !present) return;
    opener.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
    if (!d.open) d.showModal();
    const root = document.documentElement;
    const prevOverflow = root.style.overflow;
    root.style.overflow = "hidden";
    // Prefer the body or footer over the Close button, so a form field or the main action is where you start.
    const first = d.querySelector<HTMLElement>(`[data-dialog-body] :is(${FOCUSABLE}), [data-dialog-footer] :is(${FOCUSABLE})`) ?? d.querySelector<HTMLElement>(FOCUSABLE);
    (first ?? d).focus({ preventScroll: true });
    return () => {
      root.style.overflow = prevOverflow;
      if (d.open) d.close();
      const back = opener.current;
      if (back && back.isConnected) back.focus({ preventScroll: true });
      opener.current = null;
    };
  }, [present]);

  return (
    <dialog ref={ref} tabIndex={-1} role={destructive ? "alertdialog" : undefined} aria-labelledby={titleId} aria-describedby={description ? descId : undefined}
      // Escape is turned into a request, so the parent stays the one source of truth for `open`.
      onCancel={(e) => { e.preventDefault(); if (dismissible) onClose(); }}
      onClose={() => { if (openRef.current) onClose(); }}
      onClick={(e) => { if (dismissible && e.target === e.currentTarget) onClose(); }}
      className="fixed inset-0 m-0 h-full max-h-none w-full max-w-none items-end justify-center bg-transparent p-0 text-fg backdrop:bg-(--scrim) open:flex sm:items-center sm:p-4">
      {present && (
        <div className={cn(leaving && "pointer-events-none motion-safe:animate-fade-out!",
          "flex max-h-[min(90dvh,100%)] w-full flex-col overflow-hidden rounded-t-overlay border border-line bg-raised shadow-lg motion-safe:animate-sheet sm:max-h-[85dvh] sm:rounded-overlay sm:motion-safe:animate-rise forced-colors:border-[CanvasText]",
          widths[size], className,
        )}>
          <div className="flex items-start gap-3 px-5 pt-5 pb-3">
            <div className="min-w-0 flex-1">
              {destructive && (
                <p className="mb-1 inline-flex items-center gap-1.5 text-small leading-4 font-medium text-danger-fg">
                  <AlertIcon aria-hidden width={14} height={14} className="shrink-0" />Destructive
                </p>
              )}
              <h2 id={titleId} className="text-subhead leading-snug font-semibold text-balance">{title}</h2>
              {description && <p id={descId} className="mt-1 text-body leading-5 text-fg-muted">{description}</p>}
            </div>
            {dismissible && (
              <IconButton label="Close" onClick={onClose} className="-mt-1 -mr-2 shrink-0 pointer-coarse:size-11">
                <XIcon aria-hidden width={16} height={16} />
              </IconButton>
            )}
          </div>
          {children != null && <div data-dialog-body className="min-h-0 flex-1 overflow-y-auto overscroll-contain px-5 py-2 text-body leading-5">{children}</div>}
          {footer != null && <div data-dialog-footer className="flex flex-col-reverse gap-2 border-t border-line px-5 py-4 pb-[max(1rem,var(--safe-bottom))] sm:flex-row sm:justify-end">{footer}</div>}
        </div>
      )}
    </dialog>
  );
}
```

## tattva/organisms/DiffView.tsx

```tsx
import { useState } from "react";
import { cn } from "../lib/cn";
import { Button } from "../atoms/Button";
import { CheckIcon, XIcon } from "../lib/icons";
import { Badge } from "../atoms/Badge";
import { DiffFileRow, type DiffHunkState, type DiffReviewState } from "../molecules/DiffFileRow";

export type DiffLineType = "add" | "remove" | "context";
export interface DiffLine { type: DiffLineType; text: string; oldNo?: number; newNo?: number }
export interface DiffHunk {
  /** Hunk header, such as "@@ -12,6 +12,9 @@" or a function name. */
  header?: string;
  lines: DiffLine[];
}
export interface DiffFile {
  id: string;
  path: string;
  hunks: DiffHunk[];
}

export type DiffStatuses = Record<string, DiffReviewState>;
/** Decision per hunk, by file id then hunk index. Missing entries follow the file's decision, or pending. */
export type DiffHunkStatuses = Record<string, DiffHunkState[]>;
export type { DiffHunkState, DiffReviewState };

export interface DiffViewLabels {
  files?: string;
  keepAll?: string;
  rejectAll?: string;
  nextFile?: string;
  /** Receives the number of files still pending. */
  remaining?: (n: number) => string;
  allReviewed?: string;
  noChanges?: string;
  keep?: string;
  reject?: string;
  kept?: string;
  rejected?: string;
  partial?: string;
  undo?: string;
  /** Receives the 1-based hunk number. */
  hunk?: (n: number) => string;
  /** Spoken after a whole file is decided or undone, such as "src/app.ts kept." */
  announceFile?: (path: string, state: DiffHunkState) => string;
  /** Spoken after one hunk is decided or undone. Receives the 1-based hunk number and the file's state after it. */
  announceHunk?: (n: number, path: string, state: DiffHunkState, fileState: DiffReviewState) => string;
  /** Spoken after Keep all or Reject all. */
  announceAll?: (count: number, state: DiffHunkState) => string;
}

const said: Record<DiffReviewState, string> = { kept: "kept", rejected: "rejected", pending: "returned to pending", partial: "partly kept" };

export interface DiffViewProps {
  files: DiffFile[];
  /** Review state per file id (controlled). Missing ids are pending. */
  statuses?: DiffStatuses;
  /** Initial states when uncontrolled. */
  defaultStatuses?: DiffStatuses;
  onStatusesChange?: (next: DiffStatuses) => void;
  /** Decision per hunk (controlled). Missing entries follow the file's decision, or pending. */
  hunkStatuses?: DiffHunkStatuses;
  /** Initial hunk decisions when uncontrolled. */
  defaultHunkStatuses?: DiffHunkStatuses;
  onHunkStatusesChange?: (next: DiffHunkStatuses) => void;
  /** Called when one hunk's Keep, Reject or Undo is chosen. Index is zero-based. */
  onHunkChange?: (fileId: string, hunkIndex: number, state: DiffHunkState) => void;
  /** Id of the file whose hunks are shown (controlled). */
  activeId?: string;
  defaultActiveId?: string;
  onActiveChange?: (id: string) => void;
  labels?: DiffViewLabels;
  className?: string;
}

const countOf = (f: DiffFile) => f.hunks.reduce((a, h) => {
  for (const l of h.lines) { if (l.type === "add") a.added++; else if (l.type === "remove") a.removed++; }
  return a;
}, { added: 0, removed: 0 });

const sign: Record<DiffLineType, { char: string; sr: string; cls: string }> = {
  add: { char: "+", sr: "Added: ", cls: "bg-success-soft text-success-fg" },
  remove: { char: "−", sr: "Removed: ", cls: "bg-danger-soft text-danger-fg" },
  context: { char: "", sr: "", cls: "text-fg-muted" },
};

/** Hunk renderer: a header with per-hunk Keep and Reject, then line numbers, a sign column and the line text. Added and removed lines carry a + or minus sign as well as color. */
function Hunks({ file, hunkState, onHunk, l }: { file: DiffFile; hunkState: (i: number) => DiffHunkState; onHunk: (i: number, s: DiffHunkState) => void; l: Required<Pick<DiffViewLabels, "keep" | "reject" | "kept" | "rejected" | "undo" | "hunk">> }) {
  return (
    <div role="group" aria-label={`Changes in ${file.path}`}>
      {file.hunks.map((h, hi) => {
        const st = hunkState(hi);
        const where = `${l.hunk(hi + 1).toLowerCase()} in ${file.path}`;
        return (
          <div key={hi} className="mb-1 last:mb-0">
            <div className="flex flex-wrap items-center gap-x-3 gap-y-1 bg-sunken px-3 py-1.5">
              <p className="min-w-0 flex-1 font-mono text-small leading-4 text-fg-subtle"><span className="font-sans font-medium text-fg-muted">{l.hunk(hi + 1)}</span>{h.header ? ` ${h.header}` : ""}</p>
              {st === "pending" ? (
                <span className="flex gap-1.5">
                  <Button size="sm" variant="secondary" onClick={() => onHunk(hi, "kept")} aria-label={`${l.keep} ${where}`}>{l.keep}</Button>
                  <Button size="sm" variant="ghost" onClick={() => onHunk(hi, "rejected")} aria-label={`${l.reject} ${where}`}>{l.reject}</Button>
                </span>
              ) : (
                <span className="flex items-center gap-1.5">
                  <Badge tone={st === "kept" ? "success" : "danger"}>
                    {st === "kept" ? <CheckIcon width={11} height={11} /> : <XIcon width={11} height={11} />}
                    {st === "kept" ? l.kept : l.rejected}
                  </Badge>
                  <Button size="sm" variant="ghost" onClick={() => onHunk(hi, "pending")} aria-label={`${l.undo}: ${where}`}>{l.undo}</Button>
                </span>
              )}
            </div>
            <div tabIndex={0} role="group" aria-label={`Lines of ${where}`} className="overflow-x-auto font-mono text-small leading-5">
              <div className="min-w-max">
                {h.lines.map((ln, i) => {
                  const s = sign[ln.type];
                  return (
                    <div key={i} className={cn("grid grid-cols-[3rem_3rem_1.5rem_1fr]", s.cls, st === "rejected" && "opacity-60")}>
                      {/* On the tinted added and removed rows the line numbers use the darker grey, so they keep 4.5:1 in every theme. */}
                      <span aria-hidden className={cn("px-2 text-right tabular-nums select-none", ln.type === "context" ? "text-fg-subtle" : "text-fg-muted")}>{ln.oldNo ?? ""}</span>
                      <span aria-hidden className={cn("px-2 text-right tabular-nums select-none", ln.type === "context" ? "text-fg-subtle" : "text-fg-muted")}>{ln.newNo ?? ""}</span>
                      <span aria-hidden className="text-center select-none">{s.char}</span>
                      <span className="pr-3 pl-1 whitespace-pre"><span className="sr-only">{s.sr}</span>{ln.text}</span>
                    </div>
                  );
                })}
              </div>
            </div>
          </div>
        );
      })}
    </div>
  );
}

/**
 * Review of an agent's changes. A file list with per-file Keep and Reject sits beside the hunks of the
 * selected file, and each hunk has its own Keep and Reject. A file is kept or rejected when all its hunks are,
 * and partly kept when they are mixed. A bottom bar offers Keep all, Reject all and Next file with a count of files left.
 */
export function DiffView({ files, statuses, defaultStatuses, onStatusesChange, hunkStatuses, defaultHunkStatuses, onHunkStatusesChange, onHunkChange, activeId, defaultActiveId, onActiveChange, labels, className }: DiffViewProps) {
  const l = {
    files: "Changed files", keepAll: "Keep all", rejectAll: "Reject all", nextFile: "Next file",
    remaining: (n: number) => `${n} ${n === 1 ? "file" : "files"} left to review`,
    allReviewed: "All files reviewed", noChanges: "No changes to review.",
    keep: "Keep", reject: "Reject", kept: "Kept", rejected: "Rejected", partial: "Partly kept", undo: "Undo",
    hunk: (n: number) => `Hunk ${n}`,
    announceFile: (path: string, st: DiffHunkState) => `${path} ${said[st]}.`,
    announceHunk: (n: number, path: string, st: DiffHunkState, fs: DiffReviewState) => `Hunk ${n} in ${path} ${said[st]}. ${path} is ${fs === "pending" ? "pending" : said[fs]}.`,
    announceAll: (n: number, st: DiffHunkState) => `${n} ${n === 1 ? "file" : "files"} ${said[st]}.`,
    ...labels,
  };
  const [innerStatuses, setInnerStatuses] = useState<DiffStatuses>(defaultStatuses ?? {});
  const [innerHunks, setInnerHunks] = useState<DiffHunkStatuses>(defaultHunkStatuses ?? {});
  const [innerActive, setInnerActive] = useState(defaultActiveId ?? files[0]?.id);
  const [announce, setAnnounce] = useState("");
  const st = statuses ?? innerStatuses;
  const hs = hunkStatuses ?? innerHunks;
  const active = files.find((f) => f.id === (activeId ?? innerActive)) ?? files[0];

  /** Decision for one hunk. Without a hunk entry it follows the file's kept or rejected decision. */
  const hunkOf = (f: DiffFile, i: number): DiffHunkState => {
    const own = hs[f.id]?.[i];
    if (own) return own;
    const fs = st[f.id];
    return fs === "kept" || fs === "rejected" ? fs : "pending";
  };
  const hunksOf = (f: DiffFile) => f.hunks.map((_, i) => hunkOf(f, i));
  const derive = (arr: DiffHunkState[]): DiffReviewState => {
    if (arr.length && arr.every((x) => x === "kept")) return "kept";
    if (arr.length && arr.every((x) => x === "rejected")) return "rejected";
    return arr.includes("kept") ? "partial" : "pending";
  };
  const stateOf = (f: DiffFile): DiffReviewState => (f.hunks.length ? derive(hunksOf(f)) : st[f.id] ?? "pending");
  const needsReview = (f: DiffFile) => (f.hunks.length ? hunksOf(f).includes("pending") : stateOf(f) === "pending");
  const pending = files.filter(needsReview);

  const setActive = (id: string) => { if (activeId === undefined) setInnerActive(id); onActiveChange?.(id); };
  /** Write new hunk decisions for some files and derive each file's state from them. */
  const commit = (updates: Record<string, DiffHunkState[]>, message: (left: number) => string) => {
    const nextHunks = { ...hs, ...updates };
    const nextStatuses = { ...st };
    for (const id of Object.keys(updates)) nextStatuses[id] = derive(updates[id]);
    if (hunkStatuses === undefined) setInnerHunks(nextHunks);
    if (statuses === undefined) setInnerStatuses(nextStatuses);
    onHunkStatusesChange?.(nextHunks);
    onStatusesChange?.(nextStatuses);
    const left = files.filter((f) => (updates[f.id] ? updates[f.id].includes("pending") : needsReview(f))).length;
    setAnnounce(`${message(left)} ${left === 0 ? l.allReviewed : l.remaining(left)}`);
  };
  const decide = (f: DiffFile, s: DiffHunkState) => {
    const arr = f.hunks.map(() => s);
    if (!f.hunks.length) {
      const nextStatuses = { ...st, [f.id]: s };
      if (statuses === undefined) setInnerStatuses(nextStatuses);
      onStatusesChange?.(nextStatuses);
      const left = files.filter((x) => (x.id === f.id ? s === "pending" : needsReview(x))).length;
      setAnnounce(`${l.announceFile(f.path, s)} ${left === 0 ? l.allReviewed : l.remaining(left)}`);
      return;
    }
    commit({ [f.id]: arr }, () => l.announceFile(f.path, s));
  };
  const decideHunk = (f: DiffFile, i: number, s: DiffHunkState) => {
    const arr = hunksOf(f); arr[i] = s;
    onHunkChange?.(f.id, i, s);
    commit({ [f.id]: arr }, () => l.announceHunk(i + 1, f.path, s, derive(arr)));
  };
  const decideAll = (s: DiffHunkState) => {
    const updates: Record<string, DiffHunkState[]> = {};
    for (const f of pending) updates[f.id] = hunksOf(f).map((x) => (x === "pending" ? s : x));
    const n = pending.length;
    commit(updates, () => l.announceAll(n, s));
  };
  const next = () => {
    if (!pending.length) return;
    const from = files.findIndex((f) => f.id === active?.id);
    const after = [...files.slice(from + 1), ...files.slice(0, from + 1)].find(needsReview);
    if (after) setActive(after.id);
  };

  if (!files.length) return <p className="rounded-card border border-line bg-surface p-4 text-body leading-5 text-fg-muted">{l.noChanges}</p>;

  return (
    <section aria-label="Review changes" className={cn("w-full overflow-hidden rounded-card border border-line bg-surface", className)}>
      <div className="grid md:grid-cols-[minmax(0,18rem)_1fr]">
        <ul aria-label={l.files} className="space-y-0.5 border-b border-line p-2 md:border-r md:border-b-0">
          {files.map((f) => {
            const c = countOf(f);
            return (
              <li key={f.id}>
                <DiffFileRow path={f.path} added={c.added} removed={c.removed} state={stateOf(f)} selected={f.id === active?.id}
                  labels={{ keep: l.keep, reject: l.reject, kept: l.kept, rejected: l.rejected, partial: l.partial, undo: l.undo }}
                  onSelect={() => setActive(f.id)} onKeep={() => decide(f, "kept")} onReject={() => decide(f, "rejected")} onReset={() => decide(f, "pending")} />
              </li>
            );
          })}
        </ul>
        <div className="min-w-0 py-2">
          {active && <Hunks file={active} hunkState={(i) => hunkOf(active, i)} onHunk={(i, s) => decideHunk(active, i, s)} l={l} />}
        </div>
      </div>
      <div className="flex flex-wrap items-center gap-2 border-t border-line bg-sunken px-4 py-2.5">
        <p className="flex-1 text-small leading-4 text-fg-muted">{pending.length ? l.remaining(pending.length) : l.allReviewed}</p>
        <Button size="sm" variant="secondary" disabled={!pending.length} onClick={() => decideAll("kept")}>{l.keepAll}</Button>
        <Button size="sm" variant="ghost" disabled={!pending.length} onClick={() => decideAll("rejected")}>{l.rejectAll}</Button>
        <Button size="sm" disabled={!pending.length} onClick={next}>{l.nextFile}</Button>
      </div>
      <p role="status" aria-live="polite" className="sr-only">{announce}</p>
    </section>
  );
}
```

## tattva/organisms/DotPlot.tsx

```tsx
import { useRef, useState, type KeyboardEvent, type PointerEvent } from "react";
import { cn } from "../lib/cn";
import type { DataTableProps } from "../molecules/DataTable";
import { formatNumber, niceTicks, useElementWidth } from "../molecules/ChartFrame";

export interface DotDatum {
  /** Row name, for example a model or a prompt version. */
  label: string;
  /** Point estimate, such as a mean score. */
  estimate: number;
  /** Lower end of the interval. */
  low: number;
  /** Upper end of the interval. */
  high: number;
  /** Sample size behind the estimate. */
  n?: number;
}

export interface DotPlotProps {
  /** One row per estimate, in display order. */
  data: DotDatum[];
  /**
   * Required sentence that says what the dot and the line mean, with the interval level and sample size.
   * For example "Dots show mean score, lines show 95% confidence intervals, n = 200".
   */
  caption: string;
  /** Show the sample size next to each row. Use it for small samples. */
  showN?: boolean;
  /** Name of the estimate, used in announcements and the tooltip, for example "Mean score". */
  measure?: string;
  /** Names the interval in announcements and the tooltip, for example "95% confidence interval". */
  intervalLabel?: string;
  /** Formats the axis, labels and tooltip. */
  formatValue?: (n: number) => string;
  /** Unit word read after each value by screen readers. */
  unit?: string;
  /** Fixed value axis. Defaults to the interval extent. This chart does not need to start at zero. */
  domain?: [number, number];
  /** A named reference line, for example the current production model. */
  reference?: { value: number; label: string };
  /** Name for the chart region. */
  ariaLabel?: string;
  className?: string;
}

/**
 * Estimates with intervals: a filled dot for the estimate and a hairline for its range. A caption above the plot says
 * what the dot and the line mean. Estimates and intervals are printed beside each row when there is room, and the
 * sample size is optional. Rows are reachable with the arrow keys.
 */
export function DotPlot({ data, caption, showN, measure = "Estimate", intervalLabel = "interval", formatValue = formatNumber, unit = "", domain, reference, ariaLabel = "Dot plot", className }: DotPlotProps) {
  const [ref, measured] = useElementWidth<HTMLDivElement>();
  const wrapRef = useRef<HTMLDivElement | null>(null);
  const svgRef = useRef<SVGSVGElement | null>(null);
  const [active, setActive] = useState<number | null>(null);
  const [said, setSaid] = useState("");

  const w = measured || 640;
  const n = data.length;
  const f = formatValue;
  const lo0 = domain ? domain[0] : Math.min(...data.map((d) => d.low), reference?.value ?? Infinity);
  const hi0 = domain ? domain[1] : Math.max(...data.map((d) => d.high), reference?.value ?? -Infinity);
  const ticks = domain ? niceTicks(domain[0], domain[1]) : niceTicks(Number.isFinite(lo0) ? lo0 : 0, Number.isFinite(hi0) ? hi0 : 1);
  const lo = domain ? domain[0] : ticks[0];
  const hi = domain ? domain[1] : ticks[ticks.length - 1];
  const tickLabels = ticks.map((t) => f(t));
  const tickW = Math.max(...tickLabels.map((t) => t.length)) * 6.6;

  const values = (d: DotDatum) => `${f(d.estimate)} (${f(d.low)} to ${f(d.high)})`;
  const showValues = w >= 440;
  const rightText = Math.max(0, ...data.map((d) => (showValues ? values(d).length : 0) + (showN && showValues && d.n !== undefined ? 10 : 0))) * 6.4;
  const left = Math.min(w * 0.34, Math.max(0, ...data.map((d) => d.label.length)) * 6.4 + 14);
  const right = showValues ? rightText + 16 : 16;
  const band = 36;
  const plotTop = reference ? 20 : 6;
  const h = plotTop + n * band + 30;
  const plotBottom = plotTop + n * band;
  const pw = Math.max(40, w - left - right);
  const xs = (v: number) => left + ((v - lo) / (hi - lo || 1)) * pw;
  const tickStep = Math.max(1, Math.ceil((tickW + 14) / (pw / Math.max(1, ticks.length - 1))));
  const fit = (s: string, px: number) => { const k = Math.max(1, Math.floor(px / 6.4)); return s.length > k ? `${s.slice(0, Math.max(1, k - 1))}…` : s; };

  const readout = (i: number) => {
    const d = data[i];
    return `${measure}, ${d.label}, ${f(d.estimate)}${unit ? ` ${unit}` : ""}, ${intervalLabel} ${f(d.low)} to ${f(d.high)}${d.n !== undefined ? `, n = ${d.n}` : ""}`;
  };
  const move = (e: KeyboardEvent<HTMLDivElement>) => {
    if (!n) return;
    let i = active ?? 0;
    if (e.key === "ArrowDown" || e.key === "ArrowRight") i = Math.min(n - 1, i + 1);
    else if (e.key === "ArrowUp" || e.key === "ArrowLeft") i = Math.max(0, i - 1);
    else if (e.key === "Home") i = 0;
    else if (e.key === "End") i = n - 1;
    else if (e.key === "Escape") { setActive(null); setSaid(""); return; }
    else return;
    e.preventDefault();
    setActive(i);
    setSaid(readout(i));
  };
  const leave = (e: PointerEvent<SVGRectElement>) => { if (e.pointerType === "mouse" && document.activeElement !== wrapRef.current) setActive(null); };

  return (
    <div ref={ref} className={cn("relative min-w-0", className)}>
      <p className="mb-2 max-w-[68ch] text-small leading-5 text-fg-muted">{caption}</p>
      <div ref={wrapRef} tabIndex={0} role="group" aria-roledescription="dot plot"
        aria-label={`${ariaLabel}. ${n} rows. ${caption}. Use arrow keys to step through rows.`}
        onKeyDown={move} onFocus={() => { if (n) setActive((p) => p ?? 0); }} onBlur={() => { setActive(null); setSaid(""); }}
        className="relative rounded-lg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--focus-ring)]">
        <svg ref={svgRef} width={w} height={h} viewBox={`0 0 ${w} ${h}`} className="block max-w-full touch-pan-y select-none" aria-hidden focusable="false" style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-small)" }}>
          {active !== null && <rect x={0} y={plotTop + active * band} width={w} height={band} fill="var(--surface-hover)" />}
          {ticks.map((t, i) => (
            <g key={t}>
              <line x1={xs(t)} x2={xs(t)} y1={plotTop} y2={plotBottom} stroke="var(--chart-grid)" strokeWidth={0.5} shapeRendering="crispEdges" />
              {(i % tickStep === 0 || i === ticks.length - 1) && <text x={xs(t)} y={plotBottom + 18} textAnchor="middle" fill="var(--fg-muted)" style={{ fontVariantNumeric: "tabular-nums" }}>{tickLabels[i]}</text>}
            </g>
          ))}
          <line x1={left} x2={left + pw} y1={plotBottom} y2={plotBottom} stroke="var(--chart-axis)" strokeWidth={1} shapeRendering="crispEdges" />
          {reference && (
            <g>
              <line x1={xs(reference.value)} x2={xs(reference.value)} y1={plotTop - 2} y2={plotBottom} stroke="var(--chart-axis)" strokeWidth={1} shapeRendering="crispEdges" />
              <text x={xs(reference.value)} y={plotTop - 8} textAnchor="middle" fill="var(--fg-muted)">{reference.label}</text>
            </g>
          )}
          {data.map((d, i) => {
            const cy = plotTop + i * band + band / 2;
            return (
              <g key={d.label}>
                <text x={left - 10} y={cy} textAnchor="end" dominantBaseline="central" fill="var(--fg)">{fit(d.label, left - 14)}</text>
                <line x1={xs(d.low)} x2={xs(d.high)} y1={cy} y2={cy} stroke="var(--chart-1)" strokeWidth={1.5} strokeLinecap="round" />
                <line x1={xs(d.low)} x2={xs(d.low)} y1={cy - 4} y2={cy + 4} stroke="var(--chart-1)" strokeWidth={1.5} strokeLinecap="round" />
                <line x1={xs(d.high)} x2={xs(d.high)} y1={cy - 4} y2={cy + 4} stroke="var(--chart-1)" strokeWidth={1.5} strokeLinecap="round" />
                <circle cx={xs(d.estimate)} cy={cy} r={5} fill="var(--chart-1)" stroke="var(--surface)" strokeWidth={2} />
                {showValues && (
                  <text x={left + pw + 14} y={cy} dominantBaseline="central" fill="var(--fg-muted)" style={{ fontVariantNumeric: "tabular-nums" }}>
                    <tspan fill="var(--fg)">{f(d.estimate)}</tspan>{` (${f(d.low)} to ${f(d.high)})`}{showN && d.n !== undefined ? `  n = ${d.n}` : ""}
                  </text>
                )}
                {!showValues && showN && d.n !== undefined && <text x={w - 4} y={cy + 14} textAnchor="end" fill="var(--fg-muted)" style={{ fontSize: "var(--text-micro)" }}>n = {d.n}</text>}
              </g>
            );
          })}
          {data.map((d, i) => (
            <rect key={d.label} x={0} y={plotTop + i * band} width={w} height={band} fill="transparent" onPointerMove={() => setActive(i)} onPointerDown={() => setActive(i)} onPointerLeave={leave} />
          ))}
        </svg>
        {active !== null && data[active] && (
          <div aria-hidden className="pointer-events-none absolute z-10 min-w-40 max-w-64 rounded-overlay border border-line bg-surface px-3 py-2 text-small leading-4 shadow-md"
            style={{ top: plotTop + active * band + band, left: Math.min(Math.max(8, xs(data[active].estimate)), w - 8), transform: `translateX(${xs(data[active].estimate) > w / 2 ? "-100%" : "0"})` }}>
            <p className="mb-1 text-fg-muted">{data[active].label}</p>
            <p className="flex justify-between gap-3 leading-5"><span className="text-fg-muted">{measure}</span><span className="font-semibold text-fg [font-variant-numeric:tabular-nums]">{f(data[active].estimate)}</span></p>
            <p className="flex justify-between gap-3 leading-5"><span className="text-fg-muted">{intervalLabel}</span><span className="text-fg [font-variant-numeric:tabular-nums]">{f(data[active].low)} to {f(data[active].high)}</span></p>
            {data[active].n !== undefined && <p className="flex justify-between gap-3 leading-5"><span className="text-fg-muted">Sample size</span><span className="text-fg [font-variant-numeric:tabular-nums]">n = {data[active].n}</span></p>}
          </div>
        )}
      </div>
      <div className="sr-only" aria-live="polite" role="status">{said}</div>
    </div>
  );
}

/** Table data for ChartFrame's table view: estimate, interval ends and sample size. */
export function dotPlotTable(data: DotDatum[], opts: { labelHeader: string; caption: string; measure?: string; formatValue?: (n: number) => string; intervalLabel?: string }): DataTableProps {
  const f = opts.formatValue ?? formatNumber;
  const il = opts.intervalLabel ?? "Interval";
  return {
    caption: opts.caption,
    columns: [
      { key: "l", header: opts.labelHeader },
      { key: "e", header: opts.measure ?? "Estimate", numeric: true },
      { key: "lo", header: `${il}, lower`, numeric: true },
      { key: "hi", header: `${il}, upper`, numeric: true },
      { key: "n", header: "Sample size (n)", numeric: true },
    ],
    rows: data.map((d) => ({ l: d.label, e: f(d.estimate), lo: f(d.low), hi: f(d.high), n: d.n !== undefined ? formatNumber(d.n) : "Not given" })),
  };
}
```

## tattva/organisms/ExportSheet.tsx

```tsx
import { useEffect, useRef, useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, ArrowRightIcon, CheckIcon, CopyIcon, DownloadIcon, FileIcon, InfoIcon, RetryIcon, SourceIcon } from "../lib/icons";
import { headingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { Button } from "../atoms/Button";
import { Meter } from "../atoms/Meter";
import { Spinner } from "../atoms/Spinner";
import { Switch } from "../atoms/Switch";
import { RadioGroup } from "../molecules/RadioGroup";
import { Dialog } from "./Dialog";

export type ExportFormat = "pdf" | "pptx" | "png" | "link";
export type ExportState = "choosing" | "checking" | "exporting" | "done" | "failed";

export interface ExportCheck {
  id: string;
  /** fix: a person must act before sending (amber), such as AI changes nobody has reviewed or an image that is missing. info: worth knowing, nothing to do (neutral), such as a font that will be swapped. */
  severity: "fix" | "info";
  /** Plain words, worked out by your app for the chosen format, such as "Slide 6 has an image with no description". */
  message: string;
  /** The slide to jump to, with a label for the button, such as "Slide 6". */
  slideId?: string;
  slideLabel?: string;
}

export interface ExportSheetProps {
  /** Dialog mode: whether it is showing. Ignored when inline. */
  open?: boolean;
  /** Closes the dialog. Also the Close and Cancel buttons. */
  onClose?: () => void;
  /** Draw the same content as a card in the page instead of a dialog, for a side panel or a settings page. */
  inline?: boolean;
  /** Name of the deck, shown in the title, such as "Q3 churn review". */
  deckTitle?: string;
  /** Formats on offer, in order. */
  formats?: ExportFormat[];
  format: ExportFormat;
  onFormatChange: (format: ExportFormat) => void;
  /** choosing: picking a format. checking: your app is checking the deck for this format. exporting: the file is being made. done and failed: the result. */
  state: ExportState;
  /** What will be in the file, worked out by your app, such as "12 slides, 16:9" or "Content credentials on 3 AI images". */
  includes?: string[];
  includeNotes?: boolean;
  /** Shows a switch for speaker notes. Leave out for formats that cannot carry notes. */
  onIncludeNotesChange?: (value: boolean) => void;
  /** Results of the checks for the chosen format. Fix items are amber and come first. */
  checks?: ExportCheck[];
  /** Takes the person to a slide named in a check. Closes nothing by itself. */
  onJumpTo?: (slideId: string) => void;
  /** Real progress while exporting, in slides. */
  progress?: { done: number; total: number };
  /** The file that was made, such as "q3-churn-review.pdf". For a link, the link. */
  fileName?: string;
  /** Size of the file, such as "4.2 MB". */
  fileSize?: string;
  /** Why the export failed, in plain words. */
  error?: string;
  /** Starts the export. Never blocked by fix items: the button then says "Export anyway". */
  onExport: () => void;
  /** Stops an export that is running. */
  onStopExport?: () => void;
  /** Tries again after a failure. Defaults to onExport. */
  onRetry?: () => void;
  onDownload?: () => void;
  /** Copies the link (link format). The sheet says "Link copied" after. */
  onCopyLink?: () => void;
  headingLevel?: HeadingLevel;
  className?: string;
}

const FORMAT: Record<ExportFormat, { label: string; short: string; description: string; action: string }> = {
  pdf: { label: "PDF", short: "PDF", description: "Best for sending. Looks the same everywhere.", action: "Export PDF" },
  pptx: { label: "PowerPoint (.pptx)", short: "PowerPoint", description: "Others can edit it. Some fonts and effects may change.", action: "Export PowerPoint" },
  png: { label: "Images (.png)", short: "images", description: "One picture per slide, in a zip file.", action: "Export images" },
  link: { label: "View-only link", short: "a link", description: "Anyone with the link can view. It stays up to date.", action: "Create link" },
};

const plural = (n: number, one: string, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;

/**
 * Export for a deck. The person picks a format, sees what the file will hold, and reads what will not survive before
 * the file is made: fix items in amber with a link to the slide, notes in neutral. Export is never blocked. Then it
 * shows real progress, the file name when done, and Retry when it fails. A dialog on wide screens, a bottom sheet on phones.
 */
export function ExportSheet({
  open = false, onClose, inline = false, deckTitle, formats = ["pdf", "pptx", "png", "link"], format, onFormatChange, state,
  includes, includeNotes, onIncludeNotesChange, checks = [], onJumpTo, progress, fileName, fileSize, error,
  onExport, onStopExport, onRetry, onDownload, onCopyLink, headingLevel = 3, className,
}: ExportSheetProps) {
  const f = FORMAT[format];
  const fixes = checks.filter((c) => c.severity === "fix");
  const infos = checks.filter((c) => c.severity === "info");
  const picking = state === "choosing" || state === "checking" || state === "failed";
  const [copied, setCopied] = useState(false);
  useEffect(() => { setCopied(false); }, [fileName, state]);

  // Speak when a stage ends, not while it runs.
  const [spoken, setSpoken] = useState("");
  const prev = useRef(state);
  useEffect(() => {
    if (prev.current === state) return;
    prev.current = state;
    setSpoken(
      state === "choosing" ? (fixes.length ? `Checks done. ${plural(fixes.length, "thing")} to fix before you send it.` : infos.length ? `Checks done. ${plural(infos.length, "note")}, nothing to fix.` : "Checks done. Nothing to fix.") :
      state === "exporting" ? `Exporting ${f.short}` :
      state === "done" ? `Export done. ${fileName ?? ""}` :
      state === "failed" ? `Export failed. ${error ?? ""}` : "",
    );
  }, [state, fixes.length, infos.length, f.short, fileName, error]);

  const isLink = format === "link";

  const body: ReactNode = (
    <div className="space-y-5">
      <p role="status" aria-live="polite" className="sr-only">{spoken}</p>

      {state === "failed" && (
        <p className="flex items-start gap-2 rounded-card bg-danger-soft px-3 py-2 text-compact text-danger-fg">
          <AlertIcon aria-hidden width={14} height={14} className="mt-0.5 shrink-0" />
          <span><span className="font-medium">Export failed. </span>{error ?? "The file was not made. Nothing was sent."}</span>
        </p>
      )}

      {picking && (
        <div><RadioGroup label="Format" value={format} onChange={(v) => onFormatChange(v as ExportFormat)} disabled={state === "checking"}
          options={formats.map((x) => ({ value: x, label: FORMAT[x].label, description: FORMAT[x].description }))} /></div>
      )}

      {picking && (includes?.length || onIncludeNotesChange) ? (
        <section aria-label="What the file will hold" className="space-y-2">
          <p className="text-body leading-5 font-medium text-fg">What the {isLink ? "link shows" : "file holds"}</p>
          {includes && includes.length > 0 && (
            <ul className="space-y-1">
              {includes.map((t) => <li key={t} className="flex items-start gap-2 text-compact text-fg-muted"><CheckIcon aria-hidden width={14} height={14} className="mt-0.5 shrink-0" />{t}</li>)}
            </ul>
          )}
          {onIncludeNotesChange && <Switch checked={!!includeNotes} onChange={onIncludeNotesChange} label="Include speaker notes" disabled={state === "checking"}>Include speaker notes</Switch>}
        </section>
      ) : null}

      {picking && (
        <section aria-label="Before you export" className="space-y-2">
          <p className="text-body leading-5 font-medium text-fg">Before you export</p>
          {state === "checking" ? (
            <p className="flex items-center gap-2 text-compact text-fg-muted"><Spinner size={14} label="Checking" /><span>Checking the deck for {f.short}…</span></p>
          ) : checks.length === 0 ? (
            <p className="flex items-center gap-2 text-compact text-fg-muted"><CheckIcon aria-hidden width={14} height={14} className="text-success-fg" />Nothing to fix. It should look the same as {f.short}.</p>
          ) : (
            <ul className="space-y-1.5 motion-safe:animate-fade">
              {[...fixes, ...infos].map((c) => {
                const fix = c.severity === "fix";
                return (
                  <li key={c.id} className={cn("flex flex-wrap items-start gap-x-2 gap-y-1 rounded-card px-3 py-2 text-compact", fix ? "bg-attention-soft text-attention-fg" : "bg-sunken text-fg-muted")}>
                    {fix ? <AlertIcon aria-hidden width={14} height={14} className="mt-0.5 shrink-0" /> : <InfoIcon aria-hidden width={14} height={14} className="mt-0.5 shrink-0" />}
                    <span className="min-w-0 flex-1"><span className="font-medium">{fix ? "Needs you: " : "Note: "}</span>{c.message}</span>
                    {c.slideId && onJumpTo && (
                      <button type="button" onClick={() => onJumpTo(c.slideId!)}
                        className="tap ml-6 inline-flex shrink-0 basis-full cursor-pointer sm:basis-auto items-center gap-1 rounded-control text-small leading-4 font-medium underline underline-offset-2 hover:no-underline sm:ml-0">
                        Go to {c.slideLabel ?? "slide"}<ArrowRightIcon aria-hidden width={12} height={12} />
                      </button>
                    )}
                  </li>
                );
              })}
            </ul>
          )}
        </section>
      )}

      {state === "exporting" && (
        <section aria-label="Export progress" className="space-y-2">
          <p className="flex items-center gap-2 text-compact text-fg"><FileIcon aria-hidden width={14} height={14} />
            {progress ? `Making ${f.short}: slide ${progress.done} of ${progress.total}` : `Making ${f.short}…`}
          </p>
          {progress ? <Meter label="Export progress" value={progress.done} max={progress.total} valueText={`Slide ${progress.done} of ${progress.total}`} />
            : <Spinner size={16} label="Exporting" />}
          <p className="text-small leading-4 text-fg-subtle">You can close this and keep working. Changes from now on are not in this file.</p>
        </section>
      )}

      {state === "done" && (
        <section aria-label="Export done" className="space-y-3">
          <p className="flex items-center gap-2 text-compact font-medium text-fg"><CheckIcon aria-hidden width={16} height={16} className="text-success-fg" />{isLink ? "Link ready" : "Export done"}</p>
          <div className="flex min-w-0 items-center gap-3 rounded-card border border-line bg-sunken px-3 py-2.5">
            {isLink ? <SourceIcon aria-hidden width={16} height={16} className="shrink-0 text-fg-muted" /> : <FileIcon aria-hidden width={16} height={16} className="shrink-0 text-fg-muted" />}
            <span className="min-w-0 flex-1">
              <span className="block truncate font-mono text-compact text-fg">{fileName}</span>
              {fileSize && <span className="block text-small leading-4 text-fg-muted">{fileSize}</span>}
            </span>
          </div>
          {fixes.length > 0 && <p className="text-small leading-4 text-fg-muted">Exported with {plural(fixes.length, "item")} not fixed. You can fix them and export again.</p>}
          {copied && <p className="text-small leading-4 text-fg-muted">Link copied.</p>}
        </section>
      )}

    </div>
  );

  const close = onClose && <Button variant="secondary" onClick={onClose}>{state === "done" ? "Close" : "Cancel"}</Button>;
  const footer: ReactNode = (
    <>
      {state !== "exporting" && close}
      {(state === "choosing" || state === "checking") && (
        <Button onClick={onExport} leading={<DownloadIcon aria-hidden width={14} height={14} />}>{fixes.length && state === "choosing" ? "Export anyway" : f.action}</Button>
      )}
      {state === "exporting" && onStopExport && <Button variant="secondary" onClick={onStopExport}>Stop export</Button>}
      {state === "failed" && <Button onClick={onRetry ?? onExport} leading={<RetryIcon aria-hidden width={14} height={14} />}>Try again</Button>}
      {state === "done" && isLink && onCopyLink && <Button onClick={() => { onCopyLink(); setCopied(true); }} leading={<CopyIcon aria-hidden width={14} height={14} />}>Copy link</Button>}
      {state === "done" && !isLink && onDownload && <Button onClick={onDownload} leading={<DownloadIcon aria-hidden width={14} height={14} />}>Download</Button>}
    </>
  );

  const title = deckTitle ? `Export ${deckTitle}` : "Export deck";

  if (inline) {
    const H = headingTag(headingLevel);
    return (
      <section aria-label={title} className={cn("w-full overflow-hidden rounded-card border border-line bg-surface", className)}>
        <H className="px-5 pt-5 pb-3 text-subhead leading-snug font-semibold text-balance text-fg">{title}</H>
        <div className="px-5 pb-4 text-body leading-5">{body}</div>
        <div className="flex flex-col-reverse gap-2 border-t border-line px-5 py-4 sm:flex-row sm:justify-end">{footer}</div>
      </section>
    );
  }

  return (
    <Dialog open={open} onClose={onClose ?? (() => {})} title={title} dismissible={!!onClose} footer={footer} className={className}>
      {body}
    </Dialog>
  );
}
```

## tattva/organisms/GenUIRenderer.tsx

```tsx
import { Component, useRef, useState, type ErrorInfo, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { Skeleton } from "../atoms/Skeleton";
import { catalogByType, needsConfirmation, validateNode, type CatalogEntry, type GenUIActionType, type RenderContext } from "./genui/catalog";
import type { SpecNode, SpecState } from "./genui/spec";

export type { GenUIActionType };

/** What the person did, sent back to the agent. */
export interface GenUIActionEvent {
  /** Unique per action. Pass it back as `pendingActionId` while the agent is working on it. */
  actionId: string;
  nodeId: string;
  type: GenUIActionType;
  value: unknown;
}

export interface GenUIRendererLabels {
  /** Heading of a part that could not be shown. */
  fallbackTitle?: string;
  /** Text under a loading part, for screen readers. */
  loading?: string;
  /** Question in the confirm step. */
  confirmQuestion?: string;
  confirm?: string;
  cancel?: string;
  /** Shown next to a part while the agent handles its action. */
  sending?: string;
  /** Shown for a part that a node points at but that never arrived. */
  missing?: string;
}

export interface GenUIRendererProps {
  /** The spec so far. Build it with `applyChunk`. */
  state: SpecState;
  /** Called for every action the person takes. Actions that need a confirm step are sent only after the person agrees. */
  onAction?: (event: GenUIActionEvent) => void;
  /** Id of the action the agent is still handling. The part that sent it shows a waiting state and its controls are off. */
  pendingActionId?: string | null;
  labels?: GenUIRendererLabels;
  className?: string;
}

const MAX_DEPTH = 6;

/** Gathers readable text from a node's props, so a part that cannot be shown still tells the person something. */
function plainText(v: unknown, out: string[] = [], depth = 0): string[] {
  if (out.length >= 12 || depth > 3) return out;
  if (typeof v === "string") { if (v.trim()) out.push(v.trim()); }
  else if (typeof v === "number") out.push(String(v));
  else if (Array.isArray(v)) v.slice(0, 12).forEach((x) => plainText(x, out, depth + 1));
  else if (typeof v === "object" && v !== null) Object.entries(v).slice(0, 12).forEach(([k, x]) => { if (!/^(class|style|on[A-Z]|dangerously)/i.test(k)) plainText(x, out, depth + 1); });
  return out;
}

function FallbackCard({ node, reasons, title }: { node: SpecNode | { id: string }; reasons: string[]; title: string }) {
  const lines = "props" in node ? plainText(node.props) : [];
  return (
    <div role="group" aria-label={title} data-genui-fallback className="rounded-card border border-line bg-sunken p-3 text-body leading-5">
      <p className="flex items-center gap-1.5 text-small leading-4 font-medium text-fg-muted"><AlertIcon width={12} height={12} aria-hidden />{title}</p>
      {lines.length > 0 && <div className="mt-1.5 space-y-1 text-fg">{lines.map((t, i) => <p key={i} className="[overflow-wrap:anywhere]">{t}</p>)}</div>}
      {reasons.length > 0 && <p className="mt-1.5 text-small leading-4 text-fg-subtle [overflow-wrap:anywhere]">{reasons.join(". ")}.</p>}
    </div>
  );
}

/** Catches anything a part throws while drawing, and swaps in the fallback card. */
class NodeBoundary extends Component<{ resetKey: string; fallback: ReactNode; children: ReactNode }, { failed: boolean; key: string }> {
  state = { failed: false, key: this.props.resetKey };
  static getDerivedStateFromError() { return { failed: true }; }
  static getDerivedStateFromProps(p: { resetKey: string }, s: { failed: boolean; key: string }) { return p.resetKey !== s.key ? { failed: false, key: p.resetKey } : null; }
  componentDidCatch(_e: Error, _i: ErrorInfo) { /* the fallback is the report */ }
  render() { return this.state.failed ? this.props.fallback : this.props.children; }
}

function NodeBody({ entry, props, kids, ctx }: { entry: CatalogEntry; props: Record<string, unknown>; kids: ReactNode[]; ctx: RenderContext }) {
  return <>{entry.render(props, kids, ctx)}</>;
}

function signature(n: SpecNode): string {
  try { return JSON.stringify([n.type, n.props, n.children]); } catch { return n.id; }
}

/**
 * Draws a streamed spec using only parts from the catalog. Each node is checked first. A node that is unknown, invalid
 * or throws while drawing becomes a quiet card with whatever plain text it held. Nodes still on their way show as skeletons.
 * Actions go out through `onAction`. Actions marked `confirm` wait for an inline yes. This component never throws.
 */
export function GenUIRenderer({ state, onAction, pendingActionId, labels, className }: GenUIRendererProps) {
  const l = {
    fallbackTitle: "This part could not be shown", loading: "Still loading", confirmQuestion: "Are you sure?", confirm: "Confirm", cancel: "Cancel",
    sending: "Waiting for a reply…", missing: "This part never arrived", ...labels,
  };
  const [awaiting, setAwaiting] = useState<{ nodeId: string; type: GenUIActionType; value: unknown } | null>(null);
  const [sent, setSent] = useState<Record<string, { type: GenUIActionType; value: unknown }>>({});
  const [last, setLast] = useState<{ actionId: string; nodeId: string } | null>(null);
  const counter = useRef(0);

  const send = (nodeId: string, type: GenUIActionType, value: unknown) => {
    const actionId = `${nodeId}-${++counter.current}`;
    setLast({ actionId, nodeId });
    setSent((s) => ({ ...s, [nodeId]: { type, value } }));
    try { onAction?.({ actionId, nodeId, type, value }); } catch { /* a host error must not break the screen */ }
  };

  const renderNode = (id: string, path: string[]): ReactNode => {
    const node = state.nodes[id];
    if (!node) {
      if (!state.done) return <Skeleton key={id} role="presentation" className="h-16 w-full rounded-card" />;
      return <FallbackCard key={id} node={{ id }} reasons={[]} title={l.missing} />;
    }
    if (path.includes(id) || path.length >= MAX_DEPTH) return <FallbackCard key={id} node={node} reasons={["This part is nested too deeply"]} title={l.fallbackTitle} />;

    let check: ReturnType<typeof validateNode>;
    try { check = validateNode(node); } catch { check = { ok: false, props: {}, errors: ["could not be checked"], warnings: [] }; }
    const fallback = <FallbackCard key={id} node={node} reasons={check.errors} title={l.fallbackTitle} />;
    if (!check.ok) return fallback;

    const entry = catalogByType[node.type];
    const kids = entry.canHaveChildren ? (node.children ?? []).map((c) => renderNode(c, [...path, id])) : [];
    const pending = !!pendingActionId && last?.actionId === pendingActionId && last.nodeId === id;
    const ctx: RenderContext = {
      nodeId: id, pending, sent: sent[id],
      act: (type, value) => {
        if (needsConfirmation(node.type, check.props)) setAwaiting({ nodeId: id, type, value });
        else send(id, type, value);
      },
    };
    const confirming = awaiting?.nodeId === id;
    return (
      <div key={id} data-genui-node={id} className="min-w-0">
        <NodeBoundary resetKey={signature(node)} fallback={fallback}>
          <NodeBody entry={entry} props={check.props} kids={kids} ctx={ctx} />
        </NodeBoundary>
        {pending && <p role="status" className="mt-1.5 text-small leading-4 text-fg-muted">{l.sending}</p>}
        {confirming && awaiting && (
          <ConfirmStep
            question={l.confirmQuestion} consequence={typeof check.props.consequence === "string" ? check.props.consequence : undefined}
            confirmLabel={l.confirm} cancelLabel={l.cancel}
            onConfirm={() => { const a = awaiting; setAwaiting(null); send(a.nodeId, a.type, a.value); }}
            onCancel={() => setAwaiting(null)}
          />
        )}
      </div>
    );
  };

  let body: ReactNode;
  try {
    body = state.root ? renderNode(state.root, []) : state.done ? null : <Skeleton role="presentation" className="h-16 w-full rounded-card" />;
  } catch {
    body = <FallbackCard node={{ id: "root" }} reasons={[]} title={l.fallbackTitle} />;
  }
  return (
    <div aria-busy={!state.done} className={cn("min-w-0 space-y-3", className)}>
      {body}
      {!state.done && <span role="status" className="sr-only">{l.loading}</span>}
    </div>
  );
}

/** The inline question before a consequential action. Amber, because a person has to act. */
function ConfirmStep({ question, consequence, confirmLabel, cancelLabel, onConfirm, onCancel }: {
  question: string; consequence?: string; confirmLabel: string; cancelLabel: string; onConfirm: () => void; onCancel: () => void;
}) {
  return (
    <div role="group" aria-label={question} className="mt-2 rounded-card bg-attention-soft p-3 text-body leading-5 text-attention-fg">
      <p className="font-medium">{question}</p>
      {consequence && <p className="mt-0.5">{consequence}</p>}
      <div className="mt-2 flex flex-wrap gap-2">
        <Button size="sm" autoFocus onClick={onConfirm}>{confirmLabel}</Button>
        <Button size="sm" variant="secondary" onClick={onCancel}>{cancelLabel}</Button>
      </div>
    </div>
  );
}
```

## tattva/organisms/GeneratedSurface.tsx

```tsx
import { useId, useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, ChevronIcon, PinIcon, RegenerateIcon, RetryIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { Spinner } from "../atoms/Spinner";
import { AILabel } from "../molecules/AILabel";

export type GeneratedSurfaceStatus = "streaming" | "ready" | "failed";

export interface GeneratedSurfaceLabels {
  madeBy?: (source: string) => string;
  viewSpec?: string;
  hideSpec?: string;
  pin?: string;
  pinned?: string;
  regenerate?: string;
  report?: string;
  retry?: string;
  streaming?: string;
  failed?: string;
  /** Intro of the AI label's explainer. */
  overview?: string;
}

export interface GeneratedSurfaceProps {
  /** Who made this, such as "Orders helper". Always visible. */
  source: string;
  /** The generated screen, usually a GenUIRenderer. */
  children?: ReactNode;
  status?: GeneratedSurfaceStatus;
  /** The spec behind the screen. Shown as JSON under "View spec". Pass a string to show it as is. */
  spec?: unknown;
  /** Whether the screen is pinned (controlled). */
  pinned?: boolean;
  onPinnedChange?: (pinned: boolean) => void;
  onRegenerate?: () => void;
  onReport?: () => void;
  /** Called by the Try again button when status is failed. */
  onRetry?: () => void;
  /** Plain text version of the answer. Shown when status is failed, so the person still gets something. */
  fallback?: ReactNode;
  labels?: GeneratedSurfaceLabels;
  className?: string;
}

function specText(spec: unknown): string {
  if (typeof spec === "string") return spec;
  try { return JSON.stringify(spec, null, 2) ?? ""; } catch { return "The spec could not be shown."; }
}

/**
 * The frame around a generated screen. It says who made it, lets the person read the spec, pin the screen, ask for a new
 * one or report it, and falls back to plain text when generation fails. Pin and regenerate have no published source:
 * they are our own design, so treat them as experimental.
 */
export function GeneratedSurface({ source, children, status = "ready", spec, pinned, onPinnedChange, onRegenerate, onReport, onRetry, fallback, labels, className }: GeneratedSurfaceProps) {
  const l = {
    madeBy: (s: string) => `Made by ${s}`, viewSpec: "View spec", hideSpec: "Hide spec", pin: "Pin", pinned: "Pinned", regenerate: "Regenerate", report: "Report",
    retry: "Try again", streaming: "Still being made…", failed: "This screen could not be made. Here is the answer as text.",
    overview: "This screen was made by an assistant. The app checked each part before showing it.", ...labels,
  };
  const [innerPinned, setInnerPinned] = useState(false);
  const isPinned = pinned ?? innerPinned;
  const [open, setOpen] = useState(false);
  const specId = useId();
  const togglePin = () => { const v = !isPinned; if (pinned === undefined) setInnerPinned(v); onPinnedChange?.(v); };

  return (
    <section aria-label={l.madeBy(source)} className={cn("min-w-0 rounded-card border border-line bg-surface p-4", className)}>
      <header className="flex flex-wrap items-center gap-x-2 gap-y-1">
        <AILabel level="message" size={18} text={l.madeBy(source)} overview={l.overview} />
        {status === "streaming" && <span role="status" className="inline-flex items-center gap-1.5 text-small leading-4 text-fg-muted"><span aria-hidden><Spinner /></span>{l.streaming}</span>}
        <div className="ml-auto flex flex-wrap justify-end gap-1">
          <Button size="sm" variant="ghost" aria-pressed={isPinned} onClick={togglePin} leading={<PinIcon width={14} height={14} />}>{isPinned ? l.pinned : l.pin}</Button>
          {onRegenerate && <Button size="sm" variant="ghost" onClick={onRegenerate} disabled={status === "streaming"} leading={<RegenerateIcon width={14} height={14} />}>{l.regenerate}</Button>}
          {onReport && <Button size="sm" variant="ghost" onClick={onReport}>{l.report}</Button>}
        </div>
      </header>

      <div className="mt-3 min-w-0" aria-busy={status === "streaming"}>
        {status === "failed" ? (
          <div className="space-y-3">
            <p role="alert" className="flex items-start gap-2 rounded-card bg-sunken p-3 text-body leading-5 text-fg-muted"><AlertIcon className="mt-0.5 shrink-0" aria-hidden />{l.failed}</p>
            {fallback && <div className="text-body leading-5 text-fg">{fallback}</div>}
            {onRetry && <Button size="sm" variant="secondary" onClick={onRetry} leading={<RetryIcon width={14} height={14} />}>{l.retry}</Button>}
          </div>
        ) : children}
      </div>

      {spec !== undefined && (
        <div className="mt-3 border-t border-line pt-2">
          <button type="button" aria-expanded={open} aria-controls={specId} onClick={() => setOpen(!open)}
            className="inline-flex cursor-pointer items-center gap-1.5 rounded-control px-1 text-small leading-4 font-medium text-fg-muted hover:text-fg">
            <ChevronIcon width={12} height={12} className={cn("transition-transform motion-reduce:transition-none", open && "rotate-90")} aria-hidden />
            {open ? l.hideSpec : l.viewSpec}
          </button>
          <div id={specId} hidden={!open}>
            {open && <pre tabIndex={0} className="mt-2 max-h-72 overflow-auto rounded-card bg-code p-3 font-mono text-small leading-5 text-code-fg"><code>{specText(spec)}</code></pre>}
          </div>
        </div>
      )}
    </section>
  );
}
```

## tattva/organisms/GlancePanel.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { CheckIcon } from "../lib/icons";
import { Badge } from "../atoms/Badge";
import { SectionLabel } from "../atoms/SectionLabel";
import { Sheet } from "./Sheet";

export interface GlanceFact { label: string; value: ReactNode; edited?: boolean }
export interface GlanceRule { title: string; cite?: string; href?: string }
export interface GlanceStep { id: string; label: string; state: "done" | "now" | "next"; detail?: string }

export interface GlancePanelProps {
  /** Names the panel (the landmark) and shows at the top. */
  title?: string;
  /** Set when the panel scrolls, so the keyboard can reach its content. */
  scrollable?: boolean;
  /** The first and biggest thing, usually a Countdown. */
  headline?: ReactNode;
  /** Facts under "What I know". A fact with `edited` gets an Edited tag. */
  facts?: GlanceFact[];
  /** The rule the case rests on. */
  rule?: GlanceRule;
  /** The whole journey, in order, under "Where it's at". */
  steps?: GlanceStep[];
  /** Rename the section headings. */
  headings?: { facts?: string; rule?: string; steps?: string };
  /** Hides the visible title but keeps it as the landmark name. GlanceSheet uses this because the sheet already shows the title. */
  hideTitle?: boolean;
  /** Heading level of the section headings. */
  headingLevel?: 2 | 3 | 4;
  className?: string;
}

const stateWord = { done: "Done", now: "Now", next: "Next" } as const;
const HEADING_TAG = { 2: "h2", 3: "h3", 4: "h4" } as const;

function Section({ heading, level, children }: { heading: string; level: 2 | 3 | 4; children: ReactNode }) {
  return (
    <section className="flex flex-col gap-2 border-t border-line pt-4">
      <SectionLabel as={HEADING_TAG[level]}>{heading}</SectionLabel>
      {children}
    </section>
  );
}

/** The things a worried person keeps coming back to, kept in view beside a chat: the deadline, the facts, the rule and the steps. It has no frame of its own. */
export function GlancePanel({ title = "Case file", headline, facts, rule, steps, headings, hideTitle, headingLevel = 2, scrollable, className }: GlancePanelProps) {
  return (
    <aside aria-label={title} tabIndex={scrollable ? 0 : undefined} className={cn("flex min-w-0 flex-col gap-4 text-body leading-5 text-fg", className)}>
      {!hideTitle && <p className="px-1 text-lead leading-6 font-semibold">{title}</p>}
      {headline}
      {facts && facts.length > 0 && (
        <Section heading={headings?.facts ?? "What I know"} level={headingLevel}>
          <dl className="flex flex-col divide-y divide-line">
            {facts.map((f) => (
              <div key={f.label} className="flex flex-wrap items-baseline justify-between gap-x-3 gap-y-0.5 px-1 py-2 first:pt-0 last:pb-0">
                <dt className="text-fg-muted">{f.label}</dt>
                <dd className="flex min-w-0 items-center gap-2 text-right font-medium [overflow-wrap:anywhere]">
                  {f.edited && <Badge>Edited</Badge>}
                  <span>{f.value}</span>
                </dd>
              </div>
            ))}
          </dl>
        </Section>
      )}
      {rule && (
        <Section heading={headings?.rule ?? "The rule"} level={headingLevel}>
          <div className="px-1">
            <p className="font-medium">{rule.href ? <a href={rule.href} className="underline underline-offset-2 hover:no-underline">{rule.title}</a> : rule.title}</p>
            {rule.cite && <p className="mt-0.5 text-small leading-5 text-fg-muted">{rule.cite}</p>}
          </div>
        </Section>
      )}
      {steps && steps.length > 0 && (
        <Section heading={headings?.steps ?? "Where it's at"} level={headingLevel}>
          <ol className="flex flex-col">
            {steps.map((s, i) => (
              <li key={s.id} aria-current={s.state === "now" ? "step" : undefined} className="relative flex gap-3 px-1 pb-4 last:pb-0">
                {i < steps.length - 1 && <span aria-hidden className={cn("absolute top-7 bottom-1 left-3.75 w-px", s.state === "done" ? "bg-success" : "bg-line")} />}
                <span aria-hidden className={cn("relative z-10 flex size-6 shrink-0 items-center justify-center rounded-full border text-small leading-4 forced-colors:border-[CanvasText]",
                  s.state === "done" && "border-success bg-success-soft text-success-fg",
                  s.state === "now" && "border-accent bg-surface font-medium text-fg",
                  s.state === "next" && "border-line-strong bg-surface text-fg-subtle")}>
                  {s.state === "done" ? <CheckIcon width={12} height={12} /> : i + 1}
                  {s.state === "now" && <span className="absolute -inset-1 rounded-full border border-accent motion-safe:animate-pulse-dot" />}
                </span>
                <div className="min-w-0 pt-0.5">
                  <p className={cn("leading-5", s.state === "now" ? "font-medium" : s.state === "next" && "text-fg-muted")}>
                    {s.label}<span className="sr-only">, {stateWord[s.state]}</span>
                  </p>
                  {s.detail && <p className="text-small leading-5 text-fg-muted">{s.detail}</p>}
                </div>
              </li>
            ))}
          </ol>
        </Section>
      )}
    </aside>
  );
}

export interface GlanceSheetProps extends GlancePanelProps {
  open: boolean;
  /** Called when the sheet asks to close. Set `open` to false in response. */
  onClose: () => void;
}

/** The phone version: a bottom Sheet around a GlancePanel. Open it from a button in the page's header. */
export function GlanceSheet({ open, onClose, title = "Case file", className, ...panel }: GlanceSheetProps) {
  return (
    <Sheet open={open} onClose={onClose} title={title} size="lg">
      <GlancePanel {...panel} title={title} hideTitle className={cn("pb-4", className)} />
    </Sheet>
  );
}
```

## tattva/organisms/Heatmap.tsx

```tsx
import { useRef, useState, type CSSProperties, type KeyboardEvent } from "react";
import { cn } from "../lib/cn";
import type { DataTableProps } from "../molecules/DataTable";
import { formatNumber } from "../molecules/ChartFrame";

export interface HeatmapProps {
  /** Row labels, top to bottom. */
  rows: string[];
  /** Column labels, left to right. */
  cols: string[];
  /** One array per row, one value per column. Use null for a cell with no data. */
  values: (number | null)[][];
  /** "direction" colours around zero (up or down, stronger for bigger moves). "sequential" uses one hue from low to high. */
  scale?: "direction" | "sequential";
  /** Formats the number in a cell. With the direction scale it gets the size of the move and the sign is added for you. */
  formatValue?: (n: number) => string;
  /** Word read after each value by screen readers, for example "percent". */
  unit?: string;
  /** Print the number in every cell, so colour is never the only channel. */
  showValues?: boolean;
  /** Name for the grid, for example "Sector moves this week". */
  ariaLabel: string;
  /** Show a small scale bar with the lowest, zero and highest values. */
  legend?: boolean;
  /** Smallest cell width and height in px. */
  cellSize?: number;
  className?: string;
}

const BUCKETS = 5;

function dirWord(v: number): "up" | "down" | "flat" {
  return v > 0 ? "up" : v < 0 ? "down" : "flat";
}

/** Signed text for the direction scale. */
function signed(v: number, f: (n: number) => string): string {
  return `${v > 0 ? "+" : v < 0 ? "−" : ""}${f(Math.abs(v))}`;
}

/**
 * A grid of coloured cells. Colour shows size, the number printed in each cell and the words in the cell label give the same facts.
 * The grid is one tab stop. Arrow keys move between cells, Home and End go to the row ends, PageUp and PageDown go to the column ends.
 */
export function Heatmap({ rows, cols, values, scale = "direction", formatValue = formatNumber, unit = "", showValues = true, ariaLabel, legend, cellSize = 44, className }: HeatmapProps) {
  const refs = useRef<Map<string, HTMLDivElement>>(new Map());
  const [cur, setCur] = useState<{ r: number; c: number }>({ r: 0, c: 0 });
  const nr = rows.length;
  const nc = cols.length;

  const nums = values.flat().filter((v): v is number => typeof v === "number");
  const absMax = nums.length ? Math.max(...nums.map(Math.abs)) || 1 : 1;
  const vMin = nums.length ? Math.min(...nums) : 0;
  const vMax = nums.length ? Math.max(...nums) : 1;

  /** Share of the up or down colour mixed into the surface. Capped so the printed number keeps its contrast. */
  const mix = (v: number) => 14 + 46 * (Math.abs(v) / absMax);
  const dirBg = (v: number) => (v === 0 ? "var(--surface-sunken)" : `color-mix(in srgb, var(${v > 0 ? "--up" : "--down"}) ${mix(v).toFixed(0)}%, var(--surface))`);
  const bucket = (v: number) => Math.min(BUCKETS, Math.max(1, 1 + Math.floor(((v - vMin) / (vMax - vMin || 1)) * BUCKETS - 1e-9)));
  const seqBg = (v: number) => `var(--chart-seq-${bucket(v)})`;

  const text = (v: number) => (scale === "direction" ? signed(v, formatValue) : formatValue(v));
  const label = (r: number, c: number) => {
    const v = values[r]?.[c];
    if (v === null || v === undefined) return `${rows[r]}, ${cols[c]}: No data`;
    const u = unit ? ` ${unit}` : "";
    return scale === "direction"
      ? `${rows[r]}, ${cols[c]}: ${dirWord(v)}${v === 0 ? "" : ` ${formatValue(Math.abs(v))}`}${u}`
      : `${rows[r]}, ${cols[c]}: ${formatValue(v)}${u}`;
  };

  const go = (r: number, c: number) => {
    setCur({ r, c });
    refs.current.get(`${r}:${c}`)?.focus();
  };

  const onKey = (e: KeyboardEvent<HTMLDivElement>, r: number, c: number) => {
    if (!nr || !nc) return;
    if (e.key === "ArrowRight") go(r, Math.min(nc - 1, c + 1));
    else if (e.key === "ArrowLeft") go(r, Math.max(0, c - 1));
    else if (e.key === "ArrowDown") go(Math.min(nr - 1, r + 1), c);
    else if (e.key === "ArrowUp") go(Math.max(0, r - 1), c);
    else if (e.key === "Home") go(e.ctrlKey ? 0 : r, 0);
    else if (e.key === "End") go(e.ctrlKey ? nr - 1 : r, nc - 1);
    else if (e.key === "PageUp") go(0, c);
    else if (e.key === "PageDown") go(nr - 1, c);
    else return;
    e.preventDefault();
  };

  const cell = Math.max(32, cellSize);
  const grid: CSSProperties = { gridTemplateColumns: `minmax(64px, max-content) repeat(${nc}, minmax(${cell}px, 1fr))` };
  const sample = (k: number) => (scale === "direction" ? dirBg(absMax * k) : "");

  return (
    <div className={cn("min-w-0", className)}>
      <div className="overflow-x-auto">
        <div role="grid" aria-label={ariaLabel} aria-rowcount={nr + 1} aria-colcount={nc + 1} className="grid gap-px text-small leading-4" style={grid}>
          <div role="row" className="contents">
            <div role="presentation" aria-hidden />
            {cols.map((c, ci) => (
              <div key={c + ci} role="columnheader" className="truncate px-1 pb-1 text-center text-fg-muted">{c}</div>
            ))}
          </div>
          {rows.map((rl, r) => (
            <div key={rl + r} role="row" className="contents">
              <div role="rowheader" className="flex items-center pr-2 text-fg-muted" style={{ minHeight: cell }}>{rl}</div>
              {cols.map((cl, c) => {
                const v = values[r]?.[c];
                const empty = v === null || v === undefined;
                return (
                  <div key={cl + c} role="gridcell" aria-label={label(r, c)} tabIndex={cur.r === r && cur.c === c ? 0 : -1}
                    ref={(el) => { if (el) refs.current.set(`${r}:${c}`, el); else refs.current.delete(`${r}:${c}`); }}
                    onKeyDown={(e) => onKey(e, r, c)} onFocus={() => setCur({ r, c })} onPointerDown={() => setCur({ r, c })}
                    className={cn("flex items-center justify-center rounded-sm border border-transparent px-1 text-center [font-variant-numeric:tabular-nums] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--focus-ring)] forced-colors:border-[CanvasText]",
                      empty && "border-dashed border-line bg-sunken text-fg-subtle")}
                    style={{ minHeight: cell, background: empty ? undefined : scale === "direction" ? dirBg(v) : seqBg(v) }}>
                    {empty ? (
                      <span aria-hidden>{"–"}</span>
                    ) : showValues ? (
                      scale === "direction"
                        ? <span aria-hidden className="rounded-sm bg-surface px-1 text-fg">{text(v)}</span>
                        : <span aria-hidden className="rounded-sm bg-surface px-1 text-fg">{text(v)}</span>
                    ) : null}
                  </div>
                );
              })}
            </div>
          ))}
        </div>
      </div>
      {legend && nums.length > 0 && (
        <div className="mt-3 max-w-xs text-micro text-fg-muted" aria-hidden>
          {scale === "direction" ? (
            <>
              <div className="h-2 rounded-sm border border-line" style={{ background: `linear-gradient(to right, ${sample(-1)}, var(--surface), ${sample(1)})` }} />
              <div className="mt-1 flex justify-between [font-variant-numeric:tabular-nums]">
                <span>Down {formatValue(absMax)}</span><span>0</span><span>Up {formatValue(absMax)}</span>
              </div>
            </>
          ) : (
            <>
              <div className="flex h-2 overflow-hidden rounded-sm border border-line">
                {Array.from({ length: BUCKETS }, (_, i) => <span key={i} className="flex-1" style={{ background: `var(--chart-seq-${i + 1})` }} />)}
              </div>
              <div className="mt-1 flex justify-between [font-variant-numeric:tabular-nums]">
                <span>Low {formatValue(vMin)}</span><span>High {formatValue(vMax)}</span>
              </div>
            </>
          )}
        </div>
      )}
    </div>
  );
}

/** Table data for ChartFrame's table view: one row per heatmap row, one column per heatmap column. */
export function heatmapTable(opts: { rows: string[]; cols: string[]; values: (number | null)[][]; scale?: "direction" | "sequential"; formatValue?: (n: number) => string; unit?: string; rowHeader?: string; caption: string }): DataTableProps {
  const f = opts.formatValue ?? formatNumber;
  const dir = (opts.scale ?? "direction") === "direction";
  const u = opts.unit ? ` ${opts.unit}` : "";
  return {
    caption: opts.caption,
    columns: [
      { key: "row", header: opts.rowHeader ?? "Row" },
      ...opts.cols.map((c, i) => ({ key: `c${i}`, header: c, numeric: true, direction: dir ? (r: Record<string, unknown>) => { const t = String(r[`c${i}`]); return t.startsWith("+") ? "up" as const : t.startsWith("−") ? "down" as const : undefined; } : undefined })),
    ],
    rows: opts.rows.map((rl, r) => {
      const row: Record<string, string> = { row: rl };
      opts.cols.forEach((_, c) => {
        const v = opts.values[r]?.[c];
        row[`c${c}`] = v === null || v === undefined ? "No data" : `${dir ? signed(v, f) : f(v)}${u}`;
      });
      return row;
    }),
  };
}
```

## tattva/organisms/LayoutAlternatives.tsx

```tsx
import { useEffect, useId, useRef, useState, type KeyboardEvent, type PointerEvent, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { type HeadingLevel, headingTag } from "../lib/useFocusAfter";
import { AlertIcon, CheckIcon, EditIcon, RetryIcon, UndoIcon } from "../lib/icons";
import { AIMark } from "../atoms/AIMark";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { Reveal } from "../atoms/Reveal";
import { SegmentedControl } from "../atoms/SegmentedControl";

/** loading: nothing back yet. partial: some layouts back, more coming. ready: all back. failed: the request broke. none: the AI found no layout good enough. */
export type LayoutAltState = "loading" | "partial" | "ready" | "failed" | "none";

/** keep: the person's words stay exactly as written. fit: the AI may shorten text so a layout fits. */
export type LayoutTextMode = "keep" | "fit";

export interface LayoutOption {
  id: string;
  /** Short plain name, for example "Chart left, points right". Also the option's accessible name. */
  label: string;
  /** A drawing of the slide in this layout. Decorative: the label says what it is. */
  thumbnail: ReactNode;
  /** Optional one line on what changes, for example "Shortens two bullet points". */
  note?: string;
}

export interface LayoutAlternativesProps {
  /** The slide as it is now. Always the first option, so there is always a way back. */
  current: { thumbnail: ReactNode; label?: string };
  /** Layouts the AI suggested. They arrive over time in the partial state. */
  options: LayoutOption[];
  state: LayoutAltState;
  /** Placeholders to draw while loading or partial, for layouts still on the way. */
  loadingCount?: number;
  /** The option shown on the slide right now without being applied. null or undefined means the current layout. */
  previewId?: string | null;
  /** Called to preview an option on the slide (focus, hover, or a tap on touch screens), or with null to go back to the current layout. Never changes the deck. */
  onPreview: (id: string | null) => void;
  /** Called when the person applies an option: Enter, a click with a mouse, or the Apply button. */
  onApply: (id: string) => void;
  /** The option that was just applied. Shows "Applied" with an Undo button until you clear it. */
  appliedId?: string | null;
  /** Shows Undo in the applied state. Your app restores the previous layout and clears appliedId. */
  onUndo?: () => void;
  /** The promise about the person's words. Shown as a two-way control when onTextModeChange is passed. */
  textMode: LayoutTextMode;
  onTextModeChange?: (mode: LayoutTextMode) => void;
  /** Asks for more layouts. Shown as "More layouts" while nothing else is loading. */
  onMore?: () => void;
  /** Shown in the failed state. */
  onRetry?: () => void;
  /** Offered in the none state, so the person can change the content instead. */
  onEditText?: () => void;
  /** Plain reason in the failed state. */
  error?: string;
  /** Plain reason in the none state, for example "The table has too many rows for another layout." */
  noneReason?: string;
  /** Cost of asking for more, shown next to the button, for example "About 2 credits". */
  cost?: ReactNode;
  /** The slide these layouts are for, for example "Slide 3, Pricing". Used in the heading and the group name. */
  slideName?: string;
  /** Heading level of the panel title. */
  headingLevel?: HeadingLevel;
  className?: string;
}

const plural = (n: number, one: string, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;

/** The one sentence the live region speaks when the set of layouts changes. */
export function layoutAltSummary(state: LayoutAltState, ready: number, waiting: number) {
  if (state === "loading") return `Making ${plural(waiting || 3, "layout")}.`;
  if (state === "partial") return `${ready} of ${ready + waiting} layouts ready.`;
  if (state === "ready") return `${plural(ready, "layout")} ready.`;
  if (state === "none") return "No good layouts for this slide.";
  return "Could not make layouts.";
}

const CURRENT = "__current";

/**
 * Other layouts for one slide, suggested by the AI and compared side by side with the slide as it is.
 * Focus or hover previews a layout on the slide without changing the deck. Enter, a mouse click or Apply commits it.
 * On touch a tap only previews, and the Apply button commits. Escape goes back to the current layout.
 * The text mode is a visible promise about the person's words.
 */
export function LayoutAlternatives({
  current, options, state, loadingCount = 0, previewId, onPreview, onApply, appliedId, onUndo, textMode, onTextModeChange,
  onMore, onRetry, onEditText, error, noneReason, cost, slideName, headingLevel = 3, className,
}: LayoutAlternativesProps) {
  const H = headingTag(headingLevel);
  const id = useId();
  const group = useRef<HTMLDivElement>(null);
  const pointer = useRef<string>("");
  // A preview chosen by focus or tap stays when the mouse leaves; a hover preview goes back to it.
  const pinned = useRef<string | null>(previewId ?? null);
  const [said, setSaid] = useState("");

  const showOptions = state !== "none" && !(state === "failed" && options.length === 0);
  const waiting = state === "loading" || state === "partial" ? loadingCount : 0;
  const previewing = previewId && options.some((o) => o.id === previewId) ? previewId : null;
  const previewOption = options.find((o) => o.id === previewing);
  const applied = appliedId ? options.find((o) => o.id === appliedId) : undefined;
  const all = [{ id: CURRENT, label: current.label ?? "Current layout" }, ...(showOptions ? options : [])];
  const stopId = previewing ?? CURRENT;
  const nameOf = (o: string | null) => (o ? options.find((x) => x.id === o)?.label ?? "" : current.label ?? "Current layout");

  // Speak only when the set of layouts changes state, never on every hover.
  const summary = layoutAltSummary(state, options.length, waiting);
  const lastSummary = useRef("");
  useEffect(() => {
    if (summary !== lastSummary.current) { lastSummary.current = summary; setSaid(summary); }
  }, [summary]);

  const preview = (o: string | null, speak: boolean) => {
    pinned.current = o;
    if (o !== previewing) { onPreview(o); if (speak) setSaid(o ? `Previewing ${nameOf(o)}. Not applied yet.` : "Back to original."); }
  };
  const apply = (o: string) => {
    if (o === CURRENT) { preview(null, true); return; }
    pinned.current = null;
    onApply(o);
    setSaid(`${nameOf(o)} applied.`);
  };

  const onKey = (e: KeyboardEvent<HTMLDivElement>) => {
    if (e.altKey || e.ctrlKey || e.metaKey) return;
    const tiles = Array.from(group.current?.querySelectorAll<HTMLElement>('[role="radio"]') ?? []);
    const from = tiles.indexOf(document.activeElement as HTMLElement);
    if (e.key === "Escape") {
      if (previewing) { e.preventDefault(); e.stopPropagation(); preview(null, true); tiles[0]?.focus(); }
      return;
    }
    if (from < 0) return;
    let to = from;
    if (e.key === "ArrowRight" || e.key === "ArrowDown") to = (from + 1) % tiles.length;
    else if (e.key === "ArrowLeft" || e.key === "ArrowUp") to = (from - 1 + tiles.length) % tiles.length;
    else if (e.key === "Home") to = 0;
    else if (e.key === "End") to = tiles.length - 1;
    else if (e.key === "Enter") { e.preventDefault(); apply(all[from].id); return; }
    else if (e.key === " ") { e.preventDefault(); preview(all[from].id === CURRENT ? null : all[from].id, true); return; }
    else return;
    e.preventDefault();
    tiles[to]?.focus();
  };

  const tile = (o: { id: string; label: string; note?: string }, thumb: ReactNode, i: number) => {
    const isCurrent = o.id === CURRENT;
    const on = isCurrent ? !previewing : o.id === previewing;
    const isApplied = !isCurrent && o.id === appliedId;
    const optionId = isCurrent ? null : o.id;
    return (
      <Reveal as="div" key={o.id} index={i} className="min-w-0">
        <button type="button" role="radio" aria-checked={on} tabIndex={o.id === stopId ? 0 : -1}
          aria-describedby={`${id}-keys`}
          aria-label={`${o.label}${isCurrent ? ", the slide as it is now" : ""}${isApplied ? ", applied" : ""}${o.note ? `. ${o.note}` : ""}`}
          onFocus={() => preview(optionId, true)}
          onPointerDown={(e: PointerEvent) => { pointer.current = e.pointerType; }}
          onPointerEnter={(e: PointerEvent) => { if (e.pointerType === "mouse" && optionId !== previewing) onPreview(optionId); }}
          onClick={() => {
            const touch = pointer.current === "touch" || pointer.current === "pen";
            pointer.current = "";
            if (touch) preview(optionId, true); else apply(o.id);
          }}
          className={cn("group block w-full cursor-pointer rounded-card border bg-surface p-1.5 text-left transition-[border-color,box-shadow] duration-(--dur-fast) ease-(--ease-out)",
            on ? "border-fg ring-1 ring-fg" : "border-line hover:border-line-strong")}>
          <span aria-hidden className="relative block aspect-video overflow-hidden rounded-[max(2px,calc(var(--radius-card)-7px))] border border-line bg-bg">
            {thumb}
          </span>
          <span className="flex min-h-9 items-start gap-1.5 px-1 pt-1.5">
            <span className="min-w-0 flex-1">
              <span className="line-clamp-2 block text-small leading-4 font-medium text-fg">{o.label}</span>
              {o.note && <span className="line-clamp-2 block text-micro text-fg-muted">{o.note}</span>}
            </span>
            {isCurrent ? <Badge className="shrink-0">Current</Badge>
              : isApplied ? <Badge tone="success" className="shrink-0"><CheckIcon aria-hidden width={10} height={10} />Applied</Badge>
              : on ? <Badge className="shrink-0 border-fg">Previewing</Badge> : null}
          </span>
        </button>
      </Reveal>
    );
  };

  return (
    <section aria-labelledby={`${id}-title`} className={cn("@container min-w-0 space-y-3", className)}>
      <div className="flex flex-wrap items-center gap-x-3 gap-y-2">
        <H id={`${id}-title`} className="flex min-w-48 flex-1 items-center gap-2 text-body leading-5 font-semibold text-fg">
          <span aria-hidden className="inline-flex size-6 shrink-0 items-center justify-center rounded-control bg-lime text-on-lime"><AIMark size={16} /></span>
          <span className="min-w-0">Other layouts{slideName ? <span className="font-normal text-fg-muted"> for {slideName}</span> : null}</span>
        </H>
        {onTextModeChange && (
          <SegmentedControl size="sm" label="Your words" value={textMode} onChange={onTextModeChange}
            options={[{ value: "keep", label: "Keep my words" }, { value: "fit", label: "Shorten to fit" }]} />
        )}
      </div>
      <p className="text-small leading-4 text-fg-muted">
        {textMode === "keep"
          ? "Your text stays exactly as written. Layouts that cannot fit it are left out."
          : "The AI may shorten text so a layout fits. Each option says what it shortens."}
      </p>

      <p id={`${id}-keys`} className="sr-only">Arrow keys and focus preview a layout on the slide without changing it. Enter applies it. Escape goes back to the current layout.</p>

      <div ref={group} role="radiogroup" aria-label={`Layouts${slideName ? ` for ${slideName}` : ""}`} onKeyDown={onKey}
        onPointerLeave={(e) => { if (e.pointerType === "mouse" && pinned.current !== previewing) onPreview(pinned.current); }}
        className="grid grid-cols-2 gap-2 @xl:grid-cols-3 @3xl:grid-cols-4">
        {tile({ id: CURRENT, label: current.label ?? "Current layout" }, current.thumbnail, 0)}
        {showOptions && options.map((o, i) => tile(o, o.thumbnail, i + 1))}
        {waiting > 0 && Array.from({ length: waiting }, (_, i) => (
          <div key={`wait-${i}`} aria-hidden className="min-w-0 rounded-card border border-dashed border-line-strong bg-surface p-1.5">
            <div className="flex aspect-video items-center justify-center rounded-xs bg-sunken text-fg-subtle"><AIMark size={16} /></div>
            <div className="flex min-h-9 items-start px-1 pt-1.5 text-small leading-4 text-fg-muted">Making a layout</div>
          </div>
        ))}
      </div>

      {state === "none" && (
        <div className="rounded-card border border-line bg-surface p-4">
          <p className="text-body leading-5 font-medium text-fg">No good layouts for this slide</p>
          <p className="mt-1 text-small leading-4 text-fg-muted">{noneReason ?? "Rather than show weak options, the AI showed none. Changing the content usually opens up new layouts."}</p>
          <div className="mt-3 flex flex-wrap gap-2">
            {onEditText && <Button size="sm" variant="secondary" leading={<EditIcon aria-hidden width={14} height={14} />} onClick={onEditText}>Edit text</Button>}
            {onMore && <Button size="sm" variant="ghost" onClick={onMore}>Try again</Button>}
          </div>
        </div>
      )}

      {state === "failed" && (
        <div className="flex flex-wrap items-start gap-3 rounded-card border border-line bg-surface p-4">
          <AlertIcon aria-hidden width={18} height={18} className="mt-0.5 shrink-0 text-danger-fg" />
          <div className="min-w-0 flex-1">
            <p className="text-body leading-5 font-medium text-fg">{options.length ? "Could not make more layouts" : "Could not make layouts"}</p>
            <p className="mt-1 text-small leading-4 text-fg-muted">{error ?? "Something went wrong on our side. Your slide has not changed."}</p>
          </div>
          {onRetry && <Button size="sm" variant="secondary" leading={<RetryIcon aria-hidden width={14} height={14} />} onClick={onRetry}>Try again</Button>}
        </div>
      )}

      {previewOption && (
        <div className="flex flex-wrap items-center gap-2 rounded-card border border-line bg-sunken p-3">
          <p className="min-w-0 flex-1 text-small leading-4 text-fg"><span className="font-medium">Previewing {previewOption.label}.</span> <span className="text-fg-muted">Not applied yet.</span></p>
          <Button size="sm" variant="ghost" onClick={() => preview(null, true)}>Back to current</Button>
          <Button size="sm" onClick={() => apply(previewOption.id)}>Apply layout</Button>
        </div>
      )}

      {applied && !previewOption && (
        <div className="flex flex-wrap items-center gap-2 rounded-card border border-line bg-surface p-3">
          <CheckIcon aria-hidden width={16} height={16} className="shrink-0 text-success-fg" />
          <p className="min-w-0 flex-1 text-small leading-4 text-fg"><span className="font-medium">{applied.label} applied.</span></p>
          {onUndo && <Button size="sm" variant="secondary" leading={<UndoIcon aria-hidden width={14} height={14} />} onClick={() => { onUndo(); setSaid("Layout change undone."); }}>Undo</Button>}
        </div>
      )}

      {onMore && state !== "none" && (state === "ready" || (state === "failed" && options.length > 0)) && (
        <div className="flex flex-wrap items-center gap-x-3 gap-y-1">
          <Button size="sm" variant="secondary" leading={<AIMark size={16} />} onClick={onMore}>More layouts</Button>
          {cost && <span className="text-micro text-fg-muted">{cost}</span>}
        </div>
      )}

      <p role="status" aria-live="polite" className="sr-only">{said}</p>
    </section>
  );
}
```

## tattva/organisms/LikenessConsent.tsx

```tsx
import { useEffect, useId, useRef, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, ApproveIcon, CameraIcon, HistoryIcon, InfoIcon, LockIcon, MicIcon, ShieldIcon, SparkleIcon, XIcon } from "../lib/icons";
import { headingTag, subHeadingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { AttentionDot } from "../atoms/AttentionDot";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { Callout } from "../molecules/Callout";
import { InlineConfirm } from "../molecules/InlineConfirm";
import { RadioGroup } from "../molecules/RadioGroup";
import { VoiceConsentCheck, type VoiceConsentCheckProps } from "./VoiceConsentCheck";

export type LikenessStatus =
  | "notRequested" | "pending" | "expired" | "recording" | "reviewing"
  | "accepted" | "rejected" | "revoked" | "notRealPerson";
export type LikenessKind = "face" | "voice" | "faceAndVoice";
export type LikenessAudience = "onlyMe" | "approved" | "workspace" | "anyone";

export interface LikenessUse { who: string; what: string; when: string }

export interface LikenessConsentProps {
  /** The person whose face or voice it is, for example "Asha Rao". For a made-up avatar, its name. */
  subjectName: string;
  kind?: LikenessKind;
  status: LikenessStatus;
  /** How long the consent link has left, worked out by your code and written in words, for example "23 hours". Links last 24 hours. */
  linkExpiresIn?: string;
  /** Why the recording was not accepted, in plain words. */
  rejectionReason?: string;
  /** When consent was given or revoked, written by your code, for example "2 October". */
  decidedOn?: string;
  /** Who may use the likeness. Starts at the narrowest choice. */
  audience?: LikenessAudience;
  onAudienceChange?: (audience: LikenessAudience) => void;
  /** Audience choices to offer, in order. Leave "anyone" out unless your product allows it. */
  audienceOptions?: LikenessAudience[];
  /** True when the viewer is the person in the likeness (or its owner) and may change who uses it or revoke it. */
  canManage?: boolean;
  /** Where it has been used, newest first. The owner is told each time. */
  usage?: LikenessUse[];
  /** Send the subject a link to record on their own device. Also "Send a new link" after expiry or rejection. */
  onSendLink?: () => void;
  /** The subject is here: record on this device instead of sending a link. */
  onRecordHere?: () => void;
  /** Withdraw a pending request. */
  onCancelRequest?: () => void;
  onRevoke?: () => void;
  /** Props for the recording step, passed to VoiceConsentCheck with subject="likeness". Needed when status is "recording". */
  check?: Omit<VoiceConsentCheckProps, "subject" | "headingLevel">;
  /** Set when avatars of real people are not allowed where the viewer is. Nothing can be requested and the message says why. */
  region?: { allowed: boolean; message?: string };
  headingLevel?: HeadingLevel;
  className?: string;
}

const kindWords: Record<LikenessKind, { thing: string; Icon: typeof CameraIcon }> = {
  face: { thing: "face", Icon: CameraIcon },
  voice: { thing: "voice", Icon: MicIcon },
  faceAndVoice: { thing: "face and voice", Icon: CameraIcon },
};

const audienceText: Record<LikenessAudience, { label: string; description: string }> = {
  onlyMe: { label: "Only the person in it", description: "Nobody else can pick this likeness." },
  approved: { label: "People they approve", description: "Each person is approved by name." },
  workspace: { label: "Everyone in this workspace", description: "Anyone on the team can use it in their takes." },
  anyone: { label: "Anyone with the link", description: "Widest choice. Hard to keep track of." },
};

const stepNames = ["Ask", "Record", "Review", "Ready"] as const;
const stepOf: Partial<Record<LikenessStatus, number>> = { notRequested: 0, pending: 1, expired: 1, recording: 1, reviewing: 2, rejected: 2, accepted: 3 };

/**
 * Consent for using a real person's face or voice in avatars and takes. The person records it themselves (a link to their
 * own phone, or here with VoiceConsentCheck), the link lasts 24 hours, and the status is always written out. The person
 * in it decides who may use it and can revoke it, and every use is listed. Amber shows only while that person still has
 * to act. There is no way to make a likeness from an uploaded photo.
 */
export function LikenessConsent({
  subjectName, kind = "faceAndVoice", status, linkExpiresIn, rejectionReason, decidedOn, audience = "onlyMe", onAudienceChange,
  audienceOptions = ["onlyMe", "approved", "workspace"], canManage = false, usage = [], onSendLink, onRecordHere, onCancelRequest, onRevoke,
  check, region, headingLevel = 2, className,
}: LikenessConsentProps) {
  const H = headingTag(headingLevel);
  const H2 = subHeadingTag(headingLevel);
  const headId = useId();
  const headRef = useRef<HTMLHeadingElement>(null);
  const first = subjectName.split(" ")[0];
  const { thing, Icon } = kindWords[kind];
  const blockedRegion = region && !region.allowed;

  // When the status changes and the pressed button went away, start keyboard and screen reader users at the heading.
  // A change that arrives from elsewhere (the subject accepted) never takes focus from what the viewer is doing.
  const firstRender = useRef(true);
  useEffect(() => {
    if (firstRender.current) { firstRender.current = false; return; }
    const a = document.activeElement;
    if (!a || a === document.body || !a.isConnected) headRef.current?.focus({ preventScroll: true });
  }, [status]);

  const badge: Record<LikenessStatus, ReactNode> = {
    notRequested: <Badge>Not requested</Badge>,
    pending: <Badge tone="warning"><span aria-hidden className="size-1.5 shrink-0 rounded-full bg-attention" />Waiting for {first}</Badge>,
    expired: <Badge><HistoryIcon width={12} height={12} />Link expired</Badge>,
    recording: <Badge tone="warning"><span aria-hidden className="size-1.5 shrink-0 rounded-full bg-attention" />{first} is recording</Badge>,
    reviewing: <Badge><ShieldIcon width={12} height={12} />Checking the recording</Badge>,
    accepted: <Badge tone="success"><ApproveIcon width={12} height={12} />Consent given</Badge>,
    rejected: <Badge tone="danger"><AlertIcon width={12} height={12} />Not accepted</Badge>,
    revoked: <Badge><LockIcon width={12} height={12} />Revoked</Badge>,
    notRealPerson: <Badge><SparkleIcon width={12} height={12} />Not a real person</Badge>,
  };

  const heading =
    status === "notRequested" ? `Ask ${first} for consent`
    : status === "pending" ? `Waiting for ${first} to record`
    : status === "expired" ? "The link expired"
    : status === "recording" ? `${first} is recording`
    : status === "reviewing" ? "Checking the recording"
    : status === "accepted" ? `${first} said yes`
    : status === "rejected" ? "The recording was not accepted"
    : status === "revoked" ? `${first} withdrew consent`
    : "Made-up avatar";

  const announce =
    status === "pending" ? `Waiting for ${first} to record.${linkExpiresIn ? ` The link expires in ${linkExpiresIn}.` : ""}`
    : status === "accepted" ? `${first} gave consent.`
    : status === "rejected" ? "The recording was not accepted."
    : status === "revoked" ? "Consent revoked. The likeness can no longer be used."
    : status === "expired" ? "The link expired."
    : status === "reviewing" ? "Checking the recording."
    : "";

  const step = stepOf[status];
  const usable = status === "accepted" || status === "notRealPerson";

  return (
    <section aria-labelledby={headId}
      className={cn("flex w-full min-w-0 flex-col gap-4 rounded-card border border-line bg-surface p-4 sm:p-6", className)}>
      <header className="space-y-2">
        <div className="flex flex-wrap items-center justify-between gap-2">
          <p className="flex min-w-0 items-center gap-2 text-small leading-4.5 text-fg-muted">
            <Icon width={14} height={14} className="shrink-0" />
            <span className="truncate">{status === "notRealPerson" ? `Avatar: ${subjectName}` : `${subjectName}'s ${thing}`}</span>
          </p>
          {badge[status]}
        </div>
        {step !== undefined && (
          <ol aria-label="Steps" className="flex gap-1">
            {stepNames.map((n, i) => (
              <li key={n} aria-current={i === step ? "step" : undefined} className="flex-1 space-y-1">
                <span className={cn("block h-1 rounded-full", i < step || (i === step && status === "accepted") ? "bg-fg" : i === step ? "bg-fg-muted" : "bg-line")} />
                <span className={cn("block text-small leading-4.5", i === step ? "font-medium text-fg" : "text-fg-subtle")}>
                  {n}<span className="sr-only">{i < step || status === "accepted" ? ", done" : i === step ? ", current" : ""}</span>
                </span>
              </li>
            ))}
          </ol>
        )}
        <H id={headId} ref={headRef} tabIndex={-1} className="text-subhead font-semibold text-fg outline-none">{heading}</H>
      </header>

      {blockedRegion && status !== "notRealPerson" && (
        <Callout tone="info" title="Not available where you are">
          {region?.message ?? "Avatars of real people are not offered in your region. Made-up avatars still work."}
        </Callout>
      )}

      {status === "notRequested" && !blockedRegion && (
        <div className="space-y-2 text-compact text-fg">
          <p>{first} records a short video on their own device and reads a few lines aloud. That recording is the consent. A photo is not enough.</p>
          <p className="text-fg-muted">The link lasts 24 hours. {first} can say no, and can withdraw consent at any time later.</p>
        </div>
      )}

      {status === "pending" && (
        <div className="space-y-2 text-compact text-fg">
          <p className="flex items-start gap-2"><span className="mt-1.5 flex shrink-0"><AttentionDot label={`Needs ${first}`} /></span>
            <span>We sent {first} a link to record on their phone.{linkExpiresIn ? ` It expires in ${linkExpiresIn}.` : " It lasts 24 hours."}</span></p>
          <p className="text-fg-muted">Nothing can be made with this likeness until {first} records and agrees.</p>
        </div>
      )}

      {status === "expired" && (
        <p className="text-compact text-fg">{first} did not record within 24 hours, so the link stopped working. Nothing was saved. Send a new link if you still need it.</p>
      )}

      {status === "recording" && (check
        ? <VoiceConsentCheck {...check} subject="likeness" headingLevel={Math.min(headingLevel + 1, 6) as HeadingLevel} className="border-0 p-0 sm:p-0" />
        : <p className="text-compact text-fg-muted">{first} is recording on their own device. This updates when they finish.</p>)}

      {status === "reviewing" && (
        <p className="text-compact text-fg-muted">We are checking that the person in the video read the lines and agreed. This usually takes a minute. You can leave this page.</p>
      )}

      {status === "rejected" && (
        <div className="space-y-2 text-compact text-fg">
          <p>{rejectionReason ?? "The recording did not show clear agreement."}</p>
          <p className="text-fg-muted">Nothing was made from it. {first} can record again with a new link.</p>
        </div>
      )}

      {status === "revoked" && (
        <div className="space-y-2 text-compact text-fg">
          <p>{decidedOn ? `On ${decidedOn}, ${first}` : first} withdrew consent. Nobody can use this likeness in new takes.</p>
          <p className="text-fg-muted">Takes already made stay in their projects, labelled as using a withdrawn likeness. Ask {first} again to use it.</p>
        </div>
      )}

      {status === "notRealPerson" && (
        <p className="text-compact text-fg">This avatar was made from a description, not from a real person, so nobody's consent is needed. It cannot be changed to look or sound like a real person.</p>
      )}

      {status === "accepted" && (
        <p className="text-compact text-fg">{first} recorded and agreed{decidedOn ? ` on ${decidedOn}` : ""}. Only {first} can change who uses it or withdraw it.</p>
      )}

      {usable && (
        <div className="space-y-3 border-t border-line pt-4">
          {canManage && onAudienceChange
            ? <RadioGroup label="Who can use it" value={audience} onChange={(v) => onAudienceChange(v as LikenessAudience)}
                options={audienceOptions.map((a) => ({ value: a, ...audienceText[a] }))} />
            : <div className="space-y-1">
                <H2 className="text-body leading-5 font-medium text-fg">Who can use it</H2>
                <p className="text-compact text-fg">{audienceText[audience].label}</p>
                {status === "accepted" && !canManage && <p className="text-small leading-4.5 text-fg-muted">Only {first} can change this.</p>}
              </div>}
        </div>
      )}

      {(status === "accepted" || status === "revoked") && (
        <div className="space-y-2 border-t border-line pt-4">
          <H2 className="text-body leading-5 font-medium text-fg">Where it was used</H2>
          {usage.length === 0
            ? <p className="text-compact text-fg-muted">Not used yet. {first} is told each time someone uses it.</p>
            : <>
                <p className="text-small leading-4.5 text-fg-muted">Used in {usage.length} {usage.length === 1 ? "take" : "takes"}. {first} is told each time.</p>
                <ul className="divide-y divide-line">
                  {usage.map((u, i) => (
                    <li key={i} className="flex flex-wrap items-baseline justify-between gap-x-3 py-1.5 text-compact">
                      <span className="min-w-0 text-fg">{u.who} <span className="text-fg-muted">used it in</span> {u.what}</span>
                      <span className="text-small leading-4.5 text-fg-muted">{u.when}</span>
                    </li>
                  ))}
                </ul>
              </>}
        </div>
      )}

      {(() => {
        const actions: ReactNode[] = [];
        if (!blockedRegion && (status === "notRequested" || status === "expired" || status === "rejected" || status === "revoked")) {
          if (onSendLink) actions.push(<Button key="send" onClick={onSendLink}>{status === "notRequested" ? `Send ${first} a link` : "Send a new link"}</Button>);
          if (onRecordHere) actions.push(<Button key="here" variant="secondary" leading={<Icon width={14} height={14} />} onClick={onRecordHere}>{first} is here: record now</Button>);
        }
        if (status === "pending") {
          if (onSendLink) actions.push(<Button key="resend" variant="secondary" onClick={onSendLink}>Send the link again</Button>);
          if (onRecordHere) actions.push(<Button key="here" variant="secondary" leading={<Icon width={14} height={14} />} onClick={onRecordHere}>{first} is here: record now</Button>);
          if (onCancelRequest) actions.push(<Button key="cancel" variant="ghost" leading={<XIcon width={14} height={14} />} onClick={onCancelRequest}>Cancel request</Button>);
        }
        if (status === "accepted" && canManage && onRevoke) {
          actions.push(<InlineConfirm key="revoke" tone="danger" confirmLabel="Revoke" cancelLabel="Keep it" onConfirm={onRevoke}
            question={`Stop all new use of ${first}'s ${thing}?`}>Revoke consent</InlineConfirm>);
        }
        return actions.length ? <div className="flex flex-wrap items-center gap-2 border-t border-line pt-4 max-sm:[&>button]:flex-1">{actions}</div> : null;
      })()}

      {status !== "recording" && status !== "notRealPerson" && <p className="flex items-start gap-2 text-small leading-4.5 text-fg-subtle"><InfoIcon width={14} height={14} className="mt-0.5 shrink-0" />
        Consent covers this workspace only. It does not give anyone ownership of {first}'s likeness.</p>}
      <p role="status" className="sr-only">{announce}</p>
    </section>
  );
}
```

## tattva/organisms/LineChart.tsx

```tsx
import { useEffect, useId, useRef, useState, type KeyboardEvent, type PointerEvent } from "react";
import { cn } from "../lib/cn";
import type { DataTableProps } from "../molecules/DataTable";
import { formatNumber, niceTicks, useElementWidth, usePrefersReducedMotion } from "../molecules/ChartFrame";
import { ChartLegend, SeriesMark, seriesStyle } from "./ChartLegend";

export interface LineSeries {
  /** Stable id. Color follows the series position, so keep the order fixed. */
  id: string;
  /** Name shown in the legend, end label, tooltip and table. */
  label: string;
  /** One value per x position. Use null for a gap. */
  data: (number | null)[];
  /** Index of the first projected point. Points from here on are dotted, and the line is solid before it. */
  projectionFrom?: number;
}

export interface LineChartProps {
  /** Up to five series. Extra series are ignored. Fold the tail into one "Other" series before passing it in. */
  series: LineSeries[];
  /** X labels, one per data position, already formatted for display. */
  x: string[];
  /** Formats values on the axis, in labels and in the tooltip. */
  formatValue?: (n: number) => string;
  /** Unit word read after each value by screen readers, for example "milliseconds". */
  unit?: string;
  /** Plot height in px, including the x axis band. Minimum 160. */
  height?: number;
  /** Fixed y domain. Overrides zeroBaseline. */
  yDomain?: [number, number];
  /** Start the y axis at zero when every value is non-negative. Turn off only for data where zero is meaningless. */
  zeroBaseline?: boolean;
  /** The latest point of each series is hollow because it is not final yet. */
  streaming?: boolean;
  /** Reveal the lines once on mount. Skipped when the person prefers reduced motion. */
  animate?: boolean;
  /** Name for the chart region, for example "p95 latency by model". */
  ariaLabel?: string;
  className?: string;
}

const MAX_SERIES = 5;

function seg(vals: (number | null)[], from: number, to: number, px: (i: number) => number, py: (v: number) => number): string {
  let d = "";
  let pen = false;
  for (let i = Math.max(0, from); i <= Math.min(vals.length - 1, to); i++) {
    const v = vals[i];
    if (v === null || v === undefined) { pen = false; continue; }
    d += `${pen ? "L" : "M"}${px(i).toFixed(1)} ${py(v).toFixed(1)}`;
    pen = true;
  }
  return d;
}

/**
 * Multi-series line chart drawn in SVG at real pixel size. Solid lines are actuals and dotted lines are projections.
 * The plot takes focus: arrow keys step through points and each point is announced as "Series, x, value, unit".
 * The tooltip shows on focus, pointer and tap and never depends on hover.
 */
export function LineChart({ series: all, x, formatValue = formatNumber, unit = "", height = 240, yDomain, zeroBaseline = true, streaming, animate, ariaLabel = "Line chart", className }: LineChartProps) {
  const series = all.slice(0, MAX_SERIES);
  const [ref, measured] = useElementWidth<HTMLDivElement>();
  const reduced = usePrefersReducedMotion();
  const svgRef = useRef<SVGSVGElement | null>(null);
  const wrapRef = useRef<HTMLDivElement | null>(null);
  const cid = `clip-${useId().replace(/:/g, "")}`;
  const [active, setActive] = useState<{ s: number; i: number } | null>(null);
  const [said, setSaid] = useState("");
  const [revealed, setRevealed] = useState(!animate);
  useEffect(() => { if (animate && !reduced) { const t = requestAnimationFrame(() => setRevealed(true)); return () => cancelAnimationFrame(t); } setRevealed(true); }, [animate, reduced]);

  const w = measured || 640;
  const h = Math.max(160, height);
  const n = x.length;
  const all_ = series.flatMap((s) => s.data).filter((v): v is number => typeof v === "number");
  const dMin = all_.length ? Math.min(...all_) : 0;
  const dMax = all_.length ? Math.max(...all_) : 1;
  const ticks = yDomain ? niceTicks(yDomain[0], yDomain[1]) : niceTicks(zeroBaseline && dMin >= 0 ? 0 : dMin, dMax);
  const lo = yDomain ? yDomain[0] : ticks[0];
  const hi = yDomain ? yDomain[1] : ticks[ticks.length - 1];
  const tickLabels = ticks.map((t) => formatValue(t));
  const left = Math.max(28, Math.max(...tickLabels.map((t) => t.length)) * 6.6 + 14);
  const endY = (s: LineSeries) => {
    for (let i = s.data.length - 1; i >= 0; i--) if (s.data[i] !== null && s.data[i] !== undefined) return i;
    return -1;
  };

  const plotTop = 12;
  const plotBottom = h - 28;
  const py = (v: number) => plotBottom - ((v - lo) / (hi - lo || 1)) * (plotBottom - plotTop);

  const labelsWanted = series.length >= 2 && series.length <= 4 && w >= 480;
  const endYs = series.map((s) => { const e = endY(s); return e >= 0 ? py(s.data[e] as number) : -999; }).sort((a, b) => a - b);
  const collide = endYs.some((v, i) => i > 0 && v - endYs[i - 1] < 14);
  const direct = labelsWanted && !collide;
  const clip = (t: string) => (t.length > 20 ? `${t.slice(0, 19)}…` : t);
  const right = direct ? Math.min(150, Math.max(...series.map((s) => clip(s.label).length)) * 6.4 + 24) : 24;
  const pw = Math.max(40, w - left - right);
  const px = (i: number) => left + (n <= 1 ? pw / 2 : (i * pw) / (n - 1));

  const xw = Math.max(0, ...x.map((t) => t.length)) * 6.4 + 14;
  const stepK = Math.max(1, Math.ceil(xw / (n > 1 ? pw / (n - 1) : pw)));

  const pf = (s: LineSeries) => s.projectionFrom ?? n;
  const readout = (s: number, i: number) => {
    const sr = series[s];
    const v = sr.data[i];
    const tail = i >= pf(sr) ? ", projected" : streaming && i === endY(sr) ? ", provisional" : "";
    return `${sr.label}, ${x[i]}, ${v === null || v === undefined ? "no data" : `${formatValue(v)}${unit ? ` ${unit}` : ""}`}${tail}`;
  };

  const step = (e: KeyboardEvent<HTMLDivElement>) => {
    if (!n || !series.length) return;
    const cur = active ?? { s: 0, i: n - 1 };
    let { s, i } = cur;
    if (e.key === "ArrowRight") i = Math.min(n - 1, i + 1);
    else if (e.key === "ArrowLeft") i = Math.max(0, i - 1);
    else if (e.key === "Home") i = 0;
    else if (e.key === "End") i = n - 1;
    else if (e.key === "ArrowDown") s = Math.min(series.length - 1, s + 1);
    else if (e.key === "ArrowUp") s = Math.max(0, s - 1);
    else if (e.key === "Escape") { setActive(null); setSaid(""); return; }
    else return;
    e.preventDefault();
    setActive({ s, i });
    setSaid(readout(s, i));
  };

  const point = (e: PointerEvent<SVGRectElement>) => {
    const r = svgRef.current?.getBoundingClientRect();
    if (!r || !n) return;
    const i = n <= 1 ? 0 : Math.max(0, Math.min(n - 1, Math.round(((e.clientX - r.left - left) / pw) * (n - 1))));
    const yy = e.clientY - r.top;
    let best = 0, bd = Infinity;
    series.forEach((s, si) => { const v = s.data[i]; if (v === null || v === undefined) return; const d = Math.abs(py(v) - yy); if (d < bd) { bd = d; best = si; } });
    setActive({ s: best, i });
  };

  const ai = active?.i ?? -1;
  const tipRight = ai >= 0 && px(ai) > w / 2;

  return (
    <div ref={ref} className={cn("relative min-w-0", className)}>
      {series.length >= 2 && <ChartLegend items={series.map((s) => ({ id: s.id, label: s.label }))} />}
      <div ref={wrapRef} tabIndex={0} role="group" aria-roledescription="line chart"
        aria-label={`${ariaLabel}. ${series.length} series, ${n} points. Use arrow keys to step through points.`}
        onKeyDown={step} onFocus={() => { if (n) setActive((p) => p ?? { s: 0, i: n - 1 }); }}
        onBlur={() => { setActive(null); setSaid(""); }}
        className="relative rounded-lg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--focus-ring)]">
        <svg ref={svgRef} width={w} height={h} viewBox={`0 0 ${w} ${h}`} className="block max-w-full touch-pan-y select-none" aria-hidden focusable="false"
          style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-small)" }}>
          <defs>
            <clipPath id={cid}>
              <rect x={0} y={0} height={h} width={revealed ? w : 0} style={{ transition: reduced ? "none" : "width var(--dur-slow) var(--ease-arrive)" }} />
            </clipPath>
          </defs>
          {ticks.map((t, i) => (
            <g key={t}>
              <line x1={left} x2={left + pw} y1={py(t)} y2={py(t)} stroke={i === 0 ? "var(--chart-axis)" : "var(--chart-grid)"} strokeWidth={i === 0 ? 1 : 0.5} shapeRendering="crispEdges" />
              <text x={left - 8} y={py(t)} textAnchor="end" dominantBaseline="central" fill="var(--fg-muted)" style={{ fontVariantNumeric: "tabular-nums" }}>{tickLabels[i]}</text>
            </g>
          ))}
          {x.map((t, i) => {
            if (i % stepK !== 0) return null;
            const half = (t.length * 6.4) / 2;
            const cx = px(i);
            const anchor = cx - half < 0 ? "start" : cx + half > w ? "end" : "middle";
            return <text key={i} x={anchor === "start" ? Math.max(0, cx - half) : cx} y={plotBottom + 18} textAnchor={anchor} fill="var(--fg-muted)">{t}</text>;
          })}
          {ai >= 0 && <line x1={px(ai)} x2={px(ai)} y1={plotTop} y2={plotBottom} stroke="var(--chart-axis)" strokeWidth={1} shapeRendering="crispEdges" />}
          <g clipPath={`url(#${cid})`}>
            {series.map((s, si) => {
              const st = seriesStyle(si);
              const p = pf(s);
              return (
                <g key={s.id}>
                  <path d={seg(s.data, 0, p - 1, px, py)} fill="none" stroke={st.color} strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" strokeDasharray={st.dash} />
                  {p < n && <path d={seg(s.data, p - 1, n - 1, px, py)} fill="none" stroke={st.color} strokeWidth={2} strokeLinecap="round" strokeDasharray="0.1 5" />}
                </g>
              );
            })}
          </g>
          {series.map((s, si) => {
            const e = endY(s);
            if (e < 0) return null;
            const st = seriesStyle(si);
            return (
              <g key={s.id}>
                <SeriesMark shape={st.shape} cx={px(e)} cy={py(s.data[e] as number)} color={st.color} hollow={streaming} />
                {direct && <text x={px(e) + 11} y={py(s.data[e] as number)} dominantBaseline="central" fill="var(--fg)" style={{ fontWeight: 500 }}>{clip(s.label)}</text>}
              </g>
            );
          })}
          {ai >= 0 && series.map((s, si) => {
            const v = s.data[ai];
            if (v === null || v === undefined || ai === endY(s)) return null;
            const st = seriesStyle(si);
            return <SeriesMark key={s.id} shape={st.shape} cx={px(ai)} cy={py(v)} color={st.color} r={active?.s === si ? 5.5 : 4.5} />;
          })}
          <rect x={left} y={0} width={pw + Math.min(right, 12)} height={plotBottom + 4} fill="transparent" onPointerMove={point} onPointerDown={point}
            onPointerLeave={(e) => { if (e.pointerType === "mouse" && document.activeElement !== wrapRef.current) setActive(null); }} />
        </svg>
        {ai >= 0 && (
          <div aria-hidden className="pointer-events-none absolute z-10 min-w-36 max-w-64 rounded-overlay border border-line bg-surface px-3 py-2 text-small leading-4 shadow-md"
            style={{ top: plotTop, left: px(ai), transform: `translateX(${tipRight ? "calc(-100% - 12px)" : "12px"})` }}>
            <p className="mb-1 text-fg-muted">{x[ai]}</p>
            {series.map((s, si) => {
              const v = s.data[ai];
              const st = seriesStyle(si);
              return (
                <p key={s.id} className="flex items-center gap-2 leading-5">
                  <svg width={18} height={10} viewBox="0 0 18 10" aria-hidden><line x1={0} x2={18} y1={5} y2={5} stroke={st.color} strokeWidth={2} strokeDasharray={st.dash} /></svg>
                  <span className={cn("flex-1 text-fg-muted", active?.s === si && "font-medium text-fg")}>{s.label}</span>
                  <span className="font-semibold text-fg [font-variant-numeric:tabular-nums]">{v === null || v === undefined ? "No data" : formatValue(v)}</span>
                  {v !== null && v !== undefined && (ai >= pf(s) ? <span className="text-fg-subtle">projected</span> : streaming && ai === endY(s) ? <span className="text-fg-subtle">provisional</span> : null)}
                </p>
              );
            })}
          </div>
        )}
      </div>
      <div className="sr-only" aria-live="polite" role="status">{said}</div>
    </div>
  );
}

/** Table data for ChartFrame's table view, with one column per series. */
export function lineChartTable(x: string[], series: LineSeries[], opts: { xHeader: string; caption: string; formatValue?: (n: number) => string; unit?: string }): DataTableProps {
  const f = opts.formatValue ?? formatNumber;
  return {
    caption: opts.caption,
    columns: [{ key: "x", header: opts.xHeader }, ...series.slice(0, MAX_SERIES).map((s) => ({ key: s.id, header: opts.unit ? `${s.label} (${opts.unit})` : s.label, numeric: true }))],
    rows: x.map((label, i) => {
      const row: Record<string, string> = { x: label };
      series.slice(0, MAX_SERIES).forEach((s) => {
        const v = s.data[i];
        row[s.id] = v === null || v === undefined ? "No data" : `${f(v)}${s.projectionFrom !== undefined && i >= s.projectionFrom ? " (projected)" : ""}`;
      });
      return row;
    }),
  };
}
```

## tattva/organisms/LiveCaptions.tsx

```tsx
import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
import type { CSSProperties, ReactNode } from "react";
import { cn } from "../lib/cn";
import { useReducedMotion } from "../hooks/useReducedMotion";
import { AIMark } from "../atoms/AIMark";
import { SegmentedControl } from "../atoms/SegmentedControl";
import { Button } from "../atoms/Button";
import { JumpToLatest } from "../molecules/JumpToLatest";

export type CaptionStatus = "interim" | "final" | "interrupted" | "redacted";
export type CaptionSize = "sm" | "md" | "lg" | "xl";

export interface CaptionLine {
  /** Stable id. An interim line keeps its id when it becomes final, so it settles in place. */
  id: string;
  /** "person", "assistant", or any other speaker key for calls and rooms. */
  speaker: "person" | "assistant" | (string & {});
  /** For redacted lines, write `[hidden]` where the secret was; never pass the secret itself. */
  text: string;
  status: CaptionStatus;
  /** Interrupted lines: how many characters were actually played before the person cut in. */
  spokenUpTo?: number;
  /** Already formatted time or offset, such as "00:42". */
  at?: string;
  /** Redacted lines: what the hidden part was, shown in brackets. Defaults to "number hidden". */
  hiddenLabel?: string;
}

export interface LiveCaptionsProps {
  /** Every line so far, oldest first. Update an interim line by id until it is final. */
  lines: CaptionLine[];
  /** Captions on or off. Keep this setting for the person across sessions. */
  visible?: boolean;
  onVisibleChange?: (visible: boolean) => void;
  /** Caption text size. Defaults to lg on a narrow screen and md elsewhere. Store the person's choice. */
  size?: CaptionSize;
  onSizeChange?: (size: CaptionSize) => void;
  /** Keep the newest line in view. Scrolling up turns it off until the person returns to the bottom. */
  follow?: boolean;
  onFollowChange?: (follow: boolean) => void;
  /** Make each line a button that jumps the audio to it (for recordings, not live calls). */
  onSeek?: (id: string) => void;
  /** Names to show for each speaker key. */
  speakerLabels?: Record<string, string>;
  /** Speaker keys that are AI. Their labels carry the AI mark. */
  aiSpeakers?: string[];
  /** Interrupted lines: show the unspoken rest struck through, or leave it out. */
  unspoken?: "show" | "hide";
  /** Read each finished AI line aloud to screen readers, for people who mute the audio. Off by default. */
  announceFinal?: boolean;
  /** Accessible name of the caption log, also shown as its heading. */
  label?: string;
  /** Shown when there are no lines yet. */
  emptyText?: ReactNode;
  /** Show the size control and the Hide button in the header. */
  controls?: boolean;
  /** Tallest the log grows before it scrolls: spacing steps (64 is 16rem) or a CSS length. */
  maxHeight?: number | string;
  className?: string;
}

const NEAR = 4;
const sizeText: Record<CaptionSize, string> = {
  sm: "text-compact",
  md: "text-body-lg",
  lg: "text-subhead",
  xl: "text-title-sm",
};
const sizeOptions: { value: CaptionSize; label: string; description: string }[] = [
  { value: "sm", label: "S", description: "Small captions" },
  { value: "md", label: "M", description: "Medium captions" },
  { value: "lg", label: "L", description: "Large captions" },
  { value: "xl", label: "XL", description: "Extra large captions" },
];
const narrow = () => typeof window !== "undefined" && !!window.matchMedia && window.matchMedia("(max-width: 37.5rem)").matches;

/** Interim text has no end punctuation, so it never looks finished. */
const unfinished = (t: string) => t.replace(/[.!?。]+\s*$/, "");

function Redacted({ text, hiddenLabel = "number hidden" }: { text: string; hiddenLabel?: string }) {
  const chip = (k: number) => (
    <span key={k} className="inline-block rounded-control bg-sunken px-1.5 text-fg-muted">[{hiddenLabel}]</span>
  );
  const parts = text.split("[hidden]");
  if (parts.length === 1) return <>{text} {chip(0)}</>;
  return <>{parts.flatMap((p, i) => (i === 0 ? [p] : [chip(i), p]))}</>;
}

/**
 * A streaming, two-sided caption log for voice. Interim words are lighter with no full stop and settle in place.
 * When the person cuts the assistant off, the words it never said are struck through and named "Not spoken".
 * It follows the newest line until the person scrolls up, then offers Jump to latest. It is a log that people
 * read on demand; it does not read every word aloud.
 */
export function LiveCaptions({
  lines, visible, onVisibleChange, size, onSizeChange, follow = true, onFollowChange, onSeek,
  speakerLabels, aiSpeakers = ["assistant"], unspoken = "show", announceFinal = false, label = "Captions",
  emptyText = "Captions appear here as you talk.", controls = true, maxHeight = 64, className,
}: LiveCaptionsProps) {
  const reduced = useReducedMotion();
  const headId = useId();
  const scroller = useRef<HTMLDivElement>(null);
  const names: Record<string, string> = { person: "You", assistant: "Assistant", ...speakerLabels };
  const isAI = (s: string) => aiSpeakers.includes(s);

  // visible and size work with or without the parent controlling them.
  const [ownVisible, setOwnVisible] = useState(visible ?? true);
  const on = visible ?? ownVisible;
  const setVisible = (v: boolean) => { setOwnVisible(v); onVisibleChange?.(v); };
  const [ownSize, setOwnSize] = useState<CaptionSize>(() => size ?? (narrow() ? "lg" : "md"));
  const textSize = size ?? ownSize;
  const setSize = (s: CaptionSize) => { setOwnSize(s); onSizeChange?.(s); };

  const [following, setFollowing] = useState(follow);
  const [prevFollow, setPrevFollow] = useState(follow);
  const [stopAt, setStopAt] = useState<number | null>(null);
  if (prevFollow !== follow) { setPrevFollow(follow); setFollowing(follow); setStopAt(follow ? null : lines.length); }
  const unseen = following || stopAt === null ? 0 : Math.max(0, lines.length - stopAt);
  const setFollow = (v: boolean) => {
    if (v === following) return;
    setFollowing(v);
    setStopAt(v ? null : lines.length);
    onFollowChange?.(v);
  };

  // Only lines that arrive after the first render fade in. An interim line becoming final keeps its id, so it never re-animates.
  const seen = useRef<Set<string> | null>(null);
  if (seen.current === null) seen.current = new Set(lines.map((l) => l.id));
  const fresh = new Set<string>();
  for (const l of lines) if (!seen.current.has(l.id)) fresh.add(l.id);
  useEffect(() => { for (const id of fresh) seen.current?.add(id); });

  const lastText = lines.length ? lines[lines.length - 1].text : "";
  useLayoutEffect(() => {
    const el = scroller.current;
    if (el && following) el.scrollTop = el.scrollHeight;
  }, [lines.length, lastText, following, textSize, on]);

  const onScroll = () => {
    const el = scroller.current;
    if (!el) return;
    const dist = el.scrollHeight - el.scrollTop - el.clientHeight;
    if (following && dist > NEAR) setFollow(false);
    else if (!following && dist <= NEAR) setFollow(true);
  };
  const toLatest = () => {
    const el = scroller.current;
    if (el) el.scrollTop = el.scrollHeight;
    setFollow(true);
  };

  // announceFinal: read each AI line once, when it is final or interrupted (only the part that was spoken).
  const [said, setSaid] = useState("");
  const announced = useRef<Set<string> | null>(null);
  if (announced.current === null) announced.current = new Set(lines.filter((l) => l.status !== "interim").map((l) => l.id));
  useEffect(() => {
    if (!announceFinal) return;
    for (const l of lines) {
      if (!isAI(l.speaker) || l.status === "interim" || announced.current?.has(l.id)) continue;
      announced.current?.add(l.id);
      const text = l.status === "interrupted" && l.spokenUpTo != null ? l.text.slice(0, l.spokenUpTo) : l.text.split("[hidden]").join(l.hiddenLabel ?? "number hidden");
      setSaid(`${names[l.speaker] ?? l.speaker}: ${text}`);
    }
  });

  const height: CSSProperties = { maxHeight: typeof maxHeight === "number" ? `calc(var(--spacing) * ${maxHeight})` : maxHeight };

  const body = (l: CaptionLine) => {
    if (l.status === "redacted") return <Redacted text={l.text} hiddenLabel={l.hiddenLabel} />;
    if (l.status === "interim") return <>{unfinished(l.text)}</>;
    if (l.status === "interrupted") {
      const cut = Math.max(0, Math.min(l.text.length, l.spokenUpTo ?? l.text.length));
      const said = l.text.slice(0, cut).trimEnd();
      const rest = l.text.slice(cut).trim();
      return (
        <>
          {said}
          {rest && unspoken === "show" && (
            <> <span className="text-fg-subtle line-through decoration-fg-subtle"><span className="sr-only">Not spoken: </span>{rest}</span></>
          )}
        </>
      );
    }
    return <>{l.text}</>;
  };

  const tag = (l: CaptionLine) =>
    l.status === "interim" ? "Still changing"
    : l.status === "interrupted" ? "Interrupted"
    : l.status === "redacted" ? "Part hidden"
    : "";

  return (
    <section aria-labelledby={headId} className={cn("flex min-w-0 flex-col overflow-hidden rounded-card border border-line bg-surface", className)}>
      <div className="flex flex-wrap items-center gap-x-3 gap-y-2 border-b border-line px-4 py-2">
        <p id={headId} className="mr-auto text-small font-medium text-fg-muted">{label}</p>
        {controls && on && <SegmentedControl size="sm" label="Caption size" options={sizeOptions} value={textSize} onChange={setSize} />}
        {controls && (
          <Button size="sm" variant="ghost" aria-label={on ? "Hide captions" : "Show captions"} onClick={() => setVisible(!on)} className="tap">
            {on ? "Hide" : "Show"}
          </Button>
        )}
      </div>

      {on ? (
        <div className="relative min-h-0">
          <div ref={scroller} role="log" aria-live="off" aria-labelledby={headId} tabIndex={0} onScroll={onScroll} style={height}
            className="overflow-y-auto overscroll-contain px-4 py-3 focus-visible:[outline-offset:-2px]">
            {lines.length === 0 ? (
              <p className="text-compact text-fg-muted">{emptyText}</p>
            ) : (
              <ol className={cn("flex flex-col gap-3", sizeText[textSize])}>
                {lines.map((l) => {
                  const ai = isAI(l.speaker);
                  const who = names[l.speaker] ?? l.speaker;
                  const t = tag(l);
                  const content = (
                    <>
                      <span className="flex items-center gap-2 text-small leading-5">
                        {ai ? (
                          <span className="inline-flex items-center gap-1 rounded-control bg-lime-soft px-1.5 font-medium text-fg">
                            <AIMark size={16} />{who}
                          </span>
                        ) : (
                          <span className="font-medium text-fg-muted">{who}</span>
                        )}
                        {l.at && <span className="font-mono text-fg-subtle tabular-nums">{l.at}</span>}
                        {t && <span className="text-fg-subtle">{t}</span>}
                      </span>
                      <span className={cn("block break-words", l.status === "interim" ? "text-fg-muted" : "text-fg")}>
                        {body(l)}
                      </span>
                    </>
                  );
                  return (
                    <li key={l.id} className={cn("min-w-0", fresh.has(l.id) && !reduced && "animate-fade")}>
                      {onSeek ? (
                        <button type="button" onClick={() => onSeek(l.id)}
                          className="-mx-2 flex w-[calc(100%+var(--spacing)*4)] cursor-pointer flex-col gap-0.5 rounded-control px-2 py-1 text-left hover:bg-hover">
                          {content}
                        </button>
                      ) : (
                        <div className="flex flex-col gap-0.5">{content}</div>
                      )}
                    </li>
                  );
                })}
              </ol>
            )}
          </div>
          <JumpToLatest visible={!following && lines.length > 0} count={unseen} newLabel="New captions" onClick={toLatest} />
        </div>
      ) : (
        <p className="px-4 py-3 text-compact text-fg-muted">Captions are off.</p>
      )}
      <p role="status" className="sr-only">{announceFinal ? said : ""}</p>
    </section>
  );
}
```

## tattva/organisms/LiveLog.tsx

```tsx
import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
import type { CSSProperties } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, XIcon } from "../lib/icons";
import { useReducedMotion } from "../hooks/useReducedMotion";
import { JumpToLatest } from "../molecules/JumpToLatest";

export type LiveLogLevel = "info" | "warn" | "error";

export interface LiveLogLine {
  id: string;
  text: string;
  /** Already formatted, e.g. "10:42:03". */
  time?: string;
  /** info lines carry no marker. warn and error lines show an icon and a word. */
  level?: LiveLogLevel;
}

export interface LiveLogProps {
  /** Every line so far, oldest first. Append only: new lines go on the end. */
  lines: LiveLogLine[];
  /** Keep the newest line in view. Scrolling up turns it off; scrolling back to the bottom turns it on. */
  follow?: boolean;
  onFollowChange?: (follow: boolean) => void;
  /** Hold new lines back. They wait behind an "N new lines" button. */
  paused?: boolean;
  onPauseChange?: (paused: boolean) => void;
  /** Tallest the log grows before it scrolls: a number of spacing steps (64 is 16rem) or a CSS length such as "var(--container-phone)". */
  maxHeight?: number | string;
  /** Accessible name of the log, also shown as its heading. */
  label: string;
  /** Wrap long lines instead of scrolling sideways. */
  wrap?: boolean;
  className?: string;
}

/** At most one spoken update per this many ms, so a burst of lines is read as one summary. */
const SPEAK_GAP = 2000;
/** Lines in a burst after this many arrive together with the last staggered one. */
const STAGGER_CAP = 4;
/** Pixels from the bottom that still count as "at the newest line". */
const NEAR = 4;

const levelMark: Record<Exclude<LiveLogLevel, "info">, { word: string; cls: string; Icon: typeof AlertIcon }> = {
  // Warnings use plain text, not amber: amber is kept for "a person must act".
  warn: { word: "Warning", cls: "text-fg font-medium", Icon: AlertIcon },
  error: { word: "Error", cls: "text-danger-fg font-medium", Icon: XIcon },
};

const spoken = (l: LiveLogLine) => `${l.level && l.level !== "info" ? `${levelMark[l.level].word}: ` : ""}${l.text}`;

/**
 * A streaming, monospaced log of what an agent is doing. It follows the newest line until the person scrolls up,
 * then offers "Jump to latest". Pausing holds new lines back. Screen readers hear single lines as they come and a
 * short summary, at most every 2 seconds, when many arrive at once; nothing is spoken while paused or scrolled up.
 */
export function LiveLog({ lines, follow = true, onFollowChange, paused = false, onPauseChange, maxHeight = 64, label, wrap = false, className }: LiveLogProps) {
  const reduced = useReducedMotion();
  const scroller = useRef<HTMLDivElement>(null);

  const headId = useId();
  // How many lines were showing when the pause began. Lines past that are held.
  const [frozenAt, setFrozenAt] = useState<number | null>(paused ? lines.length : null);
  // How many lines were showing when following stopped, for the Jump to latest count.
  const [stopAt, setStopAt] = useState<number | null>(follow ? null : lines.length);

  // follow and paused work with or without the parent controlling them.
  const [following, setFollowing] = useState(follow);
  const [prevFollow, setPrevFollow] = useState(follow);
  if (prevFollow !== follow) { setPrevFollow(follow); setFollowing(follow); setStopAt(follow ? null : lines.length); }
  const [isPaused, setIsPaused] = useState(paused);
  const [prevPaused, setPrevPaused] = useState(paused);
  if (prevPaused !== paused) { setPrevPaused(paused); setIsPaused(paused); setFrozenAt(paused ? lines.length : null); }

  const shownCount = isPaused ? Math.min(frozenAt ?? lines.length, lines.length) : lines.length;
  const shown = lines.slice(0, shownCount);
  const held = lines.length - shownCount;
  const unseen = following || stopAt === null ? 0 : Math.max(0, shownCount - stopAt);

  const setFollow = (v: boolean) => {
    if (v === following) return;
    setFollowing(v);
    setStopAt(v ? null : shownCount);
    onFollowChange?.(v);
  };
  const setPause = (v: boolean) => {
    setIsPaused(v);
    setFrozenAt(v ? lines.length : null);
    onPauseChange?.(v);
  };

  // Arrival motion: lines present on first render do not animate. Each later line keeps the delay it got when it
  // first appeared, so a re-render mid-animation does not restart or cut it.
  const delays = useRef<Map<string, number> | null>(null);
  if (delays.current === null) delays.current = new Map(lines.map((l) => [l.id, -1]));
  let burst = 0;
  for (const l of shown) if (!delays.current.has(l.id)) delays.current.set(l.id, Math.min(burst++, STAGGER_CAP));

  useLayoutEffect(() => {
    const el = scroller.current;
    if (el && following) el.scrollTop = el.scrollHeight;
  }, [shownCount, following, wrap]);

  // Announcements. The log itself is polite while following and not paused, so a lone line is read. During a
  // burst it goes quiet and a status line reads a count instead, no more than once per SPEAK_GAP.
  const live = following && !isPaused;
  const [summary, setSummary] = useState("");
  const lastSpoke = useRef(0);
  const pending = useRef(0);
  const timer = useRef<number | null>(null);
  const prevCount = useRef(shownCount);
  const latest = useRef<LiveLogLine | undefined>(undefined);
  latest.current = shown[shown.length - 1];

  useLayoutEffect(() => {
    const el = scroller.current;
    const delta = shownCount - prevCount.current;
    prevCount.current = shownCount;
    if (!el) return;
    if (!live) {
      el.setAttribute("aria-live", "off");
      pending.current = 0;
      if (timer.current !== null) { window.clearTimeout(timer.current); timer.current = null; }
      return;
    }
    if (delta <= 0) { if (timer.current === null) el.setAttribute("aria-live", "polite"); return; }
    const now = Date.now();
    if (delta === 1 && timer.current === null && now - lastSpoke.current >= SPEAK_GAP) {
      el.setAttribute("aria-live", "polite");
      lastSpoke.current = now;
      return;
    }
    el.setAttribute("aria-live", "off");
    pending.current += delta;
    if (timer.current !== null) return;
    timer.current = window.setTimeout(() => {
      timer.current = null;
      const n = pending.current;
      pending.current = 0;
      lastSpoke.current = Date.now();
      const last = latest.current;
      if (n > 0 && last) setSummary(`${n} new ${n === 1 ? "line" : "lines"}. Latest: ${spoken(last)}`);
      scroller.current?.setAttribute("aria-live", "polite");
    }, Math.max(0, lastSpoke.current + SPEAK_GAP - now));
  }, [shownCount, live]);

  useEffect(() => () => { if (timer.current !== null) window.clearTimeout(timer.current); }, []);

  const onScroll = () => {
    const el = scroller.current;
    if (!el) return;
    const dist = el.scrollHeight - el.scrollTop - el.clientHeight;
    if (following && dist > NEAR) setFollow(false);
    else if (!following && dist <= NEAR) setFollow(true);
  };
  const toLatest = () => {
    const el = scroller.current;
    if (el) el.scrollTop = el.scrollHeight;
    setFollow(true);
  };

  const height: CSSProperties = { maxHeight: typeof maxHeight === "number" ? `calc(var(--spacing) * ${maxHeight})` : maxHeight };
  return (
    <div className={cn("flex min-w-0 flex-col overflow-hidden rounded-card border border-line bg-sunken", className)}>
      <div className="flex items-center gap-3 border-b border-line px-3 py-1.5">
        <p id={headId} className="min-w-0 flex-1 truncate text-small font-medium text-fg-muted">{label}</p>
        {isPaused && <span className="text-small text-fg-muted">Paused</span>}
        <button type="button" aria-label={`${isPaused ? "Resume" : "Pause"} ${label}`} onClick={() => setPause(!isPaused)}
          className="inline-flex h-7 cursor-pointer items-center rounded-control px-3 text-small text-fg hover:bg-hover tap">
          {isPaused ? "Resume" : "Pause"}
        </button>
      </div>
      <div className="relative min-h-0">
        {/* aria-live is set by the effect above, so React never fights it. */}
        <div ref={scroller} role="log" aria-labelledby={headId} tabIndex={0} onScroll={onScroll} style={height}
          className={cn("overflow-auto overscroll-contain px-3 py-2 font-mono text-small text-fg tabular-nums")}>
          {shown.length === 0 ? (
            <p className="text-fg-muted">Nothing yet.</p>
          ) : (
            <ol className="flex flex-col gap-0.5">
              {shown.map((l) => {
                const d = delays.current?.get(l.id) ?? -1;
                const mark = l.level && l.level !== "info" ? levelMark[l.level] : null;
                return (
                  <li key={l.id} style={!reduced && d >= 0 ? { animationDelay: `calc(var(--stagger) * ${d})` } : undefined}
                    className={cn("flex gap-3 leading-5", !reduced && d >= 0 && "animate-blur-in")}>
                    {l.time && <span className="shrink-0 text-fg-subtle">{l.time}</span>}
                    {mark && (
                      <span className={cn("inline-flex shrink-0 items-center gap-1", mark.cls)}>
                        <mark.Icon width={12} height={12} />{mark.word}
                      </span>
                    )}
                    <span className={cn("min-w-0", wrap ? "whitespace-pre-wrap break-words" : "whitespace-pre")}>{l.text}</span>
                  </li>
                );
              })}
            </ol>
          )}
        </div>
        {isPaused && held > 0 ? (
          <div className="pointer-events-none absolute inset-x-0 bottom-3 z-10 flex justify-center">
            <button type="button" onClick={() => { setPause(false); toLatest(); }}
              className="pointer-events-auto inline-flex h-9 cursor-pointer items-center gap-2 rounded-control border border-line-strong bg-raised px-4 text-compact text-fg shadow-md hover:bg-hover tap">
              <span className="tabular-nums">{held}</span> new {held === 1 ? "line" : "lines"}
            </button>
          </div>
        ) : (
          <JumpToLatest visible={!following && shown.length > 0} count={unseen} newLabel="New lines" onClick={toLatest} />
        )}
      </div>
      <p role="status" className="sr-only">{live ? summary : ""}</p>
    </div>
  );
}
```

## tattva/organisms/Markdown.tsx

```tsx
import { Fragment, type ReactNode } from "react";
import { CitationMarker } from "../atoms/CitationMarker";
import { CodeBlock } from "../molecules/CodeBlock";

/**
 * Small, dependency-free Markdown renderer for assistant output.
 * - Builds React elements only (no innerHTML), so model output cannot inject markup.
 * - Tolerates partial input: an unclosed code fence renders as code while streaming.
 * - Links are limited to http(s)/mailto. `[1]` becomes a CitationMarker.
 * - Lists nest up to 3 levels by leading-space indentation (2 or 4 spaces per level, or tabs). Each list is ordered or not by its own first item.
 * Supports: headings, paragraphs, bold, italic, inline code, links, lists, blockquotes, tables, hr, fenced code.
 */

const SAFE_URL = /^(https?:\/\/|mailto:)/i;

function inline(text: string, keyBase = ""): ReactNode[] {
  const out: ReactNode[] = [];
  const re = /(`[^`]+`)|(\*\*[^*]+\*\*)|(\*[^*\s][^*]*\*)|(\[([^\]]+)\]\(([^)\s]+)\))|(\[(\d{1,2})\])/g;
  let last = 0, m: RegExpExecArray | null, i = 0;
  while ((m = re.exec(text))) {
    if (m.index > last) out.push(text.slice(last, m.index));
    const k = `${keyBase}${i++}`;
    if (m[1]) out.push(<code key={k} className="rounded bg-sunken px-1 py-0.5 font-mono text-[0.9em]">{m[1].slice(1, -1)}</code>);
    else if (m[2]) out.push(<strong key={k} className="font-semibold">{inline(m[2].slice(2, -2), k)}</strong>);
    else if (m[3]) out.push(<em key={k}>{inline(m[3].slice(1, -1), k)}</em>);
    else if (m[4]) {
      out.push(SAFE_URL.test(m[6])
        ? <a key={k} href={m[6]} target="_blank" rel="noreferrer noopener" className="text-accent-fg underline underline-offset-2 hover:text-accent">{m[5]}</a>
        : <Fragment key={k}>{m[5]}</Fragment>);
    } else if (m[7]) out.push(<CitationMarker key={k} n={Number(m[8])} />);
    last = m.index + m[0].length;
  }
  if (last < text.length) out.push(text.slice(last));
  return out;
}

const isTableSep = (l?: string) => !!l && /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$/.test(l);
const cells = (l: string) => l.trim().replace(/^\||\|$/g, "").split("|").map((c) => c.trim());

const LIST_RE = /^([ \t]*)([-*+]|\d+\.)\s+(.*)$/;
const MAX_DEPTH = 3;
interface ListItem { depth: number; ordered: boolean; text: string }

/** Turn consecutive list lines into items with a depth of 0 to 2, from leading whitespace. Tabs count as 4 spaces. */
function listItems(raw: string[]): ListItem[] {
  const stack: number[] = [];
  return raw.map((line) => {
    const m = line.match(LIST_RE)!;
    const indent = m[1].replace(/\t/g, "    ").length;
    while (stack.length && indent < stack[stack.length - 1]) stack.pop();
    if (!stack.length || indent > stack[stack.length - 1]) { if (stack.length < MAX_DEPTH) stack.push(indent); }
    return { depth: stack.length - 1, ordered: /\d/.test(m[2]), text: m[3] };
  });
}

/** Render items from `from` as one list at `depth`. The list is ordered or not by its first item and ends when a sibling of the other type starts. */
function renderList(items: ListItem[], from: number, depth: number, key: string | number): [ReactNode, number] {
  const ordered = items[from].ordered;
  const Tag = ordered ? "ol" : "ul";
  const lis: ReactNode[] = [];
  let i = from;
  while (i < items.length && items[i].depth >= depth) {
    const it = items[i];
    if (it.depth === depth && it.ordered !== ordered) break;
    if (it.depth > depth) break;
    i++;
    let nested: ReactNode = null;
    if (i < items.length && items[i].depth > depth) {
      const nestedNodes: ReactNode[] = [];
      while (i < items.length && items[i].depth > depth) {
        const [node, next] = renderList(items, i, depth + 1, nestedNodes.length);
        nestedNodes.push(node); i = next;
      }
      nested = nestedNodes;
    }
    lis.push(<li key={lis.length}>{inline(it.text)}{nested}</li>);
  }
  return [<Tag key={key} className={`space-y-1 pl-5 ${depth > 0 ? "mt-1 " : ""}${ordered ? "list-decimal" : "list-disc"} marker:text-fg-subtle`}>{lis}</Tag>, i];
}

export function Markdown({ children }: { children: string }) {
  const lines = children.replace(/\r\n/g, "\n").split("\n");
  const blocks: ReactNode[] = [];
  let i = 0, k = 0;

  while (i < lines.length) {
    const line = lines[i];
    if (!line.trim()) { i++; continue; }

    const fence = line.match(/^```\s*([\w+-]*)/);
    if (fence) {
      const buf: string[] = []; i++;
      while (i < lines.length && !lines[i].startsWith("```")) buf.push(lines[i++]);
      i++; // closing fence (may be missing while streaming)
      blocks.push(<CodeBlock key={k++} language={fence[1] || "text"} code={buf.join("\n")} />);
      continue;
    }
    const h = line.match(/^(#{1,4})\s+(.*)$/);
    if (h) {
      const size = ["text-title-xs leading-7", "text-subhead leading-7", "text-lead leading-6", "text-body-lg"][h[1].length - 1];
      blocks.push(<p key={k++} role="heading" aria-level={h[1].length + 1} className={`${size} mt-2 font-semibold tracking-tight`}>{inline(h[2])}</p>);
      i++; continue;
    }
    if (/^\s*([-*_])\1{2,}\s*$/.test(line)) { blocks.push(<hr key={k++} className="border-line" />); i++; continue; }
    if (line.startsWith(">")) {
      const buf: string[] = [];
      while (i < lines.length && lines[i].startsWith(">")) buf.push(lines[i++].replace(/^>\s?/, ""));
      blocks.push(<blockquote key={k++} className="border-l-2 border-accent-line pl-4 text-fg-muted">{inline(buf.join(" "))}</blockquote>);
      continue;
    }
    if (line.includes("|") && isTableSep(lines[i + 1])) {
      const head = cells(line); i += 2;
      const rows: string[][] = [];
      while (i < lines.length && lines[i].includes("|") && lines[i].trim()) rows.push(cells(lines[i++]));
      blocks.push(
        <div key={k++} tabIndex={0} className="overflow-x-auto rounded-card border border-line">
          <table className="w-full text-left text-body leading-5">
            <thead className="bg-sunken text-fg-muted"><tr>{head.map((c, j) => <th key={j} scope="col" className="px-3 py-2 font-medium">{inline(c)}</th>)}</tr></thead>
            <tbody>{rows.map((r, a) => <tr key={a} className="border-t border-line">{r.map((c, j) => <td key={j} className="px-3 py-2">{inline(c)}</td>)}</tr>)}</tbody>
          </table>
        </div>,
      );
      continue;
    }
    if (LIST_RE.test(line)) {
      const raw: string[] = [];
      while (i < lines.length && LIST_RE.test(lines[i])) raw.push(lines[i++]);
      const items = listItems(raw);
      let at = 0;
      while (at < items.length) {
        // A list that starts indented is still a top-level list, so shift everything up to depth 0.
        const [node, next] = renderList(items, at, 0, k++);
        blocks.push(node); at = next;
      }
      continue;
    }
    const buf: string[] = [];
    while (i < lines.length && lines[i].trim() && !/^(```|#{1,4}\s|>|[ \t]*([-*+]|\d+\.)\s)/.test(lines[i]) && !(lines[i].includes("|") && isTableSep(lines[i + 1]))) buf.push(lines[i++]);
    blocks.push(<p key={k++} className="[overflow-wrap:anywhere]">{inline(buf.join(" "))}</p>);
  }
  return <>{blocks}</>;
}
```

## tattva/organisms/MediaGrid.tsx

```tsx
import { useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { CheckIcon } from "../lib/icons";
import { EmptyState } from "../molecules/EmptyState";
import { MediaFrame, type MediaFrameProps, type MediaRatio } from "../molecules/MediaFrame";

export interface MediaGridItem extends Omit<MediaFrameProps, "ratio" | "className"> {
  id: string;
}

export interface MediaGridProps {
  items: MediaGridItem[];
  /** Maximum columns. The grid uses 2 on small screens, 3 from the md breakpoint and 4 from xl, capped by this value. */
  columns?: 2 | 3 | 4;
  /** Ratio shared by every tile, so rows stay even. */
  ratio?: MediaRatio;
  /** Lets people pick tiles with a checkbox button on each one. */
  selectable?: boolean;
  /** Controlled selection, as item ids. */
  selected?: string[];
  onSelectedChange?: (ids: string[]) => void;
  /** Accessible name of the list. */
  label: string;
  /** Replaces the default empty state. */
  empty?: ReactNode;
  /** Builds the count line. Defaults to "N images". */
  countLabel?: (count: number, selected: number) => string;
  className?: string;
}

const cols: Record<2 | 3 | 4, string> = {
  2: "grid-cols-2",
  3: "grid-cols-2 md:grid-cols-3",
  4: "grid-cols-2 md:grid-cols-3 xl:grid-cols-4",
};

const defaultCount = (n: number, s: number) => `${n} ${n === 1 ? "image" : "images"}${s ? `, ${s} selected` : ""}`;

/**
 * Responsive gallery of MediaFrames with one gap and one ratio. Selection is optional and every control is a native button,
 * so it works with Tab, Enter and Space. When there are no items it shows an EmptyState.
 */
export function MediaGrid({
  items, columns = 4, ratio = "4:3", selectable, selected, onSelectedChange, label, empty, countLabel = defaultCount, className,
}: MediaGridProps) {
  const [inner, setInner] = useState<string[]>([]);
  const picked = selected ?? inner;
  const toggle = (id: string) => {
    const next = picked.includes(id) ? picked.filter((x) => x !== id) : [...picked, id];
    if (selected === undefined) setInner(next);
    onSelectedChange?.(next);
  };

  if (items.length === 0) return <>{empty ?? <EmptyState variant="first-use" title="No images yet" className={className}>Add images and they will appear here.</EmptyState>}</>;

  return (
    <div className={className}>
      <p role="status" className="mb-3 text-small leading-4 text-fg-muted">{countLabel(items.length, picked.length)}</p>
      <ul aria-label={label} className={cn("m-0 grid list-none gap-3 p-0", cols[columns])}>
        {items.map((it) => {
          const { id, ...frame } = it;
          const on = picked.includes(id);
          return (
            <li key={id} className="relative min-w-0">
              <div className={cn("rounded-card", on && "ring-2 ring-fg ring-offset-2 ring-offset-bg")}>
                <MediaFrame {...frame} ratio={ratio} />
              </div>
              {selectable && (
                <button
                  type="button"
                  role="checkbox"
                  aria-checked={on}
                  aria-label={`Select ${frame.decorative ? `image ${id}` : frame.alt}`}
                  onClick={() => toggle(id)}
                  className={cn(
                    "absolute top-2 right-2 flex size-7 cursor-pointer items-center justify-center rounded-full border",
                    on ? "border-transparent bg-lime text-on-lime" : "border-line-strong bg-surface text-transparent hover:text-fg-subtle",
                  )}
                >
                  <CheckIcon width={14} height={14} />
                </button>
              )}
            </li>
          );
        })}
      </ul>
    </div>
  );
}
```

## tattva/organisms/MemoryList.tsx

```tsx
import { useRef, useState, type ReactNode } from "react";
import { useAnnounceChange, useFocusReturn } from "../lib/focus";
import { Badge } from "../atoms/Badge";
import { Button, IconButton } from "../atoms/Button";
import { Switch } from "../atoms/Switch";
import { cn } from "../lib/cn";

export interface MemoryEntry {
  id: string;
  /** Short topic heading, for example "Work". */
  topic?: string;
  /** What is remembered. */
  text: string;
  /** Where it applies, for example "All chats" or "This project". */
  scope: string;
}

export interface MemoryListProps {
  entries: MemoryEntry[];
  /** Called with the entry id and new text when an edit is saved. */
  onEdit?: (id: string, text: string) => void;
  /** Called with the entry id when delete is chosen. */
  onDelete?: (id: string) => void;
  /** Whether new memories are paused. Controlled when set. */
  paused?: boolean;
  defaultPaused?: boolean;
  onPausedChange?: (paused: boolean) => void;
  /** Whether past chats can be searched for context. Controlled when set. */
  searchChats?: boolean;
  defaultSearchChats?: boolean;
  onSearchChatsChange?: (on: boolean) => void;
  /** Replaces the empty state. */
  emptyState?: ReactNode;
  pauseLabel?: string;
  pauseDescription?: string;
  searchLabel?: string;
  searchDescription?: string;
  /** What screen readers hear after a memory is deleted. */
  deletedMessage?: string;
  className?: string;
}

/** Private inline glyphs, 24px grid, 1.5 stroke. */
const svg = { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round" as const, strokeLinejoin: "round" as const, "aria-hidden": true };
const PencilIcon = () => <svg {...svg}><path d="M4 20h4L19 9l-4-4L4 16v4zM13.5 6.5l4 4" /></svg>;
const TrashIcon = () => <svg {...svg}><path d="M4 7h16M10 11v6M14 11v6M6 7l1 13h10l1-13M9 7V4h6v3" /></svg>;

function useFlag(controlled: boolean | undefined, initial: boolean, notify?: (v: boolean) => void) {
  const [inner, setInner] = useState(initial);
  return [controlled ?? inner, (v: boolean) => { if (controlled === undefined) setInner(v); notify?.(v); }] as const;
}

/** Memory manager. Two controls on top, then one row per remembered topic with scope, edit and delete. */
export function MemoryList({ entries, onEdit, onDelete, paused, defaultPaused = false, onPausedChange, searchChats, defaultSearchChats = true, onSearchChatsChange, emptyState, pauseLabel = "Pause memory", pauseDescription = "Stop saving new memories. Existing ones stay.", searchLabel = "Search past chats", searchDescription = "Let replies draw on earlier conversations.", deletedMessage = "Memory deleted.", className }: MemoryListProps) {
  const [isPaused, setPaused] = useFlag(paused, defaultPaused, onPausedChange);
  const [isSearch, setSearch] = useFlag(searchChats, defaultSearchChats, onSearchChatsChange);
  const [editing, setEditing] = useState<string | null>(null);
  const [draft, setDraft] = useState("");
  // Focus never falls to the page: after Save or Cancel it returns to that memory's Edit button; after a delete it
  // goes to the next memory (or the previous one, or the empty message).
  const root = useRef<HTMLDivElement>(null);
  const empty = useRef<HTMLDivElement>(null);
  const editButtons = useRef(new Map<string, HTMLButtonElement>());
  const lastEdited = useRef<string | null>(null);
  const afterDelete = useRef<string | null>(null);
  useFocusReturn(editing, root, () => (lastEdited.current ? editButtons.current.get(lastEdited.current) : null));
  const ids = entries.map((e) => e.id).join("|");
  useFocusReturn(ids, root, () => (afterDelete.current ? editButtons.current.get(afterDelete.current) : null) ?? empty.current);
  const said = useAnnounceChange(entries.length, (now, before) => (now < before ? deletedMessage : ""));
  const remove = (id: string) => {
    const i = entries.findIndex((e) => e.id === id);
    afterDelete.current = (entries[i + 1] ?? entries[i - 1])?.id ?? null;
    onDelete?.(id);
  };

  return (
    <div ref={root} className={cn("space-y-4", className)}>
      <p role="status" aria-live="polite" className="sr-only">{said}</p>
      <div className="divide-y divide-line rounded-card border border-line bg-surface">
        {[{ l: pauseLabel, d: pauseDescription, v: isPaused, s: setPaused }, { l: searchLabel, d: searchDescription, v: isSearch, s: setSearch }].map((r) => (
          <div key={r.l} className="flex items-center justify-between gap-4 px-4 py-3">
            <div className="min-w-0"><p className="text-body leading-5 font-medium text-fg">{r.l}</p><p className="text-small leading-4 text-fg-muted">{r.d}</p></div>
            <Switch checked={r.v} onChange={r.s} label={r.l} />
          </div>
        ))}
      </div>

      {entries.length === 0 ? (
        <div ref={empty} tabIndex={-1} className="rounded-card border border-dashed border-line-strong px-4 py-8 text-center text-body leading-5 text-fg-muted">{emptyState ?? "Nothing is remembered yet. Memories you save will appear here."}</div>
      ) : (
        <ul className="divide-y divide-line rounded-card border border-line bg-surface">
          {entries.map((e) => (
            <li key={e.id} className="px-4 py-3">
              {editing === e.id ? (
                <form className="space-y-2" onSubmit={(ev) => { ev.preventDefault(); onEdit?.(e.id, draft.trim()); lastEdited.current = e.id; setEditing(null); }}>
                  <label className="block text-small leading-4 font-medium text-fg-muted" htmlFor={`mem-${e.id}`}>{e.topic ?? "Memory"}</label>
                  <textarea id={`mem-${e.id}`} autoFocus value={draft} onChange={(ev) => setDraft(ev.target.value)} rows={2}
                    className="w-full resize-none rounded-field border border-line-strong bg-surface px-3 py-2 text-body leading-5 text-fg" />
                  <div className="flex gap-2">
                    <Button size="sm" type="submit" disabled={!draft.trim()}>Save</Button>
                    <Button size="sm" variant="ghost" onClick={() => { lastEdited.current = e.id; setEditing(null); }}>Cancel</Button>
                  </div>
                </form>
              ) : (
                <div className="flex items-start gap-3">
                  <div className="min-w-0 flex-1">
                    <div className="mb-0.5 flex flex-wrap items-center gap-2">
                      {e.topic && <span className="text-small leading-4 font-medium text-fg-muted">{e.topic}</span>}
                      <Badge>{e.scope}</Badge>
                    </div>
                    <p className="text-body leading-5 text-fg">{e.text}</p>
                  </div>
                  <div className="flex shrink-0 gap-0.5">
                    <IconButton size="sm" ref={(n) => { if (n) editButtons.current.set(e.id, n); else editButtons.current.delete(e.id); }} label={`Edit memory: ${e.topic ?? e.text}`} onClick={() => { setDraft(e.text); setEditing(e.id); }}><PencilIcon /></IconButton>
                    <IconButton size="sm" label={`Delete memory: ${e.topic ?? e.text}`} onClick={() => remove(e.id)}><TrashIcon /></IconButton>
                  </div>
                </div>
              )}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}
```

## tattva/organisms/MessageList.tsx

```tsx
import type { ReactNode } from "react";

/**
 * Labelled log for a thread. New messages are announced politely. A streaming Message opts its own text out
 * of announcements and says "Response complete" once when it finishes.
 */
export function MessageList({ children, label = "Conversation" }: { children: ReactNode; label?: string }) {
  return <div role="log" aria-label={label} aria-live="polite" aria-relevant="additions" className="mx-auto flex w-full max-w-3xl flex-col gap-6">{children}</div>;
}
```

## tattva/organisms/MessageQueue.tsx

```tsx
import { useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { ChevronIcon, XIcon } from "../lib/icons";
import { Badge } from "../atoms/Badge";
import { Button, IconButton } from "../atoms/Button";
import { Kbd } from "../atoms/Kbd";

export interface QueuedMessage {
  id: string;
  text: string;
}

export interface MessageQueueProps {
  items: QueuedMessage[];
  /** Sends this message now instead of waiting for its turn. */
  onSendNow?: (id: string) => void;
  /** Moves a message one place earlier or later. The parent reorders `items`. */
  onMove?: (id: string, direction: "up" | "down") => void;
  onDelete?: (id: string) => void;
  title?: string;
  /** Explains queueing against steering. Replace it with your own product wording. */
  description?: ReactNode;
  emptyText?: string;
  /** Shortcut shown beside Send now on the first item, for example "Ctrl Enter". Omit to hide. */
  sendNowShortcut?: string;
  className?: string;
}

/** Messages the person sent while the assistant was working. They show in a pending style, and can be sent now, reordered or removed. */
export function MessageQueue({
  items, onSendNow, onMove, onDelete, title = "Queued", sendNowShortcut, className, emptyText = "Nothing queued. Messages you send while the assistant works will wait here.",
  description = "Queued messages send in order when the assistant finishes this step. To change what it is doing now, steer it instead.",
}: MessageQueueProps) {
  const [note, setNote] = useState("");
  const move = (id: string, i: number, dir: "up" | "down") => {
    setNote(`Message moved to position ${dir === "up" ? i : i + 2} of ${items.length}`);
    onMove?.(id, dir);
  };
  return (
    <section aria-label={title} className={cn("rounded-card border border-line bg-surface p-4", className)}>
      <header className="flex items-center gap-2">
        <h3 className="font-sans! text-compact font-medium text-fg">{title}</h3>
        <Badge>{items.length}</Badge>
      </header>
      <p className="mt-1 text-small leading-4 text-fg-muted">{description}</p>
      {items.length === 0 ? (
        <p className="mt-3 rounded-card bg-sunken px-3 py-4 text-center text-small leading-4 text-fg-muted">{emptyText}</p>
      ) : (
        <ol className="mt-3 space-y-2">
          {items.map((m, i) => (
            <li key={m.id} className="flex flex-wrap items-center gap-x-2 gap-y-1 rounded-card border border-dashed border-line-strong bg-sunken py-1.5 pr-1.5 pl-3">
              <span className="w-4 shrink-0 text-small leading-4 text-fg-subtle tabular-nums">{i + 1}</span>
              <span className="min-w-32 flex-1 truncate text-compact text-fg-muted" title={m.text}>{m.text}</span>
              <span className="ml-auto flex flex-wrap items-center justify-end gap-1">
              <Button size="sm" variant="secondary" onClick={() => onSendNow?.(m.id)} aria-label={`Send now: ${m.text}`}>
                Send now{i === 0 && sendNowShortcut && <Kbd>{sendNowShortcut}</Kbd>}
              </Button>
              <IconButton size="sm" label={`Move message ${i + 1} up`} disabled={i === 0} onClick={() => move(m.id, i, "up")}><ChevronIcon width={14} height={14} className="-rotate-90" /></IconButton>
              <IconButton size="sm" label={`Move message ${i + 1} down`} disabled={i === items.length - 1} onClick={() => move(m.id, i, "down")}><ChevronIcon width={14} height={14} className="rotate-90" /></IconButton>
              <IconButton size="sm" label={`Delete message ${i + 1}`} onClick={() => onDelete?.(m.id)}><XIcon width={14} height={14} /></IconButton>
              </span>
            </li>
          ))}
        </ol>
      )}
      <p role="status" className="sr-only">{note}</p>
    </section>
  );
}
```

## tattva/organisms/OptionChain.tsx

```tsx
import { useRef } from "react";
import type { KeyboardEvent } from "react";
import { cn } from "../lib/cn";

export interface ChainSide {
  ltp: number | null;
  oi?: number | null;
  oiChange?: number | null;
  iv?: number | null;
  delta?: number | null;
}
export type ChainColumn = "oi" | "oiChange" | "iv" | "delta" | "ltp";
export interface ChainRow { strike: number; call: ChainSide; put: ChainSide }
export interface ChainPick { side: "call" | "put"; strike: number; ltp: number }

export interface OptionChainProps {
  rows: ChainRow[];
  /** Current price of the underlying. The row with the nearest strike is marked. */
  spot: number;
  /** Columns on each side. Calls show them in this order; puts mirror it so Price is next to the strike on both sides. */
  columns?: ChainColumn[];
  /** Called when a price cell is pressed. When left out, prices are plain text. */
  onPick?: (pick: ChainPick) => void;
  selected?: { side: "call" | "put"; strike: number };
  /** Shown above the table, such as "Expiry 30 Oct". */
  expiryLabel?: string;
  /** Accessible name of the scrolling region. */
  ariaLabel: string;
  /** CSS height limit for the scrolling region, such as 420 or "60vh". The header stays in view. */
  maxHeight?: number | string;
  className?: string;
}

const COLS: Record<ChainColumn, string> = { oi: "OI", oiChange: "OI change", iv: "IV", delta: "Delta", ltp: "Price" };
const NONE = "—";

/** Lakh and crore for large counts, so 1,23,456 reads as 1.23 L. */
function compact(n: number): string {
  const a = Math.abs(n);
  if (a >= 1e7) return `${(n / 1e7).toFixed(2)} Cr`;
  if (a >= 1e5) return `${(n / 1e5).toFixed(2)} L`;
  return n.toLocaleString("en-IN");
}
function show(col: ChainColumn, v: number | null | undefined): string {
  if (v == null) return NONE;
  if (col === "oi") return compact(v);
  if (col === "oiChange") return `${v > 0 ? "+" : ""}${compact(v)}`;
  if (col === "iv") return `${v.toFixed(1)}%`;
  if (col === "delta") return v.toFixed(2);
  return v.toFixed(2);
}
const strikeText = (n: number) => n.toLocaleString("en-IN");

/**
 * Calls on the left, puts on the right, strike in the middle. A real table inside its own scroll region.
 * In-the-money cells get a quiet grey tint and a small diamond. Gain and loss colours are not used.
 */
export function OptionChain({ rows, spot, columns = ["oi", "iv", "ltp"], onPick, selected, expiryLabel, ariaLabel, maxHeight, className }: OptionChainProps) {
  const root = useRef<HTMLDivElement>(null);
  const cols = columns.length ? columns : (["ltp"] as ChainColumn[]);
  const putCols = [...cols].reverse();
  const atm = rows.reduce<number | null>((best, r) => (best === null || Math.abs(r.strike - spot) < Math.abs(best - spot) ? r.strike : best), null);

  // Up and down move between rows in the same column. Left, right and Tab keep their native behaviour.
  const onKey = (e: KeyboardEvent<HTMLButtonElement>, side: "call" | "put") => {
    if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
    const all = Array.from(root.current?.querySelectorAll<HTMLButtonElement>(`button[data-pick="${side}"]`) ?? []);
    const i = all.indexOf(e.currentTarget);
    const next = all[i + (e.key === "ArrowDown" ? 1 : -1)];
    if (next) { e.preventDefault(); next.focus(); }
  };

  const th = "sticky z-20 border-b border-line bg-sunken px-2 text-small leading-4 font-medium text-fg-muted whitespace-nowrap";
  const cell = (row: ChainRow, side: "call" | "put", col: ChainColumn, edge: boolean) => {
    const data = side === "call" ? row.call : row.put;
    const itm = side === "call" ? row.strike < spot : row.strike > spot;
    const isAtm = row.strike === atm;
    const v = data[col];
    const on = selected?.side === side && selected.strike === row.strike;
    return (
      <td key={`${side}-${col}`} className={cn("px-2 py-1.5 text-right text-small leading-4 text-fg tabular-nums whitespace-nowrap", isAtm ? "border-y border-fg" : "border-b border-line", itm && "bg-sunken")}>
        {itm && edge && (
          <>
            <span aria-hidden className="mr-1.5 inline-block size-1.5 rotate-45 border border-fg-muted align-middle forced-colors:border-[CanvasText]" />
            <span className="sr-only">In the money. </span>
          </>
        )}
        {col === "ltp" && onPick && v != null ? (
          <button type="button" data-pick={side} aria-pressed={on} aria-label={`${side === "call" ? "Call" : "Put"} ${strikeText(row.strike)}, price ${v.toFixed(2)}`}
            onClick={() => onPick({ side, strike: row.strike, ltp: v })} onKeyDown={(e) => onKey(e, side)}
            className={cn("-my-1 -mx-1 min-w-12 cursor-pointer rounded-md px-1.5 py-1 text-right text-small leading-4 tabular-nums transition-colors duration-[var(--dur-fast)] pointer-coarse:min-h-11 pointer-coarse:min-w-11",
              on ? "bg-accent font-medium text-on-accent" : "hover:bg-hover")}>
            {show(col, v)}
          </button>
        ) : show(col, v)}
      </td>
    );
  };

  return (
    <div className={cn("w-full min-w-0", className)}>
      <p className="mb-2 flex flex-wrap items-baseline justify-between gap-x-3 text-body leading-5 text-fg-muted">
          <span>{expiryLabel}</span>
          <span>Spot <span className="font-medium text-fg tabular-nums">{spot.toLocaleString("en-IN", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span></span>
      </p>
      <div ref={root} role="region" aria-label={ariaLabel} tabIndex={0} style={{ maxHeight }} className="relative overflow-auto rounded-card border border-line bg-surface">
        <table className="w-full min-w-max border-separate border-spacing-0">
          <caption className="sr-only">{`${ariaLabel}. Calls on the left, puts on the right, strike in the middle.${expiryLabel ? ` ${expiryLabel}.` : ""}`}</caption>
          <thead>
            <tr>
              <th scope="colgroup" colSpan={cols.length} className={cn(th, "top-0 h-8 text-center")}>Calls</th>
              <th scope="col" rowSpan={2} className={cn(th, "top-0 left-0 right-0 z-30 border-x border-line text-center")}>Strike</th>
              <th scope="colgroup" colSpan={cols.length} className={cn(th, "top-0 h-8 text-center")}>Puts</th>
            </tr>
            <tr>
              {cols.map((c) => <th key={`c-${c}`} scope="col" className={cn(th, "top-8 py-1 text-right")}>{COLS[c]}</th>)}
              {putCols.map((c) => <th key={`p-${c}`} scope="col" className={cn(th, "top-8 py-1 text-right")}>{COLS[c]}</th>)}
            </tr>
          </thead>
          <tbody>
            {rows.map((r) => {
              const isAtm = r.strike === atm;
              return (
                <tr key={r.strike}>
                  {cols.map((c, i) => cell(r, "call", c, i === cols.length - 1))}
                  <th scope="row" className={cn("sticky left-0 right-0 z-10 border-x border-line px-3 py-1.5 text-center text-small leading-4 font-semibold text-fg tabular-nums whitespace-nowrap", isAtm ? "border-y border-y-fg bg-sunken" : "border-b bg-surface")}>
                    {strikeText(r.strike)}
                    {isAtm && (
                      <>
                        <span aria-hidden className="ml-1.5 rounded-control border border-fg px-1.5 text-nano font-medium">ATM</span>
                        <span className="sr-only"> At the money</span>
                      </>
                    )}
                  </th>
                  {putCols.map((c, i) => cell(r, "put", c, i === 0))}
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}
```

## tattva/organisms/OrderTicket.tsx

```tsx
import { useId } from "react";
import type { FormEvent, ReactNode } from "react";
import { Button } from "../atoms/Button";
import { NumberField } from "../atoms/NumberField";
import { SegmentedControl } from "../atoms/SegmentedControl";
import { Spinner } from "../atoms/Spinner";
import { Field } from "../molecules/Field";
import { cn } from "../lib/cn";
import { AlertIcon, LockIcon } from "../lib/icons";

export interface OrderTicketValue {
  side: "buy" | "sell";
  quantity: number | null;
  orderType: "market" | "limit" | "stop";
  limitPrice: number | null;
  product: "intraday" | "delivery";
  stop: number | null;
  target: number | null;
}

export interface OrderTicketProps {
  symbol: string;
  /** Last traded price, shown at the top. Formatting only; this part does no money maths. */
  price?: number;
  value: OrderTicketValue;
  onChange: (value: OrderTicketValue) => void;
  /** Size of one lot. Quantity then moves in lots and must be a multiple of it. */
  lotSize?: number;
  /** Smallest price step for the price fields. */
  tickSize?: number;
  /** What the order will cost or risk, worked out by the app. Shown as given. */
  summary?: { label: string; value: ReactNode; hint?: string }[];
  /** Messages in words, one per field. */
  errors?: Partial<Record<keyof OrderTicketValue, string>>;
  /** Called when the person presses the submit button. This part never places an order itself. */
  onSubmit: () => void;
  /** Button text. Defaults to the side and symbol, such as "Buy RELIANCE". */
  submitLabel?: string;
  /** Shows a spinner in the button and blocks a second press. */
  submitting?: boolean;
  /** Locks every field and the button. */
  disabled?: boolean;
  /** Why it is locked, in words, next to the button. */
  disabledReason?: string;
  className?: string;
}

const fmt = (n: number) => n.toLocaleString("en-IN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });

/** A small labelled group for choices that are buttons, with the error in words under it. */
function Group({ label, error, children }: { label: string; error?: string; children: ReactNode }) {
  return (
    <div className="flex flex-col gap-1.5">
      <span className="text-body leading-5 font-medium text-fg">{label}</span>
      {children}
      <div aria-live="polite" className="empty:hidden">
        {error && <p className="flex items-start gap-1.5 text-small leading-4 text-danger-fg"><AlertIcon className="mt-px shrink-0" width={14} height={14} />{error}</p>}
      </div>
    </div>
  );
}

/**
 * A controlled order form. It collects the choices and hands them back; it never sends anything.
 * The submit button calls onSubmit and the app decides what happens next.
 */
export function OrderTicket({ symbol, price, value, onChange, lotSize, tickSize = 0.05, summary, errors = {}, onSubmit, submitLabel, submitting, disabled, disabledReason, className }: OrderTicketProps) {
  const uid = useId();
  const reasonId = `${uid}-reason`;
  const set = (patch: Partial<OrderTicketValue>) => onChange({ ...value, ...patch });
  const priced = value.orderType !== "market";
  const lotError = lotSize && value.quantity != null && value.quantity % lotSize !== 0 ? `Quantity must be a multiple of ${lotSize}.` : undefined;
  const label = submitLabel ?? `${value.side === "buy" ? "Buy" : "Sell"} ${symbol}`;
  const locked = !!disabled || !!submitting;
  const submit = (e: FormEvent) => { e.preventDefault(); if (!locked) onSubmit(); };

  return (
    <form onSubmit={submit} aria-label={`Order ticket for ${symbol}`} className={cn("flex w-full max-w-phone flex-col gap-4 rounded-card border border-line bg-surface p-4", className)}>
      <div className="flex items-baseline justify-between gap-3">
        <h3 className="text-lead leading-6 font-semibold text-fg">{symbol}</h3>
        {price != null && <p className="text-body leading-5 text-fg-muted">Last price <span className="font-medium text-fg tabular-nums">{fmt(price)}</span></p>}
      </div>
      <fieldset disabled={disabled} className="m-0 flex min-w-0 flex-col gap-4 border-0 p-0">
        <legend className="sr-only">Order details</legend>
        <Group label="Side" error={errors.side}>
          <SegmentedControl label="Side" value={value.side} onChange={(side) => set({ side })} options={[{ value: "buy", label: "Buy" }, { value: "sell", label: "Sell" }]} />
        </Group>
        <Field label="Quantity" error={errors.quantity ?? lotError} hint={lotSize ? `1 lot = ${lotSize}` : undefined}>
          {(a) => <NumberField {...a} label="Quantity" value={value.quantity} onChange={(quantity) => set({ quantity })} min={lotSize ?? 1} step={lotSize ?? 1} precision={0} />}
        </Field>
        <Group label="Order type" error={errors.orderType}>
          <SegmentedControl label="Order type" value={value.orderType} onChange={(orderType) => set({ orderType })} options={[{ value: "market", label: "Market" }, { value: "limit", label: "Limit" }, { value: "stop", label: "Stop" }]} />
        </Group>
        {priced && (
          <Field label={value.orderType === "stop" ? "Trigger price" : "Limit price"} error={errors.limitPrice}>
            {(a) => <NumberField {...a} label={value.orderType === "stop" ? "Trigger price" : "Limit price"} value={value.limitPrice} onChange={(limitPrice) => set({ limitPrice })} min={0} step={tickSize} precision={2} />}
          </Field>
        )}
        <Group label="Product" error={errors.product}>
          <SegmentedControl label="Product" value={value.product} onChange={(product) => set({ product })} options={[{ value: "intraday", label: "Intraday" }, { value: "delivery", label: "Delivery" }]} />
        </Group>
        <div className="grid grid-cols-2 gap-3">
          <Field label="Stop" optional error={errors.stop}>
            {(a) => <NumberField {...a} label="Stop" value={value.stop} onChange={(stop) => set({ stop })} min={0} step={tickSize} precision={2} />}
          </Field>
          <Field label="Target" optional error={errors.target}>
            {(a) => <NumberField {...a} label="Target" value={value.target} onChange={(target) => set({ target })} min={0} step={tickSize} precision={2} />}
          </Field>
        </div>
      </fieldset>
      {summary && summary.length > 0 && (
        <dl className="m-0 flex flex-col gap-2 rounded-card bg-sunken p-3 text-body leading-5">
          {summary.map((r) => (
            // A description group holds its term and details directly, as the HTML list rules require.
            <div key={r.label} className="flex flex-wrap items-baseline justify-between gap-x-3">
              <dt className="text-fg-muted">{r.label}</dt>
              <dd className="m-0 font-medium text-fg tabular-nums">{r.value}</dd>
              {r.hint && <dd className="m-0 w-full text-small leading-4 text-fg-muted">{r.hint}</dd>}
            </div>
          ))}
        </dl>
      )}
      <div className="flex flex-col gap-2">
        <Button type="submit" size="lg" className="w-full pointer-coarse:h-12" disabled={locked} aria-busy={submitting || undefined} aria-describedby={disabled && disabledReason ? reasonId : undefined}
          leading={submitting ? <Spinner size={16} label="Submitting" /> : undefined}>
          {label}
        </Button>
        {disabled && disabledReason && (
          <p id={reasonId} className="flex items-start gap-1.5 text-body leading-5 text-fg-muted"><LockIcon className="mt-0.5 shrink-0" width={14} height={14} />{disabledReason}</p>
        )}
      </div>
    </form>
  );
}
```

## tattva/organisms/OutlineEditor.tsx

```tsx
import { useEffect, useId, useLayoutEffect, useRef, useState, type KeyboardEvent, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { headingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { AlertIcon, CheckIcon, ChevronIcon, LockIcon, PlusIcon, SourceIcon, UndoIcon, XIcon } from "../lib/icons";
import { AIMark } from "../atoms/AIMark";
import { Badge, type Tone } from "../atoms/Badge";
import { Button, IconButton } from "../atoms/Button";
import { Reveal } from "../atoms/Reveal";
import { TextField } from "../atoms/TextField";
import { WorkingEdge } from "../atoms/WorkingEdge";
import { useReducedMotion } from "../hooks/useReducedMotion";

export type OutlineState = "drafting" | "ready" | "edited" | "updating" | "approved" | "building" | "failed";

export interface OutlineItem {
  id: string;
  /** The slide title. Required before building: screen-reader users move through a deck by its titles. */
  title: string;
  /** One line on what the slide says. */
  description?: string;
  /** Files or pages the AI used for this slide. */
  sources?: { id: string; label: string }[];
  /** Update outline leaves this slide as it is. The person sets it with the Lock button. */
  locked?: boolean;
  /** Who wrote it. "ai" shows the AI mark until the person edits it; "person" says Added by you. */
  origin?: "ai" | "person";
  /** The person changed an AI-drafted slide. Set by the part on edit. */
  edited?: boolean;
}

export interface OutlineEditorLabels {
  heading: string;
  build: string;
  update: string;
  retry: string;
  addEnd: string;
  titleError: string;
  failedNote: string;
}

export interface OutlineEditorProps {
  items: OutlineItem[];
  /** Where the outline is. Any edit while "ready" shows as "edited" until the state changes again. */
  state: OutlineState;
  /** The AI's short summary of the deck, shown above the slides. */
  overview?: ReactNode;
  /** id of the slide the AI is still writing while drafting. It stays read-only until it is complete. */
  pendingId?: string;
  /** Called with the new list after every edit, insert, remove, reorder or Keep toggle. */
  onItemsChange: (items: OutlineItem[]) => void;
  /** Told when an edit turns "ready" into "edited". */
  onStateChange?: (state: OutlineState) => void;
  /** Redraft the outline around the person's edits. Adds Update outline while edited, and Try again when failed. */
  onUpdate?: () => void;
  /** Approve the outline and build the slides. Only called when every slide has a title. */
  onBuild: () => void;
  /** Worked out by your app, never by the model: "12 slides, about 60 credits". Shown beside Build. */
  estimate?: ReactNode;
  /** Plain-language reason when drafting failed, such as "The connection dropped after slide 6." */
  error?: ReactNode;
  /** Most slides allowed. Add is turned off at the limit, with the reason in words. */
  maxItems?: number;
  /** Level of the part's own heading. Default 3. */
  headingLevel?: HeadingLevel;
  labels?: Partial<OutlineEditorLabels>;
  className?: string;
}

const BADGE: Record<OutlineState, { tone: Tone; text: string; ai?: boolean }> = {
  drafting: { tone: "accent", text: "AI is drafting", ai: true },
  ready: { tone: "neutral", text: "Ready to build" },
  edited: { tone: "info", text: "Edited by you" },
  updating: { tone: "accent", text: "AI is updating", ai: true },
  approved: { tone: "success", text: "Approved" },
  building: { tone: "accent", text: "Building slides", ai: true },
  failed: { tone: "danger", text: "Drafting stopped" },
};

const NOTE: Partial<Record<OutlineState, string>> = {
  drafting: "Slides appear as they are written. You can edit each one once it is complete.",
  edited: "Update outline redrafts the unlocked slides around your changes. Build slides uses the outline as it is.",
  updating: "Redrafting around your changes. Locked slides stay as they are.",
  approved: "Approved. The slides will be built from this outline.",
  building: "Building slides from this outline. It can no longer be changed here.",
};

let seq = 0;
const newId = () => `outline-new-${Date.now().toString(36)}-${seq++}`;

/**
 * The plan of a deck before any slide is built. The AI drafts it; the person edits, adds, removes, reorders and keeps
 * slides, then builds. Focus stays where it is when the outline appears, because it is not a blocking request.
 */
export function OutlineEditor({
  items, state, overview, pendingId, onItemsChange, onStateChange, onUpdate, onBuild, estimate, error, maxItems,
  headingLevel = 3, labels, className,
}: OutlineEditorProps) {
  const l: OutlineEditorLabels = {
    heading: "Outline", build: "Build slides", update: "Update outline", retry: "Try again", addEnd: "Add slide",
    titleError: "Every slide needs a title", failedNote: "The AI stopped before the outline was finished. You can keep what is here, edit it, or try again.",
    ...labels,
  };
  const uid = useId();
  const H = headingTag(headingLevel);
  const reduced = useReducedMotion();
  const listRef = useRef<HTMLOListElement>(null);
  const undoRef = useRef<HTMLButtonElement>(null);

  // An edit while "ready" shows as "edited" until the parent moves the state on.
  const [dirty, setDirty] = useState(false);
  useEffect(() => { setDirty(false); }, [state]);
  const shown: OutlineState = state === "ready" && dirty ? "edited" : state;

  const [announce, setAnnounce] = useState("");
  const say = (t: string) => setAnnounce((prev) => (prev === t ? `${t} ` : t));
  const [showErrors, setShowErrors] = useState(false);
  const [touched, setTouched] = useState<Set<string>>(() => new Set());
  const [open, setOpen] = useState<Set<string>>(() => new Set());
  const [removed, setRemoved] = useState<{ item: OutlineItem; index: number } | null>(null);
  const focusNext = useRef<{ id: string; part: "title" | "up" | "down" } | "undo" | null>(null);

  const locked = shown === "updating" || shown === "approved" || shown === "building";
  const canEdit = (it: OutlineItem) => !locked && it.id !== pendingId;
  const atMax = maxItems !== undefined && items.length >= maxItems;
  const missing = items.filter((it) => it.id !== pendingId && !it.title.trim());
  const canBuild = items.length > 0 && (shown === "ready" || shown === "edited" || shown === "failed");

  // Announce milestones only, never each streamed word.
  const prevState = useRef(state);
  useEffect(() => {
    const was = prevState.current;
    prevState.current = state;
    if (was === state) return;
    const n = `${items.length} ${items.length === 1 ? "slide" : "slides"}`;
    if (state === "ready" && was === "drafting") say(`Outline drafted, ${n}`);
    else if (state === "ready" && was === "updating") say(`Outline updated, ${n}`);
    else if (state === "updating") say("Updating outline");
    else if (state === "approved") say("Outline approved");
    else if (state === "building") say("Building slides");
    else if (state === "failed") say("Drafting stopped. What was written is kept.");
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [state]);

  const commit = (next: OutlineItem[]) => {
    onItemsChange(next);
    if (state === "ready" && !dirty) { setDirty(true); onStateChange?.("edited"); }
  };

  const patch = (id: string, change: Partial<OutlineItem>) =>
    commit(items.map((it) => (it.id === id ? { ...it, ...change, edited: it.origin === "ai" && ("title" in change || "description" in change) ? true : it.edited } : it)));

  const move = (i: number, by: -1 | 1, from: "up" | "down" | "row") => {
    const j = i + by;
    if (j < 0 || j >= items.length) return;
    const next = items.slice();
    const [it] = next.splice(i, 1);
    next.splice(j, 0, it);
    // Keep focus on the pressed button; once a row reaches an end its button turns off, so focus moves to the other one.
    const part = from === "row" ? null : j === 0 && from === "up" ? "down" : j === items.length - 1 && from === "down" ? "up" : from;
    if (part) focusNext.current = { id: it.id, part };
    commit(next);
    say(`Slide ${i + 1} moved to ${j + 1}`);
  };

  const insert = (at: number) => {
    if (atMax) return;
    const it: OutlineItem = { id: newId(), title: "", origin: "person" };
    const next = items.slice();
    next.splice(at, 0, it);
    focusNext.current = { id: it.id, part: "title" };
    commit(next);
    say(`New slide ${at + 1} added`);
  };

  const remove = (i: number) => {
    const it = items[i];
    setRemoved({ item: it, index: i });
    focusNext.current = "undo";
    commit(items.filter((x) => x.id !== it.id));
    say(`Slide ${i + 1} removed. Undo is available.`);
  };

  const undo = () => {
    if (!removed) return;
    const next = items.slice();
    const at = Math.min(removed.index, next.length);
    next.splice(at, 0, removed.item);
    focusNext.current = { id: removed.item.id, part: "title" };
    setRemoved(null);
    commit(next);
    say(`Slide ${at + 1} restored`);
  };

  const build = () => {
    if (missing.length) {
      setShowErrors(true);
      const first = listRef.current?.querySelector<HTMLInputElement>(`[data-title="${CSS.escape(missing[0].id)}"]`);
      first?.focus();
      say(missing.length === 1 ? "One slide needs a title" : `${missing.length} slides need a title`);
      return;
    }
    onBuild();
  };

  // Put focus where the last action said, after the list has re-rendered.
  useEffect(() => {
    const f = focusNext.current;
    if (!f) return;
    focusNext.current = null;
    if (f === "undo") { undoRef.current?.focus(); return; }
    const sel = f.part === "title" ? `[data-title="${CSS.escape(f.id)}"]` : `[data-move="${CSS.escape(f.id)}-${f.part}"]`;
    listRef.current?.querySelector<HTMLElement>(sel)?.focus();
  });

  // Reorder motion: rows slide from their old place to the new one (FLIP), quickly. None under reduced motion.
  const tops = useRef(new Map<string, number>());
  useLayoutEffect(() => {
    const rows = Array.from(listRef.current?.querySelectorAll<HTMLElement>("[data-row]") ?? []);
    const next = new Map<string, number>();
    for (const el of rows) {
      const id = el.dataset.row!;
      const top = el.offsetTop;
      next.set(id, top);
      const before = tops.current.get(id);
      if (reduced || before === undefined || before === top) continue;
      el.style.transition = "none";
      el.style.transform = `translateY(${before - top}px)`;
      void el.offsetHeight;
      el.style.transition = "transform var(--dur-fast) var(--ease-out)";
      el.style.transform = "";
    }
    tops.current = next;
  }, [items, reduced]);

  const onRowKey = (e: KeyboardEvent<HTMLLIElement>, i: number) => {
    if (!e.altKey || e.ctrlKey || e.metaKey || (e.key !== "ArrowUp" && e.key !== "ArrowDown")) return;
    e.preventDefault();
    const by = e.key === "ArrowUp" ? -1 : 1;
    const j = i + by;
    if (j < 0 || j >= items.length) return;
    // Keep focus on the same field or button inside the moved row.
    const active = document.activeElement as HTMLElement | null;
    const id = items[i].id;
    if (active?.dataset.title === id) focusNext.current = { id, part: "title" };
    move(i, by, "row");
    if (!focusNext.current && active) requestAnimationFrame(() => active.isConnected && document.activeElement !== active && active.focus());
  };

  const badge = BADGE[shown];
  const ai = shown === "drafting" || shown === "updating";
  const note = shown === "failed" ? null : NOTE[shown];
  const count = `${items.length} ${items.length === 1 ? "slide" : "slides"}`;

  return (
    <section aria-labelledby={`${uid}-h`} aria-busy={ai || undefined} className={cn("w-full max-w-read", className)}>
      <WorkingEdge active={ai} label={shown === "drafting" ? "AI is drafting the outline" : "AI is updating the outline"}>
        <div className="rounded-card border border-line bg-surface">
          <div className="p-4 sm:p-5">
            <div className="flex flex-wrap items-center gap-x-3 gap-y-2">
              <H id={`${uid}-h`} className="min-w-0 flex-1 basis-40 text-lead font-semibold">{l.heading}</H>
              <span className="text-small text-fg-muted tabular-nums">{count}</span>
              <Badge tone={badge.tone}>
                {badge.ai ? <AIMark size={16} /> : shown === "approved" ? <CheckIcon width={11} height={11} /> : shown === "failed" ? <AlertIcon width={11} height={11} /> : null}
                {badge.text}
              </Badge>
            </div>
            {note && <p className="mt-2 text-small text-fg-muted">{note}</p>}
            {shown === "failed" && (
              <div role="alert" className="mt-3 flex items-start gap-2 rounded-card border border-line bg-sunken p-3 text-compact text-fg">
                <AlertIcon aria-hidden width={16} height={16} className="mt-0.5 shrink-0 text-danger-fg" />
                <div className="min-w-0"><p>{l.failedNote}</p>{error && <p className="mt-1 text-fg-muted">{error}</p>}</div>
              </div>
            )}
            {overview && (
              <div className="mt-4 flex gap-2.5 rounded-card bg-sunken p-3">
                <span aria-hidden className="mt-0.5 inline-flex size-5 shrink-0 items-center justify-center rounded-full bg-lime text-on-lime"><AIMark size={16} /></span>
                <div className="min-w-0 text-compact text-fg"><p className="text-small leading-4 font-medium text-fg-muted">Overview, drafted by AI</p><div className="mt-0.5">{overview}</div></div>
              </div>
            )}
          </div>

          {items.length === 0 && shown === "drafting" && (
            <p className="border-t border-line px-4 py-6 text-compact text-fg-muted sm:px-5">The first slide will appear here in a moment.</p>
          )}
          {items.length === 0 && shown !== "drafting" && (
            <p className="border-t border-line px-4 py-6 text-compact text-fg-muted sm:px-5">No slides yet. Add one to start, or try drafting again.</p>
          )}

          <ol ref={listRef} aria-label={`${l.heading}, ${count}`} className="border-t border-line">
            {items.map((it, i) => {
              const editable = canEdit(it);
              const pending = it.id === pendingId;
              const empty = !it.title.trim();
              const err = !pending && empty && (showErrors || touched.has(it.id));
              const descOpen = open.has(it.id) || !!err;
              const n = i + 1;
              const name = it.title.trim() || "untitled";
              return (
                <Reveal as="li" key={it.id} data-row={it.id} aria-label={`Slide ${n}: ${name}`} onKeyDown={(e) => editable && onRowKey(e as KeyboardEvent<HTMLLIElement>, i)}
                  className="relative border-b border-line bg-surface px-4 py-3 last:border-b-0 sm:px-5">
                  <div className="grid grid-cols-[auto_minmax(0,1fr)] items-start gap-x-3">
                    <span aria-hidden className="mt-2 flex size-6 shrink-0 items-center justify-center rounded-full border border-line-strong bg-sunken text-small leading-4 tabular-nums text-fg-muted pointer-coarse:mt-2.5">{n}</span>
                    <div className="min-w-0 flex-1">
                      {editable ? (
                        <>
                          <TextField data-title={it.id} aria-label={`Slide ${n} title`} value={it.title} placeholder="Slide title" invalid={err}
                            aria-describedby={err ? `${uid}-${it.id}-err` : undefined}
                            onChange={(e) => patch(it.id, { title: e.target.value })}
                            onBlur={() => setTouched((t) => (t.has(it.id) ? t : new Set(t).add(it.id)))}
                            className="font-medium" />
                          {err && <p id={`${uid}-${it.id}-err`} className="mt-1 flex items-center gap-1 text-small text-danger-fg"><AlertIcon aria-hidden width={13} height={13} />{l.titleError}</p>}
                          <button type="button" aria-expanded={descOpen} onClick={() => setOpen((o) => { const s = new Set(o); if (s.has(it.id)) s.delete(it.id); else s.add(it.id); return s; })}
                            className="mt-1 inline-flex min-h-8 cursor-pointer items-center gap-1 rounded-control text-small text-fg-muted hover:text-fg sm:hidden tap">
                            <ChevronIcon aria-hidden width={12} height={12} className={cn("transition-transform duration-(--dur-fast)", descOpen && "rotate-90")} />
                            {it.description?.trim() ? "Description" : "Add a description"}
                          </button>
                          <TextField aria-label={`Slide ${n} description`} value={it.description ?? ""} placeholder="One line on what this slide says (optional)"
                            onChange={(e) => patch(it.id, { description: e.target.value })}
                            size="sm" className={cn("mt-1.5 text-compact text-fg-muted", !descOpen && "max-sm:hidden")} />
                        </>
                      ) : (
                        <div className="pt-1">
                          <p className={cn("text-body font-medium", !it.title && "text-fg-muted")}>{it.title || (pending ? "Writing the title" : "Untitled slide")}</p>
                          {it.description && <p className="mt-0.5 text-compact text-fg-muted">{it.description}</p>}
                        </div>
                      )}
                      {it.sources && it.sources.length > 0 && (
                        <ul aria-label={`Sources for slide ${n}`} className="mt-2 flex flex-wrap gap-1.5">
                          {it.sources.map((s) => (
                            <li key={s.id} className="inline-flex max-w-full items-center gap-1 rounded-control border border-line bg-sunken px-2 py-0.5 text-micro text-fg-muted">
                              <SourceIcon aria-hidden width={11} height={11} className="shrink-0" /><span className="truncate">{s.label}</span>
                            </li>
                          ))}
                        </ul>
                      )}
                    </div>
                    <div className="col-span-2 mt-2 flex flex-wrap items-center gap-x-2 gap-y-1.5 sm:col-span-1 sm:col-start-2">
                        {pending ? (
                          <span className="inline-flex items-center gap-1.5 text-small text-fg-muted">
                            <span aria-hidden className="inline-flex size-4 items-center justify-center rounded-full bg-lime text-on-lime"><AIMark size={16} /></span>
                            Still writing this slide
                          </span>
                        ) : it.origin === "ai" && !it.edited ? (
                          <span className="inline-flex items-center gap-1.5 text-small text-fg-muted">
                            <span aria-hidden className="inline-flex size-4 items-center justify-center rounded-full bg-lime text-on-lime"><AIMark size={16} /></span>
                            Drafted by AI
                          </span>
                        ) : it.origin === "ai" ? <Badge>Edited by you</Badge> : it.origin === "person" ? <Badge>Added by you</Badge> : null}
                        {it.locked && <Badge><LockIcon width={11} height={11} />Locked</Badge>}

                        {editable && (
                          <span className="ml-auto flex flex-wrap items-center justify-end gap-1 max-sm:w-full max-sm:justify-between">
                            <span className="flex items-center gap-1">
                            <IconButton size="sm" data-move={`${it.id}-up`} label={`Move slide ${n} up`} disabled={i === 0} onClick={() => move(i, -1, "up")} className="pointer-coarse:size-11">
                              <ChevronIcon width={14} height={14} className="-rotate-90" />
                            </IconButton>
                            <IconButton size="sm" data-move={`${it.id}-down`} label={`Move slide ${n} down`} disabled={i === items.length - 1} onClick={() => move(i, 1, "down")} className="pointer-coarse:size-11">
                              <ChevronIcon width={14} height={14} className="rotate-90" />
                            </IconButton>
                            <IconButton size="sm" label={`Add a slide after slide ${n}`} disabled={atMax} onClick={() => insert(i + 1)} className="pointer-coarse:size-11">
                              <PlusIcon width={14} height={14} />
                            </IconButton>
                            {onUpdate && (
                              <IconButton size="sm" label={`Lock slide ${n} so Update outline leaves it as it is`} aria-pressed={!!it.locked} active={!!it.locked}
                                onClick={() => { patch(it.id, { locked: !it.locked }); say(it.locked ? `Slide ${n} unlocked` : `Slide ${n} locked`); }}
                                className={cn("pointer-coarse:size-11", it.locked && "bg-hover text-fg")}>
                                <LockIcon width={14} height={14} />
                              </IconButton>
                            )}
                            </span>
                            <IconButton size="sm" label={`Remove slide ${n}: ${name}`} onClick={() => remove(i)} className="pointer-coarse:size-11">
                              <XIcon width={14} height={14} />
                            </IconButton>
                          </span>
                        )}
                      </div>
                  </div>
                </Reveal>
              );
            })}
          </ol>

          {removed && !locked && (
            <div className="flex flex-wrap items-center gap-2 border-t border-line bg-sunken px-4 py-2 text-compact sm:px-5">
              <span className="min-w-0 flex-1 text-fg-muted">Removed {removed.item.title.trim() ? `"${removed.item.title.trim()}"` : "an empty slide"}.</span>
              <Button ref={undoRef} size="sm" variant="secondary" leading={<UndoIcon width={14} height={14} />} onClick={undo} className="pointer-coarse:h-11">Undo remove</Button>
              <IconButton size="sm" label="Dismiss" onClick={() => setRemoved(null)} className="pointer-coarse:size-11"><XIcon width={14} height={14} /></IconButton>
            </div>
          )}

          {!locked && shown !== "drafting" && (
            <div className="border-t border-line px-4 py-3 sm:px-5">
              <Button size="sm" variant="ghost" leading={<PlusIcon width={14} height={14} />} disabled={atMax} onClick={() => insert(items.length)} className="pointer-coarse:h-11">{l.addEnd}</Button>
              {atMax && <span className="ml-2 text-small text-fg-muted">An outline can have up to {maxItems} slides.</span>}
            </div>
          )}

          {(canBuild || shown === "failed") && (
            <div className="sticky bottom-0 flex flex-wrap items-center gap-x-3 gap-y-2 rounded-b-card border-t border-line bg-surface px-4 py-3 pb-[max(--spacing(3),var(--safe-bottom))] sm:static sm:px-5">
              {showErrors && missing.length > 0 && (
                <p className="flex w-full items-center gap-1.5 text-small text-danger-fg">
                  <AlertIcon aria-hidden width={13} height={13} />
                  {missing.length === 1 ? `Slide ${items.indexOf(missing[0]) + 1} needs a title before you can build.` : `${missing.length} slides need a title before you can build.`}
                </p>
              )}
              {canBuild && <Button onClick={build} className="pointer-coarse:h-11 max-sm:flex-1">{l.build}</Button>}
              {onUpdate && (shown === "edited" || shown === "failed") && (
                <Button variant="secondary" onClick={onUpdate} className="pointer-coarse:h-11 max-sm:flex-1">{shown === "failed" ? l.retry : l.update}</Button>
              )}
              {estimate && <span className="text-small text-fg-muted max-sm:w-full">{estimate}</span>}
            </div>
          )}
        </div>
      </WorkingEdge>
      <p role="status" aria-live="polite" className="sr-only">{announce}</p>
    </section>
  );
}
```

## tattva/organisms/ParameterPanel.tsx

```tsx
import { useId, useRef, type KeyboardEvent } from "react";
import { cn } from "../lib/cn";
import { SparkleIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { SegmentedControl } from "../atoms/SegmentedControl";
import { Spinner } from "../atoms/Spinner";
import { Switch } from "../atoms/Switch";

/** A value held for one parameter. A seed is a number when fixed and null when left random. */
export type ParameterValue = string | number | boolean | null;
export type ParameterValues = Record<string, ParameterValue>;

export interface ParameterOption { value: string; label: string; description?: string }

interface BaseSpec { /** Key in the values object. */ id: string; /** Visible label. */ label: string; /** Short helper text under the label. */ hint?: string }

export type ParameterSpec =
  | (BaseSpec & { kind: "segmented"; options: ParameterOption[] })
  | (BaseSpec & { kind: "aspect"; options: ParameterOption[] })
  | (BaseSpec & { kind: "presets"; options: ParameterOption[] })
  | (BaseSpec & { kind: "number"; min?: number; max?: number; step?: number; unit?: string })
  | (BaseSpec & { kind: "seed"; /** Seed used when the person switches Fixed on. */ defaultSeed?: number; fixedLabel?: string });

export interface ParameterPanelProps {
  /** Schema that decides which controls render, in order. */
  parameters: ParameterSpec[];
  /** Current values keyed by parameter id. */
  values: ParameterValues;
  onChange: (values: ParameterValues) => void;
  /** Called with the current values when Generate is pressed. */
  onGenerate: (values: ParameterValues) => void;
  /** Returns the cost for the current values. Omit to hide the readout. */
  estimateCost?: (values: ParameterValues) => number;
  /** Formats the cost readout. */
  formatCost?: (cost: number) => string;
  title?: string;
  generateLabel?: string;
  busyLabel?: string;
  /** Disables Generate and shows a spinner. */
  busy?: boolean;
  className?: string;
}

function RatioGlyph({ ratio }: { ratio: string }) {
  const [w, h] = ratio.split(":").map(Number);
  if (!w || !h) return null;
  const scale = 12 / Math.max(w, h);
  return <span aria-hidden className="inline-block rounded-2xs border border-current" style={{ width: Math.max(4, w * scale), height: Math.max(4, h * scale) }} />;
}

/** Wrapping single-choice chips with roving tab stop and arrow keys. */
function ChipGroup({ options, value, onChange, label, glyph }: { options: ParameterOption[]; value: string; onChange: (v: string) => void; label: string; glyph?: boolean }) {
  const refs = useRef<(HTMLButtonElement | null)[]>([]);
  const move = (i: number, e: KeyboardEvent) => {
    const n = options.length;
    let next = i;
    if (e.key === "ArrowRight" || e.key === "ArrowDown") next = (i + 1) % n;
    else if (e.key === "ArrowLeft" || e.key === "ArrowUp") next = (i - 1 + n) % n;
    else if (e.key === "Home") next = 0;
    else if (e.key === "End") next = n - 1;
    else return;
    e.preventDefault();
    onChange(options[next].value);
    refs.current[next]?.focus();
  };
  const hasValue = options.some((o) => o.value === value);
  return (
    <div role="radiogroup" aria-label={label} className="flex flex-wrap gap-1.5">
      {options.map((o, i) => {
        const on = o.value === value;
        return (
          <button key={o.value} ref={(el) => { refs.current[i] = el; }} type="button" role="radio" aria-checked={on} title={o.description}
            tabIndex={on || (!hasValue && i === 0) ? 0 : -1} onClick={() => onChange(o.value)} onKeyDown={(e) => move(i, e)}
            className={cn("inline-flex cursor-pointer items-center gap-1.5 rounded-control border px-3 py-1 text-small leading-4 font-medium transition-colors",
              on ? "border-fg bg-surface text-fg shadow-sm" : "border-line text-fg-muted hover:bg-hover hover:text-fg")}>
            {glyph && <RatioGlyph ratio={o.label} />}{o.label}
          </button>
        );
      })}
    </div>
  );
}

function Field({ spec, children, labelId }: { spec: BaseSpec; children: React.ReactNode; labelId: string }) {
  return (
    <div className="space-y-1.5">
      <div>
        <span id={labelId} className="text-small leading-4 font-medium text-fg">{spec.label}</span>
        {spec.hint && <span className="ml-2 text-small leading-4 text-fg-subtle">{spec.hint}</span>}
      </div>
      {children}
    </div>
  );
}

/**
 * Generation settings driven by a schema. Values are controlled. A cost readout is computed
 * from the values by `estimateCost`, and Generate passes the values back to the caller.
 */
export function ParameterPanel({ parameters, values, onChange, onGenerate, estimateCost, formatCost = (c) => `About ${c} credits`, title = "Settings", generateLabel = "Generate", busyLabel = "Generating…", busy, className }: ParameterPanelProps) {
  const uid = useId();
  const set = (id: string, v: ParameterValue) => onChange({ ...values, [id]: v });
  const cost = estimateCost?.(values);

  return (
    <form onSubmit={(e) => { e.preventDefault(); if (!busy) onGenerate(values); }} aria-label={title}
      className={cn("space-y-4 rounded-card border border-line bg-surface p-4 shadow-sm", className)}>
      <h3 className="text-lead leading-6">{title}</h3>
      {parameters.map((p) => {
        const labelId = `${uid}-${p.id}`;
        const v = values[p.id];
        if (p.kind === "segmented") {
          return <Field key={p.id} spec={p} labelId={labelId}>
            <SegmentedControl label={p.label} options={p.options} value={String(v ?? p.options[0]?.value ?? "")} onChange={(x) => set(p.id, x)} />
          </Field>;
        }
        if (p.kind === "aspect" || p.kind === "presets") {
          return <Field key={p.id} spec={p} labelId={labelId}>
            <ChipGroup label={p.label} glyph={p.kind === "aspect"} options={p.options} value={String(v ?? "")} onChange={(x) => set(p.id, x)} />
          </Field>;
        }
        if (p.kind === "number") {
          return <Field key={p.id} spec={p} labelId={labelId}>
            <div className="inline-flex items-center gap-2 rounded-field border border-line bg-sunken px-3 py-1 focus-within:border-fg-subtle has-[input:focus-visible]:outline-2 has-[input:focus-visible]:outline-offset-2 has-[input:focus-visible]:outline-[var(--focus-ring)]">
              <input type="number" aria-labelledby={labelId} min={p.min} max={p.max} step={p.step} value={typeof v === "number" ? v : ""}
                onChange={(e) => { const n = e.target.valueAsNumber; if (!Number.isNaN(n)) set(p.id, n); }}
                className="w-16 bg-transparent text-body leading-5 outline-none" />
              {p.unit && <span className="text-small leading-4 text-fg-muted">{p.unit}</span>}
            </div>
          </Field>;
        }
        const fixed = typeof v === "number";
        return <Field key={p.id} spec={p} labelId={labelId}>
          <div className="flex flex-wrap items-center gap-3">
            <span className="inline-flex items-center gap-2 text-small leading-4 text-fg-muted">
              <Switch label={`${p.fixedLabel ?? "Fixed"} ${p.label.toLowerCase()}`} checked={fixed} onChange={(on) => set(p.id, on ? (p.defaultSeed ?? 1234) : null)} />
              {p.fixedLabel ?? "Fixed"}
            </span>
            {fixed
              ? <input type="number" aria-labelledby={labelId} value={v as number} onChange={(e) => { const n = e.target.valueAsNumber; if (!Number.isNaN(n)) set(p.id, n); }}
                  className="w-28 rounded-field border border-line bg-sunken px-3 py-1 text-body leading-5 focus:border-fg-subtle" />
              : <span className="text-small leading-4 text-fg-subtle">Random each time</span>}
          </div>
        </Field>;
      })}
      <div className="flex items-center justify-between gap-3 border-t border-line pt-3">
        <p role="status" aria-live="polite" className="text-small leading-4 text-fg-muted">{cost !== undefined ? formatCost(cost) : ""}</p>
        <Button type="submit" variant="lime" disabled={busy} leading={busy ? <Spinner size={14} /> : <SparkleIcon width={14} height={14} />}>{busy ? busyLabel : generateLabel}</Button>
      </div>
    </form>
  );
}
```

## tattva/organisms/PayoffChart.tsx

```tsx
import { useId, useRef, useState, type KeyboardEvent, type PointerEvent } from "react";
import { cn } from "../lib/cn";
import type { DataTableProps } from "../molecules/DataTable";
import { formatNumber, niceTicks, useElementWidth } from "../molecules/ChartFrame";

export interface PayoffPoint {
  /** Price of the underlying at the end. */
  spot: number;
  /** Profit (positive) or loss (negative) at that price. */
  pnl: number;
}

export interface PayoffLeg {
  kind: "call" | "put" | "stock";
  side: "buy" | "sell";
  /** Strike price for an option. For a stock leg, the price paid or received per share. */
  strike?: number;
  /** Price per unit paid (buy) or received (sell). Always positive. Defaults to 0. Not used by stock legs. */
  premium?: number;
  /** Number of units. */
  qty: number;
}

export interface PayoffChartProps {
  /** Ready-made points. Use this or legs. */
  points?: PayoffPoint[];
  /** Positions to compute the result at expiry from. Used when points is not given. */
  legs?: PayoffLeg[];
  /** Price range to draw when computing from legs. Defaults to a band around the strikes. */
  spotRange?: [number, number];
  /** Number of steps across the range when computing from legs. */
  steps?: number;
  /** Current price. Drawn as a labelled vertical marker when it is inside the range. */
  spot?: number;
  /** Formats prices and amounts. */
  formatValue?: (n: number) => string;
  /** Name for the chart region, for example "Bull call spread, result at expiry". */
  ariaLabel: string;
  /** Plot height in px. Minimum 180. */
  height?: number;
  className?: string;
}

/** Profit or loss of the legs at expiry for each price in spots. Premium is per unit and qty multiplies it. */
export function payoffAtExpiry(legs: PayoffLeg[], spots: number[]): number[] {
  return spots.map((s) => legs.reduce((sum, l) => {
    const sign = l.side === "buy" ? 1 : -1;
    const k = l.strike ?? 0;
    const prem = l.premium ?? 0;
    const intrinsic = l.kind === "call" ? Math.max(s - k, 0) : l.kind === "put" ? Math.max(k - s, 0) : s - k;
    return sum + sign * l.qty * (l.kind === "stock" ? intrinsic : intrinsic - prem);
  }, 0));
}

const clean = (n: number) => Math.round(n * 1e9) / 1e9;

/** Points from legs (with every strike included so the kinks are exact) or from the points given. */
function resolve(points: PayoffPoint[] | undefined, legs: PayoffLeg[] | undefined, range: [number, number] | undefined, steps: number): PayoffPoint[] {
  if (points && points.length) return [...points].sort((a, b) => a.spot - b.spot);
  if (!legs || !legs.length) return [];
  const ks = legs.map((l) => l.strike).filter((k): k is number => typeof k === "number");
  const kLo = ks.length ? Math.min(...ks) : 50;
  const kHi = ks.length ? Math.max(...ks) : 150;
  const span = kHi - kLo;
  const [a, b] = range ?? [Math.max(0, kLo - (span || kLo) * 0.5), kHi + (span || kLo) * 0.5];
  const n = Math.max(2, Math.floor(steps));
  const spots = Array.from({ length: n + 1 }, (_, i) => clean(a + ((b - a) * i) / n));
  ks.forEach((k) => { if (k > a && k < b) spots.push(k); });
  const sorted = [...new Set(spots)].sort((x, y) => x - y);
  const pnl = payoffAtExpiry(legs, sorted);
  return sorted.map((spot, i) => ({ spot, pnl: clean(pnl[i]) }));
}

function breakevens(p: PayoffPoint[]): number[] {
  const out: number[] = [];
  if (p.length && p[0].pnl === 0) out.push(p[0].spot);
  for (let i = 0; i < p.length - 1; i++) {
    const a = p[i].pnl, b = p[i + 1].pnl;
    if (a * b < 0) out.push(clean(p[i].spot + ((p[i + 1].spot - p[i].spot) * -a) / (b - a)));
    else if (b === 0 && a !== 0) out.push(p[i + 1].spot);
  }
  return out;
}

const verdict = (v: number, f: (n: number) => string) => (v > 0 ? `profit ${f(v)}` : v < 0 ? `loss ${f(-v)}` : "break even");

/**
 * Profit or loss at expiry as the price moves, drawn in SVG. The profit area is tinted, loss area is tinted and hatched.
 * Break-even prices and the best and worst outcomes are labelled. "Unlimited" is only said for the right edge,
 * because a price cannot fall below zero. The plot takes focus: Left and Right step through prices.
 */
export function PayoffChart({ points, legs, spotRange, steps = 120, spot, formatValue = formatNumber, ariaLabel, height = 260, className }: PayoffChartProps) {
  const [ref, measured] = useElementWidth<HTMLDivElement>();
  const svgRef = useRef<SVGSVGElement | null>(null);
  const wrapRef = useRef<HTMLDivElement | null>(null);
  const [active, setActive] = useState<number | null>(null);
  const [said, setSaid] = useState("");
  const uid = useId().replace(/:/g, "");

  const pts = resolve(points, legs, spotRange, steps);
  const n = pts.length;
  const w = measured || 640;
  const h = Math.max(180, height);

  const pMin = n ? Math.min(...pts.map((p) => p.pnl)) : 0;
  const pMax = n ? Math.max(...pts.map((p) => p.pnl)) : 1;
  const pad = (Math.max(pMax, 0) - Math.min(pMin, 0) || 1) * 0.12;
  const ticks = niceTicks(Math.min(pMin, 0) - (pMin < 0 ? pad : 0), Math.max(pMax, 0) + (pMax > 0 ? pad : 0));
  const lo = ticks[0];
  const hi = ticks[ticks.length - 1];
  const tickLabels = ticks.map((t) => formatValue(t));
  const left = Math.max(32, Math.max(...tickLabels.map((t) => t.length)) * 6.6 + 14);
  const right = 16;
  const pw = Math.max(40, w - left - right);
  const plotTop = 30;
  const plotBottom = h - 30;
  const py = (v: number) => plotBottom - ((v - lo) / (hi - lo || 1)) * (plotBottom - plotTop);
  const s0 = n ? pts[0].spot : 0;
  const s1 = n ? pts[n - 1].spot : 1;
  const px = (s: number) => left + ((s - s0) / (s1 - s0 || 1)) * pw;
  const zy = py(0);

  const line = pts.map((p, i) => `${i ? "L" : "M"}${px(p.spot).toFixed(1)} ${py(p.pnl).toFixed(1)}`).join("");
  const area = n ? `${line}L${px(s1).toFixed(1)} ${zy.toFixed(1)}L${px(s0).toFixed(1)} ${zy.toFixed(1)}Z` : "";

  const eps = (pMax - pMin || 1) * 1e-6;
  const slopeR = n > 1 ? (pts[n - 1].pnl - pts[n - 2].pnl) / (pts[n - 1].spot - pts[n - 2].spot || 1) : 0;
  const profitOpen = n > 1 && slopeR > 1e-9 && pts[n - 1].pnl >= pMax - eps;
  const lossOpen = n > 1 && slopeR < -1e-9 && pts[n - 1].pnl <= pMin + eps;
  const bes = breakevens(pts);

  const mid = (target: number) => {
    const idx = pts.map((p, i) => (Math.abs(p.pnl - target) <= eps ? i : -1)).filter((i) => i >= 0);
    return idx.length ? pts[idx[Math.floor(idx.length / 2)]] : pts[0];
  };

  /** Keep a label inside the drawing: start-, middle- or end-anchored depending on how close it is to an edge. */
  const place = (x: number, text: string) => {
    const half = (text.length * 6.4) / 2;
    return x - half < 2 ? { x: 2, a: "start" as const } : x + half > w - 2 ? { x: w - 2, a: "end" as const } : { x, a: "middle" as const };
  };
  const halo = { paintOrder: "stroke" as const, stroke: "var(--surface)", strokeWidth: 3, strokeLinejoin: "round" as const };

  const readout = (i: number) => `Price ${formatValue(pts[i].spot)}, ${verdict(pts[i].pnl, formatValue)}`;
  const step = (e: KeyboardEvent<HTMLDivElement>) => {
    if (!n) return;
    let i = active ?? Math.round((n - 1) / 2);
    if (e.key === "ArrowRight") i = Math.min(n - 1, i + 1);
    else if (e.key === "ArrowLeft") i = Math.max(0, i - 1);
    else if (e.key === "PageDown") i = Math.min(n - 1, i + 10);
    else if (e.key === "PageUp") i = Math.max(0, i - 10);
    else if (e.key === "Home") i = 0;
    else if (e.key === "End") i = n - 1;
    else if (e.key === "Escape") { setActive(null); setSaid(""); return; }
    else return;
    e.preventDefault();
    setActive(i);
    setSaid(readout(i));
  };
  const point = (e: PointerEvent<SVGRectElement>) => {
    const r = svgRef.current?.getBoundingClientRect();
    if (!r || !n) return;
    const s = s0 + ((e.clientX - r.left - left) / pw) * (s1 - s0);
    let best = 0, bd = Infinity;
    pts.forEach((p, i) => { const d = Math.abs(p.spot - s); if (d < bd) { bd = d; best = i; } });
    setActive(best);
  };

  const ai = active !== null && active < n ? active : -1;
  const ap = ai >= 0 ? pts[ai] : null;
  const tipRight = ap ? px(ap.spot) > w / 2 : false;
  const xTicks = n ? niceTicks(s0, s1, 6).filter((t) => t >= s0 - 1e-9 && t <= s1 + 1e-9) : [];
  const maxText = profitOpen ? "Max profit: Unlimited" : `Max profit ${formatValue(pMax)}`;
  const minText = lossOpen ? "Max loss: Unlimited" : `Max loss ${formatValue(-pMin)}`;
  const mp = n && pMax > 0 ? mid(pMax) : null;
  const lp = n && pMin < 0 ? mid(pMin) : null;
  const spotIn = typeof spot === "number" && n > 0 && spot >= s0 && spot <= s1;
  const spotText = spotIn ? `Price now ${formatValue(spot as number)}` : "";

  return (
    <div ref={ref} className={cn("relative min-w-0", className)}>
      <div ref={wrapRef} tabIndex={0} role="group" aria-roledescription="payoff chart"
        aria-label={`${ariaLabel}. ${n} prices. Use left and right arrow keys to step through prices.`}
        onKeyDown={step} onFocus={() => { if (n) setActive((p) => p ?? Math.round((n - 1) / 2)); }}
        onBlur={() => { setActive(null); setSaid(""); }}
        className="relative rounded-lg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--focus-ring)]">
        <svg ref={svgRef} width={w} height={h} viewBox={`0 0 ${w} ${h}`} className="block max-w-full touch-pan-y select-none" aria-hidden focusable="false"
          style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-small)" }}>
          <defs>
            <pattern id={`${uid}-hatch`} width={6} height={6} patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
              <line x1={0} y1={0} x2={0} y2={6} stroke="var(--down)" strokeWidth={1.25} strokeOpacity={0.55} />
            </pattern>
            <clipPath id={`${uid}-above`}><rect x={left} y={0} width={pw} height={Math.max(0, zy)} /></clipPath>
            <clipPath id={`${uid}-below`}><rect x={left} y={zy} width={pw} height={Math.max(0, h - zy)} /></clipPath>
          </defs>
          {ticks.map((t) => (
            <g key={t}>
              {t !== 0 && <line x1={left} x2={left + pw} y1={py(t)} y2={py(t)} stroke="var(--chart-grid)" strokeWidth={0.5} shapeRendering="crispEdges" />}
              <text x={left - 8} y={py(t)} textAnchor="end" dominantBaseline="central" fill="var(--fg-muted)" style={{ fontVariantNumeric: "tabular-nums" }}>{formatValue(t)}</text>
            </g>
          ))}
          {xTicks.map((t) => {
            const lab = formatValue(t);
            const p = place(px(t), lab);
            return <text key={t} x={p.x} y={h - 10} textAnchor={p.a} fill="var(--fg-muted)">{lab}</text>;
          })}
          {n > 0 && (
            <g>
              <path d={area} clipPath={`url(#${uid}-above)`} fill="var(--up-soft)" stroke="none" />
              <path d={area} clipPath={`url(#${uid}-below)`} fill="var(--down-soft)" stroke="none" />
              <path d={area} clipPath={`url(#${uid}-below)`} fill={`url(#${uid}-hatch)`} stroke="none" />
            </g>
          )}
          <line x1={left} x2={left + pw} y1={zy} y2={zy} stroke="var(--chart-axis)" strokeWidth={1} shapeRendering="crispEdges" />
          {spotIn && (
            <g>
              <line x1={px(spot as number)} x2={px(spot as number)} y1={plotTop - 6} y2={plotBottom} stroke="var(--fg-muted)" strokeWidth={1} strokeDasharray="3 3" shapeRendering="crispEdges" />
              <text x={place(px(spot as number), spotText).x} y={plotTop - 12} textAnchor={place(px(spot as number), spotText).a} fill="var(--fg)" style={{ fontWeight: 500 }}>{spotText}</text>
            </g>
          )}
          {n > 0 && <path d={line} fill="none" stroke="var(--chart-1)" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />}
          {bes.map((b) => {
            const lab = `Breakeven ${formatValue(b)}`;
            const p = place(px(b), lab);
            return (
              <g key={b}>
                <circle cx={px(b)} cy={zy} r={4} fill="var(--surface)" stroke="var(--fg)" strokeWidth={1.5} />
                <text x={p.x} y={zy + 18} textAnchor={p.a} fill="var(--fg)" style={halo}>{lab}</text>
              </g>
            );
          })}
          {mp && (() => { const p = place(px(mp.spot), maxText); return <text x={p.x} y={py(mp.pnl) - 8} textAnchor={p.a} fill="var(--up-fg)" style={{ ...halo, fontWeight: 500 }}>{maxText}</text>; })()}
          {lp && (() => { const p = place(px(lp.spot), minText); return <text x={p.x} y={py(lp.pnl) + 16} textAnchor={p.a} fill="var(--down-fg)" style={{ ...halo, fontWeight: 500 }}>{minText}</text>; })()}
          {ap && (
            <g>
              <line x1={px(ap.spot)} x2={px(ap.spot)} y1={plotTop} y2={plotBottom} stroke="var(--chart-axis)" strokeWidth={1} shapeRendering="crispEdges" />
              <circle cx={px(ap.spot)} cy={py(ap.pnl)} r={4.5} fill="var(--surface)" stroke="var(--chart-1)" strokeWidth={2} />
            </g>
          )}
          <rect x={left} y={0} width={pw + right} height={h - 24} fill="transparent" onPointerMove={point} onPointerDown={point}
            onPointerLeave={(e) => { if (e.pointerType === "mouse" && document.activeElement !== wrapRef.current) setActive(null); }} />
        </svg>
        {ap && (
          <div aria-hidden className="pointer-events-none absolute z-10 min-w-36 max-w-64 rounded-overlay border border-line bg-surface px-3 py-2 text-small leading-4 shadow-md"
            style={{ top: plotTop, left: px(ap.spot), transform: `translateX(${tipRight ? "calc(-100% - 12px)" : "12px"})` }}>
            <p className="mb-1 text-fg-muted">Price {formatValue(ap.spot)}</p>
            <p className={cn("font-semibold [font-variant-numeric:tabular-nums]", ap.pnl > 0 ? "text-up-fg" : ap.pnl < 0 ? "text-down-fg" : "text-fg")}>
              {ap.pnl > 0 ? `Profit ${formatValue(ap.pnl)}` : ap.pnl < 0 ? `Loss ${formatValue(-ap.pnl)}` : "Break even"}
            </p>
          </div>
        )}
      </div>
      <div className="sr-only" aria-live="polite" role="status">{said}</div>
    </div>
  );
}

/** Table data for ChartFrame's table view. The Result column says profit, loss or break even in words. */
export function payoffTable(input: { points?: PayoffPoint[]; legs?: PayoffLeg[]; spotRange?: [number, number]; steps?: number }, formatValue: (n: number) => string = formatNumber, caption = "Profit or loss at each price"): DataTableProps {
  const pts = resolve(input.points, input.legs, input.spotRange, input.steps ?? 20);
  return {
    caption,
    columns: [
      { key: "spot", header: "Price", numeric: true },
      { key: "result", header: "Result", numeric: true, direction: (r) => (String(r.result).startsWith("Profit") ? "up" : String(r.result).startsWith("Loss") ? "down" : "flat") },
    ],
    rows: pts.map((p) => ({ spot: formatValue(p.spot), result: p.pnl > 0 ? `Profit ${formatValue(p.pnl)}` : p.pnl < 0 ? `Loss ${formatValue(-p.pnl)}` : "Break even" })),
  };
}
```

## tattva/organisms/PermissionPrompt.tsx

```tsx
import { useEffect, useId, useRef, useState, type KeyboardEvent, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { useFocusAfter } from "../lib/useFocusAfter";
import { CheckIcon, ShieldIcon, XIcon } from "../lib/icons";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { Kbd } from "../atoms/Kbd";

export type PermissionChoice = "once" | "session" | "always" | "deny";
export type PermissionState = "pending" | "allowed-once" | "allowed-session" | "allowed-rule" | "denied";

export interface PermissionDecision {
  choice: PermissionChoice;
  /** The optional note the person added, trimmed. Empty when none. */
  note: string;
}

export interface PermissionPromptLabels {
  once?: string;
  /** Text of the session choice. */
  session?: string;
  /** Receives the rule scope, such as `npm test *`. */
  always?: (scope: string) => ReactNode;
  deny?: string;
  confirm?: string;
  addNote?: string;
  noteHint?: string;
  notePlaceholder?: string;
  scopeHeading?: string;
}

export interface PermissionPromptProps {
  /** Kind of action, such as "Bash command" or "Fetch a web page". Also the dialog name. */
  tool: string;
  /** Plain description of what the action does. */
  description: string;
  /** The literal command or target. Pass an array to list each subcommand of a compound command. */
  target: string | string[];
  /** Why the person is being asked. */
  reason?: string;
  /** Name of the subagent that asked, shown when the request comes from a background task. */
  requestedBy?: string;
  /** Scope of the rule saved by "Allow always", such as `npm test *`. Omit to offer only a one-time approval. */
  ruleScope?: string;
  /** What "Allow for this session" covers, such as `Emails to team@example.com`. Adds a middle choice that lasts until the session ends. Omit to hide it. */
  sessionScope?: string;
  /** Exactly what a saved rule covers, one entry per rule. Shown while the always option is selected. */
  ruleCovers?: string[];
  /** Where and how long a saved rule lasts, such as "Saved for this repository". */
  ruleLifetime?: string;
  /** Explains why only a one-time approval is offered. Shown when ruleScope is omitted. */
  onceOnlyReason?: string;
  /** State (controlled). */
  state?: PermissionState;
  onStateChange?: (state: PermissionState) => void;
  onDecide?: (decision: PermissionDecision) => void;
  /** Move focus to the options when the prompt mounts. Use only when the agent is blocked until the person answers. */
  autoFocus?: boolean;
  labels?: PermissionPromptLabels;
  className?: string;
}

const fromChoice: Record<PermissionChoice, PermissionState> = { once: "allowed-once", session: "allowed-session", always: "allowed-rule", deny: "denied" };

/**
 * Permission request for one tool action. It shows the literal command, a reason, and three choices:
 * allow once, allow always for a named scope, or deny. A saved rule lists exactly what it covers, and
 * the person can add a note to the decision. Unlike ApprovalPrompt it offers scoped persistence and amendment.
 * While focus is inside the prompt, Escape denies and N opens the note field. Tab keeps its normal focus meaning.
 */
export function PermissionPrompt({
  tool, description, target, reason = "This command requires approval", requestedBy, sessionScope, ruleScope, ruleCovers, ruleLifetime, onceOnlyReason,
  state, onStateChange, onDecide, autoFocus = false, labels, className,
}: PermissionPromptProps) {
  const l = {
    once: "Allow once", session: "Allow for this session", deny: "Deny", confirm: "Confirm", addNote: "Add a note",
    noteHint: "Optional. Your note is sent with this decision.", notePlaceholder: "For example: run it in the staging folder only",
    scopeHeading: "A saved rule covers", ...labels,
  };
  const [inner, setInner] = useState<PermissionState>("pending");
  const [choice, setChoice] = useState<PermissionChoice>("once");
  const [noteOpen, setNoteOpen] = useState(false);
  const [note, setNote] = useState("");
  const [decided, setDecided] = useState<PermissionDecision | null>(null);
  const current = state ?? inner;
  const uid = useId();
  const firstRadio = useRef<HTMLInputElement>(null);
  const noteInput = useRef<HTMLInputElement>(null);
  const focusNote = useRef(false);
  const targets = Array.isArray(target) ? target : [target];
  const resultBox = useRef<HTMLElement>(null);
  const byUs = useRef(false);
  // The options disappear once a choice is confirmed. Focus moves to the result, which names the decision.
  const arm = useFocusAfter(current, () => resultBox.current);
  useEffect(() => { if (current === "pending") byUs.current = false; }, [current]);

  useEffect(() => { if (autoFocus) firstRadio.current?.focus(); }, [autoFocus]);
  useEffect(() => { if (noteOpen && focusNote.current) { focusNote.current = false; noteInput.current?.focus(); } }, [noteOpen]);

  const options: { value: PermissionChoice; label: ReactNode }[] = [
    { value: "once", label: l.once },
    ...(sessionScope ? [{ value: "session" as const, label: l.session }] : []),
    ...(ruleScope ? [{ value: "always" as const, label: l.always ? l.always(ruleScope) : <>Allow always for <code className="font-mono text-compact">{ruleScope}</code></> }] : []),
    { value: "deny", label: l.deny },
  ];

  const submit = (picked: PermissionChoice = choice) => {
    const d = { choice: picked, note: note.trim() };
    byUs.current = true;
    arm();
    setDecided(d);
    const s = fromChoice[picked];
    if (state === undefined) setInner(s);
    onStateChange?.(s);
    onDecide?.(d);
  };

  /** Escape is the same as choosing Deny and confirming. N opens the note field unless the person is typing. */
  const onKeyDown = (e: KeyboardEvent<HTMLElement>) => {
    if (e.defaultPrevented || e.nativeEvent.isComposing || e.altKey || e.ctrlKey || e.metaKey) return;
    if (e.key === "Escape") { e.preventDefault(); setChoice("deny"); submit("deny"); return; }
    if (e.key.toLowerCase() === "n" && !e.shiftKey) {
      const t = e.target as HTMLElement;
      if (t.tagName === "TEXTAREA" || t.isContentEditable || (t.tagName === "INPUT" && (t as HTMLInputElement).type !== "radio")) return;
      e.preventDefault();
      focusNote.current = true;
      if (noteOpen) { focusNote.current = false; noteInput.current?.focus(); } else setNoteOpen(true);
    }
  };

  const resultText: Record<Exclude<PermissionState, "pending">, string> = {
    "allowed-once": "Allowed once",
    "allowed-session": "Allowed for this session",
    "allowed-rule": ruleScope ? `Allowed with a saved rule for ${ruleScope}` : "Allowed with a saved rule",
    denied: "Denied",
  };

  // One live region that stays mounted, so a decision made elsewhere is announced when its text arrives.
  // A decision made here is not announced twice: focus moves to the result instead.
  const live = <p role="status" aria-live="polite" className="sr-only">{current !== "pending" && !byUs.current ? resultText[current] : ""}</p>;

  if (current !== "pending") {
    const ok = current !== "denied";
    return (
      <>
        <section ref={resultBox} tabIndex={-1} aria-label={`${tool}: ${resultText[current]}`} className={cn("rounded-card border border-line bg-surface p-4", className)}>
          <div className="flex flex-wrap items-center gap-2">
            <Badge tone={ok ? "success" : "danger"}>{ok ? <CheckIcon width={11} height={11} /> : <XIcon width={11} height={11} />}{resultText[current]}</Badge>
            <span className="text-body leading-5 font-medium">{tool}</span>
          </div>
          <p className="mt-2 text-body leading-5 text-fg-muted">{description}</p>
          <pre tabIndex={0} className="mt-2 overflow-x-auto rounded-card bg-code p-3 font-mono text-small leading-5 text-code-fg">{targets.join("\n")}</pre>
          {decided?.note && <p className="mt-2 text-small leading-4 text-fg-muted">Your note: {decided.note}</p>}
        </section>
        {live}
      </>
    );
  }

  return (
    <>
    <section aria-label={`Permission needed: ${tool}`} onKeyDown={onKeyDown} className={cn("rounded-card border border-line bg-surface p-4", className)}>
      <div className="flex flex-wrap items-center gap-2">
        <ShieldIcon className="text-fg-muted" />
        <p className="font-sans text-body leading-5 font-medium">{tool}</p>
        {requestedBy && <Badge>From {requestedBy}</Badge>}
      </div>
      <p className="mt-1.5 text-body leading-5 text-fg-muted">{description}</p>
      <div className="mt-3 space-y-1.5">
        {targets.map((t, i) => (
          <pre key={i} tabIndex={0} className="overflow-x-auto rounded-card bg-code p-3 font-mono text-small leading-5 text-code-fg"><code>{t}</code></pre>
        ))}
      </div>
      <p className="mt-2 text-small leading-4 font-medium text-fg" id={`${uid}-reason`}>{reason}</p>

      <fieldset className="mt-3" aria-describedby={`${uid}-reason`}>
        <legend className="sr-only">Choose how to respond to this request</legend>
        <div className="space-y-1">
          {options.map((o, i) => (
            <label key={o.value} className={cn("flex cursor-pointer items-center gap-2.5 rounded-card border px-3 py-2 text-body leading-5 pointer-coarse:min-h-11 has-[:focus-visible]:outline has-[:focus-visible]:outline-(length:--focus-width)", choice === o.value ? "border-line-strong bg-sunken" : "border-transparent hover:bg-hover")}>
              <input ref={i === 0 ? firstRadio : undefined} type="radio" name={`${uid}-choice`} value={o.value} checked={choice === o.value}
                onChange={() => setChoice(o.value)} aria-keyshortcuts={o.value === "deny" ? "Escape" : undefined} className="size-4 shrink-0 accent-[var(--accent)]" />
              <span className="flex-1">{o.label}</span>
              {o.value === "deny" && <span aria-hidden><Kbd>Esc</Kbd></span>}
            </label>
          ))}
        </div>
        {!ruleScope && onceOnlyReason && <p className="mt-1.5 px-3 text-small leading-4 text-fg-subtle">{onceOnlyReason}</p>}
      </fieldset>

      {choice === "session" && sessionScope && (
        <div className="mt-2 rounded-card border border-line bg-sunken p-3" id={`${uid}-session`}>
          <p className="text-small leading-4 font-medium">Covers until this session ends</p>
          <p className="mt-1.5"><code className="rounded-md bg-surface px-1.5 py-0.5 font-mono text-small leading-4">{sessionScope}</code></p>
        </div>
      )}

      {choice === "always" && ruleScope && (
        <div className="mt-2 rounded-card border border-line bg-sunken p-3" id={`${uid}-scope`}>
          <p className="text-small leading-4 font-medium">{l.scopeHeading}{ruleLifetime ? `. ${ruleLifetime}` : ""}</p>
          <ul className="mt-1.5 space-y-1">
            {(ruleCovers?.length ? ruleCovers : [ruleScope]).map((c) => (
              <li key={c}><code className="rounded-md bg-surface px-1.5 py-0.5 font-mono text-small leading-4">{c}</code></li>
            ))}
          </ul>
        </div>
      )}

      <div className="mt-3">
        <button type="button" aria-expanded={noteOpen} aria-controls={`${uid}-note`} onClick={() => setNoteOpen(!noteOpen)} aria-keyshortcuts="N"
          className="inline-flex cursor-pointer items-center gap-1.5 rounded-control px-1 text-small leading-4 font-medium text-fg-muted hover:text-fg"><span className="underline underline-offset-2">{l.addNote}</span><span aria-hidden><Kbd>N</Kbd></span></button>
        {noteOpen && (
          <div id={`${uid}-note`} className="mt-1.5">
            <label className="sr-only" htmlFor={`${uid}-note-input`}>{l.addNote}</label>
            <input ref={noteInput} id={`${uid}-note-input`} value={note} onChange={(e) => setNote(e.target.value)} placeholder={l.notePlaceholder} aria-describedby={`${uid}-note-hint`}
              className="h-9 w-full rounded-field border border-line bg-surface px-4 text-body leading-5 placeholder:text-fg-subtle" />
            <p id={`${uid}-note-hint`} className="mt-1 px-3 text-small leading-4 text-fg-subtle">{l.noteHint}</p>
          </div>
        )}
      </div>

      <div className="mt-4">
        <Button onClick={() => submit()}>{l.confirm}</Button>
      </div>
    </section>
    {live}
    </>
  );
}
```

## tattva/organisms/PlanCard.tsx

```tsx
import { useId, useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { headingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { CheckIcon, InfoIcon, XIcon } from "../lib/icons";
import { Badge, type Tone } from "../atoms/Badge";
import { Button, IconButton } from "../atoms/Button";

export type PlanState = "draft" | "awaiting" | "approved" | "revised";

export interface PlanStep {
  id: string;
  title: string;
  /** One short sentence on what this step does. */
  description?: string;
}

export interface PlanCardLabels {
  approve?: string;
  revise?: string;
  editStep?: string;
  removeStep?: string;
  /** Shown while the plan is a draft. */
  draftNote?: string;
  approvedNote?: string;
  revisedNote?: string;
}

export interface PlanCardProps {
  title: string;
  steps: PlanStep[];
  /** Time or cost estimate, for example "About 6 minutes" or "Around 40 credits". */
  estimate?: ReactNode;
  /** State (controlled). Draft disables the actions, awaiting enables them. */
  state?: PlanState;
  /** Initial state when uncontrolled. */
  defaultState?: PlanState;
  onStateChange?: (state: PlanState) => void;
  onApprove?: () => void;
  onRevise?: () => void;
  /** Adds an Edit button to each step while the plan can still change. */
  onEditStep?: (id: string) => void;
  /** Adds a Remove button to each step while the plan can still change. */
  onRemoveStep?: (id: string) => void;
  /** Read-only badge text. Pass null to hide it. Hidden once approved. */
  readOnlyLabel?: string | null;
  labels?: PlanCardLabels;
  /** Level of the part's own heading, so it fits the page outline. Default 3. */
  headingLevel?: HeadingLevel;
  className?: string;
}

const stateBadge: Record<PlanState, { tone: Tone; text: string }> = {
  draft: { tone: "neutral", text: "Draft" },
  awaiting: { tone: "neutral", text: "Awaiting approval" },
  approved: { tone: "success", text: "Approved" },
  revised: { tone: "info", text: "Revision requested" },
};

/**
 * The plan, shown before any work starts. The person approves it or asks for a revision.
 * Focus stays where it is when the card appears, because a plan is not a blocking request.
 */
export function PlanCard({
  title, steps, estimate, state, defaultState = "awaiting", onStateChange, onApprove, onRevise, onEditStep, onRemoveStep,
  readOnlyLabel = "Nothing has changed yet", labels, headingLevel = 3, className,
}: PlanCardProps) {
  const l = {
    approve: "Approve", revise: "Revise", editStep: "Edit", removeStep: "Remove",
    draftNote: "The plan is still being written.", approvedNote: "Approved. The work can start.", revisedNote: "Revision requested. A new plan will replace this one.",
    ...labels,
  };
  const [inner, setInner] = useState<PlanState>(defaultState);
  const [announce, setAnnounce] = useState("");
  const current = state ?? inner;
  const set = (s: PlanState) => { if (state === undefined) setInner(s); onStateChange?.(s); setAnnounce(stateBadge[s].text); };
  const editable = current === "draft" || current === "awaiting";
  const badge = stateBadge[current];
  const note = current === "draft" ? l.draftNote : current === "approved" ? l.approvedNote : current === "revised" ? l.revisedNote : null;
  const headingId = `${useId()}-plan`;
  const H = headingTag(headingLevel);

  return (
    <section aria-labelledby={headingId} className={cn("rounded-card border border-line bg-surface p-5", className)}>
      <div className="flex flex-wrap items-center gap-2">
        <H id={headingId} className="min-w-0 flex-1 text-title-xs leading-7">{title}</H>
        <Badge tone={badge.tone}>{current === "approved" && <CheckIcon width={11} height={11} />}{badge.text}</Badge>
      </div>
      <div className="mt-2 flex flex-wrap items-center gap-2 text-small leading-4 text-fg-muted">
        {readOnlyLabel && current !== "approved" && <Badge><InfoIcon width={11} height={11} />{readOnlyLabel}</Badge>}
        {estimate && <span>{estimate}</span>}
      </div>
      <ol className="mt-4 space-y-3">
        {steps.map((s, i) => (
          <li key={s.id} className="flex items-start gap-3">
            <span aria-hidden className="flex size-6 shrink-0 items-center justify-center rounded-full border border-line-strong bg-sunken text-small leading-4 tabular-nums text-fg-muted">{i + 1}</span>
            <div className="min-w-0 flex-1 pt-0.5">
              <p className="text-body leading-5 font-medium">{s.title}</p>
              {s.description && <p className="text-small leading-4 text-fg-muted">{s.description}</p>}
            </div>
            {editable && (onEditStep || onRemoveStep) && (
              <div className="flex shrink-0 items-center gap-1">
                {onEditStep && <Button size="sm" variant="ghost" onClick={() => onEditStep(s.id)} aria-label={`${l.editStep} step ${i + 1}: ${s.title}`}>{l.editStep}</Button>}
                {onRemoveStep && <IconButton size="sm" label={`${l.removeStep} step ${i + 1}: ${s.title}`} onClick={() => onRemoveStep(s.id)}><XIcon width={14} height={14} /></IconButton>}
              </div>
            )}
          </li>
        ))}
      </ol>
      <div className="mt-5 flex flex-wrap items-center gap-2">
        {editable ? (
          <>
            <Button disabled={current === "draft"} onClick={() => { onApprove?.(); set("approved"); }}>{l.approve}</Button>
            <Button variant="secondary" disabled={current === "draft"} onClick={() => { onRevise?.(); set("revised"); }}>{l.revise}</Button>
          </>
        ) : null}
        {note && <p className="text-small leading-4 text-fg-muted">{note}</p>}
      </div>
      <p role="status" aria-live="polite" className="sr-only">{announce}</p>
    </section>
  );
}
```

## tattva/organisms/ProcessDiagram.tsx

```tsx
import { useEffect, useMemo, useRef, useState, type ReactNode } from "react";
import { DiagramFigure } from "../molecules/DiagramFigure";

export type ProcessNodeKind = "start" | "step" | "decision" | "end" | "attention";

export interface ProcessNode {
  id: string;
  label: string;
  /** Shape and meaning. start is a pill, step a rounded box, decision a hexagon, end a box with a double outline, attention a dashed box with a "!" marker. Default step. */
  kind?: ProcessNodeKind;
  /** One sentence shown under the diagram when the node is focused or clicked. */
  detail?: string;
}

export interface ProcessEdge {
  from: string;
  to: string;
  /** Short label such as "yes" or "no". */
  label?: string;
}

export interface ProcessDiagramProps {
  nodes: ProcessNode[];
  /** Arrows. Omit to connect the nodes in order. Arrows run between neighbouring nodes in the layout. */
  edges?: ProcessEdge[];
  /** Short alt for the figure. Name the diagram and say that the full description follows. */
  label: string;
  caption: string;
  /** Long description, visible to everyone. */
  description?: ReactNode;
  /** Override the generated textual definition shown by "View source". */
  source?: string;
}

const KIND_NAME: Record<ProcessNodeKind, string> = { start: "Start", step: "Step", decision: "Decision", end: "End", attention: "Needs a person" };
const W = 132, H = 56, GAP = 44, PAD = 4;

function lines(label: string, max = 16): string[] {
  const out: string[] = [];
  let cur = "";
  for (const w of label.split(" ")) {
    if (cur && (cur + " " + w).length > max) { out.push(cur); cur = w; } else cur = cur ? cur + " " + w : w;
  }
  if (cur) out.push(cur);
  return out.slice(0, 3);
}

const ids = (s: string) => s.replace(/[^A-Za-z0-9_]/g, "_");
function mermaid(nodes: ProcessNode[], edges: ProcessEdge[]) {
  const shape = (n: ProcessNode) => {
    const t = n.label.replace(/"/g, "'");
    switch (n.kind) { case "start": return `${ids(n.id)}([${t}])`; case "decision": return `${ids(n.id)}{{${t}}}`; case "end": return `${ids(n.id)}[[${t}]]`; case "attention": return `${ids(n.id)}[/${t}/]`; default: return `${ids(n.id)}[${t}]`; }
  };
  const byId = new Map(nodes.map((n) => [n.id, n]));
  return ["flowchart LR", ...edges.map((e) => `  ${shape(byId.get(e.from)!)} -->${e.label ? `|${e.label}|` : ""} ${shape(byId.get(e.to)!)}`)].join("\n");
}

/**
 * Flow of nodes and arrows drawn as SVG, left to right, switching to a vertical column when the width is too small.
 * Meaning is carried by shape and label. Nodes are keyboard focusable and describe themselves under the diagram.
 * It renders inside a DiagramFigure with a short aria-label, a visible long description, an ordered-list text alternative and a view-source toggle.
 */
export function ProcessDiagram({ nodes, edges, label, caption, description, source }: ProcessDiagramProps) {
  const wrap = useRef<HTMLDivElement>(null);
  const [width, setWidth] = useState(0);
  const [active, setActive] = useState<string | null>(null);

  useEffect(() => {
    const el = wrap.current;
    if (!el) return;
    setWidth(el.clientWidth);
    if (typeof ResizeObserver === "undefined") return;
    const ro = new ResizeObserver(() => setWidth(el.clientWidth));
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  const links = useMemo<ProcessEdge[]>(() => edges ?? nodes.slice(1).map((n, i) => ({ from: nodes[i].id, to: n.id })), [edges, nodes]);
  const horizontal = width === 0 || width >= nodes.length * W + (nodes.length - 1) * GAP + PAD * 2;
  const pos = nodes.map((_, i) => horizontal ? { x: PAD + i * (W + GAP), y: PAD } : { x: PAD, y: PAD + i * (H + GAP) });
  const vbW = horizontal ? PAD * 2 + nodes.length * W + (nodes.length - 1) * GAP : PAD * 2 + W;
  const vbH = horizontal ? PAD * 2 + H : PAD * 2 + nodes.length * H + (nodes.length - 1) * GAP;
  const index = new Map(nodes.map((n, i) => [n.id, i]));
  const act = nodes.find((n) => n.id === active);

  const shapeOf = (n: ProcessNode) => {
    const common = { strokeWidth: 1.5 } as const;
    const stroke = n.kind === "attention" ? "var(--attention)" : "var(--border-strong)";
    const fill = n.kind === "attention" ? "var(--attention-soft)" : n.kind === "end" ? "var(--lime)" : "var(--surface)";
    const on = active === n.id ? "var(--fg)" : stroke;
    switch (n.kind) {
      case "start": return <rect width={W} height={H} rx={H / 2} fill={fill} stroke={on} {...common} />;
      case "decision": return <polygon points={`16,0 ${W - 16},0 ${W},${H / 2} ${W - 16},${H} 16,${H} 0,${H / 2}`} fill={fill} stroke={on} {...common} />;
      case "end": return <><rect width={W} height={H} rx={12} fill={fill} stroke={on} {...common} /><rect x={4} y={4} width={W - 8} height={H - 8} rx={9} fill="none" stroke="var(--on-lime)" strokeWidth={1} /></>;
      case "attention": return <rect width={W} height={H} rx={12} fill={fill} stroke={on} strokeDasharray="5 3" {...common} />;
      default: return <rect width={W} height={H} rx={12} fill={fill} stroke={on} {...common} />;
    }
  };

  const svg = (
    <div ref={wrap} className="w-full">
      <svg role="list" aria-label={label} viewBox={`0 0 ${vbW} ${vbH}`} width={vbW} height={vbH} style={{ maxWidth: "100%", height: "auto", display: "block", margin: "0 auto" }}>
        <defs>
          <marker id="pd-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 1 L10 5 L0 9 z" fill="var(--fg-subtle)" /></marker>
        </defs>
        {links.map((e, i) => {
          const a = index.get(e.from), b = index.get(e.to);
          if (a === undefined || b === undefined) return null;
          const pa = pos[a], pb = pos[b];
          const [x1, y1, x2, y2] = horizontal ? [pa.x + W, pa.y + H / 2, pb.x, pb.y + H / 2] : [pa.x + W / 2, pa.y + H, pb.x + W / 2, pb.y];
          return (
            <g key={i} aria-hidden>
              <line x1={x1} y1={y1} x2={x2} y2={y2} stroke="var(--fg-subtle)" strokeWidth={1.5} markerEnd="url(#pd-arrow)" />
              {e.label && <text x={(x1 + x2) / 2} y={(y1 + y2) / 2 - 6} textAnchor="middle" fontSize={11} fill="var(--fg-muted)">{e.label}</text>}
            </g>
          );
        })}
        {nodes.map((n, i) => {
          const ls = lines(n.label);
          const kind = n.kind ?? "step";
          return (
            <g key={n.id} role="listitem" tabIndex={0} aria-label={`${i + 1}. ${KIND_NAME[kind]}: ${n.label}`} transform={`translate(${pos[i].x} ${pos[i].y})`}
              style={{ cursor: "pointer", outline: "none" }} onFocus={() => setActive(n.id)} onBlur={() => setActive((c) => (c === n.id ? null : c))} onClick={() => setActive(n.id)}>
              {shapeOf(n)}
              <text textAnchor="middle" fontSize={12} fill={kind === "end" ? "var(--on-lime)" : "var(--fg)"}>
                {ls.map((l, k) => <tspan key={k} x={W / 2} y={H / 2 + 4 + (k - (ls.length - 1) / 2) * 14}>{l}</tspan>)}
              </text>
              {kind === "attention" && <text x={W - 12} y={14} textAnchor="middle" fontSize={12} fontWeight={700} fill="var(--attention-fg)">!</text>}
            </g>
          );
        })}
      </svg>
    </div>
  );

  const alternative = (
    <>
      <p className="mb-1 text-micro font-bold uppercase tracking-caps text-fg-muted">Text alternative</p>
      <ol className="m-0 list-decimal space-y-1 pl-5">
        {nodes.map((n) => {
          const out = links.filter((e) => e.from === n.id).map((e) => `${e.label ? `${e.label}: ` : ""}${nodes.find((x) => x.id === e.to)?.label ?? e.to}`);
          return <li key={n.id}><span className="font-medium text-fg">{KIND_NAME[n.kind ?? "step"]}: {n.label}.</span>{n.detail ? ` ${n.detail}` : ""}{out.length > 0 ? ` Leads to ${out.join(", ")}.` : ""}</li>;
        })}
      </ol>
    </>
  );

  return (
    <DiagramFigure label={label} caption={caption} description={description} alternative={<>
      <div role="status" className="mb-3 min-h-6 text-compact text-fg">{act ? <><span className="font-medium">{KIND_NAME[act.kind ?? "step"]}: {act.label}.</span> <span className="text-fg-muted">{act.detail}</span></> : <span className="text-fg-subtle">Focus or select a node to read its detail.</span>}</div>
      <ul className="m-0 mb-3 flex list-none flex-wrap gap-x-4 gap-y-1 p-0 text-small text-fg-muted">
        {(["start", "step", "decision", "end", "attention"] as const).filter((k) => nodes.some((n) => (n.kind ?? "step") === k)).map((k) => <li key={k}>{{ start: "Pill", step: "Rounded box", decision: "Hexagon", end: "Double outline", attention: "Dashed box with !" }[k]} is {KIND_NAME[k].toLowerCase()}</li>)}
      </ul>
      {alternative}
    </>}
      source={source ?? mermaid(nodes, links)} sourceLanguage="mermaid">
      {svg}
    </DiagramFigure>
  );
}
```

## tattva/organisms/ProjectPanel.tsx

```tsx
import { useId, useRef, useState } from "react";
import { cn } from "../lib/cn";
import { headingTag, subHeadingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { EditIcon, FileIcon, NotebookIcon, PlusIcon, XIcon } from "../lib/icons";
import { Button, IconButton } from "../atoms/Button";
import { Meter } from "../atoms/Meter";

export interface ProjectFile {
  id: string;
  name: string;
  /** Size or length, such as "12 pages". */
  detail?: string;
}

export interface ProjectPanelLabels {
  instructions?: string;
  noInstructions?: string;
  editInstructions?: string;
  files?: string;
  noFiles?: string;
  addFiles?: string;
  /** Receives the file name. */
  remove?: (name: string) => string;
  /** Receives the file name. */
  removed?: (name: string) => string;
  /** Receives used and total, already formatted. */
  capacity?: (used: string, total: string) => string;
  capacityLabel?: string;
  /** Receives how many chats use the project. */
  chats?: (n: number) => string;
  showMore?: string;
  showLess?: string;
}

export interface ProjectPanelProps {
  /** The project's name. */
  name: string;
  /** One line about what the project is for. */
  description?: string;
  /** How the assistant should work in every chat of this project. */
  instructions?: string;
  onEditInstructions?: () => void;
  /** Files every chat in the project can read. */
  files: ProjectFile[];
  onAddFiles?: () => void;
  onRemoveFile?: (id: string) => void;
  /** How much of the project's knowledge room the files fill. */
  capacity?: { used: number; total: number };
  /** How many chats belong to the project. */
  chatCount?: number;
  labels?: ProjectPanelLabels;
  /** Level of the project name's heading, so it fits the page outline. Default 2. */
  headingLevel?: HeadingLevel;
  className?: string;
}

const fmt = (n: number) => n.toLocaleString("en-US");

/**
 * A project: chats that share the same instructions and files. The panel shows the project's name, its instructions
 * (folded after three lines), the files every chat can read, and how full the project's knowledge is. Taking a file
 * out moves focus to the next file, so a keyboard user never lands on nothing.
 */
export function ProjectPanel({ name, description, instructions, onEditInstructions, files, onAddFiles, onRemoveFile, capacity, chatCount, labels, headingLevel = 2, className }: ProjectPanelProps) {
  const l = {
    instructions: "Instructions", noInstructions: "No instructions yet. Tell the assistant how to work in this project.", editInstructions: "Edit instructions",
    files: "Files", noFiles: "No files yet. Every chat in this project can read the files you add here.", addFiles: "Add files",
    remove: (n: string) => `Remove ${n}`, removed: (n: string) => `${n} removed.`,
    capacity: (u: string, t: string) => `${u} of ${t} tokens used`, capacityLabel: "Project knowledge used",
    chats: (n: number) => (n === 1 ? "1 chat" : `${n} chats`), showMore: "Show all", showLess: "Show less", ...labels,
  };
  const [more, setMore] = useState(false);
  const uid = useId();
  const H = headingTag(headingLevel);
  const H2 = subHeadingTag(headingLevel);
  const list = useRef<HTMLUListElement>(null);
  const said = useRef<HTMLParagraphElement>(null);
  const long = (instructions?.length ?? 0) > 180;
  const remove = (f: ProjectFile) => {
    if (!onRemoveFile) return;
    const buttons = [...(list.current?.querySelectorAll<HTMLButtonElement>("[data-remove]") ?? [])];
    const at = buttons.findIndex((b) => b.dataset.remove === f.id);
    const next = buttons[at + 1] ?? buttons[at - 1];
    onRemoveFile(f.id);
    if (said.current) said.current.textContent = l.removed(f.name);
    requestAnimationFrame(() => (next && next.isConnected ? next : document.getElementById(`${uid}-files`))?.focus());
  };
  return (
    <section aria-labelledby={`${uid}-h`} className={cn("space-y-5 rounded-card border border-line bg-surface p-4 sm:p-5", className)}>
      <div>
        <div className="flex items-baseline gap-3">
          <H id={`${uid}-h`} className="m-0 min-w-0 flex-1 text-title-sm leading-7 font-semibold text-fg">{name}</H>
          {chatCount != null && <span className="shrink-0 text-small leading-5 text-fg-muted">{l.chats(chatCount)}</span>}
        </div>
        {description && <p className="m-0 mt-1 text-body leading-6 text-fg-muted">{description}</p>}
      </div>

      <div>
        <div className="flex items-center gap-2">
          <NotebookIcon aria-hidden width={16} height={16} className="shrink-0 text-fg-muted" />
          <H2 className="m-0 min-w-0 flex-1 text-body leading-5 font-semibold text-fg">{l.instructions}</H2>
          {onEditInstructions && <IconButton size="sm" label={l.editInstructions} onClick={onEditInstructions}><EditIcon width={14} height={14} /></IconButton>}
        </div>
        {instructions
          ? <>
              <p id={`${uid}-ins`} className={cn("m-0 mt-2 text-body leading-6 whitespace-pre-wrap text-fg", long && !more && "line-clamp-3")}>{instructions}</p>
              {long && <button type="button" aria-expanded={more} aria-controls={`${uid}-ins`} onClick={() => setMore(!more)} className="mt-1 cursor-pointer text-small leading-5 font-medium text-fg hover:text-accent transition-colors pointer-coarse:min-h-11">{more ? l.showLess : l.showMore}</button>}
            </>
          : <p className="m-0 mt-2 text-small leading-5 text-fg-muted">{l.noInstructions}</p>}
      </div>

      <div>
        <div className="flex items-center gap-2">
          <FileIcon aria-hidden width={16} height={16} className="shrink-0 text-fg-muted" />
          <H2 id={`${uid}-files`} tabIndex={-1} className="m-0 min-w-0 flex-1 text-body leading-5 font-semibold text-fg outline-none">{l.files}</H2>
          {onAddFiles && <Button size="sm" variant="ghost" onClick={onAddFiles} leading={<PlusIcon aria-hidden width={14} height={14} />}>{l.addFiles}</Button>}
        </div>
        {files.length === 0
          ? <p className="m-0 mt-2 text-small leading-5 text-fg-muted">{l.noFiles}</p>
          : (
            <ul ref={list} className="mt-2 space-y-1">
              {files.map((f) => (
                <li key={f.id} className="flex min-h-10 items-center gap-2.5 rounded-control px-2 py-1 hover:bg-hover">
                  <span className="min-w-0 flex-1">
                    <span className="block truncate text-body leading-5 text-fg" title={f.name}>{f.name}</span>
                    {f.detail && <span className="block text-small leading-4 text-fg-muted">{f.detail}</span>}
                  </span>
                  {onRemoveFile && <IconButton size="sm" label={l.remove(f.name)} data-remove={f.id} onClick={() => remove(f)}><XIcon width={14} height={14} /></IconButton>}
                </li>
              ))}
            </ul>
          )}
        {capacity && (
          <div className="mt-4">
            <p className="m-0 mb-1.5 text-small leading-4 text-fg-muted">{l.capacity(fmt(capacity.used), fmt(capacity.total))}</p>
            <Meter value={capacity.used} max={capacity.total} warnAt={0.9} label={l.capacityLabel} valueText={l.capacity(fmt(capacity.used), fmt(capacity.total))} />
          </div>
        )}
      </div>
      <p ref={said} role="status" className="sr-only" />
    </section>
  );
}
```

## tattva/organisms/PromptBox.tsx

```tsx
import { useId, useState } from "react";
import { useAnnounceChange } from "../lib/focus";
import { SparkleIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { Spinner } from "../atoms/Spinner";

/** Prompt field with a Generate button and example prompts. Screen readers hear when generating starts and ends. */
export function PromptBox({ onGenerate, busy, placeholder = "Describe what you want to create…", examples = [], busyMessage = "Generating", doneMessage = "Done. The results are ready." }: {
  onGenerate: (prompt: string) => void; busy?: boolean; placeholder?: string; examples?: string[];
  /** What screen readers hear when generating starts. */
  busyMessage?: string;
  /** What screen readers hear when it ends. Say where the results are if that helps, such as "4 images below". */
  doneMessage?: string;
}) {
  const [v, setV] = useState("");
  const said = useAnnounceChange(!!busy, (now) => (now ? busyMessage : doneMessage));
  const inputId = useId();
  return (
    <form onSubmit={(e) => { e.preventDefault(); if (v.trim() && !busy) onGenerate(v.trim()); }} className="space-y-2">
      <div className="rounded-field border border-line-strong bg-surface p-2.5 shadow-sm focus-within:border-fg-subtle has-[textarea:focus-visible]:outline-2 has-[textarea:focus-visible]:outline-offset-2 has-[textarea:focus-visible]:outline-[var(--focus-ring)]">
        <label htmlFor={inputId} className="sr-only">Prompt</label>
        <textarea id={inputId} rows={3} value={v} onChange={(e) => setV(e.target.value)} placeholder={placeholder}
          className="block w-full resize-none bg-transparent p-2 text-body-lg outline-none placeholder:text-fg-subtle [field-sizing:content]" />
        <div className="flex items-center justify-between px-1 pt-1">
          <span className="text-small leading-4 text-fg-subtle">{v.length} characters</span>
          <Button type="submit" variant="lime" size="sm" disabled={!v.trim() || busy} leading={busy ? <Spinner size={14} /> : <SparkleIcon width={14} height={14} />}>{busy ? "Generating…" : "Generate"}</Button>
        </div>
      </div>
      <p role="status" aria-live="polite" className="sr-only">{said}</p>
      {examples.length > 0 && (
        <p className="flex flex-wrap items-center gap-1.5 text-small leading-4 text-fg-muted">Try:
          {examples.map((x) => <button key={x} type="button" onClick={() => setV(x)} className="rounded-control border border-line px-2 py-0.5 hover:bg-hover">{x}</button>)}
        </p>
      )}
    </form>
  );
}
```

## tattva/organisms/PromptIndex.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import type { KeyboardEvent as ReactKeyboardEvent, PointerEvent as ReactPointerEvent } from "react";
import { cn } from "../lib/cn";
import { Sheet } from "./Sheet";

export interface PromptIndexItem {
  id: string;
  /** The person's message. Shown on one line; the full text stays in the button for screen readers. */
  text: string;
  /** When it was sent, already formatted, e.g. "10:42". */
  time?: string;
}

export interface PromptIndexProps {
  /** The person's own messages in this thread, oldest first. */
  items: PromptIndexItem[];
  /** The message in view. Marked with aria-current and a longer mark. */
  currentId?: string;
  /** Called with the id of the message to scroll to. Your code scrolls and moves focus to that message. */
  onJump: (id: string) => void;
  /** Name of the navigation, and the heading of the list. */
  label?: string;
  /** list: a plain list for a side panel or sheet. rail: thin marks on the right edge of a thread that open on hover, focus or tap. */
  variant?: "list" | "rail";
  className?: string;
}

const rowsIn = (root: HTMLElement | null) => Array.from(root?.querySelectorAll<HTMLButtonElement>("[data-pi-item]") ?? []);

/** Arrow keys, Home and End move focus between the items. Enter and Space press the focused one natively. */
function moveFocus(e: ReactKeyboardEvent<HTMLElement>) {
  const rows = rowsIn(e.currentTarget);
  const at = rows.indexOf(document.activeElement as HTMLButtonElement);
  if (at < 0) return;
  const next = { ArrowDown: at + 1, ArrowUp: at - 1, Home: 0, End: rows.length - 1 }[e.key];
  if (next === undefined) return;
  e.preventDefault();
  rows[Math.max(0, Math.min(rows.length - 1, next))]?.focus();
}

interface ListProps {
  items: PromptIndexItem[];
  currentId?: string;
  onPick: (id: string) => void;
  /** Hidden when a sheet title already names the list. */
  heading?: string;
  label: string;
  /** Move focus to the current item once mounted (inside the sheet). */
  focusCurrent?: boolean;
}

function IndexList({ items, currentId, onPick, heading, label, focusCurrent }: ListProps) {
  const headId = useId();
  const ref = useRef<HTMLElement>(null);
  const tabbable = items.some((i) => i.id === currentId) ? currentId : items[0]?.id;
  useEffect(() => {
    if (!focusCurrent) return;
    // One frame later, so this runs after the sheet has put focus on its first control.
    const f = requestAnimationFrame(() => ref.current?.querySelector<HTMLElement>('[data-pi-item][tabindex="0"]')?.focus());
    return () => cancelAnimationFrame(f);
  }, [focusCurrent]);
  return (
    <nav ref={ref} aria-label={heading ? undefined : label} aria-labelledby={heading ? headId : undefined} onKeyDown={moveFocus} className="flex min-w-0 flex-col gap-1">
      {heading && <p id={headId} className="px-2 text-small font-medium text-fg-muted">{heading}</p>}
      {items.length === 0 ? (
        <p className="px-2 py-2 text-compact text-fg-muted">No questions yet.</p>
      ) : (
        <ol className="flex flex-col">
          {items.map((it) => {
            const current = it.id === currentId;
            return (
              <li key={it.id}>
                <button type="button" data-pi-item tabIndex={it.id === tabbable ? 0 : -1} aria-current={current ? "location" : undefined} title={it.text} onClick={() => onPick(it.id)}
                  className={cn("group flex min-h-9 w-full cursor-pointer items-center gap-3 rounded-lg px-2 py-1.5 text-left text-compact hover:bg-hover pointer-coarse:min-h-11",
                    current ? "font-medium text-fg" : "text-fg-muted hover:text-fg")}>
                  <span aria-hidden className={cn("h-4 w-0.5 shrink-0 rounded-full motion-safe:transition-colors motion-safe:duration-[var(--dur-fast)] forced-colors:bg-[CanvasText]", current ? "bg-fg" : "bg-line-strong")} />
                  <span className="min-w-0 flex-1 truncate group-focus-visible:whitespace-normal">{it.text}</span>
                  {it.time && <span className="shrink-0 text-small text-fg-subtle tabular-nums"><span className="sr-only">, </span>{it.time}</span>}
                </button>
              </li>
            );
          })}
        </ol>
      )}
    </nav>
  );
}

/**
 * An index of the person's own messages in a long thread, so they can jump back to one.
 * The list variant is a plain list. The rail variant is a column of short marks that opens into a list on hover, on
 * keyboard focus or on a tap; below the sm breakpoint it is replaced by a "Questions" button that opens a bottom sheet.
 */
export function PromptIndex({ items, currentId, onJump, label = "Your questions", variant = "list", className }: PromptIndexProps) {
  if (variant === "list") {
    return (
      <div className={className}>
        <IndexList items={items} currentId={currentId} onPick={onJump} heading={label} label={label} />
      </div>
    );
  }
  return (
    <div className={className}>
      <Rail items={items} currentId={currentId} onJump={onJump} label={label} />
      <PhoneTrigger items={items} currentId={currentId} onJump={onJump} label={label} />
    </div>
  );
}

function Rail({ items, currentId, onJump, label }: { items: PromptIndexItem[]; currentId?: string; onJump: (id: string) => void; label: string }) {
  const ref = useRef<HTMLElement>(null);
  const [hover, setHover] = useState(false);
  const [focusIn, setFocusIn] = useState(false);
  const [tapped, setTapped] = useState(false);
  const [held, setHeld] = useState(false); // Escape closes the rail until the pointer or focus leaves
  // A tap on a closed rail only opens it; the click that follows that tap must not jump.
  const swallow = useRef(false);
  const open = (hover || focusIn || tapped) && !held;
  const tabbable = items.some((i) => i.id === currentId) ? currentId : items[0]?.id;

  useEffect(() => {
    if (!tapped) return;
    const away = (e: PointerEvent) => { if (!ref.current?.contains(e.target as Node)) setTapped(false); };
    document.addEventListener("pointerdown", away);
    return () => document.removeEventListener("pointerdown", away);
  }, [tapped]);

  if (items.length === 0) return null;
  const onPointerDown = (e: ReactPointerEvent) => {
    swallow.current = e.pointerType === "touch" && !open;
    if (swallow.current) setTapped(true);
  };
  return (
    <nav ref={ref} aria-label={label}
      onPointerEnter={(e) => { if (e.pointerType !== "touch") setHover(true); }}
      onPointerLeave={(e) => { if (e.pointerType !== "touch") { setHover(false); setHeld(false); } }}
      onPointerDown={onPointerDown}
      // Only keyboard focus opens the rail; a mouse click also focuses the button but should not hold it open.
      onFocus={(e) => setFocusIn(e.target.matches(":focus-visible"))}
      onBlur={(e) => { if (!e.currentTarget.contains(e.relatedTarget as Node | null)) { setFocusIn(false); setHeld(false); } }}
      onKeyDown={(e) => {
        if (e.key === "Escape" && open) { e.stopPropagation(); setHeld(true); setTapped(false); return; }
        moveFocus(e);
      }}
      tabIndex={0}
      className={cn("ml-auto flex max-h-full flex-col overflow-y-auto overscroll-contain rounded-overlay border max-sm:hidden",
        "motion-safe:transition-colors motion-safe:duration-[var(--dur-fast)]",
        open ? "w-72 border-line bg-raised p-1 shadow-lg" : "border-transparent p-1")}>
      <ol className="flex flex-col">
        {items.map((it) => {
          const current = it.id === currentId;
          return (
            <li key={it.id}>
              <button type="button" data-pi-item tabIndex={it.id === tabbable ? 0 : -1} aria-current={current ? "location" : undefined} title={open ? it.text : undefined}
                onClick={() => { if (swallow.current) { swallow.current = false; return; } onJump(it.id); setTapped(false); }}
                className={cn("group flex min-h-6 w-full cursor-pointer items-center justify-end gap-3 rounded-lg text-left text-compact pointer-coarse:min-h-11",
                  open ? "px-2 py-1 hover:bg-hover" : "px-1", current ? "font-medium text-fg" : "text-fg-muted hover:text-fg")}>
                <span className={cn(open ? "min-w-0 flex-1 truncate group-focus-visible:whitespace-normal" : "sr-only")}>{it.text}</span>
                {it.time && <span className={cn(open ? "shrink-0 text-small text-fg-subtle tabular-nums" : "sr-only")}><span className="sr-only">, </span>{it.time}</span>}
                <span aria-hidden className={cn("h-0.5 shrink-0 rounded-full forced-colors:bg-[CanvasText]",
                  "motion-safe:transition-[width,background-color] motion-safe:duration-[var(--dur-fast)] motion-safe:ease-[var(--ease-out)]",
                  current ? "w-5 bg-fg" : "w-3 bg-line-strong")} />
              </button>
            </li>
          );
        })}
      </ol>
    </nav>
  );
}

function PhoneTrigger({ items, currentId, onJump, label }: { items: PromptIndexItem[]; currentId?: string; onJump: (id: string) => void; label: string }) {
  const [open, setOpen] = useState(false);
  // The sheet hands focus back to this button when it has gone. The jump waits for that, so the app's own
  // focus move to the message is not undone by the sheet closing.
  const pending = useRef<string | null>(null);
  if (items.length === 0) return null;
  return (
    <div className="sm:hidden">
      <button type="button" aria-haspopup="dialog" aria-expanded={open} onClick={() => setOpen(true)}
        onFocus={() => { const id = pending.current; pending.current = null; if (id) onJump(id); }}
        className="inline-flex h-9 cursor-pointer items-center gap-2 rounded-control border border-line bg-surface px-4 text-compact text-fg hover:bg-hover tap">
        Questions
        <span className="text-small text-fg-subtle tabular-nums"><span className="sr-only">, </span>{items.length}</span>
      </button>
      <Sheet open={open} onClose={() => setOpen(false)} title={label} side="bottom">
        <IndexList items={items} currentId={currentId} label={label} focusCurrent onPick={(id) => { pending.current = id; setOpen(false); }} />
      </Sheet>
    </div>
  );
}
```

## tattva/organisms/QuestionSet.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import type { KeyboardEvent, ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, CheckIcon } from "../lib/icons";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { NumberField } from "../atoms/NumberField";
import { TextField } from "../atoms/TextField";
import { Tabs } from "../molecules/Tabs";

export interface QuestionSetOption {
  value: string;
  label: string;
  /** One short line that tells the options apart. */
  description?: string;
}

export interface QuestionSetInput {
  kind: "text" | "number" | "date";
  placeholder?: string;
  /** Shown before a number, such as "₹". */
  prefix?: string;
}

export interface QuestionSetQuestion {
  id: string;
  /** Short name used on the tab, such as "Amount". */
  label: string;
  /** The full question. */
  question: string;
  options?: QuestionSetOption[];
  /** When set, a "Something else" choice reveals this input. Without options, the input shows straight away. */
  input?: QuestionSetInput;
  /** Default true. An optional question never blocks the final button. */
  required?: boolean;
  /** Several options may be picked. The answer holds their values joined by commas (see answerValues). */
  multiple?: boolean;
  /** The value of the option to suggest. It is marked "Recommended", and listed first. */
  recommended?: string;
}

/** Answer value when the person skipped a question. */
export const QS_SKIPPED = "__skipped__";
/** Answer value when the person let the assistant decide. */
export const QS_AGENT_DECIDES = "__agent__";
/** The picked values of an answer, for a question with `multiple`. */
export const answerValues = (value: string | undefined) => (value ?? "").split(",").map((v) => v.trim()).filter(Boolean);

export interface QuestionSetProps {
  questions: QuestionSetQuestion[];
  /** Answer text per question id. An empty or missing value means unanswered. */
  answers: Record<string, string>;
  onAnswer: (id: string, value: string) => void;
  /** The single final action. Called from the button on the question that completes the set. */
  onSubmit: () => void;
  /** Facts already known, shown above the questions as "Already have". */
  known?: { label: string; value: ReactNode }[];
  /** Messages from the parent for answers it found invalid. The card jumps to the first one. */
  errors?: Record<string, string>;
  /** Replace the built-in input, for example to drop in a DatePicker. Called when a question's input is showing. */
  renderInput?: (q: QuestionSetQuestion, value: string, onChange: (value: string) => void) => ReactNode;
  /** Label of the final button. */
  submitLabel?: string;
  /** Label of the button while questions remain. */
  nextLabel?: string;
  title?: string;
  /** Name of the run that asked. */
  runName?: string;
  /** Show "Question 2 of 3" above the question. Default true. */
  showProgress?: boolean;
  /** Offer Skip on questions that are not required. Default false. */
  allowSkip?: boolean;
  /** Offer "You decide" on every question, so the assistant picks. Default false. */
  allowAgentDecides?: boolean;
  /** Show every answer for a last look, with a Change link each, before the final button. Default false. */
  review?: boolean;
  /** Called by "Use the defaults": the assistant fills in the rest. The button shows only when this is set. */
  onUseDefaults?: () => void;
  /** Words on the buttons, for localisation. */
  labels?: Partial<Record<"back" | "skip" | "agentDecides" | "recommended" | "useDefaults" | "review" | "change" | "skipped" | "agentDecided" | "of" | "question", string>>;
  className?: string;
}

const LABELS = { back: "Back", skip: "Skip", agentDecides: "You decide", recommended: "Recommended", useDefaults: "Use the defaults", review: "Check your answers", change: "Change", skipped: "Skipped", agentDecided: "Assistant decides", of: "of", question: "Question" };

const filled = (v: string | undefined) => (v ?? "").trim() !== "";
const field = "flex min-h-10 w-full items-center gap-2 rounded-field border bg-surface px-3 text-body leading-5 text-fg transition-colors duration-[var(--dur-fast)] pointer-coarse:min-h-11 focus-within:outline-(length:--focus-width) focus-within:outline-solid focus-within:outline-offset-2 focus-within:outline-[color:var(--focus-ring)]";

/**
 * One card that asks every missing fact at once. A tab per question, one panel visible, and one button.
 * Tapping an option answers and moves on to the next unanswered question. The button reads "Next" until the
 * answer that completes the set, where it reads the submit label. After submit, the parent should replace the card
 * with a Receipt so the thread stays short.
 */
export function QuestionSet({ questions, answers, onAnswer, onSubmit, known, errors, renderInput, submitLabel = "Continue", nextLabel = "Next", title = "A few quick questions", runName,
  showProgress = true, allowSkip = false, allowAgentDecides = false, review = false, onUseDefaults, labels, className }: QuestionSetProps) {
  const L = { ...LABELS, ...labels };
  const uid = useId();
  const root = useRef<HTMLElement>(null);
  const firstOpen = questions.findIndex((q) => q.required !== false && !filled(answers[q.id]));
  const [cur, setCur] = useState(firstOpen < 0 ? 0 : firstOpen);
  const [otherOpen, setOtherOpen] = useState<Record<string, boolean>>({});
  const [announce, setAnnounce] = useState("");
  const [reviewing, setReviewing] = useState(false);
  const moved = useRef<"tab" | "input" | "button" | null>(null);

  const n = questions.length;
  const idx = Math.min(cur, Math.max(n - 1, 0));
  const q = questions[idx];
  const isRequired = (x: QuestionSetQuestion) => x.required !== false;
  /** Next question still to answer: any unanswered one ahead (optional ones are still shown on the way), then,
   *  wrapping around, a required one left behind. Optional questions already passed never block the end. */
  const nextOpen = (from: number, a: Record<string, string>) => {
    for (let i = 1; i < n; i++) { const j = (from + i) % n; if (!filled(a[questions[j].id]) && (j > from || isRequired(questions[j]))) return j; }
    return -1;
  };

  const go = (i: number, focus: "tab" | "input" | "button" | null) => { moved.current = focus; setCur(i); };
  const answerNow = (id: string, value: string) => { onAnswer(id, value); };

  // After a move we made, put focus somewhere sensible: the tapped option disappeared with its panel.
  useEffect(() => {
    const m = moved.current; moved.current = null;
    if (!m || !root.current) return;
    const sel = m === "tab" ? '[role="tab"][aria-selected="true"]' : m === "input" ? "[data-qs-input] input" : "[data-qs-submit]";
    root.current.querySelector<HTMLElement>(sel)?.focus({ preventScroll: true });
  });

  useEffect(() => { if (q) setAnnounce(reviewing ? L.review : `${L.question} ${idx + 1} ${L.of} ${n}: ${q.label}`); }, [idx, n, q, reviewing, L.review, L.question, L.of]);

  const errKey = Object.keys(errors ?? {}).filter((k) => errors?.[k]).sort().join("|");
  useEffect(() => {
    const i = questions.findIndex((x) => errors?.[x.id]);
    if (i >= 0) setCur(i);
    // Only when the set of errored questions changes, not on every render.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [errKey]);

  // Latest-state ref so Enter (which waits a tick for a number field to commit) never reads stale answers.
  const latest = useRef<() => void>(() => {});
  const remaining = questions.filter((x, i) => i !== idx && !filled(answers[x.id]) && (i > idx || isRequired(x)));
  const last = remaining.length === 0;
  const canAct = !q || !isRequired(q) || filled(answers[q.id]);
  const act = () => {
    if (reviewing) { onSubmit(); return; }
    if (!canAct) return;
    if (last) { if (review) { setReviewing(true); moved.current = "button"; } else onSubmit(); return; }
    const j = nextOpen(idx, answers);
    if (j >= 0) go(j, "tab");
  };
  latest.current = act;

  if (!q) return null;

  const onKeyDown = (e: KeyboardEvent<HTMLElement>) => {
    const t = e.target as HTMLElement;
    // Number keys pick an option, as in other agent tools; never while typing in a field.
    if (!reviewing && /^[1-9]$/.test(e.key) && !["INPUT", "TEXTAREA", "SELECT"].includes(t.tagName) && !e.metaKey && !e.ctrlKey && !e.altKey) {
      // In the order shown: the recommended option is first.
      const shown = [...(q.options ?? [])].sort((a, b) => Number(b.value === q.recommended) - Number(a.value === q.recommended));
      const o = shown[Number(e.key) - 1];
      if (o) { e.preventDefault(); pick(o); }
      return;
    }
    if (e.key !== "Enter" || t.tagName !== "INPUT" || e.nativeEvent.isComposing) return;
    e.preventDefault();
    // A number field commits its draft in its own Enter handler; wait one tick so we see the result.
    setTimeout(() => latest.current(), 0);
  };

  const chosen = answers[q.id] ?? "";
  // The recommended option leads, so the suggestion is the first thing read.
  const opts = [...(q.options ?? [])].sort((a, b) => Number(b.value === q.recommended) - Number(a.value === q.recommended));
  const isOption = opts.some((o) => o.value === chosen) || (!!q.multiple && answerValues(chosen).some((v) => opts.some((o) => o.value === v)));
  /** A choice that is not an option: skip, or let the assistant decide. Moves on like an option does. */
  const special = (value: string) => {
    setOtherOpen((s) => ({ ...s, [q.id]: false }));
    answerNow(q.id, value);
    const j = nextOpen(idx, { ...answers, [q.id]: value });
    go(j >= 0 ? j : idx, j >= 0 ? "tab" : "button");
  };
  const showOther = !q.multiple && !!q.input && (opts.length === 0 || (otherOpen[q.id] ?? (filled(chosen) && !isOption)));
  const err = errors?.[q.id];

  const pick = (o: QuestionSetOption) => {
    if (q.multiple) {
      // Several may be picked: toggle this one and stay, so more can be chosen. The button moves on.
      const cur = answerValues(chosen).filter((v) => v !== QS_SKIPPED && v !== QS_AGENT_DECIDES);
      answerNow(q.id, (cur.includes(o.value) ? cur.filter((v) => v !== o.value) : [...cur, o.value]).join(","));
      return;
    }
    setOtherOpen((s) => ({ ...s, [q.id]: false }));
    answerNow(q.id, o.value);
    const j = nextOpen(idx, { ...answers, [q.id]: o.value });
    go(j >= 0 ? j : idx, j >= 0 ? "tab" : "button");
  };
  const openOther = () => {
    setOtherOpen((s) => ({ ...s, [q.id]: true }));
    if (isOption) answerNow(q.id, "");
    moved.current = "input"; // the state change above re-renders, and the focus effect then runs
  };

  const setValue = (v: string) => answerNow(q.id, v);
  const inp = q.input;
  const inputEl = !inp ? null : renderInput ? renderInput(q, chosen, setValue) : inp.kind === "number" ? (
    <span className="flex items-center gap-2">
      {inp.prefix && <span aria-hidden className="text-body leading-5 text-fg-muted">{inp.prefix}</span>}
      <NumberField label={inp.prefix ? `${q.label} (${inp.prefix})` : q.label} value={filled(chosen) && Number.isFinite(Number(chosen)) ? Number(chosen) : null}
        onChange={(v) => setValue(v === null ? "" : String(v))} invalid={!!err} aria-describedby={err ? `${uid}-err` : undefined} />
    </span>
  ) : inp.kind === "date" ? (
    <span className={cn(field, err ? "border-danger" : "border-line hover:border-line-strong", "sm:max-w-56")}>
      <input type="date" aria-label={q.label} value={chosen} onChange={(e) => setValue(e.target.value)} aria-invalid={err ? true : undefined} aria-describedby={err ? `${uid}-err` : undefined}
        className="min-w-0 flex-1 bg-transparent py-1 text-fg outline-none focus-visible:outline-none" />
    </span>
  ) : (
    <TextField aria-label={q.label} value={chosen} onChange={(e) => setValue(e.target.value)} placeholder={inp.placeholder} invalid={!!err} aria-describedby={err ? `${uid}-err` : undefined} />
  );

  const panel = (
    <div>
      {showProgress && n > 1 && <p className="mb-1 text-small leading-4 text-fg-muted tabular-nums">{L.question} {idx + 1} {L.of} {n}</p>}
      <p id={`${uid}-q`} className="text-body leading-5 font-medium break-words">{q.question}{!isRequired(q) && <span className="ml-2 text-small leading-4 font-normal text-fg-muted">Optional</span>}</p>
      {opts.length > 0 && (
        <ul className="mt-3 grid gap-2 sm:grid-cols-2">
          {opts.map((o) => {
            const on = (q.multiple ? answerValues(chosen).includes(o.value) : chosen === o.value) && !showOther;
            return (
              <li key={o.value}>
                <button type="button" {...(q.multiple ? { role: "checkbox", "aria-checked": on } : { "aria-pressed": on })} onClick={() => pick(o)}
                  className={cn("h-full w-full cursor-pointer rounded-card border px-3 py-2 text-left transition-colors duration-[var(--dur-fast)] pointer-coarse:min-h-11 forced-colors:border-[CanvasText]",
                    on ? "border-fg bg-selected" : "border-line bg-surface hover:border-line-strong hover:bg-bg")}>
                  <span className="flex items-start gap-2">
                    <span className="min-w-0 flex-1">
                      <span className="flex flex-wrap items-center gap-x-2 gap-y-1 text-body leading-5 font-medium">
                        <span>{o.label}</span>
                        {o.value === q.recommended && <Badge tone="neutral">{L.recommended}</Badge>}
                      </span>
                      {o.description && <span className="mt-0.5 block text-small leading-4 text-fg-muted">{o.description}</span>}
                    </span>
                    {q.multiple
                      ? <span aria-hidden className={cn("mt-0.5 inline-flex size-4 shrink-0 items-center justify-center rounded-[calc(var(--radius-field)/2)] border", on ? "border-fg bg-fg text-bg" : "border-line-strong")}>{on && <CheckIcon width={12} height={12} />}</span>
                      : on && <CheckIcon aria-hidden className="mt-0.5 shrink-0" />}
                  </span>
                </button>
              </li>
            );
          })}
          {inp && !q.multiple && (
            <li>
              <button type="button" aria-pressed={showOther} onClick={openOther}
                className={cn("h-full w-full cursor-pointer rounded-card border border-dashed px-3 py-2 text-left text-body leading-5 font-medium transition-colors duration-[var(--dur-fast)] pointer-coarse:min-h-11 forced-colors:border-[CanvasText]",
                  showOther ? "border-fg bg-sunken" : "border-line-strong bg-surface hover:bg-bg")}>
                Something else
              </button>
            </li>
          )}
        </ul>
      )}
      {showOther && <div data-qs-input className="mt-3">{inputEl}</div>}
      {((allowSkip && !isRequired(q)) || allowAgentDecides) && (
        <div className="mt-3 flex flex-wrap gap-2">
          {allowAgentDecides && <Button type="button" size="sm" variant="ghost" aria-pressed={chosen === QS_AGENT_DECIDES} onClick={() => special(QS_AGENT_DECIDES)}>{L.agentDecides}</Button>}
          {allowSkip && !isRequired(q) && <Button type="button" size="sm" variant="ghost" aria-pressed={chosen === QS_SKIPPED} onClick={() => special(QS_SKIPPED)}>{L.skip}</Button>}
        </div>
      )}
      {err && (
        <p id={`${uid}-err`} role="alert" className="mt-2 flex items-start gap-1.5 text-body leading-5 text-danger-fg">
          <AlertIcon aria-hidden width={16} height={16} className="mt-0.5 shrink-0" />
          <span>{err}</span>
        </p>
      )}
    </div>
  );

  const tabs = questions.map((x, i) => {
    const done = filled(answers[x.id]);
    const bad = !!errors?.[x.id];
    return {
      id: x.id,
      label: (
        <>
          <span aria-hidden className="inline-flex size-5 shrink-0 items-center justify-center rounded-full border border-current text-micro tabular-nums">
            {bad ? <AlertIcon width={12} height={12} /> : done ? <CheckIcon width={12} height={12} /> : i + 1}
          </span>
          {/* On a phone only the current step shows its name, so all the steps fit; the others keep their name for screen readers. */}
          <span className={cn(i !== idx && "max-sm:sr-only")}>{x.label}</span>
          <span className="sr-only">{bad ? ", needs another answer" : done ? ", answered" : ", not answered yet"}</span>
        </>
      ),
      panel: i === idx ? panel : undefined,
    };
  });

  const hasKnown = known && known.length > 0;
  /** An answer in words, for the review: option labels, typed text, Skipped or Assistant decides. */
  const answerText = (x: QuestionSetQuestion) => {
    const v = answers[x.id];
    if (!filled(v)) return "—";
    if (v === QS_SKIPPED) return L.skipped;
    if (v === QS_AGENT_DECIDES) return L.agentDecided;
    const label = (val: string) => x.options?.find((o) => o.value === val)?.label ?? val;
    return x.multiple ? answerValues(v).map(label).join(", ") : label(v);
  };
  const reviewList = (
    <div>
      <p className="text-body leading-5 font-medium">{L.review}</p>
      <dl className="mt-3 divide-y divide-line rounded-card border border-line">
        {questions.map((x, i) => (
          <div key={x.id} className="flex items-start justify-between gap-3 px-3 py-2">
            <div className="min-w-0">
              <dt className="text-small leading-4 text-fg-muted">{x.label}</dt>
              <dd className="m-0 mt-0.5 text-body leading-5 font-medium break-words">{answerText(x)}</dd>
            </div>
            <Button type="button" size="sm" variant="ghost" aria-label={`${L.change}: ${x.label}`} onClick={() => { setReviewing(false); go(i, "tab"); }}>{L.change}</Button>
          </div>
        ))}
      </dl>
    </div>
  );
  return (
    <section ref={root} aria-labelledby={`${uid}-t`} onKeyDown={onKeyDown} className={cn("rounded-card border border-attention bg-surface p-4", className)}>
      <div className="flex flex-wrap items-center gap-2">
        <Badge tone="warning">Needs your answers</Badge>
        {runName && <span className="text-small leading-4 text-fg-muted">{runName}</span>}
      </div>
      <h3 id={`${uid}-t`} className="mt-2 text-body leading-5 font-semibold">{title}</h3>
      {hasKnown && (
        <dl className="mt-2 flex flex-wrap gap-x-4 gap-y-1 text-small leading-4 text-fg-muted">
          <dt className="font-medium">Already have:</dt>
          {known.map((k) => (
            <dd key={k.label} className="m-0"><span>{k.label} </span><span className="font-medium text-fg">{k.value}</span></dd>
          ))}
        </dl>
      )}
      {reviewing
        ? <div className="mt-3">{reviewList}</div>
        : <Tabs className="mt-3" label="Questions" tabs={tabs} value={q.id} onChange={(id) => go(questions.findIndex((x) => x.id === id), null)} />}
      <div className="mt-4 flex flex-wrap items-center justify-between gap-2">
        <div className="flex flex-wrap gap-2">
          {(reviewing || idx > 0) && <Button type="button" size="md" variant="ghost" className="pointer-coarse:h-11" onClick={() => (reviewing ? setReviewing(false) : go(idx - 1, "tab"))}>{L.back}</Button>}
          {onUseDefaults && !reviewing && <Button type="button" size="md" variant="ghost" className="pointer-coarse:h-11" onClick={onUseDefaults}>{L.useDefaults}</Button>}
        </div>
        <Button data-qs-submit="" aria-disabled={(!reviewing && !canAct) || undefined} onClick={act} variant="primary" size="md" className={cn("ml-auto pointer-coarse:h-11", !reviewing && !canAct && "opacity-50")}>
          {reviewing ? submitLabel : last ? (review ? nextLabel : submitLabel) : nextLabel}
        </Button>
      </div>
      <p role="status" aria-live="polite" className="sr-only">{announce}</p>
    </section>
  );
}
```

## tattva/organisms/ResearchRun.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import { cn } from "../lib/cn";
import { headingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { AlertIcon, CheckIcon, SearchIcon } from "../lib/icons";
import { Button } from "../atoms/Button";

export interface ResearchPhase {
  id: string;
  /** What happens in this phase, such as "Searching the web". */
  label: string;
  state: "done" | "active" | "waiting";
}

export interface ResearchRunLabels {
  heading?: string;
  /** Receives the number of sources read. */
  sources?: (n: number) => string;
  /** Receives the time so far, such as "4 min". */
  elapsed?: (t: string) => string;
  stop?: string;
  open?: string;
  /** Receives sources read and the time taken. */
  doneSummary?: (n: number, t: string) => string;
  stopped?: string;
  failed?: string;
  retry?: string;
  phaseNow?: (label: string) => string;
}

export interface ResearchRunProps {
  /** What is being researched, in the person's words. */
  question: string;
  status: "running" | "done" | "stopped" | "failed";
  phases: ResearchPhase[];
  /** Sources read so far. */
  sourcesRead: number;
  /** Time so far, already in words: "4 min". */
  elapsed: string;
  /** What it is doing this moment, such as "Reading reuters.com". Shown while running. */
  activity?: string;
  onStop?: () => void;
  /** Opens the finished report. */
  onOpenReport?: () => void;
  onRetry?: () => void;
  labels?: ResearchRunLabels;
  /** Level of the part's heading, so it fits the page outline. Default 3. */
  headingLevel?: HeadingLevel;
  className?: string;
}

/**
 * A deep research run, which can take many minutes. While it runs it shows the phases (done, now, next), how many
 * sources it has read and for how long, and what it is reading this moment; Stop is always there. Screen readers
 * hear each new phase, not every source. When it finishes, the card says what it did in one line and opens the report.
 */
export function ResearchRun({ question, status, phases, sourcesRead, elapsed, activity, onStop, onOpenReport, onRetry, labels, headingLevel = 3, className }: ResearchRunProps) {
  const l = {
    heading: "Research", sources: (n: number) => (n === 1 ? "1 source read" : `${n} sources read`), elapsed: (t: string) => t, stop: "Stop", open: "Open report",
    doneSummary: (n: number, t: string) => `Read ${n} sources in ${t}.`, stopped: "Stopped. The report covers what was read so far.", failed: "The research stopped because of an error.",
    retry: "Try again", phaseNow: (p: string) => `Now: ${p}`, ...labels,
  };
  const uid = useId();
  const H = headingTag(headingLevel);
  const active = phases.find((p) => p.state === "active");
  // Announce each new phase once, not each source.
  const [said, setSaid] = useState("");
  const last = useRef<string | undefined>(undefined);
  useEffect(() => { if (status === "running" && active && active.id !== last.current) { last.current = active.id; setSaid(l.phaseNow(active.label)); } }, [status, active]);
  const running = status === "running";
  return (
    <section aria-labelledby={`${uid}-h`} aria-busy={running || undefined} className={cn("rounded-card border border-line bg-surface p-4", className)}>
      <div className="flex items-start gap-2.5">
        <SearchIcon aria-hidden width={18} height={18} className="mt-0.5 shrink-0 text-fg-muted" />
        <div className="min-w-0 flex-1">
          <H id={`${uid}-h`} className="m-0 text-small leading-4 font-medium text-fg-muted">{l.heading}</H>
          <p className="m-0 mt-1 text-body leading-6 font-medium text-fg">{question}</p>
        </div>
      </div>

      <ol className="mt-4 space-y-2">
        {phases.map((p) => (
          <li key={p.id} className="flex items-center gap-2.5" aria-current={p.state === "active" ? "step" : undefined}>
            <span aria-hidden className={cn("grid size-5 shrink-0 place-items-center rounded-full",
              p.state === "done" ? "bg-success-soft text-success-fg" : p.state === "active" ? "border-2 border-lime" : "border border-line-strong")}>
              {p.state === "done" && <CheckIcon width={12} height={12} />}
              {p.state === "active" && <span className={cn("size-2 rounded-full bg-lime", running && "motion-safe:animate-pulse")} />}
            </span>
            <span className={cn("text-body leading-5", p.state === "waiting" ? "text-fg-muted" : "text-fg", p.state === "active" && "font-medium")}>{p.label}</span>
            <span className="sr-only">{p.state === "done" ? "(done)" : p.state === "active" ? "(now)" : "(next)"}</span>
          </li>
        ))}
      </ol>

      <div className="mt-4 flex flex-wrap items-center gap-x-4 gap-y-2">
        <p className="m-0 text-small leading-5 text-fg-muted tabular-nums">{l.sources(sourcesRead)} · {l.elapsed(elapsed)}</p>
        {running && activity && <p className="m-0 min-w-0 flex-1 truncate text-small leading-5 text-fg-muted" title={activity}>{activity}</p>}
      </div>

      {status === "done" && <p className="m-0 mt-3 text-body leading-6 text-fg">{l.doneSummary(sourcesRead, elapsed)}</p>}
      {status === "stopped" && <p className="m-0 mt-3 text-body leading-6 text-fg">{l.stopped}</p>}
      {status === "failed" && <p className="m-0 mt-3 inline-flex items-center gap-1.5 text-body leading-6 text-danger-fg"><AlertIcon aria-hidden width={15} height={15} />{l.failed}</p>}

      <div className="mt-4 flex flex-wrap gap-2">
        {running && onStop && <Button size="sm" variant="secondary" onClick={onStop}>{l.stop}</Button>}
        {(status === "done" || status === "stopped") && onOpenReport && <Button size="sm" onClick={onOpenReport}>{l.open}</Button>}
        {status === "failed" && onRetry && <Button size="sm" variant="secondary" onClick={onRetry}>{l.retry}</Button>}
      </div>
      <p role="status" className="sr-only">{status === "done" ? l.doneSummary(sourcesRead, elapsed) : status === "stopped" ? l.stopped : status === "failed" ? l.failed : said}</p>
    </section>
  );
}
```

## tattva/organisms/ResponseCompare.tsx

```tsx
import { useId, useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { headingTag, subHeadingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { CheckIcon } from "../lib/icons";
import { Button } from "../atoms/Button";

export interface CompareReply {
  id: string;
  /** Short name for the reply, such as "Reply A". */
  label: string;
  content: ReactNode;
}

export interface ResponseCompareLabels {
  heading?: string;
  hint?: string;
  choose?: string;
  same?: string;
  chosen?: string;
  /** Read out after a choice. Receives the chosen reply's label, or null for "about the same". */
  thanks?: (label: string | null) => string;
}

export interface ResponseCompareProps {
  /** Exactly two replies to the same question. */
  replies: [CompareReply, CompareReply];
  /** Called with the chosen reply's id, or "same". */
  onChoose: (id: string | "same") => void;
  /** The chosen reply's id, or "same". Leave it out to let the part keep its own choice. */
  chosen?: string | "same" | null;
  /** Offer an "About the same" choice. On by default. */
  allowSame?: boolean;
  labels?: ResponseCompareLabels;
  /** Level of the part's heading, so it fits the page outline. Default 3. */
  headingLevel?: HeadingLevel;
  className?: string;
}

/**
 * Two replies to the same question, side by side, and the person picks the better one. The choice is a plain button
 * under each reply, so it works the same with a mouse, a finger, a keyboard or a screen reader. After a choice, the
 * kept reply says so in words and the other steps back; nothing moves.
 */
export function ResponseCompare({ replies, onChoose, chosen: chosenProp, allowSame = true, labels, headingLevel = 3, className }: ResponseCompareProps) {
  const l = {
    heading: "Which reply is better?", hint: "Your choice helps the assistant learn what you prefer.", choose: "Choose this reply", same: "About the same",
    chosen: "You chose this", thanks: (label: string | null) => (label ? `Thanks. You chose ${label}.` : "Thanks. You said they are about the same."), ...labels,
  };
  const [own, setOwn] = useState<string | "same" | null>(null);
  const chosen = chosenProp !== undefined ? chosenProp : own;
  const uid = useId();
  const H = headingTag(headingLevel);
  const H2 = subHeadingTag(headingLevel);
  const pick = (id: string | "same") => { setOwn(id); onChoose(id); };
  const label = chosen && chosen !== "same" ? replies.find((r) => r.id === chosen)?.label ?? null : null;
  return (
    <section aria-labelledby={`${uid}-h`} className={cn("rounded-card border border-line bg-surface p-4 sm:p-5", className)}>
      <H id={`${uid}-h`} className="m-0 text-body leading-5 font-semibold text-fg">{l.heading}</H>
      <p className="mt-1 text-small leading-5 text-fg-muted">{l.hint}</p>
      <div className="mt-4 grid gap-3 sm:grid-cols-2">
        {replies.map((r) => {
          const isChosen = chosen === r.id;
          const faded = !!chosen && chosen !== "same" && !isChosen;
          return (
            <article key={r.id} aria-labelledby={`${uid}-${r.id}`}
              className={cn("flex min-w-0 flex-col rounded-control border p-4 transition-colors duration-(--dur-fast) motion-reduce:transition-none",
                isChosen ? "border-selected-line bg-selected" : "border-line bg-bg", faded && "opacity-70")}>
              <H2 id={`${uid}-${r.id}`} className="m-0 text-small leading-4 font-medium text-fg-muted">{r.label}</H2>
              <div className="mt-2 min-w-0 flex-1 space-y-3 text-body leading-6 text-fg">{r.content}</div>
              <div className="mt-4">
                {isChosen
                  ? <p className="m-0 inline-flex items-center gap-1.5 text-small leading-5 font-medium text-fg"><CheckIcon aria-hidden width={14} height={14} />{l.chosen}</p>
                  : <Button size="sm" variant="secondary" onClick={() => pick(r.id)} aria-describedby={`${uid}-${r.id}`}>{l.choose}</Button>}
              </div>
            </article>
          );
        })}
      </div>
      {allowSame && chosen !== "same" && (
        <div className="mt-3"><Button size="sm" variant="ghost" onClick={() => pick("same")}>{l.same}</Button></div>
      )}
      <p role="status" className={cn("text-small leading-5 text-fg-muted", chosen ? "mt-3" : "sr-only")}>{chosen ? l.thanks(label) : ""}</p>
    </section>
  );
}
```

## tattva/organisms/RiskLimits.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import type { KeyboardEvent, ReactNode } from "react";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { Meter } from "../atoms/Meter";
import { cn } from "../lib/cn";
import { useFocusAfter } from "../lib/useFocusAfter";
import { InfoIcon, LockIcon, StopIcon } from "../lib/icons";

export interface RiskLimit {
  id: string;
  label: string;
  used: number;
  max: number;
  /** Text after the numbers, such as "trades". */
  unit?: string;
  /** Formats a number for display, such as a money format. The app owns money formatting. */
  format?: (n: number) => string;
}

export interface RiskLimitsProps {
  limits: RiskLimit[];
  /** True when trading is stopped. Every limit then says "Paused". */
  paused?: boolean;
  /** Why it is paused, in words. */
  pausedReason?: string;
  /** The kill switch. It asks once, in place, before calling this. Not shown while paused. */
  onKill?: () => void;
  killLabel?: string;
  /** A line of reassurance under the limits, such as "Exits are never blocked." */
  note?: ReactNode;
  className?: string;
}

const plain = (n: number) => n.toLocaleString("en-IN");

/** The limits that protect the person, each with a meter in words and numbers, and a kill switch that asks in place. */
export function RiskLimits({ limits, paused, pausedReason, onKill, killLabel = "Stop all trading", note, className }: RiskLimitsProps) {
  const uid = useId();
  const headId = `${uid}-head`, askId = `${uid}-ask`;
  const [asking, setAsking] = useState(false);
  // Button takes no ref, so focus is found through these wrappers.
  const killWrap = useRef<HTMLDivElement>(null);
  const keepWrap = useRef<HTMLDivElement>(null);
  const heading = useRef<HTMLHeadingElement>(null);
  const back = useRef(false);
  const arm = useFocusAfter(paused, () => heading.current);

  useEffect(() => { if (asking) keepWrap.current?.querySelector<HTMLButtonElement>("button")?.focus(); }, [asking]);
  useEffect(() => { if (!asking && back.current) { back.current = false; killWrap.current?.querySelector<HTMLButtonElement>("button")?.focus(); } }, [asking]);

  const close = () => { back.current = true; setAsking(false); };
  const stop = () => { arm(); setAsking(false); onKill?.(); };
  const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") { e.stopPropagation(); close(); } };

  return (
    <section aria-labelledby={headId} className={cn("flex w-full max-w-phone flex-col gap-4 rounded-card border border-line bg-surface p-4", className)}>
      <div className="flex items-center justify-between gap-3">
        <h3 id={headId} ref={heading} tabIndex={-1} className="text-lead leading-6 font-semibold text-fg">Risk limits</h3>
        {paused && <Badge tone="unsure"><LockIcon aria-hidden width={12} height={12} />Paused</Badge>}
      </div>
      {paused && pausedReason && <p className="text-body leading-5 text-fg-muted">{pausedReason}</p>}
      <ul className="m-0 flex list-none flex-col gap-4 p-0">
        {limits.map((l) => {
          const fmt = l.format ?? plain;
          const ratio = l.max > 0 ? l.used / l.max : 0;
          const reached = ratio >= 1, near = !reached && ratio >= 0.8;
          const text = `${fmt(l.used)} of ${fmt(l.max)}${l.unit ? ` ${l.unit}` : ""}`;
          return (
            <li key={l.id} className="flex flex-col gap-1.5">
              <div className="flex items-baseline justify-between gap-3 text-body leading-5">
                <span className="font-medium text-fg">{l.label}</span>
                <span className="text-fg-muted tabular-nums">{text}</span>
              </div>
              <Meter value={Math.min(l.used, l.max)} max={l.max} label={l.label} valueText={text} />
              <div className="flex flex-wrap gap-1.5 empty:hidden">
                {reached && <Badge tone="unsure"><LockIcon aria-hidden width={12} height={12} />Limit reached</Badge>}
                {near && <Badge tone="unsure"><InfoIcon aria-hidden width={12} height={12} />Near the limit</Badge>}
                {paused && <Badge tone="neutral"><LockIcon aria-hidden width={12} height={12} />Paused</Badge>}
              </div>
            </li>
          );
        })}
      </ul>
      {note && <p className="text-body leading-5 text-fg-muted">{note}</p>}
      {onKill && !paused && (asking ? (
        <div role="group" aria-labelledby={askId} onKeyDown={onKey} className="flex flex-col gap-3 rounded-card border border-line-strong bg-sunken p-3">
          <p id={askId} className="text-body leading-5 text-fg">{killLabel}? This pauses new orders. Exits stay open.</p>
          <div className="flex gap-2">
            <Button variant="secondary" size="lg" className="flex-1 pointer-coarse:h-12" onClick={stop}>Stop trading</Button>
            <div ref={keepWrap} className="flex flex-1"><Button variant="secondary" size="lg" className="w-full pointer-coarse:h-12" onClick={close}>Keep trading</Button></div>
          </div>
        </div>
      ) : (
        <div ref={killWrap} className="flex"><Button variant="secondary" size="lg" className="w-full pointer-coarse:h-12" leading={<StopIcon aria-hidden width={14} height={14} />} onClick={() => setAsking(true)}>{killLabel}</Button></div>
      ))}
      <p role="status" aria-live="polite" className="sr-only">{paused ? `Trading is paused.${pausedReason ? ` ${pausedReason}` : ""}` : ""}</p>
    </section>
  );
}
```

## tattva/organisms/SandboxedFrame.tsx

```tsx
import { useCallback, useEffect, useRef, useState } from "react";
import { cn } from "../lib/cn";
import { ShieldIcon } from "../lib/icons";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";

export type SandboxPermission = "open-links" | "call-tools";

/**
 * Messages the page inside the frame may send. Anything else is ignored.
 *  { type: "tattva:resize", height: number }
 *  { type: "tattva:open-link", url: string }
 *  { type: "tattva:call-tool", id?: string, name: string, args?: object }
 * The host answers a tool request with { type: "tattva:tool-result", id, ok: boolean }.
 */
export const SANDBOX_MESSAGES = { resize: "tattva:resize", openLink: "tattva:open-link", callTool: "tattva:call-tool", toolResult: "tattva:tool-result" } as const;

export interface SandboxedFrameLabels {
  generatedBy?: (source: string) => string;
  whatItCanDo?: string;
  allow?: string;
  deny?: string;
}

export interface SandboxedFrameProps {
  /** Who made the page. Always visible. */
  source: string;
  /** Name of the frame for screen readers, for example "Trip budget calculator". */
  title: string;
  /** The page as a string. Runs with no access to this site. Use this or `src`. */
  srcDoc?: string;
  /** Address of a page on another origin. Use this or `srcDoc`. */
  src?: string;
  /** What the page may ask to do. Each one still needs a yes from the person every time. Default none. */
  permissions?: SandboxPermission[];
  /** Tool names the page may ask for. Requests for other names are ignored. */
  allowedTools?: string[];
  /** Called after the person agrees to open a link. Default opens it in a new tab with no link back to this site. */
  onOpenLink?: (url: string) => void;
  /** Called after the person agrees to a tool request. May return a promise. A rejected promise tells the page it failed. */
  onCallTool?: (name: string, args: Record<string, unknown>) => unknown | Promise<unknown>;
  minHeight?: number;
  maxHeight?: number;
  /** Start height in px. */
  initialHeight?: number;
  /** Let scripts send forms. Off by default. */
  allowForms?: boolean;
  /**
   * Gives the page this site's origin, which lets it read everything the site can. Almost never right for generated pages.
   * Off by default. Never turn it on together with `srcDoc` for pages you did not write.
   */
  allowSameOrigin?: boolean;
  /** Overrides the plain-language line under the frame. */
  summary?: string;
  labels?: SandboxedFrameLabels;
  className?: string;
}

type Request = { kind: "link"; url: string } | { kind: "tool"; id?: string; name: string; args: Record<string, unknown> };

const permissionText: Record<SandboxPermission, string> = { "open-links": "Ask to open a link", "call-tools": "Ask the assistant to do something for you" };

const permissionSentence: Record<SandboxPermission, string> = { "open-links": "ask to open a link", "call-tools": "ask the assistant to do something for you" };

function webUrl(v: unknown): string | null {
  if (typeof v !== "string") return null;
  try { const u = new URL(v); return u.protocol === "https:" || u.protocol === "http:" ? u.href : null; } catch { return null; }
}

function originOf(src?: string): string | null {
  if (!src) return null;
  try { return new URL(src, window.location.href).origin; } catch { return null; }
}

/**
 * Runs a page made by someone else, such as a generated mini app, inside a locked box. The box has no access to this
 * site's data and cannot open links or call tools on its own. It can only send a few messages, and the host checks
 * each one: who sent it, where it came from, and whether the person agrees. Use it when a catalog part cannot do the job.
 */
export function SandboxedFrame({
  source, title, srcDoc, src, permissions = [], allowedTools = [], onOpenLink, onCallTool, minHeight = 120, maxHeight = 640, initialHeight = 240,
  allowForms = false, allowSameOrigin = false, summary, labels, className,
}: SandboxedFrameProps) {
  const l = { generatedBy: (s: string) => `Generated by ${s}`, whatItCanDo: "What this can do", allow: "Allow", deny: "Don't allow", ...labels };
  const frame = useRef<HTMLIFrameElement>(null);
  const [height, setHeight] = useState(() => Math.min(maxHeight, Math.max(minHeight, initialHeight)));
  const [request, setRequest] = useState<Request | null>(null);
  const requestRef = useRef<Request | null>(null);
  requestRef.current = request;
  const expectedOrigin = srcDoc !== undefined ? "null" : originOf(src);

  const tokens = ["allow-scripts", ...(allowForms ? ["allow-forms"] : []), ...(allowSameOrigin ? ["allow-same-origin"] : [])].join(" ");

  const reply = useCallback((msg: Record<string, unknown>) => {
    const target = expectedOrigin && expectedOrigin !== "null" ? expectedOrigin : "*";
    try { frame.current?.contentWindow?.postMessage(msg, target); } catch { /* the frame is gone */ }
  }, [expectedOrigin]);

  useEffect(() => {
    const onMessage = (e: MessageEvent) => {
      // Only the frame we made, from the origin we expect.
      if (!frame.current || e.source !== frame.current.contentWindow) return;
      if (expectedOrigin === null || e.origin !== expectedOrigin) return;
      const d: unknown = e.data;
      if (typeof d !== "object" || d === null || Array.isArray(d)) return;
      const m = { ...(d as Record<string, unknown>) };
      // Frames written before the rename send "sandstone:*"; read them as the same messages.
      if (typeof m.type === "string") m.type = m.type.replace(/^sandstone:/, "tattva:");
      if (m.type === SANDBOX_MESSAGES.resize) {
        if (typeof m.height === "number" && Number.isFinite(m.height)) setHeight(Math.min(maxHeight, Math.max(minHeight, Math.round(m.height))));
      } else if (m.type === SANDBOX_MESSAGES.openLink) {
        const url = webUrl(m.url);
        if (url && permissions.includes("open-links") && !requestRef.current) setRequest({ kind: "link", url });
      } else if (m.type === SANDBOX_MESSAGES.callTool) {
        if (typeof m.name === "string" && permissions.includes("call-tools") && allowedTools.includes(m.name) && !requestRef.current) {
          setRequest({ kind: "tool", id: typeof m.id === "string" ? m.id : undefined, name: m.name, args: typeof m.args === "object" && m.args !== null && !Array.isArray(m.args) ? (m.args as Record<string, unknown>) : {} });
        }
      }
      // Anything else is ignored on purpose.
    };
    window.addEventListener("message", onMessage);
    return () => window.removeEventListener("message", onMessage);
  }, [expectedOrigin, maxHeight, minHeight, permissions, allowedTools]);

  const allowRequest = async () => {
    const r = request;
    setRequest(null);
    if (!r) return;
    if (r.kind === "link") {
      if (onOpenLink) onOpenLink(r.url); else window.open(r.url, "_blank", "noopener,noreferrer");
      return;
    }
    let ok = true;
    try { await onCallTool?.(r.name, r.args); } catch { ok = false; }
    if (r.id) reply({ type: SANDBOX_MESSAGES.toolResult, id: r.id, ok });
  };
  const denyRequest = () => {
    const r = request;
    setRequest(null);
    if (r?.kind === "tool" && r.id) reply({ type: SANDBOX_MESSAGES.toolResult, id: r.id, ok: false });
  };

  const can = permissions.length
    ? `This page runs in a locked box and cannot see your account. It can ${permissions.map((p) => permissionSentence[p]).join(" and ")}, and it asks you every time.`
    : "This page runs in a locked box and cannot see your account. It can only show things on this screen.";

  return (
    <section aria-label={l.generatedBy(source)} className={cn("min-w-0 rounded-card border border-line bg-surface p-3", className)}>
      <header className="flex flex-wrap items-center gap-2 px-1 pb-2">
        <Badge><ShieldIcon width={11} height={11} aria-hidden />{l.generatedBy(source)}</Badge>
        {permissions.length > 0 && (
          <ul aria-label="Permissions this page asked for" className="flex flex-wrap gap-1">
            {permissions.map((p) => <li key={p}><Badge tone="info">{permissionText[p]}</Badge></li>)}
          </ul>
        )}
      </header>
      <iframe
        ref={frame} title={title} sandbox={tokens} srcDoc={srcDoc} src={srcDoc === undefined ? src : undefined}
        referrerPolicy="no-referrer" allow="" loading="lazy" style={{ height }}
        className="block w-full max-w-full rounded-card border border-line bg-bg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--focus-ring)]"
      />
      {request && (
        <div role="group" aria-label="This page is asking for permission" className="mt-2 rounded-card bg-attention-soft p-3 text-body leading-5 text-attention-fg">
          <p className="font-medium">
            {request.kind === "link" ? "This page wants to open a link." : `This page wants the assistant to do this: ${request.name}.`}
          </p>
          {request.kind === "link" && <p className="mt-0.5 [overflow-wrap:anywhere]">{request.url}</p>}
          <div className="mt-2 flex flex-wrap gap-2">
            <Button size="sm" autoFocus onClick={allowRequest}>{l.allow}</Button>
            <Button size="sm" variant="secondary" onClick={denyRequest}>{l.deny}</Button>
          </div>
        </div>
      )}
      <p className="mt-2 px-1 text-small leading-4 text-fg-muted"><span className="font-medium text-fg">{l.whatItCanDo}.</span> {summary ?? can}</p>
    </section>
  );
}
```

## tattva/organisms/SceneStrip.tsx

```tsx
import { useEffect, useId, useRef, useState, type DragEvent, type KeyboardEvent } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, ApproveIcon, ChevronIcon, PlusIcon } from "../lib/icons";
import { headingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { Button } from "../atoms/Button";

/**
 * Where a shot is. `empty`: no take made yet. `generating`: the AI is making it. `needsPick`: takes are ready and a person
 * must choose one. `ready`: a take is chosen. `failed`: it did not work; say whether it was charged in `note`.
 */
export type SceneShotStatus = "empty" | "generating" | "needsPick" | "ready" | "failed";

export interface SceneShot {
  id: string;
  /** Short name, for example "Harbour at dawn". */
  label: string;
  /** What was asked for. Shown under the name. */
  prompt?: string;
  /** Length of the chosen take, in seconds. Leave out while there is none. */
  durationSec?: number;
  status: SceneShotStatus;
  /** Poster image of the chosen take. Decorative: the name says what the shot is. */
  poster?: string;
  /** A short extra line, for example "Not charged" on a failed shot or "3 takes" on one that needs a pick. */
  note?: string;
}

export interface SceneStripProps {
  shots: SceneShot[];
  /** Called with the new order after a move or a drop. */
  onReorder: (shots: SceneShot[]) => void;
  /** Adds a shot at the end. Leave out to hide Add shot. */
  onAdd?: () => void;
  /** Opens a shot to pick a take or trim it. */
  onOpen?: (id: string) => void;
  title?: string;
  headingLevel?: HeadingLevel;
  className?: string;
}

const clock = (sec: number) => {
  const s = Math.round(sec);
  return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
};
const spoken = (sec: number) => {
  const s = Math.round(sec);
  const m = Math.floor(s / 60);
  const r = s % 60;
  return [m ? `${m} ${m === 1 ? "minute" : "minutes"}` : "", r || !m ? `${r} ${r === 1 ? "second" : "seconds"}` : ""].filter(Boolean).join(" ");
};

function StatusLine({ shot }: { shot: SceneShot }) {
  const base = "inline-flex items-center gap-1.5 text-small leading-4.5 font-medium";
  if (shot.status === "generating") return <span className={cn(base, "text-fg")}><span aria-hidden className="size-2 shrink-0 rounded-full bg-lime" />Generating</span>;
  if (shot.status === "needsPick") return <span className={cn(base, "text-attention-fg")}><span aria-hidden className="size-1.5 shrink-0 rounded-full bg-attention" />Pick a take</span>;
  if (shot.status === "ready") return <span className={cn(base, "text-fg")}><ApproveIcon width={12} height={12} className="shrink-0" />Ready</span>;
  if (shot.status === "failed") return <span className={cn(base, "text-fg")}><AlertIcon width={12} height={12} className="shrink-0" />Did not work</span>;
  return <span className={cn(base, "text-fg-muted")}>No take yet</span>;
}

const statusWords: Record<SceneShotStatus, string> = { empty: "no take yet", generating: "generating", needsPick: "pick a take", ready: "ready", failed: "did not work" };

/**
 * An ordered storyboard of shots for one scene, with a total length and a status on every shot. Reorder by dragging a
 * card, with the Move earlier and Move later buttons on each card, or with Alt and the arrow keys on a focused card.
 * Each move is announced. Kept simpler than an editing timeline on purpose: order, status and length.
 */
export function SceneStrip({ shots, onReorder, onAdd, onOpen, title = "Scene", headingLevel = 3, className }: SceneStripProps) {
  const H = headingTag(headingLevel);
  const uid = useId();
  const [message, setMessage] = useState("");
  const [dragId, setDragId] = useState<string | null>(null);
  const [dropAt, setDropAt] = useState<number | null>(null);
  const cards = useRef(new Map<string, HTMLLIElement>());
  const buttons = useRef(new Map<string, HTMLButtonElement>());
  // After a move, where focus should go once the new order has rendered.
  const refocus = useRef<{ id: string; key: string } | null>(null);

  const total = shots.reduce((s, x) => s + (x.durationSec ?? 0), 0);
  const counts = {
    generating: shots.filter((s) => s.status === "generating").length,
    needsPick: shots.filter((s) => s.status === "needsPick").length,
    failed: shots.filter((s) => s.status === "failed").length,
  };
  const pending = [
    counts.generating ? `${counts.generating} generating` : "",
    counts.needsPick ? `${counts.needsPick} to pick` : "",
    counts.failed ? `${counts.failed} did not work` : "",
  ].filter(Boolean).join(", ");

  const move = (from: number, to: number, how: "button" | "key" | "drop", key?: string) => {
    if (to < 0 || to >= shots.length || from === to) return;
    const next = shots.slice();
    const [item] = next.splice(from, 1);
    next.splice(to, 0, item);
    onReorder(next);
    setMessage(`${item.label} moved to position ${to + 1} of ${shots.length}.`);
    if (how === "button" && key) {
      // Keep focus on the pressed button, or on its opposite when the shot reached an end and the button turned off.
      const atEnd = (key === "earlier" && to === 0) || (key === "later" && to === shots.length - 1);
      refocus.current = { id: item.id, key: atEnd ? (key === "earlier" ? "later" : "earlier") : key };
    } else if (how === "key") refocus.current = { id: item.id, key: "card" };
  };

  useEffect(() => {
    const r = refocus.current;
    if (!r) return;
    refocus.current = null;
    const el = r.key === "card" ? cards.current.get(r.id) : buttons.current.get(`${r.id}:${r.key}`);
    el?.focus({ preventScroll: false });
  }, [shots]);

  const onCardKey = (i: number, e: KeyboardEvent<HTMLLIElement>) => {
    if (e.target !== e.currentTarget || !e.altKey) return;
    if (e.key === "ArrowLeft" || e.key === "ArrowUp") { e.preventDefault(); move(i, i - 1, "key"); }
    else if (e.key === "ArrowRight" || e.key === "ArrowDown") { e.preventDefault(); move(i, i + 1, "key"); }
  };

  const onDragOver = (i: number, e: DragEvent<HTMLLIElement>) => {
    if (!dragId) return;
    e.preventDefault();
    const box = e.currentTarget.getBoundingClientRect();
    setDropAt(e.clientX < box.left + box.width / 2 ? i : i + 1);
  };
  const onDrop = (e: DragEvent) => {
    e.preventDefault();
    const from = shots.findIndex((s) => s.id === dragId);
    if (from >= 0 && dropAt !== null) move(from, dropAt > from ? dropAt - 1 : dropAt, "drop");
    setDragId(null); setDropAt(null);
  };

  const helpId = `${uid}-help`;

  return (
    <section aria-labelledby={`${uid}-h`} className={cn("w-full min-w-0 space-y-3 rounded-card border border-line bg-surface p-4", className)}>
      <header className="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1">
        <H id={`${uid}-h`} className="text-lead leading-6 font-semibold text-fg">{title}</H>
        <p className="text-small leading-4.5 text-fg-muted tabular-nums">
          <span className="font-medium text-fg">{clock(total)}</span><span className="sr-only"> ({spoken(total)})</span> total · {shots.length} {shots.length === 1 ? "shot" : "shots"}
          {pending && <> · {pending}</>}
        </p>
      </header>
      {shots.length > 1 && <p id={helpId} className="text-small leading-4.5 text-fg-subtle max-sm:hidden">Drag a shot, use its arrows, or press Alt and an arrow key on a focused shot to move it.</p>}

      {shots.length === 0
        ? <div className="flex flex-col items-start gap-3 rounded-card border border-dashed border-line p-4">
            <p className="text-compact text-fg-muted">No shots yet. Add your first shot to start the scene.</p>
            {onAdd && <Button size="sm" leading={<PlusIcon width={14} height={14} />} onClick={onAdd}>Add shot</Button>}
          </div>
        : <div className="-mx-1 flex snap-x snap-mandatory gap-3 overflow-x-auto px-1 pt-1 pb-2 [scrollbar-width:thin]">
          <ol aria-label={`${title} shots, in order`} onDragLeave={(e) => { if (!e.currentTarget.contains(e.relatedTarget as Node)) setDropAt(null); }}
            className="flex gap-3">
            {shots.map((s, i) => (
              <li key={s.id} ref={(el) => { if (el) cards.current.set(s.id, el); else cards.current.delete(s.id); }}
                tabIndex={0} aria-describedby={shots.length > 1 ? helpId : undefined}
                aria-label={`Shot ${i + 1} of ${shots.length}: ${s.label}, ${statusWords[s.status]}${s.durationSec ? `, ${spoken(s.durationSec)}` : ""}`}
                draggable onDragStart={(e) => { setDragId(s.id); e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", s.id); }}
                onDragEnd={() => { setDragId(null); setDropAt(null); }} onDragOver={(e) => onDragOver(i, e)} onDrop={onDrop}
                onKeyDown={(e) => onCardKey(i, e)}
                className={cn("relative flex w-44 shrink-0 snap-start flex-col overflow-hidden rounded-card border bg-surface outline-offset-2 sm:w-48",
                  "cursor-grab active:cursor-grabbing transition-opacity duration-(--dur-fast)",
                  s.status === "needsPick" ? "border-attention" : "border-line",
                  dragId === s.id && "opacity-50")}>
                {dropAt === i && <span aria-hidden className="absolute inset-y-0 -left-0.5 w-1 rounded-full bg-fg" />}
                {dropAt === i + 1 && i === shots.length - 1 && <span aria-hidden className="absolute inset-y-0 -right-0.5 w-1 rounded-full bg-fg" />}
                <div className="relative aspect-video bg-sunken">
                  {s.poster && <img src={s.poster} alt="" draggable={false} className="absolute inset-0 size-full object-cover" />}
                  <span className="absolute top-1.5 left-1.5 rounded-control bg-glass px-1.5 text-small leading-4.5 font-medium text-fg tabular-nums">{i + 1}</span>
                  {s.durationSec !== undefined && <span className="absolute right-1.5 bottom-1.5 rounded-control bg-glass px-1.5 text-small leading-4.5 text-fg tabular-nums">{clock(s.durationSec)}</span>}
                  {s.status === "generating" && <span aria-hidden className="absolute inset-x-0 bottom-0 h-0.5 bg-lime" />}
                </div>
                <div className="flex flex-1 flex-col gap-1 p-2.5">
                  <p className="truncate text-compact font-medium text-fg">{s.label}</p>
                  {s.prompt && <p className="line-clamp-2 text-small leading-4.5 text-fg-muted">{s.prompt}</p>}
                  <div className="mt-auto flex flex-wrap items-center gap-x-2 pt-1">
                    <StatusLine shot={s} />
                    {s.note && <span className="text-small leading-4.5 text-fg-muted">{s.note}</span>}
                  </div>
                </div>
                <div className="flex items-center gap-1 border-t border-line p-1.5">
                  <button type="button" aria-label={`Move ${s.label} earlier`} disabled={i === 0}
                    ref={(el) => { if (el) buttons.current.set(`${s.id}:earlier`, el); else buttons.current.delete(`${s.id}:earlier`); }}
                    onClick={() => move(i, i - 1, "button", "earlier")}
                    className="tap grid size-8 cursor-pointer place-items-center rounded-control text-fg-muted hover:bg-hover hover:text-fg disabled:cursor-not-allowed disabled:text-disabled-fg disabled:hover:bg-transparent">
                    <ChevronIcon className="rotate-180" />
                  </button>
                  <button type="button" aria-label={`Move ${s.label} later`} disabled={i === shots.length - 1}
                    ref={(el) => { if (el) buttons.current.set(`${s.id}:later`, el); else buttons.current.delete(`${s.id}:later`); }}
                    onClick={() => move(i, i + 1, "button", "later")}
                    className="tap grid size-8 cursor-pointer place-items-center rounded-control text-fg-muted hover:bg-hover hover:text-fg disabled:cursor-not-allowed disabled:text-disabled-fg disabled:hover:bg-transparent">
                    <ChevronIcon />
                  </button>
                  {onOpen && <Button size="sm" variant={s.status === "needsPick" ? "secondary" : "ghost"} className="ml-auto" aria-label={`${s.status === "needsPick" ? "Pick a take for" : "Open"} ${s.label}`}
                    onClick={() => onOpen(s.id)}>{s.status === "needsPick" ? "Pick" : "Open"}</Button>}
                </div>
              </li>
            ))}
          </ol>
            {onAdd && (
              <div className="flex w-32 shrink-0 snap-start">
                <button type="button" onClick={onAdd}
                  className="flex w-full cursor-pointer flex-col items-center justify-center gap-1.5 rounded-card border border-dashed border-line-strong text-compact font-medium text-fg-muted transition-colors duration-(--dur-fast) hover:bg-hover hover:text-fg">
                  <PlusIcon />Add shot
                </button>
              </div>
            )}
          </div>}
      <p role="status" aria-live="polite" className="sr-only">{message}</p>
    </section>
  );
}
```

## tattva/organisms/SectionLane.tsx

```tsx
import { useEffect, useId, useRef, useState, type KeyboardEvent } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, CheckIcon, ChevronIcon, LockIcon, PlusIcon, RegenerateIcon, EditIcon } from "../lib/icons";
import { headingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { Button } from "../atoms/Button";
import { WorkingEdge } from "../atoms/WorkingEdge";
import { Waveform, formatMediaTime, speakMediaTime, type WaveformRegion } from "../atoms/Waveform";

/**
 * Where a section is. `original`: as generated or recorded, nothing to do. `edited`: the person changed its lyrics or style
 * and it has not been generated yet. `stale`: an edit elsewhere means it no longer fits (for example the key or tempo moved).
 * `generating`: the AI is making it. `has-alternates`: new takes are ready and a person must keep one. `failed`: it did not work.
 */
export type SectionState = "original" | "edited" | "stale" | "generating" | "has-alternates" | "failed";

export interface SongSection {
  id: string;
  /** Plain name, such as "Verse 1" or "Chorus". */
  name: string;
  /** Start and end in seconds within the whole song. */
  start: number;
  end: number;
  /** Who made it. `ai` gets a small AI mark; recorded or uploaded parts get a word instead. */
  origin: "ai" | "person" | "upload";
  state: SectionState;
  /** Lyrics, shown in the detail panel of the selected section. */
  lyrics?: string;
  /** Style words for this section, such as "half-time drums". */
  styles?: string[];
  /** Number of new takes, for `has-alternates`. */
  alternates?: number;
  /** A short extra line, such as "Not charged" on a failed section or why it is out of date. */
  note?: string;
}

export interface SectionLaneProps {
  /** Sections in play order. */
  sections: SongSection[];
  /** Length of the whole song, in seconds. */
  duration: number;
  /** Loudness of the whole song for the waveform above the lane. Leave out to hide the waveform. */
  peaks?: number[] | null;
  /** Playhead, in seconds. */
  position?: number;
  /** Makes the waveform a seek slider. */
  onSeek?: (seconds: number) => void;
  /** The selected section, or null. */
  selectedId?: string | null;
  onSelect?: (id: string | null) => void;
  /** Called with the new order of ids after Move earlier, Move later or Alt with an arrow key. */
  onReorder?: (ids: string[]) => void;
  /** Regenerates one section. Staged: it changes nothing until Generate. */
  onRegenerate?: (id: string) => void;
  /** Adds a new section after this one (extend). */
  onExtend?: (id: string) => void;
  /** Opens the section's lyrics and styles for editing. */
  onEdit?: (id: string) => void;
  /** Opens the new takes of a section with alternates. */
  onOpenAlternates?: (id: string) => void;
  /** Tries a failed section again. */
  onRetry?: (id: string) => void;
  /** Changes waiting for Generate. Above 0 an amber bar shows the count and the Generate button. */
  pendingChanges?: number;
  /** Generates every staged change at once. */
  onGenerate?: () => void;
  /** Cost of that generation, shown next to the button, such as "about 20 credits". */
  generateCost?: string;
  /** Why Generate cannot run, such as "Not enough credits". Turns the button off and shows the reason. */
  generateBlocked?: string;
  title?: string;
  headingLevel?: HeadingLevel;
  className?: string;
}

const STATE_WORD: Record<SectionState, string> = {
  original: "Up to date", edited: "Edited, not generated", stale: "Out of date", generating: "Generating",
  "has-alternates": "New takes to compare", failed: "Did not work",
};
const ORIGIN_WORD = { ai: "Made by AI", person: "Recorded", upload: "Uploaded" } as const;
const needsYou = (s: SectionState) => s === "edited" || s === "stale" || s === "has-alternates";

function StateLine({ s }: { s: SongSection }) {
  const base = "inline-flex min-w-0 items-center gap-1.5 text-small leading-4.5 font-medium";
  if (s.state === "generating") return <span className={cn(base, "text-fg")}><span aria-hidden className="size-2 shrink-0 rounded-full bg-lime" />Generating</span>;
  if (s.state === "failed") return <span className={cn(base, "text-fg")}><AlertIcon width={12} height={12} aria-hidden className="shrink-0" />Did not work</span>;
  if (needsYou(s.state)) {
    const word = s.state === "has-alternates" ? `${s.alternates ?? 2} takes to compare` : STATE_WORD[s.state];
    return <span className={cn(base, "text-attention-fg")}><span aria-hidden className="size-1.5 shrink-0 rotate-45 bg-attention" />{word}</span>;
  }
  return <span className={cn(base, "font-normal text-fg-subtle")}>Up to date</span>;
}

function OriginMark({ origin }: { origin: SongSection["origin"] }) {
  if (origin === "ai") {
    return <span className="inline-flex items-center gap-1 text-small leading-4.5 text-fg-muted"><span aria-hidden className="size-2 shrink-0 rounded-full bg-lime ring-1 ring-fg-muted" />AI</span>;
  }
  return <span className="text-small leading-4.5 text-fg-muted">{ORIGIN_WORD[origin]}</span>;
}

/**
 * The structure of one generated song as labelled blocks on a timeline, sized to their length: select a section, regenerate
 * it, extend after it or move it. Edits are staged and shown in one amber bar until Generate, so several changes cost one
 * generation. Sections the AI made carry a small AI mark; sections that are out of date say so in words.
 */
export function SectionLane({
  sections, duration, peaks, position = 0, onSeek, selectedId = null, onSelect, onReorder, onRegenerate, onExtend, onEdit,
  onOpenAlternates, onRetry, pendingChanges = 0, onGenerate, generateCost, generateBlocked, title = "Song structure",
  headingLevel = 3, className,
}: SectionLaneProps) {
  const H = headingTag(headingLevel);
  const uid = useId();
  const [focusId, setFocusId] = useState<string | null>(selectedId ?? sections[0]?.id ?? null);
  const [message, setMessage] = useState("");
  const options = useRef(new Map<string, HTMLDivElement>());
  const buttons = useRef(new Map<string, HTMLButtonElement>());
  const refocus = useRef<{ id: string; key: string } | null>(null);

  const dur = Math.max(1, duration || sections.reduce((m, s) => Math.max(m, s.end), 0));
  const selected = sections.find((s) => s.id === selectedId) ?? null;
  const selectedIndex = selected ? sections.indexOf(selected) : -1;
  const activeId = sections.some((s) => s.id === focusId) ? focusId : selectedId ?? sections[0]?.id ?? null;
  const outOfDate = sections.filter((s) => s.state === "stale").length;

  // Announce the staged count only when it changes, not on first render.
  const lastPending = useRef(pendingChanges);
  useEffect(() => {
    if (lastPending.current === pendingChanges) return;
    lastPending.current = pendingChanges;
    setMessage(pendingChanges ? `${pendingChanges} ${pendingChanges === 1 ? "change" : "changes"} waiting to generate.` : "No changes waiting.");
  }, [pendingChanges]);

  // Announce sections that start generating, finish, fail or get new takes. The lane's own status region says it,
  // because a status region cannot sit inside the listbox.
  const lastStates = useRef(new Map(sections.map((s) => [s.id, s.state])));
  useEffect(() => {
    const said: string[] = [];
    for (const s of sections) {
      const was = lastStates.current.get(s.id);
      if (was && was !== s.state && s.state === "generating") said.push(`Generating ${s.name}.`);
      else if (was && was !== s.state && (s.state === "failed" || s.state === "has-alternates" || was === "generating")) {
        said.push(`${s.name}: ${s.state === "has-alternates" ? `${s.alternates ?? 2} takes ready` : STATE_WORD[s.state].toLowerCase()}.`);
      }
    }
    lastStates.current = new Map(sections.map((s) => [s.id, s.state]));
    if (said.length) setMessage(said.join(" "));
  }, [sections]);

  useEffect(() => {
    const r = refocus.current;
    if (!r) return;
    refocus.current = null;
    (r.key === "option" ? options.current.get(r.id) : buttons.current.get(`${r.id}:${r.key}`))?.focus();
  }, [sections]);

  const move = (from: number, to: number, how: "button" | "key", key?: string) => {
    if (!onReorder || to < 0 || to >= sections.length || from === to) return;
    const ids = sections.map((s) => s.id);
    const [id] = ids.splice(from, 1);
    ids.splice(to, 0, id);
    onReorder(ids);
    const name = sections[from].name;
    setMessage(`${name} moved to position ${to + 1} of ${sections.length}.`);
    if (how === "key") refocus.current = { id, key: "option" };
    else if (key) {
      const atEnd = (key === "earlier" && to === 0) || (key === "later" && to === sections.length - 1);
      refocus.current = { id, key: atEnd ? (key === "earlier" ? "later" : "earlier") : key };
    }
  };

  const focusOption = (id: string) => { setFocusId(id); options.current.get(id)?.focus(); };
  const onLaneKey = (e: KeyboardEvent<HTMLDivElement>) => {
    const i = sections.findIndex((s) => s.id === activeId);
    if (i < 0) return;
    const k = e.key;
    if (e.altKey && (k === "ArrowLeft" || k === "ArrowUp")) { e.preventDefault(); move(i, i - 1, "key"); return; }
    if (e.altKey && (k === "ArrowRight" || k === "ArrowDown")) { e.preventDefault(); move(i, i + 1, "key"); return; }
    if (e.altKey || e.ctrlKey || e.metaKey) return;
    let to = -1;
    if (k === "ArrowRight" || k === "ArrowDown") to = Math.min(sections.length - 1, i + 1);
    else if (k === "ArrowLeft" || k === "ArrowUp") to = Math.max(0, i - 1);
    else if (k === "Home") to = 0;
    else if (k === "End") to = sections.length - 1;
    else if (k === "Enter" || k === " ") { e.preventDefault(); onSelect?.(sections[i].id === selectedId ? null : sections[i].id); return; }
    else if (k === "Escape" && selectedId) { e.preventDefault(); onSelect?.(null); return; }
    else return;
    e.preventDefault();
    focusOption(sections[to].id);
  };

  const regions: WaveformRegion[] = sections.flatMap((s): WaveformRegion[] => {
    if (s.id === selectedId) return [{ start: s.start, end: s.end, kind: "selected" }];
    if (s.state === "stale" || s.state === "edited") return [{ start: s.start, end: s.end, kind: "stale" }];
    if (s.origin === "ai") return [{ start: s.start, end: s.end, kind: "ai" }];
    return [];
  });

  const setBtn = (key: string) => (el: HTMLButtonElement | null) => { if (el) buttons.current.set(key, el); else buttons.current.delete(key); };
  const helpId = `${uid}-help`;
  const sizeBtn = "pointer-coarse:h-11";

  return (
    <section aria-labelledby={`${uid}-h`} className={cn("@container w-full min-w-0 space-y-3 rounded-card border border-line bg-surface p-4", className)}>
      <header className="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1">
        <H id={`${uid}-h`} className="text-lead leading-6 font-semibold text-fg">{title}</H>
        <p className="text-small leading-4.5 text-fg-muted tabular-nums">
          <span className="font-medium text-fg">{formatMediaTime(dur)}</span><span className="sr-only"> ({speakMediaTime(dur)})</span> · {sections.length} {sections.length === 1 ? "section" : "sections"}
          {outOfDate > 0 && <> · <span className="text-attention-fg">{outOfDate} out of date</span></>}
        </p>
      </header>

      {pendingChanges > 0 && (
        <div className="flex flex-wrap items-center gap-x-3 gap-y-2 rounded-card border border-attention bg-attention-soft px-3 py-2">
          <p className="flex min-w-0 flex-[1_1_14rem] items-center gap-2 text-compact text-fg">
            <span aria-hidden className="size-2 shrink-0 rotate-45 bg-attention" />
            <span><span className="font-medium">{pendingChanges} {pendingChanges === 1 ? "change" : "changes"}.</span> Generate to hear {pendingChanges === 1 ? "it" : "them"}. Nothing changes until then.</span>
          </p>
          {onGenerate && (
            <div className="ml-auto flex items-center gap-2">
              {(generateBlocked || generateCost) && <span className="text-small leading-4.5 text-fg-muted">{generateBlocked ?? generateCost}</span>}
              <Button variant="lime" size="sm" className={sizeBtn} disabled={!!generateBlocked}
                leading={generateBlocked ? <LockIcon width={14} height={14} aria-hidden /> : <RegenerateIcon width={14} height={14} aria-hidden />}
                onClick={onGenerate}>Generate</Button>
            </div>
          )}
        </div>
      )}

      {peaks !== undefined && (
        <Waveform peaks={peaks} duration={dur} position={position} onSeek={onSeek} label={onSeek ? `Seek, ${title}` : undefined} regions={regions} height={48} />
      )}

      {sections.length === 0 ? (
        <p className="rounded-card border border-dashed border-line p-4 text-compact text-fg-muted">No sections yet. Generate a song to see its structure.</p>
      ) : (
        <>
          <p id={helpId} className="text-small leading-4.5 text-fg-subtle max-sm:hidden">Arrow keys move between sections, Enter selects one, Alt with an arrow moves it.</p>
          <div className="-mx-1 overflow-x-auto px-1 pt-1 pb-2 [scrollbar-width:thin]">
            <div role="listbox" aria-label={`${title}, sections in order`} aria-orientation="horizontal" aria-describedby={helpId}
              onKeyDown={onLaneKey} className="flex min-w-full gap-1.5">
              {sections.map((s, i) => {
                const len = Math.max(0, s.end - s.start);
                const isSel = s.id === selectedId;
                const label = `${s.name}, ${i + 1} of ${sections.length}, ${formatMediaTime(s.start)} to ${formatMediaTime(s.end)}, ${ORIGIN_WORD[s.origin]}, ${s.state === "has-alternates" ? `${s.alternates ?? 2} takes to compare` : STATE_WORD[s.state]}`;
                return (
                  <WorkingEdge key={s.id} active={s.state === "generating"} label={`Generating ${s.name}`} radius="card" announce={false}
                    className="flex min-w-28 shrink-0 transition-[flex-grow] duration-(--dur-fast) ease-(--ease-out)"
                    style={{ flexGrow: len, flexBasis: 0 }}>
                    <div role="option" aria-selected={isSel} tabIndex={s.id === activeId ? 0 : -1} aria-label={label}
                      ref={(el) => { if (el) options.current.set(s.id, el); else options.current.delete(s.id); }}
                      onFocus={() => setFocusId(s.id)}
                      onClick={() => { setFocusId(s.id); onSelect?.(isSel ? null : s.id); }}
                      className={cn("relative flex w-full min-w-0 cursor-pointer flex-col gap-1 rounded-card border bg-surface px-2.5 py-2 text-left outline-offset-2 transition-colors duration-(--dur-fast) hover:bg-hover pointer-coarse:min-h-11",
                        isSel ? "border-fg ring-1 ring-fg" : needsYou(s.state) ? "border-dashed border-attention" : "border-line",
                        s.state === "failed" && "bg-sunken")}>
                      <span className="flex min-w-0 items-center gap-1">
                        {isSel && <CheckIcon width={14} height={14} strokeWidth={3} aria-hidden className="shrink-0 text-fg" />}
                        <span className="min-w-0 flex-1 truncate text-compact font-medium text-fg">{s.name}</span>
                      </span>
                      <span className="flex flex-wrap items-center gap-x-2 text-small leading-4.5 text-fg-muted tabular-nums">
                        {formatMediaTime(s.start)}–{formatMediaTime(s.end)}
                        <OriginMark origin={s.origin} />
                      </span>
                      <StateLine s={s} />
                    </div>
                  </WorkingEdge>
                );
              })}
            </div>
          </div>
        </>
      )}

      {selected && (
        <div role="group" aria-labelledby={`${uid}-sel`} className="space-y-2.5 rounded-card border border-line bg-sunken p-3">
          <div className="flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1">
            <p id={`${uid}-sel`} className="text-compact font-semibold text-fg">{selected.name} <span className="font-normal text-fg-muted tabular-nums">· {formatMediaTime(selected.start)} to {formatMediaTime(selected.end)}</span></p>
            <span className="flex items-center gap-3"><OriginMark origin={selected.origin} /><StateLine s={selected} /></span>
          </div>
          {selected.note && <p className="text-small leading-4.5 text-fg-muted">{selected.note}</p>}
          {selected.lyrics && <p className="line-clamp-3 text-compact whitespace-pre-line text-fg">{selected.lyrics}</p>}
          {selected.styles && selected.styles.length > 0 && <p className="text-small leading-4.5 text-fg-muted">Style: {selected.styles.join(", ")}</p>}
          <div className="flex flex-wrap items-center gap-1.5">
            {selected.state === "failed" && onRetry && (
              <Button size="sm" variant="secondary" className={sizeBtn} onClick={() => onRetry(selected.id)} aria-label={`Try ${selected.name} again`}>Try again</Button>
            )}
            {selected.state === "has-alternates" && onOpenAlternates && (
              <Button size="sm" variant="secondary" className={sizeBtn} onClick={() => onOpenAlternates(selected.id)} aria-label={`Compare new takes of ${selected.name}`}>Compare takes</Button>
            )}
            {onRegenerate && selected.state !== "failed" && (
              <Button size="sm" variant="secondary" className={sizeBtn} disabled={selected.state === "generating"} leading={<RegenerateIcon width={14} height={14} aria-hidden />}
                onClick={() => onRegenerate(selected.id)} aria-label={`Regenerate ${selected.name}`}>Regenerate</Button>
            )}
            {onExtend && (
              <Button size="sm" variant="ghost" className={sizeBtn} leading={<PlusIcon width={14} height={14} aria-hidden />}
                onClick={() => onExtend(selected.id)} aria-label={`Extend after ${selected.name}`}>Extend after</Button>
            )}
            {onEdit && (
              <Button size="sm" variant="ghost" className={sizeBtn} leading={<EditIcon width={14} height={14} aria-hidden />}
                onClick={() => onEdit(selected.id)} aria-label={`Edit lyrics and style of ${selected.name}`}>Edit</Button>
            )}
            {onReorder && sections.length > 1 && (
              <span className="ml-auto flex items-center gap-1">
                <button type="button" ref={setBtn(`${selected.id}:earlier`)} aria-label={`Move ${selected.name} earlier`} disabled={selectedIndex === 0}
                  onClick={() => move(selectedIndex, selectedIndex - 1, "button", "earlier")}
                  className="tap grid size-8 cursor-pointer place-items-center rounded-control text-fg-muted hover:bg-hover hover:text-fg disabled:cursor-not-allowed disabled:text-disabled-fg disabled:hover:bg-transparent pointer-coarse:size-11">
                  <ChevronIcon aria-hidden className="rotate-180" />
                </button>
                <button type="button" ref={setBtn(`${selected.id}:later`)} aria-label={`Move ${selected.name} later`} disabled={selectedIndex === sections.length - 1}
                  onClick={() => move(selectedIndex, selectedIndex + 1, "button", "later")}
                  className="tap grid size-8 cursor-pointer place-items-center rounded-control text-fg-muted hover:bg-hover hover:text-fg disabled:cursor-not-allowed disabled:text-disabled-fg disabled:hover:bg-transparent pointer-coarse:size-11">
                  <ChevronIcon aria-hidden />
                </button>
              </span>
            )}
          </div>
        </div>
      )}

      <p role="status" aria-live="polite" className="sr-only">{message}</p>
    </section>
  );
}
```

## tattva/organisms/Sections.tsx

```tsx
import { useEffect, useRef, useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import type { HeadingLevel } from "../lib/useFocusAfter";
import { Collapsible } from "../molecules/Collapsible";

export interface Section {
  /** Stays the same while the answer streams, so a section keeps its open state. */
  id: string;
  title: string;
  /** A short line shown next to the title, such as "3 options". */
  meta?: string;
  content: ReactNode;
}

export interface SectionsProps {
  sections: Section[];
  /** The answer is still arriving. Each new section opens as it arrives; when the answer ends, the first one stays open. */
  streaming?: boolean;
  /** Level of each section's heading. Defaults to 3. */
  headingLevel?: HeadingLevel;
  className?: string;
}

/**
 * A long answer in foldable sections. While the answer streams, each new section opens as it arrives so people can
 * follow along. When it ends, the sections fold back to the first one. Once the person opens or closes a section
 * themselves, the answer stops moving things and leaves their choice alone.
 */
export function Sections({ sections, streaming = false, headingLevel = 3, className }: SectionsProps) {
  const [open, setOpen] = useState<Set<string>>(() => new Set(sections[0] ? [sections[0].id] : []));
  const touched = useRef(false);
  const last = sections[sections.length - 1]?.id;
  const first = sections[0]?.id;

  // While streaming, follow the newest section; when the stream ends, settle on the first. Never after a person acts.
  useEffect(() => {
    if (touched.current) return;
    if (streaming && last) setOpen(new Set([last]));
  }, [streaming, last]);
  useEffect(() => {
    if (touched.current) return;
    if (!streaming && first) setOpen(new Set([first]));
  }, [streaming, first]);

  const toggle = (id: string, value: boolean) => {
    touched.current = true;
    setOpen((o) => { const n = new Set(o); if (value) n.add(id); else n.delete(id); return n; });
  };

  return (
    <div className={cn("divide-y divide-line rounded-card border border-line bg-surface", className)} aria-busy={streaming || undefined}>
      {sections.map((s) => (
        <Collapsible key={s.id} open={open.has(s.id)} onOpenChange={(v) => toggle(s.id, v)} headingLevel={headingLevel}
          className="px-4 py-3"
          header={
            <span className="flex min-w-0 flex-1 items-baseline justify-between gap-3">
              <span className="min-w-0 text-lead leading-6 font-semibold text-fg">{s.title}</span>
              {s.meta && <span className="shrink-0 text-body leading-5 text-fg-muted">{s.meta}</span>}
            </span>
          }>
          <div className="pt-2 pl-6 text-body leading-6 text-fg">{s.content}</div>
        </Collapsible>
      ))}
    </div>
  );
}
```

## tattva/organisms/SharePanel.tsx

```tsx
import { useId, useState } from "react";
import { cn } from "../lib/cn";
import { headingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { CheckIcon, CopyIcon, GlobeIcon, LockIcon, UsersIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { RadioGroup } from "../molecules/RadioGroup";
import { InlineConfirm } from "../molecules/InlineConfirm";

export type ShareAccess = "private" | "workspace" | "link";

export interface SharePanelLabels {
  heading?: string;
  access?: string;
  options?: Partial<Record<ShareAccess, { label: string; description: string }>>;
  link?: string;
  copy?: string;
  copied?: string;
  /** What a shared link shows, in one line. */
  scope?: string;
  stop?: string;
  stopQuestion?: string;
  keep?: string;
}

export interface SharePanelProps {
  /** What is being shared, such as the chat's title. */
  title: string;
  /** Who can open it now. */
  access: ShareAccess;
  onAccessChange: (access: ShareAccess) => void;
  /** The link, once there is one. */
  link?: string;
  /** Copies the link. Defaults to the clipboard. */
  onCopy?: (link: string) => void;
  /** Turns the link off; anyone who has it can no longer open it. */
  onStopSharing?: () => void;
  /** Hide the workspace choice for people who are not in one. */
  showWorkspace?: boolean;
  labels?: SharePanelLabels;
  /** Level of the part's heading, so it fits the page outline. Default 2, for use in a dialog or sheet. */
  headingLevel?: HeadingLevel;
  className?: string;
}

const OPTIONS: Record<ShareAccess, { label: string; description: string }> = {
  private: { label: "Only you", description: "Nobody else can open it." },
  workspace: { label: "People in your workspace", description: "Anyone signed in to your workspace with the link." },
  link: { label: "Anyone with the link", description: "No sign-in needed. Think before sharing private details." },
};

/**
 * Who can open a chat or a result, and its link. Put it in a Dialog or a Sheet. The choice reads as plain sentences,
 * the link is one press to copy (and says Copied), a line says exactly what the link shows, and stopping sharing
 * asks once in place.
 */
export function SharePanel({ title, access, onAccessChange, link, onCopy, onStopSharing, showWorkspace = true, labels, headingLevel = 2, className }: SharePanelProps) {
  const l = {
    heading: "Share", access: "Who can open it", link: "Link", copy: "Copy link", copied: "Copied",
    scope: "The link shows this chat up to now. Messages you send later are not shared.",
    stop: "Stop sharing", stopQuestion: "Stop sharing? The link stops working.", keep: "Keep sharing", ...labels,
    options: { ...OPTIONS, ...labels?.options },
  };
  const [copied, setCopied] = useState(false);
  const uid = useId();
  const H = headingTag(headingLevel);
  const ICON = { private: LockIcon, workspace: UsersIcon, link: GlobeIcon };
  const copy = () => {
    if (!link) return;
    if (onCopy) onCopy(link); else void navigator.clipboard?.writeText(link).catch(() => {});
    setCopied(true); window.setTimeout(() => setCopied(false), 1800);
  };
  const keys = (Object.keys(OPTIONS) as ShareAccess[]).filter((k) => showWorkspace || k !== "workspace");
  return (
    <section aria-labelledby={`${uid}-h`} className={cn("flex flex-col gap-5", className)}>
      <div>
        <H id={`${uid}-h`} className="m-0 text-title-sm leading-7 font-semibold text-fg">{l.heading}</H>
        <p className="m-0 mt-0.5 truncate text-small leading-5 text-fg-muted" title={title}>{title}</p>
      </div>
      <RadioGroup label={l.access} value={access} onChange={(v) => onAccessChange(v as ShareAccess)}
        options={keys.map((k) => {
          const Icon = ICON[k];
          return { value: k, label: <span className="inline-flex items-center gap-2"><Icon aria-hidden width={15} height={15} className="text-fg-muted" />{l.options[k].label}</span>, description: l.options[k].description };
        })} />
      {access !== "private" && link && (
        <div className="space-y-2">
          <label htmlFor={`${uid}-link`} className="block text-small leading-5 font-medium text-fg">{l.link}</label>
          <div className="flex gap-2 max-sm:flex-col">
            <input id={`${uid}-link`} readOnly value={link} onFocus={(e) => e.currentTarget.select()} aria-describedby={`${uid}-scope`}
              className="h-10 min-w-0 flex-1 rounded-field border border-line bg-sunken px-3 font-mono text-small text-fg" />
            <Button onClick={copy} variant="secondary" className="h-10 shrink-0" leading={copied ? <CheckIcon aria-hidden width={14} height={14} /> : <CopyIcon aria-hidden width={14} height={14} />}>{copied ? l.copied : l.copy}</Button>
          </div>
          <p id={`${uid}-scope`} className="m-0 text-small leading-5 text-fg-muted">{l.scope}</p>
          <span role="status" className="sr-only">{copied ? l.copied : ""}</span>
        </div>
      )}
      {access !== "private" && link && onStopSharing && (
        <InlineConfirm className="self-start" onConfirm={onStopSharing} confirmLabel={l.stop} cancelLabel={l.keep} question={l.stopQuestion} tone="danger">{l.stop}</InlineConfirm>
      )}
    </section>
  );
}
```

## tattva/organisms/Sheet.tsx

```tsx
import { useEffect, useId, useRef, useState, type PointerEvent as ReactPointerEvent, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { XIcon } from "../lib/icons";
import { IconButton } from "../atoms/Button";

export type SheetSide = "bottom" | "right" | "left";
export type SheetSize = "sm" | "md" | "lg";

export interface SheetProps {
  open: boolean;
  /** Called when the sheet asks to close: Escape, a scrim tap, the Close control or a drag down. Set `open` to false in response. */
  onClose: () => void;
  /** Required. Rendered as the heading and used as the sheet's name. */
  title: string;
  /** Which edge the sheet slides in from. */
  side?: SheetSide;
  children?: ReactNode;
  /** Usually buttons, pinned under the body. */
  footer?: ReactNode;
  /** When false, Escape, a scrim tap and dragging do nothing and there is no Close control. Only your own buttons can close it. */
  dismissible?: boolean;
  /** Width for side sheets. Height cap for bottom sheets. */
  size?: SheetSize;
  className?: string;
}

const FOCUSABLE = 'a[href],button:not([disabled]),input:not([disabled]):not([type="hidden"]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
const CLOSE_DISTANCE = 80;
const FLICK_SPEED = 0.6; // px per ms
const TAP_SLOP = 6;
const EXIT_MS = 320; // a little under --dur-slow, so the sheet is gone before the next thing can happen

const heights: Record<SheetSize, string> = { sm: "max-h-[40dvh]", md: "max-h-[65dvh]", lg: "max-h-[90dvh]" };
const widths: Record<SheetSize, string> = { sm: "max-w-[min(24rem,calc(100%-2.5rem))]", md: "max-w-[min(30rem,calc(100%-2.5rem))]", lg: "max-w-[min(42rem,calc(100%-2.5rem))]" };
const place: Record<SheetSide, string> = { bottom: "items-end justify-center", right: "items-stretch justify-end", left: "items-stretch justify-start" };
const hidden: Record<SheetSide, string> = { bottom: "translate-y-full", right: "translate-x-full", left: "-translate-x-full" };

/**
 * A surface that slides over the page, on the native <dialog> element, so the page behind is inert and Tab stays inside.
 * A bottom sheet has a drag handle that is also a Close button, so dragging is never the only way out.
 * Focus goes to the first control in the body or footer (or the sheet) and returns to the opener on close.
 */
export function Sheet({ open, onClose, title, side = "bottom", children, footer, dismissible = true, size = "md", className }: SheetProps) {
  const ref = useRef<HTMLDialogElement>(null);
  const opener = useRef<HTMLElement | null>(null);
  const openRef = useRef(open);
  openRef.current = open;
  const id = useId();
  const titleId = `${id}-title`;
  // `present` outlasts `open` by the length of the slide-out, so the sheet leaves as smoothly as it arrives.
  const [present, setPresent] = useState(open);
  const [shown, setShown] = useState(false);
  const [drag, setDrag] = useState<number | null>(null);
  const gesture = useRef({ y: 0, t: 0, dy: 0, moved: false });
  const bottom = side === "bottom";

  useEffect(() => {
    if (open) { setPresent(true); return; }
    if (!present) return;
    setShown(false);
    const reduce = typeof window !== "undefined" && !!window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
    const t = window.setTimeout(() => setPresent(false), reduce ? 0 : EXIT_MS);
    return () => window.clearTimeout(t);
  }, [open, present]);

  useEffect(() => {
    const d = ref.current;
    if (!d || !present) return;
    opener.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
    if (!d.open) d.showModal();
    const root = document.documentElement;
    const prevOverflow = root.style.overflow;
    root.style.overflow = "hidden";
    const first = d.querySelector<HTMLElement>(`[data-sheet-body] :is(${FOCUSABLE}), [data-sheet-footer] :is(${FOCUSABLE})`);
    (first ?? d).focus({ preventScroll: true });
    // Two frames, so the off-screen position is painted once before the slide starts.
    let f2 = 0;
    const f1 = requestAnimationFrame(() => { f2 = requestAnimationFrame(() => setShown(true)); });
    return () => {
      cancelAnimationFrame(f1); cancelAnimationFrame(f2);
      setShown(false); setDrag(null);
      root.style.overflow = prevOverflow;
      if (d.open) d.close();
      const back = opener.current;
      if (back && back.isConnected) back.focus({ preventScroll: true });
      opener.current = null;
    };
  }, [present]);

  const down = (e: ReactPointerEvent<HTMLButtonElement>) => {
    if (e.button !== 0) return;
    try { e.currentTarget.setPointerCapture(e.pointerId); } catch { /* the drag still works without capture */ }
    gesture.current = { y: e.clientY, t: e.timeStamp, dy: 0, moved: false };
    setDrag(0);
  };
  const move = (e: ReactPointerEvent<HTMLButtonElement>) => {
    if (drag === null) return;
    const dy = Math.max(0, e.clientY - gesture.current.y);
    gesture.current.dy = dy;
    if (dy > TAP_SLOP) gesture.current.moved = true;
    setDrag(dy);
  };
  const up = (e: ReactPointerEvent<HTMLButtonElement>) => {
    if (drag === null) return;
    const { dy, t } = gesture.current;
    const speed = dy / Math.max(1, e.timeStamp - t);
    setDrag(null);
    if (dy > CLOSE_DISTANCE || (dy > 24 && speed > FLICK_SPEED)) onClose();
  };

  const offset = !shown ? undefined : drag ? { transform: `translateY(${drag}px)` } : undefined;
  return (
    <dialog ref={ref} tabIndex={-1} aria-labelledby={titleId}
      // Escape is turned into a request, so the parent stays the one source of truth for `open`.
      onCancel={(e) => { e.preventDefault(); if (dismissible) onClose(); }}
      onClose={() => { if (openRef.current) onClose(); }}
      onClick={(e) => { if (dismissible && e.target === e.currentTarget) onClose(); }}
      className={cn("fixed inset-0 m-0 h-full max-h-none w-full max-w-none bg-transparent p-0 text-fg backdrop:bg-[var(--scrim)] open:flex", place[side])}>
      {present && (
        <div style={offset}
          className={cn(
            "flex min-h-0 w-full flex-col overflow-hidden bg-raised shadow-lg forced-colors:border-[CanvasText]",
            bottom ? cn("max-w-2xl rounded-t-overlay border border-b-0 border-line", heights[size]) : cn("h-full border-line", widths[size], side === "right" ? "border-l" : "border-r"),
            !shown && hidden[side],
            drag === null && "motion-safe:transition-transform motion-safe:duration-[var(--dur-slow)] motion-safe:ease-[var(--ease-out)]",
            className,
          )}>
          {bottom && (dismissible ? (
            <button type="button" aria-label="Close" onClick={() => { if (!gesture.current.moved) onClose(); gesture.current.moved = false; }}
              onPointerDown={down} onPointerMove={move} onPointerUp={up} onPointerCancel={() => setDrag(null)}
              className="flex h-7 w-full shrink-0 cursor-grab touch-none items-center justify-center active:cursor-grabbing pointer-coarse:h-11">
              <span aria-hidden className="h-1 w-10 rounded-full bg-line-strong forced-colors:bg-[CanvasText]" />
            </button>
          ) : (
            <div aria-hidden className="flex h-7 shrink-0 items-center justify-center"><span className="h-1 w-10 rounded-full bg-line-strong forced-colors:bg-[CanvasText]" /></div>
          ))}
          <div className={cn("flex items-start gap-3 px-5 pb-3", bottom ? "pt-1" : "pt-5")}>
            <h2 id={titleId} className="min-w-0 flex-1 text-subhead leading-snug font-semibold text-balance">{title}</h2>
            {!bottom && dismissible && (
              <IconButton label="Close" onClick={onClose} className="-mt-1 -mr-2 shrink-0 pointer-coarse:size-11">
                <XIcon aria-hidden width={16} height={16} />
              </IconButton>
            )}
          </div>
          {children != null && <div data-sheet-body tabIndex={0} className="min-h-0 flex-1 overflow-y-auto overscroll-contain px-5 py-2 text-body leading-5">{children}</div>}
          {footer != null && <div data-sheet-footer className="flex flex-col-reverse gap-2 border-t border-line px-5 py-4 pb-[max(1rem,var(--safe-bottom))] sm:flex-row sm:justify-end">{footer}</div>}
        </div>
      )}
    </dialog>
  );
}
```

## tattva/organisms/ShotSettings.tsx

```tsx
import { useId, useRef, type KeyboardEvent, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { CheckIcon, SparkleIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { Spinner } from "../atoms/Spinner";

/** One choice in a ShotSettings group. Set `unavailable` to keep it visible but not pickable, with the reason in words. */
export interface ShotOption {
  value: string;
  label: string;
  /** Short helper text, for example "Faster and cheaper". */
  description?: string;
  /** Why the chosen model cannot use this option, for example "Reel Lite makes up to 6 seconds". */
  unavailable?: string;
}

export type ShotQuality = "draft" | "final";

export interface ShotSettingsValue {
  model: string;
  /** Length of each take, as the value of one of `lengths`. */
  length: string;
  /** Aspect ratio, as the value of one of `aspects`, for example "16:9". */
  aspect: string;
  quality: ShotQuality;
  /** How many takes one press of Generate makes. Each take is charged. */
  outputs: number;
}

export interface ShotSettingsProps {
  value: ShotSettingsValue;
  onChange: (value: ShotSettingsValue) => void;
  /** Called with the value when Generate is pressed. Never called while a setting does not fit the model or credits are short. */
  onGenerate: (value: ShotSettingsValue) => void;
  models: ShotOption[];
  /** Lengths for the chosen model, for example 4 s, 6 s, 8 s. Mark the ones it cannot make as unavailable. */
  lengths: ShotOption[];
  /** Aspect ratios. Labels must be ratios like "16:9" so the shape glyph can be drawn. */
  aspects: ShotOption[];
  /** Draft and final. Defaults to both, with draft described as faster and cheaper. */
  qualities?: ShotOption[];
  /** Cost of one take with the current settings, worked out by your code. The total is outputs × this. */
  costPerOutput: number;
  /** Credits left. Leave out to hide the balance. */
  balance?: number;
  /** Unit after numbers. */
  unit?: string;
  /** Most takes per press. */
  maxOutputs?: number;
  /** Note shown under quality while Final is picked, nudging people to try a draft first. */
  draftNote?: string;
  /** Extra controls placed after the aspect ratio, such as a CameraMovePicker. */
  children?: ReactNode;
  /** Disables Generate and shows a spinner. Settings stay readable. */
  busy?: boolean;
  title?: string;
  className?: string;
}

const defaultQualities: ShotOption[] = [
  { value: "draft", label: "Draft", description: "Faster and cheaper, lower detail" },
  { value: "final", label: "Final", description: "Full detail" },
];

const fmt = (n: number) => n.toLocaleString("en-US");

function RatioGlyph({ ratio }: { ratio: string }) {
  const [w, h] = ratio.split(":").map(Number);
  if (!w || !h) return null;
  const scale = 12 / Math.max(w, h);
  return <span aria-hidden className="inline-block shrink-0 rounded-2xs border border-current" style={{ width: Math.max(3, w * scale), height: Math.max(3, h * scale) }} />;
}

/**
 * Single-choice chips. Unavailable options stay in view with aria-disabled and point to their written reason; arrow keys
 * skip them so a person cannot land on a choice that cannot be made. A selected value that has become unavailable keeps
 * its place and is marked, so nothing changes behind the person's back.
 */
function OptionGroup({ label, options, value, onChange, glyph, reasonId }: {
  label: string; options: ShotOption[]; value: string; onChange: (v: string) => void; glyph?: boolean; reasonId: string;
}) {
  const refs = useRef<(HTMLButtonElement | null)[]>([]);
  const usable = options.map((o, i) => (o.unavailable ? -1 : i)).filter((i) => i >= 0);
  const current = options.findIndex((o) => o.value === value);
  const stop = current >= 0 ? current : usable[0] ?? 0;
  const move = (i: number, e: KeyboardEvent) => {
    if (!usable.length) return;
    const at = usable.indexOf(i);
    let next: number;
    if (e.key === "ArrowRight" || e.key === "ArrowDown") next = usable[(at + 1) % usable.length];
    else if (e.key === "ArrowLeft" || e.key === "ArrowUp") next = usable[at <= 0 ? usable.length - 1 : at - 1];
    else if (e.key === "Home") next = usable[0];
    else if (e.key === "End") next = usable[usable.length - 1];
    else return;
    e.preventDefault();
    onChange(options[next].value);
    refs.current[next]?.focus();
  };
  return (
    <div role="radiogroup" aria-label={label} className="flex flex-wrap gap-1.5">
      {options.map((o, i) => {
        const on = o.value === value;
        const off = !!o.unavailable;
        return (
          <button key={o.value} ref={(el) => { refs.current[i] = el; }} type="button" role="radio" aria-checked={on} aria-disabled={off || undefined}
            aria-describedby={off ? reasonId : undefined} tabIndex={i === stop ? 0 : -1}
            onClick={() => { if (!off) onChange(o.value); }} onKeyDown={(e) => move(i, e)}
            className={cn("tap inline-flex min-h-8 items-center gap-1.5 rounded-control border px-3 py-1 text-small leading-4 font-medium transition-colors duration-(--dur-fast)",
              on ? "border-fg bg-surface text-fg shadow-sm" : "border-line text-fg-muted",
              off ? "cursor-not-allowed border-dashed" : "cursor-pointer hover:bg-hover hover:text-fg",
              off && !on && "opacity-(--disabled-opacity)")}>
            {on && <CheckIcon width={12} height={12} className="shrink-0" />}
            {glyph && <RatioGlyph ratio={o.label} />}
            {o.label}
            {off && <span className="sr-only">, not available</span>}
          </button>
        );
      })}
    </div>
  );
}

/** A labelled settings row. Reasons for unavailable options are written under the chips, never only in a tooltip. */
function Row({ id, label, hint, options, children, problem }: { id: string; label: string; hint?: ReactNode; options: ShotOption[]; children: ReactNode; problem?: string }) {
  const off = options.filter((o) => o.unavailable);
  return (
    <div className="space-y-1.5">
      <div className="flex flex-wrap items-baseline gap-x-2">
        <span className="text-small leading-4 font-medium text-fg">{label}</span>
        {hint && <span className="text-small leading-4.5 text-fg-subtle">{hint}</span>}
      </div>
      {children}
      {problem && <p className="text-small leading-4.5 font-medium text-danger-fg">{problem}</p>}
      {off.length > 0 && (
        <ul id={id} className="space-y-0.5 text-small leading-4.5 text-fg-muted">
          {off.map((o) => <li key={o.value}><span className="text-fg">{o.label}</span>: {o.unavailable}</li>)}
        </ul>
      )}
    </div>
  );
}

/**
 * Settings for one video generation: model, length, aspect ratio, draft or final, and how many takes. The cost reads as
 * takes × cost per take before Generate is pressed. Options the model cannot use stay visible with the reason written
 * out, and the model is never switched behind the person's back: if a chosen option stops fitting, Generate waits and
 * says which setting to change.
 */
export function ShotSettings({
  value, onChange, onGenerate, models, lengths, aspects, qualities = defaultQualities, costPerOutput, balance, unit = "credits",
  maxOutputs = 4, draftNote = "Drafts are faster and cheaper. You can make a final version of any draft later.", children, busy = false,
  title = "Shot settings", className,
}: ShotSettingsProps) {
  const uid = useId();
  const set = <K extends keyof ShotSettingsValue>(k: K, v: ShotSettingsValue[K]) => onChange({ ...value, [k]: v });

  const groups: { key: "model" | "length" | "aspect" | "quality"; label: string; options: ShotOption[] }[] = [
    { key: "model", label: "Model", options: models },
    { key: "length", label: "Length", options: lengths },
    { key: "aspect", label: "Aspect ratio", options: aspects },
    { key: "quality", label: "Quality", options: qualities },
  ];
  // A chosen option that the current model cannot use. Shown in words, never fixed silently.
  const conflicts = groups.flatMap((g) => {
    const o = g.options.find((x) => x.value === String(value[g.key]));
    return o?.unavailable ? [{ key: g.key, text: `${g.label} ${o.label} cannot be used: ${o.unavailable.charAt(0).toLowerCase()}${o.unavailable.slice(1)}. Pick another ${g.label.toLowerCase()}.` }] : [];
  });
  const problemFor = (k: string) => conflicts.find((c) => c.key === k)?.text;

  const outputs = Math.min(Math.max(1, value.outputs), maxOutputs);
  const total = outputs * costPerOutput;
  const short = balance !== undefined && total > balance;
  const blocked = conflicts.length > 0 || short;
  const takeWord = (n: number) => (n === 1 ? "take" : "takes");
  const costLine = `${outputs} ${takeWord(outputs)} × ${fmt(costPerOutput)} = ${fmt(total)} ${unit}`;
  const status = conflicts.length > 0 ? "Change the setting marked above to continue."
    : short ? `Not enough ${unit}: ${fmt(total)} needed, ${fmt(balance!)} left. Make fewer takes or a draft.`
    : balance !== undefined ? `${fmt(balance - total)} left after this` : "";
  const quality = qualities.find((q) => q.value === value.quality);
  const outputOptions: ShotOption[] = Array.from({ length: maxOutputs }, (_, i) => ({ value: String(i + 1), label: String(i + 1) }));

  return (
    <form aria-label={title} onSubmit={(e) => { e.preventDefault(); if (!busy && !blocked) onGenerate({ ...value, outputs }); }}
      className={cn("w-full min-w-0 space-y-4 rounded-card border border-line bg-surface p-4 shadow-sm", className)}>
      <h3 className="text-lead leading-6 font-semibold text-fg">{title}</h3>

      {groups.map((g) => (
        <Row key={g.key} id={`${uid}-${g.key}`} label={g.label} options={g.options} problem={problemFor(g.key)}
          hint={g.key === "quality" ? quality?.description : g.key === "model" ? models.find((m) => m.value === value.model)?.description : undefined}>
          <OptionGroup label={g.label} options={g.options} value={String(value[g.key])} reasonId={`${uid}-${g.key}`} glyph={g.key === "aspect"}
            onChange={(v) => (g.key === "quality" ? set("quality", v as ShotQuality) : set(g.key, v))} />
          {g.key === "quality" && value.quality === "final" && draftNote && <p className="text-small leading-4.5 text-fg-muted">{draftNote}</p>}
          {g.key === "aspect" && children}
        </Row>
      ))}

      <Row id={`${uid}-outputs`} label="Takes" hint={`Each take is charged. Up to ${maxOutputs}.`} options={[]}>
        <OptionGroup label="Number of takes" options={outputOptions} value={String(outputs)} reasonId={`${uid}-outputs`}
          onChange={(v) => set("outputs", Number(v))} />
      </Row>

      <div className="flex flex-wrap items-center justify-between gap-3 border-t border-line pt-3">
        <div role="status" aria-live="polite" className="min-w-0 flex-1 basis-48 space-y-0.5">
          <p className="text-compact font-medium text-fg tabular-nums">{costLine}</p>
          {status && <p className={cn("text-small leading-4.5 tabular-nums", blocked ? "text-fg" : "text-fg-muted")}>{status}</p>}
        </div>
        <Button type="submit" variant="lime" disabled={busy || blocked} className="max-sm:w-full"
          leading={busy ? <Spinner size={14} /> : <SparkleIcon width={14} height={14} />}>
          {busy ? "Generating…" : `Generate ${outputs} ${takeWord(outputs)}`}
        </Button>
      </div>
    </form>
  );
}
```

## tattva/organisms/SlideRail.tsx

```tsx
import { useEffect, useId, useRef, useState, useSyncExternalStore, type DragEvent, type KeyboardEvent, type MouseEvent, type ReactNode, type SVGProps } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, CheckIcon, InfoIcon, MoreIcon, PlusIcon, RetryIcon, UndoIcon } from "../lib/icons";
import { AIMark } from "../atoms/AIMark";
import { Button } from "../atoms/Button";
import { Skeleton } from "../atoms/Skeleton";
import { WorkingEdge } from "../atoms/WorkingEdge";
import { Menu, type MenuEntry } from "../molecules/Menu";
import { SlideCanvas, type SlideRatio } from "../molecules/SlideStage";

export type SlideStatus = "pending" | "generating" | "ready" | "edited" | "failed" | "stale" | "review";

export interface SlideRef {
  id: string;
  /** The slide's title. Screen readers move through a deck by these, so every slide needs one. */
  title: string;
  /** The slide drawn at its design size. The rail scales it down. Leave out to show the title on a plain card. */
  thumbnail?: ReactNode;
  status: SlideStatus;
  /** True when the AI wrote this slide. Shows a small AI mark until a person edits it. */
  aiMade?: boolean;
  /** Why it failed, or what to review, in plain words. Read with the slide's name. */
  error?: string;
}

export interface SlideRailProps {
  slides: SlideRef[];
  /** The slide on the stage. It holds keyboard focus inside the rail. */
  currentId: string;
  /** Slides chosen as the scope of an AI edit or a bulk action. Defaults to the current slide only. */
  selectedIds?: string[];
  onCurrentChange: (id: string) => void;
  /** Turns on multi-select: Shift with an arrow or click, Ctrl or Cmd with a click, Space and Ctrl+A. */
  onSelectionChange?: (ids: string[]) => void;
  /** Turns on reordering by drag, Alt with an arrow, and Move earlier or later. `to` is the slide's new index. */
  onReorder?: (from: number, to: number) => void;
  /** Shows Try again for a failed slide. */
  onRetry?: (id: string) => void;
  /** Shows Add slide. The new slide goes after this one. */
  onInsertAfter?: (id: string) => void;
  /** Shows Duplicate for the chosen slides. */
  onDuplicate?: (ids: string[]) => void;
  /** Shows Delete for the chosen slides. The rail always asks first. */
  onDelete?: (ids: string[]) => void;
  /** Shows Undo after an add, duplicate, delete or move, and on Ctrl+Z or Cmd+Z in the rail. Your app keeps the history. */
  onUndo?: () => void;
  /** "auto" is a column on wide screens and a sideways strip on phones. */
  orientation?: "vertical" | "horizontal" | "auto";
  /** Slide shape, used for the thumbnails. */
  ratio?: SlideRatio;
  /** Name of the list. */
  label?: string;
  className?: string;
}

const STATUS_WORDS: Record<SlideStatus, string> = {
  pending: "waiting", generating: "being written", ready: "ready", edited: "edited by you", failed: "failed", stale: "outline changed since it was built", review: "needs your review",
};

const PHONE = "(max-width: 599.98px)";
const subscribe = (cb: () => void) => {
  if (typeof window === "undefined" || !window.matchMedia) return () => {};
  const mq = window.matchMedia(PHONE);
  mq.addEventListener("change", cb);
  return () => mq.removeEventListener("change", cb);
};
const usePhone = () => useSyncExternalStore(subscribe, () => !!window.matchMedia?.(PHONE).matches, () => false);

function PersonIcon(p: SVGProps<SVGSVGElement>) {
  return <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden {...p}><circle cx="12" cy="8" r="3.5" /><path d="M5 20a7 7 0 0 1 14 0" /></svg>;
}

/** The state of one slide in words, with an icon. Colour only adds to it. */
function StatusLine({ status }: { status: SlideStatus }) {
  const base = "flex min-w-0 items-center gap-1 text-small leading-4";
  switch (status) {
    case "pending": return <span className={cn(base, "text-fg-subtle")}>Waiting</span>;
    case "generating": return <span className={cn(base, "text-fg-muted")}><span aria-hidden className="size-2 shrink-0 rounded-full bg-lime" />Writing…</span>;
    case "ready": return <span className={cn(base, "text-fg-subtle")}>Ready</span>;
    case "edited": return <span className={cn(base, "text-fg-muted")}><PersonIcon width={12} height={12} className="shrink-0" />Edited by you</span>;
    case "failed": return <span className={cn(base, "font-medium text-danger-fg")}><AlertIcon width={12} height={12} aria-hidden className="shrink-0" />Failed</span>;
    case "stale": return <span className={cn(base, "text-fg-muted")}><InfoIcon width={12} height={12} aria-hidden className="shrink-0" />Outline changed</span>;
    case "review": return <span className={cn(base, "font-medium text-attention-fg")}><span aria-hidden className="size-2 shrink-0 rounded-full bg-attention" />Needs review</span>;
  }
}

/**
 * The ordered strip of slide thumbnails. Each slide shows its state in words: waiting, writing, ready, edited by you,
 * failed, outline changed or needs review. People move around, choose the scope of an AI edit, reorder, add, duplicate
 * and delete, with Undo. A column on wide screens and a sideways strip on phones.
 */
export function SlideRail({
  slides, currentId, selectedIds, onCurrentChange, onSelectionChange, onReorder, onRetry, onInsertAfter, onDuplicate, onDelete, onUndo,
  orientation = "auto", ratio = "16:9", label = "Slides", className,
}: SlideRailProps) {
  const phone = usePhone();
  const horizontal = orientation === "horizontal" || (orientation === "auto" && phone);
  const list = useRef<HTMLDivElement>(null);
  const bar = useRef<HTMLDivElement>(null);
  const keepBtn = useRef<HTMLButtonElement>(null);
  const anchor = useRef(currentId);
  const focusList = useRef(false);
  const dragFrom = useRef<number | null>(null);
  const [drop, setDrop] = useState<number | null>(null);
  const [confirm, setConfirm] = useState<string[] | null>(null);
  const [undoNote, setUndoNote] = useState<string | null>(null);
  const [announce, setAnnounce] = useState("");
  const titleId = useId();

  const total = slides.length;
  const selected = selectedIds && selectedIds.length ? selectedIds : [currentId];
  const multi = onSelectionChange && selected.length > 1;
  const idx = (id: string) => slides.findIndex((s) => s.id === id);
  const cur = Math.max(0, idx(currentId));
  const current = slides[cur];
  const name = (i: number) => `slide ${i + 1}, ${slides[i]?.title ?? ""}`;
  const say = (text: string) => setAnnounce(text);

  // Status milestones only: a slide failing or needing review, and how many are ready. Never every change.
  const prev = useRef(new Map<string, SlideStatus>());
  useEffect(() => {
    const before = prev.current;
    const msgs: string[] = [];
    let readyGain = false;
    slides.forEach((s, i) => {
      const was = before.get(s.id);
      if (!was || was === s.status) return;
      if (s.status === "failed") msgs.push(`Slide ${i + 1} failed`);
      else if (s.status === "review") msgs.push(`Slide ${i + 1} needs your review`);
      else if (s.status === "ready" && (was === "generating" || was === "pending")) readyGain = true;
    });
    if (readyGain) {
      const done = slides.filter((s) => s.status !== "pending" && s.status !== "generating" && s.status !== "failed").length;
      msgs.push(done === total ? "All slides ready" : `${done} of ${total} slides ready`);
    }
    if (before.size && msgs.length) say(msgs.join(". "));
    prev.current = new Map(slides.map((s) => [s.id, s.status]));
  }, [slides, total]);

  // Keep focus on the current slide when it changes or moves, but only if focus was already in the list.
  useEffect(() => {
    const el = list.current?.querySelector<HTMLElement>(`[data-slide-id="${CSS.escape(currentId)}"]`);
    if (!el) return;
    // Scroll the rail only, never the page: scrollIntoView would also move every scrolling ancestor, the window included.
    const box = list.current;
    if (box) {
      const b = box.getBoundingClientRect(), r = el.getBoundingClientRect();
      if (r.top < b.top) box.scrollTop -= b.top - r.top; else if (r.bottom > b.bottom) box.scrollTop += r.bottom - b.bottom;
      if (r.left < b.left) box.scrollLeft -= b.left - r.left; else if (r.right > b.right) box.scrollLeft += r.right - b.right;
    }
    if (focusList.current) { focusList.current = false; el.focus(); }
  }, [currentId, slides]);

  useEffect(() => { if (confirm) keepBtn.current?.focus(); }, [confirm]);

  const backToList = () => { list.current?.querySelector<HTMLElement>(`[data-slide-id="${CSS.escape(currentId)}"]`)?.focus(); };

  const go = (i: number, extend: boolean) => {
    const j = Math.max(0, Math.min(total - 1, i));
    const id = slides[j].id;
    focusList.current = true;
    if (extend && onSelectionChange) {
      const a = Math.max(0, idx(anchor.current));
      const [lo, hi] = a < j ? [a, j] : [j, a];
      onSelectionChange(slides.slice(lo, hi + 1).map((s) => s.id));
    } else {
      anchor.current = id;
      onSelectionChange?.([id]);
    }
    onCurrentChange(id);
  };

  const move = (from: number, to: number) => {
    if (!onReorder || to < 0 || to >= total || from === to) return;
    const t = slides[from].title;
    focusList.current = true;
    onReorder(from, to);
    const text = `Slide ${from + 1}, ${t}, moved to position ${to + 1}`;
    say(text);
    setUndoNote(`${text}.`);
  };

  const duplicate = (ids: string[]) => {
    if (!onDuplicate) return;
    onDuplicate(ids);
    const text = ids.length === 1 ? `Slide ${idx(ids[0]) + 1} duplicated.` : `${ids.length} slides duplicated.`;
    say(text); setUndoNote(text);
  };

  const insert = () => {
    if (!onInsertAfter || !current) return;
    onInsertAfter(current.id);
    const text = `New slide added after slide ${cur + 1}.`;
    say(text); setUndoNote(text);
  };

  const askDelete = (ids: string[]) => { if (onDelete && ids.length) { setUndoNote(null); setConfirm(ids); } };
  const doDelete = () => {
    if (!confirm || !onDelete) return;
    const ids = confirm;
    const text = ids.length === 1 ? `Slide ${idx(ids[0]) + 1}, ${slides[idx(ids[0])]?.title}, deleted.` : `${ids.length} slides deleted.`;
    setConfirm(null);
    focusList.current = true;
    onDelete(ids);
    say(onUndo ? `${text} Undo is available.` : text);
    setUndoNote(text);
  };
  const cancelDelete = () => { setConfirm(null); backToList(); };

  const undo = () => {
    if (!onUndo) return;
    focusList.current = true;
    onUndo();
    setUndoNote(null);
    say("Undone");
  };

  const toggle = (id: string) => {
    if (!onSelectionChange) return;
    const has = selected.includes(id);
    const next = has ? selected.filter((x) => x !== id) : [...selected, id];
    onSelectionChange(next.length ? slides.filter((s) => next.includes(s.id)).map((s) => s.id) : [currentId]);
    say(`${name(idx(id))} ${has ? "removed from" : "added to"} the selection. ${next.length || 1} selected.`);
  };

  const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
    const mod = e.ctrlKey || e.metaKey;
    const back = horizontal ? ["ArrowLeft", "ArrowUp"] : ["ArrowUp", "ArrowLeft"];
    const fwd = horizontal ? ["ArrowRight", "ArrowDown"] : ["ArrowDown", "ArrowRight"];
    const dir = back.includes(e.key) ? -1 : fwd.includes(e.key) ? 1 : 0;
    if (dir && e.altKey) { e.preventDefault(); move(cur, cur + dir); return; }
    if (dir) { e.preventDefault(); go(cur + dir, e.shiftKey); return; }
    if (e.key === "Home" || e.key === "End") { e.preventDefault(); go(e.key === "Home" ? 0 : total - 1, e.shiftKey); return; }
    if (e.key === " " && onSelectionChange) { e.preventDefault(); toggle(currentId); return; }
    if (mod && e.key.toLowerCase() === "a" && onSelectionChange) { e.preventDefault(); onSelectionChange(slides.map((s) => s.id)); say(`All ${total} slides selected`); return; }
    if (mod && e.key.toLowerCase() === "z" && !e.shiftKey && onUndo) { e.preventDefault(); undo(); return; }
    if (e.key === "Escape" && multi) { e.preventDefault(); onSelectionChange?.([currentId]); say("Selection cleared"); return; }
    // Delete never deletes on one key: it opens the question below the list, with Keep focused.
    if ((e.key === "Delete" || e.key === "Backspace") && onDelete) { e.preventDefault(); askDelete(selected); }
  };

  const onClick = (e: MouseEvent, id: string) => {
    focusList.current = false;
    if (e.shiftKey && onSelectionChange) {
      const a = Math.max(0, idx(anchor.current)), j = idx(id);
      const [lo, hi] = a < j ? [a, j] : [j, a];
      onSelectionChange(slides.slice(lo, hi + 1).map((s) => s.id));
      onCurrentChange(id);
      return;
    }
    if ((e.ctrlKey || e.metaKey) && onSelectionChange) { toggle(id); onCurrentChange(id); return; }
    anchor.current = id;
    onSelectionChange?.([id]);
    onCurrentChange(id);
  };

  // A right click or a long press opens the same menu as the visible More button.
  const onContext = (e: MouseEvent, id: string) => {
    e.preventDefault();
    if (!selected.includes(id)) { anchor.current = id; onSelectionChange?.([id]); }
    onCurrentChange(id);
    requestAnimationFrame(() => bar.current?.querySelector<HTMLButtonElement>("[aria-haspopup]")?.click());
  };

  /* ---- drag to reorder (pointer only; keyboard and the menu do the same) ---- */
  const dragStart = (e: DragEvent, i: number) => { dragFrom.current = i; e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", slides[i].id); };
  const dragOver = (e: DragEvent<HTMLElement>, i: number) => {
    if (dragFrom.current === null) return;
    e.preventDefault();
    const r = e.currentTarget.getBoundingClientRect();
    const after = horizontal ? e.clientX > r.left + r.width / 2 : e.clientY > r.top + r.height / 2;
    setDrop(after ? i + 1 : i);
  };
  const dragEnd = () => { dragFrom.current = null; setDrop(null); };
  const dropHere = (e: DragEvent) => {
    e.preventDefault();
    const from = dragFrom.current;
    if (from !== null && drop !== null) {
      const to = drop > from ? drop - 1 : drop;
      if (to !== from) move(from, to);
    }
    dragEnd();
  };

  const ar = ratio === "fluid" ? 16 / 9 : ({ "16:9": 16 / 9, "4:3": 4 / 3, "1:1": 1, "4:5": 4 / 5, "9:16": 9 / 16 } as const)[ratio];

  /* ---- action bar items ---- */
  const singleItems: MenuEntry[] = current ? [
    ...(onInsertAfter ? [{ id: "add", label: `Add slide after slide ${cur + 1}`, icon: <PlusIcon width={16} height={16} />, onSelect: insert }] : []),
    ...(onDuplicate ? [{ id: "dup", label: `Duplicate slide ${cur + 1}`, onSelect: () => duplicate([current.id]) }] : []),
    ...(onReorder ? [
      { id: "up", label: "Move earlier", disabled: cur === 0, onSelect: () => move(cur, cur - 1) },
      { id: "down", label: "Move later", disabled: cur === total - 1, onSelect: () => move(cur, cur + 1) },
    ] : []),
    ...(onDelete ? [{ type: "separator" as const }, { id: "del", label: `Delete slide ${cur + 1}`, danger: true, onSelect: () => askDelete([current.id]) }] : []),
  ] : [];
  const multiItems: MenuEntry[] = [
    ...(onDuplicate ? [{ id: "dup", label: `Duplicate ${selected.length} slides`, onSelect: () => duplicate(selected) }] : []),
    { id: "one", label: `Select only slide ${cur + 1}`, onSelect: () => { onSelectionChange?.([currentId]); say("Selection cleared"); } },
    ...(onDelete ? [{ type: "separator" as const }, { id: "del", label: `Delete ${selected.length} slides`, danger: true, onSelect: () => askDelete(selected) }] : []),
  ];

  return (
    <div className={cn("flex min-w-0 flex-col gap-2", !horizontal && "min-h-0", className)}>
      <div className="flex min-h-8 items-center justify-between gap-2 px-1">
        <p id={titleId} className="text-compact font-medium text-fg">
          {label} <span className="font-normal text-fg-muted tabular-nums">{total}{multi ? `, ${selected.length} selected` : ""}</span>
        </p>
        {onInsertAfter && current && (
          <Button size="sm" variant="secondary" leading={<PlusIcon width={14} height={14} />} onClick={insert} aria-label={`Add slide after slide ${cur + 1}`}>Add slide</Button>
        )}
      </div>

      <div ref={list} role="listbox" aria-labelledby={titleId} aria-multiselectable={onSelectionChange ? true : undefined}
        aria-orientation={horizontal ? "horizontal" : "vertical"} onKeyDown={onKeyDown}
        onFocus={() => { focusList.current = false; }}
        onDragLeave={(e) => { if (!e.currentTarget.contains(e.relatedTarget as Node | null)) setDrop(null); }}
        className={cn(
          "flex p-1",
          horizontal ? "snap-x snap-mandatory gap-2 overflow-x-auto overscroll-x-contain" : "min-h-0 flex-1 flex-col gap-1 overflow-y-auto overscroll-contain",
        )}>
        {slides.map((s, i) => {
          const isCur = s.id === currentId;
          const isSel = selected.includes(s.id);
          const words = `Slide ${i + 1} of ${total}, ${s.title}, ${STATUS_WORDS[s.status]}${s.aiMade && s.status !== "edited" ? ", made by AI" : ""}${s.error ? `. ${s.error}` : ""}`;
          const lineBefore = drop === i && dragFrom.current !== null && dragFrom.current !== i && dragFrom.current !== i - 1;
          const lineAfter = drop === i + 1 && i === total - 1 && dragFrom.current !== null && dragFrom.current !== i;
          const thumb = (
            <div aria-hidden className={cn("relative", horizontal ? "h-16" : "w-full")} style={horizontal ? { width: `calc(var(--spacing) * 16 * ${ar})` } : undefined}>
              <WorkingEdge active={s.status === "generating"} radius="control" label="">
                <div className={cn(
                  "relative overflow-hidden rounded-control border bg-surface",
                  isCur ? "border-fg ring-1 ring-fg" : "border-line",
                  horizontal && "h-16",
                )} style={horizontal ? undefined : { aspectRatio: String(ar) }}>
                  {s.status === "pending" ? (
                    <div className="absolute inset-0 grid content-center gap-1.5 bg-sunken p-2">
                      <span className="truncate text-small text-fg-muted">{s.title}</span>
                      <Skeleton className="h-1.5 w-4/5 rounded-control motion-reduce:animate-none" />
                      <Skeleton className="h-1.5 w-3/5 rounded-control motion-reduce:animate-none" />
                    </div>
                  ) : s.status === "failed" ? (
                    <div className="absolute inset-0 flex flex-col items-center justify-center gap-1 bg-sunken p-2 text-center">
                      <AlertIcon width={16} height={16} className="text-danger-fg" />
                      <span className="line-clamp-2 text-small text-fg-muted">{s.title}</span>
                    </div>
                  ) : s.thumbnail ? (
                    <div key={s.status} className={cn("size-full motion-safe:animate-[fade_var(--dur-base)_var(--ease-out)_both]", s.status === "generating" && "opacity-60")}>
                      <SlideCanvas ratio={ratio === "fluid" ? "16:9" : ratio} className="h-full">{s.thumbnail}</SlideCanvas>
                    </div>
                  ) : (
                    <div className="absolute inset-0 flex items-center justify-center p-2 text-center">
                      <span className="line-clamp-2 text-small font-medium text-fg">{s.title}</span>
                    </div>
                  )}
                  {s.aiMade && s.status !== "edited" && s.status !== "pending" && s.status !== "failed" && (
                    <span className="absolute right-1 bottom-1 inline-flex size-4 items-center justify-center rounded-full bg-lime text-on-lime"><AIMark size={16} className="size-3" /></span>
                  )}
                </div>
              </WorkingEdge>
            </div>
          );
          return (
            <div key={s.id} role="option" data-slide-id={s.id} aria-selected={isSel} aria-label={words} title={s.error}
              tabIndex={isCur ? 0 : -1} draggable={!!onReorder}
              onClick={(e) => onClick(e, s.id)} onContextMenu={(e) => onContext(e, s.id)}
              onDragStart={(e) => dragStart(e, i)} onDragOver={(e) => dragOver(e, i)} onDrop={dropHere} onDragEnd={dragEnd}
              className={cn(
                "relative shrink-0 cursor-pointer rounded-card transition-colors duration-(--dur-fast) select-none",
                horizontal ? "flex snap-start flex-col gap-1 p-1" : "flex gap-2 p-1.5",
                isSel && multi ? "bg-accent-soft" : "hover:bg-hover",
              )}>
              {lineBefore && <span aria-hidden className={cn("absolute bg-fg", horizontal ? "inset-y-1 -left-1.5 w-0.5" : "inset-x-1 -top-1 h-0.5")} />}
              {lineAfter && <span aria-hidden className={cn("absolute bg-fg", horizontal ? "inset-y-1 -right-1.5 w-0.5" : "inset-x-1 -bottom-1 h-0.5")} />}
              {horizontal ? (
                <>
                  {thumb}
                  <span aria-hidden className="flex items-center gap-1 px-0.5">
                    <span className="text-small font-medium text-fg tabular-nums">{i + 1}</span>
                    {isSel && multi && <CheckIcon width={12} height={12} className="text-fg" />}
                    <StatusLine status={s.status} />
                  </span>
                </>
              ) : (
                <>
                  <span aria-hidden className="flex w-5 shrink-0 flex-col items-end gap-1 pt-0.5">
                    <span className="text-small font-medium text-fg tabular-nums">{i + 1}</span>
                    {isSel && multi && <CheckIcon width={12} height={12} className="text-fg" />}
                  </span>
                  <span className="grid min-w-0 flex-1 gap-1">
                    {thumb}
                    <span aria-hidden><StatusLine status={s.status} /></span>
                  </span>
                </>
              )}
            </div>
          );
        })}
      </div>

      {undoNote && !confirm && (
        <div className="flex flex-wrap items-center gap-2 rounded-card border border-line bg-surface px-2.5 py-1.5 motion-safe:animate-fade">
          <p className="min-w-0 flex-1 text-compact text-fg-muted">{undoNote}</p>
          {onUndo && <Button size="sm" variant="ghost" leading={<UndoIcon width={14} height={14} />} onClick={undo}>Undo</Button>}
        </div>
      )}

      <div ref={bar} className="flex min-h-8 flex-wrap items-center gap-2 px-1">
        {confirm ? (
          <div role="group" aria-label="Confirm delete" className="flex w-full flex-wrap items-center gap-2 motion-safe:animate-fade"
            onKeyDown={(e) => { if (e.key === "Escape") { e.preventDefault(); cancelDelete(); } }}>
            <p className="w-full text-compact text-fg">
              {confirm.length === 1 ? `Delete slide ${idx(confirm[0]) + 1}, ${slides[idx(confirm[0])]?.title}?` : `Delete ${confirm.length} slides?`}
              {onUndo && <span className="text-fg-muted"> You can undo this.</span>}
            </p>
            <Button ref={keepBtn} size="sm" variant="secondary" onClick={cancelDelete}>{confirm.length === 1 ? "Keep it" : "Keep them"}</Button>
            <Button size="sm" variant="danger" onClick={doDelete}>{confirm.length === 1 ? "Delete slide" : `Delete ${confirm.length} slides`}</Button>
          </div>
        ) : multi ? (
          <>
            <p className="min-w-0 flex-1 text-compact text-fg-muted">{selected.length} slides selected</p>
            <Menu portal align="end" side="top" trigger={<MoreIcon width={16} height={16} />} triggerLabel={`Actions for ${selected.length} selected slides`} items={multiItems} />
          </>
        ) : current ? (
          <>
            <p className="min-w-0 flex-1 text-compact leading-5 text-fg-muted">
              {current.status === "failed" && current.error ? current.error : current.status === "review" && current.error ? current.error : `Slide ${cur + 1}`}
            </p>
            {current.status === "failed" && onRetry && (
              <Button size="sm" variant="secondary" leading={<RetryIcon width={14} height={14} />} onClick={() => onRetry(current.id)} aria-label={`Try slide ${cur + 1} again`}>Try again</Button>
            )}
            {singleItems.length > 0 && (
              <Menu portal align="end" side="top" trigger={<MoreIcon width={16} height={16} />} triggerLabel={`Actions for slide ${cur + 1}`} items={singleItems} />
            )}
          </>
        ) : null}
      </div>

      <p role="status" aria-live="polite" className="sr-only">{announce}</p>
    </div>
  );
}
```

## tattva/organisms/SourceList.tsx

```tsx
import { cn } from "../lib/cn";
import { type Source, SourceCard } from "../molecules/SourceCard";

export function SourceList({ sources, className }: { sources: Source[]; className?: string }) {
  return (
    <section aria-label="Sources" className={cn("space-y-1.5", className)}>
      <p className="text-nano uppercase tracking-caps text-fg-muted">Sources</p>
      <ol className="grid gap-2 sm:grid-cols-2">
        {sources.map((s) => <SourceCard key={s.id} source={s} />)}
      </ol>
    </section>
  );
}
```

## tattva/organisms/Sparkline.tsx

```tsx
import { formatNumber } from "../molecules/ChartFrame";

export interface SparklineProps {
  /** Values in time order. At least two points are drawn. */
  data: number[];
  /** What the line measures, for example "p95 latency". Used in the accessible name. */
  label: string;
  /** Unit appended to values in the accessible name, for example " ms". Include the leading space if needed. */
  unit?: string;
  /** Formats values for the accessible name. */
  formatValue?: (n: number) => string;
  /** Width in px. */
  width?: number;
  /** Height in px. */
  height?: number;
  /** Mark the minimum and maximum with dots. */
  showMinMax?: boolean;
  /** Hide from assistive technology. Use when the number beside it already says everything, such as inside a StatTile. */
  decorative?: boolean;
  /** Color of the emphasized end point. Defaults to the first chart color. */
  color?: string;
  className?: string;
}

/**
 * Tiny trend line in the context color with the latest point emphasized. The accessible name states the first,
 * last, minimum and maximum values. Set decorative when a visible number carries the same information.
 */
export function Sparkline({ data, label, unit = "", formatValue = formatNumber, width = 96, height = 28, showMinMax, decorative, color = "var(--chart-1)", className }: SparklineProps) {
  const n = data.length;
  const pad = 6;
  const min = Math.min(...data);
  const max = Math.max(...data);
  const span = max - min || 1;
  const x = (i: number) => pad + (n <= 1 ? (width - 2 * pad) / 2 : (i * (width - 2 * pad)) / (n - 1));
  const y = (v: number) => height - pad - ((v - min) / span) * (height - 2 * pad);
  const d = data.map((v, i) => `${i ? "L" : "M"}${x(i).toFixed(1)} ${y(v).toFixed(1)}`).join("");
  const iMin = data.indexOf(min);
  const iMax = data.indexOf(max);
  const f = (v: number) => `${formatValue(v)}${unit}`;
  const name = n ? `${label}: first ${f(data[0])}, last ${f(data[n - 1])}, minimum ${f(min)}, maximum ${f(max)}.` : `${label}: no data.`;
  if (!n) return null;
  return (
    <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} className={className}
      role={decorative ? undefined : "img"} aria-label={decorative ? undefined : name} aria-hidden={decorative || undefined} focusable="false">
      {n > 1 && <path d={d} fill="none" stroke="var(--chart-axis)" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" />}
      {showMinMax && iMin !== n - 1 && <circle cx={x(iMin)} cy={y(min)} r={3.5} fill="var(--surface)" stroke="var(--chart-axis)" strokeWidth={1.5} />}
      {showMinMax && iMax !== n - 1 && iMax !== iMin && <circle cx={x(iMax)} cy={y(max)} r={3.5} fill="var(--surface)" stroke="var(--chart-axis)" strokeWidth={1.5} />}
      <circle cx={x(n - 1)} cy={y(data[n - 1])} r={4.5} fill={color} stroke="var(--surface)" strokeWidth={2} />
    </svg>
  );
}
```

## tattva/organisms/StarterGallery.tsx

```tsx
import { useRef, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { keepFocus, nearestTyping, useAnnounceChange } from "../lib/focus";
import { Button } from "../atoms/Button";
import { Badge } from "../atoms/Badge";

export interface StarterIntent { id: string; label: string }
export interface StarterTemplate { id: string; title: string; description: string; category?: string }

export interface StarterGalleryProps {
  /** Heading above the statement. */
  heading?: string;
  /** One sentence on what the assistant can and cannot do. */
  statement: ReactNode;
  /** Starter intents. Three to six works best. */
  intents: StarterIntent[];
  onIntent: (id: string) => void;
  templates?: StarterTemplate[];
  onTemplate?: (id: string) => void;
  intentsLabel?: string;
  templatesLabel?: string;
  /** Slot beside the templates heading, usually a Randomize button. When the templates change, screen readers hear it. */
  randomize?: ReactNode;
  /** What screen readers hear when the templates change. */
  templatesChangedMessage?: (count: number) => string;
  /** Where keyboard focus goes after an idea or template is chosen, if the gallery goes away. By default the nearest
   * message box. */
  focusAfterChoose?: () => HTMLElement | null;
  className?: string;
}

/** First-run view. It states scope, offers starter intents next to free text, and lists templates. */
export function StarterGallery({ heading = "What would you like to make?", statement, intents, onIntent, templates = [], onTemplate, intentsLabel = "Start with", templatesLabel = "Templates", randomize, templatesChangedMessage = (n) => `New templates: ${n}`, focusAfterChoose, className }: StarterGalleryProps) {
  const ref = useRef<HTMLElement>(null);
  const choose = (f: ((id: string) => void) | undefined, id: string) => {
    const box = nearestTyping(ref.current);
    f?.(id);
    keepFocus(ref.current, () => focusAfterChoose?.() ?? box);
  };
  const said = useAnnounceChange(templates.map((t) => t.id).join("|"), () => templatesChangedMessage(templates.length));
  return (
    <section ref={ref} aria-label={heading} className={cn("space-y-6", className)}>
      <div className="space-y-2">
        <h2 className="text-title-sm leading-8">{heading}</h2>
        <p className="max-w-xl text-body leading-5 text-fg-muted">{statement}</p>
      </div>
      <div className="space-y-2">
        <h3 className="text-nano font-normal tracking-caps text-fg-muted uppercase font-sans!">{intentsLabel}</h3>
        <ul className="flex flex-wrap gap-2">
          {intents.map((i) => <li key={i.id}><Button variant="secondary" size="sm" onClick={() => choose(onIntent, i.id)}>{i.label}</Button></li>)}
        </ul>
      </div>
      <p role="status" aria-live="polite" className="sr-only">{said}</p>
      {templates.length > 0 && (
        <div className="space-y-2">
          <div className="flex items-center justify-between gap-2">
            <h3 className="text-nano font-normal tracking-caps text-fg-muted uppercase font-sans!">{templatesLabel}</h3>
            {randomize}
          </div>
          <ul className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
            {templates.map((t) => (
              <li key={t.id}>
                <button type="button" onClick={() => choose(onTemplate, t.id)}
                  className="flex h-full w-full cursor-pointer flex-col items-start gap-1 rounded-card border border-line bg-surface p-4 text-left transition-colors hover:border-line-strong hover:bg-hover">
                  {t.category && <Badge>{t.category}</Badge>}
                  <span className="text-body leading-5 font-medium">{t.title}</span>
                  <span className="text-small leading-4 text-fg-muted">{t.description}</span>
                </button>
              </li>
            ))}
          </ul>
        </div>
      )}
    </section>
  );
}
```

## tattva/organisms/StemMixer.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, DownloadIcon, LockIcon } from "../lib/icons";
import { headingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { Button } from "../atoms/Button";
import { Checkbox } from "../atoms/Checkbox";
import { Slider } from "../atoms/Slider";
import { Skeleton } from "../atoms/Skeleton";
import { Waveform } from "../atoms/Waveform";

/** Where the split into stems is. `separating`: the AI is splitting the song. `ready`: stems can be mixed and exported. `failed`: the split did not work. `plan-locked`: the person's plan does not include stems. */
export type StemMixerStatus = "separating" | "ready" | "failed" | "plan-locked";

export interface Stem {
  id: string;
  /** Plain name, such as "Vocals" or "Drums". */
  name: string;
  /** Loudness per slice, 0 to 1. Leave out for no waveform. */
  peaks?: number[];
  /** Level, 0 to 100. */
  level: number;
  muted: boolean;
  soloed: boolean;
  /** Picked for export. */
  exported: boolean;
}

export interface StemMixerProps {
  stems: Stem[];
  status: StemMixerStatus;
  /** Length of the song, in seconds, for the waveforms. */
  duration?: number;
  /** Playhead, in seconds, drawn on each waveform. */
  position?: number;
  onLevelChange?: (id: string, level: number) => void;
  onMuteChange?: (id: string, muted: boolean) => void;
  onSoloChange?: (id: string, soloed: boolean) => void;
  onExportChange?: (id: string, exported: boolean) => void;
  /** Exports the picked stems. */
  onExport?: (ids: string[]) => void;
  /** Format and limits beside Export, such as "WAV, 3 downloads left this month". */
  exportNote?: string;
  /** While separating: what is happening, such as "Splitting into 4 stems. About a minute." No percentages. */
  separatingNote?: string;
  /** When failed: what happened and whether it was charged. */
  failedNote?: string;
  onRetry?: () => void;
  /** When plan-locked: which plan has stems. */
  planNote?: string;
  /** When plan-locked: opens the plans page. */
  onSeePlans?: () => void;
  /** Rows shown while separating, before stem names are known. */
  placeholderCount?: number;
  title?: string;
  headingLevel?: HeadingLevel;
  className?: string;
}

const STATUS_WORD: Record<StemMixerStatus, string> = { separating: "Separating", ready: "Ready", failed: "Did not work", "plan-locked": "Not on your plan" };

/**
 * One lane per stem of a song: Mute, Solo, a named level slider and a box to pick it for export.
 * Shows the separation state in words: separating, ready, failed or not on the person's plan.
 * Rows stack on a phone and the waveforms hide when the mixer is narrower than about 400px.
 */
export function StemMixer({
  stems, status, duration = 0, position = 0, onLevelChange, onMuteChange, onSoloChange, onExportChange, onExport, exportNote,
  separatingNote = "Splitting the song into stems. About a minute.", failedNote = "The song could not be split. You were not charged.", onRetry,
  planNote = "Stems come with the Pro plan.", onSeePlans, placeholderCount = 4, title = "Stems", headingLevel = 3, className,
}: StemMixerProps) {
  const H = headingTag(headingLevel);
  const uid = useId();
  const [message, setMessage] = useState("");
  const anySolo = stems.some((s) => s.soloed);
  const picked = stems.filter((s) => s.exported);

  // Announce a change of separation status only.
  const lastStatus = useRef(status);
  useEffect(() => {
    if (lastStatus.current === status) return;
    lastStatus.current = status;
    setMessage(status === "ready" ? `${stems.length} stems ready.` : status === "failed" ? "Separation did not work." : status === "separating" ? "Separating." : "");
  }, [status, stems.length]);

  const toggleSolo = (s: Stem) => {
    onSoloChange?.(s.id, !s.soloed);
    const others = stems.filter((x) => x.soloed && x.id !== s.id).length;
    setMessage(!s.soloed ? `${s.name} soloed. Stems not soloed are silent.` : others ? `${s.name} no longer soloed.` : `${s.name} no longer soloed. All stems play.`);
  };
  const toggleMute = (s: Stem) => {
    onMuteChange?.(s.id, !s.muted);
    setMessage(`${s.name} ${s.muted ? "unmuted" : "muted"}.`);
  };

  const toggle = (on: boolean) => cn("tap h-8 min-w-14 cursor-pointer rounded-control border px-2.5 text-small leading-4.5 font-medium transition-colors duration-(--dur-fast) pointer-coarse:h-11 disabled:cursor-not-allowed disabled:border-disabled-line disabled:bg-disabled disabled:text-disabled-fg",
    on ? "border-fg bg-fg text-bg" : "border-line bg-surface text-fg-muted hover:bg-hover hover:text-fg");

  return (
    <section aria-labelledby={`${uid}-h`} aria-busy={status === "separating" || undefined}
      className={cn("@container w-full min-w-0 space-y-3 rounded-card border border-line bg-surface p-4", className)}>
      <header className="flex flex-wrap items-center justify-between gap-x-4 gap-y-1">
        <H id={`${uid}-h`} className="text-lead leading-6 font-semibold text-fg">{title}</H>
        <p className="inline-flex items-center gap-1.5 text-small leading-4.5 font-medium text-fg">
          {status === "separating" && <span aria-hidden className="size-2 shrink-0 rounded-full bg-lime motion-safe:animate-pulse" />}
          {status === "ready" && <span className="inline-flex items-center gap-1 font-normal text-fg-muted"><span aria-hidden className="size-2 shrink-0 rounded-full bg-lime ring-1 ring-fg-muted" />Split by AI ·</span>}
          {status === "failed" && <AlertIcon width={12} height={12} aria-hidden />}
          {status === "plan-locked" && <LockIcon width={12} height={12} aria-hidden />}
          {status === "ready" ? `${stems.length} stems` : STATUS_WORD[status]}
        </p>
      </header>

      {status === "separating" && (
        <div className="space-y-2">
          <p className="text-compact text-fg-muted">{separatingNote}</p>
          <ul aria-hidden className="space-y-2">
            {Array.from({ length: placeholderCount }, (_, i) => (
              <li key={i} className="flex items-center gap-3 rounded-card border border-line p-2.5">
                <Skeleton className="h-4 w-16" />
                <Skeleton className="h-6 flex-1 @max-sm:hidden" />
                <Skeleton className="h-4 w-24" />
              </li>
            ))}
          </ul>
        </div>
      )}

      {status === "failed" && (
        <div className="flex flex-wrap items-center gap-3 rounded-card border border-line bg-sunken p-3">
          <p className="min-w-0 flex-1 text-compact text-fg">{failedNote}</p>
          {onRetry && <Button size="sm" variant="secondary" className="pointer-coarse:h-11" onClick={onRetry}>Try again</Button>}
        </div>
      )}

      {status === "plan-locked" && (
        <div className="flex flex-wrap items-center gap-3 rounded-card border border-dashed border-line-strong p-3">
          <LockIcon width={16} height={16} aria-hidden className="shrink-0 text-fg-muted" />
          <p className="min-w-0 flex-1 text-compact text-fg">{planNote} You can still export the full song.</p>
          {onSeePlans && <Button size="sm" variant="secondary" className="pointer-coarse:h-11" onClick={onSeePlans}>See plans</Button>}
        </div>
      )}

      {status === "ready" && (
        <>
          <ul aria-label={`${title}, ${stems.length}`} className="divide-y divide-line rounded-card border border-line">
            {stems.map((s) => {
              const silent = s.muted || (anySolo && !s.soloed);
              const why = s.muted ? "Muted" : anySolo && !s.soloed ? "Silent while another stem is soloed" : s.soloed ? "Soloed" : "";
              return (
                <li key={s.id} className="grid grid-cols-[auto_minmax(0,1fr)] items-center gap-x-3 gap-y-2 p-2.5 @3xl:grid-cols-[auto_minmax(6rem,9rem)_auto_minmax(0,1fr)_minmax(8rem,10rem)]">
                  <Checkbox checked={s.exported} onChange={(v) => onExportChange?.(s.id, v)} ariaLabel={`Export ${s.name}`} />
                  <div className="min-w-0">
                    <p className="truncate text-compact font-medium text-fg">{s.name}</p>
                    <p className={cn("text-small leading-4.5", why ? "text-fg-muted" : "text-fg-subtle")}>{why || "Playing"}</p>
                  </div>
                  <div className="col-span-2 flex items-center gap-1.5 @3xl:col-span-1">
                    <button type="button" aria-pressed={s.muted} aria-label={`Mute ${s.name}`} onClick={() => toggleMute(s)} className={toggle(s.muted)}>Mute</button>
                    <button type="button" aria-pressed={s.soloed} aria-label={`Solo ${s.name}`} onClick={() => toggleSolo(s)} className={toggle(s.soloed)}>Solo</button>
                  </div>
                  <div className={cn("col-span-2 min-w-0 @max-sm:hidden @3xl:col-span-1", silent && "opacity-40")}>
                    {s.peaks && duration > 0 && <Waveform peaks={s.peaks} duration={duration} position={position} height={32} />}
                  </div>
                  <Slider className="col-span-2 @3xl:col-span-1" label={`${s.name} level`} value={s.level} onChange={(v) => onLevelChange?.(s.id, v)}
                    showValue formatValue={(v) => `${v}%`} />
                </li>
              );
            })}
          </ul>
          {onExport && (
            <div className="flex flex-wrap items-center justify-end gap-x-3 gap-y-2">
              {exportNote && <span className="text-small leading-4.5 text-fg-muted">{exportNote}</span>}
              <Button size="sm" className="pointer-coarse:h-11" disabled={picked.length === 0} leading={<DownloadIcon width={14} height={14} aria-hidden />}
                onClick={() => onExport(picked.map((s) => s.id))}>
                {picked.length === 0 ? "Pick stems to export" : `Export ${picked.length} of ${stems.length} stems`}
              </Button>
            </div>
          )}
        </>
      )}

      <p role="status" aria-live="polite" className="sr-only">{message}</p>
    </section>
  );
}
```

## tattva/organisms/StepTimeline.tsx

```tsx
import { cn } from "../lib/cn";
import { CheckIcon, XIcon } from "../lib/icons";
import { Spinner } from "../atoms/Spinner";

export type StepState = "done" | "active" | "todo" | "failed";
export interface Step { id: string; title: string; detail?: string; state: StepState }

export function StepTimeline({ steps }: { steps: Step[] }) {
  return (
    <ol aria-label="Task progress" className="space-y-0">
      {steps.map((s, i) => (
        <li key={s.id} className="relative flex gap-3 pb-4 last:pb-0" aria-current={s.state === "active" ? "step" : undefined}>
          {i < steps.length - 1 && <span aria-hidden className={cn("absolute top-6 bottom-0 left-2.75 w-px", s.state === "done" ? "bg-success" : "bg-line")} />}
          <span className={cn("z-10 flex size-6 shrink-0 items-center justify-center rounded-full border text-small leading-4",
            s.state === "done" && "border-success bg-success text-on-success",
            s.state === "failed" && "border-danger bg-danger text-on-danger",
            s.state === "active" && "border-accent bg-surface",
            s.state === "todo" && "border-line-strong bg-surface text-fg-subtle")}>
            {s.state === "done" ? <CheckIcon width={12} height={12} /> : s.state === "failed" ? <XIcon width={12} height={12} /> : s.state === "active" ? <Spinner size={14} /> : i + 1}
          </span>
          <div className="min-w-0 pt-0.5">
            <p className={cn("text-body leading-5 font-medium", s.state === "todo" && "text-fg-subtle")}>{s.title}</p>
            {s.detail && <p className="text-small leading-4 text-fg-muted">{s.detail}</p>}
            <span className="sr-only">{s.state}</span>
          </div>
        </li>
      ))}
    </ol>
  );
}
```

## tattva/organisms/SuggestionBar.tsx

```tsx
import { useRef, useState, type ReactNode } from "react";
import { nearestTyping, useFocusReturn } from "../lib/focus";
import { cn } from "../lib/cn";
import { CheckIcon, RefreshIcon, SendIcon } from "../lib/icons";
import { Button, IconButton } from "../atoms/Button";
import { Spinner } from "../atoms/Spinner";

export type SuggestionBarStatus = "generating" | "ready" | "committed" | "discarded";

export interface SuggestionBarProps {
  /** Where the staged output is in its life. Nothing reaches the document until it is committed. */
  status?: SuggestionBarStatus;
  /** Optional preview of the staged output, rendered above the bar. */
  children?: ReactNode;
  /** Label of the primary action. Use "Replace" when the output replaces a selection. */
  primaryLabel?: string;
  /** Called by the primary action. */
  onKeep?: () => void;
  onInsertBelow?: () => void;
  /** A fresh sample from the same prompt. Not a follow-up. */
  onRegenerate?: () => void;
  onDiscard?: () => void;
  /** A follow-up instruction that changes the draft. Not a fresh sample. */
  onRefine?: (instruction: string) => void;
  /** Cost of a retry, for example "Uses 1 AI response". Shown beside Regenerate. */
  retryCost?: ReactNode;
  /** Where keyboard focus goes once the draft is kept, inserted or discarded. By default the nearest text box or
   * editor outside the bar (the document), else the bar itself, which then shows what happened. */
  focusAfter?: () => HTMLElement | null;
  /** Product text, all overridable. */
  labels?: Partial<Record<"insertBelow" | "regenerate" | "discard" | "refine" | "refinePlaceholder" | "refineSend" | "generating" | "ready" | "committed" | "discarded", string>>;
  className?: string;
}

const defaults = {
  insertBelow: "Insert below", regenerate: "Regenerate", discard: "Discard", refine: "Fine-tune this", refinePlaceholder: "Tell AI what to change",
  refineSend: "Send instruction", generating: "Writing a draft", ready: "Draft ready. Nothing is added until you choose.", committed: "Added to the document.", discarded: "Draft discarded.",
};

/**
 * Result bar for staged AI output. Keep, Insert below, Regenerate and Discard sit next to a "Fine-tune this" field.
 * Regenerate asks for a fresh sample, Fine-tune sends a follow-up instruction. Status changes are announced through a separate polite region.
 */
export function SuggestionBar({
  status = "ready", children, primaryLabel = "Keep", onKeep, onInsertBelow, onRegenerate, onDiscard, onRefine, retryCost, focusAfter, labels, className,
}: SuggestionBarProps) {
  // The buttons change with the status, so focus follows: to Discard while a new draft is written, to the primary
  // action when it is ready, and back to the document (or the bar) once the draft is kept or thrown away.
  const root = useRef<HTMLDivElement>(null);
  const discardWhileWriting = useRef<HTMLButtonElement>(null);
  const primary = useRef<HTMLButtonElement>(null);
  useFocusReturn(status, root, () =>
    status === "generating" ? discardWhileWriting.current : status === "ready" ? primary.current : focusAfter?.() ?? nearestTyping(root.current) ?? root.current);
  const t = { ...defaults, ...labels };
  const [text, setText] = useState("");
  const ready = status === "ready";
  const message = t[status];
  return (
    <div ref={root} tabIndex={-1} className={cn("w-full space-y-3 rounded-card border border-line bg-surface p-3 outline-offset-2", className)}>
      <p role="status" className="sr-only">{status === "generating" ? "" : message}</p>
      {children && <div className={cn("rounded-card bg-sunken p-3 text-body leading-5 text-fg", status === "discarded" && "opacity-60")}>{children}</div>}
      {status === "generating" && (
        <div className="flex items-center gap-2 text-small leading-4 text-fg-muted"><Spinner size={14} label={t.generating} /><span aria-hidden="true">{t.generating}</span>
          <Button ref={discardWhileWriting} size="sm" variant="ghost" className="ml-auto" onClick={onDiscard}>{t.discard}</Button></div>
      )}
      {(status === "committed" || status === "discarded") && (
        <p className="flex items-center gap-1.5 text-small leading-4 text-fg-muted" aria-hidden="true">{status === "committed" && <CheckIcon width={13} height={13} />}{message}</p>
      )}
      {ready && (
        <>
          <div className="flex flex-wrap items-center gap-2">
            <Button ref={primary} size="sm" variant="lime" onClick={onKeep}>{primaryLabel}</Button>
            <Button size="sm" variant="secondary" onClick={onInsertBelow}>{t.insertBelow}</Button>
            <Button size="sm" variant="secondary" leading={<RefreshIcon width={13} height={13} />} onClick={onRegenerate}>{t.regenerate}</Button>
            {retryCost && <span className="text-micro text-fg-subtle">{retryCost}</span>}
            <Button size="sm" variant="ghost" className="ml-auto" onClick={onDiscard}>{t.discard}</Button>
          </div>
          <form onSubmit={(e) => { e.preventDefault(); const v = text.trim(); if (v) { onRefine?.(v); setText(""); } }}
            className="flex items-center gap-1 rounded-field border border-line bg-sunken pl-4 pr-1 has-[input:focus-visible]:outline-2 has-[input:focus-visible]:outline-offset-2 has-[input:focus-visible]:outline-[var(--focus-ring)]">
            <input value={text} onChange={(e) => setText(e.target.value)} aria-label={t.refine} placeholder={`${t.refine}. ${t.refinePlaceholder}`}
              className="h-9 min-w-0 flex-1 bg-transparent text-compact text-fg placeholder:text-fg-subtle focus:outline-none" />
            <IconButton type="submit" size="sm" variant="primary" label={t.refineSend} disabled={!text.trim()}><SendIcon width={13} height={13} /></IconButton>
          </form>
        </>
      )}
    </div>
  );
}
```

## tattva/organisms/SummaryCard.tsx

```tsx
import { useId, useState, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { ChevronIcon } from "../lib/icons";
import { AIBadge } from "../atoms/AIBadge";
import { Spinner } from "../atoms/Spinner";
import { FeedbackBar, type Rating } from "../molecules/FeedbackBar";
import type { Source } from "../molecules/SourceCard";
import { SourceList } from "./SourceList";

export interface SummaryCardProps {
  /** Heading of the block. */
  title?: string;
  /** Text of the AI label. */
  aiLabel?: string;
  /** Status line, for example "Based on 4 sources". Announced politely when it changes. */
  status?: ReactNode;
  /** Show a spinner beside the status while the summary is still being produced. */
  generating?: boolean;
  /** The summary. Place CitationHoverCard markers inline here. Never put aria-live on streamed text. */
  children: ReactNode;
  /** Longer detail revealed by the expand control. Omit to hide the control. */
  detail?: ReactNode;
  expandLabel?: string;
  collapseLabel?: string;
  defaultExpanded?: boolean;
  expanded?: boolean;
  onExpandedChange?: (expanded: boolean) => void;
  sources?: Source[];
  /** Disclaimer line under the content. */
  disclaimer?: ReactNode;
  /** Shows the feedback row when provided. */
  onFeedback?: (rating: Exclude<Rating, null>, reason?: string) => void;
  className?: string;
}

/** AI overview block: label and status, summary, optional expandable detail, sources, a disclaimer and a feedback row. */
export function SummaryCard({
  title = "Overview", aiLabel, status, generating, children, detail, expandLabel = "Show more", collapseLabel = "Show less", defaultExpanded = false,
  expanded, onExpandedChange, sources, disclaimer = "AI responses may include mistakes. Check important information.", onFeedback, className,
}: SummaryCardProps) {
  const [inner, setInner] = useState(defaultExpanded);
  const open = expanded ?? inner;
  const id = useId();
  const toggle = () => { const v = !open; if (expanded === undefined) setInner(v); onExpandedChange?.(v); };
  return (
    <section aria-label={title} className={cn("w-full space-y-4 rounded-card border border-line bg-surface p-5", className)}>
      <header className="flex flex-wrap items-center gap-x-2.5 gap-y-1">
        <AIBadge {...(aiLabel ? { label: aiLabel } : {})} />
        <h3 className="text-lead leading-6 text-fg">{title}</h3>
        {status && (
          <p role="status" className="ml-auto flex items-center gap-1.5 text-small leading-4 text-fg-muted">
            {generating && <Spinner size={12} label="Working" />}{status}
          </p>
        )}
      </header>
      <div className="text-body leading-relaxed text-fg">{children}</div>
      {detail && (
        <div>
          <button type="button" aria-expanded={open} aria-controls={id} onClick={toggle}
            className="inline-flex cursor-pointer items-center gap-1 rounded-control text-small leading-4 font-medium text-fg-muted hover:text-fg">
            <ChevronIcon width={12} height={12} className={cn("transition-transform duration-[var(--dur-fast)]", open ? "-rotate-90" : "rotate-90")} />
            {open ? collapseLabel : expandLabel}
          </button>
          <div id={id} hidden={!open} className="mt-2 text-body leading-relaxed text-fg-muted">{detail}</div>
        </div>
      )}
      {sources && sources.length > 0 && <SourceList sources={sources} />}
      {disclaimer && <p className="text-small leading-4 text-fg-subtle">{disclaimer}</p>}
      {onFeedback && <FeedbackBar onSubmit={onFeedback} />}
    </section>
  );
}
```

## tattva/organisms/TakeGrid.tsx

```tsx
import { useId, useRef, useState, type KeyboardEvent, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { type HeadingLevel, headingTag } from "../lib/useFocusAfter";
import { Illustration } from "../atoms/Illustration";
import { Reveal } from "../atoms/Reveal";
import { SegmentedControl } from "../atoms/SegmentedControl";
import { EmptyState } from "../molecules/EmptyState";
import { TakeCard, takeRatioClass, type TakeCardProps, type TakeRatio, type TakeStatus } from "../molecules/TakeCard";

export interface TakeGridItem extends Omit<TakeCardProps, "ratio" | "selectMode" | "selected" | "onSelect" | "announce" | "headingLevel" | "tabIndex" | "onKeyDown" | "ref" | "describedBy" | "compact"> {
  /** The prompt or request this take belongs to. Used as the group heading when groupBy is "request". */
  request?: string;
}

export type TakeGridDensity = "comfortable" | "compact";

export interface TakeGridProps {
  takes: TakeGridItem[];
  /** pick: choose one take. select: choose several, for example to download or delete. */
  mode?: "pick" | "select";
  /** Selected take ids. */
  value: string[];
  onChange: (ids: string[]) => void;
  /** One ratio for every tile, so rows stay even. */
  ratio?: TakeRatio;
  /** A heading per request, using each take's `request`. */
  groupBy?: "request" | "none";
  /** Tile size. Controlled when onDensityChange is passed. */
  density?: TakeGridDensity;
  onDensityChange?: (d: TakeGridDensity) => void;
  /** Shows the Comfortable and Compact control above the grid. */
  showDensityControl?: boolean;
  /** Called with the take id when Enter is pressed on a finished take, or its poster is pressed. */
  onOpen?: (id: string) => void;
  /** Takes requested but not yet known to the grid. Draws this many still placeholders. */
  loadingCount?: number;
  /** Replaces the default empty state. */
  empty?: ReactNode;
  /** Accessible name of the grid. */
  label?: string;
  /** The = and - keys change tile size while focus is in the grid. Set false to turn them off. */
  shortcuts?: boolean;
  /** Heading level of group headings. Each take's name is one level below. */
  headingLevel?: HeadingLevel;
  className?: string;
}

const plural = (n: number, one: string, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;

/**
 * The one sentence the grid speaks. It only changes when a status changes, never with a percentage, so a screen reader
 * hears "2 of 4 takes ready" once instead of every card speaking on its own.
 */
export function takeGridSummary(takes: { status: TakeStatus; charge?: TakeGridItem["charge"] }[]) {
  if (!takes.length) return "";
  const n = (s: TakeStatus) => takes.filter((t) => t.status === s).length;
  const parts = [`${n("done")} of ${plural(takes.length, "take")} ready`];
  const working = n("queued") + n("generating") + n("finalising");
  if (working) parts.push(`${working} still working`);
  const blocked = takes.filter((t) => t.status === "blocked");
  if (blocked.length) {
    const free = blocked.every((t) => t.charge && (t.charge.state === "notCharged" || t.charge.state === "refunded"));
    parts.push(`${plural(blocked.length, "take")} blocked${free ? ", not charged" : ""}`);
  }
  if (n("failed")) parts.push(`${n("failed")} failed`);
  if (n("cancelled")) parts.push(`${n("cancelled")} cancelled`);
  if (n("rateLimited")) parts.push(`${n("rateLimited")} paused by the rate limit`);
  return parts.join(", ") + ".";
}

// Columns follow the grid's own width, so the grid works in a side panel as well as a full page.
const COLS: Record<"wide" | "tall", Record<TakeGridDensity, string>> = {
  wide: { comfortable: "grid-cols-1 @lg:grid-cols-2 @4xl:grid-cols-3", compact: "grid-cols-2 @2xl:grid-cols-3 @4xl:grid-cols-4" },
  tall: { comfortable: "grid-cols-2 @2xl:grid-cols-3 @4xl:grid-cols-4", compact: "grid-cols-2 @lg:grid-cols-3 @2xl:grid-cols-4 @4xl:grid-cols-6" },
};

const isTyping = (el: EventTarget | null) => el instanceof HTMLElement && (el.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName));

/**
 * A set of video takes where running and finished takes sit together. Arrow keys move between takes, Enter opens a
 * finished take, Space selects. One polite live region speaks for the whole grid.
 */
export function TakeGrid({
  takes, mode = "select", value, onChange, ratio = "16:9", groupBy = "none", density: densityProp, onDensityChange,
  showDensityControl = false, onOpen, loadingCount = 0, empty, label = "Takes", shortcuts = true, headingLevel = 3, className,
}: TakeGridProps) {
  const root = useRef<HTMLDivElement>(null);
  const keysId = useId();
  const [innerDensity, setInnerDensity] = useState<TakeGridDensity>("comfortable");
  const density = densityProp ?? innerDensity;
  const setDensity = (d: TakeGridDensity) => { if (densityProp === undefined) setInnerDensity(d); onDensityChange?.(d); };
  const [focusId, setFocusId] = useState<string | undefined>(undefined);
  const GroupH = headingTag(headingLevel);
  const cardLevel = (groupBy === "request" ? Math.min(headingLevel + 1, 6) : headingLevel) as HeadingLevel;

  const canSelect = (t: TakeGridItem) => mode === "select" || t.status === "done";
  const toggle = (t: TakeGridItem) => {
    if (!canSelect(t)) return;
    if (mode === "pick") onChange(value.includes(t.id) ? [] : [t.id]);
    else onChange(value.includes(t.id) ? value.filter((x) => x !== t.id) : [...value, t.id]);
  };

  // The tab stop: the take last focused, else the first selected, else the first take.
  const stopId = takes.some((t) => t.id === focusId) ? focusId : (takes.find((t) => value.includes(t.id)) ?? takes[0])?.id;

  const tiles = () => Array.from(root.current?.querySelectorAll<HTMLElement>("article[data-take-id]") ?? []);

  const onTileKey = (t: TakeGridItem) => (e: KeyboardEvent<HTMLElement>) => {
    if (e.target !== e.currentTarget || e.altKey || e.ctrlKey || e.metaKey) return;
    const all = tiles();
    const from = all.indexOf(e.currentTarget);
    let to = -1;
    if (e.key === "ArrowRight") to = Math.min(from + 1, all.length - 1);
    else if (e.key === "ArrowLeft") to = Math.max(from - 1, 0);
    else if (e.key === "Home") to = 0;
    else if (e.key === "End") to = all.length - 1;
    else if (e.key === "ArrowDown" || e.key === "ArrowUp") {
      // Same column in the next or previous row: the nearest tile by left edge among the closest row.
      const r = e.currentTarget.getBoundingClientRect();
      const down = e.key === "ArrowDown";
      const rows = all.map((el, i) => ({ i, b: el.getBoundingClientRect() })).filter(({ b }) => (down ? b.top > r.top + 1 : b.top < r.top - 1));
      if (rows.length) {
        const rowTop = down ? Math.min(...rows.map((x) => x.b.top)) : Math.max(...rows.map((x) => x.b.top));
        const row = rows.filter((x) => Math.abs(x.b.top - rowTop) < 2);
        to = row.reduce((best, x) => (Math.abs(x.b.left - r.left) < Math.abs(best.b.left - r.left) ? x : best)).i;
      } else to = from;
    } else if (e.key === "Enter") {
      if (t.status === "done") { e.preventDefault(); (onOpen ? onOpen(t.id) : t.onOpen?.()); }
      return;
    } else if (e.key === " ") {
      e.preventDefault();
      toggle(t);
      return;
    } else return;
    e.preventDefault();
    all[to]?.focus();
  };

  const onGridKey = (e: KeyboardEvent<HTMLDivElement>) => {
    if (!shortcuts || e.altKey || e.ctrlKey || e.metaKey || isTyping(e.target)) return;
    if (e.key === "=" || e.key === "+") { e.preventDefault(); setDensity("comfortable"); }
    else if (e.key === "-") { e.preventDefault(); setDensity("compact"); }
  };

  const summary = takeGridSummary(takes);
  const cols = COLS[ratio === "9:16" ? "tall" : "wide"][density];

  if (!takes.length && !loadingCount) {
    return <>{empty ?? <EmptyState variant="first-use" art={<Illustration name="first-run" />} title="No takes yet" className={className}>Write a prompt and generate. Each take appears here while it is made.</EmptyState>}</>;
  }

  const groups: { key: string; title?: string; items: TakeGridItem[] }[] = [];
  if (groupBy === "request") {
    for (const t of takes) {
      const key = t.request ?? "";
      const g = groups.find((x) => x.key === key);
      if (g) g.items.push(t); else groups.push({ key, title: t.request, items: [t] });
    }
  } else groups.push({ key: "all", items: takes });

  let index = 0;
  const selectedCount = value.filter((id) => takes.some((t) => t.id === id)).length;

  return (
    <div ref={root} role="group" aria-label={label} onKeyDown={onGridKey}
      onFocus={(e) => { const id = (e.target as HTMLElement).closest<HTMLElement>("article[data-take-id]")?.dataset.takeId; if (id) setFocusId(id); }}
      className={cn("@container min-w-0", className)}>
      <div className="mb-3 flex flex-wrap items-center justify-between gap-2">
        <p className="text-small leading-4 text-fg-muted">
          <span role="status" aria-live="polite">{summary}</span>
          {selectedCount > 0 && <span> {mode === "pick" ? "1 picked." : `${selectedCount} selected.`}</span>}
        </p>
        {showDensityControl && (
          <SegmentedControl size="sm" label="Tile size" value={density} onChange={setDensity}
            options={[{ value: "comfortable", label: "Large" }, { value: "compact", label: "Small" }]} />
        )}
      </div>
      <p id={keysId} className="sr-only">Arrow keys move between takes. Enter opens a finished take. Space {mode === "pick" ? "picks" : "selects"} it.</p>
      <div className="space-y-5">
        {groups.map((g) => (
          <section key={g.key} aria-label={g.title}>
            {g.title && <GroupH className="mb-2 line-clamp-2 text-small font-medium text-fg">{g.title}</GroupH>}
            <ul className={cn("m-0 grid list-none gap-3 p-0", cols)}>
              {g.items.map((t) => {
                const i = index++;
                const { request: _r, ...card } = t;
                void _r;
                return (
                  <Reveal as="li" key={t.id} index={i} className="min-w-0">
                    <TakeCard {...card} ratio={ratio} announce={false} headingLevel={cardLevel}
                      tabIndex={t.id === stopId ? 0 : -1} onKeyDown={onTileKey(t)}
                      describedBy={keysId} compact={density === "compact"}
                      onOpen={t.status === "done" && (onOpen || t.onOpen) ? () => (onOpen ? onOpen(t.id) : t.onOpen?.()) : undefined}
                      selectMode={canSelect(t) ? (mode === "pick" ? "single" : "multi") : undefined}
                      selected={value.includes(t.id)} onSelect={() => toggle(t)} />
                  </Reveal>
                );
              })}
            </ul>
          </section>
        ))}
        {loadingCount > 0 && (
          <ul aria-label="Takes on the way" className={cn("m-0 grid list-none gap-3 p-0", cols)}>
            {Array.from({ length: loadingCount }, (_, i) => (
              <li key={i} className="min-w-0 overflow-hidden rounded-card border border-line bg-surface">
                <div aria-hidden className={cn("w-full bg-sunken", takeRatioClass(ratio))} />
                <div aria-hidden className="space-y-2 p-3"><div className="h-3 w-1/3 rounded-control bg-sunken" /><div className="h-3 w-2/3 rounded-control bg-sunken" /></div>
              </li>
            ))}
            <li className="sr-only">Loading {plural(loadingCount, "take")}</li>
          </ul>
        )}
      </div>
    </div>
  );
}
```

## tattva/organisms/TakePicker.tsx

```tsx
import { useCallback, useEffect, useId, useRef, useState } from "react";
import type { KeyboardEvent } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, CheckIcon, RegenerateIcon, RetryIcon } from "../lib/icons";
import { type HeadingLevel, headingTag } from "../lib/useFocusAfter";
import { AIBadge } from "../atoms/AIBadge";
import { AIMark } from "../atoms/AIMark";
import { Button } from "../atoms/Button";
import { Kbd } from "../atoms/Kbd";
import { Skeleton } from "../atoms/Skeleton";
import { Waveform, formatMediaTime } from "../atoms/Waveform";
import type { WaveformRegion } from "../atoms/Waveform";
import { useReducedMotion } from "../hooks/useReducedMotion";

/** pending: a slot kept open while the take is made. streaming: playable while the end is still being made. ready: finished. failed: could not be made. */
export type AudioTakeStatus = "pending" | "streaming" | "ready" | "failed";

export interface AudioTake {
  id: string;
  /** Short name, such as "Take 2". Used in every button name, so keep it unique. */
  label: string;
  status: AudioTakeStatus;
  /** A real audio file. Leave out to simulate playback from `duration`. */
  src?: string;
  /** Loudness per slice, each 0 to 1. Leave out for a plain bar. */
  peaks?: number[];
  /** Length in seconds. */
  duration?: number;
  /** In the streaming state, the seconds made so far. The rest is drawn dashed. */
  madeUpTo?: number;
  /** What sets this take apart from the others, in a few words each, such as "Slower, 84 BPM" or "Piano lead". */
  differs?: string[];
  /** Why it failed, in plain words, such as "The model stopped early. You were not charged." */
  error?: string;
}

export interface TakePickerProps {
  takes: AudioTake[];
  /** Ids of the kept takes. */
  kept: string[];
  onKeptChange: (ids: string[]) => void;
  /** Lets the person keep more than one take. Off: Keep works like a radio choice. */
  multiple?: boolean;
  /** Shows the make-more button. */
  onMore?: () => void;
  /** Text of the make-more button. */
  moreLabel?: string;
  /** Cost shown beside the make-more button, such as "about 10 credits". */
  moreCost?: string;
  /** The takes replace one section of a longer song. Play then runs from `context` seconds before the section to `context` after it, so the seams can be heard, and the section is marked on each waveform. */
  section?: { start: number; end: number; label: string };
  /** Seconds of the surrounding song played before and after the section. */
  context?: number;
  /** Shows Try again on a failed take. */
  onRetryTake?: (id: string) => void;
  /** Heading of the picker. */
  title?: string;
  /** The request the takes were made from, shown under the heading. */
  prompt?: string;
  /** Line by the make-more button. */
  note?: string;
  /** Once every take has finished and none is kept, says in amber that a person must pick one. */
  requirePick?: boolean;
  headingLevel?: HeadingLevel;
  className?: string;
}

const PLAY_EVENT = "tattva-audio-play";
const isTyping = (el: EventTarget | null) => el instanceof HTMLElement && (el.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName));
const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));
const plural = (n: number, one: string, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;

const PlayGlyph = () => <svg aria-hidden viewBox="0 0 24 24" className="size-5" fill="currentColor"><path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.5-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5z" /></svg>;
const PauseGlyph = () => <svg aria-hidden viewBox="0 0 24 24" className="size-5" fill="currentColor"><rect x="6.5" y="5" width="4" height="14" rx="1" /><rect x="13.5" y="5" width="4" height="14" rx="1" /></svg>;

/** The one sentence the picker speaks. It changes only when a take's status changes, never with the clock. */
export function takePickerSummary(takes: AudioTake[], keptCount = 0): string {
  if (!takes.length) return "";
  const n = (s: AudioTakeStatus) => takes.filter((t) => t.status === s).length;
  const ready = n("ready");
  const making = n("pending") + n("streaming");
  const failed = n("failed");
  let text = making && !ready ? `Making ${plural(takes.length, "take")}.`
    : making ? `${ready} of ${plural(takes.length, "take")} ready, ${making} still being made.`
    : `${plural(n("ready"), "take")} ready.`;
  if (failed) text += ` ${failed} failed.`;
  if (!making && n("ready") && !keptCount) text += " Pick one to keep.";
  return text;
}

/**
 * Compare a few AI-made takes of the same request and keep one (or several).
 * Rows are a list, not a radio group: each has Play, a waveform you can seek, the length, what is different, and Keep.
 * One shared playhead runs across every take, so moving from one take to the next plays from the same moment: the A/B comparison.
 * Nothing plays by itself, and starting a take pauses any other player on the page.
 */
export function TakePicker({
  takes, kept, onKeptChange, multiple = false, onMore, moreLabel = "Make 2 more", moreCost, section, context = 3, onRetryTake,
  title = "Takes", prompt, note = "Takes you don't keep stay in history.", requirePick = true, headingLevel = 3, className,
}: TakePickerProps) {
  const id = useId();
  const H = headingTag(headingLevel);
  const reduced = useReducedMotion();
  const audio = useRef<HTMLAudioElement>(null);
  const [playingId, setPlayingId] = useState<string | null>(null);
  const [pos, setPos] = useState(section ? Math.max(0, section.start - context) : 0);
  const posRef = useRef(pos);
  posRef.current = pos;

  const playing = takes.find((t) => t.id === playingId) ?? null;
  const playable = (t: AudioTake) => (t.status === "ready" || t.status === "streaming") && (t.duration ?? 0) > 0;
  // The stretch a take is played over: the whole take, or the section with a little of the song around it.
  const windowOf = useCallback((t: AudioTake): [number, number] => {
    const dur = t.duration ?? 0;
    const made = t.status === "streaming" ? Math.min(dur, t.madeUpTo ?? dur) : dur;
    return section ? [clamp(section.start - context, 0, made), clamp(section.end + context, 0, made)] : [0, made];
  }, [section, context]);

  const stop = useCallback(() => { setPlayingId(null); audio.current?.pause(); }, []);

  const play = (t: AudioTake) => {
    if (!playable(t)) return;
    if (playingId === t.id) { stop(); return; }
    // Same position as the take that was playing, so the switch is an instant A/B. From the start when nothing was.
    const [from, to] = windowOf(t);
    const at = posRef.current;
    const start = at >= from && at < to - 0.25 ? at : from;
    setPos(start);
    setPlayingId(t.id);
    window.dispatchEvent(new CustomEvent(PLAY_EVENT, { detail: id }));
  };

  // Only one player on the page plays at a time.
  useEffect(() => {
    const other = (e: Event) => { if ((e as CustomEvent).detail !== id) stop(); };
    window.addEventListener(PLAY_EVENT, other);
    return () => window.removeEventListener(PLAY_EVENT, other);
  }, [id, stop]);

  // A take that stops being playable (retried, removed) stops playing.
  useEffect(() => { if (playingId && (!playing || !playable(playing))) stop(); });

  // A real file plays through one audio element, starting at the shared position.
  useEffect(() => {
    const a = audio.current;
    if (!a) return;
    if (!playing?.src) { a.pause(); return; }
    const begin = () => { a.currentTime = posRef.current; a.play().catch(() => setPlayingId(null)); };
    if (a.getAttribute("src") !== playing.src) { a.src = playing.src; a.addEventListener("loadedmetadata", begin, { once: true }); }
    else begin();
  }, [playing?.id, playing?.src]); // eslint-disable-line react-hooks/exhaustive-deps

  // Without a file a clock moves the shared position. It ticks less often with reduced motion.
  useEffect(() => {
    if (!playing || playing.src) return;
    const [, to] = windowOf(playing);
    let last = performance.now();
    const t = window.setInterval(() => {
      const now = performance.now();
      const next = posRef.current + (now - last) / 1000;
      last = now;
      if (next >= to) { stop(); setPos(section ? windowOf(playing)[0] : to); } else setPos(next);
    }, reduced ? 250 : 100);
    return () => window.clearInterval(t);
  }, [playing, reduced, windowOf, stop, section]);

  const onTime = () => {
    const a = audio.current;
    if (!a || !playing) return;
    const [from, to] = windowOf(playing);
    if (a.currentTime >= to) { stop(); setPos(section ? from : to); } else setPos(a.currentTime);
  };

  const seek = (t: number) => {
    setPos(t);
    if (audio.current && playing?.src) audio.current.currentTime = t;
  };

  const toggleKeep = (t: AudioTake) => {
    const on = kept.includes(t.id);
    onKeptChange(on ? kept.filter((k) => k !== t.id) : multiple ? [...kept, t.id] : [t.id]);
  };

  // 1 to 9 play that take while focus is in the list. Never while typing, and never with a modifier key.
  const onKey = (e: KeyboardEvent<HTMLElement>) => {
    if (e.altKey || e.ctrlKey || e.metaKey || isTyping(e.target)) return;
    const n = Number(e.key);
    if (!Number.isInteger(n) || n < 1 || n > takes.length) return;
    const t = takes[n - 1];
    if (!playable(t)) return;
    e.preventDefault();
    play(t);
  };

  const making = takes.some((t) => t.status === "pending" || t.status === "streaming");
  const readyCount = takes.filter((t) => t.status === "ready").length;
  const allFailed = takes.length > 0 && takes.every((t) => t.status === "failed");
  const needsPick = requirePick && !making && readyCount > 0 && kept.length === 0;
  const playableCount = takes.filter(playable).length;
  const summary = takePickerSummary(takes, kept.length);
  const regions: WaveformRegion[] | undefined = section ? [{ start: section.start, end: section.end, kind: "ai" }] : undefined;
  const listId = `${id}-list`;

  return (
    <section aria-labelledby={`${id}-h`} className={cn("@container flex min-w-0 flex-col gap-3 rounded-card border border-line bg-surface p-3 sm:p-4", className)}>
      {takes.some((t) => t.src) ? (
        <audio ref={audio} preload="metadata" className="hidden" onTimeUpdate={onTime} onEnded={() => stop()} onError={() => stop()} />
      ) : null}

      <header className="flex flex-wrap items-start gap-x-3 gap-y-1">
        <div className="min-w-0 flex-1">
          <H id={`${id}-h`} className="text-body leading-5 font-semibold text-fg">{title}</H>
          {prompt && <p className="mt-0.5 text-small text-fg-muted">{prompt}</p>}
        </div>
        <AIBadge label="Made by AI" />
      </header>

      {section && (
        <p className="text-small text-fg-muted">
          Each take replaces {section.label}. Play runs from {context} seconds before it to {context} seconds after it, so you hear the joins.
        </p>
      )}

      {needsPick && (
        <p className="flex items-center gap-2 rounded-field bg-attention-soft px-3 py-2 text-compact text-attention-fg">
          <span aria-hidden className="size-1.5 shrink-0 rounded-full bg-attention" />
          <span><span className="font-medium">Pick a take to keep.</span> Listen to each, then press Keep.</span>
        </p>
      )}

      {allFailed ? (
        <div className="flex items-start gap-2 rounded-field bg-sunken px-3 py-3 text-compact text-fg">
          <AlertIcon width={16} height={16} aria-hidden className="mt-0.5 shrink-0 text-danger-fg" />
          <span>None of the takes could be made. {takes[0]?.error ?? "Try again, or change the request."}</span>
        </div>
      ) : (
        <ul id={listId} aria-label={title} onKeyDown={onKey} className="flex flex-col gap-2">
          {takes.map((t, i) => {
            const isKept = kept.includes(t.id);
            const isPlaying = playingId === t.id;
            const dur = t.duration ?? 0;
            const canPlay = playable(t);
            const showPos = clamp(pos, 0, dur);
            if (t.status === "pending") {
              return (
                <li key={t.id} className="flex min-h-22 items-center gap-3 rounded-card border border-dashed border-line-strong px-3 py-2">
                  <span aria-hidden className="relative inline-flex size-11 shrink-0 items-center justify-center rounded-full bg-sunken">
                    <span className="size-2 rounded-full bg-lime ring-1 ring-fg-muted motion-safe:animate-pulse-dot" />
                  </span>
                  <div className="flex min-w-0 flex-1 flex-col gap-2">
                    <span className="text-compact text-fg">Making {t.label.toLowerCase().startsWith("take") ? t.label.toLowerCase() : t.label}</span>
                    <Skeleton className="h-6 w-full motion-reduce:animate-none" />
                  </div>
                </li>
              );
            }
            if (t.status === "failed") {
              return (
                <li key={t.id} className="flex min-h-22 flex-wrap items-center gap-3 rounded-card border border-line bg-sunken px-3 py-2">
                  <AlertIcon width={20} height={20} aria-hidden className="ml-3 shrink-0 text-danger-fg" />
                  <div className="min-w-40 flex-1">
                    <p className="text-compact font-medium text-fg">{t.label} could not be made</p>
                    <p className="text-small text-fg-muted">{t.error ?? "Something went wrong while making it."}</p>
                  </div>
                  {onRetryTake && (
                    <Button size="sm" variant="secondary" onClick={() => onRetryTake(t.id)} leading={<RetryIcon width={14} height={14} />}
                      aria-label={`Try ${t.label} again`} className="pointer-coarse:h-11">Try again</Button>
                  )}
                </li>
              );
            }
            return (
              <li key={t.id}
                className={cn("flex min-h-22 items-start gap-3 rounded-card border px-3 py-2 transition-colors duration-(--dur-fast) motion-safe:animate-rise",
                  isKept ? "border-fg bg-surface forced-colors:border-[Highlight]" : "border-line bg-surface")}>
                <button type="button" onClick={() => play(t)} aria-label={`Play ${t.label}`} aria-pressed={isPlaying}
                  className="mt-1 inline-flex size-11 shrink-0 cursor-pointer items-center justify-center rounded-full bg-accent text-on-accent transition-colors duration-(--dur-instant) hover:bg-accent-hover">
                  {isPlaying ? <PauseGlyph /> : <PlayGlyph />}
                </button>
                <div className="flex min-w-0 flex-1 flex-col gap-1">
                  <div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
                    <span aria-hidden className="inline-flex size-4 shrink-0 items-center justify-center rounded-full bg-lime text-on-lime"><AIMark size={16} className="size-3" /></span>
                    <span className="text-compact font-medium text-fg">{t.label}</span>
                    {playableCount > 1 && i < 9 && <span aria-hidden className="pointer-coarse:hidden"><Kbd>{i + 1}</Kbd></span>}
                    {isKept && <span className="inline-flex items-center gap-1 text-small font-medium text-fg"><CheckIcon width={14} height={14} aria-hidden />Kept</span>}
                    <span className="text-small text-fg-muted tabular-nums">
                      {isPlaying ? <>{formatMediaTime(showPos)} / </> : null}{formatMediaTime(dur)}
                      {t.status === "streaming" && <> · Still making the end</>}
                      {isPlaying && <span className="sr-only"> playing</span>}
                    </span>
                  </div>
                  {t.differs && t.differs.length > 0 && (
                    <p className="flex flex-wrap gap-1 text-small text-fg-muted">
                      <span className="sr-only">Different: </span>
                      {t.differs.map((d, k) => (
                        <span key={k} className="rounded-control border border-line bg-sunken px-1.5 text-micro leading-5 text-fg-muted">{d}{k < t.differs!.length - 1 && <span className="sr-only">,</span>}</span>
                      ))}
                    </p>
                  )}
                  <Waveform peaks={t.peaks ?? []} simple={!t.peaks} duration={dur} position={showPos} onSeek={canPlay ? seek : undefined}
                    label={`Seek, ${t.label}`} height={32} regions={regions}
                    pendingFrom={t.status === "streaming" ? (t.madeUpTo ?? dur) : undefined} />
                </div>
                <Button size="sm" variant={isKept ? "primary" : "secondary"} onClick={() => toggleKeep(t)} aria-pressed={isKept}
                  aria-label={`Keep ${t.label}`} disabled={t.status !== "ready"} title={t.status !== "ready" ? "You can keep it once it has finished" : undefined}
                  leading={isKept ? <CheckIcon width={14} height={14} /> : undefined}
                  className="mt-1.5 shrink-0 pointer-coarse:h-11 pointer-coarse:min-w-11">
                  Keep
                </Button>
              </li>
            );
          })}
        </ul>
      )}

      {playableCount > 1 && (
        <p className="flex flex-wrap items-center gap-1.5 text-small text-fg-muted pointer-coarse:hidden">
          <Kbd>1</Kbd>to<Kbd>{Math.min(9, takes.length)}</Kbd>play a take while you are in the list. Switching takes keeps your place.
        </p>
      )}

      {(onMore || note) && (
        <footer className="flex flex-wrap items-center gap-x-3 gap-y-2 border-t border-line pt-3">
          {note && <p className="min-w-40 flex-1 text-small text-fg-muted">{note}</p>}
          {onMore && (
            <span className="flex flex-wrap items-center gap-2">
              {moreCost && <span className="text-small text-fg-muted">{moreCost}</span>}
              <Button size="sm" variant="lime" onClick={onMore} busy={making} busyLabel="Still making takes"
                leading={<RegenerateIcon width={14} height={14} />} className="pointer-coarse:h-11">{moreLabel}</Button>
            </span>
          )}
        </footer>
      )}

      {/* Status changes only: never the clock, never each take on its own. */}
      <span role="status" className="sr-only">{summary}</span>
    </section>
  );
}
```

## tattva/organisms/TaskTree.tsx

```tsx
import { useEffect, useRef, useState } from "react";
import { cn } from "../lib/cn";
import { ChevronIcon, XIcon } from "../lib/icons";
import { AgentStatusIcon, type AgentStatus } from "../atoms/AgentStatusIcon";
import { Button, IconButton } from "../atoms/Button";

export type TaskStatus = "running" | "done" | "failed" | "stopped" | "partial";

export interface TaskNode {
  id: string;
  name: string;
  status: TaskStatus;
  children?: TaskNode[];
}

export interface TaskTreeLabels {
  status?: Partial<Record<TaskStatus, string>>;
  open?: string;
  dismiss?: string;
  expand?: string;
  collapse?: string;
}

export interface TaskTreeProps {
  tasks: TaskNode[];
  /** Called when the person opens a row, for example to show that subagent's transcript. */
  onOpen?: (id: string) => void;
  /** Called when a row is dismissed: the Dismiss button on a failed row, or `dismissAfterMs` on a done row. The tree hides the row at once, and the app should also remove it from its data. */
  onDismiss?: (id: string) => void;
  /** Milliseconds after which a done row is dismissed and onDismiss(id) is called. Off when undefined. Failed rows never auto-dismiss. A done parent waits until its nested rows are done too. */
  dismissAfterMs?: number;
  /** Ids that start collapsed. Everything else starts expanded. */
  defaultCollapsed?: string[];
  labels?: TaskTreeLabels;
  className?: string;
}

const statusText: Record<TaskStatus, string> = { running: "Running", done: "Done", failed: "Failed", stopped: "Stopped", partial: "Partial" };
const mapped: Record<Exclude<TaskStatus, "partial">, AgentStatus> = { running: "working", done: "completed", failed: "failed", stopped: "stopped" };

const countDescendants = (n: TaskNode): number => (n.children ?? []).reduce((a, c) => a + 1 + countDescendants(c), 0);

/** Half-filled circle for Partial, which the shared status icon has no shape for. */
function PartialGlyph() {
  return (
    <svg width={16} height={16} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" aria-hidden className="shrink-0 text-fg-muted">
      <circle cx="12" cy="12" r="9" /><path d="M12 3a9 9 0 0 1 0 18z" fill="currentColor" />
    </svg>
  );
}

/**
 * Nested subagent or task rows. Each row shows a name, a status written out beside an icon shape,
 * a (+N) count of nested tasks, and an Open action. Parents expand and collapse. Failed rows stay
 * until the person dismisses them. With dismissAfterMs, done rows are dismissed after that delay.
 * The app owns the data and removes the row in onDismiss.
 */
export function TaskTree({ tasks, onOpen, onDismiss, dismissAfterMs, defaultCollapsed = [], labels, className }: TaskTreeProps) {
  const l = { open: "Open", dismiss: "Dismiss", expand: "Expand", collapse: "Collapse", ...labels };
  const names = { ...statusText, ...labels?.status };
  const [collapsed, setCollapsed] = useState<Set<string>>(new Set(defaultCollapsed));
  const [dismissed, setDismissed] = useState<Set<string>>(new Set());
  const [announce, setAnnounce] = useState("");

  const timers = useRef(new Map<string, ReturnType<typeof setTimeout>>());
  const latest = useRef({ onDismiss });
  latest.current = { onDismiss };

  const toggle = (id: string) => setCollapsed((c) => { const n = new Set(c); if (n.has(id)) n.delete(id); else n.add(id); return n; });
  const dismiss = (n: TaskNode) => { setDismissed((d) => new Set(d).add(n.id)); onDismiss?.(n.id); setAnnounce(`Dismissed ${n.name}`); };

  // Schedule one timer per finished row. A timer is kept while its row stays done, so re-renders do not restart it.
  useEffect(() => {
    const live = timers.current;
    const due = new Map<string, TaskNode>();
    const walk = (nodes: TaskNode[]): boolean => {
      let allDone = true;
      for (const n of nodes) {
        if (dismissed.has(n.id)) continue;
        const kidsDone = walk(n.children ?? []);
        if (n.status === "done" && kidsDone) due.set(n.id, n); else allDone = false;
      }
      return allDone;
    };
    if (dismissAfterMs !== undefined) walk(tasks);
    for (const [id, t] of live) if (!due.has(id)) { clearTimeout(t); live.delete(id); }
    for (const [id, n] of due) {
      if (live.has(id)) continue;
      live.set(id, setTimeout(() => {
        live.delete(id);
        setDismissed((d) => new Set(d).add(id));
        latest.current.onDismiss?.(id);
        setAnnounce(`Dismissed ${n.name}`);
      }, dismissAfterMs));
    }
  }, [tasks, dismissAfterMs, dismissed]);
  useEffect(() => () => { for (const t of timers.current.values()) clearTimeout(t); timers.current.clear(); }, []);

  const renderList = (nodes: TaskNode[], depth: number) => (
    <ul className="space-y-0.5">
      {nodes.filter((n) => !dismissed.has(n.id)).map((n) => {
        const kids = (n.children ?? []).filter((c) => !dismissed.has(c.id));
        const total = countDescendants({ ...n, children: kids });
        const isOpen = !collapsed.has(n.id);
        return (
          <li key={n.id}>
            <div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 rounded-card py-1 pr-2 hover:bg-hover sm:rounded-control" style={{ paddingLeft: `calc(var(--spacing) * ${depth * 5 + 1})` }}>
              {kids.length ? (
                <IconButton size="sm" label={`${isOpen ? l.collapse : l.expand} ${n.name}`} aria-expanded={isOpen} onClick={() => toggle(n.id)}>
                  <ChevronIcon width={12} height={12} className={cn("transition-transform", isOpen && "rotate-90")} />
                </IconButton>
              ) : <span aria-hidden className="size-7 shrink-0" />}
              {n.status === "partial" ? <PartialGlyph /> : <AgentStatusIcon status={mapped[n.status]} label={names[n.status]} />}
              <span className="min-w-28 flex-1 truncate text-compact font-medium">{n.name}</span>
              {total > 0 && <span className="text-small leading-4 text-fg-subtle tabular-nums"><span className="sr-only">{total} nested tasks </span><span aria-hidden>(+{total})</span></span>}
              <span className="ml-auto flex items-center gap-2"><span className="text-small leading-4 text-fg-muted">{names[n.status]}</span>
              <Button size="sm" variant="ghost" onClick={() => onOpen?.(n.id)} aria-label={`${l.open} ${n.name}`}>{l.open}</Button>
              {n.status === "failed" && <IconButton size="sm" label={`${l.dismiss} ${n.name}`} onClick={() => dismiss(n)}><XIcon width={12} height={12} /></IconButton>}</span>
            </div>
            {kids.length > 0 && isOpen && renderList(kids, depth + 1)}
          </li>
        );
      })}
    </ul>
  );

  return (
    <div className={cn("rounded-card border border-line bg-surface p-2", className)}>
      <div role="group" aria-label="Tasks">{renderList(tasks, 0)}</div>
      <p role="status" aria-live="polite" className="sr-only">{announce}</p>
    </div>
  );
}
```

## tattva/organisms/ToolMenu.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { CheckIcon, PlusIcon } from "../lib/icons";
import { IconButton } from "../atoms/Button";
import { Popover } from "../atoms/Popover";

export interface ToolMenuAction {
  id: string;
  label: string;
  icon?: ReactNode;
  description?: string;
  disabled?: boolean;
}

export interface ToolMenuTool extends ToolMenuAction {
  /** Whether the tool is switched on. Owned by the parent. */
  checked: boolean;
}

export interface ToolMenuConnector extends ToolMenuAction {
  /** Status text shown at the end of the row, for example "Connected". */
  status?: string;
}

export interface ToolMenuProps {
  /** Attach actions such as "Upload file". Selecting one closes the menu. */
  attach?: ToolMenuAction[];
  /** Tools with a checked state. Toggling keeps the menu open. */
  tools?: ToolMenuTool[];
  /** Connected sources. Selecting one closes the menu. */
  connectors?: ToolMenuConnector[];
  onAttach?: (id: string) => void;
  onToggle?: (id: string, checked: boolean) => void;
  onConnector?: (id: string) => void;
  /** Section headings. */
  labels?: { attach?: string; tools?: string; connectors?: string };
  /** Name of the trigger button and the menu. */
  label?: string;
  align?: "start" | "end";
  side?: "bottom" | "top";
}

function Row({ icon, label, description, trailing, disabled, role, checked, onSelect }: ToolMenuAction & { trailing?: ReactNode; role: "menuitem" | "menuitemcheckbox"; checked?: boolean; onSelect: () => void }) {
  return (
    <button type="button" role={role} aria-checked={role === "menuitemcheckbox" ? !!checked : undefined} disabled={disabled} tabIndex={-1} onClick={onSelect}
      className={cn("flex w-full cursor-pointer items-center gap-3 rounded-xl px-3 py-2 text-left transition-colors hover:bg-hover focus-visible:bg-hover disabled:pointer-events-none disabled:text-disabled-fg")}>
      {icon && <span className="flex shrink-0 items-center text-fg-muted">{icon}</span>}
      <span className="min-w-0 flex-1">
        <span className="block truncate text-compact font-medium text-fg">{label}</span>
        {description && <span className="block truncate text-small leading-4 text-fg-muted">{description}</span>}
      </span>
      {trailing}
      {role === "menuitemcheckbox" && <span className="flex size-4 shrink-0 items-center justify-center text-fg">{checked && <CheckIcon width={16} height={16} />}</span>}
    </button>
  );
}

function Group({ title, children }: { title?: string; children: ReactNode }) {
  return (
    <div role="group" aria-label={title} className="py-1 not-first:border-t not-first:border-line">
      {title && <div aria-hidden className="px-3 pt-1 pb-1 text-nano uppercase tracking-caps text-fg-muted">{title}</div>}
      {children}
    </div>
  );
}

/** The composer plus menu. Sections for attaching, tools with a checked state, and connectors. Focus, arrow keys, Home, End, Escape and Tab come from Popover with role menu. */
export function ToolMenu({ attach = [], tools = [], connectors = [], onAttach, onToggle, onConnector, labels, label = "Add to message", align = "start", side = "top" }: ToolMenuProps) {
  return (
    <Popover role="menu" label={label} align={align} side={side} panelClassName="w-72 max-w-[calc(100vw-2rem)]"
      trigger={({ toggle, triggerProps }) => (
        <IconButton label={label} variant="secondary" onClick={toggle} {...triggerProps}><PlusIcon /></IconButton>
      )}>
      {({ close }) => (
        <div>
          {attach.length > 0 && (
            <Group title={labels?.attach ?? "Attach"}>
              {attach.map((a) => <Row key={a.id} role="menuitem" {...a} onSelect={() => { onAttach?.(a.id); close(); }} />)}
            </Group>
          )}
          {tools.length > 0 && (
            <Group title={labels?.tools ?? "Tools"}>
              {tools.map((t) => <Row key={t.id} role="menuitemcheckbox" {...t} onSelect={() => onToggle?.(t.id, !t.checked)} />)}
            </Group>
          )}
          {connectors.length > 0 && (
            <Group title={labels?.connectors ?? "Connectors"}>
              {connectors.map((c) => (
                <Row key={c.id} role="menuitem" {...c} onSelect={() => { onConnector?.(c.id); close(); }}
                  trailing={c.status ? <span className="shrink-0 text-small leading-4 text-fg-subtle">{c.status}</span> : undefined} />
              ))}
            </Group>
          )}
        </div>
      )}
    </Popover>
  );
}
```

## tattva/organisms/TranscriptAudioEditor.tsx

```tsx
import { useEffect, useId, useMemo, useRef, useState } from "react";
import type { KeyboardEvent, PointerEvent } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, RetryIcon, UndoIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { Kbd } from "../atoms/Kbd";
import { TextField } from "../atoms/TextField";
import { Waveform, formatMediaTime, speakMediaTime } from "../atoms/Waveform";
import type { WaveformRegion } from "../atoms/Waveform";
import { AILabel } from "../molecules/AILabel";
import { AudioPlayer } from "../molecules/AudioPlayer";
import type { AudioPlayerProps } from "../molecules/AudioPlayer";

/**
 * original: untouched. removed: cut, skipped when playing. regenerating: new audio is being made. regenerated: new audio
 * is ready and waits for Keep or Revert. kept: new audio accepted. reverted: back to the recording. failed: new audio could not be made.
 */
export type TranscriptSpanState = "original" | "removed" | "regenerating" | "regenerated" | "kept" | "reverted" | "failed";

export interface TranscriptWord {
  id: string;
  text: string;
  /** Start and end in seconds from the start of the recording. */
  start: number;
  end: number;
  /** Key into `speakers`. */
  speaker?: string;
  /** A filler word such as "um" or "you know". Marked with a dotted underline and offered for removal in one go. */
  filler?: boolean;
}

export interface TranscriptEditSpan {
  id: string;
  /** The words this edit covers, in order. */
  wordIds: string[];
  state: TranscriptSpanState;
  /** The new words, for a span whose words were changed. Shown in place of the old ones. */
  newText?: string;
  /** Why making new audio failed, in plain words. */
  error?: string;
}

export interface TranscriptAudioEditorProps {
  words: TranscriptWord[];
  /** Edits so far. Words in no span are original. */
  spans?: TranscriptEditSpan[];
  /** Who is who. Each run of words by one speaker becomes a paragraph headed by the name. */
  speakers?: Record<string, { label: string }>;
  /** Wires in a docked player. The part then keeps the time itself and skips removed words while playing. */
  audio?: Omit<AudioPlayerProps, "current" | "onTimeChange" | "compact" | "regions" | "markers" | "madeUpTo">;
  /** Loudness per slice, each 0 to 1, when you keep your own player. With `audio` its peaks are used. */
  peaks?: number[] | null;
  /** Length in seconds. Defaults to the audio's duration, or the end of the last word. */
  duration?: number;
  /** Playback position in seconds, when your own player keeps it. */
  position?: number;
  /** Called when the person moves the playhead: a word click, the waveform or Play on a new phrase. */
  onSeek?: (seconds: number) => void;
  /** Called with the words to cut. Make a `removed` span for them. */
  onRemove?: (wordIds: string[]) => void;
  /** Called with the words to replace and the new text (leave the text out to remake the same words). Make a `regenerating` span. */
  onRegenerate?: (wordIds: string[], newText?: string) => void;
  /** Shows Smooth edit: remake the audio around a cut so it does not jump. */
  onSmooth?: (wordIds: string[]) => void;
  onKeep?: (spanId: string) => void;
  onRetry?: (spanId: string) => void;
  /** Back to the recording. Also restores removed words. */
  onRevert?: (spanId: string) => void;
  /** Undoes the last edit. Also on Ctrl+Z or Cmd+Z in the editor. */
  onUndo?: () => void;
  /** What Undo will undo, such as "Undo: removed 2 words". */
  undoLabel?: string;
  /** False when there is no consented voice to make new audio with. Change words then says why. */
  voiceAvailable?: boolean;
  /** Whose voice new audio uses, such as "Maya Okafor, consent on file". */
  voiceName?: string;
  /** Why the voice is not available, shown in one line. */
  voiceUnavailableReason?: string;
  /** Limits of the voice model, said before the person types. */
  limits?: { maxChars?: number; languages?: string[] };
  /** Name of the transcript. */
  label?: string;
  className?: string;
}

interface Token {
  key: string;
  wordIds: string[];
  text: string;
  start: number;
  end: number;
  speaker?: string;
  span?: TranscriptEditSpan;
  removed: boolean;
  filler: boolean;
}

const REPLACED: TranscriptSpanState[] = ["regenerating", "regenerated", "kept", "failed"];
const PENDING: TranscriptSpanState[] = ["regenerating", "regenerated", "failed"];
const isTyping = (el: EventTarget | null) => el instanceof HTMLElement && (el.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName));
const plural = (n: number, w: string) => `${n} ${w}${n === 1 ? "" : "s"}`;

/** Words and edits flattened into what is shown: one token per word, or one per changed phrase. */
function buildTokens(words: TranscriptWord[], spans: TranscriptEditSpan[]): Token[] {
  const spanOf = new Map<string, TranscriptEditSpan>();
  for (const s of spans) if (s.state !== "original" && s.state !== "reverted") for (const w of s.wordIds) spanOf.set(w, s);
  const out: Token[] = [];
  const done = new Set<string>();
  for (const w of words) {
    const s = spanOf.get(w.id);
    if (s && REPLACED.includes(s.state)) {
      if (done.has(s.id)) continue;
      done.add(s.id);
      const ws = words.filter((x) => s.wordIds.includes(x.id));
      out.push({ key: w.id, wordIds: ws.map((x) => x.id), text: s.newText ?? ws.map((x) => x.text).join(" "), start: ws[0].start, end: ws[ws.length - 1].end, speaker: w.speaker, span: s, removed: false, filler: false });
    } else {
      out.push({ key: w.id, wordIds: [w.id], text: w.text, start: w.start, end: w.end, speaker: w.speaker, span: s, removed: s?.state === "removed", filler: !!w.filler });
    }
  }
  return out;
}

/** Stretches of time that playback skips: from a removed word to the next word that stays. */
function removedRuns(tokens: Token[], dur: number): { start: number; end: number }[] {
  const runs: { start: number; end: number }[] = [];
  for (let i = 0; i < tokens.length; i++) {
    if (!tokens[i].removed) continue;
    let j = i;
    while (j < tokens.length && tokens[j].removed) j++;
    runs.push({ start: tokens[i].start, end: tokens[j]?.start ?? dur });
    i = j;
  }
  return runs;
}

/**
 * Edit speech by editing its words. Each word is tied to a time; removing words cuts the audio, and changing words makes
 * new audio for that phrase with a consented voice. The waveform shows the same cuts and new phrases. New audio is marked
 * as AI and waits for Keep or Revert. The words are a selectable list with one tab stop; Delete removes, D changes words,
 * Enter keeps, Ctrl+Z undoes. Words are never announced as they play.
 */
export function TranscriptAudioEditor({
  words, spans = [], speakers, audio, peaks, duration, position, onSeek, onRemove, onRegenerate, onSmooth, onKeep, onRetry, onRevert,
  onUndo, undoLabel = "Undo", voiceAvailable = true, voiceName, voiceUnavailableReason, limits, label = "Transcript", className,
}: TranscriptAudioEditorProps) {
  const uid = useId();
  const hintId = `${uid}-hint`;
  const tokens = useMemo(() => buildTokens(words, spans), [words, spans]);
  const tokenOfWord = useMemo(() => {
    const m = new Map<string, number>();
    tokens.forEach((t, i) => t.wordIds.forEach((w) => m.set(w, i)));
    return m;
  }, [tokens]);
  const dur = audio?.duration ?? duration ?? (words.length ? words[words.length - 1].end : 0);
  const runs = useMemo(() => removedRuns(tokens, dur), [tokens, dur]);

  // Cursor and selection are kept as word ids, so they survive words merging into a changed phrase.
  const [cursorId, setCursorId] = useState<string | null>(null);
  const [sel, setSel] = useState<{ anchor: string; focus: string } | null>(null);
  const [changing, setChanging] = useState(false);
  const [draft, setDraft] = useState("");
  const [said, setSaid] = useState("");
  const [ownTime, setOwnTime] = useState(0);
  const [seekTo, setSeekTo] = useState<number | undefined>(undefined);
  const [playing, setPlaying] = useState(false);
  const [, setFocusTick] = useState(0);
  const optionRefs = useRef<Map<string, HTMLSpanElement>>(new Map());
  const playerRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);
  const dragging = useRef(false);

  const time = audio ? ownTime : position ?? 0;
  const idx = (id: string | null) => (id ? tokenOfWord.get(id) ?? -1 : -1);
  const cursor = Math.max(0, idx(cursorId));
  const range = sel ? [Math.min(idx(sel.anchor), idx(sel.focus)), Math.max(idx(sel.anchor), idx(sel.focus))] as const : null;
  const picked = range && range[0] >= 0 ? tokens.slice(range[0], range[1] + 1) : [];
  const current = playing || time > 0 ? tokens.findIndex((t) => !t.removed && time >= t.start && time < t.end) : -1;

  // Announce edits finishing or failing. Status changes only, never the word being played.
  const prev = useRef(new Map<string, TranscriptSpanState>());
  useEffect(() => {
    for (const s of spans) {
      const was = prev.current.get(s.id);
      if (was === "regenerating" && s.state === "regenerated") setSaid("New audio ready. Play it, then keep or revert.");
      if (was === "regenerating" && s.state === "failed") setSaid(`Could not make new audio.${s.error ? ` ${s.error}` : ""}`);
    }
    prev.current = new Map(spans.map((s) => [s.id, s.state]));
  }, [spans]);

  const focusToken = (i: number) => {
    const t = tokens[Math.max(0, Math.min(tokens.length - 1, i))];
    if (!t) return;
    setCursorId(t.key);
    optionRefs.current.get(t.key)?.focus();
  };
  // After an edit the cursor's word may have merged into a phrase; put focus back on whatever holds it now.
  const wantFocus = useRef(false);
  const refocus = () => { wantFocus.current = true; setFocusTick((n) => n + 1); };
  useEffect(() => {
    if (!wantFocus.current) return;
    wantFocus.current = false;
    const t = tokens[Math.max(0, idx(cursorId))];
    if (t) optionRefs.current.get(t.key)?.focus();
  });

  const seek = (t: number) => {
    if (audio) { setSeekTo(t); setOwnTime(t); }
    onSeek?.(t);
  };

  // Playback skips removed words.
  const onPlayerTime = (t: number) => {
    const run = runs.find((r) => t >= r.start && t < r.end);
    if (run && playing) { setSeekTo(run.end); setOwnTime(run.end); return; }
    setOwnTime(t);
    setSeekTo(undefined);
  };

  const playFrom = (t: number) => {
    seek(t);
    if (!audio) return;
    // The docked player owns playback; press its own Play button once the seek has landed.
    window.setTimeout(() => {
      const btn = playerRef.current?.querySelector<HTMLButtonElement>('button[aria-pressed="false"]');
      btn?.click();
    }, 60);
  };

  /* ---------- what the selection allows ---------- */
  const pickedIds = picked.flatMap((t) => t.wordIds);
  const pickedText = picked.map((t) => t.text).join(" ");
  const allRemoved = picked.length > 0 && picked.every((t) => t.removed);
  const someRemoved = picked.some((t) => t.removed);
  const pending = picked.some((t) => t.span && PENDING.includes(t.span.state));
  const speakersIn = new Set(picked.map((t) => t.speaker));
  const maxChars = limits?.maxChars;
  const changeBlocked =
    !voiceAvailable ? voiceUnavailableReason ?? "No voice to use. The speaker has not agreed to their voice being used."
    : pending ? "Keep or revert the new audio first."
    : allRemoved ? "These words are removed. Restore them first."
    : someRemoved ? "The selection crosses a cut. Select words on one side of it."
    : speakersIn.size > 1 ? "Select words from one speaker."
    : maxChars !== undefined && pickedText.length > maxChars ? `Too long: ${pickedText.length} of ${maxChars} characters. Select fewer words.`
    : null;
  const limitText = [maxChars !== undefined ? `Up to ${maxChars} characters` : "", limits?.languages?.length ? `${limits.languages.join(" or ")} only` : ""].filter(Boolean).join(". ");

  const fillers = tokens.filter((t) => t.filler && !t.removed && !t.span);

  const remove = () => {
    if (!onRemove || !picked.length) return;
    if (allRemoved) { restore(); return; }
    const ids = picked.filter((t) => !t.removed && t.span?.state !== "regenerating").flatMap((t) => t.wordIds);
    if (!ids.length) return;
    onRemove(ids);
    setSaid(`Removed ${plural(ids.length, "word")}. Playback will skip ${ids.length === 1 ? "it" : "them"}.`);
    setSel(null);
    refocus();
  };
  const restore = () => {
    const ids = new Set(picked.filter((t) => t.removed && t.span).map((t) => t.span!.id));
    ids.forEach((id) => onRevert?.(id));
    setSaid(`Restored ${plural(picked.filter((t) => t.removed).length, "word")}.`);
    setSel(null);
    refocus();
  };
  const openChange = () => {
    if (!onRegenerate || !picked.length || changeBlocked) return;
    setDraft(pickedText);
    setChanging(true);
    window.requestAnimationFrame(() => { inputRef.current?.focus(); inputRef.current?.select(); });
  };
  const closeChange = () => { setChanging(false); refocus(); };
  const submitChange = () => {
    const text = draft.trim();
    if (!text || !onRegenerate || (maxChars !== undefined && text.length > maxChars)) return;
    onRegenerate(pickedIds, text === pickedText ? undefined : text);
    setSaid("Making new audio.");
    setChanging(false);
    setSel(null);
    refocus();
  };
  const keep = (s: TranscriptEditSpan) => { onKeep?.(s.id); setSaid("Kept the new audio."); };
  const revert = (s: TranscriptEditSpan) => { onRevert?.(s.id); setSaid("Back to the original audio."); };
  const retry = (s: TranscriptEditSpan) => { onRetry?.(s.id); setSaid("Making new audio."); };

  /* ---------- keyboard and pointer ---------- */
  const paraStarts = useMemo(() => tokens.map((t, i) => i === 0 || tokens[i - 1].speaker !== t.speaker), [tokens]);
  const move = (to: number, extend: boolean) => {
    const t = tokens[Math.max(0, Math.min(tokens.length - 1, to))];
    if (!t) return;
    if (extend) setSel({ anchor: sel?.anchor ?? tokens[cursor].key, focus: t.key });
    focusToken(to);
  };

  const onListKey = (e: KeyboardEvent<HTMLDivElement>) => {
    if (isTyping(e.target) || e.altKey) return;
    const mod = e.ctrlKey || e.metaKey;
    const k = e.key;
    const i = cursor;
    if (mod && (k === "z" || k === "Z") && !e.shiftKey) { if (onUndo) { e.preventDefault(); onUndo(); setSaid(`${undoLabel}. Done.`); } return; }
    if (mod && (k === "a" || k === "A")) { e.preventDefault(); setSel({ anchor: tokens[0].key, focus: tokens[tokens.length - 1].key }); return; }
    if (mod && k !== "Home" && k !== "End") return;
    let to: number | null = null;
    if (k === "ArrowRight") to = i + 1;
    else if (k === "ArrowLeft") to = i - 1;
    else if (k === "ArrowDown") { to = paraStarts.findIndex((p, j) => p && j > i); if (to < 0) to = tokens.length - 1; }
    else if (k === "ArrowUp") { let j = i; while (j > 0 && !paraStarts[j]) j--; to = j === i ? Math.max(0, paraStarts.lastIndexOf(true, i - 1)) : j; }
    else if (k === "Home") { let j = i; while (!mod && j > 0 && !paraStarts[j]) j--; to = mod ? 0 : j; }
    else if (k === "End") { let j = i; while (!mod && j < tokens.length - 1 && !paraStarts[j + 1]) j++; to = mod ? tokens.length - 1 : j; }
    if (to !== null) { e.preventDefault(); if (!e.shiftKey) setSel(null); move(to, e.shiftKey); return; }
    if (k === " ") { e.preventDefault(); const t = tokens[i]; setSel(sel && picked.length === 1 && picked[0] === t ? null : { anchor: t.key, focus: t.key }); return; }
    if (k === "Escape") { if (sel) { e.preventDefault(); setSel(null); } return; }
    if ((k === "Delete" || k === "Backspace") && picked.length && onRemove) { e.preventDefault(); remove(); return; }
    if ((k === "d" || k === "D") && !e.shiftKey && picked.length) { e.preventDefault(); openChange(); return; }
    if (k === "Enter") {
      e.preventDefault();
      const t = tokens[i];
      if (t.span?.state === "regenerated" && onKeep) keep(t.span);
      else seek(t.start);
    }
  };

  const onRootKey = (e: KeyboardEvent<HTMLDivElement>) => {
    if (isTyping(e.target) || !(e.ctrlKey || e.metaKey) || e.shiftKey || (e.key !== "z" && e.key !== "Z") || !onUndo) return;
    if ((e.target as HTMLElement).getAttribute("role") === "option") return; // handled by the list
    e.preventDefault();
    onUndo();
    setSaid(`${undoLabel}. Done.`);
  };

  const down = (ev: PointerEvent<HTMLSpanElement>, i: number) => {
    if (ev.pointerType === "mouse" && ev.button !== 0) return;
    const t = tokens[i];
    if (ev.shiftKey) { setSel({ anchor: sel?.anchor ?? tokens[cursor].key, focus: t.key }); setCursorId(t.key); return; }
    setCursorId(t.key);
    setSel({ anchor: t.key, focus: t.key });
    if (ev.pointerType === "mouse") dragging.current = true;
    seek(t.start);
  };
  const enter = (i: number) => {
    if (!dragging.current) return;
    setSel((s) => s && { anchor: s.anchor, focus: tokens[i].key });
    setCursorId(tokens[i].key);
  };
  useEffect(() => {
    const up = () => { dragging.current = false; };
    window.addEventListener("pointerup", up);
    return () => window.removeEventListener("pointerup", up);
  }, []);

  // A selection that no longer exists (its words merged or were undone) is dropped.
  useEffect(() => { if (sel && (idx(sel.anchor) < 0 || idx(sel.focus) < 0)) setSel(null); });

  /* ---------- summary ---------- */
  const cuts = runs.length;
  const changed = spans.filter((s) => REPLACED.includes(s.state)).length;
  const removedSeconds = runs.reduce((a, r) => a + (r.end - r.start), 0);
  const summary = cuts || changed
    ? `Edited: ${[cuts && plural(cuts, "cut"), changed && plural(changed, "changed phrase")].filter(Boolean).join(", ")}. Length ${formatMediaTime(dur - removedSeconds)}, was ${formatMediaTime(dur)}.`
    : "No edits yet.";

  /* ---------- waveform ---------- */
  const regions: WaveformRegion[] = [
    ...runs.map((r) => ({ start: r.start, end: r.end, kind: "highlight" as const, label: "Removed, skipped when playing" })),
    ...tokens.filter((t) => t.span && REPLACED.includes(t.span.state) && t.span.state !== "failed")
      .map((t) => ({ start: t.start, end: t.end, kind: "ai" as const, label: t.span!.state === "regenerating" ? "Making new audio" : "New audio by AI" })),
    ...(picked.length ? [{ start: picked[0].start, end: picked[picked.length - 1].end, kind: "selected" as const }] : []),
  ];
  const shownPeaks = audio ? audio.peaks : peaks;
  const pct = (s: number) => `${dur ? (Math.min(Math.max(s, 0), dur) / dur) * 100 : 0}%`;

  /* ---------- paragraphs ---------- */
  const paras: { start: number; speaker?: string; items: number[] }[] = [];
  tokens.forEach((t, i) => {
    if (paraStarts[i]) paras.push({ start: t.start, speaker: t.speaker, items: [] });
    paras[paras.length - 1].items.push(i);
  });

  const renderToken = (i: number) => {
    const t = tokens[i];
    const s = t.span?.state;
    const selected = !!range && i >= range[0] && i <= range[1];
    const prevT = tokens[i - 1];
    const editPoint = !!prevT && !paraStarts[i] && (prevT.removed !== t.removed || (prevT.span !== t.span && (REPLACED.includes(s as TranscriptSpanState) || REPLACED.includes(prevT.span?.state as TranscriptSpanState))));
    return (
      <span key={t.key}>
        {i > 0 && !paraStarts[i] && " "}
        {editPoint && <span aria-hidden className="mx-0.5 inline-block h-4 border-l border-dashed border-fg-subtle align-middle" />}
        <span
          ref={(el) => { if (el) optionRefs.current.set(t.key, el); else optionRefs.current.delete(t.key); }}
          id={`${uid}-${t.key}`} role="option" aria-selected={selected} tabIndex={i === cursor ? 0 : -1}
          onPointerDown={(ev) => down(ev, i)} onPointerEnter={() => enter(i)} onFocus={() => setCursorId(t.key)}
          className={cn("cursor-text rounded-sm py-0.5 select-none box-decoration-clone underline-offset-4",
            i === current && !selected && "bg-hover",
            selected && "bg-fg text-bg",
            t.removed && "text-fg-subtle line-through decoration-fg-muted",
            selected && t.removed && "text-inverse-muted decoration-inverse-fg",
            t.filler && !t.removed && "underline decoration-fg-muted decoration-dotted",
            (s === "regenerated" || s === "kept") && "underline decoration-lime",
            s === "regenerated" ? "decoration-4" : s === "kept" ? "decoration-2" : "",
            s === "regenerating" && "bg-[linear-gradient(90deg,var(--lime)_25%,var(--border)_50%,var(--lime)_75%)] bg-[length:200%_var(--spacing)] bg-bottom bg-repeat-x motion-safe:animate-shimmer",
            s === "failed" && "underline decoration-danger decoration-dashed decoration-2",
          )}>
          {t.text}
          <span className="sr-only">
            {t.removed ? ", removed" : ""}{t.filler && !t.removed ? ", filler word" : ""}
            {s === "regenerating" ? ", making new audio" : s === "regenerated" ? ", new audio by AI, not kept yet" : s === "kept" ? ", new audio by AI" : s === "failed" ? ", new audio failed" : ""}
          </span>
        </span>
      </span>
    );
  };

  const spanBar = (t: Token) => {
    const s = t.span!;
    const quote = t.text.length > 48 ? `${t.text.slice(0, 46)}…` : t.text;
    return (
      <div key={s.id} className="flex flex-wrap items-center gap-x-2 gap-y-1.5 rounded-field border border-line bg-sunken px-2.5 py-2 text-small">
        {s.state === "regenerating" && <span className="min-w-48 flex-1 text-fg-muted">Making new audio for “{quote}”</span>}
        {s.state === "regenerated" && (
          <span className="flex min-w-48 flex-1 items-center gap-1.5 text-fg">
            <AILabel level="field" size={16} text="AI" popoverLabel="About this new audio"
              overview={`These words were spoken by an AI voice${voiceName ? ` (${voiceName})` : ""}. The rest is the original recording.`} />
            <span className="min-w-0">New audio for “{quote}”. Keep it or revert.</span>
          </span>
        )}
        {s.state === "failed" && (
          <span className="flex min-w-48 flex-1 items-center gap-1.5 text-fg">
            <AlertIcon width={14} height={14} aria-hidden className="shrink-0 text-danger-fg" />
            <span className="min-w-0">Could not make new audio for “{quote}”.{s.error && <span className="text-fg-muted"> {s.error}</span>}</span>
          </span>
        )}
        {s.state === "regenerated" && <Button size="sm" variant="secondary" onClick={() => playFrom(t.start)} aria-label={`Play new audio, ${quote}`} className="pointer-coarse:h-11">Play</Button>}
        {(s.state === "regenerated" || s.state === "failed") && onRetry && (
          <Button size="sm" variant="secondary" onClick={() => retry(s)} leading={<RetryIcon width={14} height={14} />} aria-label={`Retry, ${quote}`} className="pointer-coarse:h-11">Retry</Button>
        )}
        {s.state === "regenerated" && onKeep && <Button size="sm" variant="primary" onClick={() => keep(s)} aria-label={`Keep new audio, ${quote}`} className="pointer-coarse:h-11">Keep</Button>}
        {(s.state === "regenerated" || s.state === "failed") && onRevert && (
          <Button size="sm" variant="ghost" onClick={() => revert(s)} aria-label={`Revert to the original, ${quote}`} className="pointer-coarse:h-11">Revert</Button>
        )}
      </div>
    );
  };

  return (
    <div onKeyDown={onRootKey} className={cn("flex min-w-0 flex-col gap-3 rounded-card border border-line bg-surface p-3 sm:p-4", className)}>
      {audio && (
        <div ref={playerRef}>
          <AudioPlayer {...audio} compact current={seekTo} onTimeChange={onPlayerTime} onPlayingChange={setPlaying} />
        </div>
      )}

      <div className="relative">
        <Waveform peaks={shownPeaks === undefined ? [] : shownPeaks} simple={shownPeaks === undefined} duration={dur} position={time}
          onSeek={audio || onSeek ? seek : undefined} label={`Seek, ${audio?.title ?? label}`} regions={regions} height={48} />
        {/* Cuts are hatched over the drawing, and every edit point gets a dashed line, so the waveform matches the words. */}
        <div aria-hidden className="pointer-events-none absolute inset-x-0 top-0 h-12">
          {runs.map((r, i) => (
            <span key={i} className="absolute inset-y-0 border-x border-dashed border-fg-subtle bg-glass"
              style={{ left: pct(r.start), width: `calc(${pct(r.end)} - ${pct(r.start)})`, backgroundImage: "repeating-linear-gradient(135deg, color-mix(in oklab, var(--fg-subtle) 45%, transparent) 0 calc(var(--spacing) * 0.25), transparent 0 calc(var(--spacing) * 1.5))" }} />
          ))}
        </div>
      </div>

      <div className="flex flex-wrap items-center gap-x-3 gap-y-2">
        <p className="min-w-0 flex-1 text-small text-fg-muted">{summary}</p>
        {onRemove && fillers.length > 0 && (
          <Button size="sm" variant="secondary" onClick={() => { const ids = fillers.flatMap((t) => t.wordIds); onRemove(ids); setSaid(`Removed ${plural(ids.length, "filler word")}.`); }} className="pointer-coarse:h-11">
            Remove {plural(fillers.length, "filler word")}
          </Button>
        )}
        {onUndo && (
          <Button size="sm" variant="ghost" onClick={() => { onUndo(); setSaid(`${undoLabel}. Done.`); }} leading={<UndoIcon width={14} height={14} />} aria-label={undoLabel} title={undoLabel} className="pointer-coarse:h-11">Undo</Button>
        )}
      </div>

      <p id={hintId} className="text-small text-fg-muted">
        Select words to edit them. Shift and the arrow keys select more.
        <span className="hidden sm:inline"> <Kbd>Delete</Kbd> removes, <Kbd>D</Kbd> changes words, <Kbd>Enter</Kbd> keeps new audio.</span>
      </p>

      <section aria-label={label} onKeyDown={onListKey} className="max-h-120 min-w-0 overflow-y-auto overscroll-contain">
        <div className="flex flex-col gap-4">
          {paras.map((p, pi) => {
            const who = p.speaker ? speakers?.[p.speaker]?.label ?? p.speaker : undefined;
            const groupLabel = `${who ? `${who}, ` : ""}${speakMediaTime(p.start)}`;
            const bars = p.items.map((i) => tokens[i]).filter((t) => t.span && PENDING.includes(t.span.state) && t.key === t.wordIds[0] && t.wordIds.length && !t.removed);
            return (
              <div key={pi} className="flex flex-col gap-1.5">
                <p className="flex items-center gap-2 text-small" aria-hidden>
                  {who && <span className="font-medium text-fg">{who}</span>}
                  <span className="text-fg-muted tabular-nums">{formatMediaTime(p.start)}</span>
                </p>
                <div role="listbox" aria-multiselectable aria-label={groupLabel} aria-describedby={hintId}
                  className="text-body leading-8 text-fg pointer-coarse:leading-11">
                  {p.items.map(renderToken)}
                </div>
                {bars.map(spanBar)}
              </div>
            );
          })}
        </div>
      </section>

      {/* Selection bar. It always holds its place, so nothing jumps when words are selected. */}
      <div className="sticky bottom-0 z-10 -mx-3 -mb-3 rounded-b-card border-t border-line bg-raised px-3 py-2.5 sm:-mx-4 sm:-mb-4 sm:px-4">
        {!picked.length ? (
          <p className="text-small text-fg-muted">Nothing selected. Tap or click a word to select it.</p>
        ) : changing ? (
          <form onSubmit={(e) => { e.preventDefault(); submitChange(); }} onKeyDown={(e) => { if (e.key === "Escape") { e.preventDefault(); closeChange(); } }} className="flex flex-col gap-2">
            <label htmlFor={`${uid}-new`} className="text-small font-medium text-fg">New words</label>
            <TextField ref={inputRef} id={`${uid}-new`} value={draft} onChange={(e) => setDraft(e.target.value)} aria-describedby={`${uid}-newhint`}
              invalid={maxChars !== undefined && draft.length > maxChars} autoComplete="off" />
            <p id={`${uid}-newhint`} className="text-small text-fg-muted">
              {voiceName ? `Voice: ${voiceName}. ` : ""}{limitText ? `${limitText}. ` : ""}
              {maxChars !== undefined && <span className={cn("tabular-nums", draft.length > maxChars && "text-danger-fg")}>{draft.length} of {maxChars} characters.</span>}
            </p>
            <div className="flex flex-wrap gap-2">
              <Button type="submit" size="sm" variant="lime" disabled={!draft.trim() || (maxChars !== undefined && draft.length > maxChars)} className="pointer-coarse:h-11">Make new audio</Button>
              <Button size="sm" variant="ghost" onClick={closeChange} className="pointer-coarse:h-11">Cancel</Button>
            </div>
          </form>
        ) : (
          <div role="group" aria-label="Selected words" className="flex flex-col gap-2">
            <p className="text-small text-fg">
              <span className="font-medium">{plural(pickedIds.length, "word")} selected</span>
              <span className="text-fg-muted tabular-nums">, {formatMediaTime(picked[0].start)} to {formatMediaTime(picked[picked.length - 1].end)}</span>
            </p>
            <div className="flex flex-wrap items-center gap-2">
              {onRemove && (
                <Button size="sm" variant="secondary" onClick={remove} className="pointer-coarse:h-11">{allRemoved ? "Restore" : "Remove"}</Button>
              )}
              {onRegenerate && (
                <Button size="sm" variant="secondary" onClick={openChange} aria-disabled={!!changeBlocked || undefined}
                  aria-describedby={changeBlocked ? `${uid}-why` : undefined}
                  className={cn("pointer-coarse:h-11", changeBlocked && "cursor-not-allowed opacity-(--disabled-opacity)")}>Change words</Button>
              )}
              {onSmooth && someRemoved && (
                <Button size="sm" variant="secondary" onClick={() => { onSmooth(pickedIds); setSaid("Making new audio."); setSel(null); refocus(); }} className="pointer-coarse:h-11">Smooth edit</Button>
              )}
              <span className="flex items-center gap-1">
                <Button size="sm" variant="ghost" aria-label="Select one word less" disabled={!range || range[0] === range[1]}
                  onClick={() => sel && range && setSel({ anchor: tokens[range[0]].key, focus: tokens[range[1] - 1].key })} className="pointer-coarse:size-11 pointer-coarse:px-0">−</Button>
                <Button size="sm" variant="ghost" aria-label="Select one more word" disabled={!range || range[1] >= tokens.length - 1}
                  onClick={() => sel && range && setSel({ anchor: tokens[range[0]].key, focus: tokens[range[1] + 1].key })} className="pointer-coarse:size-11 pointer-coarse:px-0">+</Button>
              </span>
              <Button size="sm" variant="ghost" onClick={() => { setSel(null); refocus(); }} className="pointer-coarse:h-11">Clear</Button>
            </div>
            {changeBlocked && onRegenerate && <p id={`${uid}-why`} className="text-small text-fg-muted">{changeBlocked}</p>}
          </div>
        )}
      </div>

      <span role="status" className="sr-only">{said}</span>
    </div>
  );
}
```

## tattva/organisms/TranscriptSync.tsx

```tsx
import { useEffect, useId, useRef, useState } from "react";
import { cn } from "../lib/cn";
import { AIMark } from "../atoms/AIMark";
import { Button } from "../atoms/Button";
import { Skeleton } from "../atoms/Skeleton";
import { formatMediaTime } from "../atoms/Waveform";
import { AudioPlayer } from "../molecules/AudioPlayer";
import type { AudioPlayerProps } from "../molecules/AudioPlayer";

export interface TranscriptLine {
  id: string;
  /** Key into `speakers`. */
  speaker: string;
  /** When the line starts, in seconds from the start of the recording. */
  start: number;
  /** When it ends. Defaults to the start of the next line. */
  end?: number;
  text: string;
  /** Wall-clock time, such as "14:02:10", shown beside the offset. */
  clock?: string;
  /** `interrupted`: the speaker was cut off, and only the first `spokenUpTo` characters were played. `redacted`: the text was hidden, such as a card number. */
  status?: "final" | "interrupted" | "redacted";
  /** With `interrupted`, how many characters were actually spoken. The rest is struck through and marked Not spoken. */
  spokenUpTo?: number;
}

export interface TranscriptSpeaker {
  /** Name shown on each line, such as "Maya Okafor" or "Support assistant". */
  label: string;
  /** This speaker is an AI. Its lines get the AI mark; people's lines stay neutral. */
  ai?: boolean;
}

export interface TranscriptSyncProps {
  lines: TranscriptLine[];
  /** Who is who. Every `speaker` in lines needs an entry. */
  speakers: Record<string, TranscriptSpeaker>;
  /** Wires in a player above the transcript. The part then keeps the time itself, so `current` and `onSeek` are not needed. */
  audio?: Omit<AudioPlayerProps, "current" | "onTimeChange">;
  /** Playback position in seconds, when your own player keeps it. */
  current?: number;
  /** Called with a line's start time when the line is pressed. Seek your player there. */
  onSeek?: (seconds: number) => void;
  /** Name of the transcript region. */
  label?: string;
  /** Shows placeholder lines while the transcript is being made. */
  loading?: boolean;
  /** Shown when there are no lines. */
  emptyText?: string;
  /** Classes for the scrolling list, for example to change its height. */
  listClassName?: string;
  className?: string;
}

/** Index of the line that holds time t, or -1 before the first line. */
function lineAt(lines: TranscriptLine[], t: number): number {
  let found = -1;
  for (let i = 0; i < lines.length; i++) {
    const end = lines[i].end ?? lines[i + 1]?.start ?? Infinity;
    if (t >= lines[i].start && t < end) return i;
    if (t >= lines[i].start) found = i;
  }
  return found;
}

/**
 * A recording's transcript that keeps in step with playback. The line being played is marked, the list follows it
 * until the person scrolls away, and pressing a line plays from there. Speakers are named in words; AI speakers also carry the AI mark.
 */
export function TranscriptSync({
  lines, speakers, audio, current, onSeek, label = "Transcript", loading = false, emptyText = "There is no transcript for this recording.",
  listClassName, className,
}: TranscriptSyncProps) {
  const hintId = useId();
  const [ownTime, setOwnTime] = useState(0);
  const [seekTo, setSeekTo] = useState<number | undefined>(undefined);
  const [follow, setFollow] = useState(true);
  const list = useRef<HTMLOListElement>(null);
  const items = useRef<(HTMLLIElement | null)[]>([]);
  const ours = useRef(0);

  const time = audio ? ownTime : current ?? 0;
  const active = lineAt(lines, time);

  const seek = (t: number) => {
    setFollow(true);
    if (audio) { setSeekTo(t); setOwnTime(t); }
    onSeek?.(t);
  };

  const inView = (i: number) => {
    const box = list.current, el = items.current[i];
    if (!box || !el) return true;
    const top = el.offsetTop;
    return top >= box.scrollTop && top + el.offsetHeight <= box.scrollTop + box.clientHeight;
  };

  // Keep the current line in view, without moving the page, until the person scrolls away.
  useEffect(() => {
    const box = list.current, el = items.current[active];
    if (!follow || !box || !el || inView(active)) return;
    ours.current = Date.now();
    box.scrollTop = Math.max(0, el.offsetTop - box.clientHeight / 3);
  }, [active, follow]);

  const onScroll = () => {
    if (Date.now() - ours.current < 150) return;
    if (active >= 0 && !inView(active)) setFollow(false);
  };

  const backToCurrent = () => {
    setFollow(true);
    const box = list.current, el = items.current[active];
    if (box && el) { ours.current = Date.now(); box.scrollTop = Math.max(0, el.offsetTop - box.clientHeight / 3); }
  };

  return (
    <div className={cn("flex min-w-0 flex-col gap-3", className)}>
      {audio && <AudioPlayer {...audio} current={seekTo} onTimeChange={(t) => { setOwnTime(t); setSeekTo(undefined); }} />}
      <div className="flex min-h-8 flex-wrap items-center justify-between gap-2">
        <p id={hintId} className="text-small text-fg-muted">{loading || lines.length === 0 ? label : "Press a line to play from there."}</p>
        {!follow && active >= 0 && (
          <Button size="sm" variant="secondary" onClick={backToCurrent} className="pointer-coarse:h-11 motion-safe:animate-fade">Back to current line</Button>
        )}
      </div>
      <section aria-label={label} aria-busy={loading || undefined} className="min-w-0">
        {loading ? (
          <div className="flex flex-col gap-3" aria-label="Transcript loading">
            {[0, 1, 2].map((i) => (
              <div key={i} className="flex flex-col gap-1.5 px-3">
                <Skeleton className="h-3 w-32" />
                <Skeleton className={cn("h-3", i === 1 ? "w-3/5" : "w-11/12")} />
              </div>
            ))}
            <span role="status" className="sr-only">Transcript loading</span>
          </div>
        ) : lines.length === 0 ? (
          <p className="rounded-card border border-dashed border-line px-4 py-6 text-center text-compact text-fg-muted">{emptyText}</p>
        ) : (
          <ol ref={list} onScroll={onScroll} aria-describedby={hintId}
            className={cn("relative flex max-h-80 flex-col gap-0.5 overflow-y-auto overscroll-contain", listClassName)}>
            {lines.map((l, i) => {
              const who = speakers[l.speaker] ?? { label: l.speaker };
              const on = i === active;
              const cut = l.status === "interrupted" && l.spokenUpTo !== undefined && l.spokenUpTo < l.text.length;
              return (
                <li key={l.id} ref={(el) => { items.current[i] = el; }}>
                  <button type="button" onClick={() => seek(l.start)} aria-current={on || undefined}
                    className={cn("relative flex w-full min-h-11 cursor-pointer flex-col gap-1 rounded-control py-2 pr-3 pl-4 text-left transition-colors duration-(--dur-fast) hover:bg-hover",
                      "before:absolute before:inset-y-2 before:left-1 before:w-0.5 before:rounded-full before:transition-colors before:duration-(--dur-fast)",
                      on ? "bg-selected before:bg-fg forced-colors:outline forced-colors:outline-1" : "before:bg-transparent")}>
                    <span className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5 text-small">
                      {who.ai ? (
                        <span aria-hidden className="inline-flex size-5 shrink-0 items-center justify-center rounded-full bg-lime text-on-lime"><AIMark size={16} /></span>
                      ) : (
                        <span aria-hidden className="inline-flex size-5 shrink-0 items-center justify-center rounded-full border border-line bg-surface text-nano font-medium text-fg-muted">{who.label.charAt(0)}</span>
                      )}
                      <span className="font-medium text-fg">{who.label}{who.ai && <span className="sr-only"> (AI)</span>}</span>
                      <span className="text-fg-muted tabular-nums">
                        <span className="sr-only">at </span>{formatMediaTime(l.start)}{l.clock && <span className="text-fg-muted"> · {l.clock}</span>}
                      </span>
                      {on && <span className="rounded-control bg-fg px-1.5 text-nano leading-4 font-medium text-bg">Now</span>}
                    </span>
                    <span className={cn("text-compact leading-5", l.status === "redacted" ? "text-fg-muted italic" : "text-fg")}>
                      {cut ? (
                        <>
                          {l.text.slice(0, l.spokenUpTo)}
                          <span className="sr-only"> Not spoken: </span>
                          <s className="text-fg-subtle">{l.text.slice(l.spokenUpTo)}</s>
                          <span aria-hidden className="ml-1.5 inline-block rounded-control border border-line px-1 text-nano leading-4 text-fg-muted not-italic">Not spoken</span>
                        </>
                      ) : l.text}
                    </span>
                  </button>
                </li>
              );
            })}
          </ol>
        )}
      </section>
    </div>
  );
}
```

## tattva/organisms/Tray.tsx

```tsx
import type { ReactNode } from "react";
import { cn } from "../lib/cn";
import { SectionLabel } from "../atoms/SectionLabel";

/**
 * A soft sand surface that holds a short list and, optionally, the composer beneath it,
 * so list and input share one width and edge.
 */
export function Tray({ label, action, children, footer, className }: { label?: string; action?: ReactNode; children: ReactNode; footer?: ReactNode; className?: string }) {
  return (
    <section aria-label={label} className={cn("rounded-card bg-sunken p-1 shadow-sm", className)}>
      <div className="space-y-1 px-3.5 pt-3 pb-2.5">
        {label && <SectionLabel action={action}>{label}</SectionLabel>}
        <ul className="space-y-0.5">{children}</ul>
      </div>
      {footer}
    </section>
  );
}
```

## tattva/organisms/VariantGrid.tsx

```tsx
import { useRef, type KeyboardEvent } from "react";
import { useAnnounceChange } from "../lib/focus";
import { Skeleton } from "../atoms/Skeleton";
import { type Variant, VariantTile } from "../molecules/VariantTile";


export type { Variant };

/**
 * Generated options as a radio group. Loading slots reserve space so the layout never jumps.
 * Focus roves like a native radio group: only the selected tile (or the first when none is selected) is a tab stop,
 * arrow keys move focus and select with wrapping, and Home and End jump to the first and last tile.
 * Screen readers hear when options start generating and when new ones arrive.
 */
export function VariantGrid({ variants, selectedId, onSelect, loadingCount = 0, label = "Generated variants", loadingMessage = "Generating options", readyMessage = (n) => (n === 1 ? "1 option ready" : `${n} options ready`) }: {
  variants: Variant[]; selectedId?: string; onSelect: (id: string) => void; loadingCount?: number;
  /** Name of the group. */
  label?: string;
  /** What screen readers hear when loading slots appear. */
  loadingMessage?: string;
  /** What screen readers hear when new options arrive, with the number now shown. */
  readyMessage?: (count: number) => string;
}) {
  const said = useAnnounceChange(`${variants.length}|${loadingCount}`, (now, before) => {
    const [n, l] = now.split("|").map(Number), [n0, l0] = before.split("|").map(Number);
    if (n > n0) return readyMessage(n);
    return l > l0 ? loadingMessage : "";
  });
  const ref = useRef<HTMLDivElement>(null);
  const selectedIndex = variants.findIndex((v) => v.id === selectedId);
  const stop = selectedIndex >= 0 ? selectedIndex : 0;

  const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
    if (e.altKey || e.ctrlKey || e.metaKey || !variants.length) return;
    const tiles = Array.from(ref.current?.querySelectorAll<HTMLElement>('[role="radio"]') ?? []);
    const from = tiles.indexOf(document.activeElement as HTMLElement);
    if (from < 0) return;
    let to = from;
    if (e.key === "ArrowRight" || e.key === "ArrowDown") to = (from + 1) % variants.length;
    else if (e.key === "ArrowLeft" || e.key === "ArrowUp") to = (from - 1 + variants.length) % variants.length;
    else if (e.key === "Home") to = 0;
    else if (e.key === "End") to = variants.length - 1;
    else return;
    e.preventDefault();
    tiles[to]?.focus();
    onSelect(variants[to].id);
  };

  return (
    <>
    <p role="status" aria-live="polite" className="sr-only">{said}</p>
    <div ref={ref} role="radiogroup" aria-label={label} onKeyDown={onKeyDown} className="grid w-full grid-cols-2 gap-3 sm:grid-cols-4">
      {variants.map((v, i) => <VariantTile key={v.id} variant={v} selected={v.id === selectedId} onSelect={onSelect} tabIndex={i === stop ? 0 : -1} />)}
      {Array.from({ length: loadingCount }, (_, i) => <Skeleton key={`l${i}`} className="aspect-square" />)}
    </div>
    </>
  );
}
```

## tattva/organisms/VersionHistory.tsx

```tsx
import { useEffect, useRef, useState } from "react";
import { useAnnounceChange, useFocusReturn } from "../lib/focus";
import { cn } from "../lib/cn";
import { SparkleIcon, UsersIcon } from "../lib/icons";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";

export interface VersionEntry {
  id: string;
  /** Overrides the automatic name "Version N". */
  name?: string;
  /** Display time such as "2 min ago". */
  time: string;
  author: "ai" | "person";
  /** Person name, or the model name for AI. */
  authorName?: string;
  /** One line about what changed. */
  summary?: string;
}

export interface VersionHistoryProps {
  /** Newest first. Versions are numbered from the end of the list, so the oldest is Version 1. */
  versions: VersionEntry[];
  /** Id of the current version. Defaults to the first entry. */
  currentId?: string;
  /** Controlled preview. Pass null for no preview. Omit to let the component manage it. */
  previewId?: string | null;
  onPreviewChange?: (id: string | null) => void;
  onRestore: (id: string) => void;
  title?: string;
  /** Note shown under each Preview action. */
  previewNote?: string;
  /** What screen readers hear after a restore. Defaults to "Restored (version name)". */
  restoredMessage?: (name: string) => string;
  className?: string;
}

/** Auto-numbered version list with a preview mode and a restore action. */
export function VersionHistory({ versions, currentId, previewId: controlled, onPreviewChange, onRestore, title = "Version history", previewNote = "Preview does not create a new version.", restoredMessage = (name) => `Restored ${name}`, className }: VersionHistoryProps) {
  const [inner, setInner] = useState<string | null>(null);
  const previewId = controlled === undefined ? inner : controlled;
  const setPreview = (id: string | null) => { if (controlled === undefined) setInner(id); onPreviewChange?.(id); };
  const current = currentId ?? versions[0]?.id;
  const nameOf = (v: VersionEntry, i: number) => v.name ?? `Version ${versions.length - i}`;
  const previewIdx = versions.findIndex((v) => v.id === previewId);
  const previewing = previewIdx >= 0 ? versions[previewIdx] : undefined;
  // After a restore the restored row loses its buttons, so focus moves to that row and the restore is read out.
  // Leaving a preview from the bar above returns focus to that version's Preview button.
  const root = useRef<HTMLElement>(null);
  const rows = useRef(new Map<string, HTMLLIElement>());
  const lastPreview = useRef<string | null>(null);
  if (previewId) lastPreview.current = previewId;
  const seenCurrent = useRef(current);
  useFocusReturn(`${current}|${previewId}`, root, () => {
    const row = rows.current.get(current ?? "");
    if (current !== seenCurrent.current) return row;
    return (lastPreview.current ? rows.current.get(lastPreview.current)?.querySelector<HTMLElement>("button[aria-pressed]") : null) ?? row;
  });
  useEffect(() => { seenCurrent.current = current; });
  const restored = useAnnounceChange(current, (now) => { const i = versions.findIndex((v) => v.id === now); return i >= 0 ? restoredMessage(nameOf(versions[i], i)) : ""; });

  return (
    <section ref={root} aria-label={title} className={cn("space-y-3 rounded-card border border-line bg-surface p-3", className)}>
      <h3 className="px-1 text-lead leading-6">{title}</h3>
      <p role="status" aria-live="polite" className="sr-only">{restored || (previewing ? `Previewing ${nameOf(previewing, previewIdx)}` : "")}</p>
      {previewing && (
        <div className="flex flex-wrap items-center justify-between gap-2 rounded-card border border-line bg-sunken px-3 py-2">
          <p className="text-body leading-5">Previewing <b>{nameOf(previewing, previewIdx)}</b>. {previewNote}</p>
          <div className="flex gap-2">
            <Button size="sm" variant="secondary" onClick={() => setPreview(null)}>Exit preview</Button>
            <Button size="sm" variant="primary" onClick={() => onRestore(previewing.id)}>Restore</Button>
          </div>
        </div>
      )}
      <ol className="space-y-1">
        {versions.map((v, i) => {
          const isCurrent = v.id === current;
          const isPreview = v.id === previewId;
          const name = nameOf(v, i);
          return (
            <li key={v.id} ref={(n) => { if (n) rows.current.set(v.id, n); else rows.current.delete(v.id); }} tabIndex={-1} aria-current={isCurrent ? "true" : undefined} className={cn("flex flex-wrap items-center gap-x-3 gap-y-1 rounded-card px-3 py-2", isPreview ? "bg-sunken" : "hover:bg-hover")}>
              <div className="min-w-48 flex-1">
                <p className="flex flex-wrap items-center gap-1.5 text-body leading-5 font-medium">
                  {name}
                  {isCurrent && <Badge tone="accent">Current</Badge>}
                  {isPreview && <Badge>Previewing</Badge>}
                </p>
                <p className="flex flex-wrap items-center gap-1.5 text-small leading-4 text-fg-muted">
                  <span className="inline-flex items-center gap-1">
                    {v.author === "ai" ? <SparkleIcon width={11} height={11} /> : <UsersIcon width={11} height={11} />}
                    {v.author === "ai" ? "AI" : "Person"}{v.authorName ? `, ${v.authorName}` : ""}
                  </span>
                  <span aria-hidden>·</span>
                  <span>{v.time}</span>
                </p>
                {v.summary && <p className="mt-0.5 text-small leading-4 text-fg-subtle">{v.summary}</p>}
              </div>
              {!isCurrent && (
                <div className="flex gap-1.5">
                  <Button size="sm" variant="ghost" aria-pressed={isPreview} aria-label={`Preview ${name}`} onClick={() => setPreview(isPreview ? null : v.id)}>Preview</Button>
                  <Button size="sm" variant="secondary" aria-label={`Restore ${name}`} onClick={() => onRestore(v.id)}>Restore</Button>
                </div>
              )}
            </li>
          );
        })}
      </ol>
      <p className="px-1 text-small leading-4 text-fg-subtle">{previewNote}</p>
    </section>
  );
}
```

## tattva/organisms/VoiceConsentCheck.tsx

```tsx
import { useEffect, useId, useRef, useState, type KeyboardEvent, type ReactNode } from "react";
import { cn } from "../lib/cn";
import { AlertIcon, ApproveIcon, CameraIcon, LockIcon, MicIcon, ShieldIcon, StopIcon } from "../lib/icons";
import { headingTag, type HeadingLevel } from "../lib/useFocusAfter";
import { Badge } from "../atoms/Badge";
import { Button } from "../atoms/Button";
import { Checkbox } from "../atoms/Checkbox";
import { Callout } from "../molecules/Callout";

export type ConsentStep = "explain" | "micCheck" | "reading" | "checking" | "verified" | "failed" | "lockedOut";
/** What is being verified: a voice to clone, or a likeness (face and voice) for video. */
export type ConsentSubject = "voice" | "likeness";

export interface VoiceConsentCheckProps {
  step: ConsentStep;
  /** The lines the person reads out, one at a time. Make them new each attempt so a recording cannot be replayed. */
  lines: string[];
  /** Index of the line being read. */
  currentLine: number;
  /** "voice" for a voice clone. "likeness" for face and voice in video: the copy then talks about the camera too. */
  subject?: ConsentSubject;
  /** What the voice or likeness will be used for, who can use it, and how to remove it. Shown before anything is recorded. */
  consentText: ReactNode;
  /** True while the current line is being recorded. */
  recording?: boolean;
  /** Input level from 0 to 1 during the mic check and while recording. Shown as quiet, speaking or loud. */
  level?: number;
  /** Result of the mic check. Leave out while still listening. */
  noise?: "ok" | "noisy";
  /** Tries left after a failed check. */
  attemptsLeft?: number;
  /** When the person may try again, written out by your code, for example "14:20 tomorrow". */
  retryAt?: string;
  /** The person ticked the agreement and pressed Continue. Move to the mic check. */
  onAgree: () => void;
  /** Leave the mic check and start reading. Also "Continue anyway" when it is noisy. */
  onContinue?: () => void;
  /** Listen again after moving somewhere quieter. */
  onCheckAgain?: () => void;
  onStartLine: () => void;
  onStopLine: () => void;
  /** Start a new attempt after a failed check. */
  onRetry?: () => void;
  onCancel: () => void;
  /** Close after success. */
  onDone?: () => void;
  /** Link to a check by a person, for anyone who cannot read aloud. Shown on every step before the result. */
  manualReviewHref?: string;
  /** Link to support, shown when locked out. */
  supportHref?: string;
  headingLevel?: HeadingLevel;
  className?: string;
}

const words = {
  voice: {
    title: "Confirm this is your voice",
    agree: "I am the person whose voice this is, and I agree to it being cloned as described.",
    device: "mic",
    deviceCheck: "Check your mic",
    checkPrompt: "Say a few words in your normal voice so we can check your mic.",
    readHint: "Read this once, then press Stop.",
    verified: "Your voice is verified. You can now use it.",
    manual: "Cannot read aloud? Ask for a check by a person",
  },
  likeness: {
    title: "Confirm this is you",
    agree: "I am the person shown and heard, and I agree to my likeness being used as described.",
    device: "camera and mic",
    deviceCheck: "Check your camera and mic",
    checkPrompt: "Look at the camera and say a few words so we can check your camera and mic.",
    readHint: "Look at the camera, read this once, then press Stop.",
    verified: "Your likeness is verified. You can now use it.",
    manual: "Cannot read aloud or use a camera? Ask for a check by a person",
  },
} as const;

const stepNames = ["Agree", "Check", "Read lines", "Result"] as const;
const stepIndex: Record<ConsentStep, number> = { explain: 0, micCheck: 1, reading: 2, checking: 3, verified: 3, failed: 3, lockedOut: 3 };

const levelWord = (l: number) => (l < 0.2 ? "quiet" : l < 0.75 ? "speaking" : "loud");

/** Three steps of level, never a moving wave. Neutral: this is the person's own voice, not the AI. */
function LevelMeter({ level, label }: { level: number; label: string }) {
  const word = levelWord(level);
  const filled = word === "quiet" ? 1 : word === "speaking" ? 2 : 3;
  return (
    <div className="flex items-center gap-2 text-small leading-4.5 text-fg-muted">
      <span role="meter" aria-label={label} aria-valuemin={0} aria-valuemax={3} aria-valuenow={filled} aria-valuetext={word}
        className="flex h-4 items-end gap-0.5">
        {[1, 2, 3].map((n) => (
          <span key={n} className={cn("w-1.5 rounded-full transition-colors duration-(--dur-fast)", n === 1 ? "h-1.5" : n === 2 ? "h-2.5" : "h-4", n <= filled ? "bg-fg" : "bg-line")} />
        ))}
      </span>
      <span>Level: {word}</span>
    </div>
  );
}

/**
 * Live consent check before cloning a voice or using someone's likeness. The person agrees to plain terms, checks their
 * mic, then reads prompted lines aloud one at a time, so a recording of someone else cannot pass. Every step is driven by
 * props: your code records, checks and counts attempts.
 */
export function VoiceConsentCheck({
  step, lines, currentLine, subject = "voice", consentText, recording = false, level = 0, noise, attemptsLeft, retryAt,
  onAgree, onContinue, onCheckAgain, onStartLine, onStopLine, onRetry, onCancel, onDone, manualReviewHref, supportHref,
  headingLevel = 2, className,
}: VoiceConsentCheckProps) {
  const w = words[subject];
  const H = headingTag(headingLevel);
  const headId = useId();
  const headRef = useRef<HTMLHeadingElement>(null);
  const [agreed, setAgreed] = useState(false);
  const [agreeError, setAgreeError] = useState(false);
  const [leftPage, setLeftPage] = useState(false);
  const idx = stepIndex[step];
  const line = lines[currentLine] ?? "";
  const DeviceIcon = subject === "likeness" ? CameraIcon : MicIcon;

  // Move focus to the heading when the step changes, so keyboard and screen reader users start at the top of it.
  const firstRender = useRef(true);
  useEffect(() => {
    if (firstRender.current) { firstRender.current = false; return; }
    headRef.current?.focus({ preventScroll: true });
  }, [step]);

  // Recording stops if the page goes to the background, as on a phone when the app is left.
  const recRef = useRef(recording);
  recRef.current = recording;
  const stopRef = useRef(onStopLine);
  stopRef.current = onStopLine;
  useEffect(() => {
    const onHide = () => { if (document.hidden && recRef.current) { stopRef.current(); setLeftPage(true); } };
    document.addEventListener("visibilitychange", onHide);
    return () => document.removeEventListener("visibilitychange", onHide);
  }, []);
  useEffect(() => { if (recording) setLeftPage(false); }, [recording]);

  // Space starts or stops a line from anywhere in the panel that is not itself a control or a text field.
  const onKey = (e: KeyboardEvent<HTMLElement>) => {
    if (step !== "reading" || e.key !== " ") return;
    const t = e.target as HTMLElement;
    if (t.closest("button, a, input, textarea, select, [contenteditable='true']")) return;
    e.preventDefault();
    if (recording) onStopLine(); else onStartLine();
  };

  const stepTitle =
    step === "explain" ? "Before you start"
    : step === "micCheck" ? w.deviceCheck
    : step === "reading" ? `Read line ${currentLine + 1} of ${lines.length}`
    : step === "checking" ? "Checking your recordings"
    : step === "verified" ? "Verified"
    : step === "failed" ? "We could not verify it"
    : "Too many tries";

  // One polite message per change. During reading it carries the line itself, so each line is read once.
  const announce =
    step === "reading" ? (recording ? "Recording" : `Line ${currentLine + 1} of ${lines.length}: ${line}`)
    : step === "micCheck" ? (noise === "ok" ? `Your ${w.device} sounds clear` : noise === "noisy" ? "It is noisy where you are" : "Listening")
    : step === "checking" ? "Checking your recordings"
    : step === "verified" ? "Verified"
    : step === "failed" ? `Not verified.${attemptsLeft !== undefined ? ` ${attemptsLeft} ${attemptsLeft === 1 ? "try" : "tries"} left.` : ""}`
    : step === "lockedOut" ? `Too many tries.${retryAt ? ` Try again after ${retryAt}.` : ""}`
    : "";

  const manual = manualReviewHref && step !== "checking" && step !== "verified"
    ? <a href={manualReviewHref} className="tap text-compact text-fg underline underline-offset-2">{w.manual}</a> : null;

  return (
    <section aria-labelledby={headId} onKeyDown={onKey}
      className={cn("flex w-full min-w-0 flex-col gap-4 rounded-card border border-line bg-surface p-4 sm:p-6", className)}>
      <header className="space-y-2">
        <p className="flex items-center gap-2 text-small leading-4.5 text-fg-muted">
          <ShieldIcon width={14} height={14} className="shrink-0" />{w.title}
        </p>
        <ol aria-label="Steps" className="flex gap-1">
          {stepNames.map((n, i) => (
            <li key={n} aria-current={i === idx ? "step" : undefined} className="flex-1">
              <span className={cn("block h-1 rounded-full", i < idx ? "bg-fg" : i === idx ? "bg-fg-muted" : "bg-line")} />
              <span className="sr-only">{n}{i < idx ? ", done" : i === idx ? ", current" : ""}</span>
            </li>
          ))}
        </ol>
        <p className="text-small leading-4.5 text-fg-muted" aria-hidden>Step {idx + 1} of 4: {stepNames[idx]}</p>
        <H id={headId} ref={headRef} tabIndex={-1} className="text-subhead font-semibold text-fg outline-none">{stepTitle}</H>
      </header>

      {step === "explain" && (
        <div className="space-y-4">
          <div className="space-y-2 rounded-card bg-sunken p-4 text-compact text-fg">{consentText}</div>
          <p className="text-compact text-fg-muted">Next you will read {lines.length} short {lines.length === 1 ? "line" : "lines"} out loud. Nothing is recorded until you press Start.</p>
          <div>
            <Checkbox checked={agreed} onChange={(c) => { setAgreed(c); if (c) setAgreeError(false); }} label={w.agree}
              invalid={agreeError} aria-describedby={agreeError ? `${headId}-err` : undefined} />
            {agreeError && <p id={`${headId}-err`} className="mt-1 pl-8 text-small leading-4.5 text-danger-fg">Tick the box to continue.</p>}
          </div>
        </div>
      )}

      {step === "micCheck" && (
        <div className="space-y-3">
          <p className="flex items-start gap-2 text-compact text-fg"><DeviceIcon className="mt-0.5 shrink-0" />{w.checkPrompt}</p>
          <LevelMeter level={level} label="Input level" />
          {noise === "ok" && <p className="flex items-center gap-1 text-compact text-success-fg"><ApproveIcon className="shrink-0" />Sounds clear.</p>}
          {noise === "noisy" && (
            <Callout tone="warning" title="It is noisy where you are">
              Background sound can make the check fail. Move somewhere quieter, then check again.
            </Callout>
          )}
          {!noise && <p className="text-compact text-fg-muted">Listening…</p>}
        </div>
      )}

      {step === "reading" && (
        <div className="space-y-3">
          <p className="text-compact text-fg-muted">{w.readHint}</p>
          <blockquote key={currentLine} className="rounded-card bg-sunken px-4 py-5 text-title-sm leading-snug font-medium text-fg motion-safe:animate-rise sm:text-title-md">
            {line}
          </blockquote>
          <div className="flex flex-wrap items-center gap-x-4 gap-y-2">
            {recording
              ? <span className="inline-flex items-center gap-1.5 text-compact font-medium text-danger-fg"><span aria-hidden className="size-2 rounded-full bg-danger" />Recording</span>
              : <span className="text-compact text-fg-muted">Not recording</span>}
            <LevelMeter level={recording ? level : 0} label="Input level" />
          </div>
          <p className="text-small leading-4.5 text-fg-subtle max-sm:hidden">Space also starts and stops.</p>
          {leftPage && <p className="text-compact text-fg-muted">Recording stopped when you left this page. Read the line again.</p>}
        </div>
      )}

      {step === "checking" && (
        <p className="flex items-center gap-2 text-compact text-fg-muted">
          <svg aria-hidden viewBox="0 0 24 24" className="size-4 motion-safe:animate-spin"><circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" strokeOpacity="0.25" strokeWidth="3" /><path d="M21 12a9 9 0 0 0-9-9" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" /></svg>
          This takes a few seconds. You can keep this open or leave it.
        </p>
      )}

      {step === "verified" && (
        <div className="space-y-3">
          <p className="flex items-start gap-2 text-compact text-success-fg"><ApproveIcon className="mt-0.5 shrink-0" />{w.verified}</p>
          <div className="space-y-2 rounded-card bg-sunken p-4 text-compact text-fg">{consentText}</div>
        </div>
      )}

      {step === "failed" && (
        <div className="space-y-2">
          <p className="flex flex-wrap items-center gap-2"><Badge tone="danger"><AlertIcon width={12} height={12} />Not verified</Badge>
            {attemptsLeft !== undefined && <span className="text-compact text-fg-muted">{attemptsLeft} {attemptsLeft === 1 ? "try" : "tries"} left</span>}</p>
          <p className="text-compact text-fg">The lines did not match well enough. Read in a quiet place, at your normal pace.</p>
          <p className="text-compact text-fg-muted">Your earlier samples are kept safe. You will not need to record them again.</p>
        </div>
      )}

      {step === "lockedOut" && (
        <div className="space-y-2">
          <p className="flex items-start gap-2 text-compact text-fg"><LockIcon className="mt-0.5 shrink-0" />
            {retryAt ? `You can try again after ${retryAt}.` : "You can try again later."}</p>
          <p className="text-compact text-fg-muted">This limit protects people from having their {subject === "voice" ? "voice" : "likeness"} copied. Your samples are kept safe.</p>
          {supportHref && <a href={supportHref} className="tap text-compact text-fg underline underline-offset-2">Contact support</a>}
        </div>
      )}

      {manual}

      <div className="flex flex-wrap items-center gap-2 border-t border-line pt-4 max-sm:[&>*]:flex-1">
        {step === "explain" && <Button onClick={() => { if (!agreed) { setAgreeError(true); return; } onAgree(); }}>Agree and continue</Button>}
        {step === "micCheck" && noise === "ok" && <Button onClick={onContinue}>Continue</Button>}
        {step === "micCheck" && noise === "noisy" && <>
          <Button onClick={onCheckAgain}>Check again</Button>
          <Button variant="secondary" onClick={onContinue}>Continue anyway</Button>
        </>}
        {step === "reading" && (recording
          ? <Button variant="secondary" leading={<StopIcon width={14} height={14} />} onClick={onStopLine}>Stop</Button>
          : <Button leading={<MicIcon width={16} height={16} />} onClick={onStartLine}>{currentLine === 0 ? "Start reading" : "Start next line"}</Button>)}
        {step === "failed" && onRetry && (attemptsLeft === undefined || attemptsLeft > 0) && <Button onClick={onRetry}>Try again</Button>}
        {step === "verified" && <Button onClick={onDone}>Done</Button>}
        {step !== "verified" && <Button variant="ghost" onClick={onCancel}>{step === "lockedOut" ? "Close" : "Cancel"}</Button>}
      </div>
      <p role="status" className="sr-only">{announce}</p>
    </section>
  );
}
```

## tattva/organisms/VoicePanel.tsx

```tsx
import { useRef, useState, type KeyboardEvent, type PointerEvent, type ReactNode, type SVGProps } from "react";
import { cn } from "../lib/cn";
import { XIcon } from "../lib/icons";
import { Button } from "../atoms/Button";
import { Kbd } from "../atoms/Kbd";
import { SegmentedControl } from "../atoms/SegmentedControl";
import { AIPresence, type AIPresenceState } from "../atoms/AIPresence";

/**
 * Every state a voice session can be in. The first six are the original VoicePanel states, so old code keeps working.
 * micBlocked and noMic are the only states where the person must act, so they are the only amber ones.
 */
export type VoiceState =
  | "listening" | "thinking" | "speaking" | "interrupted" | "muted" | "reconnecting"
  | "connecting" | "ready" | "userSpeaking" | "working" | "held" | "micBlocked" | "noMic" | "ended" | "error";
export type VoiceSessionState = VoiceState;
export type VoiceInputMode = "handsFree" | "pushToTalk";
export type VoiceMediaState = "on" | "off" | "stoppedBySystem";

export interface VoiceCaption {
  id: string;
  speaker: "you" | "assistant";
  text: string;
}

export interface VoicePanelProps {
  state: VoiceState;
  /** Overrides the visible text for each state. */
  stateLabels?: Partial<Record<VoiceState, string>>;
  /** Hands-free listens all the time. Push-to-talk turns the main control into Hold to talk. */
  inputMode?: VoiceInputMode;
  /** Shows a Hands-free / Push to talk switch when given. */
  onInputModeChange?: (mode: VoiceInputMode) => void;
  /** Push-to-talk: the person pressed Hold to talk (pointer down, or Space held). */
  onTalkStart?: () => void;
  /** Push-to-talk: the person let go. `cancelled` is true when they slid off the button before letting go. */
  onTalkEnd?: (cancelled: boolean) => void;
  /** Live captions. Either simple caption objects, or your own node such as LiveCaptions. */
  captions?: VoiceCaption[] | ReactNode;
  /** Speaker names used in simple captions. */
  speakerLabels?: { you: string; assistant: string };
  emptyCaptions?: string;
  muted?: boolean;
  onMutedChange?: (muted: boolean) => void;
  /** Shows Hold when given. The session pauses: nothing is heard or said until Resume. */
  onHold?: () => void;
  /** Called by Resume in the held state. */
  onResume?: () => void;
  onEnd?: () => void;
  /** Shows Interrupt while the assistant speaks, and makes Esc stop it. */
  onInterrupt?: () => void;
  /** Microphone level from 0 to 1. Moves the disc while listening and fills the neutral level bars. */
  inputLevel?: number;
  /** Assistant audio level from 0 to 1. Moves the disc while it speaks. Ignored under reduced motion. */
  outputLevel?: number;
  /** Text for the working state, for example "Checking your calendar". */
  workingLabel?: string;
  /** Mic blocked or no mic: the button that asks for the microphone again. */
  onMicRetry?: () => void;
  /** Mic blocked or no mic: the way out to typing. */
  onTypeInstead?: () => void;
  /** Camera and screen share. "stoppedBySystem" shows a notice with Turn on again. */
  media?: { camera?: VoiceMediaState; screen?: VoiceMediaState };
  /** Camera toggle shows when `onCameraChange` is given. */
  cameraOn?: boolean;
  onCameraChange?: (on: boolean) => void;
  /** Screen share toggle shows when `onScreenChange` or `onScreenShareChange` is given. */
  screenSharing?: boolean;
  onScreenChange?: (on: boolean) => void;
  /** Older name for onScreenChange. */
  onScreenShareChange?: (on: boolean) => void;
  /** Time since the session started, for example "02:14". Shown, never announced. */
  elapsed?: string;
  /** Minutes or credits used, for example a compact UsageMeter. */
  usage?: ReactNode;
  /** Who is talking and whether it is recorded, for example an AIDisclosure. */
  disclosure?: ReactNode;
  /** Extra content under the state, for example a connection hint or a retry button. */
  footer?: ReactNode;
  /** Shows the key hints (Esc, M, Space) on screens with a fine pointer. */
  showKeyHints?: boolean;
  className?: string;
}
export type VoiceSessionProps = VoicePanelProps;

const DEFAULT_LABELS: Record<VoiceState, string> = {
  connecting: "Connecting",
  ready: "Ready. Start talking.",
  listening: "Listening",
  userSpeaking: "Hearing you",
  thinking: "Thinking",
  working: "Working on it",
  speaking: "Speaking",
  interrupted: "Stopped. Go ahead.",
  muted: "Muted. The assistant cannot hear you.",
  held: "On hold. Tap Resume.",
  reconnecting: "Reconnecting",
  micBlocked: "Microphone blocked",
  noMic: "No microphone found",
  ended: "Call ended",
  error: "Something went wrong",
};

/** How each session state drives the shared orb. The disc has five looks; dimming covers the paused ones. */
const ORB: Record<VoiceState, { look: AIPresenceState; dim?: "soft" | "still" }> = {
  connecting: { look: "idle", dim: "soft" },
  ready: { look: "idle" },
  listening: { look: "listening" },
  userSpeaking: { look: "listening" },
  thinking: { look: "thinking" },
  working: { look: "thinking" },
  speaking: { look: "speaking" },
  interrupted: { look: "idle" },
  muted: { look: "idle", dim: "soft" },
  held: { look: "idle", dim: "still" },
  reconnecting: { look: "idle", dim: "soft" },
  micBlocked: { look: "idle", dim: "still" },
  noMic: { look: "idle", dim: "still" },
  ended: { look: "idle", dim: "still" },
  error: { look: "error" },
};

const icon = (p: SVGProps<SVGSVGElement>) => ({ width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round" as const, strokeLinejoin: "round" as const, "aria-hidden": true, ...p });
const MicIcon = () => <svg {...icon({})}><rect x="9" y="3" width="6" height="11" rx="3" /><path d="M5 11a7 7 0 0 0 14 0M12 18v3" /></svg>;
const MicOffIcon = () => <svg {...icon({})}><path d="M4 4l16 16M9 9v2a3 3 0 0 0 5 2M15 9V6a3 3 0 0 0-5.5-1.7M5 11a7 7 0 0 0 11 5.7M12 18v3" /></svg>;
const CameraIcon = () => <svg {...icon({})}><rect x="3" y="6" width="13" height="12" rx="2" /><path d="M16 10l5-3v10l-5-3" /></svg>;
const ScreenIcon = () => <svg {...icon({})}><rect x="3" y="4" width="18" height="12" rx="2" /><path d="M8 20h8M12 16v4" /></svg>;
const PauseIcon = () => <svg {...icon({})}><path d="M9 6v12M15 6v12" /></svg>;
const PlayIcon = () => <svg {...icon({})}><path d="M8 5.5v13l10-6.5z" /></svg>;
const StopIcon = () => <svg {...icon({})}><rect x="7" y="7" width="10" height="10" rx="1.5" /></svg>;

const isCaptionList = (c: unknown): c is VoiceCaption[] =>
  Array.isArray(c) && c.every((x) => x !== null && typeof x === "object" && "speaker" in x && "text" in x && "id" in x);

/** Three neutral bars for the person's own mic level. Never lime: lime marks the AI only. Steps, not a moving wave. */
function MicLevel({ level, off }: { level: number; off: boolean }) {
  const step = off ? 0 : level > 0.66 ? 3 : level > 0.25 ? 2 : level > 0.04 ? 1 : 0;
  return (
    <span aria-hidden className="inline-flex h-4 items-end gap-0.5">
      {[1, 2, 3].map((i) => (
        <span key={i} className={cn("w-1 rounded-full transition-colors duration-(--dur-fast)", i === 1 ? "h-1.5" : i === 2 ? "h-2.5" : "h-4", step >= i ? "bg-fg" : "bg-line-strong")} />
      ))}
    </span>
  );
}

/** A notice where the person must act. Amber, no role of its own: the state is already announced once by the disc's status region. */
function ActNotice({ title, children, actions }: { title: string; children: ReactNode; actions?: ReactNode }) {
  return (
    <div className="w-full rounded-card bg-warning-soft p-4 text-body leading-5 text-warning-fg motion-safe:animate-rise">
      <p className="font-medium">{title}</p>
      <p className="mt-0.5">{children}</p>
      {actions && <div className="mt-3 flex flex-wrap items-center gap-2">{actions}</div>}
    </div>
  );
}

/** A calm notice that needs no action. Neutral, no role. */
function InfoNotice({ children, action }: { children: ReactNode; action?: ReactNode }) {
  return (
    <div className="flex w-full flex-wrap items-center justify-between gap-2 rounded-card border border-line bg-sunken px-3 py-2 text-compact text-fg motion-safe:animate-rise">
      <span>{children}</span>{action}
    </div>
  );
}

const typing = (el: EventTarget | null) => {
  const t = el as HTMLElement | null;
  return !!t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT" || t.isContentEditable);
};

/**
 * A voice conversation (VoiceSession). The disc is the shared AIPresence, the state is always written in words, and
 * captions give the conversation without audio. Mute, Hold and End are separate: End sits apart, in a danger outline.
 * The mic blocked and no mic states are the only amber ones, because there the person must act.
 */
export function VoiceSession({
  state, stateLabels, inputMode = "handsFree", onInputModeChange, onTalkStart, onTalkEnd,
  captions, speakerLabels = { you: "You", assistant: "Assistant" }, emptyCaptions = "Captions appear here as you talk.",
  muted, onMutedChange, onHold, onResume, onEnd, onInterrupt, inputLevel = 0, outputLevel = 0, workingLabel,
  onMicRetry, onTypeInstead, media, cameraOn, onCameraChange, screenSharing, onScreenChange, onScreenShareChange,
  elapsed, usage, disclosure, footer, showKeyHints = true, className,
}: VoicePanelProps) {
  const labels = { ...DEFAULT_LABELS, ...(workingLabel ? { working: workingLabel } : {}), ...stateLabels };
  const label = labels[state];
  const orb = ORB[state];
  const isMuted = muted ?? state === "muted";
  const camera: VoiceMediaState = media?.camera ?? (cameraOn ? "on" : "off");
  const screen: VoiceMediaState = media?.screen ?? (screenSharing ? "on" : "off");
  const setScreen = onScreenChange ?? onScreenShareChange;
  const ended = state === "ended";
  const held = state === "held";
  const blocked = state === "micBlocked" || state === "noMic";
  const ptt = inputMode === "pushToTalk";

  // Simple captions are a log people read on demand. A custom node (LiveCaptions) brings its own log.
  const list = captions === undefined ? [] : isCaptionList(captions) ? captions : null;
  const hasCaptions = list ? list.length > 0 : !!captions;

  // Push-to-talk: pointer or Space held. Sliding off the button before letting go cancels.
  const [talk, setTalk] = useState<"idle" | "talking" | "cancel">("idle");
  const talkRef = useRef(talk);
  const setPhase = (p: typeof talk) => { talkRef.current = p; setTalk(p); };
  const canTalk = ptt && !ended && !held && !blocked && !isMuted;
  const startTalk = () => { if (!canTalk || talkRef.current !== "idle") return; setPhase("talking"); onTalkStart?.(); };
  const endTalk = (forceCancel = false) => {
    if (talkRef.current === "idle") return;
    const cancelled = forceCancel || talkRef.current === "cancel";
    setPhase("idle");
    onTalkEnd?.(cancelled);
  };
  const onTalkMove = (e: PointerEvent<HTMLButtonElement>) => {
    if (talkRef.current === "idle") return;
    const r = e.currentTarget.getBoundingClientRect();
    const inside = e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom;
    const next = inside ? "talking" : "cancel";
    if (next !== talkRef.current) setPhase(next);
  };

  // Keys work while focus is inside the panel, never while typing, never with a modifier. End has no key.
  const onKeyDown = (e: KeyboardEvent<HTMLElement>) => {
    if (typing(e.target) || e.ctrlKey || e.metaKey || e.altKey) return;
    if (e.key === "Escape" && state === "speaking" && onInterrupt) {
      e.preventDefault();
      const hadFocusOnInterrupt = (e.target as HTMLElement).dataset.interrupt !== undefined;
      if (hadFocusOnInterrupt) interrupt(); else onInterrupt();
      return;
    }
    if ((e.key === "m" || e.key === "M") && onMutedChange && !ended) { e.preventDefault(); onMutedChange(!isMuted); return; }
    if (e.key === " " && ptt) {
      const onButton = (e.target as HTMLElement).tagName === "BUTTON" && !(e.target as HTMLElement).dataset.talk;
      if (onButton) return; // Space presses whichever other button has focus
      e.preventDefault();
      if (!e.repeat) startTalk();
    }
  };
  const onKeyUp = (e: KeyboardEvent<HTMLElement>) => { if (e.key === " " && ptt) endTalk(); };

  // Interrupt disappears once pressed, so hand focus to the main control instead of losing it to the page.
  const section = useRef<HTMLElement>(null);
  const interrupt = () => {
    onInterrupt?.();
    requestAnimationFrame(() => section.current?.querySelector<HTMLButtonElement>("[data-main]")?.focus());
  };

  const talkText = talk === "talking" ? "Release to send" : talk === "cancel" ? "Release to cancel" : "Hold to talk";
  const mainSize = "h-14 px-6 text-body leading-5";
  // Every button in the control row is as tall as the main one, so the row reads as one even line. The main control
  // leads by place and width, not by height.
  const rowSize = "h-14";

  const muteButton = (main: boolean) => (
    <Button variant="secondary" data-main={main ? "" : undefined} aria-pressed={isMuted} onClick={() => onMutedChange?.(!isMuted)}
      leading={isMuted ? <MicOffIcon /> : <MicIcon />}
      trailing={!isMuted && !ptt && !blocked && !held ? <MicLevel level={inputLevel} off={state !== "listening" && state !== "userSpeaking"} /> : undefined}
      className={cn(main ? mainSize : rowSize, "aria-pressed:border-fg aria-pressed:bg-fg aria-pressed:text-bg aria-pressed:hover:bg-fg")}>
      Mute
    </Button>
  );

  return (
    <section ref={section} aria-label="Voice conversation" onKeyDown={onKeyDown} onKeyUp={onKeyUp}
      className={cn("flex w-full max-w-lg flex-col items-center gap-4 rounded-card border border-line bg-surface p-6 max-sm:p-4", className)}>
      {(disclosure || elapsed || usage) && (
        <div className="flex w-full flex-wrap items-center justify-between gap-x-3 gap-y-1 text-small leading-4 text-fg-muted">
          <div className="min-w-0">{disclosure}</div>
          <div className="flex items-center gap-3">
            {usage}
            {elapsed && <span className="font-mono tabular-nums"><span className="sr-only">Time </span>{elapsed}</span>}
          </div>
        </div>
      )}

      <div className={cn("flex flex-col items-center gap-2 transition-[opacity,filter] duration-(--dur-slow) ease-(--ease-standard)",
        orb.dim === "soft" && "opacity-70", orb.dim === "still" && "opacity-45 grayscale")}>
        {/* The disc's status region is the one polite announcement for state changes. Its own label is hidden; ours cross-fades below. */}
        <AIPresence state={orb.look} size={hasCaptions ? "md" : "lg"} showLabel={false}
          level={orb.look === "speaking" ? outputLevel : orb.look === "listening" ? inputLevel : 0}
          labels={{ [orb.look]: label }} />
      </div>
      <p aria-hidden key={label} className={cn("-mt-1 text-center text-body leading-5 font-medium motion-safe:animate-fade",
        state === "error" ? "text-danger-fg" : blocked ? "text-warning-fg" : "text-fg")}>
        {label}
      </p>

      {state === "speaking" && onInterrupt && (
        <Button variant="secondary" size="sm" data-interrupt="" onClick={interrupt} leading={<StopIcon />}
          trailing={showKeyHints ? <Kbd className="pointer-coarse:hidden">Esc</Kbd> : undefined}>
          Interrupt
        </Button>
      )}
      {state === "micBlocked" && (
        <ActNotice title="Allow the microphone to talk"
          actions={<>
            {onMicRetry && <Button size="sm" variant="secondary" onClick={onMicRetry} leading={<MicIcon />}>Allow microphone</Button>}
            {onTypeInstead && <Button size="sm" variant="ghost" onClick={onTypeInstead}>Type instead</Button>}
          </>}>
          Your browser is blocking it. Allow it in the address bar or in your settings, or type instead.
        </ActNotice>
      )}
      {state === "noMic" && (
        <ActNotice title="Connect a microphone to talk"
          actions={<>
            {onMicRetry && <Button size="sm" variant="secondary" onClick={onMicRetry}>Check again</Button>}
            {onTypeInstead && <Button size="sm" variant="ghost" onClick={onTypeInstead}>Type instead</Button>}
          </>}>
          We could not find one on this device. Plug in a headset, or type instead.
        </ActNotice>
      )}
      {camera === "stoppedBySystem" && !ended && (
        <InfoNotice action={onCameraChange && <Button size="sm" variant="secondary" onClick={() => onCameraChange(true)}>Turn camera on</Button>}>
          Camera stopped when your phone locked.
        </InfoNotice>
      )}
      {screen === "stoppedBySystem" && !ended && (
        <InfoNotice action={setScreen && <Button size="sm" variant="secondary" onClick={() => setScreen(true)}>Share again</Button>}>
          Screen sharing stopped when your phone locked.
        </InfoNotice>
      )}
      {footer}

      {list ? (
        <div role="log" aria-live="off" aria-label={ended ? "Transcript" : "Live captions"} tabIndex={0}
          className="max-h-48 w-full space-y-2 overflow-y-auto rounded-card bg-sunken p-3 text-compact">
          {list.length === 0 ? (
            <p className="text-fg-muted">{emptyCaptions}</p>
          ) : list.map((c) => (
            <p key={c.id} className={c.speaker === "you" ? "text-fg-muted" : "text-fg"}>
              {c.speaker === "assistant" && <span aria-hidden className="mr-1.5 inline-block size-1.5 rounded-full bg-lime align-middle" />}
              <span className="font-medium">{speakerLabels[c.speaker]}: </span>{c.text}
            </p>
          ))}
        </div>
      ) : (
        <div className="w-full">{captions as ReactNode}</div>
      )}

      {!ended && (
        <div className="flex w-full flex-col gap-3 motion-safe:animate-rise">
          {onInputModeChange && (
            <SegmentedControl size="sm" label="How you talk" value={inputMode} onChange={onInputModeChange} className="self-center"
              options={[{ value: "handsFree", label: "Hands-free" }, { value: "pushToTalk", label: "Push to talk" }]} />
          )}
          <div className="flex w-full flex-wrap items-center gap-2">
            {ptt ? (
              <Button variant="secondary" data-talk="1" data-main="" disabled={!canTalk} aria-pressed={talk !== "idle"}
                onPointerDown={(e) => { if (e.button !== 0) return; e.currentTarget.setPointerCapture(e.pointerId); startTalk(); }}
                onPointerMove={onTalkMove} onPointerUp={() => endTalk()} onPointerCancel={() => endTalk(true)}
                onBlur={() => endTalk(true)} onContextMenu={(e) => e.preventDefault()}
                className={cn(mainSize, "touch-none", talk === "talking" && "border-fg bg-fg text-bg hover:bg-fg", talk === "cancel" && "border-line-strong text-fg-muted")}
                leading={<MicIcon />}
                trailing={showKeyHints && talk === "idle" ? <Kbd className="pointer-coarse:hidden">Space</Kbd> : undefined}>
                {talkText}
              </Button>
            ) : muteButton(true)}
            {ptt && muteButton(false)}
            {held ? (
              onResume && <Button variant="secondary" onClick={onResume} leading={<PlayIcon />} className={rowSize}>Resume</Button>
            ) : (
              onHold && <Button variant="secondary" onClick={onHold} disabled={blocked} leading={<PauseIcon />} className={rowSize}>Hold</Button>
            )}
            {onCameraChange && (
              <Button variant="secondary" aria-pressed={camera === "on"} onClick={() => onCameraChange(camera !== "on")} leading={<CameraIcon />}
                className={cn(rowSize, "aria-pressed:border-fg aria-pressed:bg-fg aria-pressed:text-bg aria-pressed:hover:bg-fg")}>
                <span className="max-sm:sr-only">Camera</span>
              </Button>
            )}
            {setScreen && (
              <Button variant="secondary" aria-pressed={screen === "on"} onClick={() => setScreen(screen !== "on")} leading={<ScreenIcon />}
                className={cn(rowSize, "aria-pressed:border-fg aria-pressed:bg-fg aria-pressed:text-bg aria-pressed:hover:bg-fg")}>
                <span className="max-sm:sr-only">Share screen</span>
              </Button>
            )}
            <Button variant="secondary" onClick={onEnd} leading={<XIcon width={14} height={14} />}
              className={cn(rowSize, "ml-auto border-danger-line text-danger-fg hover:border-danger hover:bg-danger-soft")}>
              End
            </Button>
          </div>
          {showKeyHints && (onMutedChange || onInterrupt || ptt) && (
            <p className="flex flex-wrap items-center justify-center gap-x-3 gap-y-1 text-small leading-4 text-fg-muted pointer-coarse:hidden">
              {ptt && <span className="inline-flex items-center gap-1"><Kbd>Space</Kbd> hold to talk</span>}
              {onMutedChange && <span className="inline-flex items-center gap-1"><Kbd>M</Kbd> mute</span>}
              {onInterrupt && <span className="inline-flex items-center gap-1"><Kbd>Esc</Kbd> interrupt</span>}
            </p>
          )}
        </div>
      )}
    </section>
  );
}

/** The original name. Same component: every VoicePanel prop still works. */
export const VoicePanel = VoiceSession;
```

## tattva/organisms/VoicePicker.tsx

```tsx
import { useEffect, useId, useMemo, useState } from "react";
import { cn } from "../lib/cn";
import { Skeleton } from "../atoms/Skeleton";
import { VoiceCard, useSimulatedSamples, type VoiceConsent, type VoicePreview, type VoiceSource, type VoiceStatus } from "../molecules/VoiceCard";

export interface VoiceOption {
  id: string;
  name: string;
  description?: string;
  /** Used for the filter chips as well as shown on the card. */
  tags?: string[];
  source: VoiceSource;
  owner?: string;
  consent?: VoiceConsent;
  status?: VoiceStatus;
  /** Written-out date from your code, for example "3 March 2027". */
  withdrawsOn?: string;
  lockedReason?: string;
  /** Sample length in seconds, for the built-in stand-in player. */
  sampleSeconds?: number;
  /** Makes the stand-in sample fail, to show the error state. */
  sampleFails?: boolean;
}

export interface VoicePickerProps {
  voices: VoiceOption[];
  /** Id of the chosen voice. */
  value?: string;
  onChange: (id: string) => void;
  /** Name of the group, shown above the list. */
  label?: string;
  hideLabel?: boolean;
  /** Shows filter chips built from the voices' tags. */
  filters?: boolean;
  /** Placeholder rows while the voices load. */
  loading?: boolean;
  /** One column (the default), or two columns from 640px wide. Always one column on a phone. */
  layout?: "list" | "grid";
  /**
   * Your own player. Return the preview for a voice. Leave out to use the stand-in player, which simulates a sample
   * with a timer. Either way only one sample plays at a time: when one starts, stop the others.
   */
  previewFor?: (voice: VoiceOption) => VoicePreview;
  /** Text when there are no voices at all. */
  emptyText?: string;
  className?: string;
}

/**
 * A radio group of VoiceCards with optional tag filters. Each card has its own sample button, and starting one sample
 * stops any other. Choosing a voice never plays it, and nothing plays by itself.
 */
export function VoicePicker({
  voices, value, onChange, label = "Voice", hideLabel, filters = false, loading = false, layout = "list",
  previewFor, emptyText = "No voices yet.", className,
}: VoicePickerProps) {
  const labelId = useId();
  const group = useId();
  const sim = useSimulatedSamples();
  const [tag, setTag] = useState<string | null>(null);

  const tags = useMemo(() => Array.from(new Set(voices.flatMap((v) => v.tags ?? []))), [voices]);
  const shown = tag ? voices.filter((v) => v.tags?.includes(tag)) : voices;

  // A sample for a voice the filter just hid would keep playing out of sight, so stop it.
  const { id: playingId, stop } = sim;
  useEffect(() => { if (playingId && !shown.some((v) => v.id === playingId)) stop(); }, [playingId, shown, stop]);

  const preview = (v: VoiceOption) => previewFor ? previewFor(v) : sim.previewFor(v.id, { seconds: v.sampleSeconds ?? 8, fails: v.sampleFails });

  const chip = (t: string | null, text: string) => {
    const on = tag === t;
    return (
      <li key={text}>
        <button type="button" aria-pressed={on} onClick={() => setTag(t)}
          className={cn("tap inline-flex h-8 cursor-pointer items-center gap-1 rounded-control border px-3 text-compact transition-colors duration-(--dur-fast)",
            on ? "border-fg bg-fg text-bg" : "border-line bg-surface text-fg-muted hover:border-line-strong hover:text-fg")}>
          {text}
        </button>
      </li>
    );
  };

  return (
    <div className={cn("min-w-0", className)}>
      <p id={labelId} className={cn("mb-2 text-body leading-5 font-medium text-fg", hideLabel && "sr-only")}>{label}</p>

      {filters && tags.length > 0 && !loading && (
        <ul aria-label={`Filter ${label.toLowerCase()} by tag`} className="mb-3 flex flex-wrap gap-2">
          {chip(null, "All")}
          {tags.map((t) => chip(t, t))}
        </ul>
      )}

      {loading ? (
        <div aria-busy="true" aria-labelledby={labelId} className="space-y-2">
          <span role="status" className="sr-only">Loading voices</span>
          {[0, 1, 2].map((i) => <Skeleton key={i} className="h-20 w-full rounded-card" />)}
        </div>
      ) : voices.length === 0 ? (
        <p className="rounded-card border border-dashed border-line px-4 py-6 text-center text-compact text-fg-muted">{emptyText}</p>
      ) : shown.length === 0 ? (
        <div className="rounded-card border border-dashed border-line px-4 py-6 text-center text-compact text-fg-muted">
          <p>No voices tagged {tag}.</p>
          <button type="button" onClick={() => setTag(null)} className="tap mt-1 cursor-pointer text-fg underline underline-offset-2">Show all voices</button>
        </div>
      ) : (
        <div role="radiogroup" aria-labelledby={labelId}
          className={cn("grid gap-2", layout === "grid" && "sm:grid-cols-2")}>
          {shown.map((v) => (
            <VoiceCard key={v.id} groupName={group} name={v.name} description={v.description} tags={v.tags} source={v.source}
              owner={v.owner} consent={v.consent} status={v.status} withdrawsOn={v.withdrawsOn} lockedReason={v.lockedReason}
              selected={v.id === value} onSelect={() => onChange(v.id)} preview={preview(v)} />
          ))}
        </div>
      )}
      {filters && !loading && voices.length > 0 && (
        <p role="status" className="sr-only">{tag ? `${shown.length} ${shown.length === 1 ? "voice" : "voices"} tagged ${tag}` : ""}</p>
      )}
    </div>
  );
}
```

## tattva/organisms/genui/catalog.ts

```ts
import { createElement as h, useState, type ReactNode } from "react";
import { Badge } from "../../atoms/Badge";
import { Button } from "../../atoms/Button";
import { Callout } from "../../molecules/Callout";
import { ConfidenceIndicator } from "../../molecules/ConfidenceIndicator";
import { EmptyState } from "../../molecules/EmptyState";
import { StatTile } from "../../molecules/StatTile";
import { DataTable } from "../../molecules/DataTable";
import type { Source } from "../../molecules/SourceCard";
import { BarChart } from "../BarChart";
import { Checklist } from "../Checklist";
import { LineChart } from "../LineChart";
import { ParameterPanel, type ParameterSpec, type ParameterValues } from "../ParameterPanel";
import { PlanCard } from "../PlanCard";
import { SourceList } from "../SourceList";
import { SummaryCard } from "../SummaryCard";
import { VariantGrid } from "../VariantGrid";

/**
 * The allowlist. A model may only ask for the parts listed here, with the props listed here.
 * Everything else is dropped or shown as a quiet fallback. The host owns styling: no part accepts classes or styles.
 */

export type PropType = "string" | "number" | "boolean" | "enum" | "array" | "object";

export interface PropDescriptor {
  name: string;
  type: PropType;
  required?: boolean;
  /** Allowed values when type is "enum". */
  enumValues?: string[];
  description: string;
  /** For arrays and objects: the shape of the contents, written for the model. */
  shape?: string;
}

export type GenUIActionType = "press" | "submit" | "select" | "change";

export interface RenderContext {
  nodeId: string;
  /** True while the host is still handling an action from this node. Controls should be disabled. */
  pending: boolean;
  /** The last action from this node that went out, so a part can show that it was used. */
  sent?: { type: GenUIActionType; value: unknown };
  /** Report something the person did. The renderer decides whether to ask for confirmation first. */
  act: (type: GenUIActionType, value: unknown) => void;
}

export interface CatalogEntry {
  type: string;
  description: string;
  whenToUse: string;
  props: PropDescriptor[];
  canHaveChildren: boolean;
  interactive: boolean;
  /** Props that must be present and not empty, because a person using a screen reader needs them. */
  accessibilityRequires: string[];
  render: (props: Record<string, unknown>, children: ReactNode[], ctx: RenderContext) => ReactNode;
}

/** Props the renderer handles itself on interactive parts. */
export const COMMON_INTERACTIVE_PROPS: PropDescriptor[] = [
  { name: "confirm", type: "boolean", description: "Set to true when pressing this does something that is hard to undo, such as sending, buying or deleting. The person is asked to confirm first." },
  { name: "consequence", type: "string", description: "One plain sentence saying what will happen if the person confirms, for example \"This emails 12 customers.\"" },
];

/** Names that are never accepted anywhere in a node's props. The host owns styling, scripts and handlers. */
const FORBIDDEN = new Set(["classname", "class", "style", "styles", "dangerouslysetinnerhtml", "innerhtml", "css", "sx", "srcdoc", "src", "href"]);
const isForbiddenName = (k: string) => FORBIDDEN.has(k.toLowerCase()) || /^on[A-Z]/.test(k);

/* ---------- small readers, so render never trusts a value ---------- */

const isRec = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null && !Array.isArray(v);
const str = (v: unknown, d = ""): string => (typeof v === "string" ? v : typeof v === "number" ? String(v) : d);
const optStr = (v: unknown): string | undefined => (typeof v === "string" && v ? v : undefined);
const num = (v: unknown, d = 0): number => (typeof v === "number" && Number.isFinite(v) ? v : d);
const arr = (v: unknown): unknown[] => (Array.isArray(v) ? v : []);
const recs = (v: unknown): Record<string, unknown>[] => arr(v).filter(isRec);
const oneOf = <T extends string>(v: unknown, allowed: readonly T[], d: T): T => (allowed.includes(v as T) ? (v as T) : d);

/** Only web addresses are kept. Anything else (javascript:, data:, file:) is dropped. */
export function safeHttpUrl(v: unknown): string | null {
  if (typeof v !== "string") return null;
  try {
    const u = new URL(v);
    return u.protocol === "https:" || u.protocol === "http:" ? u.href : null;
  } catch {
    return null;
  }
}

function toSources(v: unknown): Source[] {
  const out: Source[] = [];
  recs(v).forEach((s, i) => {
    const url = safeHttpUrl(s.url);
    if (!url) return;
    out.push({ id: i + 1, title: str(s.title, new URL(url).hostname), url, domain: str(s.domain, new URL(url).hostname.replace(/^www\./, "")), snippet: optStr(s.snippet) });
  });
  return out;
}

const tones = ["info", "success", "warning", "danger"] as const;
const badgeTones = ["neutral", "accent", "lime", "success", "warning", "danger", "info"] as const;

/* ---------- small stateful wrappers (the model never sees these) ---------- */

function ParametersNode({ props, ctx }: { props: Record<string, unknown>; ctx: RenderContext }) {
  const kinds = ["segmented", "aspect", "presets", "number", "seed"];
  const parameters = recs(props.parameters)
    .filter((p) => typeof p.id === "string" && typeof p.label === "string" && kinds.includes(str(p.kind)))
    .map((p) => ({ ...p, options: recs(p.options).map((o) => ({ value: str(o.value), label: str(o.label, str(o.value)) })) })) as unknown as ParameterSpec[];
  const [values, setValues] = useState<ParameterValues>(() => {
    const init: ParameterValues = {};
    for (const p of recs(props.parameters)) {
      if (typeof p.id !== "string") continue;
      const d = p.default;
      init[p.id] = typeof d === "string" || typeof d === "number" || typeof d === "boolean" ? d : p.kind === "number" ? num(p.min, 0) : recs(p.options)[0] ? str(recs(p.options)[0].value) : null;
    }
    return init;
  });
  return h(ParameterPanel, { parameters, values, onChange: setValues, onGenerate: (v) => ctx.act("submit", v), title: str(props.title, "Settings"), generateLabel: str(props.submitLabel, "Apply"), busy: ctx.pending });
}

function VariantsNode({ props, ctx }: { props: Record<string, unknown>; ctx: RenderContext }) {
  const [selected, setSelected] = useState<string | undefined>(undefined);
  const variants = recs(props.variants).filter((v) => typeof v.id === "string").map((v) => ({
    id: str(v.id), label: str(v.label, str(v.id)),
    node: h("span", { className: "flex size-full items-center justify-center p-3 text-center text-body leading-5 text-fg" }, str(v.text, str(v.label))),
  }));
  return h("div", { className: "space-y-2" },
    optStr(props.title) ? h("p", { className: "text-body leading-5 font-medium" }, str(props.title)) : null,
    h(VariantGrid, { variants, selectedId: selected, onSelect: (id: string) => { setSelected(id); ctx.act("select", id); } }));
}

function PlanNode({ props, ctx }: { props: Record<string, unknown>; ctx: RenderContext }) {
  const steps = recs(props.steps).map((s, i) => ({ id: str(s.id, String(i + 1)), title: str(s.title), description: optStr(s.description) }));
  const sent = ctx.sent?.value;
  const state = sent === "approve" ? "approved" : sent === "revise" ? "revised" : "awaiting";
  return h(PlanCard, { title: str(props.title, "Plan"), steps, estimate: optStr(props.estimate), state, onApprove: () => ctx.act("press", "approve"), onRevise: () => ctx.act("press", "revise"), readOnlyLabel: null });
}

/* ---------- the catalog ---------- */

export const catalog: CatalogEntry[] = [
  {
    type: "Group",
    description: "A container that lays its children out in a column or a grid.",
    whenToUse: "Use it to put several parts together under an optional heading. Keep it to one level of nesting.",
    props: [
      { name: "title", type: "string", description: "Optional heading above the group." },
      { name: "layout", type: "enum", enumValues: ["stack", "grid"], description: "stack puts children in one column. grid puts them side by side when there is room. Default stack." },
    ],
    canHaveChildren: true, interactive: false, accessibilityRequires: [],
    render: (p, children) => h("section", { "aria-label": optStr(p.title), className: "space-y-3" },
      optStr(p.title) ? h("h3", { className: "text-lead leading-6" }, str(p.title)) : null,
      h("div", { className: p.layout === "grid" ? "grid gap-3 sm:grid-cols-2 lg:grid-cols-3 [&>*]:min-w-0" : "space-y-3 [&>*]:min-w-0" }, ...children)),
  },
  {
    type: "Text",
    description: "A short paragraph of plain text.",
    whenToUse: "Use it for a sentence or two of explanation. Do not use it for long answers; reply in text instead.",
    props: [
      { name: "text", type: "string", required: true, description: "The words to show. Plain text only." },
      { name: "tone", type: "enum", enumValues: ["normal", "muted"], description: "muted is for small side notes. Default normal." },
    ],
    canHaveChildren: false, interactive: false, accessibilityRequires: ["text"],
    render: (p) => h("p", { className: p.tone === "muted" ? "text-body leading-5 text-fg-muted" : "text-body leading-5 text-fg" }, str(p.text)),
  },
  {
    type: "Callout",
    description: "A highlighted note with a short title and a sentence of detail.",
    whenToUse: "Use it for one thing the person should notice: a result, a warning or a tip. Only use warning when a person has to act.",
    props: [
      { name: "tone", type: "enum", enumValues: [...tones], description: "info, success, warning or danger. Default info." },
      { name: "title", type: "string", description: "Short heading." },
      { name: "text", type: "string", required: true, description: "The note itself." },
    ],
    canHaveChildren: false, interactive: false, accessibilityRequires: ["text"],
    render: (p) => h(Callout, { tone: oneOf(p.tone, tones, "info"), title: optStr(p.title) }, str(p.text)),
  },
  {
    type: "StatTile",
    description: "One headline number with a label and an optional change.",
    whenToUse: "Use it for the two to four numbers that answer the question, such as orders this week. Put them in a grid Group.",
    props: [
      { name: "label", type: "string", required: true, description: "What the number measures, in sentence case." },
      { name: "value", type: "string", required: true, description: "The number, already formatted, for example \"1,240\" or \"$4.2K\"." },
      { name: "delta", type: "object", description: "Change since before.", shape: "{ text: string (for example \"+4.2%\"), direction: \"up\" | \"down\" | \"flat\", versus?: string }" },
      { name: "goodDirection", type: "enum", enumValues: ["up", "down", "neutral"], description: "Which way counts as better. Default up." },
      { name: "detail", type: "string", description: "A small line under the number." },
    ],
    canHaveChildren: false, interactive: false, accessibilityRequires: ["label", "value"],
    render: (p) => {
      const d = isRec(p.delta) && typeof p.delta.text === "string" ? { text: str(p.delta.text), direction: oneOf(p.delta.direction, ["up", "down", "flat"] as const, "flat"), versus: optStr(p.delta.versus) } : undefined;
      return h(StatTile, { label: str(p.label), value: str(p.value), delta: d, goodDirection: oneOf(p.goodDirection, ["up", "down", "neutral"] as const, "up"), detail: optStr(p.detail) });
    },
  },
  {
    type: "LineChart",
    description: "A line chart of one to five series over a list of labels such as days.",
    whenToUse: "Use it to show how something changed over time. For comparing categories use BarChart.",
    props: [
      { name: "ariaLabel", type: "string", required: true, description: "A name that says what the chart shows, for example \"Orders per day\"." },
      { name: "x", type: "array", required: true, description: "Labels along the bottom.", shape: "string[]" },
      { name: "series", type: "array", required: true, description: "The lines.", shape: "{ id: string, label: string, data: (number | null)[] }[] with one value per label" },
      { name: "unit", type: "string", description: "Unit word read aloud after each value, for example \"orders\"." },
    ],
    canHaveChildren: false, interactive: false, accessibilityRequires: ["ariaLabel"],
    render: (p) => h(LineChart, {
      ariaLabel: str(p.ariaLabel), x: arr(p.x).map((v) => str(v)), unit: str(p.unit),
      series: recs(p.series).map((s, i) => ({ id: str(s.id, `s${i}`), label: str(s.label, `Series ${i + 1}`), data: arr(s.data).map((v) => (typeof v === "number" && Number.isFinite(v) ? v : null)) })),
    }),
  },
  {
    type: "BarChart",
    description: "A bar chart that compares categories.",
    whenToUse: "Use it to compare a handful of categories, such as sales by product. Keep it under about ten bars.",
    props: [
      { name: "ariaLabel", type: "string", required: true, description: "A name that says what the chart shows, for example \"Orders by product\"." },
      { name: "data", type: "array", required: true, description: "The bars, in order.", shape: "{ label: string, value: number (zero or more) }[]" },
      { name: "orientation", type: "enum", enumValues: ["horizontal", "vertical"], description: "Default horizontal." },
      { name: "measure", type: "string", description: "Name of what is measured, for example \"Orders\"." },
      { name: "unit", type: "string", description: "Unit word read aloud after each value." },
      { name: "highlight", type: "string", description: "Label of one bar to emphasize." },
    ],
    canHaveChildren: false, interactive: false, accessibilityRequires: ["ariaLabel"],
    render: (p) => h(BarChart, {
      ariaLabel: str(p.ariaLabel), orientation: oneOf(p.orientation, ["horizontal", "vertical"] as const, "horizontal"), measure: str(p.measure, "Value"), unit: str(p.unit), highlight: optStr(p.highlight),
      data: recs(p.data).map((d) => ({ label: str(d.label), value: Math.max(0, num(d.value)) })),
    }),
  },
  {
    type: "DataTable",
    description: "A table of rows and columns.",
    whenToUse: "Use it when the person needs exact values or many attributes. For two or three numbers use StatTile instead.",
    props: [
      { name: "caption", type: "string", required: true, description: "Says what the table holds, for example \"Orders this week by day\"." },
      { name: "columns", type: "array", required: true, description: "Column definitions in order. The first column is the row header.", shape: "{ key: string, header: string, numeric?: boolean }[]" },
      { name: "rows", type: "array", required: true, description: "One object per row, keyed by column key.", shape: "Record<string, string | number>[]" },
    ],
    canHaveChildren: false, interactive: false, accessibilityRequires: ["caption"],
    render: (p) => h(DataTable, {
      caption: str(p.caption),
      columns: recs(p.columns).filter((c) => typeof c.key === "string").map((c) => ({ key: str(c.key), header: str(c.header, str(c.key)), numeric: c.numeric === true })),
      rows: recs(p.rows).map((r) => Object.fromEntries(Object.entries(r).map(([k, v]) => [k, typeof v === "string" || typeof v === "number" ? String(v) : ""]))),
    }),
  },
  {
    type: "SourceList",
    description: "A numbered list of the web pages an answer is based on.",
    whenToUse: "Use it under a summary or claim so the person can check where it came from. Only list pages you really used.",
    props: [
      { name: "sources", type: "array", required: true, description: "The pages.", shape: "{ title: string, url: string (https only), domain?: string, snippet?: string }[]" },
    ],
    canHaveChildren: false, interactive: false, accessibilityRequires: ["sources"],
    render: (p) => h(SourceList, { sources: toSources(p.sources) }),
  },
  {
    type: "SummaryCard",
    description: "A short written summary with an AI label, optional extra detail and sources.",
    whenToUse: "Use it for a short overview of something that was looked up or worked out. Add sources when the content is factual.",
    props: [
      { name: "title", type: "string", description: "Heading. Default Overview." },
      { name: "text", type: "string", required: true, description: "The summary, plain text." },
      { name: "detail", type: "string", description: "Longer detail the person can open." },
      { name: "sources", type: "array", description: "Pages the summary is based on.", shape: "{ title: string, url: string (https only) }[]" },
    ],
    canHaveChildren: false, interactive: false, accessibilityRequires: ["text"],
    render: (p) => h(SummaryCard, { title: str(p.title, "Overview"), detail: optStr(p.detail) ? h("p", null, str(p.detail)) : undefined, sources: toSources(p.sources).length ? toSources(p.sources) : undefined, children: h("p", null, str(p.text)) }),
  },
  {
    type: "PlanCard",
    description: "A plan with numbered steps that the person can approve or ask to change.",
    whenToUse: "Use it before doing work with several steps. Set confirm to true when approving starts something that is hard to undo.",
    props: [
      { name: "title", type: "string", required: true, description: "Name of the plan." },
      { name: "steps", type: "array", required: true, description: "Steps in order.", shape: "{ id: string, title: string, description?: string }[]" },
      { name: "estimate", type: "string", description: "Time or cost, for example \"About 6 minutes\"." },
      ...COMMON_INTERACTIVE_PROPS,
    ],
    canHaveChildren: false, interactive: true, accessibilityRequires: ["title", "steps"],
    render: (p, _c, ctx) => h(PlanNode, { props: p, ctx }),
  },
  {
    type: "ParameterPanel",
    description: "A small form of choices and numbers with an Apply button.",
    whenToUse: "Use it when the person should set a few options before you continue, such as size or style. Never invent other input types.",
    props: [
      { name: "title", type: "string", required: true, description: "Name of the panel." },
      { name: "parameters", type: "array", required: true, description: "The controls, in order.", shape: "{ id: string, label: string, kind: \"segmented\" | \"presets\" | \"number\", options?: { value: string, label: string }[], min?: number, max?: number, step?: number, unit?: string, default?: string | number }[]" },
      { name: "submitLabel", type: "string", description: "Text of the button. Default Apply." },
      ...COMMON_INTERACTIVE_PROPS,
    ],
    canHaveChildren: false, interactive: true, accessibilityRequires: ["title", "parameters"],
    render: (p, _c, ctx) => h(ParametersNode, { props: p, ctx }),
  },
  {
    type: "VariantGrid",
    description: "A set of options the person picks one from.",
    whenToUse: "Use it to offer two to eight alternatives, such as draft titles. Each option needs a label.",
    props: [
      { name: "title", type: "string", description: "Question above the options, for example \"Pick a title\"." },
      { name: "variants", type: "array", required: true, description: "The options.", shape: "{ id: string, label: string, text?: string }[]" },
      ...COMMON_INTERACTIVE_PROPS,
    ],
    canHaveChildren: false, interactive: true, accessibilityRequires: ["variants"],
    render: (p, _c, ctx) => h(VariantsNode, { props: p, ctx }),
  },
  {
    type: "Checklist",
    description: "A list of tasks with a short status next to each.",
    whenToUse: "Use it to show progress on a few steps. Mark an item attention only when a person has to act.",
    props: [
      { name: "items", type: "array", required: true, description: "The tasks.", shape: "{ id: string, title: string, status?: string, attention?: boolean }[]" },
      { name: "doneSummary", type: "string", description: "One line for work that is finished, for example \"3 steps done\"." },
    ],
    canHaveChildren: false, interactive: false, accessibilityRequires: ["items"],
    render: (p) => h(Checklist, { doneSummary: optStr(p.doneSummary), items: recs(p.items).map((i, n) => ({ id: str(i.id, String(n)), title: str(i.title), status: optStr(i.status), attention: i.attention === true })) }),
  },
  {
    type: "ConfidenceIndicator",
    description: "Shows how sure the answer is: low, medium or high.",
    whenToUse: "Use it next to a claim you are not fully sure about. Do not use it to decorate answers you are sure of.",
    props: [{ name: "level", type: "enum", required: true, enumValues: ["low", "medium", "high"], description: "How sure you are." }],
    canHaveChildren: false, interactive: false, accessibilityRequires: ["level"],
    render: (p) => h(ConfidenceIndicator, { level: oneOf(p.level, ["low", "medium", "high"] as const, "medium") }),
  },
  {
    type: "Badge",
    description: "A small label such as a status.",
    whenToUse: "Use it for one or two words, like \"Shipped\" or \"Late\". Do not use it for sentences.",
    props: [
      { name: "text", type: "string", required: true, description: "One or two words." },
      { name: "tone", type: "enum", enumValues: [...badgeTones], description: "Default neutral. Use warning only when a person has to act." },
    ],
    canHaveChildren: false, interactive: false, accessibilityRequires: ["text"],
    render: (p) => h(Badge, { tone: oneOf(p.tone, badgeTones, "neutral") }, str(p.text)),
  },
  {
    type: "EmptyState",
    description: "A message for when there is nothing to show, with a reason and a next step.",
    whenToUse: "Use it when a search or list came back empty or something could not be loaded. Say why, and what to try.",
    props: [
      { name: "variant", type: "enum", enumValues: ["first-use", "no-results", "cleared", "error", "offline"], description: "Sets the picture and default words. Default no-results." },
      { name: "title", type: "string", required: true, description: "What happened." },
      { name: "text", type: "string", description: "The next step, in one sentence." },
    ],
    canHaveChildren: false, interactive: false, accessibilityRequires: ["title"],
    render: (p) => h(EmptyState, { variant: oneOf(p.variant, ["first-use", "no-results", "cleared", "error", "offline"] as const, "no-results"), title: str(p.title), compact: true }, optStr(p.text)),
  },
  {
    type: "Button",
    description: "A button that sends the person's choice back to you.",
    whenToUse: "Use it for one clear next step. Use at most two buttons together. Set confirm to true for anything hard to undo.",
    props: [
      { name: "label", type: "string", required: true, description: "What the button does, as a short verb phrase, for example \"Send reminders\"." },
      { name: "variant", type: "enum", enumValues: ["primary", "secondary", "ghost", "danger"], description: "Default primary. danger always asks the person to confirm." },
      { name: "value", type: "string", description: "A word you will recognise when the press comes back to you. Defaults to the label." },
      ...COMMON_INTERACTIVE_PROPS,
    ],
    canHaveChildren: false, interactive: true, accessibilityRequires: ["label"],
    render: (p, _c, ctx) => h(Button, { variant: oneOf(p.variant, ["primary", "secondary", "ghost", "danger"] as const, "primary"), disabled: ctx.pending, "aria-busy": ctx.pending || undefined, onClick: () => ctx.act("press", optStr(p.value) ?? str(p.label)) }, str(p.label)),
  },
];

export const catalogByType: Record<string, CatalogEntry> = Object.fromEntries(catalog.map((e) => [e.type, e]));

/* ---------- validation ---------- */

export interface ValidationResult {
  ok: boolean;
  /** Props with unknown ones removed. Only meaningful when ok is true. */
  props: Record<string, unknown>;
  /** Reasons the node cannot be shown. */
  errors: string[];
  /** Things that were dropped but did not stop the node from showing. */
  warnings: string[];
}

function matchesType(d: PropDescriptor, v: unknown): boolean {
  switch (d.type) {
    case "string": return typeof v === "string";
    case "number": return typeof v === "number" && Number.isFinite(v);
    case "boolean": return typeof v === "boolean";
    case "enum": return typeof v === "string" && (d.enumValues ?? []).includes(v);
    case "array": return Array.isArray(v);
    case "object": return isRec(v);
  }
}

function findForbidden(v: unknown, path: string, depth = 0): string | null {
  if (depth > 5 || typeof v !== "object" || v === null) return null;
  if (Array.isArray(v)) {
    for (let i = 0; i < Math.min(v.length, 200); i++) { const f = findForbidden(v[i], `${path}[${i}]`, depth + 1); if (f) return f; }
    return null;
  }
  for (const [k, val] of Object.entries(v)) {
    if (isForbiddenName(k)) return `${path}.${k}`;
    const f = findForbidden(val, `${path}.${k}`, depth + 1);
    if (f) return f;
  }
  return null;
}

/** Hand-written light check of one node against the catalog. It never throws. */
export function validateNode(node: { type?: unknown; props?: unknown; children?: unknown }): ValidationResult {
  const res: ValidationResult = { ok: false, props: {}, errors: [], warnings: [] };
  try {
    const entry = typeof node.type === "string" && Object.prototype.hasOwnProperty.call(catalogByType, node.type) ? catalogByType[node.type] : undefined;
    if (!entry) { res.errors.push(`"${String(node.type)}" is not in the catalog`); return res; }
    const raw = isRec(node.props) ? node.props : {};
    for (const k of Object.keys(raw)) if (isForbiddenName(k)) res.errors.push(`"${k}" is not allowed: the host owns styling and behavior`);
    const deep = findForbidden(raw, "props");
    if (deep && !res.errors.length) res.errors.push(`"${deep}" is not allowed: the host owns styling and behavior`);
    const known = new Map<string, PropDescriptor>(entry.props.map((d) => [d.name, d]));
    for (const d of entry.props) {
      const v = raw[d.name];
      if (v === undefined || v === null) { if (d.required) res.errors.push(`missing required "${d.name}"`); continue; }
      if (!matchesType(d, v)) { res.errors.push(`"${d.name}" should be ${d.type === "enum" ? `one of ${(d.enumValues ?? []).join(", ")}` : `a ${d.type}`}`); continue; }
      res.props[d.name] = v;
    }
    for (const k of Object.keys(raw)) if (!known.has(k) && !isForbiddenName(k)) res.warnings.push(`ignored unknown prop "${k}"`);
    for (const name of entry.accessibilityRequires) {
      const v = res.props[name];
      const empty = v === undefined || (typeof v === "string" && !v.trim()) || (Array.isArray(v) && v.length === 0);
      if (empty && !res.errors.some((e) => e.includes(`"${name}"`))) res.errors.push(`needs "${name}" so everyone can use it`);
    }
    if (!entry.canHaveChildren && Array.isArray(node.children) && node.children.length) res.warnings.push("this component cannot have children; they were ignored");
    res.ok = res.errors.length === 0;
    return res;
  } catch {
    res.errors.push("could not be checked");
    return res;
  }
}

/** True when this node's actions should ask first: confirm is set, or it is a danger button. */
export function needsConfirmation(type: string, props: Record<string, unknown>): boolean {
  const entry = catalogByType[type];
  if (!entry?.interactive) return false;
  return props.confirm === true || (type === "Button" && props.variant === "danger");
}

/** The catalog as plain JSON, without render functions. */
export function catalogToJSON() {
  return catalog.map(({ render: _render, ...rest }) => rest);
}

/** A system prompt that tells a model exactly what it may build. */
export function buildSystemPrompt(): string {
  const parts = catalog.map((e) => {
    const props = e.props.map((p) => `  - ${p.name} (${p.type === "enum" ? `one of: ${(p.enumValues ?? []).join(" | ")}` : p.type}${p.required ? ", required" : ""}): ${p.description}${p.shape ? ` Shape: ${p.shape}` : ""}`).join("\n");
    return `### ${e.type}\n${e.description}\nWhen to use: ${e.whenToUse}\nChildren: ${e.canHaveChildren ? "yes (list child ids in \"children\")" : "no"}. Interactive: ${e.interactive ? "yes" : "no"}.\nMust always have: ${e.accessibilityRequires.length ? e.accessibilityRequires.join(", ") : "nothing extra"}.\nProps:\n${props}`;
  });
  return `# Building screens with the Tattva catalog

You can answer with a small screen instead of plain text when a screen helps the person: numbers to compare, a plan to approve, options to pick. You only propose the screen. The app checks it and draws it using its own design. If something is wrong, the app shows a quiet text fallback.

## Format

Reply with a JSON object, or several lines of JSON (one object per line) if you are writing as you go:

{ "root": "page", "nodes": [ { "id": "page", "type": "Group", "props": { "title": "Orders this week" }, "children": ["orders", "chart"] }, { "id": "orders", "type": "StatTile", "props": { "label": "Orders", "value": "1,240" } } ], "done": true }

- A screen is a flat list of nodes. Each node has "id" (unique, short), "type" (a component below), "props", and, for containers, "children" (a list of node ids).
- "root" is the id of the first node to draw.
- Nodes can be sent in any order and in several pieces. A node sent again with the same id updates its props. Send "done": true in the last piece.
- Every value you show must come from data you really have. Do not make up numbers.

## Rules

1. Never invent components or props. Use only the components below. Anything else is dropped.
2. Never set styles, classes, colors, sizes, HTML, scripts or event handlers. The app owns how things look. Props named className, style, class, dangerouslySetInnerHTML or starting with "on" are rejected and the whole node is replaced by a fallback.
3. Always give the accessible words: every component lists what it must always have (for example ariaLabel for charts, caption for tables, label for buttons). Write them so a person who cannot see the screen understands.
4. Ask before anything that is hard to undo (sending, buying, deleting, publishing). Set "confirm": true and write a plain "consequence" sentence on that control. Never hide a consequence behind a vague label.
5. Keep it small. Prefer 2 to 8 nodes. At most two buttons together. Do not nest groups more than one level.
6. Use plain words a smart person who has never used AI would understand. No jargon.
7. Do not build a screen for a simple answer. If a sentence does the job, reply with text.
8. Links must start with https://. Never ask for passwords, payment details or other secrets in a screen.
9. When the person presses something, you will receive an event: { nodeId, type, value }. Reply to it. Do not assume it worked until you are told.

## Components

${parts.join("\n\n")}

### Interactive parts and confirmation
Interactive parts also accept "confirm" (boolean) and "consequence" (string). With confirm true, the app shows "Are you sure?" with your consequence sentence and only sends the press if the person agrees.
`;
}
```

## tattva/organisms/genui/spec.ts

```ts
/**
 * Generated UI spec: a flat list of nodes, so a model can write it a piece at a time and patch it later.
 * Inspired by the flat-list design of A2UI. It has no dependencies and holds no styling: the host owns look and feel.
 */

export interface SpecNode {
  /** Stable id. Other nodes point at it from `children`. The renderer uses it as the React key. */
  id: string;
  /** Name of a catalog component, such as "StatTile". */
  type: string;
  /** Content and options. Never styles. */
  props?: Record<string, unknown>;
  /** Ids of child nodes, in order. A child may arrive after its parent. */
  children?: string[];
}

/** One piece of a stream. Any field may be missing. */
export interface SpecChunk {
  /** Id of the node to render first. */
  root?: string;
  /** New nodes, or new versions of nodes already sent. */
  nodes?: SpecNode[];
  /** Set on the last chunk. Until then missing children are treated as still on their way. */
  done?: boolean;
}

export interface SpecState {
  root: string | null;
  nodes: Record<string, SpecNode>;
  /** Ids in the order they first arrived. */
  order: string[];
  done: boolean;
}

export const emptySpec = (): SpecState => ({ root: null, nodes: {}, order: [], done: false });

const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null && !Array.isArray(v);

/** Reads one node from unknown input. Returns null when it has no usable id or type. */
export function toNode(v: unknown): SpecNode | null {
  if (!isRecord(v)) return null;
  if (typeof v.id !== "string" || !v.id || typeof v.type !== "string" || !v.type) return null;
  if (["__proto__", "constructor", "prototype"].includes(v.id)) return null;
  const node: SpecNode = { id: v.id, type: v.type };
  if (isRecord(v.props)) node.props = v.props;
  if (Array.isArray(v.children)) node.children = v.children.filter((c): c is string => typeof c === "string");
  return node;
}

/**
 * Merges one streamed chunk into the state. It never mutates and never throws.
 * Nodes may arrive in any order. A node sent again keeps its old props unless the new ones replace them,
 * and its children list is replaced when the new node has one. Bad entries are skipped.
 * Accepts a chunk, a single node, or a list of nodes.
 */
export function applyChunk(state: SpecState, chunk: SpecChunk | SpecNode | SpecNode[] | null | undefined): SpecState {
  if (!chunk) return state;
  const c: SpecChunk = Array.isArray(chunk) ? { nodes: chunk } : "type" in (chunk as object) && "id" in (chunk as object) ? { nodes: [chunk as SpecNode] } : (chunk as SpecChunk);
  const nodes = { ...state.nodes };
  const order = state.order.slice();
  for (const raw of Array.isArray(c.nodes) ? c.nodes : []) {
    const n = toNode(raw);
    if (!n) continue;
    const prev = nodes[n.id];
    if (!prev) order.push(n.id);
    nodes[n.id] = prev ? { id: n.id, type: n.type, props: { ...prev.props, ...n.props }, children: n.children ?? prev.children } : n;
  }
  return {
    root: typeof c.root === "string" && c.root ? c.root : state.root,
    nodes,
    order,
    done: c.done === true ? true : state.done,
  };
}

/** Reads one line of newline-delimited JSON. Returns null for blank or broken lines, so a stream can carry on. */
export function parseChunkLine(line: string): SpecChunk | SpecNode | SpecNode[] | null {
  const t = line.trim();
  if (!t) return null;
  try {
    const v: unknown = JSON.parse(t);
    return Array.isArray(v) || isRecord(v) ? (v as SpecChunk) : null;
  } catch {
    return null;
  }
}

/** Builds a finished state from a complete spec in one step. */
export function specFrom(root: string, nodes: SpecNode[]): SpecState {
  return applyChunk(emptySpec(), { root, nodes, done: true });
}

/** The state as a plain `{ root, nodes }` object, in arrival order. This is what "View spec" shows. */
export function specToJSON(state: SpecState): { root: string | null; nodes: SpecNode[] } {
  return { root: state.root, nodes: state.order.map((id) => state.nodes[id]).filter(Boolean) };
}

/** Ids that some node points at but that have not arrived yet. */
export function missingIds(state: SpecState): string[] {
  const out = new Set<string>();
  if (state.root && !state.nodes[state.root]) out.add(state.root);
  for (const n of Object.values(state.nodes)) for (const c of n.children ?? []) if (!state.nodes[c]) out.add(c);
  return [...out];
}
```

## tattva/pages/HomePage.tsx

```tsx
import { AppShell } from "../templates/AppShell";
import { HomeTemplate } from "../templates/HomeTemplate";
import { HeroHeading } from "../atoms/HeroHeading";
import { AIDisclosure } from "../atoms/AIDisclosure";
import { TrayRow } from "../molecules/TrayRow";
import { ContextChip } from "../molecules/ContextChip";
import { Tray } from "../organisms/Tray";
import { Composer } from "../organisms/Composer";
import { UsersIcon } from "../lib/icons";
import { Rail } from "./Rail";

/** Landing: the headline answers itself in the tray below it. */
export function HomePage() {
  return (
    <AppShell rail={<Rail />} className="h-180 max-h-dvh">
      <HomeTemplate
        heading={<HeroHeading text="3 tasks need you today. Where should we start?" />}
        footnote={<AIDisclosure />}
      >
        <Tray label="Needs you" action="4 running →" footer={
          <Composer onAttach={() => {}} placeholder="Give me a task or ask a question…" onSend={() => {}}
            tools={<ContextChip icon={<UsersIcon width={14} height={14} />} onClick={() => {}}>All projects</ContextChip>} />
        }>
          <TrayRow attention title="Quarterly report" detail="Approval pending for 6 days" trailing="Open →" />
          <TrayRow attention title="Customer survey" detail="Missing 2 answers, due in 2 days" trailing="Open →" />
          <TrayRow attention title="Launch plan" detail="Needs a decision by Friday" trailing="Open →" />
        </Tray>
      </HomeTemplate>
    </AppShell>
  );
}
```

## tattva/pages/Rail.tsx

```tsx
import { useState } from "react";
import { ConversationList } from "../organisms/ConversationList";
import { Avatar } from "../atoms/Avatar";

const items = [
  { id: "1", title: "Why sales grew last quarter", group: "Today" },
  { id: "2", title: "Welcome email draft", group: "Today" },
  { id: "3", title: "Request missing documents", group: "Workflows" },
  { id: "4", title: "Spreadsheet help", group: "Yesterday" },
];

/** Shared rail for the example pages: conversation list plus a user card. */
export function Rail({ activeId = "1" }: { activeId?: string }) {
  const [active, setActive] = useState(activeId);
  return (
    <>
      <div className="flex items-center gap-2 px-1"><Avatar kind="ai" size="sm" /><span className="font-serif text-body-lg">Tattva</span></div>
      <ConversationList items={items} activeId={active} onSelect={setActive} onNew={() => {}} />
      <div className="mt-auto flex items-center gap-2.5 rounded-card bg-sunken p-3">
        <Avatar kind="user" name="Maya Okafor" />
        <div className="min-w-0"><p className="truncate text-small leading-4 font-medium">Maya Okafor</p><p className="truncate text-micro text-fg-subtle">Product team</p></div>
      </div>
    </>
  );
}
```

## tattva/pages/StudioPage.tsx

```tsx
import { useState } from "react";
import { Button } from "../atoms/Button";
import { Rail } from "./Rail";
import { AppShell } from "../templates/AppShell";
import { SplitCanvasTemplate } from "../templates/SplitCanvasTemplate";
import { PromptBox } from "../organisms/PromptBox";
import { VariantGrid } from "../organisms/VariantGrid";
import { CanvasPanel } from "../organisms/CanvasPanel";
import { Markdown } from "../organisms/Markdown";
import { AIBadge } from "../atoms/AIBadge";

// Documented exception to the no-hex rule: sample picture tones that stand in for user images. See scripts/check-docs.mjs.
const tones = ["#24282c", "#c2ef72", "#c97b4a", "#919395"];
const DRAFT = ["# Launch announcement", "", "We are introducing **Tattva**, a clearer way to build with AI.", "", "- Answers that appear without the page jumping", "- Sources you can check", "- Your approval before anything that cannot be undone"];

/** Generative studio: prompt and variants on the left, the chosen artifact with version history on the right. */
export function StudioPage() {
  const [busy, setBusy] = useState(false);
  const [sel, setSel] = useState("a");
  const [version, setVersion] = useState(2);
  return (
    <AppShell rail={<Rail activeId="2" />} className="h-180 max-h-dvh">
      <SplitCanvasTemplate header="Welcome email draft" actions={<Button size="sm" variant="secondary">Share</Button>} primaryLabel="Make options" canvasLabel="Draft"
        primary={<>
          <PromptBox busy={busy} examples={["Launch announcement", "Release notes"]} onGenerate={() => { setBusy(true); setTimeout(() => setBusy(false), 1800); }} />
          <VariantGrid selectedId={sel} onSelect={setSel} loadingCount={busy ? 2 : 0}
            variants={["a", "b", "c", "d"].slice(0, busy ? 2 : 4).map((id, i) => ({ id, label: `Option ${id.toUpperCase()}`, node: <div className="size-full" style={{ background: `linear-gradient(135deg, ${tones[i]}, color-mix(in srgb, ${tones[i]} 35%, #f7f6f0))` }} /> }))} />
        </>}
        canvas={
          <CanvasPanel title="Launch announcement.md" version={version} versions={3} onVersionChange={setVersion} onRegenerate={() => {}} onClose={() => {}} actions={<AIBadge />}>
            <div className="space-y-4 text-body-lg leading-7"><Markdown>{DRAFT.join("\n")}</Markdown></div>
            <p className="mt-6 text-small leading-4 text-fg-subtle">Viewing version {version} of 3.</p>
          </CanvasPanel>
        } />
    </AppShell>
  );
}
```

## tattva/pages/ThreadPage.tsx

```tsx
import { useState } from "react";
import { Button } from "../atoms/Button";
import { AppShell } from "../templates/AppShell";
import { ThreadTemplate } from "../templates/ThreadTemplate";
import { AIDisclosure } from "../atoms/AIDisclosure";
import { TypingIndicator } from "../atoms/TypingIndicator";
import { Message } from "../molecules/Message";
import { MessageActions } from "../molecules/MessageActions";
import { ThinkingBlock } from "../molecules/ThinkingBlock";
import { ToolCallCard } from "../molecules/ToolCallCard";
import { FeedbackBar } from "../molecules/FeedbackBar";
import { ConfidenceIndicator } from "../molecules/ConfidenceIndicator";
import { SuggestionChips } from "../molecules/SuggestionChips";
import { Composer } from "../organisms/Composer";
import { Markdown } from "../organisms/Markdown";
import { MessageList } from "../organisms/MessageList";
import { SourceList } from "../organisms/SourceList";
import { useStreamingText } from "../hooks/useStreamingText";
import { Rail } from "./Rail";

const REPLY = `Sales grew **12%** over the last quarter, mostly from big business customers.[1] About 2.1% of customers left, the same as before.[2]

| Customers | Growth |
|---|---|
| Big business | +18% |
| Individuals | +4% |

Two things to watch next quarter:
- Many big customers renew in November
- Fewer individuals signed up in the last two weeks`;

/** A full research answer: reasoning, tool use, cited prose, sources, confidence and feedback. */
export function ThreadPage() {
  const { text, streaming, start, stop } = useStreamingText(REPLY, { speed: 22 });
  const [asked, setAsked] = useState<string[]>([]);
  return (
    <AppShell rail={<Rail />} className="h-180 max-h-dvh">
      <ThreadTemplate header="Why sales grew last quarter" actions={<Button size="sm" variant="secondary">Share</Button>}
        composer={<Composer onAttach={() => {}} generating={streaming} onStop={stop} onSend={(t) => { setAsked([...asked, t]); start(); }} />}
        footnote={<AIDisclosure />}>
        <MessageList>
          <Message role="user">What made sales grow last quarter?</Message>
          <Message role="assistant" streaming={streaming}
            footer={!streaming && (
              <div className="space-y-4">
                <ConfidenceIndicator level="high" />
                <SourceList sources={[
                  { id: 1, title: "Q3 2026 earnings report", url: "https://example.com/q3", domain: "investor.example.com", snippet: "Total sales rose 12% from the quarter before." },
                  { id: 2, title: "Customer numbers update", url: "https://example.com/metrics", domain: "example.com" },
                ]} />
                <MessageActions text={REPLY} onRegenerate={start} />
                <FeedbackBar onSubmit={() => {}} />
              </div>
            )}>
            <ThinkingBlock seconds={4}>Read the earnings report first, then checked the customer numbers against it.</ThinkingBlock>
            <ToolCallCard name="Searched the web" status="success" summary='Looked up "q3 sales growth"' durationMs={1400} input={{ query: "q3 sales growth" }} output="5 results" />
            {text ? <Markdown>{text}</Markdown> : <TypingIndicator />}
          </Message>
          {asked.map((q, i) => <Message key={i} role="user">{q}</Message>)}
        </MessageList>
        {!streaming && <SuggestionChips suggestions={["Break it down by region", "Compare with Q2", "Draft a summary email"]} onPick={(s) => setAsked([...asked, s])} />}
      </ThreadTemplate>
    </AppShell>
  );
}
```

## tattva/pages/WorkflowPage.tsx

```tsx
import { useState } from "react";
import { Button } from "../atoms/Button";
import { AppShell } from "../templates/AppShell";
import { ThreadTemplate } from "../templates/ThreadTemplate";
import { AIDisclosure } from "../atoms/AIDisclosure";
import { Badge } from "../atoms/Badge";
import { ContextChip } from "../molecules/ContextChip";
import { Checklist } from "../organisms/Checklist";
import { ApprovalPrompt } from "../organisms/ApprovalPrompt";
import { StepTimeline } from "../organisms/StepTimeline";
import { Composer } from "../organisms/Composer";
import { Rail } from "./Rail";

/** A running workflow is a thread: heading, checklist, and gates that need a person. */
export function WorkflowPage() {
  const [decision, setDecision] = useState("");
  return (
    <AppShell rail={<Rail activeId="3" />} className="h-180 max-h-dvh">
      <ThreadTemplate header="Request missing documents" actions={<Button size="sm" variant="secondary">Share</Button>}
        composer={<Composer onAttach={() => {}} placeholder="Ask a follow-up…" onSend={() => {}} tools={<ContextChip onClear={() => {}}>5 people</ContextChip>} />}
        footnote={<AIDisclosure />}>
        <section className="space-y-3">
          <h2 className="text-title-lg leading-10">Ask for the missing documents</h2>
          <p className="text-body leading-5 text-fg-muted">3 of 5 done. Here is where things stand:</p>
        </section>
        <Checklist doneSummary="Received from Atlas, Birch and Cedar" items={[
          { id: "1", title: <span className="font-medium">Dana Whitfield</span>, badge: <Badge tone="accent">Form A</Badge>, status: "No reply in 5 days, due in 2", attention: true },
          { id: "2", title: <span className="font-medium">Owen Park</span>, badge: <Badge tone="accent">Form A</Badge>, status: "Reminder sent yesterday" },
        ]} />
        <ApprovalPrompt risk="medium" action="Send a second reminder to Dana Whitfield"
          detail={<>An email from <b>team@example.com</b> with the subject “Documents due Friday” will be sent now.</>}
          onApprove={() => setDecision("Approved")} onDeny={() => setDecision("Denied")} />
        {decision && <p role="status" className="text-body leading-5 text-fg-muted">Decision: {decision}</p>}
        <StepTimeline steps={[
          { id: "a", title: "Find missing documents", detail: "2 people", state: "done" },
          { id: "b", title: "Send reminders", detail: "Waiting for approval", state: "active" },
          { id: "c", title: "Confirm receipt", state: "todo" },
        ]} />
      </ThreadTemplate>
    </AppShell>
  );
}
```

## tattva/templates/AppShell.tsx

```tsx
import { useContext, useState, type ReactNode } from "react";
import { Button } from "../atoms/Button";
import { Sheet } from "../organisms/Sheet";
import { EmbeddedContext } from "../lib/embedded";
import { cn } from "../lib/cn";

export interface AppShellProps {
  /** Left rail: conversation list, navigation, user card. Below the md breakpoint it moves into a side sheet opened from a Menu button. */
  rail?: ReactNode;
  children: ReactNode;
  /** Fix the shell to a height (for previews). By default it fills its container. */
  className?: string;
  /** Name of the button that opens the rail on a phone. */
  menuLabel?: string;
}

/** Two-column frame: a narrow rail and one main room. The rail never competes with the main column. */
export function AppShell({ rail, children, className, menuLabel = "Menu" }: AppShellProps) {
  const [menu, setMenu] = useState(false);
  // Inside a documentation preview the landmarks become plain containers so the host page keeps one main.
  const embedded = useContext(EmbeddedContext);
  const Rail = embedded ? "div" : "aside";
  const Main = embedded ? "div" : "main";
  return (
    <div className={cn("flex h-full min-h-0 w-full bg-bg text-fg", className)}>
      {rail && <Rail aria-label={embedded ? undefined : "Sidebar"} className="hidden w-68 shrink-0 flex-col gap-4 overflow-y-auto p-5 md:flex">{rail}</Rail>}
      <Main className="flex min-w-0 flex-1 flex-col">
        {rail && (
          <div className="flex shrink-0 items-center px-4 pt-3 md:hidden">
            <Button variant="secondary" size="sm" aria-haspopup="dialog" onClick={() => setMenu(true)}>{menuLabel}</Button>
          </div>
        )}
        {children}
      </Main>
      {rail && <Sheet open={menu} onClose={() => setMenu(false)} title={menuLabel} side="left" size="sm"><div className="flex flex-col gap-4 pb-4">{rail}</div></Sheet>}
    </div>
  );
}
```

## tattva/templates/HomeTemplate.tsx

```tsx
import type { ReactNode } from "react";
import { PageBar } from "./PageBar";

export interface HomeTemplateProps {
  /** The greeting. Use HeroHeading. */
  heading: ReactNode;
  /** The tray that answers the headline (Tray with its Composer as footer). */
  children: ReactNode;
  /** Quiet line under the tray, e.g. AIDisclosure. */
  footnote?: ReactNode;
  /** Title bar text. */
  header?: ReactNode;
  actions?: ReactNode;
}

/** Empty-thread landing: one greeting, one tray, vertically centered. Nothing else competes. */
export function HomeTemplate({ heading, children, footnote, header = "New chat", actions }: HomeTemplateProps) {
  return (
    <>
      <PageBar title={header} actions={actions} />
      <div className="flex min-h-0 flex-1 flex-col items-center justify-start overflow-y-auto px-4 py-8 md:justify-center md:py-10">
        <div className="w-full max-w-3xl space-y-8">
          {heading}
          {children}
          {footnote}
        </div>
      </div>
    </>
  );
}
```

## tattva/templates/PageBar.tsx

```tsx
import type { ReactNode } from "react";

export interface PageBarProps {
  /** Page title, one line. */
  title?: ReactNode;
  /** Quiet controls on the right, such as Share. */
  actions?: ReactNode;
}

/** The top bar every template shares, so pages differ in their body and never in their frame. */
export function PageBar({ title, actions }: PageBarProps) {
  return (
    <header className="flex h-14 shrink-0 items-center justify-between gap-3 border-b border-line px-4 sm:px-6">
      {/* A bar with no title has no heading at all, rather than an empty one. */}
      {title ? <h1 className="font-sans! truncate text-body leading-5 font-medium text-fg">{title}</h1> : <div />}
      {actions && <div className="flex shrink-0 items-center gap-2">{actions}</div>}
    </header>
  );
}
```

## tattva/templates/SplitCanvasTemplate.tsx

```tsx
import type { ReactNode } from "react";
import { PageBar } from "./PageBar";

export interface SplitCanvasTemplateProps {
  /** Left: the conversation or controls. */
  primary: ReactNode;
  /** Right: the artifact that outlives a single message (CanvasPanel). Stacks under primary on narrow screens. */
  canvas: ReactNode;
  /** Accessible names for the two regions. */
  primaryLabel?: string;
  canvasLabel?: string;
  /** Title bar text and controls, shared with the other templates. */
  header?: ReactNode;
  actions?: ReactNode;
}

export function SplitCanvasTemplate({ primary, canvas, primaryLabel = "Conversation", canvasLabel = "Canvas", header, actions }: SplitCanvasTemplateProps) {
  return (
    <>
    <PageBar title={header} actions={actions} />
    <div className="grid min-h-0 flex-1 grid-cols-1 gap-6 overflow-y-auto px-4 py-6 sm:px-6 lg:grid-cols-[minmax(0,5fr)_minmax(0,6fr)] lg:overflow-hidden">
      <section aria-label={primaryLabel} className="min-h-0 space-y-6 overflow-y-auto">{primary}</section>
      <section aria-label={canvasLabel} className="min-h-90 lg:min-h-0">{canvas}</section>
    </div>
    </>
  );
}
```

## tattva/templates/ThreadTemplate.tsx

```tsx
import type { ReactNode } from "react";
import { PageBar } from "./PageBar";

export interface ThreadTemplateProps {
  /** Optional title row above the thread. */
  header?: ReactNode;
  /** Controls on the right of the title bar. */
  actions?: ReactNode;
  /** The conversation or workflow body. Scrolls. */
  children: ReactNode;
  /** The composer. Stays pinned to the bottom. */
  composer: ReactNode;
  footnote?: ReactNode;
}

/** Scrolling thread with a pinned composer. Thread and composer share one reading width. */
export function ThreadTemplate({ header, actions, children, composer, footnote }: ThreadTemplateProps) {
  return (
    <>
      <PageBar title={header} actions={actions} />
      <div className="min-h-0 flex-1 overflow-y-auto px-4 py-6 sm:px-6">
        <div className="mx-auto w-full max-w-3xl space-y-8">{children}</div>
      </div>
      <div className="shrink-0 px-4 pb-[max(1rem,var(--safe-bottom))] sm:px-6">
        <div className="mx-auto w-full max-w-3xl space-y-2">{composer}{footnote}</div>
      </div>
    </>
  );
}
```
