@ai-matrx/alchemy 0.8.59 → 0.9.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 (76) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +2 -0
  3. package/dist/actions.cjs +1 -1
  4. package/dist/actions.cjs.map +1 -1
  5. package/dist/actions.d.cts +300 -3
  6. package/dist/actions.d.ts +300 -3
  7. package/dist/actions.js +1 -1
  8. package/dist/actions.js.map +1 -1
  9. package/dist/checks.cjs +1 -1
  10. package/dist/checks.cjs.map +1 -1
  11. package/dist/checks.d.cts +22 -1
  12. package/dist/checks.d.ts +22 -1
  13. package/dist/checks.js +2 -2
  14. package/dist/checks.js.map +1 -1
  15. package/dist/core.d.cts +141 -26
  16. package/dist/core.d.ts +141 -26
  17. package/dist/declare.cjs +1 -1
  18. package/dist/declare.cjs.map +1 -1
  19. package/dist/declare.d.cts +12 -4
  20. package/dist/declare.d.ts +12 -4
  21. package/dist/declare.js +1 -1
  22. package/dist/declare.js.map +1 -1
  23. package/dist/index.d.cts +141 -26
  24. package/dist/index.d.ts +141 -26
  25. package/dist/menu.cjs +1 -1
  26. package/dist/menu.cjs.map +1 -1
  27. package/dist/menu.d.cts +109 -2
  28. package/dist/menu.d.ts +109 -2
  29. package/dist/menu.js +1 -1
  30. package/dist/menu.js.map +1 -1
  31. package/dist/operate-server.d.cts +6 -0
  32. package/dist/operate-server.d.ts +6 -0
  33. package/dist/operate.d.cts +141 -26
  34. package/dist/operate.d.ts +141 -26
  35. package/dist/ports.cjs.map +1 -1
  36. package/dist/ports.d.cts +116 -1
  37. package/dist/ports.d.ts +116 -1
  38. package/dist/react-bar.cjs +2 -2
  39. package/dist/react-bar.cjs.map +1 -1
  40. package/dist/react-bar.d.cts +4 -2
  41. package/dist/react-bar.d.ts +4 -2
  42. package/dist/react-bar.js +2 -2
  43. package/dist/react-bar.js.map +1 -1
  44. package/dist/react-host.d.cts +124 -3
  45. package/dist/react-host.d.ts +124 -3
  46. package/dist/react-menu.cjs +2 -2
  47. package/dist/react-menu.cjs.map +1 -1
  48. package/dist/react-menu.d.cts +20 -4
  49. package/dist/react-menu.d.ts +20 -4
  50. package/dist/react-menu.js +2 -2
  51. package/dist/react-menu.js.map +1 -1
  52. package/dist/react-overflow.cjs +2 -2
  53. package/dist/react-overflow.cjs.map +1 -1
  54. package/dist/react-overflow.d.cts +109 -2
  55. package/dist/react-overflow.d.ts +109 -2
  56. package/dist/react-overflow.js +2 -2
  57. package/dist/react-overflow.js.map +1 -1
  58. package/dist/react-palette.cjs +1 -1
  59. package/dist/react-palette.cjs.map +1 -1
  60. package/dist/react-palette.d.cts +4 -2
  61. package/dist/react-palette.d.ts +4 -2
  62. package/dist/react-palette.js +1 -1
  63. package/dist/react-palette.js.map +1 -1
  64. package/dist/react-selection.cjs +2 -2
  65. package/dist/react-selection.cjs.map +1 -1
  66. package/dist/react-selection.d.cts +4 -2
  67. package/dist/react-selection.d.ts +4 -2
  68. package/dist/react-selection.js +2 -2
  69. package/dist/react-selection.js.map +1 -1
  70. package/dist/react-sheet.cjs +2 -2
  71. package/dist/react-sheet.cjs.map +1 -1
  72. package/dist/react-sheet.d.cts +4 -2
  73. package/dist/react-sheet.d.ts +4 -2
  74. package/dist/react-sheet.js +2 -2
  75. package/dist/react-sheet.js.map +1 -1
  76. package/package.json +3 -3
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  * Framework-neutral transfer contracts (Alchemy Operate, CONTRACT §2.3). All data crossing this boundary is JSON.
3
3
  * Moved from `@ai-matrx/kit/content-transfer` in ALC-16; the format-adapter contract lives in `./contract`.
4
4
  */
5
- type Json$1 = null | boolean | number | string | Json$1[] | {
6
- [key: string]: Json$1;
5
+ type Json$2 = null | boolean | number | string | Json$2[] | {
6
+ [key: string]: Json$2;
7
7
  };
8
8
  type Payload =
9
9
  /** A text payload may name its language (code blocks): downloads take its extension, Markdown fences it. */
@@ -16,15 +16,15 @@ type Payload =
16
16
  text: string;
17
17
  } | {
18
18
  kind: "json";
19
- value: Json$1;
19
+ value: Json$2;
20
20
  } | {
21
21
  kind: "rows";
22
- rows: Record<string, Json$1>[];
22
+ rows: Record<string, Json$2>[];
23
23
  columns: Column[];
24
24
  } | {
25
25
  kind: "registered";
26
26
  format: string;
27
- value: Json$1;
27
+ value: Json$2;
28
28
  };
29
29
  type Column = {
30
30
  id: string;
@@ -38,9 +38,9 @@ type Column = {
38
38
  dataType?: string;
39
39
  order?: number;
40
40
  required?: boolean;
41
- defaultValue?: Json$1;
42
- validation?: Json$1;
43
- metadata?: Json$1;
41
+ defaultValue?: Json$2;
42
+ validation?: Json$2;
43
+ metadata?: Json$2;
44
44
  };
45
45
  };
