@hachej/boring-ui-kit 0.1.111 → 0.2.1

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.
Files changed (95) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +255 -239
  3. package/THIRD_PARTY_NOTICES.md +85 -0
  4. package/dist/anchor-conformance.d.ts +78 -0
  5. package/dist/anchor-conformance.js +288 -0
  6. package/dist/canvas-document.d.ts +25 -0
  7. package/dist/canvas-document.js +141 -0
  8. package/dist/canvas-editor-native.d.ts +22 -0
  9. package/dist/canvas-editor-native.js +112 -0
  10. package/dist/canvas-editor.d.ts +6 -0
  11. package/dist/canvas-editor.js +13 -0
  12. package/dist/canvas-mounted.d.ts +41 -0
  13. package/dist/canvas-mounted.js +180 -0
  14. package/dist/canvas-proposals.d.ts +11 -0
  15. package/dist/canvas-proposals.js +45 -0
  16. package/dist/canvas-records.d.ts +3 -0
  17. package/dist/canvas-records.js +39 -0
  18. package/dist/canvas.d.ts +92 -0
  19. package/dist/canvas.js +264 -0
  20. package/dist/contracts.d.ts +125 -0
  21. package/dist/contracts.js +1 -0
  22. package/dist/experience-catalog.d.ts +142 -0
  23. package/dist/experience-catalog.js +25 -0
  24. package/dist/experience-compose.d.ts +83 -0
  25. package/dist/experience-compose.js +218 -0
  26. package/dist/experience-composition-schema.d.ts +29 -0
  27. package/dist/experience-composition-schema.js +22 -0
  28. package/dist/experience-document-viewer.d.ts +13 -0
  29. package/dist/experience-document-viewer.js +54 -0
  30. package/dist/experience-document.d.ts +64 -0
  31. package/dist/experience-document.js +234 -0
  32. package/dist/experience-region-text.d.ts +3 -0
  33. package/dist/experience-region-text.js +84 -0
  34. package/dist/experience-region-tree.d.ts +22 -0
  35. package/dist/experience-region-tree.js +50 -0
  36. package/dist/experience-regions.d.ts +9 -0
  37. package/dist/experience-regions.js +122 -0
  38. package/dist/experience-renderer.d.ts +16 -0
  39. package/dist/experience-renderer.js +37 -0
  40. package/dist/experience.d.ts +12 -0
  41. package/dist/experience.js +23 -0
  42. package/dist/file-tree-shadcn.d.ts +8 -0
  43. package/dist/file-tree-shadcn.js +14 -0
  44. package/dist/file-tree-view.d.ts +66 -0
  45. package/dist/file-tree-view.js +256 -0
  46. package/dist/file-tree.d.ts +57 -0
  47. package/dist/file-tree.js +204 -0
  48. package/dist/html-preview.d.ts +2 -0
  49. package/dist/html-preview.js +47 -0
  50. package/dist/html-viewer.d.ts +13 -0
  51. package/dist/html-viewer.js +69 -0
  52. package/dist/html.d.ts +21 -0
  53. package/dist/html.js +21 -0
  54. package/dist/index.d.ts +2 -472
  55. package/dist/index.js +1 -2671
  56. package/dist/markdown-editor.d.ts +28 -0
  57. package/dist/markdown-editor.js +230 -0
  58. package/dist/markdown-extensions.d.ts +16 -0
  59. package/dist/markdown-extensions.js +96 -0
  60. package/dist/markdown-icons.d.ts +244 -0
  61. package/dist/markdown-icons.js +32 -0
  62. package/dist/markdown-mounted.d.ts +63 -0
  63. package/dist/markdown-mounted.js +263 -0
  64. package/dist/markdown-parser.d.ts +4 -0
  65. package/dist/markdown-parser.js +33 -0
  66. package/dist/markdown-proposal-diff.d.ts +5 -0
  67. package/dist/markdown-proposal-diff.js +122 -0
  68. package/dist/markdown-safety.d.ts +21 -0
  69. package/dist/markdown-safety.js +135 -0
  70. package/dist/markdown.d.ts +86 -0
  71. package/dist/markdown.js +121 -0
  72. package/dist/native-chat.d.ts +150 -0
  73. package/dist/native-chat.js +376 -0
  74. package/dist/pi.d.ts +12 -0
  75. package/dist/pi.js +1 -0
  76. package/dist/public-text-buffer.d.ts +5 -0
  77. package/dist/public-text-buffer.js +1 -0
  78. package/dist/remote-chat.d.ts +76 -0
  79. package/dist/remote-chat.js +324 -0
  80. package/dist/resources.d.ts +34 -0
  81. package/dist/resources.js +1 -0
  82. package/dist/text-buffer-state.d.ts +24 -0
  83. package/dist/text-buffer-state.js +1 -0
  84. package/dist/text-buffer.d.ts +52 -0
  85. package/dist/text-buffer.js +315 -0
  86. package/dist/text-draft-controls.d.ts +9 -0
  87. package/dist/text-draft-controls.js +33 -0
  88. package/dist/text-draft-types.d.ts +111 -0
  89. package/dist/text-draft-types.js +1 -0
  90. package/dist/text-drafts.d.ts +29 -0
  91. package/dist/text-drafts.js +378 -0
  92. package/package.json +226 -58
  93. package/styles/file-tree.css +46 -0
  94. package/dist/styles.css +0 -2308
  95. package/dist/tokens.css +0 -155
