@hachej/boring-ui-kit 0.1.111 → 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 +255 -239
- package/THIRD_PARTY_NOTICES.md +85 -0
- package/dist/anchor-conformance.d.ts +78 -0
- package/dist/anchor-conformance.js +288 -0
- package/dist/canvas-document.d.ts +25 -0
- package/dist/canvas-document.js +141 -0
- package/dist/canvas-editor-native.d.ts +22 -0
- package/dist/canvas-editor-native.js +112 -0
- package/dist/canvas-editor.d.ts +6 -0
- package/dist/canvas-editor.js +13 -0
- package/dist/canvas-mounted.d.ts +41 -0
- package/dist/canvas-mounted.js +180 -0
- package/dist/canvas-proposals.d.ts +11 -0
- package/dist/canvas-proposals.js +45 -0
- package/dist/canvas-records.d.ts +3 -0
- package/dist/canvas-records.js +39 -0
- package/dist/canvas.d.ts +92 -0
- package/dist/canvas.js +264 -0
- package/dist/contracts.d.ts +125 -0
- package/dist/contracts.js +1 -0
- package/dist/experience-catalog.d.ts +142 -0
- package/dist/experience-catalog.js +25 -0
- package/dist/experience-compose.d.ts +83 -0
- package/dist/experience-compose.js +218 -0
- package/dist/experience-composition-schema.d.ts +29 -0
- package/dist/experience-composition-schema.js +22 -0
- package/dist/experience-document-viewer.d.ts +13 -0
- package/dist/experience-document-viewer.js +54 -0
- package/dist/experience-document.d.ts +64 -0
- package/dist/experience-document.js +234 -0
- package/dist/experience-region-text.d.ts +3 -0
- package/dist/experience-region-text.js +84 -0
- package/dist/experience-region-tree.d.ts +22 -0
- package/dist/experience-region-tree.js +50 -0
- package/dist/experience-regions.d.ts +9 -0
- package/dist/experience-regions.js +122 -0
- package/dist/experience-renderer.d.ts +16 -0
- package/dist/experience-renderer.js +37 -0
- package/dist/experience.d.ts +12 -0
- package/dist/experience.js +23 -0
- package/dist/file-tree-shadcn.d.ts +8 -0
- package/dist/file-tree-shadcn.js +14 -0
- package/dist/file-tree-view.d.ts +64 -0
- package/dist/file-tree-view.js +256 -0
- package/dist/file-tree.d.ts +57 -0
- package/dist/file-tree.js +204 -0
- package/dist/html-preview.d.ts +2 -0
- package/dist/html-preview.js +47 -0
- package/dist/html-viewer.d.ts +13 -0
- package/dist/html-viewer.js +69 -0
- package/dist/html.d.ts +21 -0
- package/dist/html.js +21 -0
- package/dist/index.d.ts +2 -472
- package/dist/index.js +1 -2671
- package/dist/markdown-editor.d.ts +28 -0
- package/dist/markdown-editor.js +230 -0
- package/dist/markdown-extensions.d.ts +16 -0
- package/dist/markdown-extensions.js +96 -0
- package/dist/markdown-icons.d.ts +244 -0
- package/dist/markdown-icons.js +32 -0
- package/dist/markdown-mounted.d.ts +63 -0
- package/dist/markdown-mounted.js +263 -0
- package/dist/markdown-parser.d.ts +4 -0
- package/dist/markdown-parser.js +33 -0
- package/dist/markdown-proposal-diff.d.ts +5 -0
- package/dist/markdown-proposal-diff.js +122 -0
- package/dist/markdown-safety.d.ts +21 -0
- package/dist/markdown-safety.js +135 -0
- package/dist/markdown.d.ts +86 -0
- package/dist/markdown.js +121 -0
- package/dist/native-chat.d.ts +150 -0
- package/dist/native-chat.js +376 -0
- package/dist/pi.d.ts +12 -0
- package/dist/pi.js +1 -0
- package/dist/public-text-buffer.d.ts +5 -0
- package/dist/public-text-buffer.js +1 -0
- package/dist/remote-chat.d.ts +76 -0
- package/dist/remote-chat.js +324 -0
- package/dist/resources.d.ts +34 -0
- package/dist/resources.js +1 -0
- package/dist/text-buffer-state.d.ts +24 -0
- package/dist/text-buffer-state.js +1 -0
- package/dist/text-buffer.d.ts +52 -0
- package/dist/text-buffer.js +315 -0
- package/dist/text-draft-controls.d.ts +9 -0
- package/dist/text-draft-controls.js +33 -0
- package/dist/text-draft-types.d.ts +111 -0
- package/dist/text-draft-types.js +1 -0
- package/dist/text-drafts.d.ts +29 -0
- package/dist/text-drafts.js +378 -0
- package/package.json +226 -58
- package/styles/file-tree.css +46 -0
- package/dist/styles.css +0 -2308
- package/dist/tokens.css +0 -155
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { identifier, locator, reference } from '@hachej/boring-files/publication';
|
|
2
|
+
import { randomUUID } from '@hachej/boring-files/platform';
|
|
3
|
+
function object(value) {
|
|
4
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
5
|
+
throw new TypeError('Invalid recovery record');
|
|
6
|
+
return Object.fromEntries(Object.entries(value));
|
|
7
|
+
}
|
|
8
|
+
function integer(value, minimum = 1) {
|
|
9
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < minimum)
|
|
10
|
+
throw new TypeError('Invalid recovery bound');
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
function expectation(value) {
|
|
14
|
+
const input = object(value);
|
|
15
|
+
if (input.kind === 'absent')
|
|
16
|
+
return { kind: 'absent', target: locator(input.target) };
|
|
17
|
+
if (input.kind === 'revision')
|
|
18
|
+
return { kind: 'revision', target: reference(input.target) };
|
|
19
|
+
throw new TypeError('Invalid recovery base');
|
|
20
|
+
}
|
|
21
|
+
function key(value) {
|
|
22
|
+
const input = object(value), identity = object(input.identity);
|
|
23
|
+
return { identity: { principalId: identifier(identity.principalId), scopeId: identifier(identity.scopeId), initiatorId: identifier(identity.initiatorId) },
|
|
24
|
+
providerInstanceId: identifier(input.providerInstanceId), target: locator(input.target), format: identifier(input.format) };
|
|
25
|
+
}
|
|
26
|
+
function draftRef(value) {
|
|
27
|
+
const input = object(value);
|
|
28
|
+
return { key: key(input.key), base: expectation(input.base), writerId: identifier(input.writerId), sequence: integer(input.sequence) };
|
|
29
|
+
}
|
|
30
|
+
function sameRef(left, right) { return JSON.stringify(left) === JSON.stringify(right); }
|
|
31
|
+
function failure(value) {
|
|
32
|
+
const input = object(value);
|
|
33
|
+
if (input.kind !== 'denied' && input.kind !== 'expired' && input.kind !== 'unavailable' && input.kind !== 'unknown')
|
|
34
|
+
throw new TypeError('Invalid recovery result');
|
|
35
|
+
return { kind: input.kind, reason: typeof input.reason === 'string' ? input.reason.slice(0, 1024) : 'Draft storage failed' };
|
|
36
|
+
}
|
|
37
|
+
function unavailable(reason) { return { kind: 'unavailable', reason }; }
|
|
38
|
+
function conflict() { return { kind: 'conflict', reason: 'The recovery choice, current document, or local buffer changed' }; }
|
|
39
|
+
function validateText(text, maxBytes) {
|
|
40
|
+
if (typeof text !== 'string' || text.length > maxBytes)
|
|
41
|
+
throw new TypeError('Recovery text exceeds its limit');
|
|
42
|
+
const bytes = new TextEncoder().encode(text);
|
|
43
|
+
if (bytes.length > maxBytes || new TextDecoder('utf-8', { ignoreBOM: true }).decode(bytes) !== text)
|
|
44
|
+
throw new TypeError('Recovery text is not bounded valid Unicode');
|
|
45
|
+
return text;
|
|
46
|
+
}
|
|
47
|
+
export function createTextDrafts(owner) {
|
|
48
|
+
const binding = owner.binding;
|
|
49
|
+
const maxBytes = integer(binding?.maxBytes ?? 8 * 1024 * 1024), maxDrafts = integer(binding?.maxDrafts ?? 20);
|
|
50
|
+
if (binding) {
|
|
51
|
+
identifier(binding.providerInstanceId);
|
|
52
|
+
identifier(binding.format);
|
|
53
|
+
integer(binding.expiresAt);
|
|
54
|
+
integer(binding.retentionMs);
|
|
55
|
+
}
|
|
56
|
+
const writerId = randomUUID();
|
|
57
|
+
let sequence = 0, checkSequence = 0, closed = false;
|
|
58
|
+
let recovery = binding ? { kind: 'active', checkpoint: { kind: 'idle' }, discovery: { kind: 'idle' } } : { kind: 'disabled' };
|
|
59
|
+
let current;
|
|
60
|
+
let restored;
|
|
61
|
+
let timer;
|
|
62
|
+
const activeKey = binding ? key({ identity: owner.snapshot().identity, providerInstanceId: binding.providerInstanceId, target: owner.snapshot().base.target, format: binding.format }) : undefined;
|
|
63
|
+
function emit(next) { recovery = next; owner.changed(next); }
|
|
64
|
+
function stop(kind) {
|
|
65
|
+
checkSequence++;
|
|
66
|
+
current = undefined;
|
|
67
|
+
if (kind !== 'disposed')
|
|
68
|
+
restored = undefined;
|
|
69
|
+
if (timer !== undefined)
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
binding?.signal.removeEventListener('abort', revoke);
|
|
72
|
+
emit({ kind });
|
|
73
|
+
}
|
|
74
|
+
function revoke() { if (!closed)
|
|
75
|
+
stop('revoked'); }
|
|
76
|
+
function access(allowDisposed = false) {
|
|
77
|
+
if (!binding)
|
|
78
|
+
return unavailable('Draft recovery is disabled');
|
|
79
|
+
if (binding.signal.aborted) {
|
|
80
|
+
if (!closed && recovery.kind !== 'revoked')
|
|
81
|
+
stop('revoked');
|
|
82
|
+
return { kind: 'denied', reason: 'Draft access was revoked' };
|
|
83
|
+
}
|
|
84
|
+
if (binding.expiresAt <= Date.now()) {
|
|
85
|
+
if (!closed && recovery.kind !== 'expired')
|
|
86
|
+
stop('expired');
|
|
87
|
+
return { kind: 'expired', reason: 'Draft access expired' };
|
|
88
|
+
}
|
|
89
|
+
if (closed && !allowDisposed)
|
|
90
|
+
return unavailable('Viewer is disposed');
|
|
91
|
+
if (owner.snapshot().readOnly)
|
|
92
|
+
return { kind: 'denied', reason: 'Viewer is read-only' };
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
function scheduleExpiry() {
|
|
96
|
+
if (!binding || closed || recovery.kind !== 'active')
|
|
97
|
+
return;
|
|
98
|
+
if (timer !== undefined)
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
let expiry = binding.expiresAt;
|
|
101
|
+
if (current && current.draft.expiresAt > Date.now())
|
|
102
|
+
expiry = Math.min(expiry, current.draft.expiresAt);
|
|
103
|
+
if (recovery.discovery.kind === 'offered')
|
|
104
|
+
for (const choice of recovery.discovery.choices)
|
|
105
|
+
expiry = Math.min(expiry, choice.expiresAt);
|
|
106
|
+
timer = setTimeout(() => {
|
|
107
|
+
if (access())
|
|
108
|
+
return;
|
|
109
|
+
if (current && current.draft.expiresAt <= Date.now())
|
|
110
|
+
checkpointFailure(current.draft.ref, 'write', { kind: 'expired', reason: 'The recovery checkpoint expired' });
|
|
111
|
+
if (recovery.kind === 'active' && recovery.discovery.kind === 'offered') {
|
|
112
|
+
const choices = recovery.discovery.choices.filter(choice => choice.expiresAt > Date.now());
|
|
113
|
+
emit({ ...recovery, discovery: choices.length ? { ...recovery.discovery, choices } : { kind: 'empty' } });
|
|
114
|
+
}
|
|
115
|
+
scheduleExpiry();
|
|
116
|
+
}, Math.min(2_147_483_647, Math.max(1, expiry - Date.now())));
|
|
117
|
+
if (typeof timer === 'object' && 'unref' in timer)
|
|
118
|
+
timer.unref();
|
|
119
|
+
}
|
|
120
|
+
function discovery(value) {
|
|
121
|
+
if (recovery.kind === 'active') {
|
|
122
|
+
emit({ ...recovery, discovery: value });
|
|
123
|
+
scheduleExpiry();
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
function parseDraft(value) {
|
|
128
|
+
const input = object(value), ref = draftRef(input.ref);
|
|
129
|
+
if (input.version !== 1 || !activeKey || JSON.stringify(ref.key) !== JSON.stringify(activeKey) || !owner.sameLocator(ref.base.target, activeKey.target))
|
|
130
|
+
throw new TypeError('Draft belongs to a different identity, provider, resource, view, or format');
|
|
131
|
+
const createdAt = integer(input.createdAt, 0), expiresAt = integer(input.expiresAt);
|
|
132
|
+
if (expiresAt <= createdAt)
|
|
133
|
+
throw new TypeError('Invalid draft retention');
|
|
134
|
+
return { version: 1, ref, text: validateText(input.text, maxBytes), createdAt, expiresAt };
|
|
135
|
+
}
|
|
136
|
+
async function list() {
|
|
137
|
+
if (!binding || !activeKey)
|
|
138
|
+
return unavailable('Draft recovery is disabled');
|
|
139
|
+
try {
|
|
140
|
+
const value = object(await binding.store.list(structuredClone(activeKey), maxDrafts));
|
|
141
|
+
if (value.kind !== 'available')
|
|
142
|
+
return failure(value);
|
|
143
|
+
if (!Array.isArray(value.drafts) || value.drafts.length > maxDrafts || typeof value.truncated !== 'boolean')
|
|
144
|
+
return unavailable('Draft storage returned an unbounded listing');
|
|
145
|
+
const drafts = value.drafts.map(parseDraft);
|
|
146
|
+
if (new Set(drafts.map(item => JSON.stringify(item.ref))).size !== drafts.length)
|
|
147
|
+
return unavailable('Draft storage returned duplicate records');
|
|
148
|
+
return { kind: 'available', drafts: drafts.filter(item => item.expiresAt > Date.now()), truncated: value.truncated };
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return unavailable('Draft storage could not be read or validated');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function checkpointFailure(ref, operation, result) {
|
|
155
|
+
if (!access() && recovery.kind === 'active' && (operation === 'remove' || current && sameRef(current.draft.ref, ref)))
|
|
156
|
+
emit({ ...recovery, checkpoint: { kind: 'failed', ref, operation, result } });
|
|
157
|
+
}
|
|
158
|
+
async function write(draft) {
|
|
159
|
+
const denied = access();
|
|
160
|
+
if (denied || !binding)
|
|
161
|
+
return denied ?? unavailable('Draft recovery is disabled');
|
|
162
|
+
let result;
|
|
163
|
+
try {
|
|
164
|
+
const value = object(await binding.store.write(structuredClone(draft)));
|
|
165
|
+
result = value.kind === 'stored' ? { kind: 'stored', ref: draft.ref } : value.kind === 'superseded' ? { kind: 'superseded' } : failure(value);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
result = { kind: 'unknown', reason: 'Draft storage acknowledgement was lost' };
|
|
169
|
+
}
|
|
170
|
+
const revoked = access();
|
|
171
|
+
if (revoked)
|
|
172
|
+
return revoked;
|
|
173
|
+
if (result.kind === 'stored' && draft.expiresAt <= Date.now())
|
|
174
|
+
result = { kind: 'expired', reason: 'The recovery checkpoint expired before acknowledgement' };
|
|
175
|
+
if (result.kind === 'stored' && current && sameRef(current.draft.ref, draft.ref) && recovery.kind === 'active')
|
|
176
|
+
emit({ ...recovery, checkpoint: { kind: 'stored', ref: draft.ref } });
|
|
177
|
+
else if (result.kind === 'superseded' && current && sameRef(current.draft.ref, draft.ref) && recovery.kind === 'active')
|
|
178
|
+
emit({ ...recovery, checkpoint: { kind: 'idle' } });
|
|
179
|
+
else if ('reason' in result)
|
|
180
|
+
checkpointFailure(draft.ref, 'write', result);
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
function capture() {
|
|
184
|
+
const state = owner.snapshot();
|
|
185
|
+
if (access() || !binding || !activeKey || !state.dirty)
|
|
186
|
+
return undefined;
|
|
187
|
+
if (current && current.version === state.bufferVersion && owner.sameBase(current.draft.ref.base, state.base) && current.draft.text === state.text && current.draft.expiresAt > Date.now())
|
|
188
|
+
return current.draft.ref;
|
|
189
|
+
const now = Date.now();
|
|
190
|
+
const draft = { version: 1, ref: { key: activeKey, base: structuredClone(state.base), writerId, sequence: ++sequence }, text: state.text, createdAt: now, expiresAt: Math.min(binding.expiresAt, now + binding.retentionMs) };
|
|
191
|
+
const item = { draft, version: state.bufferVersion, promise: Promise.resolve({ kind: 'superseded' }) };
|
|
192
|
+
current = item;
|
|
193
|
+
try {
|
|
194
|
+
validateText(draft.text, maxBytes);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
const result = unavailable('The current text cannot be stored as a recovery draft');
|
|
198
|
+
item.promise = Promise.resolve(result);
|
|
199
|
+
checkpointFailure(draft.ref, 'write', result);
|
|
200
|
+
return draft.ref;
|
|
201
|
+
}
|
|
202
|
+
item.promise = Promise.resolve().then(() => write(draft));
|
|
203
|
+
if (recovery.kind === 'active')
|
|
204
|
+
emit({ ...recovery, checkpoint: { kind: 'pending', ref: draft.ref } });
|
|
205
|
+
scheduleExpiry();
|
|
206
|
+
return draft.ref;
|
|
207
|
+
}
|
|
208
|
+
async function remove(ref) {
|
|
209
|
+
const denied = access(true);
|
|
210
|
+
if (denied || !binding)
|
|
211
|
+
return denied ?? unavailable('Draft recovery is disabled');
|
|
212
|
+
let result;
|
|
213
|
+
try {
|
|
214
|
+
const value = object(await binding.store.remove(structuredClone(ref)));
|
|
215
|
+
if (value.kind !== 'removed' && value.kind !== 'missing' && value.kind !== 'superseded')
|
|
216
|
+
result = failure(value);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
result = { kind: 'unknown', reason: 'Draft removal acknowledgement was lost' };
|
|
220
|
+
}
|
|
221
|
+
if (result)
|
|
222
|
+
checkpointFailure(ref, 'remove', result);
|
|
223
|
+
else if (!access() && current && sameRef(current.draft.ref, ref) && recovery.kind === 'active')
|
|
224
|
+
emit({ ...recovery, checkpoint: { kind: 'idle' } });
|
|
225
|
+
return access(true) ?? result;
|
|
226
|
+
}
|
|
227
|
+
async function latest() {
|
|
228
|
+
try {
|
|
229
|
+
const read = await owner.read();
|
|
230
|
+
if (read.kind === 'missing')
|
|
231
|
+
return { kind: 'absent', target: locator(owner.snapshot().base.target) };
|
|
232
|
+
if (read.kind !== 'available')
|
|
233
|
+
return unavailable('The current document could not be checked');
|
|
234
|
+
const ref = reference(read.snapshot.ref);
|
|
235
|
+
if (!activeKey || !owner.sameLocator(ref, activeKey.target))
|
|
236
|
+
return unavailable('The current read returned a different resource');
|
|
237
|
+
return { kind: 'revision', target: ref };
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return unavailable('The current document could not be checked');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function guarded(selection) {
|
|
244
|
+
if (access())
|
|
245
|
+
return false;
|
|
246
|
+
owner.sync();
|
|
247
|
+
return !access() && owner.selected(selection) && !owner.pending();
|
|
248
|
+
}
|
|
249
|
+
async function choose(input, restore) {
|
|
250
|
+
const denied = access();
|
|
251
|
+
if (denied)
|
|
252
|
+
return denied;
|
|
253
|
+
let selected;
|
|
254
|
+
try {
|
|
255
|
+
selected = structuredClone(input);
|
|
256
|
+
selected = { ...selected, draft: draftRef(selected.draft) };
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return conflict();
|
|
260
|
+
}
|
|
261
|
+
const offer = recovery.kind === 'active' && recovery.discovery.kind === 'offered' ? recovery.discovery.choices.find(choice => JSON.stringify(choice.selection) === JSON.stringify(selected)) : undefined;
|
|
262
|
+
if (!offer || offer.expiresAt <= Date.now() || !guarded(selected.viewer))
|
|
263
|
+
return conflict();
|
|
264
|
+
const serial = checkSequence;
|
|
265
|
+
const guard = () => serial === checkSequence && offer.expiresAt > Date.now() && guarded(selected.viewer);
|
|
266
|
+
if (restore && owner.snapshot().dirty && !(owner.snapshot().base.kind === 'absent' && owner.snapshot().text === owner.emptyText && owner.snapshot().bufferVersion === 0))
|
|
267
|
+
return conflict();
|
|
268
|
+
const remote = await latest();
|
|
269
|
+
if (!guard())
|
|
270
|
+
return access() ?? conflict();
|
|
271
|
+
if (remote.kind !== 'revision' && remote.kind !== 'absent')
|
|
272
|
+
return remote;
|
|
273
|
+
if (!owner.sameBase(remote, selected.observedCurrent))
|
|
274
|
+
return conflict();
|
|
275
|
+
const records = await list();
|
|
276
|
+
if (!guard())
|
|
277
|
+
return access() ?? conflict();
|
|
278
|
+
if (records.kind !== 'available')
|
|
279
|
+
return records;
|
|
280
|
+
const draft = records.drafts.find(item => sameRef(item.ref, selected.draft));
|
|
281
|
+
if (!draft || draft.text !== offer.text || draft.expiresAt !== offer.expiresAt)
|
|
282
|
+
return conflict();
|
|
283
|
+
if (!restore) {
|
|
284
|
+
const result = await remove(draft.ref);
|
|
285
|
+
if (result)
|
|
286
|
+
return result;
|
|
287
|
+
if (guard())
|
|
288
|
+
discovery({ kind: 'idle' });
|
|
289
|
+
return { kind: 'discarded' };
|
|
290
|
+
}
|
|
291
|
+
if (!owner.sameBase(remote, draft.ref.base) || !owner.sameBase(remote, owner.snapshot().base))
|
|
292
|
+
return conflict();
|
|
293
|
+
try {
|
|
294
|
+
binding?.validateText?.(draft.text);
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return unavailable('The recovered document is not valid for this viewer');
|
|
298
|
+
}
|
|
299
|
+
if (!guard())
|
|
300
|
+
return access() ?? conflict();
|
|
301
|
+
if (!owner.restore(draft.text, guard))
|
|
302
|
+
return conflict();
|
|
303
|
+
restored = draft;
|
|
304
|
+
checkSequence++;
|
|
305
|
+
discovery({ kind: 'idle' });
|
|
306
|
+
return { kind: 'restored', selection: owner.selection() };
|
|
307
|
+
}
|
|
308
|
+
const actions = {
|
|
309
|
+
checkDrafts: async () => {
|
|
310
|
+
const denied = access();
|
|
311
|
+
if (denied)
|
|
312
|
+
return denied;
|
|
313
|
+
owner.sync();
|
|
314
|
+
const selection = owner.selection(), serial = ++checkSequence;
|
|
315
|
+
if (!guarded(selection))
|
|
316
|
+
return unavailable('A save must be settled before checking recovery drafts');
|
|
317
|
+
discovery({ kind: 'checking' });
|
|
318
|
+
const remote = await latest();
|
|
319
|
+
if (serial !== checkSequence || !guarded(selection)) {
|
|
320
|
+
const result = access() ?? unavailable('The viewer changed during recovery discovery');
|
|
321
|
+
return serial === checkSequence ? discovery(result) : result;
|
|
322
|
+
}
|
|
323
|
+
if (remote.kind !== 'revision' && remote.kind !== 'absent')
|
|
324
|
+
return discovery(remote);
|
|
325
|
+
const records = await list();
|
|
326
|
+
if (serial !== checkSequence || !guarded(selection)) {
|
|
327
|
+
const result = access() ?? unavailable('The viewer changed during recovery discovery');
|
|
328
|
+
return serial === checkSequence ? discovery(result) : result;
|
|
329
|
+
}
|
|
330
|
+
if (records.kind !== 'available')
|
|
331
|
+
return discovery(records);
|
|
332
|
+
const offerId = randomUUID();
|
|
333
|
+
return discovery(records.drafts.length ? { kind: 'offered', truncated: records.truncated, choices: records.drafts.map(draft => ({ selection: { offerId, draft: draft.ref, viewer: selection, observedCurrent: remote }, text: draft.text, createdAt: draft.createdAt, expiresAt: draft.expiresAt, compatibility: owner.sameBase(draft.ref.base, remote) ? 'exact' : 'conflict' })) } : { kind: 'empty' });
|
|
334
|
+
},
|
|
335
|
+
checkpointDraft: async () => {
|
|
336
|
+
const denied = access();
|
|
337
|
+
if (denied)
|
|
338
|
+
return denied;
|
|
339
|
+
owner.sync();
|
|
340
|
+
const retry = recovery.kind === 'active' && recovery.checkpoint.kind === 'failed' && recovery.checkpoint.operation === 'write' ? recovery.checkpoint.ref : undefined;
|
|
341
|
+
const ref = capture();
|
|
342
|
+
if (!ref || !current)
|
|
343
|
+
return { kind: 'clean' };
|
|
344
|
+
const item = current;
|
|
345
|
+
if (retry && sameRef(retry, ref))
|
|
346
|
+
item.promise = write(item.draft);
|
|
347
|
+
return item.promise;
|
|
348
|
+
},
|
|
349
|
+
restoreDraft: async (choice) => {
|
|
350
|
+
const result = await choose(choice, true);
|
|
351
|
+
return result.kind === 'discarded' ? conflict() : result;
|
|
352
|
+
},
|
|
353
|
+
discardDraft: async (choice) => {
|
|
354
|
+
const result = await choose(choice, false);
|
|
355
|
+
return result.kind === 'restored' ? conflict() : result;
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
if (binding) {
|
|
359
|
+
binding.signal.addEventListener('abort', revoke, { once: true });
|
|
360
|
+
if (!access())
|
|
361
|
+
scheduleExpiry();
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
actions, capture, remove, currentRef: () => current?.draft.ref, state: () => recovery,
|
|
365
|
+
acknowledge: (ref, text) => {
|
|
366
|
+
if (ref)
|
|
367
|
+
void remove(ref);
|
|
368
|
+
if (restored?.text === text) {
|
|
369
|
+
void remove(restored.ref);
|
|
370
|
+
restored = undefined;
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
dispose: () => { if (!closed) {
|
|
374
|
+
stop('disposed');
|
|
375
|
+
closed = true;
|
|
376
|
+
} },
|
|
377
|
+
};
|
|
378
|
+
}
|
package/package.json
CHANGED
|
@@ -1,75 +1,243 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-ui-kit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"type": "module",
|
|
3
|
+
"version": "0.2.0",
|
|
5
4
|
"license": "MIT",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"description": "Independent headless viewer contracts with optional resource and native Pi subpaths.",
|
|
8
|
+
"sideEffects": [
|
|
9
|
+
"**/*.css"
|
|
10
|
+
],
|
|
12
11
|
"files": [
|
|
13
|
-
"dist"
|
|
12
|
+
"dist/**/*.js",
|
|
13
|
+
"dist/**/*.d.ts",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"THIRD_PARTY_NOTICES.md",
|
|
17
|
+
"styles"
|
|
14
18
|
],
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
15
20
|
"exports": {
|
|
16
21
|
".": {
|
|
17
22
|
"types": "./dist/index.d.ts",
|
|
18
23
|
"import": "./dist/index.js"
|
|
19
24
|
},
|
|
20
|
-
"./
|
|
21
|
-
"
|
|
22
|
-
"
|
|
25
|
+
"./contracts": {
|
|
26
|
+
"types": "./dist/contracts.d.ts",
|
|
27
|
+
"import": "./dist/contracts.js"
|
|
23
28
|
},
|
|
24
|
-
"./
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
}
|
|
29
|
+
"./anchor-conformance": {
|
|
30
|
+
"types": "./dist/anchor-conformance.d.ts",
|
|
31
|
+
"import": "./dist/anchor-conformance.js"
|
|
32
|
+
},
|
|
33
|
+
"./resources": {
|
|
34
|
+
"types": "./dist/resources.d.ts",
|
|
35
|
+
"import": "./dist/resources.js"
|
|
36
|
+
},
|
|
37
|
+
"./markdown": {
|
|
38
|
+
"types": "./dist/markdown.d.ts",
|
|
39
|
+
"import": "./dist/markdown.js"
|
|
40
|
+
},
|
|
41
|
+
"./pi": {
|
|
42
|
+
"types": "./dist/pi.d.ts",
|
|
43
|
+
"import": "./dist/pi.js"
|
|
44
|
+
},
|
|
45
|
+
"./markdown-editor": {
|
|
46
|
+
"types": "./dist/markdown-editor.d.ts",
|
|
47
|
+
"import": "./dist/markdown-editor.js"
|
|
48
|
+
},
|
|
49
|
+
"./native-chat": {
|
|
50
|
+
"types": "./dist/native-chat.d.ts",
|
|
51
|
+
"import": "./dist/native-chat.js"
|
|
52
|
+
},
|
|
53
|
+
"./remote-chat": {
|
|
54
|
+
"types": "./dist/remote-chat.d.ts",
|
|
55
|
+
"import": "./dist/remote-chat.js"
|
|
56
|
+
},
|
|
57
|
+
"./canvas": {
|
|
58
|
+
"types": "./dist/canvas.d.ts",
|
|
59
|
+
"import": "./dist/canvas.js"
|
|
60
|
+
},
|
|
61
|
+
"./canvas-editor": {
|
|
62
|
+
"types": "./dist/canvas-editor.d.ts",
|
|
63
|
+
"import": "./dist/canvas-editor.js"
|
|
64
|
+
},
|
|
65
|
+
"./html": {
|
|
66
|
+
"types": "./dist/html.d.ts",
|
|
67
|
+
"import": "./dist/html.js"
|
|
68
|
+
},
|
|
69
|
+
"./html-viewer": {
|
|
70
|
+
"types": "./dist/html-viewer.d.ts",
|
|
71
|
+
"import": "./dist/html-viewer.js"
|
|
72
|
+
},
|
|
73
|
+
"./experience": {
|
|
74
|
+
"types": "./dist/experience.d.ts",
|
|
75
|
+
"import": "./dist/experience.js"
|
|
76
|
+
},
|
|
77
|
+
"./experience/compose": {
|
|
78
|
+
"types": "./dist/experience-compose.d.ts",
|
|
79
|
+
"import": "./dist/experience-compose.js"
|
|
80
|
+
},
|
|
81
|
+
"./experience/document": {
|
|
82
|
+
"types": "./dist/experience-document.d.ts",
|
|
83
|
+
"import": "./dist/experience-document.js"
|
|
84
|
+
},
|
|
85
|
+
"./experience/document-viewer": {
|
|
86
|
+
"types": "./dist/experience-document-viewer.d.ts",
|
|
87
|
+
"import": "./dist/experience-document-viewer.js"
|
|
88
|
+
},
|
|
89
|
+
"./experience/regions": {
|
|
90
|
+
"types": "./dist/experience-regions.d.ts",
|
|
91
|
+
"import": "./dist/experience-regions.js"
|
|
92
|
+
},
|
|
93
|
+
"./canvas-document": {
|
|
94
|
+
"types": "./dist/canvas-document.d.ts",
|
|
95
|
+
"import": "./dist/canvas-document.js"
|
|
96
|
+
},
|
|
97
|
+
"./text-buffer": {
|
|
98
|
+
"types": "./dist/public-text-buffer.d.ts",
|
|
99
|
+
"import": "./dist/public-text-buffer.js"
|
|
100
|
+
},
|
|
101
|
+
"./file-tree": {
|
|
102
|
+
"types": "./dist/file-tree.d.ts",
|
|
103
|
+
"import": "./dist/file-tree.js"
|
|
104
|
+
},
|
|
105
|
+
"./file-tree-view": {
|
|
106
|
+
"types": "./dist/file-tree-view.d.ts",
|
|
107
|
+
"import": "./dist/file-tree-view.js"
|
|
108
|
+
},
|
|
109
|
+
"./file-tree.css": "./styles/file-tree.css"
|
|
28
110
|
},
|
|
29
|
-
"sideEffects": [
|
|
30
|
-
"*.css"
|
|
31
|
-
],
|
|
32
111
|
"peerDependencies": {
|
|
33
|
-
"
|
|
34
|
-
"
|
|
112
|
+
"@hachej/boring-files": "0.2.0",
|
|
113
|
+
"@earendil-works/pi-durable": "1.1.0",
|
|
114
|
+
"react": "19.3.0",
|
|
115
|
+
"react-dom": "19.3.0",
|
|
116
|
+
"marked": "17.0.6",
|
|
117
|
+
"@tiptap/core": "3.31.4",
|
|
118
|
+
"@tiptap/react": "3.31.4",
|
|
119
|
+
"@tiptap/pm": "3.31.4",
|
|
120
|
+
"@tiptap/starter-kit": "3.31.4",
|
|
121
|
+
"@tiptap/markdown": "3.31.4",
|
|
122
|
+
"@tiptap/extensions": "3.31.4",
|
|
123
|
+
"@tiptap/extension-list": "3.31.4",
|
|
124
|
+
"@tiptap/extension-table": "3.31.4",
|
|
125
|
+
"@tiptap/extension-image": "3.31.4",
|
|
126
|
+
"@tiptap/extension-highlight": "3.31.4",
|
|
127
|
+
"@tiptap/extension-bubble-menu": "3.31.4",
|
|
128
|
+
"@tiptap/extension-floating-menu": "3.31.4",
|
|
129
|
+
"@earendil-works/chord": "1.1.0",
|
|
130
|
+
"@earendil-works/pi-ai": "1.1.0",
|
|
131
|
+
"tldraw": "5.5.2",
|
|
132
|
+
"@tldraw/editor": "5.5.2",
|
|
133
|
+
"@tldraw/store": "5.5.2",
|
|
134
|
+
"@json-render/core": "0.21.0",
|
|
135
|
+
"@json-render/react": "0.21.0",
|
|
136
|
+
"zod": "4.6.5",
|
|
137
|
+
"@tldraw/tlschema": "5.5.2",
|
|
138
|
+
"@radix-ui/react-collapsible": "1.1.20",
|
|
139
|
+
"lucide-react": "1.51.0"
|
|
140
|
+
},
|
|
141
|
+
"peerDependenciesMeta": {
|
|
142
|
+
"@hachej/boring-files": {
|
|
143
|
+
"optional": true
|
|
144
|
+
},
|
|
145
|
+
"@earendil-works/pi-durable": {
|
|
146
|
+
"optional": true
|
|
147
|
+
},
|
|
148
|
+
"react": {
|
|
149
|
+
"optional": true
|
|
150
|
+
},
|
|
151
|
+
"react-dom": {
|
|
152
|
+
"optional": true
|
|
153
|
+
},
|
|
154
|
+
"marked": {
|
|
155
|
+
"optional": true
|
|
156
|
+
},
|
|
157
|
+
"@tiptap/core": {
|
|
158
|
+
"optional": true
|
|
159
|
+
},
|
|
160
|
+
"@tiptap/react": {
|
|
161
|
+
"optional": true
|
|
162
|
+
},
|
|
163
|
+
"@tiptap/pm": {
|
|
164
|
+
"optional": true
|
|
165
|
+
},
|
|
166
|
+
"@tiptap/starter-kit": {
|
|
167
|
+
"optional": true
|
|
168
|
+
},
|
|
169
|
+
"@tiptap/markdown": {
|
|
170
|
+
"optional": true
|
|
171
|
+
},
|
|
172
|
+
"@tiptap/extensions": {
|
|
173
|
+
"optional": true
|
|
174
|
+
},
|
|
175
|
+
"@tiptap/extension-list": {
|
|
176
|
+
"optional": true
|
|
177
|
+
},
|
|
178
|
+
"@tiptap/extension-table": {
|
|
179
|
+
"optional": true
|
|
180
|
+
},
|
|
181
|
+
"@tiptap/extension-image": {
|
|
182
|
+
"optional": true
|
|
183
|
+
},
|
|
184
|
+
"@tiptap/extension-highlight": {
|
|
185
|
+
"optional": true
|
|
186
|
+
},
|
|
187
|
+
"@tiptap/extension-bubble-menu": {
|
|
188
|
+
"optional": true
|
|
189
|
+
},
|
|
190
|
+
"@tiptap/extension-floating-menu": {
|
|
191
|
+
"optional": true
|
|
192
|
+
},
|
|
193
|
+
"@earendil-works/chord": {
|
|
194
|
+
"optional": true
|
|
195
|
+
},
|
|
196
|
+
"@earendil-works/pi-ai": {
|
|
197
|
+
"optional": true
|
|
198
|
+
},
|
|
199
|
+
"tldraw": {
|
|
200
|
+
"optional": true
|
|
201
|
+
},
|
|
202
|
+
"@tldraw/editor": {
|
|
203
|
+
"optional": true
|
|
204
|
+
},
|
|
205
|
+
"@tldraw/store": {
|
|
206
|
+
"optional": true
|
|
207
|
+
},
|
|
208
|
+
"@json-render/core": {
|
|
209
|
+
"optional": true
|
|
210
|
+
},
|
|
211
|
+
"@json-render/react": {
|
|
212
|
+
"optional": true
|
|
213
|
+
},
|
|
214
|
+
"zod": {
|
|
215
|
+
"optional": true
|
|
216
|
+
},
|
|
217
|
+
"@tldraw/tlschema": {
|
|
218
|
+
"optional": true
|
|
219
|
+
},
|
|
220
|
+
"@radix-ui/react-collapsible": {
|
|
221
|
+
"optional": true
|
|
222
|
+
},
|
|
223
|
+
"lucide-react": {
|
|
224
|
+
"optional": true
|
|
225
|
+
}
|
|
35
226
|
},
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"@radix-ui/react-label": "^2.1.15",
|
|
44
|
-
"@radix-ui/react-popover": "^1.1.23",
|
|
45
|
-
"@radix-ui/react-scroll-area": "^1.2.18",
|
|
46
|
-
"@radix-ui/react-select": "^2.3.7",
|
|
47
|
-
"@radix-ui/react-separator": "^1.1.15",
|
|
48
|
-
"@radix-ui/react-slot": "^1.3.3",
|
|
49
|
-
"@radix-ui/react-tabs": "^1.1.21",
|
|
50
|
-
"@radix-ui/react-tooltip": "^1.2.16",
|
|
51
|
-
"class-variance-authority": "^0.7.1",
|
|
52
|
-
"clsx": "^2.1.1",
|
|
53
|
-
"cmdk": "^1.1.1",
|
|
54
|
-
"tailwind-merge": "^3.5.0"
|
|
227
|
+
"engines": {
|
|
228
|
+
"node": ">=22.19.0"
|
|
229
|
+
},
|
|
230
|
+
"repository": {
|
|
231
|
+
"type": "git",
|
|
232
|
+
"url": "git+https://github.com/hachej/boring-ui.git",
|
|
233
|
+
"directory": "packages/ui"
|
|
55
234
|
},
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
"react": "^19.2.8",
|
|
60
|
-
"react-dom": "^19.2.8",
|
|
61
|
-
"tsup": "^8.4.0",
|
|
62
|
-
"typescript": "^6.0.3",
|
|
63
|
-
"vitest": "^4.1.11",
|
|
64
|
-
"@tailwindcss/postcss": "^4.3.3",
|
|
65
|
-
"postcss": "^8.5.25",
|
|
66
|
-
"tailwindcss": "^4.3.3"
|
|
235
|
+
"homepage": "https://github.com/hachej/boring-ui#readme",
|
|
236
|
+
"bugs": {
|
|
237
|
+
"url": "https://github.com/hachej/boring-ui/issues"
|
|
67
238
|
},
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"lint": "pnpm run typecheck",
|
|
72
|
-
"test": "vitest run --passWithNoTests",
|
|
73
|
-
"clean": "rm -rf dist .tsbuildinfo"
|
|
239
|
+
"publishConfig": {
|
|
240
|
+
"access": "public",
|
|
241
|
+
"registry": "https://registry.npmjs.org/"
|
|
74
242
|
}
|
|
75
|
-
}
|
|
243
|
+
}
|