Studio
ProjectsProject preparation

Projects

Operate Scott’s portfolio cockpit while preserving its project intelligence, command layer, local privacy boundaries, and Tempo presentation system.

adopted
Design Engineering Maestro

Review the bounded implementation plan

The audit exists, but no project-bound migration and agent plan has been compiled.

blocked
Project application

See the change before the repository does.

The bundle is concrete. The application boundary stays honest about generated files, collisions, product edits, evidence, validation, and rollback.

held
Bundle11 hashed files
KitTempo v2
Source lockcec18dd8c3bf
Generated facade

Private bundle contents

held
src/ui-system/tokens.cssProject-owned semantic aliases for the exact KitVersion.
210 Beec2cc2683
src/ui-system/contracts.tsStable project contract IDs mapped to kit implementation names.
485 B66053710ed
src/ui-system/components.tsxDeclarative component binding boundary without product-screen imports.
1028 Bd0f92990f3
src/ui-system/screen-recipes.tsComplete-composition ownership, screen-recipe, and legacy-style retirement contract.
1068 B55752541da
src/ui-system/kit-compatibility.tsMechanical contract and workflow completeness inspection.
1731 Bf8c76e1010
src/ui-system/studio-adoption.jsonVersion, provenance, component, pattern, and source lock manifest.
2501 B0dd37854e7
src/ui-system/kits/tempo/tempo-components.tsxMaintained Tempo implementations for the v4 contracts and complete inventory-v2 Actions family.
22805 Bf2c408cd0a
src/ui-system/kits/tempo/tempo.cssScoped Tempo foundations, component states, responsive behavior, and reduced-motion rules.
25973 Ba7eedf673e
src/ui-system/kits/tempo/tempo-recipes.tsMaintained Tempo dashboard, library, document, agent, search, grouped-list, and sheet compositions.
3039 B009090c30b
src/ui-system/kits/tempo/index.tsStable Tempo package export surface for project-owned adapters.
69 Bb2f4c92d58
src/ui-system/kits/tempo/README.mdProject-local Tempo integration boundary and validation checklist.
896 Bbb0f17e5ac
Repository gate

Held before repository execution

3/10 passed
Complete composition selectedcomplete-composition
passed
Source snapshot locked68 files · cec18dd8c3bf
passed
Migration plan reviewableneeds-review
needs review
Component contracts readyneeds-review
needs review
Workflow patterns readyneeds-review
needs review
Release candidate eligibleNo candidate linked to this KitVersion
needs review
Canonical kit human visual proofproductized-needs-human-proof
needs review
Project validation observednpm run typecheck · npm run build
passed
Generated file collisions3 existing files would be replaced
needs review
Product-screen edits specified12 evidence-backed edits still require an implementation agent
needs review
Dry application

16 source-locked changes

1 create · 3 collision · 12 manual
manual editsrc/ui-system/tokens.cssmedium
The generated token set omits semantic roles already owned by the project. Review or merge the token migration explicitly instead of downgrading the facade.

Review the package or evidence upgrade separately before replacing this protected project-owned file.

--- a/src/ui-system/tokens.css
+++ b/src/ui-system/tokens.css
@@ -1,27 +1,7 @@
-/* Tempo v4 · generated from Studio's locked semantic foundation roles. */
-:root {
-  --studio-color-action-primary: #a99af0;
-  --studio-color-surface-canvas: #f2f1ea;
-  --studio-color-surface-card: #fffefb;
-  --studio-color-text-primary: #1a1913;
-  --studio-color-text-muted: #6f6d63;
-  --studio-color-border-subtle: rgb(26 25 19 / 9%);
-  --studio-type-family-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
-  --studio-type-family-display: "Avenir Next Condensed", "Arial Narrow", "Helvetica Neue", Arial, sans-serif;
-  --studio-type-size-body: 14px;
-  --studio-type-size-heading: 42px;
-  --studio-type-weight-regular: 400;
-  --studio-type-weight-medium: 600;
-  --studio-type-line-height-body: 1.5;
-  --studio-space-base: 12px;
-  --studio-space-section: 32px;
-  --studio-shape-control-radius: 999px;
-  --studio-shape-card-radius: 22px;
-  --studio-size-control-height: 44px;
-  --studio-density-interface: spacious;
-  --studio-material-surface: editorial;
-  --studio-motion-duration-fast: 120ms;
-  --studio-motion-duration-standard: 200ms;
-  --studio-motion-easing-standard: cubic-bezier(0.23, 1, 0.32, 1);
-  --studio-motion-reduced: respect;
-}
+/* Tempo v2 · generated by Studio */
+:root {
+  --studio-color-action-primary: #a99af0;
+  --studio-density-interface: spacious;
+  --studio-material-surface: editorial;
+  --studio-shape-control-radius: 999px;
+}
replace previewsrc/ui-system/contracts.tsmedium
The generated facade collides with a preserved project file; replacement is previewed when readable but cannot be applied automatically.

Run the observed project validation commands before continuing to product-screen edits.

--- a/src/ui-system/contracts.ts
+++ b/src/ui-system/contracts.ts
@@ -1,12 +1,13 @@
-export const studioContracts = {
-  "action.button": "KitButton",
-  "field.text": "KitField",
-  "surface.card": "KitCard",
-  "navigation.tabs": "KitSegmented",
-  "feedback.status": "KitPill",
-  "overlay.dialog": "KitAlert",
-  "data.list": "KitList",
-  "control.switch": "KitToggle"
-} as const;
-
-export type StudioContractId = keyof typeof studioContracts;
+/** Stable project contract surface for Tempo v2. */
+export const studioContracts = {
+  "action.button": "KitButton",
+  "control.switch": "KitToggle",
+  "data.list": "KitList",
+  "feedback.status": "KitPill",
+  "field.text": "KitField",
+  "navigation.tabs": "KitSegmented",
+  "overlay.dialog": "KitAlert",
+  "surface.card": "KitCard"
+} as const;
+export type StudioContractId = keyof typeof studioContracts;
+export type StudioComponentName = (typeof studioContracts)[StudioContractId];
replace previewsrc/ui-system/components.tsxmedium
The generated facade collides with a preserved project file; replacement is previewed when readable but cannot be applied automatically.

Run the observed project validation commands before continuing to product-screen edits.

--- a/src/ui-system/components.tsx
+++ b/src/ui-system/components.tsx
@@ -1,21 +1,10 @@
-export {
-  TempoAgentThinking as AgentThinking,
-  TempoButton as Button,
-  TempoCard as Card,
-  TempoComposer as Composer,
-  TempoDialog as Dialog,
-  TempoField as Field,
-  TempoList as List,
-  TempoListItem as ListItem,
-  TempoRoot as Root,
-  TempoStatus as Status,
-  TempoSwitch as Switch,
-  TempoTabs as Tabs,
-} from "./kits/tempo";
-
-export type {
-  TempoButtonProps as ButtonProps,
-  TempoTabItem as TabItem,
-  TempoTheme as Theme,
-  TempoTone as Tone,
-} from "./kits/tempo";
+import { studioContracts, type StudioContractId } from "./contracts";
+export { KitRoot, KitButton, KitField, KitCard, KitSegmented, KitPill, KitAlert, KitList, KitToggle, KitComposer, KitAgentThinking, KitDeviceFrame } from "./kits/tempo/tempo-components";
+export { TempoAgentThinking as AgentThinking, TempoButton as Button, TempoCard as Card, TempoComposer as Composer, TempoDialog as Dialog, TempoField as Field, TempoList as List, TempoListItem as ListItem, TempoRoot as Root, TempoStatus as Status, TempoSwitch as Switch, TempoTabs as Tabs } from "./kits/tempo/tempo-components";
+export type { TempoButtonProps as ButtonProps, TempoTabItem as TabItem, TempoTheme as Theme, TempoTone as Tone } from "./kits/tempo/tempo-components";
+
+/** Declarative bindings only. Resolve these names through project-owned adapters. */
+export const studioComponentBindings: Record<StudioContractId, string> = studioContracts;
+export function studioComponentFor(contractId: StudioContractId) {
+  return studioComponentBindings[contractId];
+}
createsrc/ui-system/screen-recipes.tslow
Complete-composition ownership, screen-recipe, and legacy-style retirement contract.

Run the observed project validation commands before continuing to product-screen edits.

--- /dev/null
+++ b/src/ui-system/screen-recipes.ts
@@ -0,0 +1,41 @@
+/** Complete-composition contract for Tempo v2. */
+export const studioAdoptionMode = "complete-composition" as const;
+export const studioScreenRecipes = [
+  "workflow.settings",
+  "workflow.resource-browser",
+  "workflow.review-approve",
+  "workflow.command-confirmation",
+  "workflow.empty-to-active",
+  "workflow.form-recovery"
+] as const;
+export const studioPresentationOwnership = {
+  "kitOwns": [
+    "tokens",
+    "typography",
+    "components",
+    "shell",
+    "screen-recipes",
+    "responsive-behavior",
+    "motion",
+    "interaction-density"
+  ],
+  "projectPreserves": [
+    "data",
+    "domain-state",
+    "api-contracts",
+    "command-actions",
+    "persistence",
+    "accessibility-semantics"
+  ],
+  "legacyStylesheetAuthority": false
+} as const;
+export const studioCompositionChecks = [
+  "legacy-styles-removed-from-runtime",
+  "kit-shell-applied",
+  "all-product-views-mapped",
+  "mobile-navigation-applied",
+  "empty-loading-error-states-mapped",
+  "responsive-viewports-proven",
+  "keyboard-and-focus-proven",
+  "human-visual-approval"
+] as const;
replace previewsrc/ui-system/kit-compatibility.tsmedium
The generated facade collides with a preserved project file; replacement is previewed when readable but cannot be applied automatically.

Run the observed project validation commands before continuing to product-screen edits.

--- a/src/ui-system/kit-compatibility.ts
+++ b/src/ui-system/kit-compatibility.ts
@@ -1,16 +1,42 @@
-import { studioContracts, type StudioContractId } from "./contracts";
-
-export const requiredStudioContracts = Object.keys(studioContracts) as StudioContractId[];
-export const requiredStudioPatterns = ["workflow.dashboard", "workflow.resource-browser", "workflow.command-center"] as const;
-export const studioVisualProof = {
-  status: "approved",
-  requiredViewports: ["390x844", "430x932", "768x1024", "1440x900"],
-  requiredChecks: ["canonical-package-hash", "typography", "spacing-and-alignment", "color-and-material", "radius-and-control-geometry", "bounded-list-behavior", "keyboard-and-focus", "reduced-motion", "reduced-transparency", "increased-contrast"],
-} as const;
-
-export function inspectStudioCompatibility() {
-  const missingContracts = requiredStudioContracts.filter((id) => !studioContracts[id]);
-  const structurallyCompatible = missingContracts.length === 0;
-  const visuallyCompatible = (studioVisualProof.status as string) === "approved";
-  return { compatible: structurallyCompatible && visuallyCompatible, structurallyCompatible, visuallyCompatible, missingContracts, requiredPatterns: requiredStudioPatterns, visualProof: studioVisualProof };
-}
+import { studioContracts, type StudioContractId } from "./contracts";
+import { studioAdoptionMode, studioCompositionChecks, studioPresentationOwnership, studioScreenRecipes } from "./screen-recipes";
+
+export const requiredStudioContracts = [
+  "action.button",
+  "control.switch",
+  "data.list",
+  "feedback.status",
+  "field.text",
+  "navigation.tabs",
+  "overlay.dialog",
+  "surface.card"
+] as const satisfies readonly StudioContractId[];
+export const requiredStudioPatterns = studioScreenRecipes;
+export const studioVisualProof = {
+  "status": "required",
+  "requiredViewports": [
+    "390x844",
+    "430x932",
+    "768x1024",
+    "1440x900"
+  ],
+  "requiredChecks": [
+    "canonical-package-hash",
+    "typography",
+    "spacing-and-alignment",
+    "color-and-material",
+    "radius-and-control-geometry",
+    "bounded-list-behavior",
+    "keyboard-and-focus",
+    "reduced-motion",
+    "reduced-transparency",
+    "increased-contrast"
+  ]
+} as const;
+export function inspectStudioCompatibility() {
+  const missingContracts = requiredStudioContracts.filter((id) => !studioContracts[id]);
+  const structurallyCompatible = missingContracts.length === 0;
+  const visuallyCompatible = (studioVisualProof.status as string) === 'approved';
+  const completeComposition = studioAdoptionMode === 'complete-composition' && studioPresentationOwnership.legacyStylesheetAuthority === false;
+  return { compatible: structurallyCompatible && visuallyCompatible && completeComposition, structurallyCompatible, visuallyCompatible, completeComposition, missingContracts, requiredPatterns: requiredStudioPatterns, compositionChecks: studioCompositionChecks, presentationOwnership: studioPresentationOwnership, visualProof: studioVisualProof };
+}
manual editsrc/ui-system/studio-adoption.jsonmedium
The project's durable adoption and proof record is protected from generated replacement. Update it only with matching evidence.

Review the package or evidence upgrade separately before replacing this protected project-owned file.

