Widget ID: approval-cards. API: documented below.
Purpose
Ask the participants for a decision: an approval (Approve / Needs changes / Reject), a checkpoint (Confirmed / Needs attention), or a choice between 2–6 options. The author sets whether all reviewers or the first response completes the card. Reviewers can add a comment and change their answer while the card is pending.
Good to know
- Reviewers are the participants connected when the card is created; later participants can see the card but not answer it.
- A notification never approves a card on its own.
- Responses are sent to the card's author, who decides when it completes. A card cannot be completed while its author is disconnected.
Getting the API
The API is available as soon as loadWeei() resolves, if the widget is enabled for your site. The widget window does not need to be open.
const weei = await window.weeiReady;
const cards = weei.widgetsApi.get('approval-cards');
if (!cards) throw new Error('Approval cards is not enabled for this site.');interface ApprovalCardsApi {
getCards(): DecisionCardSummary[];
subscribe(listener: (cards: DecisionCardSummary[]) => void): () => void;
create(input: DecisionCardInput): Promise<DecisionCardSummary>;
respond(cardId: string, choice: string, comment?: string): Promise<void>;
cancel(cardId: string): Promise<void>;
whenFinished(cardId: string): Promise<DecisionCardSummary>;
}
interface DecisionCardInput {
title: string; // up to 120 characters
description?: string; // up to 1,000 characters
kind?: 'approval' | 'decision' | 'checkpoint'; // default 'approval'
options?: string[]; // 2–6 distinct, up to 80 characters each; default depends on kind
rule?: 'all' | 'any'; // default 'all'
}
interface DecisionCardSummary {
id: string;
owner: string; // peer ID of the author
title: string;
description: string;
kind: 'approval' | 'decision' | 'checkpoint';
options: string[];
reviewers: string[]; // peer IDs fixed when the card was created
rule: 'all' | 'any';
responses: Record<string, { choice: string; comment: string; time: number }>; // by peer ID
status: 'pending' | 'completed' | 'cancelled';
result?: string; // the common choice, or 'Mixed decisions'
createdAt: number; // ms since epoch
finishedAt?: number;
mine: boolean; // created by this browser
canRespond: boolean; // this browser is a reviewer and the card is pending
waitingFor: string[]; // reviewers who have not responded
}Every action returns a promise that rejects with an Error whose message says why, for example an unknown ID or a participant that is not allowed to act. subscribe() calls the listener at once with the current list, then on every change, and returns a function that unsubscribes. Lists are copies: changing them has no effect.
| Kind | Default options |
|---|---|
approval | Approve, Needs changes, Reject |
decision | Option A, Option B — pass your own options |
checkpoint | Confirmed, Needs attention |
| Method | Behavior |
|---|---|
getCards() | Cards known to this browser, newest first: its own and those of authors who are connected. Up to 100. |
create(input) | Creates a card and resolves with it. Reviewers are the participants connected at that moment; with nobody connected, the author is the only reviewer. |
respond(id, choice, comment?) | Answers as this participant with one of the card's options and an optional comment of up to 500 characters. While the card is pending the answer can be changed. Rejects if this participant is not a reviewer, the choice is not an option, or the card is finished. |
cancel(id) | Cancels a pending card. Only the author can cancel it. |
whenFinished(id) | Resolves with the card once it is completed or cancelled. With rule: 'all' that happens after every reviewer has answered; with 'any', after the first answer. It does not resolve if the author leaves first. |
Example: ask for approval before an action
const weei = await window.weeiReady;
const cards = weei.widgetsApi.get('approval-cards');
async function applyDiscount(percent) {
const card = await cards.create({
title: 'Apply a ' + percent + '% discount?',
description: 'Customer: ACME Corp, order #1042',
rule: 'any'
});
const decision = await cards.whenFinished(card.id);
if (decision.status === 'completed' && decision.result === 'Approve') {
await fetch('/api/orders/1042/discount', { method: 'POST', body: String(percent) });
}
return decision;
}