@ai-matrx/alchemy 0.8.10 → 0.8.13

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,11 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.13
4
+ ### ALC-15 round 3 (D2, D3)
5
+
6
+ - **An open lazy submenu fills when its data arrives (D2).** The engine remembers which lazy submenus the person opened; a re-resolve (late agent libraries) refills them from the new actions instead of leaving them blank until reopened. Only the latest expand of a node writes its rows.
7
+ - **The phone sheet keeps its drill position on late data (D3).** The path re-resolves against the live model and skips a step that no longer exists (a section that stopped folding), so she stays where she was; a path with nothing left is the root — never a root list under a stale "‹ Back". The verb strip heads the sheet at every drill level.
8
+ - `react-bar` size limit 8,000 → 8,400 B gzip: the shared engine gained the refill.
9
+ - Proof: `src/react/late-data.test.tsx` (3 tests, red before the fix).
10
+
11
+ ## 0.8.12
12
+
13
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
14
+ `git diff npm/alchemy/v0.8.11..npm/alchemy/v0.8.12 -- apps/shared/alchemy`).
15
+ No source changes intended and no consumer action required.
16
+
17
+ ## 0.8.11
18
+ ### ALC-15 final fixes
19
+
20
+ - Every verb button (strip, bar) and every phone-sheet row carries the design-system touch floor (`data-matrx-button data-tap-floor[-square]`): 44px on coarse pointers; desktop density unchanged. The verbs were 28px on a phone.
21
+ - `/menu` adds `menuHeadings`, `unapprovedHeadings(model, approved)` and `isContainerWord`: a heading (named section, fold, group submenu) must be in the host's approved set, and a generic container word ("More…", "Other", "Actions") never is — the check hosts run so no lane folds rows under an invented heading (chair ruling 7).
22
+
23
+ ### Consumer action
24
+
25
+ Hosts: assert `unapprovedHeadings(model, <your classic headings>)` is empty in a test. The frontend imports `@ai-matrx/design-system/styles.css` already, which carries the floor.
3
26
  ## 0.8.10
4
27
 
5
28
  Automatic changed-only republish (docs/metadata drift since the last tag — see
6
29
  `git diff npm/alchemy/v0.8.9..npm/alchemy/v0.8.10 -- apps/shared/alchemy`).
7
30
  No source changes intended and no consumer action required.
8
-
9
31
  ## 0.8.9
10
32
 
11
33
  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/** 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\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-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/** 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-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":[]}
@@ -531,6 +531,12 @@ interface ActionSection {
531
531
  labelKey?: string;
532
532
  icon?: string;
533
533
  primary?: boolean;
534
+ /**
535
+ * "group" (default): a heading over related rows — must be in the package's
536
+ * APPROVED_HEADINGS (@ai-matrx/alchemy/menu). "target": the label of the
537
+ * thing the person clicked, supplied at run time ("Row · Widget A").
538
+ */
539
+ kind?: "group" | "target";
534
540
  }
