@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.
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 +64 -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,324 @@
1
+ import { applyImmutable } from '@earendil-works/chord/delta';
2
+ /** A watch request the host answered with an error status. */
3
+ class WatchRefused extends Error {
4
+ status;
5
+ constructor(message, status) {
6
+ super(message);
7
+ this.status = status;
8
+ }
9
+ }
10
+ /** The host refused a submitted input before it reached the conversation (status 402, `submission-refused`); `message` is the host's words. */
11
+ export class SubmissionRefused extends Error {
12
+ code = 'submission-refused';
13
+ constructor(message) { super(message); this.name = 'SubmissionRefused'; }
14
+ }
15
+ async function reason(response) {
16
+ try {
17
+ const value = await response.json();
18
+ if (typeof value.reason === 'string')
19
+ return value.reason;
20
+ }
21
+ catch { /* fall through */ }
22
+ return `status ${response.status}`;
23
+ }
24
+ /**
25
+ * Connect a browser to one remote native conversation. Resolves after the first view so the conversation identity is the
26
+ * server's. A dropped or silent stream is reopened automatically with backoff (see `reconnect` and `link`); it never means the
27
+ * native task stopped.
28
+ */
29
+ export async function createRemoteChat(options) {
30
+ const base = new URL(options.endpoint, globalThis.location?.href);
31
+ const send = options.fetch, pollMs = options.pollMs ?? 500;
32
+ const backoff = { base: options.reconnect?.baseDelayMs ?? 1000, max: options.reconnect?.maxDelayMs ?? 30_000 };
33
+ const url = (op, extra = {}) => {
34
+ const target = new URL(base);
35
+ target.searchParams.set('op', op);
36
+ for (const [key, value] of Object.entries(extra))
37
+ target.searchParams.set(key, value);
38
+ return target;
39
+ };
40
+ async function call(op, init = {}, extra = {}) {
41
+ const response = await send(new Request(url(op, extra), init));
42
+ if (response.status === 402 && op === 'submit') {
43
+ const refused = await response.json().catch(() => ({}));
44
+ if (refused.reason === 'submission-refused')
45
+ throw new SubmissionRefused(typeof refused.message === 'string' && refused.message ? refused.message : 'The host refused this message');
46
+ }
47
+ if (!response.ok)
48
+ throw new Error(`Remote chat ${op} failed: ${await reason(response)}`);
49
+ return await response.json();
50
+ }
51
+ const post = (op, value) => call(op, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(value) });
52
+ const lookup = async (requestId) => (await call('submission', {}, { requestId })).record ?? undefined;
53
+ async function watch() {
54
+ const stopped = new AbortController();
55
+ const response = await send(new Request(url('watch'), { signal: stopped.signal }));
56
+ if (!response.ok || !response.body)
57
+ throw new WatchRefused(`Remote chat watch failed: ${await reason(response)}`, response.status);
58
+ const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
59
+ let pending = '', staleMs = 0, lost = false;
60
+ // Once the server has announced its heartbeat interval, a read waiting 2.5 intervals means the stream is dead even if the
61
+ // socket still looks open (a proxy that dropped it silently).
62
+ async function read() {
63
+ if (!staleMs)
64
+ return reader.read();
65
+ let timer;
66
+ try {
67
+ return await Promise.race([reader.read(), new Promise((_, reject) => {
68
+ timer = setTimeout(() => { lost = true; stopped.abort(); reject(new Error('Remote chat stream went silent')); }, staleMs);
69
+ })]);
70
+ }
71
+ finally {
72
+ clearTimeout(timer);
73
+ }
74
+ }
75
+ async function next() {
76
+ for (;;) {
77
+ const end = pending.indexOf('\n');
78
+ if (end >= 0) {
79
+ const line = pending.slice(0, end);
80
+ pending = pending.slice(end + 1);
81
+ const frame = JSON.parse(line);
82
+ if (frame.kind === 'heartbeat') {
83
+ if (typeof frame.intervalMs === 'number' && frame.intervalMs > 0)
84
+ staleMs = frame.intervalMs * 2.5;
85
+ continue;
86
+ }
87
+ return frame;
88
+ }
89
+ const chunk = await read();
90
+ if (chunk.done)
91
+ return undefined;
92
+ pending += chunk.value;
93
+ }
94
+ }
95
+ const first = await next().catch(() => undefined);
96
+ if (first?.kind !== 'view') {
97
+ stopped.abort();
98
+ throw new Error('Remote chat watch ended before its first view');
99
+ }
100
+ let value = first.view, started = false, settle, ended;
101
+ const closed = new Promise(resolve => { settle = resolve; });
102
+ const finish = (end) => { if (!ended) {
103
+ ended = end;
104
+ stopped.abort();
105
+ settle(end);
106
+ } };
107
+ const handle = {
108
+ get value() { return value; },
109
+ closed,
110
+ lost: () => lost,
111
+ stop: async () => { finish({ reason: 'stopped' }); return closed; },
112
+ start: listener => {
113
+ if (started)
114
+ throw new Error('Watch already started');
115
+ started = true;
116
+ void (async () => {
117
+ try {
118
+ for (;;) {
119
+ const frame = await next();
120
+ if (ended)
121
+ return;
122
+ // The stream ending without an end frame is a lost connection, not a native terminal state. A server-sent
123
+ // `session_closed` (a host restart, for example) is reopened too; `retired`, `stopped` and `revoked` are final.
124
+ if (!frame || frame.kind === 'end') {
125
+ lost = !frame || frame.reason === 'session_closed';
126
+ return finish({ reason: frame?.reason === 'retired' ? 'retired' : frame?.reason === 'stopped' ? 'stopped' : 'session_closed' });
127
+ }
128
+ // Native operation batches are replayed with the upstream delta implementation; a view frame resets the replica.
129
+ value = frame.kind === 'view' ? frame.view : applyImmutable(value, frame.ops);
130
+ try {
131
+ await listener(value, [], context);
132
+ }
133
+ catch (error) {
134
+ return finish({ reason: 'listener_error', error: error instanceof Error ? error : new Error(String(error)) });
135
+ }
136
+ }
137
+ }
138
+ catch (error) {
139
+ if (ended)
140
+ return;
141
+ // A failed read (network error, silence) is a lost connection; a line the client cannot parse is not.
142
+ if (error instanceof SyntaxError)
143
+ return finish({ reason: 'listener_error', error });
144
+ lost = true;
145
+ finish({ reason: 'session_closed' });
146
+ }
147
+ })();
148
+ },
149
+ };
150
+ return handle;
151
+ }
152
+ const page = options.pauseWhenHidden === false ? undefined : globalThis.document;
153
+ const hidden = () => page?.visibilityState === 'hidden';
154
+ const linkListeners = new Set();
155
+ let reconnecting = false;
156
+ const setReconnecting = (next) => {
157
+ if (reconnecting === next)
158
+ return;
159
+ reconnecting = next;
160
+ for (const listener of [...linkListeners]) {
161
+ try {
162
+ listener();
163
+ }
164
+ catch { /* a viewer's listener never breaks the link */ }
165
+ }
166
+ };
167
+ const link = { reconnecting: () => reconnecting, subscribe: listener => { linkListeners.add(listener); return () => { linkListeners.delete(listener); }; } };
168
+ /**
169
+ * Wrap a watch so its stream can be lost and reopened, and (with `pauseWhenHidden`) released while the page is hidden. The
170
+ * wrapper stays open for the controller: every reopened stream begins with a complete view, which replaces the replica, so
171
+ * nothing is lost or duplicated. While a lost stream is being reopened (capped exponential backoff with jitter) `link` reports
172
+ * reconnecting. The wrapper ends only when stopped, when the server ends the stream for good, or when it refuses to reopen it.
173
+ */
174
+ function supervise(first) {
175
+ let current, last = first.value, listener;
176
+ let ended, settle, opening = false, attempt = 0, retry;
177
+ const closed = new Promise(resolve => { settle = resolve; });
178
+ const wanted = () => listener !== undefined && !ended && !hidden();
179
+ const cancelRetry = () => { clearTimeout(retry); retry = undefined; };
180
+ const finish = (end) => {
181
+ if (ended)
182
+ return;
183
+ ended = end;
184
+ page?.removeEventListener('visibilitychange', onVisibility);
185
+ cancelRetry();
186
+ setReconnecting(false);
187
+ settle(end);
188
+ const live = current;
189
+ current = undefined;
190
+ void live?.stop();
191
+ };
192
+ const fail = (error) => finish({ reason: 'listener_error', error: error instanceof Error ? error : new Error(String(error)) });
193
+ const forward = async (view) => { last = view; await listener(view, [], context); };
194
+ const schedule = () => {
195
+ cancelRetry();
196
+ const delay = Math.min(backoff.max, backoff.base * 2 ** attempt++) * (0.5 + Math.random() / 2);
197
+ retry = setTimeout(() => { retry = undefined; void open(); }, delay);
198
+ };
199
+ function attach(watch) {
200
+ current = watch;
201
+ void watch.closed.then(end => {
202
+ if (current !== watch)
203
+ return;
204
+ current = undefined;
205
+ last = watch.value;
206
+ if (ended)
207
+ return;
208
+ if (watch.lost()) {
209
+ setReconnecting(true);
210
+ schedule();
211
+ }
212
+ else
213
+ finish(end);
214
+ });
215
+ watch.start(async (view) => { if (current === watch)
216
+ await forward(view); });
217
+ }
218
+ async function open() {
219
+ if (opening || current || !wanted())
220
+ return;
221
+ opening = true;
222
+ try {
223
+ const reopened = await watch();
224
+ if (!wanted() || current) {
225
+ await reopened.stop();
226
+ return;
227
+ }
228
+ try {
229
+ await forward(reopened.value);
230
+ }
231
+ catch (error) {
232
+ await reopened.stop();
233
+ return fail(error);
234
+ }
235
+ if (!wanted() || current) {
236
+ await reopened.stop();
237
+ return;
238
+ }
239
+ attempt = 0;
240
+ setReconnecting(false);
241
+ attach(reopened);
242
+ }
243
+ catch (error) {
244
+ if (ended)
245
+ return;
246
+ // The host refused (signed out, revoked, gone): reopening cannot help. A network failure or a 5xx is retried.
247
+ const status = error instanceof WatchRefused ? error.status : 0;
248
+ if (status >= 400 && status < 500 && status !== 408 && status !== 429)
249
+ return finish({ reason: 'session_closed' });
250
+ setReconnecting(true);
251
+ schedule();
252
+ }
253
+ finally {
254
+ opening = false;
255
+ }
256
+ }
257
+ async function pause() {
258
+ cancelRetry();
259
+ const live = current;
260
+ if (!live)
261
+ return;
262
+ last = live.value;
263
+ current = undefined;
264
+ await live.stop();
265
+ }
266
+ function onVisibility() { if (hidden())
267
+ void pause();
268
+ else {
269
+ cancelRetry();
270
+ void open();
271
+ } }
272
+ page?.addEventListener('visibilitychange', onVisibility);
273
+ return {
274
+ get value() { return current?.value ?? last; },
275
+ closed,
276
+ stop: async () => { finish({ reason: 'stopped' }); return closed; },
277
+ start: next => {
278
+ if (listener)
279
+ throw new Error('Watch already started');
280
+ listener = next;
281
+ if (hidden())
282
+ void first.stop();
283
+ else
284
+ attach(first);
285
+ },
286
+ };
287
+ }
288
+ // The first watch tells us the conversation identity. The controller's first `watch()` takes it over instead of opening a second one.
289
+ let unclaimed = await watch();
290
+ const id = unclaimed.value.conversation.id;
291
+ const requests = new Map();
292
+ const withdraw = async (submissionId, requestId = requests.get(submissionId)) => (await post('withdraw', { submissionId, ...(requestId === undefined ? {} : { requestId }) })).result;
293
+ const conversation = {
294
+ id,
295
+ watch: async () => { const first = unclaimed; unclaimed = undefined; return supervise(first ?? await watch()); },
296
+ submit: async (draft) => {
297
+ if (draft.type !== 'input' || !draft.requestId)
298
+ throw new Error('Remote chat submits input drafts with a request ID');
299
+ const requestId = draft.requestId;
300
+ const { submissionId } = await post('submit', { requestId, content: draft.content, whenBusy: draft.whenBusy });
301
+ requests.set(submissionId, requestId);
302
+ const status = async () => { const record = await lookup(requestId); if (!record)
303
+ throw new Error('Submission is unknown to the remote host'); return record; };
304
+ return {
305
+ id: submissionId, status,
306
+ wait: async () => { for (;;) {
307
+ const record = await status();
308
+ if (record.status === 'done' || record.status === 'unanswered')
309
+ return record;
310
+ await new Promise(resolve => setTimeout(resolve, pollMs));
311
+ } },
312
+ abort: () => withdraw(submissionId, requestId),
313
+ };
314
+ },
315
+ // The controller only reconciles a submission by request ID; no other transaction work crosses the wire.
316
+ commit: async (change, _context) => change({ submissionByRequest: (_conversationId, requestId) => lookup(requestId) }),
317
+ abort: async () => { await post('abort', {}); },
318
+ entries: async (_query, limit, cursor) => (await call('entries', {}, { limit: String(limit), ...(cursor === undefined ? {} : { cursor: JSON.stringify(cursor) }) })).page,
319
+ };
320
+ const context = Object.freeze({});
321
+ const close = async () => { const first = unclaimed; unclaimed = undefined; await first?.stop(); };
322
+ return { conversation, context, close, link, withdraw: id => withdraw(id), definitelyNotAdmitted: error => error instanceof SubmissionRefused,
323
+ answer: (callId, answer) => post('answer', { callId, answer }), configure: change => post('configure', change) };
324
+ }
@@ -0,0 +1,34 @@
1
+ /** Optional document adapter contracts. Importing the headless UI root does
2
+ * not require this file or @hachej/boring-files. No native Pi type dependency here.
3
+ */
4
+ import type { ResourceRead, ResourceExpectation, ResourceRef, PublicationReceipt, PublicationResult } from '@hachej/boring-files';
5
+ import type { ViewerController, ViewerDescriptor, ViewerTarget } from './contracts.js';
6
+ export interface ResourceViewerDescriptor extends ViewerDescriptor {
7
+ readonly source: ResourceRead;
8
+ }
9
+ /** An acknowledged selection includes editor incarnation and buffer version.
10
+ * An absent target represents create-if-absent: no fabricated saved revision.
11
+ */
12
+ export interface SaveSelection {
13
+ readonly target: ViewerTarget<{
14
+ readonly scopeId: string;
15
+ readonly base: ResourceExpectation;
16
+ readonly bufferVersion: number;
17
+ }>;
18
+ }
19
+ export type SaveResult = {
20
+ readonly kind: 'saved';
21
+ readonly selection: SaveSelection;
22
+ /** Actual resulting revision, also present in the checked receipt. */
23
+ readonly ref: ResourceRef;
24
+ readonly receipt: PublicationReceipt;
25
+ } | Exclude<PublicationResult, {
26
+ readonly kind: 'committed';
27
+ }>;
28
+ export interface EditableViewerController<State, Actions, Tools = unknown> extends ViewerController<State, Actions, Tools> {
29
+ /** Acknowledge only this selection or fail; prior/partial/unknown effects
30
+ * remain explicit. Merely loading or rendering a document never saves it.
31
+ */
32
+ readonly flush: (selection: SaveSelection, signal?: AbortSignal) => Promise<SaveResult>;
33
+ }
34
+ //# sourceMappingURL=resources.d.ts.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import type { ResourceAccess, ResourceExpectation } from '@hachej/boring-files';
2
+ import type { SaveResult } from './resources.js';
3
+ import type { TextDraftRecoveryState } from './text-draft-types.js';
4
+ export interface TextBufferState {
5
+ readonly identity: Pick<ResourceAccess, 'scopeId' | 'principalId' | 'initiatorId'>;
6
+ readonly text: string;
7
+ readonly base: ResourceExpectation;
8
+ readonly readOnly: boolean;
9
+ readonly bufferVersion: number;
10
+ readonly dirty: boolean;
11
+ readonly lifecycle: 'active' | 'disposed';
12
+ readonly save: {
13
+ readonly kind: 'idle';
14
+ } | {
15
+ readonly kind: 'pending';
16
+ readonly operationId: string;
17
+ } | {
18
+ readonly kind: 'settled';
19
+ readonly result: SaveResult;
20
+ };
21
+ readonly remote: ResourceExpectation | null;
22
+ readonly recovery: TextDraftRecoveryState;
23
+ }
24
+ //# sourceMappingURL=text-buffer-state.d.ts.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,52 @@
1
+ import type { ReadResult, ResourceClient, ResourceExpectation, ResourceLocator, ResourceSnapshot } from '@hachej/boring-files';
2
+ import type { SaveResult, SaveSelection } from './resources.js';
3
+ import type { TextDraftBinding } from './text-draft-types.js';
4
+ import type { TextBufferState } from './text-buffer-state.js';
5
+ export type { TextBufferState } from './text-buffer-state.js';
6
+ export type TextBufferSource = {
7
+ readonly kind: 'saved';
8
+ readonly snapshot: ResourceSnapshot;
9
+ } | {
10
+ readonly kind: 'new';
11
+ readonly target: ResourceLocator;
12
+ readonly text?: string;
13
+ };
14
+ export interface TextBufferOptions {
15
+ readonly identity: TextBufferState['identity'];
16
+ readonly instanceId: string;
17
+ readonly epoch: string;
18
+ readonly source: TextBufferSource;
19
+ readonly client: ResourceClient;
20
+ readonly readOnly?: boolean;
21
+ readonly onListenerError?: (error: unknown) => void;
22
+ readonly mediaType: string;
23
+ readonly emptyText?: string;
24
+ readonly drafts?: TextDraftBinding;
25
+ readonly replaceText?: (text: string) => void;
26
+ readonly sync?: () => void;
27
+ readonly readText: (snapshot: ResourceSnapshot) => string;
28
+ }
29
+ export declare function freeze<Value>(value: Value): Value;
30
+ export declare function sameBase(left: ResourceExpectation, right: ResourceExpectation): boolean;
31
+ export declare function createTextBuffer(options: TextBufferOptions): {
32
+ getSnapshot: () => TextBufferState;
33
+ subscribe: (listener: () => void) => (() => void);
34
+ edit: (text: string, force?: boolean) => void;
35
+ observe: (text: string, force?: boolean) => void;
36
+ selection: () => SaveSelection;
37
+ refresh: (discard: boolean) => Promise<ReadResult>;
38
+ abandon: () => Promise<ReadResult>;
39
+ reconcile: () => Promise<SaveResult>;
40
+ flush: (value: SaveSelection, signal?: AbortSignal) => Promise<SaveResult>;
41
+ dispose: () => void;
42
+ checkDrafts: () => Promise<import("./text-draft-types.js").TextDraftDiscovery>;
43
+ checkpointDraft: () => Promise<import("./text-draft-types.js").TextDraftCheckpointResult>;
44
+ restoreDraft: (choice: import("./text-draft-types.js").TextDraftChoiceSelection) => Promise<{
45
+ readonly kind: "restored";
46
+ readonly selection: SaveSelection;
47
+ } | import("./text-draft-types.js").TextDraftChoiceFailure>;
48
+ discardDraft: (choice: import("./text-draft-types.js").TextDraftChoiceSelection) => Promise<{
49
+ readonly kind: "discarded";
50
+ } | import("./text-draft-types.js").TextDraftChoiceFailure>;
51
+ };
52
+ //# sourceMappingURL=text-buffer.d.ts.map