@ai-matrx/alchemy 0.6.2 → 0.8.2

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 (113) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/dist/actions.cjs +374 -0
  3. package/dist/actions.cjs.map +1 -0
  4. package/dist/actions.d.cts +773 -0
  5. package/dist/actions.d.ts +773 -0
  6. package/dist/actions.js +351 -0
  7. package/dist/actions.js.map +1 -0
  8. package/dist/checks.cjs +31 -2
  9. package/dist/checks.cjs.map +1 -1
  10. package/dist/checks.d.cts +24 -1
  11. package/dist/checks.d.ts +24 -1
  12. package/dist/checks.js +31 -2
  13. package/dist/checks.js.map +1 -1
  14. package/dist/core.cjs +1757 -2
  15. package/dist/core.cjs.map +1 -1
  16. package/dist/core.d.cts +595 -1
  17. package/dist/core.d.ts +595 -1
  18. package/dist/core.js +1749 -2
  19. package/dist/core.js.map +1 -1
  20. package/dist/declare.cjs +12 -10
  21. package/dist/declare.cjs.map +1 -1
  22. package/dist/declare.d.cts +18 -2
  23. package/dist/declare.d.ts +18 -2
  24. package/dist/declare.js +12 -10
  25. package/dist/declare.js.map +1 -1
  26. package/dist/index.cjs +1757 -2
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +595 -1
  29. package/dist/index.d.ts +595 -1
  30. package/dist/index.js +1749 -2
  31. package/dist/index.js.map +1 -1
  32. package/dist/menu.cjs +436 -0
  33. package/dist/menu.cjs.map +1 -0
  34. package/dist/menu.d.cts +834 -0
  35. package/dist/menu.d.ts +834 -0
  36. package/dist/menu.js +413 -0
  37. package/dist/menu.js.map +1 -0
  38. package/dist/operate-document.cjs +163 -0
  39. package/dist/operate-document.cjs.map +1 -0
  40. package/dist/operate-document.d.cts +132 -0
  41. package/dist/operate-document.d.ts +132 -0
  42. package/dist/operate-document.js +130 -0
  43. package/dist/operate-document.js.map +1 -0
  44. package/dist/operate-rich-html.cjs +129 -0
  45. package/dist/operate-rich-html.cjs.map +1 -0
  46. package/dist/operate-rich-html.d.cts +136 -0
  47. package/dist/operate-rich-html.d.ts +136 -0
  48. package/dist/operate-rich-html.js +96 -0
  49. package/dist/operate-rich-html.js.map +1 -0
  50. package/dist/operate-server.cjs +157 -0
  51. package/dist/operate-server.cjs.map +1 -0
  52. package/dist/operate-server.d.cts +183 -0
  53. package/dist/operate-server.d.ts +183 -0
  54. package/dist/operate-server.js +134 -0
  55. package/dist/operate-server.js.map +1 -0
  56. package/dist/operate-xlsx.cjs +248 -0
  57. package/dist/operate-xlsx.cjs.map +1 -0
  58. package/dist/operate-xlsx.d.cts +129 -0
  59. package/dist/operate-xlsx.d.ts +129 -0
  60. package/dist/operate-xlsx.js +215 -0
  61. package/dist/operate-xlsx.js.map +1 -0
  62. package/dist/operate.cjs +1774 -0
  63. package/dist/operate.cjs.map +1 -0
  64. package/dist/operate.d.cts +595 -0
  65. package/dist/operate.d.ts +595 -0
  66. package/dist/operate.js +1750 -0
  67. package/dist/operate.js.map +1 -0
  68. package/dist/ports.cjs.map +1 -1
  69. package/dist/ports.d.cts +361 -3
  70. package/dist/ports.d.ts +361 -3
  71. package/dist/react-bar.cjs +686 -0
  72. package/dist/react-bar.cjs.map +1 -0
  73. package/dist/react-bar.d.cts +379 -0
  74. package/dist/react-bar.d.ts +379 -0
  75. package/dist/react-bar.js +665 -0
  76. package/dist/react-bar.js.map +1 -0
  77. package/dist/react-host.cjs +301 -0
  78. package/dist/react-host.cjs.map +1 -0
  79. package/dist/react-host.d.cts +820 -0
  80. package/dist/react-host.d.ts +820 -0
  81. package/dist/react-host.js +269 -0
  82. package/dist/react-host.js.map +1 -0
  83. package/dist/react-menu.cjs +735 -0
  84. package/dist/react-menu.cjs.map +1 -0
  85. package/dist/react-menu.d.cts +370 -0
  86. package/dist/react-menu.d.ts +370 -0
  87. package/dist/react-menu.js +722 -0
  88. package/dist/react-menu.js.map +1 -0
  89. package/dist/react-overflow.cjs +592 -0
  90. package/dist/react-overflow.cjs.map +1 -0
  91. package/dist/react-overflow.d.cts +817 -0
  92. package/dist/react-overflow.d.ts +817 -0
  93. package/dist/react-overflow.js +571 -0
  94. package/dist/react-overflow.js.map +1 -0
  95. package/dist/react-palette.cjs +489 -0
  96. package/dist/react-palette.cjs.map +1 -0
  97. package/dist/react-palette.d.cts +359 -0
  98. package/dist/react-palette.d.ts +359 -0
  99. package/dist/react-palette.js +457 -0
  100. package/dist/react-palette.js.map +1 -0
  101. package/dist/react-selection.cjs +617 -0
  102. package/dist/react-selection.cjs.map +1 -0
  103. package/dist/react-selection.d.cts +357 -0
  104. package/dist/react-selection.d.ts +357 -0
  105. package/dist/react-selection.js +596 -0
  106. package/dist/react-selection.js.map +1 -0
  107. package/dist/react-sheet.cjs +616 -0
  108. package/dist/react-sheet.cjs.map +1 -0
  109. package/dist/react-sheet.d.cts +361 -0
  110. package/dist/react-sheet.d.ts +361 -0
  111. package/dist/react-sheet.js +595 -0
  112. package/dist/react-sheet.js.map +1 -0
  113. package/package.json +148 -5
