@ai-matrx/alchemy 0.8.31 → 0.8.32

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.32
4
+
5
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
6
+ `git diff npm/alchemy/v0.8.31..npm/alchemy/v0.8.32 -- apps/shared/alchemy`).
7
+ No source changes intended and no consumer action required.
8
+
3
9
  ## 0.8.31
4
10
 
5
11
  Automatic changed-only republish (docs/metadata drift since the last tag — see
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/actions.ts","../src/actions/click-target.ts","../src/actions/types.ts","../src/actions/registry.ts","../src/declare/sensitivity.ts","../src/actions/projection.ts","../src/declare/keys.ts","../src/actions/target.ts","../src/actions/run.ts"],"sourcesContent":["/**\n * `@ai-matrx/alchemy/actions` — the one action registry (Matrx Alchemy CONTRACT §2.4).\n * Framework-neutral: zero React, zero `@ai-matrx/agents`.\n */\nexport {\n ACTION_CATEGORIES,\n UNIVERSAL_VERBS,\n type Action,\n type ActionCategory,\n type ActionProvider,\n type ActionRegistry,\n type ActionRunContext,\n type ActionSection,\n type ClickTarget,\n type Eligibility,\n type ProjectedReads,\n type ReadOmission,\n type ResolveRestriction,\n type ResolvedAction,\n type UniversalVerb,\n} from \"./actions/types\";\nexport {\n DuplicateActionError,\n DuplicateProviderError,\n alwaysEligible,\n computeEligibility,\n createActionRegistry,\n staticActionProvider,\n type ActionRegistryOptions,\n} from \"./actions/registry\";\nexport { createProjectedReads, projectValues, type ProjectionSource } from \"./actions/projection\";\nexport { createClickTarget, type ClickTargetInput } from \"./actions/target\";\nexport { runAction, type RunActionOptions, type RunResult } from \"./actions/run\";\n","/**\n * The click target (CONTRACT §2.4) in its own module: `/ports` references it,\n * and `/ports` must stay free of every other package (types only).\n */\nimport type { QualifiedValueKey, ResolvedItem, ResolvedSurfaceDeclaration, WriteTarget } from \"../declare/types\";\n\n/** The six universal verbs — the ONLY rows that may grey (R1 c). */\nexport const UNIVERSAL_VERBS = [\"copy\", \"cut\", \"paste\", \"undo\", \"redo\", \"find\"] as const;\nexport type UniversalVerb = (typeof UNIVERSAL_VERBS)[number];\n\n/** The transfer Payload's kinds (`actions/types.ts` asserts equality at compile time). */\nexport type PayloadKind = \"text\" | \"markdown\" | \"json\" | \"rows\" | \"registered\";\n\n/** What eligibility is computed from: everything declared at the click target. */\nexport interface ClickTarget {\n surface: { instanceId: string; surfaceName: string } | null;\n /** The resolved declaration (inheritance applied). Eligibility decides by KEY existence over it. */\n declaration: ResolvedSurfaceDeclaration | null;\n /** Declared value keys with a read path here (screen names + qualified item keys). Key existence, not population. */\n valueKeys: ReadonlySet<QualifiedValueKey>;\n /** Scope chain, most general first: [\"global\", \"<domain>\", \"<surfaceName>\"]. */\n scopeChain: readonly string[];\n /** The surface's explicit exclusions (the one override). */\n excludedActionIds: ReadonlySet<string>;\n item: ResolvedItem | null;\n level: string | null;\n /** Offsets + the live range handle, editable or not. */\n selection: {\n text: string;\n type: \"editable\" | \"non-editable\";\n start: number;\n end: number;\n /** Opaque host handle (DOM Range / element) used to restore or stream into the selection. */\n handle?: unknown;\n } | null;\n /** Payload kinds a capture of this target would produce (from declared value types/kinds). */\n payloadKinds: readonly PayloadKind[];\n /** Declared targets writable on this target right now. */\n writable: readonly WriteTarget[];\n readOnly: boolean;\n /** True when any declared value here is secret/credential or non-exportable. */\n restricted: boolean;\n organizationId: string | null;\n auth: { authenticated: boolean; admin: boolean; creator: boolean };\n /**\n * Host-owned context a provider reads to act (the rich-document source, the\n * editable field's callbacks). Opaque to the package; never crosses into the\n * model. Providers that need it narrow it themselves.\n */\n host?: unknown;\n}\n\n","/**\n * Matrx Alchemy — Actions types (CONTRACT §2.4).\n *\n * One registry of everything a person can do to a thing. Providers plug in;\n * eligibility is computed from what is declared at the click target; duplicate\n * ids are refused; an action that does not apply is ABSENT (chair ruling R1).\n *\n * Framework-neutral: zero React, zero `@ai-matrx/agents`. Icons are string KEYS\n * resolved by the host's `IconResolverPort`; labels go through `I18nPort`.\n */\nimport type { Payload, TransferOutcome } from \"@ai-matrx/kit/content-transfer\";\nimport type { Json, LiveValues } from \"../declare/types\";\nimport type { AlchemyHostPorts } from \"../ports\";\nimport type { ClickTarget, PayloadKind } from \"./click-target\";\n\nexport type { ClickTarget, PayloadKind } from \"./click-target\";\n\n/** PayloadKind is the transfer Payload's kind union, kept equal at compile time. */\ntype Equal<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;\nconst payloadKindsAgree: Equal<PayloadKind, Payload[\"kind\"]> = true;\nvoid payloadKindsAgree;\n\nexport { UNIVERSAL_VERBS, type UniversalVerb } from \"./click-target\";\nimport type { UniversalVerb } from \"./click-target\";\n\n/**\n * Chair ruling R1: an action that does not apply or cannot run now is ABSENT.\n * Only the six universal verbs may answer \"unavailable-verb\": greyed in the\n * strip/bar, the sentence as tooltip. `requiresAuth` is NOT unavailability.\n */\nexport type Eligibility =\n | { status: \"absent\" }\n | { status: \"available\" }\n | { status: \"unavailable-verb\"; verb: UniversalVerb; sentence: string }\n /**\n * ABSENT OR HONEST (coordinator ruling 2026-09-27, admin pass): a row that cannot run here but\n * SAYS WHY (\"Unchanged from the pack\", \"Manifested surfaces are managed in code\") stays visible,\n * greyed, with its reason — the menu is the same everywhere, only availability changes. A row\n * with no reason is absent (the registry refuses an empty sentence).\n */\n | { status: \"unavailable\"; sentence: string };\n\n/** rich-document's ActionCategory, unchanged, plus the three the context menu groups by. */\nexport type ActionCategory =\n | \"feedback\"\n | \"copy\"\n | \"export\"\n | \"save\"\n | \"edit\"\n | \"share\"\n | \"creator\"\n | \"app\"\n | \"listen\"\n | \"study\"\n | \"ask\"\n | \"ai\"\n | \"admin\"\n | \"clipboard\"\n | \"history\"\n | \"surface-info\";\n\nexport const ACTION_CATEGORIES: readonly ActionCategory[] = [\n \"feedback\",\n \"copy\",\n \"export\",\n \"save\",\n \"edit\",\n \"share\",\n \"creator\",\n \"app\",\n \"listen\",\n \"study\",\n \"ask\",\n \"ai\",\n \"admin\",\n \"clipboard\",\n \"history\",\n \"surface-info\",\n];\n\n/**\n * An optional named section inside the action's group (a grid's cell / row /\n * column). `primary` marks the thing the person clicked: every layout renders\n * it first, inline, heading kept (context-menu-v3's primary section law).\n */\nexport interface ActionSection {\n id: string;\n label: string;\n labelKey?: string;\n icon?: string;\n primary?: boolean;\n /**\n * \"group\" (default): a heading over related rows — must be in the package's\n * APPROVED_HEADINGS (@ai-matrx/alchemy/menu). \"target\": the label of the\n * thing the person clicked, supplied at run time (\"Row · Widget A\").\n */\n kind?: \"group\" | \"target\";\n}\n\nexport interface Action {\n /** Globally unique. A duplicate is refused (never overwritten). */\n id: string;\n /** The provider that registered it — filled by the registry. */\n provider?: string;\n /** Resolved through I18nPort.t(labelKey ?? id, label). */\n label: string | ((target: ClickTarget) => string);\n labelKey?: string;\n /** Icon KEY resolved by IconResolverPort — the model stays framework-neutral. */\n icon?: string;\n /** Host styling hint for the glyph (rich-document iconColor). Renderers may ignore it. */\n iconTone?: string;\n /** Replaces `icon` while a state holds (a spinner while speaking). */\n stateIcon?(target: ClickTarget): { icon: string; spin?: boolean } | null;\n hint?: string;\n hintKey?: string;\n description?: string;\n category: ActionCategory;\n /** Which universal verb this clipboard action is (addressed by role in every layout). */\n verb?: UniversalVerb;\n order?: number;\n /** Bar layout slot. Default \"overflow\". */\n placement?: \"primary\" | \"overflow\" | \"both\";\n section?: ActionSection;\n /** A signed-out person still sees it; running goes through AuthResumePort (sign in, then resume). */\n requiresAuth?: boolean;\n /** Declared write targets this action lands through. Absent on a read-only target. */\n writes?: readonly string[];\n destructive?: boolean;\n preserveSelection?: boolean;\n /** Link rows navigate instead of running. */\n href?: string | ((target: ClickTarget) => string);\n eligible(target: ClickTarget): Eligibility;\n /** Toggle/pressed state: a pressed button or a checked row. */\n pressed?(target: ClickTarget): boolean;\n /** Present = the row is a checkbox (pressed is its state). */\n toggle?: boolean;\n subscribe?(onChange: () => void, target: ClickTarget): () => void;\n /**\n * The rows behind this submenu are still loading (an agent library's first\n * fetch). The row shows as loading — never absent, never silent — and its\n * `expand` resolves once they arrive.\n */\n pending?(target: ClickTarget): boolean;\n /** Submenus whose rows load on open (agent lists). */\n expand?(target: ClickTarget, signal: AbortSignal): Promise<readonly Action[]>;\n /** Shown centred when `expand` resolves empty. */\n emptyLabel?: string;\n run(target: ClickTarget, context: ActionRunContext): Promise<TransferOutcome | void> | TransferOutcome | void;\n}\n\n/** One removed read, recorded (kit content-transfer's omission shape). */\nexport interface ReadOmission {\n path: string;\n reason: \"excluded\" | \"filtered\";\n classification?: \"secret\" | \"credential\";\n}\n\n/**\n * PROJECTION BEFORE PROVIDERS. Providers never receive a raw read. Every read\n * they can make is projected first: secret and credential values, values\n * declared `exportable: false`, and values nobody declared are removed, with an\n * omission recorded. Raw reads never leave the package.\n */\nexport interface ProjectedReads {\n values(signal: AbortSignal): Promise<{ values: Record<string, Json>; omissions: readonly ReadOmission[] }>;\n item(signal: AbortSignal): Promise<{ values: Record<string, Json>; omissions: readonly ReadOmission[] } | null>;\n live(signal: AbortSignal): Promise<LiveValues>;\n}\n\n/**\n * What a running action receives. `door` (ALC-17) and `operate` (ALC-16) join\n * with their waves; until then those capabilities are absent (N8).\n */\nexport interface ActionRunContext {\n reads: ProjectedReads;\n ports: AlchemyHostPorts;\n signal: AbortSignal;\n}\n\nexport interface ActionProvider {\n id: string;\n /** T0 providers are known at mount; T1 providers load with the menu on first open. */\n tier: \"T0\" | \"T1\";\n /**\n * The ids this provider can ever yield, when known up front. Lets the\n * registry refuse a duplicate at registration instead of at first open.\n */\n declaredIds?(): readonly string[];\n actions(target: ClickTarget): readonly Action[] | Promise<readonly Action[]>;\n}\n\nexport interface ResolvedAction {\n action: Action;\n eligibility: Exclude<Eligibility, { status: \"absent\" }>;\n}\n\nexport interface ResolveRestriction {\n exclude?: readonly string[];\n categories?: readonly ActionCategory[];\n}\n\nexport interface ActionRegistry {\n /** Throws DuplicateActionError naming both providers; nothing is overwritten. */\n register(provider: ActionProvider): () => void;\n /** restrict: a host may remove ids or categories, never add ineligible ones. */\n resolve(target: ClickTarget, restrict?: ResolveRestriction): Promise<readonly ResolvedAction[]>;\n /** Registered provider ids, in registration order. */\n providers(): readonly string[];\n /** Change notification (a provider registered or unregistered). */\n subscribe(listener: () => void): () => void;\n}\n","/**\n * The one action registry (CONTRACT §2.4).\n *\n * - Duplicate ids are REFUSED, never overwritten (rich-document's registry\n * silently overwrote — R12). A duplicate known at registration throws; one\n * that only appears when a provider yields it is dropped and captured.\n * - Eligibility is one pure pass over the resolved click target, under\n * \"restrict, never relax\": a host may remove ids or categories, never add.\n * - R1: an action that does not apply is ABSENT. Only the six universal verbs\n * (category \"clipboard\") may answer `unavailable-verb`, with a sentence; any\n * other action answering it is a contract break → absent + diagnostics.\n */\nimport type { AlchemyHostPorts, Json } from \"../ports\";\nimport {\n UNIVERSAL_VERBS,\n type Action,\n type ActionProvider,\n type ActionRegistry,\n type ClickTarget,\n type Eligibility,\n type ResolveRestriction,\n type ResolvedAction,\n} from \"./types\";\n\nexport class DuplicateActionError extends Error {\n readonly actionId: string;\n readonly providers: readonly [string, string];\n constructor(actionId: string, first: string, second: string) {\n super(\n first === second\n ? `Action \"${actionId}\" is yielded twice by provider \"${first}\". Ids are unique; the second was refused.`\n : `Action \"${actionId}\" is registered by provider \"${first}\" and again by \"${second}\". Ids are unique; \"${second}\"'s copy was refused.`,\n );\n this.name = \"DuplicateActionError\";\n this.actionId = actionId;\n this.providers = [first, second];\n }\n}\n\nexport class DuplicateProviderError extends Error {\n readonly providerId: string;\n constructor(providerId: string) {\n super(`Action provider \"${providerId}\" is already registered. Unregister it first; nothing was overwritten.`);\n this.name = \"DuplicateProviderError\";\n this.providerId = providerId;\n }\n}\n\nexport interface ActionRegistryOptions {\n /** `diagnostics` is required (nothing fails silently); `auth` decides whether requiresAuth actions reach a signed-out person. */\n ports: Pick<AlchemyHostPorts, \"diagnostics\" | \"auth\">;\n}\n\nconst VERBS = new Set<string>(UNIVERSAL_VERBS);\n\nfunction capture(ports: ActionRegistryOptions[\"ports\"], error: unknown, detail: Json): void {\n try {\n ports.diagnostics.capture(error, { area: \"actions\", detail });\n } catch {\n // A broken diagnostics port must never take the menu down with it.\n }\n}\n\n/**\n * Eligibility of one action at one target — the ONE pure pass (CONTRACT §1.6).\n * Exported for providers' tests; hosts call `registry.resolve`.\n */\nexport function computeEligibility(\n action: Action,\n target: ClickTarget,\n options: { restrict?: ResolveRestriction; authResumable: boolean },\n): Eligibility | { status: \"contract-break\"; reason: string } {\n const restrict = options.restrict;\n if (target.excludedActionIds.has(action.id)) return { status: \"absent\" };\n if (restrict?.exclude?.includes(action.id)) return { status: \"absent\" };\n if (restrict?.categories && !restrict.categories.includes(action.category)) return { status: \"absent\" };\n if (action.writes && action.writes.length > 0) {\n // A read-only target offers nothing that would change the record.\n if (target.readOnly) return { status: \"absent\" };\n const writable = new Set(target.writable.map((w) => w.name));\n if (!action.writes.some((name) => writable.has(name))) return { status: \"absent\" };\n }\n if (action.requiresAuth && !target.auth.authenticated && !options.authResumable) {\n // Sign-in-then-resume is the capability; without the auth port it is absent (N8).\n return { status: \"absent\" };\n }\n const own = action.eligible(target);\n if (own.status === \"unavailable\") {\n // A greyed row must say why; without a sentence it is absent.\n return own.sentence.trim() ? own : { status: \"absent\" };\n }\n if (own.status !== \"unavailable-verb\") return own;\n if (action.category !== \"clipboard\" || !VERBS.has(own.verb)) {\n return {\n status: \"contract-break\",\n reason: `Action \"${action.id}\" answered unavailable-verb but only the six universal verbs (category \"clipboard\") may grey; it is absent instead.`,\n };\n }\n if (!own.sentence.trim()) {\n return {\n status: \"contract-break\",\n reason: `Universal verb \"${action.id}\" greyed without a sentence; a greyed verb must say why.`,\n };\n }\n return own;\n}\n\ninterface Entry {\n provider: ActionProvider;\n declared: readonly string[];\n}\n\nexport function createActionRegistry(options: ActionRegistryOptions): ActionRegistry {\n const ports = options.ports;\n const entries = new Map<string, Entry>();\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const l of [...listeners]) l();\n };\n\n return {\n register(provider) {\n if (entries.has(provider.id)) throw new DuplicateProviderError(provider.id);\n const declared = provider.declaredIds?.() ?? [];\n const own = new Set<string>();\n for (const id of declared) {\n if (own.has(id)) throw new DuplicateActionError(id, provider.id, provider.id);\n own.add(id);\n for (const [otherId, other] of entries) {\n if (other.declared.includes(id)) throw new DuplicateActionError(id, otherId, provider.id);\n }\n }\n const entry: Entry = { provider, declared };\n entries.set(provider.id, entry);\n notify();\n return () => {\n if (entries.get(provider.id) === entry) {\n entries.delete(provider.id);\n notify();\n }\n };\n },\n\n async resolve(target, restrict) {\n const authResumable = Boolean(ports.auth);\n const seen = new Map<string, string>();\n const out: ResolvedAction[] = [];\n for (const [providerId, { provider }] of [...entries]) {\n let actions: readonly Action[];\n try {\n actions = await provider.actions(target);\n } catch (error) {\n capture(ports, error, { providerId, stage: \"actions\" });\n continue;\n }\n for (const raw of actions) {\n const first = seen.get(raw.id);\n if (first !== undefined) {\n capture(ports, new DuplicateActionError(raw.id, first, providerId), { actionId: raw.id });\n continue;\n }\n seen.set(raw.id, providerId);\n const action: Action = raw.provider === providerId ? raw : { ...raw, provider: providerId };\n let eligibility: ReturnType<typeof computeEligibility>;\n try {\n eligibility = computeEligibility(action, target, { restrict: restrict ?? {}, authResumable });\n } catch (error) {\n capture(ports, error, { actionId: action.id, stage: \"eligible\" });\n continue;\n }\n if (eligibility.status === \"contract-break\") {\n capture(ports, new Error(eligibility.reason), { actionId: action.id, stage: \"eligible\" });\n continue;\n }\n if (eligibility.status === \"absent\") continue;\n out.push({ action, eligibility });\n }\n }\n return out;\n },\n\n providers() {\n return [...entries.keys()];\n },\n\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\n/** A provider over a fixed list; its ids are declared up front so duplicates fail at registration. */\nexport function staticActionProvider(\n id: string,\n actions: readonly Action[],\n tier: ActionProvider[\"tier\"] = \"T0\",\n): ActionProvider {\n const list = actions.map((a) => (a.provider === id ? a : { ...a, provider: id }));\n return {\n id,\n tier,\n declaredIds: () => list.map((a) => a.id),\n actions: () => list,\n };\n}\n\n/** Always-available eligibility, for actions with no rule of their own. */\nexport const alwaysEligible = (): Eligibility => ({ status: \"available\" });\n","/**\n * Sensitivity defaults and resolution (CONTRACT §2.1). Its own module so the\n * lightweight entries (`/actions`, the T0 React host) can project without\n * pulling the declaration registry.\n */\nimport type { DeclaredValue, Sensitivity } from \"./types\";\n\nexport const DEFAULT_SENSITIVITY: Sensitivity = Object.freeze({\n exportable: true,\n classification: \"ordinary\",\n includedByDefault: true,\n});\n\nexport function resolveSensitivity(value: Pick<DeclaredValue, \"sensitivity\">): Sensitivity {\n return {\n exportable: value.sensitivity?.exportable ?? DEFAULT_SENSITIVITY.exportable,\n classification: value.sensitivity?.classification ?? DEFAULT_SENSITIVITY.classification,\n includedByDefault: value.sensitivity?.includedByDefault ?? DEFAULT_SENSITIVITY.includedByDefault,\n };\n}\n","/**\n * PROJECTION BEFORE PROVIDERS (CONTRACT §2.4).\n *\n * The only place a raw read (`MountedSurface.readValues`,\n * `InternalResolvedItem.readValues`) is consumed on behalf of an action.\n * What leaves is projected: a value is kept only when it is DECLARED, its\n * classification is \"ordinary\" and it is exportable. Everything removed is\n * recorded as an omission, so the person and the agent can see what was held\n * back. No caller can relax this.\n */\nimport { resolveSensitivity } from \"../declare/sensitivity\";\nimport type { InternalResolvedItem, MountedSurface } from \"../declare/host\";\nimport type { DeclaredValue, Json, LiveValues, ResolvedSurfaceDeclaration } from \"../declare/types\";\nimport type { ProjectedReads, ReadOmission } from \"./types\";\n\nexport interface ProjectionSource {\n surface: MountedSurface | null;\n declaration: ResolvedSurfaceDeclaration | null;\n item: InternalResolvedItem | null;\n}\n\nfunction toJson(value: unknown): Json | undefined {\n if (value === undefined || typeof value === \"function\" || typeof value === \"symbol\") return undefined;\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") return value;\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n if (typeof value === \"bigint\") return value.toString();\n if (value instanceof Date) return value.toISOString();\n if (Array.isArray(value)) return value.map((v) => toJson(v) ?? null);\n if (typeof value === \"object\") {\n const out: { [key: string]: Json } = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n const j = toJson(v);\n if (j !== undefined) out[k] = j;\n }\n return out;\n }\n return undefined;\n}\n\n/** Keep declared, ordinary, exportable values; record every removal. */\nexport function projectValues(\n raw: Record<string, unknown>,\n declared: readonly Pick<DeclaredValue, \"name\" | \"sensitivity\">[],\n pathPrefix = \"\",\n): { values: Record<string, Json>; omissions: ReadOmission[] } {\n const byName = new Map(declared.map((d) => [d.name, d]));\n const values: Record<string, Json> = {};\n const omissions: ReadOmission[] = [];\n for (const [name, value] of Object.entries(raw)) {\n const path = pathPrefix ? `${pathPrefix}.${name}` : name;\n const decl = byName.get(name);\n if (!decl) {\n omissions.push({ path, reason: \"filtered\" });\n continue;\n }\n const sensitivity = resolveSensitivity(decl);\n if (sensitivity.classification !== \"ordinary\") {\n omissions.push({ path, reason: \"excluded\", classification: sensitivity.classification });\n continue;\n }\n if (!sensitivity.exportable) {\n omissions.push({ path, reason: \"excluded\" });\n continue;\n }\n const json = toJson(value);\n if (json === undefined) continue;\n values[name] = json;\n }\n return { values, omissions };\n}\n\nexport function createProjectedReads(source: ProjectionSource): ProjectedReads {\n return {\n async values(signal) {\n if (!source.surface || !source.declaration) return { values: {}, omissions: [] };\n const raw = await source.surface.readValues(signal);\n const screenValues = source.declaration.values;\n return projectValues(raw, screenValues);\n },\n async item(signal) {\n const item = source.item;\n if (!item || !source.declaration) return null;\n const type = source.declaration.itemTypes?.find((t) => t.name === item.itemType);\n if (!type) return { values: {}, omissions: [{ path: item.itemType, reason: \"filtered\" }] };\n const raw = await item.readValues(signal);\n return projectValues(raw, type.values, item.itemType);\n },\n async live(signal): Promise<LiveValues> {\n if (!source.surface?.readLive) return {};\n return source.surface.readLive(signal);\n },\n };\n}\n","import type { QualifiedValueKey } from \"./types\";\n\n/** Every value, write-target, item-type and group name (the DB CHECK rule). */\nexport const DECLARATION_NAME_RE = /^[a-z][a-z0-9_]*$/;\n\n/** \"<client>/<local>\" — the surface naming convention. */\nexport const SURFACE_NAME_RE = /^[a-z][a-z0-9-]*\\/[a-z0-9-/]+$/;\n\n/** A bare screen value (`status`) or an item value (`table_row.status`). */\nexport const QUALIFIED_VALUE_KEY_RE = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)?$/;\n\nexport interface ParsedValueKey {\n /** \"\" for a screen-level value. */\n itemType: string;\n name: string;\n}\n\n/** Split a qualified key; null when it is not a valid bare or qualified key. */\nexport function parseQualifiedValueKey(key: string): ParsedValueKey | null {\n if (!QUALIFIED_VALUE_KEY_RE.test(key)) return null;\n const dot = key.indexOf(\".\");\n if (dot === -1) return { itemType: \"\", name: key };\n return { itemType: key.slice(0, dot), name: key.slice(dot + 1) };\n}\n\nexport function qualifyValueKey(itemType: string, name: string): QualifiedValueKey {\n return itemType ? `${itemType}.${name}` : name;\n}\n\n/** The client that owns a surface: its name prefix. */\nexport function surfaceClientOf(surfaceName: string): string {\n return surfaceName.split(\"/\")[0] ?? \"\";\n}\n","/**\n * Building a ClickTarget from what is declared at the click (CONTRACT §2.4).\n * Key existence, never value population: a value key is present when the\n * declaration gives it a read path here.\n */\nimport { resolveSensitivity } from \"../declare/sensitivity\";\nimport { qualifyValueKey } from \"../declare/keys\";\nimport type { ResolvedItem, ResolvedSurfaceDeclaration, WriteTarget } from \"../declare/types\";\nimport type { ClickTarget } from \"./types\";\n\nexport interface ClickTargetInput {\n surface?: ClickTarget[\"surface\"];\n declaration?: ResolvedSurfaceDeclaration | null;\n item?: ResolvedItem | null;\n level?: string | null;\n selection?: ClickTarget[\"selection\"];\n payloadKinds?: ClickTarget[\"payloadKinds\"];\n /** Defaults to the item's readOnly (or false). */\n readOnly?: boolean;\n /** Extra exclusions on top of the declaration's own. */\n excludedActionIds?: Iterable<string>;\n scopeChain?: readonly string[];\n organizationId?: string | null;\n auth?: Partial<ClickTarget[\"auth\"]>;\n /** Write targets the host can serve right now; defaults to every declared target for the screen/item. */\n writable?: readonly WriteTarget[];\n host?: unknown;\n}\n\nfunction domainOf(surfaceName: string): string {\n const slash = surfaceName.indexOf(\"/\");\n return slash > 0 ? surfaceName.slice(0, slash) : surfaceName;\n}\n\nexport function createClickTarget(input: ClickTargetInput = {}): ClickTarget {\n const declaration = input.declaration ?? null;\n const item = input.item ?? null;\n const valueKeys = new Set<string>();\n let restricted = false;\n const writable: WriteTarget[] = [];\n if (declaration) {\n for (const value of declaration.values) {\n valueKeys.add(value.name);\n const s = resolveSensitivity(value);\n if (s.classification !== \"ordinary\" || !s.exportable) restricted = true;\n }\n const itemType = item ? declaration.itemTypes?.find((t) => t.name === item.itemType) : undefined;\n if (itemType) {\n for (const value of itemType.values) {\n valueKeys.add(qualifyValueKey(itemType.name, value.name));\n const s = resolveSensitivity(value);\n if (s.classification !== \"ordinary\" || !s.exportable) restricted = true;\n }\n for (const t of itemType.writeTargets ?? []) writable.push(t);\n }\n for (const t of declaration.writeTargets ?? []) {\n if (!t.itemType || t.itemType === item?.itemType) writable.push(t);\n }\n }\n const readOnly = input.readOnly ?? item?.readOnly ?? false;\n const surfaceName = input.surface?.surfaceName ?? declaration?.surfaceName ?? null;\n const scopeChain = input.scopeChain ?? (surfaceName ? [\"global\", domainOf(surfaceName), surfaceName] : [\"global\"]);\n return {\n surface: input.surface ?? null,\n declaration,\n valueKeys,\n scopeChain,\n excludedActionIds: new Set(input.excludedActionIds ?? []),\n item,\n level: input.level ?? item?.level ?? null,\n selection: input.selection ?? null,\n payloadKinds: input.payloadKinds ?? [],\n writable: readOnly ? [] : (input.writable ?? writable),\n readOnly,\n restricted,\n organizationId: input.organizationId ?? null,\n auth: {\n authenticated: input.auth?.authenticated ?? false,\n admin: input.auth?.admin ?? false,\n creator: input.auth?.creator ?? false,\n },\n host: input.host,\n };\n}\n","/**\n * The one run path (T1e). Every layout runs an action through here, so the\n * sign-in-then-resume rule, projection and failure capture live once.\n */\nimport type { TransferOutcome } from \"@ai-matrx/kit/content-transfer\";\nimport type { AlchemyHostPorts } from \"../ports\";\nimport { createProjectedReads, type ProjectionSource } from \"./projection\";\nimport type { Action, ClickTarget } from \"./types\";\n\nexport interface RunActionOptions {\n ports: AlchemyHostPorts;\n /** Raw sources; projected before the action sees them. */\n source?: ProjectionSource;\n /** Stable fingerprint of the content, for sign-in-then-resume. */\n contentFingerprint?: string;\n signal?: AbortSignal;\n}\n\nexport type RunResult =\n | { status: \"ran\"; outcome: TransferOutcome | void }\n | { status: \"auth-pending\" }\n | { status: \"failed\"; error: unknown };\n\nexport async function runAction(action: Action, target: ClickTarget, options: RunActionOptions): Promise<RunResult> {\n const { ports } = options;\n if (action.requiresAuth && !target.auth.authenticated) {\n if (!ports.auth) {\n // Unreachable through resolve (the action is absent without the port); guard direct callers.\n ports.diagnostics.capture(new Error(`requiresAuth action \"${action.id}\" ran signed out with no auth port`), {\n area: \"actions\",\n detail: { actionId: action.id },\n });\n return { status: \"failed\", error: new Error(\"sign-in unavailable\") };\n }\n ports.auth.signInThenResume({ actionId: action.id, contentFingerprint: options.contentFingerprint ?? action.id });\n return { status: \"auth-pending\" };\n }\n const reads = createProjectedReads(options.source ?? { surface: null, declaration: target.declaration, item: null });\n const signal = options.signal ?? new AbortController().signal;\n try {\n const outcome = await action.run(target, { reads, ports, signal });\n return { status: \"ran\", outcome };\n } catch (error) {\n ports.diagnostics.capture(error, { area: \"actions\", detail: { actionId: action.id, stage: \"run\" } });\n const label = typeof action.label === \"string\" ? action.label : action.id;\n ports.notify?.error(`${label} did not finish.`, \"Try again; if it keeps failing, report it from the feedback button.\");\n return { status: \"failed\", error };\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/actions.ts","../src/actions/click-target.ts","../src/actions/types.ts","../src/actions/registry.ts","../src/declare/sensitivity.ts","../src/actions/projection.ts","../src/declare/keys.ts","../src/actions/target.ts","../src/actions/run.ts"],"sourcesContent":["/**\n * `@ai-matrx/alchemy/actions` — the one action registry (Matrx Alchemy CONTRACT §2.4).\n * Framework-neutral: zero React, zero `@ai-matrx/agents`.\n */\nexport {\n ACTION_CATEGORIES,\n UNIVERSAL_VERBS,\n type Action,\n type ActionCategory,\n type ActionProvider,\n type ActionRegistry,\n type ActionRunContext,\n type ActionSection,\n type ClickTarget,\n type Eligibility,\n type ProjectedReads,\n type ReadOmission,\n type ResolveRestriction,\n type ResolvedAction,\n type UniversalVerb,\n} from \"./actions/types\";\nexport {\n DuplicateActionError,\n DuplicateProviderError,\n alwaysEligible,\n computeEligibility,\n createActionRegistry,\n staticActionProvider,\n type ActionRegistryOptions,\n} from \"./actions/registry\";\nexport { createProjectedReads, projectValues, type ProjectionSource } from \"./actions/projection\";\nexport { createClickTarget, type ClickTargetInput } from \"./actions/target\";\nexport { runAction, type RunActionOptions, type RunResult } from \"./actions/run\";\n","/**\n * The click target (CONTRACT §2.4) in its own module: `/ports` references it,\n * and `/ports` must stay free of every other package (types only).\n */\nimport type { QualifiedValueKey, ResolvedItem, ResolvedSurfaceDeclaration, WriteTarget } from \"../declare/types\";\n\n/** The six universal verbs — the ONLY rows that may grey (R1 c). */\nexport const UNIVERSAL_VERBS = [\"copy\", \"cut\", \"paste\", \"undo\", \"redo\", \"find\"] as const;\nexport type UniversalVerb = (typeof UNIVERSAL_VERBS)[number];\n\n/** The transfer Payload's kinds (`actions/types.ts` asserts equality at compile time). */\nexport type PayloadKind = \"text\" | \"markdown\" | \"json\" | \"rows\" | \"registered\";\n\n/** What eligibility is computed from: everything declared at the click target. */\nexport interface ClickTarget {\n surface: { instanceId: string; surfaceName: string } | null;\n /** The resolved declaration (inheritance applied). Eligibility decides by KEY existence over it. */\n declaration: ResolvedSurfaceDeclaration | null;\n /** Declared value keys with a read path here (screen names + qualified item keys). Key existence, not population. */\n valueKeys: ReadonlySet<QualifiedValueKey>;\n /** Scope chain, most general first: [\"global\", \"<domain>\", \"<surfaceName>\"]. */\n scopeChain: readonly string[];\n /** The surface's explicit exclusions (the one override). */\n excludedActionIds: ReadonlySet<string>;\n item: ResolvedItem | null;\n level: string | null;\n /** Offsets + the live range handle, editable or not. */\n selection: {\n text: string;\n type: \"editable\" | \"non-editable\";\n start: number;\n end: number;\n /** Opaque host handle (DOM Range / element) used to restore or stream into the selection. */\n handle?: unknown;\n } | null;\n /** Payload kinds a capture of this target would produce (from declared value types/kinds). */\n payloadKinds: readonly PayloadKind[];\n /** Declared targets writable on this target right now. */\n writable: readonly WriteTarget[];\n readOnly: boolean;\n /** True when any declared value here is secret/credential or non-exportable. */\n restricted: boolean;\n organizationId: string | null;\n auth: { authenticated: boolean; admin: boolean; creator: boolean };\n /**\n * Host-owned context a provider reads to act (the rich-document source, the\n * editable field's callbacks). Opaque to the package; never crosses into the\n * model. Providers that need it narrow it themselves.\n */\n host?: unknown;\n}\n\n","/**\n * Matrx Alchemy — Actions types (CONTRACT §2.4).\n *\n * One registry of everything a person can do to a thing. Providers plug in;\n * eligibility is computed from what is declared at the click target; duplicate\n * ids are refused; an action that does not apply is ABSENT (chair ruling R1).\n *\n * Framework-neutral: zero React, zero `@ai-matrx/agents`. Icons are string KEYS\n * resolved by the host's `IconResolverPort`; labels go through `I18nPort`.\n */\nimport type { Payload, TransferOutcome } from \"@ai-matrx/kit/content-transfer\";\nimport type { Json, LiveValues } from \"../declare/types\";\nimport type { AlchemyHostPorts } from \"../ports\";\nimport type { ClickTarget, PayloadKind } from \"./click-target\";\n\nexport type { ClickTarget, PayloadKind } from \"./click-target\";\n\n/** PayloadKind is the transfer Payload's kind union, kept equal at compile time. */\ntype Equal<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;\nconst payloadKindsAgree: Equal<PayloadKind, Payload[\"kind\"]> = true;\nvoid payloadKindsAgree;\n\nexport { UNIVERSAL_VERBS, type UniversalVerb } from \"./click-target\";\nimport type { UniversalVerb } from \"./click-target\";\n\n/**\n * Chair ruling R1: an action that does not apply or cannot run now is ABSENT.\n * Only the six universal verbs may answer \"unavailable-verb\": greyed in the\n * strip/bar, the sentence as tooltip. `requiresAuth` is NOT unavailability.\n */\nexport type Eligibility =\n | { status: \"absent\" }\n | { status: \"available\" }\n | { status: \"unavailable-verb\"; verb: UniversalVerb; sentence: string }\n /**\n * ABSENT OR HONEST (coordinator ruling 2026-09-27, admin pass): a row that cannot run here but\n * SAYS WHY (\"Unchanged from the pack\", \"Manifested surfaces are managed in code\") stays visible,\n * greyed, with its reason — the menu is the same everywhere, only availability changes. A row\n * with no reason is absent (the registry refuses an empty sentence).\n */\n | { status: \"unavailable\"; sentence: string };\n\n/** rich-document's ActionCategory, unchanged, plus the three the context menu groups by. */\nexport type ActionCategory =\n | \"feedback\"\n | \"copy\"\n | \"export\"\n | \"save\"\n | \"edit\"\n | \"share\"\n | \"creator\"\n | \"app\"\n | \"listen\"\n | \"study\"\n | \"ask\"\n | \"ai\"\n | \"admin\"\n | \"clipboard\"\n | \"history\"\n | \"surface-info\";\n\nexport const ACTION_CATEGORIES: readonly ActionCategory[] = [\n \"feedback\",\n \"copy\",\n \"export\",\n \"save\",\n \"edit\",\n \"share\",\n \"creator\",\n \"app\",\n \"listen\",\n \"study\",\n \"ask\",\n \"ai\",\n \"admin\",\n \"clipboard\",\n \"history\",\n \"surface-info\",\n];\n\n/**\n * An optional named section inside the action's group (a grid's cell / row /\n * column). `primary` marks the thing the person clicked: every layout renders\n * it first, inline, heading kept (context-menu-v3's primary section law).\n */\nexport interface ActionSection {\n id: string;\n label: string;\n labelKey?: string;\n icon?: string;\n primary?: boolean;\n /**\n * \"group\" (default): a heading over related rows — must be in the package's\n * APPROVED_HEADINGS (@ai-matrx/alchemy/menu). \"target\": the label of the\n * thing the person clicked, supplied at run time (\"Row · Widget A\").\n */\n kind?: \"group\" | \"target\";\n}\n\nexport interface Action {\n /** Globally unique. A duplicate is refused (never overwritten). */\n id: string;\n /** The provider that registered it — filled by the registry. */\n provider?: string;\n /** Resolved through I18nPort.t(labelKey ?? id, label). */\n label: string | ((target: ClickTarget) => string);\n labelKey?: string;\n /** Icon KEY resolved by IconResolverPort — the model stays framework-neutral. */\n icon?: string;\n /** Host styling hint for the glyph (rich-document iconColor). Renderers may ignore it. */\n iconTone?: string;\n /** Replaces `icon` while a state holds (a spinner while speaking). */\n stateIcon?(target: ClickTarget): { icon: string; spin?: boolean } | null;\n hint?: string;\n hintKey?: string;\n description?: string;\n category: ActionCategory;\n /** Which universal verb this clipboard action is (addressed by role in every layout). */\n verb?: UniversalVerb;\n order?: number;\n /** Bar layout slot. Default \"overflow\". */\n placement?: \"primary\" | \"overflow\" | \"both\";\n section?: ActionSection;\n /** A signed-out person still sees it; running goes through AuthResumePort (sign in, then resume). */\n requiresAuth?: boolean;\n /** Declared write targets this action lands through. Absent on a read-only target. */\n writes?: readonly string[];\n destructive?: boolean;\n /**\n * STARTS A NEW GROUP inside its section: the menu draws a divider above it (never as a section's\n * first row). A delete sits in its own group, last, never beside Duplicate (DATA-V2-BASICS-2).\n */\n startsGroup?: boolean;\n preserveSelection?: boolean;\n /** Link rows navigate instead of running. */\n href?: string | ((target: ClickTarget) => string);\n eligible(target: ClickTarget): Eligibility;\n /** Toggle/pressed state: a pressed button or a checked row. */\n pressed?(target: ClickTarget): boolean;\n /** Present = the row is a checkbox (pressed is its state). */\n toggle?: boolean;\n subscribe?(onChange: () => void, target: ClickTarget): () => void;\n /**\n * The rows behind this submenu are still loading (an agent library's first\n * fetch). The row shows as loading — never absent, never silent — and its\n * `expand` resolves once they arrive.\n */\n pending?(target: ClickTarget): boolean;\n /** Submenus whose rows load on open (agent lists). */\n expand?(target: ClickTarget, signal: AbortSignal): Promise<readonly Action[]>;\n /** Shown centred when `expand` resolves empty. */\n emptyLabel?: string;\n run(target: ClickTarget, context: ActionRunContext): Promise<TransferOutcome | void> | TransferOutcome | void;\n}\n\n/** One removed read, recorded (kit content-transfer's omission shape). */\nexport interface ReadOmission {\n path: string;\n reason: \"excluded\" | \"filtered\";\n classification?: \"secret\" | \"credential\";\n}\n\n/**\n * PROJECTION BEFORE PROVIDERS. Providers never receive a raw read. Every read\n * they can make is projected first: secret and credential values, values\n * declared `exportable: false`, and values nobody declared are removed, with an\n * omission recorded. Raw reads never leave the package.\n */\nexport interface ProjectedReads {\n values(signal: AbortSignal): Promise<{ values: Record<string, Json>; omissions: readonly ReadOmission[] }>;\n item(signal: AbortSignal): Promise<{ values: Record<string, Json>; omissions: readonly ReadOmission[] } | null>;\n live(signal: AbortSignal): Promise<LiveValues>;\n}\n\n/**\n * What a running action receives. `door` (ALC-17) and `operate` (ALC-16) join\n * with their waves; until then those capabilities are absent (N8).\n */\nexport interface ActionRunContext {\n reads: ProjectedReads;\n ports: AlchemyHostPorts;\n signal: AbortSignal;\n}\n\nexport interface ActionProvider {\n id: string;\n /** T0 providers are known at mount; T1 providers load with the menu on first open. */\n tier: \"T0\" | \"T1\";\n /**\n * The ids this provider can ever yield, when known up front. Lets the\n * registry refuse a duplicate at registration instead of at first open.\n */\n declaredIds?(): readonly string[];\n actions(target: ClickTarget): readonly Action[] | Promise<readonly Action[]>;\n}\n\nexport interface ResolvedAction {\n action: Action;\n eligibility: Exclude<Eligibility, { status: \"absent\" }>;\n}\n\nexport interface ResolveRestriction {\n exclude?: readonly string[];\n categories?: readonly ActionCategory[];\n}\n\nexport interface ActionRegistry {\n /** Throws DuplicateActionError naming both providers; nothing is overwritten. */\n register(provider: ActionProvider): () => void;\n /** restrict: a host may remove ids or categories, never add ineligible ones. */\n resolve(target: ClickTarget, restrict?: ResolveRestriction): Promise<readonly ResolvedAction[]>;\n /** Registered provider ids, in registration order. */\n providers(): readonly string[];\n /** Change notification (a provider registered or unregistered). */\n subscribe(listener: () => void): () => void;\n}\n","/**\n * The one action registry (CONTRACT §2.4).\n *\n * - Duplicate ids are REFUSED, never overwritten (rich-document's registry\n * silently overwrote — R12). A duplicate known at registration throws; one\n * that only appears when a provider yields it is dropped and captured.\n * - Eligibility is one pure pass over the resolved click target, under\n * \"restrict, never relax\": a host may remove ids or categories, never add.\n * - R1: an action that does not apply is ABSENT. Only the six universal verbs\n * (category \"clipboard\") may answer `unavailable-verb`, with a sentence; any\n * other action answering it is a contract break → absent + diagnostics.\n */\nimport type { AlchemyHostPorts, Json } from \"../ports\";\nimport {\n UNIVERSAL_VERBS,\n type Action,\n type ActionProvider,\n type ActionRegistry,\n type ClickTarget,\n type Eligibility,\n type ResolveRestriction,\n type ResolvedAction,\n} from \"./types\";\n\nexport class DuplicateActionError extends Error {\n readonly actionId: string;\n readonly providers: readonly [string, string];\n constructor(actionId: string, first: string, second: string) {\n super(\n first === second\n ? `Action \"${actionId}\" is yielded twice by provider \"${first}\". Ids are unique; the second was refused.`\n : `Action \"${actionId}\" is registered by provider \"${first}\" and again by \"${second}\". Ids are unique; \"${second}\"'s copy was refused.`,\n );\n this.name = \"DuplicateActionError\";\n this.actionId = actionId;\n this.providers = [first, second];\n }\n}\n\nexport class DuplicateProviderError extends Error {\n readonly providerId: string;\n constructor(providerId: string) {\n super(`Action provider \"${providerId}\" is already registered. Unregister it first; nothing was overwritten.`);\n this.name = \"DuplicateProviderError\";\n this.providerId = providerId;\n }\n}\n\nexport interface ActionRegistryOptions {\n /** `diagnostics` is required (nothing fails silently); `auth` decides whether requiresAuth actions reach a signed-out person. */\n ports: Pick<AlchemyHostPorts, \"diagnostics\" | \"auth\">;\n}\n\nconst VERBS = new Set<string>(UNIVERSAL_VERBS);\n\nfunction capture(ports: ActionRegistryOptions[\"ports\"], error: unknown, detail: Json): void {\n try {\n ports.diagnostics.capture(error, { area: \"actions\", detail });\n } catch {\n // A broken diagnostics port must never take the menu down with it.\n }\n}\n\n/**\n * Eligibility of one action at one target — the ONE pure pass (CONTRACT §1.6).\n * Exported for providers' tests; hosts call `registry.resolve`.\n */\nexport function computeEligibility(\n action: Action,\n target: ClickTarget,\n options: { restrict?: ResolveRestriction; authResumable: boolean },\n): Eligibility | { status: \"contract-break\"; reason: string } {\n const restrict = options.restrict;\n if (target.excludedActionIds.has(action.id)) return { status: \"absent\" };\n if (restrict?.exclude?.includes(action.id)) return { status: \"absent\" };\n if (restrict?.categories && !restrict.categories.includes(action.category)) return { status: \"absent\" };\n if (action.writes && action.writes.length > 0) {\n // A read-only target offers nothing that would change the record.\n if (target.readOnly) return { status: \"absent\" };\n const writable = new Set(target.writable.map((w) => w.name));\n if (!action.writes.some((name) => writable.has(name))) return { status: \"absent\" };\n }\n if (action.requiresAuth && !target.auth.authenticated && !options.authResumable) {\n // Sign-in-then-resume is the capability; without the auth port it is absent (N8).\n return { status: \"absent\" };\n }\n const own = action.eligible(target);\n if (own.status === \"unavailable\") {\n // A greyed row must say why; without a sentence it is absent.\n return own.sentence.trim() ? own : { status: \"absent\" };\n }\n if (own.status !== \"unavailable-verb\") return own;\n if (action.category !== \"clipboard\" || !VERBS.has(own.verb)) {\n return {\n status: \"contract-break\",\n reason: `Action \"${action.id}\" answered unavailable-verb but only the six universal verbs (category \"clipboard\") may grey; it is absent instead.`,\n };\n }\n if (!own.sentence.trim()) {\n return {\n status: \"contract-break\",\n reason: `Universal verb \"${action.id}\" greyed without a sentence; a greyed verb must say why.`,\n };\n }\n return own;\n}\n\ninterface Entry {\n provider: ActionProvider;\n declared: readonly string[];\n}\n\nexport function createActionRegistry(options: ActionRegistryOptions): ActionRegistry {\n const ports = options.ports;\n const entries = new Map<string, Entry>();\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const l of [...listeners]) l();\n };\n\n return {\n register(provider) {\n if (entries.has(provider.id)) throw new DuplicateProviderError(provider.id);\n const declared = provider.declaredIds?.() ?? [];\n const own = new Set<string>();\n for (const id of declared) {\n if (own.has(id)) throw new DuplicateActionError(id, provider.id, provider.id);\n own.add(id);\n for (const [otherId, other] of entries) {\n if (other.declared.includes(id)) throw new DuplicateActionError(id, otherId, provider.id);\n }\n }\n const entry: Entry = { provider, declared };\n entries.set(provider.id, entry);\n notify();\n return () => {\n if (entries.get(provider.id) === entry) {\n entries.delete(provider.id);\n notify();\n }\n };\n },\n\n async resolve(target, restrict) {\n const authResumable = Boolean(ports.auth);\n const seen = new Map<string, string>();\n const out: ResolvedAction[] = [];\n for (const [providerId, { provider }] of [...entries]) {\n let actions: readonly Action[];\n try {\n actions = await provider.actions(target);\n } catch (error) {\n capture(ports, error, { providerId, stage: \"actions\" });\n continue;\n }\n for (const raw of actions) {\n const first = seen.get(raw.id);\n if (first !== undefined) {\n capture(ports, new DuplicateActionError(raw.id, first, providerId), { actionId: raw.id });\n continue;\n }\n seen.set(raw.id, providerId);\n const action: Action = raw.provider === providerId ? raw : { ...raw, provider: providerId };\n let eligibility: ReturnType<typeof computeEligibility>;\n try {\n eligibility = computeEligibility(action, target, { restrict: restrict ?? {}, authResumable });\n } catch (error) {\n capture(ports, error, { actionId: action.id, stage: \"eligible\" });\n continue;\n }\n if (eligibility.status === \"contract-break\") {\n capture(ports, new Error(eligibility.reason), { actionId: action.id, stage: \"eligible\" });\n continue;\n }\n if (eligibility.status === \"absent\") continue;\n out.push({ action, eligibility });\n }\n }\n return out;\n },\n\n providers() {\n return [...entries.keys()];\n },\n\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\n/** A provider over a fixed list; its ids are declared up front so duplicates fail at registration. */\nexport function staticActionProvider(\n id: string,\n actions: readonly Action[],\n tier: ActionProvider[\"tier\"] = \"T0\",\n): ActionProvider {\n const list = actions.map((a) => (a.provider === id ? a : { ...a, provider: id }));\n return {\n id,\n tier,\n declaredIds: () => list.map((a) => a.id),\n actions: () => list,\n };\n}\n\n/** Always-available eligibility, for actions with no rule of their own. */\nexport const alwaysEligible = (): Eligibility => ({ status: \"available\" });\n","/**\n * Sensitivity defaults and resolution (CONTRACT §2.1). Its own module so the\n * lightweight entries (`/actions`, the T0 React host) can project without\n * pulling the declaration registry.\n */\nimport type { DeclaredValue, Sensitivity } from \"./types\";\n\nexport const DEFAULT_SENSITIVITY: Sensitivity = Object.freeze({\n exportable: true,\n classification: \"ordinary\",\n includedByDefault: true,\n});\n\nexport function resolveSensitivity(value: Pick<DeclaredValue, \"sensitivity\">): Sensitivity {\n return {\n exportable: value.sensitivity?.exportable ?? DEFAULT_SENSITIVITY.exportable,\n classification: value.sensitivity?.classification ?? DEFAULT_SENSITIVITY.classification,\n includedByDefault: value.sensitivity?.includedByDefault ?? DEFAULT_SENSITIVITY.includedByDefault,\n };\n}\n","/**\n * PROJECTION BEFORE PROVIDERS (CONTRACT §2.4).\n *\n * The only place a raw read (`MountedSurface.readValues`,\n * `InternalResolvedItem.readValues`) is consumed on behalf of an action.\n * What leaves is projected: a value is kept only when it is DECLARED, its\n * classification is \"ordinary\" and it is exportable. Everything removed is\n * recorded as an omission, so the person and the agent can see what was held\n * back. No caller can relax this.\n */\nimport { resolveSensitivity } from \"../declare/sensitivity\";\nimport type { InternalResolvedItem, MountedSurface } from \"../declare/host\";\nimport type { DeclaredValue, Json, LiveValues, ResolvedSurfaceDeclaration } from \"../declare/types\";\nimport type { ProjectedReads, ReadOmission } from \"./types\";\n\nexport interface ProjectionSource {\n surface: MountedSurface | null;\n declaration: ResolvedSurfaceDeclaration | null;\n item: InternalResolvedItem | null;\n}\n\nfunction toJson(value: unknown): Json | undefined {\n if (value === undefined || typeof value === \"function\" || typeof value === \"symbol\") return undefined;\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") return value;\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n if (typeof value === \"bigint\") return value.toString();\n if (value instanceof Date) return value.toISOString();\n if (Array.isArray(value)) return value.map((v) => toJson(v) ?? null);\n if (typeof value === \"object\") {\n const out: { [key: string]: Json } = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n const j = toJson(v);\n if (j !== undefined) out[k] = j;\n }\n return out;\n }\n return undefined;\n}\n\n/** Keep declared, ordinary, exportable values; record every removal. */\nexport function projectValues(\n raw: Record<string, unknown>,\n declared: readonly Pick<DeclaredValue, \"name\" | \"sensitivity\">[],\n pathPrefix = \"\",\n): { values: Record<string, Json>; omissions: ReadOmission[] } {\n const byName = new Map(declared.map((d) => [d.name, d]));\n const values: Record<string, Json> = {};\n const omissions: ReadOmission[] = [];\n for (const [name, value] of Object.entries(raw)) {\n const path = pathPrefix ? `${pathPrefix}.${name}` : name;\n const decl = byName.get(name);\n if (!decl) {\n omissions.push({ path, reason: \"filtered\" });\n continue;\n }\n const sensitivity = resolveSensitivity(decl);\n if (sensitivity.classification !== \"ordinary\") {\n omissions.push({ path, reason: \"excluded\", classification: sensitivity.classification });\n continue;\n }\n if (!sensitivity.exportable) {\n omissions.push({ path, reason: \"excluded\" });\n continue;\n }\n const json = toJson(value);\n if (json === undefined) continue;\n values[name] = json;\n }\n return { values, omissions };\n}\n\nexport function createProjectedReads(source: ProjectionSource): ProjectedReads {\n return {\n async values(signal) {\n if (!source.surface || !source.declaration) return { values: {}, omissions: [] };\n const raw = await source.surface.readValues(signal);\n const screenValues = source.declaration.values;\n return projectValues(raw, screenValues);\n },\n async item(signal) {\n const item = source.item;\n if (!item || !source.declaration) return null;\n const type = source.declaration.itemTypes?.find((t) => t.name === item.itemType);\n if (!type) return { values: {}, omissions: [{ path: item.itemType, reason: \"filtered\" }] };\n const raw = await item.readValues(signal);\n return projectValues(raw, type.values, item.itemType);\n },\n async live(signal): Promise<LiveValues> {\n if (!source.surface?.readLive) return {};\n return source.surface.readLive(signal);\n },\n };\n}\n","import type { QualifiedValueKey } from \"./types\";\n\n/** Every value, write-target, item-type and group name (the DB CHECK rule). */\nexport const DECLARATION_NAME_RE = /^[a-z][a-z0-9_]*$/;\n\n/** \"<client>/<local>\" — the surface naming convention. */\nexport const SURFACE_NAME_RE = /^[a-z][a-z0-9-]*\\/[a-z0-9-/]+$/;\n\n/** A bare screen value (`status`) or an item value (`table_row.status`). */\nexport const QUALIFIED_VALUE_KEY_RE = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)?$/;\n\nexport interface ParsedValueKey {\n /** \"\" for a screen-level value. */\n itemType: string;\n name: string;\n}\n\n/** Split a qualified key; null when it is not a valid bare or qualified key. */\nexport function parseQualifiedValueKey(key: string): ParsedValueKey | null {\n if (!QUALIFIED_VALUE_KEY_RE.test(key)) return null;\n const dot = key.indexOf(\".\");\n if (dot === -1) return { itemType: \"\", name: key };\n return { itemType: key.slice(0, dot), name: key.slice(dot + 1) };\n}\n\nexport function qualifyValueKey(itemType: string, name: string): QualifiedValueKey {\n return itemType ? `${itemType}.${name}` : name;\n}\n\n/** The client that owns a surface: its name prefix. */\nexport function surfaceClientOf(surfaceName: string): string {\n return surfaceName.split(\"/\")[0] ?? \"\";\n}\n","/**\n * Building a ClickTarget from what is declared at the click (CONTRACT §2.4).\n * Key existence, never value population: a value key is present when the\n * declaration gives it a read path here.\n */\nimport { resolveSensitivity } from \"../declare/sensitivity\";\nimport { qualifyValueKey } from \"../declare/keys\";\nimport type { ResolvedItem, ResolvedSurfaceDeclaration, WriteTarget } from \"../declare/types\";\nimport type { ClickTarget } from \"./types\";\n\nexport interface ClickTargetInput {\n surface?: ClickTarget[\"surface\"];\n declaration?: ResolvedSurfaceDeclaration | null;\n item?: ResolvedItem | null;\n level?: string | null;\n selection?: ClickTarget[\"selection\"];\n payloadKinds?: ClickTarget[\"payloadKinds\"];\n /** Defaults to the item's readOnly (or false). */\n readOnly?: boolean;\n /** Extra exclusions on top of the declaration's own. */\n excludedActionIds?: Iterable<string>;\n scopeChain?: readonly string[];\n organizationId?: string | null;\n auth?: Partial<ClickTarget[\"auth\"]>;\n /** Write targets the host can serve right now; defaults to every declared target for the screen/item. */\n writable?: readonly WriteTarget[];\n host?: unknown;\n}\n\nfunction domainOf(surfaceName: string): string {\n const slash = surfaceName.indexOf(\"/\");\n return slash > 0 ? surfaceName.slice(0, slash) : surfaceName;\n}\n\nexport function createClickTarget(input: ClickTargetInput = {}): ClickTarget {\n const declaration = input.declaration ?? null;\n const item = input.item ?? null;\n const valueKeys = new Set<string>();\n let restricted = false;\n const writable: WriteTarget[] = [];\n if (declaration) {\n for (const value of declaration.values) {\n valueKeys.add(value.name);\n const s = resolveSensitivity(value);\n if (s.classification !== \"ordinary\" || !s.exportable) restricted = true;\n }\n const itemType = item ? declaration.itemTypes?.find((t) => t.name === item.itemType) : undefined;\n if (itemType) {\n for (const value of itemType.values) {\n valueKeys.add(qualifyValueKey(itemType.name, value.name));\n const s = resolveSensitivity(value);\n if (s.classification !== \"ordinary\" || !s.exportable) restricted = true;\n }\n for (const t of itemType.writeTargets ?? []) writable.push(t);\n }\n for (const t of declaration.writeTargets ?? []) {\n if (!t.itemType || t.itemType === item?.itemType) writable.push(t);\n }\n }\n const readOnly = input.readOnly ?? item?.readOnly ?? false;\n const surfaceName = input.surface?.surfaceName ?? declaration?.surfaceName ?? null;\n const scopeChain = input.scopeChain ?? (surfaceName ? [\"global\", domainOf(surfaceName), surfaceName] : [\"global\"]);\n return {\n surface: input.surface ?? null,\n declaration,\n valueKeys,\n scopeChain,\n excludedActionIds: new Set(input.excludedActionIds ?? []),\n item,\n level: input.level ?? item?.level ?? null,\n selection: input.selection ?? null,\n payloadKinds: input.payloadKinds ?? [],\n writable: readOnly ? [] : (input.writable ?? writable),\n readOnly,\n restricted,\n organizationId: input.organizationId ?? null,\n auth: {\n authenticated: input.auth?.authenticated ?? false,\n admin: input.auth?.admin ?? false,\n creator: input.auth?.creator ?? false,\n },\n host: input.host,\n };\n}\n","/**\n * The one run path (T1e). Every layout runs an action through here, so the\n * sign-in-then-resume rule, projection and failure capture live once.\n */\nimport type { TransferOutcome } from \"@ai-matrx/kit/content-transfer\";\nimport type { AlchemyHostPorts } from \"../ports\";\nimport { createProjectedReads, type ProjectionSource } from \"./projection\";\nimport type { Action, ClickTarget } from \"./types\";\n\nexport interface RunActionOptions {\n ports: AlchemyHostPorts;\n /** Raw sources; projected before the action sees them. */\n source?: ProjectionSource;\n /** Stable fingerprint of the content, for sign-in-then-resume. */\n contentFingerprint?: string;\n signal?: AbortSignal;\n}\n\nexport type RunResult =\n | { status: \"ran\"; outcome: TransferOutcome | void }\n | { status: \"auth-pending\" }\n | { status: \"failed\"; error: unknown };\n\nexport async function runAction(action: Action, target: ClickTarget, options: RunActionOptions): Promise<RunResult> {\n const { ports } = options;\n if (action.requiresAuth && !target.auth.authenticated) {\n if (!ports.auth) {\n // Unreachable through resolve (the action is absent without the port); guard direct callers.\n ports.diagnostics.capture(new Error(`requiresAuth action \"${action.id}\" ran signed out with no auth port`), {\n area: \"actions\",\n detail: { actionId: action.id },\n });\n return { status: \"failed\", error: new Error(\"sign-in unavailable\") };\n }\n ports.auth.signInThenResume({ actionId: action.id, contentFingerprint: options.contentFingerprint ?? action.id });\n return { status: \"auth-pending\" };\n }\n const reads = createProjectedReads(options.source ?? { surface: null, declaration: target.declaration, item: null });\n const signal = options.signal ?? new AbortController().signal;\n try {\n const outcome = await action.run(target, { reads, ports, signal });\n return { status: \"ran\", outcome };\n } catch (error) {\n ports.diagnostics.capture(error, { area: \"actions\", detail: { actionId: action.id, stage: \"run\" } });\n const label = typeof action.label === \"string\" ? action.label : action.id;\n ports.notify?.error(`${label} did not finish.`, \"Try again; if it keeps failing, report it from the feedback button.\");\n return { status: \"failed\", error };\n }\n}\n"],"mappings":"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","names":[]}
@@ -649,6 +649,11 @@ interface Action {
649
649
  /** Declared write targets this action lands through. Absent on a read-only target. */
650
650
  writes?: readonly string[];
651
651
  destructive?: boolean;
652
+ /**
653
+ * STARTS A NEW GROUP inside its section: the menu draws a divider above it (never as a section's
654
+ * first row). A delete sits in its own group, last, never beside Duplicate (DATA-V2-BASICS-2).
655
+ */
656
+ startsGroup?: boolean;
652
657
  preserveSelection?: boolean;
653
658
  /** Link rows navigate instead of running. */
654
659
  href?: string | ((target: ClickTarget) => string);
package/dist/actions.d.ts CHANGED
@@ -649,6 +649,11 @@ interface Action {
649
649
  /** Declared write targets this action lands through. Absent on a read-only target. */
650
650
  writes?: readonly string[];
651
651
  destructive?: boolean;
652
+ /**
653
+ * STARTS A NEW GROUP inside its section: the menu draws a divider above it (never as a section's
654
+ * first row). A delete sits in its own group, last, never beside Duplicate (DATA-V2-BASICS-2).
655
+ */
656
+ startsGroup?: boolean;
652
657
  preserveSelection?: boolean;
653
658
  /** Link rows navigate instead of running. */
654
659
  href?: string | ((target: ClickTarget) => string);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/actions/click-target.ts","../src/actions/types.ts","../src/actions/registry.ts","../src/declare/sensitivity.ts","../src/actions/projection.ts","../src/declare/keys.ts","../src/actions/target.ts","../src/actions/run.ts"],"sourcesContent":["/**\n * The click target (CONTRACT §2.4) in its own module: `/ports` references it,\n * and `/ports` must stay free of every other package (types only).\n */\nimport type { QualifiedValueKey, ResolvedItem, ResolvedSurfaceDeclaration, WriteTarget } from \"../declare/types\";\n\n/** The six universal verbs — the ONLY rows that may grey (R1 c). */\nexport const UNIVERSAL_VERBS = [\"copy\", \"cut\", \"paste\", \"undo\", \"redo\", \"find\"] as const;\nexport type UniversalVerb = (typeof UNIVERSAL_VERBS)[number];\n\n/** The transfer Payload's kinds (`actions/types.ts` asserts equality at compile time). */\nexport type PayloadKind = \"text\" | \"markdown\" | \"json\" | \"rows\" | \"registered\";\n\n/** What eligibility is computed from: everything declared at the click target. */\nexport interface ClickTarget {\n surface: { instanceId: string; surfaceName: string } | null;\n /** The resolved declaration (inheritance applied). Eligibility decides by KEY existence over it. */\n declaration: ResolvedSurfaceDeclaration | null;\n /** Declared value keys with a read path here (screen names + qualified item keys). Key existence, not population. */\n valueKeys: ReadonlySet<QualifiedValueKey>;\n /** Scope chain, most general first: [\"global\", \"<domain>\", \"<surfaceName>\"]. */\n scopeChain: readonly string[];\n /** The surface's explicit exclusions (the one override). */\n excludedActionIds: ReadonlySet<string>;\n item: ResolvedItem | null;\n level: string | null;\n /** Offsets + the live range handle, editable or not. */\n selection: {\n text: string;\n type: \"editable\" | \"non-editable\";\n start: number;\n end: number;\n /** Opaque host handle (DOM Range / element) used to restore or stream into the selection. */\n handle?: unknown;\n } | null;\n /** Payload kinds a capture of this target would produce (from declared value types/kinds). */\n payloadKinds: readonly PayloadKind[];\n /** Declared targets writable on this target right now. */\n writable: readonly WriteTarget[];\n readOnly: boolean;\n /** True when any declared value here is secret/credential or non-exportable. */\n restricted: boolean;\n organizationId: string | null;\n auth: { authenticated: boolean; admin: boolean; creator: boolean };\n /**\n * Host-owned context a provider reads to act (the rich-document source, the\n * editable field's callbacks). Opaque to the package; never crosses into the\n * model. Providers that need it narrow it themselves.\n */\n host?: unknown;\n}\n\n","/**\n * Matrx Alchemy — Actions types (CONTRACT §2.4).\n *\n * One registry of everything a person can do to a thing. Providers plug in;\n * eligibility is computed from what is declared at the click target; duplicate\n * ids are refused; an action that does not apply is ABSENT (chair ruling R1).\n *\n * Framework-neutral: zero React, zero `@ai-matrx/agents`. Icons are string KEYS\n * resolved by the host's `IconResolverPort`; labels go through `I18nPort`.\n */\nimport type { Payload, TransferOutcome } from \"@ai-matrx/kit/content-transfer\";\nimport type { Json, LiveValues } from \"../declare/types\";\nimport type { AlchemyHostPorts } from \"../ports\";\nimport type { ClickTarget, PayloadKind } from \"./click-target\";\n\nexport type { ClickTarget, PayloadKind } from \"./click-target\";\n\n/** PayloadKind is the transfer Payload's kind union, kept equal at compile time. */\ntype Equal<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;\nconst payloadKindsAgree: Equal<PayloadKind, Payload[\"kind\"]> = true;\nvoid payloadKindsAgree;\n\nexport { UNIVERSAL_VERBS, type UniversalVerb } from \"./click-target\";\nimport type { UniversalVerb } from \"./click-target\";\n\n/**\n * Chair ruling R1: an action that does not apply or cannot run now is ABSENT.\n * Only the six universal verbs may answer \"unavailable-verb\": greyed in the\n * strip/bar, the sentence as tooltip. `requiresAuth` is NOT unavailability.\n */\nexport type Eligibility =\n | { status: \"absent\" }\n | { status: \"available\" }\n | { status: \"unavailable-verb\"; verb: UniversalVerb; sentence: string }\n /**\n * ABSENT OR HONEST (coordinator ruling 2026-09-27, admin pass): a row that cannot run here but\n * SAYS WHY (\"Unchanged from the pack\", \"Manifested surfaces are managed in code\") stays visible,\n * greyed, with its reason — the menu is the same everywhere, only availability changes. A row\n * with no reason is absent (the registry refuses an empty sentence).\n */\n | { status: \"unavailable\"; sentence: string };\n\n/** rich-document's ActionCategory, unchanged, plus the three the context menu groups by. */\nexport type ActionCategory =\n | \"feedback\"\n | \"copy\"\n | \"export\"\n | \"save\"\n | \"edit\"\n | \"share\"\n | \"creator\"\n | \"app\"\n | \"listen\"\n | \"study\"\n | \"ask\"\n | \"ai\"\n | \"admin\"\n | \"clipboard\"\n | \"history\"\n | \"surface-info\";\n\nexport const ACTION_CATEGORIES: readonly ActionCategory[] = [\n \"feedback\",\n \"copy\",\n \"export\",\n \"save\",\n \"edit\",\n \"share\",\n \"creator\",\n \"app\",\n \"listen\",\n \"study\",\n \"ask\",\n \"ai\",\n \"admin\",\n \"clipboard\",\n \"history\",\n \"surface-info\",\n];\n\n/**\n * An optional named section inside the action's group (a grid's cell / row /\n * column). `primary` marks the thing the person clicked: every layout renders\n * it first, inline, heading kept (context-menu-v3's primary section law).\n */\nexport interface ActionSection {\n id: string;\n label: string;\n labelKey?: string;\n icon?: string;\n primary?: boolean;\n /**\n * \"group\" (default): a heading over related rows — must be in the package's\n * APPROVED_HEADINGS (@ai-matrx/alchemy/menu). \"target\": the label of the\n * thing the person clicked, supplied at run time (\"Row · Widget A\").\n */\n kind?: \"group\" | \"target\";\n}\n\nexport interface Action {\n /** Globally unique. A duplicate is refused (never overwritten). */\n id: string;\n /** The provider that registered it — filled by the registry. */\n provider?: string;\n /** Resolved through I18nPort.t(labelKey ?? id, label). */\n label: string | ((target: ClickTarget) => string);\n labelKey?: string;\n /** Icon KEY resolved by IconResolverPort — the model stays framework-neutral. */\n icon?: string;\n /** Host styling hint for the glyph (rich-document iconColor). Renderers may ignore it. */\n iconTone?: string;\n /** Replaces `icon` while a state holds (a spinner while speaking). */\n stateIcon?(target: ClickTarget): { icon: string; spin?: boolean } | null;\n hint?: string;\n hintKey?: string;\n description?: string;\n category: ActionCategory;\n /** Which universal verb this clipboard action is (addressed by role in every layout). */\n verb?: UniversalVerb;\n order?: number;\n /** Bar layout slot. Default \"overflow\". */\n placement?: \"primary\" | \"overflow\" | \"both\";\n section?: ActionSection;\n /** A signed-out person still sees it; running goes through AuthResumePort (sign in, then resume). */\n requiresAuth?: boolean;\n /** Declared write targets this action lands through. Absent on a read-only target. */\n writes?: readonly string[];\n destructive?: boolean;\n preserveSelection?: boolean;\n /** Link rows navigate instead of running. */\n href?: string | ((target: ClickTarget) => string);\n eligible(target: ClickTarget): Eligibility;\n /** Toggle/pressed state: a pressed button or a checked row. */\n pressed?(target: ClickTarget): boolean;\n /** Present = the row is a checkbox (pressed is its state). */\n toggle?: boolean;\n subscribe?(onChange: () => void, target: ClickTarget): () => void;\n /**\n * The rows behind this submenu are still loading (an agent library's first\n * fetch). The row shows as loading — never absent, never silent — and its\n * `expand` resolves once they arrive.\n */\n pending?(target: ClickTarget): boolean;\n /** Submenus whose rows load on open (agent lists). */\n expand?(target: ClickTarget, signal: AbortSignal): Promise<readonly Action[]>;\n /** Shown centred when `expand` resolves empty. */\n emptyLabel?: string;\n run(target: ClickTarget, context: ActionRunContext): Promise<TransferOutcome | void> | TransferOutcome | void;\n}\n\n/** One removed read, recorded (kit content-transfer's omission shape). */\nexport interface ReadOmission {\n path: string;\n reason: \"excluded\" | \"filtered\";\n classification?: \"secret\" | \"credential\";\n}\n\n/**\n * PROJECTION BEFORE PROVIDERS. Providers never receive a raw read. Every read\n * they can make is projected first: secret and credential values, values\n * declared `exportable: false`, and values nobody declared are removed, with an\n * omission recorded. Raw reads never leave the package.\n */\nexport interface ProjectedReads {\n values(signal: AbortSignal): Promise<{ values: Record<string, Json>; omissions: readonly ReadOmission[] }>;\n item(signal: AbortSignal): Promise<{ values: Record<string, Json>; omissions: readonly ReadOmission[] } | null>;\n live(signal: AbortSignal): Promise<LiveValues>;\n}\n\n/**\n * What a running action receives. `door` (ALC-17) and `operate` (ALC-16) join\n * with their waves; until then those capabilities are absent (N8).\n */\nexport interface ActionRunContext {\n reads: ProjectedReads;\n ports: AlchemyHostPorts;\n signal: AbortSignal;\n}\n\nexport interface ActionProvider {\n id: string;\n /** T0 providers are known at mount; T1 providers load with the menu on first open. */\n tier: \"T0\" | \"T1\";\n /**\n * The ids this provider can ever yield, when known up front. Lets the\n * registry refuse a duplicate at registration instead of at first open.\n */\n declaredIds?(): readonly string[];\n actions(target: ClickTarget): readonly Action[] | Promise<readonly Action[]>;\n}\n\nexport interface ResolvedAction {\n action: Action;\n eligibility: Exclude<Eligibility, { status: \"absent\" }>;\n}\n\nexport interface ResolveRestriction {\n exclude?: readonly string[];\n categories?: readonly ActionCategory[];\n}\n\nexport interface ActionRegistry {\n /** Throws DuplicateActionError naming both providers; nothing is overwritten. */\n register(provider: ActionProvider): () => void;\n /** restrict: a host may remove ids or categories, never add ineligible ones. */\n resolve(target: ClickTarget, restrict?: ResolveRestriction): Promise<readonly ResolvedAction[]>;\n /** Registered provider ids, in registration order. */\n providers(): readonly string[];\n /** Change notification (a provider registered or unregistered). */\n subscribe(listener: () => void): () => void;\n}\n","/**\n * The one action registry (CONTRACT §2.4).\n *\n * - Duplicate ids are REFUSED, never overwritten (rich-document's registry\n * silently overwrote — R12). A duplicate known at registration throws; one\n * that only appears when a provider yields it is dropped and captured.\n * - Eligibility is one pure pass over the resolved click target, under\n * \"restrict, never relax\": a host may remove ids or categories, never add.\n * - R1: an action that does not apply is ABSENT. Only the six universal verbs\n * (category \"clipboard\") may answer `unavailable-verb`, with a sentence; any\n * other action answering it is a contract break → absent + diagnostics.\n */\nimport type { AlchemyHostPorts, Json } from \"../ports\";\nimport {\n UNIVERSAL_VERBS,\n type Action,\n type ActionProvider,\n type ActionRegistry,\n type ClickTarget,\n type Eligibility,\n type ResolveRestriction,\n type ResolvedAction,\n} from \"./types\";\n\nexport class DuplicateActionError extends Error {\n readonly actionId: string;\n readonly providers: readonly [string, string];\n constructor(actionId: string, first: string, second: string) {\n super(\n first === second\n ? `Action \"${actionId}\" is yielded twice by provider \"${first}\". Ids are unique; the second was refused.`\n : `Action \"${actionId}\" is registered by provider \"${first}\" and again by \"${second}\". Ids are unique; \"${second}\"'s copy was refused.`,\n );\n this.name = \"DuplicateActionError\";\n this.actionId = actionId;\n this.providers = [first, second];\n }\n}\n\nexport class DuplicateProviderError extends Error {\n readonly providerId: string;\n constructor(providerId: string) {\n super(`Action provider \"${providerId}\" is already registered. Unregister it first; nothing was overwritten.`);\n this.name = \"DuplicateProviderError\";\n this.providerId = providerId;\n }\n}\n\nexport interface ActionRegistryOptions {\n /** `diagnostics` is required (nothing fails silently); `auth` decides whether requiresAuth actions reach a signed-out person. */\n ports: Pick<AlchemyHostPorts, \"diagnostics\" | \"auth\">;\n}\n\nconst VERBS = new Set<string>(UNIVERSAL_VERBS);\n\nfunction capture(ports: ActionRegistryOptions[\"ports\"], error: unknown, detail: Json): void {\n try {\n ports.diagnostics.capture(error, { area: \"actions\", detail });\n } catch {\n // A broken diagnostics port must never take the menu down with it.\n }\n}\n\n/**\n * Eligibility of one action at one target — the ONE pure pass (CONTRACT §1.6).\n * Exported for providers' tests; hosts call `registry.resolve`.\n */\nexport function computeEligibility(\n action: Action,\n target: ClickTarget,\n options: { restrict?: ResolveRestriction; authResumable: boolean },\n): Eligibility | { status: \"contract-break\"; reason: string } {\n const restrict = options.restrict;\n if (target.excludedActionIds.has(action.id)) return { status: \"absent\" };\n if (restrict?.exclude?.includes(action.id)) return { status: \"absent\" };\n if (restrict?.categories && !restrict.categories.includes(action.category)) return { status: \"absent\" };\n if (action.writes && action.writes.length > 0) {\n // A read-only target offers nothing that would change the record.\n if (target.readOnly) return { status: \"absent\" };\n const writable = new Set(target.writable.map((w) => w.name));\n if (!action.writes.some((name) => writable.has(name))) return { status: \"absent\" };\n }\n if (action.requiresAuth && !target.auth.authenticated && !options.authResumable) {\n // Sign-in-then-resume is the capability; without the auth port it is absent (N8).\n return { status: \"absent\" };\n }\n const own = action.eligible(target);\n if (own.status === \"unavailable\") {\n // A greyed row must say why; without a sentence it is absent.\n return own.sentence.trim() ? own : { status: \"absent\" };\n }\n if (own.status !== \"unavailable-verb\") return own;\n if (action.category !== \"clipboard\" || !VERBS.has(own.verb)) {\n return {\n status: \"contract-break\",\n reason: `Action \"${action.id}\" answered unavailable-verb but only the six universal verbs (category \"clipboard\") may grey; it is absent instead.`,\n };\n }\n if (!own.sentence.trim()) {\n return {\n status: \"contract-break\",\n reason: `Universal verb \"${action.id}\" greyed without a sentence; a greyed verb must say why.`,\n };\n }\n return own;\n}\n\ninterface Entry {\n provider: ActionProvider;\n declared: readonly string[];\n}\n\nexport function createActionRegistry(options: ActionRegistryOptions): ActionRegistry {\n const ports = options.ports;\n const entries = new Map<string, Entry>();\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const l of [...listeners]) l();\n };\n\n return {\n register(provider) {\n if (entries.has(provider.id)) throw new DuplicateProviderError(provider.id);\n const declared = provider.declaredIds?.() ?? [];\n const own = new Set<string>();\n for (const id of declared) {\n if (own.has(id)) throw new DuplicateActionError(id, provider.id, provider.id);\n own.add(id);\n for (const [otherId, other] of entries) {\n if (other.declared.includes(id)) throw new DuplicateActionError(id, otherId, provider.id);\n }\n }\n const entry: Entry = { provider, declared };\n entries.set(provider.id, entry);\n notify();\n return () => {\n if (entries.get(provider.id) === entry) {\n entries.delete(provider.id);\n notify();\n }\n };\n },\n\n async resolve(target, restrict) {\n const authResumable = Boolean(ports.auth);\n const seen = new Map<string, string>();\n const out: ResolvedAction[] = [];\n for (const [providerId, { provider }] of [...entries]) {\n let actions: readonly Action[];\n try {\n actions = await provider.actions(target);\n } catch (error) {\n capture(ports, error, { providerId, stage: \"actions\" });\n continue;\n }\n for (const raw of actions) {\n const first = seen.get(raw.id);\n if (first !== undefined) {\n capture(ports, new DuplicateActionError(raw.id, first, providerId), { actionId: raw.id });\n continue;\n }\n seen.set(raw.id, providerId);\n const action: Action = raw.provider === providerId ? raw : { ...raw, provider: providerId };\n let eligibility: ReturnType<typeof computeEligibility>;\n try {\n eligibility = computeEligibility(action, target, { restrict: restrict ?? {}, authResumable });\n } catch (error) {\n capture(ports, error, { actionId: action.id, stage: \"eligible\" });\n continue;\n }\n if (eligibility.status === \"contract-break\") {\n capture(ports, new Error(eligibility.reason), { actionId: action.id, stage: \"eligible\" });\n continue;\n }\n if (eligibility.status === \"absent\") continue;\n out.push({ action, eligibility });\n }\n }\n return out;\n },\n\n providers() {\n return [...entries.keys()];\n },\n\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\n/** A provider over a fixed list; its ids are declared up front so duplicates fail at registration. */\nexport function staticActionProvider(\n id: string,\n actions: readonly Action[],\n tier: ActionProvider[\"tier\"] = \"T0\",\n): ActionProvider {\n const list = actions.map((a) => (a.provider === id ? a : { ...a, provider: id }));\n return {\n id,\n tier,\n declaredIds: () => list.map((a) => a.id),\n actions: () => list,\n };\n}\n\n/** Always-available eligibility, for actions with no rule of their own. */\nexport const alwaysEligible = (): Eligibility => ({ status: \"available\" });\n","/**\n * Sensitivity defaults and resolution (CONTRACT §2.1). Its own module so the\n * lightweight entries (`/actions`, the T0 React host) can project without\n * pulling the declaration registry.\n */\nimport type { DeclaredValue, Sensitivity } from \"./types\";\n\nexport const DEFAULT_SENSITIVITY: Sensitivity = Object.freeze({\n exportable: true,\n classification: \"ordinary\",\n includedByDefault: true,\n});\n\nexport function resolveSensitivity(value: Pick<DeclaredValue, \"sensitivity\">): Sensitivity {\n return {\n exportable: value.sensitivity?.exportable ?? DEFAULT_SENSITIVITY.exportable,\n classification: value.sensitivity?.classification ?? DEFAULT_SENSITIVITY.classification,\n includedByDefault: value.sensitivity?.includedByDefault ?? DEFAULT_SENSITIVITY.includedByDefault,\n };\n}\n","/**\n * PROJECTION BEFORE PROVIDERS (CONTRACT §2.4).\n *\n * The only place a raw read (`MountedSurface.readValues`,\n * `InternalResolvedItem.readValues`) is consumed on behalf of an action.\n * What leaves is projected: a value is kept only when it is DECLARED, its\n * classification is \"ordinary\" and it is exportable. Everything removed is\n * recorded as an omission, so the person and the agent can see what was held\n * back. No caller can relax this.\n */\nimport { resolveSensitivity } from \"../declare/sensitivity\";\nimport type { InternalResolvedItem, MountedSurface } from \"../declare/host\";\nimport type { DeclaredValue, Json, LiveValues, ResolvedSurfaceDeclaration } from \"../declare/types\";\nimport type { ProjectedReads, ReadOmission } from \"./types\";\n\nexport interface ProjectionSource {\n surface: MountedSurface | null;\n declaration: ResolvedSurfaceDeclaration | null;\n item: InternalResolvedItem | null;\n}\n\nfunction toJson(value: unknown): Json | undefined {\n if (value === undefined || typeof value === \"function\" || typeof value === \"symbol\") return undefined;\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") return value;\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n if (typeof value === \"bigint\") return value.toString();\n if (value instanceof Date) return value.toISOString();\n if (Array.isArray(value)) return value.map((v) => toJson(v) ?? null);\n if (typeof value === \"object\") {\n const out: { [key: string]: Json } = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n const j = toJson(v);\n if (j !== undefined) out[k] = j;\n }\n return out;\n }\n return undefined;\n}\n\n/** Keep declared, ordinary, exportable values; record every removal. */\nexport function projectValues(\n raw: Record<string, unknown>,\n declared: readonly Pick<DeclaredValue, \"name\" | \"sensitivity\">[],\n pathPrefix = \"\",\n): { values: Record<string, Json>; omissions: ReadOmission[] } {\n const byName = new Map(declared.map((d) => [d.name, d]));\n const values: Record<string, Json> = {};\n const omissions: ReadOmission[] = [];\n for (const [name, value] of Object.entries(raw)) {\n const path = pathPrefix ? `${pathPrefix}.${name}` : name;\n const decl = byName.get(name);\n if (!decl) {\n omissions.push({ path, reason: \"filtered\" });\n continue;\n }\n const sensitivity = resolveSensitivity(decl);\n if (sensitivity.classification !== \"ordinary\") {\n omissions.push({ path, reason: \"excluded\", classification: sensitivity.classification });\n continue;\n }\n if (!sensitivity.exportable) {\n omissions.push({ path, reason: \"excluded\" });\n continue;\n }\n const json = toJson(value);\n if (json === undefined) continue;\n values[name] = json;\n }\n return { values, omissions };\n}\n\nexport function createProjectedReads(source: ProjectionSource): ProjectedReads {\n return {\n async values(signal) {\n if (!source.surface || !source.declaration) return { values: {}, omissions: [] };\n const raw = await source.surface.readValues(signal);\n const screenValues = source.declaration.values;\n return projectValues(raw, screenValues);\n },\n async item(signal) {\n const item = source.item;\n if (!item || !source.declaration) return null;\n const type = source.declaration.itemTypes?.find((t) => t.name === item.itemType);\n if (!type) return { values: {}, omissions: [{ path: item.itemType, reason: \"filtered\" }] };\n const raw = await item.readValues(signal);\n return projectValues(raw, type.values, item.itemType);\n },\n async live(signal): Promise<LiveValues> {\n if (!source.surface?.readLive) return {};\n return source.surface.readLive(signal);\n },\n };\n}\n","import type { QualifiedValueKey } from \"./types\";\n\n/** Every value, write-target, item-type and group name (the DB CHECK rule). */\nexport const DECLARATION_NAME_RE = /^[a-z][a-z0-9_]*$/;\n\n/** \"<client>/<local>\" — the surface naming convention. */\nexport const SURFACE_NAME_RE = /^[a-z][a-z0-9-]*\\/[a-z0-9-/]+$/;\n\n/** A bare screen value (`status`) or an item value (`table_row.status`). */\nexport const QUALIFIED_VALUE_KEY_RE = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)?$/;\n\nexport interface ParsedValueKey {\n /** \"\" for a screen-level value. */\n itemType: string;\n name: string;\n}\n\n/** Split a qualified key; null when it is not a valid bare or qualified key. */\nexport function parseQualifiedValueKey(key: string): ParsedValueKey | null {\n if (!QUALIFIED_VALUE_KEY_RE.test(key)) return null;\n const dot = key.indexOf(\".\");\n if (dot === -1) return { itemType: \"\", name: key };\n return { itemType: key.slice(0, dot), name: key.slice(dot + 1) };\n}\n\nexport function qualifyValueKey(itemType: string, name: string): QualifiedValueKey {\n return itemType ? `${itemType}.${name}` : name;\n}\n\n/** The client that owns a surface: its name prefix. */\nexport function surfaceClientOf(surfaceName: string): string {\n return surfaceName.split(\"/\")[0] ?? \"\";\n}\n","/**\n * Building a ClickTarget from what is declared at the click (CONTRACT §2.4).\n * Key existence, never value population: a value key is present when the\n * declaration gives it a read path here.\n */\nimport { resolveSensitivity } from \"../declare/sensitivity\";\nimport { qualifyValueKey } from \"../declare/keys\";\nimport type { ResolvedItem, ResolvedSurfaceDeclaration, WriteTarget } from \"../declare/types\";\nimport type { ClickTarget } from \"./types\";\n\nexport interface ClickTargetInput {\n surface?: ClickTarget[\"surface\"];\n declaration?: ResolvedSurfaceDeclaration | null;\n item?: ResolvedItem | null;\n level?: string | null;\n selection?: ClickTarget[\"selection\"];\n payloadKinds?: ClickTarget[\"payloadKinds\"];\n /** Defaults to the item's readOnly (or false). */\n readOnly?: boolean;\n /** Extra exclusions on top of the declaration's own. */\n excludedActionIds?: Iterable<string>;\n scopeChain?: readonly string[];\n organizationId?: string | null;\n auth?: Partial<ClickTarget[\"auth\"]>;\n /** Write targets the host can serve right now; defaults to every declared target for the screen/item. */\n writable?: readonly WriteTarget[];\n host?: unknown;\n}\n\nfunction domainOf(surfaceName: string): string {\n const slash = surfaceName.indexOf(\"/\");\n return slash > 0 ? surfaceName.slice(0, slash) : surfaceName;\n}\n\nexport function createClickTarget(input: ClickTargetInput = {}): ClickTarget {\n const declaration = input.declaration ?? null;\n const item = input.item ?? null;\n const valueKeys = new Set<string>();\n let restricted = false;\n const writable: WriteTarget[] = [];\n if (declaration) {\n for (const value of declaration.values) {\n valueKeys.add(value.name);\n const s = resolveSensitivity(value);\n if (s.classification !== \"ordinary\" || !s.exportable) restricted = true;\n }\n const itemType = item ? declaration.itemTypes?.find((t) => t.name === item.itemType) : undefined;\n if (itemType) {\n for (const value of itemType.values) {\n valueKeys.add(qualifyValueKey(itemType.name, value.name));\n const s = resolveSensitivity(value);\n if (s.classification !== \"ordinary\" || !s.exportable) restricted = true;\n }\n for (const t of itemType.writeTargets ?? []) writable.push(t);\n }\n for (const t of declaration.writeTargets ?? []) {\n if (!t.itemType || t.itemType === item?.itemType) writable.push(t);\n }\n }\n const readOnly = input.readOnly ?? item?.readOnly ?? false;\n const surfaceName = input.surface?.surfaceName ?? declaration?.surfaceName ?? null;\n const scopeChain = input.scopeChain ?? (surfaceName ? [\"global\", domainOf(surfaceName), surfaceName] : [\"global\"]);\n return {\n surface: input.surface ?? null,\n declaration,\n valueKeys,\n scopeChain,\n excludedActionIds: new Set(input.excludedActionIds ?? []),\n item,\n level: input.level ?? item?.level ?? null,\n selection: input.selection ?? null,\n payloadKinds: input.payloadKinds ?? [],\n writable: readOnly ? [] : (input.writable ?? writable),\n readOnly,\n restricted,\n organizationId: input.organizationId ?? null,\n auth: {\n authenticated: input.auth?.authenticated ?? false,\n admin: input.auth?.admin ?? false,\n creator: input.auth?.creator ?? false,\n },\n host: input.host,\n };\n}\n","/**\n * The one run path (T1e). Every layout runs an action through here, so the\n * sign-in-then-resume rule, projection and failure capture live once.\n */\nimport type { TransferOutcome } from \"@ai-matrx/kit/content-transfer\";\nimport type { AlchemyHostPorts } from \"../ports\";\nimport { createProjectedReads, type ProjectionSource } from \"./projection\";\nimport type { Action, ClickTarget } from \"./types\";\n\nexport interface RunActionOptions {\n ports: AlchemyHostPorts;\n /** Raw sources; projected before the action sees them. */\n source?: ProjectionSource;\n /** Stable fingerprint of the content, for sign-in-then-resume. */\n contentFingerprint?: string;\n signal?: AbortSignal;\n}\n\nexport type RunResult =\n | { status: \"ran\"; outcome: TransferOutcome | void }\n | { status: \"auth-pending\" }\n | { status: \"failed\"; error: unknown };\n\nexport async function runAction(action: Action, target: ClickTarget, options: RunActionOptions): Promise<RunResult> {\n const { ports } = options;\n if (action.requiresAuth && !target.auth.authenticated) {\n if (!ports.auth) {\n // Unreachable through resolve (the action is absent without the port); guard direct callers.\n ports.diagnostics.capture(new Error(`requiresAuth action \"${action.id}\" ran signed out with no auth port`), {\n area: \"actions\",\n detail: { actionId: action.id },\n });\n return { status: \"failed\", error: new Error(\"sign-in unavailable\") };\n }\n ports.auth.signInThenResume({ actionId: action.id, contentFingerprint: options.contentFingerprint ?? action.id });\n return { status: \"auth-pending\" };\n }\n const reads = createProjectedReads(options.source ?? { surface: null, declaration: target.declaration, item: null });\n const signal = options.signal ?? new AbortController().signal;\n try {\n const outcome = await action.run(target, { reads, ports, signal });\n return { status: \"ran\", outcome };\n } catch (error) {\n ports.diagnostics.capture(error, { area: \"actions\", detail: { actionId: action.id, stage: \"run\" } });\n const label = typeof action.label === \"string\" ? action.label : action.id;\n ports.notify?.error(`${label} did not finish.`, \"Try again; if it keeps failing, report it from the feedback button.\");\n return { status: \"failed\", error };\n }\n}\n"],"mappings":"AAOO,IAAM,gBAAkB,CAAC,OAAQ,MAAO,QAAS,OAAQ,OAAQ,MAAM,ECsDvE,IAAM,kBAA+C,CAC1D,WACA,OACA,SACA,OACA,OACA,QACA,UACA,MACA,SACA,QACA,MACA,KACA,QACA,YACA,UACA,cACF,ECtDO,IAAM,qBAAN,cAAmC,KAAM,CACrC,SACA,UACT,YAAY,SAAkB,MAAe,OAAgB,CAC3D,MACE,QAAU,OACN,WAAW,QAAQ,mCAAmC,KAAK,6CAC3D,WAAW,QAAQ,gCAAgC,KAAK,mBAAmB,MAAM,uBAAuB,MAAM,uBACpH,EACA,KAAK,KAAO,uBACZ,KAAK,SAAW,SAChB,KAAK,UAAY,CAAC,MAAO,MAAM,CACjC,CACF,EAEa,uBAAN,cAAqC,KAAM,CACvC,WACT,YAAY,WAAoB,CAC9B,MAAM,oBAAoB,UAAU,wEAAwE,EAC5G,KAAK,KAAO,yBACZ,KAAK,WAAa,UACpB,CACF,EAOM,MAAQ,IAAI,IAAY,eAAe,EAE7C,SAAS,QAAQ,MAAuC,MAAgB,OAAoB,CAC1F,GAAI,CACF,MAAM,YAAY,QAAQ,MAAO,CAAE,KAAM,UAAW,MAAO,CAAC,CAC9D,MAAQ,CAER,CACF,CAMO,SAAS,mBACd,OACA,OACA,QAC4D,CAC5D,IAAM,SAAW,QAAQ,SACzB,GAAI,OAAO,kBAAkB,IAAI,OAAO,EAAE,EAAG,MAAO,CAAE,OAAQ,QAAS,EACvE,GAAI,UAAU,SAAS,SAAS,OAAO,EAAE,EAAG,MAAO,CAAE,OAAQ,QAAS,EACtE,GAAI,UAAU,YAAc,CAAC,SAAS,WAAW,SAAS,OAAO,QAAQ,EAAG,MAAO,CAAE,OAAQ,QAAS,EACtG,GAAI,OAAO,QAAU,OAAO,OAAO,OAAS,EAAG,CAE7C,GAAI,OAAO,SAAU,MAAO,CAAE,OAAQ,QAAS,EAC/C,IAAM,SAAW,IAAI,IAAI,OAAO,SAAS,IAAK,GAAM,EAAE,IAAI,CAAC,EAC3D,GAAI,CAAC,OAAO,OAAO,KAAM,MAAS,SAAS,IAAI,IAAI,CAAC,EAAG,MAAO,CAAE,OAAQ,QAAS,CACnF,CACA,GAAI,OAAO,cAAgB,CAAC,OAAO,KAAK,eAAiB,CAAC,QAAQ,cAEhE,MAAO,CAAE,OAAQ,QAAS,EAE5B,IAAM,IAAM,OAAO,SAAS,MAAM,EAClC,OAAI,IAAI,SAAW,cAEV,IAAI,SAAS,KAAK,EAAI,IAAM,CAAE,OAAQ,QAAS,EAEpD,IAAI,SAAW,mBAA2B,IAC1C,OAAO,WAAa,aAAe,CAAC,MAAM,IAAI,IAAI,IAAI,EACjD,CACL,OAAQ,iBACR,OAAQ,WAAW,OAAO,EAAE,qHAC9B,EAEG,IAAI,SAAS,KAAK,EAMhB,IALE,CACL,OAAQ,iBACR,OAAQ,mBAAmB,OAAO,EAAE,0DACtC,CAGJ,CAOO,SAAS,qBAAqB,QAAgD,CACnF,IAAM,MAAQ,QAAQ,MAChB,QAAU,IAAI,IACd,UAAY,IAAI,IAChB,OAAS,IAAM,CACnB,QAAW,IAAK,CAAC,GAAG,SAAS,EAAG,EAAE,CACpC,EAEA,MAAO,CACL,SAAS,SAAU,CACjB,GAAI,QAAQ,IAAI,SAAS,EAAE,EAAG,MAAM,IAAI,uBAAuB,SAAS,EAAE,EAC1E,IAAM,SAAW,SAAS,cAAc,GAAK,CAAC,EACxC,IAAM,IAAI,IAChB,QAAW,MAAM,SAAU,CACzB,GAAI,IAAI,IAAI,EAAE,EAAG,MAAM,IAAI,qBAAqB,GAAI,SAAS,GAAI,SAAS,EAAE,EAC5E,IAAI,IAAI,EAAE,EACV,OAAW,CAAC,QAAS,KAAK,IAAK,QAC7B,GAAI,MAAM,SAAS,SAAS,EAAE,EAAG,MAAM,IAAI,qBAAqB,GAAI,QAAS,SAAS,EAAE,CAE5F,CACA,IAAM,MAAe,CAAE,SAAU,QAAS,EAC1C,eAAQ,IAAI,SAAS,GAAI,KAAK,EAC9B,OAAO,EACA,IAAM,CACP,QAAQ,IAAI,SAAS,EAAE,IAAM,QAC/B,QAAQ,OAAO,SAAS,EAAE,EAC1B,OAAO,EAEX,CACF,EAEA,MAAM,QAAQ,OAAQ,SAAU,CAC9B,IAAM,cAAgB,EAAQ,MAAM,KAC9B,KAAO,IAAI,IACX,IAAwB,CAAC,EAC/B,OAAW,CAAC,WAAY,CAAE,QAAS,CAAC,GAAK,CAAC,GAAG,OAAO,EAAG,CACrD,IAAI,QACJ,GAAI,CACF,QAAU,MAAM,SAAS,QAAQ,MAAM,CACzC,OAAS,MAAO,CACd,QAAQ,MAAO,MAAO,CAAE,WAAY,MAAO,SAAU,CAAC,EACtD,QACF,CACA,QAAW,OAAO,QAAS,CACzB,IAAM,MAAQ,KAAK,IAAI,IAAI,EAAE,EAC7B,GAAI,QAAU,OAAW,CACvB,QAAQ,MAAO,IAAI,qBAAqB,IAAI,GAAI,MAAO,UAAU,EAAG,CAAE,SAAU,IAAI,EAAG,CAAC,EACxF,QACF,CACA,KAAK,IAAI,IAAI,GAAI,UAAU,EAC3B,IAAM,OAAiB,IAAI,WAAa,WAAa,IAAM,CAAE,GAAG,IAAK,SAAU,UAAW,EACtF,YACJ,GAAI,CACF,YAAc,mBAAmB,OAAQ,OAAQ,CAAE,SAAU,UAAY,CAAC,EAAG,aAAc,CAAC,CAC9F,OAAS,MAAO,CACd,QAAQ,MAAO,MAAO,CAAE,SAAU,OAAO,GAAI,MAAO,UAAW,CAAC,EAChE,QACF,CACA,GAAI,YAAY,SAAW,iBAAkB,CAC3C,QAAQ,MAAO,IAAI,MAAM,YAAY,MAAM,EAAG,CAAE,SAAU,OAAO,GAAI,MAAO,UAAW,CAAC,EACxF,QACF,CACI,YAAY,SAAW,UAC3B,IAAI,KAAK,CAAE,OAAQ,WAAY,CAAC,CAClC,CACF,CACA,OAAO,GACT,EAEA,WAAY,CACV,MAAO,CAAC,GAAG,QAAQ,KAAK,CAAC,CAC3B,EAEA,UAAU,SAAU,CAClB,iBAAU,IAAI,QAAQ,EACf,IAAM,CACX,UAAU,OAAO,QAAQ,CAC3B,CACF,CACF,CACF,CAGO,SAAS,qBACd,GACA,QACA,KAA+B,KACf,CAChB,IAAM,KAAO,QAAQ,IAAK,GAAO,EAAE,WAAa,GAAK,EAAI,CAAE,GAAG,EAAG,SAAU,EAAG,CAAE,EAChF,MAAO,CACL,GACA,KACA,YAAa,IAAM,KAAK,IAAK,GAAM,EAAE,EAAE,EACvC,QAAS,IAAM,IACjB,CACF,CAGO,IAAM,eAAiB,KAAoB,CAAE,OAAQ,WAAY,GC3MjE,IAAM,oBAAmC,OAAO,OAAO,CAC5D,WAAY,GACZ,eAAgB,WAChB,kBAAmB,EACrB,CAAC,EAEM,SAAS,mBAAmB,MAAwD,CACzF,MAAO,CACL,WAAY,MAAM,aAAa,YAAc,oBAAoB,WACjE,eAAgB,MAAM,aAAa,gBAAkB,oBAAoB,eACzE,kBAAmB,MAAM,aAAa,mBAAqB,oBAAoB,iBACjF,CACF,CCEA,SAAS,OAAO,MAAkC,CAChD,GAAI,UAAU,QAAa,OAAO,OAAU,YAAc,OAAO,OAAU,UAC3E,IAAI,QAAU,MAAQ,OAAO,OAAU,UAAY,OAAO,OAAU,UAAW,OAAO,MACtF,GAAI,OAAO,OAAU,SAAU,OAAO,OAAO,SAAS,KAAK,EAAI,MAAQ,KACvE,GAAI,OAAO,OAAU,SAAU,OAAO,MAAM,SAAS,EACrD,GAAI,iBAAiB,KAAM,OAAO,MAAM,YAAY,EACpD,GAAI,MAAM,QAAQ,KAAK,EAAG,OAAO,MAAM,IAAK,GAAM,OAAO,CAAC,GAAK,IAAI,EACnE,GAAI,OAAO,OAAU,SAAU,CAC7B,IAAM,IAA+B,CAAC,EACtC,OAAW,CAAC,EAAG,CAAC,IAAK,OAAO,QAAQ,KAAgC,EAAG,CACrE,IAAM,EAAI,OAAO,CAAC,EACd,IAAM,SAAW,IAAI,CAAC,EAAI,EAChC,CACA,OAAO,GACT,EAEF,CAGO,SAAS,cACd,IACA,SACA,WAAa,GACgD,CAC7D,IAAM,OAAS,IAAI,IAAI,SAAS,IAAK,GAAM,CAAC,EAAE,KAAM,CAAC,CAAC,CAAC,EACjD,OAA+B,CAAC,EAChC,UAA4B,CAAC,EACnC,OAAW,CAAC,KAAM,KAAK,IAAK,OAAO,QAAQ,GAAG,EAAG,CAC/C,IAAM,KAAO,WAAa,GAAG,UAAU,IAAI,IAAI,GAAK,KAC9C,KAAO,OAAO,IAAI,IAAI,EAC5B,GAAI,CAAC,KAAM,CACT,UAAU,KAAK,CAAE,KAAM,OAAQ,UAAW,CAAC,EAC3C,QACF,CACA,IAAM,YAAc,mBAAmB,IAAI,EAC3C,GAAI,YAAY,iBAAmB,WAAY,CAC7C,UAAU,KAAK,CAAE,KAAM,OAAQ,WAAY,eAAgB,YAAY,cAAe,CAAC,EACvF,QACF,CACA,GAAI,CAAC,YAAY,WAAY,CAC3B,UAAU,KAAK,CAAE,KAAM,OAAQ,UAAW,CAAC,EAC3C,QACF,CACA,IAAM,KAAO,OAAO,KAAK,EACrB,OAAS,SACb,OAAO,IAAI,EAAI,KACjB,CACA,MAAO,CAAE,OAAQ,SAAU,CAC7B,CAEO,SAAS,qBAAqB,OAA0C,CAC7E,MAAO,CACL,MAAM,OAAO,OAAQ,CACnB,GAAI,CAAC,OAAO,SAAW,CAAC,OAAO,YAAa,MAAO,CAAE,OAAQ,CAAC,EAAG,UAAW,CAAC,CAAE,EAC/E,IAAM,IAAM,MAAM,OAAO,QAAQ,WAAW,MAAM,EAC5C,aAAe,OAAO,YAAY,OACxC,OAAO,cAAc,IAAK,YAAY,CACxC,EACA,MAAM,KAAK,OAAQ,CACjB,IAAM,KAAO,OAAO,KACpB,GAAI,CAAC,MAAQ,CAAC,OAAO,YAAa,OAAO,KACzC,IAAM,KAAO,OAAO,YAAY,WAAW,KAAM,GAAM,EAAE,OAAS,KAAK,QAAQ,EAC/E,GAAI,CAAC,KAAM,MAAO,CAAE,OAAQ,CAAC,EAAG,UAAW,CAAC,CAAE,KAAM,KAAK,SAAU,OAAQ,UAAW,CAAC,CAAE,EACzF,IAAM,IAAM,MAAM,KAAK,WAAW,MAAM,EACxC,OAAO,cAAc,IAAK,KAAK,OAAQ,KAAK,QAAQ,CACtD,EACA,MAAM,KAAK,OAA6B,CACtC,OAAK,OAAO,SAAS,SACd,OAAO,QAAQ,SAAS,MAAM,EADC,CAAC,CAEzC,CACF,CACF,CCnEO,SAAS,gBAAgB,SAAkB,KAAiC,CACjF,OAAO,SAAW,GAAG,QAAQ,IAAI,IAAI,GAAK,IAC5C,CCEA,SAAS,SAAS,YAA6B,CAC7C,IAAM,MAAQ,YAAY,QAAQ,GAAG,EACrC,OAAO,MAAQ,EAAI,YAAY,MAAM,EAAG,KAAK,EAAI,WACnD,CAEO,SAAS,kBAAkB,MAA0B,CAAC,EAAgB,CAC3E,IAAM,YAAc,MAAM,aAAe,KACnC,KAAO,MAAM,MAAQ,KACrB,UAAY,IAAI,IAClB,WAAa,GACX,SAA0B,CAAC,EACjC,GAAI,YAAa,CACf,QAAW,SAAS,YAAY,OAAQ,CACtC,UAAU,IAAI,MAAM,IAAI,EACxB,IAAM,EAAI,mBAAmB,KAAK,GAC9B,EAAE,iBAAmB,YAAc,CAAC,EAAE,cAAY,WAAa,GACrE,CACA,IAAM,SAAW,KAAO,YAAY,WAAW,KAAM,GAAM,EAAE,OAAS,KAAK,QAAQ,EAAI,OACvF,GAAI,SAAU,CACZ,QAAW,SAAS,SAAS,OAAQ,CACnC,UAAU,IAAI,gBAAgB,SAAS,KAAM,MAAM,IAAI,CAAC,EACxD,IAAM,EAAI,mBAAmB,KAAK,GAC9B,EAAE,iBAAmB,YAAc,CAAC,EAAE,cAAY,WAAa,GACrE,CACA,QAAW,KAAK,SAAS,cAAgB,CAAC,EAAG,SAAS,KAAK,CAAC,CAC9D,CACA,QAAW,KAAK,YAAY,cAAgB,CAAC,GACvC,CAAC,EAAE,UAAY,EAAE,WAAa,MAAM,WAAU,SAAS,KAAK,CAAC,CAErE,CACA,IAAM,SAAW,MAAM,UAAY,MAAM,UAAY,GAC/C,YAAc,MAAM,SAAS,aAAe,aAAa,aAAe,KACxE,WAAa,MAAM,aAAe,YAAc,CAAC,SAAU,SAAS,WAAW,EAAG,WAAW,EAAI,CAAC,QAAQ,GAChH,MAAO,CACL,QAAS,MAAM,SAAW,KAC1B,YACA,UACA,WACA,kBAAmB,IAAI,IAAI,MAAM,mBAAqB,CAAC,CAAC,EACxD,KACA,MAAO,MAAM,OAAS,MAAM,OAAS,KACrC,UAAW,MAAM,WAAa,KAC9B,aAAc,MAAM,cAAgB,CAAC,EACrC,SAAU,SAAW,CAAC,EAAK,MAAM,UAAY,SAC7C,SACA,WACA,eAAgB,MAAM,gBAAkB,KACxC,KAAM,CACJ,cAAe,MAAM,MAAM,eAAiB,GAC5C,MAAO,MAAM,MAAM,OAAS,GAC5B,QAAS,MAAM,MAAM,SAAW,EAClC,EACA,KAAM,MAAM,IACd,CACF,CC5DA,eAAsB,UAAU,OAAgB,OAAqB,QAA+C,CAClH,GAAM,CAAE,KAAM,EAAI,QAClB,GAAI,OAAO,cAAgB,CAAC,OAAO,KAAK,cACtC,OAAK,MAAM,MAQX,MAAM,KAAK,iBAAiB,CAAE,SAAU,OAAO,GAAI,mBAAoB,QAAQ,oBAAsB,OAAO,EAAG,CAAC,EACzG,CAAE,OAAQ,cAAe,IAP9B,MAAM,YAAY,QAAQ,IAAI,MAAM,wBAAwB,OAAO,EAAE,oCAAoC,EAAG,CAC1G,KAAM,UACN,OAAQ,CAAE,SAAU,OAAO,EAAG,CAChC,CAAC,EACM,CAAE,OAAQ,SAAU,MAAO,IAAI,MAAM,qBAAqB,CAAE,GAKvE,IAAM,MAAQ,qBAAqB,QAAQ,QAAU,CAAE,QAAS,KAAM,YAAa,OAAO,YAAa,KAAM,IAAK,CAAC,EAC7G,OAAS,QAAQ,QAAU,IAAI,gBAAgB,EAAE,OACvD,GAAI,CAEF,MAAO,CAAE,OAAQ,MAAO,QADR,MAAM,OAAO,IAAI,OAAQ,CAAE,MAAO,MAAO,MAAO,CAAC,CACjC,CAClC,OAAS,MAAO,CACd,MAAM,YAAY,QAAQ,MAAO,CAAE,KAAM,UAAW,OAAQ,CAAE,SAAU,OAAO,GAAI,MAAO,KAAM,CAAE,CAAC,EACnG,IAAM,MAAQ,OAAO,OAAO,OAAU,SAAW,OAAO,MAAQ,OAAO,GACvE,aAAM,QAAQ,MAAM,GAAG,KAAK,mBAAoB,qEAAqE,EAC9G,CAAE,OAAQ,SAAU,KAAM,CACnC,CACF","names":[]}
