@lotics/ui 28.3.1 → 29.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/AGENTS.md +12 -1
- package/MIGRATION.md +26 -0
- package/docs/ai_patterns.md +158 -111
- package/docs/catalog.md +100 -25
- package/docs/data_entry.md +13 -0
- package/docs/templates.md +31 -12
- package/examples/tpl_item_list.tsx +201 -28
- package/examples/tpl_record.tsx +390 -123
- package/examples/tpl_task_board.tsx +45 -29
- package/package.json +7 -3
- package/src/agent_run.tsx +1 -1
- package/src/clarify_wizard.tsx +1 -1
- package/src/diff_mark.tsx +110 -0
- package/src/diff_value.tsx +232 -0
- package/src/file_badge.tsx +2 -0
- package/src/file_intake.ts +17 -2
- package/src/file_row.tsx +58 -6
- package/src/file_thumbnail.tsx +113 -30
- package/src/file_thumbnail_grid.tsx +13 -1
- package/src/finding.tsx +2 -3
- package/src/inline_slot.tsx +58 -0
- package/src/inline_static.tsx +5 -23
- package/src/locale.tsx +18 -46
- package/src/mime.ts +76 -0
- package/src/result_header.tsx +1 -1
- package/src/use_change_set.ts +115 -0
- package/src/change_review.tsx +0 -800
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export type ChangeDecision = "pending" | "accepted" | "rejected";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* THE REVIEW BOOKKEEPING, WITHOUT A SURFACE.
|
|
7
|
+
*
|
|
8
|
+
* Keeping which proposals are in and which are out is genuinely fiddly — the
|
|
9
|
+
* counter, keep-all, undo, the commit gate — and genuinely the same every time.
|
|
10
|
+
* What is NOT the same every time is where any of it renders. So this is a hook:
|
|
11
|
+
* the host gets the state machine and draws whatever its screen needs, instead
|
|
12
|
+
* of adopting a container to get the arithmetic.
|
|
13
|
+
*
|
|
14
|
+
* Decisions are stored as OVERRIDES against a default, never seeded from `ids`
|
|
15
|
+
* into state. Seeding would need an effect to keep in step, and `ids` is
|
|
16
|
+
* typically a `useMemo` over query rows — a new array every render, so the
|
|
17
|
+
* effect fires every render and sets state every time. (That exact loop has bit
|
|
18
|
+
* this codebase before: `Maximum update depth exceeded` in a settle drawer that
|
|
19
|
+
* seeded "everything ticked" from its rows.) Storing the exceptions makes the
|
|
20
|
+
* selection derived: nothing to seed, nothing to re-sync, and ids that come and
|
|
21
|
+
* go simply pick up the default.
|
|
22
|
+
*/
|
|
23
|
+
export interface ChangeSet<Id extends string = string> {
|
|
24
|
+
status: (id: Id) => ChangeDecision;
|
|
25
|
+
accept: (id: Id) => void;
|
|
26
|
+
reject: (id: Id) => void;
|
|
27
|
+
/** Back to the default — the Undo on a decided row. */
|
|
28
|
+
undo: (id: Id) => void;
|
|
29
|
+
acceptAll: () => void;
|
|
30
|
+
rejectAll: () => void;
|
|
31
|
+
/** Every id back to the default. */
|
|
32
|
+
reset: () => void;
|
|
33
|
+
accepted: readonly Id[];
|
|
34
|
+
rejected: readonly Id[];
|
|
35
|
+
pending: readonly Id[];
|
|
36
|
+
keptCount: number;
|
|
37
|
+
total: number;
|
|
38
|
+
/** Nothing is left undecided — the usual gate on a commit button. */
|
|
39
|
+
settled: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface UseChangeSetOptions {
|
|
43
|
+
/**
|
|
44
|
+
* What an untouched proposal counts as. Default `accepted`: the operator
|
|
45
|
+
* drops the exceptions rather than approving each of eight identical lines,
|
|
46
|
+
* which is the difference between a review and a second round of data entry.
|
|
47
|
+
* Use `pending` when each change genuinely deserves its own verdict — and
|
|
48
|
+
* gate the commit on `settled`.
|
|
49
|
+
*/
|
|
50
|
+
initial?: ChangeDecision;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function useChangeSet<Id extends string = string>(
|
|
54
|
+
ids: readonly Id[],
|
|
55
|
+
options?: UseChangeSetOptions,
|
|
56
|
+
): ChangeSet<Id> {
|
|
57
|
+
const initial = options?.initial ?? "accepted";
|
|
58
|
+
const [overrides, setOverrides] = useState<ReadonlyMap<Id, ChangeDecision>>(new Map());
|
|
59
|
+
|
|
60
|
+
const set = useCallback((id: Id, decision: ChangeDecision) => {
|
|
61
|
+
setOverrides((prev) => {
|
|
62
|
+
const next = new Map(prev);
|
|
63
|
+
next.set(id, decision);
|
|
64
|
+
return next;
|
|
65
|
+
});
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
const undo = useCallback((id: Id) => {
|
|
69
|
+
setOverrides((prev) => {
|
|
70
|
+
if (!prev.has(id)) return prev;
|
|
71
|
+
const next = new Map(prev);
|
|
72
|
+
next.delete(id);
|
|
73
|
+
return next;
|
|
74
|
+
});
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
const all = useCallback(
|
|
78
|
+
(decision: ChangeDecision) => setOverrides(new Map(ids.map((id) => [id, decision]))),
|
|
79
|
+
[ids],
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const status = useCallback((id: Id) => overrides.get(id) ?? initial, [overrides, initial]);
|
|
83
|
+
|
|
84
|
+
const groups = useMemo(() => {
|
|
85
|
+
const accepted: Id[] = [];
|
|
86
|
+
const rejected: Id[] = [];
|
|
87
|
+
const pending: Id[] = [];
|
|
88
|
+
for (const id of ids) {
|
|
89
|
+
const s = overrides.get(id) ?? initial;
|
|
90
|
+
if (s === "accepted") accepted.push(id);
|
|
91
|
+
else if (s === "rejected") rejected.push(id);
|
|
92
|
+
else pending.push(id);
|
|
93
|
+
}
|
|
94
|
+
return { accepted, rejected, pending };
|
|
95
|
+
}, [ids, overrides, initial]);
|
|
96
|
+
|
|
97
|
+
return useMemo(
|
|
98
|
+
() => ({
|
|
99
|
+
status,
|
|
100
|
+
accept: (id: Id) => set(id, "accepted"),
|
|
101
|
+
reject: (id: Id) => set(id, "rejected"),
|
|
102
|
+
undo,
|
|
103
|
+
acceptAll: () => all("accepted"),
|
|
104
|
+
rejectAll: () => all("rejected"),
|
|
105
|
+
reset: () => setOverrides(new Map()),
|
|
106
|
+
accepted: groups.accepted,
|
|
107
|
+
rejected: groups.rejected,
|
|
108
|
+
pending: groups.pending,
|
|
109
|
+
keptCount: groups.accepted.length,
|
|
110
|
+
total: ids.length,
|
|
111
|
+
settled: groups.pending.length === 0,
|
|
112
|
+
}),
|
|
113
|
+
[status, set, undo, all, groups, ids.length],
|
|
114
|
+
);
|
|
115
|
+
}
|