--- a/src/ui-system/studio-adoption.json
+++ b/src/ui-system/studio-adoption.json
@@ -1,28 +1,88 @@
-{
-  "schemaVersion": 1,
-  "projectId": "projects",
-  "kit": {
-    "id": "kit_tempo",
-    "name": "Tempo",
-    "version": 4
-  },
-  "implementation": {
-    "status": "complete-composition-approved",
-    "mode": "complete-composition",
-    "packageId": "tempo",
-    "packageVersion": "1.0.0-rc.2",
-    "sourceSha256": "7b256379176e2be38d42e9a4a8019b2a85cfdfeb890254deab224368dbc6515d"
-  },
-  "compatibility": {
-    "status": "approved",
-    "structuralCompatibilityIsNotVisualFidelity": true,
-    "humanApprovalRequired": true,
-    "humanApprovalSatisfied": true
-  },
-  "presentationOwnership": {
-    "kitOwns": ["tokens", "components", "shell", "screen-recipes", "responsive-behavior", "motion", "interaction-density"],
-    "projectPreserves": ["data", "domain-state", "api-contracts", "command-actions", "persistence"],
-    "legacyStylesheetAuthority": false
-  },
-  "boundary": "No readiness or compatibility claim is valid until canonical-source, visual, responsive, keyboard, and accessibility proof are approved."
-}
+{
+  "schemaVersion": 1,
+  "adoptionMode": "complete-composition",
+  "projectId": "prj_c666a585-abca-476d-9ac3-79bcddf7aa4f",
+  "kit": {
+    "id": "kit_tempo",
+    "name": "Tempo",
+    "versionId": "ver_ffa97a66-7d8d-417a-8d23-fd4e0492a0b3",
+    "version": 2
+  },
+  "migrationPlanId": "mig_ee9d759f-3c80-4d62-a4ed-1ff93e475385",
+  "semanticRoles": [
+    "color.action.primary",
+    "shape.control.radius",
+    "density.interface",
+    "material.surface"
+  ],
+  "componentContracts": {
+    "action.button": "KitButton",
+    "control.switch": "KitToggle",
+    "data.list": "KitList",
+    "feedback.status": "KitPill",
+    "field.text": "KitField",
+    "navigation.tabs": "KitSegmented",
+    "overlay.dialog": "KitAlert",
+    "surface.card": "KitCard"
+  },
+  "patternIds": [
+    "workflow.settings",
+    "workflow.resource-browser",
+    "workflow.review-approve",
+    "workflow.command-confirmation",
+    "workflow.empty-to-active",
+    "workflow.form-recovery"
+  ],
+  "extensions": [
+    "KitComposer",
+    "KitAgentThinking",
+    "KitDeviceFrame"
+  ],
+  "capabilities": {
+    "textEffects": []
+  },
+  "implementation": {
+    "status": "productized-needs-human-proof",
+    "mode": "complete-composition",
+    "packageId": "tempo",
+    "packageVersion": "1.0.0-rc.4",
+    "sourceSha256": "7b256379176e2be38d42e9a4a8019b2a85cfdfeb890254deab224368dbc6515d",
+    "componentImport": "./kits/tempo/tempo-components",
+    "stylesheetImport": "./kits/tempo/tempo.css"
+  },
+  "compatibility": {
+    "status": "needs-visual-proof",
+    "structuralCompatibilityIsNotVisualFidelity": true,
+    "completeCompositionRequired": true,
+    "requiredViewports": [
+      "390x844",
+      "430x932",
+      "768x1024",
+      "1440x900"
+    ],
+    "humanApprovalRequired": true
+  },
+  "presentationOwnership": {
+    "kitOwns": [
+      "tokens",
+      "typography",
+      "components",
+      "shell",
+      "screen-recipes",
+      "responsive-behavior",
+      "motion",
+      "interaction-density"
+    ],
+    "projectPreserves": [
+      "data",
+      "domain-state",
+      "api-contracts",
+      "command-actions",
+      "persistence",
+      "accessibility-semantics"
+    ],
+    "legacyStylesheetAuthority": false
+  },
+  "sourceSha256": "cec18dd8c3bf8d51dfe3b43532bc80841f5368f8df00c069633dea306f343884",
+  "boundary": "Project behavior remains behind the facade; the selected kit replaces complete presentation composition and legacy styles lose runtime authority. Repository application remains separately approval-gated."
+}
manual editsrc/ui-system/kits/tempo/tempo-components.tsxmedium
An existing project-owned kit package is protected from incidental adoption replacement. Upgrade it through a separate source-locked package migration.

Review the package or evidence upgrade separately before replacing this protected project-owned file.

