@ai-matrx/alchemy 0.8.9 → 0.8.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/dist/actions.cjs.map +1 -1
- package/dist/actions.d.cts +6 -0
- package/dist/actions.d.ts +6 -0
- package/dist/actions.js.map +1 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/menu.cjs +30 -1
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.d.cts +24 -1
- package/dist/menu.d.ts +24 -1
- package/dist/menu.js +30 -1
- package/dist/menu.js.map +1 -1
- package/dist/operate-document.cjs.map +1 -1
- package/dist/operate-document.js.map +1 -1
- package/dist/operate-rich-html.cjs.map +1 -1
- package/dist/operate-rich-html.js.map +1 -1
- package/dist/operate-server.cjs.map +1 -1
- package/dist/operate-server.js.map +1 -1
- package/dist/operate-xlsx.cjs.map +1 -1
- package/dist/operate-xlsx.js.map +1 -1
- package/dist/operate.cjs.map +1 -1
- package/dist/operate.js.map +1 -1
- package/dist/react-bar.cjs +20 -5
- package/dist/react-bar.cjs.map +1 -1
- package/dist/react-bar.js +20 -5
- package/dist/react-bar.js.map +1 -1
- package/dist/react-host.d.cts +6 -0
- package/dist/react-host.d.ts +6 -0
- package/dist/react-menu.cjs +20 -5
- package/dist/react-menu.cjs.map +1 -1
- package/dist/react-menu.js +20 -5
- package/dist/react-menu.js.map +1 -1
- package/dist/react-overflow.cjs +13 -4
- package/dist/react-overflow.cjs.map +1 -1
- package/dist/react-overflow.d.cts +10 -0
- package/dist/react-overflow.d.ts +10 -0
- package/dist/react-overflow.js +13 -4
- package/dist/react-overflow.js.map +1 -1
- package/dist/react-palette.cjs +11 -3
- package/dist/react-palette.cjs.map +1 -1
- package/dist/react-palette.js +11 -3
- package/dist/react-palette.js.map +1 -1
- package/dist/react-selection.cjs +20 -5
- package/dist/react-selection.cjs.map +1 -1
- package/dist/react-selection.js +20 -5
- package/dist/react-selection.js.map +1 -1
- package/dist/react-sheet.cjs +27 -7
- package/dist/react-sheet.cjs.map +1 -1
- package/dist/react-sheet.js +27 -7
- package/dist/react-sheet.js.map +1 -1
- package/package.json +3 -3
package/dist/menu.cjs
CHANGED
|
@@ -31,14 +31,17 @@ __export(menu_exports, {
|
|
|
31
31
|
context: () => context,
|
|
32
32
|
filterPalette: () => filterPalette,
|
|
33
33
|
findNode: () => findNode,
|
|
34
|
+
isContainerWord: () => isContainerWord,
|
|
34
35
|
layouts: () => layouts,
|
|
35
36
|
leafIds: () => leafIds,
|
|
37
|
+
menuHeadings: () => menuHeadings,
|
|
36
38
|
overflow: () => overflow,
|
|
37
39
|
palette: () => palette,
|
|
38
40
|
resolveMenuLayout: () => resolveMenuLayout,
|
|
39
41
|
sectionLabel: () => sectionLabel,
|
|
40
42
|
selectionToolbar: () => selectionToolbar,
|
|
41
43
|
sheet: () => sheet,
|
|
44
|
+
unapprovedHeadings: () => unapprovedHeadings,
|
|
42
45
|
withExpandedChildren: () => withExpandedChildren
|
|
43
46
|
});
|
|
44
47
|
module.exports = __toCommonJS(menu_exports);
|
|
@@ -118,6 +121,7 @@ function actionToNode(resolved, target, options = {}) {
|
|
|
118
121
|
label,
|
|
119
122
|
lazy: true,
|
|
120
123
|
loading: false,
|
|
124
|
+
...action.pending?.(target) ? { pending: true } : {},
|
|
121
125
|
children: [],
|
|
122
126
|
...action.emptyLabel !== void 0 ? { emptyLabel: action.emptyLabel } : {}
|
|
123
127
|
};
|
|
@@ -211,7 +215,7 @@ function withExpandedChildren(model, nodeId, children) {
|
|
|
211
215
|
const visit = (nodes) => nodes.map((n) => {
|
|
212
216
|
if (n.kind !== "submenu") return n;
|
|
213
217
|
if (n.id === nodeId) {
|
|
214
|
-
return children === "loading" ? { ...n, loading: true } : { ...n, lazy: false, loading: false, children };
|
|
218
|
+
return children === "loading" ? { ...n, loading: true } : { ...n, lazy: false, loading: false, pending: false, children };
|
|
215
219
|
}
|
|
216
220
|
return { ...n, children: visit(n.children) };
|
|
217
221
|
});
|
|
@@ -428,4 +432,29 @@ async function resolveMenuLayout(ports, organizationId) {
|
|
|
428
432
|
}
|
|
429
433
|
return base;
|
|
430
434
|
}
|
|
435
|
+
|
|
436
|
+
// src/menu/headings.ts
|
|
437
|
+
var CONTAINER_WORD = /^(more|more…|more\.\.\.|other|others|misc|miscellaneous|additional|extra|extras|actions|options|advanced|stuff|everything else)$/i;
|
|
438
|
+
function isContainerWord(label) {
|
|
439
|
+
return CONTAINER_WORD.test(label.trim());
|
|
440
|
+
}
|
|
441
|
+
function menuHeadings(model) {
|
|
442
|
+
const out = [];
|
|
443
|
+
const walk = (nodes) => {
|
|
444
|
+
for (const n of nodes) {
|
|
445
|
+
if (n.kind === "submenu") {
|
|
446
|
+
out.push(n.label);
|
|
447
|
+
walk(n.children);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
for (const s of model.sections) {
|
|
452
|
+
if (s.label) out.push(s.label);
|
|
453
|
+
walk(s.nodes);
|
|
454
|
+
}
|
|
455
|
+
return [...new Set(out)];
|
|
456
|
+
}
|
|
457
|
+
function unapprovedHeadings(model, approved) {
|
|
458
|
+
return menuHeadings(model).filter((h) => isContainerWord(h) || !approved.has(h));
|
|
459
|
+
}
|
|
431
460
|
//# sourceMappingURL=menu.cjs.map
|
package/dist/menu.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/menu.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 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\";\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 { ActionCategory, ClickTarget, ResolvedAction } from \"../actions/types\";\nimport type { I18nPort } from \"../ports\";\nimport type { MenuGroup, MenuModel, MenuNode, MenuSection, MenuSubmenuNode } from \"./types\";\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 i18n?: I18nPort;\n /** Content the menu acts on when nothing is selected. */\n content?: 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 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 ...(eligibility.status === \"unavailable-verb\" ? { unavailable: { sentence: eligibility.sentence } } : {}),\n };\n}\n\nexport function buildMenuModel(\n target: ClickTarget,\n resolved: readonly ResolvedAction[],\n options: BuildMenuModelOptions = {},\n): MenuModel {\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 };\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 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 header = selected\n ? { label: \"Selected\" as const, text: target.selection?.text ?? \"\" }\n : content\n ? { label: \"Content\" as const, 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). */\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, 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 * 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/** 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/model.ts","../src/menu/layouts.ts","../src/menu/knobs.ts","../src/menu/headings.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 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 { isContainerWord, menuHeadings, unapprovedHeadings } from \"./menu/headings\";\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 { ActionCategory, ClickTarget, ResolvedAction } from \"../actions/types\";\nimport type { I18nPort } from \"../ports\";\nimport type { MenuGroup, MenuModel, MenuNode, MenuSection, MenuSubmenuNode } from \"./types\";\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 i18n?: I18nPort;\n /** Content the menu acts on when nothing is selected. */\n content?: 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 ...(eligibility.status === \"unavailable-verb\" ? { unavailable: { sentence: eligibility.sentence } } : {}),\n };\n}\n\nexport function buildMenuModel(\n target: ClickTarget,\n resolved: readonly ResolvedAction[],\n options: BuildMenuModelOptions = {},\n): MenuModel {\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 };\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 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 header = selected\n ? { label: \"Selected\" as const, text: target.selection?.text ?? \"\" }\n : content\n ? { label: \"Content\" as const, 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). */\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 * 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/** 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","/**\n * THE LOSSLESS LAW for headings (ALC-15, chair ruling 7). A heading a person\n * sees — a named section, a fold, a group submenu — must be a name the classic\n * menus already use (the host's approved set: \"Save as\", \"Copy as\", \"History\",\n * a surface's own section label…). A generic container word (\"More…\", \"Other\",\n * \"Actions\") names no content and is never approved, whatever a host passes:\n * rows a lane wants to tidy away stay inline instead.\n */\nimport type { MenuModel, MenuNode } from \"./types\";\n\nconst CONTAINER_WORD = /^(more|more…|more\\.\\.\\.|other|others|misc|miscellaneous|additional|extra|extras|actions|options|advanced|stuff|everything else)$/i;\n\nexport function isContainerWord(label: string): boolean {\n return CONTAINER_WORD.test(label.trim());\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/** Headings that are not in the approved set (container words are never approved). */\nexport function unapprovedHeadings(model: Pick<MenuModel, \"sections\">, approved: ReadonlySet<string>): string[] {\n return menuHeadings(model).filter((h) => isContainerWord(h) || !approved.has(h));\n}\n"],"mappings":"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","names":[]}
|
package/dist/menu.d.cts
CHANGED
|
@@ -40,6 +40,8 @@ interface MenuSubmenuNode extends NodeBase {
|
|
|
40
40
|
/** Rows load on open (the action's `expand`). */
|
|
41
41
|
lazy?: boolean;
|
|
42
42
|
loading?: boolean;
|
|
43
|
+
/** Its rows are still loading at the source (shown as loading until they arrive). */
|
|
44
|
+
pending?: boolean;
|
|
43
45
|
emptyLabel?: string;
|
|
44
46
|
placement?: "primary" | "overflow" | "both";
|
|
45
47
|
children: MenuNode[];
|
|
@@ -690,6 +692,12 @@ interface Action {
|
|
|
690
692
|
/** Present = the row is a checkbox (pressed is its state). */
|
|
691
693
|
toggle?: boolean;
|
|
692
694
|
subscribe?(onChange: () => void, target: ClickTarget): () => void;
|
|
695
|
+
/**
|
|
696
|
+
* The rows behind this submenu are still loading (an agent library's first
|
|
697
|
+
* fetch). The row shows as loading — never absent, never silent — and its
|
|
698
|
+
* `expand` resolves once they arrive.
|
|
699
|
+
*/
|
|
700
|
+
pending?(target: ClickTarget): boolean;
|
|
693
701
|
/** Submenus whose rows load on open (agent lists). */
|
|
694
702
|
expand?(target: ClickTarget, signal: AbortSignal): Promise<readonly Action[]>;
|
|
695
703
|
/** Shown centred when `expand` resolves empty. */
|
|
@@ -831,4 +839,19 @@ declare function resolveMenuLayout(ports: Pick<AlchemyHostPorts, "persistence" |
|
|
|
831
839
|
density: DensityKnob;
|
|
832
840
|
}>;
|
|
833
841
|
|
|
834
|
-
|
|
842
|
+
/**
|
|
843
|
+
* THE LOSSLESS LAW for headings (ALC-15, chair ruling 7). A heading a person
|
|
844
|
+
* sees — a named section, a fold, a group submenu — must be a name the classic
|
|
845
|
+
* menus already use (the host's approved set: "Save as", "Copy as", "History",
|
|
846
|
+
* a surface's own section label…). A generic container word ("More…", "Other",
|
|
847
|
+
* "Actions") names no content and is never approved, whatever a host passes:
|
|
848
|
+
* rows a lane wants to tidy away stay inline instead.
|
|
849
|
+
*/
|
|
850
|
+
|
|
851
|
+
declare function isContainerWord(label: string): boolean;
|
|
852
|
+
/** Every heading in a model: section labels, folds, and submenu rows at any depth. */
|
|
853
|
+
declare function menuHeadings(model: Pick<MenuModel, "sections">): string[];
|
|
854
|
+
/** Headings that are not in the approved set (container words are never approved). */
|
|
855
|
+
declare function unapprovedHeadings(model: Pick<MenuModel, "sections">, approved: ReadonlySet<string>): string[];
|
|
856
|
+
|
|
857
|
+
export { type ArrangedBar, type ArrangedContext, type ArrangedPalette, type ArrangedSelectionToolbar, type ArrangedSheet, type BuildMenuModelOptions, CATEGORY_GROUP, type ContextArrangement, type Density, GROUP_ORDER, INLINE_SECTION_MAX, type LayoutName, MENU_LAYOUT_SETTING, type MenuCheckboxNode, type MenuGroup, type MenuItemNode, type MenuLabelNode, type MenuLeafNode, type MenuLinkNode, type MenuModel, type MenuNode, type MenuSection, type MenuSeparatorNode, type MenuSubmenuNode, PLATFORM_MENU_LAYOUT, actionToNode, bar, buildMenuModel, context, filterPalette, findNode, isContainerWord, layouts, leafIds, menuHeadings, overflow, palette, resolveMenuLayout, sectionLabel, selectionToolbar, sheet, unapprovedHeadings, withExpandedChildren };
|
package/dist/menu.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ interface MenuSubmenuNode extends NodeBase {
|
|
|
40
40
|
/** Rows load on open (the action's `expand`). */
|
|
41
41
|
lazy?: boolean;
|
|
42
42
|
loading?: boolean;
|
|
43
|
+
/** Its rows are still loading at the source (shown as loading until they arrive). */
|
|
44
|
+
pending?: boolean;
|
|
43
45
|
emptyLabel?: string;
|
|
44
46
|
placement?: "primary" | "overflow" | "both";
|
|
45
47
|
children: MenuNode[];
|
|
@@ -690,6 +692,12 @@ interface Action {
|
|
|
690
692
|
/** Present = the row is a checkbox (pressed is its state). */
|
|
691
693
|
toggle?: boolean;
|
|
692
694
|
subscribe?(onChange: () => void, target: ClickTarget): () => void;
|
|
695
|
+
/**
|
|
696
|
+
* The rows behind this submenu are still loading (an agent library's first
|
|
697
|
+
* fetch). The row shows as loading — never absent, never silent — and its
|
|
698
|
+
* `expand` resolves once they arrive.
|
|
699
|
+
*/
|
|
700
|
+
pending?(target: ClickTarget): boolean;
|
|
693
701
|
/** Submenus whose rows load on open (agent lists). */
|
|
694
702
|
expand?(target: ClickTarget, signal: AbortSignal): Promise<readonly Action[]>;
|
|
695
703
|
/** Shown centred when `expand` resolves empty. */
|
|
@@ -831,4 +839,19 @@ declare function resolveMenuLayout(ports: Pick<AlchemyHostPorts, "persistence" |
|
|
|
831
839
|
density: DensityKnob;
|
|
832
840
|
}>;
|
|
833
841
|
|
|
834
|
-
|
|
842
|
+
/**
|
|
843
|
+
* THE LOSSLESS LAW for headings (ALC-15, chair ruling 7). A heading a person
|
|
844
|
+
* sees — a named section, a fold, a group submenu — must be a name the classic
|
|
845
|
+
* menus already use (the host's approved set: "Save as", "Copy as", "History",
|
|
846
|
+
* a surface's own section label…). A generic container word ("More…", "Other",
|
|
847
|
+
* "Actions") names no content and is never approved, whatever a host passes:
|
|
848
|
+
* rows a lane wants to tidy away stay inline instead.
|
|
849
|
+
*/
|
|
850
|
+
|
|
851
|
+
declare function isContainerWord(label: string): boolean;
|
|
852
|
+
/** Every heading in a model: section labels, folds, and submenu rows at any depth. */
|
|
853
|
+
declare function menuHeadings(model: Pick<MenuModel, "sections">): string[];
|
|
854
|
+
/** Headings that are not in the approved set (container words are never approved). */
|
|
855
|
+
declare function unapprovedHeadings(model: Pick<MenuModel, "sections">, approved: ReadonlySet<string>): string[];
|
|
856
|
+
|
|
857
|
+
export { type ArrangedBar, type ArrangedContext, type ArrangedPalette, type ArrangedSelectionToolbar, type ArrangedSheet, type BuildMenuModelOptions, CATEGORY_GROUP, type ContextArrangement, type Density, GROUP_ORDER, INLINE_SECTION_MAX, type LayoutName, MENU_LAYOUT_SETTING, type MenuCheckboxNode, type MenuGroup, type MenuItemNode, type MenuLabelNode, type MenuLeafNode, type MenuLinkNode, type MenuModel, type MenuNode, type MenuSection, type MenuSeparatorNode, type MenuSubmenuNode, PLATFORM_MENU_LAYOUT, actionToNode, bar, buildMenuModel, context, filterPalette, findNode, isContainerWord, layouts, leafIds, menuHeadings, overflow, palette, resolveMenuLayout, sectionLabel, selectionToolbar, sheet, unapprovedHeadings, withExpandedChildren };
|
package/dist/menu.js
CHANGED
|
@@ -73,6 +73,7 @@ function actionToNode(resolved, target, options = {}) {
|
|
|
73
73
|
label,
|
|
74
74
|
lazy: true,
|
|
75
75
|
loading: false,
|
|
76
|
+
...action.pending?.(target) ? { pending: true } : {},
|
|
76
77
|
children: [],
|
|
77
78
|
...action.emptyLabel !== void 0 ? { emptyLabel: action.emptyLabel } : {}
|
|
78
79
|
};
|
|
@@ -166,7 +167,7 @@ function withExpandedChildren(model, nodeId, children) {
|
|
|
166
167
|
const visit = (nodes) => nodes.map((n) => {
|
|
167
168
|
if (n.kind !== "submenu") return n;
|
|
168
169
|
if (n.id === nodeId) {
|
|
169
|
-
return children === "loading" ? { ...n, loading: true } : { ...n, lazy: false, loading: false, children };
|
|
170
|
+
return children === "loading" ? { ...n, loading: true } : { ...n, lazy: false, loading: false, pending: false, children };
|
|
170
171
|
}
|
|
171
172
|
return { ...n, children: visit(n.children) };
|
|
172
173
|
});
|
|
@@ -383,6 +384,31 @@ async function resolveMenuLayout(ports, organizationId) {
|
|
|
383
384
|
}
|
|
384
385
|
return base;
|
|
385
386
|
}
|
|
387
|
+
|
|
388
|
+
// src/menu/headings.ts
|
|
389
|
+
var CONTAINER_WORD = /^(more|more…|more\.\.\.|other|others|misc|miscellaneous|additional|extra|extras|actions|options|advanced|stuff|everything else)$/i;
|
|
390
|
+
function isContainerWord(label) {
|
|
391
|
+
return CONTAINER_WORD.test(label.trim());
|
|
392
|
+
}
|
|
393
|
+
function menuHeadings(model) {
|
|
394
|
+
const out = [];
|
|
395
|
+
const walk = (nodes) => {
|
|
396
|
+
for (const n of nodes) {
|
|
397
|
+
if (n.kind === "submenu") {
|
|
398
|
+
out.push(n.label);
|
|
399
|
+
walk(n.children);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
for (const s of model.sections) {
|
|
404
|
+
if (s.label) out.push(s.label);
|
|
405
|
+
walk(s.nodes);
|
|
406
|
+
}
|
|
407
|
+
return [...new Set(out)];
|
|
408
|
+
}
|
|
409
|
+
function unapprovedHeadings(model, approved) {
|
|
410
|
+
return menuHeadings(model).filter((h) => isContainerWord(h) || !approved.has(h));
|
|
411
|
+
}
|
|
386
412
|
export {
|
|
387
413
|
CATEGORY_GROUP,
|
|
388
414
|
GROUP_ORDER,
|
|
@@ -395,14 +421,17 @@ export {
|
|
|
395
421
|
context,
|
|
396
422
|
filterPalette,
|
|
397
423
|
findNode,
|
|
424
|
+
isContainerWord,
|
|
398
425
|
layouts,
|
|
399
426
|
leafIds,
|
|
427
|
+
menuHeadings,
|
|
400
428
|
overflow,
|
|
401
429
|
palette,
|
|
402
430
|
resolveMenuLayout,
|
|
403
431
|
sectionLabel,
|
|
404
432
|
selectionToolbar,
|
|
405
433
|
sheet,
|
|
434
|
+
unapprovedHeadings,
|
|
406
435
|
withExpandedChildren
|
|
407
436
|
};
|
|
408
437
|
//# sourceMappingURL=menu.js.map
|
package/dist/menu.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/menu/model.ts","../src/menu/layouts.ts","../src/menu/knobs.ts"],"sourcesContent":["/**\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 { ActionCategory, ClickTarget, ResolvedAction } from \"../actions/types\";\nimport type { I18nPort } from \"../ports\";\nimport type { MenuGroup, MenuModel, MenuNode, MenuSection, MenuSubmenuNode } from \"./types\";\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 i18n?: I18nPort;\n /** Content the menu acts on when nothing is selected. */\n content?: 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 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 ...(eligibility.status === \"unavailable-verb\" ? { unavailable: { sentence: eligibility.sentence } } : {}),\n };\n}\n\nexport function buildMenuModel(\n target: ClickTarget,\n resolved: readonly ResolvedAction[],\n options: BuildMenuModelOptions = {},\n): MenuModel {\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 };\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 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 header = selected\n ? { label: \"Selected\" as const, text: target.selection?.text ?? \"\" }\n : content\n ? { label: \"Content\" as const, 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). */\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, 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 * 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/** 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/model.ts","../src/menu/layouts.ts","../src/menu/knobs.ts","../src/menu/headings.ts"],"sourcesContent":["/**\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 { ActionCategory, ClickTarget, ResolvedAction } from \"../actions/types\";\nimport type { I18nPort } from \"../ports\";\nimport type { MenuGroup, MenuModel, MenuNode, MenuSection, MenuSubmenuNode } from \"./types\";\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 i18n?: I18nPort;\n /** Content the menu acts on when nothing is selected. */\n content?: 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 ...(eligibility.status === \"unavailable-verb\" ? { unavailable: { sentence: eligibility.sentence } } : {}),\n };\n}\n\nexport function buildMenuModel(\n target: ClickTarget,\n resolved: readonly ResolvedAction[],\n options: BuildMenuModelOptions = {},\n): MenuModel {\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 };\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 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 header = selected\n ? { label: \"Selected\" as const, text: target.selection?.text ?? \"\" }\n : content\n ? { label: \"Content\" as const, 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). */\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 * 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/** 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","/**\n * THE LOSSLESS LAW for headings (ALC-15, chair ruling 7). A heading a person\n * sees — a named section, a fold, a group submenu — must be a name the classic\n * menus already use (the host's approved set: \"Save as\", \"Copy as\", \"History\",\n * a surface's own section label…). A generic container word (\"More…\", \"Other\",\n * \"Actions\") names no content and is never approved, whatever a host passes:\n * rows a lane wants to tidy away stay inline instead.\n */\nimport type { MenuModel, MenuNode } from \"./types\";\n\nconst CONTAINER_WORD = /^(more|more…|more\\.\\.\\.|other|others|misc|miscellaneous|additional|extra|extras|actions|options|advanced|stuff|everything else)$/i;\n\nexport function isContainerWord(label: string): boolean {\n return CONTAINER_WORD.test(label.trim());\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/** Headings that are not in the approved set (container words are never approved). */\nexport function unapprovedHeadings(model: Pick<MenuModel, \"sections\">, approved: ReadonlySet<string>): string[] {\n return menuHeadings(model).filter((h) => isContainerWord(h) || !approved.has(h));\n}\n"],"mappings":";AAUO,IAAM,iBAA8D;AAAA,EACzE,WAAW;AAAA,EACX,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AAAA,EACN,SAAS;AAAA,EACT,IAAI;AAAA,EACJ,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,KAAK;AAAA,EACL,UAAU;AAAA,EACV,SAAS;AAAA,EACT,OAAO;AAAA,EACP,gBAAgB;AAClB;AAGO,IAAM,cAAoC;AAAA,EAC/C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,iBAA4C;AAAA,EAChD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQA,SAAS,UAAU,MAA4B,KAAa,UAA0B;AACpF,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,WAAO,KAAK,EAAE,KAAK,QAAQ;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,aACd,UACA,QACA,UAAiC,CAAC,GACxB;AACV,QAAM,EAAE,QAAQ,YAAY,IAAI;AAChC,QAAM,WAAW,OAAO,OAAO,UAAU,aAAa,OAAO,MAAM,MAAM,IAAI,OAAO;AACpF,QAAM,QAAQ,UAAU,QAAQ,MAAM,OAAO,YAAY,OAAO,IAAI,QAAQ;AAC5E,QAAM,OAAO;AAAA,IACX,IAAI,OAAO;AAAA,IACX,UAAU,OAAO;AAAA,IACjB,GAAI,OAAO,SAAS,SAAY,EAAE,MAAM,OAAO,KAAK,IAAI,CAAC;AAAA,IACzD,GAAI,OAAO,cAAc,SAAY,EAAE,WAAW,OAAO,UAAU,IAAI,CAAC;AAAA,EAC1E;AACA,MAAI,OAAO,QAAQ;AACjB,UAAM,OAAwB;AAAA,MAC5B,GAAG;AAAA,MACH,MAAM;AAAA,MACN;AAAA,MACA,MAAM;AAAA,MACN,SAAS;AAAA,MACT,GAAI,OAAO,UAAU,MAAM,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;AAAA,MACpD,UAAU,CAAC;AAAA,MACX,GAAI,OAAO,eAAe,SAAY,EAAE,YAAY,OAAO,WAAW,IAAI,CAAC;AAAA,IAC7E;AACA,WAAO;AAAA,EACT;AACA,MAAI,OAAO,SAAS,QAAW;AAC7B,UAAM,OAAO,OAAO,OAAO,SAAS,aAAa,OAAO,KAAK,MAAM,IAAI,OAAO;AAC9E,WAAO,EAAE,GAAG,MAAM,MAAM,QAAQ,OAAO,KAAK;AAAA,EAC9C;AACA,MAAI,OAAO,QAAQ;AACjB,WAAO;AAAA,MACL,GAAG;AAAA,MACH,MAAM;AAAA,MACN;AAAA,MACA,SAAS,OAAO,UAAU,MAAM,KAAK;AAAA,MACrC,GAAI,OAAO,gBAAgB,SAAY,EAAE,aAAa,OAAO,YAAY,IAAI,CAAC;AAAA,IAChF;AAAA,EACF;AACA,QAAM,QAAQ,OAAO,YAAY,MAAM,KAAK;AAC5C,QAAM,UAAU,OAAO,UAAU,MAAM;AACvC,QAAM,OAAO,OAAO,SAAS,SAAY,UAAU,QAAQ,MAAM,OAAO,WAAW,GAAG,OAAO,EAAE,SAAS,OAAO,IAAI,IAAI;AACvH,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM;AAAA,IACN;AAAA,IACA,GAAI,OAAO,aAAa,SAAY,EAAE,UAAU,OAAO,SAAS,IAAI,CAAC;AAAA,IACrE,GAAI,QAAQ,EAAE,WAAW,MAAM,IAAI,CAAC;AAAA,IACpC,GAAI,OAAO,gBAAgB,SAAY,EAAE,aAAa,OAAO,YAAY,IAAI,CAAC;AAAA,IAC9E,GAAI,SAAS,SAAY,EAAE,KAAK,IAAI,CAAC;AAAA,IACrC,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C,GAAI,OAAO,cAAc,EAAE,aAAa,KAAK,IAAI,CAAC;AAAA,IAClD,GAAI,OAAO,oBAAoB,EAAE,mBAAmB,KAAK,IAAI,CAAC;AAAA,IAC9D,GAAI,YAAY,WAAW,qBAAqB,EAAE,aAAa,EAAE,UAAU,YAAY,SAAS,EAAE,IAAI,CAAC;AAAA,EACzG;AACF;AAEO,SAAS,eACd,QACA,UACA,UAAiC,CAAC,GACvB;AACX,QAAM,UAAU,SACb,IAAI,CAAC,GAAG,WAAW,EAAE,GAAG,MAAM,EAAE,EAChC,KAAK,CAAC,GAAG,MAAM;AACd,UAAM,KAAK,eAAe,QAAQ,EAAE,EAAE,OAAO,QAAQ;AACrD,UAAM,KAAK,eAAe,QAAQ,EAAE,EAAE,OAAO,QAAQ;AACrD,QAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,UAAM,KAAK,EAAE,EAAE,OAAO,SAAS;AAC/B,UAAM,KAAK,EAAE,EAAE,OAAO,SAAS;AAC/B,QAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,WAAO,EAAE,QAAQ,EAAE;AAAA,EACrB,CAAC,EACA,IAAI,CAAC,MAAM,EAAE,CAAC;AAEjB,QAAM,WAAW,oBAAI,IAAyB;AAC9C,QAAM,QAAgC,CAAC;AAGvC,QAAM,WAAW,oBAAI,IAAgF;AACrG,WAAS,QAAQ,CAAC,GAAG,UAAU;AAC7B,UAAM,MAAM,EAAE,OAAO;AACrB,QAAI,CAAC,IAAK;AACV,UAAM,QAAQ,SAAS,IAAI,IAAI,EAAE,KAAK,EAAE,OAAO,eAAe,EAAE,OAAO,QAAQ,GAAG,MAAM,CAAC,EAAE;AAC3F,UAAM,KAAK,KAAK,EAAE,GAAG,MAAM,CAAC;AAC5B,aAAS,IAAI,IAAI,IAAI,KAAK;AAAA,EAC5B,CAAC;AACD,QAAM,SAAS,oBAAI,IAAY;AAC/B,aAAW,KAAK,SAAS;AACvB,UAAM,MAAM,EAAE,OAAO;AACrB,QAAI,OAAO,OAAO,IAAI,IAAI,EAAE,EAAG;AAC/B,UAAM,QAAQ,MAAO,SAAS,IAAI,IAAI,EAAE,GAAG,SAAS,eAAe,EAAE,OAAO,QAAQ,IAAK,eAAe,EAAE,OAAO,QAAQ;AACzH,UAAM,YAAY,MAAM,WAAW,IAAI,EAAE,KAAK;AAC9C,QAAI,UAAU,SAAS,IAAI,SAAS;AACpC,QAAI,CAAC,SAAS;AACZ,gBAAU;AAAA,QACR,IAAI;AAAA,QACJ;AAAA,QACA,OAAO,CAAC;AAAA,QACR,GAAI,MAAM,EAAE,OAAO,UAAU,QAAQ,MAAM,IAAI,YAAY,WAAW,IAAI,EAAE,IAAI,IAAI,KAAK,EAAE,IAAI,CAAC;AAAA,QAChG,GAAI,KAAK,SAAS,SAAY,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AAAA,QACpD,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,IAAI,CAAC;AAAA,MAC1C;AACA,eAAS,IAAI,WAAW,OAAO;AAAA,IACjC;AACA,UAAM,OAAO,OACR,SAAS,IAAI,IAAI,EAAE,GAAG,QAAQ,CAAC,GAC7B,KAAK,CAAC,GAAG,OAAO,EAAE,EAAE,OAAO,SAAS,MAAM,EAAE,EAAE,OAAO,SAAS,MAAM,EAAE,QAAQ,EAAE,KAAK,EACrF,IAAI,CAAC,MAAM,EAAE,CAAC,IACjB,CAAC,CAAC;AACN,QAAI,IAAK,QAAO,IAAI,IAAI,EAAE;AAC1B,eAAW,OAAO,MAAM;AACtB,cAAQ,MAAM,KAAK,aAAa,KAAK,QAAQ,OAAO,CAAC;AACrD,UAAI,IAAI,OAAO,KAAM,OAAM,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO;AAAA,IAC3D;AAAA,EACF;AAEA,QAAM,OAAO,CAAC,GAAG,SAAS,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM;AACjD,QAAI,QAAQ,EAAE,OAAO,MAAM,QAAQ,EAAE,OAAO,EAAG,QAAO,EAAE,UAAU,KAAK;AACvE,WAAO,YAAY,QAAQ,EAAE,KAAK,IAAI,YAAY,QAAQ,EAAE,KAAK;AAAA,EACnE,CAAC;AAED,QAAM,WAAW,OAAO,WAAW,MAAM,KAAK;AAC9C,QAAM,UAAU,QAAQ,SAAS,KAAK;AACtC,QAAM,SAAS,WACX,EAAE,OAAO,YAAqB,MAAM,OAAO,WAAW,QAAQ,GAAG,IACjE,UACE,EAAE,OAAO,WAAoB,MAAM,QAAQ,WAAW,GAAG,IACzD;AAEN,SAAO,EAAE,QAAQ,UAAU,MAAM,MAAM;AACzC;AAGO,SAAS,qBAAqB,OAAkB,QAAgB,UAA6C;AAClH,QAAM,QAAQ,CAAC,UACb,MAAM,IAAI,CAAC,MAAM;AACf,QAAI,EAAE,SAAS,UAAW,QAAO;AACjC,QAAI,EAAE,OAAO,QAAQ;AACnB,aAAO,aAAa,YAAY,EAAE,GAAG,GAAG,SAAS,KAAK,IAAI,EAAE,GAAG,GAAG,MAAM,OAAO,SAAS,OAAO,SAAS,OAAO,SAAS;AAAA,IAC1H;AACA,WAAO,EAAE,GAAG,GAAG,UAAU,MAAM,EAAE,QAAQ,EAAE;AAAA,EAC7C,CAAC;AACH,SAAO,EAAE,GAAG,OAAO,UAAU,MAAM,SAAS,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE;AAC5F;AAGO,SAAS,SAAS,OAA4B,IAA6B;AAChF,aAAW,KAAK,OAAO;AACrB,QAAI,EAAE,OAAO,GAAI,QAAO;AACxB,QAAI,EAAE,SAAS,WAAW;AACxB,YAAM,MAAM,SAAS,EAAE,UAAU,EAAE;AACnC,UAAI,IAAK,QAAO;AAAA,IAClB;AAAA,EACF;AACA,SAAO;AACT;;;ACjNO,IAAM,qBAAqB;AAQ3B,SAAS,aAAa,SAAqC;AAChE,SAAO,QAAQ,SAAS;AAC1B;AAEA,SAAS,OAAO,MAAsC;AACpD,SAAO,KAAK,SAAS,UAAU,KAAK,SAAS,cAAc,KAAK,SAAS;AAC3E;AAGA,SAAS,QAAQ,OAA4B,KAAqB;AAChE,aAAW,KAAK,OAAO;AACrB,QAAI,OAAO,CAAC,EAAG,KAAI,KAAK,EAAE,EAAE;AAAA,aACnB,EAAE,SAAS,WAAW;AAC7B,UAAI,EAAE,KAAM,KAAI,KAAK,EAAE,EAAE;AAAA,UACpB,SAAQ,EAAE,UAAU,GAAG;AAAA,IAC9B;AAAA,EACF;AACF;AAYO,SAAS,QAAQ,UAAuC;AAC7D,QAAM,MAAgB,CAAC;AACvB,MAAI,MAAM,QAAQ,QAAQ,GAAG;AAC3B,eAAW,KAAK,SAAU,SAAQ,EAAE,OAAO,GAAG;AAAA,EAChD,WAAW,YAAY,UAAU;AAC/B,eAAW,KAAK,SAAS,OAAQ,KAAI,KAAK,EAAE,KAAK,EAAE;AAAA,EACrD,WAAW,aAAa,YAAY,cAAc,UAAU;AAC1D,YAAQ,SAAS,SAAS,GAAG;AAC7B,eAAW,KAAK,SAAS,SAAU,SAAQ,EAAE,OAAO,GAAG;AAAA,EACzD,WAAW,UAAU,UAAU;AAC7B,YAAQ,SAAS,OAAO,GAAG;AAC3B,YAAQ,SAAS,MAAM,GAAG;AAAA,EAC5B,WAAW,UAAU,UAAU;AAC7B,YAAQ,SAAS,OAAO,GAAG;AAC3B,eAAW,KAAK,SAAS,KAAM,SAAQ,EAAE,OAAO,GAAG;AAAA,EACrD,WAAW,gBAAgB,UAAU;AACnC,YAAQ,SAAS,OAAO,GAAG;AAC3B,eAAW,KAAK,SAAS,SAAU,SAAQ,EAAE,OAAO,GAAG;AAAA,EACzD,OAAO;AACL,eAAW,KAAK,SAAS,SAAU,SAAQ,EAAE,OAAO,GAAG;AAAA,EACzD;AACA,SAAO,CAAC,GAAG,IAAI,IAAI,GAAG,CAAC,EAAE,KAAK;AAChC;AAGA,SAAS,KAAK,SAA2D;AACvE,SAAO;AAAA,IACL,MAAM;AAAA,IACN,IAAI,GAAG,QAAQ,EAAE;AAAA,IACjB,OAAO,QAAQ;AAAA,IACf,GAAI,QAAQ,SAAS,SAAY,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;AAAA,IAC3D,UAAU,QAAQ;AAAA,EACpB;AACF;AAEA,SAAS,SAAS,OAAoC;AACpD,SAAO,MAAM,OAAO,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,SAAS,OAAO,EAAE;AAC3E;AAEA,SAAS,QAAQ,OAAkC;AACjD,QAAM,YAAY,MAAM,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU,eAAe,CAAC,EAAE,OAAO;AAClF,SAAO,YAAY,UAAU,MAAM,OAAO,MAAM,IAAI,CAAC;AACvD;AAEA,SAAS,cAAc,OAA8B;AACnD,QAAM,YAAY,MAAM,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU,eAAe,CAAC,EAAE,OAAO;AAClF,SAAO,YAAY,UAAU,MAAM,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,SAAS,IAAI,CAAC;AAC1F;AAEA,SAAS,YAAY,MAAiD;AACpE,SAAO,eAAe,QAAQ,KAAK,YAAY,KAAK,YAAY;AAClE;AAGA,SAAS,QAAQ,OAAmC;AAClD,SAAO,EAAE,OAAO,CAAC,GAAG,UAAU,MAAM,UAAU,YAAY,MAAM;AAClE;AAQA,SAAS,OAAO,OAAkB,YAAsC;AACtE,QAAM,QAAQ,QAAQ,KAAK;AAC3B,QAAM,WAA0B,CAAC;AACjC,QAAM,OAAO,cAAc,KAAK;AAChC,aAAW,KAAK,MAAM,UAAU;AAC9B,QAAI,EAAE,UAAU,eAAe,CAAC,EAAE,SAAS;AACzC,UAAI,KAAK,OAAQ,UAAS,KAAK,EAAE,GAAG,GAAG,OAAO,KAAK,CAAC;AACpD;AAAA,IACF;AACA,QAAI,EAAE,SAAS;AACb,eAAS,KAAK,CAAC;AACf;AAAA,IACF;AACA,QAAI,CAAC,EAAE,SAAS,EAAE,UAAU,kBAAkB,SAAS,EAAE,KAAK,KAAK,oBAAoB;AAGrF,eAAS,KAAK,CAAC;AACf;AAAA,IACF;AACA,aAAS,KAAK,EAAE,IAAI,EAAE,IAAI,OAAO,EAAE,OAAO,OAAO,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;AAAA,EACrF;AACA,SAAO,EAAE,OAAO,UAAU,WAAW;AACvC;AAEO,SAAS,QAAQ,OAAkB,aAAkD;AAC1F,MAAI,gBAAgB,UAAW,QAAO,QAAQ,KAAK;AACnD,SAAO,OAAO,OAAO,gBAAgB,SAAS;AAChD;AAOA,SAAS,UAAU,UAAiD;AAClE,SAAO,SAAS;AAAA,IAAI,CAAC,MACnB,EAAE,SAAS,CAAC,EAAE,WAAW,SAAS,EAAE,KAAK,IAAI,qBAAqB,EAAE,IAAI,EAAE,IAAI,OAAO,EAAE,OAAO,OAAO,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,EAAE,MAAM,CAAC,CAAC,EAAE,IAAI;AAAA,EAC5I;AACF;AAGO,SAAS,SAAS,OAAiC;AACxD,SAAO,UAAU,MAAM,QAAQ;AACjC;AAOO,SAAS,IAAI,OAAkB,WAA+B,CAAC,GAAgB;AACpF,QAAM,UAAsB,CAAC;AAC7B,QAAM,mBAAkC,CAAC;AACzC,aAAW,KAAK,MAAM,UAAU;AAC9B,UAAM,OAAmB,CAAC;AAC1B,eAAW,KAAK,EAAE,OAAO;AACvB,UAAI,EAAE,SAAS,eAAe,EAAE,SAAS,SAAS;AAChD,aAAK,KAAK,CAAC;AACX;AAAA,MACF;AACA,YAAM,IAAI,YAAY,CAAC;AACvB,UAAI,MAAM,aAAa,MAAM,OAAQ,SAAQ,KAAK,CAAC;AACnD,UAAI,MAAM,cAAc,MAAM,OAAQ,MAAK,KAAK,CAAC;AAAA,IACnD;AACA,QAAI,SAAS,IAAI,IAAI,EAAG,kBAAiB,KAAK,EAAE,GAAG,GAAG,OAAO,KAAK,CAAC;AAAA,EACrE;AACA,SAAO,EAAE,SAAS,UAAU,UAAU,gBAAgB,EAAE;AAC1D;AAGO,SAAS,iBAAiB,OAA4C;AAC3E,QAAM,QAAoB,CAAC;AAC3B,QAAM,OAAsB,CAAC;AAC7B,aAAW,KAAK,MAAM,UAAU;AAC9B,UAAM,OAAmB,CAAC;AAC1B,eAAW,KAAK,EAAE,OAAO;AACvB,YAAM,UAAW,EAAE,UAAU,eAAe,CAAC,EAAE,WAAW,OAAO,CAAC,KAAM,YAAY,CAAC,MAAM;AAC3F,UAAI,QAAS,OAAM,KAAK,CAAC;AAAA,UACpB,MAAK,KAAK,CAAC;AAAA,IAClB;AACA,QAAI,SAAS,IAAI,IAAI,EAAG,MAAK,KAAK,EAAE,GAAG,GAAG,OAAO,KAAK,CAAC;AAAA,EACzD;AACA,SAAO,EAAE,OAAO,KAAK;AACvB;AAOO,SAAS,MAAM,OAAiC;AACrD,QAAM,QAAQ,QAAQ,KAAK;AAC3B,QAAM,OAAmB,CAAC;AAC1B,QAAM,OAAO,cAAc,KAAK;AAChC,aAAW,KAAK,MAAM,UAAU;AAC9B,UAAM,QAAQ,EAAE,UAAU,eAAe,CAAC,EAAE,UAAU,OAAO,EAAE;AAC/D,QAAI,SAAS,KAAK,MAAM,EAAG;AAC3B,QAAI,KAAK,OAAQ,MAAK,KAAK,EAAE,MAAM,aAAa,IAAI,GAAG,EAAE,EAAE,OAAO,CAAC;AACnE,QAAI,EAAE,WAAW,CAAC,EAAE,SAAS,SAAS,KAAK,KAAK,oBAAoB;AAClE,UAAI,EAAE,MAAO,MAAK,KAAK,EAAE,MAAM,SAAS,IAAI,GAAG,EAAE,EAAE,UAAU,OAAO,EAAE,MAAM,CAAC;AAC7E,WAAK,KAAK,GAAG,KAAK;AAAA,IACpB,OAAO;AACL,WAAK,KAAK,KAAK,EAAE,GAAG,GAAG,OAAO,EAAE,OAAO,MAAM,CAAC,CAAC;AAAA,IACjD;AAAA,EACF;AACA,SAAO,EAAE,OAAO,KAAK;AACvB;AAGO,SAAS,QAAQ,OAAmC;AACzD,QAAM,SAAoC,CAAC;AAC3C,QAAM,OAAO,CAAC,OAA4B,UAAoB;AAC5D,eAAW,KAAK,OAAO;AACrB,UAAI,OAAO,CAAC,EAAG,QAAO,KAAK,EAAE,MAAM,GAAG,YAAY,MAAM,CAAC;AAAA,eAChD,EAAE,SAAS,WAAW;AAC7B,YAAI,EAAE,KAAM,QAAO,KAAK,EAAE,MAAM,GAAG,YAAY,MAAM,CAAC;AAAA,YACjD,MAAK,EAAE,UAAU,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC;AAAA,MAC3C;AAAA,IACF;AAAA,EACF;AACA,aAAW,KAAK,MAAM,SAAU,MAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC;AACtE,SAAO,EAAE,OAAO;AAClB;AAGO,SAAS,cAAc,UAA2B,OAAgC;AACvF,QAAM,QAAQ,MAAM,YAAY,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO;AAC7D,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,SAAO;AAAA,IACL,QAAQ,SAAS,OAAO,OAAO,CAAC,MAAM;AACpC,YAAM,MAAM,GAAG,EAAE,KAAK,KAAK,IAAI,EAAE,WAAW,KAAK,GAAG,CAAC,GAAG,YAAY;AACpE,aAAO,MAAM,MAAM,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC;AAAA,IAC3C,CAAC;AAAA,EACH;AACF;AAEO,IAAM,UAAU,EAAE,KAAK,UAAU,SAAS,kBAAkB,OAAO,QAAQ;;;ACjQ3E,IAAM,uBAAkF;AAAA,EAC7F,SAAS;AAAA,EACT,SAAS;AACX;AAEO,IAAM,sBAAsB;AAEnC,IAAM,eAAe,oBAAI,IAAY,CAAC,WAAW,UAAU,SAAS,CAAC;AACrE,IAAM,YAAY,oBAAI,IAAY,CAAC,eAAe,SAAS,CAAC;AAE5D,eAAsB,kBACpB,OACA,gBACoE;AACpE,QAAM,MAAM,MAAM,OAAO,OAAO,cAAc,KAAK;AACnD,QAAM,OAAO,EAAE,GAAG,sBAAsB,GAAI,OAAO,CAAC,EAAG;AACvD,MAAI,MAAe;AACnB,MAAI;AACF,UAAO,MAAM,MAAM,aAAa,YAAY,mBAAmB,KAAM;AAAA,EACvE,QAAQ;AACN,UAAM;AAAA,EACR;AACA,MAAI,OAAO,OAAO,QAAQ,YAAY,CAAC,MAAM,QAAQ,GAAG,GAAG;AACzD,UAAM,IAAI;AACV,WAAO;AAAA,MACL,SAAS,OAAO,EAAE,YAAY,YAAY,aAAa,IAAI,EAAE,OAAO,IAAK,EAAE,UAAqC,KAAK;AAAA,MACrH,SAAS,OAAO,EAAE,YAAY,YAAY,UAAU,IAAI,EAAE,OAAO,IAAK,EAAE,UAA0B,KAAK;AAAA,IACzG;AAAA,EACF;AACA,SAAO;AACT;;;AC3BA,IAAM,iBAAiB;AAEhB,SAAS,gBAAgB,OAAwB;AACtD,SAAO,eAAe,KAAK,MAAM,KAAK,CAAC;AACzC;AAGO,SAAS,aAAa,OAA8C;AACzE,QAAM,MAAgB,CAAC;AACvB,QAAM,OAAO,CAAC,UAA+B;AAC3C,eAAW,KAAK,OAAO;AACrB,UAAI,EAAE,SAAS,WAAW;AACxB,YAAI,KAAK,EAAE,KAAK;AAChB,aAAK,EAAE,QAAQ;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AACA,aAAW,KAAK,MAAM,UAAU;AAC9B,QAAI,EAAE,MAAO,KAAI,KAAK,EAAE,KAAK;AAC7B,SAAK,EAAE,KAAK;AAAA,EACd;AACA,SAAO,CAAC,GAAG,IAAI,IAAI,GAAG,CAAC;AACzB;AAGO,SAAS,mBAAmB,OAAoC,UAAyC;AAC9G,SAAO,aAAa,KAAK,EAAE,OAAO,CAAC,MAAM,gBAAgB,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,CAAC;AACjF;","names":[]}
|