1
+ {"version":3,"sources":["../src/actions/click-target.ts","../src/actions/types.ts","../src/actions/registry.ts","../src/declare/sensitivity.ts","../src/actions/projection.ts","../src/declare/keys.ts","../src/actions/target.ts","../src/actions/run.ts"],"sourcesContent":["/**\n * The click target (CONTRACT §2.4) in its own module: `/ports` references it,\n * and `/ports` must stay free of every other package (types only).\n */\nimport type { QualifiedValueKey, ResolvedItem, ResolvedSurfaceDeclaration, WriteTarget } from \"../declare/types\";\n\n/** The six universal verbs — the ONLY rows that may grey (R1 c). */\nexport const UNIVERSAL_VERBS = [\"copy\", \"cut\", \"paste\", \"undo\", \"redo\", \"find\"] as const;\nexport type UniversalVerb = (typeof UNIVERSAL_VERBS)[number];\n\n/** The transfer Payload's kinds (`actions/types.ts` asserts equality at compile time). */\nexport type PayloadKind = \"text\" | \"markdown\" | \"json\" | \"rows\" | \"registered\";\n\n/** What eligibility is computed from: everything declared at the click target. */\nexport interface ClickTarget {\n surface: { instanceId: string; surfaceName: string } | null;\n /** The resolved declaration (inheritance applied). Eligibility decides by KEY existence over it. */\n declaration: ResolvedSurfaceDeclaration | null;\n /** Declared value keys with a read path here (screen names + qualified item keys). Key existence, not population. */\n valueKeys: ReadonlySet<QualifiedValueKey>;\n /** Scope chain, most general first: [\"global\", \"<domain>\", \"<surfaceName>\"]. */\n scopeChain: readonly string[];\n /** The surface's explicit exclusions (the one override). */\n excludedActionIds: ReadonlySet<string>;\n item: ResolvedItem | null;\n level: string | null;\n /** Offsets + the live range handle, editable or not. */\n selection: {\n text: string;\n type: \"editable\" | \"non-editable\";\n start: number;\n end: number;\n /** Opaque host handle (DOM Range / element) used to restore or stream into the selection. */\n handle?: unknown;\n } | null;\n /** Payload kinds a capture of this target would produce (from declared value types/kinds). */\n payloadKinds: readonly PayloadKind[];\n /** Declared targets writable on this target right now. */\n writable: readonly WriteTarget[];\n readOnly: boolean;\n /** True when any declared value here is secret/credential or non-exportable. */\n restricted: boolean;\n organizationId: string | null;\n auth: { authenticated: boolean; admin: boolean; creator: boolean };\n /**\n * Host-owned context a provider reads to act (the rich-document source, the\n * editable field's callbacks). Opaque to the package; never crosses into the\n * model. Providers that need it narrow it themselves.\n */\n host?: unknown;\n}\n\n","/**\n * Matrx Alchemy — Actions types (CONTRACT §2.4).\n *\n * One registry of everything a person can do to a thing. Providers plug in;\n * eligibility is computed from what is declared at the click target; duplicate\n * ids are refused; an action that does not apply is ABSENT (chair ruling R1).\n *\n * Framework-neutral: zero React, zero `@ai-matrx/agents`. Icons are string KEYS\n * resolved by the host's `IconResolverPort`; labels go through `I18nPort`.\n */\nimport type { Payload, TransferOutcome } from \"@ai-matrx/kit/content-transfer\";\nimport type { Json, LiveValues } from \"../declare/types\";\nimport type { AlchemyHostPorts } from \"../ports\";\nimport type { ClickTarget, PayloadKind } from \"./click-target\";\n\nexport type { ClickTarget, PayloadKind } from \"./click-target\";\n\n/** PayloadKind is the transfer Payload's kind union, kept equal at compile time. */\ntype Equal<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;\nconst payloadKindsAgree: Equal<PayloadKind, Payload[\"kind\"]> = true;\nvoid payloadKindsAgree;\n\nexport { UNIVERSAL_VERBS, type UniversalVerb } from \"./click-target\";\nimport type { UniversalVerb } from \"./click-target\";\n\n/**\n * Chair ruling R1: an action that does not apply or cannot run now is ABSENT.\n * Only the six universal verbs may answer \"unavailable-verb\": greyed in the\n * strip/bar, the sentence as tooltip. `requiresAuth` is NOT unavailability.\n */\nexport type Eligibility =\n | { status: \"absent\" }\n | { status: \"available\" }\n | { status: \"unavailable-verb\"; verb: UniversalVerb; sentence: string }\n /**\n * ABSENT OR HONEST (coordinator ruling 2026-09-27, admin pass): a row that cannot run here but\n * SAYS WHY (\"Unchanged from the pack\", \"Manifested surfaces are managed in code\") stays visible,\n * greyed, with its reason — the menu is the same everywhere, only availability changes. A row\n * with no reason is absent (the registry refuses an empty sentence).\n */\n | { status: \"unavailable\"; sentence: string };\n\n/** rich-document's ActionCategory, unchanged, plus the three the context menu groups by. */\nexport type ActionCategory =\n | \"feedback\"\n | \"copy\"\n | \"export\"\n | \"save\"\n | \"edit\"\n | \"share\"\n | \"creator\"\n | \"app\"\n | \"listen\"\n | \"study\"\n | \"ask\"\n | \"ai\"\n | \"admin\"\n | \"clipboard\"\n | \"history\"\n | \"surface-info\";\n\nexport const ACTION_CATEGORIES: readonly ActionCategory[] = [\n \"feedback\",\n \"copy\",\n \"export\",\n \"save\",\n \"edit\",\n \"share\",\n \"creator\",\n \"app\",\n \"listen\",\n \"study\",\n \"ask\",\n \"ai\",\n \"admin\",\n \"clipboard\",\n \"history\",\n \"surface-info\",\n];\n\n/**\n * An optional named section inside the action's group (a grid's cell / row /\n * column). `primary` marks the thing the person clicked: every layout renders\n * it first, inline, heading kept (context-menu-v3's primary section law).\n */\nexport interface ActionSection {\n id: string;\n label: string;\n labelKey?: string;\n icon?: string;\n primary?: boolean;\n /**\n * \"group\" (default): a heading over related rows — must be in the package's\n * APPROVED_HEADINGS (@ai-matrx/alchemy/menu). \"target\": the label of the\n * thing the person clicked, supplied at run time (\"Row · Widget A\").\n */\n kind?: \"group\" | \"target\";\n}\n\nexport interface Action {\n /** Globally unique. A duplicate is refused (never overwritten). */\n id: string;\n /** The provider that registered it — filled by the registry. */\n provider?: string;\n /** Resolved through I18nPort.t(labelKey ?? id, label). */\n label: string | ((target: ClickTarget) => string);\n labelKey?: string;\n /** Icon KEY resolved by IconResolverPort — the model stays framework-neutral. */\n icon?: string;\n /** Host styling hint for the glyph (rich-document iconColor). Renderers may ignore it. */\n iconTone?: string;\n /** Replaces `icon` while a state holds (a spinner while speaking). */\n stateIcon?(target: ClickTarget): { icon: string; spin?: boolean } | null;\n hint?: string;\n hintKey?: string;\n description?: string;\n category: ActionCategory;\n /** Which universal verb this clipboard action is (addressed by role in every layout). */\n verb?: UniversalVerb;\n order?: number;\n /** Bar layout slot. Default \"overflow\". */\n placement?: \"primary\" | \"overflow\" | \"both\";\n section?: ActionSection;\n /** A signed-out person still sees it; running goes through AuthResumePort (sign in, then resume). */\n requiresAuth?: boolean;\n /** Declared write targets this action lands through. Absent on a read-only target. */\n writes?: readonly string[];\n destructive?: boolean;\n /**\n * STARTS A NEW GROUP inside its section: the menu draws a divider above it (never as a section's\n * first row). A delete sits in its own group, last, never beside Duplicate (DATA-V2-BASICS-2).\n */\n startsGroup?: boolean;\n preserveSelection?: boolean;\n /** Link rows navigate instead of running. */\n href?: string | ((target: ClickTarget) => string);\n eligible(target: ClickTarget): Eligibility;\n /** Toggle/pressed state: a pressed button or a checked row. */\n pressed?(target: ClickTarget): boolean;\n /** Present = the row is a checkbox (pressed is its state). */\n toggle?: boolean;\n subscribe?(onChange: () => void, target: ClickTarget): () => void;\n /**\n * The rows behind this submenu are still loading (an agent library's first\n * fetch). The row shows as loading — never absent, never silent — and its\n * `expand` resolves once they arrive.\n */\n pending?(target: ClickTarget): boolean;\n /** Submenus whose rows load on open (agent lists). */\n expand?(target: ClickTarget, signal: AbortSignal): Promise<readonly Action[]>;\n /** Shown centred when `expand` resolves empty. */\n emptyLabel?: string;\n run(target: ClickTarget, context: ActionRunContext): Promise<TransferOutcome | void> | TransferOutcome | void;\n}\n\n/** One removed read, recorded (kit content-transfer's omission shape). */\nexport interface ReadOmission {\n path: string;\n reason: \"excluded\" | \"filtered\";\n classification?: \"secret\" | \"credential\";\n}\n\n/**\n * PROJECTION BEFORE PROVIDERS. Providers never receive a raw read. Every read\n * they can make is projected first: secret and credential values, values\n * declared `exportable: false`, and values nobody declared are removed, with an\n * omission recorded. Raw reads never leave the package.\n */\nexport interface ProjectedReads {\n values(signal: AbortSignal): Promise<{ values: Record<string, Json>; omissions: readonly ReadOmission[] }>;\n item(signal: AbortSignal): Promise<{ values: Record<string, Json>; omissions: readonly ReadOmission[] } | null>;\n live(signal: AbortSignal): Promise<LiveValues>;\n}\n\n/**\n * What a running action receives. `door` (ALC-17) and `operate` (ALC-16) join\n * with their waves; until then those capabilities are absent (N8).\n */\nexport interface ActionRunContext {\n reads: ProjectedReads;\n ports: AlchemyHostPorts;\n signal: AbortSignal;\n}\n\nexport interface ActionProvider {\n id: string;\n /** T0 providers are known at mount; T1 providers load with the menu on first open. */\n tier: \"T0\" | \"T1\";\n /**\n * The ids this provider can ever yield, when known up front. Lets the\n * registry refuse a duplicate at registration instead of at first open.\n */\n declaredIds?(): readonly string[];\n actions(target: ClickTarget): readonly Action[] | Promise<readonly Action[]>;\n}\n\nexport interface ResolvedAction {\n action: Action;\n eligibility: Exclude<Eligibility, { status: \"absent\" }>;\n}\n\nexport interface ResolveRestriction {\n exclude?: readonly string[];\n categories?: readonly ActionCategory[];\n}\n\nexport interface ActionRegistry {\n /** Throws DuplicateActionError naming both providers; nothing is overwritten. */\n register(provider: ActionProvider): () => void;\n /** restrict: a host may remove ids or categories, never add ineligible ones. */\n resolve(target: ClickTarget, restrict?: ResolveRestriction): Promise<readonly ResolvedAction[]>;\n /** Registered provider ids, in registration order. */\n providers(): readonly string[];\n /** Change notification (a provider registered or unregistered). */\n subscribe(listener: () => void): () => void;\n}\n","/**\n * The one action registry (CONTRACT §2.4).\n *\n * - Duplicate ids are REFUSED, never overwritten (rich-document's registry\n * silently overwrote — R12). A duplicate known at registration throws; one\n * that only appears when a provider yields it is dropped and captured.\n * - Eligibility is one pure pass over the resolved click target, under\n * \"restrict, never relax\": a host may remove ids or categories, never add.\n * - R1: an action that does not apply is ABSENT. Only the six universal verbs\n * (category \"clipboard\") may answer `unavailable-verb`, with a sentence; any\n * other action answering it is a contract break → absent + diagnostics.\n */\nimport type { AlchemyHostPorts, Json } from \"../ports\";\nimport {\n UNIVERSAL_VERBS,\n type Action,\n type ActionProvider,\n type ActionRegistry,\n type ClickTarget,\n type Eligibility,\n type ResolveRestriction,\n type ResolvedAction,\n} from \"./types\";\n\nexport class DuplicateActionError extends Error {\n readonly actionId: string;\n readonly providers: readonly [string, string];\n constructor(actionId: string, first: string, second: string) {\n super(\n first === second\n ? `Action \"${actionId}\" is yielded twice by provider \"${first}\". Ids are unique; the second was refused.`\n : `Action \"${actionId}\" is registered by provider \"${first}\" and again by \"${second}\". Ids are unique; \"${second}\"'s copy was refused.`,\n );\n this.name = \"DuplicateActionError\";\n this.actionId = actionId;\n this.providers = [first, second];\n }\n}\n\nexport class DuplicateProviderError extends Error {\n readonly providerId: string;\n constructor(providerId: string) {\n super(`Action provider \"${providerId}\" is already registered. Unregister it first; nothing was overwritten.`);\n this.name = \"DuplicateProviderError\";\n this.providerId = providerId;\n }\n}\n\nexport interface ActionRegistryOptions {\n /** `diagnostics` is required (nothing fails silently); `auth` decides whether requiresAuth actions reach a signed-out person. */\n ports: Pick<AlchemyHostPorts, \"diagnostics\" | \"auth\">;\n}\n\nconst VERBS = new Set<string>(UNIVERSAL_VERBS);\n\nfunction capture(ports: ActionRegistryOptions[\"ports\"], error: unknown, detail: Json): void {\n try {\n ports.diagnostics.capture(error, { area: \"actions\", detail });\n } catch {\n // A broken diagnostics port must never take the menu down with it.\n }\n}\n\n/**\n * Eligibility of one action at one target — the ONE pure pass (CONTRACT §1.6).\n * Exported for providers' tests; hosts call `registry.resolve`.\n */\nexport function computeEligibility(\n action: Action,\n target: ClickTarget,\n options: { restrict?: ResolveRestriction; authResumable: boolean },\n): Eligibility | { status: \"contract-break\"; reason: string } {\n const restrict = options.restrict;\n if (target.excludedActionIds.has(action.id)) return { status: \"absent\" };\n if (restrict?.exclude?.includes(action.id)) return { status: \"absent\" };\n if (restrict?.categories && !restrict.categories.includes(action.category)) return { status: \"absent\" };\n if (action.writes && action.writes.length > 0) {\n // A read-only target offers nothing that would change the record.\n if (target.readOnly) return { status: \"absent\" };\n const writable = new Set(target.writable.map((w) => w.name));\n if (!action.writes.some((name) => writable.has(name))) return { status: \"absent\" };\n }\n if (action.requiresAuth && !target.auth.authenticated && !options.authResumable) {\n // Sign-in-then-resume is the capability; without the auth port it is absent (N8).\n return { status: \"absent\" };\n }\n const own = action.eligible(target);\n if (own.status === \"unavailable\") {\n // A greyed row must say why; without a sentence it is absent.\n return own.sentence.trim() ? own : { status: \"absent\" };\n }\n if (own.status !== \"unavailable-verb\") return own;\n if (action.category !== \"clipboard\" || !VERBS.has(own.verb)) {\n return {\n status: \"contract-break\",\n reason: `Action \"${action.id}\" answered unavailable-verb but only the six universal verbs (category \"clipboard\") may grey; it is absent instead.`,\n };\n }\n if (!own.sentence.trim()) {\n return {\n status: \"contract-break\",\n reason: `Universal verb \"${action.id}\" greyed without a sentence; a greyed verb must say why.`,\n };\n }\n return own;\n}\n\ninterface Entry {\n provider: ActionProvider;\n declared: readonly string[];\n}\n\nexport function createActionRegistry(options: ActionRegistryOptions): ActionRegistry {\n const ports = options.ports;\n const entries = new Map<string, Entry>();\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const l of [...listeners]) l();\n };\n\n return {\n register(provider) {\n if (entries.has(provider.id)) throw new DuplicateProviderError(provider.id);\n const declared = provider.declaredIds?.() ?? [];\n const own = new Set<string>();\n for (const id of declared) {\n if (own.has(id)) throw new DuplicateActionError(id, provider.id, provider.id);\n own.add(id);\n for (const [otherId, other] of entries) {\n if (other.declared.includes(id)) throw new DuplicateActionError(id, otherId, provider.id);\n }\n }\n const entry: Entry = { provider, declared };\n entries.set(provider.id, entry);\n notify();\n return () => {\n if (entries.get(provider.id) === entry) {\n entries.delete(provider.id);\n notify();\n }\n };\n },\n\n async resolve(target, restrict) {\n const authResumable = Boolean(ports.auth);\n const seen = new Map<string, string>();\n const out: ResolvedAction[] = [];\n for (const [providerId, { provider }] of [...entries]) {\n let actions: readonly Action[];\n try {\n actions = await provider.actions(target);\n } catch (error) {\n capture(ports, error, { providerId, stage: \"actions\" });\n continue;\n }\n for (const raw of actions) {\n const first = seen.get(raw.id);\n if (first !== undefined) {\n capture(ports, new DuplicateActionError(raw.id, first, providerId), { actionId: raw.id });\n continue;\n }\n seen.set(raw.id, providerId);\n const action: Action = raw.provider === providerId ? raw : { ...raw, provider: providerId };\n let eligibility: ReturnType<typeof computeEligibility>;\n try {\n eligibility = computeEligibility(action, target, { restrict: restrict ?? {}, authResumable });\n } catch (error) {\n capture(ports, error, { actionId: action.id, stage: \"eligible\" });\n continue;\n }\n if (eligibility.status === \"contract-break\") {\n capture(ports, new Error(eligibility.reason), { actionId: action.id, stage: \"eligible\" });\n continue;\n }\n if (eligibility.status === \"absent\") continue;\n out.push({ action, eligibility });\n }\n }\n return out;\n },\n\n providers() {\n return [...entries.keys()];\n },\n\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\n/** A provider over a fixed list; its ids are declared up front so duplicates fail at registration. */\nexport function staticActionProvider(\n id: string,\n actions: readonly Action[],\n tier: ActionProvider[\"tier\"] = \"T0\",\n): ActionProvider {\n const list = actions.map((a) => (a.provider === id ? a : { ...a, provider: id }));\n return {\n id,\n tier,\n declaredIds: () => list.map((a) => a.id),\n actions: () => list,\n };\n}\n\n/** Always-available eligibility, for actions with no rule of their own. */\nexport const alwaysEligible = (): Eligibility => ({ status: \"available\" });\n","/**\n * Sensitivity defaults and resolution (CONTRACT §2.1). Its own module so the\n * lightweight entries (`/actions`, the T0 React host) can project without\n * pulling the declaration registry.\n */\nimport type { DeclaredValue, Sensitivity } from \"./types\";\n\nexport const DEFAULT_SENSITIVITY: Sensitivity = Object.freeze({\n exportable: true,\n classification: \"ordinary\",\n includedByDefault: true,\n});\n\nexport function resolveSensitivity(value: Pick<DeclaredValue, \"sensitivity\">): Sensitivity {\n return {\n exportable: value.sensitivity?.exportable ?? DEFAULT_SENSITIVITY.exportable,\n classification: value.sensitivity?.classification ?? DEFAULT_SENSITIVITY.classification,\n includedByDefault: value.sensitivity?.includedByDefault ?? DEFAULT_SENSITIVITY.includedByDefault,\n };\n}\n","/**\n * PROJECTION BEFORE PROVIDERS (CONTRACT §2.4).\n *\n * The only place a raw read (`MountedSurface.readValues`,\n * `InternalResolvedItem.readValues`) is consumed on behalf of an action.\n * What leaves is projected: a value is kept only when it is DECLARED, its\n * classification is \"ordinary\" and it is exportable. Everything removed is\n * recorded as an omission, so the person and the agent can see what was held\n * back. No caller can relax this.\n */\nimport { resolveSensitivity } from \"../declare/sensitivity\";\nimport type { InternalResolvedItem, MountedSurface } from \"../declare/host\";\nimport type { DeclaredValue, Json, LiveValues, ResolvedSurfaceDeclaration } from \"../declare/types\";\nimport type { ProjectedReads, ReadOmission } from \"./types\";\n\nexport interface ProjectionSource {\n surface: MountedSurface | null;\n declaration: ResolvedSurfaceDeclaration | null;\n item: InternalResolvedItem | null;\n}\n\nfunction toJson(value: unknown): Json | undefined {\n if (value === undefined || typeof value === \"function\" || typeof value === \"symbol\") return undefined;\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") return value;\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n if (typeof value === \"bigint\") return value.toString();\n if (value instanceof Date) return value.toISOString();\n if (Array.isArray(value)) return value.map((v) => toJson(v) ?? null);\n if (typeof value === \"object\") {\n const out: { [key: string]: Json } = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n const j = toJson(v);\n if (j !== undefined) out[k] = j;\n }\n return out;\n }\n return undefined;\n}\n\n/** Keep declared, ordinary, exportable values; record every removal. */\nexport function projectValues(\n raw: Record<string, unknown>,\n declared: readonly Pick<DeclaredValue, \"name\" | \"sensitivity\">[],\n pathPrefix = \"\",\n): { values: Record<string, Json>; omissions: ReadOmission[] } {\n const byName = new Map(declared.map((d) => [d.name, d]));\n const values: Record<string, Json> = {};\n const omissions: ReadOmission[] = [];\n for (const [name, value] of Object.entries(raw)) {\n const path = pathPrefix ? `${pathPrefix}.${name}` : name;\n const decl = byName.get(name);\n if (!decl) {\n omissions.push({ path, reason: \"filtered\" });\n continue;\n }\n const sensitivity = resolveSensitivity(decl);\n if (sensitivity.classification !== \"ordinary\") {\n omissions.push({ path, reason: \"excluded\", classification: sensitivity.classification });\n continue;\n }\n if (!sensitivity.exportable) {\n omissions.push({ path, reason: \"excluded\" });\n continue;\n }\n const json = toJson(value);\n if (json === undefined) continue;\n values[name] = json;\n }\n return { values, omissions };\n}\n\nexport function createProjectedReads(source: ProjectionSource): ProjectedReads {\n return {\n async values(signal) {\n if (!source.surface || !source.declaration) return { values: {}, omissions: [] };\n const raw = await source.surface.readValues(signal);\n const screenValues = source.declaration.values;\n return projectValues(raw, screenValues);\n },\n async item(signal) {\n const item = source.item;\n if (!item || !source.declaration) return null;\n const type = source.declaration.itemTypes?.find((t) => t.name === item.itemType);\n if (!type) return { values: {}, omissions: [{ path: item.itemType, reason: \"filtered\" }] };\n const raw = await item.readValues(signal);\n return projectValues(raw, type.values, item.itemType);\n },\n async live(signal): Promise<LiveValues> {\n if (!source.surface?.readLive) return {};\n return source.surface.readLive(signal);\n },\n };\n}\n","import type { QualifiedValueKey } from \"./types\";\n\n/** Every value, write-target, item-type and group name (the DB CHECK rule). */\nexport const DECLARATION_NAME_RE = /^[a-z][a-z0-9_]*$/;\n\n/** \"<client>/<local>\" — the surface naming convention. */\nexport const SURFACE_NAME_RE = /^[a-z][a-z0-9-]*\\/[a-z0-9-/]+$/;\n\n/** A bare screen value (`status`) or an item value (`table_row.status`). */\nexport const QUALIFIED_VALUE_KEY_RE = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)?$/;\n\nexport interface ParsedValueKey {\n /** \"\" for a screen-level value. */\n itemType: string;\n name: string;\n}\n\n/** Split a qualified key; null when it is not a valid bare or qualified key. */\nexport function parseQualifiedValueKey(key: string): ParsedValueKey | null {\n if (!QUALIFIED_VALUE_KEY_RE.test(key)) return null;\n const dot = key.indexOf(\".\");\n if (dot === -1) return { itemType: \"\", name: key };\n return { itemType: key.slice(0, dot), name: key.slice(dot + 1) };\n}\n\nexport function qualifyValueKey(itemType: string, name: string): QualifiedValueKey {\n return itemType ? `${itemType}.${name}` : name;\n}\n\n/** The client that owns a surface: its name prefix. */\nexport function surfaceClientOf(surfaceName: string): string {\n return surfaceName.split(\"/\")[0] ?? \"\";\n}\n","/**\n * Building a ClickTarget from what is declared at the click (CONTRACT §2.4).\n * Key existence, never value population: a value key is present when the\n * declaration gives it a read path here.\n */\nimport { resolveSensitivity } from \"../declare/sensitivity\";\nimport { qualifyValueKey } from \"../declare/keys\";\nimport type { ResolvedItem, ResolvedSurfaceDeclaration, WriteTarget } from \"../declare/types\";\nimport type { ClickTarget } from \"./types\";\n\nexport interface ClickTargetInput {\n surface?: ClickTarget[\"surface\"];\n declaration?: ResolvedSurfaceDeclaration | null;\n item?: ResolvedItem | null;\n level?: string | null;\n selection?: ClickTarget[\"selection\"];\n payloadKinds?: ClickTarget[\"payloadKinds\"];\n /** Defaults to the item's readOnly (or false). */\n readOnly?: boolean;\n /** Extra exclusions on top of the declaration's own. */\n excludedActionIds?: Iterable<string>;\n scopeChain?: readonly string[];\n organizationId?: string | null;\n auth?: Partial<ClickTarget[\"auth\"]>;\n /** Write targets the host can serve right now; defaults to every declared target for the screen/item. */\n writable?: readonly WriteTarget[];\n host?: unknown;\n}\n\nfunction domainOf(surfaceName: string): string {\n const slash = surfaceName.indexOf(\"/\");\n return slash > 0 ? surfaceName.slice(0, slash) : surfaceName;\n}\n\nexport function createClickTarget(input: ClickTargetInput = {}): ClickTarget {\n const declaration = input.declaration ?? null;\n const item = input.item ?? null;\n const valueKeys = new Set<string>();\n let restricted = false;\n const writable: WriteTarget[] = [];\n if (declaration) {\n for (const value of declaration.values) {\n valueKeys.add(value.name);\n const s = resolveSensitivity(value);\n if (s.classification !== \"ordinary\" || !s.exportable) restricted = true;\n }\n const itemType = item ? declaration.itemTypes?.find((t) => t.name === item.itemType) : undefined;\n if (itemType) {\n for (const value of itemType.values) {\n valueKeys.add(qualifyValueKey(itemType.name, value.name));\n const s = resolveSensitivity(value);\n if (s.classification !== \"ordinary\" || !s.exportable) restricted = true;\n }\n for (const t of itemType.writeTargets ?? []) writable.push(t);\n }\n for (const t of declaration.writeTargets ?? []) {\n if (!t.itemType || t.itemType === item?.itemType) writable.push(t);\n }\n }\n const readOnly = input.readOnly ?? item?.readOnly ?? false;\n const surfaceName = input.surface?.surfaceName ?? declaration?.surfaceName ?? null;\n const scopeChain = input.scopeChain ?? (surfaceName ? [\"global\", domainOf(surfaceName), surfaceName] : [\"global\"]);\n return {\n surface: input.surface ?? null,\n declaration,\n valueKeys,\n scopeChain,\n excludedActionIds: new Set(input.excludedActionIds ?? []),\n item,\n level: input.level ?? item?.level ?? null,\n selection: input.selection ?? null,\n payloadKinds: input.payloadKinds ?? [],\n writable: readOnly ? [] : (input.writable ?? writable),\n readOnly,\n restricted,\n organizationId: input.organizationId ?? null,\n auth: {\n authenticated: input.auth?.authenticated ?? false,\n admin: input.auth?.admin ?? false,\n creator: input.auth?.creator ?? false,\n },\n host: input.host,\n };\n}\n","/**\n * The one run path (T1e). Every layout runs an action through here, so the\n * sign-in-then-resume rule, projection and failure capture live once.\n */\nimport type { TransferOutcome } from \"@ai-matrx/kit/content-transfer\";\nimport type { AlchemyHostPorts } from \"../ports\";\nimport { createProjectedReads, type ProjectionSource } from \"./projection\";\nimport type { Action, ClickTarget } from \"./types\";\n\nexport interface RunActionOptions {\n ports: AlchemyHostPorts;\n /** Raw sources; projected before the action sees them. */\n source?: ProjectionSource;\n /** Stable fingerprint of the content, for sign-in-then-resume. */\n contentFingerprint?: string;\n signal?: AbortSignal;\n}\n\nexport type RunResult =\n | { status: \"ran\"; outcome: TransferOutcome | void }\n | { status: \"auth-pending\" }\n | { status: \"failed\"; error: unknown };\n\nexport async function runAction(action: Action, target: ClickTarget, options: RunActionOptions): Promise<RunResult> {\n const { ports } = options;\n if (action.requiresAuth && !target.auth.authenticated) {\n if (!ports.auth) {\n // Unreachable through resolve (the action is absent without the port); guard direct callers.\n ports.diagnostics.capture(new Error(`requiresAuth action \"${action.id}\" ran signed out with no auth port`), {\n area: \"actions\",\n detail: { actionId: action.id },\n });\n return { status: \"failed\", error: new Error(\"sign-in unavailable\") };\n }\n ports.auth.signInThenResume({ actionId: action.id, contentFingerprint: options.contentFingerprint ?? action.id });\n return { status: \"auth-pending\" };\n }\n const reads = createProjectedReads(options.source ?? { surface: null, declaration: target.declaration, item: null });\n const signal = options.signal ?? new AbortController().signal;\n try {\n const outcome = await action.run(target, { reads, ports, signal });\n return { status: \"ran\", outcome };\n } catch (error) {\n ports.diagnostics.capture(error, { area: \"actions\", detail: { actionId: action.id, stage: \"run\" } });\n const label = typeof action.label === \"string\" ? action.label : action.id;\n ports.notify?.error(`${label} did not finish.`, \"Try again; if it keeps failing, report it from the feedback button.\");\n return { status: \"failed\", error };\n }\n}\n"],"mappings":"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","names":[]}
package/dist/menu.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var menu_exports={};__export(menu_exports,{APPROVED_HEADINGS:()=>APPROVED_HEADINGS,CATEGORY_GROUP:()=>CATEGORY_GROUP,GROUP_ORDER:()=>GROUP_ORDER,INLINE_SECTION_MAX:()=>INLINE_SECTION_MAX,MENU_LAYOUT_SETTING:()=>MENU_LAYOUT_SETTING,PLATFORM_MENU_LAYOUT:()=>PLATFORM_MENU_LAYOUT,UnapprovedHeadingError:()=>UnapprovedHeadingError,actionToNode:()=>actionToNode,bar:()=>bar,buildMenuModel:()=>buildMenuModel,context:()=>context,defaultHeadingPolicy:()=>defaultHeadingPolicy,filterPalette:()=>filterPalette,findNode:()=>findNode,foldUnderSubject:()=>foldUnderSubject,isApprovedHeading:()=>isApprovedHeading,isContainerWord:()=>isContainerWord,layouts:()=>layouts,leafIds:()=>leafIds,menuHeadings:()=>menuHeadings,overflow:()=>overflow,palette:()=>palette,resolveMenuLayout:()=>resolveMenuLayout,sectionLabel:()=>sectionLabel,selectionToolbar:()=>selectionToolbar,sheet:()=>sheet,unapprovedHeadings:()=>unapprovedHeadings,withExpandedChildren:()=>withExpandedChildren});module.exports=__toCommonJS(menu_exports);var APPROVED_HEADINGS=new Set(["Save","Copy as","Export","Edit","Creator tools","Admin","App","Compare","History"]),CONTAINER_WORD=/^(more|more…|more\.\.\.|other|others|misc|miscellaneous|additional|extra|extras|actions|options|advanced|stuff|everything else)$/i,TARGET_SHAPE=/^(Cell|Row|Column|Table|Range|Selection|Record) · .+$/;function isContainerWord(label){return CONTAINER_WORD.test(label.trim())}function isApprovedHeading(label,kind="group",approved=APPROVED_HEADINGS){let l=label.trim();return!l||isContainerWord(l)?!1:approved.has(l)?!0:kind==="target"||TARGET_SHAPE.test(l)}function defaultHeadingPolicy(){return globalThis.process?.env?.NODE_ENV==="production"?"inline":"throw"}var UnapprovedHeadingError=class extends Error{heading;constructor(heading,where){super(`Menu heading "${heading}" (${where}) is not an approved heading. Remedy: show these rows inline \u2014 declare no section (or kind "target" for the clicked thing's own label) \u2014 or ask Arman to add the name to APPROVED_HEADINGS in @ai-matrx/alchemy/menu. A generic container word ("More\u2026", "Other") is never approved.`),this.name="UnapprovedHeadingError",this.heading=heading}};function menuHeadings(model){let out=[],walk=nodes=>{for(let n of nodes)n.kind==="submenu"&&(out.push(n.label),walk(n.children))};for(let s of model.sections)s.label&&out.push(s.label),walk(s.nodes);return[...new Set(out)]}function unapprovedHeadings(model,approved=APPROVED_HEADINGS){let out=new Set;for(let s of model.sections){s.label&&!isApprovedHeading(s.label,s.primary||s.target?"target":"group",approved)&&out.add(s.label);let walk=nodes=>{for(let n of nodes)n.kind==="submenu"&&(isContainerWord(n.label)&&out.add(n.label),walk(n.children))};walk(s.nodes)}return[...out]}var CATEGORY_GROUP={clipboard:"clipboard",copy:"transfer",export:"transfer",save:"document",share:"document",edit:"editable",history:"history",ai:"ai",ask:"ai",listen:"tools",study:"tools",app:"tools",feedback:"quick",creator:"admin",admin:"admin","surface-info":"surface-info"},GROUP_ORDER=["clipboard","editable","transfer","document","history","ai","tools","quick","surface","admin","surface-info"],CATEGORY_ORDER=["clipboard","edit","copy","export","save","share","history","ask","ai","listen","study","app","feedback","creator","admin","surface-info"];function translate(i18n,key,fallback){if(!i18n)return fallback;try{return i18n.t(key,fallback)}catch{return fallback}}function actionToNode(resolved,target,options={}){let{action,eligibility}=resolved,rawLabel=typeof action.label=="function"?action.label(target):action.label,label=translate(options.i18n,action.labelKey??action.id,rawLabel),base={id:action.id,actionId:action.id,...action.icon!==void 0?{icon:action.icon}:{},...action.placement!==void 0?{placement:action.placement}:{}};if(action.expand)return{...base,kind:"submenu",label,lazy:!0,loading:!1,...action.pending?.(target)?{pending:!0}:{},children:[],...action.emptyLabel!==void 0?{emptyLabel:action.emptyLabel}:{}};if(action.href!==void 0){let href=typeof action.href=="function"?action.href(target):action.href;return{...base,kind:"link",label,href}}if(action.toggle)return{...base,kind:"checkbox",label,checked:action.pressed?.(target)??!1,...action.description!==void 0?{description:action.description}:{}};let state=action.stateIcon?.(target)??null,pressed=action.pressed?.(target),hint=action.hint!==void 0?translate(options.i18n,action.hintKey??`${action.id}.hint`,action.hint):void 0;return{...base,kind:"item",label,...action.iconTone!==void 0?{iconTone:action.iconTone}:{},...state?{stateIcon:state}:{},...action.description!==void 0?{description:action.description}:{},...hint!==void 0?{hint}:{},...pressed!==void 0?{pressed}:{},...action.destructive?{destructive:!0}:{},...action.preserveSelection?{preserveSelection:!0}:{},...eligibility.status==="unavailable-verb"||eligibility.status==="unavailable"?{unavailable:{sentence:eligibility.sentence}}:{}}}function buildMenuModel(target,resolved,options={}){let policy=options.headingPolicy??defaultHeadingPolicy();resolved=resolved.map(r=>{let sec=r.action.section;if(r.action.expand&&isContainerWord(typeof r.action.label=="string"?r.action.label:"")&&policy==="throw")throw new UnapprovedHeadingError(r.action.label,`submenu of ${r.action.id}`);if(!sec)return r;let kind=sec.kind??(sec.primary?"target":"group");if(isApprovedHeading(sec.label,kind))return r;if(policy==="throw")throw new UnapprovedHeadingError(sec.label,`section of ${r.action.id}`);let{section:_dropped,...rest}=r.action;return{...r,action:rest}});let ordered=resolved.map((r,index)=>({r,index})).sort((a,b)=>{let ca=CATEGORY_ORDER.indexOf(a.r.action.category),cb=CATEGORY_ORDER.indexOf(b.r.action.category);if(ca!==cb)return ca-cb;let oa=a.r.action.order??0,ob=b.r.action.order??0;return oa!==ob?oa-ob:a.index-b.index}).map(x=>x.r),sections=new Map,roles={},explicit=new Map;resolved.forEach((r,index)=>{let sec=r.action.section;if(!sec)return;let entry=explicit.get(sec.id)??{group:CATEGORY_GROUP[r.action.category],rows:[]};entry.rows.push({r,index}),explicit.set(sec.id,entry)});let placed=new Set;for(let r of ordered){let sec=r.action.section;if(sec&&placed.has(sec.id))continue;let group=sec?explicit.get(sec.id)?.group??CATEGORY_GROUP[r.action.category]:CATEGORY_GROUP[r.action.category],sectionId=sec?`section:${sec.id}`:group,section=sections.get(sectionId);section||(section={id:sectionId,group,nodes:[],...sec?{label:translate(options.i18n,sec.labelKey??`section.${sec.id}`,sec.label)}:{},...sec?.icon!==void 0?{icon:sec.icon}:{},...sec?.primary?{primary:!0}:{},...sec&&(sec.kind??(sec.primary?"target":"group"))==="target"?{target:!0}:{}},sections.set(sectionId,section));let rows=sec?(explicit.get(sec.id)?.rows??[]).sort((x,y)=>(x.r.action.order??0)-(y.r.action.order??0)||x.index-y.index).map(x=>x.r):[r];sec&&placed.add(sec.id);for(let row of rows)section.nodes.push(actionToNode(row,target,options)),row.action.verb&&(roles[row.action.verb]=row.action.id)}let list=[...sections.values()].sort((a,b)=>!!a.primary!=!!b.primary?a.primary?-1:1:GROUP_ORDER.indexOf(a.group)-GROUP_ORDER.indexOf(b.group)),selected=target.selection?.text?.trim(),content=options.content?.trim(),fieldName=options.contentLabel?.trim();return{header:selected?{label:"Selected",text:target.selection?.text??""}:fieldName?{label:fieldName,text:options.content??""}:content?{label:"Content",text:options.content??""}:null,sections:list,roles}}function withExpandedChildren(model,nodeId,children){let visit=nodes=>nodes.map(n=>n.kind!=="submenu"?n:n.id===nodeId?children==="loading"?{...n,loading:!0}:{...n,lazy:!1,loading:!1,pending:!1,children}:{...n,children:visit(n.children)});return{...model,sections:model.sections.map(s=>({...s,nodes:visit(s.nodes)}))}}function findNode(nodes,id){for(let n of nodes){if(n.id===id)return n;if(n.kind==="submenu"){let hit=findNode(n.children,id);if(hit)return hit}}return null}var INLINE_SECTION_MAX=3;function sectionLabel(section){return section.label??null}function isLeaf(node){return node.kind==="item"||node.kind==="checkbox"||node.kind==="link"}function collect(nodes,out){for(let n of nodes)isLeaf(n)?out.push(n.id):n.kind==="submenu"&&(n.lazy?out.push(n.id):collect(n.children,out))}function leafIds(arranged){let out=[];if(Array.isArray(arranged))for(let s of arranged)collect(s.nodes,out);else if("leaves"in arranged)for(let l of arranged.leaves)out.push(l.node.id);else if("primary"in arranged&&"overflow"in arranged){collect(arranged.primary,out);for(let s of arranged.overflow)collect(s.nodes,out)}else if("root"in arranged)collect(arranged.strip,out),collect(arranged.root,out);else if("more"in arranged){collect(arranged.strip,out);for(let s of arranged.more)collect(s.nodes,out)}else if("filterable"in arranged){collect(arranged.strip,out);for(let s of arranged.sections)collect(s.nodes,out)}else for(let s of arranged.sections)collect(s.nodes,out);return[...new Set(out)].sort()}function fold(section){return{kind:"submenu",id:`${section.id}:fold`,label:section.label,...section.icon!==void 0?{icon:section.icon}:{},children:section.nodes}}function rowCount(nodes){return nodes.filter(n=>n.kind!=="separator"&&n.kind!=="label").length}function stripOf(model){let clipboard=model.sections.find(s=>s.group==="clipboard"&&!s.primary);return clipboard?clipboard.nodes.filter(isLeaf):[]}function clipboardRest(model){let clipboard=model.sections.find(s=>s.group==="clipboard"&&!s.primary);return clipboard?clipboard.nodes.filter(n=>!isLeaf(n)&&n.kind==="submenu"):[]}function placementOf(node){return"placement"in node&&node.placement?node.placement:"overflow"}function classic(model){return{strip:[],sections:model.sections,filterable:!1}}function tiered(model,filterable){let strip=stripOf(model),sections=[],rest=clipboardRest(model);for(let s of model.sections){if(s.group==="clipboard"&&!s.primary){rest.length&&sections.push({...s,nodes:rest});continue}if(s.primary){sections.push(s);continue}if(!s.label||s.group==="surface-info"||rowCount(s.nodes)<=3){sections.push(s);continue}sections.push({id:s.id,group:s.group,nodes:[fold({...s,label:s.label})]})}return{strip,sections,filterable}}function context(model,arrangement){return arrangement==="classic"?classic(model):tiered(model,arrangement==="command")}function foldUnderSubject(arranged,subject){let label=subject.trim(),primary=arranged.sections.filter(s=>s.primary),rest=arranged.sections.filter(s=>!s.primary);if(!label||primary.length===0||rest.length===0&&arranged.strip.length===0)return arranged;let children=[];arranged.strip.length&&children.push(...arranged.strip);for(let s of rest)rowCount(s.nodes)!==0&&(children.length&&children.push({kind:"separator",id:`${s.id}:subject-sep`}),s.label&&children.push({kind:"label",id:`${s.id}:subject-label`,label:s.label}),children.push(...s.nodes));return rowCount(children)===0?{...arranged,strip:[]}:{strip:[],filterable:!1,sections:[...primary,{id:"subject",group:rest[0]?.group??primary[0].group,nodes:[{kind:"submenu",id:"subject:fold",label,children}]}]}}function foldNamed(sections){return sections.map(s=>s.label&&!s.primary&&rowCount(s.nodes)>3?{id:s.id,group:s.group,nodes:[fold({...s,label:s.label})]}:s)}function overflow(model){return foldNamed(model.sections)}function bar(model,_options={}){let primary=[],overflowSections=[];for(let s of model.sections){let rest=[];for(let n of s.nodes){if(n.kind==="separator"||n.kind==="label"){rest.push(n);continue}let p=placementOf(n);(p==="primary"||p==="both")&&primary.push(n),(p==="overflow"||p==="both")&&rest.push(n)}rowCount(rest)>0&&overflowSections.push({...s,nodes:rest})}return{primary,overflow:foldNamed(overflowSections)}}function selectionToolbar(model){let strip=[],more=[];for(let s of model.sections){let rest=[];for(let n of s.nodes)s.group==="clipboard"&&!s.primary&&isLeaf(n)||placementOf(n)!=="overflow"?strip.push(n):rest.push(n);rowCount(rest)>0&&more.push({...s,nodes:rest})}return{strip,more}}function sheet(model){let strip=stripOf(model),root=[],rest=clipboardRest(model);for(let s of model.sections){let nodes=s.group==="clipboard"&&!s.primary?rest:s.nodes;rowCount(nodes)!==0&&(root.length&&root.push({kind:"separator",id:`${s.id}:sep`}),s.primary||!s.label||rowCount(nodes)<=3?(s.label&&root.push({kind:"label",id:`${s.id}:label`,label:s.label}),root.push(...nodes)):root.push(fold({...s,label:s.label,nodes})))}return{strip,root}}function palette(model){let leaves=[],walk=(nodes,trail)=>{for(let n of nodes)isLeaf(n)?leaves.push({node:n,breadcrumb:trail}):n.kind==="submenu"&&(n.lazy?leaves.push({node:n,breadcrumb:trail}):walk(n.children,[...trail,n.label]))};for(let s of model.sections)walk(s.nodes,s.label?[s.label]:[]);return{leaves}}function filterPalette(arranged,query){let words=query.toLowerCase().split(/\s+/).filter(Boolean);return words.length?{leaves:arranged.leaves.filter(l=>{let hay=`${l.node.label} ${l.breadcrumb.join(" ")}`.toLowerCase();return words.every(w=>hay.includes(w))})}:arranged}var layouts={bar,overflow,context,selectionToolbar,sheet,palette};var PLATFORM_MENU_LAYOUT={context:"command",density:"comfortable"},MENU_LAYOUT_SETTING="alchemy.menu.layout",ARRANGEMENTS=new Set(["classic","tiered","command"]),DENSITIES=new Set(["comfortable","compact"]);async function resolveMenuLayout(ports,organizationId){let org=ports.knobs?.layout(organizationId)??null,base={...PLATFORM_MENU_LAYOUT,...org??{}},own=null;try{own=await ports.persistence?.readSetting(MENU_LAYOUT_SETTING)??null}catch{own=null}if(own&&typeof own=="object"&&!Array.isArray(own)){let o=own;return{context:typeof o.context=="string"&&ARRANGEMENTS.has(o.context)?o.context:base.context,density:typeof o.density=="string"&&DENSITIES.has(o.density)?o.density:base.density}}return base}
1
+ "use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var menu_exports={};__export(menu_exports,{APPROVED_HEADINGS:()=>APPROVED_HEADINGS,CATEGORY_GROUP:()=>CATEGORY_GROUP,GROUP_ORDER:()=>GROUP_ORDER,INLINE_SECTION_MAX:()=>INLINE_SECTION_MAX,MENU_LAYOUT_SETTING:()=>MENU_LAYOUT_SETTING,PLATFORM_MENU_LAYOUT:()=>PLATFORM_MENU_LAYOUT,UnapprovedHeadingError:()=>UnapprovedHeadingError,actionToNode:()=>actionToNode,bar:()=>bar,buildMenuModel:()=>buildMenuModel,context:()=>context,defaultHeadingPolicy:()=>defaultHeadingPolicy,filterPalette:()=>filterPalette,findNode:()=>findNode,foldUnderSubject:()=>foldUnderSubject,isApprovedHeading:()=>isApprovedHeading,isContainerWord:()=>isContainerWord,layouts:()=>layouts,leafIds:()=>leafIds,menuHeadings:()=>menuHeadings,overflow:()=>overflow,palette:()=>palette,resolveMenuLayout:()=>resolveMenuLayout,sectionLabel:()=>sectionLabel,selectionToolbar:()=>selectionToolbar,sheet:()=>sheet,unapprovedHeadings:()=>unapprovedHeadings,withExpandedChildren:()=>withExpandedChildren});module.exports=__toCommonJS(menu_exports);var APPROVED_HEADINGS=new Set(["Save","Copy as","Export","Edit","Creator tools","Admin","App","Compare","History"]),CONTAINER_WORD=/^(more|more…|more\.\.\.|other|others|misc|miscellaneous|additional|extra|extras|actions|options|advanced|stuff|everything else)$/i,TARGET_SHAPE=/^(Cell|Row|Column|Table|Range|Selection|Record) · .+$/;function isContainerWord(label){return CONTAINER_WORD.test(label.trim())}function isApprovedHeading(label,kind="group",approved=APPROVED_HEADINGS){let l=label.trim();return!l||isContainerWord(l)?!1:approved.has(l)?!0:kind==="target"||TARGET_SHAPE.test(l)}function defaultHeadingPolicy(){return globalThis.process?.env?.NODE_ENV==="production"?"inline":"throw"}var UnapprovedHeadingError=class extends Error{heading;constructor(heading,where){super(`Menu heading "${heading}" (${where}) is not an approved heading. Remedy: show these rows inline \u2014 declare no section (or kind "target" for the clicked thing's own label) \u2014 or ask Arman to add the name to APPROVED_HEADINGS in @ai-matrx/alchemy/menu. A generic container word ("More\u2026", "Other") is never approved.`),this.name="UnapprovedHeadingError",this.heading=heading}};function menuHeadings(model){let out=[],walk=nodes=>{for(let n of nodes)n.kind==="submenu"&&(out.push(n.label),walk(n.children))};for(let s of model.sections)s.label&&out.push(s.label),walk(s.nodes);return[...new Set(out)]}function unapprovedHeadings(model,approved=APPROVED_HEADINGS){let out=new Set;for(let s of model.sections){s.label&&!isApprovedHeading(s.label,s.primary||s.target?"target":"group",approved)&&out.add(s.label);let walk=nodes=>{for(let n of nodes)n.kind==="submenu"&&(isContainerWord(n.label)&&out.add(n.label),walk(n.children))};walk(s.nodes)}return[...out]}var CATEGORY_GROUP={clipboard:"clipboard",copy:"transfer",export:"transfer",save:"document",share:"document",edit:"editable",history:"history",ai:"ai",ask:"ai",listen:"tools",study:"tools",app:"tools",feedback:"quick",creator:"admin",admin:"admin","surface-info":"surface-info"},GROUP_ORDER=["clipboard","editable","transfer","document","history","ai","tools","quick","surface","admin","surface-info"],CATEGORY_ORDER=["clipboard","edit","copy","export","save","share","history","ask","ai","listen","study","app","feedback","creator","admin","surface-info"];function translate(i18n,key,fallback){if(!i18n)return fallback;try{return i18n.t(key,fallback)}catch{return fallback}}function actionToNode(resolved,target,options={}){let{action,eligibility}=resolved,rawLabel=typeof action.label=="function"?action.label(target):action.label,label=translate(options.i18n,action.labelKey??action.id,rawLabel),base={id:action.id,actionId:action.id,...action.icon!==void 0?{icon:action.icon}:{},...action.placement!==void 0?{placement:action.placement}:{}};if(action.expand)return{...base,kind:"submenu",label,lazy:!0,loading:!1,...action.pending?.(target)?{pending:!0}:{},children:[],...action.emptyLabel!==void 0?{emptyLabel:action.emptyLabel}:{}};if(action.href!==void 0){let href=typeof action.href=="function"?action.href(target):action.href;return{...base,kind:"link",label,href}}if(action.toggle)return{...base,kind:"checkbox",label,checked:action.pressed?.(target)??!1,...action.description!==void 0?{description:action.description}:{}};let state=action.stateIcon?.(target)??null,pressed=action.pressed?.(target),hint=action.hint!==void 0?translate(options.i18n,action.hintKey??`${action.id}.hint`,action.hint):void 0;return{...base,kind:"item",label,...action.iconTone!==void 0?{iconTone:action.iconTone}:{},...state?{stateIcon:state}:{},...action.description!==void 0?{description:action.description}:{},...hint!==void 0?{hint}:{},...pressed!==void 0?{pressed}:{},...action.destructive?{destructive:!0}:{},...action.preserveSelection?{preserveSelection:!0}:{},...eligibility.status==="unavailable-verb"||eligibility.status==="unavailable"?{unavailable:{sentence:eligibility.sentence}}:{}}}function buildMenuModel(target,resolved,options={}){let policy=options.headingPolicy??defaultHeadingPolicy();resolved=resolved.map(r=>{let sec=r.action.section;if(r.action.expand&&isContainerWord(typeof r.action.label=="string"?r.action.label:"")&&policy==="throw")throw new UnapprovedHeadingError(r.action.label,`submenu of ${r.action.id}`);if(!sec)return r;let kind=sec.kind??(sec.primary?"target":"group");if(isApprovedHeading(sec.label,kind))return r;if(policy==="throw")throw new UnapprovedHeadingError(sec.label,`section of ${r.action.id}`);let{section:_dropped,...rest}=r.action;return{...r,action:rest}});let ordered=resolved.map((r,index)=>({r,index})).sort((a,b)=>{let ca=CATEGORY_ORDER.indexOf(a.r.action.category),cb=CATEGORY_ORDER.indexOf(b.r.action.category);if(ca!==cb)return ca-cb;let oa=a.r.action.order??0,ob=b.r.action.order??0;return oa!==ob?oa-ob:a.index-b.index}).map(x=>x.r),sections=new Map,roles={},explicit=new Map;resolved.forEach((r,index)=>{let sec=r.action.section;if(!sec)return;let entry=explicit.get(sec.id)??{group:CATEGORY_GROUP[r.action.category],rows:[]};entry.rows.push({r,index}),explicit.set(sec.id,entry)});let placed=new Set;for(let r of ordered){let sec=r.action.section;if(sec&&placed.has(sec.id))continue;let group=sec?explicit.get(sec.id)?.group??CATEGORY_GROUP[r.action.category]:CATEGORY_GROUP[r.action.category],sectionId=sec?`section:${sec.id}`:group,section=sections.get(sectionId);section||(section={id:sectionId,group,nodes:[],...sec?{label:translate(options.i18n,sec.labelKey??`section.${sec.id}`,sec.label)}:{},...sec?.icon!==void 0?{icon:sec.icon}:{},...sec?.primary?{primary:!0}:{},...sec&&(sec.kind??(sec.primary?"target":"group"))==="target"?{target:!0}:{}},sections.set(sectionId,section));let rows=sec?(explicit.get(sec.id)?.rows??[]).sort((x,y)=>(x.r.action.order??0)-(y.r.action.order??0)||x.index-y.index).map(x=>x.r):[r];sec&&placed.add(sec.id);for(let row of rows)row.action.startsGroup&&section.nodes.length>0&&section.nodes.push({kind:"separator",id:`${row.action.id}:group`}),section.nodes.push(actionToNode(row,target,options)),row.action.verb&&(roles[row.action.verb]=row.action.id)}let list=[...sections.values()].sort((a,b)=>!!a.primary!=!!b.primary?a.primary?-1:1:GROUP_ORDER.indexOf(a.group)-GROUP_ORDER.indexOf(b.group)),selected=target.selection?.text?.trim(),content=options.content?.trim(),fieldName=options.contentLabel?.trim();return{header:selected?{label:"Selected",text:target.selection?.text??""}:fieldName?{label:fieldName,text:options.content??""}:content?{label:"Content",text:options.content??""}:null,sections:list,roles}}function withExpandedChildren(model,nodeId,children){let visit=nodes=>nodes.map(n=>n.kind!=="submenu"?n:n.id===nodeId?children==="loading"?{...n,loading:!0}:{...n,lazy:!1,loading:!1,pending:!1,children}:{...n,children:visit(n.children)});return{...model,sections:model.sections.map(s=>({...s,nodes:visit(s.nodes)}))}}function findNode(nodes,id){for(let n of nodes){if(n.id===id)return n;if(n.kind==="submenu"){let hit=findNode(n.children,id);if(hit)return hit}}return null}var INLINE_SECTION_MAX=3;function sectionLabel(section){return section.label??null}function isLeaf(node){return node.kind==="item"||node.kind==="checkbox"||node.kind==="link"}function collect(nodes,out){for(let n of nodes)isLeaf(n)?out.push(n.id):n.kind==="submenu"&&(n.lazy?out.push(n.id):collect(n.children,out))}function leafIds(arranged){let out=[];if(Array.isArray(arranged))for(let s of arranged)collect(s.nodes,out);else if("leaves"in arranged)for(let l of arranged.leaves)out.push(l.node.id);else if("primary"in arranged&&"overflow"in arranged){collect(arranged.primary,out);for(let s of arranged.overflow)collect(s.nodes,out)}else if("root"in arranged)collect(arranged.strip,out),collect(arranged.root,out);else if("more"in arranged){collect(arranged.strip,out);for(let s of arranged.more)collect(s.nodes,out)}else if("filterable"in arranged){collect(arranged.strip,out);for(let s of arranged.sections)collect(s.nodes,out)}else for(let s of arranged.sections)collect(s.nodes,out);return[...new Set(out)].sort()}function fold(section){return{kind:"submenu",id:`${section.id}:fold`,label:section.label,...section.icon!==void 0?{icon:section.icon}:{},children:section.nodes}}function rowCount(nodes){return nodes.filter(n=>n.kind!=="separator"&&n.kind!=="label").length}function stripOf(model){let clipboard=model.sections.find(s=>s.group==="clipboard"&&!s.primary);return clipboard?clipboard.nodes.filter(isLeaf):[]}function clipboardRest(model){let clipboard=model.sections.find(s=>s.group==="clipboard"&&!s.primary);return clipboard?clipboard.nodes.filter(n=>!isLeaf(n)&&n.kind==="submenu"):[]}function placementOf(node){return"placement"in node&&node.placement?node.placement:"overflow"}function classic(model){return{strip:[],sections:model.sections,filterable:!1}}function tiered(model,filterable){let strip=stripOf(model),sections=[],rest=clipboardRest(model);for(let s of model.sections){if(s.group==="clipboard"&&!s.primary){rest.length&&sections.push({...s,nodes:rest});continue}if(s.primary){sections.push(s);continue}if(!s.label||s.group==="surface-info"||rowCount(s.nodes)<=3){sections.push(s);continue}sections.push({id:s.id,group:s.group,nodes:[fold({...s,label:s.label})]})}return{strip,sections,filterable}}function context(model,arrangement){return arrangement==="classic"?classic(model):tiered(model,arrangement==="command")}function foldUnderSubject(arranged,subject){let label=subject.trim(),primary=arranged.sections.filter(s=>s.primary),rest=arranged.sections.filter(s=>!s.primary);if(!label||primary.length===0||rest.length===0&&arranged.strip.length===0)return arranged;let children=[];arranged.strip.length&&children.push(...arranged.strip);for(let s of rest)rowCount(s.nodes)!==0&&(children.length&&children.push({kind:"separator",id:`${s.id}:subject-sep`}),s.label&&children.push({kind:"label",id:`${s.id}:subject-label`,label:s.label}),children.push(...s.nodes));return rowCount(children)===0?{...arranged,strip:[]}:{strip:[],filterable:!1,sections:[...primary,{id:"subject",group:rest[0]?.group??primary[0].group,nodes:[{kind:"submenu",id:"subject:fold",label,children}]}]}}function foldNamed(sections){return sections.map(s=>s.label&&!s.primary&&rowCount(s.nodes)>3?{id:s.id,group:s.group,nodes:[fold({...s,label:s.label})]}:s)}function overflow(model){return foldNamed(model.sections)}function bar(model,_options={}){let primary=[],overflowSections=[];for(let s of model.sections){let rest=[];for(let n of s.nodes){if(n.kind==="separator"||n.kind==="label"){rest.push(n);continue}let p=placementOf(n);(p==="primary"||p==="both")&&primary.push(n),(p==="overflow"||p==="both")&&rest.push(n)}rowCount(rest)>0&&overflowSections.push({...s,nodes:rest})}return{primary,overflow:foldNamed(overflowSections)}}function selectionToolbar(model){let strip=[],more=[];for(let s of model.sections){let rest=[];for(let n of s.nodes)s.group==="clipboard"&&!s.primary&&isLeaf(n)||placementOf(n)!=="overflow"?strip.push(n):rest.push(n);rowCount(rest)>0&&more.push({...s,nodes:rest})}return{strip,more}}function sheet(model){let strip=stripOf(model),root=[],rest=clipboardRest(model);for(let s of model.sections){let nodes=s.group==="clipboard"&&!s.primary?rest:s.nodes;rowCount(nodes)!==0&&(root.length&&root.push({kind:"separator",id:`${s.id}:sep`}),s.primary||!s.label||rowCount(nodes)<=3?(s.label&&root.push({kind:"label",id:`${s.id}:label`,label:s.label}),root.push(...nodes)):root.push(fold({...s,label:s.label,nodes})))}return{strip,root}}function palette(model){let leaves=[],walk=(nodes,trail)=>{for(let n of nodes)isLeaf(n)?leaves.push({node:n,breadcrumb:trail}):n.kind==="submenu"&&(n.lazy?leaves.push({node:n,breadcrumb:trail}):walk(n.children,[...trail,n.label]))};for(let s of model.sections)walk(s.nodes,s.label?[s.label]:[]);return{leaves}}function filterPalette(arranged,query){let words=query.toLowerCase().split(/\s+/).filter(Boolean);return words.length?{leaves:arranged.leaves.filter(l=>{let hay=`${l.node.label} ${l.breadcrumb.join(" ")}`.toLowerCase();return words.every(w=>hay.includes(w))})}:arranged}var layouts={bar,overflow,context,selectionToolbar,sheet,palette};var PLATFORM_MENU_LAYOUT={context:"command",density:"comfortable"},MENU_LAYOUT_SETTING="alchemy.menu.layout",ARRANGEMENTS=new Set(["classic","tiered","command"]),DENSITIES=new Set(["comfortable","compact"]);async function resolveMenuLayout(ports,organizationId){let org=ports.knobs?.layout(organizationId)??null,base={...PLATFORM_MENU_LAYOUT,...org??{}},own=null;try{own=await ports.persistence?.readSetting(MENU_LAYOUT_SETTING)??null}catch{own=null}if(own&&typeof own=="object"&&!Array.isArray(own)){let o=own;return{context:typeof o.context=="string"&&ARRANGEMENTS.has(o.context)?o.context:base.context,density:typeof o.density=="string"&&DENSITIES.has(o.density)?o.density:base.density}}return base}
2
2
  //# sourceMappingURL=menu.cjs.map