Skip to content

Releases: dgesteves/signoff-ui

signoff-ui@0.7.1

Choose a tag to compare

@github-actions github-actions released this 12 Oct 23:51
1102b5c

Patch Changes

  • #102 03fa6ad Thanks @dgesteves! - In narrow containers with longer labels, such as a translation, DiffReview wraps its file buttons, its footer and (under 30rem) its lines instead of cutting them off, keeps "+n −n" together and shows a decided hunk's badge under its range, and ApprovalCard shows four or five choices in two columns with Approve across the last row; RunMeter reaches new numbers within 200 ms and starts each run from "–", its cost too, instead of counting down from the last run.

signoff-ui@0.7.0

Choose a tag to compare

@github-actions github-actions released this 12 Oct 20:58
3b0330b

Minor Changes

  • #84 cb0bae9 Thanks @dgesteves! - AgentMessage and Reasoning can render text with your own Markdown renderer, such as Streamdown, through a renderMarkdown prop or MarkdownRendererProvider, and signoff-ui/custom-markdown exports them without react-markdown, so it stays out of your bundle.

  • #85 2420e62 Thanks @dgesteves! - DiffReview takes an actions prop: your own controls, such as a "Copy as a patch" button, in its header next to the layout toggle. They sit in a data-signoff-slot element, so styles.css leaves them to your CSS, and keys pressed on them don't trigger the review's shortcuts.

  • #83 70c341a Thanks @dgesteves! - reviewToolOutput and reviewResumeEntry now tell the agent which files the reviewer marked viewed: each such file has viewed: true, and the others have no viewed key.

  • #86 d29ff27 Thanks @dgesteves! - To send a person's approval rules with each request, policy.getRules() returns the latest rules as plain data for the transport's body, and parseApprovalRules(rules) from signoff-ui/core checks them in the route before toToolApproval.

    getRules is a new member of the ApprovalPolicy interface. Code that builds its own ApprovalPolicy object, rather than using useApprovalPolicy, needs to add it.

  • #72 5972edc Thanks @dgesteves! - Sub-agents nest in the timeline. The AG-UI adapter maps SUBAGENT_STARTED, SUBAGENT_FINISHED and SUBAGENT_ERROR, and the messages and events that carry a subagentRunId, into one data-signoff-subagent part on the parent message, after the call that started it: { subagentRunId, parentToolCallId, name, status, parts }, with the sub-agent's tool calls and text in the same shapes as the parent's. ToolCallTimeline, in AgentMessage too, shows it under that call as a collapsed row with its name, its number of calls and its state, which opens to its calls, with the same keys, names and announcements as the others. A sub-agent's call can wait on approval like any: its row opens to the card, deriveAgentState reads awaiting-approval and "Approve all" includes it. The run state lists the sub-agents (subagents), events may arrive in any order, and the part's types (SubagentPart, SubagentData, SubagentStatus) and isSubagentPart are exported, so other sources can stream their own. The labels gain toolCallTimeline.subagent.

  • #82 7a0aa14 Thanks @dgesteves! - toToolApproval(rules, { reason }) words the reason for a call a rule decides, so the server's rules and a person's own can each say whose rule it was; without reason, the reason is the same as before.

Patch Changes

  • #92 1e9ad7d Thanks @dgesteves! - ApprovalCard (and ToolApprovalCard, and the cards in AgentMessage through approvalProps) no longer approves or denies when Y, S, A, N, Shift+N or ⌘/Ctrl+Enter is pressed on a control in a custom preview, or in a menu that preview opens in a portal. Those keys go to the control. With globalShortcut, ⌘/Ctrl+Enter still approves from anywhere on the page.

  • #81 ad50100 Thanks @dgesteves! - useApprovalPolicy: an always rule from defaultRules that a person removes now stays removed after a reload, instead of coming back on every load. A default your app adds in a later version still appears. webStorageRules() keeps the removed ids under <key>:removed-defaults and memoryRuleStorage() keeps them in memory; a storage of your own can keep them with the new optional loadRemovedDefaults() and saveRemovedDefaults(ids). Rules saved by earlier versions load as before.

  • #94 9d8d48a Thanks @dgesteves! - DiffReview renders no actions slot for actions={true} or actions="", as for undefined, null and false: React renders nothing for them, and the empty slot narrowed the title.

  • #91 bcfda1c Thanks @dgesteves! - Approval rules match an argument in time proportional to its length times the pattern's. matchesGlob, and with it evaluateRules, toToolApproval and useApprovalPolicy, used a regular expression that backtracked: a pattern with a few *, such as *a*a*a*b, took seconds on an argument of a few hundred characters that it didn't match, and held up the server or the page meanwhile. It now reads the value once and gives the same answers. globToRegExp still returns the same RegExp, which backtracks.

  • #69 34ff527 Thanks @dgesteves! - npm's homepage link for the package is now the website, signoff-ui.vercel.app, instead of the GitHub README.

  • #66 89e4314 Thanks @dgesteves! - RunMeter no longer shows a negative number for a moment as a value animates, such as "↑ -89" or "$-0.0002" at the start of a run. A frame that began before the animation counts as its start.