--- a/src/ui-system/kits/tempo/tempo-components.tsx
+++ b/src/ui-system/kits/tempo/tempo-components.tsx
@@ -1,488 +1,609 @@
-"use client";
-
-import {
-  useEffect,
-  useId,
-  useRef,
-  type ButtonHTMLAttributes,
-  type CSSProperties,
-  type HTMLAttributes,
-  type InputHTMLAttributes,
-  type KeyboardEvent,
-  type ReactNode,
-} from "react";
-
-function cx(...values: Array<string | false | null | undefined>) {
-  return values.filter(Boolean).join(" ");
-}
-
-export type TempoTheme = "light" | "dark";
-export type TempoTone = "neutral" | "accent" | "success" | "warning" | "danger";
-
-export function TempoRoot({
-  theme = "light",
-  accent,
-  className,
-  children,
-  ...props
-}: HTMLAttributes<HTMLDivElement> & { theme?: TempoTheme; accent?: string }) {
-  return (
-    <div
-      {...props}
-      className={cx("tempo", className)}
-      data-tempo-theme={theme}
-      style={{ ...props.style, ...(accent ? { "--tempo-color-action-primary": accent } as CSSProperties : {}) }}
-    >
-      {children}
-    </div>
-  );
-}
-
-export type TempoButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
-  tone?: "primary" | "tonal" | "outline" | "plain" | "danger";
-  loading?: boolean;
-  loadingLabel?: string;
-  leading?: ReactNode;
-  trailing?: ReactNode;
-};
-
-export function TempoButton({
-  tone = "primary",
-  loading = false,
-  loadingLabel = "Loading",
-  leading,
-  trailing,
-  className,
-  disabled,
-  children,
-  type = "button",
-  ...props
-}: TempoButtonProps) {
-  return (
-    <button
-      {...props}
-      aria-label={loading ? loadingLabel : props["aria-label"]}
-      aria-busy={loading || undefined}
-      className={cx("tempo-button", `tempo-button--${tone}`, className)}
-      disabled={disabled || loading}
-      type={type}
-    >
-      <span className="tempo-button__content" data-loading={loading || undefined}>
-        {leading ? <span className="tempo-button__icon" aria-hidden>{leading}</span> : null}
-        <span>{children}</span>
-        {trailing ? <span className="tempo-button__icon" aria-hidden>{trailing}</span> : null}
-      </span>
-      {loading ? <span className="tempo-spinner" aria-hidden /> : null}
-    </button>
-  );
-}
-
-export type TempoFieldProps = Omit<InputHTMLAttributes<HTMLInputElement>, "size"> & {
-  label: string;
-  description?: string;
-  error?: string;
-};
-
-export function TempoField({ label, description, error, id, className, ...props }: TempoFieldProps) {
-  const generatedId = useId();
-  const inputId = id || `tempo-field-${generatedId}`;
-  const descriptionId = description ? `${inputId}-description` : undefined;
-  const errorId = error ? `${inputId}-error` : undefined;
-  return (
-    <div className={cx("tempo-field", error && "tempo-field--error", className)}>
-      <label htmlFor={inputId}>{label}</label>
-      {description ? <span className="tempo-field__description" id={descriptionId}>{description}</span> : null}
-      <input
-        {...props}
-        aria-describedby={[descriptionId, errorId].filter(Boolean).join(" ") || undefined}
-        aria-invalid={error ? true : props["aria-invalid"]}
-        id={inputId}
-      />
-      {error ? <span className="tempo-field__error" id={errorId} role="alert">{error}</span> : null}
-    </div>
-  );
-}
-
-export function TempoCard({
-  heading,
-  eyebrow,
-  footer,
-  tone = "default",
-  selected = false,
-  disabled = false,
-  className,
-  children,
-  ...props
-}: HTMLAttributes<HTMLDivElement> & {
-  heading?: ReactNode;
-  eyebrow?: ReactNode;
-  footer?: ReactNode;
-  tone?: "default" | "soft" | "accent";
-  selected?: boolean;
-  disabled?: boolean;
-}) {
-  return (
-    <div
-      {...props}
-      aria-disabled={disabled || undefined}
-      className={cx("tempo-card", `tempo-card--${tone}`, selected && "is-selected", disabled && "is-disabled", className)}
-    >
-      {eyebrow || heading ? <header className="tempo-card__header">{eyebrow ? <span>{eyebrow}</span> : null}{heading ? <h3>{heading}</h3> : null}</header> : null}
-      <div className="tempo-card__content">{children}</div>
-      {footer ? <footer className="tempo-card__footer">{footer}</footer> : null}
-    </div>
-  );
-}
-
-export type TempoTabItem<T extends string> = { value: T; label: string; disabled?: boolean; panel?: ReactNode };
-
-export function TempoTabs<T extends string>({
-  items,
-  value,
-  onValueChange,
-  label = "Views",
-  activation = "automatic",
-  className,
-}: {
-  items: Array<TempoTabItem<T>>;
-  value: T;
-  onValueChange: (value: T) => void;
-  label?: string;
-  activation?: "automatic" | "manual";
-  className?: string;
-}) {
-  const baseId = useId();
-  const buttonRefs = useRef<Array<HTMLButtonElement | null>>([]);
-  const selected = items.find((item) => item.value === value) || items[0];
-
-  function moveFocus(event: KeyboardEvent<HTMLButtonElement>, index: number) {
-    const enabled = items.map((item, itemIndex) => ({ item, itemIndex })).filter(({ item }) => !item.disabled);
-    if (!enabled.length) return;
-    const current = enabled.findIndex(({ itemIndex }) => itemIndex === index);
-    let target = current;
-    if (event.key === "ArrowRight" || event.key === "ArrowDown") target = (current + 1) % enabled.length;
-    else if (event.key === "ArrowLeft" || event.key === "ArrowUp") target = (current - 1 + enabled.length) % enabled.length;
-    else if (event.key === "Home") target = 0;
-    else if (event.key === "End") target = enabled.length - 1;
-    else if ((event.key === "Enter" || event.key === " ") && activation === "manual") {
-      event.preventDefault();
-      onValueChange(items[index].value);
-      return;
-    } else return;
-    event.preventDefault();
-    const next = enabled[target];
-    buttonRefs.current[next.itemIndex]?.focus();
-    if (activation === "automatic") onValueChange(next.item.value);
-  }
-
-  return (
-    <div className={cx("tempo-tabs", className)}>
-      <div className="tempo-tabs__list" role="tablist" aria-label={label} aria-orientation="horizontal">
-        {items.map((item, index) => (
-          <button
-            aria-controls={`${baseId}-panel-${index}`}
-            aria-selected={item.value === value}
-            className={item.value === value ? "is-selected" : undefined}
-            disabled={item.disabled}
-            id={`${baseId}-tab-${index}`}
-            key={item.value}
-            onClick={() => onValueChange(item.value)}
-            onKeyDown={(event) => moveFocus(event, index)}
-            ref={(node) => { buttonRefs.current[index] = node; }}
-            role="tab"
-            tabIndex={item.value === value ? 0 : -1}
-            type="button"
-          >
-            {item.label}
-          </button>
-        ))}
-      </div>
-      {selected?.panel !== undefined ? (
-        <div
-          aria-labelledby={`${baseId}-tab-${items.indexOf(selected)}`}
-          className="tempo-tabs__panel"
-          id={`${baseId}-panel-${items.indexOf(selected)}`}
-          role="tabpanel"
-          tabIndex={0}
-        >
-          {selected.panel}
-        </div>
-      ) : null}
-    </div>
-  );
-}
-
-export function TempoStatus({
-  label,
-  tone = "neutral",
-  live = false,
-  icon,
-  className,
-}: {
-  label: string;
-  tone?: TempoTone | "loading";
-  live?: boolean;
-  icon?: ReactNode;
-  className?: string;
-}) {
-  return (
-    <span
-      aria-live={live ? "polite" : undefined}
-      className={cx("tempo-status", `tempo-status--${tone}`, className)}
-      role={live ? "status" : undefined}
-    >
-      {tone === "loading" ? <span className="tempo-spinner" aria-hidden /> : icon ? <span aria-hidden>{icon}</span> : <span className="tempo-status__dot" aria-hidden />}
-      <span>{label}</span>
-    </span>
-  );
-}
-
-const focusableSelector = "button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex='-1'])";
-
-export function TempoDialog({
-  open,
-  onOpenChange,
-  title,
-  description,
-  children,
-  actions,
-  closeLabel = "Close dialog",
-  busy = false,
-  destructive = false,
-  contained = false,
-}: {
-  open: boolean;
-  onOpenChange: (open: boolean) => void;
-  title: string;
-  description?: string;
-  children?: ReactNode;
-  actions?: ReactNode;
-  closeLabel?: string;
-  busy?: boolean;
-  destructive?: boolean;
-  contained?: boolean;
-}) {
-  const titleId = useId();
-  const descriptionId = useId();
-  const dialogRef = useRef<HTMLDivElement>(null);
-  const returnFocusRef = useRef<HTMLElement | null>(null);
-  const onOpenChangeRef = useRef(onOpenChange);
-
-  useEffect(() => {
-    onOpenChangeRef.current = onOpenChange;
-  }, [onOpenChange]);
-
-  useEffect(() => {
-    if (!open) return;
-    returnFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
-    const dialog = dialogRef.current;
-    const first = dialog?.querySelector<HTMLElement>(focusableSelector);
-    first?.focus();
-    const onKeyDown = (event: globalThis.KeyboardEvent) => {
-      if (event.key === "Escape" && !busy) {
-        event.preventDefault();
-        onOpenChangeRef.current(false);
-      }
-      if (event.key !== "Tab" || !dialog) return;
-      const focusable = Array.from(dialog.querySelectorAll<HTMLElement>(focusableSelector));
-      if (!focusable.length) return;
-      const firstItem = focusable[0];
-      const lastItem = focusable[focusable.length - 1];
-      if (event.shiftKey && document.activeElement === firstItem) { event.preventDefault(); lastItem.focus(); }
-      if (!event.shiftKey && document.activeElement === lastItem) { event.preventDefault(); firstItem.focus(); }
-    };
-    document.addEventListener("keydown", onKeyDown);
-    const priorOverflow = document.body.style.overflow;
-    if (!contained) document.body.style.overflow = "hidden";
-    return () => {
-      document.removeEventListener("keydown", onKeyDown);
-      if (!contained) document.body.style.overflow = priorOverflow;
-      returnFocusRef.current?.focus();
-    };
-  }, [busy, contained, open]);
-
-  if (!open) return null;
-  return (
-    <div className={cx("tempo-dialog__backdrop", contained && "tempo-dialog__backdrop--contained")} onMouseDown={(event) => { if (event.target === event.currentTarget && !busy) onOpenChange(false); }}>
-      <div
-        aria-describedby={description ? descriptionId : undefined}
-        aria-labelledby={titleId}
-        aria-modal="true"
-        className={cx("tempo-dialog", destructive && "tempo-dialog--destructive")}
-        ref={dialogRef}
-        role="dialog"
-      >
-        <button aria-label={closeLabel} className="tempo-dialog__close" disabled={busy} onClick={() => onOpenChange(false)} type="button">×</button>
-        <TempoStatus label={destructive ? "Confirmation required" : busy ? "Working" : "Decision"} tone={destructive ? "danger" : busy ? "loading" : "accent"} />
-        <h2 id={titleId}>{title}</h2>
-        {description ? <p id={descriptionId}>{description}</p> : null}
-        {children ? <div className="tempo-dialog__content">{children}</div> : null}
-        {actions ? <div className="tempo-dialog__actions">{actions}</div> : null}
-      </div>
-    </div>
-  );
-}
-
-export type TempoListState = "default" | "loading" | "empty" | "error";
-
-export function TempoList<T>({
-  items,
-  getKey,
-  renderItem,
-  state = "default",
-  emptyState = "No items yet.",
-  error = "Items could not be loaded.",
-  label = "Items",
-  className,
-}: {
-  items: T[];
-  getKey: (item: T) => string;
-  renderItem: (item: T) => ReactNode;
-  state?: TempoListState;
-  emptyState?: ReactNode;
-  error?: ReactNode;
-  label?: string;
-  className?: string;
-}) {
-  const message = state === "loading" ? "Loading…" : state === "error" ? error : state === "empty" || !items.length ? emptyState : null;
-  return (
-    <div aria-busy={state === "loading" || undefined} aria-label={label} className={cx("tempo-list", className)} role="list">
-      {message ? <div className={cx("tempo-list__message", state === "error" && "is-error")} role={state === "error" ? "alert" : "status"}>{message}</div> : null}
-      {!message ? items.map((item) => <div className="tempo-list__row" key={getKey(item)} role="listitem">{renderItem(item)}</div>) : null}
-    </div>
-  );
-}
-
-export function TempoListItem({
-  title,
-  detail,
-  leading,
-  trailing,
-  selected = false,
-}: {
-  title: ReactNode;
-  detail?: ReactNode;
-  leading?: ReactNode;
-  trailing?: ReactNode;
-  selected?: boolean;
-}) {
-  return (
-    <div className={cx("tempo-list-item", selected && "is-selected")}>
-      {leading ? <span className="tempo-list-item__leading" aria-hidden>{leading}</span> : null}
-      <span className="tempo-list-item__copy"><strong>{title}</strong>{detail ? <small>{detail}</small> : null}</span>
-      {trailing ? <span className="tempo-list-item__trailing">{trailing}</span> : null}
-    </div>
-  );
-}
-
-export function TempoSwitch({
-  label,
-  description,
-  checked,
-  onCheckedChange,
-  disabled = false,
-  className,
-}: {
-  label: string;
-  description?: string;
-  checked: boolean;
-  onCheckedChange: (checked: boolean) => void;
-  disabled?: boolean;
-  className?: string;
-}) {
-  const labelId = useId();
-  const descriptionId = useId();
-  return (
-    <div className={cx("tempo-switch", disabled && "is-disabled", className)}>
-      <span className="tempo-switch__copy"><strong id={labelId}>{label}</strong>{description ? <small id={descriptionId}>{description}</small> : null}</span>
-      <button
-        aria-checked={checked}
-        aria-describedby={description ? descriptionId : undefined}
-        aria-labelledby={labelId}
-        className="tempo-switch__control"
-        disabled={disabled}
-        onClick={() => onCheckedChange(!checked)}
-        role="switch"
-        type="button"
-      ><span /></button>
-    </div>
-  );
-}
-
-export function TempoComposer({
-  value,
-  onValueChange,
-  onSend,
-  placeholder = "Describe what you want to do…",
-  chips = [],
-  disabled = false,
-}: {
-  value: string;
-  onValueChange: (value: string) => void;
-  onSend: () => void;
-  placeholder?: string;
-  chips?: string[];
-  disabled?: boolean;
-}) {
-  const inputId = useId();
-  return (
-    <div className="tempo-composer">
-      <label className="tempo-visually-hidden" htmlFor={inputId}>Command</label>
-      <textarea id={inputId} value={value} onChange={(event) => onValueChange(event.target.value)} placeholder={placeholder} disabled={disabled} />
-      <div className="tempo-composer__footer">
-        <div className="tempo-composer__chips">{chips.map((chip) => <span key={chip}>{chip}</span>)}</div>
-        <TempoButton aria-label="Send command" disabled={disabled || !value.trim()} onClick={onSend}>Send</TempoButton>
-      </div>
-    </div>
-  );
-}
-
-export function TempoAgentThinking({ label = "Thinking", text, detail }: { label?: string; text: string; detail?: string }) {
-  return <div className="tempo-thinking" role="status"><span>{label}</span><strong>{text}</strong>{detail ? <p>{detail}</p> : null}</div>;
-}
-
-export function TempoDeviceFrame({
-  title,
-  navigation = [],
-  activeId,
-  children,
-}: {
-  title: string;
-  navigation?: Array<{ id: string; label: string; icon?: ReactNode }>;
-  activeId?: string;
-  children: ReactNode;
-}) {
-  return (
-    <div className="tempo-device">
-      <header><span aria-hidden><i /><i /><i /></span><strong>{title}</strong></header>
-      <div className="tempo-device__body">
-        {navigation.length ? <nav aria-label={`${title} navigation`}>{navigation.map((item) => <span className={item.id === activeId ? "is-active" : undefined} key={item.id}>{item.icon}{item.label}</span>)}</nav> : null}
-        <main>{children}</main>
-      </div>
-    </div>
-  );
-}
-
-// Compatibility names implement the stable contract props stored in Tempo v4.
-export const KitRoot = TempoRoot;
-export function KitButton({ label, icon, tone = "primary", ...props }: Omit<TempoButtonProps, "children" | "leading"> & { label: ReactNode; icon?: ReactNode }) {
-  return <TempoButton {...props} leading={icon} tone={tone}>{label}</TempoButton>;
-}
-export const KitField = TempoField;
-export function KitCard({ content, heading, ...props }: Omit<Parameters<typeof TempoCard>[0], "children"> & { content: ReactNode; heading?: ReactNode }) {
-  return <TempoCard {...props} heading={heading}>{content}</TempoCard>;
-}
-export function KitSegmented<T extends string>({ items, onChange, ...props }: Omit<Parameters<typeof TempoTabs<T>>[0], "items" | "onValueChange"> & { items: Array<TempoTabItem<T>>; onChange: (value: T) => void }) {
-  return <TempoTabs {...props} items={items} onValueChange={onChange} />;
-}
-export function KitPill({ label, ...props }: Omit<Parameters<typeof TempoStatus>[0], "label"> & { label: string }) {
-  return <TempoStatus {...props} label={label} />;
-}
-export const KitAlert = TempoDialog;
-export function KitList<T>({ selection, actions, renderItem, ...props }: Parameters<typeof TempoList<T>>[0] & { selection?: Set<string>; actions?: (item: T) => ReactNode }) {
-  return <TempoList {...props} renderItem={(item) => <div className={selection?.has(props.getKey(item)) ? "is-selected" : undefined}>{renderItem(item)}{actions ? <span className="tempo-list__actions">{actions(item)}</span> : null}</div>} />;
-}
-export const KitToggle = TempoSwitch;
-export const KitComposer = TempoComposer;
-export const KitAgentThinking = TempoAgentThinking;
-export const KitDeviceFrame = TempoDeviceFrame;
+"use client";
+
+import {
+  useEffect,
+  useId,
+  useRef,
+  type AnchorHTMLAttributes,
+  type ButtonHTMLAttributes,
+  type CSSProperties,
+  type HTMLAttributes,
+  type InputHTMLAttributes,
+  type KeyboardEvent,
+  type ReactNode,
+} from "react";
+
+function cx(...values: Array<string | false | null | undefined>) {
+  return values.filter(Boolean).join(" ");
+}
+
+export type TempoTheme = "light" | "dark";
+export type TempoTone = "neutral" | "accent" | "success" | "warning" | "danger";
+
+export function TempoRoot({
+  theme = "light",
+  accent,
+  className,
+  children,
+  ...props
+}: HTMLAttributes<HTMLDivElement> & { theme?: TempoTheme; accent?: string }) {
+  return (
+    <div
+      {...props}
+      className={cx("tempo", className)}
+      data-tempo-theme={theme}
+      style={{ ...props.style, ...(accent ? { "--tempo-color-action-primary": accent } as CSSProperties : {}) }}
+    >
+      {children}
+    </div>
+  );
+}
+
+export type TempoButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
+  tone?: "primary" | "tonal" | "outline" | "plain" | "danger";
+  loading?: boolean;
+  loadingLabel?: string;
+  leading?: ReactNode;
+  trailing?: ReactNode;
+};
+
+export function TempoButton({
+  tone = "primary",
+  loading = false,
+  loadingLabel = "Loading",
+  leading,
+  trailing,
+  className,
+  disabled,
+  children,
+  type = "button",
+  ...props
+}: TempoButtonProps) {
+  return (
+    <button
+      {...props}
+      aria-label={loading ? loadingLabel : props["aria-label"]}
+      aria-busy={loading || undefined}
+      className={cx("tempo-button", `tempo-button--${tone}`, className)}
+      disabled={disabled || loading}
+      type={type}
+    >
+      <span className="tempo-button__content" data-loading={loading || undefined}>
+        {leading ? <span className="tempo-button__icon" aria-hidden>{leading}</span> : null}
+        <span>{children}</span>
+        {trailing ? <span className="tempo-button__icon" aria-hidden>{trailing}</span> : null}
+      </span>
+      {loading ? <span className="tempo-spinner" aria-hidden /> : null}
+    </button>
+  );
+}
+
+export type TempoIconButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "aria-label"> & {
+  label: string;
+  tone?: "neutral" | "accent" | "danger";
+  size?: "small" | "medium" | "large";
+};
+
+export function TempoIconButton({
+  label,
+  tone = "neutral",
+  size = "medium",
+  className,
+  children,
+  type = "button",
+  ...props
+}: TempoIconButtonProps) {
+  return <button {...props} aria-label={label} className={cx("tempo-icon-button", `tempo-icon-button--${tone}`, `tempo-icon-button--${size}`, className)} type={type}><span aria-hidden>{children}</span></button>;
+}
+
+export type TempoLinkButtonProps = AnchorHTMLAttributes<HTMLAnchorElement> & {
+  tone?: "primary" | "tonal" | "plain";
+  leading?: ReactNode;
+  trailing?: ReactNode;
+  disabled?: boolean;
+};
+
+export function TempoLinkButton({ tone = "plain", leading, trailing, disabled = false, className, children, onClick, tabIndex, ...props }: TempoLinkButtonProps) {
+  return <a {...props} aria-disabled={disabled || undefined} className={cx("tempo-link-button", `tempo-link-button--${tone}`, disabled && "is-disabled", className)} onClick={(event) => { if (disabled) { event.preventDefault(); return; } onClick?.(event); }} tabIndex={disabled ? -1 : tabIndex}>{leading ? <span aria-hidden>{leading}</span> : null}<span>{children}</span>{trailing ? <span aria-hidden>{trailing}</span> : null}</a>;
+}
+
+export function TempoFab({ label, className, children, type = "button", ...props }: Omit<TempoIconButtonProps, "tone" | "size">) {
+  return <button {...props} aria-label={label} className={cx("tempo-fab", className)} type={type}><span aria-hidden>{children}</span></button>;
+}
+
+export function TempoSplitButton({
+  label,
+  menuLabel = "More options",
+  onPrimaryAction,
+  onMenuAction,
+  leading,
+  disabled = false,
+  className,
+}: {
+  label: ReactNode;
+  menuLabel?: string;
+  onPrimaryAction: () => void;
+  onMenuAction: () => void;
+  leading?: ReactNode;
+  disabled?: boolean;
+  className?: string;
+}) {
+  return <div className={cx("tempo-split-button", className)} role="group" aria-label={typeof label === "string" ? label : "Split action"}>
+    <button disabled={disabled} onClick={onPrimaryAction} type="button">{leading ? <span aria-hidden>{leading}</span> : null}<span>{label}</span></button>
+    <button aria-label={menuLabel} disabled={disabled} onClick={onMenuAction} type="button"><span aria-hidden>⌄</span></button>
+  </div>;
+}
+
+export type TempoButtonGroupItem<T extends string> = { value: T; label: string; icon?: ReactNode; disabled?: boolean };
+
+export function TempoButtonGroup<T extends string>({
+  items,
+  value,
+  onValueChange,
+  label = "Actions",
+  className,
+}: {
+  items: Array<TempoButtonGroupItem<T>>;
+  value?: T;
+  onValueChange?: (value: T) => void;
+  label?: string;
+  className?: string;
+}) {
+  return <div aria-label={label} className={cx("tempo-button-group", className)} role="group">{items.map((item) => <button aria-pressed={value === undefined ? undefined : item.value === value} className={value === item.value ? "is-active" : undefined} disabled={item.disabled} key={item.value} onClick={() => onValueChange?.(item.value)} type="button">{item.icon ? <span aria-hidden>{item.icon}</span> : null}<span>{item.label}</span></button>)}</div>;
+}
+
+export function TempoToggleButton({
+  pressed,
+  onPressedChange,
+  leading,
+  className,
+  children,
+  type = "button",
+  ...props
+}: Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onChange"> & { pressed: boolean; onPressedChange: (pressed: boolean) => void; leading?: ReactNode }) {
+  return <button {...props} aria-pressed={pressed} className={cx("tempo-toggle-button", pressed && "is-pressed", className)} onClick={(event) => { props.onClick?.(event); if (!event.defaultPrevented) onPressedChange(!pressed); }} type={type}>{leading ? <span aria-hidden>{leading}</span> : null}<span>{children}</span></button>;
+}
+
+export type TempoFeedbackValue = "helpful" | "unhelpful" | null;
+
+export function TempoFeedbackRow({
+  value,
+  onValueChange,
+  onCopy,
+  onRegenerate,
+  copied = false,
+  label = "Response actions",
+  disabled = false,
+}: {
+  value: TempoFeedbackValue;
+  onValueChange: (value: TempoFeedbackValue) => void;
+  onCopy?: () => void;
+  onRegenerate?: () => void;
+  copied?: boolean;
+  label?: string;
+  disabled?: boolean;
+}) {
+  return <div aria-label={label} className="tempo-feedback-row" role="group">
+    <TempoIconButton aria-pressed={value === "helpful"} disabled={disabled} label="Helpful response" onClick={() => onValueChange(value === "helpful" ? null : "helpful")} tone={value === "helpful" ? "accent" : "neutral"}><span className="tempo-feedback-glyph">+</span></TempoIconButton>
+    <TempoIconButton aria-pressed={value === "unhelpful"} disabled={disabled} label="Unhelpful response" onClick={() => onValueChange(value === "unhelpful" ? null : "unhelpful")} tone={value === "unhelpful" ? "danger" : "neutral"}><span className="tempo-feedback-glyph">−</span></TempoIconButton>
+    {onCopy ? <TempoIconButton disabled={disabled} label={copied ? "Copied" : "Copy response"} onClick={onCopy}><span className="tempo-feedback-glyph">{copied ? "✓" : "⧉"}</span></TempoIconButton> : null}
+    {onRegenerate ? <TempoIconButton disabled={disabled} label="Regenerate response" onClick={onRegenerate}><span className="tempo-feedback-glyph">↻</span></TempoIconButton> : null}
+  </div>;
+}
+
+export type TempoFieldProps = Omit<InputHTMLAttributes<HTMLInputElement>, "size"> & {
+  label: string;
+  description?: string;
+  error?: string;
+};
+
+export function TempoField({ label, description, error, id, className, ...props }: TempoFieldProps) {
+  const generatedId = useId();
+  const inputId = id || `tempo-field-${generatedId}`;
+  const descriptionId = description ? `${inputId}-description` : undefined;
+  const errorId = error ? `${inputId}-error` : undefined;
+  return (
+    <div className={cx("tempo-field", error && "tempo-field--error", className)}>
+      <label htmlFor={inputId}>{label}</label>
+      {description ? <span className="tempo-field__description" id={descriptionId}>{description}</span> : null}
+      <input
+        {...props}
+        aria-describedby={[descriptionId, errorId].filter(Boolean).join(" ") || undefined}
+        aria-invalid={error ? true : props["aria-invalid"]}
+        id={inputId}
+      />
+      {error ? <span className="tempo-field__error" id={errorId} role="alert">{error}</span> : null}
+    </div>
+  );
+}
+
+export function TempoCard({
+  heading,
+  eyebrow,
+  footer,
+  tone = "default",
+  selected = false,
+  disabled = false,
+  className,
+  children,
+  ...props
+}: HTMLAttributes<HTMLDivElement> & {
+  heading?: ReactNode;
+  eyebrow?: ReactNode;
+  footer?: ReactNode;
+  tone?: "default" | "soft" | "accent";
+  selected?: boolean;
+  disabled?: boolean;
+}) {
+  return (
+    <div
+      {...props}
+      aria-disabled={disabled || undefined}
+      className={cx("tempo-card", `tempo-card--${tone}`, selected && "is-selected", disabled && "is-disabled", className)}
+    >
+      {eyebrow || heading ? <header className="tempo-card__header">{eyebrow ? <span>{eyebrow}</span> : null}{heading ? <h3>{heading}</h3> : null}</header> : null}
+      <div className="tempo-card__content">{children}</div>
+      {footer ? <footer className="tempo-card__footer">{footer}</footer> : null}
+    </div>
+  );
+}
+
+export type TempoTabItem<T extends string> = { value: T; label: string; disabled?: boolean; panel?: ReactNode };
+
+export function TempoTabs<T extends string>({
+  items,
+  value,
+  onValueChange,
+  label = "Views",
+  activation = "automatic",
+  className,
+}: {
+  items: Array<TempoTabItem<T>>;
+  value: T;
+  onValueChange: (value: T) => void;
+  label?: string;
+  activation?: "automatic" | "manual";
+  className?: string;
+}) {
+  const baseId = useId();
+  const buttonRefs = useRef<Array<HTMLButtonElement | null>>([]);
+  const selected = items.find((item) => item.value === value) || items[0];
+
+  function moveFocus(event: KeyboardEvent<HTMLButtonElement>, index: number) {
+    const enabled = items.map((item, itemIndex) => ({ item, itemIndex })).filter(({ item }) => !item.disabled);
+    if (!enabled.length) return;
+    const current = enabled.findIndex(({ itemIndex }) => itemIndex === index);
+    let target = current;
+    if (event.key === "ArrowRight" || event.key === "ArrowDown") target = (current + 1) % enabled.length;
+    else if (event.key === "ArrowLeft" || event.key === "ArrowUp") target = (current - 1 + enabled.length) % enabled.length;
+    else if (event.key === "Home") target = 0;
+    else if (event.key === "End") target = enabled.length - 1;
+    else if ((event.key === "Enter" || event.key === " ") && activation === "manual") {
+      event.preventDefault();
+      onValueChange(items[index].value);
+      return;
+    } else return;
+    event.preventDefault();
+    const next = enabled[target];
+    buttonRefs.current[next.itemIndex]?.focus();
+    if (activation === "automatic") onValueChange(next.item.value);
+  }
+
+  return (
+    <div className={cx("tempo-tabs", className)}>
+      <div className="tempo-tabs__list" role="tablist" aria-label={label} aria-orientation="horizontal">
+        {items.map((item, index) => (
+          <button
+            aria-controls={`${baseId}-panel-${index}`}
+            aria-selected={item.value === value}
+            className={item.value === value ? "is-selected" : undefined}
+            disabled={item.disabled}
+            id={`${baseId}-tab-${index}`}
+            key={item.value}
+            onClick={() => onValueChange(item.value)}
+            onKeyDown={(event) => moveFocus(event, index)}
+            ref={(node) => { buttonRefs.current[index] = node; }}
+            role="tab"
+            tabIndex={item.value === value ? 0 : -1}
+            type="button"
+          >
+            {item.label}
+          </button>
+        ))}
+      </div>
+      {selected?.panel !== undefined ? (
+        <div
+          aria-labelledby={`${baseId}-tab-${items.indexOf(selected)}`}
+          className="tempo-tabs__panel"
+          id={`${baseId}-panel-${items.indexOf(selected)}`}
+          role="tabpanel"
+          tabIndex={0}
+        >
+          {selected.panel}
+        </div>
+      ) : null}
+    </div>
+  );
+}
+
+export function TempoStatus({
+  label,
+  tone = "neutral",
+  live = false,
+  icon,
+  className,
+}: {
+  label: string;
+  tone?: TempoTone | "loading";
+  live?: boolean;
+  icon?: ReactNode;
+  className?: string;
+}) {
+  return (
+    <span
+      aria-live={live ? "polite" : undefined}
+      className={cx("tempo-status", `tempo-status--${tone}`, className)}
+      role={live ? "status" : undefined}
+    >
+      {tone === "loading" ? <span className="tempo-spinner" aria-hidden /> : icon ? <span aria-hidden>{icon}</span> : <span className="tempo-status__dot" aria-hidden />}
+      <span>{label}</span>
+    </span>
+  );
+}
+
+const focusableSelector = "button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex='-1'])";
+
+export function TempoDialog({
+  open,
+  onOpenChange,
+  title,
+  description,
+  children,
+  actions,
+  closeLabel = "Close dialog",
+  busy = false,
+  destructive = false,
+  contained = false,
+}: {
+  open: boolean;
+  onOpenChange: (open: boolean) => void;
+  title: string;
+  description?: string;
+  children?: ReactNode;
+  actions?: ReactNode;
+  closeLabel?: string;
+  busy?: boolean;
+  destructive?: boolean;
+  contained?: boolean;
+}) {
+  const titleId = useId();
+  const descriptionId = useId();
+  const dialogRef = useRef<HTMLDivElement>(null);
+  const returnFocusRef = useRef<HTMLElement | null>(null);
+  const onOpenChangeRef = useRef(onOpenChange);
+
+  useEffect(() => {
+    onOpenChangeRef.current = onOpenChange;
+  }, [onOpenChange]);
+
+  useEffect(() => {
+    if (!open) return;
+    returnFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
+    const dialog = dialogRef.current;
+    const first = dialog?.querySelector<HTMLElement>(focusableSelector);
+    first?.focus();
+    const onKeyDown = (event: globalThis.KeyboardEvent) => {
+      if (event.key === "Escape" && !busy) {
+        event.preventDefault();
+        onOpenChangeRef.current(false);
+      }
+      if (event.key !== "Tab" || !dialog) return;
+      const focusable = Array.from(dialog.querySelectorAll<HTMLElement>(focusableSelector));
+      if (!focusable.length) return;
+      const firstItem = focusable[0];
+      const lastItem = focusable[focusable.length - 1];
+      if (event.shiftKey && document.activeElement === firstItem) { event.preventDefault(); lastItem.focus(); }
+      if (!event.shiftKey && document.activeElement === lastItem) { event.preventDefault(); firstItem.focus(); }
+    };
+    document.addEventListener("keydown", onKeyDown);
+    const priorOverflow = document.body.style.overflow;
+    if (!contained) document.body.style.overflow = "hidden";
+    return () => {
+      document.removeEventListener("keydown", onKeyDown);
+      if (!contained) document.body.style.overflow = priorOverflow;
+      returnFocusRef.current?.focus();
+    };
+  }, [busy, contained, open]);
+
+  if (!open) return null;
+  return (
+    <div className={cx("tempo-dialog__backdrop", contained && "tempo-dialog__backdrop--contained")} onMouseDown={(event) => { if (event.target === event.currentTarget && !busy) onOpenChange(false); }}>
+      <div
+        aria-describedby={description ? descriptionId : undefined}
+        aria-labelledby={titleId}
+        aria-modal="true"
+        className={cx("tempo-dialog", destructive && "tempo-dialog--destructive")}
+        ref={dialogRef}
+        role="dialog"
+      >
+        <button aria-label={closeLabel} className="tempo-dialog__close" disabled={busy} onClick={() => onOpenChange(false)} type="button">×</button>
+        <TempoStatus label={destructive ? "Confirmation required" : busy ? "Working" : "Decision"} tone={destructive ? "danger" : busy ? "loading" : "accent"} />
+        <h2 id={titleId}>{title}</h2>
+        {description ? <p id={descriptionId}>{description}</p> : null}
+        {children ? <div className="tempo-dialog__content">{children}</div> : null}
+        {actions ? <div className="tempo-dialog__actions">{actions}</div> : null}
+      </div>
+    </div>
+  );
+}
+
+export type TempoListState = "default" | "loading" | "empty" | "error";
+
+export function TempoList<T>({
+  items,
+  getKey,
+  renderItem,
+  state = "default",
+  emptyState = "No items yet.",
+  error = "Items could not be loaded.",
+  label = "Items",
+  className,
+}: {
+  items: T[];
+  getKey: (item: T) => string;
+  renderItem: (item: T) => ReactNode;
+  state?: TempoListState;
+  emptyState?: ReactNode;
+  error?: ReactNode;
+  label?: string;
+  className?: string;
+}) {
+  const message = state === "loading" ? "Loading…" : state === "error" ? error : state === "empty" || !items.length ? emptyState : null;
+  return (
+    <div aria-busy={state === "loading" || undefined} aria-label={label} className={cx("tempo-list", className)} role="list">
+      {message ? <div className={cx("tempo-list__message", state === "error" && "is-error")} role={state === "error" ? "alert" : "status"}>{message}</div> : null}
+      {!message ? items.map((item) => <div className="tempo-list__row" key={getKey(item)} role="listitem">{renderItem(item)}</div>) : null}
+    </div>
+  );
+}
+
+export function TempoListItem({
+  title,
+  detail,
+  leading,
+  trailing,
+  selected = false,
+}: {
+  title: ReactNode;
+  detail?: ReactNode;
+  leading?: ReactNode;
+  trailing?: ReactNode;
+  selected?: boolean;
+}) {
+  return (
+    <div className={cx("tempo-list-item", selected && "is-selected")}>
+      {leading ? <span className="tempo-list-item__leading" aria-hidden>{leading}</span> : null}
+      <span className="tempo-list-item__copy"><strong>{title}</strong>{detail ? <small>{detail}</small> : null}</span>
+      {trailing ? <span className="tempo-list-item__trailing">{trailing}</span> : null}
+    </div>
+  );
+}
+
+export function TempoSwitch({
+  label,
+  description,
+  checked,
+  onCheckedChange,
+  disabled = false,
+  className,
+}: {
+  label: string;
+  description?: string;
+  checked: boolean;
+  onCheckedChange: (checked: boolean) => void;
+  disabled?: boolean;
+  className?: string;
+}) {
+  const labelId = useId();
+  const descriptionId = useId();
+  return (
+    <div className={cx("tempo-switch", disabled && "is-disabled", className)}>
+      <span className="tempo-switch__copy"><strong id={labelId}>{label}</strong>{description ? <small id={descriptionId}>{description}</small> : null}</span>
+      <button
+        aria-checked={checked}
+        aria-describedby={description ? descriptionId : undefined}
+        aria-labelledby={labelId}
+        className="tempo-switch__control"
+        disabled={disabled}
+        onClick={() => onCheckedChange(!checked)}
+        role="switch"
+        type="button"
+      ><span /></button>
+    </div>
+  );
+}
+
+export function TempoComposer({
+  value,
+  onValueChange,
+  onSend,
+  placeholder = "Describe what you want to do…",
+  chips = [],
+  disabled = false,
+}: {
+  value: string;
+  onValueChange: (value: string) => void;
+  onSend: () => void;
+  placeholder?: string;
+  chips?: string[];
+  disabled?: boolean;
+}) {
+  const inputId = useId();
+  return (
+    <div className="tempo-composer">
+      <label className="tempo-visually-hidden" htmlFor={inputId}>Command</label>
+      <textarea id={inputId} value={value} onChange={(event) => onValueChange(event.target.value)} placeholder={placeholder} disabled={disabled} />
+      <div className="tempo-composer__footer">
+        <div className="tempo-composer__chips">{chips.map((chip) => <span key={chip}>{chip}</span>)}</div>
+        <TempoButton aria-label="Send command" disabled={disabled || !value.trim()} onClick={onSend}>Send</TempoButton>
+      </div>
+    </div>
+  );
+}
+
+export function TempoAgentThinking({ label = "Thinking", text, detail }: { label?: string; text: string; detail?: string }) {
+  return <div className="tempo-thinking" role="status"><span>{label}</span><strong>{text}</strong>{detail ? <p>{detail}</p> : null}</div>;
+}
+
+export function TempoDeviceFrame({
+  title,
+  navigation = [],
+  activeId,
+  children,
+}: {
+  title: string;
+  navigation?: Array<{ id: string; label: string; icon?: ReactNode }>;
+  activeId?: string;
+  children: ReactNode;
+}) {
+  return (
+    <div className="tempo-device">
+      <header><span aria-hidden><i /><i /><i /></span><strong>{title}</strong></header>
+      <div className="tempo-device__body">
+        {navigation.length ? <nav aria-label={`${title} navigation`}>{navigation.map((item) => <span className={item.id === activeId ? "is-active" : undefined} key={item.id}>{item.icon}{item.label}</span>)}</nav> : null}
+        <main>{children}</main>
+      </div>
+    </div>
+  );
+}
+
+// Compatibility names implement the stable contract props stored in Tempo v4.
+export const KitRoot = TempoRoot;
+export function KitButton({ label, icon, tone = "primary", ...props }: Omit<TempoButtonProps, "children" | "leading"> & { label: ReactNode; icon?: ReactNode }) {
+  return <TempoButton {...props} leading={icon} tone={tone}>{label}</TempoButton>;
+}
+export const KitField = TempoField;
+export function KitCard({ content, heading, ...props }: Omit<Parameters<typeof TempoCard>[0], "children"> & { content: ReactNode; heading?: ReactNode }) {
+  return <TempoCard {...props} heading={heading}>{content}</TempoCard>;
+}
+export function KitSegmented<T extends string>({ items, onChange, ...props }: Omit<Parameters<typeof TempoTabs<T>>[0], "items" | "onValueChange"> & { items: Array<TempoTabItem<T>>; onChange: (value: T) => void }) {
+  return <TempoTabs {...props} items={items} onValueChange={onChange} />;
+}
+export function KitPill({ label, ...props }: Omit<Parameters<typeof TempoStatus>[0], "label"> & { label: string }) {
+  return <TempoStatus {...props} label={label} />;
+}
+export const KitAlert = TempoDialog;
+export function KitList<T>({ selection, actions, renderItem, ...props }: Parameters<typeof TempoList<T>>[0] & { selection?: Set<string>; actions?: (item: T) => ReactNode }) {
+  return <TempoList {...props} renderItem={(item) => <div className={selection?.has(props.getKey(item)) ? "is-selected" : undefined}>{renderItem(item)}{actions ? <span className="tempo-list__actions">{actions(item)}</span> : null}</div>} />;
+}
+export const KitToggle = TempoSwitch;
+export const KitComposer = TempoComposer;
+export const KitAgentThinking = TempoAgentThinking;
+export const KitDeviceFrame = TempoDeviceFrame;
+export const KitIconButton = TempoIconButton;
+export const KitLinkButton = TempoLinkButton;
+export const KitFab = TempoFab;
+export const KitSplitButton = TempoSplitButton;
+export const KitButtonGroup = TempoButtonGroup;
+export const KitToggleButton = TempoToggleButton;
+export const KitFeedbackRow = TempoFeedbackRow;
manual editsrc/ui-system/kits/tempo/tempo.cssmedium
An existing project-owned kit package is protected from incidental adoption replacement. Upgrade it through a separate source-locked package migration.

