Changelog

What changed in Candor UI, newest first.

How versions work

Candor UI follows semantic versioning for the kit as a whole:

  • Patch (1.0.1): fixes only. Safe to take.
  • Minor (1.1.0): new components, blocks, templates and optional props. Existing code keeps working.
  • Major (2.0.0): a change that can break code you installed, such as a renamed or removed prop. Each one is listed here with what to change.

Because shadcn installs copy the code into your app, you own it and can change it. To take a new version without losing your changes, install with npx candor-ui add, which records each item's version and file hashes in candor-lock.json, and later run npx candor-ui update: files you haven't touched are replaced, files you changed get a three-way merge, and where your change and ours touch the same lines you get conflict markers to resolve. Items you installed before 1.1 can join with npx candor-ui update <item> --from <version>.

Every version stays installable: https://candor.themethodic.com/r/v/<version>/<name>.json serves an item as that version shipped it, with the same licence check as the latest, and /r/versions.json lists the versions. Your licence includes every version of Candor UI we publish, major versions included.

1.9.0 - 2026-11-07

Reach and rigour: setup for every coding tool, a design system and fundamentals agents read, a lint for the honesty rules, honest motion, and components for the moments after an agent acts. With CLI 0.7.0; @candor-ui/react 1.9.0 carries the free changes.

A ninth principle: motion follows the outcome. Motion reports a change that happened and never predicts or decorates one. A success mark is drawn after the result settles, a rail fills only to the step that finished, and a state that was already final when the page drew does not animate. The Principles page has it.

For coding agents and their tools

  • Setup guides at /guides, one per tool: Claude Code, Cursor, Codex, VS Code with Copilot, Windsurf, Zed, Cline, OpenCode, JetBrains, v0, Lovable and Replit. Each gives the registry, the docs MCP server in that tool's own config (verified against the tool's documentation), the skill or the brief, and an example prompt. Linked from Get started, the footer and llms.txt.
  • Plugins. Claude Code: claude plugin marketplace add MethodicDev/candor, then claude plugin install candor-ui@candor installs the skill and the docs MCP server together (.claude-plugin/). Cursor, Codex and VS Code / Copilot packages under plugins/, with .cursor-plugin/marketplace.json and .github/plugin/marketplace.json (copilot plugin install candor-ui@candor). .mcp/server.json describes the docs MCP server for the MCP registry (com.themethodic/candor, remote, no auth). Every description says the server is docs-only and keyless. llms-install.md tells an agent how to set all of it up by itself. The public repo carries them.
  • A second skill, `skills/candor-agent-ui` (MIT, npx skills add MethodicDev/candor): design fundamentals for screens where an AI agent acts, in any framework and design system: the nine honesty rules with examples, approval ergonomics, the status vocabulary and the states of a tool call, folding turns, progress and finishing while away, blast radius and undo, AI disclosure, accessibility, and a review checklist to run after each screen.
  • A design system agents read: skills/candor-ui/references/design.md and /design.md, generated from the shipped CSS by scripts/build-design.mjs (every colour role and what it's for, the type scale, radius, shadows, motion, density, themes, how to re-token for a brand, and a do/don't per honesty rule); checked fresh in CI. The candor-ui skill gains an "After each screen" section pointing at the checklist and design.md.
  • Docs MCP: new tools get_design, get_review_checklist and get_brief (an item's brief, the same text the docs page's "Copy for a coding agent" copies).