@@ -0,0 +1,85 @@
1
+ # Third-party notices
2
+
3
+ The Markdown toolbar includes paths from Lucide, including Feather-derived icons.
4
+ The notices below are from lucide-react 1.51.0.
5
+
6
+ ```text
7
+ ISC License
8
+
9
+ Copyright (c) 2026 Lucide Icons and Contributors
10
+
11
+ Permission to use, copy, modify, and/or distribute this software for any
12
+ purpose with or without fee is hereby granted, provided that the above
13
+ copyright notice and this permission notice appear in all copies.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
16
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
18
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
21
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22
+
23
+ ---
24
+
25
+ The following Lucide icons are derived from the Feather project:
26
+
27
+ airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
28
+
29
+ The MIT License (MIT) (for the icons listed above)
30
+
31
+ Copyright (c) 2013-present Cole Bemis
32
+
33
+ Permission is hereby granted, free of charge, to any person obtaining a copy
34
+ of this software and associated documentation files (the "Software"), to deal
35
+ in the Software without restriction, including without limitation the rights
36
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
37
+ copies of the Software, and to permit persons to whom the Software is
38
+ furnished to do so, subject to the following conditions:
39
+
40
+ The above copyright notice and this permission notice shall be included in all
41
+ copies or substantial portions of the Software.
42
+
43
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
44
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
45
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
46
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
47
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
48
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
49
+ SOFTWARE.
50
+
51
+ ```
52
+
53
+ ## shadcn/ui file tree
54
+
55
+ The file tree adapts `sidebar-11` and its menu primitives from shadcn/ui at
56
+ commit `6ea090075cd537d3b792c6c1a625e2448b6ede26`:
57
+ https://github.com/shadcn-ui/ui/tree/6ea090075cd537d3b792c6c1a625e2448b6ede26/apps/v4/registry/new-york-v4
58
+
59
+ The adaptation replaces sample data with the revision controller, adds keyboard
60
+ navigation, and omits the outer sidebar, sidebar-provider tooltip behavior and
61
+ unused menu variants so the same tree can render in a detached container.
62
+
63
+ ```text
64
+ MIT License
65
+
66
+ Copyright (c) 2023 shadcn
67
+
68
+ Permission is hereby granted, free of charge, to any person obtaining a copy
69
+ of this software and associated documentation files (the "Software"), to deal
70
+ in the Software without restriction, including without limitation the rights
71
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
72
+ copies of the Software, and to permit persons to whom the Software is
73
+ furnished to do so, subject to the following conditions:
74
+
75
+ The above copyright notice and this permission notice shall be included in all
76
+ copies or substantial portions of the Software.
77
+
78
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
79
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
80
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
81
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
82
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
83
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
84
+ SOFTWARE.
85
+ ```
@@ -0,0 +1,78 @@
1
+ import type { Anchor, AnchorResolution, Placement } from './contracts.js';
2
+ /** Anchor conformance kit (FEEDBACK.md, FEEDBACK-4). It drives one
3
+ * `AnchorResolution` over a named corpus and returns structured results; it
4
+ * imports no test runner, so any runner (or none) can assert on the output.
5
+ * Its verdict is bounded to the corpus: "zero wrong spots for the named corpus".
6
+ * The purity trap covers fetch, timers, Date.now, Math.random and
7
+ * performance.now during `resolve`; it bounds the purity claim, it does not prove it.
8
+ */
9
+ export type AnchorExpectation<Range> = {
10
+ readonly kind: 'exact' | 'moved';
11
+ readonly range: Range;
12
+ } | {
13
+ readonly kind: 'ambiguous';
14
+ readonly candidates: readonly Range[];
15
+ } | {
16
+ readonly kind: 'missing' | 'unsupported';
17
+ };
18
+ export interface AnchorConformanceStep<Snapshot, Range> {
19
+ readonly name: string;
20
+ /** The full snapshot this step resolves against. */
21
+ readonly snapshot: Snapshot;
22
+ readonly evaluated: string;
23
+ readonly expect: AnchorExpectation<Range>;
24
+ }
25
+ export interface AnchorConformanceCase<Snapshot, Range> {
26
+ readonly name: string;
27
+ /** Rates are reported per group. */
28
+ readonly group: string;
29
+ /** The anchor is data as stored; an unknown kind is kept, never parsed. */
30
+ readonly capture: {
31
+ readonly snapshot: Snapshot;
32
+ readonly anchor: unknown;
33
+ };
34
+ readonly steps: readonly AnchorConformanceStep<Snapshot, Range>[];
35
+ }
36
+ export interface AnchorConformanceInput<A extends Anchor, Snapshot, Range> {
37
+ readonly resolution: AnchorResolution<A, Snapshot, Range>;
38
+ readonly cases: readonly AnchorConformanceCase<Snapshot, Range>[];
39
+ }
40
+ export type PlacementKind = Placement<unknown>['kind'];
41
+ export interface AnchorStepResult<Range> {
42
+ readonly name: string;
43
+ readonly expect: AnchorExpectation<Range>;
44
+ /** Absent when `resolve` threw or returned no placement. */
45
+ readonly placement?: Placement<Range>;
46
+ readonly passed: boolean;
47
+ readonly wrongSpots: number;
48
+ readonly failures: readonly string[];
49
+ }
50
+ export interface AnchorCaseResult<Range> {
51
+ readonly name: string;
52
+ readonly group: string;
53
+ readonly passed: boolean;
54
+ readonly wrongSpots: number;
55
+ /** Case-level checks (schema round-trip, fallback) and every step failure, prefixed. */
56
+ readonly failures: readonly string[];
57
+ readonly steps: readonly AnchorStepResult<Range>[];
58
+ }
59
+ export interface AnchorGroupRates {
60
+ readonly cases: number;
61
+ readonly passedCases: number;
62
+ readonly steps: number;
63
+ readonly wrongSpots: number;
64
+ readonly placements: Readonly<Record<PlacementKind | 'error', number>>;
65
+ /** (exact + moved) / steps; reported, never thresholded. */
66
+ readonly automatic: number;
67
+ }
68
+ export interface AnchorConformanceReport<Range> {
69
+ readonly results: readonly AnchorCaseResult<Range>[];
70
+ readonly wrongSpots: number;
71
+ readonly rates: Readonly<Record<string, AnchorGroupRates>>;
72
+ /** Every case passed every check. */
73
+ readonly passed: boolean;
74
+ readonly verdict: string;
75
+ }
76
+ export declare const ANCHOR_CONFORMANCE_VERDICT = "zero wrong spots for the named corpus";
77
+ export declare function runAnchorConformance<A extends Anchor, Snapshot, Range>(input: AnchorConformanceInput<A, Snapshot, Range>): AnchorConformanceReport<Range>;
78
+ //# sourceMappingURL=anchor-conformance.d.ts.map
@@ -0,0 +1,288 @@
1
+ export const ANCHOR_CONFORMANCE_VERDICT = 'zero wrong spots for the named corpus';
2
+ const DETERMINISM_CALLS = 3;
3
+ export function runAnchorConformance(input) {
4
+ const results = input.cases.map(item => runCase(input.resolution, item));
5
+ const wrongSpots = results.reduce((sum, result) => sum + result.wrongSpots, 0);
6
+ const passed = results.every(result => result.passed);
7
+ return {
8
+ results,
9
+ wrongSpots,
10
+ rates: groupRates(results),
11
+ passed,
12
+ verdict: wrongSpots === 0 ? ANCHOR_CONFORMANCE_VERDICT : `${wrongSpots} wrong spot${wrongSpots === 1 ? '' : 's'} for the named corpus`,
13
+ };
14
+ }
15
+ function runCase(resolution, item) {
16
+ const failures = [];
17
+ const stored = item.capture.anchor;
18
+ const kind = isRecord(stored) && typeof stored['kind'] === 'string' ? stored['kind'] : undefined;
19
+ let anchor;
20
+ if (kind === undefined)
21
+ failures.push('anchor has no string kind');
22
+ else if (kind === resolution.kind)
23
+ anchor = checkSchema(resolution, stored, failures);
24
+ else
25
+ checkUnknown(stored, failures);
26
+ const steps = item.steps.map(step => runStep(resolution, kind, anchor, step));
27
+ for (const step of steps)
28
+ for (const failure of step.failures)
29
+ failures.push(`step "${step.name}": ${failure}`);
30
+ const wrongSpots = steps.reduce((sum, step) => sum + step.wrongSpots, 0);
31
+ return {
32
+ name: item.name,
33
+ group: item.group,
34
+ passed: failures.length === 0,
35
+ wrongSpots,
36
+ failures: failures.map(failure => `case "${item.name}": ${failure}`),
37
+ steps,
38
+ };
39
+ }
40
+ /** Parse, serialize and parse again: the anchor must survive as plain data. */
41
+ function checkSchema(resolution, stored, failures) {
42
+ let anchor;
43
+ try {
44
+ anchor = resolution.schema.parse(stored);
45
+ }
46
+ catch (error) {
47
+ failures.push(`schema rejected the captured anchor: ${message(error)}`);
48
+ return undefined;
49
+ }
50
+ try {
51
+ const again = resolution.schema.parse(JSON.parse(JSON.stringify(anchor)));
52
+ if (!deepEqual(again, anchor))
53
+ failures.push(`schema round-trip changed the anchor: ${show(anchor)} became ${show(again)}`);
54
+ }
55
+ catch (error) {
56
+ failures.push(`schema round-trip failed: ${message(error)}`);
57
+ }
58
+ let fallback;
59
+ try {
60
+ fallback = resolution.fallback(anchor);
61
+ }
62
+ catch (error) {
63
+ failures.push(`fallback threw: ${message(error)}`);
64
+ }
65
+ if (typeof fallback !== 'string' || fallback.trim() === '')
66
+ failures.push(`fallback is empty: ${show(fallback)}`);
67
+ return anchor;
68
+ }
69
+ /** An uninstalled kind is preserved as data and still carries its own fallback. */
70
+ function checkUnknown(stored, failures) {
71
+ let copy;
72
+ try {
73
+ copy = JSON.parse(JSON.stringify(stored));
74
+ }
75
+ catch (error) {
76
+ failures.push(`unknown-kind anchor is not plain data: ${message(error)}`);
77
+ return;
78
+ }
79
+ if (!deepEqual(copy, stored))
80
+ failures.push('unknown-kind anchor does not survive serialization');
81
+ const fallback = isRecord(stored) ? stored['fallback'] : undefined;
82
+ if (typeof fallback !== 'string' || fallback.trim() === '')
83
+ failures.push(`unknown-kind anchor has an empty fallback: ${show(fallback)}`);
84
+ }
85
+ function runStep(resolution, kind, anchor, step) {
86
+ const failures = [];
87
+ let placement;
88
+ if (kind !== undefined && kind !== resolution.kind) {
89
+ // Uninstalled kinds are placed as `unsupported` without calling another kind's resolver (FEEDBACK-3).
90
+ placement = { kind: 'unsupported', evaluated: step.evaluated };
91
+ }
92
+ else if (anchor !== undefined) {
93
+ placement = resolveChecked(resolution, anchor, step, failures);
94
+ }
95
+ else {
96
+ failures.push('not resolved: the anchor did not parse');
97
+ }
98
+ let wrongSpots = 0;
99
+ if (placement !== undefined) {
100
+ if (placement.evaluated !== step.evaluated)
101
+ failures.push(`reported evaluated ${show(placement.evaluated)}, expected ${show(step.evaluated)}`);
102
+ const judged = judge(step.expect, placement);
103
+ wrongSpots = judged.wrongSpots;
104
+ failures.push(...judged.failures);
105
+ }
106
+ const base = { name: step.name, expect: step.expect, passed: failures.length === 0, wrongSpots, failures };
107
+ return placement === undefined ? base : { ...base, placement };
108
+ }
109
+ /** Three trapped calls on cloned inputs: they must agree, leave inputs untouched and touch no trapped global. */
110
+ function resolveChecked(resolution, anchor, step, failures) {
111
+ const outputs = [];
112
+ for (let call = 0; call < DETERMINISM_CALLS; call += 1) {
113
+ const anchorCopy = structuredClone(anchor);
114
+ const snapshotCopy = structuredClone(step.snapshot);
115
+ const { value, touched, error } = trapped(() => resolution.resolve(anchorCopy, snapshotCopy, step.evaluated));
116
+ if (touched.length)
117
+ failures.push(`purity trap: resolve touched ${[...new Set(touched)].join(', ')}`);
118
+ if (error !== undefined) {
119
+ failures.push(`resolve threw: ${message(error)}`);
120
+ return undefined;
121
+ }
122
+ if (!deepEqual(anchorCopy, anchor) || !deepEqual(snapshotCopy, step.snapshot))
123
+ failures.push('resolve mutated its anchor or snapshot');
124
+ outputs.push(value);
125
+ }
126
+ const [first] = outputs;
127
+ if (outputs.some(output => !deepEqual(output, first)))
128
+ failures.push(`not deterministic over ${DETERMINISM_CALLS} calls: ${outputs.map(show).join(' / ')}`);
129
+ if (!isPlacement(first)) {
130
+ failures.push(`resolve returned no placement: ${show(first)}`);
131
+ return undefined;
132
+ }
133
+ return first;
134
+ }
135
+ function judge(expect, placement) {
136
+ const failures = [];
137
+ let wrongSpots = 0;
138
+ const expectedSet = expect.kind === 'exact' || expect.kind === 'moved' ? [expect.range] : expect.kind === 'ambiguous' ? expect.candidates : [];
139
+ const wanted = describe(expect);
140
+ if (placement.kind === 'exact' || placement.kind === 'moved' || placement.kind === 'partial') {
141
+ const sameRange = (expect.kind === 'exact' || expect.kind === 'moved') && deepEqual(placement.range, expect.range);
142
+ if (!sameRange) {
143
+ wrongSpots += 1;
144
+ failures.push(`wrong spot: ${placement.kind} at ${show(placement.range)} but expected ${wanted}`);
145
+ }
146
+ else if (placement.kind !== expect.kind) {
147
+ failures.push(`placed ${placement.kind} at the right range but expected ${wanted}`);
148
+ }
149
+ return { wrongSpots, failures };
150
+ }
151
+ if (placement.kind === 'ambiguous') {
152
+ for (const candidate of placement.candidates) {
153
+ if (!expectedSet.some(range => deepEqual(range, candidate))) {
154
+ wrongSpots += 1;
155
+ failures.push(`wrong spot: candidate ${show(candidate)} is outside the expected set ${show(expectedSet)}`);
156
+ }
157
+ }
158
+ if (expect.kind !== 'ambiguous')
159
+ failures.push(`ambiguous ${show(placement.candidates)} but expected ${wanted}`);
160
+ else if (!sameSet(placement.candidates, expect.candidates))
161
+ failures.push(`incomplete candidate set ${show(placement.candidates)}, expected ${show(expect.candidates)}`);
162
+ return { wrongSpots, failures };
163
+ }
164
+ if (placement.kind !== expect.kind)
165
+ failures.push(`${placement.kind} but expected ${wanted}`);
166
+ return { wrongSpots, failures };
167
+ }
168
+ function describe(expect) {
169
+ if (expect.kind === 'exact' || expect.kind === 'moved')
170
+ return `${expect.kind} at ${show(expect.range)}`;
171
+ if (expect.kind === 'ambiguous')
172
+ return `ambiguous ${show(expect.candidates)}`;
173
+ return expect.kind;
174
+ }
175
+ function groupRates(results) {
176
+ const groups = new Map();
177
+ for (const result of results)
178
+ groups.set(result.group, [...(groups.get(result.group) ?? []), result]);
179
+ const rates = {};
180
+ for (const [group, members] of groups) {
181
+ const placements = { exact: 0, moved: 0, ambiguous: 0, partial: 0, missing: 0, unsupported: 0, error: 0 };
182
+ let steps = 0;
183
+ for (const member of members)
184
+ for (const step of member.steps) {
185
+ steps += 1;
186
+ placements[step.placement?.kind ?? 'error'] += 1;
187
+ }
188
+ rates[group] = {
189
+ cases: members.length,
190
+ passedCases: members.filter(member => member.passed).length,
191
+ steps,
192
+ wrongSpots: members.reduce((sum, member) => sum + member.wrongSpots, 0),
193
+ placements,
194
+ automatic: steps === 0 ? 0 : (placements.exact + placements.moved) / steps,
195
+ };
196
+ }
197
+ return rates;
198
+ }
199
+ // --- purity trap -----------------------------------------------------------
200
+ const TRAPS = [
201
+ [() => globalThis, 'fetch', 'fetch'],
202
+ [() => globalThis, 'setTimeout', 'setTimeout'],
203
+ [() => globalThis, 'setInterval', 'setInterval'],
204
+ [() => globalThis, 'setImmediate', 'setImmediate'],
205
+ [() => globalThis, 'requestAnimationFrame', 'requestAnimationFrame'],
206
+ [() => globalThis, 'requestIdleCallback', 'requestIdleCallback'],
207
+ [() => Date, 'now', 'Date.now'],
208
+ [() => Math, 'random', 'Math.random'],
209
+ [() => (typeof performance === 'object' && performance !== null ? performance : undefined), 'now', 'performance.now'],
210
+ ];
211
+ /** Runs `run` with each trapped global replaced by a recorder that throws.
212
+ * Every replaced property is restored in `finally`, also when `run` throws.
213
+ */
214
+ function trapped(run) {
215
+ const touched = [];
216
+ const restore = [];
217
+ try {
218
+ for (const [ownerOf, property, label] of TRAPS) {
219
+ const owner = ownerOf();
220
+ if (owner === undefined || !(property in owner))
221
+ continue;
222
+ const own = Object.getOwnPropertyDescriptor(owner, property);
223
+ if (own !== undefined && !own.configurable)
224
+ continue;
225
+ Object.defineProperty(owner, property, {
226
+ configurable: true, writable: true, enumerable: own?.enumerable ?? false,
227
+ value: () => { touched.push(label); throw new Error(`purity trap: ${label} is not allowed in resolve`); },
228
+ });
229
+ restore.push(own === undefined ? () => { delete owner[property]; } : () => { Object.defineProperty(owner, property, own); });
230
+ }
231
+ return { value: run(), touched };
232
+ }
233
+ catch (error) {
234
+ return { touched, error };
235
+ }
236
+ finally {
237
+ for (const undo of restore.reverse())
238
+ undo();
239
+ }
240
+ }
241
+ // --- plain-data helpers ----------------------------------------------------
242
+ function isRecord(value) {
243
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
244
+ }
245
+ function isPlacement(value) {
246
+ if (!isRecord(value) || typeof value['evaluated'] !== 'string')
247
+ return false;
248
+ switch (value['kind']) {
249
+ case 'exact':
250
+ case 'moved': return 'range' in value;
251
+ case 'partial': return 'range' in value && Array.isArray(value['missing']);
252
+ case 'ambiguous': return Array.isArray(value['candidates']);
253
+ case 'missing':
254
+ case 'unsupported': return true;
255
+ default: return false;
256
+ }
257
+ }
258
+ function sameSet(actual, expected) {
259
+ return actual.length === expected.length
260
+ && actual.every(item => expected.some(other => deepEqual(item, other)))
261
+ && expected.every(item => actual.some(other => deepEqual(item, other)));
262
+ }
263
+ /** Structural equality for plain data: primitives, arrays and plain objects. */
264
+ function deepEqual(left, right) {
265
+ if (Object.is(left, right))
266
+ return true;
267
+ if (typeof left !== 'object' || typeof right !== 'object' || left === null || right === null)
268
+ return false;
269
+ if (Array.isArray(left) !== Array.isArray(right))
270
+ return false;
271
+ if (Array.isArray(left) && Array.isArray(right))
272
+ return left.length === right.length && left.every((item, index) => deepEqual(item, right[index]));
273
+ const a = left;
274
+ const b = right;
275
+ const keys = Object.keys(a);
276
+ return keys.length === Object.keys(b).length && keys.every(key => Object.prototype.hasOwnProperty.call(b, key) && deepEqual(a[key], b[key]));
277
+ }
278
+ function show(value) {
279
+ try {
280
+ return JSON.stringify(value) ?? String(value);
281
+ }
282
+ catch {
283
+ return String(value);
284
+ }
285
+ }
286
+ function message(error) {
287
+ return error instanceof Error ? error.message : String(error);
288
+ }
@@ -0,0 +1,25 @@
1
+ import type { TLBinding, TLShape, TLStoreSchema, TLStoreSnapshot } from '@tldraw/tlschema';
2
+ export declare const canvasMediaType = "application/vnd.tldraw+json";
3
+ export type CanvasEditableRecord = TLShape | TLBinding;
4
+ /** Updates replace complete native records. Removing a shape also removes its descendants and connected arrows. */
5
+ export type CanvasEdit = {
6
+ readonly kind: 'create' | 'update';
7
+ readonly record: CanvasEditableRecord;
8
+ } | {
9
+ readonly kind: 'remove';
10
+ readonly id: CanvasEditableRecord['id'];
11
+ };
12
+ export type CanvasEditResult = {
13
+ readonly kind: 'applied';
14
+ readonly document: TLStoreSnapshot;
15
+ } | {
16
+ readonly kind: 'rejected';
17
+ readonly reason: string;
18
+ };
19
+ /** Parse a complete document without mounting an editor, migrating records or repairing the graph. */
20
+ export declare function parseCanvasDocument(value: unknown, schema: TLStoreSchema): TLStoreSnapshot;
21
+ /** Parse edit inputs with the selected native schema, without changing a document. */
22
+ export declare function parseCanvasEdits(value: unknown, schema: TLStoreSchema): readonly CanvasEdit[];
23
+ /** Apply one atomic edit batch to a detached candidate. The caller owns conditional publication. */
24
+ export declare function applyCanvasEdits(document: TLStoreSnapshot, edits: readonly CanvasEdit[], schema: TLStoreSchema): CanvasEditResult;
25
+ //# sourceMappingURL=canvas-document.d.ts.map
@@ -0,0 +1,141 @@
1
+ import { isBindingId, isShapeId, TLDOCUMENT_ID } from '@tldraw/tlschema';
2
+ import { parseCanvasRecords } from './canvas-records.js';
3
+ export const canvasMediaType = 'application/vnd.tldraw+json';
4
+ /** Parse a complete document without mounting an editor, migrating records or repairing the graph. */
5
+ export function parseCanvasDocument(value, schema) {
6
+ const document = parseCanvasRecords(value, schema), records = Object.values(document.store);
7
+ const documents = records.filter(record => record.typeName === 'document');
8
+ if (documents.length !== 1 || documents[0]?.id !== TLDOCUMENT_ID || !records.some(record => record.typeName === 'page')) {
9
+ throw new TypeError('Canvas requires its native document record and at least one page');
10
+ }
11
+ const pages = new Map();
12
+ function pageOf(shape) {
13
+ const known = pages.get(shape.id);
14
+ if (known !== undefined)
15
+ return known;
16
+ const visited = new Set();
17
+ let current = shape;
18
+ while (true) {
19
+ const cached = pages.get(current.id);
20
+ if (cached !== undefined) {
21
+ for (const id of visited)
22
+ pages.set(id, cached);
23
+ return cached;
24
+ }
25
+ if (visited.has(current.id))
26
+ throw new TypeError('Canvas shape parents contain a cycle');
27
+ visited.add(current.id);
28
+ const parent = document.store[current.parentId];
29
+ if (parent?.typeName === 'page') {
30
+ for (const id of visited)
31
+ pages.set(id, parent.id);
32
+ return parent.id;
33
+ }
34
+ if (parent?.typeName !== 'shape' || (parent.type !== 'group' && parent.type !== 'frame')) {
35
+ throw new TypeError('Canvas shape parent must be an existing page, group or frame');
36
+ }
37
+ current = parent;
38
+ }
39
+ }
40
+ for (const record of records)
41
+ if (record.typeName === 'shape')
42
+ pageOf(record);
43
+ const terminals = new Set();
44
+ for (const record of records) {
45
+ if (record.typeName !== 'binding')
46
+ continue;
47
+ const from = document.store[record.fromId], to = document.store[record.toId];
48
+ if (from?.typeName !== 'shape' || from.type !== 'arrow' || to?.typeName !== 'shape' || to.type === 'arrow') {
49
+ throw new TypeError('Canvas arrow binding must connect an existing arrow to a non-arrow shape');
50
+ }
51
+ if (pageOf(from) !== pageOf(to))
52
+ throw new TypeError('Canvas arrow binding cannot cross pages');
53
+ const terminal = JSON.stringify([record.fromId, record.props['terminal']]);
54
+ if (terminals.has(terminal))
55
+ throw new TypeError('Canvas arrow has duplicate terminal bindings');
56
+ terminals.add(terminal);
57
+ }
58
+ return document;
59
+ }
60
+ /** Parse edit inputs with the selected native schema, without changing a document. */
61
+ export function parseCanvasEdits(value, schema) {
62
+ if (!Array.isArray(value) || value.length === 0)
63
+ throw new TypeError('Canvas edits must be a nonempty array');
64
+ const edits = [], requested = new Set();
65
+ for (const item of structuredClone(value)) {
66
+ const edit = item;
67
+ if (!edit || typeof edit !== 'object' || !('kind' in edit))
68
+ throw new TypeError('Invalid canvas edit');
69
+ let parsed;
70
+ if (edit.kind === 'remove') {
71
+ if (Object.keys(edit).some(key => key !== 'kind' && key !== 'id') || !('id' in edit) || typeof edit.id !== 'string'
72
+ || (!isShapeId(edit.id) && !isBindingId(edit.id)))
73
+ throw new TypeError('Invalid canvas removal');
74
+ parsed = { kind: 'remove', id: edit.id };
75
+ }
76
+ else if (edit.kind === 'create' || edit.kind === 'update') {
77
+ if (Object.keys(edit).some(key => key !== 'kind' && key !== 'record') || !('record' in edit) || !edit.record || typeof edit.record !== 'object'
78
+ || !('typeName' in edit.record))
79
+ throw new TypeError('Invalid canvas edit record');
80
+ const record = edit.record.typeName === 'shape' ? schema.types.shape.validate(edit.record)
81
+ : edit.record.typeName === 'binding' ? schema.types.binding.validate(edit.record) : undefined;
82
+ if (!record || (record.typeName !== 'shape' && record.typeName !== 'binding'))
83
+ throw new TypeError('Canvas edits accept only shapes and bindings');
84
+ parsed = { kind: edit.kind, record };
85
+ }
86
+ else
87
+ throw new TypeError('Unknown canvas edit kind');
88
+ const id = parsed.kind === 'remove' ? parsed.id : parsed.record.id;
89
+ if (requested.has(id))
90
+ throw new TypeError('Canvas batch edits an identity more than once');
91
+ requested.add(id);
92
+ edits.push(parsed);
93
+ }
94
+ return edits;
95
+ }
96
+ /** Apply one atomic edit batch to a detached candidate. The caller owns conditional publication. */
97
+ export function applyCanvasEdits(document, edits, schema) {
98
+ try {
99
+ const candidate = parseCanvasDocument(document, schema);
100
+ const removed = new Set(), written = new Set();
101
+ for (const edit of parseCanvasEdits(edits, schema)) {
102
+ if (edit.kind === 'remove') {
103
+ const current = candidate.store[edit.id];
104
+ if (current?.typeName !== 'shape' && current?.typeName !== 'binding')
105
+ throw new TypeError('Canvas removal requires an existing shape or binding');
106
+ removed.add(edit.id);
107
+ }
108
+ else {
109
+ const record = edit.record, current = candidate.store[record.id];
110
+ if (edit.kind === 'create' ? current !== undefined : current?.typeName !== record.typeName || !('type' in current) || current.type !== record.type) {
111
+ throw new TypeError('Canvas create requires absence; update requires an existing record of the same type');
112
+ }
113
+ written.add(record.id);
114
+ candidate.store[record.id] = record;
115
+ }
116
+ }
117
+ const records = Object.values(candidate.store);
118
+ let previousSize = -1;
119
+ while (removed.size !== previousSize) {
120
+ previousSize = removed.size;
121
+ for (const record of records) {
122
+ if (record.typeName === 'shape' && removed.has(record.parentId))
123
+ removed.add(record.id);
124
+ if (record.typeName === 'binding' && !removed.has(record.id) && removed.has(record.toId))
125
+ removed.add(record.fromId);
126
+ }
127
+ }
128
+ for (const record of records)
129
+ if (record.typeName === 'binding' && (removed.has(record.fromId) || removed.has(record.toId)))
130
+ removed.add(record.id);
131
+ for (const id of removed) {
132
+ if (written.has(id))
133
+ throw new TypeError('Canvas batch writes a record removed by its deletion cascade');
134
+ delete candidate.store[id];
135
+ }
136
+ return { kind: 'applied', document: parseCanvasDocument(candidate, schema) };
137
+ }
138
+ catch (error) {
139
+ return { kind: 'rejected', reason: error instanceof Error ? error.message : 'Invalid canvas edit batch' };
140
+ }
141
+ }
@@ -0,0 +1,22 @@
1
+ import type { CSSProperties } from 'react';
2
+ import type { TLOnMountHandler } from '@tldraw/editor';
3
+ import type { TLDefaultFonts, TLUiAssetUrls } from 'tldraw';
4
+ import type { CanvasController } from './canvas.js';
5
+ import type { CanvasMountedTools } from './canvas-mounted.js';
6
+ export type CanvasFontUrls = Readonly<Record<`${keyof TLDefaultFonts}${'' | '_italic' | '_bold' | '_italic_bold'}`, string>>;
7
+ export type CanvasAssetUrls = Omit<TLUiAssetUrls, 'fonts' | 'embedIcons'> & {
8
+ readonly fonts: CanvasFontUrls;
9
+ readonly embedIcons: Required<TLUiAssetUrls['embedIcons']>;
10
+ };
11
+ export interface CanvasEditorProps {
12
+ readonly controller: CanvasController;
13
+ readonly assetUrls: CanvasAssetUrls;
14
+ readonly title?: string;
15
+ readonly className?: string;
16
+ readonly height?: CSSProperties['height'];
17
+ readonly licenseKey?: string;
18
+ readonly onMount?: TLOnMountHandler;
19
+ readonly onMountedTools?: (tools: CanvasMountedTools | null) => void;
20
+ }
21
+ export default function NativeCanvas({ controller, assetUrls, title, className, height, licenseKey, onMount, onMountedTools }: CanvasEditorProps): import("react").JSX.Element;
22
+ //# sourceMappingURL=canvas-editor-native.d.ts.map