46
46
  type ExportProjection = {
@@ -93,21 +93,21 @@ type Snapshot = RawSnapshot & {
93
93
  type QuerySnapshot = {
94
94
  sourceId: string;
95
95
  revision: string;
96
- query: Json$1;
96
+ query: Json$2;
97
97
  order: {
98
98
  field: string;
99
99
  direction: "asc" | "desc";
100
100
  }[];
101
101
  };
102
102
  type ExportPage = {
103
- rows: Record<string, Json$1>[];
103
+ rows: Record<string, Json$2>[];
104
104
  nextCursor: string | null;
105
105
  coverage: Coverage;
106
106
  consistencyToken: string | null;
107
107
  };
108
108
  type CapturedAllMatching = {
109
109
  query: QuerySnapshot;
110
- getRowId(row: Record<string, Json$1>): string;
110
+ getRowId(row: Record<string, Json$2>): string;
111
111
  page(request: {
112
112
  query: QuerySnapshot;
113
113
  cursor: string | null;
@@ -147,7 +147,7 @@ type Draft = {
147
147
  };
148
148
  type Transform = {
149
149
  id: string;
150
- apply(draft: Draft, options: Json$1, signal: AbortSignal): Promise<Draft>;
150
+ apply(draft: Draft, options: Json$2, signal: AbortSignal): Promise<Draft>;
151
151
  };
152
152
  type Artifact = {
153
153
  snapshotId: string;
@@ -338,9 +338,9 @@ type BoundAllMatching = {
338
338
  type NormalizeTransferJsonOptions = {
339
339
  omitUndefinedObjectProperties?: boolean;
340
340
  };
341
- declare function normalizeTransferJson(value: unknown, options?: NormalizeTransferJsonOptions): Json$1;
341
+ declare function normalizeTransferJson(value: unknown, options?: NormalizeTransferJsonOptions): Json$2;
342
342
  /** Column ids are identities, never an alternative data address. */
343
- declare function readTransferCell(row: Record<string, Json$1>, column: Column): Json$1 | undefined;
343
+ declare function readTransferCell(row: Record<string, Json$2>, column: Column): Json$2 | undefined;
344
344
  declare function projectSnapshot(raw: RawSnapshot, projection: ExportProjection): Snapshot;
345
345
  declare function directSource(payload: Payload, options?: Partial<Pick<RawSnapshot, "id" | "sourceId" | "revision" | "label" | "coverage" | "unsavedChanges" | "sections" | "limits"> & {
346
346
  projection: ExportProjection;
@@ -385,7 +385,7 @@ declare function reduceJson(draft: Draft, options: {
385
385
  maxArrayItems?: number | null;
386
386
  }): Draft;
387
387
  declare function collectAllMatching(c: BoundAllMatching, signal: AbortSignal): Promise<{
388
- rows: Record<string, Json$1>[];
388
+ rows: Record<string, Json$2>[];
389
389
  coverage: RawSnapshot["coverage"];
390
390
  omissions: Omission[];
391
391
  error?: ContentTransferError;
@@ -433,6 +433,16 @@ declare function createSurfaceTransferHandle(options: {
433
433
  /** Applies explicit preparation/source limits with exact omissions; token target is advisory. */
434
434
  declare function applyTransferLimits(draft: Draft, limits: Partial<TransferLimits>): Draft;
435
435
 
436
+ /**
437
+ * Matrx Alchemy — Declare types (CONTRACT §2.1).
438
+ *
439
+ * A declaration is plain JSON data (chair ruling N2): no functions, no classes,
440
+ * no React. Runtime behaviour lives in `MountedSurface` (`/declare/host`) and in
441
+ * handler registrations, never here. Zero React, zero `@ai-matrx/agents`.
442
+ */
443
+ type Json$1 = null | boolean | number | string | Json$1[] | {
444
+ [key: string]: Json$1;
445
+ };
436
446
  /** Unchanged from `ui_surface_value_type_chk`. */
437
447
  type ValueType = "string" | "number" | "boolean" | "object" | "array" | "document";
438
448
  type Classification = "ordinary" | "secret" | "credential";
@@ -639,6 +649,75 @@ interface ClickTarget {
639
649
  host?: unknown;
640
650
  }
641
651
 
652
+ /**
653
+ * Surface Actions — the shapes (Applets CONTRACTS §6, FROZEN; exported here as a superset).
654
+ *
655
+ * An Action is one thing a surface can have done to it, by a person, an agent,
656
+ * an applet or an automation: a write target, a client tool, a Kind Action or a
657
+ * row in `ui.ui_surface_action`. All of them project onto `ActionDeclaration`
658
+ * (`./declarations.ts`) and run through ONE runner (`./surface-run.ts`). There is
659
+ * never a second registry: `name` IS the alchemy action id, and `category`
660
+ * defaults to "applet" in alchemy's closed `ActionCategory` union.
661
+ *
662
+ * Types only — no runtime code, no React, no `@ai-matrx/agents`. `/ports`
663
+ * imports from here, so this file imports nothing but local types.
664
+ */
665
+
666
+ /** Who started a run. */
667
+ type ActionCaller = "person" | "agent" | "applet" | "automation";
668
+ /** Who may run it without a click: "manual" = only a person; "ask" = proposed first; "auto" = runs. */
669
+ type ActionPolicy = "ask" | "auto" | "manual";
670
+ interface ActionContext {
671
+ runId: string;
672
+ by: ActionCaller;
673
+ viewerId: string | null;
674
+ surfaceName?: string;
675
+ policy?: ActionPolicy;
676
+ }
677
+ /** The system's own sentence, never thrown into a UI. Superset of RecordsError (CONTRACTS §0 `AppletError`). */
678
+ interface ActionError {
679
+ code: string;
680
+ message: string;
681
+ retryable: boolean;
682
+ diagnostic?: string;
683
+ }
684
+ /** CONTRACTS §0 `Result<T>`. */
685
+ type ActionResult<T = unknown> = {
686
+ ok: true;
687
+ data: T;
688
+ } | {
689
+ ok: false;
690
+ error: ActionError;
691
+ };
692
+ /** `POST /actions/run` (aidream) — the request. */
693
+ interface ServerActionRunRequest {
694
+ name: string;
695
+ input: Json$1;
696
+ by: ActionCaller;
697
+ surface_name?: string;
698
+ policy?: ActionPolicy;
699
+ }
700
+ /** `POST /actions/run` (aidream) — the response. */
701
+ interface ServerActionRunResponse {
702
+ status: "applied" | "proposed" | "refused";
703
+ message: string;
704
+ receipts: Json$1[];
705
+ proposal?: Json$1;
706
+ }
707
+ /** A mandate run request: by alias (an Action's `target`) or by key. */
708
+ type MandateRunRequest = ({
709
+ alias: string;
710
+ key?: never;
711
+ } | {
712
+ key: string;
713
+ alias?: never;
714
+ }) & {
715
+ variables: Record<string, Json$1>;
716
+ userInput?: string;
717
+ by: ActionCaller;
718
+ policy: ActionPolicy;
719
+ };
720
+
642
721
  /**
643
722
  * `@ai-matrx/alchemy/ports` — the host ports (Matrx Alchemy CONTRACT §2.2).
644
723
  *
@@ -687,6 +766,12 @@ interface ClickTarget {
687
766
  * to an overlay carrying the session as data)
688
767
  * transferKnobs absent → no organization default recipe; a person's own
689
768
  * default (persistence) or the package's Full applies
769
+ * serverActions absent → `runs: "server"` Actions refuse with "not available here"
770
+ * mandates absent → `runs: "mandate"` Actions refuse the same way
771
+ * pageActions absent → `runs: "page"` Actions refuse the same way
772
+ * approvals absent → a policy "ask" Action started by an agent, applet or automation is refused
773
+ * ("needs a person's approval"); a person's own click is the approval
774
+ * (`runs: "navigate"` goes through `overlay.navigate`; overlay absent → refused)
690
775
  *
691
776
  * Types only: this entry has no runtime code.
692
777
  */
@@ -832,6 +917,36 @@ interface AlchemyHostPorts {
832
917
  window?: WindowPort;
833
918
  /** Organization default recipes, resolved by the host's Feature Knob ladder (platform → organization). */
834
919
  transferKnobs?: TransferKnobsPort;
920
+ /** aidream `POST /actions/run`: a registered server action by name, as the caller, under the apply-policy cascade. */
921
+ serverActions?: ServerActionPort;
922
+ /** Runs a mandate by alias or key (the host binds `@ai-matrx/agents`; alchemy never imports it). */
923
+ mandates?: MandatePort;
924
+ /** The page's own handlers for `runs: "page"` Actions (in an applet frame: routed as `{ action, input, runId }`). */
925
+ pageActions?: PageActionPort;
926
+ /** Asks a person, in place, to approve an `ask` Action that an agent, applet or automation started. */
927
+ approvals?: ApprovalPort;
928
+ }
929
+ interface ServerActionPort {
930
+ run(request: ServerActionRunRequest): Promise<ServerActionRunResponse>;
931
+ }
932
+ interface MandatePort {
933
+ /** Resolves with the run's result (a kind envelope or JSON); rejects with the system's sentence. */
934
+ run(request: MandateRunRequest): Promise<unknown>;
935
+ }
936
+ interface ApprovalRequest {
937
+ action: string;
938
+ label: string;
939
+ description: string;
940
+ input: Json;
941
+ by: "agent" | "applet" | "automation";
942
+ surfaceName: string;
943
+ }
944
+ interface ApprovalPort {
945
+ /** Resolves true when the person approves; false when they decline or dismiss. */
946
+ ask(request: ApprovalRequest): Promise<boolean>;
947
+ }
948
+ interface PageActionPort {
949
+ dispatch(name: string, input: unknown, context: ActionContext): Promise<ActionResult<unknown>>;
835
950
  }
836
951
  /**
837
952
  * Why a port and not `OverlayHostPort`: an overlay's `data` is `Json`, and a live preparation
@@ -983,7 +1098,7 @@ type TransferOperation = {
983
1098
  } | {
984
1099
  kind: "transform";
985
1100
  transform: string;
986
- options?: Json$1;
1101
+ options?: Json$2;
987
1102
  } | {
988
1103
  kind: "prepare-for-ai";
989
1104
  instruction?: string;
@@ -1028,7 +1143,7 @@ interface ServerOperation {
1028
1143
  statement: string;
1029
1144
  supports(payload: Payload): boolean;
1030
1145
  /** Projected draft only; sensitivity already applied. */
1031
- toInput(draft: Draft): Json$1;
1146
+ toInput(draft: Draft): Json$2;
1032
1147
  }
1033
1148
  /** Delivery results: `copy` and `download` only ever deliver, degrade, cancel or fail (CONTRACT R16). */
1034
1149
  type DeliveryOutcome = Extract<TransferOutcome, {
@@ -1110,7 +1225,7 @@ interface PreparationSession {
1110
1225
  readonly snapshot: Snapshot;
1111
1226
  readonly draft: Draft;
1112
1227
  /** Deterministic transform, AI preparation or any registered transform — bumps draft.revision. */
1113
- transform(id: string, options: Json$1, signal: AbortSignal): Promise<Draft>;
1228
+ transform(id: string, options: Json$2, signal: AbortSignal): Promise<Draft>;
1114
1229
  /** A manual edit. */
1115
1230
  edit(payload: Payload): Draft;
1116
1231
  /** Keep only these sections (ids from the snapshot). */
@@ -1228,19 +1343,19 @@ declare function patternBelow(pattern: string, pointer: string): boolean;
1228
1343
  /** The path a person reads and copies: `/messages/*\/metadata` → `data.messages[*].metadata`. */
1229
1344
  declare function displayPath(pointer: string, root?: string): string;
1230
1345
  /** Exactly `JSON.stringify(value).length`, computed once per object (large sources stay cheap). */
1231
- declare function jsonSize(value: Json$1): number;
1346
+ declare function jsonSize(value: Json$2): number;
1232
1347
  /** The size a person expects for a node: a string's own length, else its JSON. */
1233
- declare function nodeChars(value: Json$1): number;
1348
+ declare function nodeChars(value: Json$2): number;
1234
1349
  /**
1235
1350
  * The one-line stub that stands in for a node: its header (a string's first line, an object's
1236
1351
  * short scalar members) and its original size.
1237
1352
  */
1238
- declare function stubText(value: Json$1): string;
1353
+ declare function stubText(value: Json$2): string;
1239
1354
  /** Every concrete pointer in `value` a pattern names (for counts, diffs and highlights). */
1240
- declare function concretePointers(value: Json$1, pattern: string): string[];
1355
+ declare function concretePointers(value: Json$2, pattern: string): string[];
1241
1356
  /** Apply exclusions and per-node limits to a JSON value; returns the new value and what changed. */
1242
- declare function applyStructureToJson(value: Json$1, recipe: StructureRecipe, labels?: Readonly<Record<string, string>>): {
1243
- value: Json$1;
1357
+ declare function applyStructureToJson(value: Json$2, recipe: StructureRecipe, labels?: Readonly<Record<string, string>>): {
1358
+ value: Json$2;
1244
1359
  omissions: Omission[];
1245
1360
  };
1246
1361
  /** A JSON (or registered) draft with the person's structure choices applied. */
@@ -1250,7 +1365,7 @@ declare function applyJsonStructure(draft: Draft, recipe: StructureRecipe, label
1250
1365
  * earlier sibling is excluded). Null when the node is excluded or cut; a stubbed ancestor answers
1251
1366
  * with the stub's own pointer.
1252
1367
  */
1253
- declare function outputPointer(value: Json$1, recipe: StructureRecipe, pointer: string): string | null;
1368
+ declare function outputPointer(value: Json$2, recipe: StructureRecipe, pointer: string): string | null;
1254
1369
  declare function isProseSection(section: Section): boolean;
1255
1370
  interface SectionLayout {
1256
1371
  text: string;
@@ -1321,4 +1436,4 @@ declare function setPersonDefaultRecipe(ports: Pick<AlchemyHostPorts, "persisten
1321
1436
  [key: string]: Json;
1322
1437
  } | null): Promise<void>;
1323
1438
 
1324
- export { type AgentPayloadEnvironment, type AgentPayloadInput, type AiPreparation, type Artifact, type BoundAllMatching, type BuiltInFormatId, type CapturedAllMatching, type CapturedSource, type Column, ContentTransferError, type Coverage, type Delivery, type DeliveryOutcome, type Draft, EMPTY_STRUCTURE, type ExportPage, type ExportProjection, FORMAT_ADAPTERS, type FormatAdapter, type FormatEngine, type FormatOptions, type Json$1 as Json, type NodeLimit, type NormalizeTransferJsonOptions, type Notice, type Omission, type OutputSpan, type PageAction, type Payload, type PreparationOptions, type PreparationSession, type QuerySnapshot, type RawSnapshot, type Receipt, type RecipeOrigin, type ResolvedRecipe, type SealedArtifact, type Section, type SectionLayout, type ServerOperation, type Snapshot, type Source, type SpanMap, type StructureRecipe, type SurfaceHandle, TRANSFER_DEFAULT_RECIPE_SETTING, type TransferLimits, type TransferOperation, type TransferOutcome, type TransferPreferenceOverrides, type TransferPreferences, type TransferScope, type TransferSurfaceDeclaration, type TransferTarget, type Transform, type Transport, type WriteTargetRef, agentPayloadWithSpans, applyJsonStructure, applySectionSelection, applySectionStructure, applyStructureToJson, applyTransferLimits, assembleAgentPayload, buildAgentPayload, capture, collectAllMatching, concretePointers, createBrowserTransport, createDelivery, createDraft, createSurfaceTransferHandle, deriveDraft, directSource, displayPath, editDraft, eligibleFormats, exportedColumns, extensionForLanguage, fenceJsonBlock, formatAdapter, isProseSection, isSealed, joinPointer, jsonSize, jsonTextSpans, nodeChars, normalizeTransferJson, offsetSpans, openPreparation, outputPointer, patternBelow, patternCovers, patternMatches, pointerSegments, projectSnapshot, readTransferCell, recipeKey, reduceJson, refreshDraft, resetDraft, resolveDefaultRecipe, resolveTransferPreferences, resolveTransferRegistry, sealArtifact, sealDraft, sealTransferArtifact, sectionLayout, serialize, setPersonDefaultRecipe, sourceChanged, spanMap, stubText, transformRows, undoDraft };
1439
+ export { type AgentPayloadEnvironment, type AgentPayloadInput, type AiPreparation, type Artifact, type BoundAllMatching, type BuiltInFormatId, type CapturedAllMatching, type CapturedSource, type Column, ContentTransferError, type Coverage, type Delivery, type DeliveryOutcome, type Draft, EMPTY_STRUCTURE, type ExportPage, type ExportProjection, FORMAT_ADAPTERS, type FormatAdapter, type FormatEngine, type FormatOptions, type Json$2 as Json, type NodeLimit, type NormalizeTransferJsonOptions, type Notice, type Omission, type OutputSpan, type PageAction, type Payload, type PreparationOptions, type PreparationSession, type QuerySnapshot, type RawSnapshot, type Receipt, type RecipeOrigin, type ResolvedRecipe, type SealedArtifact, type Section, type SectionLayout, type ServerOperation, type Snapshot, type Source, type SpanMap, type StructureRecipe, type SurfaceHandle, TRANSFER_DEFAULT_RECIPE_SETTING, type TransferLimits, type TransferOperation, type TransferOutcome, type TransferPreferenceOverrides, type TransferPreferences, type TransferScope, type TransferSurfaceDeclaration, type TransferTarget, type Transform, type Transport, type WriteTargetRef, agentPayloadWithSpans, applyJsonStructure, applySectionSelection, applySectionStructure, applyStructureToJson, applyTransferLimits, assembleAgentPayload, buildAgentPayload, capture, collectAllMatching, concretePointers, createBrowserTransport, createDelivery, createDraft, createSurfaceTransferHandle, deriveDraft, directSource, displayPath, editDraft, eligibleFormats, exportedColumns, extensionForLanguage, fenceJsonBlock, formatAdapter, isProseSection, isSealed, joinPointer, jsonSize, jsonTextSpans, nodeChars, normalizeTransferJson, offsetSpans, openPreparation, outputPointer, patternBelow, patternCovers, patternMatches, pointerSegments, projectSnapshot, readTransferCell, recipeKey, reduceJson, refreshDraft, resetDraft, resolveDefaultRecipe, resolveTransferPreferences, resolveTransferRegistry, sealArtifact, sealDraft, sealTransferArtifact, sectionLayout, serialize, setPersonDefaultRecipe, sourceChanged, spanMap, stubText, transformRows, undoDraft };
package/dist/menu.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var menu_exports={};__export(menu_exports,{APPROVED_HEADINGS:()=>APPROVED_HEADINGS,CATEGORY_GROUP:()=>CATEGORY_GROUP,GROUP_ORDER:()=>GROUP_ORDER,INLINE_SECTION_MAX:()=>INLINE_SECTION_MAX,MENU_LAYOUT_SETTING:()=>MENU_LAYOUT_SETTING,PLATFORM_MENU_LAYOUT:()=>PLATFORM_MENU_LAYOUT,UnapprovedHeadingError:()=>UnapprovedHeadingError,actionToNode:()=>actionToNode,bar:()=>bar,buildMenuModel:()=>buildMenuModel,context:()=>context,defaultHeadingPolicy:()=>defaultHeadingPolicy,filterPalette:()=>filterPalette,findNode:()=>findNode,foldUnderSubject:()=>foldUnderSubject,isApprovedHeading:()=>isApprovedHeading,isContainerWord:()=>isContainerWord,layouts:()=>layouts,leafIds:()=>leafIds,menuHeadings:()=>menuHeadings,overflow:()=>overflow,palette:()=>palette,resolveMenuLayout:()=>resolveMenuLayout,sectionLabel:()=>sectionLabel,selectionToolbar:()=>selectionToolbar,sheet:()=>sheet,unapprovedHeadings:()=>unapprovedHeadings,withExpandedChildren:()=>withExpandedChildren});module.exports=__toCommonJS(menu_exports);var APPROVED_HEADINGS=new Set(["Save","Copy as","Export","Edit","Creator tools","Admin","App","Compare","History"]),CONTAINER_WORD=/^(more|more…|more\.\.\.|other|others|misc|miscellaneous|additional|extra|extras|actions|options|advanced|stuff|everything else)$/i,TARGET_SHAPE=/^(Cell|Row|Column|Table|Range|Selection|Record) · .+$/;function isContainerWord(label){return CONTAINER_WORD.test(label.trim())}function isApprovedHeading(label,kind="group",approved=APPROVED_HEADINGS){let l=label.trim();return!l||isContainerWord(l)?!1:approved.has(l)?!0:kind==="target"||TARGET_SHAPE.test(l)}function defaultHeadingPolicy(){return globalThis.process?.env?.NODE_ENV==="production"?"inline":"throw"}var UnapprovedHeadingError=class extends Error{heading;constructor(heading,where){super(`Menu heading "${heading}" (${where}) is not an approved heading. Remedy: show these rows inline \u2014 declare no section (or kind "target" for the clicked thing's own label) \u2014 or ask Arman to add the name to APPROVED_HEADINGS in @ai-matrx/alchemy/menu. A generic container word ("More\u2026", "Other") is never approved.`),this.name="UnapprovedHeadingError",this.heading=heading}};function menuHeadings(model){let out=[],walk=nodes=>{for(let n of nodes)n.kind==="submenu"&&(out.push(n.label),walk(n.children))};for(let s of model.sections)s.label&&out.push(s.label),walk(s.nodes);return[...new Set(out)]}function unapprovedHeadings(model,approved=APPROVED_HEADINGS){let out=new Set;for(let s of model.sections){s.label&&!isApprovedHeading(s.label,s.primary||s.target?"target":"group",approved)&&out.add(s.label);let walk=nodes=>{for(let n of nodes)n.kind==="submenu"&&(isContainerWord(n.label)&&out.add(n.label),walk(n.children))};walk(s.nodes)}return[...out]}var CATEGORY_GROUP={clipboard:"clipboard",copy:"transfer",export:"transfer",save:"document",share:"document",edit:"editable",history:"history",ai:"ai",ask:"ai",listen:"tools",study:"tools",app:"tools",feedback:"quick",creator:"admin",admin:"admin","surface-info":"surface-info"},GROUP_ORDER=["clipboard","editable","transfer","document","history","ai","tools","quick","surface","admin","surface-info"],CATEGORY_ORDER=["clipboard","edit","copy","export","save","share","history","ask","ai","listen","study","app","feedback","creator","admin","surface-info"];function translate(i18n,key,fallback){if(!i18n)return fallback;try{return i18n.t(key,fallback)}catch{return fallback}}function actionToNode(resolved,target,options={}){let{action,eligibility}=resolved,rawLabel=typeof action.label=="function"?action.label(target):action.label,label=translate(options.i18n,action.labelKey??action.id,rawLabel),base={id:action.id,actionId:action.id,...action.icon!==void 0?{icon:action.icon}:{},...action.placement!==void 0?{placement:action.placement}:{}};if(action.expand)return{...base,kind:"submenu",label,lazy:!0,loading:!1,...action.pending?.(target)?{pending:!0}:{},children:[],...action.emptyLabel!==void 0?{emptyLabel:action.emptyLabel}:{}};if(action.href!==void 0){let href=typeof action.href=="function"?action.href(target):action.href;return{...base,kind:"link",label,href}}if(action.toggle)return{...base,kind:"checkbox",label,checked:action.pressed?.(target)??!1,...action.description!==void 0?{description:action.description}:{}};let state=action.stateIcon?.(target)??null,pressed=action.pressed?.(target),hint=action.hint!==void 0?translate(options.i18n,action.hintKey??`${action.id}.hint`,action.hint):void 0;return{...base,kind:"item",label,...action.iconTone!==void 0?{iconTone:action.iconTone}:{},...state?{stateIcon:state}:{},...action.description!==void 0?{description:action.description}:{},...hint!==void 0?{hint}:{},...pressed!==void 0?{pressed}:{},...action.destructive?{destructive:!0}:{},...action.preserveSelection?{preserveSelection:!0}:{},...action.keepsMenuOpen?{keepsMenuOpen:!0}:{},...eligibility.status==="unavailable-verb"||eligibility.status==="unavailable"?{unavailable:{sentence:eligibility.sentence}}:{}}}function buildMenuModel(target,resolved,options={}){let policy=options.headingPolicy??defaultHeadingPolicy();resolved=resolved.map(r=>{let sec=r.action.section;if(r.action.expand&&isContainerWord(typeof r.action.label=="string"?r.action.label:"")&&policy==="throw")throw new UnapprovedHeadingError(r.action.label,`submenu of ${r.action.id}`);if(!sec)return r;let kind=sec.kind??(sec.primary?"target":"group");if(isApprovedHeading(sec.label,kind))return r;if(policy==="throw")throw new UnapprovedHeadingError(sec.label,`section of ${r.action.id}`);let{section:_dropped,...rest}=r.action;return{...r,action:rest}});let ordered=resolved.map((r,index)=>({r,index})).sort((a,b)=>{let ca=CATEGORY_ORDER.indexOf(a.r.action.category),cb=CATEGORY_ORDER.indexOf(b.r.action.category);if(ca!==cb)return ca-cb;let oa=a.r.action.order??0,ob=b.r.action.order??0;return oa!==ob?oa-ob:a.index-b.index}).map(x=>x.r),sections=new Map,roles={},explicit=new Map;resolved.forEach((r,index)=>{let sec=r.action.section;if(!sec)return;let entry=explicit.get(sec.id)??{group:CATEGORY_GROUP[r.action.category],rows:[]};entry.rows.push({r,index}),explicit.set(sec.id,entry)});let placed=new Set;for(let r of ordered){let sec=r.action.section;if(sec&&placed.has(sec.id))continue;let group=sec?explicit.get(sec.id)?.group??CATEGORY_GROUP[r.action.category]:CATEGORY_GROUP[r.action.category],sectionId=sec?`section:${sec.id}`:group,section=sections.get(sectionId);section||(section={id:sectionId,group,nodes:[],...sec?{label:translate(options.i18n,sec.labelKey??`section.${sec.id}`,sec.label)}:{},...sec?.icon!==void 0?{icon:sec.icon}:{},...sec?.primary?{primary:!0}:{},...sec&&(sec.kind??(sec.primary?"target":"group"))==="target"?{target:!0}:{}},sections.set(sectionId,section));let rows=sec?(explicit.get(sec.id)?.rows??[]).sort((x,y)=>(x.r.action.order??0)-(y.r.action.order??0)||x.index-y.index).map(x=>x.r):[r];sec&&placed.add(sec.id);for(let row of rows)row.action.startsGroup&&section.nodes.length>0&&section.nodes.push({kind:"separator",id:`${row.action.id}:group`}),section.nodes.push(actionToNode(row,target,options)),row.action.verb&&(roles[row.action.verb]=row.action.id)}let list=[...sections.values()].sort((a,b)=>!!a.primary!=!!b.primary?a.primary?-1:1:GROUP_ORDER.indexOf(a.group)-GROUP_ORDER.indexOf(b.group)),selected=target.selection?.text?.trim(),content=options.content?.trim(),fieldName=options.contentLabel?.trim();return{header:selected?{label:"Selected",text:target.selection?.text??""}:fieldName?{label:fieldName,text:options.content??""}:content?{label:"Content",text:options.content??""}:null,sections:list,roles}}function withExpandedChildren(model,nodeId,children){let visit=nodes=>nodes.map(n=>n.kind!=="submenu"?n:n.id===nodeId?children==="loading"?{...n,loading:!0}:{...n,lazy:!1,loading:!1,pending:!1,children}:{...n,children:visit(n.children)});return{...model,sections:model.sections.map(s=>({...s,nodes:visit(s.nodes)}))}}function findNode(nodes,id){for(let n of nodes){if(n.id===id)return n;if(n.kind==="submenu"){let hit=findNode(n.children,id);if(hit)return hit}}return null}var INLINE_SECTION_MAX=3;function sectionLabel(section){return section.label??null}function isLeaf(node){return node.kind==="item"||node.kind==="checkbox"||node.kind==="link"}function collect(nodes,out){for(let n of nodes)isLeaf(n)?out.push(n.id):n.kind==="submenu"&&(n.lazy?out.push(n.id):collect(n.children,out))}function leafIds(arranged){let out=[];if(Array.isArray(arranged))for(let s of arranged)collect(s.nodes,out);else if("leaves"in arranged)for(let l of arranged.leaves)out.push(l.node.id);else if("primary"in arranged&&"overflow"in arranged){collect(arranged.primary,out);for(let s of arranged.overflow)collect(s.nodes,out)}else if("root"in arranged)collect(arranged.strip,out),collect(arranged.root,out);else if("more"in arranged){collect(arranged.strip,out);for(let s of arranged.more)collect(s.nodes,out)}else if("filterable"in arranged){collect(arranged.strip,out);for(let s of arranged.sections)collect(s.nodes,out)}else for(let s of arranged.sections)collect(s.nodes,out);return[...new Set(out)].sort()}function fold(section){return{kind:"submenu",id:`${section.id}:fold`,label:section.label,...section.icon!==void 0?{icon:section.icon}:{},children:section.nodes}}function rowCount(nodes){return nodes.filter(n=>n.kind!=="separator"&&n.kind!=="label").length}function stripOf(model){let clipboard=model.sections.find(s=>s.group==="clipboard"&&!s.primary);return clipboard?clipboard.nodes.filter(isLeaf):[]}function clipboardRest(model){let clipboard=model.sections.find(s=>s.group==="clipboard"&&!s.primary);return clipboard?clipboard.nodes.filter(n=>!isLeaf(n)&&n.kind==="submenu"):[]}function placementOf(node){return"placement"in node&&node.placement?node.placement:"overflow"}function classic(model){return{strip:[],sections:model.sections,filterable:!1}}function tiered(model,filterable){let strip=stripOf(model),sections=[],rest=clipboardRest(model);for(let s of model.sections){if(s.group==="clipboard"&&!s.primary){rest.length&&sections.push({...s,nodes:rest});continue}if(s.primary){sections.push(s);continue}if(!s.label||s.group==="surface-info"||rowCount(s.nodes)<=3){sections.push(s);continue}sections.push({id:s.id,group:s.group,nodes:[fold({...s,label:s.label})]})}return{strip,sections,filterable}}function context(model,arrangement){return arrangement==="classic"?classic(model):tiered(model,arrangement==="command")}function foldUnderSubject(arranged,subject){let label=subject.trim(),primary=arranged.sections.filter(s=>s.primary),rest=arranged.sections.filter(s=>!s.primary);if(!label||primary.length===0||rest.length===0&&arranged.strip.length===0)return arranged;let children=[];arranged.strip.length&&children.push(...arranged.strip);for(let s of rest)rowCount(s.nodes)!==0&&(children.length&&children.push({kind:"separator",id:`${s.id}:subject-sep`}),s.label&&children.push({kind:"label",id:`${s.id}:subject-label`,label:s.label}),children.push(...s.nodes));return rowCount(children)===0?{...arranged,strip:[]}:{strip:[],filterable:!1,sections:[...primary,{id:"subject",group:rest[0]?.group??primary[0].group,nodes:[{kind:"submenu",id:"subject:fold",label,children}]}]}}function foldNamed(sections){return sections.map(s=>s.label&&!s.primary&&rowCount(s.nodes)>3?{id:s.id,group:s.group,nodes:[fold({...s,label:s.label})]}:s)}function overflow(model){return foldNamed(model.sections)}function bar(model,_options={}){let primary=[],overflowSections=[];for(let s of model.sections){let rest=[];for(let n of s.nodes){if(n.kind==="separator"||n.kind==="label"){rest.push(n);continue}let p=placementOf(n);(p==="primary"||p==="both")&&primary.push(n),(p==="overflow"||p==="both")&&rest.push(n)}rowCount(rest)>0&&overflowSections.push({...s,nodes:rest})}return{primary,overflow:foldNamed(overflowSections)}}function selectionToolbar(model){let strip=[],more=[];for(let s of model.sections){let rest=[];for(let n of s.nodes)s.group==="clipboard"&&!s.primary&&isLeaf(n)||placementOf(n)!=="overflow"?strip.push(n):rest.push(n);rowCount(rest)>0&&more.push({...s,nodes:rest})}return{strip,more}}function sheet(model){let strip=stripOf(model),root=[],rest=clipboardRest(model);for(let s of model.sections){let nodes=s.group==="clipboard"&&!s.primary?rest:s.nodes;rowCount(nodes)!==0&&(root.length&&root.push({kind:"separator",id:`${s.id}:sep`}),s.primary||!s.label||rowCount(nodes)<=3?(s.label&&root.push({kind:"label",id:`${s.id}:label`,label:s.label}),root.push(...nodes)):root.push(fold({...s,label:s.label,nodes})))}return{strip,root}}function palette(model){let leaves=[],walk=(nodes,trail)=>{for(let n of nodes)isLeaf(n)?leaves.push({node:n,breadcrumb:trail}):n.kind==="submenu"&&(n.lazy?leaves.push({node:n,breadcrumb:trail}):walk(n.children,[...trail,n.label]))};for(let s of model.sections)walk(s.nodes,s.label?[s.label]:[]);return{leaves}}function filterPalette(arranged,query){let words=query.toLowerCase().split(/\s+/).filter(Boolean);return words.length?{leaves:arranged.leaves.filter(l=>{let hay=`${l.node.label} ${l.breadcrumb.join(" ")}`.toLowerCase();return words.every(w=>hay.includes(w))})}:arranged}var layouts={bar,overflow,context,selectionToolbar,sheet,palette};var PLATFORM_MENU_LAYOUT={context:"command",density:"comfortable"},MENU_LAYOUT_SETTING="alchemy.menu.layout",ARRANGEMENTS=new Set(["classic","tiered","command"]),DENSITIES=new Set(["comfortable","compact"]);async function resolveMenuLayout(ports,organizationId){let org=ports.knobs?.layout(organizationId)??null,base={...PLATFORM_MENU_LAYOUT,...org??{}},own=null;try{own=await ports.persistence?.readSetting(MENU_LAYOUT_SETTING)??null}catch{own=null}if(own&&typeof own=="object"&&!Array.isArray(own)){let o=own;return{context:typeof o.context=="string"&&ARRANGEMENTS.has(o.context)?o.context:base.context,density:typeof o.density=="string"&&DENSITIES.has(o.density)?o.density:base.density}}return base}
1
+ "use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var menu_exports={};__export(menu_exports,{APPROVED_HEADINGS:()=>APPROVED_HEADINGS,CATEGORY_GROUP:()=>CATEGORY_GROUP,GROUP_ORDER:()=>GROUP_ORDER,INLINE_SECTION_MAX:()=>INLINE_SECTION_MAX,MENU_LAYOUT_SETTING:()=>MENU_LAYOUT_SETTING,PLATFORM_MENU_LAYOUT:()=>PLATFORM_MENU_LAYOUT,UnapprovedHeadingError:()=>UnapprovedHeadingError,actionToNode:()=>actionToNode,bar:()=>bar,buildMenuModel:()=>buildMenuModel,context:()=>context,defaultHeadingPolicy:()=>defaultHeadingPolicy,filterPalette:()=>filterPalette,findNode:()=>findNode,foldUnderSubject:()=>foldUnderSubject,isApprovedHeading:()=>isApprovedHeading,isContainerWord:()=>isContainerWord,layouts:()=>layouts,leafIds:()=>leafIds,menuHeadings:()=>menuHeadings,overflow:()=>overflow,palette:()=>palette,resolveMenuLayout:()=>resolveMenuLayout,sectionLabel:()=>sectionLabel,selectionToolbar:()=>selectionToolbar,sheet:()=>sheet,unapprovedHeadings:()=>unapprovedHeadings,withExpandedChildren:()=>withExpandedChildren});module.exports=__toCommonJS(menu_exports);var APPROVED_HEADINGS=new Set(["Save","Copy as","Export","Edit","Creator tools","Admin","App","Compare","History"]),CONTAINER_WORD=/^(more|more…|more\.\.\.|other|others|misc|miscellaneous|additional|extra|extras|actions|options|advanced|stuff|everything else)$/i,TARGET_SHAPE=/^(Cell|Row|Column|Table|Range|Selection|Record) · .+$/;function isContainerWord(label){return CONTAINER_WORD.test(label.trim())}function isApprovedHeading(label,kind="group",approved=APPROVED_HEADINGS){let l=label.trim();return!l||isContainerWord(l)?!1:approved.has(l)?!0:kind==="target"||TARGET_SHAPE.test(l)}function defaultHeadingPolicy(){return globalThis.process?.env?.NODE_ENV==="production"?"inline":"throw"}var UnapprovedHeadingError=class extends Error{heading;constructor(heading,where){super(`Menu heading "${heading}" (${where}) is not an approved heading. Remedy: show these rows inline \u2014 declare no section (or kind "target" for the clicked thing's own label) \u2014 or ask Arman to add the name to APPROVED_HEADINGS in @ai-matrx/alchemy/menu. A generic container word ("More\u2026", "Other") is never approved.`),this.name="UnapprovedHeadingError",this.heading=heading}};function menuHeadings(model){let out=[],walk=nodes=>{for(let n of nodes)n.kind==="submenu"&&(out.push(n.label),walk(n.children))};for(let s of model.sections)s.label&&out.push(s.label),walk(s.nodes);return[...new Set(out)]}function unapprovedHeadings(model,approved=APPROVED_HEADINGS){let out=new Set;for(let s of model.sections){s.label&&!isApprovedHeading(s.label,s.primary||s.target?"target":"group",approved)&&out.add(s.label);let walk=nodes=>{for(let n of nodes)n.kind==="submenu"&&(isContainerWord(n.label)&&out.add(n.label),walk(n.children))};walk(s.nodes)}return[...out]}var CATEGORY_GROUP={clipboard:"clipboard",copy:"transfer",export:"transfer",save:"document",share:"document",edit:"editable",history:"history",ai:"ai",ask:"ai",listen:"tools",study:"tools",app:"tools",applet:"tools",feedback:"quick",creator:"admin",admin:"admin","surface-info":"surface-info"},GROUP_ORDER=["clipboard","editable","transfer","document","history","ai","tools","quick","surface","admin","surface-info"],CATEGORY_ORDER=["clipboard","edit","copy","export","save","share","history","ask","ai","listen","study","app","applet","feedback","creator","admin","surface-info"];function translate(i18n,key,fallback){if(!i18n)return fallback;try{return i18n.t(key,fallback)}catch{return fallback}}function actionToNode(resolved,target,options={}){let{action,eligibility}=resolved,rawLabel=typeof action.label=="function"?action.label(target):action.label,label=translate(options.i18n,action.labelKey??action.id,rawLabel),base={id:action.id,actionId:action.id,...action.icon!==void 0?{icon:action.icon}:{},...action.placement!==void 0?{placement:action.placement}:{}};if(action.expand)return{...base,kind:"submenu",label,lazy:!0,loading:!1,...action.pending?.(target)?{pending:!0}:{},children:[],...action.emptyLabel!==void 0?{emptyLabel:action.emptyLabel}:{}};if(action.href!==void 0){let href=typeof action.href=="function"?action.href(target):action.href;return{...base,kind:"link",label,href}}if(action.toggle)return{...base,kind:"checkbox",label,checked:action.pressed?.(target)??!1,...action.description!==void 0?{description:action.description}:{}};let state=action.stateIcon?.(target)??null,pressed=action.pressed?.(target),hint=action.hint!==void 0?translate(options.i18n,action.hintKey??`${action.id}.hint`,action.hint):void 0;return{...base,kind:"item",label,...action.iconTone!==void 0?{iconTone:action.iconTone}:{},...state?{stateIcon:state}:{},...action.description!==void 0?{description:action.description}:{},...hint!==void 0?{hint}:{},...pressed!==void 0?{pressed}:{},...action.destructive?{destructive:!0}:{},...action.preserveSelection?{preserveSelection:!0}:{},...action.keepsMenuOpen?{keepsMenuOpen:!0}:{},...eligibility.status==="unavailable-verb"||eligibility.status==="unavailable"?{unavailable:{sentence:eligibility.sentence}}:{}}}function buildMenuModel(target,resolved,options={}){let policy=options.headingPolicy??defaultHeadingPolicy();resolved=resolved.map(r=>{let sec=r.action.section;if(r.action.expand&&isContainerWord(typeof r.action.label=="string"?r.action.label:"")&&policy==="throw")throw new UnapprovedHeadingError(r.action.label,`submenu of ${r.action.id}`);if(!sec)return r;let kind=sec.kind??(sec.primary?"target":"group");if(isApprovedHeading(sec.label,kind))return r;if(policy==="throw")throw new UnapprovedHeadingError(sec.label,`section of ${r.action.id}`);let{section:_dropped,...rest}=r.action;return{...r,action:rest}});let ordered=resolved.map((r,index)=>({r,index})).sort((a,b)=>{let ca=CATEGORY_ORDER.indexOf(a.r.action.category),cb=CATEGORY_ORDER.indexOf(b.r.action.category);if(ca!==cb)return ca-cb;let oa=a.r.action.order??0,ob=b.r.action.order??0;return oa!==ob?oa-ob:a.index-b.index}).map(x=>x.r),sections=new Map,roles={},explicit=new Map;resolved.forEach((r,index)=>{let sec=r.action.section;if(!sec)return;let entry=explicit.get(sec.id)??{group:CATEGORY_GROUP[r.action.category],rows:[]};entry.rows.push({r,index}),explicit.set(sec.id,entry)});let placed=new Set;for(let r of ordered){let sec=r.action.section;if(sec&&placed.has(sec.id))continue;let group=sec?explicit.get(sec.id)?.group??CATEGORY_GROUP[r.action.category]:CATEGORY_GROUP[r.action.category],sectionId=sec?`section:${sec.id}`:group,section=sections.get(sectionId);section||(section={id:sectionId,group,nodes:[],...sec?{label:translate(options.i18n,sec.labelKey??`section.${sec.id}`,sec.label)}:{},...sec?.icon!==void 0?{icon:sec.icon}:{},...sec?.primary?{primary:!0}:{},...sec&&(sec.kind??(sec.primary?"target":"group"))==="target"?{target:!0}:{}},sections.set(sectionId,section));let rows=sec?(explicit.get(sec.id)?.rows??[]).sort((x,y)=>(x.r.action.order??0)-(y.r.action.order??0)||x.index-y.index).map(x=>x.r):[r];sec&&placed.add(sec.id);for(let row of rows)row.action.startsGroup&&section.nodes.length>0&&section.nodes.push({kind:"separator",id:`${row.action.id}:group`}),section.nodes.push(actionToNode(row,target,options)),row.action.verb&&(roles[row.action.verb]=row.action.id)}let list=[...sections.values()].sort((a,b)=>!!a.primary!=!!b.primary?a.primary?-1:1:GROUP_ORDER.indexOf(a.group)-GROUP_ORDER.indexOf(b.group)),selected=target.selection?.text?.trim(),content=options.content?.trim(),fieldName=options.contentLabel?.trim();return{header:selected?{label:"Selected",text:target.selection?.text??""}:fieldName?{label:fieldName,text:options.content??""}:content?{label:"Content",text:options.content??""}:null,sections:list,roles}}function withExpandedChildren(model,nodeId,children){let visit=nodes=>nodes.map(n=>n.kind!=="submenu"?n:n.id===nodeId?children==="loading"?{...n,loading:!0}:{...n,lazy:!1,loading:!1,pending:!1,children}:{...n,children:visit(n.children)});return{...model,sections:model.sections.map(s=>({...s,nodes:visit(s.nodes)}))}}function findNode(nodes,id){for(let n of nodes){if(n.id===id)return n;if(n.kind==="submenu"){let hit=findNode(n.children,id);if(hit)return hit}}return null}var INLINE_SECTION_MAX=3;function sectionLabel(section){return section.label??null}function isLeaf(node){return node.kind==="item"||node.kind==="checkbox"||node.kind==="link"}function collect(nodes,out){for(let n of nodes)isLeaf(n)?out.push(n.id):n.kind==="submenu"&&(n.lazy?out.push(n.id):collect(n.children,out))}function leafIds(arranged){let out=[];if(Array.isArray(arranged))for(let s of arranged)collect(s.nodes,out);else if("leaves"in arranged)for(let l of arranged.leaves)out.push(l.node.id);else if("primary"in arranged&&"overflow"in arranged){collect(arranged.primary,out);for(let s of arranged.overflow)collect(s.nodes,out)}else if("root"in arranged)collect(arranged.strip,out),collect(arranged.root,out);else if("more"in arranged){collect(arranged.strip,out);for(let s of arranged.more)collect(s.nodes,out)}else if("filterable"in arranged){collect(arranged.strip,out);for(let s of arranged.sections)collect(s.nodes,out)}else for(let s of arranged.sections)collect(s.nodes,out);return[...new Set(out)].sort()}function fold(section){return{kind:"submenu",id:`${section.id}:fold`,label:section.label,...section.icon!==void 0?{icon:section.icon}:{},children:section.nodes}}function rowCount(nodes){return nodes.filter(n=>n.kind!=="separator"&&n.kind!=="label").length}function stripOf(model){let clipboard=model.sections.find(s=>s.group==="clipboard"&&!s.primary);return clipboard?clipboard.nodes.filter(isLeaf):[]}function clipboardRest(model){let clipboard=model.sections.find(s=>s.group==="clipboard"&&!s.primary);return clipboard?clipboard.nodes.filter(n=>!isLeaf(n)&&n.kind==="submenu"):[]}function placementOf(node){return"placement"in node&&node.placement?node.placement:"overflow"}function classic(model){return{strip:[],sections:model.sections,filterable:!1}}function tiered(model,filterable){let strip=stripOf(model),sections=[],rest=clipboardRest(model);for(let s of model.sections){if(s.group==="clipboard"&&!s.primary){rest.length&&sections.push({...s,nodes:rest});continue}if(s.primary){sections.push(s);continue}if(!s.label||s.group==="surface-info"||rowCount(s.nodes)<=3){sections.push(s);continue}sections.push({id:s.id,group:s.group,nodes:[fold({...s,label:s.label})]})}return{strip,sections,filterable}}function context(model,arrangement){return arrangement==="classic"?classic(model):tiered(model,arrangement==="command")}function foldUnderSubject(arranged,subject){let label=subject.trim(),primary=arranged.sections.filter(s=>s.primary),rest=arranged.sections.filter(s=>!s.primary);if(!label||primary.length===0||rest.length===0&&arranged.strip.length===0)return arranged;let children=[];arranged.strip.length&&children.push(...arranged.strip);for(let s of rest)rowCount(s.nodes)!==0&&(children.length&&children.push({kind:"separator",id:`${s.id}:subject-sep`}),s.label&&children.push({kind:"label",id:`${s.id}:subject-label`,label:s.label}),children.push(...s.nodes));return rowCount(children)===0?{...arranged,strip:[]}:{strip:[],filterable:!1,sections:[...primary,{id:"subject",group:rest[0]?.group??primary[0].group,nodes:[{kind:"submenu",id:"subject:fold",label,children}]}]}}function foldNamed(sections){return sections.map(s=>s.label&&!s.primary&&rowCount(s.nodes)>3?{id:s.id,group:s.group,nodes:[fold({...s,label:s.label})]}:s)}function overflow(model){return foldNamed(model.sections)}function bar(model,_options={}){let primary=[],overflowSections=[];for(let s of model.sections){let rest=[];for(let n of s.nodes){if(n.kind==="separator"||n.kind==="label"){rest.push(n);continue}let p=placementOf(n);(p==="primary"||p==="both")&&primary.push(n),(p==="overflow"||p==="both")&&rest.push(n)}rowCount(rest)>0&&overflowSections.push({...s,nodes:rest})}return{primary,overflow:foldNamed(overflowSections)}}function selectionToolbar(model){let strip=[],more=[];for(let s of model.sections){let rest=[];for(let n of s.nodes)s.group==="clipboard"&&!s.primary&&isLeaf(n)||placementOf(n)!=="overflow"?strip.push(n):rest.push(n);rowCount(rest)>0&&more.push({...s,nodes:rest})}return{strip,more}}function sheet(model){let strip=stripOf(model),root=[],rest=clipboardRest(model);for(let s of model.sections){let nodes=s.group==="clipboard"&&!s.primary?rest:s.nodes;rowCount(nodes)!==0&&(root.length&&root.push({kind:"separator",id:`${s.id}:sep`}),s.primary||!s.label||rowCount(nodes)<=3?(s.label&&root.push({kind:"label",id:`${s.id}:label`,label:s.label}),root.push(...nodes)):root.push(fold({...s,label:s.label,nodes})))}return{strip,root}}function palette(model){let leaves=[],walk=(nodes,trail)=>{for(let n of nodes)isLeaf(n)?leaves.push({node:n,breadcrumb:trail}):n.kind==="submenu"&&(n.lazy?leaves.push({node:n,breadcrumb:trail}):walk(n.children,[...trail,n.label]))};for(let s of model.sections)walk(s.nodes,s.label?[s.label]:[]);return{leaves}}function filterPalette(arranged,query){let words=query.toLowerCase().split(/\s+/).filter(Boolean);return words.length?{leaves:arranged.leaves.filter(l=>{let hay=`${l.node.label} ${l.breadcrumb.join(" ")}`.toLowerCase();return words.every(w=>hay.includes(w))})}:arranged}var layouts={bar,overflow,context,selectionToolbar,sheet,palette};var PLATFORM_MENU_LAYOUT={context:"command",density:"comfortable"},MENU_LAYOUT_SETTING="alchemy.menu.layout",ARRANGEMENTS=new Set(["classic","tiered","command"]),DENSITIES=new Set(["comfortable","compact"]);async function resolveMenuLayout(ports,organizationId){let org=ports.knobs?.layout(organizationId)??null,base={...PLATFORM_MENU_LAYOUT,...org??{}},own=null;try{own=await ports.persistence?.readSetting(MENU_LAYOUT_SETTING)??null}catch{own=null}if(own&&typeof own=="object"&&!Array.isArray(own)){let o=own;return{context:typeof o.context=="string"&&ARRANGEMENTS.has(o.context)?o.context:base.context,density:typeof o.density=="string"&&DENSITIES.has(o.density)?o.density:base.density}}return base}
2
2
  //# sourceMappingURL=menu.cjs.map
package/dist/menu.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/menu.ts","../src/menu/headings.ts","../src/menu/model.ts","../src/menu/layouts.ts","../src/menu/knobs.ts"],"sourcesContent":["/**\n * `@ai-matrx/alchemy/menu` — the one menu model and its pure layouts\n * (Matrx Alchemy CONTRACT §2.5). Framework-neutral: icons are keys, no React.\n */\nexport type {\n ArrangedBar,\n ArrangedContext,\n ArrangedPalette,\n ArrangedSelectionToolbar,\n ArrangedSheet,\n ContextArrangement,\n Density,\n LayoutName,\n MenuCheckboxNode,\n MenuGroup,\n MenuItemNode,\n MenuLabelNode,\n MenuLeafNode,\n MenuLinkNode,\n MenuModel,\n MenuNode,\n MenuSection,\n MenuSeparatorNode,\n MenuSubmenuNode,\n} from \"./menu/types\";\nexport {\n CATEGORY_GROUP,\n GROUP_ORDER,\n actionToNode,\n buildMenuModel,\n findNode,\n withExpandedChildren,\n type BuildMenuModelOptions,\n} from \"./menu/model\";\nexport {\n INLINE_SECTION_MAX,\n bar,\n context,\n foldUnderSubject,\n filterPalette,\n layouts,\n leafIds,\n overflow,\n palette,\n sectionLabel,\n selectionToolbar,\n sheet,\n} from \"./menu/layouts\";\nexport { MENU_LAYOUT_SETTING, PLATFORM_MENU_LAYOUT, resolveMenuLayout } from \"./menu/knobs\";\nexport {\n APPROVED_HEADINGS,\n UnapprovedHeadingError,\n defaultHeadingPolicy,\n isApprovedHeading,\n isContainerWord,\n menuHeadings,\n unapprovedHeadings,\n type HeadingKind,\n type HeadingPolicy,\n} from \"./menu/headings\";\n","/**\n * THE ONE APPROVED-HEADING LIST (ALC-15, chair rulings 7 and D1; the Lossless\n * Law and the vocabulary law). A heading a person sees above or around rows\n * must be a name the classic menus used before agents started coining names.\n *\n * 🚨 ADDING A HEADING TO THIS LIST IS ARMAN'S CALL — never an agent's.\n *\n * Two kinds of section heading exist:\n * - a GROUP heading (\"Copy as\", \"History\"): must be in APPROVED_HEADINGS;\n * - a TARGET label, the thing the person clicked, supplied by the surface at\n * run time (\"Cell · Visit date\", \"Row · Widget A\", \"This finding\", the\n * surface's own label): allowed as declared (`section.kind: \"target\"`, or\n * the \"<Target> · <name>\" shape), except a generic container word.\n * A generic container word (\"More…\", \"Other\", \"Actions\") is never a heading.\n * An unapproved heading renders its rows INLINE in production; in development\n * and tests `buildMenuModel` throws with the remedy.\n */\nimport type { MenuModel, MenuNode } from \"./types\";\n\n/** Names with their provenance (the commit where each classic menu used them). */\nexport const APPROVED_HEADINGS: ReadonlySet<string> = new Set([\n \"Save\", // rich-document menuStructure, e5b8d01229 (2026-05-19)\n \"Copy as\", // rich-document menuStructure, e5b8d01229 (2026-05-19)\n \"Export\", // rich-document menuStructure, e5b8d01229 (2026-05-19)\n \"Edit\", // rich-document menuStructure, e5b8d01229 (2026-05-19)\n \"Creator tools\", // rich-document menuStructure, e5b8d01229 (2026-05-19)\n \"Admin\", // rich-document menuStructure, e5b8d01229 (2026-05-19)\n \"App\", // rich-document menuStructure, e5b8d01229 (2026-05-19)\n \"Compare\", // rich-document menuStructure, 2e3a8a9fdf (2026-06-11)\n \"History\", // context-menu-v3 tiered layout — the one grouping Arman approved, 2026-08-22\n]);\n\n// Ruling (chair, 2026-09-26, per R26): \"Conversation\" is a TARGET label (`kind: \"target\"`) — it names the thing its rows act on (the whole conversation), like \"Cell · …\" and \"Table · …\"; it is not a group heading and is not added above.\n\nconst CONTAINER_WORD =\n /^(more|more…|more\\.\\.\\.|other|others|misc|miscellaneous|additional|extra|extras|actions|options|advanced|stuff|everything else)$/i;\n\n/** \"Cell · Visit date\", \"Row · Widget A\", \"Column · Price\", \"Table · Route sheet\". */\nconst TARGET_SHAPE = /^(Cell|Row|Column|Table|Range|Selection|Record) · .+$/;\n\nexport function isContainerWord(label: string): boolean {\n return CONTAINER_WORD.test(label.trim());\n}\n\nexport type HeadingKind = \"group\" | \"target\";\n\n/** Is this heading allowed? */\nexport function isApprovedHeading(label: string, kind: HeadingKind = \"group\", approved: ReadonlySet<string> = APPROVED_HEADINGS): boolean {\n const l = label.trim();\n if (!l || isContainerWord(l)) return false;\n if (approved.has(l)) return true;\n return kind === \"target\" || TARGET_SHAPE.test(l);\n}\n\nexport type HeadingPolicy = \"throw\" | \"inline\";\n\n/** Development and tests throw; production shows the rows inline. */\nexport function defaultHeadingPolicy(): HeadingPolicy {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.NODE_ENV;\n return env === \"production\" ? \"inline\" : \"throw\";\n}\n\nexport class UnapprovedHeadingError extends Error {\n readonly heading: string;\n constructor(heading: string, where: string) {\n super(\n `Menu heading \"${heading}\" (${where}) is not an approved heading. Remedy: show these rows inline — declare no section ` +\n `(or kind \"target\" for the clicked thing's own label) — or ask Arman to add the name to APPROVED_HEADINGS in ` +\n `@ai-matrx/alchemy/menu. A generic container word (\"More…\", \"Other\") is never approved.`,\n );\n this.name = \"UnapprovedHeadingError\";\n this.heading = heading;\n }\n}\n\n/** Every heading in a model: section labels, folds, and submenu rows at any depth. */\nexport function menuHeadings(model: Pick<MenuModel, \"sections\">): string[] {\n const out: string[] = [];\n const walk = (nodes: readonly MenuNode[]) => {\n for (const n of nodes) {\n if (n.kind === \"submenu\") {\n out.push(n.label);\n walk(n.children);\n }\n }\n };\n for (const s of model.sections) {\n if (s.label) out.push(s.label);\n walk(s.nodes);\n }\n return [...new Set(out)];\n}\n\n/** Section headings not in the approved set (container words are never approved). */\nexport function unapprovedHeadings(model: Pick<MenuModel, \"sections\">, approved: ReadonlySet<string> = APPROVED_HEADINGS): string[] {\n const out = new Set<string>();\n for (const s of model.sections) {\n if (s.label && !isApprovedHeading(s.label, s.primary || s.target ? \"target\" : \"group\", approved)) out.add(s.label);\n const walk = (nodes: readonly MenuNode[]) => {\n for (const n of nodes) if (n.kind === \"submenu\") {\n if (isContainerWord(n.label)) out.add(n.label);\n walk(n.children);\n }\n };\n walk(s.nodes);\n }\n return [...out];\n}\n","/**\n * buildMenuModel — WHAT exists at a click target, as one framework-neutral\n * tree (CONTRACT §2.5). Categories GROUP, never gate: eligibility already ran\n * in the registry; this only arranges the resolved set into sections.\n */\nimport type { Action, ActionCategory, ClickTarget, ResolvedAction } from \"../actions/types\";\nimport type { I18nPort } from \"../ports\";\nimport type { MenuGroup, MenuModel, MenuNode, MenuSection, MenuSubmenuNode } from \"./types\";\nimport { UnapprovedHeadingError, defaultHeadingPolicy, isApprovedHeading, isContainerWord, type HeadingPolicy } from \"./headings\";\n\n/** THE category → menu group table (CONTRACT §2.5). */\nexport const CATEGORY_GROUP: Readonly<Record<ActionCategory, MenuGroup>> = {\n clipboard: \"clipboard\",\n copy: \"transfer\",\n export: \"transfer\",\n save: \"document\",\n share: \"document\",\n edit: \"editable\",\n history: \"history\",\n ai: \"ai\",\n ask: \"ai\",\n listen: \"tools\",\n study: \"tools\",\n app: \"tools\",\n feedback: \"quick\",\n creator: \"admin\",\n admin: \"admin\",\n \"surface-info\": \"surface-info\",\n};\n\n/** Classic order of the groups. `surface-info` is engine-built and always last. */\nexport const GROUP_ORDER: readonly MenuGroup[] = [\n \"clipboard\",\n \"editable\",\n \"transfer\",\n \"document\",\n \"history\",\n \"ai\",\n \"tools\",\n \"quick\",\n \"surface\",\n \"admin\",\n \"surface-info\",\n];\n\nconst CATEGORY_ORDER: readonly ActionCategory[] = [\n \"clipboard\",\n \"edit\",\n \"copy\",\n \"export\",\n \"save\",\n \"share\",\n \"history\",\n \"ask\",\n \"ai\",\n \"listen\",\n \"study\",\n \"app\",\n \"feedback\",\n \"creator\",\n \"admin\",\n \"surface-info\",\n];\n\nexport interface BuildMenuModelOptions {\n /** What an unapproved heading does: \"throw\" (development/tests) or \"inline\" (production). */\n headingPolicy?: HeadingPolicy;\n i18n?: I18nPort;\n /** Content the menu acts on when nothing is selected. */\n content?: string | null;\n /**\n * The name of the field the content is (a text box's label, \"Body\"). The\n * header then names the field — never \"Content: {{reply.body}}\", its raw text.\n */\n contentLabel?: string | null;\n}\n\nfunction translate(i18n: I18nPort | undefined, key: string, fallback: string): string {\n if (!i18n) return fallback;\n try {\n return i18n.t(key, fallback);\n } catch {\n return fallback;\n }\n}\n\n/** One resolved action → one model node (item, checkbox, link or lazy submenu). */\nexport function actionToNode(\n resolved: ResolvedAction,\n target: ClickTarget,\n options: BuildMenuModelOptions = {},\n): MenuNode {\n const { action, eligibility } = resolved;\n const rawLabel = typeof action.label === \"function\" ? action.label(target) : action.label;\n const label = translate(options.i18n, action.labelKey ?? action.id, rawLabel);\n const base = {\n id: action.id,\n actionId: action.id,\n ...(action.icon !== undefined ? { icon: action.icon } : {}),\n ...(action.placement !== undefined ? { placement: action.placement } : {}),\n };\n if (action.expand) {\n const node: MenuSubmenuNode = {\n ...base,\n kind: \"submenu\",\n label,\n lazy: true,\n loading: false,\n ...(action.pending?.(target) ? { pending: true } : {}),\n children: [],\n ...(action.emptyLabel !== undefined ? { emptyLabel: action.emptyLabel } : {}),\n };\n return node;\n }\n if (action.href !== undefined) {\n const href = typeof action.href === \"function\" ? action.href(target) : action.href;\n return { ...base, kind: \"link\", label, href };\n }\n if (action.toggle) {\n return {\n ...base,\n kind: \"checkbox\",\n label,\n checked: action.pressed?.(target) ?? false,\n ...(action.description !== undefined ? { description: action.description } : {}),\n };\n }\n const state = action.stateIcon?.(target) ?? null;\n const pressed = action.pressed?.(target);\n const hint = action.hint !== undefined ? translate(options.i18n, action.hintKey ?? `${action.id}.hint`, action.hint) : undefined;\n return {\n ...base,\n kind: \"item\",\n label,\n ...(action.iconTone !== undefined ? { iconTone: action.iconTone } : {}),\n ...(state ? { stateIcon: state } : {}),\n ...(action.description !== undefined ? { description: action.description } : {}),\n ...(hint !== undefined ? { hint } : {}),\n ...(pressed !== undefined ? { pressed } : {}),\n ...(action.destructive ? { destructive: true } : {}),\n ...(action.preserveSelection ? { preserveSelection: true } : {}),\n ...(action.keepsMenuOpen ? { keepsMenuOpen: true } : {}),\n ...(eligibility.status === \"unavailable-verb\" || eligibility.status === \"unavailable\"\n ? { unavailable: { sentence: eligibility.sentence } }\n : {}),\n };\n}\n\nexport function buildMenuModel(\n target: ClickTarget,\n resolved: readonly ResolvedAction[],\n options: BuildMenuModelOptions = {},\n): MenuModel {\n // THE ONE HEADING CHECK, for every host: an unapproved heading's rows go\n // inline (production) or the build throws with the remedy (dev/tests).\n const policy = options.headingPolicy ?? defaultHeadingPolicy();\n resolved = resolved.map((r) => {\n const sec = r.action.section;\n if (r.action.expand && isContainerWord(typeof r.action.label === \"string\" ? r.action.label : \"\") && policy === \"throw\") {\n throw new UnapprovedHeadingError(r.action.label as string, `submenu of ${r.action.id}`);\n }\n if (!sec) return r;\n const kind = sec.kind ?? (sec.primary ? \"target\" : \"group\");\n if (isApprovedHeading(sec.label, kind)) return r;\n if (policy === \"throw\") throw new UnapprovedHeadingError(sec.label, `section of ${r.action.id}`);\n const { section: _dropped, ...rest } = r.action;\n return { ...r, action: rest };\n });\n const ordered = resolved\n .map((r, index) => ({ r, index }))\n .sort((a, b) => {\n const ca = CATEGORY_ORDER.indexOf(a.r.action.category);\n const cb = CATEGORY_ORDER.indexOf(b.r.action.category);\n if (ca !== cb) return ca - cb;\n const oa = a.r.action.order ?? 0;\n const ob = b.r.action.order ?? 0;\n if (oa !== ob) return oa - ob;\n return a.index - b.index;\n })\n .map((x) => x.r);\n\n const sections = new Map<string, MenuSection>();\n const roles: Record<string, string> = {};\n // An explicit section sits in the group of its first-registered action and\n // keeps its own row order (order, then registration) across categories.\n const explicit = new Map<string, { group: MenuGroup; rows: { r: ResolvedAction; index: number }[] }>();\n resolved.forEach((r, index) => {\n const sec = r.action.section;\n if (!sec) return;\n const entry = explicit.get(sec.id) ?? { group: CATEGORY_GROUP[r.action.category], rows: [] };\n entry.rows.push({ r, index });\n explicit.set(sec.id, entry);\n });\n const placed = new Set<string>();\n for (const r of ordered) {\n const sec = r.action.section;\n if (sec && placed.has(sec.id)) continue;\n const group = sec ? (explicit.get(sec.id)?.group ?? CATEGORY_GROUP[r.action.category]) : CATEGORY_GROUP[r.action.category];\n const sectionId = sec ? `section:${sec.id}` : group;\n let section = sections.get(sectionId);\n if (!section) {\n section = {\n id: sectionId,\n group,\n nodes: [],\n ...(sec ? { label: translate(options.i18n, sec.labelKey ?? `section.${sec.id}`, sec.label) } : {}),\n ...(sec?.icon !== undefined ? { icon: sec.icon } : {}),\n ...(sec?.primary ? { primary: true } : {}),\n ...(sec && (sec.kind ?? (sec.primary ? \"target\" : \"group\")) === \"target\" ? { target: true } : {}),\n };\n sections.set(sectionId, section);\n }\n const rows = sec\n ? (explicit.get(sec.id)?.rows ?? [])\n .sort((x, y) => (x.r.action.order ?? 0) - (y.r.action.order ?? 0) || x.index - y.index)\n .map((x) => x.r)\n : [r];\n if (sec) placed.add(sec.id);\n for (const row of rows) {\n if (row.action.startsGroup && section.nodes.length > 0) {\n section.nodes.push({ kind: \"separator\", id: `${row.action.id}:group` });\n }\n section.nodes.push(actionToNode(row, target, options));\n if (row.action.verb) roles[row.action.verb] = row.action.id;\n }\n }\n\n const list = [...sections.values()].sort((a, b) => {\n if (Boolean(a.primary) !== Boolean(b.primary)) return a.primary ? -1 : 1;\n return GROUP_ORDER.indexOf(a.group) - GROUP_ORDER.indexOf(b.group);\n });\n\n const selected = target.selection?.text?.trim();\n const content = options.content?.trim();\n const fieldName = options.contentLabel?.trim();\n const header = selected\n ? { label: \"Selected\", text: target.selection?.text ?? \"\" }\n : fieldName\n ? { label: fieldName, text: options.content ?? \"\" }\n : content\n ? { label: \"Content\", text: options.content ?? \"\" }\n : null;\n\n return { header, sections: list, roles };\n}\n\n/** Fill a lazy submenu's rows once they load (pure: returns a new model). */\n/**\n * AN EXPANDED SUBMENU KEEPS ITS GROUPS (DATA-V2-BASICS-2, 2026-09-28). A provider's rows arrive flat,\n * so a submenu drew \"Highlight row\" and \"Delete row…\" as neighbours with no divider, while the same\n * rows in a top-level menu sit in their sections. A divider now goes wherever the rows' `section`\n * changes — a row with no section and a row with one are different groups too.\n */\nexport function withGroupDividers(children: readonly Action[], nodes: readonly MenuNode[]): MenuNode[] {\n const out: MenuNode[] = [];\n children.forEach((child, i) => {\n const node = nodes[i];\n if (!node) return;\n if (i > 0 && (child.startsGroup || (children[i - 1]!.section?.id ?? null) !== (child.section?.id ?? null))) {\n out.push({ kind: \"separator\", id: `${child.id}:group` });\n }\n out.push(node);\n });\n return out;\n}\n\nexport function withExpandedChildren(model: MenuModel, nodeId: string, children: MenuNode[] | \"loading\"): MenuModel {\n const visit = (nodes: MenuNode[]): MenuNode[] =>\n nodes.map((n) => {\n if (n.kind !== \"submenu\") return n;\n if (n.id === nodeId) {\n return children === \"loading\" ? { ...n, loading: true } : { ...n, lazy: false, loading: false, pending: false, children };\n }\n return { ...n, children: visit(n.children) };\n });\n return { ...model, sections: model.sections.map((s) => ({ ...s, nodes: visit(s.nodes) })) };\n}\n\n/** Find a node anywhere in a tree. */\nexport function findNode(nodes: readonly MenuNode[], id: string): MenuNode | null {\n for (const n of nodes) {\n if (n.id === id) return n;\n if (n.kind === \"submenu\") {\n const hit = findNode(n.children, id);\n if (hit) return hit;\n }\n }\n return null;\n}\n","/**\n * Layouts — pure arrangements of one MenuModel (CONTRACT §2.5).\n *\n * Every layout is a function over the SAME model: it decides which nodes sit\n * at the top level, which fold under a named submenu and which go into a\n * strip. None adds, drops or renames a leaf — `leafIds(arranged)` equals\n * `leafIds(model)` for every layout (THE LOSSLESS LAW, proven by test).\n *\n * The clipboard group IS the universal verb strip: in the strip layouts it is\n * drawn icon-only; elsewhere its rows are drawn in place. Unavailable verbs\n * (the only greyed nodes, R1 c) appear in every layout identically.\n */\nimport type {\n ArrangedBar,\n ArrangedContext,\n ArrangedPalette,\n ArrangedSelectionToolbar,\n ArrangedSheet,\n ContextArrangement,\n MenuLeafNode,\n MenuModel,\n MenuNode,\n MenuSection,\n MenuSubmenuNode,\n} from \"./types\";\n\n/** A section with more rows than this folds under its label in tiered/command/sheet. */\nexport const INLINE_SECTION_MAX = 3;\n\n/**\n * A section's heading is ONLY the name its actions declare (an approved\n * heading the classic menu already uses: \"Save as\", \"Copy as\", \"History\").\n * A group with no declared name has no heading and is never folded — its rows\n * show inline. The package never coins a heading (chair ruling, ALC-15).\n */\nexport function sectionLabel(section: MenuSection): string | null {\n return section.label ?? null;\n}\n\nfunction isLeaf(node: MenuNode): node is MenuLeafNode {\n return node.kind === \"item\" || node.kind === \"checkbox\" || node.kind === \"link\";\n}\n\n/** Every actionable id in a node list, depth-first. A lazy submenu counts as one leaf. */\nfunction collect(nodes: readonly MenuNode[], out: string[]): void {\n for (const n of nodes) {\n if (isLeaf(n)) out.push(n.id);\n else if (n.kind === \"submenu\") {\n if (n.lazy) out.push(n.id);\n else collect(n.children, out);\n }\n }\n}\n\ntype Arranged =\n | MenuModel\n | ArrangedBar\n | ArrangedContext\n | ArrangedSheet\n | ArrangedPalette\n | ArrangedSelectionToolbar\n | MenuSection[];\n\n/** Proof obligation for every layout: its leaf set equals the model's (sorted, de-duplicated). */\nexport function leafIds(arranged: Arranged): readonly string[] {\n const out: string[] = [];\n if (Array.isArray(arranged)) {\n for (const s of arranged) collect(s.nodes, out);\n } else if (\"leaves\" in arranged) {\n for (const l of arranged.leaves) out.push(l.node.id);\n } else if (\"primary\" in arranged && \"overflow\" in arranged) {\n collect(arranged.primary, out);\n for (const s of arranged.overflow) collect(s.nodes, out);\n } else if (\"root\" in arranged) {\n collect(arranged.strip, out);\n collect(arranged.root, out);\n } else if (\"more\" in arranged) {\n collect(arranged.strip, out);\n for (const s of arranged.more) collect(s.nodes, out);\n } else if (\"filterable\" in arranged) {\n collect(arranged.strip, out);\n for (const s of arranged.sections) collect(s.nodes, out);\n } else {\n for (const s of arranged.sections) collect(s.nodes, out);\n }\n return [...new Set(out)].sort();\n}\n\n/** Only a NAMED section folds; callers check `section.label` first. */\nfunction fold(section: MenuSection & { label: string }): MenuSubmenuNode {\n return {\n kind: \"submenu\",\n id: `${section.id}:fold`,\n label: section.label,\n ...(section.icon !== undefined ? { icon: section.icon } : {}),\n children: section.nodes,\n };\n}\n\nfunction rowCount(nodes: readonly MenuNode[]): number {\n return nodes.filter((n) => n.kind !== \"separator\" && n.kind !== \"label\").length;\n}\n\nfunction stripOf(model: MenuModel): MenuLeafNode[] {\n const clipboard = model.sections.find((s) => s.group === \"clipboard\" && !s.primary);\n return clipboard ? clipboard.nodes.filter(isLeaf) : [];\n}\n\nfunction clipboardRest(model: MenuModel): MenuNode[] {\n const clipboard = model.sections.find((s) => s.group === \"clipboard\" && !s.primary);\n return clipboard ? clipboard.nodes.filter((n) => !isLeaf(n) && n.kind === \"submenu\") : [];\n}\n\nfunction placementOf(node: MenuNode): \"primary\" | \"overflow\" | \"both\" {\n return \"placement\" in node && node.placement ? node.placement : \"overflow\";\n}\n\n/** The historical flat column: every section top-level. */\nfunction classic(model: MenuModel): ArrangedContext {\n return { strip: [], sections: model.sections, filterable: false };\n}\n\n/**\n * Tiered / command: the universal verbs as an icon strip; every other row by\n * its own name; the clicked target's (primary) section first, inline, heading\n * kept; a long non-primary section folds under ITS OWN label (never a coined\n * heading); History is the one approved grouping and already a group.\n */\nfunction tiered(model: MenuModel, filterable: boolean): ArrangedContext {\n const strip = stripOf(model);\n const sections: MenuSection[] = [];\n const rest = clipboardRest(model);\n for (const s of model.sections) {\n if (s.group === \"clipboard\" && !s.primary) {\n if (rest.length) sections.push({ ...s, nodes: rest });\n continue;\n }\n if (s.primary) {\n sections.push(s);\n continue;\n }\n if (!s.label || s.group === \"surface-info\" || rowCount(s.nodes) <= INLINE_SECTION_MAX) {\n // A section keeps its declared heading in EVERY layout (the Lossless\n // Law covers headings: ⋯, the bar's ⋯ and the phone sheet show it too).\n sections.push(s);\n continue;\n }\n sections.push({ id: s.id, group: s.group, nodes: [fold({ ...s, label: s.label })] });\n }\n return { strip, sections, filterable };\n}\n\nexport function context(model: MenuModel, arrangement: ContextArrangement): ArrangedContext {\n if (arrangement === \"classic\") return classic(model);\n return tiered(model, arrangement === \"command\");\n}\n\n/**\n * FOLD UNDER THE SUBJECT — a control that belongs to a CONTAINER of the thing\n * (a tab's \"…\" over the note it shows) draws the container's own rows (the\n * primary sections) on top, and EVERY other row — the verb strip included —\n * under ONE submenu named for the subject (\"Note ▸\"). The label is the\n * subject's name the host supplies (the menu header's label), never a coined\n * group heading. Lossless: rows move, none is dropped or renamed. With no\n * primary section there is nothing to put first, so the arrangement is left\n * as it was. (page-pass /notes, 2026-09-28, Arman's ruling for the tab \"…\".)\n */\nexport function foldUnderSubject(arranged: ArrangedContext, subject: string): ArrangedContext {\n const label = subject.trim();\n const primary = arranged.sections.filter((s) => s.primary);\n const rest = arranged.sections.filter((s) => !s.primary);\n if (!label || primary.length === 0 || (rest.length === 0 && arranged.strip.length === 0)) return arranged;\n const children: MenuNode[] = [];\n if (arranged.strip.length) children.push(...arranged.strip);\n for (const s of rest) {\n if (rowCount(s.nodes) === 0) continue;\n if (children.length) children.push({ kind: \"separator\", id: `${s.id}:subject-sep` });\n if (s.label) children.push({ kind: \"label\", id: `${s.id}:subject-label`, label: s.label });\n children.push(...s.nodes);\n }\n if (rowCount(children) === 0) return { ...arranged, strip: [] };\n return {\n strip: [],\n filterable: false,\n sections: [\n ...primary,\n { id: \"subject\", group: rest[0]?.group ?? primary[0]!.group, nodes: [{ kind: \"submenu\", id: \"subject:fold\", label, children }] },\n ],\n };\n}\n\n/**\n * A long NAMED section folds under its own name (\"Save as ▸\", \"Copy as ▸\");\n * primary sections and unnamed groups stay inline. Folding moves rows, never\n * renames or drops them, and never under a coined heading.\n */\nfunction foldNamed(sections: readonly MenuSection[]): MenuSection[] {\n return sections.map((s) =>\n s.label && !s.primary && rowCount(s.nodes) > INLINE_SECTION_MAX ? { id: s.id, group: s.group, nodes: [fold({ ...s, label: s.label })] } : s,\n );\n}\n\n/** ⋯ overflow: every section in classic order; long named sections fold under their own name. */\nexport function overflow(model: MenuModel): MenuSection[] {\n return foldNamed(model.sections);\n}\n\n/**\n * The action bar: primary buttons inline, EVERYTHING else under ⋯. A \"both\"\n * action is mirrored in both halves (the leaf set is still the model's).\n * `mini` changes presentation only (icon-only primaries), never membership.\n */\nexport function bar(model: MenuModel, _options: { mini?: boolean } = {}): ArrangedBar {\n const primary: MenuNode[] = [];\n const overflowSections: MenuSection[] = [];\n for (const s of model.sections) {\n const rest: MenuNode[] = [];\n for (const n of s.nodes) {\n if (n.kind === \"separator\" || n.kind === \"label\") {\n rest.push(n);\n continue;\n }\n const p = placementOf(n);\n if (p === \"primary\" || p === \"both\") primary.push(n);\n if (p === \"overflow\" || p === \"both\") rest.push(n);\n }\n if (rowCount(rest) > 0) overflowSections.push({ ...s, nodes: rest });\n }\n return { primary, overflow: foldNamed(overflowSections) };\n}\n\n/** Selection toolbar: the verbs plus primary actions as a strip; the rest under More. */\nexport function selectionToolbar(model: MenuModel): ArrangedSelectionToolbar {\n const strip: MenuNode[] = [];\n const more: MenuSection[] = [];\n for (const s of model.sections) {\n const rest: MenuNode[] = [];\n for (const n of s.nodes) {\n const inStrip = (s.group === \"clipboard\" && !s.primary && isLeaf(n)) || placementOf(n) !== \"overflow\";\n if (inStrip) strip.push(n);\n else rest.push(n);\n }\n if (rowCount(rest) > 0) more.push({ ...s, nodes: rest });\n }\n return { strip, more };\n}\n\n/**\n * Mobile bottom sheet: the verb strip on top; primary and short sections\n * inline; longer sections become one drill-down row named by the section.\n * Consumes the model directly, so it can never drift from desktop.\n */\nexport function sheet(model: MenuModel): ArrangedSheet {\n const strip = stripOf(model);\n const root: MenuNode[] = [];\n const rest = clipboardRest(model);\n for (const s of model.sections) {\n const nodes = s.group === \"clipboard\" && !s.primary ? rest : s.nodes;\n if (rowCount(nodes) === 0) continue;\n if (root.length) root.push({ kind: \"separator\", id: `${s.id}:sep` });\n if (s.primary || !s.label || rowCount(nodes) <= INLINE_SECTION_MAX) {\n if (s.label) root.push({ kind: \"label\", id: `${s.id}:label`, label: s.label });\n root.push(...nodes);\n } else {\n root.push(fold({ ...s, label: s.label, nodes }));\n }\n }\n return { strip, root };\n}\n\n/** Command palette: every leaf flattened with its breadcrumb (Linear). */\nexport function palette(model: MenuModel): ArrangedPalette {\n const leaves: ArrangedPalette[\"leaves\"] = [];\n const walk = (nodes: readonly MenuNode[], trail: string[]) => {\n for (const n of nodes) {\n if (isLeaf(n)) leaves.push({ node: n, breadcrumb: trail });\n else if (n.kind === \"submenu\") {\n if (n.lazy) leaves.push({ node: n, breadcrumb: trail });\n else walk(n.children, [...trail, n.label]);\n }\n }\n };\n for (const s of model.sections) walk(s.nodes, s.label ? [s.label] : []);\n return { leaves };\n}\n\n/**\n * A SEARCH REACHES EVERY ROW (clone 2026-10-01: \"Patient\" found nothing while\n * \"Patient reminder card\" sat under AI Actions ▸ My Personal). `palette` lists a\n * lazy submenu as ONE leaf — its label only — because its rows load on open.\n * A layout that searches opens every lazy submenu it can see: these are the\n * ones still to open (not already loading).\n */\nexport function lazySubmenuIds(model: MenuModel | null): string[] {\n if (!model) return [];\n const out: string[] = [];\n const walk = (nodes: readonly MenuNode[]) => {\n for (const n of nodes) {\n if (n.kind !== \"submenu\") continue;\n if (n.lazy) {\n if (!n.loading) out.push(n.id);\n } else walk(n.children);\n }\n };\n for (const s of model.sections) walk(s.nodes);\n return out;\n}\n\n/** True while some submenu's rows are not in the model yet (a search could still find more). */\nexport function paletteStillOpening(model: MenuModel | null): boolean {\n if (!model) return true;\n const walk = (nodes: readonly MenuNode[]): boolean =>\n nodes.some((n) => n.kind === \"submenu\" && (n.lazy || n.loading || n.pending || walk(n.children)));\n return model.sections.some((s) => walk(s.nodes));\n}\n\n/** Filter the palette by a query (every word must match label or breadcrumb). */\nexport function filterPalette(arranged: ArrangedPalette, query: string): ArrangedPalette {\n const words = query.toLowerCase().split(/\\s+/).filter(Boolean);\n if (!words.length) return arranged;\n return {\n leaves: arranged.leaves.filter((l) => {\n const hay = `${l.node.label} ${l.breadcrumb.join(\" \")}`.toLowerCase();\n return words.every((w) => hay.includes(w));\n }),\n };\n}\n\nexport const layouts = { bar, overflow, context, selectionToolbar, sheet, palette };\n","/**\n * N9: layout and density are a PERSON's choice layered over the organization\n * default: persistence (\"alchemy.menu.layout\") → knobs.layout(org) → platform\n * default (command / comfortable, Arman 2026-08-22).\n */\nimport type { AlchemyHostPorts, ContextArrangementKnob, DensityKnob } from \"../ports\";\n\nexport const PLATFORM_MENU_LAYOUT: { context: ContextArrangementKnob; density: DensityKnob } = {\n context: \"command\",\n density: \"comfortable\",\n};\n\nexport const MENU_LAYOUT_SETTING = \"alchemy.menu.layout\";\n\nconst ARRANGEMENTS = new Set<string>([\"classic\", \"tiered\", \"command\"]);\nconst DENSITIES = new Set<string>([\"comfortable\", \"compact\"]);\n\nexport async function resolveMenuLayout(\n ports: Pick<AlchemyHostPorts, \"persistence\" | \"knobs\">,\n organizationId: string | null,\n): Promise<{ context: ContextArrangementKnob; density: DensityKnob }> {\n const org = ports.knobs?.layout(organizationId) ?? null;\n const base = { ...PLATFORM_MENU_LAYOUT, ...(org ?? {}) };\n let own: unknown = null;\n try {\n own = (await ports.persistence?.readSetting(MENU_LAYOUT_SETTING)) ?? null;\n } catch {\n own = null;\n }\n if (own && typeof own === \"object\" && !Array.isArray(own)) {\n const o = own as { context?: unknown; density?: unknown };\n return {\n context: typeof o.context === \"string\" && ARRANGEMENTS.has(o.context) ? (o.context as ContextArrangementKnob) : base.context,\n density: typeof o.density === \"string\" && DENSITIES.has(o.density) ? (o.density as DensityKnob) : base.density,\n };\n }\n return base;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/menu.ts","../src/menu/headings.ts","../src/menu/model.ts","../src/menu/layouts.ts","../src/menu/knobs.ts"],"sourcesContent":["/**\n * `@ai-matrx/alchemy/menu` — the one menu model and its pure layouts\n * (Matrx Alchemy CONTRACT §2.5). Framework-neutral: icons are keys, no React.\n */\nexport type {\n ArrangedBar,\n ArrangedContext,\n ArrangedPalette,\n ArrangedSelectionToolbar,\n ArrangedSheet,\n ContextArrangement,\n Density,\n LayoutName,\n MenuCheckboxNode,\n MenuGroup,\n MenuItemNode,\n MenuLabelNode,\n MenuLeafNode,\n MenuLinkNode,\n MenuModel,\n MenuNode,\n MenuSection,\n MenuSeparatorNode,\n MenuSubmenuNode,\n} from \"./menu/types\";\nexport {\n CATEGORY_GROUP,\n GROUP_ORDER,\n actionToNode,\n buildMenuModel,\n findNode,\n withExpandedChildren,\n type BuildMenuModelOptions,\n} from \"./menu/model\";\nexport {\n INLINE_SECTION_MAX,\n bar,\n context,\n foldUnderSubject,\n filterPalette,\n layouts,\n leafIds,\n overflow,\n palette,\n sectionLabel,\n selectionToolbar,\n sheet,\n} from \"./menu/layouts\";\nexport { MENU_LAYOUT_SETTING, PLATFORM_MENU_LAYOUT, resolveMenuLayout } from \"./menu/knobs\";\nexport {\n APPROVED_HEADINGS,\n UnapprovedHeadingError,\n defaultHeadingPolicy,\n isApprovedHeading,\n isContainerWord,\n menuHeadings,\n unapprovedHeadings,\n type HeadingKind,\n type HeadingPolicy,\n} from \"./menu/headings\";\n","/**\n * THE ONE APPROVED-HEADING LIST (ALC-15, chair rulings 7 and D1; the Lossless\n * Law and the vocabulary law). A heading a person sees above or around rows\n * must be a name the classic menus used before agents started coining names.\n *\n * 🚨 ADDING A HEADING TO THIS LIST IS ARMAN'S CALL — never an agent's.\n *\n * Two kinds of section heading exist:\n * - a GROUP heading (\"Copy as\", \"History\"): must be in APPROVED_HEADINGS;\n * - a TARGET label, the thing the person clicked, supplied by the surface at\n * run time (\"Cell · Visit date\", \"Row · Widget A\", \"This finding\", the\n * surface's own label): allowed as declared (`section.kind: \"target\"`, or\n * the \"<Target> · <name>\" shape), except a generic container word.\n * A generic container word (\"More…\", \"Other\", \"Actions\") is never a heading.\n * An unapproved heading renders its rows INLINE in production; in development\n * and tests `buildMenuModel` throws with the remedy.\n */\nimport type { MenuModel, MenuNode } from \"./types\";\n\n/** Names with their provenance (the commit where each classic menu used them). */\nexport const APPROVED_HEADINGS: ReadonlySet<string> = new Set([\n \"Save\", // rich-document menuStructure, e5b8d01229 (2026-05-19)\n \"Copy as\", // rich-document menuStructure, e5b8d01229 (2026-05-19)\n \"Export\", // rich-document menuStructure, e5b8d01229 (2026-05-19)\n \"Edit\", // rich-document menuStructure, e5b8d01229 (2026-05-19)\n \"Creator tools\", // rich-document menuStructure, e5b8d01229 (2026-05-19)\n \"Admin\", // rich-document menuStructure, e5b8d01229 (2026-05-19)\n \"App\", // rich-document menuStructure, e5b8d01229 (2026-05-19)\n \"Compare\", // rich-document menuStructure, 2e3a8a9fdf (2026-06-11)\n \"History\", // context-menu-v3 tiered layout — the one grouping Arman approved, 2026-08-22\n]);\n\n// Ruling (chair, 2026-09-26, per R26): \"Conversation\" is a TARGET label (`kind: \"target\"`) — it names the thing its rows act on (the whole conversation), like \"Cell · …\" and \"Table · …\"; it is not a group heading and is not added above.\n\nconst CONTAINER_WORD =\n /^(more|more…|more\\.\\.\\.|other|others|misc|miscellaneous|additional|extra|extras|actions|options|advanced|stuff|everything else)$/i;\n\n/** \"Cell · Visit date\", \"Row · Widget A\", \"Column · Price\", \"Table · Route sheet\". */\nconst TARGET_SHAPE = /^(Cell|Row|Column|Table|Range|Selection|Record) · .+$/;\n\nexport function isContainerWord(label: string): boolean {\n return CONTAINER_WORD.test(label.trim());\n}\n\nexport type HeadingKind = \"group\" | \"target\";\n\n/** Is this heading allowed? */\nexport function isApprovedHeading(label: string, kind: HeadingKind = \"group\", approved: ReadonlySet<string> = APPROVED_HEADINGS): boolean {\n const l = label.trim();\n if (!l || isContainerWord(l)) return false;\n if (approved.has(l)) return true;\n return kind === \"target\" || TARGET_SHAPE.test(l);\n}\n\nexport type HeadingPolicy = \"throw\" | \"inline\";\n\n/** Development and tests throw; production shows the rows inline. */\nexport function defaultHeadingPolicy(): HeadingPolicy {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.NODE_ENV;\n return env === \"production\" ? \"inline\" : \"throw\";\n}\n\nexport class UnapprovedHeadingError extends Error {\n readonly heading: string;\n constructor(heading: string, where: string) {\n super(\n `Menu heading \"${heading}\" (${where}) is not an approved heading. Remedy: show these rows inline — declare no section ` +\n `(or kind \"target\" for the clicked thing's own label) — or ask Arman to add the name to APPROVED_HEADINGS in ` +\n `@ai-matrx/alchemy/menu. A generic container word (\"More…\", \"Other\") is never approved.`,\n );\n this.name = \"UnapprovedHeadingError\";\n this.heading = heading;\n }\n}\n\n/** Every heading in a model: section labels, folds, and submenu rows at any depth. */\nexport function menuHeadings(model: Pick<MenuModel, \"sections\">): string[] {\n const out: string[] = [];\n const walk = (nodes: readonly MenuNode[]) => {\n for (const n of nodes) {\n if (n.kind === \"submenu\") {\n out.push(n.label);\n walk(n.children);\n }\n }\n };\n for (const s of model.sections) {\n if (s.label) out.push(s.label);\n walk(s.nodes);\n }\n return [...new Set(out)];\n}\n\n/** Section headings not in the approved set (container words are never approved). */\nexport function unapprovedHeadings(model: Pick<MenuModel, \"sections\">, approved: ReadonlySet<string> = APPROVED_HEADINGS): string[] {\n const out = new Set<string>();\n for (const s of model.sections) {\n if (s.label && !isApprovedHeading(s.label, s.primary || s.target ? \"target\" : \"group\", approved)) out.add(s.label);\n const walk = (nodes: readonly MenuNode[]) => {\n for (const n of nodes) if (n.kind === \"submenu\") {\n if (isContainerWord(n.label)) out.add(n.label);\n walk(n.children);\n }\n };\n walk(s.nodes);\n }\n return [...out];\n}\n","/**\n * buildMenuModel — WHAT exists at a click target, as one framework-neutral\n * tree (CONTRACT §2.5). Categories GROUP, never gate: eligibility already ran\n * in the registry; this only arranges the resolved set into sections.\n */\nimport type { Action, ActionCategory, ClickTarget, ResolvedAction } from \"../actions/types\";\nimport type { I18nPort } from \"../ports\";\nimport type { MenuGroup, MenuModel, MenuNode, MenuSection, MenuSubmenuNode } from \"./types\";\nimport { UnapprovedHeadingError, defaultHeadingPolicy, isApprovedHeading, isContainerWord, type HeadingPolicy } from \"./headings\";\n\n/** THE category → menu group table (CONTRACT §2.5). */\nexport const CATEGORY_GROUP: Readonly<Record<ActionCategory, MenuGroup>> = {\n clipboard: \"clipboard\",\n copy: \"transfer\",\n export: \"transfer\",\n save: \"document\",\n share: \"document\",\n edit: \"editable\",\n history: \"history\",\n ai: \"ai\",\n ask: \"ai\",\n listen: \"tools\",\n study: \"tools\",\n app: \"tools\",\n applet: \"tools\",\n feedback: \"quick\",\n creator: \"admin\",\n admin: \"admin\",\n \"surface-info\": \"surface-info\",\n};\n\n/** Classic order of the groups. `surface-info` is engine-built and always last. */\nexport const GROUP_ORDER: readonly MenuGroup[] = [\n \"clipboard\",\n \"editable\",\n \"transfer\",\n \"document\",\n \"history\",\n \"ai\",\n \"tools\",\n \"quick\",\n \"surface\",\n \"admin\",\n \"surface-info\",\n];\n\nconst CATEGORY_ORDER: readonly ActionCategory[] = [\n \"clipboard\",\n \"edit\",\n \"copy\",\n \"export\",\n \"save\",\n \"share\",\n \"history\",\n \"ask\",\n \"ai\",\n \"listen\",\n \"study\",\n \"app\",\n \"applet\",\n \"feedback\",\n \"creator\",\n \"admin\",\n \"surface-info\",\n];\n\nexport interface BuildMenuModelOptions {\n /** What an unapproved heading does: \"throw\" (development/tests) or \"inline\" (production). */\n headingPolicy?: HeadingPolicy;\n i18n?: I18nPort;\n /** Content the menu acts on when nothing is selected. */\n content?: string | null;\n /**\n * The name of the field the content is (a text box's label, \"Body\"). The\n * header then names the field — never \"Content: {{reply.body}}\", its raw text.\n */\n contentLabel?: string | null;\n}\n\nfunction translate(i18n: I18nPort | undefined, key: string, fallback: string): string {\n if (!i18n) return fallback;\n try {\n return i18n.t(key, fallback);\n } catch {\n return fallback;\n }\n}\n\n/** One resolved action → one model node (item, checkbox, link or lazy submenu). */\nexport function actionToNode(\n resolved: ResolvedAction,\n target: ClickTarget,\n options: BuildMenuModelOptions = {},\n): MenuNode {\n const { action, eligibility } = resolved;\n const rawLabel = typeof action.label === \"function\" ? action.label(target) : action.label;\n const label = translate(options.i18n, action.labelKey ?? action.id, rawLabel);\n const base = {\n id: action.id,\n actionId: action.id,\n ...(action.icon !== undefined ? { icon: action.icon } : {}),\n ...(action.placement !== undefined ? { placement: action.placement } : {}),\n };\n if (action.expand) {\n const node: MenuSubmenuNode = {\n ...base,\n kind: \"submenu\",\n label,\n lazy: true,\n loading: false,\n ...(action.pending?.(target) ? { pending: true } : {}),\n children: [],\n ...(action.emptyLabel !== undefined ? { emptyLabel: action.emptyLabel } : {}),\n };\n return node;\n }\n if (action.href !== undefined) {\n const href = typeof action.href === \"function\" ? action.href(target) : action.href;\n return { ...base, kind: \"link\", label, href };\n }\n if (action.toggle) {\n return {\n ...base,\n kind: \"checkbox\",\n label,\n checked: action.pressed?.(target) ?? false,\n ...(action.description !== undefined ? { description: action.description } : {}),\n };\n }\n const state = action.stateIcon?.(target) ?? null;\n const pressed = action.pressed?.(target);\n const hint = action.hint !== undefined ? translate(options.i18n, action.hintKey ?? `${action.id}.hint`, action.hint) : undefined;\n return {\n ...base,\n kind: \"item\",\n label,\n ...(action.iconTone !== undefined ? { iconTone: action.iconTone } : {}),\n ...(state ? { stateIcon: state } : {}),\n ...(action.description !== undefined ? { description: action.description } : {}),\n ...(hint !== undefined ? { hint } : {}),\n ...(pressed !== undefined ? { pressed } : {}),\n ...(action.destructive ? { destructive: true } : {}),\n ...(action.preserveSelection ? { preserveSelection: true } : {}),\n ...(action.keepsMenuOpen ? { keepsMenuOpen: true } : {}),\n ...(eligibility.status === \"unavailable-verb\" || eligibility.status === \"unavailable\"\n ? { unavailable: { sentence: eligibility.sentence } }\n : {}),\n };\n}\n\nexport function buildMenuModel(\n target: ClickTarget,\n resolved: readonly ResolvedAction[],\n options: BuildMenuModelOptions = {},\n): MenuModel {\n // THE ONE HEADING CHECK, for every host: an unapproved heading's rows go\n // inline (production) or the build throws with the remedy (dev/tests).\n const policy = options.headingPolicy ?? defaultHeadingPolicy();\n resolved = resolved.map((r) => {\n const sec = r.action.section;\n if (r.action.expand && isContainerWord(typeof r.action.label === \"string\" ? r.action.label : \"\") && policy === \"throw\") {\n throw new UnapprovedHeadingError(r.action.label as string, `submenu of ${r.action.id}`);\n }\n if (!sec) return r;\n const kind = sec.kind ?? (sec.primary ? \"target\" : \"group\");\n if (isApprovedHeading(sec.label, kind)) return r;\n if (policy === \"throw\") throw new UnapprovedHeadingError(sec.label, `section of ${r.action.id}`);\n const { section: _dropped, ...rest } = r.action;\n return { ...r, action: rest };\n });\n const ordered = resolved\n .map((r, index) => ({ r, index }))\n .sort((a, b) => {\n const ca = CATEGORY_ORDER.indexOf(a.r.action.category);\n const cb = CATEGORY_ORDER.indexOf(b.r.action.category);\n if (ca !== cb) return ca - cb;\n const oa = a.r.action.order ?? 0;\n const ob = b.r.action.order ?? 0;\n if (oa !== ob) return oa - ob;\n return a.index - b.index;\n })\n .map((x) => x.r);\n\n const sections = new Map<string, MenuSection>();\n const roles: Record<string, string> = {};\n // An explicit section sits in the group of its first-registered action and\n // keeps its own row order (order, then registration) across categories.\n const explicit = new Map<string, { group: MenuGroup; rows: { r: ResolvedAction; index: number }[] }>();\n resolved.forEach((r, index) => {\n const sec = r.action.section;\n if (!sec) return;\n const entry = explicit.get(sec.id) ?? { group: CATEGORY_GROUP[r.action.category], rows: [] };\n entry.rows.push({ r, index });\n explicit.set(sec.id, entry);\n });\n const placed = new Set<string>();\n for (const r of ordered) {\n const sec = r.action.section;\n if (sec && placed.has(sec.id)) continue;\n const group = sec ? (explicit.get(sec.id)?.group ?? CATEGORY_GROUP[r.action.category]) : CATEGORY_GROUP[r.action.category];\n const sectionId = sec ? `section:${sec.id}` : group;\n let section = sections.get(sectionId);\n if (!section) {\n section = {\n id: sectionId,\n group,\n nodes: [],\n ...(sec ? { label: translate(options.i18n, sec.labelKey ?? `section.${sec.id}`, sec.label) } : {}),\n ...(sec?.icon !== undefined ? { icon: sec.icon } : {}),\n ...(sec?.primary ? { primary: true } : {}),\n ...(sec && (sec.kind ?? (sec.primary ? \"target\" : \"group\")) === \"target\" ? { target: true } : {}),\n };\n sections.set(sectionId, section);\n }\n const rows = sec\n ? (explicit.get(sec.id)?.rows ?? [])\n .sort((x, y) => (x.r.action.order ?? 0) - (y.r.action.order ?? 0) || x.index - y.index)\n .map((x) => x.r)\n : [r];\n if (sec) placed.add(sec.id);\n for (const row of rows) {\n if (row.action.startsGroup && section.nodes.length > 0) {\n section.nodes.push({ kind: \"separator\", id: `${row.action.id}:group` });\n }\n section.nodes.push(actionToNode(row, target, options));\n if (row.action.verb) roles[row.action.verb] = row.action.id;\n }\n }\n\n const list = [...sections.values()].sort((a, b) => {\n if (Boolean(a.primary) !== Boolean(b.primary)) return a.primary ? -1 : 1;\n return GROUP_ORDER.indexOf(a.group) - GROUP_ORDER.indexOf(b.group);\n });\n\n const selected = target.selection?.text?.trim();\n const content = options.content?.trim();\n const fieldName = options.contentLabel?.trim();\n const header = selected\n ? { label: \"Selected\", text: target.selection?.text ?? \"\" }\n : fieldName\n ? { label: fieldName, text: options.content ?? \"\" }\n : content\n ? { label: \"Content\", text: options.content ?? \"\" }\n : null;\n\n return { header, sections: list, roles };\n}\n\n/** Fill a lazy submenu's rows once they load (pure: returns a new model). */\n/**\n * AN EXPANDED SUBMENU KEEPS ITS GROUPS (DATA-V2-BASICS-2, 2026-09-28). A provider's rows arrive flat,\n * so a submenu drew \"Highlight row\" and \"Delete row…\" as neighbours with no divider, while the same\n * rows in a top-level menu sit in their sections. A divider now goes wherever the rows' `section`\n * changes — a row with no section and a row with one are different groups too.\n */\nexport function withGroupDividers(children: readonly Action[], nodes: readonly MenuNode[]): MenuNode[] {\n const out: MenuNode[] = [];\n children.forEach((child, i) => {\n const node = nodes[i];\n if (!node) return;\n if (i > 0 && (child.startsGroup || (children[i - 1]!.section?.id ?? null) !== (child.section?.id ?? null))) {\n out.push({ kind: \"separator\", id: `${child.id}:group` });\n }\n out.push(node);\n });\n return out;\n}\n\nexport function withExpandedChildren(model: MenuModel, nodeId: string, children: MenuNode[] | \"loading\"): MenuModel {\n const visit = (nodes: MenuNode[]): MenuNode[] =>\n nodes.map((n) => {\n if (n.kind !== \"submenu\") return n;\n if (n.id === nodeId) {\n return children === \"loading\" ? { ...n, loading: true } : { ...n, lazy: false, loading: false, pending: false, children };\n }\n return { ...n, children: visit(n.children) };\n });\n return { ...model, sections: model.sections.map((s) => ({ ...s, nodes: visit(s.nodes) })) };\n}\n\n/** Find a node anywhere in a tree. */\nexport function findNode(nodes: readonly MenuNode[], id: string): MenuNode | null {\n for (const n of nodes) {\n if (n.id === id) return n;\n if (n.kind === \"submenu\") {\n const hit = findNode(n.children, id);\n if (hit) return hit;\n }\n }\n return null;\n}\n","/**\n * Layouts — pure arrangements of one MenuModel (CONTRACT §2.5).\n *\n * Every layout is a function over the SAME model: it decides which nodes sit\n * at the top level, which fold under a named submenu and which go into a\n * strip. None adds, drops or renames a leaf — `leafIds(arranged)` equals\n * `leafIds(model)` for every layout (THE LOSSLESS LAW, proven by test).\n *\n * The clipboard group IS the universal verb strip: in the strip layouts it is\n * drawn icon-only; elsewhere its rows are drawn in place. Unavailable verbs\n * (the only greyed nodes, R1 c) appear in every layout identically.\n */\nimport type {\n ArrangedBar,\n ArrangedContext,\n ArrangedPalette,\n ArrangedSelectionToolbar,\n ArrangedSheet,\n ContextArrangement,\n MenuLeafNode,\n MenuModel,\n MenuNode,\n MenuSection,\n MenuSubmenuNode,\n} from \"./types\";\n\n/** A section with more rows than this folds under its label in tiered/command/sheet. */\nexport const INLINE_SECTION_MAX = 3;\n\n/**\n * A section's heading is ONLY the name its actions declare (an approved\n * heading the classic menu already uses: \"Save as\", \"Copy as\", \"History\").\n * A group with no declared name has no heading and is never folded — its rows\n * show inline. The package never coins a heading (chair ruling, ALC-15).\n */\nexport function sectionLabel(section: MenuSection): string | null {\n return section.label ?? null;\n}\n\nfunction isLeaf(node: MenuNode): node is MenuLeafNode {\n return node.kind === \"item\" || node.kind === \"checkbox\" || node.kind === \"link\";\n}\n\n/** Every actionable id in a node list, depth-first. A lazy submenu counts as one leaf. */\nfunction collect(nodes: readonly MenuNode[], out: string[]): void {\n for (const n of nodes) {\n if (isLeaf(n)) out.push(n.id);\n else if (n.kind === \"submenu\") {\n if (n.lazy) out.push(n.id);\n else collect(n.children, out);\n }\n }\n}\n\ntype Arranged =\n | MenuModel\n | ArrangedBar\n | ArrangedContext\n | ArrangedSheet\n | ArrangedPalette\n | ArrangedSelectionToolbar\n | MenuSection[];\n\n/** Proof obligation for every layout: its leaf set equals the model's (sorted, de-duplicated). */\nexport function leafIds(arranged: Arranged): readonly string[] {\n const out: string[] = [];\n if (Array.isArray(arranged)) {\n for (const s of arranged) collect(s.nodes, out);\n } else if (\"leaves\" in arranged) {\n for (const l of arranged.leaves) out.push(l.node.id);\n } else if (\"primary\" in arranged && \"overflow\" in arranged) {\n collect(arranged.primary, out);\n for (const s of arranged.overflow) collect(s.nodes, out);\n } else if (\"root\" in arranged) {\n collect(arranged.strip, out);\n collect(arranged.root, out);\n } else if (\"more\" in arranged) {\n collect(arranged.strip, out);\n for (const s of arranged.more) collect(s.nodes, out);\n } else if (\"filterable\" in arranged) {\n collect(arranged.strip, out);\n for (const s of arranged.sections) collect(s.nodes, out);\n } else {\n for (const s of arranged.sections) collect(s.nodes, out);\n }\n return [...new Set(out)].sort();\n}\n\n/** Only a NAMED section folds; callers check `section.label` first. */\nfunction fold(section: MenuSection & { label: string }): MenuSubmenuNode {\n return {\n kind: \"submenu\",\n id: `${section.id}:fold`,\n label: section.label,\n ...(section.icon !== undefined ? { icon: section.icon } : {}),\n children: section.nodes,\n };\n}\n\nfunction rowCount(nodes: readonly MenuNode[]): number {\n return nodes.filter((n) => n.kind !== \"separator\" && n.kind !== \"label\").length;\n}\n\nfunction stripOf(model: MenuModel): MenuLeafNode[] {\n const clipboard = model.sections.find((s) => s.group === \"clipboard\" && !s.primary);\n return clipboard ? clipboard.nodes.filter(isLeaf) : [];\n}\n\nfunction clipboardRest(model: MenuModel): MenuNode[] {\n const clipboard = model.sections.find((s) => s.group === \"clipboard\" && !s.primary);\n return clipboard ? clipboard.nodes.filter((n) => !isLeaf(n) && n.kind === \"submenu\") : [];\n}\n\nfunction placementOf(node: MenuNode): \"primary\" | \"overflow\" | \"both\" {\n return \"placement\" in node && node.placement ? node.placement : \"overflow\";\n}\n\n/** The historical flat column: every section top-level. */\nfunction classic(model: MenuModel): ArrangedContext {\n return { strip: [], sections: model.sections, filterable: false };\n}\n\n/**\n * Tiered / command: the universal verbs as an icon strip; every other row by\n * its own name; the clicked target's (primary) section first, inline, heading\n * kept; a long non-primary section folds under ITS OWN label (never a coined\n * heading); History is the one approved grouping and already a group.\n */\nfunction tiered(model: MenuModel, filterable: boolean): ArrangedContext {\n const strip = stripOf(model);\n const sections: MenuSection[] = [];\n const rest = clipboardRest(model);\n for (const s of model.sections) {\n if (s.group === \"clipboard\" && !s.primary) {\n if (rest.length) sections.push({ ...s, nodes: rest });\n continue;\n }\n if (s.primary) {\n sections.push(s);\n continue;\n }\n if (!s.label || s.group === \"surface-info\" || rowCount(s.nodes) <= INLINE_SECTION_MAX) {\n // A section keeps its declared heading in EVERY layout (the Lossless\n // Law covers headings: ⋯, the bar's ⋯ and the phone sheet show it too).\n sections.push(s);\n continue;\n }\n sections.push({ id: s.id, group: s.group, nodes: [fold({ ...s, label: s.label })] });\n }\n return { strip, sections, filterable };\n}\n\nexport function context(model: MenuModel, arrangement: ContextArrangement): ArrangedContext {\n if (arrangement === \"classic\") return classic(model);\n return tiered(model, arrangement === \"command\");\n}\n\n/**\n * FOLD UNDER THE SUBJECT — a control that belongs to a CONTAINER of the thing\n * (a tab's \"…\" over the note it shows) draws the container's own rows (the\n * primary sections) on top, and EVERY other row — the verb strip included —\n * under ONE submenu named for the subject (\"Note ▸\"). The label is the\n * subject's name the host supplies (the menu header's label), never a coined\n * group heading. Lossless: rows move, none is dropped or renamed. With no\n * primary section there is nothing to put first, so the arrangement is left\n * as it was. (page-pass /notes, 2026-09-28, Arman's ruling for the tab \"…\".)\n */\nexport function foldUnderSubject(arranged: ArrangedContext, subject: string): ArrangedContext {\n const label = subject.trim();\n const primary = arranged.sections.filter((s) => s.primary);\n const rest = arranged.sections.filter((s) => !s.primary);\n if (!label || primary.length === 0 || (rest.length === 0 && arranged.strip.length === 0)) return arranged;\n const children: MenuNode[] = [];\n if (arranged.strip.length) children.push(...arranged.strip);\n for (const s of rest) {\n if (rowCount(s.nodes) === 0) continue;\n if (children.length) children.push({ kind: \"separator\", id: `${s.id}:subject-sep` });\n if (s.label) children.push({ kind: \"label\", id: `${s.id}:subject-label`, label: s.label });\n children.push(...s.nodes);\n }\n if (rowCount(children) === 0) return { ...arranged, strip: [] };\n return {\n strip: [],\n filterable: false,\n sections: [\n ...primary,\n { id: \"subject\", group: rest[0]?.group ?? primary[0]!.group, nodes: [{ kind: \"submenu\", id: \"subject:fold\", label, children }] },\n ],\n };\n}\n\n/**\n * A long NAMED section folds under its own name (\"Save as ▸\", \"Copy as ▸\");\n * primary sections and unnamed groups stay inline. Folding moves rows, never\n * renames or drops them, and never under a coined heading.\n */\nfunction foldNamed(sections: readonly MenuSection[]): MenuSection[] {\n return sections.map((s) =>\n s.label && !s.primary && rowCount(s.nodes) > INLINE_SECTION_MAX ? { id: s.id, group: s.group, nodes: [fold({ ...s, label: s.label })] } : s,\n );\n}\n\n/** ⋯ overflow: every section in classic order; long named sections fold under their own name. */\nexport function overflow(model: MenuModel): MenuSection[] {\n return foldNamed(model.sections);\n}\n\n/**\n * The action bar: primary buttons inline, EVERYTHING else under ⋯. A \"both\"\n * action is mirrored in both halves (the leaf set is still the model's).\n * `mini` changes presentation only (icon-only primaries), never membership.\n */\nexport function bar(model: MenuModel, _options: { mini?: boolean } = {}): ArrangedBar {\n const primary: MenuNode[] = [];\n const overflowSections: MenuSection[] = [];\n for (const s of model.sections) {\n const rest: MenuNode[] = [];\n for (const n of s.nodes) {\n if (n.kind === \"separator\" || n.kind === \"label\") {\n rest.push(n);\n continue;\n }\n const p = placementOf(n);\n if (p === \"primary\" || p === \"both\") primary.push(n);\n if (p === \"overflow\" || p === \"both\") rest.push(n);\n }\n if (rowCount(rest) > 0) overflowSections.push({ ...s, nodes: rest });\n }\n return { primary, overflow: foldNamed(overflowSections) };\n}\n\n/** Selection toolbar: the verbs plus primary actions as a strip; the rest under More. */\nexport function selectionToolbar(model: MenuModel): ArrangedSelectionToolbar {\n const strip: MenuNode[] = [];\n const more: MenuSection[] = [];\n for (const s of model.sections) {\n const rest: MenuNode[] = [];\n for (const n of s.nodes) {\n const inStrip = (s.group === \"clipboard\" && !s.primary && isLeaf(n)) || placementOf(n) !== \"overflow\";\n if (inStrip) strip.push(n);\n else rest.push(n);\n }\n if (rowCount(rest) > 0) more.push({ ...s, nodes: rest });\n }\n return { strip, more };\n}\n\n/**\n * Mobile bottom sheet: the verb strip on top; primary and short sections\n * inline; longer sections become one drill-down row named by the section.\n * Consumes the model directly, so it can never drift from desktop.\n */\nexport function sheet(model: MenuModel): ArrangedSheet {\n const strip = stripOf(model);\n const root: MenuNode[] = [];\n const rest = clipboardRest(model);\n for (const s of model.sections) {\n const nodes = s.group === \"clipboard\" && !s.primary ? rest : s.nodes;\n if (rowCount(nodes) === 0) continue;\n if (root.length) root.push({ kind: \"separator\", id: `${s.id}:sep` });\n if (s.primary || !s.label || rowCount(nodes) <= INLINE_SECTION_MAX) {\n if (s.label) root.push({ kind: \"label\", id: `${s.id}:label`, label: s.label });\n root.push(...nodes);\n } else {\n root.push(fold({ ...s, label: s.label, nodes }));\n }\n }\n return { strip, root };\n}\n\n/** Command palette: every leaf flattened with its breadcrumb (Linear). */\nexport function palette(model: MenuModel): ArrangedPalette {\n const leaves: ArrangedPalette[\"leaves\"] = [];\n const walk = (nodes: readonly MenuNode[], trail: string[]) => {\n for (const n of nodes) {\n if (isLeaf(n)) leaves.push({ node: n, breadcrumb: trail });\n else if (n.kind === \"submenu\") {\n if (n.lazy) leaves.push({ node: n, breadcrumb: trail });\n else walk(n.children, [...trail, n.label]);\n }\n }\n };\n for (const s of model.sections) walk(s.nodes, s.label ? [s.label] : []);\n return { leaves };\n}\n\n/**\n * A SEARCH REACHES EVERY ROW (clone 2026-10-01: \"Patient\" found nothing while\n * \"Patient reminder card\" sat under AI Actions ▸ My Personal). `palette` lists a\n * lazy submenu as ONE leaf — its label only — because its rows load on open.\n * A layout that searches opens every lazy submenu it can see: these are the\n * ones still to open (not already loading).\n */\nexport function lazySubmenuIds(model: MenuModel | null): string[] {\n if (!model) return [];\n const out: string[] = [];\n const walk = (nodes: readonly MenuNode[]) => {\n for (const n of nodes) {\n if (n.kind !== \"submenu\") continue;\n if (n.lazy) {\n if (!n.loading) out.push(n.id);\n } else walk(n.children);\n }\n };\n for (const s of model.sections) walk(s.nodes);\n return out;\n}\n\n/** True while some submenu's rows are not in the model yet (a search could still find more). */\nexport function paletteStillOpening(model: MenuModel | null): boolean {\n if (!model) return true;\n const walk = (nodes: readonly MenuNode[]): boolean =>\n nodes.some((n) => n.kind === \"submenu\" && (n.lazy || n.loading || n.pending || walk(n.children)));\n return model.sections.some((s) => walk(s.nodes));\n}\n\n/** Filter the palette by a query (every word must match label or breadcrumb). */\nexport function filterPalette(arranged: ArrangedPalette, query: string): ArrangedPalette {\n const words = query.toLowerCase().split(/\\s+/).filter(Boolean);\n if (!words.length) return arranged;\n return {\n leaves: arranged.leaves.filter((l) => {\n const hay = `${l.node.label} ${l.breadcrumb.join(\" \")}`.toLowerCase();\n return words.every((w) => hay.includes(w));\n }),\n };\n}\n\nexport const layouts = { bar, overflow, context, selectionToolbar, sheet, palette };\n","/**\n * N9: layout and density are a PERSON's choice layered over the organization\n * default: persistence (\"alchemy.menu.layout\") → knobs.layout(org) → platform\n * default (command / comfortable, Arman 2026-08-22).\n */\nimport type { AlchemyHostPorts, ContextArrangementKnob, DensityKnob } from \"../ports\";\n\nexport const PLATFORM_MENU_LAYOUT: { context: ContextArrangementKnob; density: DensityKnob } = {\n context: \"command\",\n density: \"comfortable\",\n};\n\nexport const MENU_LAYOUT_SETTING = \"alchemy.menu.layout\";\n\nconst ARRANGEMENTS = new Set<string>([\"classic\", \"tiered\", \"command\"]);\nconst DENSITIES = new Set<string>([\"comfortable\", \"compact\"]);\n\nexport async function resolveMenuLayout(\n ports: Pick<AlchemyHostPorts, \"persistence\" | \"knobs\">,\n organizationId: string | null,\n): Promise<{ context: ContextArrangementKnob; density: DensityKnob }> {\n const org = ports.knobs?.layout(organizationId) ?? null;\n const base = { ...PLATFORM_MENU_LAYOUT, ...(org ?? {}) };\n let own: unknown = null;\n try {\n own = (await ports.persistence?.readSetting(MENU_LAYOUT_SETTING)) ?? null;\n } catch {\n own = null;\n }\n if (own && typeof own === \"object\" && !Array.isArray(own)) {\n const o = own as { context?: unknown; density?: unknown };\n return {\n context: typeof o.context === \"string\" && ARRANGEMENTS.has(o.context) ? (o.context as ContextArrangementKnob) : base.context,\n density: typeof o.density === \"string\" && DENSITIES.has(o.density) ? (o.density as DensityKnob) : base.density,\n };\n }\n return base;\n}\n"],"mappings":"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","names":[]}