@morscherlab/mint-sdk 1.4.0-beta.16 → 1.4.0-beta.18

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 (78) hide show
  1. package/README.md +14 -3
  2. package/dist/{AlertBox-SnsbVow-.js → AlertBox-CzLg0UaL.js} +2 -2
  3. package/dist/{AlertBox-SnsbVow-.js.map → AlertBox-CzLg0UaL.js.map} +1 -1
  4. package/dist/{AppSidebar-CB0-RQ4m.js → AppSidebar-6oTT-gPO.js} +43 -13
  5. package/dist/AppSidebar-6oTT-gPO.js.map +1 -0
  6. package/dist/{ExperimentPopover-ChKCrklH.js → ExperimentPopover-9l4QZlvQ.js} +2 -2
  7. package/dist/{ExperimentPopover-ChKCrklH.js.map → ExperimentPopover-9l4QZlvQ.js.map} +1 -1
  8. package/dist/{ExperimentSelectorModal-BqifjNaN.js → ExperimentSelectorModal-pR5fK3pA.js} +3 -3
  9. package/dist/{ExperimentSelectorModal-BqifjNaN.js.map → ExperimentSelectorModal-pR5fK3pA.js.map} +1 -1
  10. package/dist/{SegmentedControl-hOgBVdju.js → SegmentedControl-CJ7EAmOq.js} +2 -2
  11. package/dist/{SegmentedControl-hOgBVdju.js.map → SegmentedControl-CJ7EAmOq.js.map} +1 -1
  12. package/dist/{SettingsModal-CoZK3EDN.js → SettingsModal-Dyitgzfp.js} +6 -6
  13. package/dist/{SettingsModal-CoZK3EDN.js.map → SettingsModal-Dyitgzfp.js.map} +1 -1
  14. package/dist/components/AppTopBar.vue.d.ts +7 -0
  15. package/dist/components/JobsStatusTray.vue.d.ts +11 -4
  16. package/dist/components/PluginWorkspaceView.props.d.ts +3 -0
  17. package/dist/components/PluginWorkspaceView.vue.d.ts +3 -0
  18. package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +8 -0
  19. package/dist/composables/useJobCenter.d.ts +11 -3
  20. package/dist/{controlSchemaModel-CIH5Hqpw.js → controlSchemaModel-DCUOx8ll.js} +2 -2
  21. package/dist/{controlSchemaModel-CIH5Hqpw.js.map → controlSchemaModel-DCUOx8ll.js.map} +1 -1
  22. package/dist/{data-OZ94DhVC.js → data-BIiV4kKw.js} +9 -9
  23. package/dist/{data-OZ94DhVC.js.map → data-BIiV4kKw.js.map} +1 -1
  24. package/dist/data.js +7 -7
  25. package/dist/index.js +16 -16
  26. package/dist/{install-BKgb5qZw.js → install-Dg-Z6ze5.js} +13 -13
  27. package/dist/{install-BKgb5qZw.js.map → install-Dg-Z6ze5.js.map} +1 -1
  28. package/dist/install.js +1 -1
  29. package/dist/{lab-ZDKlx1Oo.js → lab-BHVT1qN2.js} +9 -9
  30. package/dist/{lab-ZDKlx1Oo.js.map → lab-BHVT1qN2.js.map} +1 -1
  31. package/dist/lab.js +4 -4
  32. package/dist/{records-B4yl2oIu.js → records-CR_pYzIP.js} +62 -62
  33. package/dist/{records-B4yl2oIu.js.map → records-CR_pYzIP.js.map} +1 -1
  34. package/dist/{runtime-Cz1GJa_O.js → runtime-cHv1phcc.js} +379 -31
  35. package/dist/runtime-cHv1phcc.js.map +1 -0
  36. package/dist/runtime.d.ts +1 -1
  37. package/dist/runtime.js +6 -6
  38. package/dist/{shell-kq9p4VsX.js → shell-BDuZf2zb.js} +4 -4
  39. package/dist/{shell-kq9p4VsX.js.map → shell-BDuZf2zb.js.map} +1 -1
  40. package/dist/shell.js +4 -4
  41. package/dist/styles.css +163 -307
  42. package/dist/types/components.d.ts +2 -0
  43. package/dist/{useExperimentSamples-BLx29CkM.js → useExperimentSamples-Dno1fFMq.js} +3 -3
  44. package/dist/{useExperimentSamples-BLx29CkM.js.map → useExperimentSamples-Dno1fFMq.js.map} +1 -1
  45. package/dist/{useListReorder-C_8RYeVP.js → useListReorder-CQ9OCScc.js} +76 -76
  46. package/dist/useListReorder-CQ9OCScc.js.map +1 -0
  47. package/dist/{useRackEditor-BhYlXKFM.js → useRackEditor-wL3kPrRT.js} +6 -6
  48. package/dist/{useRackEditor-BhYlXKFM.js.map → useRackEditor-wL3kPrRT.js.map} +1 -1
  49. package/dist/useRealtime-BW02xy4Z.js +398 -0
  50. package/dist/useRealtime-BW02xy4Z.js.map +1 -0
  51. package/dist/{useRequest-C9uwM79q.js → useRequest-CDkrXNU7.js} +2 -2
  52. package/dist/{useRequest-C9uwM79q.js.map → useRequest-CDkrXNU7.js.map} +1 -1
  53. package/package.json +1 -1
  54. package/src/__tests__/components/AppTopBar.test.ts +33 -2
  55. package/src/__tests__/components/GeneratedPluginView.test.ts +1 -1
  56. package/src/__tests__/components/JobsStatusTray.test.ts +137 -71
  57. package/src/__tests__/components/PluginWorkspaceView.test.ts +56 -1
  58. package/src/__tests__/composables/useJobCenter.test.ts +223 -3
  59. package/src/__tests__/docs/frontendDocsCatalog.test.ts +3 -3
  60. package/src/__tests__/styles/v2AuditRules.test.ts +8 -0
  61. package/src/components/AppTopBar.vue +14 -0
  62. package/src/components/GeneratedPluginView.vue +4 -9
  63. package/src/components/JobsStatusTray.story.vue +78 -43
  64. package/src/components/JobsStatusTray.vue +171 -279
  65. package/src/components/PluginWorkspaceView.props.ts +3 -0
  66. package/src/components/PluginWorkspaceView.vue +17 -0
  67. package/src/components/internal/WorkspaceShellInternal.vue +12 -0
  68. package/src/composables/useJobCenter.ts +127 -6
  69. package/src/runtime.ts +0 -1
  70. package/src/styles/components/app-top-bar.css +7 -0
  71. package/src/styles/components/jobs-status-tray.css +129 -296
  72. package/src/styles/variables.css +16 -0
  73. package/src/types/components.ts +2 -0
  74. package/dist/AppSidebar-CB0-RQ4m.js.map +0 -1
  75. package/dist/runtime-Cz1GJa_O.js.map +0 -1
  76. package/dist/useListReorder-C_8RYeVP.js.map +0 -1
  77. package/dist/useRealtime-CUMbW_ln.js +0 -699
  78. package/dist/useRealtime-CUMbW_ln.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"useRealtime-CUMbW_ln.js","names":[],"sources":["../src/composables/useJobCenter.ts","../src/components/JobsStatusTray.vue","../src/components/JobsStatusTray.vue","../src/composables/useRealtime.ts"],"sourcesContent":["import {\n computed,\n onMounted,\n onUnmounted,\n ref,\n shallowRef,\n toValue,\n type ComputedRef,\n type MaybeRefOrGetter,\n type Ref,\n type ShallowRef,\n} from 'vue'\n\nimport { isActiveJobStatus, normalizeJobState } from '../jobs'\nimport type {\n JobListPayload,\n JobState,\n JobStateInput,\n} from '../types/jobs'\n\nexport type JobsStatusTrayTab = 'active' | 'history'\n\n/**\n * The canonical owner of a collection of plugin jobs.\n *\n * Sources, rather than job-center UI, own polling, event streams, and all job\n * mutations. This keeps components and consumers on one reactive job graph.\n */\nexport interface PluginJobCenterSource {\n jobs: MaybeRefOrGetter<readonly JobStateInput[]>\n refresh?: () => Promise<JobListPayload | readonly JobStateInput[] | void>\n cancel?: (jobId: string) => Promise<JobStateInput | void>\n delete?: (jobId: string) => Promise<boolean | void>\n clear?: () => Promise<number | void>\n dispose?: () => void\n}\n\nexport interface CombinedPluginJobCenterSource extends PluginJobCenterSource {\n jobs: ComputedRef<readonly JobStateInput[]>\n}\n\n/**\n * Combines independent job runtimes without copying their reactive state.\n *\n * Job ids must be globally unique across the sources. A duplicate id throws\n * when jobs are read or an id-routed action is requested, rather than silently\n * dispatching a mutation to the wrong owner.\n */\nexport function combinePluginJobSources(\n sources: readonly PluginJobCenterSource[],\n): CombinedPluginJobCenterSource {\n const jobs = computed<readonly JobStateInput[]>(() => {\n const combined: JobStateInput[] = []\n const owners = new Set<string>()\n for (const source of sources) {\n for (const job of toValue(source.jobs)) {\n if (owners.has(job.job_id)) throw duplicatePluginJobId(job.job_id)\n owners.add(job.job_id)\n combined.push(job)\n }\n }\n return combined\n })\n\n function ownerFor(jobId: string): PluginJobCenterSource {\n let owner: PluginJobCenterSource | undefined\n for (const source of sources) {\n if (!toValue(source.jobs).some(job => job.job_id === jobId)) continue\n if (owner) throw duplicatePluginJobId(jobId)\n owner = source\n }\n if (!owner) throw new Error(`Unknown plugin job id: ${jobId}`)\n return owner\n }\n\n const combined: CombinedPluginJobCenterSource = { jobs }\n if (sources.some(source => source.refresh)) {\n combined.refresh = async () => {\n await Promise.all(sources.map(source => source.refresh?.()))\n return jobs.value\n }\n }\n if (sources.some(source => source.cancel)) {\n combined.cancel = async (jobId) => {\n const owner = ownerFor(jobId)\n if (!owner.cancel) throw new Error(`Job ${jobId} cannot be cancelled by its source`)\n return owner.cancel(jobId)\n }\n }\n if (sources.length > 0 && sources.every(source => source.delete)) {\n combined.delete = async (jobId) => {\n const owner = ownerFor(jobId)\n if (!owner.delete) throw new Error(`Job ${jobId} cannot be deleted by its source`)\n return owner.delete(jobId)\n }\n }\n // Never report a successful combined clear after mutating only a subset.\n // Without bulk support from every source, the job center can still fall\n // back to this source's id-routed delete method when all owners expose it.\n if (sources.length > 0 && sources.every(source => source.clear)) {\n combined.clear = async () => {\n const results = await Promise.all(\n sources.map(source => source.clear!()),\n )\n return results.every((result): result is number => typeof result === 'number')\n ? results.reduce((total, result) => total + result, 0)\n : undefined\n }\n }\n if (sources.some(source => source.dispose)) {\n combined.dispose = () => {\n const errors: unknown[] = []\n for (const source of sources) {\n try {\n source.dispose?.()\n } catch (cause) {\n errors.push(cause)\n }\n }\n if (errors.length === 1) throw errors[0]\n if (errors.length > 1) throw new PluginJobSourceDisposeError(errors)\n }\n }\n return combined\n}\n\nclass PluginJobSourceDisposeError extends Error {\n readonly cause: unknown\n readonly errors: readonly unknown[]\n\n constructor(errors: readonly unknown[]) {\n super('Failed to dispose plugin job sources')\n this.name = 'PluginJobSourceDisposeError'\n this.cause = errors[0]\n this.errors = Object.freeze([...errors])\n }\n}\n\nfunction duplicatePluginJobId(jobId: string): Error {\n return new Error(`Duplicate plugin job id across combined sources: ${jobId}`)\n}\n\nexport interface UseJobCenterOptions {\n source?: MaybeRefOrGetter<PluginJobCenterSource | undefined>\n loadOnMount?: boolean\n}\n\nexport interface UseJobCenterReturn {\n jobs: ComputedRef<JobState[]>\n activeJobs: ComputedRef<JobState[]>\n historyJobs: ComputedRef<JobState[]>\n deletableJobs: ComputedRef<JobState[]>\n featuredJob: ComputedRef<JobState | null>\n canRefresh: ComputedRef<boolean>\n canCancel: ComputedRef<boolean>\n canDelete: ComputedRef<boolean>\n canClearFinished: ComputedRef<boolean>\n isOpen: Ref<boolean>\n activeTab: Ref<JobsStatusTrayTab>\n isLoading: Ref<boolean>\n error: ShallowRef<string | null>\n pendingJobIds: Ref<Set<string>>\n open: () => void\n close: () => void\n toggle: () => void\n refresh: () => Promise<boolean>\n cancelJob: (job: JobState) => Promise<boolean>\n deleteJob: (job: JobState) => Promise<boolean>\n clearFinished: () => Promise<boolean>\n isPending: (job: JobState) => boolean\n}\n\nfunction errorMessage(error: unknown, fallback: string): string {\n return error instanceof Error ? error.message : fallback\n}\n\n/**\n * Headless controller for plugin job presentation and actions.\n *\n * Every job list is derived directly from the source. The controller never\n * mirrors source jobs into another ref.\n */\nexport function useJobCenter(\n options: UseJobCenterOptions = {},\n): UseJobCenterReturn {\n const isOpen = ref(false)\n const activeTab = ref<JobsStatusTrayTab>('active')\n const isLoading = ref(false)\n const error = shallowRef<string | null>(null)\n const pendingJobIds = ref(new Set<string>())\n let requestGeneration = 0\n let active = true\n const source = computed(() => toValue(options.source))\n\n const jobs = computed<JobState[]>(() => {\n const currentSource = source.value\n return currentSource ? [...toValue(currentSource.jobs)].map(normalizeJobState) : []\n })\n const activeJobs = computed(() => jobs.value.filter(job => isActiveJobStatus(job.status)))\n const historyJobs = computed(() => jobs.value.filter(job => !isActiveJobStatus(job.status)))\n const deletableJobs = computed(() => historyJobs.value.filter(job => job.can_delete))\n const featuredJob = computed(() => activeJobs.value[0] ?? historyJobs.value[0] ?? null)\n const canRefresh = computed(() => Boolean(source.value?.refresh))\n const canCancel = computed(() => Boolean(source.value?.cancel))\n const canDelete = computed(() => Boolean(source.value?.delete))\n const canClearFinished = computed(() => Boolean(\n source.value?.clear || source.value?.delete,\n ))\n\n function setPending(jobId: string, pending: boolean): void {\n const next = new Set(pendingJobIds.value)\n if (pending) next.add(jobId)\n else next.delete(jobId)\n pendingJobIds.value = next\n }\n\n async function refresh(): Promise<boolean> {\n const listJobs = source.value?.refresh\n if (!listJobs) return false\n const generation = ++requestGeneration\n isLoading.value = true\n error.value = null\n try {\n await listJobs()\n return active && generation === requestGeneration\n } catch (cause) {\n if (active && generation === requestGeneration) {\n error.value = errorMessage(cause, 'Failed to load jobs')\n }\n return false\n } finally {\n if (active && generation === requestGeneration) isLoading.value = false\n }\n }\n\n async function cancelJob(job: JobState): Promise<boolean> {\n const sourceCancel = source.value?.cancel\n if (!job.can_cancel || !sourceCancel || pendingJobIds.value.has(job.job_id)) {\n return false\n }\n setPending(job.job_id, true)\n error.value = null\n try {\n await sourceCancel(job.job_id)\n return active\n } catch (cause) {\n if (active) error.value = errorMessage(cause, 'Failed to cancel job')\n return false\n } finally {\n if (active) setPending(job.job_id, false)\n }\n }\n\n async function deleteJob(job: JobState): Promise<boolean> {\n const sourceDelete = source.value?.delete\n if (!job.can_delete || !sourceDelete || pendingJobIds.value.has(job.job_id)) {\n return false\n }\n setPending(job.job_id, true)\n error.value = null\n try {\n const deleted = await sourceDelete(job.job_id)\n return active && deleted !== false\n } catch (cause) {\n if (active) error.value = errorMessage(cause, 'Failed to delete job')\n return false\n } finally {\n if (active) setPending(job.job_id, false)\n }\n }\n\n async function clearFinished(): Promise<boolean> {\n const targets = deletableJobs.value\n if (targets.length === 0) return false\n error.value = null\n try {\n const currentSource = source.value\n if (currentSource?.clear) {\n const deleted = await currentSource.clear()\n if (!active) return false\n return typeof deleted !== 'number' || deleted === targets.length\n }\n if (!currentSource?.delete) return false\n const results = await Promise.all(targets.map(job => deleteJob(job)))\n return results.every(Boolean)\n } catch (cause) {\n if (active) error.value = errorMessage(cause, 'Failed to clear jobs')\n return false\n }\n }\n\n onMounted(() => {\n if (options.loadOnMount !== false) void refresh()\n })\n onUnmounted(() => {\n active = false\n requestGeneration += 1\n })\n\n return {\n jobs,\n activeJobs,\n historyJobs,\n deletableJobs,\n featuredJob,\n canRefresh,\n canCancel,\n canDelete,\n canClearFinished,\n isOpen,\n activeTab,\n isLoading,\n error,\n pendingJobIds,\n open: () => { isOpen.value = true },\n close: () => { isOpen.value = false },\n toggle: () => { isOpen.value = !isOpen.value },\n refresh,\n cancelJob,\n deleteJob,\n clearFinished,\n isPending: job => pendingJobIds.value.has(job.job_id),\n }\n}\n","<script setup lang=\"ts\">\n/**\n * Floating, source-driven job status tray for plugin and platform workflows.\n *\n * @example Recommended\n * ```vue\n * <JobsStatusTray :source=\"jobs\" />\n * ```\n */\nimport { computed, nextTick, ref, toRef, useId } from 'vue'\n\nimport {\n jobStatusLabel,\n normalizeJobPercent,\n} from '../jobs'\nimport {\n useJobCenter,\n type PluginJobCenterSource,\n} from '../composables/useJobCenter'\nimport type { JobState } from '../types/jobs'\n\ninterface Props {\n source?: PluginJobCenterSource\n loadOnMount?: boolean\n title?: string\n teleportTo?: string | HTMLElement | false\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n loadOnMount: true,\n title: 'Analysis jobs',\n teleportTo: 'body',\n})\n\nconst emit = defineEmits<{\n select: [job: JobState]\n error: [message: string]\n}>()\n\ndefineSlots<{\n /** Extra per-row actions, rendered before the built-in Cancel / Delete. */\n 'job-actions'?: (props: { job: JobState }) => unknown\n}>()\n\nconst panelId = `mint-jobs-tray-${useId().replace(/:/g, '')}`\nconst titleId = `${panelId}-title`\nconst triggerRef = ref<HTMLButtonElement | null>(null)\nconst panelRef = ref<HTMLElement | null>(null)\nconst teleportTarget = computed(() => props.teleportTo || 'body')\nconst tray = useJobCenter({\n source: toRef(props, 'source'),\n loadOnMount: props.loadOnMount,\n})\n// 2f: a job waiting for input is the only one that needs a person — pin it first.\nconst activeRows = computed(() => [\n ...tray.activeJobs.value.filter(job => job.status === 'awaiting_input'),\n ...tray.activeJobs.value.filter(job => job.status !== 'awaiting_input'),\n])\nconst subtitle = computed(() => {\n const count = (status: JobState['status']) => tray.activeJobs.value.filter(job => job.status === status).length\n const parts = [\n [count('running'), 'running'],\n [count('queued'), 'queued'],\n [count('awaiting_input'), 'waiting'],\n ].filter(([n]) => n).map(([n, word]) => `${n} ${word}`)\n return parts.length ? parts.join(' · ') : `${tray.jobs.value.length} total`\n})\nconst canClear = computed(() => (\n tray.deletableJobs.value.length > 0\n && tray.canClearFinished.value\n))\n\nasync function openTray(): Promise<void> {\n tray.open()\n await nextTick()\n panelRef.value?.focus()\n}\n\nasync function closeTray(): Promise<void> {\n tray.close()\n await nextTick()\n triggerRef.value?.focus()\n}\n\nasync function toggleTray(): Promise<void> {\n if (tray.isOpen.value) await closeTray()\n else await openTray()\n}\n\nfunction onPanelKeydown(event: KeyboardEvent): void {\n if (event.key === 'Escape') {\n void closeTray()\n return\n }\n if (event.key !== 'Tab' || !panelRef.value) return\n const focusable = Array.from(panelRef.value.querySelectorAll<HTMLElement>(\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n ))\n if (focusable.length === 0) {\n event.preventDefault()\n panelRef.value.focus()\n return\n }\n const first = focusable[0]\n const last = focusable[focusable.length - 1]\n const activeElement = document.activeElement as HTMLElement | null\n if (!activeElement || !focusable.includes(activeElement)) {\n event.preventDefault()\n if (event.shiftKey) last?.focus()\n else first?.focus()\n } else if (event.shiftKey && activeElement === first) {\n event.preventDefault()\n last?.focus()\n } else if (!event.shiftKey && activeElement === last) {\n event.preventDefault()\n first?.focus()\n }\n}\n\nfunction progressText(job: JobState): string {\n if (job.status === 'awaiting_input') {\n return job.progress.message || 'Waiting for external input'\n }\n if (job.status === 'queued' && job.queue_position > 0) {\n return `Queue position ${job.queue_position}`\n }\n return job.progress.message || job.progress.stage || jobStatusLabel(job.status)\n}\n\nfunction formatTimestamp(value: string | null): string {\n if (!value) return ''\n const date = new Date(value)\n return Number.isNaN(date.getTime())\n ? ''\n : new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(date)\n}\n\nfunction statusClass(job: JobState): string {\n return `mint-jobs-tray__status--${job.status.replace('_', '-')}`\n}\n\nfunction jobPercent(job: JobState): number {\n return Math.round(normalizeJobPercent(job.progress.percent))\n}\n\nasync function cancelJob(job: JobState): Promise<void> {\n const succeeded = await tray.cancelJob(job)\n if (!succeeded && tray.error.value) emit('error', tray.error.value)\n}\n\nasync function deleteJob(job: JobState): Promise<void> {\n const succeeded = await tray.deleteJob(job)\n if (!succeeded && tray.error.value) emit('error', tray.error.value)\n}\n\nasync function clearFinished(): Promise<void> {\n const succeeded = await tray.clearFinished()\n if (!succeeded && tray.error.value) emit('error', tray.error.value)\n}\n</script>\n\n<template>\n <Teleport :to=\"teleportTarget\" :disabled=\"teleportTo === false\">\n <div class=\"mint-jobs-tray\">\n <button\n ref=\"triggerRef\"\n type=\"button\"\n class=\"mint-jobs-tray__trigger\"\n aria-haspopup=\"dialog\"\n :aria-expanded=\"tray.isOpen.value\"\n :aria-controls=\"panelId\"\n @click=\"toggleTray\"\n >\n <svg\n class=\"mint-jobs-tray__trigger-icon\"\n :class=\"{ 'mint-jobs-tray__trigger-icon--active': tray.activeJobs.value.length > 0 }\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path d=\"M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2\" />\n <path d=\"M9 5a3 3 0 0 1 6 0v2H9V5Z\" />\n </svg>\n <span class=\"mint-jobs-tray__trigger-label\">\n {{ tray.featuredJob.value ? jobStatusLabel(tray.featuredJob.value.status) : 'Jobs' }}\n </span>\n <span\n v-if=\"tray.activeJobs.value.length > 0\"\n class=\"mint-jobs-tray__count\"\n aria-live=\"polite\"\n >\n {{ tray.activeJobs.value.length }}\n </span>\n </button>\n\n <Transition name=\"mint-jobs-tray-fade\">\n <div\n v-if=\"tray.isOpen.value\"\n class=\"mint-jobs-tray__backdrop\"\n aria-hidden=\"true\"\n @click=\"closeTray\"\n />\n </Transition>\n\n <Transition name=\"mint-jobs-tray-slide\">\n <section\n v-if=\"tray.isOpen.value\"\n :id=\"panelId\"\n ref=\"panelRef\"\n class=\"mint-jobs-tray__panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-labelledby=\"titleId\"\n tabindex=\"-1\"\n @keydown=\"onPanelKeydown\"\n >\n <header class=\"mint-jobs-tray__header\">\n <div>\n <h2 :id=\"titleId\" class=\"mint-jobs-tray__title\">{{ title }}</h2>\n <p class=\"mint-jobs-tray__subtitle\" :title=\"subtitle\">{{ subtitle }}</p>\n </div>\n <div class=\"mint-jobs-tray__header-actions\">\n <button\n v-if=\"tray.canRefresh.value\"\n type=\"button\"\n class=\"mint-jobs-tray__icon-button\"\n aria-label=\"Refresh jobs\"\n :disabled=\"tray.isLoading.value\"\n @click=\"tray.refresh\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M20 6v5h-5M4 18v-5h5\" />\n <path d=\"M18.4 9A7 7 0 0 0 6.1 6.1L4 8m2 7a7 7 0 0 0 11.9 2.9L20 16\" />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"mint-jobs-tray__icon-button\"\n aria-label=\"Close jobs\"\n @click=\"closeTray\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path d=\"m6 6 12 12M18 6 6 18\" />\n </svg>\n </button>\n </div>\n </header>\n\n <div class=\"mint-jobs-tray__tabs\" role=\"tablist\" aria-label=\"Job groups\">\n <button\n type=\"button\"\n role=\"tab\"\n data-tab=\"active\"\n class=\"mint-jobs-tray__tab\"\n :class=\"{ 'mint-jobs-tray__tab--active': tray.activeTab.value === 'active' }\"\n :aria-selected=\"tray.activeTab.value === 'active'\"\n @click=\"tray.activeTab.value = 'active'\"\n >\n Active\n <span class=\"mint-jobs-tray__tab-count\">{{ tray.activeJobs.value.length }}</span>\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n data-tab=\"history\"\n class=\"mint-jobs-tray__tab\"\n :class=\"{ 'mint-jobs-tray__tab--active': tray.activeTab.value === 'history' }\"\n :aria-selected=\"tray.activeTab.value === 'history'\"\n @click=\"tray.activeTab.value = 'history'\"\n >\n History\n <span class=\"mint-jobs-tray__tab-count\">{{ tray.historyJobs.value.length }}</span>\n </button>\n </div>\n\n <p v-if=\"tray.error.value\" class=\"mint-jobs-tray__error\" role=\"alert\">\n {{ tray.error.value }}\n </p>\n\n <div class=\"mint-jobs-tray__body\">\n <template v-if=\"tray.activeTab.value === 'active'\">\n <p v-if=\"tray.activeJobs.value.length === 0\" class=\"mint-jobs-tray__empty\">\n No active jobs\n </p>\n <article\n v-for=\"job in activeRows\"\n :key=\"job.job_id\"\n class=\"mint-jobs-tray__job\"\n :class=\"{ 'mint-jobs-tray__job--awaiting-input': job.status === 'awaiting_input' }\"\n >\n <div class=\"mint-jobs-tray__job-heading\">\n <div class=\"mint-jobs-tray__job-identity\">\n <button\n type=\"button\"\n class=\"mint-jobs-tray__job-select\"\n @click=\"emit('select', job)\"\n >\n {{ job.name }}\n </button>\n <span class=\"mint-jobs-tray__kind\">{{ job.kind }}</span>\n </div>\n <span class=\"mint-jobs-tray__status\" :class=\"statusClass(job)\">\n <span class=\"mint-jobs-tray__status-dot\" aria-hidden=\"true\" />\n {{ jobStatusLabel(job.status) }}\n </span>\n <span v-if=\"job.status === 'running'\" class=\"mint-jobs-tray__percent\">{{ jobPercent(job) }}%</span>\n </div>\n <div\n v-if=\"job.status === 'running'\"\n class=\"mint-jobs-tray__progress\"\n role=\"progressbar\"\n aria-label=\"Job progress\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n :aria-valuenow=\"jobPercent(job)\"\n >\n <span :style=\"{ width: `${jobPercent(job)}%` }\" />\n </div>\n <div class=\"mint-jobs-tray__job-footer\">\n <span>{{ progressText(job) }}</span>\n <div class=\"mint-jobs-tray__job-actions\">\n <slot name=\"job-actions\" :job=\"job\" />\n <button\n v-if=\"job.can_cancel && tray.canCancel.value\"\n type=\"button\"\n class=\"mint-jobs-tray__action mint-jobs-tray__action--danger\"\n aria-label=\"Cancel job\"\n :disabled=\"tray.isPending(job)\"\n @click=\"cancelJob(job)\"\n >\n Cancel\n </button>\n <button\n v-if=\"job.can_delete && tray.canDelete.value\"\n type=\"button\"\n class=\"mint-jobs-tray__icon-button mint-jobs-tray__icon-button--danger\"\n aria-label=\"Delete job\"\n :disabled=\"tray.isPending(job)\"\n @click=\"deleteJob(job)\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M4 7h16M9 7V4h6v3m-8 0 1 13h8l1-13M10 11v5M14 11v5\" />\n </svg>\n </button>\n </div>\n </div>\n </article>\n </template>\n\n <template v-else>\n <div v-if=\"canClear\" class=\"mint-jobs-tray__clear-row\">\n <button\n type=\"button\"\n class=\"mint-jobs-tray__action mint-jobs-tray__action--danger\"\n @click=\"clearFinished\"\n >\n Clear finished\n </button>\n </div>\n <p v-if=\"tray.historyJobs.value.length === 0\" class=\"mint-jobs-tray__empty\">\n No job history\n </p>\n <article\n v-for=\"job in tray.historyJobs.value\"\n :key=\"job.job_id\"\n class=\"mint-jobs-tray__job\"\n >\n <div class=\"mint-jobs-tray__job-heading\">\n <div class=\"mint-jobs-tray__job-identity\">\n <button\n type=\"button\"\n class=\"mint-jobs-tray__job-select\"\n @click=\"emit('select', job)\"\n >\n {{ job.name }}\n </button>\n <span class=\"mint-jobs-tray__kind\">{{ job.kind }}</span>\n </div>\n <span class=\"mint-jobs-tray__status\" :class=\"statusClass(job)\">\n <span class=\"mint-jobs-tray__status-dot\" aria-hidden=\"true\" />\n {{ jobStatusLabel(job.status) }}\n </span>\n </div>\n <div class=\"mint-jobs-tray__job-footer\">\n <span>{{ formatTimestamp(job.completed_at || job.created_at) }}</span>\n <div class=\"mint-jobs-tray__job-actions\">\n <slot name=\"job-actions\" :job=\"job\" />\n <button\n v-if=\"job.can_delete && tray.canDelete.value\"\n type=\"button\"\n class=\"mint-jobs-tray__icon-button mint-jobs-tray__icon-button--danger\"\n aria-label=\"Delete job\"\n :disabled=\"tray.isPending(job)\"\n @click=\"deleteJob(job)\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M4 7h16M9 7V4h6v3m-8 0 1 13h8l1-13M10 11v5M14 11v5\" />\n </svg>\n </button>\n </div>\n </div>\n </article>\n </template>\n </div>\n </section>\n </Transition>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Floating, source-driven job status tray for plugin and platform workflows.\n *\n * @example Recommended\n * ```vue\n * <JobsStatusTray :source=\"jobs\" />\n * ```\n */\nimport { computed, nextTick, ref, toRef, useId } from 'vue'\n\nimport {\n jobStatusLabel,\n normalizeJobPercent,\n} from '../jobs'\nimport {\n useJobCenter,\n type PluginJobCenterSource,\n} from '../composables/useJobCenter'\nimport type { JobState } from '../types/jobs'\n\ninterface Props {\n source?: PluginJobCenterSource\n loadOnMount?: boolean\n title?: string\n teleportTo?: string | HTMLElement | false\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n loadOnMount: true,\n title: 'Analysis jobs',\n teleportTo: 'body',\n})\n\nconst emit = defineEmits<{\n select: [job: JobState]\n error: [message: string]\n}>()\n\ndefineSlots<{\n /** Extra per-row actions, rendered before the built-in Cancel / Delete. */\n 'job-actions'?: (props: { job: JobState }) => unknown\n}>()\n\nconst panelId = `mint-jobs-tray-${useId().replace(/:/g, '')}`\nconst titleId = `${panelId}-title`\nconst triggerRef = ref<HTMLButtonElement | null>(null)\nconst panelRef = ref<HTMLElement | null>(null)\nconst teleportTarget = computed(() => props.teleportTo || 'body')\nconst tray = useJobCenter({\n source: toRef(props, 'source'),\n loadOnMount: props.loadOnMount,\n})\n// 2f: a job waiting for input is the only one that needs a person — pin it first.\nconst activeRows = computed(() => [\n ...tray.activeJobs.value.filter(job => job.status === 'awaiting_input'),\n ...tray.activeJobs.value.filter(job => job.status !== 'awaiting_input'),\n])\nconst subtitle = computed(() => {\n const count = (status: JobState['status']) => tray.activeJobs.value.filter(job => job.status === status).length\n const parts = [\n [count('running'), 'running'],\n [count('queued'), 'queued'],\n [count('awaiting_input'), 'waiting'],\n ].filter(([n]) => n).map(([n, word]) => `${n} ${word}`)\n return parts.length ? parts.join(' · ') : `${tray.jobs.value.length} total`\n})\nconst canClear = computed(() => (\n tray.deletableJobs.value.length > 0\n && tray.canClearFinished.value\n))\n\nasync function openTray(): Promise<void> {\n tray.open()\n await nextTick()\n panelRef.value?.focus()\n}\n\nasync function closeTray(): Promise<void> {\n tray.close()\n await nextTick()\n triggerRef.value?.focus()\n}\n\nasync function toggleTray(): Promise<void> {\n if (tray.isOpen.value) await closeTray()\n else await openTray()\n}\n\nfunction onPanelKeydown(event: KeyboardEvent): void {\n if (event.key === 'Escape') {\n void closeTray()\n return\n }\n if (event.key !== 'Tab' || !panelRef.value) return\n const focusable = Array.from(panelRef.value.querySelectorAll<HTMLElement>(\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n ))\n if (focusable.length === 0) {\n event.preventDefault()\n panelRef.value.focus()\n return\n }\n const first = focusable[0]\n const last = focusable[focusable.length - 1]\n const activeElement = document.activeElement as HTMLElement | null\n if (!activeElement || !focusable.includes(activeElement)) {\n event.preventDefault()\n if (event.shiftKey) last?.focus()\n else first?.focus()\n } else if (event.shiftKey && activeElement === first) {\n event.preventDefault()\n last?.focus()\n } else if (!event.shiftKey && activeElement === last) {\n event.preventDefault()\n first?.focus()\n }\n}\n\nfunction progressText(job: JobState): string {\n if (job.status === 'awaiting_input') {\n return job.progress.message || 'Waiting for external input'\n }\n if (job.status === 'queued' && job.queue_position > 0) {\n return `Queue position ${job.queue_position}`\n }\n return job.progress.message || job.progress.stage || jobStatusLabel(job.status)\n}\n\nfunction formatTimestamp(value: string | null): string {\n if (!value) return ''\n const date = new Date(value)\n return Number.isNaN(date.getTime())\n ? ''\n : new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(date)\n}\n\nfunction statusClass(job: JobState): string {\n return `mint-jobs-tray__status--${job.status.replace('_', '-')}`\n}\n\nfunction jobPercent(job: JobState): number {\n return Math.round(normalizeJobPercent(job.progress.percent))\n}\n\nasync function cancelJob(job: JobState): Promise<void> {\n const succeeded = await tray.cancelJob(job)\n if (!succeeded && tray.error.value) emit('error', tray.error.value)\n}\n\nasync function deleteJob(job: JobState): Promise<void> {\n const succeeded = await tray.deleteJob(job)\n if (!succeeded && tray.error.value) emit('error', tray.error.value)\n}\n\nasync function clearFinished(): Promise<void> {\n const succeeded = await tray.clearFinished()\n if (!succeeded && tray.error.value) emit('error', tray.error.value)\n}\n</script>\n\n<template>\n <Teleport :to=\"teleportTarget\" :disabled=\"teleportTo === false\">\n <div class=\"mint-jobs-tray\">\n <button\n ref=\"triggerRef\"\n type=\"button\"\n class=\"mint-jobs-tray__trigger\"\n aria-haspopup=\"dialog\"\n :aria-expanded=\"tray.isOpen.value\"\n :aria-controls=\"panelId\"\n @click=\"toggleTray\"\n >\n <svg\n class=\"mint-jobs-tray__trigger-icon\"\n :class=\"{ 'mint-jobs-tray__trigger-icon--active': tray.activeJobs.value.length > 0 }\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path d=\"M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2\" />\n <path d=\"M9 5a3 3 0 0 1 6 0v2H9V5Z\" />\n </svg>\n <span class=\"mint-jobs-tray__trigger-label\">\n {{ tray.featuredJob.value ? jobStatusLabel(tray.featuredJob.value.status) : 'Jobs' }}\n </span>\n <span\n v-if=\"tray.activeJobs.value.length > 0\"\n class=\"mint-jobs-tray__count\"\n aria-live=\"polite\"\n >\n {{ tray.activeJobs.value.length }}\n </span>\n </button>\n\n <Transition name=\"mint-jobs-tray-fade\">\n <div\n v-if=\"tray.isOpen.value\"\n class=\"mint-jobs-tray__backdrop\"\n aria-hidden=\"true\"\n @click=\"closeTray\"\n />\n </Transition>\n\n <Transition name=\"mint-jobs-tray-slide\">\n <section\n v-if=\"tray.isOpen.value\"\n :id=\"panelId\"\n ref=\"panelRef\"\n class=\"mint-jobs-tray__panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-labelledby=\"titleId\"\n tabindex=\"-1\"\n @keydown=\"onPanelKeydown\"\n >\n <header class=\"mint-jobs-tray__header\">\n <div>\n <h2 :id=\"titleId\" class=\"mint-jobs-tray__title\">{{ title }}</h2>\n <p class=\"mint-jobs-tray__subtitle\" :title=\"subtitle\">{{ subtitle }}</p>\n </div>\n <div class=\"mint-jobs-tray__header-actions\">\n <button\n v-if=\"tray.canRefresh.value\"\n type=\"button\"\n class=\"mint-jobs-tray__icon-button\"\n aria-label=\"Refresh jobs\"\n :disabled=\"tray.isLoading.value\"\n @click=\"tray.refresh\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M20 6v5h-5M4 18v-5h5\" />\n <path d=\"M18.4 9A7 7 0 0 0 6.1 6.1L4 8m2 7a7 7 0 0 0 11.9 2.9L20 16\" />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"mint-jobs-tray__icon-button\"\n aria-label=\"Close jobs\"\n @click=\"closeTray\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path d=\"m6 6 12 12M18 6 6 18\" />\n </svg>\n </button>\n </div>\n </header>\n\n <div class=\"mint-jobs-tray__tabs\" role=\"tablist\" aria-label=\"Job groups\">\n <button\n type=\"button\"\n role=\"tab\"\n data-tab=\"active\"\n class=\"mint-jobs-tray__tab\"\n :class=\"{ 'mint-jobs-tray__tab--active': tray.activeTab.value === 'active' }\"\n :aria-selected=\"tray.activeTab.value === 'active'\"\n @click=\"tray.activeTab.value = 'active'\"\n >\n Active\n <span class=\"mint-jobs-tray__tab-count\">{{ tray.activeJobs.value.length }}</span>\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n data-tab=\"history\"\n class=\"mint-jobs-tray__tab\"\n :class=\"{ 'mint-jobs-tray__tab--active': tray.activeTab.value === 'history' }\"\n :aria-selected=\"tray.activeTab.value === 'history'\"\n @click=\"tray.activeTab.value = 'history'\"\n >\n History\n <span class=\"mint-jobs-tray__tab-count\">{{ tray.historyJobs.value.length }}</span>\n </button>\n </div>\n\n <p v-if=\"tray.error.value\" class=\"mint-jobs-tray__error\" role=\"alert\">\n {{ tray.error.value }}\n </p>\n\n <div class=\"mint-jobs-tray__body\">\n <template v-if=\"tray.activeTab.value === 'active'\">\n <p v-if=\"tray.activeJobs.value.length === 0\" class=\"mint-jobs-tray__empty\">\n No active jobs\n </p>\n <article\n v-for=\"job in activeRows\"\n :key=\"job.job_id\"\n class=\"mint-jobs-tray__job\"\n :class=\"{ 'mint-jobs-tray__job--awaiting-input': job.status === 'awaiting_input' }\"\n >\n <div class=\"mint-jobs-tray__job-heading\">\n <div class=\"mint-jobs-tray__job-identity\">\n <button\n type=\"button\"\n class=\"mint-jobs-tray__job-select\"\n @click=\"emit('select', job)\"\n >\n {{ job.name }}\n </button>\n <span class=\"mint-jobs-tray__kind\">{{ job.kind }}</span>\n </div>\n <span class=\"mint-jobs-tray__status\" :class=\"statusClass(job)\">\n <span class=\"mint-jobs-tray__status-dot\" aria-hidden=\"true\" />\n {{ jobStatusLabel(job.status) }}\n </span>\n <span v-if=\"job.status === 'running'\" class=\"mint-jobs-tray__percent\">{{ jobPercent(job) }}%</span>\n </div>\n <div\n v-if=\"job.status === 'running'\"\n class=\"mint-jobs-tray__progress\"\n role=\"progressbar\"\n aria-label=\"Job progress\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n :aria-valuenow=\"jobPercent(job)\"\n >\n <span :style=\"{ width: `${jobPercent(job)}%` }\" />\n </div>\n <div class=\"mint-jobs-tray__job-footer\">\n <span>{{ progressText(job) }}</span>\n <div class=\"mint-jobs-tray__job-actions\">\n <slot name=\"job-actions\" :job=\"job\" />\n <button\n v-if=\"job.can_cancel && tray.canCancel.value\"\n type=\"button\"\n class=\"mint-jobs-tray__action mint-jobs-tray__action--danger\"\n aria-label=\"Cancel job\"\n :disabled=\"tray.isPending(job)\"\n @click=\"cancelJob(job)\"\n >\n Cancel\n </button>\n <button\n v-if=\"job.can_delete && tray.canDelete.value\"\n type=\"button\"\n class=\"mint-jobs-tray__icon-button mint-jobs-tray__icon-button--danger\"\n aria-label=\"Delete job\"\n :disabled=\"tray.isPending(job)\"\n @click=\"deleteJob(job)\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M4 7h16M9 7V4h6v3m-8 0 1 13h8l1-13M10 11v5M14 11v5\" />\n </svg>\n </button>\n </div>\n </div>\n </article>\n </template>\n\n <template v-else>\n <div v-if=\"canClear\" class=\"mint-jobs-tray__clear-row\">\n <button\n type=\"button\"\n class=\"mint-jobs-tray__action mint-jobs-tray__action--danger\"\n @click=\"clearFinished\"\n >\n Clear finished\n </button>\n </div>\n <p v-if=\"tray.historyJobs.value.length === 0\" class=\"mint-jobs-tray__empty\">\n No job history\n </p>\n <article\n v-for=\"job in tray.historyJobs.value\"\n :key=\"job.job_id\"\n class=\"mint-jobs-tray__job\"\n >\n <div class=\"mint-jobs-tray__job-heading\">\n <div class=\"mint-jobs-tray__job-identity\">\n <button\n type=\"button\"\n class=\"mint-jobs-tray__job-select\"\n @click=\"emit('select', job)\"\n >\n {{ job.name }}\n </button>\n <span class=\"mint-jobs-tray__kind\">{{ job.kind }}</span>\n </div>\n <span class=\"mint-jobs-tray__status\" :class=\"statusClass(job)\">\n <span class=\"mint-jobs-tray__status-dot\" aria-hidden=\"true\" />\n {{ jobStatusLabel(job.status) }}\n </span>\n </div>\n <div class=\"mint-jobs-tray__job-footer\">\n <span>{{ formatTimestamp(job.completed_at || job.created_at) }}</span>\n <div class=\"mint-jobs-tray__job-actions\">\n <slot name=\"job-actions\" :job=\"job\" />\n <button\n v-if=\"job.can_delete && tray.canDelete.value\"\n type=\"button\"\n class=\"mint-jobs-tray__icon-button mint-jobs-tray__icon-button--danger\"\n aria-label=\"Delete job\"\n :disabled=\"tray.isPending(job)\"\n @click=\"deleteJob(job)\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M4 7h16M9 7V4h6v3m-8 0 1 13h8l1-13M10 11v5M14 11v5\" />\n </svg>\n </button>\n </div>\n </div>\n </article>\n </template>\n </div>\n </section>\n </Transition>\n </div>\n </Teleport>\n</template>\n","import {\n getCurrentScope,\n onScopeDispose,\n readonly,\n ref,\n toValue,\n watch,\n type MaybeRefOrGetter,\n type Ref,\n} from 'vue'\n\n/**\n * One event of the platform's realtime stream (`GET /api/events`).\n *\n * Platform events carry no entity data: refetch the entity through REST.\n * `type` is `resync` (refetch everything you show), the audit action of a\n * committed write, such as `experiment.update` or `analysis.artifact_create`,\n * or the `\"<plugin>.<event>\"` name of a plugin event, which carries `payload`.\n * Only `usePresence` receives `presence.changed`.\n */\nexport interface RealtimeEvent {\n type: string\n id?: string\n /**\n * `experiment:{id}`, `project:{id}`, `experiments`, `user:{id}`, or a plugin's\n * `plugin:{name}` / `plugin:{name}:experiment:{id}`; absent on `resync`.\n */\n topic?: string\n /** A plugin event: the JSON payload the plugin published (at most 16 KiB). */\n payload?: unknown\n /** Changed fields of an update, when the write names them. */\n fields?: string[] | null\n actor_id?: string | null\n request_id?: string | null\n at?: string\n /** `presence.changed`: the topic's viewers (at most 20, `editing` first). */\n viewers?: { user_id: string, activity: string }[]\n /** `presence.changed`: the number of viewers, also those beyond `viewers`. */\n count?: number\n}\n\nexport type RealtimeHandler = (event: RealtimeEvent) => void\n\nexport interface UseRealtimeReturn {\n /** True while the shared stream is open. */\n connected: Readonly<Ref<boolean>>\n /** Unsubscribe; also runs when the calling component or effect scope is disposed. */\n stop: () => void\n}\n\nconst EVENTS_URL = '/api/events'\nconst RECONNECT_BASE_MS = 1_000\nconst RECONNECT_MAX_MS = 30_000\n/** Merges the subscriptions of components that mount together into one connection. */\nconst TOPIC_DEBOUNCE_MS = 50\nconst RESYNC: RealtimeEvent = { type: 'resync' }\n\ninterface Listener {\n topics: () => string[]\n handler: RealtimeHandler\n /** Receives `presence.changed`; a write listener would refetch on each viewer change. */\n presence: boolean\n}\n\nconst listeners = new Set<Listener>()\n/** The last `presence.changed` of each topic of the open stream; the server sends one per topic at open. */\nconst presence = new Map<string, RealtimeEvent>()\nconst connected = ref(false)\nlet source: EventSource | null = null\nlet sourceTopics = ''\nlet failures = 0\nlet syncTimer: ReturnType<typeof setTimeout> | null = null\nlet retryTimer: ReturnType<typeof setTimeout> | null = null\n\nfunction dispatch(event: RealtimeEvent): void {\n const isPresence = event.type === 'presence.changed'\n if (isPresence && event.topic) presence.set(event.topic, event)\n for (const listener of [...listeners]) {\n if (isPresence && !listener.presence) continue\n if (event.type === 'resync' || (event.topic && listener.topics().includes(event.topic))) {\n listener.handler(event)\n }\n }\n}\n\nfunction close(): void {\n source?.close()\n source = null\n sourceTopics = ''\n presence.clear()\n connected.value = false\n}\n\nfunction open(topics: string): void {\n const stream = new EventSource(`${EVENTS_URL}?topics=${encodeURIComponent(topics)}`)\n source = stream\n sourceTopics = topics\n stream.onopen = () => {\n connected.value = true\n // The server subscribes only now: a write between a caller's REST load (or\n // the close of the previous stream) and this point sent no event, so every caller refetches.\n dispatch(RESYNC)\n failures = 0\n }\n stream.onmessage = (message) => {\n dispatch(JSON.parse(message.data) as RealtimeEvent)\n }\n stream.onerror = () => {\n // EventSource retries a dropped connection itself but gives up after an\n // HTTP error, so one backoff path handles both.\n if (source !== stream) return\n close()\n // A topic change during the wait does not shorten it: the retry reads the topics when it runs.\n if (syncTimer !== null) clearTimeout(syncTimer)\n syncTimer = null\n retryTimer = setTimeout(() => {\n retryTimer = null\n sync()\n }, Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * 2 ** failures))\n failures += 1\n }\n}\n\nfunction sync(): void {\n syncTimer = null\n const topics = [...new Set([...listeners].flatMap(listener => listener.topics()))].sort().join(',')\n if (source && topics === sourceTopics) return\n close()\n if (topics) open(topics)\n}\n\nfunction schedule(): void {\n if (retryTimer !== null) return\n if (syncTimer !== null) clearTimeout(syncTimer)\n syncTimer = setTimeout(sync, TOPIC_DEBOUNCE_MS)\n}\n\n/**\n * Subscribe to topics of the platform's realtime stream.\n *\n * Every caller in a tab shares one `EventSource`, opened on the union of\n * their topics and reopened when that union changes. `handler` receives the\n * write events of the caller's own topics (not `presence.changed`, which goes\n * to `usePresence`) plus every `resync`: one comes from the\n * server when this tab fell behind, and one follows each open of the stream:\n * the first open, each reconnect (backoff from 1 s to 30 s) and each reopen for\n * a changed topic union. On `resync`, refetch. The union of all callers' topics\n * in a tab may hold at most 50 topics; the platform refuses a larger stream.\n *\n * The stream authenticates with the session cookie, so it works in the\n * platform and in plugin frontends served by it. It exists only when the\n * platform's `realtime` feature flag is on; check that flag first.\n *\n * A plugin topic reaches a caller whose role may use the plugin; an\n * experiment plugin topic also needs a caller who may view the experiment.\n *\n * @example\n * useRealtime(() => [`experiment:${id.value}`], () => refetch())\n * @example\n * useRealtime(['plugin:drp'], (event) => {\n * if (event.type === 'drp.run.progress') progress.value = event.payload as RunProgress\n * })\n */\nexport function useRealtime(\n topics: MaybeRefOrGetter<string[]>,\n handler: RealtimeHandler,\n): UseRealtimeReturn {\n return subscribeRealtime(topics, handler, false)\n}\n\n/** The last `presence.changed` of `topic` while the open stream views it (for `usePresence`). */\nexport function currentPresence(topic: string): RealtimeEvent | undefined {\n return presence.get(topic)\n}\n\n/** `useRealtime`, and with `withPresence` also the `presence.changed` events (for `usePresence`). */\nexport function subscribeRealtime(\n topics: MaybeRefOrGetter<string[]>,\n handler: RealtimeHandler,\n withPresence: boolean,\n): UseRealtimeReturn {\n const listener: Listener = { topics: () => toValue(topics), handler, presence: withPresence }\n listeners.add(listener)\n const stopWatch = watch(() => listener.topics().join(','), () => schedule())\n schedule()\n\n function stop(): void {\n stopWatch()\n if (listeners.delete(listener)) schedule()\n }\n\n if (getCurrentScope()) onScopeDispose(stop)\n return { connected: readonly(connected), stop }\n}\n"],"mappings":";;;;;;;;;;AAgDA,SAAgB,wBACd,SAC+B;CAC/B,MAAM,OAAO,eAAyC;EACpD,MAAM,WAA4B,CAAC;EACnC,MAAM,yBAAS,IAAI,IAAY;EAC/B,KAAK,MAAM,UAAU,SACnB,KAAK,MAAM,OAAO,QAAQ,OAAO,IAAI,GAAG;GACtC,IAAI,OAAO,IAAI,IAAI,MAAM,GAAG,MAAM,qBAAqB,IAAI,MAAM;GACjE,OAAO,IAAI,IAAI,MAAM;GACrB,SAAS,KAAK,GAAG;EACnB;EAEF,OAAO;CACT,CAAC;CAED,SAAS,SAAS,OAAsC;EACtD,IAAI;EACJ,KAAK,MAAM,UAAU,SAAS;GAC5B,IAAI,CAAC,QAAQ,OAAO,IAAI,CAAC,CAAC,MAAK,QAAO,IAAI,WAAW,KAAK,GAAG;GAC7D,IAAI,OAAO,MAAM,qBAAqB,KAAK;GAC3C,QAAQ;EACV;EACA,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,0BAA0B,OAAO;EAC7D,OAAO;CACT;CAEA,MAAM,WAA0C,EAAE,KAAK;CACvD,IAAI,QAAQ,MAAK,WAAU,OAAO,OAAO,GACvC,SAAS,UAAU,YAAY;EAC7B,MAAM,QAAQ,IAAI,QAAQ,KAAI,WAAU,OAAO,UAAU,CAAC,CAAC;EAC3D,OAAO,KAAK;CACd;CAEF,IAAI,QAAQ,MAAK,WAAU,OAAO,MAAM,GACtC,SAAS,SAAS,OAAO,UAAU;EACjC,MAAM,QAAQ,SAAS,KAAK;EAC5B,IAAI,CAAC,MAAM,QAAQ,MAAM,IAAI,MAAM,OAAO,MAAM,mCAAmC;EACnF,OAAO,MAAM,OAAO,KAAK;CAC3B;CAEF,IAAI,QAAQ,SAAS,KAAK,QAAQ,OAAM,WAAU,OAAO,MAAM,GAC7D,SAAS,SAAS,OAAO,UAAU;EACjC,MAAM,QAAQ,SAAS,KAAK;EAC5B,IAAI,CAAC,MAAM,QAAQ,MAAM,IAAI,MAAM,OAAO,MAAM,iCAAiC;EACjF,OAAO,MAAM,OAAO,KAAK;CAC3B;CAKF,IAAI,QAAQ,SAAS,KAAK,QAAQ,OAAM,WAAU,OAAO,KAAK,GAC5D,SAAS,QAAQ,YAAY;EAC3B,MAAM,UAAU,MAAM,QAAQ,IAC5B,QAAQ,KAAI,WAAU,OAAO,MAAO,CAAC,CACvC;EACA,OAAO,QAAQ,OAAO,WAA6B,OAAO,WAAW,QAAQ,IACzE,QAAQ,QAAQ,OAAO,WAAW,QAAQ,QAAQ,CAAC,IACnD,KAAA;CACN;CAEF,IAAI,QAAQ,MAAK,WAAU,OAAO,OAAO,GACvC,SAAS,gBAAgB;EACvB,MAAM,SAAoB,CAAC;EAC3B,KAAK,MAAM,UAAU,SACnB,IAAI;GACF,OAAO,UAAU;EACnB,SAAS,OAAO;GACd,OAAO,KAAK,KAAK;EACnB;EAEF,IAAI,OAAO,WAAW,GAAG,MAAM,OAAO;EACtC,IAAI,OAAO,SAAS,GAAG,MAAM,IAAI,4BAA4B,MAAM;CACrE;CAEF,OAAO;AACT;AAEA,IAAM,8BAAN,cAA0C,MAAM;CAC9C;CACA;CAEA,YAAY,QAA4B;EACtC,MAAM,sCAAsC;EAC5C,KAAK,OAAO;EACZ,KAAK,QAAQ,OAAO;EACpB,KAAK,SAAS,OAAO,OAAO,CAAC,GAAG,MAAM,CAAC;CACzC;AACF;AAEA,SAAS,qBAAqB,OAAsB;CAClD,uBAAO,IAAI,MAAM,oDAAoD,OAAO;AAC9E;AAgCA,SAAS,aAAa,OAAgB,UAA0B;CAC9D,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAClD;;;;;;;AAQA,SAAgB,aACd,UAA+B,CAAC,GACZ;CACpB,MAAM,SAAS,IAAI,KAAK;CACxB,MAAM,YAAY,IAAuB,QAAQ;CACjD,MAAM,YAAY,IAAI,KAAK;CAC3B,MAAM,QAAQ,WAA0B,IAAI;CAC5C,MAAM,gBAAgB,oBAAI,IAAI,IAAY,CAAC;CAC3C,IAAI,oBAAoB;CACxB,IAAI,SAAS;CACb,MAAM,SAAS,eAAe,QAAQ,QAAQ,MAAM,CAAC;CAErD,MAAM,OAAO,eAA2B;EACtC,MAAM,gBAAgB,OAAO;EAC7B,OAAO,gBAAgB,CAAC,GAAG,QAAQ,cAAc,IAAI,CAAC,CAAC,CAAC,IAAI,iBAAiB,IAAI,CAAC;CACpF,CAAC;CACD,MAAM,aAAa,eAAe,KAAK,MAAM,QAAO,QAAO,kBAAkB,IAAI,MAAM,CAAC,CAAC;CACzF,MAAM,cAAc,eAAe,KAAK,MAAM,QAAO,QAAO,CAAC,kBAAkB,IAAI,MAAM,CAAC,CAAC;CAC3F,MAAM,gBAAgB,eAAe,YAAY,MAAM,QAAO,QAAO,IAAI,UAAU,CAAC;CACpF,MAAM,cAAc,eAAe,WAAW,MAAM,MAAM,YAAY,MAAM,MAAM,IAAI;CACtF,MAAM,aAAa,eAAe,QAAQ,OAAO,OAAO,OAAO,CAAC;CAChE,MAAM,YAAY,eAAe,QAAQ,OAAO,OAAO,MAAM,CAAC;CAC9D,MAAM,YAAY,eAAe,QAAQ,OAAO,OAAO,MAAM,CAAC;CAC9D,MAAM,mBAAmB,eAAe,QACtC,OAAO,OAAO,SAAS,OAAO,OAAO,MACvC,CAAC;CAED,SAAS,WAAW,OAAe,SAAwB;EACzD,MAAM,OAAO,IAAI,IAAI,cAAc,KAAK;EACxC,IAAI,SAAS,KAAK,IAAI,KAAK;OACtB,KAAK,OAAO,KAAK;EACtB,cAAc,QAAQ;CACxB;CAEA,eAAe,UAA4B;EACzC,MAAM,WAAW,OAAO,OAAO;EAC/B,IAAI,CAAC,UAAU,OAAO;EACtB,MAAM,aAAa,EAAE;EACrB,UAAU,QAAQ;EAClB,MAAM,QAAQ;EACd,IAAI;GACF,MAAM,SAAS;GACf,OAAO,UAAU,eAAe;EAClC,SAAS,OAAO;GACd,IAAI,UAAU,eAAe,mBAC3B,MAAM,QAAQ,aAAa,OAAO,qBAAqB;GAEzD,OAAO;EACT,UAAU;GACR,IAAI,UAAU,eAAe,mBAAmB,UAAU,QAAQ;EACpE;CACF;CAEA,eAAe,UAAU,KAAiC;EACxD,MAAM,eAAe,OAAO,OAAO;EACnC,IAAI,CAAC,IAAI,cAAc,CAAC,gBAAgB,cAAc,MAAM,IAAI,IAAI,MAAM,GACxE,OAAO;EAET,WAAW,IAAI,QAAQ,IAAI;EAC3B,MAAM,QAAQ;EACd,IAAI;GACF,MAAM,aAAa,IAAI,MAAM;GAC7B,OAAO;EACT,SAAS,OAAO;GACd,IAAI,QAAQ,MAAM,QAAQ,aAAa,OAAO,sBAAsB;GACpE,OAAO;EACT,UAAU;GACR,IAAI,QAAQ,WAAW,IAAI,QAAQ,KAAK;EAC1C;CACF;CAEA,eAAe,UAAU,KAAiC;EACxD,MAAM,eAAe,OAAO,OAAO;EACnC,IAAI,CAAC,IAAI,cAAc,CAAC,gBAAgB,cAAc,MAAM,IAAI,IAAI,MAAM,GACxE,OAAO;EAET,WAAW,IAAI,QAAQ,IAAI;EAC3B,MAAM,QAAQ;EACd,IAAI;GACF,MAAM,UAAU,MAAM,aAAa,IAAI,MAAM;GAC7C,OAAO,UAAU,YAAY;EAC/B,SAAS,OAAO;GACd,IAAI,QAAQ,MAAM,QAAQ,aAAa,OAAO,sBAAsB;GACpE,OAAO;EACT,UAAU;GACR,IAAI,QAAQ,WAAW,IAAI,QAAQ,KAAK;EAC1C;CACF;CAEA,eAAe,gBAAkC;EAC/C,MAAM,UAAU,cAAc;EAC9B,IAAI,QAAQ,WAAW,GAAG,OAAO;EACjC,MAAM,QAAQ;EACd,IAAI;GACF,MAAM,gBAAgB,OAAO;GAC7B,IAAI,eAAe,OAAO;IACxB,MAAM,UAAU,MAAM,cAAc,MAAM;IAC1C,IAAI,CAAC,QAAQ,OAAO;IACpB,OAAO,OAAO,YAAY,YAAY,YAAY,QAAQ;GAC5D;GACA,IAAI,CAAC,eAAe,QAAQ,OAAO;GAEnC,QAAO,MADe,QAAQ,IAAI,QAAQ,KAAI,QAAO,UAAU,GAAG,CAAC,CAAC,EAAA,CACrD,MAAM,OAAO;EAC9B,SAAS,OAAO;GACd,IAAI,QAAQ,MAAM,QAAQ,aAAa,OAAO,sBAAsB;GACpE,OAAO;EACT;CACF;CAEA,gBAAgB;EACd,IAAI,QAAQ,gBAAgB,OAAO,QAAa;CAClD,CAAC;CACD,kBAAkB;EAChB,SAAS;EACT,qBAAqB;CACvB,CAAC;CAED,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YAAY;GAAE,OAAO,QAAQ;EAAK;EAClC,aAAa;GAAE,OAAO,QAAQ;EAAM;EACpC,cAAc;GAAE,OAAO,QAAQ,CAAC,OAAO;EAAM;EAC7C;EACA;EACA;EACA;EACA,YAAW,QAAO,cAAc,MAAM,IAAI,IAAI,MAAM;CACtD;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECvSA,MAAM,QAAQ;EAMd,MAAM,OAAO;EAUb,MAAM,UAAU,kBAAkB,MAAM,CAAC,CAAC,QAAQ,MAAM,EAAE;EAC1D,MAAM,UAAU,GAAG,QAAQ;EAC3B,MAAM,aAAa,IAA8B,IAAI;EACrD,MAAM,WAAW,IAAwB,IAAI;EAC7C,MAAM,iBAAiB,eAAe,MAAM,cAAc,MAAM;EAChE,MAAM,OAAO,aAAa;GACxB,QAAQ,MAAM,OAAO,QAAQ;GAC7B,aAAa,MAAM;EACrB,CAAC;EAED,MAAM,aAAa,eAAe,CAChC,GAAG,KAAK,WAAW,MAAM,QAAO,QAAO,IAAI,WAAW,gBAAgB,GACtE,GAAG,KAAK,WAAW,MAAM,QAAO,QAAO,IAAI,WAAW,gBAAgB,CACxE,CAAC;EACD,MAAM,WAAW,eAAe;GAC9B,MAAM,SAAS,WAA+B,KAAK,WAAW,MAAM,QAAO,QAAO,IAAI,WAAW,MAAM,CAAC,CAAC;GACzG,MAAM,QAAQ;IACZ,CAAC,MAAM,SAAS,GAAG,SAAS;IAC5B,CAAC,MAAM,QAAQ,GAAG,QAAQ;IAC1B,CAAC,MAAM,gBAAgB,GAAG,SAAS;GACrC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,UAAU,GAAG,EAAE,GAAG,MAAM;GACtD,OAAO,MAAM,SAAS,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,KAAK,MAAM,OAAO;EACtE,CAAC;EACD,MAAM,WAAW,eACf,KAAK,cAAc,MAAM,SAAS,KAC/B,KAAK,iBAAiB,KAC1B;EAED,eAAe,WAA0B;GACvC,KAAK,KAAK;GACV,MAAM,SAAS;GACf,SAAS,OAAO,MAAM;EACxB;EAEA,eAAe,YAA2B;GACxC,KAAK,MAAM;GACX,MAAM,SAAS;GACf,WAAW,OAAO,MAAM;EAC1B;EAEA,eAAe,aAA4B;GACzC,IAAI,KAAK,OAAO,OAAO,MAAM,UAAU;QAClC,MAAM,SAAS;EACtB;EAEA,SAAS,eAAe,OAA4B;GAClD,IAAI,MAAM,QAAQ,UAAU;IAC1B,UAAe;IACf;GACF;GACA,IAAI,MAAM,QAAQ,SAAS,CAAC,SAAS,OAAO;GAC5C,MAAM,YAAY,MAAM,KAAK,SAAS,MAAM,iBAC1C,4IACF,CAAC;GACD,IAAI,UAAU,WAAW,GAAG;IAC1B,MAAM,eAAe;IACrB,SAAS,MAAM,MAAM;IACrB;GACF;GACA,MAAM,QAAQ,UAAU;GACxB,MAAM,OAAO,UAAU,UAAU,SAAS;GAC1C,MAAM,gBAAgB,SAAS;GAC/B,IAAI,CAAC,iBAAiB,CAAC,UAAU,SAAS,aAAa,GAAG;IACxD,MAAM,eAAe;IACrB,IAAI,MAAM,UAAU,MAAM,MAAM;SAC3B,OAAO,MAAM;GACpB,OAAO,IAAI,MAAM,YAAY,kBAAkB,OAAO;IACpD,MAAM,eAAe;IACrB,MAAM,MAAM;GACd,OAAO,IAAI,CAAC,MAAM,YAAY,kBAAkB,MAAM;IACpD,MAAM,eAAe;IACrB,OAAO,MAAM;GACf;EACF;EAEA,SAAS,aAAa,KAAuB;GAC3C,IAAI,IAAI,WAAW,kBACjB,OAAO,IAAI,SAAS,WAAW;GAEjC,IAAI,IAAI,WAAW,YAAY,IAAI,iBAAiB,GAClD,OAAO,kBAAkB,IAAI;GAE/B,OAAO,IAAI,SAAS,WAAW,IAAI,SAAS,SAAS,eAAe,IAAI,MAAM;EAChF;EAEA,SAAS,gBAAgB,OAA8B;GACrD,IAAI,CAAC,OAAO,OAAO;GACnB,MAAM,OAAO,IAAI,KAAK,KAAK;GAC3B,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,IAC9B,KACA,IAAI,KAAK,eAAe,KAAA,GAAW;IAAE,WAAW;IAAU,WAAW;GAAQ,CAAC,CAAC,CAAC,OAAO,IAAI;EACjG;EAEA,SAAS,YAAY,KAAuB;GAC1C,OAAO,2BAA2B,IAAI,OAAO,QAAQ,KAAK,GAAG;EAC/D;EAEA,SAAS,WAAW,KAAuB;GACzC,OAAO,KAAK,MAAM,oBAAoB,IAAI,SAAS,OAAO,CAAC;EAC7D;EAEA,eAAe,UAAU,KAA8B;GAErD,IAAI,CAAC,MADmB,KAAK,UAAU,GAAG,KACxB,KAAK,MAAM,OAAO,KAAK,SAAS,KAAK,MAAM,KAAK;EACpE;EAEA,eAAe,UAAU,KAA8B;GAErD,IAAI,CAAC,MADmB,KAAK,UAAU,GAAG,KACxB,KAAK,MAAM,OAAO,KAAK,SAAS,KAAK,MAAM,KAAK;EACpE;EAEA,eAAe,gBAA+B;GAE5C,IAAI,CAAC,MADmB,KAAK,cAAc,KACzB,KAAK,MAAM,OAAO,KAAK,SAAS,KAAK,MAAM,KAAK;EACpE;;GAIE,OAAA,UAAA,GAAA,YAsPW,UAAA;IAtPA,IAAI,eAAA;IAAiB,UAAU,QAAA,eAAU;GAClD,GAAA,CAAA,mBAoPM,OApPN,YAoPM;IAnPJ,mBA8BS,UAAA;KA7BH,SAAA;KAAJ,KAAI;KACJ,MAAK;KACL,OAAM;KACN,iBAAc;KACb,iBAAe,MAAA,IAAA,CAAI,CAAC,OAAO;KAC3B,iBAAe;KACf,SAAO;;MAER,UAAA,GAAA,mBAUM,OAAA;MATJ,OAAK,eAAA,CAAC,gCAA8B,EAAA,wCACc,MAAA,IAAA,CAAI,CAAC,WAAW,MAAM,SAAM,EAAA,CAAA,CAAA;MAC9E,SAAQ;MACR,MAAK;MACL,QAAO;MACP,eAAY;KAEZ,GAAA,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAA,mBAAsF,QAAA,EAAhF,GAAE,4EAA2E,GAAA,MAAA,EAAA,GACnF,mBAAsC,QAAA,EAAhC,GAAE,4BAA2B,GAAA,MAAA,EAAA,CAAA,EAAA,GAAA,CAAA;KAErC,mBAEO,QAFP,YAEO,gBADF,MAAA,IAAA,CAAI,CAAC,YAAY,QAAQ,MAAA,cAAA,CAAc,CAAC,MAAA,IAAA,CAAI,CAAC,YAAY,MAAM,MAAM,IAAA,MAAA,GAAA,CAAA;KAGlE,MAAA,IAAA,CAAI,CAAC,WAAW,MAAM,SAAM,KADpC,UAAA,GAAA,mBAMO,QANP,YAMO,gBADF,MAAA,IAAA,CAAI,CAAC,WAAW,MAAM,MAAM,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;;IAInC,YAOa,YAAA,EAPD,MAAK,sBAAqB,GAAA;KACpC,SAAA,cAKE,CAJM,MAAA,IAAA,CAAI,CAAC,OAAO,SADpB,UAAA,GAAA,mBAKE,OAAA;;MAHA,OAAM;MACN,eAAY;MACX,SAAO;;;;IAIZ,YAyMa,YAAA,EAzMD,MAAK,uBAAsB,GAAA;KACrC,SAAA,cAuMU,CAtMF,MAAA,IAAA,CAAI,CAAC,OAAO,SADpB,UAAA,GAAA,mBAuMU,WAAA;;MArMP,IAAI;MACD,SAAA;MAAJ,KAAI;MACJ,OAAM;MACN,MAAK;MACL,cAAW;MACV,mBAAiB;MAClB,UAAS;MACR,WAAS;;MAEV,mBA8BS,UA9BT,YA8BS,CA7BP,mBAGM,OAAA,MAAA,CAFJ,mBAAgE,MAAA;OAA3D,IAAI;OAAS,OAAM;MAA2B,GAAA,gBAAA,QAAA,KAAK,GAAA,CAAA,GACxD,mBAAwE,KAAA;OAArE,OAAM;OAA4B,OAAO,SAAA;MAAa,GAAA,gBAAA,SAAA,KAAQ,GAAA,GAAA,UAAA,CAAA,CAAA,GAEnE,mBAwBM,OAxBN,YAwBM,CAtBI,MAAA,IAAA,CAAI,CAAC,WAAW,SADxB,UAAA,GAAA,mBAYS,UAAA;;OAVP,MAAK;OACL,OAAM;OACN,cAAW;OACV,UAAU,MAAA,IAAA,CAAI,CAAC,UAAU;OACzB,SAAK,OAAA,OAAA,OAAA,MAAE,GAAA,SAAA,MAAA,IAAA,CAAI,CAAC,WAAL,MAAA,IAAA,CAAI,CAAC,QAAO,GAAA,IAAA;MAEpB,GAAA,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAA,mBAGM,OAAA;OAHD,SAAQ;OAAY,MAAK;OAAO,QAAO;OAAe,eAAY;MACrE,GAAA,CAAA,mBAAiC,QAAA,EAA3B,GAAE,uBAAsB,CAAA,GAC9B,mBAAuE,QAAA,EAAjE,GAAE,6DAA4D,CAAA,CAAA,GAAA,EAAA,CAAA,EAAA,GAAA,GAAA,UAAA,KAAA,mBAAA,IAAA,IAAA,GAGxE,mBASS,UAAA;OARP,MAAK;OACL,OAAM;OACN,cAAW;OACV,SAAO;MAER,GAAA,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAA,mBAEM,OAAA;OAFD,SAAQ;OAAY,MAAK;OAAO,QAAO;OAAe,eAAY;MACrE,GAAA,CAAA,mBAAiC,QAAA,EAA3B,GAAE,uBAAsB,CAAA,CAAA,GAAA,EAAA,CAAA,EAAA,CAAA,CAAA,CAAA,CAAA,CAAA;MAMtC,mBAyBM,OAzBN,YAyBM,CAxBJ,mBAWS,UAAA;OAVP,MAAK;OACL,MAAK;OACL,YAAS;OACT,OAAK,eAAA,CAAC,uBAAqB,EAAA,+BACc,MAAA,IAAA,CAAI,CAAC,UAAU,UAAK,SAAA,CAAA,CAAA;OAC5D,iBAAe,MAAA,IAAA,CAAI,CAAC,UAAU,UAAK;OACnC,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,MAAA,IAAA,CAAI,CAAC,UAAU,QAAK;MAC7B,GAAA,CAAA,OAAA,OAAA,OAAA,KAAA,gBAAA,YAEC,EAAA,IAAA,mBAAiF,QAAjF,aAAiF,gBAAtC,MAAA,IAAA,CAAI,CAAC,WAAW,MAAM,MAAM,GAAA,CAAA,CAAA,GAAA,IAAA,WAAA,GAEzE,mBAWS,UAAA;OAVP,MAAK;OACL,MAAK;OACL,YAAS;OACT,OAAK,eAAA,CAAC,uBAAqB,EAAA,+BACc,MAAA,IAAA,CAAI,CAAC,UAAU,UAAK,UAAA,CAAA,CAAA;OAC5D,iBAAe,MAAA,IAAA,CAAI,CAAC,UAAU,UAAK;OACnC,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,MAAA,IAAA,CAAI,CAAC,UAAU,QAAK;MAC7B,GAAA,CAAA,OAAA,OAAA,OAAA,KAAA,gBAAA,aAEC,EAAA,IAAA,mBAAkF,QAAlF,aAAkF,gBAAvC,MAAA,IAAA,CAAI,CAAC,YAAY,MAAM,MAAM,GAAA,CAAA,CAAA,GAAA,IAAA,WAAA,CAAA,CAAA;MAInE,MAAA,IAAA,CAAI,CAAC,MAAM,SAApB,UAAA,GAAA,mBAEI,KAFJ,aAEI,gBADC,MAAA,IAAA,CAAI,CAAC,MAAM,KAAK,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;MAGrB,mBA4HM,OA5HN,aA4HM,CA3HY,MAAA,IAAA,CAAI,CAAC,UAAU,UAAK,YAApC,UAAA,GAAA,mBAmEW,UAAA,EAAA,KAAA,EAAA,GAAA,CAlEA,MAAA,IAAA,CAAI,CAAC,WAAW,MAAM,WAAM,KAArC,UAAA,GAAA,mBAEI,KAFJ,aAA2E,kBAE3E,KAAA,mBAAA,IAAA,IAAA,IACA,UAAA,IAAA,GAAA,mBA8DU,UAAA,MAAA,WA7DM,WAAA,QAAP,QAAG;OADZ,OAAA,UAAA,GAAA,mBA8DU,WAAA;QA5DP,KAAK,IAAI;QACV,OAAK,eAAA,CAAC,uBAAqB,EAAA,uCACsB,IAAI,WAAM,iBAAA,CAAA,CAAA;;QAE3D,mBAgBM,OAhBN,aAgBM;SAfJ,mBASM,OATN,aASM,CARJ,mBAMS,UAAA;UALP,MAAK;UACL,OAAM;UACL,UAAK,WAAE,KAAI,UAAW,GAAG;SAEvB,GAAA,gBAAA,IAAI,IAAI,GAAA,GAAA,WAAA,GAEb,mBAAwD,QAAxD,aAAwD,gBAAlB,IAAI,IAAI,GAAA,CAAA,CAAA,CAAA;SAEhD,mBAGO,QAAA,EAHD,OAAK,eAAA,CAAC,0BAAiC,YAAY,GAAG,CAAA,CAAA,EAAA,GAAA,CAC1D,OAAA,OAAA,OAAA,KAAA,mBAA8D,QAAA;UAAxD,OAAM;UAA6B,eAAY;SAAS,GAAA,MAAA,EAAA,IAAA,gBAAA,MAC9D,gBAAG,MAAA,cAAA,CAAc,CAAC,IAAI,MAAM,CAAA,GAAA,CAAA,CAAA,GAAA,CAAA;SAElB,IAAI,WAAM,aAAtB,UAAA,GAAA,mBAAmG,QAAnG,aAAmG,gBAA1B,WAAW,GAAG,CAAA,IAAI,KAAC,CAAA,KAAA,mBAAA,IAAA,IAAA;;QAGtF,IAAI,WAAM,aADlB,UAAA,GAAA,mBAUM,OAAA;;SARJ,OAAM;SACN,MAAK;SACL,cAAW;SACX,iBAAc;SACd,iBAAc;SACb,iBAAe,WAAW,GAAG;QAE9B,GAAA,CAAA,mBAAkD,QAAA,EAA3C,OAAK,eAAA,EAAA,OAAA,GAAc,WAAW,GAAG,EAAA,GAAA,CAAA,EAAA,GAAA,MAAA,CAAA,CAAA,GAAA,GAAA,WAAA,KAAA,mBAAA,IAAA,IAAA;QAE1C,mBA2BM,OA3BN,aA2BM,CA1BJ,mBAAoC,QAAA,MAAA,gBAA3B,aAAa,GAAG,CAAA,GAAA,CAAA,GACzB,mBAwBM,OAxBN,aAwBM;SAvBJ,WAAsC,KAAA,QAAA,eAAA,EAAP,IAAG,CAAA;SAE1B,IAAI,cAAc,MAAA,IAAA,CAAI,CAAC,UAAU,SADzC,UAAA,GAAA,mBASS,UAAA;;UAPP,MAAK;UACL,OAAM;UACN,cAAW;UACV,UAAU,MAAA,IAAA,CAAI,CAAC,UAAU,GAAG;UAC5B,UAAK,WAAE,UAAU,GAAG;SACtB,GAAA,YAED,GAAA,WAAA,KAAA,mBAAA,IAAA,IAAA;SAEQ,IAAI,cAAc,MAAA,IAAA,CAAI,CAAC,UAAU,SADzC,UAAA,GAAA,mBAWS,UAAA;;UATP,MAAK;UACL,OAAM;UACN,cAAW;UACV,UAAU,MAAA,IAAA,CAAI,CAAC,UAAU,GAAG;UAC5B,UAAK,WAAE,UAAU,GAAG;SAErB,GAAA,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAA,mBAEM,OAAA;UAFD,SAAQ;UAAY,MAAK;UAAO,QAAO;UAAe,eAAY;SACrE,GAAA,CAAA,mBAA+D,QAAA,EAAzD,GAAE,qDAAoD,CAAA,CAAA,GAAA,EAAA,CAAA,EAAA,GAAA,GAAA,WAAA,KAAA,mBAAA,IAAA,IAAA;;;MAQxE,CAAA,GAAA,GAAA,EAAA,GAAA,EAAA,MAAA,UAAA,GAAA,mBAqDW,UAAA,EAAA,KAAA,EAAA,GAAA;OApDE,SAAA,SAAX,UAAA,GAAA,mBAQM,OARN,aAQM,CAPJ,mBAMS,UAAA;QALP,MAAK;QACL,OAAM;QACL,SAAO;OACT,GAAA,kBAED,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;OAEO,MAAA,IAAA,CAAI,CAAC,YAAY,MAAM,WAAM,KAAtC,UAAA,GAAA,mBAEI,KAFJ,aAA4E,kBAE5E,KAAA,mBAAA,IAAA,IAAA;QACA,UAAA,IAAA,GAAA,mBAuCU,UAAA,MAAA,WAtCM,MAAA,IAAA,CAAI,CAAC,YAAY,QAAxB,QAAG;QADZ,OAAA,UAAA,GAAA,mBAuCU,WAAA;SArCP,KAAK,IAAI;SACV,OAAM;QAEN,GAAA,CAAA,mBAeM,OAfN,aAeM,CAdJ,mBASM,OATN,aASM,CARJ,mBAMS,UAAA;SALP,MAAK;SACL,OAAM;SACL,UAAK,WAAE,KAAI,UAAW,GAAG;QAEvB,GAAA,gBAAA,IAAI,IAAI,GAAA,GAAA,WAAA,GAEb,mBAAwD,QAAxD,aAAwD,gBAAlB,IAAI,IAAI,GAAA,CAAA,CAAA,CAAA,GAEhD,mBAGO,QAAA,EAHD,OAAK,eAAA,CAAC,0BAAiC,YAAY,GAAG,CAAA,CAAA,EAAA,GAAA,CAC1D,OAAA,QAAA,OAAA,MAAA,mBAA8D,QAAA;SAAxD,OAAM;SAA6B,eAAY;QAAS,GAAA,MAAA,EAAA,IAAA,gBAAA,MAC9D,gBAAG,MAAA,cAAA,CAAc,CAAC,IAAI,MAAM,CAAA,GAAA,CAAA,CAAA,GAAA,CAAA,CAAA,CAAA,GAGhC,mBAiBM,OAjBN,aAiBM,CAhBJ,mBAAsE,QAAA,MAAA,gBAA7D,gBAAgB,IAAI,gBAAgB,IAAI,UAAU,CAAA,GAAA,CAAA,GAC3D,mBAcM,OAdN,aAcM,CAbJ,WAAsC,KAAA,QAAA,eAAA,EAAP,IAAG,CAAA,GAE1B,IAAI,cAAc,MAAA,IAAA,CAAI,CAAC,UAAU,SADzC,UAAA,GAAA,mBAWS,UAAA;;SATP,MAAK;SACL,OAAM;SACN,cAAW;SACV,UAAU,MAAA,IAAA,CAAI,CAAC,UAAU,GAAG;SAC5B,UAAK,WAAE,UAAU,GAAG;QAErB,GAAA,CAAA,GAAA,OAAA,QAAA,OAAA,MAAA,CAAA,mBAEM,OAAA;SAFD,SAAQ;SAAY,MAAK;SAAO,QAAO;SAAe,eAAY;QACrE,GAAA,CAAA,mBAA+D,QAAA,EAAzD,GAAE,qDAAoD,CAAA,CAAA,GAAA,EAAA,CAAA,EAAA,GAAA,GAAA,WAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;;;;;;;;;;;;AE3VpF,IAAM,aAAa;AACnB,IAAM,oBAAoB;AAC1B,IAAM,mBAAmB;;AAEzB,IAAM,oBAAoB;AAC1B,IAAM,SAAwB,EAAE,MAAM,SAAS;AAS/C,IAAM,4BAAY,IAAI,IAAc;;AAEpC,IAAM,2BAAW,IAAI,IAA2B;AAChD,IAAM,YAAY,IAAI,KAAK;AAC3B,IAAI,SAA6B;AACjC,IAAI,eAAe;AACnB,IAAI,WAAW;AACf,IAAI,YAAkD;AACtD,IAAI,aAAmD;AAEvD,SAAS,SAAS,OAA4B;CAC5C,MAAM,aAAa,MAAM,SAAS;CAClC,IAAI,cAAc,MAAM,OAAO,SAAS,IAAI,MAAM,OAAO,KAAK;CAC9D,KAAK,MAAM,YAAY,CAAC,GAAG,SAAS,GAAG;EACrC,IAAI,cAAc,CAAC,SAAS,UAAU;EACtC,IAAI,MAAM,SAAS,YAAa,MAAM,SAAS,SAAS,OAAO,CAAC,CAAC,SAAS,MAAM,KAAK,GACnF,SAAS,QAAQ,KAAK;CAE1B;AACF;AAEA,SAAS,QAAc;CACrB,QAAQ,MAAM;CACd,SAAS;CACT,eAAe;CACf,SAAS,MAAM;CACf,UAAU,QAAQ;AACpB;AAEA,SAAS,KAAK,QAAsB;CAClC,MAAM,SAAS,IAAI,YAAY,GAAG,WAAW,UAAU,mBAAmB,MAAM,GAAG;CACnF,SAAS;CACT,eAAe;CACf,OAAO,eAAe;EACpB,UAAU,QAAQ;EAGlB,SAAS,MAAM;EACf,WAAW;CACb;CACA,OAAO,aAAa,YAAY;EAC9B,SAAS,KAAK,MAAM,QAAQ,IAAI,CAAkB;CACpD;CACA,OAAO,gBAAgB;EAGrB,IAAI,WAAW,QAAQ;EACvB,MAAM;EAEN,IAAI,cAAc,MAAM,aAAa,SAAS;EAC9C,YAAY;EACZ,aAAa,iBAAiB;GAC5B,aAAa;GACb,KAAK;EACP,GAAG,KAAK,IAAI,kBAAkB,oBAAoB,KAAK,QAAQ,CAAC;EAChE,YAAY;CACd;AACF;AAEA,SAAS,OAAa;CACpB,YAAY;CACZ,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC,SAAQ,aAAY,SAAS,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG;CAClG,IAAI,UAAU,WAAW,cAAc;CACvC,MAAM;CACN,IAAI,QAAQ,KAAK,MAAM;AACzB;AAEA,SAAS,WAAiB;CACxB,IAAI,eAAe,MAAM;CACzB,IAAI,cAAc,MAAM,aAAa,SAAS;CAC9C,YAAY,WAAW,MAAM,iBAAiB;AAChD;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,YACd,QACA,SACmB;CACnB,OAAO,kBAAkB,QAAQ,SAAS,KAAK;AACjD;;AAGA,SAAgB,gBAAgB,OAA0C;CACxE,OAAO,SAAS,IAAI,KAAK;AAC3B;;AAGA,SAAgB,kBACd,QACA,SACA,cACmB;CACnB,MAAM,WAAqB;EAAE,cAAc,QAAQ,MAAM;EAAG;EAAS,UAAU;CAAa;CAC5F,UAAU,IAAI,QAAQ;CACtB,MAAM,YAAY,YAAY,SAAS,OAAO,CAAC,CAAC,KAAK,GAAG,SAAS,SAAS,CAAC;CAC3E,SAAS;CAET,SAAS,OAAa;EACpB,UAAU;EACV,IAAI,UAAU,OAAO,QAAQ,GAAG,SAAS;CAC3C;CAEA,IAAI,gBAAgB,GAAG,eAAe,IAAI;CAC1C,OAAO;EAAE,WAAW,SAAS,SAAS;EAAG;CAAK;AAChD"}