Cloud|

@k2b/cloud

Cloud approvals

List and decide the signed-in person's open Action approvals for one app, one resource, or all apps.

Approvals@k2b/cloud/approvals/uiApprovalsNavStatus@k2b/cloud/approvals/ui

Backend-required list of the signed-in person's open Action approvals, decided through the Core approvals route.

Real approval requests required

Rows come from Cloud's platform approval requests, and a decision continues the chat that asked. The catalog shows the contract without inventing requests that a click could not decide.

TSX

Copy
// SSR: the first page renders with the page, so the list never pushes content down.
const page = await loadPendingApprovals(c, { app: "mail", link: draftHref });
<Approvals app="mail" link={draftHref} initial={page} layout="band" />;
<Approvals app="mail" initial={page} />;

Approvals lists the signed-in person's open Action approvals and lets them approve or reject each one in place. Every app uses the same component and the same Core route, /api/approvals, for its own scope: the requests whose effect belongs to the app, or that the app asked for. The app builds no route, table, or card of its own.

Use Cloud approvals

Mount the list where people look for what the app is waiting on, such as an Approvals view or the page of the resource an Action would change. Use the band layout on a resource page and the list layout everywhere else. Do not mount it to authorize anything: Cloud decides through its platform approval requests and continues the waiting chat itself.

Load the first page during SSR with loadPendingApprovals from @k2b/cloud/server and pass it as initial. The list then renders with the page and never pushes content down when it arrives. Without initial, the component loads in the browser and shows a loading state first.

Import

ts
import { Approvals, ApprovalsNavStatus, approvalNavCount } from "@k2b/cloud/approvals/ui";
import { loadPendingApprovals } from "@k2b/cloud/server";

Props

  • app: list the requests whose effect belongs to this app or that it asked for. Without it, the list shows every app's requests grouped by app, as the Core page My account → Approvals does.
  • link: only requests whose review links to this same-origin path. Pass the same href your Action's review uses in links, for example the draft that a send Action would send.
  • initial: the SSR page for the same app and link.
  • layout: list (default) or band.

Presentation

A list row shows the app's sentence as its title, the owning app, the chat it waits in as a link, and how long ago Cloud asked. Below follow the review's message and fields and the first long field, cut after three lines. The foot names the level ("Asks every time" for confirm) and the expiry, then Details, Reject, and Approve. Details opens a panel with the whole review, its links, and the same decision.

Rows are set apart by space, not by lines or boxes. A decided row stays in place at its height: its content dims and the buttons turn into a receipt. Only an approval turns the icon into a green check. A rejection shows a neutral cross. When another tab or channel decided first, or the request expired or was withdrawn, the row shows an info icon, the receipt Already decided elsewhere or Expired or withdrawn, and the note "Your decision was not applied". The list changes only when it loads again. With more than one open request, Reject all asks once and rejects them one after another. There is no "approve all".

Show more loads the next 20 requests below the rows already shown. If that fails, the rows stay, an error appears next to the button, and the button becomes Try again.

The approve buttons arm half a second after a row appears, so a click meant for the page cannot approve it. Remember options (Approve for this chat, Always approve) appear only on standard requests that offer them.

The band shows each request as one tinted row with its title, the chat, and the decision. It renders nothing when the loaded page has no requests.

The empty list says "Nothing is waiting for you." When app or link is set and other apps have open requests, a line links to the Core page.

Every app can add an Approvals row to its own navigation. Render the row only when approvalNavCount(appId, initialCount) is above zero or the view is open, and evaluate that only when the person navigates, so the row never appears or leaves under the pointer. Put <ApprovalsNavStatus app="mail" initial={page.count} /> in the row's meta; it shows the live count and keeps a muted 0 when the count drops while the row is shown.

The rail and the app grid already add each app's open approvals to its icon's count. An app does not need a nav.badge route for that.

Accessibility

Each row is an article named after its title. Receipts and errors replace the buttons in the same place, and a failed decision says so in the row's foot as an alert. A failed Show more is announced as an alert, and focus stays on the button that retries it. The navigation count tells screen readers how many requests wait, in the inherited locale.

Runtime

The component reads GET /api/approvals and decides with POST /api/approvals/:id/decision as the signed-in person. Core checks that the person is the approver, that the request is still open, and the channel rules of the request's level; then it continues the chat that asked, exactly as a decision in the chat card does. After a decision the component refreshes the rail counts.

Example

Load the first page in the authenticated SSR handler:

ts
const page = await loadPendingApprovals(c, { app: "mail", link: draftHref });

Pass it to the island that shows the draft:

tsx
<Approvals app="mail" link={draftHref} initial={page} layout="band" />;

Use the same draftHref in the Action review's links. See Action approvals for the server and island imports.

Connect a coding agent

The Cloud skill provides compact working instructions. MCP supplies exact current documentation when details matter.

CLI command
bunx skills add https://docs.example.com

This command installs the working instructions published by this website in the selected coding agent.

For exact, current details, also connect the MCP server through one of the agent tabs.

Installation uses the open-source Vercel Skills CLI.

The skill and MCP complement each other: the skill describes workflows, while MCP supplies current documentation.