Review the package or evidence upgrade separately before replacing this protected project-owned file.

--- a/src/ui-system/kits/tempo/tempo.css
+++ b/src/ui-system/kits/tempo/tempo.css
@@ -1,283 +1,405 @@
-.tempo {
-  --tempo-color-action-primary: var(--studio-color-action-primary, #a99af0);
-  --tempo-color-surface-canvas: var(--studio-color-surface-canvas, #f2f1ea);
-  --tempo-color-surface-card: var(--studio-color-surface-card, #fffefb);
-  --tempo-color-surface-soft: #f7f6ef;
-  --tempo-color-text-primary: var(--studio-color-text-primary, #1a1913);
-  --tempo-color-text-muted: var(--studio-color-text-muted, #6f6d63);
-  --tempo-color-border-subtle: var(--studio-color-border-subtle, rgb(26 25 19 / 9%));
-  --tempo-color-danger: #c74731;
-  --tempo-color-success: #397653;
-  --tempo-color-warning: #956516;
-  --tempo-color-on-action: #181816;
-  --tempo-type-family-sans: var(--studio-type-family-sans, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif);
-  --tempo-type-family-display: var(--studio-type-family-display, "Avenir Next Condensed", "Arial Narrow", "Helvetica Neue", sans-serif);
-  --tempo-type-size-body: var(--studio-type-size-body, 14px);
-  --tempo-type-size-heading: var(--studio-type-size-heading, 42px);
-  --tempo-type-weight-regular: var(--studio-type-weight-regular, 400);
-  --tempo-type-weight-medium: var(--studio-type-weight-medium, 600);
-  --tempo-type-line-height-body: var(--studio-type-line-height-body, 1.5);
-  --tempo-space-base: var(--studio-space-base, 12px);
-  --tempo-space-section: var(--studio-space-section, 32px);
-  --tempo-shape-control-radius: var(--studio-shape-control-radius, 999px);
-  --tempo-shape-card-radius: var(--studio-shape-card-radius, 22px);
-  --tempo-size-control-height: var(--studio-size-control-height, 44px);
-  --tempo-density-interface: var(--studio-density-interface, spacious);
-  --tempo-material-surface: var(--studio-material-surface, editorial);
-  --tempo-motion-fast: var(--studio-motion-duration-fast, 120ms);
-  --tempo-motion-standard: var(--studio-motion-duration-standard, 200ms);
-  --tempo-motion-ease: var(--studio-motion-easing-standard, cubic-bezier(0.23, 1, 0.32, 1));
-  --tempo-motion-reduced: var(--studio-motion-reduced, respect);
-  --tempo-shadow-card: 0 1px 3px rgb(26 25 19 / 5%), 0 8px 24px -18px rgb(26 25 19 / 25%);
-  color: var(--tempo-color-text-primary);
-  color-scheme: light;
-  font-family: var(--tempo-type-family-sans);
-  font-size: var(--tempo-type-size-body);
-  line-height: var(--tempo-type-line-height-body);
-  -webkit-font-smoothing: antialiased;
-}
-
-.tempo[data-tempo-theme="dark"] {
-  --tempo-color-surface-canvas: #171711;
-  --tempo-color-surface-card: #232219;
-  --tempo-color-surface-soft: #2c2a20;
-  --tempo-color-text-primary: #f3f1e8;
-  --tempo-color-text-muted: #aaa597;
-  --tempo-color-border-subtle: #3b392e;
-  --tempo-color-danger: #f07a5a;
-  --tempo-color-success: #75c493;
-  --tempo-color-warning: #e8b64c;
-  --tempo-color-on-action: #181816;
-  --tempo-shadow-card: 0 1px 2px rgb(0 0 0 / 24%), 0 24px 60px -38px rgb(0 0 0 / 90%);
-  color-scheme: dark;
-}
-
-.tempo,
-.tempo *,
-.tempo *::before,
-.tempo *::after {
-  box-sizing: border-box;
-}
-
-.tempo button,
-.tempo input,
-.tempo textarea,
-.tempo select {
-  font: inherit;
-}
-
-.tempo-visually-hidden {
-  position: absolute;
-  width: 1px;
-  height: 1px;
-  padding: 0;
-  margin: -1px;
-  overflow: hidden;
-  clip: rect(0, 0, 0, 0);
-  white-space: nowrap;
-  border: 0;
-}
-
-.tempo-button {
-  position: relative;
-  display: inline-grid;
-  min-height: var(--tempo-size-control-height);
-  place-items: center;
-  overflow: hidden;
-  border: 0;
-  border-radius: var(--tempo-shape-control-radius);
-  padding: 12px 22px;
-  background: var(--tempo-color-action-primary);
-  color: var(--tempo-color-on-action);
-  cursor: pointer;
-  font-size: 14.5px;
-  font-weight: 700;
-  touch-action: manipulation;
-  transition: transform var(--tempo-motion-fast) var(--tempo-motion-ease), filter var(--tempo-motion-fast) ease, box-shadow var(--tempo-motion-fast) ease;
-}
-
-.tempo-button__content {
-  display: inline-flex;
-  align-items: center;
-  justify-content: center;
-  gap: 8px;
-}
-
-.tempo-button__content[data-loading="true"] { visibility: hidden; }
-.tempo-button__icon { display: inline-grid; place-items: center; }
-.tempo-button--tonal { background: color-mix(in srgb, var(--tempo-color-action-primary) 19%, var(--tempo-color-surface-card)); color: var(--tempo-color-text-primary); }
-.tempo-button--outline { background: var(--tempo-color-surface-card); box-shadow: inset 0 0 0 1px var(--tempo-color-border-subtle); color: var(--tempo-color-text-primary); }
-.tempo-button--plain { background: transparent; color: var(--tempo-color-text-muted); padding-inline: 8px; }
-.tempo-button--danger { background: var(--tempo-color-danger); color: #fff; }
-.tempo-button:focus-visible,
-.tempo-field input:focus-visible,
-.tempo-tabs__list button:focus-visible,
-.tempo-switch__control:focus-visible,
-.tempo-dialog__close:focus-visible,
-.tempo-composer textarea:focus-visible {
-  outline: 3px solid color-mix(in srgb, var(--tempo-color-action-primary) 42%, transparent);
-  outline-offset: 2px;
-}
-.tempo-button:active:not(:disabled) { transform: scale(.97); }
-.tempo-button:disabled { cursor: not-allowed; filter: saturate(.35); opacity: .55; }
-
-.tempo-spinner {
-  position: absolute;
-  width: 16px;
-  height: 16px;
-  border: 2px solid currentColor;
-  border-right-color: transparent;
-  border-radius: 50%;
-  animation: tempo-spin .7s linear infinite;
-}
-
-@keyframes tempo-spin { to { transform: rotate(360deg); } }
-
-.tempo-field { display: grid; gap: 7px; }
-.tempo-field label { color: var(--tempo-color-text-primary); font-size: 12px; font-weight: 700; }
-.tempo-field__description { color: var(--tempo-color-text-muted); font-size: 12px; }
-.tempo-field input {
-  width: 100%;
-  min-height: var(--tempo-size-control-height);
-  border: 1px solid var(--tempo-color-border-subtle);
-  border-radius: var(--tempo-shape-control-radius);
-  padding: 10px 13px;
-  background: var(--tempo-color-surface-card);
-  color: var(--tempo-color-text-primary);
-  transition: border-color var(--tempo-motion-fast) ease, box-shadow var(--tempo-motion-fast) ease;
-}
-.tempo-field input::placeholder { color: color-mix(in srgb, var(--tempo-color-text-muted) 72%, transparent); }
-.tempo-field--error input { border-color: var(--tempo-color-danger); }
-.tempo-field__error { color: var(--tempo-color-danger); font-size: 12px; font-weight: 600; }
-.tempo-field input:disabled { cursor: not-allowed; opacity: .55; }
-
-.tempo-card {
-  overflow: hidden;
-  border: 1px solid var(--tempo-color-border-subtle);
-  border-radius: var(--tempo-shape-card-radius);
-  background: var(--tempo-color-surface-card);
-  box-shadow: var(--tempo-shadow-card);
-}
-.tempo-card--soft { background: var(--tempo-color-surface-soft); box-shadow: none; }
-.tempo-card--accent { background: color-mix(in srgb, var(--tempo-color-action-primary) 14%, var(--tempo-color-surface-card)); }
-.tempo-card.is-selected { box-shadow: inset 0 0 0 2px var(--tempo-color-action-primary), var(--tempo-shadow-card); }
-.tempo-card.is-disabled { opacity: .55; }
-.tempo-card__header { padding: 18px 18px 0; }
-.tempo-card__header > span { color: var(--tempo-color-text-muted); font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
-.tempo-card__header h3 { margin: 6px 0 0; font-family: var(--tempo-type-family-display); font-size: 20px; font-weight: 900; letter-spacing: -.01em; line-height: 1; text-transform: uppercase; }
-.tempo-card__content { padding: 18px; }
-.tempo-card__header + .tempo-card__content { padding-top: 12px; }
-.tempo-card__footer { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; border-top: 1px solid var(--tempo-color-border-subtle); padding: 12px 18px; }
-
-.tempo-tabs__list { display: inline-flex; max-width: 100%; gap: 3px; overflow-x: auto; border-radius: var(--tempo-shape-control-radius); padding: 3px; background: var(--tempo-color-surface-soft); scrollbar-width: none; }
-.tempo-tabs__list::-webkit-scrollbar { display: none; }
-.tempo-tabs__list button { min-height: 38px; flex: 0 0 auto; border: 0; border-radius: calc(var(--tempo-shape-control-radius) - 3px); padding: 8px 14px; background: transparent; color: var(--tempo-color-text-muted); cursor: pointer; font-weight: 600; }
-.tempo-tabs__list button.is-selected { background: var(--tempo-color-surface-card); color: var(--tempo-color-text-primary); box-shadow: 0 1px 3px rgb(24 24 22 / 10%); }
-.tempo-tabs__list button:disabled { cursor: not-allowed; opacity: .45; }
-.tempo-tabs__panel { margin-top: var(--tempo-space-base); }
-
-.tempo-status { display: inline-flex; min-height: 28px; align-items: center; gap: 7px; border-radius: 999px; padding: 5px 10px; background: var(--tempo-color-surface-soft); color: var(--tempo-color-text-primary); font-size: 12px; font-weight: 700; }
-.tempo-status__dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
-.tempo-status--accent { background: color-mix(in srgb, var(--tempo-color-action-primary) 20%, var(--tempo-color-surface-card)); }
-.tempo-status--success { background: color-mix(in srgb, var(--tempo-color-success) 14%, var(--tempo-color-surface-card)); color: var(--tempo-color-success); }
-.tempo-status--warning { background: color-mix(in srgb, var(--tempo-color-warning) 15%, var(--tempo-color-surface-card)); color: var(--tempo-color-warning); }
-.tempo-status--danger { background: color-mix(in srgb, var(--tempo-color-danger) 13%, var(--tempo-color-surface-card)); color: var(--tempo-color-danger); }
-.tempo-status--loading { position: relative; padding-left: 32px; }
-.tempo-status--loading .tempo-spinner { left: 10px; width: 13px; height: 13px; }
-
-.tempo-dialog__backdrop { position: fixed; z-index: 1000; inset: 0; display: grid; place-items: center; padding: 20px; background: rgb(24 24 22 / 48%); backdrop-filter: blur(8px); }
-.tempo-dialog__backdrop--contained { position: absolute; z-index: 5; }
-.tempo-dialog { position: relative; width: min(100%, 460px); max-height: min(680px, calc(100dvh - 40px)); overflow: auto; border: 1px solid var(--tempo-color-border-subtle); border-radius: var(--tempo-shape-card-radius); padding: 22px; background: var(--tempo-color-surface-card); box-shadow: 0 32px 100px -30px rgb(0 0 0 / 58%); color: var(--tempo-color-text-primary); transform-origin: center; animation: tempo-dialog-in var(--tempo-motion-standard) var(--tempo-motion-ease); }
-.tempo-dialog--destructive { box-shadow: inset 0 3px 0 var(--tempo-color-danger), 0 32px 100px -30px rgb(0 0 0 / 58%); }
-.tempo-dialog__close { position: absolute; top: 14px; right: 14px; display: grid; width: 38px; height: 38px; place-items: center; border: 0; border-radius: 50%; background: var(--tempo-color-surface-soft); color: var(--tempo-color-text-primary); cursor: pointer; font-size: 22px; line-height: 1; }
-.tempo-dialog h2 { max-width: calc(100% - 52px); margin: 18px 0 7px; font-family: var(--tempo-type-family-display); font-size: 26px; line-height: 1.08; }
-.tempo-dialog > p { margin: 0; color: var(--tempo-color-text-muted); }
-.tempo-dialog__content { margin-top: 18px; }
-.tempo-dialog__actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin-top: 22px; }
-@keyframes tempo-dialog-in { from { opacity: 0; transform: scale(.97) translateY(6px); } }
-
-.tempo-list { overflow: hidden; border: 1px solid var(--tempo-color-border-subtle); border-radius: var(--tempo-shape-card-radius); background: var(--tempo-color-surface-card); }
-.tempo-list__row + .tempo-list__row { border-top: 1px solid var(--tempo-color-border-subtle); }
-.tempo-list__message { min-height: 84px; display: grid; place-items: center; padding: 18px; color: var(--tempo-color-text-muted); text-align: center; }
-.tempo-list__message.is-error { color: var(--tempo-color-danger); }
-.tempo-list-item { display: flex; min-height: 58px; align-items: center; gap: 12px; padding: 12px 14px; }
-.tempo-list-item.is-selected { background: color-mix(in srgb, var(--tempo-color-action-primary) 13%, transparent); }
-.tempo-list-item__leading { display: grid; flex: 0 0 32px; width: 32px; height: 32px; place-items: center; border-radius: 50%; background: color-mix(in srgb, var(--tempo-color-action-primary) 24%, var(--tempo-color-surface-card)); color: var(--tempo-color-text-primary); }
-.tempo-list-item__copy { display: grid; min-width: 0; flex: 1; gap: 2px; }
-.tempo-list-item__copy strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.tempo-list-item__copy small { overflow: hidden; color: var(--tempo-color-text-muted); text-overflow: ellipsis; white-space: nowrap; }
-.tempo-list-item__trailing { flex: 0 0 auto; }
-.tempo-list__actions { display: flex; flex: 0 0 auto; align-items: center; gap: 6px; }
-
-.tempo-switch { display: flex; min-height: 58px; align-items: center; gap: 14px; }
-.tempo-switch__copy { display: grid; min-width: 0; flex: 1; gap: 2px; }
-.tempo-switch__copy small { color: var(--tempo-color-text-muted); }
-.tempo-switch__control { position: relative; flex: 0 0 48px; width: 48px; height: 28px; border: 0; border-radius: 999px; padding: 3px; background: var(--tempo-color-border-subtle); cursor: pointer; transition: background-color var(--tempo-motion-fast) ease; }
-.tempo-switch__control span { display: block; width: 22px; height: 22px; border-radius: 50%; background: #fff; box-shadow: 0 2px 5px rgb(0 0 0 / 25%); transform: translateX(0); transition: transform var(--tempo-motion-fast) var(--tempo-motion-ease); }
-.tempo-switch__control[aria-checked="true"] { background: var(--tempo-color-action-primary); }
-.tempo-switch__control[aria-checked="true"] span { transform: translateX(20px); }
-.tempo-switch.is-disabled { opacity: .55; }
-
-.tempo-composer { border: 1px solid var(--tempo-color-border-subtle); border-radius: var(--tempo-shape-card-radius); padding: 14px; background: var(--tempo-color-surface-card); box-shadow: var(--tempo-shadow-card); }
-.tempo-composer textarea { display: block; width: 100%; min-height: 88px; resize: vertical; border: 0; padding: 4px; background: transparent; color: var(--tempo-color-text-primary); outline: none; }
-.tempo-composer__footer { display: flex; align-items: flex-end; gap: 10px; margin-top: 10px; }
-.tempo-composer__chips { display: flex; flex: 1; flex-wrap: wrap; gap: 6px; }
-.tempo-composer__chips span { border-radius: 999px; padding: 5px 9px; background: var(--tempo-color-surface-soft); color: var(--tempo-color-text-muted); font-size: 11px; font-weight: 600; }
-
-.tempo-thinking { display: grid; grid-template-columns: auto 1fr; gap: 4px 10px; border-radius: var(--tempo-shape-card-radius); padding: 13px 14px; background: var(--tempo-color-surface-soft); }
-.tempo-thinking > span { grid-row: 1 / span 2; align-self: start; border-radius: 999px; padding: 4px 8px; background: color-mix(in srgb, var(--tempo-color-action-primary) 22%, var(--tempo-color-surface-card)); font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
-.tempo-thinking strong { font-size: 13px; }
-.tempo-thinking p { grid-column: 2; margin: 0; color: var(--tempo-color-text-muted); font-size: 12px; }
-
-.tempo-device { overflow: hidden; border: 7px solid #24231d; border-radius: 28px; background: var(--tempo-color-surface-canvas); box-shadow: 0 36px 100px -40px rgb(0 0 0 / 72%); }
-.tempo-device > header { position: relative; display: flex; min-height: 38px; align-items: center; justify-content: center; border-bottom: 1px solid var(--tempo-color-border-subtle); background: var(--tempo-color-surface-soft); }
-.tempo-device > header > span { position: absolute; left: 13px; display: flex; gap: 6px; }
-.tempo-device > header i { width: 9px; height: 9px; border-radius: 50%; background: var(--tempo-color-danger); }
-.tempo-device > header i:nth-child(2) { background: var(--tempo-color-warning); }
-.tempo-device > header i:nth-child(3) { background: var(--tempo-color-success); }
-.tempo-device > header strong { font-size: 12px; }
-.tempo-device__body { display: grid; grid-template-columns: 160px minmax(0, 1fr); min-height: 420px; }
-.tempo-device__body nav { display: flex; flex-direction: column; gap: 4px; border-right: 1px solid var(--tempo-color-border-subtle); padding: 12px; background: color-mix(in srgb, var(--tempo-color-surface-soft) 70%, transparent); }
-.tempo-device__body nav span { display: flex; min-height: 38px; align-items: center; gap: 8px; border-radius: var(--tempo-shape-control-radius); padding: 7px 10px; color: var(--tempo-color-text-muted); font-size: 12px; font-weight: 600; }
-.tempo-device__body nav span.is-active { background: color-mix(in srgb, var(--tempo-color-action-primary) 18%, transparent); color: var(--tempo-color-text-primary); }
-.tempo-device__body main { min-width: 0; padding: 18px; }
-
-.tempo-proof-root { position: relative; min-height: 280px; padding: clamp(12px, 2.5vw, 28px); border-radius: var(--tempo-shape-card-radius); background: var(--tempo-color-surface-canvas); }
-.tempo-proof-root.is-compact { min-height: 220px; padding: 10px; font-size: 12px; }
-.tempo-proof-root .tempo-card { width: min(100%, 680px); margin-inline: auto; }
-.tempo-proof-root.is-compact .tempo-card__header { padding: 12px 12px 0; }
-.tempo-proof-root.is-compact .tempo-card__header h3 { font-size: 15px; }
-.tempo-proof-root.is-compact .tempo-card__content { padding: 12px; }
-.tempo-proof-root.is-compact .tempo-card__footer { padding: 10px 12px; }
-.tempo-proof-root.is-compact .tempo-button { min-height: 38px; padding: 7px 11px; font-size: 11px; }
-.tempo-proof-copy { margin: 14px 0 0; color: var(--tempo-color-text-muted); }
-.tempo-proof-stack { display: grid; gap: 16px; }
-.tempo-proof-dialog-stage { position: relative; min-height: 360px; overflow: hidden; border-radius: var(--tempo-shape-card-radius); background: var(--tempo-color-surface-soft); }
-.tempo-proof-root.is-compact .tempo-proof-dialog-stage { min-height: 280px; }
-.tempo-proof-root.is-compact .tempo-dialog { padding: 15px; transform: scale(.86); }
-
-@media (hover: hover) and (pointer: fine) {
-  .tempo-button:hover:not(:disabled) { filter: brightness(1.04); }
-  .tempo-tabs__list button:hover:not(:disabled) { color: var(--tempo-color-text-primary); }
-}
-
-@media (max-width: 620px) {
-  .tempo-dialog__backdrop { padding: 12px; align-items: end; }
-  .tempo-dialog { width: 100%; max-height: calc(100dvh - 24px); border-radius: var(--tempo-shape-card-radius) var(--tempo-shape-card-radius) 12px 12px; }
-  .tempo-dialog__actions > * { flex: 1; }
-  .tempo-device__body { grid-template-columns: 1fr; }
-  .tempo-device__body nav { flex-direction: row; overflow-x: auto; border-right: 0; border-bottom: 1px solid var(--tempo-color-border-subtle); }
-  .tempo-device__body nav span { flex: 0 0 auto; }
-  .tempo-composer__footer { align-items: stretch; flex-direction: column; }
-}
-
-@media (prefers-reduced-motion: reduce) {
-  .tempo *,
-  .tempo *::before,
-  .tempo *::after {
-    scroll-behavior: auto !important;
-    animation-duration: .01ms !important;
-    animation-iteration-count: 1 !important;
-    transition-duration: .01ms !important;
-  }
-}
+.tempo {
+  --tempo-color-action-primary: var(--studio-color-action-primary, #a99af0);
+  --tempo-color-surface-canvas: var(--studio-color-surface-canvas, #f2f1ea);
+  --tempo-color-surface-card: var(--studio-color-surface-card, #fffefb);
+  --tempo-color-surface-soft: #f7f6ef;
+  --tempo-color-text-primary: var(--studio-color-text-primary, #1a1913);
+  --tempo-color-text-muted: var(--studio-color-text-muted, #6f6d63);
+  --tempo-color-border-subtle: var(--studio-color-border-subtle, rgb(26 25 19 / 9%));
+  --tempo-color-danger: #c74731;
+  --tempo-color-success: #397653;
+  --tempo-color-warning: #956516;
+  --tempo-color-on-action: #181816;
+  --tempo-type-family-sans: var(--studio-type-family-sans, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif);
+  --tempo-type-family-display: var(--studio-type-family-display, "Avenir Next Condensed", "Arial Narrow", "Helvetica Neue", sans-serif);
+  --tempo-type-size-body: var(--studio-type-size-body, 14px);
+  --tempo-type-size-heading: var(--studio-type-size-heading, 42px);
+  --tempo-type-weight-regular: var(--studio-type-weight-regular, 400);
+  --tempo-type-weight-medium: var(--studio-type-weight-medium, 600);
+  --tempo-type-line-height-body: var(--studio-type-line-height-body, 1.5);
+  --tempo-space-base: var(--studio-space-base, 12px);
+  --tempo-space-section: var(--studio-space-section, 32px);
+  --tempo-shape-control-radius: var(--studio-shape-control-radius, 999px);
+  --tempo-shape-card-radius: var(--studio-shape-card-radius, 22px);
+  --tempo-size-control-height: var(--studio-size-control-height, 44px);
+  --tempo-density-interface: var(--studio-density-interface, spacious);
+  --tempo-material-surface: var(--studio-material-surface, editorial);
+  --tempo-motion-fast: var(--studio-motion-duration-fast, 120ms);
+  --tempo-motion-standard: var(--studio-motion-duration-standard, 200ms);
+  --tempo-motion-ease: var(--studio-motion-easing-standard, cubic-bezier(0.23, 1, 0.32, 1));
+  --tempo-motion-reduced: var(--studio-motion-reduced, respect);
+  --tempo-shadow-card: 0 1px 3px rgb(26 25 19 / 5%), 0 8px 24px -18px rgb(26 25 19 / 25%);
+  color: var(--tempo-color-text-primary);
+  color-scheme: light;
+  font-family: var(--tempo-type-family-sans);
+  font-size: var(--tempo-type-size-body);
+  line-height: var(--tempo-type-line-height-body);
+  -webkit-font-smoothing: antialiased;
+}
+
+.tempo[data-tempo-theme="dark"] {
+  --tempo-color-surface-canvas: #171711;
+  --tempo-color-surface-card: #232219;
+  --tempo-color-surface-soft: #2c2a20;
+  --tempo-color-text-primary: #f3f1e8;
+  --tempo-color-text-muted: #aaa597;
+  --tempo-color-border-subtle: #3b392e;
+  --tempo-color-danger: #f07a5a;
+  --tempo-color-success: #75c493;
+  --tempo-color-warning: #e8b64c;
+  --tempo-color-on-action: #181816;
+  --tempo-shadow-card: 0 1px 2px rgb(0 0 0 / 24%), 0 24px 60px -38px rgb(0 0 0 / 90%);
+  color-scheme: dark;
+}
+
+.tempo,
+.tempo *,
+.tempo *::before,
+.tempo *::after {
+  box-sizing: border-box;
+}
+
+.tempo button,
+.tempo input,
+.tempo textarea,
+.tempo select {
+  font: inherit;
+}
+
+.tempo-visually-hidden {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  white-space: nowrap;
+  border: 0;
+}
+
+.tempo-button {
+  position: relative;
+  display: inline-grid;
+  min-height: var(--tempo-size-control-height);
+  place-items: center;
+  overflow: hidden;
+  border: 0;
+  border-radius: var(--tempo-shape-control-radius);
+  padding: 12px 22px;
+  background: var(--tempo-color-action-primary);
+  color: var(--tempo-color-on-action);
+  cursor: pointer;
+  font-size: 14.5px;
+  font-weight: 700;
+  touch-action: manipulation;
+  transition: transform var(--tempo-motion-fast) var(--tempo-motion-ease), filter var(--tempo-motion-fast) ease, box-shadow var(--tempo-motion-fast) ease;
+}
+
+.tempo-button__content {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+}
+
+.tempo-button__content[data-loading="true"] { visibility: hidden; }
+.tempo-button__icon { display: inline-grid; place-items: center; }
+.tempo-button--tonal { background: color-mix(in srgb, var(--tempo-color-action-primary) 19%, var(--tempo-color-surface-card)); color: var(--tempo-color-text-primary); }
+.tempo-button--outline { background: var(--tempo-color-surface-card); box-shadow: inset 0 0 0 1px var(--tempo-color-border-subtle); color: var(--tempo-color-text-primary); }
+.tempo-button--plain { background: transparent; color: var(--tempo-color-text-muted); padding-inline: 8px; }
+.tempo-button--danger { background: var(--tempo-color-danger); color: #fff; }
+.tempo-button:focus-visible,
+.tempo-icon-button:focus-visible,
+.tempo-link-button:focus-visible,
+.tempo-fab:focus-visible,
+.tempo-split-button button:focus-visible,
+.tempo-button-group button:focus-visible,
+.tempo-toggle-button:focus-visible,
+.tempo-field input:focus-visible,
+.tempo-tabs__list button:focus-visible,
+.tempo-switch__control:focus-visible,
+.tempo-dialog__close:focus-visible,
+.tempo-composer textarea:focus-visible {
+  outline: 3px solid color-mix(in srgb, var(--tempo-color-action-primary) 42%, transparent);
+  outline-offset: 2px;
+}
+.tempo-button:active:not(:disabled) { transform: scale(.97); }
+.tempo-button:disabled { cursor: not-allowed; filter: saturate(.35); opacity: .55; }
+
+.tempo-icon-button,
+.tempo-fab,
+.tempo-split-button button,
+.tempo-button-group button,
+.tempo-toggle-button {
+  touch-action: manipulation;
+  transition: transform var(--tempo-motion-fast) var(--tempo-motion-ease), filter var(--tempo-motion-fast) ease, background-color var(--tempo-motion-fast) ease, box-shadow var(--tempo-motion-fast) ease, color var(--tempo-motion-fast) ease;
+}
+.tempo-icon-button {
+  display: inline-grid;
+  width: 44px;
+  height: 44px;
+  flex: 0 0 44px;
+  place-items: center;
+  border: 0;
+  border-radius: 50%;
+  background: var(--tempo-color-surface-card);
+  box-shadow: inset 0 0 0 1px var(--tempo-color-border-subtle), var(--tempo-shadow-card);
+  color: var(--tempo-color-text-primary);
+  cursor: pointer;
+}
+.tempo-icon-button > span, .tempo-fab > span { display: inline-grid; place-items: center; }
+.tempo-icon-button--small { width: 44px; height: 44px; flex-basis: 44px; }
+.tempo-icon-button--large { width: 52px; height: 52px; flex-basis: 52px; }
+.tempo-icon-button--accent { background: color-mix(in srgb, var(--tempo-color-action-primary) 24%, var(--tempo-color-surface-card)); color: var(--tempo-color-text-primary); }
+.tempo-icon-button--danger { background: color-mix(in srgb, var(--tempo-color-danger) 14%, var(--tempo-color-surface-card)); color: var(--tempo-color-danger); }
+.tempo-icon-button:active:not(:disabled), .tempo-fab:active:not(:disabled), .tempo-toggle-button:active:not(:disabled) { transform: scale(.94); }
+.tempo-icon-button:disabled, .tempo-fab:disabled, .tempo-split-button button:disabled, .tempo-button-group button:disabled, .tempo-toggle-button:disabled { cursor: not-allowed; filter: saturate(.35); opacity: .5; }
+
+.tempo-link-button {
+  display: inline-flex;
+  min-height: var(--tempo-size-control-height);
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  border-radius: var(--tempo-shape-control-radius);
+  padding: 10px 14px;
+  color: var(--tempo-color-text-primary);
+  font-weight: 700;
+  text-decoration: none;
+  touch-action: manipulation;
+  transition: transform var(--tempo-motion-fast) var(--tempo-motion-ease), background-color var(--tempo-motion-fast) ease, color var(--tempo-motion-fast) ease;
+}
+.tempo-link-button > span { display: inline-grid; place-items: center; }
+.tempo-link-button--primary { background: var(--tempo-color-action-primary); color: var(--tempo-color-on-action); }
+.tempo-link-button--tonal { background: color-mix(in srgb, var(--tempo-color-action-primary) 19%, var(--tempo-color-surface-card)); }
+.tempo-link-button--plain { color: var(--tempo-color-text-muted); }
+.tempo-link-button:active { transform: translateY(1px); }
+.tempo-link-button.is-disabled { cursor: not-allowed; filter: saturate(.35); opacity: .5; }
+
+.tempo-fab {
+  display: inline-grid;
+  width: 56px;
+  height: 56px;
+  place-items: center;
+  border: 0;
+  border-radius: 19px;
+  background: var(--tempo-color-action-primary);
+  box-shadow: 0 16px 34px -18px color-mix(in srgb, var(--tempo-color-action-primary) 75%, #000), inset 0 1px 0 rgb(255 255 255 / 38%);
+  color: var(--tempo-color-on-action);
+  cursor: pointer;
+}
+
+.tempo-split-button {
+  display: inline-flex;
+  min-height: var(--tempo-size-control-height);
+  overflow: hidden;
+  border-radius: var(--tempo-shape-control-radius);
+  background: var(--tempo-color-action-primary);
+  box-shadow: var(--tempo-shadow-card);
+}
+.tempo-split-button button { display: inline-flex; min-height: var(--tempo-size-control-height); align-items: center; justify-content: center; gap: 8px; border: 0; padding: 10px 18px; background: transparent; color: var(--tempo-color-on-action); cursor: pointer; font-weight: 700; }
+.tempo-split-button button + button { width: 46px; padding: 0; box-shadow: inset 1px 0 0 color-mix(in srgb, var(--tempo-color-on-action) 20%, transparent); }
+.tempo-split-button button:active:not(:disabled) { background: color-mix(in srgb, var(--tempo-color-on-action) 10%, transparent); }
+
+.tempo-button-group {
+  display: inline-flex;
+  max-width: 100%;
+  gap: 3px;
+  overflow-x: auto;
+  border-radius: var(--tempo-shape-control-radius);
+  padding: 3px;
+  background: var(--tempo-color-surface-soft);
+  scrollbar-width: none;
+}
+.tempo-button-group::-webkit-scrollbar { display: none; }
+.tempo-button-group button { display: inline-flex; min-height: var(--tempo-size-control-height); flex: 0 0 auto; align-items: center; gap: 7px; border: 0; border-radius: calc(var(--tempo-shape-control-radius) - 3px); padding: 9px 14px; background: transparent; color: var(--tempo-color-text-muted); cursor: pointer; font-weight: 650; }
+.tempo-button-group button.is-active, .tempo-button-group button[aria-pressed="true"] { background: var(--tempo-color-surface-card); box-shadow: 0 1px 3px rgb(24 24 22 / 10%); color: var(--tempo-color-text-primary); }
+
+.tempo-toggle-button {
+  display: inline-flex;
+  min-height: var(--tempo-size-control-height);
+  align-items: center;
+  gap: 8px;
+  border: 1px solid var(--tempo-color-border-subtle);
+  border-radius: var(--tempo-shape-control-radius);
+  padding: 10px 15px;
+  background: var(--tempo-color-surface-card);
+  color: var(--tempo-color-text-muted);
+  cursor: pointer;
+  font-weight: 700;
+}
+.tempo-toggle-button.is-pressed, .tempo-toggle-button[aria-pressed="true"] { border-color: color-mix(in srgb, var(--tempo-color-action-primary) 48%, var(--tempo-color-border-subtle)); background: color-mix(in srgb, var(--tempo-color-action-primary) 20%, var(--tempo-color-surface-card)); color: var(--tempo-color-text-primary); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--tempo-color-action-primary) 18%, transparent); }
+
+.tempo-feedback-row { display: inline-flex; align-items: center; gap: 4px; border-radius: var(--tempo-shape-control-radius); padding: 4px; background: var(--tempo-color-surface-soft); }
+.tempo-feedback-row .tempo-icon-button { box-shadow: none; background: transparent; }
+.tempo-feedback-row .tempo-icon-button[aria-pressed="true"] { background: var(--tempo-color-surface-card); box-shadow: var(--tempo-shadow-card); }
+.tempo-feedback-glyph { font-family: var(--tempo-type-family-mono, ui-monospace, monospace); font-size: 19px; font-weight: 700; line-height: 1; }
+
+.tempo-spinner {
+  position: absolute;
+  width: 16px;
+  height: 16px;
+  border: 2px solid currentColor;
+  border-right-color: transparent;
+  border-radius: 50%;
+  animation: tempo-spin .7s linear infinite;
+}
+
+@keyframes tempo-spin { to { transform: rotate(360deg); } }
+
+.tempo-field { display: grid; gap: 7px; }
+.tempo-field label { color: var(--tempo-color-text-primary); font-size: 12px; font-weight: 700; }
+.tempo-field__description { color: var(--tempo-color-text-muted); font-size: 12px; }
+.tempo-field input {
+  width: 100%;
+  min-height: var(--tempo-size-control-height);
+  border: 1px solid var(--tempo-color-border-subtle);
+  border-radius: var(--tempo-shape-control-radius);
+  padding: 10px 13px;
+  background: var(--tempo-color-surface-card);
+  color: var(--tempo-color-text-primary);
+  transition: border-color var(--tempo-motion-fast) ease, box-shadow var(--tempo-motion-fast) ease;
+}
+.tempo-field input::placeholder { color: color-mix(in srgb, var(--tempo-color-text-muted) 72%, transparent); }
+.tempo-field--error input { border-color: var(--tempo-color-danger); }
+.tempo-field__error { color: var(--tempo-color-danger); font-size: 12px; font-weight: 600; }
+.tempo-field input:disabled { cursor: not-allowed; opacity: .55; }
+
+.tempo-card {
+  overflow: hidden;
+  border: 1px solid var(--tempo-color-border-subtle);
+  border-radius: var(--tempo-shape-card-radius);
+  background: var(--tempo-color-surface-card);
+  box-shadow: var(--tempo-shadow-card);
+}
+.tempo-card--soft { background: var(--tempo-color-surface-soft); box-shadow: none; }
+.tempo-card--accent { background: color-mix(in srgb, var(--tempo-color-action-primary) 14%, var(--tempo-color-surface-card)); }
+.tempo-card.is-selected { box-shadow: inset 0 0 0 2px var(--tempo-color-action-primary), var(--tempo-shadow-card); }
+.tempo-card.is-disabled { opacity: .55; }
+.tempo-card__header { padding: 18px 18px 0; }
+.tempo-card__header > span { color: var(--tempo-color-text-muted); font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
+.tempo-card__header h3 { margin: 6px 0 0; font-family: var(--tempo-type-family-display); font-size: 20px; font-weight: 900; letter-spacing: -.01em; line-height: 1; text-transform: uppercase; }
+.tempo-card__content { padding: 18px; }
+.tempo-card__header + .tempo-card__content { padding-top: 12px; }
+.tempo-card__footer { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; border-top: 1px solid var(--tempo-color-border-subtle); padding: 12px 18px; }
+
+.tempo-tabs__list { display: inline-flex; max-width: 100%; gap: 3px; overflow-x: auto; border-radius: var(--tempo-shape-control-radius); padding: 3px; background: var(--tempo-color-surface-soft); scrollbar-width: none; }
+.tempo-tabs__list::-webkit-scrollbar { display: none; }
+.tempo-tabs__list button { min-height: 38px; flex: 0 0 auto; border: 0; border-radius: calc(var(--tempo-shape-control-radius) - 3px); padding: 8px 14px; background: transparent; color: var(--tempo-color-text-muted); cursor: pointer; font-weight: 600; }
+.tempo-tabs__list button.is-selected { background: var(--tempo-color-surface-card); color: var(--tempo-color-text-primary); box-shadow: 0 1px 3px rgb(24 24 22 / 10%); }
+.tempo-tabs__list button:disabled { cursor: not-allowed; opacity: .45; }
+.tempo-tabs__panel { margin-top: var(--tempo-space-base); }
+
+.tempo-status { display: inline-flex; min-height: 28px; align-items: center; gap: 7px; border-radius: 999px; padding: 5px 10px; background: var(--tempo-color-surface-soft); color: var(--tempo-color-text-primary); font-size: 12px; font-weight: 700; }
+.tempo-status__dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
+.tempo-status--accent { background: color-mix(in srgb, var(--tempo-color-action-primary) 20%, var(--tempo-color-surface-card)); }
+.tempo-status--success { background: color-mix(in srgb, var(--tempo-color-success) 14%, var(--tempo-color-surface-card)); color: var(--tempo-color-success); }
+.tempo-status--warning { background: color-mix(in srgb, var(--tempo-color-warning) 15%, var(--tempo-color-surface-card)); color: var(--tempo-color-warning); }
+.tempo-status--danger { background: color-mix(in srgb, var(--tempo-color-danger) 13%, var(--tempo-color-surface-card)); color: var(--tempo-color-danger); }
+.tempo-status--loading { position: relative; padding-left: 32px; }
+.tempo-status--loading .tempo-spinner { left: 10px; width: 13px; height: 13px; }
+
+.tempo-dialog__backdrop { position: fixed; z-index: 1000; inset: 0; display: grid; place-items: center; padding: 20px; background: rgb(24 24 22 / 48%); backdrop-filter: blur(8px); }
+.tempo-dialog__backdrop--contained { position: absolute; z-index: 5; }
+.tempo-dialog { position: relative; width: min(100%, 460px); max-height: min(680px, calc(100dvh - 40px)); overflow: auto; border: 1px solid var(--tempo-color-border-subtle); border-radius: var(--tempo-shape-card-radius); padding: 22px; background: var(--tempo-color-surface-card); box-shadow: 0 32px 100px -30px rgb(0 0 0 / 58%); color: var(--tempo-color-text-primary); transform-origin: center; animation: tempo-dialog-in var(--tempo-motion-standard) var(--tempo-motion-ease); }
+.tempo-dialog--destructive { box-shadow: inset 0 3px 0 var(--tempo-color-danger), 0 32px 100px -30px rgb(0 0 0 / 58%); }
+.tempo-dialog__close { position: absolute; top: 14px; right: 14px; display: grid; width: 38px; height: 38px; place-items: center; border: 0; border-radius: 50%; background: var(--tempo-color-surface-soft); color: var(--tempo-color-text-primary); cursor: pointer; font-size: 22px; line-height: 1; }
+.tempo-dialog h2 { max-width: calc(100% - 52px); margin: 18px 0 7px; font-family: var(--tempo-type-family-display); font-size: 26px; line-height: 1.08; }
+.tempo-dialog > p { margin: 0; color: var(--tempo-color-text-muted); }
+.tempo-dialog__content { margin-top: 18px; }
+.tempo-dialog__actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin-top: 22px; }
+@keyframes tempo-dialog-in { from { opacity: 0; transform: scale(.97) translateY(6px); } }
+
+.tempo-list { overflow: hidden; border: 1px solid var(--tempo-color-border-subtle); border-radius: var(--tempo-shape-card-radius); background: var(--tempo-color-surface-card); }
+.tempo-list__row + .tempo-list__row { border-top: 1px solid var(--tempo-color-border-subtle); }
+.tempo-list__message { min-height: 84px; display: grid; place-items: center; padding: 18px; color: var(--tempo-color-text-muted); text-align: center; }
+.tempo-list__message.is-error { color: var(--tempo-color-danger); }
+.tempo-list-item { display: flex; min-height: 58px; align-items: center; gap: 12px; padding: 12px 14px; }
+.tempo-list-item.is-selected { background: color-mix(in srgb, var(--tempo-color-action-primary) 13%, transparent); }
+.tempo-list-item__leading { display: grid; flex: 0 0 32px; width: 32px; height: 32px; place-items: center; border-radius: 50%; background: color-mix(in srgb, var(--tempo-color-action-primary) 24%, var(--tempo-color-surface-card)); color: var(--tempo-color-text-primary); }
+.tempo-list-item__copy { display: grid; min-width: 0; flex: 1; gap: 2px; }
+.tempo-list-item__copy strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.tempo-list-item__copy small { overflow: hidden; color: var(--tempo-color-text-muted); text-overflow: ellipsis; white-space: nowrap; }
+.tempo-list-item__trailing { flex: 0 0 auto; }
+.tempo-list__actions { display: flex; flex: 0 0 auto; align-items: center; gap: 6px; }
+
+.tempo-switch { display: flex; min-height: 58px; align-items: center; gap: 14px; }
+.tempo-switch__copy { display: grid; min-width: 0; flex: 1; gap: 2px; }
+.tempo-switch__copy small { color: var(--tempo-color-text-muted); }
+.tempo-switch__control { position: relative; flex: 0 0 48px; width: 48px; height: 28px; border: 0; border-radius: 999px; padding: 3px; background: var(--tempo-color-border-subtle); cursor: pointer; transition: background-color var(--tempo-motion-fast) ease; }
+.tempo-switch__control span { display: block; width: 22px; height: 22px; border-radius: 50%; background: #fff; box-shadow: 0 2px 5px rgb(0 0 0 / 25%); transform: translateX(0); transition: transform var(--tempo-motion-fast) var(--tempo-motion-ease); }
+.tempo-switch__control[aria-checked="true"] { background: var(--tempo-color-action-primary); }
+.tempo-switch__control[aria-checked="true"] span { transform: translateX(20px); }
+.tempo-switch.is-disabled { opacity: .55; }
+
+.tempo-composer { border: 1px solid var(--tempo-color-border-subtle); border-radius: var(--tempo-shape-card-radius); padding: 14px; background: var(--tempo-color-surface-card); box-shadow: var(--tempo-shadow-card); }
+.tempo-composer textarea { display: block; width: 100%; min-height: 88px; resize: vertical; border: 0; padding: 4px; background: transparent; color: var(--tempo-color-text-primary); outline: none; }
+.tempo-composer__footer { display: flex; align-items: flex-end; gap: 10px; margin-top: 10px; }
+.tempo-composer__chips { display: flex; flex: 1; flex-wrap: wrap; gap: 6px; }
+.tempo-composer__chips span { border-radius: 999px; padding: 5px 9px; background: var(--tempo-color-surface-soft); color: var(--tempo-color-text-muted); font-size: 11px; font-weight: 600; }
+
+.tempo-thinking { display: grid; grid-template-columns: auto 1fr; gap: 4px 10px; border-radius: var(--tempo-shape-card-radius); padding: 13px 14px; background: var(--tempo-color-surface-soft); }
+.tempo-thinking > span { grid-row: 1 / span 2; align-self: start; border-radius: 999px; padding: 4px 8px; background: color-mix(in srgb, var(--tempo-color-action-primary) 22%, var(--tempo-color-surface-card)); font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
+.tempo-thinking strong { font-size: 13px; }
+.tempo-thinking p { grid-column: 2; margin: 0; color: var(--tempo-color-text-muted); font-size: 12px; }
+
+.tempo-device { overflow: hidden; border: 7px solid #24231d; border-radius: 28px; background: var(--tempo-color-surface-canvas); box-shadow: 0 36px 100px -40px rgb(0 0 0 / 72%); }
+.tempo-device > header { position: relative; display: flex; min-height: 38px; align-items: center; justify-content: center; border-bottom: 1px solid var(--tempo-color-border-subtle); background: var(--tempo-color-surface-soft); }
+.tempo-device > header > span { position: absolute; left: 13px; display: flex; gap: 6px; }
+.tempo-device > header i { width: 9px; height: 9px; border-radius: 50%; background: var(--tempo-color-danger); }
+.tempo-device > header i:nth-child(2) { background: var(--tempo-color-warning); }
+.tempo-device > header i:nth-child(3) { background: var(--tempo-color-success); }
+.tempo-device > header strong { font-size: 12px; }
+.tempo-device__body { display: grid; grid-template-columns: 160px minmax(0, 1fr); min-height: 420px; }
+.tempo-device__body nav { display: flex; flex-direction: column; gap: 4px; border-right: 1px solid var(--tempo-color-border-subtle); padding: 12px; background: color-mix(in srgb, var(--tempo-color-surface-soft) 70%, transparent); }
+.tempo-device__body nav span { display: flex; min-height: 38px; align-items: center; gap: 8px; border-radius: var(--tempo-shape-control-radius); padding: 7px 10px; color: var(--tempo-color-text-muted); font-size: 12px; font-weight: 600; }
+.tempo-device__body nav span.is-active { background: color-mix(in srgb, var(--tempo-color-action-primary) 18%, transparent); color: var(--tempo-color-text-primary); }
+.tempo-device__body main { min-width: 0; padding: 18px; }
+
+.tempo-proof-root { position: relative; min-height: 280px; padding: clamp(12px, 2.5vw, 28px); border-radius: var(--tempo-shape-card-radius); background: var(--tempo-color-surface-canvas); }
+.tempo-proof-root.is-compact { min-height: 220px; padding: 10px; font-size: 12px; }
+.tempo-proof-root .tempo-card { width: min(100%, 680px); margin-inline: auto; }
+.tempo-proof-root.is-compact .tempo-card__header { padding: 12px 12px 0; }
+.tempo-proof-root.is-compact .tempo-card__header h3 { font-size: 15px; }
+.tempo-proof-root.is-compact .tempo-card__content { padding: 12px; }
+.tempo-proof-root.is-compact .tempo-card__footer { padding: 10px 12px; }
+.tempo-proof-root.is-compact .tempo-button { min-height: 38px; padding: 7px 11px; font-size: 11px; }
+.tempo-proof-copy { margin: 14px 0 0; color: var(--tempo-color-text-muted); }
+.tempo-proof-stack { display: grid; gap: 16px; }
+.tempo-proof-dialog-stage { position: relative; min-height: 360px; overflow: hidden; border-radius: var(--tempo-shape-card-radius); background: var(--tempo-color-surface-soft); }
+.tempo-proof-root.is-compact .tempo-proof-dialog-stage { min-height: 280px; }
+.tempo-proof-root.is-compact .tempo-dialog { padding: 15px; transform: scale(.86); }
+
+@media (hover: hover) and (pointer: fine) {
+  .tempo-button:hover:not(:disabled) { filter: brightness(1.04); }
+  .tempo-icon-button:hover:not(:disabled), .tempo-fab:hover:not(:disabled) { filter: brightness(1.04); transform: translateY(-1px); }
+  .tempo-link-button:hover { background: color-mix(in srgb, var(--tempo-color-action-primary) 13%, transparent); color: var(--tempo-color-text-primary); }
+  .tempo-split-button button:hover:not(:disabled) { background: color-mix(in srgb, var(--tempo-color-on-action) 8%, transparent); }
+  .tempo-button-group button:hover:not(:disabled), .tempo-toggle-button:hover:not(:disabled) { color: var(--tempo-color-text-primary); }
+  .tempo-tabs__list button:hover:not(:disabled) { color: var(--tempo-color-text-primary); }
+}
+
+@media (max-width: 620px) {
+  .tempo-dialog__backdrop { padding: 12px; align-items: end; }
+  .tempo-dialog { width: 100%; max-height: calc(100dvh - 24px); border-radius: var(--tempo-shape-card-radius) var(--tempo-shape-card-radius) 12px 12px; }
+  .tempo-dialog__actions > * { flex: 1; }
+  .tempo-device__body { grid-template-columns: 1fr; }
+  .tempo-device__body nav { flex-direction: row; overflow-x: auto; border-right: 0; border-bottom: 1px solid var(--tempo-color-border-subtle); }
+  .tempo-device__body nav span { flex: 0 0 auto; }
+  .tempo-composer__footer { align-items: stretch; flex-direction: column; }
+  .tempo-split-button { width: 100%; }
+  .tempo-split-button button:first-child { min-width: 0; flex: 1; }
+  .tempo-feedback-row { max-width: 100%; }
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .tempo *,
+  .tempo *::before,
+  .tempo *::after {
+    scroll-behavior: auto !important;
+    animation-duration: .01ms !important;
+    animation-iteration-count: 1 !important;
+    transition-duration: .01ms !important;
+  }
+}
manual editsrc/ui-system/kits/tempo/tempo-recipes.tsmedium
An existing project-owned kit package is protected from incidental adoption replacement. Upgrade it through a separate source-locked package migration.

Review the package or evidence upgrade separately before replacing this protected project-owned file.

--- /dev/null
+++ b/src/ui-system/kits/tempo/tempo-recipes.ts
@@ -0,0 +1,57 @@
+export type TempoScreenRecipe = {
+  id: string;
+  purpose: string;
+  composition: readonly string[];
+  mobile: readonly string[];
+};
+
+export const tempoScreenRecipes = [
+  {
+    id: "tempo.dashboard",
+    purpose: "Decision-first overview with evidence, focus, and the next meaningful action.",
+    composition: ["compact chrome", "eyebrow + condensed display title", "bounded KPI strip", "grouped evidence", "focus cards", "attention list"],
+    mobile: ["two-column KPIs", "single-column cards", "bottom tab bar", "safe-area padding"],
+  },
+  {
+    id: "tempo.library",
+    purpose: "Browse and filter durable objects as a visual asset library.",
+    composition: ["search capsule", "segmented filters", "large tinted thumbnails", "short metadata", "compact resource actions"],
+    mobile: ["one-column assets", "full-width filters", "44px resource actions", "persistent bottom tab bar"],
+  },
+  {
+    id: "tempo.document",
+    purpose: "Inspect one object as an outcome-led working document.",
+    composition: ["compact back action", "identity + status", "outcome band", "current/build/next panels", "provenance timeline", "related objects"],
+    mobile: ["stacked identity", "wrapped resource pills", "single-column panels", "sheet editing"],
+  },
+  {
+    id: "tempo.agent",
+    purpose: "Run a bounded agent workflow with readable intelligence and proof of work.",
+    composition: ["command composer", "runtime KPIs", "intelligence document", "recommended moves", "operator envelope", "receipt list"],
+    mobile: ["full-width composer", "two-column KPIs", "stacked document and moves", "single-column runtime"],
+  },
+  {
+    id: "tempo.search",
+    purpose: "Search a corpus and return source-linked results.",
+    composition: ["prominent search composer", "result summary", "source metadata", "bounded result cards", "direct actions"],
+    mobile: ["stacked submit action", "single-column results", "wrapped metadata"],
+  },
+  {
+    id: "tempo.grouped-list",
+    purpose: "Review operational items, issues, receipts, or settings without passive dashboard clutter.",
+    composition: ["section label", "optional decision KPIs", "grouped rows", "leading state mark", "trailing action"],
+    mobile: ["reduced columns", "secondary metadata wraps or collapses", "44px rows and actions"],
+  },
+  {
+    id: "tempo.sheet",
+    purpose: "Edit or confirm one bounded decision without losing context.",
+    composition: ["blurred backdrop", "decision status", "condensed title", "clear fields", "persistent actions"],
+    mobile: ["bottom-aligned sheet", "safe-area inset", "44px close and actions", "internal scroll when needed"],
+  },
+] as const satisfies readonly TempoScreenRecipe[];
+
+export const tempoPresentationOwnership = {
+  kitOwns: ["tokens", "typography", "components", "shell", "screen-recipes", "responsive-behavior", "motion", "interaction-density"],
+  projectPreserves: ["data", "domain-state", "api-contracts", "command-actions", "persistence", "accessibility-semantics"],
+  legacyStylesheetAuthority: false,
+} as const;
manual editsrc/ui-system/kits/tempo/index.tsmedium
An existing project-owned kit package is protected from incidental adoption replacement. Upgrade it through a separate source-locked package migration.

Review the package or evidence upgrade separately before replacing this protected project-owned file.

--- a/src/ui-system/kits/tempo/index.ts
+++ b/src/ui-system/kits/tempo/index.ts
@@ -1,2 +1,2 @@
-export * from "./manifest";
-export * from "./tempo-components";
+export * from "./tempo-components";
+export * from "./tempo-recipes";
manual editsrc/ui-system/kits/tempo/README.mdmedium
An existing project-owned kit package is protected from incidental adoption replacement. Upgrade it through a separate source-locked package migration.

Review the package or evidence upgrade separately before replacing this protected project-owned file.

--- /dev/null
+++ b/src/ui-system/kits/tempo/README.md
@@ -0,0 +1,11 @@
+# Tempo project package
+
+Version 1.0.0-rc.4, derived from preserved source 7b256379176e2be38d42e9a4a8019b2a85cfdfeb890254deab224368dbc6515d.
+
+1. Load `../../tokens.css` before `./tempo.css` in the project root or global stylesheet boundary.
+2. Import stable `Kit*` contract components from `./index` through the project-owned UI facade.
+3. Map every meaningful view to `tempo-recipes.ts`; Tempo owns the complete shell and screen composition while product behavior remains outside this directory.
+4. Remove legacy project stylesheet authority after recipe coverage. Never layer Tempo as a cosmetic override over the old UI.
+5. Run the project validation commands and inspect mobile, desktop, keyboard, reduced-motion, loading, empty, error, and disabled states.
+
+This directory may be replaced by a later Studio-generated Tempo package. Product screens should not deep-import its internal files.
manual editsrc/App.tsxmedium
Separate product behavior from the current visual composition, then rebuild the complete screen through the selected kit shell, screen recipes, components, typography, spacing, materials, motion, and responsive rules. The existing layout and class structure are not compatibility requirements. Preserve 29 state and 3 network signals.

Preserve data flow, event handling, routing, state, copy, and accessibility semantics; verify that no legacy presentation authority survives after the screen recipe is applied.

Exact code is intentionally absent until an implementation agent inspects this product file.
manual editsrc/tempo-runtime.cssmedium
Retire this legacy presentation layer from runtime or replace it wholesale with the selected kit composition. Do not append an override stylesheet. Preserve only non-visual behavior that is genuinely encoded here. Preserve 0 state and 0 network signals.

Preserve data flow, event handling, routing, state, copy, and accessibility semantics; verify that no legacy presentation authority survives after the screen recipe is applied.

Exact code is intentionally absent until an implementation agent inspects this product file.
manual editscripts/sync-workspace.mjsmedium
Separate product behavior from the current visual composition, then rebuild the complete screen through the selected kit shell, screen recipes, components, typography, spacing, materials, motion, and responsive rules. The existing layout and class structure are not compatibility requirements. Preserve 0 state and 0 network signals.

Preserve data flow, event handling, routing, state, copy, and accessibility semantics; verify that no legacy presentation authority survives after the screen recipe is applied.

Exact code is intentionally absent until an implementation agent inspects this product file.
manual editscripts/validate-ui-system.mjsmedium
Separate product behavior from the current visual composition, then rebuild the complete screen through the selected kit shell, screen recipes, components, typography, spacing, materials, motion, and responsive rules. The existing layout and class structure are not compatibility requirements. Preserve 0 state and 0 network signals.

Preserve data flow, event handling, routing, state, copy, and accessibility semantics; verify that no legacy presentation authority survives after the screen recipe is applied.

Exact code is intentionally absent until an implementation agent inspects this product file.
manual editindex.htmlmedium
Separate product behavior from the current visual composition, then rebuild the complete screen through the selected kit shell, screen recipes, components, typography, spacing, materials, motion, and responsive rules. The existing layout and class structure are not compatibility requirements. Preserve 0 state and 0 network signals.

Preserve data flow, event handling, routing, state, copy, and accessibility semantics; verify that no legacy presentation authority survives after the screen recipe is applied.

Exact code is intentionally absent until an implementation agent inspects this product file.
Validation

Observed project commands

npm run typechecknpm run build
Rollback boundary

Branch before change

Apply changes only on a new source-control branch.

Keep the preserved project snapshot and every original file hash unchanged in Studio.

Revert the application commit or close the branch if validation fails.

Do not deploy or merge until project-specific visual and behavioral review passes.

Application runValidated on studio/projects-experiment-38447a47

4 files committed at 8a270e5e6e9b · 2 checks passed.

Production · VercelLive from 0f0c707ff070

Studio verified the existing production lineage and provider deployment.

https://projects.scottelling.comdpl_8gnPhqYATi1wevGy59yGVbwzbmzF
Exact Patch AgentTurn every manual boundary into reviewable code.

Studio will build a provider-neutral prompt from only the listed source files. Agent output returns here as draft replacement content and still requires your file-by-file approval.

The selected kit remains needs-review. Studio can prepare the architecture, but it will not claim implementation readiness.