signoff-ui@0.6.0

Choose a tag to compare

@github-actions github-actions released this 11 Oct 09:10
3aaadaf

Minor Changes

  • #59 782ed22 Thanks @dgesteves! - signoff-ui/acp: render Agent Client Protocol sessions with the components. Pure functions, no React, nothing new to install.

    • A prompt turn as a message. createAcpTurn(), then reduceAcpTurn(turn, notification) for each session/update: message and thought chunks become text and reasoning, and tool calls and their updates merge by id. requestAcpPermission(turn, request) marks a call as waiting on its session/request_permission, answerAcpPermission(turn, toolCallId, decision) records the answer, and endAcpTurn(turn, response) ends the turn with its stopReason (or an error). toAcpMessage(turn) is the assistant UIMessage for AgentMessage and ToolCallTimeline: a call waiting on a person is approval-requested, its tool call id the approval id, and a denied or cancelled one is output-denied.

    • Diffs as review files. fromAcpDiffs(content, { root }) maps a tool call's { type: 'diff', path, oldText, newText } content to DiffReview files, relative to the session's cwd. toAcpDiffs goes back.

    • The permission converters (decisionsFromAcpOptions, toAcpPermissionResponse, fromAcpPermissionResponse, fromAcpPermissionRequest) are re-exported here. fromAcpPermissionRequest now names the tool by ACP 1.8's name when the agent sends one, then by kind and title as before (acpToolName). Rules saved against a call's kind (execute) no longer match when the agent sends a name (Bash): those calls are asked about again, and a deny-always rule on the kind needs a rule on the name added to keep blocking them.

    • ApprovalCard's ruleScope={false} hides the argument patterns a session or always decision would cover, for when the agent keeps what "always" means, as ACP's allow_always does. The decision then comes without args.

    The types mirror what is read, and a type test checks that @agentclientprotocol/sdk 1.8's notifications, permission requests and diff content fit them, and that the responses fit the SDK's. The components page has a demo with a scripted ACP agent.

  • #57 66d5c2f Thanks @dgesteves! - signoff-ui/assistant-ui: review and approve assistant-ui tool calls.

    • signoffTools({ review }) goes in MessagePrimitive.Parts' components.tools. A call to one of the review tools gets a DiffReview once its arguments are in, and the review goes back as the call's result through addResult (reviewToolOutput(review)), then stays on the page read-only. Any other call at an approval gate gets the approval card, answered through respondToApproval. Fallback renders the calls with no gate.
    • SignoffToolsProvider, optional, passes approval rules from useApprovalPolicy (once, this session or always, and a call a rule decides is answered without a card once the run has paused for it), labels and risk levels by tool, props for every DiffReview and approval card, and reviewToolOutput's options.
    • ReviewToolUI and ApprovalToolUI are the two components, for your own tool components.

    It works with any assistant-ui runtime; it ran end to end with the AI SDK runtime (@assistant-ui/react-ai-sdk 1.4) and the local runtime. @assistant-ui/react (^0.15.0) is an optional peer dependency: the entry imports only its types, and nothing of it reaches the main entry. The shadcn registry has it as the assistant-ui item.

    ToolApprovalCard with a policy no longer answers a call twice when a person's session or always decision adds a rule while the call still reads pending, as it does with a runtime that records the answer a moment later.

  • #63 2a7cff8 Thanks @dgesteves! - ToolCallTimeline keeps up with very long runs. At 2,000 calls, appending one took about 105 ms of the main thread until its frame was painted; it now takes about 2.5 ms (12 ms with the CPU six times slower). With a call running, frames dropped to about 50 ms; they now keep the display's pace. The first render goes from about 440 ms to 40 ms.

    • A long run renders only the calls near the screen. Past 150 calls, the timeline renders the calls within 800 px of the viewport, in chunks of 25. A chunk away from it is an empty item as tall as its calls, hidden from assistive tech. A chunk holding an open call, a call still going (running, streaming its input or waiting on approval) or focus stays rendered. The calls that render carry aria-posinset and aria-setsize. Arrow Up and Down, Home and End move between all the calls, scrolling a call that isn't rendered into view and focusing it. Announcements are unchanged. What a call far from the screen renders, including renderExtra's content, unmounts until it comes back near. Shorter timelines render every call, as before.
    • Only the calls that change render again. A new call, or a running call's clock, no longer renders every call. The waterfall's bars are placed from each call's times and the timeline's span, set on the bars that are rendered.
    • The enter animation leaves nothing behind. --animate-signoff-enter fills backwards, not both: the end state is the same, but a finished animation no longer stays in effect on every element it ran on. A call that renders again as its chunk comes back near the screen doesn't slide in.

