@dowel-ui/react 0.1.1 → 0.2.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 +187 -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-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-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 +126 -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/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/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/toast/toast.d.ts +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +7 -1
- package/dist/registry/components.d.ts.map +1 -1
- package/dist/registry/components.js +57 -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-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-structured-output/ai-structured-output.tsx +244 -0
- package/src/components/ai-structured-output/index.ts +10 -0
- package/src/components/ai-structured-output/meta.ts +18 -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/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/index.ts +6 -0
- package/src/registry/components.ts +12 -0
package/LICENSE
CHANGED
package/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
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
|
+
**52 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 · **Action Ledger** ·
|
|
63
|
+
**Structured Output** · **Inline Completion**
|
|
64
|
+
|
|
65
|
+
**Forms**
|
|
66
|
+
Input · Label · Checkbox · Radio Group · Switch · Slider · Select · Combobox ·
|
|
67
|
+
Form · Calendar · Date Picker
|
|
68
|
+
|
|
69
|
+
**Overlays**
|
|
70
|
+
Dialog · Sheet · Drawer · Popover · Tooltip · Dropdown Menu
|
|
71
|
+
|
|
72
|
+
**Data**
|
|
73
|
+
Table · Data Table · Accordion · Activity Feed · Code Block · **Metric Delta** ·
|
|
74
|
+
**Record Diff**
|
|
75
|
+
|
|
76
|
+
**Feedback**
|
|
77
|
+
Alert · Toast · Progress · **Meter** · Skeleton · Spinner · Empty State
|
|
78
|
+
|
|
79
|
+
**Navigation & layout**
|
|
80
|
+
Tabs · Command · Pagination · Card · Badge · Avatar · Button · Separator
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## The AI components
|
|
85
|
+
|
|
86
|
+
Most "AI component libraries" ship a chat transcript and stop. Real AI features
|
|
87
|
+
inside real software are extraction, enrichment, autofill and agents that
|
|
88
|
+
_change things_ — and those need surfaces nobody else provides.
|
|
89
|
+
|
|
90
|
+
**`ai-action-ledger`** — what the agent actually did, and what can be undone.
|
|
91
|
+
Everyone ships approval _before_ a tool runs. Nothing ships the part after,
|
|
92
|
+
where a deletion can be reverted, a refund can only be offset by another
|
|
93
|
+
transaction, and a sent email cannot be taken back at all. Hiding those three
|
|
94
|
+
behind one "Undo" button is a lie you discover after clicking.
|
|
95
|
+
|
|
96
|
+
**`ai-structured-output`** — an object arriving field by field, with the layout
|
|
97
|
+
reserved up front so nothing jumps as it fills in.
|
|
98
|
+
|
|
99
|
+
**`ai-inline-completion`** — ghost text in a real textarea. Tab accepts,
|
|
100
|
+
Alt+Right takes a word, and Escape always gives Tab back to focus management, so
|
|
101
|
+
a keyboard user is never trapped in the field.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Blocks
|
|
106
|
+
|
|
107
|
+
Whole sections, assembled from components. Installing one brings everything it
|
|
108
|
+
is built from:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npx @dowel-ui/cli add ai-chat
|
|
112
|
+
# ✓ Added ai-conversation, ai-message, select, ai-model-selector,
|
|
113
|
+
# ai-prompt-input, ai-reasoning, ai-response, ai-sources, ai-token-usage,
|
|
114
|
+
# ai-tool, spinner, button, empty-state, ai-chat
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Login · Signup · Forgot Password · Dashboard · Admin Users · Settings ·
|
|
118
|
+
Pricing · AI Chat
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Theming
|
|
123
|
+
|
|
124
|
+
Two tiers of design tokens, in plain CSS. No config file, no JavaScript theme
|
|
125
|
+
object.
|
|
126
|
+
|
|
127
|
+
```css
|
|
128
|
+
:root {
|
|
129
|
+
--primary: oklch(0.55 0.2 275);
|
|
130
|
+
--radius-scale: 1; /* one knob re-proportions every corner in the system */
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Seven presets ship with it, and every one passes **WCAG AA contrast in both
|
|
135
|
+
light and dark** — verified by an audit that checks 322 colour pairs across 14
|
|
136
|
+
schemes on every commit, not by eye.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Accessibility is not a checklist here
|
|
141
|
+
|
|
142
|
+
Every component is tested for behaviour, not appearance. A few things that fell
|
|
143
|
+
out of taking it seriously:
|
|
144
|
+
|
|
145
|
+
- `Meter` uses `role="meter"`, not `progressbar` — a level that may never move
|
|
146
|
+
is a different thing from a task heading for completion, and screen readers
|
|
147
|
+
say them differently.
|
|
148
|
+
- `MetricDelta` never puts meaning in colour alone. Rising churn is not good
|
|
149
|
+
news, and a red triangle is not information.
|
|
150
|
+
- Every pointer gesture has a keyboard equivalent, or the component was not
|
|
151
|
+
built. One candidate was dropped for failing exactly that test.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Requirements
|
|
156
|
+
|
|
157
|
+
React 19 · TypeScript · Tailwind CSS v4
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Install
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# The CLI — writes components into your project as source
|
|
165
|
+
npx @dowel-ui/cli init
|
|
166
|
+
|
|
167
|
+
# Or import from the package
|
|
168
|
+
npm install @dowel-ui/react
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
import { Button } from "@dowel-ui/react";
|
|
173
|
+
|
|
174
|
+
export function Save() {
|
|
175
|
+
return <Button variant="primary">Save changes</Button>;
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
<div align="center">
|
|
182
|
+
|
|
183
|
+
[**Read the docs →**](https://dowel-eight.vercel.app)
|
|
184
|
+
|
|
185
|
+
MIT licensed
|
|
186
|
+
|
|
187
|
+
</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":"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"}
|
|
@@ -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,45 @@
|
|
|
1
|
+
import { ComponentPropsWithRef } from "react";
|
|
2
|
+
//#region src/components/ai-inline-completion/ai-inline-completion.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Ghost-text suggestion inside a real textarea or input.
|
|
5
|
+
*
|
|
6
|
+
* Outside chat this is the most-copied AI affordance in software — Smart
|
|
7
|
+
* Compose, Copilot's grey continuation, Notion and Linear's inline suggestions —
|
|
8
|
+
* and every team rebuilds the same overlay-mirror trick from the same handful of
|
|
9
|
+
* blog posts.
|
|
10
|
+
*
|
|
11
|
+
* Two deliberate limits, both stated rather than discovered:
|
|
12
|
+
*
|
|
13
|
+
* 1. It works on `textarea` and `input`, not on contenteditable. Ghost text in
|
|
14
|
+
* contenteditable means owning selection, undo and inline formatting, which
|
|
15
|
+
* is an editor, not a component. Anyone in that territory already has
|
|
16
|
+
* ProseMirror or Lexical and should extend it there.
|
|
17
|
+
* 2. The suggestion completes the END of the value. Mid-caret insertion needs
|
|
18
|
+
* per-character measurement of wrapped text; the ghost hides whenever the
|
|
19
|
+
* caret is not at the end rather than rendering in the wrong place.
|
|
20
|
+
*
|
|
21
|
+
* There is no WAI-ARIA pattern for generative ghost text. Combobox is the
|
|
22
|
+
* nearest and does not fit: the suggestion is not one of a known set of options,
|
|
23
|
+
* so a listbox would be a lie. What the standard does require is that the
|
|
24
|
+
* suggestion be perceivable and that the keyboard is never trapped — so the text
|
|
25
|
+
* is announced through a live description, and Escape always restores plain Tab.
|
|
26
|
+
*/
|
|
27
|
+
interface InlineCompletionProps extends Omit<ComponentPropsWithRef<"textarea">, "value" | "onChange" | "children"> {
|
|
28
|
+
value: string;
|
|
29
|
+
onValueChange: (value: string) => void;
|
|
30
|
+
/**
|
|
31
|
+
* The continuation to show after the value. Empty or undefined shows nothing.
|
|
32
|
+
* The caller supplies this; the component never requests it.
|
|
33
|
+
*/
|
|
34
|
+
suggestion?: string;
|
|
35
|
+
/** Called with the full text after the suggestion is taken. */
|
|
36
|
+
onAccept?: (value: string) => void;
|
|
37
|
+
/** Called when the reader rejects the suggestion outright. */
|
|
38
|
+
onDismiss?: () => void;
|
|
39
|
+
/** Renders a single-line input instead of a textarea. */
|
|
40
|
+
singleLine?: boolean;
|
|
41
|
+
}
|
|
42
|
+
declare function InlineCompletion({ className, value, onValueChange, suggestion, onAccept, onDismiss, singleLine, onKeyDown, onScroll, ...props }: InlineCompletionProps): import("react").JSX.Element;
|
|
43
|
+
//#endregion
|
|
44
|
+
export { InlineCompletion, InlineCompletionProps };
|
|
45
|
+
//# sourceMappingURL=ai-inline-completion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-inline-completion.d.ts","names":[],"sources":["../../../src/components/ai-inline-completion/ai-inline-completion.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;UAwCiB,8BAA8B,KAC7C;EAGA;EACA,gBAAgB;;;;;EAKhB;;EAEA,YAAY;;EAEZ;;EAEA;;iBASc,mBACd,WACA,OACA,eACA,YACA,UACA,WACA,YACA,WACA,aACG,SACF,wCAAqB,IAAA"}
|