Frame reference guide

AI interaction patterns for trustworthy products

Use these patterns to design AI interfaces that show progress, preserve user control, explain uncertainty, and recover when an answer or action goes wrong.

Each pattern names the user problem, the recommended interaction, and its current implementation status. Open the full Frame chapter to inspect live examples and the shared React components behind them.

Implementation status

Status is descriptive, not a promise: shipped means source material exists in components; partial means pieces exist with gaps; placeholder means the interaction is specified but no dedicated component ships yet.

Conversation layout — Prompt / Reply / Grow

Shipped

Use it when

An AI conversation needs a predictable structure for the prompt, the reply, and useful next steps.

Pattern guidance

  • Keep the user prompt, assistant reply, and follow-up actions visually distinct.
  • Use the same spacing and component vocabulary across every conversation turn.
  • Keep retry and other recovery actions available on assistant messages.
Read the full pattern

Streaming

Shipped

Use it when

The model produces tokens incrementally (200ms–10s). Users need to (a) see progress immediately, (b) stop generation, (c) trust that "no movement" means done, not stuck.

Pattern guidance

  • Render tokens as they arrive (no buffer-then-flush) inside the ChatMessage assistant bubble.
  • Show a 3-dot brand-gradient loader (.loading-dot) when the stream hasn’t started yet (network round-trip).
  • Token arrival itself signals liveness — no extra blinking caret needed during active streaming.
  • Show a Stop affordance in the composer while streaming; collapses to Send when done.
  • Entry animation uses var(--motion-duration-normal) var(--motion-ease-decelerate) — see .message-appear.
Read the full pattern

Citation

Partial

Use it when

Model output makes claims that need to be traceable to source material (a doc, an email, a meeting transcript, a URL). Without citations, every AI claim has the same trust level — which means none.

Pattern guidance

  • Inline citation: a .type-footnote chip with a leading source-type icon (File, Mail, Calendar, Link). Click → opens the source in DetailPanel.
  • Multiple citations stack with gap-1, never wrap mid-sentence — push the entire group to end-of-paragraph if needed.
  • Source preview: hovering surfaces a Tooltip (dark, --color-tooltip-bg) with the source title + 1-line excerpt.
  • Use --color-accent-blue for the citation chip text — consistent with link styling, marks it as a navigation affordance.
Read the full pattern

Confirmation

Shipped

Use it when

The agent is about to take a side-effecting action (create ticket, send email, run script). Doing it silently breaks user agency; asking before every action breaks the autonomy promise.

Pattern guidance

  • Side-effecting actions render a MessageCard variant (ticket, schedule, meeting) with a preview + explicit confirm/cancel pair.
  • Confirm = PrimaryButton (gradient — one per card). Cancel = TertiaryButton.
  • Reversible read-only actions (search, summarize, draft) skip confirmation — go directly to result.
  • After confirm, the card collapses to a StatusTag row showing the result.
  • "Don’t ask again for X" is reserved for repeated identical actions in the same session — never persist across sessions without explicit consent.
Read the full pattern

Undo

Use it when

Even with confirmation, the agent’s action lands in the real world (ticket created in Linear, email drafted in Gmail). User needs a way to back out within seconds without going to the source system.

Pattern guidance

  • After a side-effecting action lands, the resulting MessageCard shows an Undo affordance for N seconds (default 10s for low-stakes; 30s for irreversible-ish like sending email).
  • Undo countdown uses a token-exempt loop duration (10s/30s — like progressFill bespoke timing).
  • Undo collapses the card to "Reverted" StatusTag (neutral variant).
  • After the window expires, the Undo affordance is replaced by a permanent link to the action source ("View ticket in Linear").
Read the full pattern

Confidence / Status

Shipped

Use it when

Not every AI output is a finished result — some are guesses, some are partial, some are awaiting user input. A flat list of messages hides this distinction; users default to either over-trusting or never trusting.

Pattern guidance

  • Use StatusTag (8 variants) on every AI-produced artifact card to signal where it sits.
  • Map: generating → in-progress (blue). Finished, awaiting user → in-review (amber). Approved → success (green). Rejected/failed → failed (red). Sent to external system → submitted (violet). Blocked on missing input → pending (orange).
  • Never use raw probability numbers ("87% confident") — they imply false precision. Use the categorical pills.
  • Insight-level content uses InsightCard ("Maya’s insight") as a higher-confidence signal — reserved for synthesized observations, not raw output.
Read the full pattern

Tool transparency

Use it when

The agent calls tools (search docs, query Calendar, run code). When something goes wrong — or even when it goes right — the user needs to know what the agent did, not just the final answer. Black-box agents collapse trust the moment they’re wrong once.

Pattern guidance

  • Every tool call emits a collapsed TaskProgressCard-like row inside the ChatMessage: tool icon + tool name + 1-line input summary + status.
  • Default state: collapsed. Expanded view shows full input + output (truncated to 200 lines, "View raw" link for the full payload).
  • Multiple tool calls in one turn stack vertically inside a single card with StepIndicator glyphs (done/in-progress/pending).
  • Failed tool calls surface inline immediately (red StatusTag), not folded into the agent’s natural-language response.
  • Use --color-accent-violet for the agent’s "calling tool X" header to distinguish from user-initiated actions.
Read the full pattern