535
541
  interface Action {
536
542
  /** Globally unique. A duplicate is refused (never overwritten). */
package/dist/actions.d.ts CHANGED
@@ -531,6 +531,12 @@ interface ActionSection {
531
531
  labelKey?: string;
532
532
  icon?: string;
533
533
  primary?: boolean;
534
+ /**
535
+ * "group" (default): a heading over related rows — must be in the package's
536
+ * APPROVED_HEADINGS (@ai-matrx/alchemy/menu). "target": the label of the
537
+ * thing the person clicked, supplied at run time ("Row · Widget A").
538
+ */
539
+ kind?: "group" | "target";
534
540
  }
535
541
  interface Action {
536
542
  /** Globally unique. A duplicate is refused (never overwritten). */
@@ -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/** 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\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-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,kBAAkB,CAAC,QAAQ,OAAO,SAAS,QAAQ,QAAQ,MAAM;;;AC+CvE,IAAM,oBAA+C;AAAA,EAC1D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;;;AC/CO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EACrC;AAAA,EACA;AAAA,EACT,YAAY,UAAkB,OAAe,QAAgB;AAC3D;AAAA,MACE,UAAU,SACN,WAAW,QAAQ,mCAAmC,KAAK,+CAC3D,WAAW,QAAQ,gCAAgC,KAAK,mBAAmB,MAAM,uBAAuB,MAAM;AAAA,IACpH;AACA,SAAK,OAAO;AACZ,SAAK,WAAW;AAChB,SAAK,YAAY,CAAC,OAAO,MAAM;AAAA,EACjC;AACF;AAEO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EACvC;AAAA,EACT,YAAY,YAAoB;AAC9B,UAAM,oBAAoB,UAAU,wEAAwE;AAC5G,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;AAOA,IAAM,QAAQ,IAAI,IAAY,eAAe;AAE7C,SAAS,QAAQ,OAAuC,OAAgB,QAAoB;AAC1F,MAAI;AACF,UAAM,YAAY,QAAQ,OAAO,EAAE,MAAM,WAAW,OAAO,CAAC;AAAA,EAC9D,QAAQ;AAAA,EAER;AACF;AAMO,SAAS,mBACd,QACA,QACA,SAC4D;AAC5D,QAAM,WAAW,QAAQ;AACzB,MAAI,OAAO,kBAAkB,IAAI,OAAO,EAAE,EAAG,QAAO,EAAE,QAAQ,SAAS;AACvE,MAAI,UAAU,SAAS,SAAS,OAAO,EAAE,EAAG,QAAO,EAAE,QAAQ,SAAS;AACtE,MAAI,UAAU,cAAc,CAAC,SAAS,WAAW,SAAS,OAAO,QAAQ,EAAG,QAAO,EAAE,QAAQ,SAAS;AACtG,MAAI,OAAO,UAAU,OAAO,OAAO,SAAS,GAAG;AAE7C,QAAI,OAAO,SAAU,QAAO,EAAE,QAAQ,SAAS;AAC/C,UAAM,WAAW,IAAI,IAAI,OAAO,SAAS,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAC3D,QAAI,CAAC,OAAO,OAAO,KAAK,CAAC,SAAS,SAAS,IAAI,IAAI,CAAC,EAAG,QAAO,EAAE,QAAQ,SAAS;AAAA,EACnF;AACA,MAAI,OAAO,gBAAgB,CAAC,OAAO,KAAK,iBAAiB,CAAC,QAAQ,eAAe;AAE/E,WAAO,EAAE,QAAQ,SAAS;AAAA,EAC5B;AACA,QAAM,MAAM,OAAO,SAAS,MAAM;AAClC,MAAI,IAAI,WAAW,mBAAoB,QAAO;AAC9C,MAAI,OAAO,aAAa,eAAe,CAAC,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3D,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ,WAAW,OAAO,EAAE;AAAA,IAC9B;AAAA,EACF;AACA,MAAI,CAAC,IAAI,SAAS,KAAK,GAAG;AACxB,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ,mBAAmB,OAAO,EAAE;AAAA,IACtC;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,qBAAqB,SAAgD;AACnF,QAAM,QAAQ,QAAQ;AACtB,QAAM,UAAU,oBAAI,IAAmB;AACvC,QAAM,YAAY,oBAAI,IAAgB;AACtC,QAAM,SAAS,MAAM;AACnB,eAAW,KAAK,CAAC,GAAG,SAAS,EAAG,GAAE;AAAA,EACpC;AAEA,SAAO;AAAA,IACL,SAAS,UAAU;AACjB,UAAI,QAAQ,IAAI,SAAS,EAAE,EAAG,OAAM,IAAI,uBAAuB,SAAS,EAAE;AAC1E,YAAM,WAAW,SAAS,cAAc,KAAK,CAAC;AAC9C,YAAM,MAAM,oBAAI,IAAY;AAC5B,iBAAW,MAAM,UAAU;AACzB,YAAI,IAAI,IAAI,EAAE,EAAG,OAAM,IAAI,qBAAqB,IAAI,SAAS,IAAI,SAAS,EAAE;AAC5E,YAAI,IAAI,EAAE;AACV,mBAAW,CAAC,SAAS,KAAK,KAAK,SAAS;AACtC,cAAI,MAAM,SAAS,SAAS,EAAE,EAAG,OAAM,IAAI,qBAAqB,IAAI,SAAS,SAAS,EAAE;AAAA,QAC1F;AAAA,MACF;AACA,YAAM,QAAe,EAAE,UAAU,SAAS;AAC1C,cAAQ,IAAI,SAAS,IAAI,KAAK;AAC9B,aAAO;AACP,aAAO,MAAM;AACX,YAAI,QAAQ,IAAI,SAAS,EAAE,MAAM,OAAO;AACtC,kBAAQ,OAAO,SAAS,EAAE;AAC1B,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,QAAQ,QAAQ,UAAU;AAC9B,YAAM,gBAAgB,QAAQ,MAAM,IAAI;AACxC,YAAM,OAAO,oBAAI,IAAoB;AACrC,YAAM,MAAwB,CAAC;AAC/B,iBAAW,CAAC,YAAY,EAAE,SAAS,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG;AACrD,YAAI;AACJ,YAAI;AACF,oBAAU,MAAM,SAAS,QAAQ,MAAM;AAAA,QACzC,SAAS,OAAO;AACd,kBAAQ,OAAO,OAAO,EAAE,YAAY,OAAO,UAAU,CAAC;AACtD;AAAA,QACF;AACA,mBAAW,OAAO,SAAS;AACzB,gBAAM,QAAQ,KAAK,IAAI,IAAI,EAAE;AAC7B,cAAI,UAAU,QAAW;AACvB,oBAAQ,OAAO,IAAI,qBAAqB,IAAI,IAAI,OAAO,UAAU,GAAG,EAAE,UAAU,IAAI,GAAG,CAAC;AACxF;AAAA,UACF;AACA,eAAK,IAAI,IAAI,IAAI,UAAU;AAC3B,gBAAM,SAAiB,IAAI,aAAa,aAAa,MAAM,EAAE,GAAG,KAAK,UAAU,WAAW;AAC1F,cAAI;AACJ,cAAI;AACF,0BAAc,mBAAmB,QAAQ,QAAQ,EAAE,UAAU,YAAY,CAAC,GAAG,cAAc,CAAC;AAAA,UAC9F,SAAS,OAAO;AACd,oBAAQ,OAAO,OAAO,EAAE,UAAU,OAAO,IAAI,OAAO,WAAW,CAAC;AAChE;AAAA,UACF;AACA,cAAI,YAAY,WAAW,kBAAkB;AAC3C,oBAAQ,OAAO,IAAI,MAAM,YAAY,MAAM,GAAG,EAAE,UAAU,OAAO,IAAI,OAAO,WAAW,CAAC;AACxF;AAAA,UACF;AACA,cAAI,YAAY,WAAW,SAAU;AACrC,cAAI,KAAK,EAAE,QAAQ,YAAY,CAAC;AAAA,QAClC;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IAEA,YAAY;AACV,aAAO,CAAC,GAAG,QAAQ,KAAK,CAAC;AAAA,IAC3B;AAAA,IAEA,UAAU,UAAU;AAClB,gBAAU,IAAI,QAAQ;AACtB,aAAO,MAAM;AACX,kBAAU,OAAO,QAAQ;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACF;AAGO,SAAS,qBACd,IACA,SACA,OAA+B,MACf;AAChB,QAAM,OAAO,QAAQ,IAAI,CAAC,MAAO,EAAE,aAAa,KAAK,IAAI,EAAE,GAAG,GAAG,UAAU,GAAG,CAAE;AAChF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,aAAa,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,EAAE;AAAA,IACvC,SAAS,MAAM;AAAA,EACjB;AACF;AAGO,IAAM,iBAAiB,OAAoB,EAAE,QAAQ,YAAY;;;ACvMjE,IAAM,sBAAmC,OAAO,OAAO;AAAA,EAC5D,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,mBAAmB;AACrB,CAAC;AAEM,SAAS,mBAAmB,OAAwD;AACzF,SAAO;AAAA,IACL,YAAY,MAAM,aAAa,cAAc,oBAAoB;AAAA,IACjE,gBAAgB,MAAM,aAAa,kBAAkB,oBAAoB;AAAA,IACzE,mBAAmB,MAAM,aAAa,qBAAqB,oBAAoB;AAAA,EACjF;AACF;;;ACEA,SAAS,OAAO,OAAkC;AAChD,MAAI,UAAU,UAAa,OAAO,UAAU,cAAc,OAAO,UAAU,SAAU,QAAO;AAC5F,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,OAAO,UAAU,UAAW,QAAO;AACtF,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AACvE,MAAI,OAAO,UAAU,SAAU,QAAO,MAAM,SAAS;AACrD,MAAI,iBAAiB,KAAM,QAAO,MAAM,YAAY;AACpD,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,CAAC,MAAM,OAAO,CAAC,KAAK,IAAI;AACnE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,MAA+B,CAAC;AACtC,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAgC,GAAG;AACrE,YAAM,IAAI,OAAO,CAAC;AAClB,UAAI,MAAM,OAAW,KAAI,CAAC,IAAI;AAAA,IAChC;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGO,SAAS,cACd,KACA,UACA,aAAa,IACgD;AAC7D,QAAM,SAAS,IAAI,IAAI,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AACvD,QAAM,SAA+B,CAAC;AACtC,QAAM,YAA4B,CAAC;AACnC,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC/C,UAAM,OAAO,aAAa,GAAG,UAAU,IAAI,IAAI,KAAK;AACpD,UAAM,OAAO,OAAO,IAAI,IAAI;AAC5B,QAAI,CAAC,MAAM;AACT,gBAAU,KAAK,EAAE,MAAM,QAAQ,WAAW,CAAC;AAC3C;AAAA,IACF;AACA,UAAM,cAAc,mBAAmB,IAAI;AAC3C,QAAI,YAAY,mBAAmB,YAAY;AAC7C,gBAAU,KAAK,EAAE,MAAM,QAAQ,YAAY,gBAAgB,YAAY,eAAe,CAAC;AACvF;AAAA,IACF;AACA,QAAI,CAAC,YAAY,YAAY;AAC3B,gBAAU,KAAK,EAAE,MAAM,QAAQ,WAAW,CAAC;AAC3C;AAAA,IACF;AACA,UAAM,OAAO,OAAO,KAAK;AACzB,QAAI,SAAS,OAAW;AACxB,WAAO,IAAI,IAAI;AAAA,EACjB;AACA,SAAO,EAAE,QAAQ,UAAU;AAC7B;AAEO,SAAS,qBAAqB,QAA0C;AAC7E,SAAO;AAAA,IACL,MAAM,OAAO,QAAQ;AACnB,UAAI,CAAC,OAAO,WAAW,CAAC,OAAO,YAAa,QAAO,EAAE,QAAQ,CAAC,GAAG,WAAW,CAAC,EAAE;AAC/E,YAAM,MAAM,MAAM,OAAO,QAAQ,WAAW,MAAM;AAClD,YAAM,eAAe,OAAO,YAAY;AACxC,aAAO,cAAc,KAAK,YAAY;AAAA,IACxC;AAAA,IACA,MAAM,KAAK,QAAQ;AACjB,YAAM,OAAO,OAAO;AACpB,UAAI,CAAC,QAAQ,CAAC,OAAO,YAAa,QAAO;AACzC,YAAM,OAAO,OAAO,YAAY,WAAW,KAAK,CAAC,MAAM,EAAE,SAAS,KAAK,QAAQ;AAC/E,UAAI,CAAC,KAAM,QAAO,EAAE,QAAQ,CAAC,GAAG,WAAW,CAAC,EAAE,MAAM,KAAK,UAAU,QAAQ,WAAW,CAAC,EAAE;AACzF,YAAM,MAAM,MAAM,KAAK,WAAW,MAAM;AACxC,aAAO,cAAc,KAAK,KAAK,QAAQ,KAAK,QAAQ;AAAA,IACtD;AAAA,IACA,MAAM,KAAK,QAA6B;AACtC,UAAI,CAAC,OAAO,SAAS,SAAU,QAAO,CAAC;AACvC,aAAO,OAAO,QAAQ,SAAS,MAAM;AAAA,IACvC;AAAA,EACF;AACF;;;ACnEO,SAAS,gBAAgB,UAAkB,MAAiC;AACjF,SAAO,WAAW,GAAG,QAAQ,IAAI,IAAI,KAAK;AAC5C;;;ACEA,SAAS,SAAS,aAA6B;AAC7C,QAAM,QAAQ,YAAY,QAAQ,GAAG;AACrC,SAAO,QAAQ,IAAI,YAAY,MAAM,GAAG,KAAK,IAAI;AACnD;AAEO,SAAS,kBAAkB,QAA0B,CAAC,GAAgB;AAC3E,QAAM,cAAc,MAAM,eAAe;AACzC,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,YAAY,oBAAI,IAAY;AAClC,MAAI,aAAa;AACjB,QAAM,WAA0B,CAAC;AACjC,MAAI,aAAa;AACf,eAAW,SAAS,YAAY,QAAQ;AACtC,gBAAU,IAAI,MAAM,IAAI;AACxB,YAAM,IAAI,mBAAmB,KAAK;AAClC,UAAI,EAAE,mBAAmB,cAAc,CAAC,EAAE,WAAY,cAAa;AAAA,IACrE;AACA,UAAM,WAAW,OAAO,YAAY,WAAW,KAAK,CAAC,MAAM,EAAE,SAAS,KAAK,QAAQ,IAAI;AACvF,QAAI,UAAU;AACZ,iBAAW,SAAS,SAAS,QAAQ;AACnC,kBAAU,IAAI,gBAAgB,SAAS,MAAM,MAAM,IAAI,CAAC;AACxD,cAAM,IAAI,mBAAmB,KAAK;AAClC,YAAI,EAAE,mBAAmB,cAAc,CAAC,EAAE,WAAY,cAAa;AAAA,MACrE;AACA,iBAAW,KAAK,SAAS,gBAAgB,CAAC,EAAG,UAAS,KAAK,CAAC;AAAA,IAC9D;AACA,eAAW,KAAK,YAAY,gBAAgB,CAAC,GAAG;AAC9C,UAAI,CAAC,EAAE,YAAY,EAAE,aAAa,MAAM,SAAU,UAAS,KAAK,CAAC;AAAA,IACnE;AAAA,EACF;AACA,QAAM,WAAW,MAAM,YAAY,MAAM,YAAY;AACrD,QAAM,cAAc,MAAM,SAAS,eAAe,aAAa,eAAe;AAC9E,QAAM,aAAa,MAAM,eAAe,cAAc,CAAC,UAAU,SAAS,WAAW,GAAG,WAAW,IAAI,CAAC,QAAQ;AAChH,SAAO;AAAA,IACL,SAAS,MAAM,WAAW;AAAA,IAC1B;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB,IAAI,IAAI,MAAM,qBAAqB,CAAC,CAAC;AAAA,IACxD;AAAA,IACA,OAAO,MAAM,SAAS,MAAM,SAAS;AAAA,IACrC,WAAW,MAAM,aAAa;AAAA,IAC9B,cAAc,MAAM,gBAAgB,CAAC;AAAA,IACrC,UAAU,WAAW,CAAC,IAAK,MAAM,YAAY;AAAA,IAC7C;AAAA,IACA;AAAA,IACA,gBAAgB,MAAM,kBAAkB;AAAA,IACxC,MAAM;AAAA,MACJ,eAAe,MAAM,MAAM,iBAAiB;AAAA,MAC5C,OAAO,MAAM,MAAM,SAAS;AAAA,MAC5B,SAAS,MAAM,MAAM,WAAW;AAAA,IAClC;AAAA,IACA,MAAM,MAAM;AAAA,EACd;AACF;;;AC5DA,eAAsB,UAAU,QAAgB,QAAqB,SAA+C;AAClH,QAAM,EAAE,MAAM,IAAI;AAClB,MAAI,OAAO,gBAAgB,CAAC,OAAO,KAAK,eAAe;AACrD,QAAI,CAAC,MAAM,MAAM;AAEf,YAAM,YAAY,QAAQ,IAAI,MAAM,wBAAwB,OAAO,EAAE,oCAAoC,GAAG;AAAA,QAC1G,MAAM;AAAA,QACN,QAAQ,EAAE,UAAU,OAAO,GAAG;AAAA,MAChC,CAAC;AACD,aAAO,EAAE,QAAQ,UAAU,OAAO,IAAI,MAAM,qBAAqB,EAAE;AAAA,IACrE;AACA,UAAM,KAAK,iBAAiB,EAAE,UAAU,OAAO,IAAI,oBAAoB,QAAQ,sBAAsB,OAAO,GAAG,CAAC;AAChH,WAAO,EAAE,QAAQ,eAAe;AAAA,EAClC;AACA,QAAM,QAAQ,qBAAqB,QAAQ,UAAU,EAAE,SAAS,MAAM,aAAa,OAAO,aAAa,MAAM,KAAK,CAAC;AACnH,QAAM,SAAS,QAAQ,UAAU,IAAI,gBAAgB,EAAE;AACvD,MAAI;AACF,UAAM,UAAU,MAAM,OAAO,IAAI,QAAQ,EAAE,OAAO,OAAO,OAAO,CAAC;AACjE,WAAO,EAAE,QAAQ,OAAO,QAAQ;AAAA,EAClC,SAAS,OAAO;AACd,UAAM,YAAY,QAAQ,OAAO,EAAE,MAAM,WAAW,QAAQ,EAAE,UAAU,OAAO,IAAI,OAAO,MAAM,EAAE,CAAC;AACnG,UAAM,QAAQ,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ,OAAO;AACvE,UAAM,QAAQ,MAAM,GAAG,KAAK,oBAAoB,qEAAqE;AACrH,WAAO,EAAE,QAAQ,UAAU,MAAM;AAAA,EACnC;AACF;","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/** 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-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,kBAAkB,CAAC,QAAQ,OAAO,SAAS,QAAQ,QAAQ,MAAM;;;AC+CvE,IAAM,oBAA+C;AAAA,EAC1D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;;;AC/CO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EACrC;AAAA,EACA;AAAA,EACT,YAAY,UAAkB,OAAe,QAAgB;AAC3D;AAAA,MACE,UAAU,SACN,WAAW,QAAQ,mCAAmC,KAAK,+CAC3D,WAAW,QAAQ,gCAAgC,KAAK,mBAAmB,MAAM,uBAAuB,MAAM;AAAA,IACpH;AACA,SAAK,OAAO;AACZ,SAAK,WAAW;AAChB,SAAK,YAAY,CAAC,OAAO,MAAM;AAAA,EACjC;AACF;AAEO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EACvC;AAAA,EACT,YAAY,YAAoB;AAC9B,UAAM,oBAAoB,UAAU,wEAAwE;AAC5G,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;AAOA,IAAM,QAAQ,IAAI,IAAY,eAAe;AAE7C,SAAS,QAAQ,OAAuC,OAAgB,QAAoB;AAC1F,MAAI;AACF,UAAM,YAAY,QAAQ,OAAO,EAAE,MAAM,WAAW,OAAO,CAAC;AAAA,EAC9D,QAAQ;AAAA,EAER;AACF;AAMO,SAAS,mBACd,QACA,QACA,SAC4D;AAC5D,QAAM,WAAW,QAAQ;AACzB,MAAI,OAAO,kBAAkB,IAAI,OAAO,EAAE,EAAG,QAAO,EAAE,QAAQ,SAAS;AACvE,MAAI,UAAU,SAAS,SAAS,OAAO,EAAE,EAAG,QAAO,EAAE,QAAQ,SAAS;AACtE,MAAI,UAAU,cAAc,CAAC,SAAS,WAAW,SAAS,OAAO,QAAQ,EAAG,QAAO,EAAE,QAAQ,SAAS;AACtG,MAAI,OAAO,UAAU,OAAO,OAAO,SAAS,GAAG;AAE7C,QAAI,OAAO,SAAU,QAAO,EAAE,QAAQ,SAAS;AAC/C,UAAM,WAAW,IAAI,IAAI,OAAO,SAAS,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAC3D,QAAI,CAAC,OAAO,OAAO,KAAK,CAAC,SAAS,SAAS,IAAI,IAAI,CAAC,EAAG,QAAO,EAAE,QAAQ,SAAS;AAAA,EACnF;AACA,MAAI,OAAO,gBAAgB,CAAC,OAAO,KAAK,iBAAiB,CAAC,QAAQ,eAAe;AAE/E,WAAO,EAAE,QAAQ,SAAS;AAAA,EAC5B;AACA,QAAM,MAAM,OAAO,SAAS,MAAM;AAClC,MAAI,IAAI,WAAW,mBAAoB,QAAO;AAC9C,MAAI,OAAO,aAAa,eAAe,CAAC,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3D,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ,WAAW,OAAO,EAAE;AAAA,IAC9B;AAAA,EACF;AACA,MAAI,CAAC,IAAI,SAAS,KAAK,GAAG;AACxB,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ,mBAAmB,OAAO,EAAE;AAAA,IACtC;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,qBAAqB,SAAgD;AACnF,QAAM,QAAQ,QAAQ;AACtB,QAAM,UAAU,oBAAI,IAAmB;AACvC,QAAM,YAAY,oBAAI,IAAgB;AACtC,QAAM,SAAS,MAAM;AACnB,eAAW,KAAK,CAAC,GAAG,SAAS,EAAG,GAAE;AAAA,EACpC;AAEA,SAAO;AAAA,IACL,SAAS,UAAU;AACjB,UAAI,QAAQ,IAAI,SAAS,EAAE,EAAG,OAAM,IAAI,uBAAuB,SAAS,EAAE;AAC1E,YAAM,WAAW,SAAS,cAAc,KAAK,CAAC;AAC9C,YAAM,MAAM,oBAAI,IAAY;AAC5B,iBAAW,MAAM,UAAU;AACzB,YAAI,IAAI,IAAI,EAAE,EAAG,OAAM,IAAI,qBAAqB,IAAI,SAAS,IAAI,SAAS,EAAE;AAC5E,YAAI,IAAI,EAAE;AACV,mBAAW,CAAC,SAAS,KAAK,KAAK,SAAS;AACtC,cAAI,MAAM,SAAS,SAAS,EAAE,EAAG,OAAM,IAAI,qBAAqB,IAAI,SAAS,SAAS,EAAE;AAAA,QAC1F;AAAA,MACF;AACA,YAAM,QAAe,EAAE,UAAU,SAAS;AAC1C,cAAQ,IAAI,SAAS,IAAI,KAAK;AAC9B,aAAO;AACP,aAAO,MAAM;AACX,YAAI,QAAQ,IAAI,SAAS,EAAE,MAAM,OAAO;AACtC,kBAAQ,OAAO,SAAS,EAAE;AAC1B,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,QAAQ,QAAQ,UAAU;AAC9B,YAAM,gBAAgB,QAAQ,MAAM,IAAI;AACxC,YAAM,OAAO,oBAAI,IAAoB;AACrC,YAAM,MAAwB,CAAC;AAC/B,iBAAW,CAAC,YAAY,EAAE,SAAS,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG;AACrD,YAAI;AACJ,YAAI;AACF,oBAAU,MAAM,SAAS,QAAQ,MAAM;AAAA,QACzC,SAAS,OAAO;AACd,kBAAQ,OAAO,OAAO,EAAE,YAAY,OAAO,UAAU,CAAC;AACtD;AAAA,QACF;AACA,mBAAW,OAAO,SAAS;AACzB,gBAAM,QAAQ,KAAK,IAAI,IAAI,EAAE;AAC7B,cAAI,UAAU,QAAW;AACvB,oBAAQ,OAAO,IAAI,qBAAqB,IAAI,IAAI,OAAO,UAAU,GAAG,EAAE,UAAU,IAAI,GAAG,CAAC;AACxF;AAAA,UACF;AACA,eAAK,IAAI,IAAI,IAAI,UAAU;AAC3B,gBAAM,SAAiB,IAAI,aAAa,aAAa,MAAM,EAAE,GAAG,KAAK,UAAU,WAAW;AAC1F,cAAI;AACJ,cAAI;AACF,0BAAc,mBAAmB,QAAQ,QAAQ,EAAE,UAAU,YAAY,CAAC,GAAG,cAAc,CAAC;AAAA,UAC9F,SAAS,OAAO;AACd,oBAAQ,OAAO,OAAO,EAAE,UAAU,OAAO,IAAI,OAAO,WAAW,CAAC;AAChE;AAAA,UACF;AACA,cAAI,YAAY,WAAW,kBAAkB;AAC3C,oBAAQ,OAAO,IAAI,MAAM,YAAY,MAAM,GAAG,EAAE,UAAU,OAAO,IAAI,OAAO,WAAW,CAAC;AACxF;AAAA,UACF;AACA,cAAI,YAAY,WAAW,SAAU;AACrC,cAAI,KAAK,EAAE,QAAQ,YAAY,CAAC;AAAA,QAClC;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IAEA,YAAY;AACV,aAAO,CAAC,GAAG,QAAQ,KAAK,CAAC;AAAA,IAC3B;AAAA,IAEA,UAAU,UAAU;AAClB,gBAAU,IAAI,QAAQ;AACtB,aAAO,MAAM;AACX,kBAAU,OAAO,QAAQ;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACF;AAGO,SAAS,qBACd,IACA,SACA,OAA+B,MACf;AAChB,QAAM,OAAO,QAAQ,IAAI,CAAC,MAAO,EAAE,aAAa,KAAK,IAAI,EAAE,GAAG,GAAG,UAAU,GAAG,CAAE;AAChF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,aAAa,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,EAAE;AAAA,IACvC,SAAS,MAAM;AAAA,EACjB;AACF;AAGO,IAAM,iBAAiB,OAAoB,EAAE,QAAQ,YAAY;;;ACvMjE,IAAM,sBAAmC,OAAO,OAAO;AAAA,EAC5D,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,mBAAmB;AACrB,CAAC;AAEM,SAAS,mBAAmB,OAAwD;AACzF,SAAO;AAAA,IACL,YAAY,MAAM,aAAa,cAAc,oBAAoB;AAAA,IACjE,gBAAgB,MAAM,aAAa,kBAAkB,oBAAoB;AAAA,IACzE,mBAAmB,MAAM,aAAa,qBAAqB,oBAAoB;AAAA,EACjF;AACF;;;ACEA,SAAS,OAAO,OAAkC;AAChD,MAAI,UAAU,UAAa,OAAO,UAAU,cAAc,OAAO,UAAU,SAAU,QAAO;AAC5F,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,OAAO,UAAU,UAAW,QAAO;AACtF,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AACvE,MAAI,OAAO,UAAU,SAAU,QAAO,MAAM,SAAS;AACrD,MAAI,iBAAiB,KAAM,QAAO,MAAM,YAAY;AACpD,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,CAAC,MAAM,OAAO,CAAC,KAAK,IAAI;AACnE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,MAA+B,CAAC;AACtC,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAgC,GAAG;AACrE,YAAM,IAAI,OAAO,CAAC;AAClB,UAAI,MAAM,OAAW,KAAI,CAAC,IAAI;AAAA,IAChC;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGO,SAAS,cACd,KACA,UACA,aAAa,IACgD;AAC7D,QAAM,SAAS,IAAI,IAAI,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AACvD,QAAM,SAA+B,CAAC;AACtC,QAAM,YAA4B,CAAC;AACnC,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC/C,UAAM,OAAO,aAAa,GAAG,UAAU,IAAI,IAAI,KAAK;AACpD,UAAM,OAAO,OAAO,IAAI,IAAI;AAC5B,QAAI,CAAC,MAAM;AACT,gBAAU,KAAK,EAAE,MAAM,QAAQ,WAAW,CAAC;AAC3C;AAAA,IACF;AACA,UAAM,cAAc,mBAAmB,IAAI;AAC3C,QAAI,YAAY,mBAAmB,YAAY;AAC7C,gBAAU,KAAK,EAAE,MAAM,QAAQ,YAAY,gBAAgB,YAAY,eAAe,CAAC;AACvF;AAAA,IACF;AACA,QAAI,CAAC,YAAY,YAAY;AAC3B,gBAAU,KAAK,EAAE,MAAM,QAAQ,WAAW,CAAC;AAC3C;AAAA,IACF;AACA,UAAM,OAAO,OAAO,KAAK;AACzB,QAAI,SAAS,OAAW;AACxB,WAAO,IAAI,IAAI;AAAA,EACjB;AACA,SAAO,EAAE,QAAQ,UAAU;AAC7B;AAEO,SAAS,qBAAqB,QAA0C;AAC7E,SAAO;AAAA,IACL,MAAM,OAAO,QAAQ;AACnB,UAAI,CAAC,OAAO,WAAW,CAAC,OAAO,YAAa,QAAO,EAAE,QAAQ,CAAC,GAAG,WAAW,CAAC,EAAE;AAC/E,YAAM,MAAM,MAAM,OAAO,QAAQ,WAAW,MAAM;AAClD,YAAM,eAAe,OAAO,YAAY;AACxC,aAAO,cAAc,KAAK,YAAY;AAAA,IACxC;AAAA,IACA,MAAM,KAAK,QAAQ;AACjB,YAAM,OAAO,OAAO;AACpB,UAAI,CAAC,QAAQ,CAAC,OAAO,YAAa,QAAO;AACzC,YAAM,OAAO,OAAO,YAAY,WAAW,KAAK,CAAC,MAAM,EAAE,SAAS,KAAK,QAAQ;AAC/E,UAAI,CAAC,KAAM,QAAO,EAAE,QAAQ,CAAC,GAAG,WAAW,CAAC,EAAE,MAAM,KAAK,UAAU,QAAQ,WAAW,CAAC,EAAE;AACzF,YAAM,MAAM,MAAM,KAAK,WAAW,MAAM;AACxC,aAAO,cAAc,KAAK,KAAK,QAAQ,KAAK,QAAQ;AAAA,IACtD;AAAA,IACA,MAAM,KAAK,QAA6B;AACtC,UAAI,CAAC,OAAO,SAAS,SAAU,QAAO,CAAC;AACvC,aAAO,OAAO,QAAQ,SAAS,MAAM;AAAA,IACvC;AAAA,EACF;AACF;;;ACnEO,SAAS,gBAAgB,UAAkB,MAAiC;AACjF,SAAO,WAAW,GAAG,QAAQ,IAAI,IAAI,KAAK;AAC5C;;;ACEA,SAAS,SAAS,aAA6B;AAC7C,QAAM,QAAQ,YAAY,QAAQ,GAAG;AACrC,SAAO,QAAQ,IAAI,YAAY,MAAM,GAAG,KAAK,IAAI;AACnD;AAEO,SAAS,kBAAkB,QAA0B,CAAC,GAAgB;AAC3E,QAAM,cAAc,MAAM,eAAe;AACzC,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,YAAY,oBAAI,IAAY;AAClC,MAAI,aAAa;AACjB,QAAM,WAA0B,CAAC;AACjC,MAAI,aAAa;AACf,eAAW,SAAS,YAAY,QAAQ;AACtC,gBAAU,IAAI,MAAM,IAAI;AACxB,YAAM,IAAI,mBAAmB,KAAK;AAClC,UAAI,EAAE,mBAAmB,cAAc,CAAC,EAAE,WAAY,cAAa;AAAA,IACrE;AACA,UAAM,WAAW,OAAO,YAAY,WAAW,KAAK,CAAC,MAAM,EAAE,SAAS,KAAK,QAAQ,IAAI;AACvF,QAAI,UAAU;AACZ,iBAAW,SAAS,SAAS,QAAQ;AACnC,kBAAU,IAAI,gBAAgB,SAAS,MAAM,MAAM,IAAI,CAAC;AACxD,cAAM,IAAI,mBAAmB,KAAK;AAClC,YAAI,EAAE,mBAAmB,cAAc,CAAC,EAAE,WAAY,cAAa;AAAA,MACrE;AACA,iBAAW,KAAK,SAAS,gBAAgB,CAAC,EAAG,UAAS,KAAK,CAAC;AAAA,IAC9D;AACA,eAAW,KAAK,YAAY,gBAAgB,CAAC,GAAG;AAC9C,UAAI,CAAC,EAAE,YAAY,EAAE,aAAa,MAAM,SAAU,UAAS,KAAK,CAAC;AAAA,IACnE;AAAA,EACF;AACA,QAAM,WAAW,MAAM,YAAY,MAAM,YAAY;AACrD,QAAM,cAAc,MAAM,SAAS,eAAe,aAAa,eAAe;AAC9E,QAAM,aAAa,MAAM,eAAe,cAAc,CAAC,UAAU,SAAS,WAAW,GAAG,WAAW,IAAI,CAAC,QAAQ;AAChH,SAAO;AAAA,IACL,SAAS,MAAM,WAAW;AAAA,IAC1B;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB,IAAI,IAAI,MAAM,qBAAqB,CAAC,CAAC;AAAA,IACxD;AAAA,IACA,OAAO,MAAM,SAAS,MAAM,SAAS;AAAA,IACrC,WAAW,MAAM,aAAa;AAAA,IAC9B,cAAc,MAAM,gBAAgB,CAAC;AAAA,IACrC,UAAU,WAAW,CAAC,IAAK,MAAM,YAAY;AAAA,IAC7C;AAAA,IACA;AAAA,IACA,gBAAgB,MAAM,kBAAkB;AAAA,IACxC,MAAM;AAAA,MACJ,eAAe,MAAM,MAAM,iBAAiB;AAAA,MAC5C,OAAO,MAAM,MAAM,SAAS;AAAA,MAC5B,SAAS,MAAM,MAAM,WAAW;AAAA,IAClC;AAAA,IACA,MAAM,MAAM;AAAA,EACd;AACF;;;AC5DA,eAAsB,UAAU,QAAgB,QAAqB,SAA+C;AAClH,QAAM,EAAE,MAAM,IAAI;AAClB,MAAI,OAAO,gBAAgB,CAAC,OAAO,KAAK,eAAe;AACrD,QAAI,CAAC,MAAM,MAAM;AAEf,YAAM,YAAY,QAAQ,IAAI,MAAM,wBAAwB,OAAO,EAAE,oCAAoC,GAAG;AAAA,QAC1G,MAAM;AAAA,QACN,QAAQ,EAAE,UAAU,OAAO,GAAG;AAAA,MAChC,CAAC;AACD,aAAO,EAAE,QAAQ,UAAU,OAAO,IAAI,MAAM,qBAAqB,EAAE;AAAA,IACrE;AACA,UAAM,KAAK,iBAAiB,EAAE,UAAU,OAAO,IAAI,oBAAoB,QAAQ,sBAAsB,OAAO,GAAG,CAAC;AAChH,WAAO,EAAE,QAAQ,eAAe;AAAA,EAClC;AACA,QAAM,QAAQ,qBAAqB,QAAQ,UAAU,EAAE,SAAS,MAAM,aAAa,OAAO,aAAa,MAAM,KAAK,CAAC;AACnH,QAAM,SAAS,QAAQ,UAAU,IAAI,gBAAgB,EAAE;AACvD,MAAI;AACF,UAAM,UAAU,MAAM,OAAO,IAAI,QAAQ,EAAE,OAAO,OAAO,OAAO,CAAC;AACjE,WAAO,EAAE,QAAQ,OAAO,QAAQ;AAAA,EAClC,SAAS,OAAO;AACd,UAAM,YAAY,QAAQ,OAAO,EAAE,MAAM,WAAW,QAAQ,EAAE,UAAU,OAAO,IAAI,OAAO,MAAM,EAAE,CAAC;AACnG,UAAM,QAAQ,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ,OAAO;AACvE,UAAM,QAAQ,MAAM,GAAG,KAAK,oBAAoB,qEAAqE;AACrH,WAAO,EAAE,QAAQ,UAAU,MAAM;AAAA,EACnC;AACF;","names":[]}
package/dist/menu.cjs CHANGED
@@ -20,29 +20,113 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/menu.ts
21
21
  var menu_exports = {};
22
22
  __export(menu_exports, {
23
+ APPROVED_HEADINGS: () => APPROVED_HEADINGS,
23
24
  CATEGORY_GROUP: () => CATEGORY_GROUP,
24
25
  GROUP_ORDER: () => GROUP_ORDER,
25
26
  INLINE_SECTION_MAX: () => INLINE_SECTION_MAX,
26
27
  MENU_LAYOUT_SETTING: () => MENU_LAYOUT_SETTING,
27
28
  PLATFORM_MENU_LAYOUT: () => PLATFORM_MENU_LAYOUT,
29
+ UnapprovedHeadingError: () => UnapprovedHeadingError,
28
30
  actionToNode: () => actionToNode,
29
31
  bar: () => bar,
30
32
  buildMenuModel: () => buildMenuModel,
31
33
  context: () => context,
34
+ defaultHeadingPolicy: () => defaultHeadingPolicy,
32
35
  filterPalette: () => filterPalette,
33
36
  findNode: () => findNode,
37
+ isApprovedHeading: () => isApprovedHeading,
38
+ isContainerWord: () => isContainerWord,
34
39
  layouts: () => layouts,
35
40
  leafIds: () => leafIds,
41
+ menuHeadings: () => menuHeadings,
36
42
  overflow: () => overflow,
37
43
  palette: () => palette,
38
44
  resolveMenuLayout: () => resolveMenuLayout,
39
45
  sectionLabel: () => sectionLabel,
40
46
  selectionToolbar: () => selectionToolbar,
41
47
  sheet: () => sheet,
48
+ unapprovedHeadings: () => unapprovedHeadings,
42
49
  withExpandedChildren: () => withExpandedChildren
43
50
  });
44
51
  module.exports = __toCommonJS(menu_exports);
45
52
 
53
+ // src/menu/headings.ts
54
+ var APPROVED_HEADINGS = /* @__PURE__ */ new Set([
55
+ "Save",
56
+ // rich-document menuStructure, e5b8d01229 (2026-05-19)
57
+ "Copy as",
58
+ // rich-document menuStructure, e5b8d01229 (2026-05-19)
59
+ "Export",
60
+ // rich-document menuStructure, e5b8d01229 (2026-05-19)
61
+ "Edit",
62
+ // rich-document menuStructure, e5b8d01229 (2026-05-19)
63
+ "Creator tools",
64
+ // rich-document menuStructure, e5b8d01229 (2026-05-19)
65
+ "Admin",
66
+ // rich-document menuStructure, e5b8d01229 (2026-05-19)
67
+ "App",
68
+ // rich-document menuStructure, e5b8d01229 (2026-05-19)
69
+ "Compare",
70
+ // rich-document menuStructure, 2e3a8a9fdf (2026-06-11)
71
+ "History"
72
+ // context-menu-v3 tiered layout — the one grouping Arman approved, 2026-08-22
73
+ ]);
74
+ var CONTAINER_WORD = /^(more|more…|more\.\.\.|other|others|misc|miscellaneous|additional|extra|extras|actions|options|advanced|stuff|everything else)$/i;
75
+ var TARGET_SHAPE = /^(Cell|Row|Column|Table|Range|Selection|Record) · .+$/;
76
+ function isContainerWord(label) {
77
+ return CONTAINER_WORD.test(label.trim());
78
+ }
79
+ function isApprovedHeading(label, kind = "group", approved = APPROVED_HEADINGS) {
80
+ const l = label.trim();
81
+ if (!l || isContainerWord(l)) return false;
82
+ if (approved.has(l)) return true;
83
+ return kind === "target" || TARGET_SHAPE.test(l);
84
+ }
85
+ function defaultHeadingPolicy() {
86
+ const env = globalThis.process?.env?.NODE_ENV;
87
+ return env === "production" ? "inline" : "throw";
88
+ }
89
+ var UnapprovedHeadingError = class extends Error {
90
+ heading;
91
+ constructor(heading, where) {
92
+ super(
93
+ `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.`
94
+ );
95
+ this.name = "UnapprovedHeadingError";
96
+ this.heading = heading;
97
+ }
98
+ };
99
+ function menuHeadings(model) {
100
+ const out = [];
101
+ const walk = (nodes) => {
102
+ for (const n of nodes) {
103
+ if (n.kind === "submenu") {
104
+ out.push(n.label);
105
+ walk(n.children);
106
+ }
107
+ }
108
+ };
109
+ for (const s of model.sections) {
110
+ if (s.label) out.push(s.label);
111
+ walk(s.nodes);
112
+ }
113
+ return [...new Set(out)];
114
+ }
115
+ function unapprovedHeadings(model, approved = APPROVED_HEADINGS) {
116
+ const out = /* @__PURE__ */ new Set();
117
+ for (const s of model.sections) {
118
+ if (s.label && !isApprovedHeading(s.label, s.primary || s.target ? "target" : "group", approved)) out.add(s.label);
119
+ const walk = (nodes) => {
120
+ for (const n of nodes) if (n.kind === "submenu") {
121
+ if (isContainerWord(n.label)) out.add(n.label);
122
+ walk(n.children);
123
+ }
124
+ };
125
+ walk(s.nodes);
126
+ }
127
+ return [...out];
128
+ }
129
+
46
130
  // src/menu/model.ts
47
131
  var CATEGORY_GROUP = {
48
132
  clipboard: "clipboard",
@@ -155,6 +239,19 @@ function actionToNode(resolved, target, options = {}) {
155
239
  };
156
240
  }
157
241
  function buildMenuModel(target, resolved, options = {}) {
242
+ const policy = options.headingPolicy ?? defaultHeadingPolicy();
243
+ resolved = resolved.map((r) => {
244
+ const sec = r.action.section;
245
+ if (r.action.expand && isContainerWord(typeof r.action.label === "string" ? r.action.label : "") && policy === "throw") {
246
+ throw new UnapprovedHeadingError(r.action.label, `submenu of ${r.action.id}`);
247
+ }
248
+ if (!sec) return r;
249
+ const kind = sec.kind ?? (sec.primary ? "target" : "group");
250
+ if (isApprovedHeading(sec.label, kind)) return r;
251
+ if (policy === "throw") throw new UnapprovedHeadingError(sec.label, `section of ${r.action.id}`);
252
+ const { section: _dropped, ...rest } = r.action;
253
+ return { ...r, action: rest };
254
+ });
158
255
  const ordered = resolved.map((r, index) => ({ r, index })).sort((a, b) => {
159
256
  const ca = CATEGORY_ORDER.indexOf(a.r.action.category);
160
257
  const cb = CATEGORY_ORDER.indexOf(b.r.action.category);
@@ -188,7 +285,8 @@ function buildMenuModel(target, resolved, options = {}) {
188
285
  nodes: [],
189
286
  ...sec ? { label: translate(options.i18n, sec.labelKey ?? `section.${sec.id}`, sec.label) } : {},
190
287
  ...sec?.icon !== void 0 ? { icon: sec.icon } : {},
191
- ...sec?.primary ? { primary: true } : {}
288
+ ...sec?.primary ? { primary: true } : {},
289
+ ...sec && (sec.kind ?? (sec.primary ? "target" : "group")) === "target" ? { target: true } : {}
192
290
  };
193
291
  sections.set(sectionId, section);
194
292
  }