Agent Proposal
A change the AI wants to make to something the person owns, shown in full before anything happens: what changes, from what to what, and why. Then Apply, an optional Adjust, and Not now, which is always there.
When to Use
Variants
A timed change, with Adjust
Each change is a label with an optional at (a time or a position), and was and new. A change with only new is an addition. Try the buttons: Apply hides the card and offers Undo in a toast, Not now removes it, and the line under the card shows each event.
Suggested change
Still end at 11:00, and keep the full decision
- 9:55 Break was 15m , now 5m
- 10:12 Restate the question now New, 3m
- 10:45 Commit: owners and first steps was 20m , now 15m
Why: You are 12 minutes behind, and three people asked which question they are answering. Restating it costs 3 minutes; the break and the close pay for it.
No event yet.
<?= ws_agent_proposal([
'id' => 'adapt-' . $proposalId,
'title' => 'Still end at 11:00, and keep the full decision',
'changes' => [
['at' => '9:55', 'label' => 'Break', 'was' => '15m', 'new' => '5m'],
['at' => '10:12', 'label' => 'Restate the question', 'new' => 'New, 3m'],
],
'why' => 'You are 12 minutes behind, and three people asked which question they are answering.',
'apply' => 'Apply to the session',
'adjust' => 'Adjust',
'toast' => 'Applied. The session still ends at 11:00.',
]) ?>
<?= ws_toast_container() ?>
<script src="/js/ws-agent.js?v=<?= filemtime(WS_ROOT . '/js/ws-agent.js') ?>"></script>Removed items, no Adjust
'removed' => true strikes the item through and says Removed in words. Without an adjust label there is no Adjust button; Not now is always there.
Suggested change
Make room for the decision
- Lightning Demos Removed
- Dot voting was 10m , now 25m
Why: The decision needs about 24 minutes for 12 people choosing 3 of 12.
From JavaScript, and saving before you confirm
WsAgent.proposal(proposal) prints the same markup, so a chat reply can draw one. When Apply has to save first, prevent the event, save, then call WsAgent.applied(card), which hides the card and offers Undo.
thread.insertAdjacentHTML('beforeend', WsAgent.proposal({ id: 'chat-7', title: 'Tighten the recap', changes, why }));
document.addEventListener('ws:proposal-apply', async (event) => {
event.preventDefault(); // the toast waits for the save
await saveChange(event.detail.id);
WsAgent.applied(event.detail.proposal);
});
document.addEventListener('ws:proposal-undo', (event) => revertChange(event.detail.id));
document.addEventListener('ws:proposal-dismiss', (event) => logDismissed(event.detail.id));
document.addEventListener('ws:proposal-adjust', (event) => openEditor(event.detail.id));States
| State | Behavior |
|---|---|
| Proposed | The card, its changes, the reason and the actions. |
| Applied | ws:proposal-apply ran and nothing prevented it: the card is hidden, carries data-ws-proposal-state="applied", and a success toast says so with Undo (unless 'undo' => false). It stays in the page so Undo can bring it back. |
| Undone | Undo dispatches ws:proposal-undo; unless prevented, the card comes back and its Apply button takes focus. |
| Dismissed | Not now dispatches ws:proposal-dismiss; unless prevented, the card is removed. |
| Adjusting | Adjust dispatches ws:proposal-adjust. The card stays; the page opens its own editor. |
Real-World Usage
Synthesize's "Draft a change" and Agenda Check's "Fix" move onto it; Synthesize gains the Not now it never had. Facilitate's Help me adapt proposes changes to the rest of a live session the same way.
Options
| Option | Type | Default | Purpose |
|---|---|---|---|
id | string | 'ws-agent-proposal' | Required in practice: unique on the page. The title takes {id}-title; events carry it as detail.id. |
kicker | string | 'Suggested change' | The line above the title |
title | string | '' | What the change achieves, in a sentence |
changes | array | [] | Each: label, at, was, new, removed |
why | string | '' | The reason, in the person's terms |
apply / adjust / dismiss | string | 'Apply' / '' / 'Not now' | Button text. No Adjust without its text. |
toast | string | 'Change applied.' | The toast after Apply |
undo | bool | true | Offer Undo in that toast. Turn it off only when the change truly cannot be reversed. |
returnFocus | string | '' | A selector for where focus goes when the card leaves |
class / attrs | string / array | Additional classes and attributes |
Markup contract: the section carries data-ws-proposal="{id}" and data-ws-proposal-toast; the buttons carry data-ws-proposal-action (apply | adjust | dismiss). The page needs ws_toast_container(), components.js and /js/ws-agent.js.
Accessibility
| Concern | Behavior |
|---|---|
| Region | A <section> named by its title through aria-labelledby, so a screen reader can find it among the page's regions; the title is an h3. |
| Before and after | Strikethrough is not read aloud, so each change carries visually hidden words: "was 15m, now 5m". A removed item says Removed in visible text. |
| Actions | Apply, Adjust and Not now are real buttons, in that order, so Tab reaches the primary action first. |
| Focus when the card leaves | Focus moves to returnFocus when set, else to the next proposal beside it, else the previous one, else to the card's container. Undo brings the card back and focuses its Apply button. |
| The toast | The success toast is announced by the toast container, and it pauses while hovered or focused, so there is time to reach Undo. |
Tokens
| Token | Used for |
|---|---|
--brand | The 2px border that marks a decision waiting |
--brand-tint / --brand-line / --brand-ink | The head, its rule, the kicker and the new values |
--font-heading / --text-h4 | The title |
--card-bg | The actions row |
--radius-xl / --shadow-sm | Shape and lift, matched to the cards around it |
CSS Classes
| Class | Purpose |
|---|---|
.ws-agent-proposal | The section |
.ws-agent-proposal__head / __kicker / __title | The head |
.ws-agent-proposal__changes / __change | The list of changes; __change--removed for a removed item |
.ws-agent-proposal__at / __what / __delta / __was / __new | The parts of one change |
.ws-agent-proposal__why / __why-label | The reason |
.ws-agent-proposal__actions | The buttons |
Files
| File | Purpose |
|---|---|
includes/components/helpers.php | ws_agent_proposal() helper |
includes/components/agent-proposal.php | Template |
includes/components/components.css | Styles, in the agent components section |
js/ws-agent.js | WsAgent.proposal(), WsAgent.applied() and the events |