CLI 0.7.0

  • `candor-ui mcp <tool>` and `init --mcp <tool>` add the docs MCP server (https://candor.themethodic.com/mcp, read-only, no key) as a server named candor to a tool's project-level config: Claude Code (.mcp.json), Cursor (.cursor/mcp.json), Codex (.codex/config.toml), VS Code (.vscode/mcp.json) and OpenCode (opencode.json). Existing servers, comments and formatting are kept, running it twice changes nothing, and doctor says which tools have it. Zed, Windsurf and Cline have no documented project-level file, so the command names them and points at their settings.
  • `candor-ui lint [paths…]` checks an app's .tsx/.ts/.jsx/.js files against the honesty rules: model-html (error: HTML rendered from a string in a file that shows a chat), disabled-without-reason, preselected-decision and estimate-as-measured (warnings). Prints path:line:col rule message, exits 1 on an error; --strict fails on warnings, --json for machines. Each rule is documented in the CLI README with the principle it serves. Candor's own source (the registry, blocks, templates, starter, site and docs examples) passes with one warning.

Free items

  • theme: data-motion="none" | "subtle" | "expressive" on <html> or any container, like data-theme. Subtle is the default and today's behaviour; none turns every animation and transition off, with or without the OS setting; expressive scales pops and lets a pressed locked control shake. Duration tokens --cd-dur-quick, --cd-dur-base, --cd-dur-slow (use as duration-(--cd-dur-base)), and keyframes collapse-down, collapse-up, pop-in, pop-out, tick-draw, fill, shake, attention. The reduced-motion rule stays last and wins. No new props anywhere in this list.
  • collapsible: content opens and closes to its measured height; ToolCall, Reasoning, FoldedTurns and a card's summary inherit it. Nothing moves on content that mounts open.
  • tool-call, step-list, status-dot: a mark whose status reaches done or failed while on screen draws its tick or cross; one that mounts finished is drawn still. StepList's rail fills under a finished step, never ahead of it. StatusDot pops into its final colour (too small for a drawn mark).
  • message-actions, copy-field: the copy icon stays until the clipboard write settles, then the tick (copied) or cross (failed) appears; data-copy carries the state.
  • context-meter: the ring sweeps to a new measured count and changes colour at the 80% line; neither the first render nor server markup sweeps from zero.
  • composer: mention and attachment chips added after the first draw pop in; a removed chip pops out as an inert ghost, hidden outright under data-motion="none".
  • locked: pressing a locked control keeps its reason open and emphasises a printed one; the shake exists only under data-motion="expressive". Activation stays blocked.
  • permission-prompt: pulses three times when a decision arrives, then stays still (and not at all while busy). A press on inert space outside it, the gesture that would dismiss a dialog, nudges it instead; nothing but Allow or Deny is ever a decision, as before.
  • thread: "Jump to latest" pulses once each time a message arrives while the reader is scrolled up, then stays still; the jump scrolls instantly under reduced motion or data-motion="none".

Pro items

  • hold-to-confirm (new): HoldToConfirm, a button held, by pointer or with Space or Enter, for holdMs (1200 unless the app says otherwise) before onConfirm fires. The bar fills from the clock; letting go early cancels and says so. The required reason (what it does, whether it can be undone) is read with the button; locked says why. Nothing pre-armed, no one-key path, never auto-focused.
  • undo-window (new): UndoWindow, an action just taken with Undo for exactly the window the server granted (until or seconds; no default, so without either the app's cannotUndo text shows and there is no Undo). Time left is read from the clock, so a slept tab catches up. Pending until onUndo settles, then "Undone." or "Couldn't undo: <the rejection's reason>"; when the window closes, "Can't be undone now." with what happened kept.
  • use-attention (new): useAttention, a count and a "(n) " title badge for what waits on a person or finished while they were away, cleared on focus, on the tab becoming visible, or by clear(). System notifications only with notify on and permission already granted (the hook never asks; requestPermission() is for the app's own button); sound only with a src the app passed and enabled: true. SSR-safe.

Site and repo

  • Home page: a "Built with Candor" strip naming OrgSmith and StoryWright as Methodic's products on Candor, and Methodic as a Salesforce consulting and AppExchange partner.
  • SECURITY.md: how to report a vulnerability privately, what's in scope, and how fixes reach installed components (npx candor-ui update).
  • The README quotes the measured counts; CI fails if the README or the Polar copy disagrees with src/site/stats.json.
  • docs/RELEASING.md is the release runbook, CLAUDE.md the repo's standing instructions, and npm run release:check runs CI's checks locally with a preflight on the version, the CHANGELOG and the package versions.

1.8.1 - 2026-11-06

Three accessibility and theming fixes a customer found in 1.8. No new props.

  • CommandPalette: the search box also carries aria-label (the search string). In 1.8 it was named only by cmdk's label, hidden with display: none; Chromium follows that through aria-labelledby, but not every accessibility tree does, and there the box had no name.
  • SessionList: each group ("Today", or your own "Needs you", "Projects") is a group named by its heading, so a screen reader can move between groups and says which one a row is in. This holds with virtualize too, where a group's heading can be outside the rendered rows. The visible heading is hidden from screen readers so it isn't read twice.
  • AppLayout: the panel's sheet is 448px wide (max-w-md), as it was meant to be. The sheet primitive's own sm:max-w-sm (384px) had won at every width the panel is a sheet.

1.8.0 - 2026-11-06

Links where things navigate, a palette and composer that say only what's true, agents that ask mid-call and connect to new servers, and plans that say when a run left them. New optional props and CLI 0.6.0; no breaking changes.

Shell and composer: a customer's asks

  • SessionList: a session with href (new on Session) is a real link, so it opens in a new tab, its address can be copied, and the open one is the current page to a screen reader. A plain click still goes through onOpen for your router; other clicks are the browser's. Without href, rows are buttons as before.
  • AppLayout: navOpen and onNavOpenChange control the sidebar sheet, so the app can close it when the person navigates. navButton={false} drops the built-in menu button for an app with its own top bar. The frame carries data-nav ("rail" or "sheet") and group/app-layout, so your own button can show only while the sidebar is a sheet (group-data-[nav=rail]/app-layout:hidden).
  • CommandPalette is named once: the dialog by its title, the search box "Search" and the list "Results" (new strings search and results). Before, a screen reader heard the title three times before the first item, and the list's name was cmdk's English "Suggestions" whatever the locale.
  • Composer's keyboard hints name only what's on: "@ mention" only with mentions, "/ command" (new string hintCommand) only with commands. hints={false} hides them.

Plan drift and autonomy (Pro)

  • Plan: drift. A step can say how the run left the approved plan, with drift on PlanStep ({ kind: "added" | "skipped" | "changed", note?, from? }), from your app's own records. Candor never works drift out by itself. - Each drifted step says so in words: "Added during the run, not in the approved plan", "Skipped by the run, though it was approved" or "Changed from the approved plan". A changed step can say what was approved ("Approved as: Backfill from billing country"), and any step can say why (note). - A line above the steps counts them: "The run left the approved plan: 1 step added and 1 step skipped".
  • Plan: autonomy on accept. With autonomy (a list of PlanAutonomyOption), accepting says what it allows. The person picks one before Accept is available; none is picked for them. onAccept gets the choice as its second argument. - Two options have built-in words: "askBeforeEdits" ("Ask before each edit") and "runWithoutAsking" ("Run without asking"). Your own take label and detail; locked says why one can't be picked.
  • The new words are in planStrings, so LocaleProvider can override them.

New optional props and fields only: a plan without drift or autonomy looks and accepts exactly as before. onAccept still gets the skipped steps first.

MCP servers that ask, and connecting one

MCP's 2026-07-28 revision stopped servers pushing questions at the client: a call that needs more comes back "input required", and the client asks the person and retries the call with the answers. Candor shows those questions with the components that already ask people things, and gives the person a straight account of a server before connecting it.

  • New: `mcp-input` (free lib): an MCP input_required result as Candor data, and the person's answers back as the retry. - mcpInputView(result, { server }) turns each of the server's inputRequests into an item, in the server's order. A form (elicitation/create) becomes questions in ClarifyingQuestions' shape: an enum (with or without titles, single or multiple) a choice, a boolean Yes or No, text and numbers a line. A field's title becomes the tag when it also has a description. A form with no fields, a plain "go ahead?", becomes a two-answer Decision for ApprovalDecision. - A link the server wants opened (URL mode: a sign-in, a payment) is a Decision too, with the page's host and its full address as facts, and a warning for an encoded (xn--) name or a page not on https. Nothing opens until the person accepts; an address that isn't http or https isn't offered at all. - The server's default is marked as its suggestion and never chosen for the person; a text field shows it as a hint and stays empty. A choice the server doesn't require offers "Leave unanswered" rather than assuming one. - What it can't show stays on screen as "not supported", with the method and why: a model prompt (sampling), a request for folders (roots), a form with a field it can't draw, or a kind newer than this file. Nothing is dropped. Answer those yourself and pass them as own. - mcpInputResponses(view, replies) checks the answers against each form's schema first (required fields, choices, whole numbers, minimum and maximum, length, email, URL and date formats, how many to pick) and returns the problems in words, or the inputResponses to send. Declined and dismissed requests are sent as such, and what wasn't sent is listed in notSent. - mcpRetryParams(original, view, inputResponses) is the original call's params with the answers and the server's requestState exactly as it came, and without an earlier round's state when this round has none. - The view keeps your app's name for the server (server) apart from the name the server gives itself (serverClaims), so the person sees who is asking as your app knows it. - Free items can't depend on paid ones, so the question and decision shapes are defined in the file; they fit ClarifyingQuestion and Decision as they are. The words are in mcpInputWords; pass your own.
  • New: `McpConnect` (mcp-connect, Pro): the card for connecting a new MCP server. - Where data goes first: the server's host, then its full address (or the command a local server runs with), and who runs it as your app knows it. Left out, it says the owner isn't known. - What it says it can do: each tool by name, with its title and description. A tool with no description says "Not described by the server". The server's own hints ("Says it only reads", "Says it can change or delete") are labelled as its claims, which nothing has checked. Past toolLimit (6), the rest fold behind "Show all". - What it asks for: each permission with a box nobody has ticked. One the server doesn't explain says "Not described by the server", never hidden. A permission it needs to connect is marked, and Connect stays locked, saying why, until it's ticked. onConnect gets only the ids the person ticked. - A list the server hasn't given is said to be unknown ("The server hasn't listed what it can do"), not shown as empty; an empty one says there's nothing. - How to disconnect later, always. An optional acknowledgement the person ticks themselves, busy and error. - Connect and Cancel are the same size and style, so neither is the easy way out. - Its words are under mcpConnect in LocaleProvider's strings.

New items only: nothing existing changes.

Usage that isn't metered yet (Pro)

  • UsageMeters: pending is work already done that the provider hasn't metered yet. It's drawn after what's used as a striped part of the bar and said in words ("about $1.10 not yet metered"), and read with the meter's value. mayExceedBy says how far a run may still go past the limit ("May go over the limit by up to $2."), from your own figure: a provider that checks the limit between steps, a run already under way. Both have …Text versions for server-formatted money. The meter never works either out.

React 19.3 and the React Compiler

  • SidePanel: keepTabs keeps tabs that aren't showing mounted, so switching back finds each as it was (its scroll, a half-filled form). On React 19.2 and later it uses React's <Activity>, so a hidden tab's effects pause until it's shown again; on 19.0 and 19.1 hidden tabs simply stay mounted. Without it, only the showing tab is rendered, as before.
  • CI now runs the React Compiler (1.0) over every component, block and template (npm run check:compiler), and fails if it has to skip one for a reason that isn't on a short, explained list (syntax the compiler can't lower yet, and demo code that writes a cookie in a click handler). Turn the compiler on in your app and Candor's components are optimised with it.
  • Candor still supports every React 19 release. Fragment Refs (19.3) would have meant dropping 19.0 to 19.2, so the Thread's focus handling stays as it is.

Claude Agent SDK adapter (free)

  • When the SDK summarises earlier messages to make room (compact_boundary), the thread now says so, as a system line between replies: "Earlier messages were summarised to make room. The agent works from that summary now; the messages above are unchanged." Before, a long session lost its history from the agent's view without a word.
  • A cleared conversation (conversation_reset, from /clear and similar) gets a line too: the agent doesn't see the messages above.
  • eventText gives these lines your own words, for other languages.
  • recalled on the adapter's state lists the memories the SDK recalled for each reply, by message id, with whose memory each is (personal, team or organization), ready for a MemoryUsed line in that message's footer.

CLI (candor-ui 0.6.0) and finding components

  • update <item> --moved a=b records the move even when the item is already at the target version; before, it said "already at" and wrote nothing, so the lockfile couldn't learn about a file moved between releases. A --moved naming a file no item here installed is refused, with the paths the lockfile does know.
  • add with a component's name (npx candor-ui add ApprovalDecision) says which item it lives in (approval-request) and installs nothing, instead of failing on a name no item has.
  • llms.txt and the docs MCP's list_components and search_docs name an item's other components: approval-request exports ApprovalRequest and ApprovalDecision. get_component_docs already found an item by any of its components.

Site

  • Honest vs. typical (/honest-vs-typical): four moments side by side, a plain rendering of what an agent UI typically shows beside the Candor component with the same data: an approval for a change that moved since it was asked for, a run that went quiet, a restore that missed a shell change, and a cost the SDK didn't report. Each links to the components' docs and the principle it keeps. No products are named.

1.7.0 - 2026-11-06

Agents that work while nobody watches, approvals that can be edited and answered from anywhere, memory that says what it used, and the next round of a customer's asks for the thread, messages and composer. New items, optional props and CLI 0.5.0; no breaking changes. One behaviour to know: candor-ui add --overwrite now stops before replacing an item you've changed.

Background work hub (Pro)

Runs that go on without the person watching, shown honestly: a run is never called running when nothing has been heard from it.

  • New: `RunHeartbeat` (run-heartbeat, Pro): a run's state that never claims it's alive on no evidence. - With lastHeardAt, a running run heard from recently says "Running · heard 1 minute ago". Silent for longer than staleAfter (two minutes unless set), it says "No update since 10:42" instead, and that it may have stopped. A queued or waiting run is quiet by design, so it doesn't go stale. - hostOffline ({ host?, since? }) says the machine or worker can't be reached ("Runner 3 offline since 10:40"), with the last update. It comes ahead of everything but a finished run. - A run that needs the person (status "waiting", or needs) says what for, with a Respond your app handles (onRespond). An offline run that needs the person still says what for. - Before the page has a clock (server rendering), it gives the last update's time and claims nothing. - Without lastHeardAt, the app isn't tracking freshness, and the status is shown as given, as before. - compact is one line, for lists. useLivenessWords() gives the same words for your own lists.
  • New: `SteerComposer` (steer-composer, Pro): send a message into a run while it works. - Each message says where it is: sending, sent but not picked up yet ("Build agent hasn't picked it up yet"), picked up (and when), or not delivered, with the reason and a Retry. A message isn't shown as read until your service says so. - onWithdraw takes back a message the agent hasn't read yet; one it has read can't be withdrawn. - The note above the box says when the agent reads messages (pickupNote, "between steps" by default), or that the run is quiet, its host is offline, or it's waiting on the person: a message doesn't answer what it asked. - A finished run takes no messages and says so. Enter sends; Shift+Enter breaks a line.
  • `lastHeardAt` and `hostOffline` on BackgroundTask, AgentActivityEntry and Session, with a staleAfter prop on TaskTray, AgentActivity and SessionList. A silent or offline run stops showing as running in each: - TaskTray counts it as "not heard from" instead of running ("1 running · 2 not heard from"), and its row says "No update since 10:42" or that its host is offline. - AgentActivity shows the same line instead of the agent's status. - SessionList shows it instead of the status with showStatus; without it, the dot stops pulsing and its label says why.
  • status (free) gains runLiveness(run, now, staleAfter), the RunLiveness and HostOffline types, and STALE_AFTER.
  • The "Unknown stays unknown" principle now names run-heartbeat.

New optional props, fields and functions only: existing code renders and runs as before. Data without lastHeardAt or hostOffline looks exactly as it did.

Approvals (Pro)

  • ApprovalRequest: edit before approving. With onApproveEdited and the request's new input (the change as it will run, as the agent proposed it), the person deciding can edit it as JSON first. They see exactly what they changed against the proposal, the same view PermissionPrompt shows for an edited tool call, and the edited change's own hash once it's worked out. "Approve as edited" calls onApproveEdited(hash, input) with that hash; an edit that changes nothing approves through onApprove as before. Nothing is pre-selected, and an edit to a change in production or one that can't be undone needs one more tick ("I've checked my edit") before it can be approved. The compact density never edits: it opens the full request, as before. hashOf works the hash out (by default changeHash: the sha256 of the input's canonical JSON); pass your server's own, and have the server recompute it from the input it receives. Once decided, decision.editedFrom (the proposed change's hash) shows "Edited before it ran", the words the tool call uses. With the Claude Agent SDK, the edited input is the answer's updatedInput.
  • New: `changeHash` (change-hash): the sha256 of a change in the same canonical form (keys sorted at every depth) in the browser and on the server, so an approval made against a hash can be checked where the change runs.
  • New types: ApprovalRequest.input and decision.editedFrom (both optional).

Blocks (Premium)

  • Approve from anywhere (new, approve-from-anywhere and approve-from-anywhere-claude): an agent that changes feature-flag rollouts, whose one write pauses the run until someone else decides, however long that takes. - The approver gets an email (EmailLayout) with the approval's id and the change's hash, and no link: they open the approval page themselves and type the id. The email says to check the hash on the page matches. - The approval page is made for a phone: the compact ApprovalRequest, which opens the full request to decide a production change or to edit the percentage first (edit before approving, above). The server works out the edited change's hash again and refuses the approval if it isn't the one the approver was shown, and only the percentage can be edited. Only the people it was sent to (people, through mayDecide) can decide it, never the person whose run asked. - The decision is data, so the run resumes whether or not the process that paused it is still there. On the Vercel AI SDK the run is its saved messages, resumed from them as soon as someone decides. On the Claude Agent SDK the waiting process polls the block's ApprovalStore; if it went away (a restart or a deploy), its heartbeat stops, and the decision is applied and the agent carried on from its session (the SDK's resume). Browsers re-attach to a Claude run from its stored events (ClaudeRunStore, attachClaudeAgentRun). Resuming is claimed with a conditional write, so one process does it. The approver's answer comes back as soon as the decision is recorded; the run carries on after, so a long or second approval never holds their request open. - The change applies once, exactly as approved, only if the flag hasn't moved since it was asked for. A request nobody decides expires after a day and the run is told. The person who asked can't approve their own run's write, including by answering the adapter's approval directly. - Data is kept in memory, or in Postgres in the anywhere_ tables; only Postgres survives a restart. Emails go to an outbox table for your mailer.

Memory (Pro)

  • `MemoryUsed` (new, memory-used): the line under a reply that says what it remembered, "Used 2 memories", which opens them, each with who taught it and when. onOpen makes each one a button (to open it in MemoryManager, say) and onManage adds "Manage memory". fromHistory says the reply also drew on past chats, which can't be listed, in words rather than as an empty or made-up list ("Used past chats" when that's all it used). forgotten counts memories it used that have since been forgotten, without words it no longer has. With no memory used it renders nothing. Put it in Message's new footer.
  • `MemoryMode` (new, memory-mode): memory on, paused or off, with what each does to the memories already kept. Paused always says they're kept and come back. What off does is the app's to say (whenOff, required): "kept", or "deleted", which asks first, says how many go (count) and offers "Pause instead". The mode shown is the one the app reported; pending, error and locked work as in PermissionMode.
  • `MemoryManager`: history (with chats when known) says agents also learn from past chats, which can't be shown or forgotten one by one here, and an empty list then reads "No saved memories." instead of "Nothing remembered yet." mode says above the list when memory is paused (kept, not used) or off.
  • `Message` (free): footer, a node shown under an agent reply after who checked it and above the actions row, for lines like MemoryUsed. MessageFooter takes it too. Without it, nothing changes.
  • know: the MemoryModeValue type ("on", "paused", "off").

New strings: memoryUsed and memoryMode tables; memoryManager gains emptyWithHistory, history, historyCount, paused and off.

Thread, messages and composer (free)

  • Thread: anchor opens it at an element in it (a chapter divider, the card that needs the reader) instead of the bottom, and waits for the reader instead of following; scrollerRef hands over the scrolling element; onDrop takes files dropped on the thread; fade={false} turns off the soft fade at the bottom.
  • Message and MessageHeader: density="compact" draws a 12px name in a 24px row.
  • Message and MessageContent: bareCode shows code and other payload blocks without their frame.
  • ThreadEvent: align="start", a left-aligned line (right-aligned in RTL) with the time at its end; pair it with 1.6's timeFormat="time" for a clock time.
  • FoldedTurns: header replaces the dashed line with your own (a card with a title, how it ended, its cost), with Show and Hide after it.
  • Composer: - A held state, { kind: "held", reason }: typing stays open, sending waits, and the box says why ("Your partner is still thinking"). locked still disables the box. - textareaRef to focus the box from outside. - sendShape="round" for a round send button.

CLI (candor-ui 0.5.0)

  • add --overwrite stops before shadcn replaces an installed item you've changed: it names the files, installs nothing, and says to bring them up to date with update (which merges) first. --replace-edits replaces them anyway; it isn't passed to shadcn.
  • When shadcn writes nothing (it stops if a file it would update has your changes, and still exits 0), add says so, names that file, records nothing for those items and exits 1. Before, it said the items were "already installed".

1.6.0 - 2026-11-06

From a team building on Candor: what got in their way, fixed. Strict TypeScript, moved files, money your server formats, absolute times, and the accessibility statement. New optional props, one new free item and CLI 0.4.0; no breaking changes. One thing to do if you use Inter: see "Inter is its own item".

Strict TypeScript

  • The kit, blocks and templates compile with exactOptionalPropertyTypes on. Every optional property is written ?: T | undefined, so passing a value that may be undefined to an optional prop works under both settings, and lock-reason maps (ToolSetting.locked, RestoreDialog, ReviewLadder, NotificationPreferences) accept undefined. CI checks it (npm run check:exact).
  • It also compiles with noImplicitOverride: the error boundaries in card-registry and a2ui-surface mark state and render as override.

Messages and times

  • Message, MessageHeader, MessageFooter and ThreadEvent take timeFormat: "relative" (the default), or an absolute "time", "date" or "datetime" in the locale, for every time the message shows.
  • Message takes meta: false hides the "You · time" line under a person's message, a node replaces it.
  • message-content installs safe-markdown, and the docs name plainBlocks and markdownBlocks as the way to build a message's blocks. Import them from safe-markdown: they run on the server too.

Money as your server wrote it

  • When your server decides what's exact and what's "about", give it the text and Candor shows it as written, never rounding or summing: UsageMeters (usedText, limitText, estimateText, read to screen readers through aria-valuetext), RunReceipt (totalText, and a model's costText), RunEstimate (costText), SpendBreakdown (totalText) and StatCard (valueText). The numbers still place bars and markers.

Toasts

  • Toaster takes offset (or the --cd-toast-offset token) to keep clear of something fixed at the bottom, such as a chat composer.
  • When the focused control is under the stack anyway, the stack moves to the top (WCAG 2.4.11). Your own className="bottom-…" on Toaster still wins over the default position.

Inter is its own item

  • The theme no longer imports Inter. It's the new free item font-inter (styles/candor-font-inter.css, OFL-1.1), so a project can keep its own font and its own font licences. Without it, --cd-font falls back to the system's sans-serif.
  • candor-ui init adds and imports it unless you pass --no-font. If you update the theme and want Inter, run npx candor-ui init again (it adds the font and changes nothing else), or npx candor-ui add font-inter and import styles/candor-font-inter.css after candor.css. update says so when it takes Inter away, and doctor notes when text uses the system font.

CLI (candor-ui 0.4.0)

  • update follows a file you moved: it finds it when it's still exactly as installed, or you say where with --moved <installed path>=<where it is now>, and candor-lock.json remembers the move. A move must stay inside the project and keep the file's name, so a typo can't merge Candor's file into another one.
  • An item with a file update can't find is now left alone: nothing of it is written, it stays at its old version, and update exits 1. Before, it moved on without that file.
  • verify, for CI: checks candor-lock.json against the registry's versioned snapshots and the files on disk, without running shadcn. Files you changed are listed, not failed.
  • Installing several items at once no longer leaves some unrecorded: small differences shadcn makes in a batch (a "use client" line, trailing spaces) count as the same install.
  • init --no-font, and doctor flags a monorepo's shared package that still points at the old GitHub registry.
  • The Get started guide now covers the lockfile, merges, --dry-run, --from, --moved and verify; /docs/cli and /docs/updating lead there.

Accessibility (WCAG 2.2 AA)

  • A public accessibility statement at [/accessibility](https://candor.themethodic.com/accessibility): WCAG 2.2 AA as the target, what's checked on every release, and the known gaps (no screen-reader pass by people yet).
  • Focus rings that carry focus on their own go from 40-50% to 80% opacity, meeting 3:1 on every surface in every theme; npm run theme now measures the ring as drawn, blended at 80%.
  • The chosen segment in segmented controls has a 1px edge as well as its fill.
  • RoleMatrix, DataRetention, ComparisonView, RunLog, MetadataDiff's file body, DataImport's preview, RestoreDialog's checks, TestResults' stack traces and Diagram can be scrolled from the keyboard. They're new tab stops: a test that counts Tab presses through these components needs one more for each.
  • AppSidebar takes help, which puts Help in the same place on every page (3.2.6).
  • A test fails on any new button or link smaller than 24px without a hit area (2.5.8).

1.5.1 - 2026-11-06

Two fixes, both found by a team building on Candor.

Toast

  • A fifth toast no longer pushes out an error. The oldest toast that isn't an error goes first, the newest always shows, and errors stay until someone dismisses them, as the docs always said. Before, the toaster kept the newest four whatever their tone, so four notices after a failure hid it. Errors can now stack past four, so the stack scrolls once it reaches the top of the screen, keeping the newest in view. capToasts is exported for tests.

CLI (candor-ui 0.3.1)

  • init and key (including key --remove) move @candor off the pre-launch raw GitHub address (raw.githubusercontent.com/…/public/r/) to the public registry, and say so. Before, they kept it, so a project set up before launch stayed frozen at that commit and needed a GitHub token. --registry-url still sets any address you choose.
  • On the old address, add and update stop and say to run init, rather than sending your licence key to GitHub. doctor flags it (in a monorepo's shared package too) instead of saying "All good", and skips checking the key.

1.5.0 - 2026-11-05

Approvals that last, AI that says it's AI. Seven new items (two free), durable approvals and the full Claude Agent SDK permission model in the adapters, and the CLI in monorepos. New optional props and functions only: existing code renders and runs as before. Two type changes widen what's accepted (AuditAction, ClaudeApproval); see below.

Durable approvals and runs (free adapters)

  • Claude Agent SDK, server half (claude-agent-server): - Permission requests wait in an ApprovalStore instead of only in process memory. memoryApprovalStore() is the default; pass your own (a table, a queue) as approvals, so any instance can take the answer. waitByPolling helps a store that can't push. Answering and closing each happen once, whichever comes first, so a late answer to a run that already ended is refused, not lost. - claudeCanUseTool({ approvals, emit, owner }) is the same canUseTool outside streamClaudeAgent, for an agent run by your own worker or workflow engine. Answer it from your own route, such as a workflow signal. - answerClaudeApproval(store, id, answer, { owner }) records the full answer: allowed, denied, allowed with an edited input, answers to the agent's questions, and the "always allow" rules the person picked. It checks the answer fits the request (an edited input must be an object; only offered rules can be kept). The 1.4 answerClaudeAgentApproval still works, synchronously, on the default store. - A ClaudeRunStore (memoryRunStore()) keeps a run's events. With detach, a run carries on when the browser goes away, and attachClaudeAgentRun streams it again from any point, then live. stopClaudeAgentRun stops it. Responses carry the run's id in x-candor-run-id. onEvent hands you every event to keep for replay, and prompt records what the person sent as the run's first event. A run store creates a run only on start, and memoryRunStore forgets only finished runs (oldest first, past maxRuns), so a live run keeps its owner. It keeps up to maxEvents (5,000) per run, after which partial-text events are dropped. - approvalTimeoutMs closes a request nobody answered as expired: the tool is denied, never allowed. Set one with detach, or a run can wait for ever. - editable(tool, input) says which tools' input a person may edit (every tool by default). An edit to any other tool is refused by the server, whatever the browser sends, and the request tells the browser (editable: false). - The input a person edited is recorded: candor_approval_closed carries it, and the tool card and any replay show what ran, marked "Edited before it ran", with the agent's proposal beside it. - A stop and an answer at the same moment: the request closes as stopped, and the answer is refused, so the record never says "approved" for a tool that didn't run. - setClaudeAgentPermissionMode(runId, mode, owner, { allowed }) changes a running agent's mode (the SDK's setPermissionMode, streaming input only) and tells the browser. The route enforces policy: modes outside allowed, or the run's modes, are refused, so a mode PermissionMode shows as locked can't be set by posting to the route directly. - The agent still runs in the process that started it: a store makes the answer and the record durable, not the run.
  • Claude Agent SDK, browser half (use-claude-agent): initialEvents replays a stored conversation; attachUrl and runId re-attach to a run after a reload (attach() does it on demand); onRespond answers through your own route instead of approveApi, which is now optional; stopApi stops a detached run; modeApi, permissionMode and setPermissionMode. respond takes a fourth argument with updatedInput, answers and alwaysAllow. Requests now send a promptId too: pass it to the server's prompt, and re-attaching with initialEvents replaces the run's events instead of adding them again.
  • Vercel AI SDK 7 (ai-sdk, use-candor-chat): runs on WorkflowAgent stream the same UI messages, so they work as they are. A tool waiting for approval stays waiting for as long as the workflow does, and an approval made automatically is marked on the tool call. Re-attaching after a reload is the AI SDK's own WorkflowChatTransport.

The Claude Agent SDK's permission model (free)

  • AskUserQuestion becomes questions: ClaudeApproval.questions, shaped for ClarifyingQuestions (claudeQuestions). Answers go back as the SDK expects, keyed by the question's text, several choices joined with ", ", the person's own words used as typed.
  • ClaudeApproval also carries suggestions (the SDK's "always allow" rules, offered and never kept unless the person picks one), noAlwaysAllow, mode, displayName, agentId and mcpServer. claudeSuggestions turns the rules into plain parts to show. claudePermissionResult(request, answer) turns an answer into the SDK's PermissionResult, wherever the answer lands.
  • fromClaudeAgentEvents reports permissionMode from the SDK's init and status messages and the new candor_mode event.
  • New: `PermissionPrompt` (permission-prompt, free): a tool call waiting on the person. It says why it reached them and the run's permission mode, lets them edit the input as JSON and shows exactly what they changed before "Allow as edited", offers "don't ask again" rules as boxes nobody has ticked, and opens on Deny when the request asks. Nothing carries over from one request to the next. A rule that switches to "Allow everything" or "Automatic" says what that hands away. When the agent asks questions, it shows them as a short form with nothing chosen. The Claude gallery demo uses it.
  • ToolCall takes approval: { by: "person" | "automatic", note } and marks a call "Approved automatically".

AI disclosure

  • New: `AiDisclosure` (ai-disclosure, free): tells people they're interacting with an AI system, as a first line in the thread or a compact "AI" badge for a header, with the app's own explanation. The agent is named only by the app. It helps with EU AI Act Article 50(1); it makes no legal claim, and whether it's enough is the deployer's call. Free by the owner's decision, as part of "a good agent chat".
  • New: `AiContentLabel` (ai-content-label, Pro): "AI-generated", "AI-modified", "No AI used" or "AI involvement not recorded", after the two categories in the EU's voluntary Code of Practice on marking and labelling AI-generated content (final, 10 June 2026): fully AI-generated and partially AI-modified. The level comes from the app's records, never a guess, and the mark is Candor's own. The label goes with the content: provenanceText, withProvenance for copied text, provenanceMetadata (ai:involvement, ai:by, ai:at) for downloads and sidecar files, and useCopyWithProvenance.
  • A docs page, "Telling people they're talking to an AI", says what the components do and what stays the deployer's.

Checkpoints and an honest restore (Pro)

  • New: `CheckpointList` (checkpoint-list): restore points per turn, what each holds (files, conversation or both, with counts when known), who made it, and where the work is now. A checkpoint that can't be restored stays listed with its Restore locked and the reason.
  • New: `RestoreDialog` (restore-dialog): restore code, conversation or both, with nothing chosen for the person. It says what a restore doesn't undo: shell commands run since (listed when the app knows them, or "not recorded"), changes made outside the agent, and effects such as sent messages and deployments. After a restore, RestoreResult shows what the app checked, item by item: "Restored" only when every check matched, a mismatch flagged with what differs, and "not checked" said as such. Restoring both needs a check of each: one that covers only the files is "Partly checked", naming the conversation (RestoreCheck.area).

Approvals (Pro)

  • ApprovalRequest: - target and values: a change to one thing shows each value before and after (from: null reads "Not set"; a from left out reads "Not recorded"). - restrictedTo: { who, reason }: a request only certain people may decide carries a badge saying who, with the reason shown and read to screen readers. - density="compact" for a narrow column, a phone or a notification: the values or summary, where, how many things it touches, the hash, expiry and what happens if nobody answers. Nothing is pre-selected. A change that can't be undone, or that changes production, has no Approve there; onOpen opens the full request.
  • New: `TypeToConfirm` (type-to-confirm): a step that can't be undone, confirmed by typing a name exactly. Pasting is allowed. DataControls now uses it; its string keys are unchanged.
  • New: `PermissionMode` (permission-mode): the agent's permission mode with what each mode allows written out, as a list or a compact toolbar button. "Allow everything" and "Automatic" ask "Switch to …?" first; a mode your policy turns off is locked with the reason (enforce the same policy on the server). It shows the mode the agent last reported, never the one just picked. One tab stop; arrow keys move between modes, Space or Enter changes it.
  • ClarifyingQuestions: a choice can offer other (the person's own words, sent as text), a tag above the question, and during drops "Before I plan" from the heading when the agent asks mid-run.
  • Changed type: AuditAction is now open: Candor's known actions (KnownAuditAction) or any string. AuditLog takes actions to label and group your own; one with no label shows its code. An "Other" filter appears only when a row needs it. Code that switched over every AuditAction should add a default branch, and a Record<AuditAction, …> map in your code no longer type-checks: key it by KnownAuditAction, with a fallback for other actions.

AG-UI 1.0 (free)

  • STATE_SNAPSHOT and STATE_DELTA fold into state (on fromAgUiEvents and useAgUi). Deltas apply all or nothing; a patch that fails keeps the last good state and sets stateOutOfDate with the reason until the next snapshot.
  • ACTIVITY_SNAPSHOT and ACTIVITY_DELTA become activities, in arrival order. Steps map to plan (shaped like PlanStep) and status content to agentActivity (shaped like AgentActivityEntry), only when every status is a known word.
  • New applyJsonPatch, a small RFC 6902 patcher that never changes its input. fromAgUiEvents and useAgUi take agents and initialState.

Citations from tool output (free)

  • The Claude adapter fills Message.sources from Claude's own citations and web search results, and places [n] only where a citation is, never from numbers in the text.
  • Both the Claude and the AI SDK adapters take sourcesFrom(tool, input, output) to read sources from your own tools' results. The ToolSource type is in lib/chat.

Docs MCP server

  • /mcp speaks MCP 2026-07-28: no handshake, server/discover, the version checked on every request (MCP-Protocol-Version must match _meta, else 400 HeaderMismatch -32020), and an unsupported version answered with -32022 listing the supported ones. A 2026-07-28 request must send Mcp-Method, and Mcp-Name for the methods that name something (400 HeaderMismatch without them); both are checked against the body. GET and DELETE answer 405, and no session is kept.
  • Older clients (2025-11-25 back to 2024-11-05) work as before, with initialize, ping and batches.

Docs

  • New pages: "Coming from AI Elements" (which Candor item does each job, what Candor adds, and how it sits beside shadcn's Trace and Message Scroller), "Durable approvals for long-running agents" and "Telling people they're talking to an AI".

Accessibility (WCAG 2.2 AA audit)

  • Every control now has a pointer target of at least 24×24 px (2.5.8), including the Dialog and Sheet close buttons, radios, chip remove buttons, tree chevrons, the approval's hash and the small text buttons. The look is unchanged, except that ProseDiff's accept and reject buttons now sit further apart.
  • AppLayout's side panel can be resized without dragging: a click on the divider steps through its widths, and Home and End jump to either end (2.5.7).
  • SignIn: pasting a code with a space or a dash ("123 456") works. Thread keeps a focused message clear of the "Jump to latest" button (2.4.11).
  • Scrolling code, excerpts, result tables, chart tables, logs and the restore dialog's lists can be reached and scrolled from the keyboard. Popovers fit a 320px screen, and charts show a clear focus outline.
  • What's left (the focus ring's contrast, segmented controls, toasts over focused controls, a few wide tables) is listed for 1.6 in internal/ACCESSIBILITY-2026-11.md, with the public conformance statement.

CLI (candor-ui 0.3.0)

  • Monorepos (pnpm and Turborepo, packages/ui plus apps/web) now work: - init from an app puts the theme in the shared UI package next to its CSS, imports it with a correct relative path (it used to be resolved from where candor-ui was launched), and adds the registry to both components.json files. - The licence key reaches shadcn when it runs in or writes to the shared package. - Run at the repository root, init, add, update and key stop, name the workspaces and say which --cwd to use; doctor checks every workspace with a components.json. - A root .gitignore that already covers .env*.local counts; React given as catalog: and Tailwind in the shared package are found; install commands use the project's package manager (pnpm --filter … add …). - candor-lock.json stays beside the app's components.json; update run from the shared package says where it is.
  • doctor says which base the project uses (Radix, Base UI or React Aria). Candor's components are built on Radix, so on React Aria it says what breaks and which of the project's primitives Candor's would replace. It checks that cn resolves, including after shadcn's migrate cn to the cn package, and gives the line and the install command when it doesn't.

1.4.0 - 2026-11-05

The gaps the 1.3 blocks worked around, and a limit on the docs server. New optional props only: existing code renders as before, except that FailureClusters now prints a change of 0 as "0" instead of a dash (see below).

Knowledge and evals (Pro)

  • ProseDiff takes readOnly: the changes between two texts with no accept or reject, headed "Changes" with a count ("3 changes"), and onResolve is never called. Use it to show what changed between two published versions.
  • RetrievalInspector takes verb, the word before each passage's date, for what its readAt records: "Reviewed", "Published", "Synced". The default is still "Read". The knowledge-base blocks pass "Reviewed".
  • FailureClusters: - A cluster's delta can be null when there's nothing to compare with, shown as a dash and read as "change unknown". The new FailureClusterInput type (a FailureCluster whose delta may be null) is what it takes; FailureCluster itself is unchanged. - Changed: a change of 0 is now printed "0" and read as "no change since last period". Before, it showed a dash, the same as you'd now see for an unknown change. - since says what the change is measured against ("production's run"), read after "more than" and "fewer than". The default is "last period". New strings: since, unknown; more, fewer and noChange now take {since}. - The eval-dashboard blocks pass null before there's a production run to compare with, and since="production's run". Before, they showed no change. If your own code passed 0 to mean "nothing to compare with", pass null now. - FailureClusterInput lives in ops, so update ops with it: npx candor-ui update failure-clusters ops (a plain npx candor-ui update updates everything). candor-ui 0.2.3 says so when you leave one behind.

Docs MCP server

  • /mcp allows each client 120 messages a minute per server instance, counting every message in a batch, and answers past that with HTTP 429, Retry-After (readable from a browser) and a JSON-RPC error. A client is an IP address, or an IPv6 /64. createDocsMcpHandler takes rateLimit.

CLI (candor-ui 0.2.3)

  • update now says when an item it updates uses another installed item that stays on an older version, and gives the command to update it too. Before, it only listed dependencies you hadn't installed.

1.3.0 - 2026-11-05

Docs for coding agents

  • Every docs page is also Markdown at its address plus .md: /docs/<name>.md for every item, /patterns/<slug>.md and /get-started.md. A page has the item's tier, its install command, its example with the import paths your app uses, every prop, function and type, and the LocaleProvider string keys. llms.txt lists them, and each web page links its Markdown version in its <head> and from a Markdown button. "Copy for a coding agent" copies the same text. Pages are built from the docs data, never from source; the build fails if a line of a paid item's code would appear in one.
  • A docs MCP server at https://candor.themethodic.com/mcp, free and read-only, with no licence key. Coding agents can search the docs, read any page, list items by group, read patterns and setup, and suggest_components for a task, with one install command. In Claude Code: claude mcp add --transport http candor https://candor.themethodic.com/mcp. Other clients add the URL as a remote (streamable HTTP) server. The agent skill and the Get started page point to it.

Composer (free)

  • tools puts your own controls in the composer's toolbar, after the attach button.

Dictation (Pro)

  • DictationButton and useDictation (new, dictation) let a person speak into the composer. It's opt-in: put <DictationButton value={value} onChange={setValue} /> in the composer's tools. Words appear in the box as they're recognised, then settle, and stay plain text the person can edit before they send. Nothing sends by itself. Editing while it listens keeps the edit. Emptying the box (sending) stops it with either engine and drops what it hadn't written yet, so nothing lands in the next message.
  • Audio goes only where your app chooses. With transcribe(audio, { lang, signal }), it records and hands the audio to your own speech-to-text. Without it, it uses the browser's speech recognition only where that runs on the device. allowBrowserService opts in to the browser's own service, which in some browsers sends audio to the browser maker.
  • While it records, the button is pressed and red, the state is written beside it and announced, and Escape is never taken from the composer. Where dictation can't run, the button is hidden, or with whenUnsupported="explain" it is locked with the reason. A blocked microphone, no microphone, nothing heard, a network or language problem, and a failed transcription each say what happened. It stops after maxSeconds (120, counted from when it starts listening) and when disabled, and pressing it again while the microphone is starting cancels it. Unmounting releases the microphone, even one granted after the component went away. Every word is in dictationStrings, under dictation in LocaleProvider.
  • Why Pro: the free tier stays the chat core, and dictation is an add-on to it. A separate item keeps free installs as they are, since a free item can't depend on a paid one. The free Composer gains only the generic tools slot.

Blocks (Premium)

  • Knowledge base (new, knowledge-base and knowledge-base-claude): - An agent answers only from published help-article versions and cites the article and version. The code checks each citation against what search_articles actually returned earlier in the same run, not against a question the model supplies, and the answer card shows the question that search was for. - Retrieval is deterministic word scoring, shown in the retrieval inspector. - Questions it can't answer are recorded as gaps. - The changes it drafts publish as a new version only after a person approves, and only against the version they were written for. - On a second page, people edit articles under the same conflict rule, mark them reviewed and dismiss gaps. - Data is kept in memory, or in Postgres in the kb_ tables.
  • Eval dashboard (new, eval-dashboard and eval-dashboard-claude): offline regression evals for an agent's versions. - A suite of cases, checked in code, runs against a version through a runner you plug in (setCaseRunner). The demo replays recorded outputs and says no model was called. A case the runner couldn't run is recorded as that, not as a fail. - The page shows the pass rate over runs, a run's scorecard against production's latest run, failure clusters, and regressions and fixes case by case. A second page compares any two runs. - The agent reads, compares and starts runs. Promoting a version needs a person's approval, and the server still refuses it when the version has no finished run on the current suite, production moved since it was asked, or its latest run fails a case production passes, naming the cases. - Data is kept in memory, or in Postgres in the evdash_ tables.

1.2.1 - 2026-11-05

A patch: no component changes.

CLI (candor-ui 0.2.2)

  • Behind a proxy, key, doctor and update now send their own requests through HTTPS_PROXY (honouring NO_PROXY) on Node 24.14 or later. On Node 22.21+ and 24.0+, candor-ui says to set NODE_USE_ENV_PROXY=1; on older Node, to upgrade. shadcn already used the proxy.
  • update and the lockfile record matched each item file to an installed path by name and took the first match. When an item and one of its dependencies have a file of the same name, the file's type (ui, lib or component) now decides, and if that still leaves two, it stops rather than pick one.

Checks

  • CI now follows the Get started page in a brand-new Next.js app on every change: create-next-app, candor-ui init --yes, candor-ui add thread message composer, the "Render it" code as app/page.tsx, and next build, against that change's registry and CLI. It also checks that add recorded the items in candor-lock.json.

Docs

  • The agent skill explains theme nesting: dark and hc work on an inner element, light doesn't switch back inside a dark one.

1.2.0 - 2026-11-05

App shell (Pro)

  • CommandPalette takes filter, passed to cmdk as (value, search, keywords) => score. The new matchEveryWord is a strict matcher: every word typed must start a word of the item's label, detail or keywords, in any order, ignoring case and accents. It reads keywords only, never cmdk's value, which holds ids.
  • Fixed: closing CommandPalette now puts focus back on what had it when the palette opened. Before, a palette opened by a shortcut (no DialogTrigger) dropped focus to the page. A chosen item now runs after focus is back and the closing animation is done, so an item that moves focus (to a tab, a field) keeps it there. Before, the return could undo it. onCloseAutoFocus lets you steer focus yourself.
  • AppLayout takes resizable (default true). With resizable={false} the panel stays at defaultPanel's width and the divider is a plain border with no handle.
  • SidePanel: a tab can carry a count (hidden at 0), read by screen readers as countLabel ("2 need you") or the sidePanel.count string (joined to the name by sidePanel.countAfterName), with countTone="attention" for things waiting on the person. actions puts buttons at the end of the tab row (hide the panel, open full width), outside the tab list.
  • SessionList takes groupBy (your own headings instead of pinned and days) with groupOrder, and showStatus to print each session's status in words with its dot. Session gains detail (a second line, such as what it is doing now) and statusLabel (your word for the status). Without these the list looks as before.

Artifact panel (Pro)

  • ArtifactPanel (new, artifact-panel) reads a document or file beside the work, as a SidePanel tab or full width. A document's text is read into safe blocks with markdownBlocks(text, { headings: true }), or you pass blocks. It is never rendered as HTML, so markup in a model's document stays text and its links aren't made. A file with no text shows its type and size, and an image shows the preview your app serves. onDownload gets the artifact's id and the chosen format (formats: Word, PDF…), and onFullWidthChange adds Open full width / Back to the panel. headingLevel (default 2) fits the title into your page's outline. New types in work: Artifact and FileFormat.

Variants (Pro)

  • Plan takes density="compact" for inside a thread: a smaller header and tighter rows, and a step's summary only while it's running or waiting (the "now" line). actions puts buttons under the steps (Pause, Stop) in any mode, beside Accept when proposed.
  • UsageMeters: a usage's estimate (your own figure, such as a quote) is a tick on the bar, printed under it as "Estimate: 2.6 USD", or with your estimateLabel ("My guess"). Without a limit it is printed with no tick. The meter never projects an estimate itself.
  • ApprovalDecision (new export of approval-request) is a decision the work stops for, with two answers. It shows the question, the facts, what carries on meanwhile, and an optional deadline with what happens if nobody answers. The two answers are equal buttons. Nothing is pre-selected and neither is styled as the default. The agent's pick is marked, not chosen. It covers recording, decided and failed states, and locked gives a reason. New types in work: Decision and DecisionChoice.
  • A setup gate from AccountState: steps lists what's left as a checklist in the card, children holds your own controls, and an action can be locked with its reason until the steps are done. email and onSignOut are now optional (the signed-in line shows when both are set). SetupChecklist takes bare to sit inside another card.

New components (Pro)

  • AgentActivity (agent-activity) shows who is working right now: each agent, what it is doing in plain words, and its state in words beside the dot (your statusLabel, or the status's own). An agent that isn't working shows only its state, with nothing made up for it to be doing. It is a panel list, not TaskTray's pill of background jobs. New type in work: AgentActivityEntry.
  • FileList (file-list) shows what the work produced. Each file has its type, size and state ("Checked", "Not yet checked", with a tone), Open for files the reader can show, and Download (a menu when there are several formats). Files can be grouped ("Result 2", "Sources"), with group headings at headingLevel (default 3). Nothing is a link: open and download go through your handlers. New type in work: Deliverable.
  • ResultCard (result-card) is a finished piece of work, handed over. It shows the answer first, as safe blocks, then a receipt of your own values as given (cost, time, what checked it), then the files, then your actions. Lines you don't pass aren't shown. New type in work: ReceiptLine.

Long histories

  • Thread takes virtualize (free): only the messages in or near view are rendered, each child being one item keyed by its key. The column stays a log, the message holding focus stays rendered as it scrolls away (with its neighbours, so Tab still reaches the next one, and by key, so it survives older messages being added), and Jump to latest works as before. The server and the first browser render show the newest 20, so hydration matches.
  • SessionList takes virtualize (Pro): only the rows in or near view, with their group headings. It starts from the top before it can be measured. Search still covers every session, and the focused row stays rendered.
  • Both use useVirtualList (new free item, use-virtual-list), which keeps measured heights by key, so a list that grows at either end keeps its place. Without virtualize, nothing changes.

Project workspace template (Premium)

  • project-workspace (Vercel AI SDK) and project-workspace-claude (Claude Agent SDK) install under app/project-workspace/. Each project is a conversation with an agent team. The rail lists projects grouped into Needs you, Projects and Finished (SessionList groupBy, with each state in words and a live line). The thread is in the middle. A fixed panel (AppLayout resizable={false}, 404px) has four tabs: Overview (spend against the limit with the agent's estimate marked as its own, steps done, AgentActivity), Plan, Files (FileList, with ArtifactPanel as the reader and Open full width) and History. The ⌘K palette uses matchEveryWord and opens panel tabs.
  • The rules it shows, enforced on the server: - No run goes past a project's spending limit without the person. At the limit the next run is refused and a decision opens: raise the limit, or finish with what's done. - While a decision waits (ApprovalDecision, nothing pre-selected), the agent doesn't work on the project. An answer is recorded once. - Files say whether they've been handed over. - The result (ResultCard) puts the answer first. Its receipt comes only from what was recorded: spend against the limit, steps done, and time from start to hand-over.
  • Spend comes from the AI SDK's token counts at list price (a model with no price set is refused, since its spend couldn't be kept to the limit), or from the Claude Agent SDK's own total_cost_usd. That figure is the session's running total, so each run adds only its change since the last total seen for the session. A file saved again under the same name replaces the earlier text. An answered decision stays in the thread, decided, with focus on it.
  • Storage runs in memory with no setup, or on Postgres through Drizzle when DATABASE_URL is set (tables start with workspace_, with migrations and a seed). It is tested on both, including two answers racing and two questions asked at once.

1.1.1 - 2026-11-05

Fixed

  • Getting started works as pasted. /get-started has a "Render it" step, and every docs example is shown and copied with the import paths an app has after add (@/components/…, @/lib/…) and "use client". A test checks every example's imports against where shadcn installs each file.
  • The theme wins over shadcn's defaults. In a fresh Next.js app, the colour blocks and .dark variant that shadcn init writes came after the candor.css import and replaced Candor's theme (only system dark mode survived). candor-ui init --yes now comments out shadcn's default :root and .dark colour blocks and its dark variant; doctor flags them. Overrides you wrote yourself, and a dark variant of your own, are left alone, and overriding Candor's tokens after candor.css works as before.
  • `candor-ui add` records what it installs. Most fresh installs were reported as "already installed, with changes" and left out of candor-lock.json, so update couldn't merge them. The CLI is 0.2.1.
  • `candor-ui key` checks the key with the registry (10-second timeout) and says whether it's valid and for which tier; doctor does the same.
  • A paid item without a key now says to run npx candor-ui key <your key> and where the key is; errors carry a message so shadcn no longer prints "undefined".
  • One install story: docs pages, llms.txt and the skill use the candor-ui CLI, say where files land and which import paths to use, and the skill covers AG-UI. Twelve items' one-line descriptions now describe the item, not a helper in its file.

1.1.0 - 2026-11-05

Languages and right to left

  • LocaleProvider (new free item, locale) sets the interface's words, the locale for dates and numbers, and the text direction. Every fixed string in the kit (labels, buttons, empty states, screen-reader text, "just now") is in a table per component, English by default, and you override any of them by the component's name: <LocaleProvider locale="fr" strings={{ thread: { jumpToLatest: "Aller au plus récent" } }}>. Strings take {name} placeholders and plural forms, so overrides stay plain data. Each docs page lists its keys. TermsProvider still names your nouns.
  • Right-to-left layouts: components use logical spacing and edges (ms-, ps-, start-, border-s), directional icons mirror, tree, inbox and the layout's resize handle swap their arrow keys, and AppLayout's navigation and panel sheets slide in from the matching sides. Sheet's side stays the physical edge you name. dir follows the locale (Arabic, Hebrew, Persian, Urdu…) or is set on the provider; set it on <html> as well.
  • RelativeTime takes format="date" | "datetime" | "time" for an absolute time, and its words and units follow the locale.

Messages

  • Message takes actions: false hides the row under an agent reply (copy, retry, rating), a node replaces it. Either way the model and cost stay visible, on their own line (MessageAttribution).
  • Message's parts are exported: MessageHeader (name, detail, time, and a slot for more), MessageBody (reasoning, blocks, the streaming caret) and MessageFooter (stopped and failed states, sources, who checked it, actions), to rebuild a message your way. App content still goes in a card block, so messages stay plain data.
  • Thread pieces (free): ThreadDivider marks a day ("Today", "Yesterday", a date) or a chapter; ThreadEvent is a quiet centred line for something that happened ("Sam joined"), with an attention tone and a time, and system messages are now drawn with it; FoldedTurns folds a run of turns into one line with your summary and opens them in place.
  • Attachments (free, attachment-preview): a new attachments message block shows sent files as tiles, with an image preview when your app supplies a previewUrl, an icon by type otherwise, and the size in the locale. A file is never a link: onOpenAttachment on Message makes each tile a button. AttachmentChip is the composer's chip, AttachmentList a row of tiles. Attachment gains optional mediaType and previewUrl.
  • Composer attaches files that are pasted or dropped on it (with a drop target while dragging), and takes accept for the picker.
  • New block kind. MessageBlock has a new kind, attachments. If you switch over block kinds in your own code, the default branch the 1.0 notes asked for covers it; without one, TypeScript will point at the switch.
  • Syntax highlighting in Payload (free, highlight): code is split into tokens (keywords, strings, comments, numbers, keys, tags) and rendered as spans of text, never HTML, by a small built-in tokenizer for JS/TS, Python, SQL, JSON, shell, YAML, CSS, HTML/XML, Go, Rust, Java and C. Off by default so nothing changes: turn it on with <Payload highlight> or data-syntax="highlight" on <html>. Its colours are in the theme's contrast checks.
  • Payload takes bare: just the code, without the frame, language label or copy button, for inside a card.

Theme

  • Size tokens on the --cd-* scheme: --cd-text-prose (conversation text, Tailwind text-prose; it follows --cd-text-md, 14px, as before), --cd-thread-width (48rem, the message column and composer) and --cd-sidebar-width (16rem, AppLayout's sidebar). Set them after candor.css to fit your brand.
  • A compact preset: data-density="compact" on <html> or any container gives 14px conversation text, 13px body and label text (text-base and below; headings keep their sizes), a 44rem thread and a 14rem sidebar.

Updates

  • npx candor-ui update (CLI 0.2.0) updates installed items and keeps your changes, with a three-way merge and a clear conflict report; add now writes candor-lock.json. See "How versions work" above.
  • The registry serves every release at /r/v/<version>/<name>.json, gated exactly like the latest, from 1.0.0 on.

Fixed

  • Dates and numbers no longer cause hydration mismatches when the server's locale or time zone differs from the browser's. Without a locale, numbers render as en-US on the server and switch to the browser's locale after hydration, and absolute times show their ISO date (UTC) until then. Set locale and timeZone on the provider to render the final text on the server.
  • Counts of one read in the singular where the English used to say "1 steps", "1 runs", "1 tests".
  • Product words that leaked from another app are gone: Know's labels read "Knowledge sources", "Nothing has been read yet.", "What was retrieved" and "Add knowledge"; doc comments no longer mention Salesforce or Know.
  • MetadataDiff is now ChangeDiff, labelled "Changes"; the old name still works and is deprecated until 2.0 (the item is still installed as metadata-diff). Doc comments in the work and chat types no longer use Salesforce examples.
  • AccessRequest shows the environment a request was sent for by its name (and Production or its detail), not the id onSend returned.
  • BulletBar bands now shade from the end when higherIsBetter={false}, so the darkest end is always the worst.
  • EmailPreview shows an email inline, without a whole <html> document, with its title at your page's heading level. renderEmail and EmailLayout take the email's language and words.
  • ChartFrame and AppLayout take defaultWidth, used until they can measure themselves (on the server, and in jsdom tests, which measure everything as 0). ChartSizeProvider sets it for every chart inside.
  • SsoSetup and SubscriptionStatus take a label for their landmark, so two on one page can be told apart.
  • Dates in the audit log, downgrade impact and subscription status use the locale's medium date style ("Nov 5, 2026"), the same as the rest of the kit.

Notes

  • Components that now read the provider call hooks, so about two dozen that were server components gained "use client". They still render in React Server Components apps. EmailLayout stays server-safe: it takes strings and lang as props, and so does renderEmail.
  • Text the adapters produce (tool errors, approval titles), the A2UI standard cards and applyA2uiMessage's diagnostics are data, not interface chrome, and stay English in this release.

1.0.1 - 2026-11-05

Fixed

  • npx candor-ui init --yes sets up shadcn in a new project without stopping for questions; at a terminal, init runs shadcn's own setup. The CLI is 0.1.2.
  • Pro's pricing card counts what a Pro key unlocks (the full-stack blocks and templates are Premium).
  • Docs pages for blocks no longer scroll sideways on phones, inline code reads as code, and names read as written (AG-UI, MCP, SSO).
  • Unknown pages answer 404.
  • A malformed licence key is reported as not valid, and the no-key message says where to buy one.

1.0.0 - 2026-11-05

The first public release.

Components

  • 201 registry items: chat, agent work (plans, approvals, diffs, runs, recovery), the app shell, team and admin screens, knowledge, data and charts, ops and cost, evals and billing.
  • Honest by design: unknown values show as unknown, a control that can't be used says why, nothing is pre-selected in a decision, and model output is never rendered as HTML or a clickable link.
  • Light, dark and high-contrast themes, with 120 contrast checks. Every component has a live docs example, and CI runs axe on each one; tab lists, the toast and side panel also have keyboard and screen-reader tests.
  • Works in Vite and in Next.js with React Server Components ("use client" where needed, a hydration-safe clock).
  • Works in shadcn projects on Radix or Base UI styles: primitives take asChild and Base UI's render.

Adapters and agent UI

  • Vercel AI SDK 7, Claude Agent SDK and AG-UI adapters, all with the same shape: messages, a composer, and tool calls waiting for a person.
  • A person's own words are kept exactly as typed (a plain block: line breaks kept, nothing parsed); only an agent's text is read as safe markdown. markdownBlocks(text, { headings: true }) turns # lines into heading blocks. If you switch over MessageBlock kinds in your own code, give it a default branch: new kinds can arrive in a minor release.
  • The inline node for a value read from the user's system is measured (it was orgData, which still works and is deprecated until 2.0).
  • A2UI surfaces drawn with Candor's components, and an MCP Apps frame that runs only behind a separate-origin sandbox.

Blocks and templates (Premium)

  • Eight full-stack blocks, each for the Vercel AI SDK and the Claude Agent SDK: approval-gated change, plan-build- review, run console, eval review loop, cost guard, agent admin, audit and data controls, human handoff.
  • Three templates: an agent ops console, an approvals inbox and a support copilot.

For coding agents

  • A Candor skill (npx skills add MethodicDev/candor), llms.txt and llms-full.txt, and setup for shadcn's MCP server.

Licences

  • The free tier (43 items) is MIT. Everything else is under the Candor UI Commercial Licence.