package/dist/menu.d.ts ADDED
@@ -0,0 +1,834 @@
1
+ import { TransferOutcome } from '@ai-matrx/kit/content-transfer';
2
+
3
+ /**
4
+ * Matrx Alchemy — the menu model (CONTRACT §2.5).
5
+ *
6
+ * One model, many layouts. The model is framework-neutral: icons are KEYS,
7
+ * labels are already translated, nothing holds a handler — a node names its
8
+ * `actionId` and the engine runs it. Layouts rearrange; they never add, drop
9
+ * or rename a leaf (the Lossless Law).
10
+ */
11
+ interface NodeBase {
12
+ id: string;
13
+ actionId?: string;
14
+ }
15
+ interface MenuItemNode extends NodeBase {
16
+ kind: "item";
17
+ label: string;
18
+ icon?: string;
19
+ iconTone?: string;
20
+ stateIcon?: {
21
+ icon: string;
22
+ spin?: boolean;
23
+ };
24
+ description?: string;
25
+ hint?: string;
26
+ pressed?: boolean;
27
+ destructive?: boolean;
28
+ /** Bar layout slot (primary buttons vs ⋯). Default "overflow". */
29
+ placement?: "primary" | "overflow" | "both";
30
+ preserveSelection?: boolean;
31
+ /** Only on the six universal verbs: greyed, the sentence as tooltip. */
32
+ unavailable?: {
33
+ sentence: string;
34
+ };
35
+ }
36
+ interface MenuSubmenuNode extends NodeBase {
37
+ kind: "submenu";
38
+ label: string;
39
+ icon?: string;
40
+ /** Rows load on open (the action's `expand`). */
41
+ lazy?: boolean;
42
+ loading?: boolean;
43
+ emptyLabel?: string;
44
+ placement?: "primary" | "overflow" | "both";
45
+ children: MenuNode[];
46
+ }
47
+ interface MenuCheckboxNode extends NodeBase {
48
+ kind: "checkbox";
49
+ label: string;
50
+ icon?: string;
51
+ description?: string;
52
+ checked: boolean;
53
+ placement?: "primary" | "overflow" | "both";
54
+ }
55
+ interface MenuLinkNode extends NodeBase {
56
+ kind: "link";
57
+ label: string;
58
+ icon?: string;
59
+ href: string;
60
+ target?: string;
61
+ placement?: "primary" | "overflow" | "both";
62
+ }
63
+ interface MenuLabelNode extends NodeBase {
64
+ kind: "label";
65
+ label: string;
66
+ }
67
+ interface MenuSeparatorNode extends NodeBase {
68
+ kind: "separator";
69
+ }
70
+ type MenuNode = MenuItemNode | MenuSubmenuNode | MenuCheckboxNode | MenuLinkNode | MenuLabelNode | MenuSeparatorNode;
71
+ /** A node that does something when chosen. */
72
+ type MenuLeafNode = MenuItemNode | MenuCheckboxNode | MenuLinkNode;
73
+ type MenuGroup = "clipboard" | "tools" | "history" | "document" | "surface" | "ai" | "quick" | "editable" | "admin" | "surface-info" | "transfer";
74
+ interface MenuSection {
75
+ id: string;
76
+ group: MenuGroup;
77
+ label?: string;
78
+ icon?: string;
79
+ primary?: boolean;
80
+ nodes: MenuNode[];
81
+ }
82
+ interface MenuModel {
83
+ header: {
84
+ label: "Selected" | "Content";
85
+ text: string;
86
+ } | null;
87
+ /** Classic order. Layouts rearrange; they never add, drop or rename a leaf. */
88
+ sections: MenuSection[];
89
+ /** Well-known nodes addressed by role id (the universal verbs by verb), never by label. */
90
+ roles: Record<string, string>;
91
+ }
92
+ type LayoutName = "bar" | "overflow" | "context" | "selection-toolbar" | "sheet" | "palette";
93
+ type ContextArrangement = "classic" | "tiered" | "command";
94
+ type Density = "comfortable" | "compact";
95
+ interface ArrangedBar {
96
+ primary: MenuNode[];
97
+ overflow: MenuSection[];
98
+ }
99
+ interface ArrangedContext {
100
+ strip: MenuLeafNode[];
101
+ sections: MenuSection[];
102
+ filterable: boolean;
103
+ }
104
+ /** Drill-down, path-based. */
105
+ interface ArrangedSheet {
106
+ strip: MenuLeafNode[];
107
+ root: MenuNode[];
108
+ }
109
+ /** The command palette (Linear): every leaf with its breadcrumb. */
110
+ interface ArrangedPalette {
111
+ leaves: {
112
+ node: MenuLeafNode | MenuSubmenuNode;
113
+ breadcrumb: string[];
114
+ }[];
115
+ }
116
+ interface ArrangedSelectionToolbar {
117
+ strip: MenuNode[];
118
+ more: MenuSection[];
119
+ }
120
+
121
+ /**
122
+ * Matrx Alchemy — Declare types (CONTRACT §2.1).
123
+ *
124
+ * A declaration is plain JSON data (chair ruling N2): no functions, no classes,
125
+ * no React. Runtime behaviour lives in `MountedSurface` (`/declare/host`) and in
126
+ * handler registrations, never here. Zero React, zero `@ai-matrx/agents`.
127
+ */
128
+ type Json$1 = null | boolean | number | string | Json$1[] | {
129
+ [key: string]: Json$1;
130
+ };
131
+ /** Unchanged from `ui_surface_value_type_chk`. */
132
+ type ValueType = "string" | "number" | "boolean" | "object" | "array" | "document";
133
+ type Classification = "ordinary" | "secret" | "credential";
134
+ /** Persisted (CONTRACT §2.7). Projection enforces it before any value leaves. */
135
+ interface Sensitivity {
136
+ /** false = never leaves through Operate. Default true. */
137
+ exportable: boolean;
138
+ /** secret/credential are always excluded; no caller can relax this. Default "ordinary". */
139
+ classification: Classification;
140
+ /** Pre-checked in the preparation workspace. Default true. */
141
+ includedByDefault: boolean;
142
+ }
143
+ interface ValueGroup {
144
+ key: string;
145
+ label: string;
146
+ sortOrder: number;
147
+ description?: string;
148
+ }
149
+ /** The four runtime slots a capture fills (absorbs page-capture). */
150
+ type LiveSlot = "selection" | "on-demand" | "errors" | "requests";
151
+ interface DeclaredValue {
152
+ name: string;
153
+ label: string;
154
+ description: string;
155
+ valueType: ValueType;
156
+ /** Registered content-ir kind key (DB: kind_key). */
157
+ valueKind?: string;
158
+ alwaysAvailable: boolean;
159
+ typicalCharCount: number;
160
+ autoContext?: boolean;
161
+ group?: string;
162
+ sortOrder?: number;
163
+ sensitivity?: Partial<Sensitivity>;
164
+ /** Filled at capture time from a live slot instead of the scope. */
165
+ liveSlot?: LiveSlot;
166
+ /** "request" values drop out of "Only the data". Default "data". */
167
+ role?: "data" | "request";
168
+ }
169
+ /** Where a write lands. "stream" = applied live, op by op, into an editable text. */
170
+ type WriteMode = "draft" | "entity" | "ui" | "stream";
171
+ /** Who may apply WITHOUT a click. "queued" = filed for an organization's review. */
172
+ type ApplyPolicy = "manual" | "ask" | "auto" | "queued";
173
+ /** The WidgetHandle text op set, unchanged in meaning. */
174
+ type StreamOp = "replace" | "insert_before" | "insert_after" | "prepend" | "append" | "patch";
175
+ interface WriteTarget {
176
+ name: string;
177
+ label: string;
178
+ description: string;
179
+ valueType: ValueType;
180
+ valueKind?: string;
181
+ /** Read twin (evidence loop). Required when patchable or mode === "stream". */
182
+ updatesValue?: string;
183
+ patchable?: boolean;
184
+ approvalComparison?: "text-replacement";
185
+ mode: WriteMode;
186
+ /** Required iff mode === "stream". */
187
+ streamOps?: readonly StreamOp[];
188
+ /** Default "manual". Binding overrides may only tighten. */
189
+ applyPolicy?: ApplyPolicy;
190
+ destination?: boolean;
191
+ /** Present when the target writes into an item rather than the screen. */
192
+ itemType?: string;
193
+ group?: string;
194
+ sortOrder?: number;
195
+ }
196
+ /** One kind of thing inside a screen (a row, a message, a code block, a card). */
197
+ interface ItemType {
198
+ name: string;
199
+ label: string;
200
+ description: string;
201
+ /** Value names that together identify one item. */
202
+ identity: readonly string[];
203
+ levels?: readonly string[];
204
+ entityType?: string;
205
+ values: readonly DeclaredValue[];
206
+ writeTargets?: readonly WriteTarget[];
207
+ }
208
+ type Readiness = "verified" | "partial" | "stub";
209
+ /** `ui_surface_execution_mode_check`, unchanged. */
210
+ type ExecutionMode = "python-stream" | "nextjs-stream" | "browser-realtime" | "local-runtime";
211
+ interface ConfigNamespaceDeclaration {
212
+ namespace: string;
213
+ label: string;
214
+ description: string;
215
+ }
216
+ interface EvidenceSourceDeclaration {
217
+ kind: "processed_document";
218
+ idValue: string;
219
+ fileIdValue?: string;
220
+ labelValue?: string;
221
+ representation?: "clean" | "raw";
222
+ }
223
+ /**
224
+ * One screen's declaration. Owning packages add fields (agent roles, client
225
+ * tools) as EXTENSION SLOTS — by interface extension or declaration merging —
226
+ * and register a `DeclarationExtension` so validation and the sync cover them.
227
+ */
228
+ interface SurfaceDeclaration {
229
+ surfaceName: string;
230
+ /** REQUIRED. `ui_surface.client_name`; must equal the surfaceName prefix. */
231
+ client: string;
232
+ /** REQUIRED. `ui_surface.execution_mode`. */
233
+ executionMode: ExecutionMode;
234
+ /** REQUIRED. `ui_surface.description` ("" only where the live row is empty). */
235
+ description: string;
236
+ label: string;
237
+ readiness: Readiness;
238
+ readinessNote?: string;
239
+ urlPattern?: string;
240
+ inheritsFrom?: string;
241
+ intro?: string;
242
+ overlayId?: string;
243
+ values: readonly DeclaredValue[];
244
+ groups?: readonly ValueGroup[];
245
+ writeTargets?: readonly WriteTarget[];
246
+ itemTypes?: readonly ItemType[];
247
+ liveSlots?: readonly LiveSlot[];
248
+ configNamespaces?: readonly ConfigNamespaceDeclaration[];
249
+ evidenceSources?: readonly EvidenceSourceDeclaration[];
250
+ skipBaselineValues?: boolean;
251
+ }
252
+ type ValueProvenance = {
253
+ kind: "own";
254
+ } | {
255
+ kind: "inherited";
256
+ from: string;
257
+ } | {
258
+ kind: "baseline";
259
+ };
260
+ type ResolvedValue = DeclaredValue & {
261
+ provenance: ValueProvenance;
262
+ groupKey: string;
263
+ /** Defaults filled. */
264
+ resolvedSensitivity: Sensitivity;
265
+ };
266
+ interface ResolvedSurfaceDeclaration extends SurfaceDeclaration {
267
+ contentHash: string;
268
+ values: readonly ResolvedValue[];
269
+ groups: readonly ValueGroup[];
270
+ }
271
+ /**
272
+ * ITEM KEYS. A value or write target is identified by (surfaceName, itemType ?? "", name).
273
+ * The qualified key "<itemType>.<name>" names an item value; a bare name is a screen value.
274
+ */
275
+ type QualifiedValueKey = string;
276
+ /** Live slot payloads (page-capture's shapes, kept). */
277
+ interface LiveValues {
278
+ selection?: {
279
+ choices: Record<string, Json$1>;
280
+ text: string | null;
281
+ };
282
+ "on-demand"?: readonly OnDemandSection[];
283
+ errors?: readonly string[];
284
+ requests?: readonly RecentRequest[];
285
+ }
286
+ interface OnDemandSection {
287
+ id: string;
288
+ title: string;
289
+ description?: string;
290
+ role: "data" | "request";
291
+ brief?: Json$1;
292
+ load(signal: AbortSignal): Promise<Json$1>;
293
+ }
294
+ interface RecentRequest {
295
+ method: string;
296
+ path: string;
297
+ status: string;
298
+ httpStatus?: number;
299
+ durationMs?: number;
300
+ requestId?: string;
301
+ client?: string;
302
+ requestBody?: Json$1;
303
+ requestBodyNote?: string;
304
+ errorSentence?: string;
305
+ timestamp: number;
306
+ }
307
+ /** RAW-FREE. What leaves the package about a clicked item. */
308
+ interface ResolvedItem {
309
+ itemType: string;
310
+ identity: Record<string, string>;
311
+ level?: string;
312
+ readOnly: boolean;
313
+ }
314
+
315
+ /**
316
+ * The click target (CONTRACT §2.4) in its own module: `/ports` references it,
317
+ * and `/ports` must stay free of every other package (types only).
318
+ */
319
+
320
+ /** The six universal verbs — the ONLY rows that may grey (R1 c). */
321
+ declare const UNIVERSAL_VERBS: readonly ["copy", "cut", "paste", "undo", "redo", "find"];
322
+ type UniversalVerb = (typeof UNIVERSAL_VERBS)[number];
323
+ /** The transfer Payload's kinds (`actions/types.ts` asserts equality at compile time). */
324
+ type PayloadKind = "text" | "markdown" | "json" | "rows" | "registered";
325
+ /** What eligibility is computed from: everything declared at the click target. */
326
+ interface ClickTarget {
327
+ surface: {
328
+ instanceId: string;
329
+ surfaceName: string;
330
+ } | null;
331
+ /** The resolved declaration (inheritance applied). Eligibility decides by KEY existence over it. */
332
+ declaration: ResolvedSurfaceDeclaration | null;
333
+ /** Declared value keys with a read path here (screen names + qualified item keys). Key existence, not population. */
334
+ valueKeys: ReadonlySet<QualifiedValueKey>;
335
+ /** Scope chain, most general first: ["global", "<domain>", "<surfaceName>"]. */
336
+ scopeChain: readonly string[];
337
+ /** The surface's explicit exclusions (the one override). */
338
+ excludedActionIds: ReadonlySet<string>;
339
+ item: ResolvedItem | null;
340
+ level: string | null;
341
+ /** Offsets + the live range handle, editable or not. */
342
+ selection: {
343
+ text: string;
344
+ type: "editable" | "non-editable";
345
+ start: number;
346
+ end: number;
347
+ /** Opaque host handle (DOM Range / element) used to restore or stream into the selection. */
348
+ handle?: unknown;
349
+ } | null;
350
+ /** Payload kinds a capture of this target would produce (from declared value types/kinds). */
351
+ payloadKinds: readonly PayloadKind[];
352
+ /** Declared targets writable on this target right now. */
353
+ writable: readonly WriteTarget[];
354
+ readOnly: boolean;
355
+ /** True when any declared value here is secret/credential or non-exportable. */
356
+ restricted: boolean;
357
+ organizationId: string | null;
358
+ auth: {
359
+ authenticated: boolean;
360
+ admin: boolean;
361
+ creator: boolean;
362
+ };
363
+ /**
364
+ * Host-owned context a provider reads to act (the rich-document source, the
365
+ * editable field's callbacks). Opaque to the package; never crosses into the
366
+ * model. Providers that need it narrow it themselves.
367
+ */
368
+ host?: unknown;
369
+ }
370
+
371
+ /**
372
+ * `@ai-matrx/alchemy/ports` — the host ports (Matrx Alchemy CONTRACT §2.2).
373
+ *
374
+ * The app binds these ONCE, at its boundary (where the Alchemy host is
375
+ * mounted). The package never imports the host.
376
+ *
377
+ * ── Ports arrive with the wave that needs them (chair ruling N8) ────────────
378
+ *
379
+ * This entry declares only the ports the current waves use:
380
+ *
381
+ * ALC-13 `kinds` (content-ir's validator, injected), `diagnostics` (required)
382
+ * ALC-14 `identity`, `persistence`
383
+ * ALC-15 `store`, `notify`, `overlay`, `icons`, `knobs`, `i18n`, `auth`, `dialogs`
384
+ *
385
+ * ALC-16 `transport`, `assets`, `server`
386
+ *
387
+ * ALC-17 adds approvals/bodies/receipts.
388
+ *
389
+ * ── Absent-port semantics ───────────────────────────────────────────────────
390
+ *
391
+ * Every port except `diagnostics` is OPTIONAL. A port the host does not bind
392
+ * means that capability is ABSENT: the actions, formats or writes that need
393
+ * it are not offered at all. A missing port is never stubbed, never faked
394
+ * with a default that pretends to work, and never shown as a dead control.
395
+ *
396
+ * kinds absent → kind validation is absent (no value can be checked,
397
+ * so a kind-bearing write is not offered)
398
+ * diagnostics REQUIRED → nothing fails silently; contract breaks land here
399
+ * identity absent → organization-scoped declarations are absent
400
+ * persistence absent → per-person settings are absent (organization and
401
+ * platform defaults apply)
402
+ * store absent → actions that read named host state or run named
403
+ * host commands are absent
404
+ * notify absent → outcomes are not announced (diagnostics still captures)
405
+ * overlay absent → actions that open an overlay or navigate are absent
406
+ * icons absent → rows render label-only (a key is never shown as text)
407
+ * knobs absent → the platform default layout/density applies
408
+ * i18n absent → labels as written
409
+ * auth absent → a signed-out person does not see requiresAuth actions
410
+ * (sign-in-then-resume is the capability that is missing)
411
+ * dialogs absent → actions that need a host dialog are absent
412
+ *
413
+ * Types only: this entry has no runtime code.
414
+ */
415
+ /** JSON as it crosses a port. */
416
+ type Json = null | boolean | number | string | Json[] | {
417
+ [key: string]: Json;
418
+ };
419
+ /**
420
+ * content-ir's kind validator, injected so Alchemy validates without
421
+ * importing the app or the kind catalog. The host builds it from
422
+ * `@ai-matrx/content-ir`'s `createKindValidator` over its own
423
+ * `SchemaSourcePort` (which content-ir defines and exports; Alchemy never
424
+ * declares it).
425
+ *
426
+ * `unverifiable: true` marks a verdict where the kind could not be checked
427
+ * (not registered, no schema, uncompilable schema, catalog unreachable) — a
428
+ * skip, which is never a pass.
429
+ */
430
+ interface KindValidatorPort {
431
+ validate(kindKey: string, value: unknown, signal: AbortSignal): Promise<{
432
+ ok: true;
433
+ } | {
434
+ ok: false;
435
+ sentence: string;
436
+ remedy: string;
437
+ unverifiable?: true;
438
+ }>;
439
+ }
440
+
441
+ /** Where contract breaks are captured. Required: nothing fails silently. */
442
+ interface DiagnosticsPort {
443
+ capture(error: unknown, context: {
444
+ area: "declare" | "operate" | "write" | "actions" | "menu";
445
+ detail: Json;
446
+ }): void;
447
+ }
448
+ /** Who is using the app right now, and in which organization. */
449
+ interface IdentityPort {
450
+ /** `null` while the host has no resolved identity yet. */
451
+ current(): {
452
+ userId: string;
453
+ organizationId: string | null;
454
+ isAuthenticated: boolean;
455
+ isAdmin: boolean;
456
+ } | null;
457
+ /** Called whenever `current()` may answer differently; returns unsubscribe. */
458
+ onChange(listener: () => void): () => void;
459
+ }
460
+ /** Per-person settings (layout, density, transfer preferences), layered by the host's settings system. */
461
+ interface PersistencePort {
462
+ readSetting<T extends Json>(key: string): Promise<T | null>;
463
+ writeSetting(key: string, value: Json): Promise<void>;
464
+ }
465
+ /** Neutral state access: named reads and named commands. No Redux type or dispatch crosses this line. */
466
+ interface StorePort {
467
+ /** A named, typed read the host registered ("activeConversationId", "organizationName"). */
468
+ read<T extends Json>(key: string): T | null;
469
+ /** A named command the host registered ("tasks.setPendingSource"); resolves with its result. */
470
+ command(name: string, input: Json): Promise<Json | void>;
471
+ subscribe(key: string, listener: () => void): () => void;
472
+ }
473
+ interface NotifyPort {
474
+ success(sentence: string): void;
475
+ info(sentence: string): void;
476
+ /** Every failure names its remedy. */
477
+ error(sentence: string, remedy: string): void;
478
+ }
479
+ /** Overlays are dispatched through the host; the menu never renders a modal. */
480
+ interface OverlayHostPort {
481
+ open(overlayId: string, data: Json, instanceId?: string): void;
482
+ close(overlayId: string, instanceId?: string): void;
483
+ navigate(href: string): void;
484
+ }
485
+ /** Icon key → the host's icon component. The model only ever holds keys. */
486
+ interface IconResolverPort {
487
+ resolve(key: string): unknown | null;
488
+ }
489
+ type ContextArrangementKnob = "classic" | "tiered" | "command";
490
+ type DensityKnob = "comfortable" | "compact";
491
+ /** ORGANIZATION defaults only; every method takes the organization. */
492
+ interface MenuKnobsPort {
493
+ /** Replaces the constant PLACEMENT_LABEL_OVERRIDE. */
494
+ placementLabel(placement: string, organizationId: string | null): string | null;
495
+ /** Replaces the constant DEFAULT_PLACEMENT_MODE. */
496
+ placementMode(placement: string, organizationId: string | null): "show" | "hide";
497
+ /** The organization's default layout and density (platform default when null: command / comfortable). */
498
+ layout(organizationId: string | null): {
499
+ context: ContextArrangementKnob;
500
+ density: DensityKnob;
501
+ } | null;
502
+ }
503
+ /** Label translation. Absent = labels as written. */
504
+ interface I18nPort {
505
+ t(key: string, fallback: string): string;
506
+ }
507
+ /** Sign in, then resume the action the person picked. */
508
+ interface AuthResumePort {
509
+ isSignedIn(): boolean;
510
+ /** Stash {actionId, target fingerprint} and open sign-in; after sign-in the next matching menu host resumes it. */
511
+ signInThenResume(pending: {
512
+ actionId: string;
513
+ contentFingerprint: string;
514
+ }): void;
515
+ takePending(contentFingerprint: string): {
516
+ actionId: string;
517
+ } | null;
518
+ }
519
+ /** Per-instance host dialogs (today's RichDocumentActionContextCallbacks). */
520
+ interface HostDialogsPort {
521
+ requestDelete?(target: ClickTarget): void;
522
+ requestConvert?(target: ClickTarget): void;
523
+ requestEditHistory?(target: ClickTarget): void;
524
+ requestFullPrint?(target: ClickTarget): void;
525
+ requestSaveTable?(table: {
526
+ headers: string[];
527
+ rows: string[][];
528
+ }): void;
529
+ requestFlashcard?(answer: string): void;
530
+ }
531
+ /** The ports a host binds. Optional = absent capability when unbound (see above). */
532
+ interface AlchemyHostPorts {
533
+ kinds?: KindValidatorPort;
534
+ /** REQUIRED from ALC-13 (the only non-optional port): contract breaks are captured here. */
535
+ diagnostics: DiagnosticsPort;
536
+ identity?: IdentityPort;
537
+ persistence?: PersistencePort;
538
+ store?: StorePort;
539
+ notify?: NotifyPort;
540
+ overlay?: OverlayHostPort;
541
+ icons?: IconResolverPort;
542
+ /** Organization defaults for menu knobs; a person's own choice is layered over them via persistence (N9). */
543
+ knobs?: MenuKnobsPort;
544
+ i18n?: I18nPort;
545
+ auth?: AuthResumePort;
546
+ dialogs?: HostDialogsPort;
547
+ /** Clipboard and download for this runtime (the browser default ships in /operate). */
548
+ transport?: TransportPort;
549
+ /** Private or signed images → share-safe URLs before HTML is sealed. Absent = images keep their URL. */
550
+ assets?: AssetResolverPort;
551
+ /** Present when the host can reach aidream server operations; absent = server formats are not offered. */
552
+ server?: ServerPort;
553
+ }
554
+ /** A transferable artifact as the transport sees it (structurally `@ai-matrx/alchemy/operate`'s Artifact). */
555
+ interface TransportArtifact {
556
+ format: string;
557
+ plainText: string;
558
+ html?: string;
559
+ file?: {
560
+ filename: string;
561
+ mime: string;
562
+ bytes: Uint8Array;
563
+ };
564
+ }
565
+ type TransportOutcome = {
566
+ status: "success";
567
+ delivered: "clipboard" | "download-started";
568
+ mimeTypes: string[];
569
+ } | {
570
+ status: "degraded";
571
+ delivered: "plain-text";
572
+ reason: string;
573
+ } | {
574
+ status: "cancelled";
575
+ } | {
576
+ status: "error";
577
+ code: string;
578
+ message: string;
579
+ retryable: boolean;
580
+ };
581
+ /** Clipboard and download for a non-browser runtime (MV3 service worker, Tauri — CONTRACT R8). */
582
+ interface TransportPort {
583
+ copy(artifact: TransportArtifact, signal: AbortSignal): Promise<TransportOutcome>;
584
+ download(artifact: TransportArtifact, signal: AbortSignal): Promise<TransportOutcome>;
585
+ }
586
+ interface AssetResolverPort {
587
+ resolve(url: string, signal: AbortSignal): Promise<{
588
+ url: string;
589
+ } | null>;
590
+ }
591
+ type ServerResult = {
592
+ status: "bytes";
593
+ filename: string;
594
+ mime: string;
595
+ bytes: Uint8Array;
596
+ notices: readonly {
597
+ code: string;
598
+ sentence: string;
599
+ remedy: string;
600
+ }[];
601
+ } | {
602
+ status: "receipt";
603
+ receipt: {
604
+ status: string;
605
+ sentence: string;
606
+ remedy?: string;
607
+ };
608
+ };
609
+ interface ServerPort {
610
+ call(operationId: string, input: Json, signal: AbortSignal): Promise<ServerResult>;
611
+ }
612
+
613
+ /**
614
+ * Matrx Alchemy — Actions types (CONTRACT §2.4).
615
+ *
616
+ * One registry of everything a person can do to a thing. Providers plug in;
617
+ * eligibility is computed from what is declared at the click target; duplicate
618
+ * ids are refused; an action that does not apply is ABSENT (chair ruling R1).
619
+ *
620
+ * Framework-neutral: zero React, zero `@ai-matrx/agents`. Icons are string KEYS
621
+ * resolved by the host's `IconResolverPort`; labels go through `I18nPort`.
622
+ */
623
+
624
+ /**
625
+ * Chair ruling R1: an action that does not apply or cannot run now is ABSENT.
626
+ * Only the six universal verbs may answer "unavailable-verb": greyed in the
627
+ * strip/bar, the sentence as tooltip. `requiresAuth` is NOT unavailability.
628
+ */
629
+ type Eligibility = {
630
+ status: "absent";
631
+ } | {
632
+ status: "available";
633
+ } | {
634
+ status: "unavailable-verb";
635
+ verb: UniversalVerb;
636
+ sentence: string;
637
+ };
638
+ /** rich-document's ActionCategory, unchanged, plus the three the context menu groups by. */
639
+ type ActionCategory = "feedback" | "copy" | "export" | "save" | "edit" | "share" | "creator" | "app" | "listen" | "study" | "ask" | "ai" | "admin" | "clipboard" | "history" | "surface-info";
640
+ /**
641
+ * An optional named section inside the action's group (a grid's cell / row /
642
+ * column). `primary` marks the thing the person clicked: every layout renders
643
+ * it first, inline, heading kept (context-menu-v3's primary section law).
644
+ */
645
+ interface ActionSection {
646
+ id: string;
647
+ label: string;
648
+ labelKey?: string;
649
+ icon?: string;
650
+ primary?: boolean;
651
+ }
652
+ interface Action {
653
+ /** Globally unique. A duplicate is refused (never overwritten). */
654
+ id: string;
655
+ /** The provider that registered it — filled by the registry. */
656
+ provider?: string;
657
+ /** Resolved through I18nPort.t(labelKey ?? id, label). */
658
+ label: string | ((target: ClickTarget) => string);
659
+ labelKey?: string;
660
+ /** Icon KEY resolved by IconResolverPort — the model stays framework-neutral. */
661
+ icon?: string;
662
+ /** Host styling hint for the glyph (rich-document iconColor). Renderers may ignore it. */
663
+ iconTone?: string;
664
+ /** Replaces `icon` while a state holds (a spinner while speaking). */
665
+ stateIcon?(target: ClickTarget): {
666
+ icon: string;
667
+ spin?: boolean;
668
+ } | null;
669
+ hint?: string;
670
+ hintKey?: string;
671
+ description?: string;
672
+ category: ActionCategory;
673
+ /** Which universal verb this clipboard action is (addressed by role in every layout). */
674
+ verb?: UniversalVerb;
675
+ order?: number;
676
+ /** Bar layout slot. Default "overflow". */
677
+ placement?: "primary" | "overflow" | "both";
678
+ section?: ActionSection;
679
+ /** A signed-out person still sees it; running goes through AuthResumePort (sign in, then resume). */
680
+ requiresAuth?: boolean;
681
+ /** Declared write targets this action lands through. Absent on a read-only target. */
682
+ writes?: readonly string[];
683
+ destructive?: boolean;
684
+ preserveSelection?: boolean;
685
+ /** Link rows navigate instead of running. */
686
+ href?: string | ((target: ClickTarget) => string);
687
+ eligible(target: ClickTarget): Eligibility;
688
+ /** Toggle/pressed state: a pressed button or a checked row. */
689
+ pressed?(target: ClickTarget): boolean;
690
+ /** Present = the row is a checkbox (pressed is its state). */
691
+ toggle?: boolean;
692
+ subscribe?(onChange: () => void, target: ClickTarget): () => void;
693
+ /** Submenus whose rows load on open (agent lists). */
694
+ expand?(target: ClickTarget, signal: AbortSignal): Promise<readonly Action[]>;
695
+ /** Shown centred when `expand` resolves empty. */
696
+ emptyLabel?: string;
697
+ run(target: ClickTarget, context: ActionRunContext): Promise<TransferOutcome | void> | TransferOutcome | void;
698
+ }
699
+ /** One removed read, recorded (kit content-transfer's omission shape). */
700
+ interface ReadOmission {
701
+ path: string;
702
+ reason: "excluded" | "filtered";
703
+ classification?: "secret" | "credential";
704
+ }
705
+ /**
706
+ * PROJECTION BEFORE PROVIDERS. Providers never receive a raw read. Every read
707
+ * they can make is projected first: secret and credential values, values
708
+ * declared `exportable: false`, and values nobody declared are removed, with an
709
+ * omission recorded. Raw reads never leave the package.
710
+ */
711
+ interface ProjectedReads {
712
+ values(signal: AbortSignal): Promise<{
713
+ values: Record<string, Json$1>;
714
+ omissions: readonly ReadOmission[];
715
+ }>;
716
+ item(signal: AbortSignal): Promise<{
717
+ values: Record<string, Json$1>;
718
+ omissions: readonly ReadOmission[];
719
+ } | null>;
720
+ live(signal: AbortSignal): Promise<LiveValues>;
721
+ }
722
+ /**
723
+ * What a running action receives. `door` (ALC-17) and `operate` (ALC-16) join
724
+ * with their waves; until then those capabilities are absent (N8).
725
+ */
726
+ interface ActionRunContext {
727
+ reads: ProjectedReads;
728
+ ports: AlchemyHostPorts;
729
+ signal: AbortSignal;
730
+ }
731
+ interface ResolvedAction {
732
+ action: Action;
733
+ eligibility: Exclude<Eligibility, {
734
+ status: "absent";
735
+ }>;
736
+ }
737
+
738
+ /**
739
+ * buildMenuModel — WHAT exists at a click target, as one framework-neutral
740
+ * tree (CONTRACT §2.5). Categories GROUP, never gate: eligibility already ran
741
+ * in the registry; this only arranges the resolved set into sections.
742
+ */
743
+
744
+ /** THE category → menu group table (CONTRACT §2.5). */
745
+ declare const CATEGORY_GROUP: Readonly<Record<ActionCategory, MenuGroup>>;
746
+ /** Classic order of the groups. `surface-info` is engine-built and always last. */
747
+ declare const GROUP_ORDER: readonly MenuGroup[];
748
+ interface BuildMenuModelOptions {
749
+ i18n?: I18nPort;
750
+ /** Content the menu acts on when nothing is selected. */
751
+ content?: string | null;
752
+ }
753
+ /** One resolved action → one model node (item, checkbox, link or lazy submenu). */
754
+ declare function actionToNode(resolved: ResolvedAction, target: ClickTarget, options?: BuildMenuModelOptions): MenuNode;
755
+ declare function buildMenuModel(target: ClickTarget, resolved: readonly ResolvedAction[], options?: BuildMenuModelOptions): MenuModel;
756
+ /** Fill a lazy submenu's rows once they load (pure: returns a new model). */
757
+ declare function withExpandedChildren(model: MenuModel, nodeId: string, children: MenuNode[] | "loading"): MenuModel;
758
+ /** Find a node anywhere in a tree. */
759
+ declare function findNode(nodes: readonly MenuNode[], id: string): MenuNode | null;
760
+
761
+ /**
762
+ * Layouts — pure arrangements of one MenuModel (CONTRACT §2.5).
763
+ *
764
+ * Every layout is a function over the SAME model: it decides which nodes sit
765
+ * at the top level, which fold under a named submenu and which go into a
766
+ * strip. None adds, drops or renames a leaf — `leafIds(arranged)` equals
767
+ * `leafIds(model)` for every layout (THE LOSSLESS LAW, proven by test).
768
+ *
769
+ * The clipboard group IS the universal verb strip: in the strip layouts it is
770
+ * drawn icon-only; elsewhere its rows are drawn in place. Unavailable verbs
771
+ * (the only greyed nodes, R1 c) appear in every layout identically.
772
+ */
773
+
774
+ /** A section with more rows than this folds under its label in tiered/command/sheet. */
775
+ declare const INLINE_SECTION_MAX = 3;
776
+ /**
777
+ * A section's heading is ONLY the name its actions declare (an approved
778
+ * heading the classic menu already uses: "Save as", "Copy as", "History").
779
+ * A group with no declared name has no heading and is never folded — its rows
780
+ * show inline. The package never coins a heading (chair ruling, ALC-15).
781
+ */
782
+ declare function sectionLabel(section: MenuSection): string | null;
783
+ type Arranged = MenuModel | ArrangedBar | ArrangedContext | ArrangedSheet | ArrangedPalette | ArrangedSelectionToolbar | MenuSection[];
784
+ /** Proof obligation for every layout: its leaf set equals the model's (sorted, de-duplicated). */
785
+ declare function leafIds(arranged: Arranged): readonly string[];
786
+ declare function context(model: MenuModel, arrangement: ContextArrangement): ArrangedContext;
787
+ /** ⋯ overflow: every section in classic order; long named sections fold under their own name. */
788
+ declare function overflow(model: MenuModel): MenuSection[];
789
+ /**
790
+ * The action bar: primary buttons inline, EVERYTHING else under ⋯. A "both"
791
+ * action is mirrored in both halves (the leaf set is still the model's).
792
+ * `mini` changes presentation only (icon-only primaries), never membership.
793
+ */
794
+ declare function bar(model: MenuModel, _options?: {
795
+ mini?: boolean;
796
+ }): ArrangedBar;
797
+ /** Selection toolbar: the verbs plus primary actions as a strip; the rest under More. */
798
+ declare function selectionToolbar(model: MenuModel): ArrangedSelectionToolbar;
799
+ /**
800
+ * Mobile bottom sheet: the verb strip on top; primary and short sections
801
+ * inline; longer sections become one drill-down row named by the section.
802
+ * Consumes the model directly, so it can never drift from desktop.
803
+ */
804
+ declare function sheet(model: MenuModel): ArrangedSheet;
805
+ /** Command palette: every leaf flattened with its breadcrumb (Linear). */
806
+ declare function palette(model: MenuModel): ArrangedPalette;
807
+ /** Filter the palette by a query (every word must match label or breadcrumb). */
808
+ declare function filterPalette(arranged: ArrangedPalette, query: string): ArrangedPalette;
809
+ declare const layouts: {
810
+ bar: typeof bar;
811
+ overflow: typeof overflow;
812
+ context: typeof context;
813
+ selectionToolbar: typeof selectionToolbar;
814
+ sheet: typeof sheet;
815
+ palette: typeof palette;
816
+ };
817
+
818
+ /**
819
+ * N9: layout and density are a PERSON's choice layered over the organization
820
+ * default: persistence ("alchemy.menu.layout") → knobs.layout(org) → platform
821
+ * default (command / comfortable, Arman 2026-08-22).
822
+ */
823
+
824
+ declare const PLATFORM_MENU_LAYOUT: {
825
+ context: ContextArrangementKnob;
826
+ density: DensityKnob;
827
+ };
828
+ declare const MENU_LAYOUT_SETTING = "alchemy.menu.layout";
829
+ declare function resolveMenuLayout(ports: Pick<AlchemyHostPorts, "persistence" | "knobs">, organizationId: string | null): Promise<{
830
+ context: ContextArrangementKnob;
831
+ density: DensityKnob;
832
+ }>;
833
+
834
+ export { type ArrangedBar, type ArrangedContext, type ArrangedPalette, type ArrangedSelectionToolbar, type ArrangedSheet, type BuildMenuModelOptions, CATEGORY_GROUP, type ContextArrangement, type Density, GROUP_ORDER, INLINE_SECTION_MAX, type LayoutName, MENU_LAYOUT_SETTING, type MenuCheckboxNode, type MenuGroup, type MenuItemNode, type MenuLabelNode, type MenuLeafNode, type MenuLinkNode, type MenuModel, type MenuNode, type MenuSection, type MenuSeparatorNode, type MenuSubmenuNode, PLATFORM_MENU_LAYOUT, actionToNode, bar, buildMenuModel, context, filterPalette, findNode, layouts, leafIds, overflow, palette, resolveMenuLayout, sectionLabel, selectionToolbar, sheet, withExpandedChildren };