@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
package/dist/canvas.js ADDED
@@ -0,0 +1,264 @@
1
+ import { applyCanvasEdits, canvasMediaType, parseCanvasDocument, parseCanvasEdits } from './canvas-document.js';
2
+ import { parseCanvasRecords } from './canvas-records.js';
3
+ import { loadSnapshot } from '@tldraw/editor';
4
+ import { Store } from '@tldraw/store';
5
+ import { randomUUID } from '@hachej/boring-files/platform';
6
+ import { createTextBuffer, freeze, sameBase } from './text-buffer.js';
7
+ export { canvasMediaType } from './canvas-document.js';
8
+ function documentText(document) {
9
+ return JSON.stringify({ schema: document.schema, store: document.store });
10
+ }
11
+ function documentFrom(value, owner) {
12
+ const records = parseCanvasRecords(value, owner.schema);
13
+ const scratch = new Store({ schema: owner.schema, props: owner.props });
14
+ try {
15
+ scratch.loadStoreSnapshot(records);
16
+ return parseCanvasDocument(scratch.getStoreSnapshot('document'), owner.schema);
17
+ }
18
+ finally {
19
+ scratch.dispose();
20
+ }
21
+ }
22
+ /** Revision-checked document publication over a borrowed native tldraw store. */
23
+ export function createCanvasController(options) {
24
+ const { store } = options;
25
+ let loading = false, disposed = false, adopting = false;
26
+ let proposals = [];
27
+ let problem = null;
28
+ const readText = (snapshot) => {
29
+ if (snapshot.mediaType !== canvasMediaType)
30
+ throw new TypeError('Expected a tldraw document media type');
31
+ const input = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(snapshot.bytes));
32
+ return documentText(documentFrom(parseCanvasDocument(input, store.schema), store));
33
+ };
34
+ const emptyText = documentText(documentFrom({ schema: store.schema.serialize(), store: {} }, store));
35
+ const initialText = options.source.kind === 'saved' ? readText(options.source.snapshot)
36
+ : documentText(documentFrom(store.getStoreSnapshot('document'), store));
37
+ const existing = store.getStoreSnapshot('document');
38
+ if (options.source.kind === 'saved' && Object.keys(existing.store).length > 0
39
+ && documentText(documentFrom(existing, store)) !== initialText)
40
+ throw new Error('Borrowed canvas store differs from the saved source; select an empty store or explicitly discard before mounting');
41
+ let captured = initialText;
42
+ const replaceText = (text) => {
43
+ const input = JSON.parse(text);
44
+ const document = documentFrom(input, store);
45
+ loading = true;
46
+ try {
47
+ loadSnapshot(store, { document });
48
+ captured = documentText(document);
49
+ problem = null;
50
+ }
51
+ finally {
52
+ loading = false;
53
+ }
54
+ };
55
+ const { drafts, ...bufferOptions } = options;
56
+ const buffer = createTextBuffer({ ...bufferOptions, ...(drafts ? { drafts: { ...drafts, format: 'canvas/v1', validateText: (text) => { documentFrom(parseCanvasDocument(JSON.parse(text), store.schema), store); } } } : {}), source: options.source.kind === 'new' ? { ...options.source, text: initialText } : options.source,
57
+ mediaType: canvasMediaType, emptyText, readText, replaceText, sync: () => capture() });
58
+ replaceText(initialText);
59
+ function snapshot() {
60
+ const { text: _text, ...state } = buffer.getSnapshot();
61
+ return freeze({ ...state, document: structuredClone(store.getStoreSnapshot('document')), problem, proposals });
62
+ }
63
+ let state = snapshot();
64
+ const listeners = new Set();
65
+ const notify = () => {
66
+ state = snapshot();
67
+ for (const listener of [...listeners]) {
68
+ try {
69
+ listener();
70
+ }
71
+ catch (error) {
72
+ queueMicrotask(() => { if (options.onListenerError)
73
+ options.onListenerError(error);
74
+ else
75
+ throw error; });
76
+ }
77
+ }
78
+ };
79
+ const unsubscribe = buffer.subscribe(notify);
80
+ const capture = () => {
81
+ if (disposed || loading)
82
+ return;
83
+ const document = store.getStoreSnapshot('document'), text = documentText(document);
84
+ if (text === captured)
85
+ return;
86
+ try {
87
+ parseCanvasDocument(document, store.schema);
88
+ problem = null;
89
+ }
90
+ catch (error) {
91
+ problem = error instanceof Error ? error.message : 'Canvas document is invalid';
92
+ }
93
+ captured = text;
94
+ buffer.observe(text);
95
+ };
96
+ const cleanup = Object.values(store.schema.types).filter(type => type.scope === 'document').flatMap(type => [
97
+ store.sideEffects.registerAfterCreateHandler(type.typeName, capture),
98
+ store.sideEffects.registerAfterChangeHandler(type.typeName, capture),
99
+ store.sideEffects.registerAfterDeleteHandler(type.typeName, capture),
100
+ ]);
101
+ cleanup.push(store.listen(capture, { scope: 'document', source: 'all' }));
102
+ const selected = (value) => {
103
+ const current = buffer.selection().target, supplied = value.target;
104
+ return current.instanceId === supplied.instanceId && current.epoch === supplied.epoch
105
+ && current.subject.scopeId === supplied.subject.scopeId && current.subject.bufferVersion === supplied.subject.bufferVersion
106
+ && sameBase(current.subject.base, supplied.subject.base);
107
+ };
108
+ const conflict = () => ({ kind: 'conflict', current: state.base.kind === 'revision' ? [state.base.target] : [], reason: 'Proposal base changed or the proposal was already adopted' });
109
+ function propose(base, input, summary = '', command) {
110
+ const expected = structuredClone(base);
111
+ capture();
112
+ if (disposed)
113
+ return { kind: 'unavailable', reason: 'Viewer is disposed' };
114
+ if (loading || adopting)
115
+ return { kind: 'unavailable', reason: 'Canvas document is being updated' };
116
+ if (state.readOnly)
117
+ return { kind: 'denied', reason: 'Viewer is read-only' };
118
+ if (problem)
119
+ return { kind: 'denied', reason: problem };
120
+ if (!selected(expected))
121
+ return { kind: 'stale', reason: 'Proposal target has changed' };
122
+ if (typeof summary !== 'string')
123
+ throw new TypeError('Proposal summary must be text');
124
+ const before = structuredClone(state.document), edits = parseCanvasEdits(input, store.schema);
125
+ const result = applyCanvasEdits(before, edits, store.schema);
126
+ if (result.kind === 'rejected')
127
+ return { kind: 'denied', reason: result.reason };
128
+ capture();
129
+ if (disposed)
130
+ return { kind: 'unavailable', reason: 'Viewer is disposed' };
131
+ if (command?.signal?.aborted)
132
+ return { kind: 'denied', reason: 'Command was cancelled' };
133
+ if (command && command.expiresAt <= Date.now())
134
+ return { kind: 'stale', reason: 'Command expired' };
135
+ if (!selected(expected))
136
+ return { kind: 'stale', reason: 'Proposal target changed during validation' };
137
+ const proposal = freeze({ id: randomUUID(), base: expected, before, after: result.document, edits, summary, adopted: false });
138
+ proposals = [...proposals, proposal];
139
+ notify();
140
+ return { kind: 'proposed', proposalId: proposal.id, base: proposal.base.target };
141
+ }
142
+ function accept(proposalId) {
143
+ capture();
144
+ if (disposed)
145
+ return Promise.resolve({ kind: 'unavailable', reason: 'Viewer is disposed' });
146
+ if (loading || adopting)
147
+ return Promise.resolve({ kind: 'unavailable', reason: 'Canvas document is being updated' });
148
+ if (state.readOnly)
149
+ return Promise.resolve({ kind: 'denied', reason: 'Viewer is read-only' });
150
+ if (problem)
151
+ return Promise.resolve({ kind: 'denied', reason: problem });
152
+ const pending = state.save.kind === 'pending' ? state.save.operationId
153
+ : state.save.kind === 'settled' && state.save.result.kind === 'unknown' ? state.save.result.operationId : undefined;
154
+ if (pending !== undefined)
155
+ return Promise.resolve({ kind: 'unknown', operationId: pending, reason: 'Reconcile or await the earlier save before accepting a proposal' });
156
+ const proposal = proposals.find(value => value.id === proposalId);
157
+ if (!proposal || proposal.adopted || !selected(proposal.base))
158
+ return Promise.resolve(conflict());
159
+ adopting = true;
160
+ try {
161
+ proposals = proposals.map(value => value.id === proposal.id ? freeze({ ...value, adopted: true }) : value);
162
+ loading = true;
163
+ try {
164
+ loadSnapshot(store, { document: structuredClone(proposal.after) });
165
+ }
166
+ finally {
167
+ loading = false;
168
+ }
169
+ capture();
170
+ if (state.proposals !== proposals)
171
+ notify();
172
+ capture();
173
+ if (disposed)
174
+ return Promise.resolve({ kind: 'unavailable', reason: 'Viewer is disposed' });
175
+ if (problem)
176
+ return Promise.resolve({ kind: 'denied', reason: problem });
177
+ if (documentText(store.getStoreSnapshot('document')) !== documentText(proposal.after))
178
+ return Promise.resolve(conflict());
179
+ return buffer.flush(buffer.selection());
180
+ }
181
+ finally {
182
+ adopting = false;
183
+ }
184
+ }
185
+ function commandInput(value) {
186
+ if (!value || typeof value !== 'object' || !('expiresAt' in value) || typeof value.expiresAt !== 'number' || !Number.isSafeInteger(value.expiresAt))
187
+ throw new TypeError('Command expiry is required');
188
+ return { expiresAt: value.expiresAt };
189
+ }
190
+ function proposalInput(value) {
191
+ const expiry = commandInput(value);
192
+ if (!value || typeof value !== 'object' || !('edits' in value) || !('summary' in value) || typeof value.summary !== 'string')
193
+ throw new TypeError('Expected edits and summary');
194
+ return { ...expiry, edits: parseCanvasEdits(value.edits, store.schema), summary: value.summary };
195
+ }
196
+ const tools = {
197
+ inspect: {
198
+ name: 'inspect_canvas_buffer', input: { jsonSchema: { type: 'object', properties: { expiresAt: { type: 'integer' } }, required: ['expiresAt'] }, parse: commandInput },
199
+ invoke: async (target, input, signal) => {
200
+ const base = structuredClone({ target }), expiry = commandInput(input);
201
+ capture();
202
+ if (disposed || loading)
203
+ return { kind: 'unavailable', reason: 'Canvas document is unavailable' };
204
+ if (signal?.aborted)
205
+ return { kind: 'denied', reason: 'Command was cancelled' };
206
+ if (expiry.expiresAt <= Date.now() || !selected(base))
207
+ return { kind: 'stale', reason: 'Command expired or its target changed' };
208
+ if (problem)
209
+ return { kind: 'unavailable', reason: problem };
210
+ return { kind: 'applied', value: freeze({ selection: buffer.selection(), document: structuredClone(state.document), dirty: state.dirty }) };
211
+ },
212
+ },
213
+ propose: {
214
+ name: 'propose_canvas_edits', input: {
215
+ jsonSchema: { type: 'object', properties: { expiresAt: { type: 'integer' }, summary: { type: 'string' }, edits: { type: 'array', minItems: 1, items: { oneOf: [
216
+ { type: 'object', properties: { kind: { enum: ['create', 'update'] }, record: { type: 'object', description: 'Complete native TLShape or TLBinding record validated by the selected schema' } }, required: ['kind', 'record'], additionalProperties: false },
217
+ { type: 'object', properties: { kind: { const: 'remove' }, id: { type: 'string' } }, required: ['kind', 'id'], additionalProperties: false },
218
+ ] } } }, required: ['expiresAt', 'summary', 'edits'] }, parse: proposalInput,
219
+ },
220
+ invoke: async (target, input, signal) => {
221
+ const base = structuredClone({ target }), parsed = proposalInput(input);
222
+ if (signal?.aborted)
223
+ return { kind: 'denied', reason: 'Command was cancelled' };
224
+ if (parsed.expiresAt <= Date.now())
225
+ return { kind: 'stale', reason: 'Command expired' };
226
+ return propose(base, parsed.edits, parsed.summary, { expiresAt: parsed.expiresAt, ...(signal ? { signal } : {}) });
227
+ },
228
+ },
229
+ };
230
+ return {
231
+ store,
232
+ getSnapshot: () => state,
233
+ subscribe: (listener) => { if (disposed)
234
+ return () => { }; listeners.add(listener); return () => { listeners.delete(listener); }; },
235
+ actions: {
236
+ selection: () => { capture(); if (problem)
237
+ throw new Error(problem); return buffer.selection(); },
238
+ refresh: () => { capture(); return buffer.refresh(false); },
239
+ discardToRemote: () => { capture(); return buffer.refresh(true); },
240
+ reconcile: buffer.reconcile, abandon: buffer.abandon,
241
+ checkDrafts: buffer.checkDrafts, checkpointDraft: buffer.checkpointDraft, restoreDraft: buffer.restoreDraft, discardDraft: buffer.discardDraft,
242
+ propose: (base, edits, summary) => propose(base, edits, summary), accept,
243
+ reject: (proposalId) => { if (!disposed) {
244
+ proposals = proposals.filter(value => value.id !== proposalId);
245
+ notify();
246
+ } },
247
+ },
248
+ tools: Object.freeze(tools),
249
+ flush: (selection, signal) => {
250
+ capture();
251
+ return problem ? Promise.resolve({ kind: 'denied', reason: problem }) : buffer.flush(selection, signal);
252
+ },
253
+ dispose: () => {
254
+ if (disposed)
255
+ return;
256
+ disposed = true;
257
+ for (const remove of cleanup)
258
+ remove();
259
+ buffer.dispose();
260
+ unsubscribe();
261
+ listeners.clear();
262
+ },
263
+ };
264
+ }
@@ -0,0 +1,125 @@
1
+ /** Headless presentation contracts; no Pi, files, agent or React imports.
2
+ * Services are closed over by the host. An interface does not install code or
3
+ * confer authority, and a feature preserves its concrete controller type.
4
+ */
5
+ export interface ValueSchema<Value> {
6
+ readonly jsonSchema: Readonly<Record<string, unknown>>;
7
+ readonly parse: (value: unknown) => Value;
8
+ }
9
+ export interface ViewerDescriptor {
10
+ readonly kind: string;
11
+ readonly version: number;
12
+ readonly title?: string;
13
+ }
14
+ export interface ViewerController<State, Actions, Tools = unknown> {
15
+ /** Pure cached snapshot. Without a change, return the same value/object.
16
+ * Do not mutate an already published snapshot or perform I/O while reading.
17
+ */
18
+ readonly getSnapshot: () => State;
19
+ /** Synchronous subscription and unsubscription. Change notification occurs
20
+ * after snapshot publication. This callback is not an async execution queue.
21
+ */
22
+ readonly subscribe: (listener: () => void) => () => void;
23
+ readonly actions: Actions;
24
+ readonly tools: Tools;
25
+ /** Explicit owner teardown can be asynchronous. Await/handle its result.
26
+ * Releasing this controller must not dispose a borrowed provider or task.
27
+ * React unsubscription stays synchronous; owner teardown is a separate step.
28
+ */
29
+ readonly dispose: () => void | Promise<void>;
30
+ }
31
+ /** Preserve the whole concrete controller, including flush, custom methods,
32
+ * server-rendering snapshots and other host extensions. Do not reconstruct a
33
+ * base controller from State/Actions/Tools and silently erase its capabilities.
34
+ */
35
+ export interface ViewerFeature<Descriptor extends ViewerDescriptor, Controller extends ViewerController<unknown, unknown, unknown>> {
36
+ readonly kind: Descriptor['kind'];
37
+ readonly version: Descriptor['version'];
38
+ readonly descriptor: ValueSchema<Descriptor>;
39
+ readonly createController: (descriptor: Descriptor) => Controller;
40
+ }
41
+ export type ViewerRenderer<Controller, Rendered> = (controller: Controller) => Rendered;
42
+ export interface ViewerTarget<Subject = unknown> {
43
+ readonly instanceId: string;
44
+ readonly epoch: string;
45
+ readonly subject: Subject;
46
+ }
47
+ export type PresentationResult<Output, Subject = unknown> = {
48
+ readonly kind: 'applied';
49
+ readonly value: Output;
50
+ } | {
51
+ readonly kind: 'proposed';
52
+ readonly proposalId: string;
53
+ readonly base: ViewerTarget<Subject>;
54
+ } | {
55
+ readonly kind: 'stale' | 'conflict' | 'denied' | 'unavailable';
56
+ readonly reason: string;
57
+ } | {
58
+ readonly kind: 'unknown';
59
+ readonly reason: string;
60
+ };
61
+ /** Function properties retain strict input/target variance. Native tool
62
+ * adapters bind this metadata through Pi; this is not another tool engine.
63
+ */
64
+ export interface PresentationCommand<Input, Output, Subject = unknown> {
65
+ readonly name: string;
66
+ readonly input: ValueSchema<Input>;
67
+ readonly invoke: (target: ViewerTarget<Subject>, input: Input, signal?: AbortSignal) => Promise<PresentationResult<Output, Subject>>;
68
+ }
69
+ /** Anchors: data a feature keeps to point into a viewer's subject (FEEDBACK.md).
70
+ * `kind` is `<viewer>.<type>@<version>`; `fallback` is a human-readable
71
+ * description that never needs the viewer. Type-only: a viewer implements the
72
+ * two halves below without importing any feature that stores anchors.
73
+ */
74
+ export interface Anchor {
75
+ readonly kind: `${string}.${string}@${number}`;
76
+ readonly fallback: string;
77
+ }
78
+ /** Where an anchor lands in one explicit snapshot. `evaluated` names that
79
+ * snapshot. Only `exact` and `moved` are unique matches; `ambiguous` needs a
80
+ * person's choice even with one candidate.
81
+ */
82
+ export type Placement<Range> = {
83
+ readonly kind: 'exact' | 'moved';
84
+ readonly range: Range;
85
+ readonly evaluated: string;
86
+ } | {
87
+ readonly kind: 'ambiguous';
88
+ readonly candidates: readonly Range[];
89
+ readonly evaluated: string;
90
+ } | {
91
+ readonly kind: 'partial';
92
+ readonly range: Range;
93
+ readonly missing: readonly string[];
94
+ readonly evaluated: string;
95
+ } | {
96
+ readonly kind: 'missing' | 'unsupported';
97
+ readonly evaluated: string;
98
+ };
99
+ /** Pure half: no DOM, no I/O, a function of the anchor and one snapshot. */
100
+ export interface AnchorResolution<A extends Anchor, Snapshot, Range> {
101
+ readonly kind: A['kind'];
102
+ readonly schema: ValueSchema<A>;
103
+ readonly resolve: (anchor: A, snapshot: Snapshot, evaluated: string) => Placement<Range>;
104
+ readonly fallback: (anchor: A) => string;
105
+ }
106
+ /** Mounted half: browser only, bound to a live viewer. */
107
+ export interface AnchorCapture<A extends Anchor, Selection, Subject = unknown> {
108
+ readonly anchorOf: (selection: Selection) => {
109
+ readonly kind: 'captured';
110
+ readonly anchor: A;
111
+ } | {
112
+ readonly kind: 'refused';
113
+ readonly reason: string;
114
+ };
115
+ readonly reveal: PresentationCommand<{
116
+ readonly anchor: A;
117
+ readonly range: unknown;
118
+ }, void, Subject>;
119
+ }
120
+ /** Declared beside a viewer feature; no registry and no required member. */
121
+ export interface ViewerAnchors<A extends Anchor, Snapshot, Range, Selection, Subject = unknown> {
122
+ readonly resolution: AnchorResolution<A, Snapshot, Range>;
123
+ readonly capture?: AnchorCapture<A, Selection, Subject>;
124
+ }
125
+ //# sourceMappingURL=contracts.d.ts.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,142 @@
1
+ import { z } from 'zod';
2
+ export declare const experienceIdentifier: z.ZodString;
3
+ export declare const layoutProps: {
4
+ 'boring/stack': z.ZodObject<{
5
+ gap: z.ZodDefault<z.ZodEnum<{
6
+ small: "small";
7
+ medium: "medium";
8
+ large: "large";
9
+ }>>;
10
+ }, z.core.$strict>;
11
+ 'boring/row': z.ZodObject<{
12
+ gap: z.ZodDefault<z.ZodEnum<{
13
+ small: "small";
14
+ medium: "medium";
15
+ large: "large";
16
+ }>>;
17
+ }, z.core.$strict>;
18
+ 'boring/grid': z.ZodObject<{
19
+ columns: z.ZodNumber;
20
+ gap: z.ZodDefault<z.ZodEnum<{
21
+ small: "small";
22
+ medium: "medium";
23
+ large: "large";
24
+ }>>;
25
+ }, z.core.$strict>;
26
+ 'boring/cell': z.ZodObject<{
27
+ ref: z.ZodString;
28
+ }, z.core.$strict>;
29
+ 'boring/generated': z.ZodObject<{
30
+ region: z.ZodString;
31
+ candidates: z.ZodArray<z.ZodString>;
32
+ kinds: z.ZodOptional<z.ZodArray<z.ZodString>>;
33
+ maxElements: z.ZodDefault<z.ZodNumber>;
34
+ minWidth: z.ZodDefault<z.ZodNumber>;
35
+ regenerate: z.ZodDefault<z.ZodArray<z.ZodEnum<{
36
+ open: "open";
37
+ phase: "phase";
38
+ request: "request";
39
+ }>>>;
40
+ prompt: z.ZodOptional<z.ZodString>;
41
+ }, z.core.$strict>;
42
+ };
43
+ export declare const experienceCatalog: import("@json-render/core").Catalog<{
44
+ spec: import("@json-render/core").SchemaType<"object", {
45
+ root: import("@json-render/core").SchemaType<"string", unknown>;
46
+ elements: import("@json-render/core").SchemaType<"record", import("@json-render/core").SchemaType<"object", {
47
+ type: import("@json-render/core").SchemaType<"ref", string>;
48
+ props: import("@json-render/core").SchemaType<"propsOf", string>;
49
+ children: import("@json-render/core").SchemaType<"array", import("@json-render/core").SchemaType<"string", unknown>>;
50
+ slots: {
51
+ optional: true;
52
+ kind: "record";
53
+ inner?: import("@json-render/core").SchemaType<"array", import("@json-render/core").SchemaType<"string", unknown>> | undefined;
54
+ };
55
+ visible: {
56
+ optional: true;
57
+ kind: "any";
58
+ inner?: unknown;
59
+ };
60
+ repeat: {
61
+ optional: true;
62
+ kind: "any";
63
+ inner?: unknown;
64
+ };
65
+ }>>;
66
+ }>;
67
+ catalog: import("@json-render/core").SchemaType<"object", {
68
+ components: import("@json-render/core").SchemaType<"map", {
69
+ props: import("@json-render/core").SchemaType<"zod", unknown>;
70
+ slots: import("@json-render/core").SchemaType<"array", import("@json-render/core").SchemaType<"string", unknown>>;
71
+ description: import("@json-render/core").SchemaType<"string", unknown>;
72
+ example: import("@json-render/core").SchemaType<"any", unknown>;
73
+ }>;
74
+ actions: import("@json-render/core").SchemaType<"map", {
75
+ params: import("@json-render/core").SchemaType<"zod", unknown>;
76
+ description: import("@json-render/core").SchemaType<"string", unknown>;
77
+ }>;
78
+ }>;
79
+ }, {
80
+ components: {
81
+ 'boring/stack': {
82
+ props: z.ZodObject<{
83
+ gap: z.ZodDefault<z.ZodEnum<{
84
+ small: "small";
85
+ medium: "medium";
86
+ large: "large";
87
+ }>>;
88
+ }, z.core.$strict>;
89
+ slots: string[];
90
+ description: string;
91
+ };
92
+ 'boring/row': {
93
+ props: z.ZodObject<{
94
+ gap: z.ZodDefault<z.ZodEnum<{
95
+ small: "small";
96
+ medium: "medium";
97
+ large: "large";
98
+ }>>;
99
+ }, z.core.$strict>;
100
+ slots: string[];
101
+ description: string;
102
+ };
103
+ 'boring/grid': {
104
+ props: z.ZodObject<{
105
+ columns: z.ZodNumber;
106
+ gap: z.ZodDefault<z.ZodEnum<{
107
+ small: "small";
108
+ medium: "medium";
109
+ large: "large";
110
+ }>>;
111
+ }, z.core.$strict>;
112
+ slots: string[];
113
+ description: string;
114
+ };
115
+ 'boring/cell': {
116
+ props: z.ZodObject<{
117
+ ref: z.ZodString;
118
+ }, z.core.$strict>;
119
+ slots: never[];
120
+ description: string;
121
+ };
122
+ 'boring/generated': {
123
+ props: z.ZodObject<{
124
+ region: z.ZodString;
125
+ candidates: z.ZodArray<z.ZodString>;
126
+ kinds: z.ZodOptional<z.ZodArray<z.ZodString>>;
127
+ maxElements: z.ZodDefault<z.ZodNumber>;
128
+ minWidth: z.ZodDefault<z.ZodNumber>;
129
+ regenerate: z.ZodDefault<z.ZodArray<z.ZodEnum<{
130
+ open: "open";
131
+ phase: "phase";
132
+ request: "request";
133
+ }>>>;
134
+ prompt: z.ZodOptional<z.ZodString>;
135
+ }, z.core.$strict>;
136
+ slots: string[];
137
+ description: string;
138
+ };
139
+ };
140
+ actions: {};
141
+ }>;
142
+ //# sourceMappingURL=experience-catalog.d.ts.map
@@ -0,0 +1,25 @@
1
+ import { defineCatalog } from '@json-render/core';
2
+ import { schema } from '@json-render/react/schema';
3
+ import { z } from 'zod';
4
+ export const experienceIdentifier = z.string().regex(/^[a-zA-Z0-9_-]+$/).max(80).refine(value => !Object.hasOwn(Object.prototype, value), 'Reserved element identifier');
5
+ export const layoutProps = {
6
+ 'boring/stack': z.strictObject({ gap: z.enum(['small', 'medium', 'large']).default('medium') }),
7
+ 'boring/row': z.strictObject({ gap: z.enum(['small', 'medium', 'large']).default('medium') }),
8
+ 'boring/grid': z.strictObject({ columns: z.number().int().min(1).max(4), gap: z.enum(['small', 'medium', 'large']).default('medium') }),
9
+ 'boring/cell': z.strictObject({ ref: z.string().regex(/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+$/).max(160) }),
10
+ 'boring/generated': z.strictObject({ region: experienceIdentifier, candidates: z.array(z.string().min(1).max(160)).max(128),
11
+ kinds: z.array(z.string().min(1).max(160)).max(128).optional(), maxElements: z.number().int().min(1).max(200).default(64),
12
+ minWidth: z.number().int().min(0).max(4096).default(0), regenerate: z.array(z.enum(['open', 'phase', 'request'])).max(3).default(['request']),
13
+ prompt: z.string().min(1).max(1200).optional(),
14
+ }),
15
+ };
16
+ export const experienceCatalog = defineCatalog(schema, {
17
+ components: {
18
+ 'boring/stack': { props: layoutProps['boring/stack'], slots: ['default'], description: 'Vertical layout' },
19
+ 'boring/row': { props: layoutProps['boring/row'], slots: ['default'], description: 'Wrapping horizontal layout' },
20
+ 'boring/grid': { props: layoutProps['boring/grid'], slots: ['default'], description: 'Bounded column layout' },
21
+ 'boring/cell': { props: layoutProps['boring/cell'], slots: [], description: 'Host-owned cell reference' },
22
+ 'boring/generated': { props: layoutProps['boring/generated'], slots: ['default'], description: 'Bounded generated region with saved defaults' },
23
+ },
24
+ actions: {},
25
+ });
@@ -0,0 +1,83 @@
1
+ import { z } from 'zod';
2
+ import type { Experimental_CompositionEvaluator } from '@json-render/core';
3
+ declare const descriptor: z.ZodObject<{
4
+ format: z.ZodLiteral<"boring.experience">;
5
+ version: z.ZodLiteral<1>;
6
+ name: z.ZodString;
7
+ title: z.ZodOptional<z.ZodString>;
8
+ source: z.ZodEnum<{
9
+ fixed: "fixed";
10
+ derived: "derived";
11
+ generated: "generated";
12
+ }>;
13
+ kinds: z.ZodRecord<z.ZodString, z.ZodNumber>;
14
+ root: z.ZodString;
15
+ elements: z.ZodRecord<z.ZodString, z.ZodObject<{
16
+ type: z.ZodEnum<{
17
+ "boring/stack": "boring/stack";
18
+ "boring/row": "boring/row";
19
+ "boring/grid": "boring/grid";
20
+ "boring/cell": "boring/cell";
21
+ "boring/generated": "boring/generated";
22
+ }>;
23
+ props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
24
+ children: z.ZodDefault<z.ZodArray<z.ZodString>>;
25
+ }, z.core.$strict>>;
26
+ }, z.core.$strict>;
27
+ export type ExperienceDescriptor = z.infer<typeof descriptor>;
28
+ export interface ExperienceCell {
29
+ readonly ref: string;
30
+ readonly kind: string;
31
+ readonly version: number;
32
+ readonly maxUses?: number;
33
+ }
34
+ export interface ExperienceAccess {
35
+ readonly cells: readonly ExperienceCell[];
36
+ readonly canView: (ref: string) => boolean;
37
+ }
38
+ export declare function validateExperience(value: unknown, access: ExperienceAccess): ExperienceDescriptor;
39
+ export interface ExperienceCompositionKind {
40
+ readonly kind: string;
41
+ readonly description: string;
42
+ readonly metadata: Readonly<Record<string, readonly string[]>>;
43
+ }
44
+ export interface ExperienceCompositionDefinition {
45
+ readonly name: string;
46
+ readonly title?: string;
47
+ readonly intents: Readonly<Record<string, string>>;
48
+ readonly kinds: readonly ExperienceCompositionKind[];
49
+ }
50
+ export interface ExperienceCompositionCandidate {
51
+ readonly ref: string;
52
+ readonly metadata: Readonly<Record<string, string>>;
53
+ readonly root?: boolean;
54
+ readonly resource?: string;
55
+ }
56
+ export interface ExperienceCompositionOptions extends ExperienceAccess {
57
+ readonly definition: ExperienceCompositionDefinition;
58
+ readonly candidates: readonly ExperienceCompositionCandidate[];
59
+ readonly intent: string;
60
+ readonly layouts?: readonly ('boring/stack' | 'boring/row' | 'boring/grid')[];
61
+ readonly fallback: unknown;
62
+ readonly evaluate: Experimental_CompositionEvaluator;
63
+ readonly signal: AbortSignal;
64
+ readonly limits: {
65
+ readonly maxElements: number;
66
+ readonly maxDepth: number;
67
+ readonly maxEvaluations: number;
68
+ };
69
+ }
70
+ export type ExperienceCompositionSnapshot = {
71
+ readonly compositionId: string;
72
+ readonly sequence: number;
73
+ } & ({
74
+ readonly kind: 'partial' | 'final';
75
+ readonly descriptor: ExperienceDescriptor;
76
+ } | {
77
+ readonly kind: 'default';
78
+ readonly descriptor: ExperienceDescriptor | null;
79
+ readonly reason: 'pending' | 'unavailable' | 'cancelled' | 'limit';
80
+ });
81
+ export declare function composeExperience(options: ExperienceCompositionOptions): AsyncGenerator<ExperienceCompositionSnapshot>;
82
+ export {};
83
+ //# sourceMappingURL=experience-compose.d.ts.map