Hallucination / Uncertainty disclosure

Use it when

The model produces confident-sounding output even when its source coverage is thin or it’s interpolating. A flat assistant bubble makes "confident facts" and "best guesses" look identical — users either over-trust everything or learn to under-trust everything.

Pattern guidance

  • Three uncertainty grades, three visual signals — Cited fact: claim has a Citation chip per the Citation pattern. Default trust level.
  • Synthesis: model’s interpretation of cited material (no chip needed). Standard body text.
  • Inference / guess: model has no source and is interpolating. Render in text-text-tertiary with an inline marker chip (StatusTag neutral variant + ? icon) immediately preceding the inferred sentence/clause.
  • "I don’t know" mode: when the model can’t answer with reasonable confidence, the assistant message is a single short paragraph naming the gap and offering a path forward (e.g. "I don’t have access to that calendar yet — connect it on Connectors to ask again."). Voice from Voice & Tone → Style guide; ladders into the Refusal & safety pattern.
  • Never show probability numbers ("87% confident"); they imply false precision. Categorical pills only — same rule as Confidence / Status.

Status detail

Placeholder. No component yet. The pieces (StatusTag, citation chip prototype, tertiary text token) all exist; spec reserved here so the first hallucination-aware flow converges on this shape rather than re-inventing.

Read the full pattern

Memory surface

Shipped

Use it when

An assistant that "remembers you" is hostile if the memory is invisible. Users can’t verify what’s stored, can’t correct stale facts, can’t remove sensitive entries — and silently-applied memory feels manipulative even when it’s working.

Pattern guidance

  • Dedicated MemoryPage (src/components/MemoryPage.tsx) lists every stored memory grouped by kind. Three kinds: Core — always-on user facts (role, language, locale). Preference — how-to-collaborate guidance ("reply in Chinese", "skip the summary"). Project — facts scoped to a specific project.
  • CRUD must be inline + immediate. Add via inline MemoryForm, edit/delete on each row, no modal indirection. Memory writes that need protection pop a PasswordModal; never silent.
  • In-conversation reference: when the assistant uses a memory in its reply, surface the trigger inline as a citation-style chip (same pattern as Citation) so users can click → MemoryPage → verify or edit. (Not yet shipped — see status note below.)
  • Filter chip row: FilterChip for All / Core / Preference / Project lets users browse what’s stored without a search. Empty state per Voice & Tone → Empty-state copy.

Status detail

Shipped — see src/components/MemoryPage.tsx. The in-conversation memory citation chip is the next gap; promote alongside the Citation pattern when the second consumer of the chip primitive appears.

Read the full pattern

Retry / Regenerate / Fork

Partial

Use it when

The model’s first attempt isn’t always right — wrong tone, missed constraint, hallucinated detail. Users need a low-friction escape to "try again" without re-typing the prompt or losing the original.

Pattern guidance

  • Retry button sits on the assistant message toolbar (FeedbackBar in ChatMessage.tsx, far right after Bad). Clicking re-runs the same prompt with fresh sampling. Always present on assistant messages — see the locked toolbar order in Conversation layout.
  • Regenerate-with-mode (proposed): a small caret next to Retry expands a popover with single-tap mode shifts — more concise / more detail / change tone / try a different angle. Each prepends a system instruction to the regen call. Closing the popover defaults to plain Retry.
  • Fork, not replace: a regenerated reply lands as a sibling of the original assistant message, with both reachable through a 1/2 ▸ 2/2 navigator at the top of the bubble. Never destroy the prior reply — users often want it back after seeing a worse regeneration.
  • Regen counter cap: after 5 sibling regens for the same prompt, surface a hint to refine the prompt instead. Prevents low-value churn.

Status detail

Partial. Toolbar Retry button is rendered but no regenerate handler is wired up; click is a no-op today. Promote when the first regen flow ships.

Read the full pattern

Refusal & safety

Use it when

Sometimes the assistant can’t or won’t help — out-of-scope task, missing connector, destructive action, sensitive content. A flat "I can’t help with that" feels dismissive and severs the conversation. Refusals are part of the trust loop, not error states.

Pattern guidance

  • Refusal copy follows the Voice & Tone chapter — name the gap, offer the next step. Categories: Capability gap (no tool): "I can’t run shell commands yet — let me know what you’re trying to do and I’ll suggest the steps." · Permission gap (need user to connect): "I don’t have access to your calendar yet. Connect it on Connectors to ask again." · Policy refusal (destructive / irreversible): "I can’t move money for you, but I can draft the steps if you’d like." · Safety refusal (could harm user): handled with the same calm-structured tone — never lecturing, never moralizing.
  • Visually a normal assistant message, no special chrome. Refusals are part of the conversation, not error states. Do not wrap in a red error card — that conflates "I refused" with "system broke."
  • Optional StatusTag neutral with label "Out of scope" if a downstream surface needs to filter refusal turns later (e.g. analytics dashboard).
  • Never expose: stack traces, policy names, model IDs, or "as an AI language model…" preambles. The user needs the gap and the path forward, nothing else.

Status detail

Placeholder. No component-level affordance needed (it’s a copy/voice pattern). Voice templates pinned here; promote to a dedicated Voice & Tone section ("Refusal copy templates") when a second refusal surface appears.

Read the full pattern