@exponential_app/engine 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +54 -0
  3. package/README.md +35 -2
  4. package/dist/capabilities.js +29 -0
  5. package/dist/capabilities.js.map +9 -0
  6. package/dist/chunks/index-3nf9f7pv.js +315 -0
  7. package/dist/chunks/index-3nf9f7pv.js.map +10 -0
  8. package/dist/chunks/index-3rgr16n2.js +443 -0
  9. package/dist/chunks/index-3rgr16n2.js.map +9 -0
  10. package/dist/chunks/index-79swfbsm.js +366 -0
  11. package/dist/chunks/index-79swfbsm.js.map +10 -0
  12. package/dist/chunks/index-knntt4cb.js +259 -0
  13. package/dist/chunks/index-knntt4cb.js.map +10 -0
  14. package/dist/chunks/index-m2845dn6.js +72 -0
  15. package/dist/chunks/index-m2845dn6.js.map +10 -0
  16. package/dist/chunks/index-pqpnz4ns.js +2213 -0
  17. package/dist/chunks/index-pqpnz4ns.js.map +9 -0
  18. package/dist/chunks/index-q4zws02e.js +2301 -0
  19. package/dist/chunks/index-q4zws02e.js.map +10 -0
  20. package/dist/chunks/index-s3xjr701.js +170 -0
  21. package/dist/chunks/index-s3xjr701.js.map +10 -0
  22. package/dist/chunks/index-sk8b0dh4.js +126 -0
  23. package/dist/chunks/index-sk8b0dh4.js.map +10 -0
  24. package/dist/chunks/index-tvsykgcv.js +3015 -0
  25. package/dist/chunks/index-tvsykgcv.js.map +19 -0
  26. package/dist/chunks/index-v0m7r4y3.js +6869 -0
  27. package/dist/chunks/index-v0m7r4y3.js.map +13 -0
  28. package/dist/consent.js +24 -0
  29. package/dist/consent.js.map +9 -0
  30. package/dist/index.js +654 -0
  31. package/dist/index.js.map +12 -0
  32. package/dist/lifecycle.js +11 -0
  33. package/dist/lifecycle.js.map +9 -0
  34. package/dist/outbox.js +27 -0
  35. package/dist/outbox.js.map +9 -0
  36. package/dist/pdf.js +15 -0
  37. package/dist/pdf.js.map +9 -0
  38. package/dist/providers.js +26 -0
  39. package/dist/providers.js.map +9 -0
  40. package/dist/runner.js +24 -0
  41. package/dist/runner.js.map +9 -0
  42. package/dist/tools.js +38 -0
  43. package/dist/tools.js.map +9 -0
  44. package/dist/types/domain-contract/src/app-records.d.ts +3148 -0
  45. package/dist/types/domain-contract/src/app-share.d.ts +222 -0
  46. package/dist/types/domain-contract/src/apps.d.ts +612 -0
  47. package/dist/types/eengine/fixtures/providers.json +55 -0
  48. package/dist/types/eengine/src/broker.d.ts +80 -0
  49. package/dist/types/eengine/src/capabilities.d.ts +88 -0
  50. package/dist/types/eengine/src/consent.d.ts +50 -0
  51. package/dist/types/eengine/src/index.d.ts +12 -0
  52. package/dist/types/eengine/src/instance.d.ts +131 -0
  53. package/dist/types/eengine/src/lifecycle.d.ts +64 -0
  54. package/dist/types/eengine/src/outbox.d.ts +3 -0
  55. package/dist/types/eengine/src/pdf.d.ts +36 -0
  56. package/dist/types/eengine/src/providers.d.ts +38 -0
  57. package/dist/types/eengine/src/runner.d.ts +82 -0
  58. package/dist/types/eengine/src/stores.d.ts +125 -0
  59. package/dist/types/eengine/src/synced.d.ts +59 -0
  60. package/dist/types/eengine/src/tools.d.ts +79 -0
  61. package/dist/types/eengine/src/web/index.d.ts +20 -0
  62. package/dist/types/eengine/src/web/mcp.d.ts +27 -0
  63. package/dist/types/eengine/src/web/platform.d.ts +91 -0
  64. package/dist/types/eengine/src/web/stores.d.ts +125 -0
  65. package/dist/types/emcp/fixtures/automations.json +521 -0
  66. package/dist/types/emcp/fixtures/hub.json +498 -0
  67. package/dist/types/emcp/src/access.d.ts +55 -0
  68. package/dist/types/emcp/src/audit.d.ts +22 -0
  69. package/dist/types/emcp/src/automations.d.ts +111 -0
  70. package/dist/types/emcp/src/exposes.d.ts +242 -0
  71. package/dist/types/emcp/src/hub.d.ts +63 -0
  72. package/dist/types/emcp/src/index.d.ts +7 -0
  73. package/dist/types/emcp/src/pure.d.ts +11 -0
  74. package/dist/types/emcp/src/upstream.d.ts +288 -0
  75. package/dist/types/emcp/src/vault.d.ts +34 -0
  76. package/dist/web/index.js +791 -0
  77. package/dist/web/index.js.map +13 -0
  78. package/fixtures/bundle.json +1165 -0
  79. package/fixtures/capabilities.json +434 -0
  80. package/fixtures/consent.json +146 -0
  81. package/fixtures/lifecycle.json +125 -0
  82. package/fixtures/outbox.json +22 -0
  83. package/fixtures/providers.json +55 -0
  84. package/fixtures/runner.json +2167 -0
  85. package/package.json +92 -4
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../packages/domain-contract/src/app-share.ts", "../../../packages/domain-contract/src/apps.ts", "../../../packages/domain-contract/src/app-records.ts", "../../../packages/eengine/src/outbox.ts"],
4
+ "sourcesContent": [
5
+ "import fixture from \"../fixtures/app-share.json\" with { type: \"json\" }\n\n// VAPP-241 (fixtures/app-share.json, spec docs/spec/apps.md §9.4): an app's\n// PUBLIC LINKS. TS reference; desktop domain::app_share, iOS ExpCore\n// AppShare.swift and Android domain/AppShare.kt replay the cases.\n\nexport const APP_SHARE = fixture\nexport const APP_SHARE_COPY = fixture.copy\nexport const APP_SHARE_CAPS = fixture.caps\n/** VAPP-247: the Public links row's expiry items, in menu order (`days` null = never). */\nexport const APP_SHARE_EXPIRY_CHOICES: readonly { days: number | null; label: string }[] = fixture.expiryChoices\n\nexport type ShareLinkState = `off` | `on` | `expired` | `orphaned`\nexport type ShareMode = `submit` | `view`\n\nexport interface ShareSchema {\n required?: string[]\n properties: Record<string, { format?: string; items?: { format?: string }; type?: string }>\n}\n\n/** A link's state at `now` (`stateCases`); `declared` = the published\n * package still declares the share. */\nexport function linkState(link: { revokedAt: string | null; expiresAt: string | null } | null, declared: boolean, now: string): ShareLinkState {\n if (!link || link.revokedAt) return `off`\n if (!declared) return `orphaned`\n if (link.expiresAt && Date.parse(link.expiresAt) <= Date.parse(now)) return `expired`\n return `on`\n}\n\n/** May one more submission land (`capCases`)? */\nexport function underShareCaps(ipCount: number, linkCount: number): boolean {\n return ipCount < fixture.caps.perIpPerHour && linkCount < fixture.caps.perLinkPerHour\n}\n\nconst isFile = (f: ShareSchema[`properties`][string]) => f.format === `attachment` || f.items?.format === `attachment`\n\n/** The fields a link exposes (`fieldCases`): the declared `fields` in their\n * order, else the Data tab's order (required, then by name); unknown names\n * and files dropped. */\nexport function shareFields(schema: ShareSchema, fields?: readonly string[]): string[] {\n const names = Object.keys(schema.properties)\n const required = (schema.required ?? []).filter((n, i, all) => names.includes(n) && all.indexOf(n) === i)\n const order = fields ?? [...required, ...names.filter((n) => !required.includes(n)).sort()]\n return order.filter((n) => schema.properties[n] && !isFile(schema.properties[n]!))\n}\n\n/** The By/author label (`bylineCases`): a shared record reads Guest. */\nexport function shareByline(shareName: string | null | undefined, byline: string): string {\n return shareName ? fixture.copy.guest : byline\n}\n\n/** A Settings row's subtitle (`rowCases`). */\nexport function shareRowSubtitle(mode: ShareMode, state: ShareLinkState, expiresAt: string | null): string {\n const parts = [fixture.copy[mode], fixture.copy[state]]\n if (state === `on` && expiresAt) parts.push(fixture.copy.expiresOn.replace(`{date}`, expiresAt))\n return parts.join(` · `)\n}\n\n/** A Settings row's Switch (`switchCases`): an orphaned link still reads on,\n * so its owner can revoke it. */\nexport function shareSwitchOn(state: ShareLinkState): boolean {\n return state === `on` || state === `orphaned`\n}\n",
6
+ "// VAPP-110: Exponential apps, the pure reference every client mirrors\n// (fixtures/apps.json is the spec; desktop `domain::apps`, iOS\n// `Apps.swift`, Android `domain/Apps.kt` replay its cases).\n\nimport fixture from \"../fixtures/apps.json\" with { type: \"json\" }\nimport contract from \"../contract.json\" with { type: \"json\" }\nimport skillsFixture from \"../fixtures/skills.json\" with { type: \"json\" }\n\nexport const APPS_COPY = fixture.copy\nexport const APPS_PANES = fixture.panes\nexport const APPS_PREVIEW = fixture.preview\nexport const APPS_TEST_IDS = fixture.testIds\nexport const APPS_EDITOR_TABS = fixture.editorTabs\n/** VAPP-242: THE RUN VIEW's `…` menu + THE NEW MENU, in order. */\nexport const APPS_RUN = fixture.run\n\n/** VAPP-177: the editor's tabs (web ≥ md and desktop: Preview | Versions |\n * Settings; phones: Chat | Preview + the `…` menu's sheets). */\nexport type AppEditorTab = `chat` | `preview` | `data` | `versions` | `settings`\n/** The phone menu items that open a tab body as a sheet. */\nexport type AppEditorSheet = `versions` | `settings`\n\n/** A requested tab (web `?tab=`, the desktop settings nav) → the tab on\n * screen and, on phones, the sheet over it (`editorTabCases`). */\nexport function editorTab(requested: string | null | undefined, phone: boolean): { tab: AppEditorTab; sheet: AppEditorSheet | null } {\n if (phone) {\n if (requested === `versions` || requested === `settings`) return { tab: APPS_EDITOR_TABS.phoneDefault as AppEditorTab, sheet: requested }\n const tab = APPS_EDITOR_TABS.phone.includes(requested ?? ``) ? requested! : APPS_EDITOR_TABS.phoneDefault\n return { tab: tab as AppEditorTab, sheet: null }\n }\n const tab = APPS_EDITOR_TABS.wide.includes(requested ?? ``) ? requested! : APPS_EDITOR_TABS.default\n return { tab: tab as AppEditorTab, sheet: null }\n}\n\n/** VAPP-176: a DRAFT = an app with no version yet (New app's \"Untitled app\"\n * until the editor session publishes). */\nexport function isDraftApp(app: { headVersion: number }): boolean {\n return app.headVersion === 0\n}\n\nexport const APP_SLUG_MAX = contract.apps.slugMax\nexport const APP_NAME_MAX = contract.apps.nameMax\nexport const APP_DESCRIPTION_MAX = contract.apps.descriptionMax\nexport const APP_NOTE_MAX = contract.apps.noteMax\nexport const APP_PACKAGE_MAX_BYTES = contract.apps.packageMaxBytes\nexport const APP_TEMPLATE_MAX_COMPONENTS = contract.apps.templateMaxComponents\nexport const APP_SLUG_PATTERN = new RegExp(contract.apps.slugPattern)\n// VAPP-122 records.\nexport const APP_RECORD_KIND_PATTERN = new RegExp(contract.apps.kindPattern)\nexport const APP_RECORD_MAX_BYTES = contract.apps.recordMaxBytes\nexport const APP_RECORD_KINDS_MAX = contract.apps.recordKindsMax\n\n/** `v{n}`, or the \"no version\" copy while an app has no head. */\nexport function versionLabel(headVersion: number): string {\n return headVersion > 0 ? APPS_COPY.version.replace(`{n}`, String(headVersion)) : APPS_COPY.noVersion\n}\n\n/** eApps V1 (#VAPP-210, fixture `publishCases`): an app's two pointers. */\nexport interface AppVersions {\n headVersion: number\n publishedVersion: number\n}\n\n/** The version the editor's preview shows: an explicitly viewed one, else\n * nothing (no version), else the HEAD (the draft) while the viewer's own\n * editor session is live or nothing is published, else the PUBLISHED one. */\nexport function shownVersion(app: AppVersions, editing: boolean, viewing: number | null): number {\n if (viewing !== null) return viewing\n if (app.headVersion === 0) return 0\n return editing || app.publishedVersion === 0 ? app.headVersion : app.publishedVersion\n}\n\n/** Publish is offered while the head is not the published version. */\nexport function canPublish(app: AppVersions): boolean {\n return app.headVersion > 0 && app.headVersion !== app.publishedVersion\n}\n\n/** The list's and the header's version pill: the PUBLISHED version. */\nexport function listPill(app: AppVersions): string {\n return versionLabel(app.publishedVersion)\n}\n\n/** The version a grant is made on and checked against (`apps.update\n * {headVersion}` carries it): the published one, else the head. */\nexport function grantVersion(app: AppVersions): number {\n return app.publishedVersion > 0 ? app.publishedVersion : app.headVersion\n}\n\n/** A Versions row's trailing: the published badge, the head badge + Publish,\n * or Preview + Publish. */\nexport function versionRow(app: AppVersions, number: number): `published` | `head` | `version` {\n if (number === app.publishedVersion) return `published`\n return number === app.headVersion ? `head` : `version`\n}\n\n/** eApps R8 (#VAPP-233, fixture `migrationCases`): a version row's sweep columns. */\nexport interface VersionMigration {\n migrationState: string | null\n migrationTotal: number\n migrationDone: number\n migrationError: string | null\n}\n\n/** A Versions row's sweep line (after its subtitle), or null. */\nexport function versionMigration(row: VersionMigration): string | null {\n if (row.migrationState === `running`) return row.migrationTotal > 0 ? APPS_COPY.migratingOf.replace(`{done}`, String(row.migrationDone)).replace(`{total}`, String(row.migrationTotal)) : APPS_COPY.migrating\n if (row.migrationState === `failed`) return APPS_COPY.migrationFailed.replace(`{error}`, row.migrationError ?? ``)\n return null\n}\n\n/** #VAPP-256 (`rollbackCases`): whether publishing `target` rolls back past a\n * version whose sweep ran, so the Publish asks first. */\nexport function rollbackWarns(published: number, target: number, rows: readonly { number: number; migrationState?: string | null }[]): boolean {\n return target < published && rows.some((row) => row.number > target && row.migrationState != null)\n}\n\nexport const rollbackTitle = (n: number): string => APPS_COPY.rollbackTitle.replace(`{n}`, String(n))\nexport const rollbackBody = (n: number): string => APPS_COPY.rollbackBody.replace(`{n}`, String(n))\n\n/** A name → its slug: lowercase ASCII letters, digits and single dashes,\n * diacritics stripped, cut at the cap, `app` when nothing is left; a taken\n * slug gets `-2`, `-3`, … */\nexport function slugify(name: string, taken: readonly string[] = []): string {\n const base =\n name\n .normalize(`NFKD`)\n .replace(/[̀-ͯ]/g, ``)\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, `-`)\n .replace(/^-+|-+$/g, ``)\n .slice(0, APP_SLUG_MAX)\n .replace(/-+$/g, ``) || `app`\n if (!taken.includes(base)) return base\n for (let n = 2; ; n++) {\n const suffix = `-${n}`\n const candidate = `${base.slice(0, APP_SLUG_MAX - suffix.length).replace(/-+$/g, ``)}${suffix}`\n if (!taken.includes(candidate)) return candidate\n }\n}\n\nexport interface AppListLike {\n slug: string\n name: string\n}\n\n/** The list order: by name (case-insensitive). No built-in apps. */\nexport function sortApps<T extends AppListLike>(apps: readonly T[]): T[] {\n return [...apps].sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: `base` }))\n}\n\n/** VAPP-213: the built-in skills every app editor session runs with\n * (fixtures/skills.json `builtin.editorDelivery`), apps first. */\nexport const EDITOR_SKILLS: readonly string[] = skillsFixture.builtin.editorDelivery\n\n/** The editor WORKFLOW: what an app is and the MCP calls that read, save\n * and publish it. ONE source: the editor brief below and the built-in\n * `exponential-apps` skill's SKILL.md (packages/exponential-ui/scripts/\n * generate-skills.ts) both print it. */\nexport function editorWorkflow(slug: string): string {\n return `An app is a declarative genUI package (manifest + A2UI templates + bindings + a function allowlist; data, never code). Read the current package with exponential_apps {op: \"get\", slug: \"${slug}\"}; learn the components, bindings, host functions and records with exponential_apps {op: \"catalog\"}; save every change as a new draft version with exponential_apps {op: \"upsert\", slug: \"${slug}\", package, note} (members keep running the published version); publish with exponential_apps {op: \"publish\", slug: \"${slug}\"} only when the person asks (they also have a Publish button). An app keeps its own data as RECORDS (declare the kinds in the manifest, bind records:<kind>, write with records.upsert); seed or inspect them with exponential_apps {op: \"records_upsert\" | \"records_list\", slug: \"${slug}\", kind, data}. An app may also EXPOSE MCP tools (exposes: declarative bindings over its records, exponential.<noun>.<op> operations or a team MCP server in upstreams) that a member's MCP client runs as that member; the catalog documents the format. A person may attach a spreadsheet (CSV, TSV or xlsx) to fill the app: follow the import recipe (the exponential-apps skill's references/import.md, docs/spec/apps.md §10.2): read it, map the columns onto a kind, show a dry run and ask, then records_upsert row by row in batches.`\n}\n\n/** The brief an app's EDITOR SESSION gets as the preface of its first\n * message (every new or continued session): what the app is, the MCP\n * calls that read and publish it, and the built-in skills it runs with. */\nexport function editorBrief(app: { name: string; slug: string; headVersion: number; description?: string | null }): string {\n const lines = [\n `You are the editor session of the Exponential app \"${app.name}\" (slug \\`${app.slug}\\`, ${versionLabel(app.headVersion)}).${app.description?.trim() ? ` It is for: ${app.description.trim()}` : ``}`,\n ...(isDraftApp(app)\n ? [`It is a new DRAFT with a placeholder name: on your FIRST exponential_apps {op: \"upsert\"} also pass name, icon, color and description that fit what the person asks for. The slug follows the new name; use the slug the upsert returns from then on.`]\n : []),\n `${editorWorkflow(app.slug)} The person sees the live preview beside this chat, so keep replies to one or two lines and save drafts early.`,\n `This session runs with the built-in skills ${EDITOR_SKILLS.join(`, `)}: read ${EDITOR_SKILLS[0]} (its references and the three worked example apps) before your first upsert.`,\n ]\n return lines.join(`\\n`)\n}\n\n/** VAPP-157: the app hosts' media rule (fixture `mediaPolicy`), a plain\n * string rule so the four hosts agree byte for byte: a `data:` image, audio\n * or video (never svg), or the instance's own `/api/attachments/<uuid>`\n * (relative or on `origin` exactly) with nothing after the id. */\nexport const APP_MEDIA_POLICY = fixture.mediaPolicy\nconst ATTACHMENT_PATH = new RegExp(`^${APP_MEDIA_POLICY.attachmentPath}[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`, `i`)\n\nexport function appMediaAllowed(src: string, origin: string): boolean {\n if (!src || src.trim() !== src) return false\n const lower = src.toLowerCase()\n if (lower.startsWith(`data:`)) {\n const comma = lower.indexOf(`,`)\n if (comma < 0) return false\n const mime = lower.slice(5, comma).split(`;`)[0]!\n return APP_MEDIA_POLICY.dataMimePrefixes.some((p) => mime.startsWith(p) && mime.length > p.length) && !APP_MEDIA_POLICY.deniedDataMimes.includes(mime)\n }\n let path: string\n if (src.startsWith(`/`) && !src.startsWith(`//`)) path = src\n else {\n const base = origin.replace(/\\/+$/, ``).toLowerCase()\n if (!base || !lower.startsWith(`${base}/`)) return false\n path = src.slice(base.length)\n }\n return ATTACHMENT_PATH.test(path)\n}\n\n/** VAPP-179: the preview's templates, sorted by id (a JSON layer, Postgres\n * JSONB included, does not keep the declared order). */\nexport function previewTemplates(pkg: { templates?: Record<string, unknown> } | null | undefined): string[] {\n return Object.keys(pkg?.templates ?? {}).sort()\n}\n\n/** The template the preview shows: the picked one while the package has it,\n * else the first (`templateCases`). */\nexport function previewTemplate(pkg: { templates?: Record<string, unknown> } | null | undefined, picked: string | null): string | null {\n const list = previewTemplates(pkg)\n return picked !== null && list.includes(picked) ? picked : (list[0] ?? null)\n}\n\n/** #VAPP-251 (`surfaceStateCases`): what an app's pane shows: nothing to run,\n * the surface, or once the host had its chance and holds no surface (it\n * refused the package), `copy.needsUpdate`. */\nexport type AppSurfaceState = `empty` | `surface` | `needsUpdate`\nexport function surfaceState(hasPackage: boolean, applied: boolean, hasSurface: boolean): AppSurfaceState {\n if (!hasPackage) return `empty`\n return applied && !hasSurface ? `needsUpdate` : `surface`\n}\n\n/** VAPP-198: one Run as option (`value` \"\" = me). */\nexport interface RunAsOption {\n value: string\n label: string\n}\n\n/** VAPP-198: the preview toolbar's Run as options (`runAsCases`): null\n * unless the viewer is a team owner with another member; else Me, then\n * every other member by name (case-insensitive, then id; no name = id). */\nexport function runAsOptions(members: readonly { userId: string; name?: string | null; role: string }[], me: string | null, role: string | null): RunAsOption[] | null {\n if (role !== `owner`) return null\n const others = members\n .filter((m) => m.userId !== me)\n .map((m) => ({ value: m.userId, label: m.name?.trim() ? m.name : m.userId }))\n .sort((a, b) => {\n const x = a.label.toLowerCase()\n const y = b.label.toLowerCase()\n return x < y ? -1 : x > y ? 1 : a.value < b.value ? -1 : a.value > b.value ? 1 : 0\n })\n if (others.length === 0) return null\n return [{ value: ``, label: fixture.copy.runAs.me }, ...others]\n}\n\n/** #FEED-204 (`editorDeviceCases`, the fixture's EDITOR DEVICE): the device\n * an app editor's composer shows and sends on. `candidates` = the member's\n * online devices (with a root: the targets a move accepts), default flagged. */\nexport interface EditorDeviceChoice {\n /** The device; null = none online and no root to fall back on. */\n deviceId: string | null\n /** A live root: the row names its device and cannot change. */\n locked: boolean\n /** An ended root's next send goes to another device. */\n move: boolean\n}\n\nexport function editorDevice(\n root: { status: string; deviceId: string | null } | null,\n candidates: readonly { deviceId: string; isDefault?: boolean }[],\n remembered: string | null\n): EditorDeviceChoice {\n if (root && root.status !== `ended`) return { deviceId: root.deviceId, locked: true, move: false }\n const known = (id: string | null | undefined): id is string => !!id && candidates.some((row) => row.deviceId === id)\n const deviceId = known(remembered)\n ? remembered\n : known(root?.deviceId)\n ? root.deviceId\n : (candidates.find((row) => row.isDefault === true)?.deviceId ?? candidates[0]?.deviceId ?? root?.deviceId ?? null)\n return { deviceId, locked: false, move: root !== null && deviceId !== root.deviceId }\n}\n\n/** #VAPP-220 (`automationRowCases`): one row of the Settings tab's\n * Automations group, from tRPC `appAccess.appTools` `automations`. */\nexport interface AutomationRowInput {\n name: string\n on: { schedule: { cron: string; tz?: string } } | { record: { kind: string; op: `create` | `update` | `delete` } } | { event: string } | { webhook: { name: string } }\n paused: string | null\n lastStatus: string | null\n}\n\nexport type AutomationStatus = `paused` | `failed` | `ok` | `never`\n\nexport function automationRow(row: AutomationRowInput): { trigger: string; status: AutomationStatus; label: string } {\n const copy = fixture.copy.automations\n const fill = (text: string, values: Record<string, string>) => text.replace(/\\{(\\w+)\\}/g, (_, key: string) => values[key] ?? ``)\n const on = row.on\n const trigger =\n `schedule` in on\n ? fill(on.schedule.tz ? copy.scheduleZone : copy.schedule, { cron: on.schedule.cron, tz: on.schedule.tz ?? `` })\n : `record` in on\n ? fill(copy[on.record.op], { kind: on.record.kind })\n : `webhook` in on\n ? fill(copy.webhook, { name: on.webhook.name })\n : fill(copy.event, { event: on.event })\n const status: AutomationStatus = row.paused ? `paused` : row.lastStatus === null ? `never` : row.lastStatus !== `ok` ? `failed` : `ok`\n return { trigger, status, label: copy[status] }\n}\n\n/** #VAPP-243 (`automationSponsorCases`): who an automation runs as, or why\n * it is paused (a pause wins); null = the second line is the trigger alone. */\nexport function automationSponsor(paused: string | null, sponsorName: string | null): string | null {\n const copy = fixture.copy.automations\n if (paused) return (copy.pausedReasons as Record<string, string>)[paused] ?? copy.paused\n return sponsorName ? copy.runsAs.replace(`{name}`, sponsorName) : null\n}\n\n/** #VAPP-255 (`automationDroppedCases`): the runs the rate cap dropped in the\n * last 24 h; null = nothing to say. */\nexport function automationDropped(n: number): string | null {\n const copy = fixture.copy.automations\n if (n <= 0) return null\n return n === 1 ? copy.droppedOne : copy.dropped.replace(`{n}`, String(n))\n}\n\n/** #VAPP-238 (`webhookRowCases`): one row of the Settings tab's Webhooks\n * group, from tRPC `appAccess.appTools` `hooks`; `isOwner` = `canToggle`. */\nexport interface WebhookRowInput {\n name: string\n hasSecret: boolean\n}\n\nexport type WebhookStatus = `ready` | `unsigned`\nexport type WebhookAction = `mint` | `rotate`\n\nexport function webhookRow(\n row: WebhookRowInput,\n isOwner: boolean,\n): { status: WebhookStatus; label: string; action: WebhookAction | null; actionLabel: string | null } {\n const copy = fixture.copy.webhooks\n const status: WebhookStatus = row.hasSecret ? `ready` : `unsigned`\n const action: WebhookAction | null = isOwner ? (row.hasSecret ? `rotate` : `mint`) : null\n return { status, label: copy[status], action, actionLabel: action ? copy[action] : null }\n}\n\n/** #VAPP-243 (`accountRowCases`): one row of the Settings tab's Accounts\n * group, from tRPC `appAccess.appTools` `upstreams` (the viewer's own\n * connection to each upstream the app declares). */\nexport interface AccountRowInput {\n name: string\n serverId: string | null\n state: string | null\n}\n\nexport type AccountStatus = `notSetUp` | `connected` | `disconnected` | `connecting` | `failed` | `reconnect` | `expired`\nexport type AccountAction = `connect` | `reconnect`\n\nexport function accountRow(row: AccountRowInput): { status: AccountStatus; label: string | null; action: AccountAction | null; actionLabel: string | null } {\n const copy = fixture.copy.accounts\n if (!row.serverId) return { status: `notSetUp`, label: copy.notSetUp, action: null, actionLabel: null }\n const status = (row.state ?? `disconnected`) as AccountStatus\n if (status === `connected` || status === `expired`) return { status, label: copy.connected, action: null, actionLabel: null }\n const action: AccountAction = status === `reconnect` ? `reconnect` : `connect`\n return { status, label: null, action, actionLabel: copy[action] }\n}\n\n/** #VAPP-254 (`upstreamPickerCases`): an Accounts or import row's server\n * picker, and the second line naming the real server. */\nexport interface UpstreamPickerRow {\n name: string\n serverId: string | null\n serverName: string | null\n}\n\nexport const UPSTREAM_PICKER_NONE = fixture.upstreamPickerCases.none\n\nexport function upstreamPicker(row: UpstreamPickerRow, servers: readonly { id: string; name: string }[]): { value: string; options: { value: string; label: string }[] } | null {\n if (servers.length === 0) return null\n const options = servers.map((server) => ({ value: server.id, label: server.name }))\n const clear = servers.some((server) => server.name === row.name) ? [] : [{ value: UPSTREAM_PICKER_NONE, label: fixture.copy.accounts.notSetUp }]\n return { value: row.serverId ?? UPSTREAM_PICKER_NONE, options: [...clear, ...options] }\n}\n\nexport function accountCaption(row: UpstreamPickerRow): string | null {\n return row.serverName && row.serverName !== row.name ? fixture.copy.accounts.usesServer.replace(`{server}`, row.serverName) : null\n}\n\n/** #VAPP-243 (`policyRowCases`): a kind's EFFECTIVE record policy as the\n * Record access row's caption, ops in record-policy.json order. */\nexport type PolicyOp = `read` | `create` | `update` | `delete`\nexport type PolicyLevel = `member` | `author` | `owners`\nexport const POLICY_OPS: readonly PolicyOp[] = [`read`, `create`, `update`, `delete`]\n\nexport function policyCaption(policy: Record<PolicyOp, PolicyLevel>): string {\n const copy = fixture.copy.policies\n return POLICY_OPS.map((op) => `${copy.ops[op]}: ${copy.levels[policy[op]]}`).join(` · `)\n}\n\n/** The declared kinds in code-point order (the group's rows). */\nexport function policyKinds(records: unknown): string[] {\n if (!records || typeof records !== `object` || Array.isArray(records)) return []\n return Object.keys(records).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n}\n\n/** VAPP-221 (eApps R7): the app hosts' FileUpload uploads (fixture\n * `appFiles`): every picked file POSTs to `uploadPath` as one part\n * `partName`; the answer's `url` lands in the FileUpload's bound list. */\nexport const APP_FILES = fixture.appFiles\n\n/** The upload url of an app (`uploadPath` with its id). */\nexport function appUploadPath(appId: string): string {\n return APP_FILES.uploadPath.replace(`{appId}`, encodeURIComponent(appId))\n}\n\n/** The absolute `files` path of the FIRST FileUpload (component order)\n * named `name` (its `name`, else its id); null when it has no absolute\n * `{path}` binding (`targetCases`). */\nexport function appUploadTarget(components: readonly Record<string, unknown>[], name: string): string | null {\n const upload = components.find((c) => c.component === `FileUpload` && (typeof c.name === `string` ? c.name : c.id) === name)\n const files = upload?.files as { path?: unknown } | undefined\n return files && typeof files === `object` && !Array.isArray(files) && typeof files.path === `string` && files.path.startsWith(`/`) ? files.path : null\n}\n\n/** `list` with `url` on every object entry named `fileName`; null = nothing\n * to write (no list, no such entry) (`urlCases`). */\nexport function withUploadedUrl(list: unknown, fileName: string, url: string): unknown[] | null {\n if (!Array.isArray(list)) return null\n let hit = false\n const next = list.map((entry) => {\n if (!entry || typeof entry !== `object` || Array.isArray(entry) || (entry as { name?: unknown }).name !== fileName) return entry\n hit = true\n return { ...(entry as Record<string, unknown>), url }\n })\n return hit ? next : null\n}\n\n/** VAPP-218 (U1): the screens contract (`screens.$comment`). */\nexport const APPS_SCREENS = fixture.screens\n\n/** Which navigation the preview frame paints for a package's screens:\n * none without screens, the phone bottom bar on a phone (or the Phone\n * frame), else the sidebar (`screenShellCases`). */\nexport function screenShell(screenCount: number, phone: boolean): `sidebar` | `bar` | null {\n if (screenCount <= 0) return null\n return phone ? `bar` : `sidebar`\n}\n\n/** The web deep link to an app screen: `/t/<team>/apps/<app>/<screen>`,\n * then the params sorted by key (`screenPathCases`). */\nexport function screenPath(teamSlug: string, appSlug: string, screen: string, params: Record<string, string> = {}): string {\n const path = `/t/${encodeURIComponent(teamSlug)}/apps/${encodeURIComponent(appSlug)}/${encodeURIComponent(screen)}`\n const keys = Object.keys(params).sort()\n if (!keys.length) return path\n return `${path}?${keys.map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(params[k]!)}`).join(`&`)}`\n}\n\n/** A screen route's query → its params: every string key but the editor's\n * own (`screens.reserved`), sorted (`screenQueryCases`). */\nexport function screenParamsFromQuery(query: Record<string, unknown>): Record<string, string> {\n const out: Record<string, string> = {}\n for (const k of Object.keys(query).sort()) {\n const v = query[k]\n if (!(APPS_SCREENS.reserved as string[]).includes(k) && typeof v === `string`) out[k] = v\n }\n return out\n}\n",
7
+ "// VAPP-122: app RECORDS, the Exponential-only rules every app host mirrors\n// (fixtures/app-records.json is the spec; desktop `domain::app_records`,\n// iOS `AppRecords.swift`, Android `AppRecords.kt` replay its cases): the\n// ownership + on-behalf rules, the count label, the purge and the Data tab.\n// VAPP-189: the `records:<kind>?…` query and its value live in eUI\n// (`@exponential_app/eui/host/query`, one evaluator ×4,\n// packages/exponential-ui fixtures/records-query.json).\n\nimport { APP_SHARE_COPY } from \"./app-share\"\nimport fixture from \"../fixtures/app-records.json\" with { type: \"json\" }\nimport { APPS_COPY } from \"./apps\"\n\n/** The keys every record row carries; a kind's schema may not declare them. */\nexport const APP_RECORD_ROW_KEYS: readonly string[] = fixture.rowKeys\n\n/** One synced `app_records` row as the host reads it. */\nexport interface AppRecordRow {\n id: string\n kind: string\n data: Record<string, unknown>\n createdBy: string | null\n /** VAPP-187: whose record it is (null/missing on a legacy row = `createdBy`). */\n authorId?: string | null\n createdAt: string\n updatedAt: string\n}\n\n/** VAPP-187: a record's AUTHOR (`authorId ?? createdBy`): whose record it\n * is, what `mine`/`user=` and the per-member views read. */\nexport function recordAuthor(row: { authorId?: string | null; createdBy: string | null }): string | null {\n return row.authorId ?? row.createdBy ?? null\n}\n\n/** VAPP-187 OWNERSHIP (fixture `ownership`): the default per-kind policy and\n * the server's refusal. */\nexport const APP_RECORDS_OWNERSHIP = {\n policy: fixture.ownership.policy as Record<`update` | `delete`, `author` | `member`>,\n refusal: fixture.ownership.refusal,\n}\n\n/** May `viewer` (with team `role`; null = not a member) `op` the record?\n * Policy `author` = the author or a team owner (`canChangeCases`). */\nexport function canChangeRecord(\n row: { authorId?: string | null; createdBy: string | null },\n viewer: string | null,\n role: string | null,\n op: `update` | `delete` = `update`\n): boolean {\n if (viewer === null || role === null) return false\n if (role === `owner`) return true\n if (APP_RECORDS_OWNERSHIP.policy[op] === `member`) return true\n const author = recordAuthor(row)\n return author !== null && author === viewer\n}\n\n/** VAPP-188 ON-BEHALF (fixture `onBehalf`): who may name another member as\n * a write's author, and the server's refusals. */\nexport const APP_RECORDS_ON_BEHALF = {\n refusal: fixture.onBehalf.refusal,\n notMember: fixture.onBehalf.notMember,\n}\n\nexport type WriteAsVerdict = `ok` | `forbidden` | `notMember`\n\n/** May `viewer` (team `role`; null = not a member) write a record as\n * `author` (null = as themself)? Another member = owners only, and the\n * author must be one of `members` (`writeAsCases`). */\nexport function writeAsVerdict(viewer: string | null, role: string | null, author: string | null | undefined, members: readonly string[]): WriteAsVerdict {\n if (viewer === null || role === null) return `forbidden`\n if (author == null || author === viewer) return `ok`\n if (role !== `owner`) return `forbidden`\n return members.includes(author) ? `ok` : `notMember`\n}\n\n/** An imported record's author (`importCases`): absent, the importer or a\n * stranger to the team = the importer; another member = them when the\n * importer is an owner, else the importer too (VAPP-192: a member\n * importing a teammate's export keeps the rows as their own). */\nexport function importAuthor(importer: string, role: string | null, author: string | null | undefined, members: readonly string[]): string {\n if (author == null || author === importer || !members.includes(author)) return importer\n return role === `owner` ? author : importer\n}\n\n/** Settings → Apps → the app: \"1,240 records\", \"1,240 of 25,000 records\"\n * (max = the team's quota, null = unlimited), \"No records\". */\nexport function recordsCountLabel(count: number, max: number | null): string {\n if (count === 0) return APPS_COPY.recordsNone\n const n = count.toLocaleString(`en-US`)\n if (max === null) return count === 1 ? APPS_COPY.recordsOne : APPS_COPY.records.replace(`{n}`, n)\n return APPS_COPY.recordsOf.replace(`{n}`, n).replace(`{max}`, max.toLocaleString(`en-US`))\n}\n\n/** VAPP-159 PURGE: how long a deleted record (or a trashed app's record)\n * stays before the server hard-deletes it, and the rows per sweep. */\nexport const APP_RECORDS_PURGE = { retentionDays: fixture.purge.retentionDays, batch: fixture.purge.batch }\nconst RETENTION_MS = APP_RECORDS_PURGE.retentionDays * 24 * 60 * 60 * 1000\n\n/** A record is due for the purge once its own `deleted_at` or its app's\n * trash stamp (`app_deleted_at`) is older than the retention. */\nexport function appRecordPurgeDue(deletedAt: Date | null, appDeletedAt: Date | null, now: Date): boolean {\n const cutoff = now.getTime() - RETENTION_MS\n return (deletedAt !== null && deletedAt.getTime() < cutoff) || (appDeletedAt !== null && appDeletedAt.getTime() < cutoff)\n}\n\n// VAPP-178 THE DATA TAB (fixture `dataTab`): the editor's records browser\n// and form, pure so the four clients replay the same cases.\n\nexport const APP_DATA_TAB = fixture.dataTab\nexport const APP_DATA_COPY = fixture.dataTab.copy\n\n/** A kind's schema field (the SDK's JSON Schema subset, host/records.ts). */\nexport interface DataField {\n type: `string` | `number` | `integer` | `boolean` | `array`\n enum?: string[]\n items?: { type: `string` | `number` | `integer` | `boolean`; format?: string }\n format?: `date` | `date-time` | `time` | `attachment`\n}\n\n/** VAPP-221: an attachment ref (eUI `RECORD_ATTACHMENT_REF`). */\nconst ATTACHMENT_REF = /^\\/api\\/attachments\\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/\nconst isAttachmentRef = (value: unknown): boolean => typeof value === `string` && ATTACHMENT_REF.test(value)\n\n/** VAPP-221: a field holding files (`format: \"attachment\"` on a string or\n * a list's string items): no form field, the app's FileUpload writes it. */\nexport function isAttachmentField(field: DataField): boolean {\n return field.type === `array` ? field.items?.type === `string` && field.items.format === `attachment` : field.type === `string` && field.format === `attachment`\n}\n\nexport interface DataSchema {\n type?: `object`\n properties: Record<string, DataField>\n required?: string[]\n}\n\n/** The head manifest's kinds, sorted by name, each with its live count\n * (`kindCases`); a row of an undeclared kind is ignored. */\nexport function dataKinds(\n manifest: { records?: Record<string, { schema: DataSchema }> } | null | undefined,\n rows: readonly { kind: string }[]\n): { kind: string; count: number }[] {\n const kinds = Object.keys(manifest?.records ?? {}).sort()\n return kinds.map((kind) => ({ kind, count: rows.filter((row) => row.kind === kind).length }))\n}\n\n/** The columns (`columnCases`): the `required` names in their order, then\n * every other property sorted by name (never the declaration order: a\n * host's JSON layer may lose it). */\nexport function dataColumns(schema: DataSchema): string[] {\n const names = Object.keys(schema.properties)\n const required = (schema.required ?? []).filter((name, i, all) => names.includes(name) && all.indexOf(name) === i)\n return [...required, ...names.filter((name) => !required.includes(name)).sort()]\n}\n\n/** A kind's rows, `updatedAt` desc, ties by id (`rowsCases`). */\nexport function dataRows<T extends { id: string; kind: string; updatedAt: string }>(rows: readonly T[], kind: string): T[] {\n return rows\n .filter((row) => row.kind === kind)\n .sort((a, b) => (a.updatedAt === b.updatedAt ? (a.id < b.id ? -1 : a.id > b.id ? 1 : 0) : a.updatedAt < b.updatedAt ? 1 : -1))\n}\n\n/** One cell's text (`cellCases`). */\nexport function cellText(value: unknown): string {\n // VAPP-221: files read as a count (a ref, or a list of refs only).\n if (isAttachmentRef(value)) return APP_DATA_COPY.file\n if (Array.isArray(value) && value.length > 0 && value.every(isAttachmentRef)) return value.length === 1 ? APP_DATA_COPY.file : APP_DATA_COPY.files.replace(`{n}`, String(value.length))\n return plainText(value)\n}\n\n/** A value as written (a form input's text; a cell without files). */\nfunction plainText(value: unknown): string {\n if (value === null || value === undefined) return ``\n if (typeof value === `boolean`) return value ? APP_DATA_COPY.yes : APP_DATA_COPY.no\n if (typeof value === `number`) return String(value)\n if (typeof value === `string`) return value\n if (Array.isArray(value)) return value.map(plainText).join(`, `)\n return JSON.stringify(value)\n}\n\nexport type DataControl = `text` | `number` | `switch` | `select` | `list`\n\nexport interface DataFormField {\n name: string\n control: DataControl\n required: boolean\n placeholder?: string\n options?: string[]\n itemType?: `string` | `number` | `integer` | `boolean`\n}\n\n/** The form's fields, in column order (`formCases`). */\nexport function formFields(schema: DataSchema): DataFormField[] {\n const required = schema.required ?? []\n return dataColumns(schema).filter((name) => !isAttachmentField(schema.properties[name]!)).map((name) => {\n const field = schema.properties[name]!\n const base = { name, required: required.includes(name) }\n if (field.type === `boolean`) return { ...base, control: `switch` }\n if (field.type === `number` || field.type === `integer`) return { ...base, control: `number` }\n if (field.type === `array`) return { ...base, control: `list`, itemType: field.items?.type ?? `string` }\n if (field.enum) return { ...base, control: `select`, options: field.enum }\n const placeholder = field.format ? (APP_DATA_TAB.placeholders as Record<string, string>)[field.format] : undefined\n return placeholder ? { ...base, control: `text`, placeholder } : { ...base, control: `text` }\n })\n}\n\n/** A stored value → the control's initial input (`inputCases`): a switch\n * takes a boolean, every other control text. */\nexport function formInput(control: DataControl, value: unknown): string | boolean {\n if (control === `switch`) return value === true\n if (value === null || value === undefined) return ``\n if (control === `list` && Array.isArray(value)) return value.map((item) => plainText(item)).join(`, `)\n return typeof value === `string` ? value : String(value)\n}\n\nfunction parseNumber(text: string): number | string {\n const n = Number(text)\n return text.trim() !== `` && Number.isFinite(n) ? n : text\n}\n\nfunction parseItem(text: string, type: DataFormField[`itemType`]): unknown {\n if (type === `number` || type === `integer`) return parseNumber(text)\n if (type === `boolean`) return text === `true` ? true : text === `false` ? false : text\n return text\n}\n\n/** The inputs → the record's data (`parseCases`): an empty field = null,\n * numbers and list items parse (unparsable stays the raw text, so the\n * server names it); CREATE omits the nulls, EDIT keeps them (null clears). */\nexport function formData(schema: DataSchema, inputs: Record<string, string | boolean | undefined>, editing: boolean): Record<string, unknown> {\n const data: Record<string, unknown> = {}\n for (const field of formFields(schema)) {\n const input = inputs[field.name]\n let value: unknown\n if (field.control === `switch`) value = input === true\n else {\n const text = typeof input === `string` ? input : ``\n if (field.control === `list`) {\n const items = text\n .split(`,`)\n .map((item) => item.trim())\n .filter((item) => item.length > 0)\n value = items.length > 0 ? items.map((item) => parseItem(item, field.itemType)) : null\n } else if (text === ``) value = null\n else value = field.control === `number` ? parseNumber(text) : text\n }\n if (value !== null || editing) data[field.name] = value\n }\n return data\n}\n\n/** A column's fixed width in px (`columns.typeWidths`), or null = it shares\n * the rest of the row. */\nexport function columnWidth(field: DataField): number | null {\n const widths = APP_DATA_TAB.columns.typeWidths as Record<string, number>\n if (field.type === `string`) return field.format ? (widths[field.format] ?? null) : null\n return widths[field.type] ?? null\n}\n\n/** VAPP-188: the `by` cell (`bylineCases`): the author's name (else\n * `fallback`, apps.json `copy.byAgent`), `{writer} for {author}` when\n * someone else with a name wrote it. */\nexport function recordByline(\n row: { authorId?: string | null; createdBy: string | null },\n names: Readonly<Record<string, string>>,\n fallback: string\n): string {\n const authorId = recordAuthor(row)\n const author = (authorId !== null ? names[authorId] : undefined) || fallback\n const writer = row.createdBy !== null && row.createdBy !== authorId ? names[row.createdBy] : undefined\n return writer ? APP_DATA_COPY.byFor.replace(`{writer}`, writer).replace(`{author}`, author) : author\n}\n\n/** VAPP-188: the owner's Author picker, members by name (case-insensitive,\n * ties by id: `authorOptionCases`). */\nexport function authorOptions<T extends { id: string; name: string }>(users: readonly T[]): T[] {\n return [...users].sort((a, b) => {\n const x = a.name.toLowerCase()\n const y = b.name.toLowerCase()\n return x < y ? -1 : x > y ? 1 : a.id < b.id ? -1 : a.id > b.id ? 1 : 0\n })\n}\n\n/** VAPP-188: the picker's default (the record's author on edit, the viewer\n * on create) and what Save sends (`send`: the pick when it differs from\n * the record's author / the viewer, else null = omit; `formAuthorCases`). */\nexport function formAuthor(\n viewer: string | null,\n record: { authorId?: string | null; createdBy: string | null } | null,\n picked: string | null\n): { default: string | null; send: string | null } {\n const base = record ? recordAuthor(record) : viewer\n return { default: base ?? viewer, send: picked !== null && picked !== base ? picked : null }\n}\n\n// VAPP-215 (R4) RECORD HISTORY + UNDO (fixture `history`): what one\n// `app_record_events` row means, its undo and its History row, pure so the\n// four clients replay the same cases.\n\nexport const APP_RECORD_HISTORY = fixture.history\nexport const APP_HISTORY_COPY = fixture.history.copy\n\nexport type RecordEventOp = `create` | `update` | `delete` | `restore`\nexport type RecordEventVia = `editor` | `app` | `mcp` | `action` | `import` | `migration` | `share:${string}`\n\n/** The part of an event the pure rules read. */\nexport interface RecordEventCore {\n op: RecordEventOp\n before: Record<string, unknown> | null\n patch: Record<string, unknown> | null\n}\n\n/** One event as `apps.records.history` returns it (newest first). */\nexport interface RecordEvent extends RecordEventCore {\n id: string\n actor: string | null\n createdAt: string\n /** VAPP-241: `share:<name>` = a guest's submission (app-share.json). */\n via?: string\n}\n\n/** The write that undoes an event: a delete, or a restore of `data`. */\nexport type InverseWrite = { op: `delete` } | { op: `restore`; data: Record<string, unknown> }\n\n/** The record an event left (`stateCases`): null = gone. */\nexport function eventState(event: RecordEventCore): Record<string, unknown> | null {\n if (event.op === `delete`) return null\n if (event.op === `update`) return { ...(event.before ?? {}), ...(event.patch ?? {}) }\n return event.patch ?? {}\n}\n\n/** Undo = a new write of the event's `before` (`inverseCases`). */\nexport function inverseWrite(event: RecordEventCore): InverseWrite {\n return event.before === null ? { op: `delete` } : { op: `restore`, data: event.before }\n}\n\n/** Deep JSON equality (objects by key, numbers by value). */\nexport function jsonEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true\n if (a === null || b === null || typeof a !== `object` || typeof b !== `object`) return false\n if (Array.isArray(a) !== Array.isArray(b)) return false\n if (Array.isArray(a)) {\n const other = b as unknown[]\n return a.length === other.length && a.every((item, i) => jsonEqual(item, other[i]))\n }\n const ka = Object.keys(a as object)\n const kb = Object.keys(b as object)\n return ka.length === kb.length && ka.every((k) => k in (b as object) && jsonEqual((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k]))\n}\n\n/** A History row's title (`labelCases`). */\nexport function eventLabel(event: RecordEventCore): string {\n const c = APP_HISTORY_COPY\n if (event.op === `create`) return c.created\n if (event.op === `delete`) return c.deleted\n if (event.op === `restore`) return c.restored\n const before = event.before ?? {}\n const patch = event.patch ?? {}\n const changed = Object.keys(patch)\n .filter((k) => !jsonEqual(before[k], patch[k]))\n .sort()\n if (changed.length === 0) return c.saved\n const max = APP_RECORD_HISTORY.maxFields\n const shown = changed.slice(0, max).join(`, `)\n const rest = changed.length - max\n return c.changed.replace(`{fields}`, rest > 0 ? `${shown} ${c.more.replace(`{n}`, String(rest))}` : shown)\n}\n\nexport interface HistoryRow {\n id: string\n label: string\n who: string\n at: string\n restorable: boolean\n}\n\n/** The History list (`rowsCases`): the events in the server's order (newest\n * first); Restore on every row but the newest, never on a delete. */\nexport function historyRows(events: readonly RecordEvent[], names: Readonly<Record<string, string>>, fallback: string): HistoryRow[] {\n return events.map((event, i) => ({\n id: event.id,\n label: eventLabel(event),\n who: event.via?.startsWith(`share:`) ? APP_SHARE_COPY.guest : ((event.actor !== null ? names[event.actor] : undefined) ?? fallback),\n at: event.createdAt,\n restorable: i > 0 && event.op !== `delete`,\n }))\n}\n\n/** The Undo toast's title for a UI write. */\nexport function undoTitle(write: `create` | `update` | `delete` | `batch`): string {\n const c = APP_HISTORY_COPY\n return write === `create` ? c.undoCreate : write === `update` ? c.undoUpdate : write === `delete` ? c.undoDelete : c.undoBatch\n}\n\n// VAPP-235 (O1) THE OFFLINE OUTBOX (fixture `outbox`): the desktop, iOS and\n// Android app hosts queue every record write, lay it over the synced rows\n// and replay it in order with an idempotency key; pure so the three hosts\n// replay the same cases (the web stays online-only).\n\nexport const APP_RECORDS_OUTBOX = fixture.outbox\nexport const APP_OUTBOX_COPY = fixture.outbox.copy\n\nexport type OutboxCall = `upsert` | `delete` | `batch`\nexport type OutboxWrite = { op: `create` | `update`; id: string; kind: string; data: Record<string, unknown> } | { op: `delete`; id: string }\nexport interface OutboxEntry {\n writeId: string\n appId: string\n call: OutboxCall\n writes: OutboxWrite[]\n /** ISO time the write was made. */\n at: string\n state: `pending` | `acked`\n ackedAt?: string | null\n}\n/** A replay's answer: ok, no network, or the server's tRPC code. */\nexport type OutboxResult = { ok: true } | { transport: true } | { code: string }\nexport type OutboxOutcome = `done` | `retry` | `dropped` | `refused`\n\ntype Obj = Record<string, unknown>\nconst isObj = (v: unknown): v is Obj => typeof v === `object` && v !== null && !Array.isArray(v)\n\n/** A host call's writes (`writesCases`): a create takes the next client id. */\nexport function outboxWrites(call: OutboxCall, args: Obj, newIds: readonly string[]): OutboxWrite[] {\n let next = 0\n const upsert = (w: Obj): OutboxWrite => {\n const data = isObj(w.data) ? w.data : {}\n const kind = typeof w.kind === `string` ? w.kind : ``\n return typeof w.id === `string` ? { op: `update`, id: w.id, kind, data } : { op: `create`, id: newIds[next++] ?? ``, kind, data }\n }\n if (call === `upsert`) return [upsert(args)]\n if (call === `delete`) return [{ op: `delete`, id: typeof args.id === `string` ? args.id : `` }]\n const writes = Array.isArray(args.writes) ? args.writes.filter(isObj) : []\n return writes.map((w) => (w.op === `delete` ? { op: `delete`, id: typeof w.id === `string` ? w.id : `` } : upsert(w)))\n}\n\n/** The replay request of an entry (`requestCases`); the host adds the app. */\nexport function outboxRequest(entry: OutboxEntry): { call: OutboxCall; input: Obj } {\n const { writeId } = entry\n if (entry.call === `batch`) {\n const writes = entry.writes.map((w) =>\n w.op === `delete` ? { op: `delete`, id: w.id } : w.op === `create` ? { op: `upsert`, kind: w.kind, data: w.data, newId: w.id } : { op: `upsert`, kind: w.kind, data: w.data, id: w.id }\n )\n return { call: `batch`, input: { writes, writeId } }\n }\n const w = entry.writes[0]!\n if (w.op === `delete`) return { call: `delete`, input: { recordId: w.id, writeId } }\n return { call: `upsert`, input: { kind: w.kind, data: w.data, ...(w.op === `create` ? { newId: w.id } : { recordId: w.id }), writeId } }\n}\n\n/** The visible rows: the synced rows with every entry laid over them, oldest\n * first (`rowsCases`). */\nexport function outboxRows(rows: readonly AppRecordRow[], entries: readonly OutboxEntry[], me: string | null): AppRecordRow[] {\n const out = rows.map((r) => ({ ...r }))\n for (const entry of entries) {\n for (const w of entry.writes) {\n const i = out.findIndex((r) => r.id === w.id)\n if (w.op === `delete`) {\n if (i >= 0) out.splice(i, 1)\n } else if (i >= 0) {\n out[i] = { ...out[i]!, data: { ...out[i]!.data, ...w.data } }\n } else if (w.op === `create`) {\n out.push({ id: w.id, kind: w.kind, data: { ...w.data }, createdBy: me, authorId: me, createdAt: entry.at, updatedAt: entry.at })\n }\n }\n }\n return out\n}\n\n/** What a replay's answer does to its entry (`outcomeCases`). */\nexport function outboxOutcome(entry: OutboxEntry, result: OutboxResult): OutboxOutcome {\n if (`ok` in result) return `done`\n if (`transport` in result) return `retry`\n if (APP_RECORDS_OUTBOX.retryCodes.includes(result.code)) return `retry`\n if (result.code === `NOT_FOUND` && entry.writes.every((w) => w.op !== `create`) && entry.call !== `batch`) return `dropped`\n return `refused`\n}\n\n/** The failed-action toast's verb for a refused entry. */\nexport function outboxVerb(entry: OutboxEntry): `save` | `delete` {\n return entry.writes.every((w) => w.op === `delete`) ? `delete` : `save`\n}\n\n/** One flush (`flushCases`): pending entries oldest first, one at a time;\n * a retry stops it; `results` = each write id's answer. */\nexport function outboxFlush(\n entries: readonly OutboxEntry[],\n results: Record<string, OutboxResult>,\n now: string\n): { sent: string[]; refused: { writeId: string; verb: `save` | `delete` }[]; after: OutboxEntry[] } {\n const sent: string[] = []\n const refused: { writeId: string; verb: `save` | `delete` }[] = []\n const after: OutboxEntry[] = []\n let stopped = false\n for (const entry of entries) {\n if (stopped || entry.state === `acked`) {\n after.push(entry)\n continue\n }\n sent.push(entry.writeId)\n const outcome = outboxOutcome(entry, results[entry.writeId] ?? { transport: true })\n if (outcome === `done`) after.push({ ...entry, state: `acked`, ackedAt: now })\n else if (outcome === `retry`) {\n stopped = true\n after.push(entry)\n } else if (outcome === `refused`) refused.push({ writeId: entry.writeId, verb: outboxVerb(entry) })\n }\n return { sent, refused, after }\n}\n\n/** Do the synced rows reflect an entry? */\nexport function outboxReflected(entry: OutboxEntry, rows: readonly AppRecordRow[]): boolean {\n return entry.writes.every((w) => {\n const row = rows.find((r) => r.id === w.id)\n if (w.op === `delete`) return row === undefined\n return row !== undefined && Object.entries(w.data).every(([k, v]) => jsonEqual(row.data[k], v))\n })\n}\n\n/** The entries that stay (`pruneCases`): pending ones, and acked ones the\n * rows don't reflect yet within the grace. */\nexport function outboxPrune(entries: readonly OutboxEntry[], rows: readonly AppRecordRow[], now: string): OutboxEntry[] {\n const grace = APP_RECORDS_OUTBOX.ackGraceSeconds * 1000\n return entries.filter((e) => {\n if (e.state !== `acked`) return true\n if (outboxReflected(e, rows)) return false\n return Date.parse(now) - Date.parse(e.ackedAt ?? e.at) < grace\n })\n}\n\n/** The header glyph's label for an app's pending entries (`pendingCases`);\n * null = no glyph. */\nexport function outboxPendingLabel(entries: readonly OutboxEntry[], appId: string): string | null {\n const n = entries.filter((e) => e.appId === appId && e.state === `pending`).length\n if (n === 0) return null\n return n === 1 ? APP_OUTBOX_COPY.pendingOne : APP_OUTBOX_COPY.pending.replace(`{n}`, String(n))\n}\n\n// VAPP-228 (X2, docs/spec/apps.md §10.1/§10.3) SPREADSHEET IMPORT (fixture\n// `csvImport`): the CSV reader, the column auto-mapping and the cell\n// coercion the Data tab's Import dialog runs, pure so the four clients\n// replay the same cases. The host appends `validateRecord` (eUI) issues.\n\nexport const APP_CSV_IMPORT = fixture.csvImport\nexport const APP_CSV_COPY = fixture.csvImport.copy\nconst CSV_ISSUES = fixture.csvImport.issues\n\nexport interface CsvRow {\n /** The record number (header = 1, blank lines counted). */\n row: number\n cells: string[]\n}\n\nexport interface CsvTable {\n header: string[]\n rows: CsvRow[]\n}\n\n/** The delimiter: the most frequent of `,` `;` TAB in the first line outside quotes. */\nfunction csvDelimiter(text: string): string {\n const counts = new Map<string, number>([\n [`,`, 0],\n [`;`, 0],\n [`\\t`, 0],\n ])\n let quoted = false\n for (const ch of text) {\n if (ch === `\"`) quoted = !quoted\n else if (!quoted && (ch === `\\n` || ch === `\\r`)) break\n else if (!quoted && counts.has(ch)) counts.set(ch, counts.get(ch)! + 1)\n }\n let best = `,`\n for (const [ch, n] of counts) if (n > counts.get(best)!) best = ch\n return best\n}\n\n/** RFC 4180 → the header + the data rows (`parseCases`). */\nexport function csvParse(input: string): CsvTable {\n const text = input.startsWith(`\uFEFF`) ? input.slice(1) : input\n const delimiter = csvDelimiter(text)\n const records: string[][] = []\n let record: string[] = []\n let field = ``\n let quoted = false\n let i = 0\n const endRecord = () => {\n record.push(field)\n records.push(record)\n record = []\n field = ``\n }\n while (i < text.length) {\n const ch = text[i]!\n if (quoted) {\n if (ch === `\"`) {\n if (text[i + 1] === `\"`) {\n field += `\"`\n i += 2\n continue\n }\n quoted = false\n } else field += ch\n i++\n continue\n }\n if (ch === `\"`) quoted = true\n else if (ch === delimiter) {\n record.push(field)\n field = ``\n } else if (ch === `\\r` || ch === `\\n`) {\n endRecord()\n if (ch === `\\r` && text[i + 1] === `\\n`) i++\n } else field += ch\n i++\n }\n if (field !== `` || record.length > 0) endRecord()\n const numbered = records.map((cells, index) => ({ row: index + 1, cells })).filter((r) => !(r.cells.length === 1 && r.cells[0] === ``))\n const [first, ...rest] = numbered\n if (!first) return { header: [], rows: [] }\n const seen = new Map<string, number>()\n const header = first.cells.map((cell, index) => {\n const name = cell.trim() || APP_CSV_COPY.column.replace(`{n}`, String(index + 1))\n const n = (seen.get(name) ?? 0) + 1\n seen.set(name, n)\n return n === 1 ? name : `${name} ${n}`\n })\n const rows = rest.map((r) => ({ row: r.row, cells: r.cells.length < header.length ? [...r.cells, ...Array<string>(header.length - r.cells.length).fill(``)] : r.cells }))\n return { header, rows }\n}\n\n/** A name's matching key: lowercased, only ASCII a–z and 0–9. */\nexport function csvKey(name: string): string {\n return name.toLowerCase().replace(/[^a-z0-9]/g, ``)\n}\n\n/** The first pick per field (`mapCases`): equal keys first, then the ONE\n * free column containing the field's key; a column maps at most once. */\nexport function csvAutoMap(fields: readonly string[], header: readonly string[]): Record<string, number | null> {\n const keys = header.map(csvKey)\n const taken = new Set<number>()\n const mapping: Record<string, number | null> = {}\n for (const field of fields) {\n const key = csvKey(field)\n const index = key === `` ? -1 : keys.findIndex((k, i) => !taken.has(i) && k === key)\n mapping[field] = index >= 0 ? index : null\n if (index >= 0) taken.add(index)\n }\n for (const field of fields) {\n const key = csvKey(field)\n if (mapping[field] !== null || key === ``) continue\n const hits = keys.flatMap((k, i) => (!taken.has(i) && k.includes(key) ? [i] : []))\n if (hits.length === 1) {\n mapping[field] = hits[0]!\n taken.add(hits[0]!)\n }\n }\n return mapping\n}\n\n/** The owner's Author pick: the first column whose key is an `authorKeys` entry, else null (= Me). */\nexport function csvAuthorColumn(header: readonly string[]): number | null {\n const index = header.findIndex((name) => APP_CSV_IMPORT.authorKeys.includes(csvKey(name)))\n return index >= 0 ? index : null\n}\n\nexport type CsvCell = { ok: true; value: unknown } | { ok: false; message: string }\n\nconst issue = (key: keyof typeof CSV_ISSUES, value: string, values = ``): CsvCell => ({\n ok: false,\n message: CSV_ISSUES[key].replace(`{value}`, value).replace(`{values}`, values),\n})\nconst pad2 = (s: string) => (s.length === 1 ? `0${s}` : s)\n\nfunction csvNumber(text: string, integer: boolean): CsvCell {\n const compact = text.replace(/ /g, ``)\n if (!/^[+-]?[0-9.,]*[0-9][0-9.,]*$/.test(compact)) return issue(`notNumber`, text)\n const dot = compact.lastIndexOf(`.`)\n const comma = compact.lastIndexOf(`,`)\n let normal: string\n if (dot >= 0 && comma >= 0) {\n const decimal = dot > comma ? `.` : `,`\n const group = decimal === `.` ? `,` : `.`\n normal = compact.split(group).join(``).replace(decimal, `.`)\n } else if (dot >= 0 || comma >= 0) {\n const sep = dot >= 0 ? `.` : `,`\n const parts = compact.split(sep)\n normal = parts.length === 2 ? parts.join(`.`) : parts.join(``)\n } else normal = compact\n if (!/^[+-]?[0-9]+(\\.[0-9]+)?$/.test(normal)) return issue(`notNumber`, text)\n const n = Number(normal)\n if (integer && !Number.isInteger(n)) return issue(`notInteger`, text)\n return { ok: true, value: n }\n}\n\nfunction csvScalar(field: { type: string; enum?: readonly string[]; format?: string }, text: string): CsvCell {\n if (field.type === `number` || field.type === `integer`) return csvNumber(text, field.type === `integer`)\n if (field.type === `boolean`) {\n const lower = text.toLowerCase()\n if (APP_CSV_IMPORT.trueWords.includes(lower)) return { ok: true, value: true }\n if (APP_CSV_IMPORT.falseWords.includes(lower)) return { ok: true, value: false }\n return issue(`notBoolean`, text)\n }\n if (field.enum) {\n const hit = field.enum.find((v) => v.toLowerCase() === text.toLowerCase())\n return hit !== undefined ? { ok: true, value: hit } : issue(`notEnum`, text, field.enum.join(`, `))\n }\n if (field.format === `date`) {\n if (/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(text)) return { ok: true, value: text }\n const m = /^([0-9]{1,2})\\.([0-9]{1,2})\\.([0-9]{4})$/.exec(text)\n if (m) return { ok: true, value: `${m[3]}-${pad2(m[2]!)}-${pad2(m[1]!)}` }\n return issue(text.includes(`/`) ? `slashDate` : `notDate`, text)\n }\n if (field.format === `time`) {\n const m = /^([0-9]{1,2}):([0-9]{2})(:[0-9]{2})?$/.exec(text)\n return m ? { ok: true, value: `${pad2(m[1]!)}:${m[2]}${m[3] ?? ``}` } : issue(`notTime`, text)\n }\n if (field.format === `date-time`) return /^[0-9]{4}-[0-9]{2}-[0-9]{2}T/.test(text) ? { ok: true, value: text } : issue(`notDateTime`, text)\n return { ok: true, value: text }\n}\n\n/** One cell → the field's value (`cellCases`); empty = null. */\nexport function csvCell(field: DataField, cell: string): CsvCell {\n const text = cell.trim()\n if (text === ``) return { ok: true, value: null }\n if (field.type !== `array`) return csvScalar(field, text)\n const items = text\n .split(text.includes(`;`) ? `;` : `,`)\n .map((item) => item.trim())\n .filter((item) => item !== ``)\n if (items.length === 0) return { ok: true, value: null }\n const values: unknown[] = []\n for (const item of items) {\n const read = csvScalar({ type: field.items?.type ?? `string` }, item)\n if (!read.ok) return read\n values.push(read.value)\n }\n return { ok: true, value: values }\n}\n\nexport interface CsvMember {\n id: string\n name: string\n email: string\n}\n\nexport interface CsvAuthor {\n /** The Author column (null = every row is the importer's). */\n column: number | null\n importer: string\n members: readonly CsvMember[]\n}\n\nexport interface CsvRecord {\n row: number\n data: Record<string, unknown>\n /** A member's id when the row is someone else's (an on-behalf write). */\n author?: string\n issues: string[]\n}\n\n/** The rows → records + their issues (`rowsCases`), before the host's schema check. */\nexport function csvRecords(schema: DataSchema, header: readonly string[], rows: readonly CsvRow[], mapping: Readonly<Record<string, number | null>>, author?: CsvAuthor): CsvRecord[] {\n return rows.map(({ row, cells }) => {\n const issues: string[] = []\n const at = (field: string, message: string) => APP_CSV_COPY.issue.replace(`{row}`, String(row)).replace(`{field}`, field).replace(`{message}`, message)\n if (cells.length > header.length) {\n const message = CSV_ISSUES.extraCells.replace(`{n}`, String(cells.length)).replace(`{m}`, String(header.length))\n issues.push(APP_CSV_COPY.rowIssue.replace(`{row}`, String(row)).replace(`{message}`, message))\n }\n const data: Record<string, unknown> = {}\n for (const name of dataColumns(schema)) {\n const column = mapping[name]\n if (column === null || column === undefined) continue\n const read = csvCell(schema.properties[name]!, cells[column] ?? ``)\n if (!read.ok) issues.push(at(name, read.message))\n else if (read.value !== null) data[name] = read.value\n }\n const record: CsvRecord = { row, data, issues }\n if (author && author.column !== null) {\n const text = (cells[author.column] ?? ``).trim()\n let id: string | null = author.importer\n if (text !== ``) {\n const lower = text.toLowerCase()\n const byEmail = author.members.find((m) => m.email.toLowerCase() === lower)\n const byName = author.members.filter((m) => m.name.toLowerCase() === lower)\n id = byEmail?.id ?? (byName.length === 1 ? byName[0]!.id : null)\n }\n if (id === null) issues.push(at(APP_DATA_COPY.author, CSV_ISSUES.notMember.replace(`{value}`, text)))\n else if (id !== author.importer) record.author = id\n }\n return record\n })\n}\n\n/** The preview's count line (`copy.ready`). */\nexport function csvSummary(records: readonly { issues: readonly string[] }[]): string {\n return APP_CSV_COPY.ready.replace(`{n}`, String(records.filter((r) => r.issues.length === 0).length)).replace(`{total}`, String(records.length))\n}\n",
8
+ "// VAPP-260: the Engine's outbox IS O1's (`@exp/domain-contract/app-records`,\n// fixture `app-records.json` `outbox`), re-exported here; the Engine adds only\n// WHEN it applies (`fixtures/outbox.json` `modeCases`).\n\nexport {\n outboxFlush,\n outboxOutcome,\n outboxPendingLabel,\n outboxPrune,\n outboxReflected,\n outboxRequest,\n outboxRows,\n outboxVerb,\n outboxWrites,\n type OutboxCall,\n type OutboxEntry,\n type OutboxOutcome,\n type OutboxResult,\n type OutboxWrite,\n} from \"@exp/domain-contract/app-records\"\n\n/** A host with a sync provider queues every app write; one without writes through. */\nexport const outboxMode = (hasSync: boolean): `queue` | `direct` => (hasSync ? `queue` : `direct`)\n"
9
+ ],
10
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOO,IAAM,iBAAiB,kBAAQ;AAC/B,IAAM,iBAAiB,kBAAQ;AAE/B,IAAM,2BAA8E,kBAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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",
11
+ "debugId": "E4BC8F0513ABCBE964756E2164756E21",
12
+ "names": []
13
+ }
@@ -0,0 +1,24 @@
1
+ import {
2
+ CONSENT_COPY2,
3
+ GRANTS_RECORD_VERSION2,
4
+ consentAnswer2,
5
+ grantWrite2,
6
+ grantsRecord2,
7
+ grantsOnPackage2,
8
+ consentSheet2,
9
+ manageSheet2
10
+ } from "./chunks/index-knntt4cb.js";
11
+ import"./chunks/index-tvsykgcv.js";
12
+ export {
13
+ CONSENT_COPY2 as CONSENT_COPY,
14
+ GRANTS_RECORD_VERSION2 as GRANTS_RECORD_VERSION,
15
+ consentAnswer2 as consentAnswer,
16
+ consentSheet2 as consentSheet,
17
+ grantWrite2 as grantWrite,
18
+ grantsOnPackage2 as grantsOnPackage,
19
+ grantsRecord2 as grantsRecord,
20
+ manageSheet2 as manageSheet
21
+ };
22
+
23
+ //# debugId=5375CA8FE54EE02164756E2164756E21
24
+ //# sourceMappingURL=consent.js.map
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [
5
+ ],
6
+ "mappings": "",
7
+ "debugId": "5375CA8FE54EE02164756E2164756E21",
8
+ "names": []
9
+ }