Patch Changes

  • #60 ae3d758 Thanks @dgesteves! - The website and the hosted shadcn registry moved to signoff-ui.vercel.app; the registry URL is now https://signoff-ui.vercel.app/r/{name}.json. The old address redirects, so existing components.json files and links keep working.

signoff-ui@0.5.0

Choose a tag to compare

@github-actions github-actions released this 10 Oct 15:09
efeded4

Minor Changes

  • #53 ebf330d Thanks @dgesteves! - Approval rules: a person approves once, for the session or always, by tool and argument pattern, and the calls that follow are decided by those rules.

    • useApprovalPolicy() holds the rules for the page. Each rule covers a tool (a name or a glob), narrowed by argument globs ({ command: 'npm test*' }, dotted names for nested arguments) or a test of your own (when), and a deny rule wins over an allow rule. In a pattern, * never runs past a shell operator (;, &, |, backticks, <, >, $(, line breaks), so npm test* does not allow npm test && rm -rf ~; ** matches anything. Session rules stay in memory. always rules go to a pluggable storage: memory by default, webStorageRules() for localStorage, or your own { load, save }, which may be async. onAudit receives every decision (by a person or by which rule, with the reason and edited arguments), every rule added or removed, and every session cleared.
    • ApprovalCard offers the choices. decisions sets which of allow-once, allow-session, allow-always, deny-once and deny-always it shows, each a button with a key (Y, S, A, N, Shift+N). A lasting choice shows the argument patterns it will cover, editable, or "Any arguments". onDecide receives { decision, reason, input, args }. ToolApprovalCard with a policy offers every choice, records the rules, answers a call a rule covers without a prompt, and shows it as "Allowed by your rule" with the rule.
    • Edit the arguments, then approve. editable on ApprovalCard, or onEditInput on ToolApprovalCard (onToolInputEdit on AgentMessage), opens the arguments as a form (an object of strings, numbers and booleans) or as JSON that must parse. With AI SDK 7, setToolInput puts the edited input in the messages, and the server runs the call with it after checking it against the tool's schema and toolApproval again. useAgUiAgent gains editInput, and the resume payload carries the edit as input.
    • Approve all pending. ToolApprovalBatch answers every approval a run waits on at once, with a second press when one of them is critical. AgentMessage shows it when two or more are waiting.
    • The same rules elsewhere. toToolApproval(rules) is AI SDK 7's toolApproval function: a matching rule approves or denies on the server, and anything else asks a person. toToolApprovalResponse answers addToolApprovalResponse. For the Agent Client Protocol's session/request_permission, decisionsFromAcpOptions, toAcpPermissionResponse, fromAcpPermissionResponse and fromAcpPermissionRequest map to and from its allow_once / allow_always / reject_once / reject_always options. allow-session answers allow_once, and the session rule stays on the client. These are types and functions only, with nothing new to install.

    onApprove and onDeny work as before. When you pass onDecide, they are not called.

  • #34 39d503f Thanks @dgesteves! - Approval cards claim only what you told them, and read better with a screen reader.

    • risk has no default. It used to default to 'medium', so every card, and every ToolApprovalCard for a tool without risk in its meta, said "Medium risk" when nobody had rated it. Without risk, the card now shows no risk badge, uses its neutral colors and has no data-risk.
    • A neutral reason placeholder. The denial reason field said "e.g. Use the existing Redis client instead", copy from the demo. It now says "What should the agent do instead?", and the new reasonPlaceholder prop sets your own.
    • Not a landmark. ApprovalCard is a group named by its title (role="group"), not a region landmark, so a run with many approvals no longer fills landmark navigation. Pass role="region" to make it one. Tests that find the card with getByRole('region') need getByRole('group').
    • "Approved" once. A decided card read "Approved, Approved": the announcement stayed in the card next to the outcome. The announcement now clears 3 seconds after it is made.
    • ToolCallTimeline.
      • Calls that settle together, such as parallel calls, are announced in one message, after a 300 ms pause. Before, only the last one was announced.
      • Calls that had already settled when the timeline mounted are not announced.
      • The awaiting-approval "!" beside a call is hidden from screen readers; the call's button already says "Needs approval".
  • #50 34dfe90 Thanks @dgesteves! - DiffReview stays responsive on large files. A 5,000-line rewrite used to block the page for 3.3 s, leave 260,000 elements and take 77 ms per keypress; it is now ready in under 0.3 s, with no task over 60 ms, about 4,000 elements and under 1 ms per keypress (Chrome, Apple M1 Max).

    • A limit on the diff. maxEditLength (default 2,000 lines added plus removed) caps the diff, whose cost grows with the square of that number. Past it, the changed region (from its first changed line to its last) is one hunk that replaces it, with no word-level highlights, and the review says so; ParsedFileDiff.fallback is 'replace' and the file carries data-fallback="replace". parseFileChange takes the same option, and DEFAULT_MAX_EDIT_LENGTH is exported from signoff-ui/core. The common lines before and after the change are set aside before diffing, so only the changed region counts, and lines only added or only removed never fall back.
    • A worker. A file that needs more than 300 edits is diffed in a worker and reads "Comparing changes…" until it is done; Apply and the bulk buttons wait for it. Webpack 5, Next.js (webpack and Turbopack) and Vite emit the package's worker as they build. The new diffWorker prop takes a function that starts your own Worker, or false; without a working worker, files are diffed on the main thread after the first paint. A server render diffs what a render may and shows the rest as being compared, so hydration matches.
    • Only what is near the screen. A review with more than 400 rows renders, of each hunk, the rows near the viewport; the rest keep their height and their lines as visually hidden text, so screen readers and find-in-page still have every line, read with "Added:" and "Removed:". Hunks always render, so focus, J and K and their names do not change, and a decision re-renders only the hunks it touches.

    Where two diffs are equally small, hunks can now differ from jsdiff's own: the region between the common first and last lines is what gets diffed. The edits are as few as before, and applyHunks gives the same files.

  • #53 4d23065 Thanks @dgesteves! - Labels: every word the components show or announce can be replaced, so the kit can be translated whole.

    • SignoffLabelsProvider takes a translation of defaultLabels, whole or in part, for every component inside. Providers nest, the inner one winning key by key. Every component also takes a labels prop, which wins over the providers for it and what it renders. useSignoffLabels() returns the labels in effect, for markup of your own, and mergeLabels() builds them anywhere.
    • 207 labels in twelve sections, 140 strings and 67 functions: visible text, accessible names, live-region announcements, keyboard hints, titles and placeholders. Text built from values (counts, paths, line numbers, tool names) is a function, so a translation gets plurals and word order right; format holds how durations, token counts, cost and percentages read. Type a translation as SignoffLabels and the compiler lists what it misses. The docs have a complete one in Portuguese.
    • useDiffReview and useApprovalPolicy take labels too, and deriveAgentState a writingResponse detail.
    • Each component bundles the English of the sections it reads only: 0.2 to 1.5 kB gzipped more per import, 2.4 kB for the whole main entry.

    Small changes that come with it:

    • Sources is now a client module, as it reads the labels. It still renders from a Server Component: its props are data.
    • The diff review's view toggle is named "Unified" and "Split", capitalized for screen readers too (before, only on screen).
  • #32 bf53438 Thanks @dgesteves! - useRunTiming times each turn on its own. Pass the chat's messages, useRunTiming(status, messages): a run then starts with each user message (or a regenerated reply) and spans its approval round trips. Without them, a run now starts with each request submitted after the last one ended. Before, every turn was added to the first: the second turn of a chat showed the first turn's time to first token and the sum of both turns' active time. Time to first token is also no longer 0 by mistake: for a run that was already streaming when the hook first saw it (keyed by a counter that changed while a stopped run was still settling, for example) it is unknown, and so, given the messages, for a request stopped before any reply arrived. The README quickstart now passes messages.

  • #37 [c459d3f](https://github.com/dgesteves/...

Read more

@dgesteves/agent-ui-kit@0.4.3

Choose a tag to compare

@github-actions github-actions released this 09 Oct 19:51
92f2037

Patch Changes

  • #30 f8dae5e Thanks @dgesteves! - The README opens with the demo, on GitHub as a video and on npm animated.

@dgesteves/agent-ui-kit@0.4.2

Choose a tag to compare

@github-actions github-actions released this 09 Oct 19:00
14cb613

Patch Changes

  • #28 13f7fec Thanks @dgesteves! - The npm page now shows the repository's README, the same one as on GitHub (every component with its screenshot, AG-UI, theming and the full quickstart), instead of a shorter separate README.

@dgesteves/agent-ui-kit@0.4.1

Choose a tag to compare

@github-actions github-actions released this 09 Oct 08:57
1ff3992

Patch Changes

  • #19 afc5071 Thanks @dgesteves! - Content that scrolls sideways is now reachable from the keyboard: a diff hunk's code, a code block or table in Markdown, an approval card's command preview and the compact RunMeter strip. While their content is wider than they are, they join the tab order as named groups ("Hunk 2 code, app/api/chat/route.ts", "Code, ts"), so the arrow keys scroll them, with a visible focus ring; once everything fits, they add no tab stop. Fixes axe's scrollable-region-focusable (WCAG 2.1.1).

  • #18 b3e4361 Thanks @dgesteves! - ToolCallTimeline fits narrow columns: a long tool label truncates instead of pushing the row wider than its container, and the waterfall bar shows from a 24rem-wide timeline rather than from a 640px-wide screen, so a timeline in a sidebar or a phone-width panel no longer scrolls sideways.

@dgesteves/agent-ui-kit@0.4.0

Choose a tag to compare

@dgesteves dgesteves released this 08 Oct 21:03
8f8e7af

A quickstart that runs, and works in more apps

An adopter audit copied the 0.3.0 README into a fresh Next.js 16 app. This release fixes everything that got in the way.

The quickstart runs (#7)

  • Complete code. The README now has the complete client, page and route: real tools with one behind an approval, stopWhen: stepCountIs(10) (without it AI SDK 7 stops after one step, so the approval card never showed), a composer, and onError. There's an AI SDK 6 variant too.
  • The same code, as an app. examples/nextjs-minimal is that exact code as a Next.js 16 app with a mock model, so it runs with no API key. CI builds it and clicks through a run: a tool call, a failed tool, approve, resume, sources and the meter.
  • Next.js 16 cacheComponents (the default in new apps): components no longer read the clock while rendering on the server, which failed next build. Every component is now tested with react-dom/server.

Works in more apps (#8)

  • Tailwind v3 and plain CSS:
    • styles.css has no cascade layers any more. Those broke Tailwind v3 builds, and lost to global resets like create-next-app's * { padding: 0 }.
    • Every rule is scoped to the kit's own elements with zero specificity, so your theme and utility classes are left alone.
    • styles.layered.css is still available.
  • React 18: peers are now react@^18.2.0 || ^19.0.0, tested in CI.
  • Dark mode from the OS: import theme.auto.css. .light or [data-theme=light] still forces light.
  • Your app's font: the kit falls back through --font-sans, so shadcn/ui apps get their own font.
  • Lighter install: the kit depends on the two Radix primitives it uses, instead of the radix-ui meta-package (about 60 packages).
  • shadcn items lint clean under a fresh Next.js ESLint config (24 warnings before).

Docs and playground (#9)

  • shadcn: the shortest install, npx shadcn@latest add @agent-ui-kit/agent-message, and the shadcn MCP setup.
  • Sizes: a gzip table per import, measured with pnpm size.
  • New recipes: LangGraph via AG-UI, and a preset that maps the kit's tokens to shadcn/ui's.
  • Screenshots: light and dark component shots.
  • Playground: social preview images, a light/dark toggle on the gallery, /llms.txt, and no more jump past the headline on phones.
  • Contributing: a contributing guide, a security policy and issue forms.

Full changes: https://github.com/dgesteves/agent-ui-kit/compare/@dgesteves/agent-ui-kit@0.3.0...@dgesteves/agent-ui-kit@0.4.0

@dgesteves/agent-ui-kit@0.3.0

Choose a tag to compare

@dgesteves dgesteves released this 08 Oct 19:08
778beee

Render AG-UI agents

AG-UI is the protocol LangGraph, CrewAI, Mastra, Pydantic AI, Google ADK and other agent frameworks use to stream runs to a frontend. The new @dgesteves/agent-ui-kit/ag-ui entry renders those agents with the same components as AI SDK apps:

import { HttpAgent } from '@ag-ui/client';
import { useAgUiAgent } from '@dgesteves/agent-ui-kit/ag-ui';

const agent = new HttpAgent({ url: '/api/agent' });
const { messages, status, usage, step, respond } = useAgUiAgent(agent);

<AgentMessage message={last} streaming={status === 'streaming'} onToolApproval={respond} />
<RunMeter usage={usage} />
  • Messages become AI SDK parts: text and reasoning with their streaming state, and tool calls as dynamic-tool parts whose arguments are parsed as they stream in, with their results. Each turn is one assistant message, as with a multi-step AI SDK run.
  • Interrupts bound to a tool call become approval cards. AG-UI resumes every open interrupt in one run, so the hook waits until each one has an answer, then calls runAgent({ resume }) with { approved, reason }. Other interrupts are in interrupts; answer them with resolve().
  • status, usage and step come from the event stream. Usage is summed across runs, cache and reasoning tokens included, and the step is e.g. the LangGraph node that's running.
  • The pure pieces (fromAgUiMessages, reduceAgUiRun, answerAgUiInterrupt, getAgUiResume) work with your own store. Nothing from @ag-ui/* is a dependency: their objects are read structurally.
  • Also a shadcn registry item: npx shadcn@latest add https://agent-ui-kit-demo.vercel.app/r/ag-ui.json.

Try it: the gallery runs a real @ag-ui/client agent replaying a LangGraph-style run, with no API key needed. Approve or deny the install and watch the run resume: https://agent-ui-kit-demo.vercel.app/gallery#ag-ui

The gallery also shows how to install each component, from npm or as a shadcn registry item (#4).

Full changes: https://github.com/dgesteves/agent-ui-kit/compare/@dgesteves/agent-ui-kit@0.2.0...@dgesteves/agent-ui-kit@0.3.0

@dgesteves/agent-ui-kit@0.2.0

Choose a tag to compare

@dgesteves dgesteves released this 08 Oct 16:39
0918814

AI SDK 6 support

The ai peer range is now ^6.0.0 || ^7.0.102. AI SDK 6 had more installs than 7 last week, and the old range turned all of those projects away.

  • On AI SDK 6: the kit reads the two approval fields AI SDK 7 added (approval.isAutomatic and approval.requestReason) only when they are present. Approvals read as a person's decision, and the card uses the description you pass.
  • AI SDK 7 only: toolApproval policies, and the "Auto-approved" and "Blocked by policy" labels.
  • CI: the typecheck and the full suite now run against AI SDK 6.0.0, the latest 6.x and 7 (#2).

Also, the README's comparison with AI Elements and assistant-ui is rewritten to reflect assistant-ui's new Elements (#1).

npm: @dgesteves/agent-ui-kit@0.2.0.