@dowel-ui/react 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +193 -0
- package/dist/components/ai-action-ledger/ai-action-ledger.d.ts +77 -0
- package/dist/components/ai-action-ledger/ai-action-ledger.d.ts.map +1 -0
- package/dist/components/ai-action-ledger/ai-action-ledger.js +224 -0
- package/dist/components/ai-action-ledger/ai-action-ledger.js.map +1 -0
- package/dist/components/ai-action-ledger/index.d.ts +2 -0
- package/dist/components/ai-action-ledger/index.js +2 -0
- package/dist/components/ai-action-ledger/meta.js +17 -0
- package/dist/components/ai-action-ledger/meta.js.map +1 -0
- package/dist/components/ai-agent-plan/ai-agent-plan.d.ts +50 -0
- package/dist/components/ai-agent-plan/ai-agent-plan.d.ts.map +1 -0
- package/dist/components/ai-agent-plan/ai-agent-plan.js +156 -0
- package/dist/components/ai-agent-plan/ai-agent-plan.js.map +1 -0
- package/dist/components/ai-agent-plan/index.d.ts +2 -0
- package/dist/components/ai-agent-plan/index.js +2 -0
- package/dist/components/ai-agent-plan/meta.js +17 -0
- package/dist/components/ai-agent-plan/meta.js.map +1 -0
- package/dist/components/ai-agent-status/ai-agent-status.d.ts +1 -1
- package/dist/components/ai-approval-request/ai-approval-request.d.ts +67 -0
- package/dist/components/ai-approval-request/ai-approval-request.d.ts.map +1 -0
- package/dist/components/ai-approval-request/ai-approval-request.js +218 -0
- package/dist/components/ai-approval-request/ai-approval-request.js.map +1 -0
- package/dist/components/ai-approval-request/index.d.ts +2 -0
- package/dist/components/ai-approval-request/index.js +2 -0
- package/dist/components/ai-approval-request/meta.js +17 -0
- package/dist/components/ai-approval-request/meta.js.map +1 -0
- package/dist/components/ai-inline-completion/ai-inline-completion.d.ts +45 -0
- package/dist/components/ai-inline-completion/ai-inline-completion.d.ts.map +1 -0
- package/dist/components/ai-inline-completion/ai-inline-completion.js +110 -0
- package/dist/components/ai-inline-completion/ai-inline-completion.js.map +1 -0
- package/dist/components/ai-inline-completion/index.d.ts +2 -0
- package/dist/components/ai-inline-completion/index.js +2 -0
- package/dist/components/ai-inline-completion/meta.js +17 -0
- package/dist/components/ai-inline-completion/meta.js.map +1 -0
- package/dist/components/ai-response/ai-response.d.ts.map +1 -1
- package/dist/components/ai-response/ai-response.js +1 -0
- package/dist/components/ai-response/ai-response.js.map +1 -1
- package/dist/components/ai-structured-output/ai-structured-output.d.ts +75 -0
- package/dist/components/ai-structured-output/ai-structured-output.d.ts.map +1 -0
- package/dist/components/ai-structured-output/ai-structured-output.js +137 -0
- package/dist/components/ai-structured-output/ai-structured-output.js.map +1 -0
- package/dist/components/ai-structured-output/index.d.ts +2 -0
- package/dist/components/ai-structured-output/index.js +2 -0
- package/dist/components/ai-structured-output/meta.js +17 -0
- package/dist/components/ai-structured-output/meta.js.map +1 -0
- package/dist/components/ai-tool/ai-tool.d.ts +1 -1
- package/dist/components/alert/alert.d.ts +1 -1
- package/dist/components/badge/badge.d.ts +1 -1
- package/dist/components/button/button.d.ts +1 -1
- package/dist/components/file-upload/file-upload.d.ts +46 -0
- package/dist/components/file-upload/file-upload.d.ts.map +1 -0
- package/dist/components/file-upload/file-upload.js +204 -0
- package/dist/components/file-upload/file-upload.js.map +1 -0
- package/dist/components/file-upload/index.d.ts +3 -0
- package/dist/components/file-upload/index.js +3 -0
- package/dist/components/file-upload/meta.js +17 -0
- package/dist/components/file-upload/meta.js.map +1 -0
- package/dist/components/file-upload/upload-queue.d.ts +87 -0
- package/dist/components/file-upload/upload-queue.d.ts.map +1 -0
- package/dist/components/file-upload/upload-queue.js +249 -0
- package/dist/components/file-upload/upload-queue.js.map +1 -0
- package/dist/components/meter/index.d.ts +2 -0
- package/dist/components/meter/index.js +2 -0
- package/dist/components/meter/meta.js +17 -0
- package/dist/components/meter/meta.js.map +1 -0
- package/dist/components/meter/meter.d.ts +69 -0
- package/dist/components/meter/meter.d.ts.map +1 -0
- package/dist/components/meter/meter.js +144 -0
- package/dist/components/meter/meter.js.map +1 -0
- package/dist/components/metric-delta/index.d.ts +2 -0
- package/dist/components/metric-delta/index.js +2 -0
- package/dist/components/metric-delta/meta.js +17 -0
- package/dist/components/metric-delta/meta.js.map +1 -0
- package/dist/components/metric-delta/metric-delta.d.ts +59 -0
- package/dist/components/metric-delta/metric-delta.d.ts.map +1 -0
- package/dist/components/metric-delta/metric-delta.js +82 -0
- package/dist/components/metric-delta/metric-delta.js.map +1 -0
- package/dist/components/progress/progress.d.ts +1 -1
- package/dist/components/progress/progress.js +1 -0
- package/dist/components/progress/progress.js.map +1 -1
- package/dist/components/record-diff/index.d.ts +2 -0
- package/dist/components/record-diff/index.js +2 -0
- package/dist/components/record-diff/meta.js +17 -0
- package/dist/components/record-diff/meta.js.map +1 -0
- package/dist/components/record-diff/record-diff.d.ts +56 -0
- package/dist/components/record-diff/record-diff.d.ts.map +1 -0
- package/dist/components/record-diff/record-diff.js +199 -0
- package/dist/components/record-diff/record-diff.js.map +1 -0
- package/dist/components/spinner/spinner.js +1 -0
- package/dist/components/spinner/spinner.js.map +1 -1
- package/dist/components/tags-input/index.d.ts +2 -0
- package/dist/components/tags-input/index.js +2 -0
- package/dist/components/tags-input/meta.js +17 -0
- package/dist/components/tags-input/meta.js.map +1 -0
- package/dist/components/tags-input/tags-input.d.ts +47 -0
- package/dist/components/tags-input/tags-input.d.ts.map +1 -0
- package/dist/components/tags-input/tags-input.js +182 -0
- package/dist/components/tags-input/tags-input.js.map +1 -0
- package/dist/components/toast/toast.d.ts +1 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +12 -1
- package/dist/registry/components.d.ts.map +1 -1
- package/dist/registry/components.js +65 -45
- package/dist/registry/components.js.map +1 -1
- package/package.json +36 -4
- package/src/components/ai-action-ledger/ai-action-ledger.tsx +381 -0
- package/src/components/ai-action-ledger/index.ts +14 -0
- package/src/components/ai-action-ledger/meta.ts +19 -0
- package/src/components/ai-agent-plan/ai-agent-plan.tsx +0 -0
- package/src/components/ai-agent-plan/index.ts +10 -0
- package/src/components/ai-agent-plan/meta.ts +19 -0
- package/src/components/ai-approval-request/ai-approval-request.tsx +368 -0
- package/src/components/ai-approval-request/index.ts +7 -0
- package/src/components/ai-approval-request/meta.ts +21 -0
- package/src/components/ai-inline-completion/ai-inline-completion.tsx +214 -0
- package/src/components/ai-inline-completion/index.ts +1 -0
- package/src/components/ai-inline-completion/meta.ts +20 -0
- package/src/components/ai-response/ai-response.tsx +1 -0
- package/src/components/ai-structured-output/ai-structured-output.tsx +268 -0
- package/src/components/ai-structured-output/index.ts +10 -0
- package/src/components/ai-structured-output/meta.ts +18 -0
- package/src/components/file-upload/file-upload.tsx +322 -0
- package/src/components/file-upload/index.ts +20 -0
- package/src/components/file-upload/meta.ts +21 -0
- package/src/components/file-upload/upload-queue.ts +337 -0
- package/src/components/meter/index.ts +8 -0
- package/src/components/meter/meta.ts +20 -0
- package/src/components/meter/meter.tsx +219 -0
- package/src/components/metric-delta/index.ts +6 -0
- package/src/components/metric-delta/meta.ts +18 -0
- package/src/components/metric-delta/metric-delta.tsx +180 -0
- package/src/components/progress/progress.tsx +3 -0
- package/src/components/record-diff/index.ts +10 -0
- package/src/components/record-diff/meta.ts +19 -0
- package/src/components/record-diff/record-diff.tsx +341 -0
- package/src/components/spinner/spinner.tsx +5 -0
- package/src/components/tags-input/index.ts +1 -0
- package/src/components/tags-input/meta.ts +23 -0
- package/src/components/tags-input/tags-input.tsx +300 -0
- package/src/index.ts +10 -0
- package/src/registry/components.ts +20 -0
package/LICENSE
CHANGED
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# Dowel
|
|
4
|
+
|
|
5
|
+
### Source-first React components for SaaS and AI products
|
|
6
|
+
|
|
7
|
+
**You install the code, not a dependency.**
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@dowel-ui/react)
|
|
10
|
+
[](https://github.com/aqkprogrammer/dowel-ui/blob/main/LICENSE)
|
|
11
|
+
[](https://www.typescriptlang.org)
|
|
12
|
+
|
|
13
|
+
[**Documentation**](https://dowel-eight.vercel.app) · [**Components**](https://dowel-eight.vercel.app/docs/components) · [**GitHub**](https://github.com/aqkprogrammer/dowel-ui)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
A dowel is the hidden pin that joins two pieces of wood without a visible
|
|
20
|
+
fastener. That is the idea here: components that fit together, hold, and then
|
|
21
|
+
get out of your way.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @dowel-ui/cli init
|
|
25
|
+
npx @dowel-ui/cli add button
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
That second command writes `button.tsx` into your project. Open it. Change it.
|
|
29
|
+
It is your file now — there is no package standing between you and the markup.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Why another component library
|
|
34
|
+
|
|
35
|
+
Most libraries hand you a black box and a theming API. When the design calls for
|
|
36
|
+
something the API did not anticipate, you fight it, wrap it, or override it with
|
|
37
|
+
`!important`.
|
|
38
|
+
|
|
39
|
+
Dowel gives you the source instead.
|
|
40
|
+
|
|
41
|
+
| | Dowel | Typical library |
|
|
42
|
+
| -------------------- | ----------------------------- | ---------------------------- |
|
|
43
|
+
| Where the code lives | **Your repo** | `node_modules` |
|
|
44
|
+
| Changing a component | **Edit the file** | Props, overrides, forks |
|
|
45
|
+
| Upgrades | **You choose, per component** | All at once, or not at all |
|
|
46
|
+
| Reading the source | **It is right there** | Sourcemaps, if you are lucky |
|
|
47
|
+
| Runtime you ship | **Only what you used** | The library |
|
|
48
|
+
|
|
49
|
+
You can still `npm install @dowel-ui/react` and import from it — that path
|
|
50
|
+
works, and is the fastest way to try things. Most people move to the CLI once
|
|
51
|
+
they want to change something.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## What is in it
|
|
56
|
+
|
|
57
|
+
**56 components** and **8 blocks**, every one keyboard-operable and audited for
|
|
58
|
+
contrast.
|
|
59
|
+
|
|
60
|
+
**AI** — the reason this library exists
|
|
61
|
+
Conversation · Message · Response · Prompt Input · Tool Call · Reasoning ·
|
|
62
|
+
Sources · Model Selector · Token Usage · Agent Status · **Agent Plan** ·
|
|
63
|
+
**Approval Request** · **Action Ledger** · **Structured Output** ·
|
|
64
|
+
**Inline Completion**
|
|
65
|
+
|
|
66
|
+
**Forms**
|
|
67
|
+
Input · Label · Checkbox · Radio Group · Switch · Slider · Select · Combobox ·
|
|
68
|
+
Form · Calendar · Date Picker · **Tags Input** · **File Upload**
|
|
69
|
+
|
|
70
|
+
**Overlays**
|
|
71
|
+
Dialog · Sheet · Drawer · Popover · Tooltip · Dropdown Menu
|
|
72
|
+
|
|
73
|
+
**Data**
|
|
74
|
+
Table · Data Table · Accordion · Activity Feed · Code Block · **Metric Delta** ·
|
|
75
|
+
**Record Diff**
|
|
76
|
+
|
|
77
|
+
**Feedback**
|
|
78
|
+
Alert · Toast · Progress · **Meter** · Skeleton · Spinner · Empty State
|
|
79
|
+
|
|
80
|
+
**Navigation & layout**
|
|
81
|
+
Tabs · Command · Pagination · Card · Badge · Avatar · Button · Separator
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## The AI components
|
|
86
|
+
|
|
87
|
+
Most "AI component libraries" ship a chat transcript and stop. Real AI features
|
|
88
|
+
inside real software are extraction, enrichment, autofill and agents that
|
|
89
|
+
_change things_ — and those need surfaces nobody else provides.
|
|
90
|
+
|
|
91
|
+
**`ai-agent-plan` + `ai-approval-request` + `ai-action-ledger`** — the whole
|
|
92
|
+
sequence, which no other library covers: what the agent intends, the gate where
|
|
93
|
+
you approve it (after correcting the arguments it got wrong), and the record of
|
|
94
|
+
what it actually did.
|
|
95
|
+
|
|
96
|
+
**`ai-action-ledger`** — what the agent actually did, and what can be undone.
|
|
97
|
+
Everyone ships approval _before_ a tool runs. Nothing ships the part after,
|
|
98
|
+
where a deletion can be reverted, a refund can only be offset by another
|
|
99
|
+
transaction, and a sent email cannot be taken back at all. Hiding those three
|
|
100
|
+
behind one "Undo" button is a lie you discover after clicking.
|
|
101
|
+
|
|
102
|
+
**`ai-structured-output`** — an object arriving field by field, with the layout
|
|
103
|
+
reserved up front so nothing jumps as it fills in.
|
|
104
|
+
|
|
105
|
+
**`ai-inline-completion`** — ghost text in a real textarea. Tab accepts,
|
|
106
|
+
Alt+Right takes a word, and Escape always gives Tab back to focus management, so
|
|
107
|
+
a keyboard user is never trapped in the field.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Blocks
|
|
112
|
+
|
|
113
|
+
Whole sections, assembled from components. Installing one brings everything it
|
|
114
|
+
is built from:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx @dowel-ui/cli add ai-chat
|
|
118
|
+
# ✓ Added ai-conversation, ai-message, select, ai-model-selector,
|
|
119
|
+
# ai-prompt-input, ai-reasoning, ai-response, ai-sources, ai-token-usage,
|
|
120
|
+
# ai-tool, spinner, button, empty-state, ai-chat
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Login · Signup · Forgot Password · Dashboard · Admin Users · Settings ·
|
|
124
|
+
Pricing · AI Chat
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Theming
|
|
129
|
+
|
|
130
|
+
Two tiers of design tokens, in plain CSS. No config file, no JavaScript theme
|
|
131
|
+
object.
|
|
132
|
+
|
|
133
|
+
```css
|
|
134
|
+
:root {
|
|
135
|
+
--primary: oklch(0.55 0.2 275);
|
|
136
|
+
--radius-scale: 1; /* one knob re-proportions every corner in the system */
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Seven presets ship with it, and every one passes **WCAG AA contrast in both
|
|
141
|
+
light and dark** — verified by an audit that checks 322 colour pairs across 14
|
|
142
|
+
schemes on every commit, not by eye.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Accessibility is not a checklist here
|
|
147
|
+
|
|
148
|
+
Every component is tested for behaviour, not appearance. A few things that fell
|
|
149
|
+
out of taking it seriously:
|
|
150
|
+
|
|
151
|
+
- `Meter` uses `role="meter"`, not `progressbar` — a level that may never move
|
|
152
|
+
is a different thing from a task heading for completion, and screen readers
|
|
153
|
+
say them differently.
|
|
154
|
+
- `MetricDelta` never puts meaning in colour alone. Rising churn is not good
|
|
155
|
+
news, and a red triangle is not information.
|
|
156
|
+
- Every pointer gesture has a keyboard equivalent, or the component was not
|
|
157
|
+
built. One candidate was dropped for failing exactly that test.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Requirements
|
|
162
|
+
|
|
163
|
+
React 19 · TypeScript · Tailwind CSS v4
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Install
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# The CLI — writes components into your project as source
|
|
171
|
+
npx @dowel-ui/cli init
|
|
172
|
+
|
|
173
|
+
# Or import from the package
|
|
174
|
+
npm install @dowel-ui/react
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
import { Button } from "@dowel-ui/react";
|
|
179
|
+
|
|
180
|
+
export function Save() {
|
|
181
|
+
return <Button variant="primary">Save changes</Button>;
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
<div align="center">
|
|
188
|
+
|
|
189
|
+
[**Read the docs →**](https://dowel-eight.vercel.app)
|
|
190
|
+
|
|
191
|
+
MIT licensed
|
|
192
|
+
|
|
193
|
+
</div>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Collapsible } from "radix-ui";
|
|
2
|
+
import { VariantProps } from "class-variance-authority";
|
|
3
|
+
import { ComponentPropsWithRef, ReactNode } from "react";
|
|
4
|
+
//#region src/components/ai-action-ledger/ai-action-ledger.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* What an agent actually did, and what can be undone.
|
|
7
|
+
*
|
|
8
|
+
* Every AI component library stops at the conversation boundary: approve a tool
|
|
9
|
+
* call before it runs, then treat whatever it did to the database as the
|
|
10
|
+
* application's problem. But the thing that blocks agents from getting write
|
|
11
|
+
* access in real products is not "did I approve this", it is "it did the wrong
|
|
12
|
+
* thing, now what".
|
|
13
|
+
*
|
|
14
|
+
* The load-bearing idea is that undo is not uniform. Deleting a row can be
|
|
15
|
+
* reverted. A refund can be compensated by another transaction, which is not
|
|
16
|
+
* the same as never having happened. A sent email cannot be taken back at all.
|
|
17
|
+
* Presenting all three behind one "Undo" button is a lie the user only
|
|
18
|
+
* discovers after clicking, so reversibility is a required field and the
|
|
19
|
+
* affordance follows from it.
|
|
20
|
+
*
|
|
21
|
+
* This renders and selects. It performs nothing — `onRevert` receives the
|
|
22
|
+
* chosen actions and the application decides what that means.
|
|
23
|
+
*/
|
|
24
|
+
type Reversibility = "revertible" | "compensable" | "irreversible";
|
|
25
|
+
type ActionStatus = "applied" | "reverting" | "reverted" | "failed";
|
|
26
|
+
interface LedgerAction {
|
|
27
|
+
id: string;
|
|
28
|
+
/** What was done, in the user's language: "Deleted 3 contacts". */
|
|
29
|
+
summary: string;
|
|
30
|
+
reversibility: Reversibility;
|
|
31
|
+
status: ActionStatus;
|
|
32
|
+
/** What the action touched, for grouping and scanning. */
|
|
33
|
+
target?: string;
|
|
34
|
+
timestamp?: string;
|
|
35
|
+
/** Why a revert failed. Shown only when status is "failed". */
|
|
36
|
+
error?: string;
|
|
37
|
+
}
|
|
38
|
+
declare const ledgerEntryVariants: (props?: ({
|
|
39
|
+
status?: "applied" | "reverting" | "reverted" | "failed" | null | undefined;
|
|
40
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
41
|
+
interface ActionLedgerProps extends Omit<ComponentPropsWithRef<"div">, "onSelect"> {
|
|
42
|
+
actions: LedgerAction[];
|
|
43
|
+
/** Receives the selected actions. The application performs the revert. */
|
|
44
|
+
onRevert?: (actions: LedgerAction[]) => void;
|
|
45
|
+
children?: ReactNode;
|
|
46
|
+
}
|
|
47
|
+
declare function ActionLedger({ className, actions, onRevert, children, ...props }: ActionLedgerProps): import("react").JSX.Element;
|
|
48
|
+
/**
|
|
49
|
+
* Selection controls and the revert action.
|
|
50
|
+
*
|
|
51
|
+
* The count is stated on the button rather than beside it, so the accessible
|
|
52
|
+
* name says what pressing it will do.
|
|
53
|
+
*/
|
|
54
|
+
declare function ActionLedgerToolbar({ className, ...props }: ComponentPropsWithRef<"div">): import("react").JSX.Element;
|
|
55
|
+
interface ActionLedgerEntryProps extends Omit<ComponentPropsWithRef<"li">, "children">, VariantProps<typeof ledgerEntryVariants> {
|
|
56
|
+
action: LedgerAction;
|
|
57
|
+
/** The payload inspector. Collapsed, because it is provenance not content. */
|
|
58
|
+
children?: ReactNode;
|
|
59
|
+
}
|
|
60
|
+
declare function ActionLedgerEntry({ className, action, children, ...props }: ActionLedgerEntryProps): import("react").JSX.Element;
|
|
61
|
+
/** The list wrapper. A real list, so its length is announced. */
|
|
62
|
+
declare function ActionLedgerList({ className, ...props }: ComponentPropsWithRef<"ul">): import("react").JSX.Element;
|
|
63
|
+
/** Collapsed payload for one entry: the arguments, the result, the diff. */
|
|
64
|
+
declare function ActionLedgerPayload({ className, label, children, ...props }: ComponentPropsWithRef<typeof Collapsible.Root> & {
|
|
65
|
+
label?: string;
|
|
66
|
+
}): import("react").JSX.Element;
|
|
67
|
+
/**
|
|
68
|
+
* A summary of what is selected and what it will and will not undo.
|
|
69
|
+
*
|
|
70
|
+
* This exists because "Undo 4 selected" is not enough information when one of
|
|
71
|
+
* the four can only be compensated. Saying so before the click is the whole
|
|
72
|
+
* difference between an honest control and a misleading one.
|
|
73
|
+
*/
|
|
74
|
+
declare function ActionLedgerSelectionSummary({ className, ...props }: ComponentPropsWithRef<"p">): import("react").JSX.Element | null;
|
|
75
|
+
//#endregion
|
|
76
|
+
export { ActionLedger, ActionLedgerEntry, ActionLedgerEntryProps, ActionLedgerList, ActionLedgerPayload, ActionLedgerProps, ActionLedgerSelectionSummary, ActionLedgerToolbar, ActionStatus, LedgerAction, Reversibility, ledgerEntryVariants };
|
|
77
|
+
//# sourceMappingURL=ai-action-ledger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-action-ledger.d.ts","names":[],"sources":["../../../src/components/ai-action-ledger/ai-action-ledger.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;KAqCY;KAEA;UAEK;EACf;;EAEA;EACA,eAAe;EACf,QAAQ;;EAER;EACA;;EAEA;;cAqBI,sBAAmB;;6CAUvB;UAsBe,0BAA0B,KAAK;EAC9C,SAAS;;EAET,YAAY,SAAS;EACrB,WAAW;;iBAGG,eACd,WACA,SACA,UACA,aACG,SACF,oCAAiB,IAAA;;;;;;;iBAmDJ,sBAAsB,cAAc,SAAS,+CAA4B,IAAA;UAiDxE,+BAEb,KAAK,0CACL,oBAAoB;EACtB,QAAQ;;EAER,WAAW;;iBAGG,oBACd,WACA,QACA,aACG,SACF,yCAAsB,IAAA;;iBA8ET,mBAAmB,cAAc,SAAS,8CAA2B,IAAA;;iBAWrE,sBACd,WACA,OACA,aACG,SACF,6BAA6B,YAAqB;EAAU;oBAAgB,IAAA;;;;;;;;iBA2B/D,+BACd,cACG,SACF,6CAA0B,IAAA"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { focusRing } from "../../lib/styles.js";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { Collapsible } from "radix-ui";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { cva } from "class-variance-authority";
|
|
7
|
+
import { createContext, useContext, useId, useMemo, useState } from "react";
|
|
8
|
+
//#region src/components/ai-action-ledger/ai-action-ledger.tsx
|
|
9
|
+
const REVERSIBILITY_LABEL = {
|
|
10
|
+
revertible: "Can be undone",
|
|
11
|
+
compensable: "Can be offset, not undone",
|
|
12
|
+
irreversible: "Cannot be undone"
|
|
13
|
+
};
|
|
14
|
+
const STATUS_LABEL = {
|
|
15
|
+
applied: "Applied",
|
|
16
|
+
reverting: "Reverting",
|
|
17
|
+
reverted: "Reverted",
|
|
18
|
+
failed: "Revert failed"
|
|
19
|
+
};
|
|
20
|
+
/** Only an applied action can be selected — the rest are already resolved. */
|
|
21
|
+
function isSelectable(action) {
|
|
22
|
+
return action.status === "applied" && action.reversibility !== "irreversible";
|
|
23
|
+
}
|
|
24
|
+
const ledgerEntryVariants = cva("rounded-lg border text-sm transition-colors", {
|
|
25
|
+
variants: { status: {
|
|
26
|
+
applied: "border-border bg-card",
|
|
27
|
+
reverting: "border-info/30 bg-info/5",
|
|
28
|
+
reverted: "border-border bg-muted/40",
|
|
29
|
+
failed: "border-destructive/40 bg-destructive/5"
|
|
30
|
+
} },
|
|
31
|
+
defaultVariants: { status: "applied" }
|
|
32
|
+
});
|
|
33
|
+
const LedgerContext = createContext(null);
|
|
34
|
+
function useLedgerContext(component) {
|
|
35
|
+
const context = useContext(LedgerContext);
|
|
36
|
+
if (!context) throw new Error(`${component} must be rendered inside <ActionLedger>.`);
|
|
37
|
+
return context;
|
|
38
|
+
}
|
|
39
|
+
function ActionLedger({ className, actions, onRevert, children, ...props }) {
|
|
40
|
+
const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
|
|
41
|
+
const selectableIds = useMemo(() => actions.filter(isSelectable).map((action) => action.id), [actions]);
|
|
42
|
+
const context = useMemo(() => ({
|
|
43
|
+
actions,
|
|
44
|
+
selected,
|
|
45
|
+
selectableIds,
|
|
46
|
+
onRevert,
|
|
47
|
+
toggle: (id) => {
|
|
48
|
+
setSelected((current) => {
|
|
49
|
+
const next = new Set(current);
|
|
50
|
+
if (next.has(id)) next.delete(id);
|
|
51
|
+
else next.add(id);
|
|
52
|
+
return next;
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
selectAll: () => {
|
|
56
|
+
setSelected(new Set(selectableIds));
|
|
57
|
+
},
|
|
58
|
+
clear: () => {
|
|
59
|
+
setSelected(/* @__PURE__ */ new Set());
|
|
60
|
+
}
|
|
61
|
+
}), [
|
|
62
|
+
actions,
|
|
63
|
+
selected,
|
|
64
|
+
selectableIds,
|
|
65
|
+
onRevert
|
|
66
|
+
]);
|
|
67
|
+
return /* @__PURE__ */ jsx(LedgerContext.Provider, {
|
|
68
|
+
value: context,
|
|
69
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
70
|
+
"data-slot": "action-ledger",
|
|
71
|
+
className: cn("flex flex-col gap-3", className),
|
|
72
|
+
...props,
|
|
73
|
+
children
|
|
74
|
+
})
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Selection controls and the revert action.
|
|
79
|
+
*
|
|
80
|
+
* The count is stated on the button rather than beside it, so the accessible
|
|
81
|
+
* name says what pressing it will do.
|
|
82
|
+
*/
|
|
83
|
+
function ActionLedgerToolbar({ className, ...props }) {
|
|
84
|
+
const { selected, selectableIds, actions, selectAll, clear, onRevert } = useLedgerContext("ActionLedgerToolbar");
|
|
85
|
+
const count = selected.size;
|
|
86
|
+
const allSelected = selectableIds.length > 0 && count === selectableIds.length;
|
|
87
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
88
|
+
"data-slot": "action-ledger-toolbar",
|
|
89
|
+
className: cn("flex flex-wrap items-center gap-2", className),
|
|
90
|
+
...props,
|
|
91
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
92
|
+
type: "button",
|
|
93
|
+
disabled: selectableIds.length === 0,
|
|
94
|
+
onClick: allSelected ? clear : selectAll,
|
|
95
|
+
className: cn("rounded-md border border-input bg-background px-2.5 py-1 text-xs font-medium", "transition-colors hover:bg-accent hover:text-accent-foreground", "disabled:pointer-events-none disabled:opacity-50", focusRing),
|
|
96
|
+
children: allSelected ? "Clear selection" : `Select all ${String(selectableIds.length)} undoable`
|
|
97
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
98
|
+
type: "button",
|
|
99
|
+
"data-slot": "action-ledger-revert",
|
|
100
|
+
disabled: count === 0,
|
|
101
|
+
onClick: () => {
|
|
102
|
+
onRevert?.(actions.filter((action) => selected.has(action.id)));
|
|
103
|
+
},
|
|
104
|
+
className: cn("rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground", "transition-colors hover:bg-primary-hover", "disabled:pointer-events-none disabled:opacity-50", focusRing),
|
|
105
|
+
children: count === 0 ? "Undo selected" : `Undo ${String(count)} selected`
|
|
106
|
+
})]
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function ActionLedgerEntry({ className, action, children, ...props }) {
|
|
110
|
+
const { selected, toggle } = useLedgerContext("ActionLedgerEntry");
|
|
111
|
+
const checkboxId = useId();
|
|
112
|
+
const selectable = isSelectable(action);
|
|
113
|
+
const isSelected = selected.has(action.id);
|
|
114
|
+
return /* @__PURE__ */ jsx("li", {
|
|
115
|
+
"data-slot": "action-ledger-entry",
|
|
116
|
+
"data-status": action.status,
|
|
117
|
+
"data-reversibility": action.reversibility,
|
|
118
|
+
className: cn(ledgerEntryVariants({ status: action.status }), className),
|
|
119
|
+
...props,
|
|
120
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
121
|
+
className: "flex items-start gap-3 p-3",
|
|
122
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
123
|
+
type: "checkbox",
|
|
124
|
+
id: checkboxId,
|
|
125
|
+
disabled: !selectable,
|
|
126
|
+
checked: isSelected,
|
|
127
|
+
onChange: () => {
|
|
128
|
+
toggle(action.id);
|
|
129
|
+
},
|
|
130
|
+
className: cn("mt-0.5 size-4 shrink-0 rounded border-input", "disabled:cursor-not-allowed disabled:opacity-40", focusRing)
|
|
131
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
132
|
+
className: "flex min-w-0 flex-1 flex-col gap-1",
|
|
133
|
+
children: [
|
|
134
|
+
/* @__PURE__ */ jsx("label", {
|
|
135
|
+
htmlFor: checkboxId,
|
|
136
|
+
className: cn("font-medium", !selectable && "opacity-70"),
|
|
137
|
+
children: action.summary
|
|
138
|
+
}),
|
|
139
|
+
/* @__PURE__ */ jsxs("p", {
|
|
140
|
+
className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground",
|
|
141
|
+
children: [
|
|
142
|
+
/* @__PURE__ */ jsx("span", {
|
|
143
|
+
"data-slot": "action-ledger-reversibility",
|
|
144
|
+
children: REVERSIBILITY_LABEL[action.reversibility]
|
|
145
|
+
}),
|
|
146
|
+
/* @__PURE__ */ jsx("span", {
|
|
147
|
+
"aria-hidden": "true",
|
|
148
|
+
children: "·"
|
|
149
|
+
}),
|
|
150
|
+
/* @__PURE__ */ jsx("span", {
|
|
151
|
+
"data-slot": "action-ledger-status",
|
|
152
|
+
children: STATUS_LABEL[action.status]
|
|
153
|
+
}),
|
|
154
|
+
action.target ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
155
|
+
"aria-hidden": "true",
|
|
156
|
+
children: "·"
|
|
157
|
+
}), /* @__PURE__ */ jsx("span", { children: action.target })] }) : null,
|
|
158
|
+
action.timestamp ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
159
|
+
"aria-hidden": "true",
|
|
160
|
+
children: "·"
|
|
161
|
+
}), /* @__PURE__ */ jsx("time", {
|
|
162
|
+
dateTime: action.timestamp,
|
|
163
|
+
children: action.timestamp
|
|
164
|
+
})] }) : null
|
|
165
|
+
]
|
|
166
|
+
}),
|
|
167
|
+
action.status === "failed" && action.error ? /* @__PURE__ */ jsx("p", {
|
|
168
|
+
"data-slot": "action-ledger-error",
|
|
169
|
+
className: "text-xs text-destructive",
|
|
170
|
+
children: action.error
|
|
171
|
+
}) : null,
|
|
172
|
+
children
|
|
173
|
+
]
|
|
174
|
+
})]
|
|
175
|
+
})
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/** The list wrapper. A real list, so its length is announced. */
|
|
179
|
+
function ActionLedgerList({ className, ...props }) {
|
|
180
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
181
|
+
"data-slot": "action-ledger-list",
|
|
182
|
+
className: cn("flex list-none flex-col gap-2", className),
|
|
183
|
+
...props
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/** Collapsed payload for one entry: the arguments, the result, the diff. */
|
|
187
|
+
function ActionLedgerPayload({ className, label = "Details", children, ...props }) {
|
|
188
|
+
return /* @__PURE__ */ jsxs(Collapsible.Root, {
|
|
189
|
+
"data-slot": "action-ledger-payload",
|
|
190
|
+
className: cn("mt-1", className),
|
|
191
|
+
...props,
|
|
192
|
+
children: [/* @__PURE__ */ jsx(Collapsible.Trigger, {
|
|
193
|
+
className: cn("rounded-md text-xs text-muted-foreground transition-colors hover:text-foreground", focusRing),
|
|
194
|
+
children: label
|
|
195
|
+
}), /* @__PURE__ */ jsx(Collapsible.Content, {
|
|
196
|
+
className: "pt-2",
|
|
197
|
+
children
|
|
198
|
+
})]
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* A summary of what is selected and what it will and will not undo.
|
|
203
|
+
*
|
|
204
|
+
* This exists because "Undo 4 selected" is not enough information when one of
|
|
205
|
+
* the four can only be compensated. Saying so before the click is the whole
|
|
206
|
+
* difference between an honest control and a misleading one.
|
|
207
|
+
*/
|
|
208
|
+
function ActionLedgerSelectionSummary({ className, ...props }) {
|
|
209
|
+
const { actions, selected } = useLedgerContext("ActionLedgerSelectionSummary");
|
|
210
|
+
const chosen = actions.filter((action) => selected.has(action.id));
|
|
211
|
+
const compensable = chosen.filter((a) => a.reversibility === "compensable").length;
|
|
212
|
+
if (chosen.length === 0) return null;
|
|
213
|
+
return /* @__PURE__ */ jsxs("p", {
|
|
214
|
+
"data-slot": "action-ledger-selection-summary",
|
|
215
|
+
"aria-live": "polite",
|
|
216
|
+
className: cn("text-xs text-muted-foreground", className),
|
|
217
|
+
...props,
|
|
218
|
+
children: [`${String(chosen.length)} selected`, compensable > 0 ? `, of which ${String(compensable)} can only be offset by a further action, not undone` : ""]
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
222
|
+
export { ActionLedger, ActionLedgerEntry, ActionLedgerList, ActionLedgerPayload, ActionLedgerSelectionSummary, ActionLedgerToolbar, ledgerEntryVariants };
|
|
223
|
+
|
|
224
|
+
//# sourceMappingURL=ai-action-ledger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-action-ledger.js","names":["CollapsiblePrimitive"],"sources":["../../../src/components/ai-action-ledger/ai-action-ledger.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Collapsible as CollapsiblePrimitive } from \"radix-ui\";\nimport {\n createContext,\n useContext,\n useId,\n useMemo,\n useState,\n type ComponentPropsWithRef,\n type ReactNode,\n} from \"react\";\n\nimport { focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * What an agent actually did, and what can be undone.\n *\n * Every AI component library stops at the conversation boundary: approve a tool\n * call before it runs, then treat whatever it did to the database as the\n * application's problem. But the thing that blocks agents from getting write\n * access in real products is not \"did I approve this\", it is \"it did the wrong\n * thing, now what\".\n *\n * The load-bearing idea is that undo is not uniform. Deleting a row can be\n * reverted. A refund can be compensated by another transaction, which is not\n * the same as never having happened. A sent email cannot be taken back at all.\n * Presenting all three behind one \"Undo\" button is a lie the user only\n * discovers after clicking, so reversibility is a required field and the\n * affordance follows from it.\n *\n * This renders and selects. It performs nothing — `onRevert` receives the\n * chosen actions and the application decides what that means.\n */\n\nexport type Reversibility = \"revertible\" | \"compensable\" | \"irreversible\";\n\nexport type ActionStatus = \"applied\" | \"reverting\" | \"reverted\" | \"failed\";\n\nexport interface LedgerAction {\n id: string;\n /** What was done, in the user's language: \"Deleted 3 contacts\". */\n summary: string;\n reversibility: Reversibility;\n status: ActionStatus;\n /** What the action touched, for grouping and scanning. */\n target?: string;\n timestamp?: string;\n /** Why a revert failed. Shown only when status is \"failed\". */\n error?: string;\n}\n\nconst REVERSIBILITY_LABEL: Record<Reversibility, string> = {\n revertible: \"Can be undone\",\n compensable: \"Can be offset, not undone\",\n irreversible: \"Cannot be undone\",\n};\n\nconst STATUS_LABEL: Record<ActionStatus, string> = {\n applied: \"Applied\",\n reverting: \"Reverting\",\n reverted: \"Reverted\",\n failed: \"Revert failed\",\n};\n\n/** Only an applied action can be selected — the rest are already resolved. */\nfunction isSelectable(action: LedgerAction): boolean {\n return action.status === \"applied\" && action.reversibility !== \"irreversible\";\n}\n\nconst ledgerEntryVariants = cva(\"rounded-lg border text-sm transition-colors\", {\n variants: {\n status: {\n applied: \"border-border bg-card\",\n reverting: \"border-info/30 bg-info/5\",\n reverted: \"border-border bg-muted/40\",\n failed: \"border-destructive/40 bg-destructive/5\",\n },\n },\n defaultVariants: { status: \"applied\" },\n});\n\ninterface LedgerContextValue {\n actions: LedgerAction[];\n selected: Set<string>;\n toggle: (id: string) => void;\n selectAll: () => void;\n clear: () => void;\n selectableIds: string[];\n onRevert?: (actions: LedgerAction[]) => void;\n}\n\nconst LedgerContext = createContext<LedgerContextValue | null>(null);\n\nfunction useLedgerContext(component: string): LedgerContextValue {\n const context = useContext(LedgerContext);\n if (!context) {\n throw new Error(`${component} must be rendered inside <ActionLedger>.`);\n }\n return context;\n}\n\nexport interface ActionLedgerProps extends Omit<ComponentPropsWithRef<\"div\">, \"onSelect\"> {\n actions: LedgerAction[];\n /** Receives the selected actions. The application performs the revert. */\n onRevert?: (actions: LedgerAction[]) => void;\n children?: ReactNode;\n}\n\nexport function ActionLedger({\n className,\n actions,\n onRevert,\n children,\n ...props\n}: ActionLedgerProps) {\n const [selected, setSelected] = useState<Set<string>>(new Set());\n\n const selectableIds = useMemo(\n () => actions.filter(isSelectable).map((action) => action.id),\n [actions],\n );\n\n const context = useMemo<LedgerContextValue>(\n () => ({\n actions,\n selected,\n selectableIds,\n onRevert,\n toggle: (id: string) => {\n setSelected((current) => {\n const next = new Set(current);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n return next;\n });\n },\n selectAll: () => {\n setSelected(new Set(selectableIds));\n },\n clear: () => {\n setSelected(new Set());\n },\n }),\n [actions, selected, selectableIds, onRevert],\n );\n\n return (\n <LedgerContext.Provider value={context}>\n <div\n data-slot=\"action-ledger\"\n className={cn(\"flex flex-col gap-3\", className)}\n {...props}\n >\n {children}\n </div>\n </LedgerContext.Provider>\n );\n}\n\n/**\n * Selection controls and the revert action.\n *\n * The count is stated on the button rather than beside it, so the accessible\n * name says what pressing it will do.\n */\nexport function ActionLedgerToolbar({ className, ...props }: ComponentPropsWithRef<\"div\">) {\n const { selected, selectableIds, actions, selectAll, clear, onRevert } =\n useLedgerContext(\"ActionLedgerToolbar\");\n\n const count = selected.size;\n const allSelected = selectableIds.length > 0 && count === selectableIds.length;\n\n return (\n <div\n data-slot=\"action-ledger-toolbar\"\n className={cn(\"flex flex-wrap items-center gap-2\", className)}\n {...props}\n >\n <button\n type=\"button\"\n disabled={selectableIds.length === 0}\n onClick={allSelected ? clear : selectAll}\n className={cn(\n \"rounded-md border border-input bg-background px-2.5 py-1 text-xs font-medium\",\n \"transition-colors hover:bg-accent hover:text-accent-foreground\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n focusRing,\n )}\n >\n {allSelected\n ? \"Clear selection\"\n : `Select all ${String(selectableIds.length)} undoable`}\n </button>\n\n <button\n type=\"button\"\n data-slot=\"action-ledger-revert\"\n disabled={count === 0}\n onClick={() => {\n onRevert?.(actions.filter((action) => selected.has(action.id)));\n }}\n className={cn(\n \"rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground\",\n \"transition-colors hover:bg-primary-hover\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n focusRing,\n )}\n >\n {count === 0 ? \"Undo selected\" : `Undo ${String(count)} selected`}\n </button>\n </div>\n );\n}\n\nexport interface ActionLedgerEntryProps\n extends\n Omit<ComponentPropsWithRef<\"li\">, \"children\">,\n VariantProps<typeof ledgerEntryVariants> {\n action: LedgerAction;\n /** The payload inspector. Collapsed, because it is provenance not content. */\n children?: ReactNode;\n}\n\nexport function ActionLedgerEntry({\n className,\n action,\n children,\n ...props\n}: ActionLedgerEntryProps) {\n const { selected, toggle } = useLedgerContext(\"ActionLedgerEntry\");\n const checkboxId = useId();\n const selectable = isSelectable(action);\n const isSelected = selected.has(action.id);\n\n return (\n <li\n data-slot=\"action-ledger-entry\"\n data-status={action.status}\n data-reversibility={action.reversibility}\n className={cn(ledgerEntryVariants({ status: action.status }), className)}\n {...props}\n >\n <div className=\"flex items-start gap-3 p-3\">\n {/* A native checkbox: this is a list of things to act on, and the\n selection has to survive without JavaScript-driven roles. */}\n <input\n type=\"checkbox\"\n id={checkboxId}\n disabled={!selectable}\n checked={isSelected}\n onChange={() => {\n toggle(action.id);\n }}\n className={cn(\n \"mt-0.5 size-4 shrink-0 rounded border-input\",\n \"disabled:cursor-not-allowed disabled:opacity-40\",\n focusRing,\n )}\n />\n\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <label\n htmlFor={checkboxId}\n className={cn(\"font-medium\", !selectable && \"opacity-70\")}\n >\n {action.summary}\n </label>\n\n <p className=\"flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground\">\n {/* Reversibility in words, always. It decides whether the undo\n button means anything, so it cannot be a colour or an icon. */}\n <span data-slot=\"action-ledger-reversibility\">\n {REVERSIBILITY_LABEL[action.reversibility]}\n </span>\n <span aria-hidden=\"true\">·</span>\n <span data-slot=\"action-ledger-status\">{STATUS_LABEL[action.status]}</span>\n {action.target ? (\n <>\n <span aria-hidden=\"true\">·</span>\n <span>{action.target}</span>\n </>\n ) : null}\n {action.timestamp ? (\n <>\n <span aria-hidden=\"true\">·</span>\n <time dateTime={action.timestamp}>{action.timestamp}</time>\n </>\n ) : null}\n </p>\n\n {/* A failed revert is the state people most need to understand, so\n the reason is shown rather than hidden behind the inspector. */}\n {action.status === \"failed\" && action.error ? (\n <p data-slot=\"action-ledger-error\" className=\"text-xs text-destructive\">\n {action.error}\n </p>\n ) : null}\n\n {children}\n </div>\n </div>\n </li>\n );\n}\n\n/** The list wrapper. A real list, so its length is announced. */\nexport function ActionLedgerList({ className, ...props }: ComponentPropsWithRef<\"ul\">) {\n return (\n <ul\n data-slot=\"action-ledger-list\"\n className={cn(\"flex list-none flex-col gap-2\", className)}\n {...props}\n />\n );\n}\n\n/** Collapsed payload for one entry: the arguments, the result, the diff. */\nexport function ActionLedgerPayload({\n className,\n label = \"Details\",\n children,\n ...props\n}: ComponentPropsWithRef<typeof CollapsiblePrimitive.Root> & { label?: string }) {\n return (\n <CollapsiblePrimitive.Root\n data-slot=\"action-ledger-payload\"\n className={cn(\"mt-1\", className)}\n {...props}\n >\n <CollapsiblePrimitive.Trigger\n className={cn(\n \"rounded-md text-xs text-muted-foreground transition-colors hover:text-foreground\",\n focusRing,\n )}\n >\n {label}\n </CollapsiblePrimitive.Trigger>\n <CollapsiblePrimitive.Content className=\"pt-2\">{children}</CollapsiblePrimitive.Content>\n </CollapsiblePrimitive.Root>\n );\n}\n\n/**\n * A summary of what is selected and what it will and will not undo.\n *\n * This exists because \"Undo 4 selected\" is not enough information when one of\n * the four can only be compensated. Saying so before the click is the whole\n * difference between an honest control and a misleading one.\n */\nexport function ActionLedgerSelectionSummary({\n className,\n ...props\n}: ComponentPropsWithRef<\"p\">) {\n const { actions, selected } = useLedgerContext(\"ActionLedgerSelectionSummary\");\n\n const chosen = actions.filter((action) => selected.has(action.id));\n const compensable = chosen.filter((a) => a.reversibility === \"compensable\").length;\n\n if (chosen.length === 0) return null;\n\n return (\n <p\n data-slot=\"action-ledger-selection-summary\"\n // Selection changes are user-driven and the wording matters, so it is\n // announced politely rather than silently updating under the button.\n aria-live=\"polite\"\n className={cn(\"text-xs text-muted-foreground\", className)}\n {...props}\n >\n {`${String(chosen.length)} selected`}\n {compensable > 0\n ? `, of which ${String(compensable)} can only be offset by a further action, not undone`\n : \"\"}\n </p>\n );\n}\n\nexport { ledgerEntryVariants };\n"],"mappings":";;;;;;;;AAsDA,MAAM,sBAAqD;CACzD,YAAY;CACZ,aAAa;CACb,cAAc;AAChB;AAEA,MAAM,eAA6C;CACjD,SAAS;CACT,WAAW;CACX,UAAU;CACV,QAAQ;AACV;;AAGA,SAAS,aAAa,QAA+B;CACnD,OAAO,OAAO,WAAW,aAAa,OAAO,kBAAkB;AACjE;AAEA,MAAM,sBAAsB,IAAI,+CAA+C;CAC7E,UAAU,EACR,QAAQ;EACN,SAAS;EACT,WAAW;EACX,UAAU;EACV,QAAQ;CACV,EACF;CACA,iBAAiB,EAAE,QAAQ,UAAU;AACvC,CAAC;AAYD,MAAM,gBAAgB,cAAyC,IAAI;AAEnE,SAAS,iBAAiB,WAAuC;CAC/D,MAAM,UAAU,WAAW,aAAa;CACxC,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,GAAG,UAAU,yCAAyC;CAExE,OAAO;AACT;AASA,SAAgB,aAAa,EAC3B,WACA,SACA,UACA,UACA,GAAG,SACiB;CACpB,MAAM,CAAC,UAAU,eAAe,yBAAsB,IAAI,IAAI,CAAC;CAE/D,MAAM,gBAAgB,cACd,QAAQ,OAAO,YAAY,CAAC,CAAC,KAAK,WAAW,OAAO,EAAE,GAC5D,CAAC,OAAO,CACV;CAEA,MAAM,UAAU,eACP;EACL;EACA;EACA;EACA;EACA,SAAS,OAAe;GACtB,aAAa,YAAY;IACvB,MAAM,OAAO,IAAI,IAAI,OAAO;IAC5B,IAAI,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,EAAE;SAC3B,KAAK,IAAI,EAAE;IAChB,OAAO;GACT,CAAC;EACH;EACA,iBAAiB;GACf,YAAY,IAAI,IAAI,aAAa,CAAC;EACpC;EACA,aAAa;GACX,4BAAY,IAAI,IAAI,CAAC;EACvB;CACF,IACA;EAAC;EAAS;EAAU;EAAe;CAAQ,CAC7C;CAEA,OACE,oBAAC,cAAc,UAAf;EAAwB,OAAO;EAC7B,UAAA,oBAAC,OAAD;GACE,aAAU;GACV,WAAW,GAAG,uBAAuB,SAAS;GAC9C,GAAI;GAEH;EACE,CAAA;CACiB,CAAA;AAE5B;;;;;;;AAQA,SAAgB,oBAAoB,EAAE,WAAW,GAAG,SAAuC;CACzF,MAAM,EAAE,UAAU,eAAe,SAAS,WAAW,OAAO,aAC1D,iBAAiB,qBAAqB;CAExC,MAAM,QAAQ,SAAS;CACvB,MAAM,cAAc,cAAc,SAAS,KAAK,UAAU,cAAc;CAExE,OACE,qBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,qCAAqC,SAAS;EAC5D,GAAI;EAHN,UAAA,CAKE,oBAAC,UAAD;GACE,MAAK;GACL,UAAU,cAAc,WAAW;GACnC,SAAS,cAAc,QAAQ;GAC/B,WAAW,GACT,gFACA,kEACA,oDACA,SACF;GAEC,UAAA,cACG,oBACA,cAAc,OAAO,cAAc,MAAM,EAAE;EACzC,CAAA,GAER,oBAAC,UAAD;GACE,MAAK;GACL,aAAU;GACV,UAAU,UAAU;GACpB,eAAe;IACb,WAAW,QAAQ,QAAQ,WAAW,SAAS,IAAI,OAAO,EAAE,CAAC,CAAC;GAChE;GACA,WAAW,GACT,iFACA,4CACA,oDACA,SACF;GAEC,UAAA,UAAU,IAAI,kBAAkB,QAAQ,OAAO,KAAK,EAAE;EACjD,CAAA,CACL;;AAET;AAWA,SAAgB,kBAAkB,EAChC,WACA,QACA,UACA,GAAG,SACsB;CACzB,MAAM,EAAE,UAAU,WAAW,iBAAiB,mBAAmB;CACjE,MAAM,aAAa,MAAM;CACzB,MAAM,aAAa,aAAa,MAAM;CACtC,MAAM,aAAa,SAAS,IAAI,OAAO,EAAE;CAEzC,OACE,oBAAC,MAAD;EACE,aAAU;EACV,eAAa,OAAO;EACpB,sBAAoB,OAAO;EAC3B,WAAW,GAAG,oBAAoB,EAAE,QAAQ,OAAO,OAAO,CAAC,GAAG,SAAS;EACvE,GAAI;EAEJ,UAAA,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CAGE,oBAAC,SAAD;IACE,MAAK;IACL,IAAI;IACJ,UAAU,CAAC;IACX,SAAS;IACT,gBAAgB;KACd,OAAO,OAAO,EAAE;IAClB;IACA,WAAW,GACT,+CACA,mDACA,SACF;GACD,CAAA,GAED,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,oBAAC,SAAD;MACE,SAAS;MACT,WAAW,GAAG,eAAe,CAAC,cAAc,YAAY;MAEvD,UAAA,OAAO;KACH,CAAA;KAEP,qBAAC,KAAD;MAAG,WAAU;MAAb,UAAA;OAGE,oBAAC,QAAD;QAAM,aAAU;QACb,UAAA,oBAAoB,OAAO;OACxB,CAAA;OACN,oBAAC,QAAD;QAAM,eAAY;QAAO,UAAA;OAAO,CAAA;OAChC,oBAAC,QAAD;QAAM,aAAU;QAAwB,UAAA,aAAa,OAAO;OAAc,CAAA;OACzE,OAAO,SACN,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,QAAD;QAAM,eAAY;QAAO,UAAA;OAAO,CAAA,GAChC,oBAAC,QAAD,EAAA,UAAO,OAAO,OAAa,CAAA,CAC3B,EAAA,CAAA,IACA;OACH,OAAO,YACN,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,QAAD;QAAM,eAAY;QAAO,UAAA;OAAO,CAAA,GAChC,oBAAC,QAAD;QAAM,UAAU,OAAO;QAAY,UAAA,OAAO;OAAgB,CAAA,CAC1D,EAAA,CAAA,IACA;MACH;;KAIF,OAAO,WAAW,YAAY,OAAO,QACpC,oBAAC,KAAD;MAAG,aAAU;MAAsB,WAAU;MAC1C,UAAA,OAAO;KACP,CAAA,IACD;KAEH;IACE;GACF,CAAA,CAAA;;CACH,CAAA;AAER;;AAGA,SAAgB,iBAAiB,EAAE,WAAW,GAAG,SAAsC;CACrF,OACE,oBAAC,MAAD;EACE,aAAU;EACV,WAAW,GAAG,iCAAiC,SAAS;EACxD,GAAI;CACL,CAAA;AAEL;;AAGA,SAAgB,oBAAoB,EAClC,WACA,QAAQ,WACR,UACA,GAAG,SAC4E;CAC/E,OACE,qBAACA,YAAqB,MAAtB;EACE,aAAU;EACV,WAAW,GAAG,QAAQ,SAAS;EAC/B,GAAI;EAHN,UAAA,CAKE,oBAACA,YAAqB,SAAtB;GACE,WAAW,GACT,oFACA,SACF;GAEC,UAAA;EAC2B,CAAA,GAC9B,oBAACA,YAAqB,SAAtB;GAA8B,WAAU;GAAQ;EAAuC,CAAA,CAC9D;;AAE/B;;;;;;;;AASA,SAAgB,6BAA6B,EAC3C,WACA,GAAG,SAC0B;CAC7B,MAAM,EAAE,SAAS,aAAa,iBAAiB,8BAA8B;CAE7E,MAAM,SAAS,QAAQ,QAAQ,WAAW,SAAS,IAAI,OAAO,EAAE,CAAC;CACjE,MAAM,cAAc,OAAO,QAAQ,MAAM,EAAE,kBAAkB,aAAa,CAAC,CAAC;CAE5E,IAAI,OAAO,WAAW,GAAG,OAAO;CAEhC,OACE,qBAAC,KAAD;EACE,aAAU;EAGV,aAAU;EACV,WAAW,GAAG,iCAAiC,SAAS;EACxD,GAAI;EANN,UAAA,CAQG,GAAG,OAAO,OAAO,MAAM,EAAE,YACzB,cAAc,IACX,cAAc,OAAO,WAAW,EAAE,uDAClC,EACH;;AAEP"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { ActionLedger, ActionLedgerEntry, ActionLedgerEntryProps, ActionLedgerList, ActionLedgerPayload, ActionLedgerProps, ActionLedgerSelectionSummary, ActionLedgerToolbar, ActionStatus, LedgerAction, Reversibility, ledgerEntryVariants } from "./ai-action-ledger.js";
|
|
2
|
+
export { ActionLedger, ActionLedgerEntry, type ActionLedgerEntryProps, ActionLedgerList, ActionLedgerPayload, type ActionLedgerProps, ActionLedgerSelectionSummary, ActionLedgerToolbar, type ActionStatus, type LedgerAction, type Reversibility, ledgerEntryVariants };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { ActionLedger, ActionLedgerEntry, ActionLedgerList, ActionLedgerPayload, ActionLedgerSelectionSummary, ActionLedgerToolbar, ledgerEntryVariants } from "./ai-action-ledger.js";
|
|
2
|
+
export { ActionLedger, ActionLedgerEntry, ActionLedgerList, ActionLedgerPayload, ActionLedgerSelectionSummary, ActionLedgerToolbar, ledgerEntryVariants };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineMeta } from "../../registry/schema.js";
|
|
2
|
+
//#region src/components/ai-action-ledger/meta.ts
|
|
3
|
+
const meta = defineMeta({
|
|
4
|
+
name: "ai-action-ledger",
|
|
5
|
+
title: "AI Action Ledger",
|
|
6
|
+
description: "What an agent actually did, classified by what can be undone.",
|
|
7
|
+
category: "ai",
|
|
8
|
+
status: "stable",
|
|
9
|
+
dependencies: ["class-variance-authority", "radix-ui"],
|
|
10
|
+
registryDependencies: [],
|
|
11
|
+
files: ["ai-action-ledger.tsx"],
|
|
12
|
+
a11y: "Reversibility is stated in words on every entry, never by colour or icon alone — it decides whether the undo control means anything, so it cannot be decoration. Selection uses native checkboxes with real labels, and the revert button carries the count in its accessible name so it says what pressing it will do. The selection summary is an aria-live region because the wording changes as the choice changes, and warns before the click when something can only be offset rather than undone."
|
|
13
|
+
});
|
|
14
|
+
//#endregion
|
|
15
|
+
export { meta };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.js","names":[],"sources":["../../../src/components/ai-action-ledger/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"ai-action-ledger\",\n title: \"AI Action Ledger\",\n description: \"What an agent actually did, classified by what can be undone.\",\n category: \"ai\",\n status: \"stable\",\n dependencies: [\"class-variance-authority\", \"radix-ui\"],\n registryDependencies: [],\n files: [\"ai-action-ledger.tsx\"],\n a11y:\n \"Reversibility is stated in words on every entry, never by colour or icon alone — it decides \" +\n \"whether the undo control means anything, so it cannot be decoration. Selection uses native \" +\n \"checkboxes with real labels, and the revert button carries the count in its accessible name \" +\n \"so it says what pressing it will do. The selection summary is an aria-live region because \" +\n \"the wording changes as the choice changes, and warns before the click when something can \" +\n \"only be offset rather than undone.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aAAa;CACb,UAAU;CACV,QAAQ;CACR,cAAc,CAAC,4BAA4B,UAAU;CACrD,sBAAsB,CAAC;CACvB,OAAO,CAAC,sBAAsB;CAC9B,MACE;AAMJ,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ComponentPropsWithRef, ReactNode } from "react";
|
|
2
|
+
//#region src/components/ai-agent-plan/ai-agent-plan.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* What an agent intends to do, and how far through it is.
|
|
5
|
+
*
|
|
6
|
+
* Not a stepper. A wizard's steps are decided up front and never change; an
|
|
7
|
+
* agent's plan is a hypothesis it revises as it learns — it adds a step when a
|
|
8
|
+
* lookup fails, drops one that turned out to be unnecessary, and reorders when
|
|
9
|
+
* a dependency appears. Every stepper treats its step list as fixed, so a plan
|
|
10
|
+
* rendered in one either silently mutates under the reader or re-renders from
|
|
11
|
+
* scratch and loses their place.
|
|
12
|
+
*
|
|
13
|
+
* So revision is the feature here, not an edge case. When the plan changes, the
|
|
14
|
+
* change is announced — "the model added a step" is information, and watching a
|
|
15
|
+
* list quietly grow is not the same as being told.
|
|
16
|
+
*
|
|
17
|
+
* Approval is not here. Deciding whether a plan may run is a different
|
|
18
|
+
* component with a different shape; this one displays and reports.
|
|
19
|
+
*/
|
|
20
|
+
type StepStatus = "pending" | "running" | "done" | "failed" | "skipped";
|
|
21
|
+
interface PlanStep {
|
|
22
|
+
id: string;
|
|
23
|
+
title: string;
|
|
24
|
+
status: StepStatus;
|
|
25
|
+
/** What the step will do, or what it found. Shown under the title. */
|
|
26
|
+
detail?: string;
|
|
27
|
+
/** Why it failed. Shown only when the status is "failed". */
|
|
28
|
+
error?: string;
|
|
29
|
+
/** One level of sub-steps. Deeper nesting is a tree, which this is not. */
|
|
30
|
+
steps?: PlanStep[];
|
|
31
|
+
}
|
|
32
|
+
interface AgentPlanProps extends Omit<ComponentPropsWithRef<"div">, "children"> {
|
|
33
|
+
steps: PlanStep[];
|
|
34
|
+
/** Names the plan, so the list is not an unlabelled sequence. */
|
|
35
|
+
label: string;
|
|
36
|
+
children?: ReactNode;
|
|
37
|
+
}
|
|
38
|
+
declare function AgentPlan({ className, steps, label, children, ...props }: AgentPlanProps): import("react").JSX.Element;
|
|
39
|
+
/** "Step 3 of 6" plus the overall state, in words. */
|
|
40
|
+
declare function AgentPlanSummary({ className, ...props }: ComponentPropsWithRef<"p">): import("react").JSX.Element;
|
|
41
|
+
declare function AgentPlanSteps({ className, ...props }: ComponentPropsWithRef<"ol">): import("react").JSX.Element;
|
|
42
|
+
interface AgentPlanStepProps extends Omit<ComponentPropsWithRef<"li">, "children"> {
|
|
43
|
+
step: PlanStep;
|
|
44
|
+
/** Set on sub-steps. Not part of the public API surface. */
|
|
45
|
+
nested?: boolean;
|
|
46
|
+
}
|
|
47
|
+
declare function AgentPlanStep({ className, step, nested, ...props }: AgentPlanStepProps): import("react").JSX.Element;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { AgentPlan, AgentPlanProps, AgentPlanStep, AgentPlanStepProps, AgentPlanSteps, AgentPlanSummary, PlanStep, StepStatus };
|
|
50
|
+
//# sourceMappingURL=ai-agent-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-agent-plan.d.ts","names":[],"sources":["../../../src/components/ai-agent-plan/ai-agent-plan.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;KA+BY;UAEK;EACf;EACA;EACA,QAAQ;;EAER;;EAEA;;EAEA,QAAQ;;UAyCO,uBAAuB,KAAK;EAC3C,OAAO;;EAEP;EACA,WAAW;;iBAGG,YAAY,WAAW,OAAO,OAAO,aAAa,SAAS,iCAAc,IAAA;;iBAqDzE,mBAAmB,cAAc,SAAS,6CAA0B,IAAA;iBA2BpE,iBAAiB,cAAc,SAAS,8CAA2B,IAAA;UAkBlE,2BAA2B,KAAK;EAC/C,MAAM;;EAEN;;iBAGc,gBACd,WACA,MACA,WACG,SACF,qCAAkB,IAAA"}
|