@elabs-ai/components-process 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/core/index.d.ts +1029 -0
- package/dist/core/index.js +1553 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/process-worker.js +462 -0
- package/dist/core/process-worker.js.map +1 -0
- package/dist/index.d.ts +1153 -0
- package/dist/index.js +3146 -0
- package/dist/index.js.map +1 -0
- package/dist/test/index.d.ts +196 -0
- package/dist/test/index.js +527 -0
- package/dist/test/index.js.map +1 -0
- package/package.json +80 -0
- package/src/abstraction-controls/abstraction-controls-fixtures.ts +86 -0
- package/src/abstraction-controls/abstraction-controls.stories.tsx +188 -0
- package/src/abstraction-controls/abstraction-controls.test.tsx +226 -0
- package/src/abstraction-controls/abstraction-controls.tsx +288 -0
- package/src/abstraction-controls/auto-abstraction.test.ts +196 -0
- package/src/abstraction-controls/auto-abstraction.ts +128 -0
- package/src/abstraction-controls/index.ts +4 -0
- package/src/core/abstract-graph.test.ts +209 -0
- package/src/core/abstract-graph.ts +407 -0
- package/src/core/adapters/csv.test.ts +131 -0
- package/src/core/adapters/csv.ts +146 -0
- package/src/core/adapters/flat.test.ts +149 -0
- package/src/core/adapters/flat.ts +168 -0
- package/src/core/aggregate-performance.test.ts +208 -0
- package/src/core/aggregate-performance.ts +200 -0
- package/src/core/detect-rework.test.ts +134 -0
- package/src/core/detect-rework.ts +100 -0
- package/src/core/discover-graph.test.ts +378 -0
- package/src/core/discover-graph.ts +202 -0
- package/src/core/duration-stats.test.ts +116 -0
- package/src/core/duration-stats.ts +162 -0
- package/src/core/event-log.test.ts +224 -0
- package/src/core/event-log.ts +244 -0
- package/src/core/extract-variants.test.ts +126 -0
- package/src/core/extract-variants.ts +140 -0
- package/src/core/filter-log.test.ts +193 -0
- package/src/core/filter-log.ts +215 -0
- package/src/core/fixtures/generate-bpi-2012-subset.test.ts +50 -0
- package/src/core/fixtures/generate-bpi-2012-subset.ts +216 -0
- package/src/core/fixtures/generate-bpi-2012-subset.write.ts +40 -0
- package/src/core/fixtures/order-to-cash-small.json +200 -0
- package/src/core/fixtures/synthetic-log.test.ts +109 -0
- package/src/core/fixtures/synthetic-log.ts +167 -0
- package/src/core/index.ts +118 -0
- package/src/core/reconcile-graph.test.ts +175 -0
- package/src/core/reconcile-graph.ts +107 -0
- package/src/core/scale.test.ts +80 -0
- package/src/core/scale.ts +100 -0
- package/src/core/types.ts +151 -0
- package/src/core/worker/create-process-worker.test.ts +255 -0
- package/src/core/worker/create-process-worker.ts +211 -0
- package/src/core/worker/process-worker.ts +80 -0
- package/src/index.ts +29 -0
- package/src/metric-layer-switch/index.ts +6 -0
- package/src/metric-layer-switch/metric-layer-switch.stories.tsx +131 -0
- package/src/metric-layer-switch/metric-layer-switch.test.tsx +102 -0
- package/src/metric-layer-switch/metric-layer-switch.tsx +276 -0
- package/src/process-explorer.stories.tsx +392 -0
- package/src/process-kpi-strip/index.ts +6 -0
- package/src/process-kpi-strip/process-kpi-strip.stories.tsx +128 -0
- package/src/process-kpi-strip/process-kpi-strip.test.tsx +106 -0
- package/src/process-kpi-strip/process-kpi-strip.tsx +237 -0
- package/src/process-map/index.ts +13 -0
- package/src/process-map/map-model.test.ts +326 -0
- package/src/process-map/map-model.ts +873 -0
- package/src/process-map/process-activity-node.tsx +200 -0
- package/src/process-map/process-map-context.ts +71 -0
- package/src/process-map/process-map.stories.tsx +673 -0
- package/src/process-map/process-map.test.tsx +523 -0
- package/src/process-map/process-map.tsx +979 -0
- package/src/process-map/process-transition-edge.test.tsx +160 -0
- package/src/process-map/process-transition-edge.tsx +151 -0
- package/src/process-map/use-process-layout.test.tsx +265 -0
- package/src/process-map/use-process-layout.ts +315 -0
- package/src/test/contract.test.ts +99 -0
- package/src/test/contract.ts +118 -0
- package/src/test/doubles.test.tsx +51 -0
- package/src/test/doubles.tsx +82 -0
- package/src/test/index.ts +34 -0
- package/src/test/primitives.tsx +35 -0
- package/src/use-process-explorer/index.ts +8 -0
- package/src/use-process-explorer/use-process-explorer.test.ts +564 -0
- package/src/use-process-explorer/use-process-explorer.ts +540 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/scale.ts","../src/core/duration-stats.ts","../src/core/event-log.ts","../src/core/extract-variants.ts","../src/core/discover-graph.ts","../src/core/aggregate-performance.ts","../src/process-map/map-model.ts","../src/process-map/process-map.tsx","../src/core/abstract-graph.ts","../src/core/detect-rework.ts","../src/process-map/process-activity-node.tsx","../src/process-map/process-map-context.ts","../src/process-map/process-transition-edge.tsx","../src/process-map/use-process-layout.ts","../src/abstraction-controls/abstraction-controls.tsx","../src/abstraction-controls/auto-abstraction.ts","../src/metric-layer-switch/metric-layer-switch.tsx","../src/process-kpi-strip/process-kpi-strip.tsx","../src/use-process-explorer/use-process-explorer.ts","../src/core/filter-log.ts","../src/core/reconcile-graph.ts","../src/core/worker/process-worker.ts","../src/core/worker/create-process-worker.ts"],"sourcesContent":["/**\n * Visual-scale helpers shared by every process view — RM-049.\n *\n * One implementation, so RM-051's edge width, RM-054's coverage bar and RM-050's\n * abstraction threshold all agree on what \"the 90th percentile\" and \"map this count to a\n * stroke width\" mean. Re-deriving a quantile per component is how two views end up\n * disagreeing about the same log.\n *\n * All three are pure and total: they never throw, and they answer for the degenerate\n * inputs (empty array, zero-width domain) rather than returning `NaN`.\n */\n\n/** Ascending numeric comparator. Extracted so every sort in `/core` uses the same one. */\nexport function ascending(a: number, b: number): number {\n return a - b;\n}\n\n/** Only finite samples take part in a scale; `NaN`/`Infinity` are dropped, not propagated. */\nfunction finiteOnly(values: readonly number[]): number[] {\n const out: number[] = [];\n for (const v of values) if (Number.isFinite(v)) out.push(v);\n return out;\n}\n\n/**\n * The extent of `values`, ignoring non-finite entries.\n *\n * @returns `[min, max]`, or `[0, 0]` when there is nothing finite to measure.\n */\nexport function minMax(values: readonly number[]): [number, number] {\n let min = Number.POSITIVE_INFINITY;\n let max = Number.NEGATIVE_INFINITY;\n let seen = false;\n for (const v of values) {\n if (!Number.isFinite(v)) continue;\n seen = true;\n if (v < min) min = v;\n if (v > max) max = v;\n }\n return seen ? [min, max] : [0, 0];\n}\n\n/**\n * The `q`-quantile of an ALREADY ASCENDING-SORTED, all-finite array, by linear\n * interpolation between order statistics (the R-7 / `d3.quantile` definition).\n *\n * Exported for callers that already hold a sorted array — {@link durationStats} sorts\n * once and reads several quantiles off it. Use {@link quantile} when the input is not\n * known to be sorted.\n */\nexport function quantileSorted(sorted: readonly number[], q: number): number {\n const n = sorted.length;\n if (n === 0) return 0;\n if (n === 1) return sorted[0] as number;\n const clamped = q < 0 ? 0 : q > 1 ? 1 : q;\n const pos = clamped * (n - 1);\n const lo = Math.floor(pos);\n const hi = Math.ceil(pos);\n const loValue = sorted[lo] as number;\n if (lo === hi) return loValue;\n const hiValue = sorted[hi] as number;\n return loValue + (hiValue - loValue) * (pos - lo);\n}\n\n/**\n * The `q`-quantile of `values` (0 ≤ `q` ≤ 1; out-of-range values are clamped).\n *\n * Sorts a COPY, so the caller's array is untouched. Non-finite entries are dropped;\n * an empty (or all-non-finite) input answers `0`.\n */\nexport function quantile(values: readonly number[], q: number): number {\n return quantileSorted(finiteOnly(values).sort(ascending), q);\n}\n\n/**\n * Map `value` from `domain` onto `range`, clamped to the range's endpoints.\n *\n * The workhorse behind \"frequency → stroke width\" and \"share → bar length\". A\n * DEGENERATE domain (`d0 === d1`, e.g. a graph whose every edge has the same count)\n * answers `range[0]`: with a zero-width domain every input sits at the domain floor, so\n * the range floor is the consistent answer — and it is what the clamped interpolation\n * gives for any `value <= d0`. A caller that wants a different neutral (the widest\n * stroke, say) special-cases it explicitly rather than relying on a surprise here.\n *\n * A descending domain (`d0 > d1`) is honoured — it simply inverts the mapping.\n */\nexport function clampWidth(\n value: number,\n domain: readonly [number, number],\n range: readonly [number, number],\n): number {\n const [d0, d1] = domain;\n const [r0, r1] = range;\n if (!Number.isFinite(value)) return r0;\n const span = d1 - d0;\n if (span === 0) return r0;\n const t = (value - d0) / span;\n const clamped = t < 0 ? 0 : t > 1 ? 1 : t;\n return r0 + (r1 - r0) * clamped;\n}\n","/**\n * Duration summary statistics — RM-049.\n *\n * The single implementation every part of the core reads: per-activity execution time,\n * per-edge flow time and per-variant case time all pass through here, which is what makes\n * \"the variant's median agrees with the edge's median on the same fixture\" true by\n * construction rather than by coincidence.\n *\n * Algorithm shape (which statistics a discovered graph carries per activity and per edge)\n * follows pm4js — see `ATTRIBUTION.md`. No pm4js code is copied; the arithmetic below is\n * the ordinary textbook definition.\n */\nimport { ascending, quantileSorted } from \"./scale\";\nimport type { DurationStats } from \"./types\";\n\n/** The fraction discarded from EACH tail by `trimmedMean`. */\nexport const TRIM_FRACTION = 0.1;\n\n/** All-zero statistics — the answer for an empty sample set. */\nexport const EMPTY_DURATION_STATS: Readonly<DurationStats> = Object.freeze({\n min: 0,\n max: 0,\n mean: 0,\n median: 0,\n p90: 0,\n sum: 0,\n trimmedMean: 0,\n});\n\n/** A fresh mutable copy of {@link EMPTY_DURATION_STATS}. */\nexport function emptyDurationStats(): DurationStats {\n return { ...EMPTY_DURATION_STATS };\n}\n\n/**\n * Summarize `samples` (milliseconds) as {@link DurationStats}.\n *\n * - Non-finite samples are DROPPED, not propagated — one unparseable timestamp upstream\n * must not turn an entire activity's statistics into `NaN`.\n * - The input array is not mutated (a copy is sorted).\n * - `p90` uses linear interpolation between order statistics (R-7 / `d3.quantile`).\n * - `trimmedMean` discards the lowest and highest {@link TRIM_FRACTION} of samples. When\n * trimming would leave nothing (fewer than 5 samples, where `floor(n * 0.1)` is 0 or\n * the tails meet), it degrades to the plain mean rather than to `NaN`.\n * - An empty (or all-non-finite) input answers all zeros.\n */\nexport function durationStats(samples: readonly number[]): DurationStats {\n const sorted: number[] = [];\n for (const s of samples) if (Number.isFinite(s)) sorted.push(s);\n const n = sorted.length;\n if (n === 0) return emptyDurationStats();\n sorted.sort(ascending);\n\n let sum = 0;\n for (const s of sorted) sum += s;\n\n const trim = Math.floor(n * TRIM_FRACTION);\n const lo = trim;\n const hi = n - trim;\n let trimmedMean: number;\n if (hi - lo <= 0) {\n trimmedMean = sum / n;\n } else {\n let trimmedSum = 0;\n for (let i = lo; i < hi; i += 1) trimmedSum += sorted[i] as number;\n trimmedMean = trimmedSum / (hi - lo);\n }\n\n return {\n min: sorted[0] as number,\n max: sorted[n - 1] as number,\n mean: sum / n,\n median: quantileSorted(sorted, 0.5),\n p90: quantileSorted(sorted, 0.9),\n sum,\n trimmedMean,\n };\n}\n\n/** Default reservoir capacity for a single activity's or edge's duration samples. */\nexport const DURATION_SAMPLE_CAP = 4096;\n\n/**\n * A deterministic 32-bit PRNG (mulberry32). Same seed, same stream — which is the whole\n * point: the reservoir below must make identical keep/discard decisions on every run, in\n * every engine, or two runs over one log would produce different `p90`s.\n */\nfunction mulberry32(seed: number): () => number {\n let a = seed >>> 0;\n return () => {\n a = (a + 0x6d2b79f5) >>> 0;\n let t = a;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/**\n * Bounded duration-sample collector.\n *\n * A log with a million events would otherwise hold a million numbers per hot edge just to\n * compute seven statistics from them. The first {@link DURATION_SAMPLE_CAP} samples are\n * kept outright; past the cap the collector switches to reservoir sampling (Algorithm R),\n * so the retained set stays a uniform sample of everything seen while memory stays flat.\n *\n * `count` keeps the TRUE number of samples offered, and `sum` the true total, so a\n * capped edge still reports an exact `sum` and an exact `mean`; only the order statistics\n * (`median`, `p90`, `min`, `max`, `trimmedMean`) are estimated from the reservoir.\n */\nexport class DurationSampler {\n private readonly capacity: number;\n private readonly random: () => number;\n private readonly reservoir: number[] = [];\n /** Number of finite samples offered, including those the reservoir discarded. */\n private seen = 0;\n private total = 0;\n private lowest = Number.POSITIVE_INFINITY;\n private highest = Number.NEGATIVE_INFINITY;\n\n constructor(seed = 0x9e3779b9, capacity: number = DURATION_SAMPLE_CAP) {\n this.capacity = capacity > 0 ? capacity : 1;\n this.random = mulberry32(seed);\n }\n\n /** Offer one sample. Non-finite values are ignored. */\n add(sample: number): void {\n if (!Number.isFinite(sample)) return;\n this.total += sample;\n if (sample < this.lowest) this.lowest = sample;\n if (sample > this.highest) this.highest = sample;\n if (this.reservoir.length < this.capacity) {\n this.reservoir.push(sample);\n } else {\n const j = Math.floor(this.random() * (this.seen + 1));\n if (j < this.capacity) this.reservoir[j] = sample;\n }\n this.seen += 1;\n }\n\n /** True number of finite samples offered. */\n get size(): number {\n return this.seen;\n }\n\n /**\n * Summarize what was collected. `sum`, `mean`, `min` and `max` are EXACT even past the\n * cap (they are accumulated, not read off the reservoir); the remaining three are\n * computed from the retained sample.\n */\n stats(): DurationStats {\n if (this.seen === 0) return emptyDurationStats();\n const fromReservoir = durationStats(this.reservoir);\n return {\n ...fromReservoir,\n min: this.lowest,\n max: this.highest,\n sum: this.total,\n mean: this.total / this.seen,\n };\n }\n}\n","/**\n * Event-log normalization — RM-049.\n *\n * Turns a flat `EventRow[]` into per-case traces of INSTANCES: one entry per real\n * activity execution, carrying a resolved `start` and `end` in epoch milliseconds. Every\n * other module in `/core` consumes the normalized form, so the messy parts of a raw log\n * (three timestamp encodings, lifecycle rows split across two lines, rows arriving out of\n * order) are handled exactly once, here.\n *\n * Deterministic: same rows in, same traces out. No `Date.now()`, no randomness.\n */\nimport type { EventLog, EventRow } from \"./types\";\n\n/** One activity execution, with both ends resolved to epoch milliseconds. */\nexport interface NormalizedEvent {\n activity: string;\n /**\n * Start of the execution. Equals `end` for an atomic event — an event with no\n * `lifecycle` pair and no `startTimestamp` has no observed duration, and inventing one\n * would put fabricated numbers into every downstream statistic.\n */\n start: number;\n /** Completion of the execution. */\n end: number;\n /** `end - start`, never negative (a clock-skewed pair is floored at 0). */\n duration: number;\n resource?: string;\n attributes?: Record<string, string | number | boolean | null>;\n /**\n * True when this instance came from a `lifecycle: \"start\"` row that never got a\n * matching `\"complete\"`. Its `end` is provisional (it equals `start`), so a performance\n * view can choose to exclude it.\n */\n isOpen: boolean;\n}\n\n/** One case (process instance): its trace, plus the case's own extent. */\nexport interface NormalizedCase {\n caseId: string;\n /** Instances in ascending `start` order. Never empty. */\n events: NormalizedEvent[];\n /** Earliest `start` in the trace. */\n start: number;\n /** Latest `end` in the trace. */\n end: number;\n /** `end - start` — the case's throughput time. */\n duration: number;\n /** Per-case attributes carried over from `EventLog.caseAttributes`, when present. */\n attributes?: Record<string, unknown>;\n}\n\n/** A normalized log: cases in first-appearance order, plus totals. */\nexport interface NormalizedLog {\n cases: NormalizedCase[];\n totals: { cases: number; events: number };\n}\n\n/** A log in either shape. Every derivation entry point accepts both. */\nexport type AnyLog = EventLog | NormalizedLog;\n\n/** Narrowing predicate — `true` when `log` has already been through {@link normalizeLog}. */\nexport function isNormalizedLog(log: AnyLog): log is NormalizedLog {\n return Array.isArray((log as NormalizedLog).cases);\n}\n\n/** Normalize `log` unless it already is normalized. The idempotent entry point. */\nexport function asNormalizedLog(log: AnyLog): NormalizedLog {\n return isNormalizedLog(log) ? log : normalizeLog(log);\n}\n\n/**\n * Resolve the three accepted timestamp encodings to epoch milliseconds.\n *\n * Returns `NaN` for an unparseable value rather than throwing: one bad cell in a\n * ten-thousand-row CSV must not abort the whole import. Downstream, a `NaN` sorts as\n * equal (so the row keeps its input position) and is dropped from duration samples.\n */\nexport function toEpochMs(value: string | number | Date | undefined | null): number {\n if (value === undefined || value === null) return Number.NaN;\n if (typeof value === \"number\") return Number.isFinite(value) ? value : Number.NaN;\n if (value instanceof Date) return value.getTime();\n const parsed = Date.parse(value);\n if (!Number.isNaN(parsed)) return parsed;\n // A bare numeric string (\"1735725600000\") is a legitimate epoch encoding that\n // `Date.parse` reads as a year in some engines; treat it as epoch ms explicitly.\n const asNumber = Number(value);\n return Number.isFinite(asNumber) && value.trim() !== \"\" ? asNumber : Number.NaN;\n}\n\n/** Stable comparator that leaves `NaN`-timestamped rows in their input order. */\nfunction byTimestamp(a: { at: number }, b: { at: number }): number {\n if (Number.isNaN(a.at) || Number.isNaN(b.at)) return 0;\n return a.at - b.at;\n}\n\ninterface StagedRow {\n at: number;\n row: EventRow;\n}\n\n/**\n * Group rows by case, order each case in time, and pair `lifecycle` rows into instances.\n *\n * The pairing rule, stated precisely because five downstream items depend on it:\n *\n * - Rows are grouped by `caseId` in ONE pass and each case is sorted independently. The\n * whole log is never sorted — sorting 200 000 rows to then split them is the shape that\n * makes a browser import feel broken.\n * - A `lifecycle: \"start\"` row OPENS an instance at its position in the case's timeline.\n * - A `lifecycle: \"complete\"` row CLOSES the oldest still-open instance of the SAME\n * activity, wherever that start sits in the trace. The two rows therefore do not have\n * to be adjacent: interleaved `A-start, B-start, A-complete, B-complete` pairs\n * correctly, and so does a log whose rows arrive in the wrong order entirely, because\n * the per-case sort runs first.\n * - Repeated executions of one activity in a case pair oldest-start-to-earliest-complete\n * (FIFO), which is the only pairing that keeps intervals non-overlapping for a\n * sequential resource.\n * - A `\"complete\"` with no open start becomes an instance in its own right, using an\n * explicit `startTimestamp` when the row carries one.\n * - A `\"start\"` that is never completed stays OPEN: `end === start` and `isOpen` is true.\n * - A row with no `lifecycle` is atomic: `start` comes from `startTimestamp` when given,\n * otherwise from `timestamp` itself.\n *\n * Rows with an empty `caseId` or `activity` are dropped — they cannot take part in a\n * directly-follows relation, and keeping them would put an unnamed node in every graph.\n */\nexport function normalizeLog(log: EventLog): NormalizedLog {\n const staged = new Map<string, StagedRow[]>();\n const order: string[] = [];\n\n for (const row of log.events) {\n if (!row || typeof row.caseId !== \"string\" || row.caseId === \"\") continue;\n if (typeof row.activity !== \"string\" || row.activity === \"\") continue;\n let bucket = staged.get(row.caseId);\n if (bucket === undefined) {\n bucket = [];\n staged.set(row.caseId, bucket);\n order.push(row.caseId);\n }\n bucket.push({ at: toEpochMs(row.timestamp), row });\n }\n\n const cases: NormalizedCase[] = [];\n let events = 0;\n\n for (const caseId of order) {\n const bucket = staged.get(caseId) as StagedRow[];\n // `Array.prototype.sort` is stable (ES2019), so equal timestamps keep input order.\n bucket.sort(byTimestamp);\n\n const instances: NormalizedEvent[] = [];\n /** activity → FIFO queue of indices into `instances` awaiting a `\"complete\"`. */\n const open = new Map<string, number[]>();\n\n for (const { at, row } of bucket) {\n if (row.lifecycle === \"start\") {\n const index = instances.length;\n instances.push(makeInstance(row, at, at, true));\n const queue = open.get(row.activity);\n if (queue === undefined) open.set(row.activity, [index]);\n else queue.push(index);\n continue;\n }\n\n if (row.lifecycle === \"complete\") {\n const queue = open.get(row.activity);\n const index = queue?.shift();\n if (index !== undefined) {\n const instance = instances[index] as NormalizedEvent;\n instance.end = at;\n instance.duration = durationOf(instance.start, at);\n instance.isOpen = false;\n if (instance.resource === undefined && row.resource !== undefined) {\n instance.resource = row.resource;\n }\n if (instance.attributes === undefined && row.attributes !== undefined) {\n instance.attributes = row.attributes;\n }\n continue;\n }\n }\n\n const end = at;\n const explicitStart = toEpochMs(row.startTimestamp);\n const start = Number.isNaN(explicitStart) ? end : explicitStart;\n instances.push(makeInstance(row, start, end, false));\n }\n\n if (instances.length === 0) continue;\n\n // Pairing can move an instance's start earlier than the row that created it, so the\n // trace is re-ordered by resolved start. It is already almost sorted, which is the\n // best case for the engine's TimSort — this is not a second full sort in practice.\n instances.sort(byStart);\n\n let caseStart = Number.POSITIVE_INFINITY;\n let caseEnd = Number.NEGATIVE_INFINITY;\n for (const instance of instances) {\n if (Number.isFinite(instance.start) && instance.start < caseStart) caseStart = instance.start;\n if (Number.isFinite(instance.end) && instance.end > caseEnd) caseEnd = instance.end;\n }\n const hasExtent =\n caseStart !== Number.POSITIVE_INFINITY && caseEnd !== Number.NEGATIVE_INFINITY;\n\n const normalizedCase: NormalizedCase = {\n caseId,\n events: instances,\n start: hasExtent ? caseStart : Number.NaN,\n end: hasExtent ? caseEnd : Number.NaN,\n duration: hasExtent ? durationOf(caseStart, caseEnd) : Number.NaN,\n };\n const caseAttributes = log.caseAttributes?.[caseId];\n if (caseAttributes !== undefined) normalizedCase.attributes = caseAttributes;\n\n cases.push(normalizedCase);\n events += instances.length;\n }\n\n return { cases, totals: { cases: cases.length, events } };\n}\n\nfunction byStart(a: NormalizedEvent, b: NormalizedEvent): number {\n if (Number.isNaN(a.start) || Number.isNaN(b.start)) return 0;\n return a.start - b.start;\n}\n\nfunction durationOf(start: number, end: number): number {\n const delta = end - start;\n if (!Number.isFinite(delta)) return Number.NaN;\n return delta < 0 ? 0 : delta;\n}\n\nfunction makeInstance(row: EventRow, start: number, end: number, isOpen: boolean): NormalizedEvent {\n const instance: NormalizedEvent = {\n activity: row.activity,\n start,\n end,\n duration: durationOf(start, end),\n isOpen,\n };\n if (row.resource !== undefined) instance.resource = row.resource;\n if (row.attributes !== undefined) instance.attributes = row.attributes;\n return instance;\n}\n","/**\n * Variant extraction — RM-049.\n *\n * A \"variant\" is one distinct activity sequence, together with every case that follows\n * it. Grouping traces by their sequence and ranking those groups by frequency is the\n * standard second view onto a log (the first being the directly-follows graph), and the\n * grouping shape below follows pm4js; see `ATTRIBUTION.md`. No pm4js code is copied.\n *\n * Deterministic: ids are hashes of the sequence, ordering is fully tie-broken, and the\n * cumulative share is computed from counts rather than by summing floats, so it is exactly\n * `1` on the last variant instead of `0.9999999999999998`.\n */\nimport { durationStats } from \"./duration-stats\";\nimport { asNormalizedLog, type AnyLog, type NormalizedCase } from \"./event-log\";\nimport type { Variant } from \"./types\";\n\n/**\n * Separator joining a sequence into its variant key.\n *\n * U+0001 (START OF HEADING) is a C0 control character. No real activity name contains\n * one, which is what makes joining on it lossless where a printable separator\n * (`\" -> \"`, `\",\"`) would collide with a name that happens to contain the separator.\n */\nexport const VARIANT_KEY_SEPARATOR = \"\\u0001\";\n\n/** Join an activity sequence into its canonical variant key. */\nexport function variantKey(sequence: readonly string[]): string {\n return sequence.join(VARIANT_KEY_SEPARATOR);\n}\n\n/** FNV-1a, 32-bit. Small, fast, dependency-free and identical in every engine. */\nfunction fnv1a32(text: string, basis: number): number {\n let hash = basis >>> 0;\n for (let i = 0; i < text.length; i += 1) {\n hash ^= text.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193) >>> 0;\n }\n return hash >>> 0;\n}\n\nfunction hex8(value: number): string {\n return (value >>> 0).toString(16).padStart(8, \"0\");\n}\n\n/**\n * The stable id for an activity sequence: `v` followed by 64 bits of FNV-1a over the\n * variant key, as hex.\n *\n * Exported because a selection is a wave-wide concern — RM-052's variant explorer and\n * RM-053's case table both need to name the same variant without holding the object, and\n * a hash of the sequence is the only identifier that survives a re-run, a worker boundary\n * and a URL.\n */\nexport function variantId(sequence: readonly string[]): string {\n const key = variantKey(sequence);\n return `v${hex8(fnv1a32(key, 0x811c9dc5))}${hex8(fnv1a32(key, 0x84222325))}`;\n}\n\ninterface VariantAccumulator {\n key: string;\n sequence: string[];\n caseIds: string[];\n durations: number[];\n}\n\n/**\n * Group `log`'s cases into variants, ranked by frequency.\n *\n * Ordering is `count` descending, ties broken by the variant key ascending — a total\n * order, so the same log always produces the same array in the same positions.\n *\n * `share` is `count / cases`. `cumulativeShare` is the running `cumulativeCount / cases`,\n * which is monotonically non-decreasing by construction and exactly `1` on the last\n * entry. `duration` summarizes the END-TO-END case durations of the variant's cases, so\n * it answers \"how long does this path take\", not \"how long does one step take\".\n *\n * An empty log yields an empty array — never a single zero-count variant.\n */\nexport function extractVariants(log: AnyLog): Variant[] {\n const normalized = asNormalizedLog(log);\n const totalCases = normalized.cases.length;\n if (totalCases === 0) return [];\n\n const groups = new Map<string, VariantAccumulator>();\n for (const kase of normalized.cases) {\n const sequence = sequenceOf(kase);\n const key = variantKey(sequence);\n let group = groups.get(key);\n if (group === undefined) {\n group = { key, sequence, caseIds: [], durations: [] };\n groups.set(key, group);\n }\n group.caseIds.push(kase.caseId);\n group.durations.push(kase.duration);\n }\n\n const ranked = [...groups.values()].sort(\n (a, b) => b.caseIds.length - a.caseIds.length || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0),\n );\n\n const taken = new Set<string>();\n const variants: Variant[] = [];\n let cumulativeCount = 0;\n\n for (const group of ranked) {\n const count = group.caseIds.length;\n cumulativeCount += count;\n // A 64-bit hash collision between two different sequences is vanishingly unlikely,\n // but \"vanishingly unlikely\" is not \"impossible\" and a duplicate id would silently\n // merge two rows in a table. Disambiguate deterministically: `ranked` is totally\n // ordered, so the suffix an id receives is the same on every run.\n let id = variantId(group.sequence);\n if (taken.has(id)) {\n let suffix = 1;\n while (taken.has(`${id}-${suffix}`)) suffix += 1;\n id = `${id}-${suffix}`;\n }\n taken.add(id);\n\n variants.push({\n id,\n sequence: group.sequence,\n count,\n share: count / totalCases,\n cumulativeShare: cumulativeCount / totalCases,\n caseIds: group.caseIds,\n duration: durationStats(group.durations),\n });\n }\n\n return variants;\n}\n\nfunction sequenceOf(kase: NormalizedCase): string[] {\n const sequence = new Array<string>(kase.events.length);\n for (let i = 0; i < kase.events.length; i += 1) {\n sequence[i] = (kase.events[i] as { activity: string }).activity;\n }\n return sequence;\n}\n","/**\n * Directly-follows graph discovery — RM-049.\n *\n * The directly-follows abstraction (one node per activity, one edge per adjacent pair in\n * a trace, with frequency and performance aggregates on both) is the standard starting\n * point of every process-mining view. The shape of what a discovered graph carries —\n * per-activity instance and case counts, per-edge count and case count, start/end\n * activity tallies — follows pm4js; see `ATTRIBUTION.md`. No pm4js code is copied, and\n * nothing here derives from the AGPL Python reference implementation.\n *\n * Deterministic and layout-free: `isBackEdge` is always `false` here, because whether an\n * edge points backwards is a property of a LAYOUT, not of a log. A layout pass sets it.\n */\nimport { DURATION_SAMPLE_CAP, DurationSampler } from \"./duration-stats\";\nimport { asNormalizedLog, type AnyLog, type NormalizedEvent } from \"./event-log\";\nimport { VARIANT_KEY_SEPARATOR } from \"./extract-variants\";\nimport type { ActivityStats, FlowTime, ProcessGraph, TransitionStats } from \"./types\";\n\n/**\n * Separator used to key an edge. The same C0 control character the variant key uses, and\n * for the same reason: no activity name can contain it, so `source + SEP + target` is an\n * injective key.\n */\nexport const EDGE_KEY_SEPARATOR = VARIANT_KEY_SEPARATOR;\n\n/** Options for {@link discoverGraph}. Every field has a default; `discoverGraph(log)` works. */\nexport interface DiscoverGraphOptions {\n /**\n * Which elapsed time an edge measures. Defaults to `\"idle_time\"` — the wait between the\n * source completing and the target starting, which is the number a bottleneck view\n * wants. For a log of atomic events the two choices coincide.\n */\n flowTime?: FlowTime;\n /**\n * Reservoir capacity per activity and per edge. Lower it for a very wide log; raise it\n * for sharper tail statistics. `sum`/`mean`/`min`/`max` stay exact regardless.\n */\n maxDurationSamples?: number;\n}\n\ninterface ActivityAccumulator {\n instances: number;\n cases: number;\n duration: DurationSampler;\n}\n\ninterface TransitionAccumulator {\n source: string;\n target: string;\n count: number;\n caseCount: number;\n duration: DurationSampler;\n}\n\n/**\n * Derive a {@link ProcessGraph} from a raw or already-normalized log.\n *\n * One pass per trace: each case's activity sequence is walked once, incrementing\n * per-activity and per-edge counters and feeding two duration samplers. Per-case\n * uniqueness (the `cases` and `caseCount` fields) is tracked with two `Set`s that are\n * cleared per case rather than a per-key `Set` of case ids, so memory stays proportional\n * to the widest trace, not to the log.\n *\n * `totals.variants` is counted from the same walk (the joined activity sequence goes into\n * a `Set`), so a caller that only needs the headline number does not also have to run\n * `extractVariants`.\n */\nexport function discoverGraph(log: AnyLog, options: DiscoverGraphOptions = {}): ProcessGraph {\n const flowTime: FlowTime = options.flowTime ?? \"idle_time\";\n const capacity = options.maxDurationSamples ?? DURATION_SAMPLE_CAP;\n const normalized = asNormalizedLog(log);\n\n const activities = new Map<string, ActivityAccumulator>();\n const transitions = new Map<string, TransitionAccumulator>();\n const startActivities = new Map<string, number>();\n const endActivities = new Map<string, number>();\n const variantKeys = new Set<string>();\n\n // Sampler seeds come from a creation counter, never from a clock or from entropy: the\n // iteration order over a given log is fixed, so the same log always seeds the same\n // samplers and the reservoir retains the same rows.\n let samplerIndex = 0;\n const nextSeed = (): number => {\n samplerIndex += 1;\n return (0x9e3779b9 ^ Math.imul(samplerIndex, 0x9e3779b1)) >>> 0;\n };\n\n let events = 0;\n const seenActivities = new Set<string>();\n const seenEdges = new Set<string>();\n\n for (const kase of normalized.cases) {\n const trace = kase.events;\n if (trace.length === 0) continue;\n events += trace.length;\n seenActivities.clear();\n seenEdges.clear();\n\n const sequence = new Array<string>(trace.length);\n\n for (let i = 0; i < trace.length; i += 1) {\n const event = trace[i] as NormalizedEvent;\n const name = event.activity;\n sequence[i] = name;\n\n let activity = activities.get(name);\n if (activity === undefined) {\n activity = { instances: 0, cases: 0, duration: new DurationSampler(nextSeed(), capacity) };\n activities.set(name, activity);\n }\n activity.instances += 1;\n activity.duration.add(event.duration);\n if (!seenActivities.has(name)) {\n seenActivities.add(name);\n activity.cases += 1;\n }\n\n if (i === 0) continue;\n const previous = trace[i - 1] as NormalizedEvent;\n const source = previous.activity;\n const key = `${source}${EDGE_KEY_SEPARATOR}${name}`;\n let edge = transitions.get(key);\n if (edge === undefined) {\n edge = {\n source,\n target: name,\n count: 0,\n caseCount: 0,\n duration: new DurationSampler(nextSeed(), capacity),\n };\n transitions.set(key, edge);\n }\n edge.count += 1;\n edge.duration.add(\n flowTime === \"inter_start_time\" ? event.start - previous.start : event.start - previous.end,\n );\n if (!seenEdges.has(key)) {\n seenEdges.add(key);\n edge.caseCount += 1;\n }\n }\n\n const first = trace[0] as NormalizedEvent;\n const last = trace[trace.length - 1] as NormalizedEvent;\n startActivities.set(first.activity, (startActivities.get(first.activity) ?? 0) + 1);\n endActivities.set(last.activity, (endActivities.get(last.activity) ?? 0) + 1);\n variantKeys.add(sequence.join(VARIANT_KEY_SEPARATOR));\n }\n\n const activityList: ActivityStats[] = [];\n for (const [id, accumulator] of activities) {\n activityList.push({\n id,\n label: id,\n instances: accumulator.instances,\n cases: accumulator.cases,\n isStart: startActivities.has(id),\n isEnd: endActivities.has(id),\n duration: accumulator.duration.stats(),\n });\n }\n // Deterministic, reader-friendly order: busiest first, ties broken by name, so the same\n // log always yields the same array in the same positions (downstream items snapshot it).\n activityList.sort((a, b) => b.instances - a.instances || compareStrings(a.id, b.id));\n\n const transitionList: TransitionStats[] = [];\n for (const accumulator of transitions.values()) {\n transitionList.push({\n source: accumulator.source,\n target: accumulator.target,\n count: accumulator.count,\n caseCount: accumulator.caseCount,\n duration: accumulator.duration.stats(),\n isSelfLoop: accumulator.source === accumulator.target,\n isBackEdge: false,\n });\n }\n transitionList.sort(\n (a, b) =>\n b.count - a.count || compareStrings(a.source, b.source) || compareStrings(a.target, b.target),\n );\n\n return {\n activities: activityList,\n transitions: transitionList,\n startActivities: toSortedRecord(startActivities),\n endActivities: toSortedRecord(endActivities),\n totals: { cases: normalized.cases.length, events, variants: variantKeys.size },\n };\n}\n\n/** Code-unit comparison — locale-independent, so the order is the same on every machine. */\nfunction compareStrings(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/** Key order is part of the output, so it is sorted rather than left to insertion order. */\nfunction toSortedRecord(counts: Map<string, number>): Record<string, number> {\n const out: Record<string, number> = {};\n for (const key of [...counts.keys()].sort()) out[key] = counts.get(key) as number;\n return out;\n}\n","/**\n * The performance metric layer — RM-050.\n *\n * A process map is read twice: once for FREQUENCY (how often does this happen) and once\n * for PERFORMANCE (how long does it take). The frequency numbers come straight off\n * discovery; the performance numbers need three further decisions that only a reader can\n * make — which aggregate (median, mean, p90 …), which elapsed time an edge measures\n * (waiting or cycle), and which unit the answer is spoken in. This module applies all\n * three to an already-discovered graph and hands back the scalar each node and edge\n * should be painted with.\n *\n * The returned graph is a NEW object graph: unlike {@link abstractGraph}, which filters,\n * this function re-expresses. The input is never mutated.\n */\nimport { discoverGraph } from \"./discover-graph\";\nimport { EDGE_KEY_SEPARATOR } from \"./discover-graph\";\nimport type { AnyLog } from \"./event-log\";\nimport { minMax } from \"./scale\";\nimport type {\n ActivityStats,\n DurationStats,\n FlowTime,\n PerformanceAgg,\n ProcessGraph,\n TransitionStats,\n} from \"./types\";\n\n/** The units a duration can be spoken in. Everything upstream is milliseconds. */\nexport type DurationUnit = \"ms\" | \"s\" | \"min\" | \"h\" | \"d\";\n\n/** How many milliseconds each {@link DurationUnit} is worth. */\nexport const DURATION_UNIT_MS: Readonly<Record<DurationUnit, number>> = Object.freeze({\n ms: 1,\n s: 1_000,\n min: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n});\n\n/** Options for {@link aggregatePerformance}. */\nexport interface PerformanceOptions {\n /** Which member of a {@link DurationStats} the layer reads. */\n agg: PerformanceAgg;\n /**\n * Which elapsed time an edge measures. Honoured only when `log` is supplied — a graph\n * alone cannot be converted from waiting time to cycle time, because the two are not\n * derivable from each other's summary statistics. Without `log` this field DECLARES\n * what the graph already carries and nothing is re-derived.\n */\n flowTime: FlowTime;\n /** The unit every returned duration is expressed in. */\n unit: DurationUnit;\n /**\n * The log the graph came from. Supply it to re-derive edge durations at `flowTime`;\n * pass an already-normalized log (see `asNormalizedLog`) to skip a second parse.\n */\n log?: AnyLog;\n /**\n * Reservoir capacity per activity and per edge when re-deriving. Order statistics\n * (`median`, `p90`, `trimmedMean`) are ESTIMATES past this many samples per key; raise\n * it when a threshold has to be exact. Only meaningful together with `log`.\n */\n maxDurationSamples?: number;\n}\n\n/** The per-element scalars a performance view paints with, plus the domains to scale them on. */\nexport interface PerformanceLayer {\n agg: PerformanceAgg;\n flowTime: FlowTime;\n unit: DurationUnit;\n /** Activity id → the selected aggregate of its execution time, in `unit`. */\n activities: Record<string, number>;\n /** `source␁target` (see `EDGE_KEY_SEPARATOR`) → the selected aggregate, in `unit`. */\n transitions: Record<string, number>;\n /** `[min, max]` over `activities` — `[0, 0]` when there are none. */\n activityDomain: [number, number];\n /** `[min, max]` over `transitions` — `[0, 0]` when there are none. */\n transitionDomain: [number, number];\n}\n\n/** What {@link aggregatePerformance} returns: a `ProcessGraph` plus its metric layer. */\nexport interface PerformanceGraph extends ProcessGraph {\n performance: PerformanceLayer;\n}\n\n/**\n * Read the member of `stats` a {@link PerformanceAgg} names.\n *\n * The one place the mapping lives, so a control, a legend and a renderer cannot disagree\n * about what \"p90\" is. `trimmed_mean` is the snake_case spelling of `trimmedMean` —\n * the type is a wire/URL value, the field is a property name.\n */\nexport function performanceValue(stats: DurationStats, agg: PerformanceAgg): number {\n switch (agg) {\n case \"mean\":\n return stats.mean;\n case \"min\":\n return stats.min;\n case \"max\":\n return stats.max;\n case \"sum\":\n return stats.sum;\n case \"p90\":\n return stats.p90;\n case \"trimmed_mean\":\n return stats.trimmedMean;\n case \"median\":\n default:\n return stats.median;\n }\n}\n\n/** Scale every member of `stats` by `factor`. Returns a new object; `stats` is untouched. */\nfunction scaleStats(stats: DurationStats, factor: number): DurationStats {\n if (factor === 1) return { ...stats };\n return {\n min: stats.min * factor,\n max: stats.max * factor,\n mean: stats.mean * factor,\n median: stats.median * factor,\n p90: stats.p90 * factor,\n sum: stats.sum * factor,\n trimmedMean: stats.trimmedMean * factor,\n };\n}\n\nfunction edgeKey(source: string, target: string): string {\n return `${source}${EDGE_KEY_SEPARATOR}${target}`;\n}\n\n/**\n * Express `graph`'s durations as a performance layer.\n *\n * Every `DurationStats` in the returned graph is converted to `opts.unit`, and\n * `performance` carries the single scalar per activity and per transition that\n * `opts.agg` selects, together with the two domains a scale needs.\n *\n * With `opts.log`, edge and activity durations are RE-DERIVED at `opts.flowTime` from the\n * FULL log — which is what keeps a performance layer honest on an abstracted graph: the\n * numbers describe everything that happened, not only the part currently drawn. Elements\n * the re-derivation does not know about keep the statistics they arrived with.\n *\n * Order statistics past the reservoir cap are estimates (see `maxDurationSamples`);\n * `sum`, `mean`, `min` and `max` are exact at any log size.\n */\nexport function aggregatePerformance(\n graph: ProcessGraph,\n opts: PerformanceOptions,\n): PerformanceGraph {\n const factor = 1 / (DURATION_UNIT_MS[opts.unit] ?? 1);\n\n let activitySource: Map<string, DurationStats> | undefined;\n let transitionSource: Map<string, DurationStats> | undefined;\n if (opts.log !== undefined) {\n const derived = discoverGraph(opts.log, {\n flowTime: opts.flowTime,\n ...(opts.maxDurationSamples === undefined\n ? {}\n : { maxDurationSamples: opts.maxDurationSamples }),\n });\n activitySource = new Map(derived.activities.map((a) => [a.id, a.duration]));\n transitionSource = new Map(\n derived.transitions.map((t) => [edgeKey(t.source, t.target), t.duration]),\n );\n }\n\n const activityValues: Record<string, number> = {};\n const activities: ActivityStats[] = graph.activities.map((activity) => {\n const source = activitySource?.get(activity.id) ?? activity.duration;\n const duration = scaleStats(source, factor);\n activityValues[activity.id] = performanceValue(duration, opts.agg);\n return { ...activity, duration };\n });\n\n const transitionValues: Record<string, number> = {};\n const transitions: TransitionStats[] = graph.transitions.map((edge) => {\n const key = edgeKey(edge.source, edge.target);\n const source = transitionSource?.get(key) ?? edge.duration;\n const duration = scaleStats(source, factor);\n transitionValues[key] = performanceValue(duration, opts.agg);\n return { ...edge, duration };\n });\n\n return {\n activities,\n transitions,\n startActivities: { ...graph.startActivities },\n endActivities: { ...graph.endActivities },\n totals: { ...graph.totals },\n performance: {\n agg: opts.agg,\n flowTime: opts.flowTime,\n unit: opts.unit,\n activities: activityValues,\n transitions: transitionValues,\n activityDomain: minMax(Object.values(activityValues)),\n transitionDomain: minMax(Object.values(transitionValues)),\n },\n };\n}\n","/**\n * map-model — the pure `ProcessGraph → React Flow nodes/edges` mapping (RM-051).\n *\n * Everything the process map DECIDES about a graph lives here, and nothing here renders:\n * no React, no DOM, no engine calls. That is deliberate and load-bearing in three ways.\n *\n * 1. **The canvas and the accessible `TableView` twin read ONE model.** Both surfaces are\n * built from the same {@link ProcessMapModel}, so a number can never drift between the\n * picture and the table a screen-reader user is actually given.\n * 2. **Encoding is testable without a browser.** Stroke widths, saturations, printed\n * labels and selection states are plain data here, so a unit test can assert the\n * encoding rules instead of screenshotting them.\n * 3. **Layout can be cached independently of metric.** The model changes whenever the\n * metric changes; the STRUCTURE key (see {@link processGraphStructureKey}) does not, so\n * `useProcessLayout` reuses positions across a metric switch.\n *\n * ## The encoding contract (WCAG 1.4.1 — colour is never the only channel)\n *\n * Two flow-layer defects (#321 edge colour collapsing on a low-chroma palette, #297 edge\n * colour not refreshing on a theme switch) mean edge COLOUR cannot be trusted to separate\n * two values. This module is designed so it never has to be:\n *\n * - an **edge** metric reaches the reader through `weight` (stroke width, the\n * `[1.5, 8]` px linear min-max clamp `computeEdgeWeightScale` applies) AND through a\n * printed `label` pill — colour (`value`/`valueDomain`) is the third channel, never the\n * first;\n * - a **node** metric reaches the reader through a printed value AND a bar whose LENGTH is\n * the same fraction the fill saturation encodes — saturation alone would be colour-only;\n * - **self-loop**, **back-edge** and each **selection** state carry a shape, dash or\n * opacity change, not a hue change.\n *\n * Render any story in greyscale and two different metric values are still distinguishable.\n */\n// Deep module imports rather than the `../core` barrel ON PURPOSE. `/core` builds in its\n// own tsup pass because it must stay engine-free (ADR 0034), and `createProcessWorker`'s\n// `new URL(\"./process-worker.ts\", import.meta.url)` literal only resolves next to\n// `dist/core/index.js`. Reaching through the barrel would put that literal on the trunk\n// bundle's tree-shaking mercy; naming the four modules the map actually needs takes it off\n// the table entirely. Same package, so this is an ordinary relative import.\nimport type { Edge, Node } from \"@xyflow/react\";\nimport { performanceValue } from \"../core/aggregate-performance\";\nimport { EDGE_KEY_SEPARATOR } from \"../core/discover-graph\";\nimport { minMax } from \"../core/scale\";\nimport type { ReworkStats } from \"../core/detect-rework\";\nimport type { FilterSpec } from \"../core/filter-log\";\nimport type {\n ActivityStats,\n FrequencyMode,\n PerformanceAgg,\n ProcessGraph,\n TransitionStats,\n} from \"../core/types\";\n\n// ── Public vocabulary ────────────────────────────────────────────────────────\n\n/**\n * What a node or an edge is painted with: a frequency reading of the discovered counts,\n * or an aggregate of the measured durations.\n */\nexport type ProcessMetric = FrequencyMode | PerformanceAgg;\n\n/** The three metric slots a process map paints from. */\nexport interface ProcessMetricSpec {\n /** Drives node saturation, the node's meter bar and the node's printed value. */\n node: ProcessMetric;\n /** Drives edge stroke width and the edge's printed label pill. */\n edge: ProcessMetric;\n /** Optional second reading, printed beside the first on both nodes and edges. */\n secondary?: ProcessMetric;\n}\n\n/** Which half of the graph a selection points at. */\nexport type ProcessSelectionKind = \"activity\" | \"transition\";\n\n/**\n * A coordinated selection. `id` is the ACTIVITY NAME for `\"activity\"` and the\n * `source`+separator+`target` edge key (see {@link processEdgeId}) for `\"transition\"` —\n * the same keys `/core` uses, so a selection round-trips through `filterLog` and a URL\n * unchanged.\n */\nexport interface ProcessSelection {\n kind: ProcessSelectionKind;\n id: string;\n}\n\n/**\n * What the map's filter menu emits. A strict subset of `/core`'s `FilterSpec`, so a\n * consumer can hand it straight to `filterLog` with no translation layer — which is the\n * point: an intent the map invents and nothing can consume is not an intent.\n */\nexport type ProcessFilterIntent = Extract<\n FilterSpec,\n { kind: \"with\" } | { kind: \"without\" } | { kind: \"startsWith\" } | { kind: \"endsWith\" }\n>;\n\n/** The four intents the map's own menu offers, in menu order. */\nexport const PROCESS_FILTER_INTENT_KINDS = [\"with\", \"without\", \"startsWith\", \"endsWith\"] as const;\n\n/** Human wording for each intent, used by the menu's VISIBLE item text. */\nexport const PROCESS_FILTER_INTENT_LABELS: Readonly<Record<ProcessFilterIntent[\"kind\"], string>> =\n Object.freeze({\n with: \"Keep cases containing\",\n without: \"Keep cases without\",\n startsWith: \"Keep cases starting with\",\n endsWith: \"Keep cases ending with\",\n });\n\n/**\n * The locale message key for each intent's ACCESSIBLE name (#346), one activity's block at\n * a time — the visible text alone ({@link PROCESS_FILTER_INTENT_LABELS}) is not unique for\n * a transition's menu, which offers the same four intents once per endpoint: eight items,\n * four distinct visible strings, each doubled. Each message takes an `{activity}` var, so\n * the accessible name states which activity the item filters by even though the visible\n * text (kept unsuffixed, so the menu stays visually compact) does not.\n */\nexport const PROCESS_FILTER_INTENT_MESSAGE_KEYS: Readonly<\n Record<ProcessFilterIntent[\"kind\"], string>\n> = Object.freeze({\n with: \"process.map.filterIntentWith\",\n without: \"process.map.filterIntentWithout\",\n startsWith: \"process.map.filterIntentStartsWith\",\n endsWith: \"process.map.filterIntentEndsWith\",\n});\n\n/**\n * How an element relates to the current selection AND the active filter.\n *\n * - `\"selected\"` — the element the reader picked.\n * - `\"associated\"` — ordinary; either nothing is selected, or this element touches the\n * selection, and no active filter excludes it.\n * - `\"excluded\"` — outside the selection's neighbourhood, or dropped by the active filter;\n * dimmed, but still fully operable. Clicking an excluded element is how a reader filters\n * it back in, so it is never `aria-disabled` — see {@link resolveSelectionState}. The\n * dimming is a REDUNDANT cue: `activityAriaLabel`/`transitionAriaLabel` already append the\n * word \"excluded\" to the element's accessible name, so the state reaches assistive\n * technology through real text, not only through opacity.\n */\nexport type ProcessSelectionState = \"selected\" | \"associated\" | \"excluded\";\n\n/**\n * The ONE ghosting rung a filter-excluded element dims against, shared by every call site\n * that owns a non-text mark: the activity node's meter fill, the transition edge's stroke\n * (`ProcessTransitionEdge`'s wrapping `<g>`), and — via `labelProps.className` — the\n * transition's own label pill's frame (`FlowWeightedEdge`/`FlowSelfLoopEdge`). Before #351/\n * #352 this was TWO separate literals (`opacity-35` on the activity node's whole subtree,\n * `EXCLUDED_OPACITY` on the edge) and the pill had no ghost treatment at all.\n *\n * **This value dims marks, never text ink.** `ProcessActivityNode` and the pill's own label\n * text are deliberately excluded from it — measured (see #352's follow-up), a `0.35` alpha\n * over EITHER's own already-modest undimmed contrast (5.97:1–6.08:1 for the eyebrow, higher\n * for the title/label) composites well under the WCAG 4.5:1 floor in both reference themes.\n * Ghosted TEXT stays at full ink; the excluded state instead reaches the reader through the\n * activity node's `border-strong` boundary swap and the pill's dashed frame — non-opacity,\n * non-colour-alone channels (WCAG 1.4.1) — plus the accessible name's own appended\n * \"excluded\" word, same as before.\n */\nexport const GHOST_OPACITY = 0.35;\n\n/**\n * Per-element selection/filter states, sparse — an id with no entry defaults through\n * {@link resolveSelectionState}'s own rules. Keyed exactly like {@link ProcessSelection}:\n * activity name for `activities`, {@link processEdgeId} for `transitions`.\n *\n * `variants` is carried here for API symmetry with the other two namespaces even though\n * `ProcessMap` does not read it (the variant LIST narrows rather than ghosts — decision\n * `RM-052-tristate-decision.md` §3); a future variant-explorer view may.\n */\nexport interface ProcessSelectionStates {\n activities?: Readonly<Record<string, ProcessSelectionState>>;\n transitions?: Readonly<Record<string, ProcessSelectionState>>;\n variants?: Readonly<Record<string, ProcessSelectionState>>;\n}\n\n// ── Node / edge data ─────────────────────────────────────────────────────────\n\n/** `data` carried by every {@link ProcessMapNode}. */\nexport interface ProcessActivityNodeData extends Record<string, unknown> {\n /** Activity name — the node identity and its visible title. */\n title: string;\n /** What the primary number MEANS (\"Cases\", \"Median duration\"), shown as the eyebrow. */\n metricLabel: string;\n /** The primary metric, already formatted for display. */\n primaryLabel: string;\n /** The primary metric as a number, for the meter and for tests. */\n primaryValue: number;\n /** The secondary metric, already formatted. Absent when `metric.secondary` is unset. */\n secondaryLabel?: string;\n /** `primaryValue` as a `0..1` fraction of the graph's node-metric domain. */\n saturation: number;\n isStart: boolean;\n isEnd: boolean;\n /** Repeat executions of this activity across the log; omitted when no rework data. */\n reworkCount?: number;\n selectionState: ProcessSelectionState;\n}\n\n/** `data` carried by every {@link ProcessMapEdge}. */\nexport interface ProcessTransitionEdgeData extends Record<string, unknown> {\n source: string;\n target: string;\n /** Drives stroke width through `computeEdgeWeightScale`'s `[1.5, 8]` px min-max clamp. */\n weight: number;\n /** Drives stroke colour — the THIRD channel, never the first. */\n value: number;\n /** `[min, max]` the colour ramp interpolates `value` across. */\n valueDomain: [number, number];\n /** The printed pill — the second, colour-free channel for the same measure as `weight`. */\n label: string;\n secondaryLabel?: string;\n isSelfLoop: boolean;\n isBackEdge: boolean;\n selectionState: ProcessSelectionState;\n}\n\n/** A process-map activity node. Register as `nodeTypes={{ \"process-activity\": … }}`. */\nexport type ProcessMapNode = Node<ProcessActivityNodeData, \"process-activity\">;\n/** A process-map transition edge. Register as `edgeTypes={{ \"process-transition\": … }}`. */\nexport type ProcessMapEdge = Edge<ProcessTransitionEdgeData, \"process-transition\">;\n\n/** One row of the accessible `TableView` twin — activities half. */\nexport interface ProcessActivityRow {\n id: string;\n title: string;\n primaryLabel: string;\n secondaryLabel?: string;\n reworkCount?: number;\n role: string;\n selectionState: ProcessSelectionState;\n}\n\n/** One row of the accessible `TableView` twin — transitions half. */\nexport interface ProcessTransitionRow {\n id: string;\n source: string;\n target: string;\n primaryLabel: string;\n secondaryLabel?: string;\n shape: string;\n selectionState: ProcessSelectionState;\n}\n\n/** Everything the canvas and the table are both rendered from. */\nexport interface ProcessMapModel {\n nodes: ProcessMapNode[];\n edges: ProcessMapEdge[];\n /** `[min, max]` of the node metric across the graph. */\n nodeDomain: [number, number];\n /** `[min, max]` of the edge metric across the graph — the `Legend` scale's domain. */\n edgeDomain: [number, number];\n /** What the node metric means, e.g. \"Cases\". */\n nodeMetricLabel: string;\n /** What the edge metric means, e.g. \"Transitions\". */\n edgeMetricLabel: string;\n activityRows: ProcessActivityRow[];\n transitionRows: ProcessTransitionRow[];\n /** Format one edge-metric value the way the map prints it — used by the `Legend`. */\n formatEdgeValue: (value: number) => string;\n /**\n * How many activities/transitions the active selection or filter has excluded (#375) —\n * derived once here so the canvas branch, the table branch and any future consumer read\n * one number instead of recomputing it three times, and so the summary is unit-testable\n * without a render.\n */\n excludedCounts: ProcessMapExcludedCounts;\n}\n\n/** See {@link ProcessMapModel.excludedCounts}. */\nexport interface ProcessMapExcludedCounts {\n activities: number;\n totalActivities: number;\n transitions: number;\n totalTransitions: number;\n}\n\n// ── Metric resolution ────────────────────────────────────────────────────────\n\n/**\n * The frequency readings an ACTIVITY can actually answer from `ActivityStats`.\n *\n * `relative_antecedent` / `relative_consequent` are edge-only readings (they are shares of\n * a source's or a target's traffic — a node has no antecedent), and `max_repetitions`\n * needs the per-case maximum, which discovery does not retain. Rather than invent a\n * number for those three, {@link resolveActivityFrequencyMode} maps them onto the nearest\n * reading the data supports and the RESOLVED mode is what labels the value — so the map\n * never prints one measure under another measure's name.\n */\nexport type ActivityFrequencyMode = Extract<\n FrequencyMode,\n \"absolute\" | \"absolute_case\" | \"relative\" | \"relative_case\"\n>;\n\n/** See {@link ActivityFrequencyMode} — a total, documented, tested resolution. */\nexport function resolveActivityFrequencyMode(mode: FrequencyMode): ActivityFrequencyMode {\n switch (mode) {\n case \"absolute_case\":\n return \"absolute_case\";\n case \"relative\":\n return \"relative\";\n case \"relative_case\":\n case \"relative_antecedent\":\n case \"relative_consequent\":\n return \"relative_case\";\n case \"max_repetitions\":\n case \"absolute\":\n default:\n return \"absolute\";\n }\n}\n\n/**\n * The frequency readings a TRANSITION can answer from `TransitionStats`.\n *\n * Only `max_repetitions` is unsupported — the per-case maximum is not retained by\n * discovery — and it resolves to `absolute`, the count it is a maximum of.\n */\nexport type TransitionFrequencyMode = Exclude<FrequencyMode, \"max_repetitions\">;\n\n/** See {@link TransitionFrequencyMode}. */\nexport function resolveTransitionFrequencyMode(mode: FrequencyMode): TransitionFrequencyMode {\n return mode === \"max_repetitions\" ? \"absolute\" : mode;\n}\n\nconst PERFORMANCE_AGGS = new Set<string>([\n \"median\",\n \"mean\",\n \"min\",\n \"max\",\n \"sum\",\n \"p90\",\n \"trimmed_mean\",\n]);\n\n/** Whether a metric names a duration aggregate rather than a frequency reading. */\nexport function isPerformanceMetric(metric: ProcessMetric): metric is PerformanceAgg {\n return PERFORMANCE_AGGS.has(metric);\n}\n\nconst PERFORMANCE_AGG_LABELS: Readonly<Record<PerformanceAgg, string>> = Object.freeze({\n median: \"Median duration\",\n mean: \"Mean duration\",\n min: \"Fastest\",\n max: \"Slowest\",\n sum: \"Total duration\",\n p90: \"90th percentile duration\",\n trimmed_mean: \"Trimmed mean duration\",\n});\n\nconst ACTIVITY_FREQUENCY_LABELS: Readonly<Record<ActivityFrequencyMode, string>> = Object.freeze({\n absolute: \"Occurrences\",\n absolute_case: \"Cases\",\n relative: \"Share of events\",\n relative_case: \"Share of cases\",\n});\n\nconst TRANSITION_FREQUENCY_LABELS: Readonly<Record<TransitionFrequencyMode, string>> =\n Object.freeze({\n absolute: \"Transitions\",\n absolute_case: \"Cases\",\n relative: \"Share of transitions\",\n relative_case: \"Share of cases\",\n relative_antecedent: \"Share of source traffic\",\n relative_consequent: \"Share of target traffic\",\n });\n\n/** What a node's metric is CALLED once resolved — the eyebrow and the a11y name use it. */\nexport function nodeMetricLabel(metric: ProcessMetric): string {\n return isPerformanceMetric(metric)\n ? PERFORMANCE_AGG_LABELS[metric]\n : ACTIVITY_FREQUENCY_LABELS[resolveActivityFrequencyMode(metric)];\n}\n\n/** What an edge's metric is CALLED once resolved. */\nexport function edgeMetricLabel(metric: ProcessMetric): string {\n return isPerformanceMetric(metric)\n ? PERFORMANCE_AGG_LABELS[metric]\n : TRANSITION_FREQUENCY_LABELS[resolveTransitionFrequencyMode(metric)];\n}\n\n/** Whether a resolved reading is a `0..1` share rather than a count. */\nfunction isShare(metric: ProcessMetric): boolean {\n if (isPerformanceMetric(metric)) return false;\n const resolved = resolveTransitionFrequencyMode(metric);\n return resolved !== \"absolute\" && resolved !== \"absolute_case\";\n}\n\n// ── Metric values ────────────────────────────────────────────────────────────\n\n/** The node metric of one activity, as a plain number. Pure and total. */\nexport function activityMetricValue(\n activity: ActivityStats,\n metric: ProcessMetric,\n totals: ProcessGraph[\"totals\"],\n): number {\n if (isPerformanceMetric(metric)) return performanceValue(activity.duration, metric);\n switch (resolveActivityFrequencyMode(metric)) {\n case \"absolute_case\":\n return activity.cases;\n case \"relative\":\n return totals.events === 0 ? 0 : activity.instances / totals.events;\n case \"relative_case\":\n return totals.cases === 0 ? 0 : activity.cases / totals.cases;\n case \"absolute\":\n default:\n return activity.instances;\n }\n}\n\n/** Denominators the two per-endpoint edge shares need, computed once per graph. */\nexport interface ProcessEdgeDenominators {\n /** Total of every transition count in the graph. */\n total: number;\n /** Activity → total count of the transitions LEAVING it. */\n outgoing: Map<string, number>;\n /** Activity → total count of the transitions ENTERING it. */\n incoming: Map<string, number>;\n}\n\n/** Build the per-graph denominators the antecedent/consequent shares divide by. */\nexport function processEdgeDenominators(\n transitions: readonly TransitionStats[],\n): ProcessEdgeDenominators {\n const outgoing = new Map<string, number>();\n const incoming = new Map<string, number>();\n let total = 0;\n for (const t of transitions) {\n total += t.count;\n outgoing.set(t.source, (outgoing.get(t.source) ?? 0) + t.count);\n incoming.set(t.target, (incoming.get(t.target) ?? 0) + t.count);\n }\n return { total, outgoing, incoming };\n}\n\nfunction ratio(numerator: number, denominator: number): number {\n return denominator === 0 ? 0 : numerator / denominator;\n}\n\n/** The edge metric of one transition, as a plain number. Pure and total. */\nexport function transitionMetricValue(\n transition: TransitionStats,\n metric: ProcessMetric,\n totals: ProcessGraph[\"totals\"],\n denominators: ProcessEdgeDenominators,\n): number {\n if (isPerformanceMetric(metric)) return performanceValue(transition.duration, metric);\n switch (resolveTransitionFrequencyMode(metric)) {\n case \"absolute_case\":\n return transition.caseCount;\n case \"relative\":\n return ratio(transition.count, denominators.total);\n case \"relative_case\":\n return ratio(transition.caseCount, totals.cases);\n case \"relative_antecedent\":\n return ratio(transition.count, denominators.outgoing.get(transition.source) ?? 0);\n case \"relative_consequent\":\n return ratio(transition.count, denominators.incoming.get(transition.target) ?? 0);\n case \"absolute\":\n default:\n return transition.count;\n }\n}\n\n// ── Formatting ───────────────────────────────────────────────────────────────\n\n/** Milliseconds per unit, largest first — the ladder {@link formatDurationMs} walks. */\nconst DURATION_LADDER: readonly (readonly [number, string])[] = [\n [86_400_000, \"d\"],\n [3_600_000, \"h\"],\n [60_000, \"min\"],\n [1_000, \"s\"],\n];\n\n/**\n * A duration in milliseconds, spoken in the largest unit that leaves a number at or above\n * one.\n *\n * One decimal below 10 and none above, so \"3.4 d\" and \"18 h\" both read as measurements\n * rather than as false precision. A non-finite or negative input answers an em dash — a\n * process map has legitimate holes (an activity that never followed anything), and a\n * printed `NaN` is worse than a printed dash.\n */\nexport function formatDurationMs(ms: number): string {\n if (!Number.isFinite(ms) || ms < 0) return \"—\";\n if (ms === 0) return \"0 s\";\n for (const [size, unit] of DURATION_LADDER) {\n if (ms >= size) {\n const scaled = ms / size;\n return `${scaled < 10 ? scaled.toFixed(1) : Math.round(scaled)} ${unit}`;\n }\n }\n return `${Math.round(ms)} ms`;\n}\n\n/** A count, grouped by the runtime locale; a share, as a percentage with one decimal. */\nexport function formatMetricValue(value: number, metric: ProcessMetric): string {\n if (isPerformanceMetric(metric)) return formatDurationMs(value);\n if (!Number.isFinite(value)) return \"—\";\n if (isShare(metric)) return `${(value * 100).toFixed(1)}%`;\n return value.toLocaleString();\n}\n\n// ── Identity ─────────────────────────────────────────────────────────────────\n\n/**\n * The stable id of a transition: source, {@link EDGE_KEY_SEPARATOR}, target — keyed\n * exactly the way `/core` keys it. A selection, a performance-layer lookup and a React\n * Flow edge therefore all name the same edge with the same string, with no translation.\n */\nexport function processEdgeId(source: string, target: string): string {\n return `${source}${EDGE_KEY_SEPARATOR}${target}`;\n}\n\n/**\n * A cheap, order-independent key over the graph's STRUCTURE — node ids and edge ids only,\n * never a metric. `useProcessLayout` caches on it, which is what lets a metric switch\n * reuse the existing positions instead of re-running dagre.\n */\nexport function processGraphStructureKey(graph: ProcessGraph): string {\n const activities = graph.activities.map((a) => a.id).sort();\n const transitions = graph.transitions.map((t) => processEdgeId(t.source, t.target)).sort();\n return `${activities.join(EDGE_KEY_SEPARATOR)}|${transitions.join(EDGE_KEY_SEPARATOR)}`;\n}\n\n// ── Selection ────────────────────────────────────────────────────────────────\n\n/** The activities and transitions a selection keeps in play. */\nexport interface ProcessSelectionNeighbourhood {\n activities: Set<string>;\n transitions: Set<string>;\n}\n\n/**\n * Which activities and transitions a selection keeps in play.\n *\n * Selecting an ACTIVITY keeps that activity, every activity directly connected to it, and\n * every edge incident to it. Selecting a TRANSITION keeps its two endpoints and itself.\n * Everything else becomes `\"excluded\"` — dimmed, but never `aria-disabled` (see\n * {@link resolveSelectionState}).\n */\nexport function selectionNeighbourhood(\n graph: ProcessGraph,\n selection: ProcessSelection | null | undefined,\n): ProcessSelectionNeighbourhood | null {\n if (!selection) return null;\n const activities = new Set<string>();\n const transitions = new Set<string>();\n if (selection.kind === \"activity\") {\n activities.add(selection.id);\n for (const t of graph.transitions) {\n if (t.source !== selection.id && t.target !== selection.id) continue;\n transitions.add(processEdgeId(t.source, t.target));\n activities.add(t.source);\n activities.add(t.target);\n }\n return { activities, transitions };\n }\n for (const t of graph.transitions) {\n if (processEdgeId(t.source, t.target) !== selection.id) continue;\n transitions.add(selection.id);\n activities.add(t.source);\n activities.add(t.target);\n }\n return { activities, transitions };\n}\n\n/**\n * Resolve one element's tri-state {@link ProcessSelectionState} from BOTH the coordinated\n * click selection and the active filter's per-element states — the two channels\n * `ProcessMap` renders together. Five rules, in precedence order (RM-052 round 2, #227):\n *\n * 1. **The element IS the click target** → `\"selected\"`. This wins over everything else,\n * including an active filter's own `\"excluded\"` — clicking an excluded element is how a\n * reader re-focuses it, so the click must always win.\n * 2. **`states` already says `\"excluded\"`** → `\"excluded\"`. This must be checked BEFORE\n * rule 3: a filter-excluded element stays excluded even when it happens to sit inside\n * the neighbourhood of whatever is currently click-selected — the filter's exclusion is\n * not something a click's neighbourhood should be able to override.\n * 3. **A click target exists and this element sits outside its neighbourhood** →\n * `\"excluded\"`.\n * 4. **`states` carries an entry for this element** → that entry (`\"associated\"` or\n * `\"selected\"` from a filter's own point of view — reserved for a future filter shape\n * that can name a \"primary\" match; today filters only ever produce `\"excluded\"`\n * entries, which rule 2 already handles).\n * 5. **Otherwise** → `\"associated\"`, the default.\n */\nexport function resolveSelectionState(\n id: string,\n kind: ProcessSelectionKind,\n selection: ProcessSelection | null | undefined,\n neighbourhood: ProcessSelectionNeighbourhood | null,\n states?: ProcessSelectionStates,\n): ProcessSelectionState {\n if (selection && selection.kind === kind && selection.id === id) return \"selected\";\n\n const namespace = kind === \"activity\" ? states?.activities : states?.transitions;\n const filterState = namespace?.[id];\n\n if (filterState === \"excluded\") return \"excluded\";\n\n if (selection && neighbourhood) {\n const kept = kind === \"activity\" ? neighbourhood.activities : neighbourhood.transitions;\n if (!kept.has(id)) return \"excluded\";\n }\n\n if (filterState !== undefined) return filterState;\n\n return \"associated\";\n}\n\n// ── The model ────────────────────────────────────────────────────────────────\n\n/** Inputs to {@link buildProcessMapModel}. */\nexport interface BuildProcessMapModelOptions {\n graph: ProcessGraph;\n metric: ProcessMetricSpec;\n /** Optional rework tallies (from `/core`'s `detectRework`) for the node badge. */\n rework?: ReworkStats;\n selection?: ProcessSelection | null;\n /**\n * Per-element states an active filter contributes (RM-052 round 2, #227) — merged with\n * the click `selection` by {@link resolveSelectionState}. Omitting it reproduces the\n * pre-filter behaviour exactly: every element resolves through rules 1/3/5 alone.\n */\n selectionStates?: ProcessSelectionStates;\n /**\n * Edge ids `layoutFlow` reported as running against the layout direction. Supplied on\n * the SECOND pass, after a layout exists — the first pass has no ranks to read, so\n * every edge starts `isBackEdge: false` exactly as `discoverGraph` leaves it.\n */\n backEdgeIds?: ReadonlySet<string>;\n}\n\n/**\n * Map a discovered (or abstracted) graph plus a metric choice into everything both the\n * canvas and the table render. Pure: same inputs, same model, no clock and no DOM.\n */\nexport function buildProcessMapModel({\n graph,\n metric,\n rework,\n selection,\n selectionStates,\n backEdgeIds,\n}: BuildProcessMapModelOptions): ProcessMapModel {\n const neighbourhood = selectionNeighbourhood(graph, selection);\n const denominators = processEdgeDenominators(graph.transitions);\n\n const nodeValues = graph.activities.map((a) => activityMetricValue(a, metric.node, graph.totals));\n const nodeDomain = minMax(nodeValues);\n const edgeValues = graph.transitions.map((t) =>\n transitionMetricValue(t, metric.edge, graph.totals, denominators),\n );\n const edgeDomain = minMax(edgeValues);\n\n const resolvedNodeMetricLabel = nodeMetricLabel(metric.node);\n const resolvedEdgeMetricLabel = edgeMetricLabel(metric.edge);\n const nodeSpan = nodeDomain[1] - nodeDomain[0];\n\n const nodes: ProcessMapNode[] = graph.activities.map((activity, index) => {\n const primaryValue = nodeValues[index] as number;\n const secondaryLabel =\n metric.secondary === undefined\n ? undefined\n : formatMetricValue(\n activityMetricValue(activity, metric.secondary, graph.totals),\n metric.secondary,\n );\n const reworkEntry = rework?.perActivity[activity.id];\n const reworkCount =\n reworkEntry === undefined ? undefined : reworkEntry.selfLoops + reworkEntry.loops;\n const selectionState = resolveSelectionState(\n activity.id,\n \"activity\",\n selection,\n neighbourhood,\n selectionStates,\n );\n const data: ProcessActivityNodeData = {\n title: activity.label || activity.id,\n metricLabel: resolvedNodeMetricLabel,\n primaryLabel: formatMetricValue(primaryValue, metric.node),\n primaryValue,\n secondaryLabel,\n // A single-activity graph (or a flat metric) has no domain to sit in; half\n // saturation is the honest answer — \"no comparison available\" — rather than a\n // full-strength fill claiming this is the busiest node in a set of one.\n saturation: nodeSpan === 0 ? 0.5 : (primaryValue - nodeDomain[0]) / nodeSpan,\n isStart: activity.isStart,\n isEnd: activity.isEnd,\n reworkCount,\n selectionState,\n };\n return {\n id: activity.id,\n type: \"process-activity\",\n position: { x: 0, y: 0 },\n data,\n draggable: false,\n // React Flow reads a node's accessible name from the node OBJECT, not from the\n // component — the same seam `withWeightedEdgeAria` exists for on the edge side\n // (#285). Without this the node announces only its id and the metric the map\n // exists to show reaches no assistive technology.\n ariaLabel: activityAriaLabel(data),\n // No `aria-disabled` here even when `selectionState === \"excluded\"` — an excluded\n // node stays fully operable (clicking it is how a reader filters it back in), and\n // `activityAriaLabel` already appends the word \"excluded\" to its accessible name, so\n // assistive technology gets the state as real text rather than a lie about\n // disablement. See `ProcessSelectionState`'s own doc comment.\n domAttributes: {\n \"data-selection\": selectionState,\n \"data-activity\": activity.id,\n } as ProcessMapNode[\"domAttributes\"],\n };\n });\n\n const edges: ProcessMapEdge[] = graph.transitions.map((transition, index) => {\n const value = edgeValues[index] as number;\n const id = processEdgeId(transition.source, transition.target);\n const secondaryLabel =\n metric.secondary === undefined\n ? undefined\n : formatMetricValue(\n transitionMetricValue(transition, metric.secondary, graph.totals, denominators),\n metric.secondary,\n );\n const selectionState = resolveSelectionState(\n id,\n \"transition\",\n selection,\n neighbourhood,\n selectionStates,\n );\n const data: ProcessTransitionEdgeData = {\n source: transition.source,\n target: transition.target,\n weight: value,\n value,\n valueDomain: edgeDomain,\n label: formatMetricValue(value, metric.edge),\n secondaryLabel,\n isSelfLoop: transition.isSelfLoop,\n isBackEdge: backEdgeIds?.has(id) ?? transition.isBackEdge,\n selectionState,\n };\n return {\n id,\n source: transition.source,\n target: transition.target,\n type: \"process-transition\",\n data,\n ariaLabel: transitionAriaLabel(data, resolvedEdgeMetricLabel),\n };\n });\n\n const activityRows: ProcessActivityRow[] = nodes.map((node) => ({\n id: node.id,\n title: node.data.title,\n primaryLabel: node.data.primaryLabel,\n secondaryLabel: node.data.secondaryLabel,\n reworkCount: node.data.reworkCount,\n role: activityRole(node.data),\n selectionState: node.data.selectionState,\n }));\n\n const transitionRows: ProcessTransitionRow[] = edges.map((edge) => {\n const data = edge.data as ProcessTransitionEdgeData;\n return {\n id: edge.id,\n source: data.source,\n target: data.target,\n primaryLabel: data.label,\n secondaryLabel: data.secondaryLabel,\n shape: transitionShape(data),\n selectionState: data.selectionState,\n };\n });\n\n const excludedCounts: ProcessMapExcludedCounts = {\n activities: nodes.reduce((n, node) => n + (node.data.selectionState === \"excluded\" ? 1 : 0), 0),\n totalActivities: nodes.length,\n transitions: edges.reduce(\n (n, edge) =>\n n + ((edge.data as ProcessTransitionEdgeData).selectionState === \"excluded\" ? 1 : 0),\n 0,\n ),\n totalTransitions: edges.length,\n };\n\n return {\n nodes,\n edges,\n nodeDomain,\n edgeDomain,\n nodeMetricLabel: resolvedNodeMetricLabel,\n edgeMetricLabel: resolvedEdgeMetricLabel,\n activityRows,\n transitionRows,\n formatEdgeValue: (value: number) => formatMetricValue(value, metric.edge),\n excludedCounts,\n };\n}\n\n/**\n * The word for an activity's position in the process — printed in the table's own column\n * and folded into the node's accessible name, so \"this is where cases start\" survives\n * without the start/end glyph.\n */\nexport function activityRole(data: ProcessActivityNodeData): string {\n if (data.isStart && data.isEnd) return \"Start and end\";\n if (data.isStart) return \"Start\";\n if (data.isEnd) return \"End\";\n return \"Step\";\n}\n\n/** The word for an edge's shape — the non-colour channel, said in text. */\nexport function transitionShape(data: ProcessTransitionEdgeData): string {\n if (data.isSelfLoop) return \"Self-loop\";\n if (data.isBackEdge) return \"Back edge\";\n return \"Forward\";\n}\n\n/**\n * The CANONICAL (English, un-localized) word for a row's selection/filter state (#373) —\n * this module is pure data shaping with no locale seam of its own, so it cannot resolve a\n * translation. It stays exported for a non-React caller (tests, a console, a non-locale\n * context). The State column's own rendered CELLS resolve the localized text instead, via\n * {@link PROCESS_SELECTION_STATE_MESSAGE_KEYS} + `t()` at the render site\n * (`process-map.tsx`) — see that key map's own comment (#413 review). `\"associated\"` is the\n * ordinary case and prints nothing, so the column reads as a marker for the two states that\n * matter, not as noise repeated on every row.\n */\nexport function selectionStateLabel(state: ProcessSelectionState): string {\n if (state === \"selected\") return \"Selected\";\n if (state === \"excluded\") return \"Excluded\";\n return \"\";\n}\n\n/**\n * The locale message key for each NON-ORDINARY selection/filter state (#413 review,\n * PRRT_kwDOT6D7ts6gJX2C) — the State column's cells are the table twin's own\n * screen-reader channel for row state (#373), so leaving them printing\n * {@link selectionStateLabel}'s bare English word left a non-English `LocaleProvider`\n * with untranslated visible AND accessible content. `\"associated\"` has no entry: it is the\n * ordinary case and the column prints nothing for it, same as {@link selectionStateLabel}.\n */\nexport const PROCESS_SELECTION_STATE_MESSAGE_KEYS: Readonly<\n Partial<Record<ProcessSelectionState, string>>\n> = Object.freeze({\n selected: \"process.map.stateSelected\",\n excluded: \"process.map.stateExcluded\",\n});\n\n/** The accessible name of one activity node. */\nexport function activityAriaLabel(data: ProcessActivityNodeData): string {\n const parts = [`${data.title} — ${activityRole(data).toLowerCase()}`];\n parts.push(`${data.metricLabel} ${data.primaryLabel}`);\n if (data.secondaryLabel) parts.push(data.secondaryLabel);\n if (data.reworkCount) parts.push(`${data.reworkCount} repeated executions`);\n if (data.selectionState !== \"associated\") parts.push(data.selectionState);\n return parts.join(\", \");\n}\n\n/** The accessible name of one transition edge. */\nexport function transitionAriaLabel(data: ProcessTransitionEdgeData, metricLabel: string): string {\n const parts = [\n data.isSelfLoop\n ? `Self-loop on ${data.source}`\n : `${data.isBackEdge ? \"Back edge\" : \"Transition\"} from ${data.source} to ${data.target}`,\n `${metricLabel} ${data.label}`,\n ];\n if (data.secondaryLabel) parts.push(data.secondaryLabel);\n if (data.selectionState !== \"associated\") parts.push(data.selectionState);\n return parts.join(\", \");\n}\n","\"use client\";\n\n/**\n * ProcessMap — the directly-follows map every process-mining session opens on (RM-051).\n *\n * ## What it is\n *\n * A composition, not a component library. Every mark on the canvas comes from\n * `@elabs-ai/components-flow`: the frame is `CanvasShell`, the layout is `layoutFlow`\n * (dagre), the controls are `ZoomControls`, the overview is `FlowMiniMap`, the key is\n * `Legend variant=\"scale\"`, and the nodes and edges are `FlowNode` / `FlowWeightedEdge` /\n * `FlowSelfLoopEdge` wrapped by this package's two thin process-shaped components. What\n * lives HERE is the domain reading — which number a node prints, which edges a selection\n * keeps, what a right-click means — and nothing else.\n *\n * ## Two inputs, one model\n *\n * Pass a `graph` you already discovered, or pass a `log` and let the map run\n * `discoverGraph` (and `detectRework`) internally — the convenience path for a story, a\n * prototype or a screen that has no explorer around it yet. `abstraction` runs\n * `abstractGraph` over whichever of the two you gave. All three are memoized on their own\n * inputs, so a metric change never re-derives the graph.\n *\n * ## Why the layout does not twitch\n *\n * `useProcessLayout` caches dagre's output on the graph's STRUCTURE (see\n * `processGraphStructureKey`) rather than on the model, so switching the metric,\n * selecting an activity or hovering a path re-renders without moving a single node. The\n * observable proof is `layoutRuns`, which a test asserts against.\n *\n * ## Accessibility\n *\n * - Nodes are React Flow's own focusable elements, and `applyPositions` emits them SORTED\n * BY THEIR LAID-OUT POSITION (top-to-bottom then left-to-right for `TB`, the transpose\n * for `LR`) rather than in model order — so DOM order, and therefore `Tab` order, is\n * layout order. `Enter`/`Space` selects via React Flow's `elementSelectionKeys`\n * handling, not a re-implementation.\n * - Edges are NOT separate tab stops (`edgesFocusable={false}`). Every edge already\n * carries a focusable, named label pill, so leaving the edge `<g>` focusable too put\n * two stops on every arrow and buried the activities behind them. Keys pressed on a\n * pill still reach the map: `EdgeLabelRenderer` is a React portal, so its events bubble\n * up the REACT tree through `ProcessTransitionEdge`, which knows its own edge id and\n * hands it to `onEdgeKey` (`ProcessMapEdgeKeyContext`).\n * - `F` opens the filter-intent menu for whatever is focused, falling back to the current\n * selection. The same menu is reachable with the mouse by right-clicking a node or an\n * edge, and with neither by the always-present \"Filter…\" button — a keyboard shortcut\n * that is the ONLY way to reach a menu is not an affordance.\n * - `tableView` renders the identical numbers as two `Table`s. It shares\n * `map-model.ts`'s formatting, so the twin can never drift from the canvas.\n */\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type KeyboardEvent as ReactKeyboardEvent,\n type MouseEvent as ReactMouseEvent,\n} from \"react\";\nimport { Filter } from \"lucide-react\";\nimport type { NodeChange } from \"@xyflow/react\";\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n StatePanel,\n Table,\n TableBody,\n TableCaption,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n useLocale,\n} from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport {\n CanvasShell,\n FlowMiniMap,\n Legend,\n ZoomControls,\n type FlowLayoutDirection,\n} from \"@elabs-ai/components-flow\";\nimport { abstractGraph, type AbstractionOptions } from \"../core/abstract-graph\";\nimport { detectRework, type ReworkStats } from \"../core/detect-rework\";\nimport { discoverGraph } from \"../core/discover-graph\";\nimport type { EventLog, ProcessGraph } from \"../core/types\";\nimport {\n buildProcessMapModel,\n processGraphStructureKey,\n PROCESS_FILTER_INTENT_KINDS,\n PROCESS_FILTER_INTENT_LABELS,\n PROCESS_FILTER_INTENT_MESSAGE_KEYS,\n PROCESS_SELECTION_STATE_MESSAGE_KEYS,\n type ProcessFilterIntent,\n type ProcessMapEdge,\n type ProcessMapModel,\n type ProcessMapNode,\n type ProcessMetricSpec,\n type ProcessSelection,\n type ProcessSelectionState,\n type ProcessSelectionStates,\n} from \"./map-model\";\nimport { ProcessActivityNode } from \"./process-activity-node\";\nimport { ProcessTransitionEdge } from \"./process-transition-edge\";\nimport {\n EMPTY_PROCESS_MAP_HOVER,\n ProcessMapEdgeKeyContext,\n ProcessMapHoverContext,\n type ProcessMapHoverState,\n} from \"./process-map-context\";\nimport {\n PROCESS_MAP_NODE_MOTION_CLASS,\n useProcessLayout,\n type UseProcessLayoutResult,\n} from \"./use-process-layout\";\n\n/**\n * Registered once at module scope. React Flow re-creates every node when this object's\n * identity changes, so an inline literal would remount the whole canvas each render.\n */\nconst NODE_TYPES = { \"process-activity\": ProcessActivityNode };\nconst EDGE_TYPES = { \"process-transition\": ProcessTransitionEdge };\n\n/**\n * The zoom the map refuses to open below, however big the process is.\n *\n * A discovered log is routinely wider than any pane, and a fit that shows ALL of it shows\n * none of it: the 24-activity fixture frames at 0.28, where the activity name (14 px on\n * the card) prints at 3.9 px and the card is a grey smudge. The floor is set from the\n * card's own type rather than from a graph size: at 0.75 the name renders at 10.5 px and\n * the secondary line (12 px) at 9 px, which is the smallest either stays a word.\n *\n * It is the OPENING zoom only — {@link MIN_ZOOM} still lets the reader pull all the way\n * back to an overview, and the minimap says where in the process the pane is sitting. A\n * fit clamped by this floor is anchored on the START of the process, not its middle;\n * `CanvasShell` does that (see `anchorToStartWhenClamped`).\n */\nexport const PROCESS_MAP_LEGIBLE_ZOOM = 0.75;\n\n/**\n * How the canvas re-frames itself after a layout.\n *\n * `padding` is generous because the edge label pills are portalled OUTSIDE the SVG and so\n * contribute nothing to React Flow's fitted bounds — a tight fit clips the pills on the\n * outermost transitions. `maxZoom: 1` stops a two-activity graph from being blown up to\n * React Flow's default 2× ceiling, which is what the reader was seeing.\n */\nconst FIT_VIEW_OPTIONS = {\n padding: 0.15,\n maxZoom: 1,\n minZoom: PROCESS_MAP_LEGIBLE_ZOOM,\n} as const;\n\n/**\n * How far out the reader may zoom.\n *\n * React Flow's own floor is `0.5`, and `fitView` CLAMPS to it — so a process with more\n * ranks than the pane is tall could not be framed at all: the fit stopped at 0.5 and left\n * the last activities below the fold with no way to pull back. Measured on the 11-activity\n * fixture in a 1200×576 pane, where the honest fit is 0.42. A discovered process routinely\n * has three or four times that many activities, so the floor has to be a real overview\n * zoom, not a legibility one — legibility at the far end of the dial is what the minimap\n * and the table twin are for.\n */\nconst MIN_ZOOM = 0.1;\n\n/** Props for {@link ProcessMap}. `onSelect` shadows the DOM handler, so it is omitted. */\nexport interface ProcessMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** A discovered graph. Takes precedence over {@link log} when both are given. */\n graph?: ProcessGraph;\n /**\n * An event log to discover from, for a surface with no explorer around it. Runs\n * `/core`'s `discoverGraph` and `detectRework` internally, memoized on the log.\n */\n log?: EventLog;\n /** Fractions of activities / paths to KEEP, as `/core`'s `abstractGraph` reads them. */\n abstraction?: Pick<AbstractionOptions, \"activities\" | \"paths\">;\n /** Which readings the nodes and edges paint. */\n metric: ProcessMetricSpec;\n /** Rework tallies for the node badge. Derived from {@link log} when that is given. */\n rework?: ReworkStats;\n /** Controlled selection. Omit for an uncontrolled map that owns its own. */\n selection?: ProcessSelection | null;\n /**\n * Per-element states an active filter contributes (RM-052 round 2, #227) — e.g. from\n * `useProcessExplorer`'s own `selectionStates`. Applying or clearing a filter never adds\n * or removes a node or an edge (Invariant F): it only re-inks the elements a filter\n * dropped as `\"excluded\"` (dimmed, never `aria-disabled`). Omit for a map with no\n * filtering, which reproduces today's selection-only behaviour exactly.\n */\n selectionStates?: ProcessSelectionStates;\n /** Fires with the new selection, or `null` when the reader deselects. */\n onSelect?: (target: ProcessSelection | null) => void;\n /** Fires with an intent from the filter menu — hand it straight to `filterLog`. */\n onFilterIntent?: (intent: ProcessFilterIntent) => void;\n /** @default \"TB\" */\n direction?: FlowLayoutDirection;\n /** @default true */\n showMiniMap?: boolean;\n /** @default true */\n showLegend?: boolean;\n /** Render the accessible table twin instead of the canvas. @default false */\n tableView?: boolean;\n /** No graph yet. Renders the loading panel rather than an empty canvas. */\n loading?: boolean;\n /**\n * Accessible name for the canvas region. Defaults to the localized\n * `process.map.label` message.\n */\n label?: string;\n}\n\n/** Resolve the graph the map paints, running `/core` only when its own inputs change. */\nfunction useResolvedGraph(\n graph: ProcessGraph | undefined,\n log: EventLog | undefined,\n abstraction: Pick<AbstractionOptions, \"activities\" | \"paths\"> | undefined,\n): ProcessGraph | undefined {\n const discovered = useMemo(\n () => (graph ? undefined : log ? discoverGraph(log) : undefined),\n [graph, log],\n );\n const base = graph ?? discovered;\n const activities = abstraction?.activities;\n const paths = abstraction?.paths;\n return useMemo(() => {\n if (!base) return undefined;\n if (activities === undefined && paths === undefined) return base;\n return abstractGraph(base, { activities: activities ?? 1, paths: paths ?? 1 });\n }, [base, activities, paths]);\n}\n\n/**\n * The process map.\n *\n * @example\n * ```tsx\n * <ProcessMap log={log} metric={{ node: \"absolute_case\", edge: \"absolute\" }} />\n * ```\n */\nexport function ProcessMap({\n graph,\n log,\n abstraction,\n metric,\n rework,\n selection,\n selectionStates,\n onSelect,\n onFilterIntent,\n direction = \"TB\",\n showMiniMap = true,\n showLegend = true,\n tableView = false,\n loading = false,\n label,\n className,\n ...props\n}: ProcessMapProps) {\n // Every user-visible string on this surface goes through the locale seam (ADR 0017);\n // the table twin's headers especially, since that table IS the accessible reading of\n // the canvas — an untranslated header leaves numbers with no measure name.\n const { t } = useLocale();\n const mapLabel = label ?? t(\"process.map.label\");\n // The State column's own cell text (#413 review, PRRT_kwDOT6D7ts6gJX2C) — `map-model.ts`\n // has no locale seam of its own, so the render site resolves\n // `PROCESS_SELECTION_STATE_MESSAGE_KEYS` through `t()` instead of printing\n // `selectionStateLabel`'s bare English word. `\"associated\"` has no key and prints nothing,\n // same as before.\n const selectionStateText = useCallback(\n (state: ProcessSelectionState): string => {\n const key = PROCESS_SELECTION_STATE_MESSAGE_KEYS[state];\n return key ? t(key) : \"\";\n },\n [t],\n );\n const resolved = useResolvedGraph(graph, log, abstraction);\n const derivedRework = useMemo(\n () => (rework ? undefined : log ? detectRework(log) : undefined),\n [rework, log],\n );\n const activeRework = rework ?? derivedRework;\n\n // Controlled/uncontrolled selection, derived once and never flipped between modes.\n const isControlled = selection !== undefined;\n const [ownSelection, setOwnSelection] = useState<ProcessSelection | null>(null);\n const activeSelection = isControlled ? (selection ?? null) : ownSelection;\n\n const applySelection = useCallback(\n (next: ProcessSelection | null) => {\n if (!isControlled) setOwnSelection(next);\n onSelect?.(next);\n },\n [isControlled, onSelect],\n );\n\n const structureKey = useMemo(\n () => (resolved ? processGraphStructureKey(resolved) : \"\"),\n [resolved],\n );\n\n // PASS 1 — a model with no back-edge knowledge, because nothing has been laid out yet.\n const firstPass = useMemo(\n () =>\n resolved\n ? buildProcessMapModel({\n graph: resolved,\n metric,\n rework: activeRework,\n selection: activeSelection,\n selectionStates,\n })\n : null,\n [resolved, metric, activeRework, activeSelection, selectionStates],\n );\n\n // ── Measured node sizes ───────────────────────────────────────────────────\n // dagre needs to know how big an activity card actually is, and React Flow only ever\n // writes its measurements onto its OWN internal record — the node objects this\n // component builds never learn them. Without this, `layoutFlow` fell back to React\n // Flow's generic 172×40 default for a card that really renders ~176×85, so every rank\n // was laid out about half a card too close and the edge label pills landed on the nodes\n // below them. Collecting the dimensions here is what lets the SECOND layout pass use\n // real sizes.\n const [nodeSizes, setNodeSizes] = useState<Record<string, { width: number; height: number }>>({});\n const handleNodesChange = useCallback((changes: NodeChange<ProcessMapNode>[]) => {\n setNodeSizes((current) => {\n let next: Record<string, { width: number; height: number }> | null = null;\n for (const change of changes) {\n if (change.type !== \"dimensions\" || !change.dimensions) continue;\n const previous = current[change.id];\n if (\n previous?.width === change.dimensions.width &&\n previous.height === change.dimensions.height\n ) {\n continue;\n }\n next ??= { ...current };\n next[change.id] = { width: change.dimensions.width, height: change.dimensions.height };\n }\n return next ?? current;\n });\n }, []);\n\n const layoutNodes = useMemo(() => {\n const source = firstPass?.nodes ?? EMPTY_NODES;\n let changed = false;\n const next = source.map((node) => {\n const measured = nodeSizes[node.id];\n if (!measured) return node;\n changed = true;\n return { ...node, measured };\n });\n return changed ? next : source;\n }, [firstPass, nodeSizes]);\n\n /**\n * The measured ENVELOPE, quantized — the layout cache key's size half.\n *\n * Deliberately not the per-node sizes: a metric switch rewrites every subtitle, which\n * can nudge a card's width by a pixel, and keying on that would re-run dagre for a\n * metric change — exactly the twitch `useProcessLayout`'s cache exists to prevent (and\n * what `layoutRuns` asserts against). The envelope, rounded to 8px, moves when the card\n * ANATOMY changes (a meter appears, a line wraps) and sits still otherwise.\n */\n const sizeKey = useMemo(() => {\n let width = 0;\n let height = 0;\n for (const size of Object.values(nodeSizes)) {\n if (size.width > width) width = size.width;\n if (size.height > height) height = size.height;\n }\n if (width === 0 && height === 0) return \"unmeasured\";\n return `${Math.round(width / 8)}x${Math.round(height / 8)}`;\n }, [nodeSizes]);\n\n const layoutKey = `${structureKey}::${sizeKey}`;\n\n const layout: UseProcessLayoutResult = useProcessLayout({\n nodes: layoutNodes,\n edges: firstPass?.edges ?? EMPTY_EDGES,\n structureKey: layoutKey,\n direction,\n });\n\n // PASS 2 — the same model, now told which edges run against the layout direction, so\n // those edges take the dashed back-edge shape. The structure key is unchanged, so this\n // is a cache HIT: `layoutRuns` does not increase.\n const model: ProcessMapModel | null = useMemo(\n () =>\n resolved\n ? buildProcessMapModel({\n graph: resolved,\n metric,\n rework: activeRework,\n selection: activeSelection,\n selectionStates,\n backEdgeIds: layout.backEdgeIds,\n })\n : null,\n [resolved, metric, activeRework, activeSelection, selectionStates, layout.backEdgeIds],\n );\n\n const positionedNodes = useMemo(\n () => (model ? applyPositions(model, layout, direction) : EMPTY_NODES),\n [model, layout, direction],\n );\n\n // ── Hover ─────────────────────────────────────────────────────────────────\n const [hover, setHover] = useState<ProcessMapHoverState>(EMPTY_PROCESS_MAP_HOVER);\n const edgesRef = useRef<ProcessMapEdge[]>([]);\n edgesRef.current = model?.edges ?? [];\n\n const handleNodeEnter = useCallback((_event: ReactMouseEvent, node: { id: string }) => {\n const incident = new Set<string>();\n for (const edge of edgesRef.current) {\n if (edge.source === node.id || edge.target === node.id) incident.add(edge.id);\n }\n setHover({ activityId: node.id, incidentEdgeIds: incident });\n }, []);\n const handleNodeLeave = useCallback(() => setHover(EMPTY_PROCESS_MAP_HOVER), []);\n\n // ── Filter-intent menu ────────────────────────────────────────────────────\n const [menuTarget, setMenuTarget] = useState<ProcessSelection | null>(null);\n const menuOpen = menuTarget !== null;\n\n /**\n * How to put focus back when the menu closes (F5).\n *\n * Radix returns focus to the TRIGGER by default, which is correct for a menu the user\n * opened from the trigger and wrong for one opened with `f` while standing on a node or\n * an arrow — that drops a keyboard user out of the graph and makes them tab back in.\n * Holds a {@link focusRestorer} rather than an element, because a portalled edge label\n * does not survive the re-render. Set only on the keyboard path; `null` means \"let Radix\n * do its usual thing\".\n */\n const returnFocusRef = useRef<(() => void) | null>(null);\n\n const openMenuFor = useCallback(\n (target: ProcessSelection | null, restoreFocus: (() => void) | null = null) => {\n if (!target) return;\n returnFocusRef.current = restoreFocus;\n setMenuTarget(target);\n },\n [],\n );\n\n // The menu filters by ACTIVITY, so a transition offers both of its endpoints.\n const menuActivities = useMemo(() => {\n if (!menuTarget) return [];\n if (menuTarget.kind === \"activity\") return [menuTarget.id];\n const edge = (model?.edges ?? []).find((e) => e.id === menuTarget.id);\n if (!edge) return [];\n return edge.source === edge.target ? [edge.source] : [edge.source, edge.target];\n }, [menuTarget, model]);\n\n /** Which element a key event is \"about\": the focused node/edge, else the selection. */\n const targetOfEvent = useCallback(\n (element: HTMLElement | null): ProcessSelection | null => {\n // React Flow stamps `data-id` on both node and edge wrappers, so the focused\n // element identifies itself; the selection is only the fallback.\n const focusedId = element?.closest<HTMLElement>(\"[data-id]\")?.dataset.id;\n if (!focusedId) return activeSelection;\n return model?.edges.some((edge) => edge.id === focusedId)\n ? { kind: \"transition\", id: focusedId }\n : { kind: \"activity\", id: focusedId };\n },\n [activeSelection, model],\n );\n\n /**\n * The two element keys, for a target the caller already resolved.\n *\n * Shared by the root handler (which resolves the target from the focused DOM element)\n * and by `ProcessTransitionEdge`, which resolves it from its own `id` because an edge's\n * label pill is portalled out of the edge's DOM subtree and so has no `[data-id]`\n * ancestor to read. Returns whether it handled the key, so the edge can stop the event\n * rather than let the root re-handle it against the wrong target.\n */\n const handleElementKey = useCallback(\n (event: ReactKeyboardEvent, target: ProcessSelection | null): boolean => {\n if (!target) return false;\n if (event.metaKey || event.ctrlKey || event.altKey) return false;\n\n // Enter / Space: React Flow's `elementSelectionKeys` handling marks its OWN\n // selection on the focused element — which is a different thing from this map's\n // domain selection. This bridges the two rather than re-implementing either: React\n // Flow still runs (no `preventDefault`), and the domain selection follows the same\n // toggle a click performs.\n if (event.key === \"Enter\" || event.key === \" \") {\n applySelection(\n activeSelection?.kind === target.kind && activeSelection.id === target.id ? null : target,\n );\n return true;\n }\n\n if (event.key !== \"f\" && event.key !== \"F\") return false;\n event.preventDefault();\n // Remember where the user was standing, so Escape puts them back there (F5).\n const active = (event.target as HTMLElement | null)?.ownerDocument.activeElement;\n openMenuFor(target, active instanceof HTMLElement ? focusRestorer(active) : null);\n return true;\n },\n [activeSelection, applySelection, openMenuFor],\n );\n\n const handleKeyDown = useCallback(\n (event: ReactKeyboardEvent<HTMLDivElement>) => {\n const element = event.target as HTMLElement | null;\n // Never steal a letter someone is typing.\n if (element?.closest(\"input, textarea, [contenteditable='true']\")) return;\n // Enter/Space only means \"select\" while standing ON a React Flow element; anywhere\n // else it is the button/menu-item activation the browser already owns.\n const onFlowElement = Boolean(element?.closest<HTMLElement>(\"[data-id]\"));\n if ((event.key === \"Enter\" || event.key === \" \") && !onFlowElement) return;\n handleElementKey(event, targetOfEvent(element));\n },\n [handleElementKey, targetOfEvent],\n );\n\n /**\n * The edge half of {@link handleElementKey}, handed to every `ProcessTransitionEdge`.\n *\n * `stopPropagation` matters: without it the same event reaches the root handler, whose\n * `targetOfEvent` finds no `[data-id]` ancestor above a portalled pill and would fall\n * back to the current SELECTION — i.e. `f` on one arrow's pill would open the menu for\n * a different element entirely.\n */\n const handleEdgeKey = useCallback(\n (edgeId: string, event: ReactKeyboardEvent) => {\n if (handleElementKey(event, { kind: \"transition\", id: edgeId })) event.stopPropagation();\n },\n [handleElementKey],\n );\n\n const emitIntent = useCallback(\n (kind: ProcessFilterIntent[\"kind\"], activity: string) => {\n onFilterIntent?.({ kind, activity } as ProcessFilterIntent);\n setMenuTarget(null);\n },\n [onFilterIntent],\n );\n\n // A selection that disappears (the graph was re-abstracted) must not keep a menu open\n // against an element that is no longer on the map.\n useEffect(() => {\n if (!menuTarget || !model) return;\n const stillThere =\n menuTarget.kind === \"activity\"\n ? model.nodes.some((n) => n.id === menuTarget.id)\n : model.edges.some((e) => e.id === menuTarget.id);\n if (!stillThere) setMenuTarget(null);\n }, [menuTarget, model]);\n\n if (loading) {\n return (\n <div\n data-slot=\"process-map\"\n data-state=\"loading\"\n className={cn(\"relative size-full min-h-64\", className)}\n {...props}\n >\n <StatePanel kind=\"loading\" title={t(\"process.map.loading\")} />\n </div>\n );\n }\n\n if (!model || model.nodes.length === 0) {\n return (\n <div\n data-slot=\"process-map\"\n data-state=\"empty\"\n className={cn(\"relative size-full min-h-64\", className)}\n {...props}\n >\n <StatePanel\n kind=\"empty\"\n title={t(\"process.map.empty\")}\n description={t(\"process.map.emptyBody\")}\n />\n </div>\n );\n }\n\n const filterMenu = (\n <DropdownMenu\n // NOT modal: a modal Radix menu calls `hideOthers`, which stamps `aria-hidden` on\n // every sibling of the menu — including the overlay that holds this menu's own\n // trigger, producing a focusable element inside an `aria-hidden` subtree (axe\n // `aria-hidden-focus`). A canvas menu also has no business scroll-locking the page.\n modal={false}\n open={menuOpen}\n onOpenChange={(open) => {\n if (!open) setMenuTarget(null);\n }}\n >\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n data-slot=\"process-map-filter-trigger\"\n aria-keyshortcuts=\"f\"\n onClick={() => {\n if (menuOpen) return;\n openMenuFor(activeSelection ?? { kind: \"activity\", id: model.nodes[0]!.id });\n }}\n >\n <Filter aria-hidden=\"true\" className=\"size-4\" />\n {t(\"process.map.filter\")}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"end\"\n data-slot=\"process-map-filter-menu\"\n // Put a keyboard user back where they were standing (F5). Radix's default is to\n // focus the TRIGGER, which is right for a menu opened FROM the trigger and wrong\n // for one opened with `f` from a node — that drops the user out of the graph and\n // makes them tab all the way back in.\n onCloseAutoFocus={(event) => {\n const restore = returnFocusRef.current;\n returnFocusRef.current = null;\n if (!restore) return;\n event.preventDefault();\n restore();\n }}\n >\n {menuActivities.map((activity, index) => (\n <div key={activity}>\n {index > 0 ? <DropdownMenuSeparator /> : null}\n <DropdownMenuLabel>{activity}</DropdownMenuLabel>\n {PROCESS_FILTER_INTENT_KINDS.map((kind) => (\n <DropdownMenuItem\n key={kind}\n // #346: the visible text alone is not unique on a transition's menu (the\n // same four intents render once per endpoint), so the accessible name\n // states which activity this item filters by, via the locale seam — the\n // visible text stays unsuffixed and compact.\n aria-label={t(PROCESS_FILTER_INTENT_MESSAGE_KEYS[kind], { activity })}\n onSelect={() => emitIntent(kind, activity)}\n >\n {PROCESS_FILTER_INTENT_LABELS[kind]}\n </DropdownMenuItem>\n ))}\n </div>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n\n // #375: one polite live region names what the active selection/filter just changed —\n // every OTHER element's per-element \"excluded\" state (activityAriaLabel/\n // transitionAriaLabel) is correct but unannounceable in aggregate; this is the summary\n // nothing else in the map derives. Two independently-pluralized fragments composed at the\n // call site (see the message's own comment) — never an empty string, even when nothing\n // is excluded, so the region is never silently blank on mount.\n //\n // #413 review (PRRT_kwDOT6D7ts6gJX2I): counts ALONE are not enough. Moving between two\n // selections/filters that exclude DIFFERENT elements but land on the SAME activity and\n // transition counts left this string byte-for-byte identical, so the region — which\n // announces content CHANGES, not model changes — stayed silent even though the map\n // re-inked. Fold in which elements are excluded (sorted, so the string is stable across a\n // render that changed nothing else), not only how many — that tracks the actual affected\n // set regardless of whether it moved via `selection` or via `selectionStates`.\n const excludedActivityNames = model.activityRows\n .filter((row) => row.selectionState === \"excluded\")\n .map((row) => row.title)\n .sort((a, b) => a.localeCompare(b));\n const excludedTransitionNames = model.transitionRows\n .filter((row) => row.selectionState === \"excluded\")\n .map((row) => `${row.source} → ${row.target}`)\n .sort((a, b) => a.localeCompare(b));\n const selectionSummary = [\n t(\"process.map.excludedActivities\", {\n count: model.excludedCounts.activities,\n total: model.excludedCounts.totalActivities,\n }),\n excludedActivityNames.length > 0\n ? t(\"process.map.excludedActivityNames\", { names: excludedActivityNames.join(\", \") })\n : null,\n t(\"process.map.excludedTransitions\", {\n count: model.excludedCounts.transitions,\n total: model.excludedCounts.totalTransitions,\n }),\n excludedTransitionNames.length > 0\n ? t(\"process.map.excludedTransitionNames\", { names: excludedTransitionNames.join(\", \") })\n : null,\n ]\n .filter((part): part is string => Boolean(part))\n .join(\" · \");\n\n if (tableView) {\n return (\n <div\n data-slot=\"process-map\"\n data-view=\"table\"\n className={cn(\"flex size-full flex-col gap-4\", className)}\n {...props}\n >\n <p\n data-slot=\"process-map-selection-summary\"\n role=\"status\"\n aria-live=\"polite\"\n className=\"sr-only\"\n >\n {selectionSummary}\n </p>\n <div className=\"flex items-center justify-end\">{filterMenu}</div>\n <Table data-slot=\"process-map-activity-table\">\n <TableCaption>\n {t(\"process.map.activityCaption\", { metric: model.nodeMetricLabel.toLowerCase() })}\n </TableCaption>\n <TableHeader>\n <TableRow>\n <TableHead scope=\"col\">{t(\"process.map.columnActivity\")}</TableHead>\n <TableHead scope=\"col\">{t(\"process.map.columnRole\")}</TableHead>\n <TableHead scope=\"col\">{model.nodeMetricLabel}</TableHead>\n <TableHead scope=\"col\">{t(\"process.map.columnRework\")}</TableHead>\n <TableHead scope=\"col\">{t(\"process.map.columnState\")}</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {model.activityRows.map((row) => (\n <TableRow\n key={row.id}\n data-selection={row.selectionState}\n // Complementary, colour-only styling hook (step 4): the real channel is the\n // State cell's text below, which fires for `selected` AND `excluded`; this\n // only lights up `TableRow`'s existing `data-[state=selected]:bg-accent`.\n data-state={row.selectionState === \"selected\" ? \"selected\" : undefined}\n >\n <TableCell>{row.title}</TableCell>\n <TableCell>{row.role}</TableCell>\n <TableCell className=\"tabular-nums\">\n {row.secondaryLabel\n ? `${row.primaryLabel} · ${row.secondaryLabel}`\n : row.primaryLabel}\n </TableCell>\n <TableCell className=\"tabular-nums\">{row.reworkCount ?? 0}</TableCell>\n <TableCell>{selectionStateText(row.selectionState)}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n <Table data-slot=\"process-map-transition-table\">\n <TableCaption>\n {t(\"process.map.transitionCaption\", {\n metric: model.edgeMetricLabel.toLowerCase(),\n })}\n </TableCaption>\n <TableHeader>\n <TableRow>\n <TableHead scope=\"col\">{t(\"process.map.columnFrom\")}</TableHead>\n <TableHead scope=\"col\">{t(\"process.map.columnTo\")}</TableHead>\n <TableHead scope=\"col\">{t(\"process.map.columnShape\")}</TableHead>\n <TableHead scope=\"col\">{model.edgeMetricLabel}</TableHead>\n <TableHead scope=\"col\">{t(\"process.map.columnState\")}</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {model.transitionRows.map((row) => (\n <TableRow\n key={row.id}\n data-selection={row.selectionState}\n data-state={row.selectionState === \"selected\" ? \"selected\" : undefined}\n >\n <TableCell>{row.source}</TableCell>\n <TableCell>{row.target}</TableCell>\n <TableCell>{row.shape}</TableCell>\n <TableCell className=\"tabular-nums\">\n {row.secondaryLabel\n ? `${row.primaryLabel} · ${row.secondaryLabel}`\n : row.primaryLabel}\n </TableCell>\n <TableCell>{selectionStateText(row.selectionState)}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n );\n }\n\n return (\n <div\n data-slot=\"process-map\"\n data-view=\"canvas\"\n data-direction={direction}\n className={cn(\"relative size-full min-h-64\", className)}\n onKeyDown={handleKeyDown}\n {...props}\n >\n <p\n data-slot=\"process-map-selection-summary\"\n role=\"status\"\n aria-live=\"polite\"\n className=\"sr-only\"\n >\n {selectionSummary}\n </p>\n <ProcessMapHoverContext value={hover}>\n <ProcessMapEdgeKeyContext value={handleEdgeKey}>\n <CanvasShell\n nodes={positionedNodes}\n edges={model.edges}\n nodeTypes={NODE_TYPES}\n edgeTypes={EDGE_TYPES}\n // dagre runs in an EFFECT, so the first paint has every node stacked at the\n // origin and React Flow's own one-shot `fitView` fits that degenerate box —\n // it clamps at `maxZoom` (2) and never fires again, which is why this canvas\n // used to open at 2× showing 2 of 11 activities with a perfectly good layout\n // underneath. Re-fit on the key the layout itself is cached on: a structural\n // change (or a direction flip) genuinely moves the picture and has to be\n // re-framed; a metric switch is a cache hit, leaves this key alone, and must\n // NOT yank the viewport out from under the reader.\n fitViewKey={`${layoutKey}::${direction}::${layout.layoutRuns}`}\n fitViewKeyOptions={FIT_VIEW_OPTIONS}\n minZoom={MIN_ZOOM}\n nodesDraggable={false}\n nodesConnectable={false}\n // One tab stop per arrow, not two. Every edge already renders a focusable,\n // named label pill (`EdgeLabelPill`), so leaving React Flow's edge `<g>`\n // focusable as well doubled the stops in front of the activities — 28 of them\n // before the first node in the shipped fixture. Keys pressed on the pill still\n // reach this component through `ProcessMapEdgeKeyContext`.\n edgesFocusable={false}\n className={PROCESS_MAP_NODE_MOTION_CLASS}\n aria-label={mapLabel}\n onNodeClick={(_event, node) =>\n applySelection(\n activeSelection?.kind === \"activity\" && activeSelection.id === node.id\n ? null\n : { kind: \"activity\", id: node.id },\n )\n }\n onEdgeClick={(_event, edge) =>\n applySelection(\n activeSelection?.kind === \"transition\" && activeSelection.id === edge.id\n ? null\n : { kind: \"transition\", id: edge.id },\n )\n }\n onNodeMouseEnter={handleNodeEnter}\n onNodeMouseLeave={handleNodeLeave}\n onNodeContextMenu={(event, node) => {\n event.preventDefault();\n openMenuFor({ kind: \"activity\", id: node.id });\n }}\n onEdgeContextMenu={(event, edge) => {\n event.preventDefault();\n openMenuFor({ kind: \"transition\", id: edge.id });\n }}\n onPaneClick={() => applySelection(null)}\n onNodesChange={handleNodesChange}\n >\n {/* `bottom-right` is the minimap's corner; the controls take the other\n bottom corner so a pannable/zoomable minimap never sits on top of them\n (#350). The top rail (legend + filter trigger) already owns the top. */}\n <ZoomControls position=\"bottom-left\" />\n {showMiniMap ? <FlowMiniMap pannable zoomable /> : null}\n </CanvasShell>\n </ProcessMapEdgeKeyContext>\n </ProcessMapHoverContext>\n\n <div\n data-slot=\"process-map-top-rail\"\n className=\"pointer-events-none absolute inset-x-3 top-3 flex items-start justify-between gap-3\"\n >\n {showLegend ? (\n <Legend\n variant=\"scale\"\n kind=\"width\"\n domain={model.edgeDomain}\n format={model.formatEdgeValue}\n title={model.edgeMetricLabel}\n className=\"pointer-events-auto\"\n />\n ) : (\n <span />\n )}\n <div className=\"pointer-events-auto\">{filterMenu}</div>\n </div>\n </div>\n );\n}\n\n/**\n * Remember how to FIND the element focus should come back to — not the element itself.\n *\n * An edge's label pill is rendered through React Flow's `EdgeLabelRenderer`, a portal\n * whose children are RE-CREATED on a re-render (measured: opening the filter menu replaces\n * all fifteen pill buttons in the shipped fixture). By the time the menu closes, the\n * button the user was standing on is detached and focusing it does nothing. A node wrapper\n * does survive, so it is found again by its `data-id`; a portalled label is found by its\n * position among its siblings in the label layer, which is stable because React reconciles\n * the portals in edge order.\n */\nfunction focusRestorer(element: HTMLElement): () => void {\n const flowId = element.closest<HTMLElement>(\"[data-id]\")?.dataset.id;\n const slot = element.dataset.slot;\n const layer = element.closest<HTMLElement>(\".react-flow__edgelabel-renderer\");\n const peers =\n slot && layer ? [...layer.querySelectorAll<HTMLElement>(`[data-slot=\"${slot}\"]`)] : [];\n const index = peers.indexOf(element);\n const root = element.closest<HTMLElement>('[data-slot=\"process-map\"]');\n\n return () => {\n if (element.isConnected) {\n element.focus();\n return;\n }\n if (flowId && root) {\n root.querySelector<HTMLElement>(`[data-id=\"${CSS.escape(flowId)}\"]`)?.focus();\n return;\n }\n if (slot && index >= 0 && layer?.isConnected) {\n layer.querySelectorAll<HTMLElement>(`[data-slot=\"${slot}\"]`)[index]?.focus();\n }\n };\n}\n\n/** Stable empty arrays, so a graph-less render does not churn the layout hook's inputs. */\nconst EMPTY_NODES: ProcessMapModel[\"nodes\"] = [];\nconst EMPTY_EDGES: ProcessMapModel[\"edges\"] = [];\n\n/**\n * Re-apply the laid-out positions onto the second-pass model, IN LAID-OUT ORDER.\n *\n * The layout hook positions the FIRST-pass nodes; the second pass rebuilds them with\n * back-edge knowledge, which produces new objects at the origin again. Matching on id is\n * the same trick `applyLayoutSnapshot` uses, one level up.\n *\n * The sort is the other half, and it is load-bearing for the keyboard: React Flow renders\n * node wrappers in array order, and each wrapper is a tab stop, so the array order IS the\n * tab order. Mapping over `model.nodes` made that MODEL order — which is discovery order,\n * so `Tab` could reach a mid-process activity before the start one. Reading down the graph\n * (`TB`: ascending `y`, then `x`; `LR`: the transpose) is what a sighted user's eye does,\n * so it is what `Tab` should do. Ties break on id so the order is total and stable — dagre\n * gives whole ranks the same coordinate.\n *\n * This runs AFTER `useProcessLayout`, on its output; the hook's inputs and its cache key\n * are untouched, so re-ordering here cannot make the layout re-run.\n */\nfunction applyPositions(\n model: ProcessMapModel,\n layout: UseProcessLayoutResult,\n direction: FlowLayoutDirection,\n) {\n const byId = new Map(layout.nodes.map((node) => [node.id, node]));\n const positioned = model.nodes.map((node) => {\n const laidOut = byId.get(node.id);\n if (!laidOut) return node;\n return {\n ...node,\n position: laidOut.position,\n sourcePosition: laidOut.sourcePosition,\n targetPosition: laidOut.targetPosition,\n };\n });\n\n const alongFlow = direction === \"LR\" || direction === \"RL\" ? \"x\" : \"y\";\n const acrossFlow = alongFlow === \"x\" ? \"y\" : \"x\";\n // `BT`/`RL` lay rank 0 out at the HIGH end of the flow axis, so reading order down the\n // process is descending there — the sign, not a different axis.\n const sign = direction === \"BT\" || direction === \"RL\" ? -1 : 1;\n return positioned.sort((a, b) => {\n const along = sign * (a.position[alongFlow] - b.position[alongFlow]);\n if (along !== 0) return along;\n const across = a.position[acrossFlow] - b.position[acrossFlow];\n if (across !== 0) return across;\n return a.id.localeCompare(b.id);\n });\n}\n","/**\n * Slider-driven graph abstraction — RM-050.\n *\n * Every process-mining product surveyed in the wave-1 analysis exposes the same two\n * controls: an ACTIVITIES slider and a PATHS slider, both of which reduce what is DRAWN\n * without touching what was MEASURED. That rule is the whole point of this module — a\n * reader who drags a slider is filtering a view, not re-running an analysis, so the\n * numbers under their cursor must not move.\n *\n * Two invariants follow, and both are asserted in `abstract-graph.test.ts`:\n *\n * 1. **Statistics are never recomputed.** The returned graph reuses the very same\n * {@link ActivityStats} and {@link TransitionStats} OBJECTS the input carried — this\n * function filters arrays, it never builds a statistic. Nothing here mutates the input.\n * 2. **Kept nodes stay connected.** With `keepConnected` (the default) every kept activity\n * is reachable from a start activity and can reach an end activity, because a graph\n * with an island in it reads as a broken process rather than a simplified one.\n *\n * Deterministic: every ranking is totally ordered and the repair walks a fixed-cost graph,\n * so the same input always yields the same reduced view.\n */\nimport { EDGE_KEY_SEPARATOR } from \"./discover-graph\";\nimport type { ActivityStats, ProcessGraph, TransitionStats } from \"./types\";\n\n/** Options for {@link abstractGraph}. Both fractions are `0..1` and both are required. */\nexport interface AbstractionOptions {\n /** Fraction of activities to KEEP, `0..1`. At least one activity is always kept. */\n activities: number;\n /** Fraction of paths (transitions) to KEEP, `0..1`, over the kept-activity subgraph. */\n paths: number;\n /**\n * Re-add whatever it takes to keep every kept activity reachable from a start activity\n * and able to reach an end activity. Defaults to `true` — an island reads as a broken\n * process, not a simplified one.\n */\n keepConnected?: boolean;\n /** Hide the MOST frequent instead of the least — the \"what is rare here\" view. */\n invert?: boolean;\n}\n\n/** What {@link abstractGraph} returns: a `ProcessGraph` plus what it left out. */\nexport interface AbstractedGraph extends ProcessGraph {\n hidden: {\n /** Activities present in the input graph and absent from this one. */\n activities: number;\n /** Transitions present in the input graph and absent from this one. */\n paths: number;\n };\n}\n\n/** Key an edge exactly the way discovery keys it, so the two agree by construction. */\nfunction edgeKey(source: string, target: string): string {\n return `${source}${EDGE_KEY_SEPARATOR}${target}`;\n}\n\n/** Code-unit comparison — locale-independent, so the order is the same on every machine. */\nfunction compareStrings(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/** A non-finite fraction is read as `1` (keep everything) rather than as an error. */\nfunction clampFraction(value: number): number {\n if (!Number.isFinite(value)) return 1;\n return value < 0 ? 0 : value > 1 ? 1 : value;\n}\n\n/**\n * How many of `total` a fraction keeps.\n *\n * `Math.round` (half up) rather than floor or ceil, so the midpoint of the slider keeps\n * half the elements on an even count and the larger half on an odd one — and, crucially,\n * `fraction === 1` keeps exactly `total`, which is what makes the identity property hold.\n * `atLeast` is enforced for activities so a reduced graph is never empty; paths pass `0`,\n * because a node-only view is a legitimate thing to ask for.\n */\nfunction countToKeep(total: number, fraction: number, atLeast: number): number {\n if (total === 0) return 0;\n const kept = Math.round(total * fraction);\n return kept < atLeast ? Math.min(atLeast, total) : kept;\n}\n\n/** A minimal binary min-heap over `(node, cost)` — Dijkstra's queue, no dependency. */\nclass MinHeap {\n private readonly nodes: string[] = [];\n private readonly costs: number[] = [];\n\n get size(): number {\n return this.nodes.length;\n }\n\n push(node: string, cost: number): void {\n this.nodes.push(node);\n this.costs.push(cost);\n let i = this.nodes.length - 1;\n while (i > 0) {\n const parent = (i - 1) >> 1;\n if ((this.costs[parent] as number) <= (this.costs[i] as number)) break;\n this.swap(i, parent);\n i = parent;\n }\n }\n\n pop(): { node: string; cost: number } | undefined {\n if (this.nodes.length === 0) return undefined;\n const node = this.nodes[0] as string;\n const cost = this.costs[0] as number;\n const lastNode = this.nodes.pop() as string;\n const lastCost = this.costs.pop() as number;\n if (this.nodes.length > 0) {\n this.nodes[0] = lastNode;\n this.costs[0] = lastCost;\n let i = 0;\n for (;;) {\n const left = i * 2 + 1;\n const right = left + 1;\n let smallest = i;\n const size = this.nodes.length;\n if (left < size && (this.costs[left] as number) < (this.costs[smallest] as number)) {\n smallest = left;\n }\n if (right < size && (this.costs[right] as number) < (this.costs[smallest] as number)) {\n smallest = right;\n }\n if (smallest === i) break;\n this.swap(i, smallest);\n i = smallest;\n }\n }\n return { node, cost };\n }\n\n private swap(a: number, b: number): void {\n const node = this.nodes[a] as string;\n this.nodes[a] = this.nodes[b] as string;\n this.nodes[b] = node;\n const cost = this.costs[a] as number;\n this.costs[a] = this.costs[b] as number;\n this.costs[b] = cost;\n }\n}\n\ninterface Route {\n /** Activities the route uses, including any the reduction had hidden. */\n activities: Set<string>;\n /** Edge keys the route uses. */\n edges: Set<string>;\n}\n\n/**\n * Reduce `graph` to the fraction of activities and paths a reader asked to see.\n *\n * Activities rank by `cases` (how many process instances touch them), ties broken by\n * `instances` then by name; paths rank by `count`, ties broken by `caseCount` then by\n * endpoint names. Ranking on `cases` rather than `instances` is deliberate: an activity\n * executed forty times inside one case is a loop, not a backbone step, and a frequency\n * slider that promoted it would hide the shape of the process.\n *\n * The returned arrays hold the INPUT's own statistic objects, in the input's order.\n * Treat the result as read-only: mutating a returned `ActivityStats` mutates the source\n * graph's, by design — that shared identity is what proves no recomputation happened.\n */\nexport function abstractGraph(graph: ProcessGraph, opts: AbstractionOptions): AbstractedGraph {\n const keepConnected = opts.keepConnected ?? true;\n const invert = opts.invert ?? false;\n const activityFraction = clampFraction(opts.activities);\n const pathFraction = clampFraction(opts.paths);\n\n const rankedActivities = [...graph.activities].sort(\n (a, b) => b.cases - a.cases || b.instances - a.instances || compareStrings(a.id, b.id),\n );\n const activityKeepCount = countToKeep(rankedActivities.length, activityFraction, 1);\n const keptActivities = new Set<string>(\n (invert\n ? rankedActivities.slice(rankedActivities.length - activityKeepCount)\n : rankedActivities.slice(0, activityKeepCount)\n ).map((activity) => activity.id),\n );\n\n const candidateEdges = graph.transitions.filter(\n (edge) => keptActivities.has(edge.source) && keptActivities.has(edge.target),\n );\n const rankedEdges = [...candidateEdges].sort(\n (a, b) =>\n b.count - a.count ||\n b.caseCount - a.caseCount ||\n compareStrings(a.source, b.source) ||\n compareStrings(a.target, b.target),\n );\n const edgeKeepCount = countToKeep(rankedEdges.length, pathFraction, 0);\n const keptEdges = new Set<string>(\n (invert\n ? rankedEdges.slice(rankedEdges.length - edgeKeepCount)\n : rankedEdges.slice(0, edgeKeepCount)\n ).map((edge) => edgeKey(edge.source, edge.target)),\n );\n\n if (keepConnected && keptActivities.size > 0) {\n reconnect(graph, keptActivities, keptEdges);\n }\n\n const activities: ActivityStats[] = graph.activities.filter((activity) =>\n keptActivities.has(activity.id),\n );\n const transitions: TransitionStats[] = graph.transitions.filter((edge) =>\n keptEdges.has(edgeKey(edge.source, edge.target)),\n );\n\n return {\n activities,\n transitions,\n startActivities: pick(graph.startActivities, keptActivities),\n endActivities: pick(graph.endActivities, keptActivities),\n // Passed through by reference: totals describe the LOG, and abstraction is a view.\n totals: graph.totals,\n hidden: {\n activities: graph.activities.length - activities.length,\n paths: graph.transitions.length - transitions.length,\n },\n };\n}\n\n/** Keep the entries of `record` whose key survived, in the record's own key order. */\nfunction pick(record: Record<string, number>, keep: ReadonlySet<string>): Record<string, number> {\n const out: Record<string, number> = {};\n for (const name of Object.keys(record)) {\n if (keep.has(name)) out[name] = record[name] as number;\n }\n return out;\n}\n\n/**\n * Repair connectivity in place, growing `keptActivities` / `keptEdges` as little as it can.\n *\n * For every kept activity that no start activity can reach, the cheapest route from a\n * start activity is found by Dijkstra over edge cost `log(maxCount) − log(count)` — the\n * non-negative form of `−log(count)`, which is what Dijkstra requires and which orders\n * paths identically: minimizing a sum of `−log` weights maximizes the PRODUCT of the edge\n * counts along the route, so the one made of the most-travelled edges wins. Every hidden\n * edge on that route is brought back; a mirrored pass over reversed edges does the same\n * for activities that cannot reach an end activity.\n *\n * The roadmap describes this as re-adding \"the max-weight edge on a shortest path\". One\n * edge is not enough to restore reachability when a route is several hops long, so ALL of\n * the route's hidden edges are re-added — the smallest change that actually holds the\n * invariant the acceptance criterion asserts.\n *\n * Two escalations, both deliberate and both visible through `hidden`:\n *\n * - If NO kept activity is a start (or end) activity, the busiest one is brought back —\n * otherwise there is no anchor to connect to and the whole graph is an island.\n * - If a route exists only through a hidden activity, that activity is brought back too.\n * The alternative is to leave the node stranded. Both escalations only ever ADD, so the\n * guarantee is exactly: an activity connected in `graph` is connected in the result.\n */\nfunction reconnect(graph: ProcessGraph, keptActivities: Set<string>, keptEdges: Set<string>): void {\n const forward = new Map<string, TransitionStats[]>();\n const backward = new Map<string, TransitionStats[]>();\n let maxCount = 1;\n for (const edge of graph.transitions) {\n if (edge.count > maxCount) maxCount = edge.count;\n index(forward, edge.source, edge);\n index(backward, edge.target, edge);\n }\n const logMax = Math.log(maxCount);\n const cost = (count: number): number => (count > 0 ? logMax - Math.log(count) : logMax);\n\n // The two passes are COUPLED: repairing \"can reach an end\" may bring back an activity\n // that nothing reaches from a start, and vice versa. So they run to a fixpoint rather\n // than once each. Both passes only ever ADD, and the sets are bounded by the input\n // graph, so the loop terminates — the bound is belt-and-braces against a future edit\n // that makes a pass able to remove something.\n const bound = graph.activities.length + graph.transitions.length + 2;\n for (let round = 0; round < bound; round += 1) {\n const before = keptActivities.size + keptEdges.size;\n repairDirection(graph.startActivities, forward, true);\n repairDirection(graph.endActivities, backward, false);\n if (keptActivities.size + keptEdges.size === before) break;\n }\n\n function repairDirection(\n seedCounts: Record<string, number>,\n adjacency: Map<string, TransitionStats[]>,\n downstream: boolean,\n ): void {\n const allSeeds = Object.keys(seedCounts);\n if (allSeeds.length === 0) return;\n if (!allSeeds.some((id) => keptActivities.has(id))) anchor(seedCounts, keptActivities);\n const keptSeeds = allSeeds.filter((id) => keptActivities.has(id));\n if (keptSeeds.length === 0) return;\n const reached = spread(keptSeeds, adjacency, downstream);\n\n // A stable, deterministic repair order: the graph's own activity order.\n for (const activity of graph.activities) {\n if (!keptActivities.has(activity.id) || reached.has(activity.id)) continue;\n // Escalate in three steps, cheapest first — kept route, then a route through hidden\n // activities, then a route to an anchor the reduction hid entirely. The third step\n // is what an INVERTED reduction needs: keeping only the rare activities can hide\n // every end activity the backbone actually leads to, and then the only honest repair\n // is to bring one of those back.\n const route =\n shortestRoute(keptSeeds, activity.id, adjacency, downstream, true) ??\n shortestRoute(keptSeeds, activity.id, adjacency, downstream, false) ??\n shortestRoute(allSeeds, activity.id, adjacency, downstream, false);\n if (route === undefined) continue; // genuinely unreachable in the FULL graph too.\n for (const id of route.activities) keptActivities.add(id);\n for (const key of route.edges) keptEdges.add(key);\n for (const id of spread([...route.activities], adjacency, downstream)) reached.add(id);\n }\n }\n\n /** BFS over the KEPT edges only — which nodes the anchors already reach. */\n function spread(\n seeds: readonly string[],\n adjacency: Map<string, TransitionStats[]>,\n downstream: boolean,\n ): Set<string> {\n const seen = new Set<string>(seeds.filter((id) => keptActivities.has(id)));\n const queue = [...seen];\n for (let head = 0; head < queue.length; head += 1) {\n const node = queue[head] as string;\n for (const edge of adjacency.get(node) ?? []) {\n const next = downstream ? edge.target : edge.source;\n if (!keptActivities.has(next)) continue;\n if (!keptEdges.has(edgeKey(edge.source, edge.target))) continue;\n if (seen.has(next)) continue;\n seen.add(next);\n queue.push(next);\n }\n }\n return seen;\n }\n\n /**\n * Cheapest route from any seed to `target`, as the activities and edge keys it uses.\n * `keptOnly` restricts the walk to already-kept activities; the caller retries without\n * it when that fails, which is the second escalation described above.\n */\n function shortestRoute(\n seeds: readonly string[],\n target: string,\n adjacency: Map<string, TransitionStats[]>,\n downstream: boolean,\n keptOnly: boolean,\n ): Route | undefined {\n const distance = new Map<string, number>();\n const previous = new Map<string, TransitionStats>();\n const settled = new Set<string>();\n const heap = new MinHeap();\n for (const seed of seeds) {\n distance.set(seed, 0);\n heap.push(seed, 0);\n }\n\n while (heap.size > 0) {\n const top = heap.pop() as { node: string; cost: number };\n if (settled.has(top.node)) continue;\n settled.add(top.node);\n if (top.node === target) break;\n for (const edge of adjacency.get(top.node) ?? []) {\n const next = downstream ? edge.target : edge.source;\n if (next === top.node) continue; // a self-loop cannot shorten anything.\n if (keptOnly && !keptActivities.has(next)) continue;\n const candidate = top.cost + cost(edge.count);\n const known = distance.get(next);\n // A strict improvement only, so a tie keeps the first route found — and the walk\n // order is the graph's own deterministic edge order.\n if (known !== undefined && known <= candidate) continue;\n distance.set(next, candidate);\n previous.set(next, edge);\n heap.push(next, candidate);\n }\n }\n\n if (!settled.has(target)) return undefined;\n const route: Route = { activities: new Set([target]), edges: new Set() };\n let cursor = target;\n for (;;) {\n const edge = previous.get(cursor);\n if (edge === undefined) break;\n route.edges.add(edgeKey(edge.source, edge.target));\n route.activities.add(edge.source);\n route.activities.add(edge.target);\n cursor = downstream ? edge.source : edge.target;\n }\n return route;\n }\n\n /** Bring back the busiest start/end activity when the reduction kept none of them. */\n function anchor(counts: Record<string, number>, kept: Set<string>): void {\n const names = Object.keys(counts);\n if (names.length === 0) return;\n if (names.some((name) => kept.has(name))) return;\n let best = names[0] as string;\n for (const name of names) {\n const value = counts[name] as number;\n const bestValue = counts[best] as number;\n if (value > bestValue || (value === bestValue && compareStrings(name, best) < 0)) best = name;\n }\n kept.add(best);\n }\n}\n\nfunction index(map: Map<string, TransitionStats[]>, key: string, edge: TransitionStats): void {\n const bucket = map.get(key);\n if (bucket === undefined) map.set(key, [edge]);\n else bucket.push(edge);\n}\n","/**\n * Rework detection — RM-050.\n *\n * \"Rework\" is the part of a process that happens more than once inside a single case: a\n * step retried immediately (a SELF-LOOP), or a step the case comes back to after going\n * somewhere else (a LOOP). Both are repeats, and separating them matters — a self-loop is\n * usually a retry or a batch, a loop is usually a rejection sending work backwards, and\n * the two lead to different conversations about the process.\n *\n * Counted per OCCURRENCE, over the normalized trace: the first execution of an activity in\n * a case is never rework, and every later one is exactly one of the two kinds. So\n * `selfLoops + loops` is precisely the number of repeated executions in the log.\n *\n * Deterministic and framework-free, like everything else in `/core`.\n */\nimport { asNormalizedLog, type AnyLog } from \"./event-log\";\n\n/** Per-activity rework tallies. */\nexport interface ActivityRework {\n /** Repeats of this activity immediately after itself. */\n selfLoops: number;\n /** Repeats of this activity later in the same case, with something else in between. */\n loops: number;\n}\n\n/** What {@link detectRework} answers. */\nexport interface ReworkStats {\n /** Adjacent repeats across the whole log. */\n selfLoops: number;\n /** Non-adjacent repeats across the whole log. */\n loops: number;\n /** Fraction of cases carrying at least one self-loop or loop. `0` for an empty log. */\n caseReworkRate: number;\n /**\n * Every activity in the log, in ascending name order, with its own two tallies. An\n * activity that never repeats is present with zeros — a renderer joining this against\n * a graph's activities never has to distinguish \"no rework\" from \"unknown activity\".\n */\n perActivity: Record<string, ActivityRework>;\n}\n\n/**\n * Count rework in `log`.\n *\n * Accepts a raw or an already-normalized log and normalizes at most once\n * ({@link asNormalizedLog} is idempotent), so a caller that has already normalized for\n * `discoverGraph` pays nothing here.\n */\nexport function detectRework(log: AnyLog): ReworkStats {\n const normalized = asNormalizedLog(log);\n\n const tallies = new Map<string, ActivityRework>();\n let selfLoops = 0;\n let loops = 0;\n let casesWithRework = 0;\n\n const seen = new Set<string>();\n for (const kase of normalized.cases) {\n seen.clear();\n let caseHasRework = false;\n let previous: string | undefined;\n\n for (const event of kase.events) {\n const name = event.activity;\n let tally = tallies.get(name);\n if (tally === undefined) {\n tally = { selfLoops: 0, loops: 0 };\n tallies.set(name, tally);\n }\n\n if (seen.has(name)) {\n caseHasRework = true;\n if (name === previous) {\n tally.selfLoops += 1;\n selfLoops += 1;\n } else {\n tally.loops += 1;\n loops += 1;\n }\n } else {\n seen.add(name);\n }\n previous = name;\n }\n\n if (caseHasRework) casesWithRework += 1;\n }\n\n const perActivity: Record<string, ActivityRework> = {};\n for (const name of [...tallies.keys()].sort()) {\n perActivity[name] = tallies.get(name) as ActivityRework;\n }\n\n return {\n selfLoops,\n loops,\n caseReworkRate: normalized.cases.length === 0 ? 0 : casesWithRework / normalized.cases.length,\n perActivity,\n };\n}\n","\"use client\";\n\n/**\n * ProcessActivityNode — one activity on the process map (RM-051).\n *\n * ## It COMPOSES `FlowNode`; it does not fork it\n *\n * The frame, the handles, the tone glyph, the selection ring and — this is the load-bearing\n * part — the keyboard focus indicator repaired in #312 all come from\n * `@elabs-ai/components-flow`'s `FlowNode`, rendered here with process-shaped data. Nothing\n * about React Flow's proxied focus (the `:focus-visible` state lands on React Flow's own\n * wrapper element, one level above anything this package renders) is re-implemented, and\n * the ancestor-variant class string that makes it work is not copied. If `FlowNode`'s focus\n * treatment changes, this node changes with it.\n *\n * The three text slots `FlowNode` already owns carry the whole reading:\n *\n * - `kind` (eyebrow) — what the number MEANS (\"Cases\", \"Median duration\");\n * - `title` — the activity name;\n * - `subtitle` — the primary metric, and the secondary metric when one is asked for.\n *\n * ## Encoding: never colour alone (WCAG 1.4.1)\n *\n * The node metric reaches the reader three ways, and the first two survive greyscale:\n * the **printed value** in the subtitle, the **length** of the meter bar under the node,\n * and the meter's **saturation** against the surface→primary ramp. Two nodes with\n * different values differ in text and in bar length before they differ in hue, which is\n * what keeps this surface honest while the two open flow-colour defects (#321, #297) are\n * unfixed.\n *\n * Start and end are a glyph PLUS a word in the accessible name (never a colour); rework is\n * a counted badge; the tri-state selection is driven by the `data-selection` attribute\n * `map-model`'s `domAttributes` sets on React Flow's own node element, so a consumer can\n * select on `[data-selection=\"excluded\"]` without reaching into this component. An\n * excluded node is deliberately NOT `aria-disabled`: it stays fully operable (clicking it\n * is how a reader filters it back in), and `activityAriaLabel` already appends the word\n * \"excluded\" to its accessible name, so the state reaches assistive technology through\n * real text rather than a lie about disablement.\n *\n * ## Ghosting an excluded node without erasing its boundary (#352)\n *\n * A single `opacity-35` on this whole subtree used to dim the card, its border AND its\n * text together — since the card's fill barely differs from the canvas\n * (white-on-near-white in the light theme), the 1px border was the ONLY structural cue\n * telling a reader \"there is still a card here\", and dimming it along with everything else\n * measured under WCAG's own 3:1 non-text floor, while the eyebrow/title text measured as\n * low as 1.64:1/1.97:1 against 4.5:1 — a real axe-caught 1.4.3 failure, not a cosmetic one.\n * This component now composes `FlowNode` UNCHANGED (no fork, no new prop on it) and reaches\n * the ghost treatment from outside on two channels that never touch text:\n *\n * - **The card's border-colour token is locally overridden** (`--border` → `--border-strong`\n * on the wrapping frame div) so `FlowNode`'s own `border-border` utility resolves to the\n * stronger rung automatically — a plain CSS custom-property cascade trick, not a fork.\n * - **The card's fill token is locally re-tinted** (`--flow-node` → a `card`/`surface-muted`\n * mix) so `FlowNode`'s `bg-flow-node` utility reads as visibly quieter. This is a\n * background-COLOUR change, not an opacity change — it cannot wash out the text painted\n * on top of it the way a translucent scrim would (a scrim was tried and rejected for\n * exactly this reason: it sits in the same paint layer as the text it was meant to spare).\n *\n * Eyebrow/title/subtitle text is never dimmed and never overlaid — see {@link GHOST_OPACITY}'s\n * own docblock for why (0.35 over that text's already-modest contrast fails 4.5:1 in both\n * themes). The meter's fill (a redundant, `aria-hidden`, non-text channel) still dims at the\n * shared rung.\n */\nimport { useMemo, type CSSProperties } from \"react\";\nimport { CircleDot, Flag, Play, RefreshCw } from \"lucide-react\";\nimport { Badge } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { FlowNode, type FlowNodeData } from \"@elabs-ai/components-flow\";\nimport type { NodeProps } from \"@xyflow/react\";\nimport { useProcessMapHover } from \"./process-map-context\";\nimport { activityRole, GHOST_OPACITY, type ProcessMapNode } from \"./map-model\";\n\n/**\n * Local override for the ghost frame: retargets `--border` (what `FlowNode`'s own\n * `border-border` utility resolves through) to the stronger rung, and `--flow-node` (what\n * `bg-flow-node` resolves through) to a quieter fill blended from two OTHER tokens\n * (`--card`, `--surface-muted` — never `--flow-node` itself, which would be a\n * self-referencing custom property and silently fail to apply) — WITHOUT touching\n * `FlowNode`'s source or props. Both values cascade to `FlowNode`'s root div through the\n * plain CSS custom-property lookup at the point each `var()` is used, same as any other\n * theme override in this codebase (`.claude/rules/theming.md` §5). `--card` stands in for\n * `--flow-node`'s own usual value here (the two are near-identical in both reference\n * themes) so the blend reads as \"quieter\", not as a hue shift.\n */\nconst GHOST_FRAME_STYLE = {\n \"--border\": \"var(--border-strong)\",\n \"--flow-node\": \"color-mix(in oklab, var(--card) 60%, var(--surface-muted) 40%)\",\n} as CSSProperties;\n\n/**\n * The meter's fill as a mix of the recessed surface and the brand plate.\n *\n * `color-mix` in an inline `style` rather than a Tailwind opacity utility because the\n * fraction is continuous, per node, and only known at render — but both endpoints stay\n * semantic tokens, so a re-brand still reaches it and no raw colour is authored.\n */\nfunction meterFill(saturation: number): string {\n const percent = Math.round(Math.min(1, Math.max(0, saturation)) * 100);\n return `color-mix(in oklab, var(--primary) ${percent}%, var(--surface-muted))`;\n}\n\n/** Start/end glyph pairing, in `FlowNode`'s own tone-glyph idiom. */\nfunction roleIcon(isStart: boolean, isEnd: boolean) {\n if (isStart && isEnd) return CircleDot;\n if (isStart) return Play;\n if (isEnd) return Flag;\n return undefined;\n}\n\n/**\n * Branded process-map activity node. Register it in\n * `nodeTypes={{ \"process-activity\": ProcessActivityNode }}` and build nodes with\n * `buildProcessMapModel` rather than by hand — the model is what keeps the canvas and the\n * `TableView` twin printing the same numbers.\n */\nexport function ProcessActivityNode(props: NodeProps<ProcessMapNode>) {\n const { data } = props;\n const hover = useProcessMapHover();\n const RoleIcon = roleIcon(data.isStart, data.isEnd);\n const isHovered = hover.activityId === props.id;\n const isDimmed = data.selectionState === \"excluded\";\n\n const percent = Math.round(Math.min(1, Math.max(0, data.saturation)) * 100);\n\n const flowData = useMemo<FlowNodeData>(\n () => ({\n title: data.title,\n kind: data.metricLabel,\n subtitle: data.secondaryLabel\n ? `${data.primaryLabel} · ${data.secondaryLabel}`\n : data.primaryLabel,\n icon: RoleIcon ? <RoleIcon aria-hidden=\"true\" /> : undefined,\n // `tone` is a COLOUR axis in FlowNode. The process map never uses it to carry a\n // metric — the fill would then be the only channel — so it stays default and the\n // role/rework signals are carried by the glyph, the badge and the accessible name.\n tone: \"default\",\n // The metric's second, colour-free channel: bar LENGTH. `aria-hidden` because the\n // same number is already printed in the subtitle above and repeated in the node's\n // accessible name — a third announcement would be noise, not access. The fill (not\n // the text) is the one thing here that still dims at the shared ghost rung.\n footer: (\n <div\n aria-hidden=\"true\"\n data-slot=\"process-activity-node-meter\"\n data-percent={percent}\n className=\"h-1.5 w-full overflow-hidden rounded-full bg-surface-muted transition-opacity duration-fast ease-standard motion-reduce:transition-none\"\n style={isDimmed ? { opacity: GHOST_OPACITY } : undefined}\n >\n <div\n className=\"h-full rounded-full transition-[width] duration-base ease-standard motion-reduce:transition-none\"\n style={{ width: `${percent}%`, background: meterFill(data.saturation) }}\n />\n </div>\n ),\n }),\n [\n data.title,\n data.metricLabel,\n data.primaryLabel,\n data.secondaryLabel,\n data.saturation,\n RoleIcon,\n percent,\n isDimmed,\n ],\n );\n\n return (\n <div\n data-slot=\"process-activity-node\"\n data-selection={data.selectionState}\n data-role={activityRole(data).toLowerCase()}\n data-hover={isHovered ? \"true\" : undefined}\n // `relative` positions the rework badge — and, because the meter now lives INSIDE\n // the card (`FlowNode`'s `footer` slot), this wrapper is exactly as tall as the\n // card. That equality is load-bearing, not cosmetic: React Flow lays every\n // `<Handle>` out against the nearest positioned ancestor, so any row rendered here\n // as a sibling of the card pushes every connector dot off the card's border by its\n // own height. The meter used to sit here and did exactly that.\n className={cn(\"relative\", isHovered && \"z-10\")}\n >\n {data.reworkCount ? (\n <Badge\n variant=\"warning\"\n data-slot=\"process-activity-node-rework\"\n className=\"absolute -end-2 -top-2 z-10 gap-1 px-1.5 py-0 text-meta tabular-nums\"\n >\n <RefreshCw aria-hidden=\"true\" className=\"size-3\" />\n <span aria-hidden=\"true\">{data.reworkCount}</span>\n <span className=\"sr-only\">{data.reworkCount} repeated executions</span>\n </Badge>\n ) : null}\n\n <div data-slot=\"process-activity-node-frame\" style={isDimmed ? GHOST_FRAME_STYLE : undefined}>\n <FlowNode {...props} type=\"brand\" data={flowData} />\n </div>\n </div>\n );\n}\n","\"use client\";\n\n/**\n * The process map's hover channel (RM-051).\n *\n * Hovering an activity has to highlight the activity AND every edge incident to it. The\n * naive way to do that is to rebuild the model with a `hovered` field, which re-derives\n * every metric and every label for a mouse move. This context is the seam that avoids it:\n * hover lives in one small object beside the model, so a pointer move re-renders the node\n * and edge COMPONENTS (which is what React does) but never rebuilds `buildProcessMapModel`\n * and never re-runs `layoutFlow`. The model and the layout are memoized on inputs that do\n * not include hover, which is the property the map depends on.\n *\n * Kept in its own module rather than inside `process-map.tsx` so the node and the edge can\n * both read it without either importing the other, and without a cycle back through the\n * component that provides it.\n */\nimport { createContext, use, type KeyboardEvent as ReactKeyboardEvent } from \"react\";\n\n/** What the node and edge components read while something is hovered. */\nexport interface ProcessMapHoverState {\n /** The hovered activity id, or `null` when the pointer is not on a node. */\n activityId: string | null;\n /** Ids of the edges incident to {@link activityId}. Empty when nothing is hovered. */\n incidentEdgeIds: ReadonlySet<string>;\n}\n\n/** The resting value — nothing hovered. Frozen and shared, so it is referentially stable. */\nexport const EMPTY_PROCESS_MAP_HOVER: ProcessMapHoverState = Object.freeze({\n activityId: null,\n incidentEdgeIds: new Set<string>() as ReadonlySet<string>,\n});\n\n/**\n * Hover state for one process map. `ProcessMap` provides it; `ProcessActivityNode` and\n * `ProcessTransitionEdge` consume it. Default is {@link EMPTY_PROCESS_MAP_HOVER}, so both\n * components render correctly outside a `ProcessMap` (in a story or a unit test).\n */\nexport const ProcessMapHoverContext = createContext<ProcessMapHoverState>(EMPTY_PROCESS_MAP_HOVER);\n\n/** Read the current hover state. `use()` per the repo's new-context-read convention. */\nexport function useProcessMapHover(): ProcessMapHoverState {\n return use(ProcessMapHoverContext);\n}\n\n/**\n * How an edge hands a key press back to the map (RM-051).\n *\n * An edge's label pill is rendered through React Flow's `EdgeLabelRenderer`, which is a\n * PORTAL: the button lands in `.react-flow__edgelabel-renderer`, outside the edge's own\n * `<g>`, so it has no `[data-id]` ancestor for the map's root key handler to read. React\n * portal events still bubble up the REACT tree, though — so `ProcessTransitionEdge`, which\n * knows its own `id`, catches the key on its wrapper `<g>` and calls this. It is the ONLY\n * way `Enter` and `f` stay usable on a transition now that the edge `<g>` itself is not a\n * tab stop.\n *\n * The default is a no-op, so `ProcessTransitionEdge` renders standalone (a story, a unit\n * test) without a provider.\n */\nexport type ProcessMapEdgeKeyHandler = (edgeId: string, event: ReactKeyboardEvent) => void;\n\nconst NOOP_EDGE_KEY_HANDLER: ProcessMapEdgeKeyHandler = () => {};\n\n/** Provided by `ProcessMap`; consumed by `ProcessTransitionEdge`. */\nexport const ProcessMapEdgeKeyContext =\n createContext<ProcessMapEdgeKeyHandler>(NOOP_EDGE_KEY_HANDLER);\n\n/** Read the map's edge key handler. `use()` per the repo's new-context-read convention. */\nexport function useProcessMapEdgeKeys(): ProcessMapEdgeKeyHandler {\n return use(ProcessMapEdgeKeyContext);\n}\n","\"use client\";\n\n/**\n * ProcessTransitionEdge — one directly-follows relation on the process map (RM-051).\n *\n * ONE registered edge type that dispatches to the three shapes `@elabs-ai/components-flow`\n * already owns, and authors none of them:\n *\n * | `data` | renders as | non-colour cue |\n * | --------------------- | ---------------------------------------------- | -------------- |\n * | `isSelfLoop: true` | `FlowSelfLoopEdge` | a closed arc above the node |\n * | `isBackEdge: true` | `FlowWeightedEdge` `variant=\"back\"` | dashed, routed clear of the forward edge |\n * | otherwise | `FlowWeightedEdge` `variant=\"forward\"` | the plain bezier |\n *\n * There is no `BaseEdge` here and no authored `<path>`: an edge is a `flow` primitive, and\n * a layer-3 composite that draws its own would be a second component library\n * (`pnpm process:reuse:check`).\n *\n * ## Why the metric is never carried by colour alone\n *\n * The delegate gets `weight` (stroke width, min-maxed to `[1.5, 8]` px against every other\n * edge in the flow) AND `label` (a printed pill with the value in it). `value`/`valueDomain`\n * tint the stroke as a third, redundant channel. Two edges with different values therefore\n * differ in thickness and in printed text before they differ in hue — which matters\n * because two open flow-layer defects (#321, #297) can currently collapse or invert that\n * hue, and this surface is designed not to depend on it.\n *\n * ## Hover and selection\n *\n * Hovering an activity raises the opacity contrast between its incident edges and the\n * rest; the selection tri-state dims an excluded edge. Both are opacity changes on a\n * wrapper `<g>` — never a hue swap — and both leave the delegate's own `selected` ring\n * alone.\n *\n * ## The label pill does not inherit this `<g>`'s opacity (#351)\n *\n * `EdgeLabelRenderer` portals the pill out from under this `<g>` into a sibling HTML layer,\n * so CSS `opacity` on the `<g>` never reaches it — an excluded edge used to fade to\n * {@link GHOST_OPACITY} while its own label pill stayed at full strength, the one part of\n * the ghosting rung a filter could never actually dim. Rather than matching the pill's\n * OWN opacity to the edge's (which would dim the pill's TEXT the same way #352 found\n * failing 4.5:1 on the activity node — the pill's label sits on `bg-flow-node` at the same\n * high-contrast rung as a node's title), the pill is reached explicitly, by DATA, with a\n * non-opacity treatment: `labelProps` (a generic pass-through on\n * `FlowWeightedEdgeData`/`FlowSelfLoopEdgeData`, mirroring `EdgeLabelPill`'s own new\n * `className`/`...props`) carries a dashed border and a `data-selection=\"excluded\"`\n * attribute straight onto the pill's root button — visible, non-text, reachable by a\n * `[data-selection=\"excluded\"]` selector same as the node and the edge, and never lower\n * than 4.5:1 because the label text itself is untouched.\n */\nimport { useMemo } from \"react\";\nimport type { EdgeProps } from \"@xyflow/react\";\nimport {\n FlowSelfLoopEdge,\n FlowWeightedEdge,\n type FlowSelfLoopEdgeData,\n type FlowWeightedEdgeData,\n} from \"@elabs-ai/components-flow\";\nimport { useProcessMapEdgeKeys, useProcessMapHover } from \"./process-map-context\";\nimport { GHOST_OPACITY, type ProcessMapEdge } from \"./map-model\";\n\n/**\n * The `scaleGroup` every process-map edge shares, so `computeEdgeWeightScale` min-maxes\n * the whole map against ONE domain — the `Legend variant=\"scale\"` beside it reads that\n * same domain, which is what stops the key and the picture from disagreeing.\n */\nexport const PROCESS_MAP_EDGE_SCALE_GROUP = \"process-map\";\n\n/** Opacity of an edge that is neither hovered-incident nor selection-excluded. */\nconst RESTING_OPACITY = 1;\n/** Opacity of an edge that is not incident to the hovered activity. */\nconst UNRELATED_OPACITY = 0.25;\n\n/** The pill's own ghost treatment — a dashed frame + a reachable `data-selection`, never an\n * opacity (see this file's own docblock, and {@link GHOST_OPACITY}'s, for why). */\nconst EXCLUDED_LABEL_PROPS = { className: \"border-dashed\", \"data-selection\": \"excluded\" } as const;\n\n/**\n * Branded process-map transition edge. Register it in\n * `edgeTypes={{ \"process-transition\": ProcessTransitionEdge }}`; build edges with\n * `buildProcessMapModel`.\n */\nexport function ProcessTransitionEdge(props: EdgeProps<ProcessMapEdge>) {\n const { data } = props;\n const hover = useProcessMapHover();\n const onEdgeKey = useProcessMapEdgeKeys();\n const isExcluded = data?.selectionState === \"excluded\";\n const labelProps = isExcluded ? EXCLUDED_LABEL_PROPS : undefined;\n\n const weightedData = useMemo<FlowWeightedEdgeData>(\n () => ({\n weight: data?.weight,\n scaleGroup: PROCESS_MAP_EDGE_SCALE_GROUP,\n value: data?.value,\n valueDomain: data?.valueDomain,\n label: data?.label,\n secondaryLabel: data?.secondaryLabel,\n variant: data?.isBackEdge ? \"back\" : \"forward\",\n labelProps,\n }),\n [\n data?.weight,\n data?.value,\n data?.valueDomain,\n data?.label,\n data?.secondaryLabel,\n data?.isBackEdge,\n labelProps,\n ],\n );\n\n const selfLoopData = useMemo<FlowSelfLoopEdgeData>(\n () => ({\n weight: data?.weight,\n scaleGroup: PROCESS_MAP_EDGE_SCALE_GROUP,\n label: data?.label,\n secondaryLabel: data?.secondaryLabel,\n labelProps,\n }),\n [data?.weight, data?.label, data?.secondaryLabel, labelProps],\n );\n\n const opacity = isExcluded\n ? GHOST_OPACITY\n : hover.activityId !== null && !hover.incidentEdgeIds.has(props.id)\n ? UNRELATED_OPACITY\n : RESTING_OPACITY;\n\n return (\n <g\n data-slot=\"process-transition-edge\"\n data-shape={data?.isSelfLoop ? \"self-loop\" : data?.isBackEdge ? \"back\" : \"forward\"}\n data-selection={data?.selectionState}\n data-incident={hover.incidentEdgeIds.has(props.id) ? \"true\" : undefined}\n className=\"transition-opacity duration-fast ease-standard motion-reduce:transition-none\"\n style={{ opacity }}\n // The label pill is portalled out of this `<g>` by `EdgeLabelRenderer`, so it has no\n // `[data-id]` ancestor the map's root handler could read — but a portal's events\n // still bubble up the REACT tree, through here, where the edge id is known. This is\n // what keeps `Enter` (select) and `f` (filter menu) working on a transition now that\n // the edge itself is not a tab stop and the pill is the only stop on the arrow.\n onKeyDown={(event) => onEdgeKey(props.id, event)}\n >\n {data?.isSelfLoop ? (\n <FlowSelfLoopEdge {...props} type=\"self-loop\" data={selfLoopData} />\n ) : (\n <FlowWeightedEdge {...props} type=\"weighted\" data={weightedData} />\n )}\n </g>\n );\n}\n","\"use client\";\n\n/**\n * useProcessLayout — the process map's cached, debounced dagre layout (RM-051).\n *\n * A process map is re-rendered constantly for reasons that do NOT move a node: switching\n * the metric from frequency to duration, selecting an activity, hovering a path. Laying\n * the graph out again for any of those is both wasteful and visually wrong — the picture\n * would twitch while the reader's eye is on it. So this hook separates two things the\n * naive version conflates:\n *\n * - **STRUCTURE** — which activities and transitions exist, and which way the graph runs.\n * Only a change here can move a node, and only a change here runs dagre.\n * - **EVERYTHING ELSE** — metric, formatting, selection, rework badges. These rebuild the\n * model (cheap, pure) and are re-applied onto the CACHED positions.\n *\n * The cache is keyed on `structureKey` (see `processGraphStructureKey`) plus the layout\n * direction, and it stores POSITIONS BY NODE ID rather than node objects — which is\n * exactly what lets a metric switch reuse a layout: the node objects are all new, their\n * ids are not.\n *\n * `layoutRuns` is returned so the \"no second `layoutFlow` call for a metric-only change\"\n * acceptance criterion is something a test can ASSERT rather than something a comment\n * claims.\n *\n * ## Debounce\n *\n * A structural change that arrives while another is still settling (dragging an\n * abstraction slider emits one graph per pointer move) is debounced by\n * {@link DEFAULT_LAYOUT_DEBOUNCE_MS}. The FIRST layout for a given hook instance is never\n * debounced — an empty canvas for 80 ms on mount is a worse trade than one extra dagre\n * run — so the debounce only ever delays a RE-layout.\n *\n * ## Motion\n *\n * Position deltas animate via a CSS transform transition on the React Flow node element\n * (see `PROCESS_MAP_NODE_MOTION_CLASS`), so nodes slide rather than re-mount, and the\n * whole thing is neutralized under `prefers-reduced-motion`.\n */\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { XYPosition } from \"@xyflow/react\";\nimport { layoutFlow, type FlowLayoutDirection } from \"@elabs-ai/components-flow\";\nimport type { ProcessMapEdge, ProcessMapNode } from \"./map-model\";\n\n/**\n * Within-rank spacing, wider than `layoutFlow`'s generic 48.\n *\n * Every transition on this canvas carries a label pill, and the pill is portalled OUTSIDE\n * the SVG — it contributes nothing to dagre's routing and nothing to React Flow's fitted\n * bounds, so a gap that reads fine for bare arrows is exactly one pill too narrow, and\n * nothing in the layout maths notices. At 48 the shipped eleven-activity fixture printed\n * three pills on top of activity cards in each direction; at 72, none.\n */\nconst NODE_SPACING = 72;\n\n/**\n * Between-rank spacing, per direction — and the asymmetry is the point.\n *\n * The rank gap is the one a label pill sits IN: a transition's pill is anchored at its\n * path's midpoint, which falls between two ranks. A pill is a wide, short shape (a number,\n * sometimes a number and a duration), so the gap it needs is not a single number — it is\n * the pill's extent ALONG the rank axis, and that axis turns with the layout:\n *\n * - **Top-to-bottom** ranks stack vertically, so the gap only has to clear the pill's\n * HEIGHT — one line of `text-meta`. `layoutFlow`'s default 72 already does.\n * - **Left-to-right** ranks run horizontally, so the same gap has to clear the pill's\n * WIDTH, several times larger. Measured on the same fixture: at 72 and at 96 two pills\n * still printed over activity cards; at 120 none did.\n *\n * The two reversed directions take the number for their own axis: `BT` is vertical like\n * `TB`, `RL` horizontal like `LR`. Neither is reachable from `ProcessMap`'s own\n * `direction` prop today, and both are spelled out anyway rather than defaulted, so a\n * future map that offers them inherits the reasoning instead of a silent fallback.\n *\n * Using the left-to-right number in both directions would be the tidier-looking constant\n * and the wrong call — a top-to-bottom map is height-constrained, so it would spend 22% of\n * the opening zoom (0.430 → 0.334 on the same fixture) buying clearance for an axis that\n * never needed it.\n */\nconst RANK_SPACING: Record<FlowLayoutDirection, number> = { TB: 72, BT: 72, LR: 120, RL: 120 };\n\n/** How long a RE-layout waits for the structure to settle. */\nexport const DEFAULT_LAYOUT_DEBOUNCE_MS = 80;\n\n/**\n * The class that animates node position deltas.\n *\n * React Flow writes `transform: translate(x, y)` onto its own node element, so the\n * transition has to live there — not on anything this package renders. The element is\n * addressed as `div[data-id]` rather than by React Flow's own `.react-flow__node` class\n * on purpose: that class contains underscores, which have to be backslash-escaped inside\n * a Tailwind arbitrary variant and would then be eaten by JavaScript string escaping the\n * moment the class string passes through `cn()`. Nodes are `div`s and edges are `g`s, so\n * the attribute form is both simpler and correctly scoped. Same reasoning as the ancestor\n * selector in `FlowNode`'s focus indicator.\n *\n * ## The `:not([data-handlepos])` is load-bearing\n *\n * React Flow renders a `<Handle>` as a `<div>` carrying BOTH `data-id` and\n * `data-handlepos`, so a bare `div[data-id]` also matched every connector dot — and a dot\n * changes sides when the layout direction flips (bottom/top becomes right/left). The dot\n * then ANIMATED to its new side, React Flow measured `handleBounds` while it was in\n * flight, and never measured again: the stored bounds said `x: 164` where the DOM had\n * settled at `172`, and every edge on the map terminated in mid-air. Measured at 69 px of\n * drift left-to-right, 24 px top-to-bottom.\n *\n * A handle is not a node position; it has no delta to animate, and it must be measurable\n * the instant it is placed. Excluding it is the fix, not a workaround.\n */\n/*\n * WRITTEN OUT IN FULL, deliberately. Tailwind extracts candidates from source TEXT and\n * never evaluates JavaScript, so a class assembled by interpolating a `MOTION_TARGET`\n * constant produces no CSS at all: `new Scanner().scanFiles()` returns only `[\"const\",\n * \"export\"]` for the interpolated form and all four utilities for this one. The selector\n * therefore repeats instead of being factored into a constant — the duplication is what\n * keeps the animation alive.\n */\nexport const PROCESS_MAP_NODE_MOTION_CLASS =\n \"[&_div[data-id]:not([data-handlepos])]:transition-transform \" +\n \"[&_div[data-id]:not([data-handlepos])]:duration-base \" +\n \"[&_div[data-id]:not([data-handlepos])]:ease-standard \" +\n \"motion-reduce:[&_div[data-id]:not([data-handlepos])]:transition-none\";\n\n/** One cached dagre result: where every node sits, plus the structure dagre reported. */\nexport interface ProcessLayoutSnapshot {\n positions: Record<string, XYPosition>;\n /** Node id → the handle side edges LEAVE from, as `layoutFlow` set it per direction. */\n sourcePosition: Record<string, ProcessMapNode[\"sourcePosition\"]>;\n /** Node id → the handle side edges ENTER on. */\n targetPosition: Record<string, ProcessMapNode[\"targetPosition\"]>;\n /** Edge ids running against the layout direction (`layoutFlow`'s `backEdges`). */\n backEdges: string[];\n /** Edge ids whose source equals their target (`layoutFlow`'s `selfLoops`). */\n selfLoops: string[];\n /** Wall-clock cost of the `layoutFlow` call that produced this, in milliseconds. */\n durationMs: number;\n}\n\n/** Inputs to {@link useProcessLayout}. */\nexport interface UseProcessLayoutOptions {\n nodes: ProcessMapNode[];\n edges: ProcessMapEdge[];\n /** Structure-only cache key — see `processGraphStructureKey`. NEVER include a metric. */\n structureKey: string;\n direction: FlowLayoutDirection;\n /** @default {@link DEFAULT_LAYOUT_DEBOUNCE_MS} */\n debounceMs?: number;\n}\n\n/** What {@link useProcessLayout} answers. */\nexport interface UseProcessLayoutResult {\n /** `nodes`, positioned. Identity changes whenever `nodes` does, so metrics stay live. */\n nodes: ProcessMapNode[];\n /** Edge ids `layoutFlow` reported as running backwards, for the back-edge shape. */\n backEdgeIds: ReadonlySet<string>;\n /** Edge ids `layoutFlow` withheld as self-loops. */\n selfLoopIds: ReadonlySet<string>;\n /** How many times `layoutFlow` has actually run for this hook instance. */\n layoutRuns: number;\n /** Cost of the most recent `layoutFlow` call, in milliseconds. `0` before the first. */\n lastLayoutMs: number;\n /** True between a structural change and the debounced layout that answers it. */\n pending: boolean;\n}\n\nconst EMPTY_SET: ReadonlySet<string> = new Set<string>();\n\n/** Snapshot a `layoutFlow` result into the id-keyed shape the cache stores. */\nfunction toSnapshot(\n result: ReturnType<typeof layoutFlow<ProcessMapNode, ProcessMapEdge>>,\n durationMs: number,\n): ProcessLayoutSnapshot {\n const positions: Record<string, XYPosition> = {};\n const sourcePosition: ProcessLayoutSnapshot[\"sourcePosition\"] = {};\n const targetPosition: ProcessLayoutSnapshot[\"targetPosition\"] = {};\n for (const node of result.nodes) {\n positions[node.id] = node.position;\n sourcePosition[node.id] = node.sourcePosition;\n targetPosition[node.id] = node.targetPosition;\n }\n return {\n positions,\n sourcePosition,\n targetPosition,\n backEdges: result.backEdges,\n selfLoops: result.selfLoops,\n durationMs,\n };\n}\n\n/** Apply a cached snapshot to a fresh set of nodes, matching on id. */\nexport function applyLayoutSnapshot(\n nodes: ProcessMapNode[],\n snapshot: ProcessLayoutSnapshot,\n): ProcessMapNode[] {\n return nodes.map((node) => {\n const position = snapshot.positions[node.id];\n if (!position) return node;\n return {\n ...node,\n position,\n sourcePosition: snapshot.sourcePosition[node.id],\n targetPosition: snapshot.targetPosition[node.id],\n };\n });\n}\n\n/**\n * Lay a process graph out, reusing a cached layout whenever the structure is unchanged.\n *\n * @see {@link UseProcessLayoutResult.layoutRuns} — the observable proof that a metric-only\n * change does not re-run dagre.\n */\nexport function useProcessLayout({\n nodes,\n edges,\n structureKey,\n direction,\n debounceMs = DEFAULT_LAYOUT_DEBOUNCE_MS,\n}: UseProcessLayoutOptions): UseProcessLayoutResult {\n const cacheKey = `${structureKey}::${direction}`;\n const cache = useRef(new Map<string, ProcessLayoutSnapshot>());\n const runs = useRef(0);\n // The layout the render below reads. Held in state (not a ref) because producing a new\n // one must re-render; keyed so a cache hit for a DIFFERENT key is never mistaken for a\n // hit for this one.\n const [applied, setApplied] = useState<{ key: string; snapshot: ProcessLayoutSnapshot } | null>(\n null,\n );\n\n const compute = useCallback(\n (key: string, currentNodes: ProcessMapNode[], currentEdges: ProcessMapEdge[]) => {\n const started = performance.now();\n const result = layoutFlow<ProcessMapNode, ProcessMapEdge>(currentNodes, currentEdges, {\n direction,\n nodeSpacing: NODE_SPACING,\n rankSpacing: RANK_SPACING[direction],\n });\n const snapshot = toSnapshot(result, performance.now() - started);\n runs.current += 1;\n cache.current.set(key, snapshot);\n setApplied({ key, snapshot });\n },\n [direction],\n );\n\n // The nodes/edges the (possibly debounced) layout should run against. Held in a ref so\n // the effect below depends on the STRUCTURE key alone — re-running it whenever a metric\n // produced new node objects is precisely the bug the cache exists to prevent.\n const latest = useRef({ nodes, edges });\n latest.current = { nodes, edges };\n\n const cachedForKey = cache.current.get(cacheKey);\n const hasLayout = applied?.key === cacheKey || cachedForKey !== undefined;\n\n useEffect(() => {\n const cached = cache.current.get(cacheKey);\n if (cached) {\n setApplied((current) =>\n current?.key === cacheKey ? current : { key: cacheKey, snapshot: cached },\n );\n return;\n }\n if (latest.current.nodes.length === 0) {\n setApplied({\n key: cacheKey,\n snapshot: {\n positions: {},\n sourcePosition: {},\n targetPosition: {},\n backEdges: [],\n selfLoops: [],\n durationMs: 0,\n },\n });\n return;\n }\n // First layout of this hook instance renders immediately; a RE-layout waits for the\n // structure to settle (an abstraction slider emits one graph per pointer move).\n if (runs.current === 0 || debounceMs <= 0) {\n compute(cacheKey, latest.current.nodes, latest.current.edges);\n return;\n }\n const timer = setTimeout(\n () => compute(cacheKey, latest.current.nodes, latest.current.edges),\n debounceMs,\n );\n return () => clearTimeout(timer);\n }, [cacheKey, compute, debounceMs]);\n\n const snapshot = applied?.key === cacheKey ? applied.snapshot : cachedForKey;\n\n const positionedNodes = useMemo(\n () => (snapshot ? applyLayoutSnapshot(nodes, snapshot) : nodes),\n [nodes, snapshot],\n );\n\n const backEdgeIds = useMemo(\n () => (snapshot ? new Set(snapshot.backEdges) : EMPTY_SET),\n [snapshot],\n );\n const selfLoopIds = useMemo(\n () => (snapshot ? new Set(snapshot.selfLoops) : EMPTY_SET),\n [snapshot],\n );\n\n return {\n nodes: positionedNodes,\n backEdgeIds,\n selfLoopIds,\n layoutRuns: runs.current,\n lastLayoutMs: snapshot?.durationMs ?? 0,\n pending: !hasLayout && nodes.length > 0,\n };\n}\n","\"use client\";\n\n/**\n * AbstractionControls — the two sliders every process-mining explorer opens with (RM-052,\n * issue #227). Activities/paths abstraction is a VIEW filter, never a re-analysis\n * (`abstractGraph`'s own contract — see `core/abstract-graph.ts`): dragging a slider hides\n * nodes/edges, it never recomputes a statistic.\n *\n * Composed entirely from `@elabs-ai/components-ui` primitives — two `Slider`s, a `Switch`,\n * a `Button` — plus the pure, framework-free `computeAutoAbstraction` heuristic. No local\n * slider/switch/select is authored here (`pnpm process:reuse:check`).\n *\n * ## Controlled, not stateful\n *\n * This component owns no abstraction state of its own — `abstraction` is the current value,\n * `onAbstractionChange` is how it asks for a new one. `useProcessExplorer` is the one place\n * that actually holds the `useState` (see the compound-component \"lift state into the\n * provider\" convention, `.claude/rules/component-api.md`).\n *\n * ## \"Auto\" reads the graph, not just the sliders\n *\n * The pre-abstraction activity total is derived, not passed as a separate prop:\n * `graph.activities.length + hiddenCounts.activities` — the CURRENT (already-abstracted)\n * graph's kept count plus what it currently hides always sums back to the full graph's\n * activity count, so \"Auto\" needs no additional field on `useProcessExplorer`.\n */\nimport { forwardRef, useCallback, useId, type HTMLAttributes } from \"react\";\nimport { Sparkles } from \"lucide-react\";\nimport { Button, Label, Slider, Switch, useLocale } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport type { AbstractionOptions } from \"../core/abstract-graph\";\nimport type { ProcessGraph } from \"../core/types\";\nimport { computeAutoAbstraction } from \"./auto-abstraction\";\n\nconst TICKS = [25, 50, 75, 100] as const;\n\n/**\n * The range every tick's percent value is drawn from — the SAME `min`/`max` handed to both\n * `Slider`s below (`min={SLIDER_MIN} max={SLIDER_MAX}`), so a tick's rail offset is always\n * derived from the slider's own range rather than a second, independently hardcoded `0..100`\n * (#355). Today they coincide numerically (percent === offset), but a future non-percent range\n * stays correct because both read this one pair of constants.\n */\nconst SLIDER_MIN = 0;\nconst SLIDER_MAX = 100;\n\n/** A tick's position along the rail, as a `0..100` offset — see {@link SLIDER_MIN}. */\nfunction tickOffsetPercent(percent: number): number {\n return ((percent - SLIDER_MIN) / (SLIDER_MAX - SLIDER_MIN)) * 100;\n}\n\n/**\n * How far above the \"Auto\" activities fraction the paths fraction is set — the roadmap's\n * \"paths % = 0.2 above the activities %\" (RM-052 round 2, #227, F2 third piece). `abstractGraph`\n * only considers an edge a candidate to keep once BOTH its endpoints survive the activities\n * cut (`core/abstract-graph.ts`'s `candidateEdges` filter), so that candidate pool is already\n * thinner than the full edge set once activities are cut — applying the SAME fraction to it\n * would keep noticeably fewer edges than activities were kept. The offset compensates for\n * that, without a second search over the real graph.\n */\nconst AUTO_PATHS_OFFSET = 0.2;\n\nfunction toPercent(fraction: number): number {\n return Math.round(fraction * 100);\n}\n\nfunction fromPercent(percent: number): number {\n return percent / 100;\n}\n\nexport interface AbstractionControlsProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** The current abstraction — both fractions, `invert`, and (read-only here) `keepConnected`. */\n abstraction: AbstractionOptions;\n /** Called with a PARTIAL patch — mirrors `useProcessExplorer`'s own `setAbstraction`. */\n onAbstractionChange(next: Partial<AbstractionOptions>): void;\n /** The CURRENT (already-abstracted) graph — only `activities.length` is read. */\n graph: Pick<ProcessGraph, \"activities\">;\n /** What the current abstraction hides, straight from `AbstractedGraph.hidden`. */\n hiddenCounts: { activities: number; paths: number };\n /** Node budget the \"Auto\" button searches for. Default `25`. */\n autoMaxActivities?: number;\n /** Accessible name for the control group. Default from locale. */\n label?: string;\n}\n\n/**\n * One clickable percentage tick — sets its slider directly rather than requiring a drag.\n * Positioned on a `relative` rail (never spread by flexbox, #355): its centre sits at its own\n * value's fraction of the rail, matching the `Slider` track's coordinate system directly below\n * it. The interior ticks centre on their value (`-translate-x-1/2`); the `100%` tick right-aligns\n * to the rail's own edge instead (`-translate-x-full`) so it is never clipped.\n */\nfunction Tick({\n percent,\n active,\n onSelect,\n}: {\n percent: number;\n active: boolean;\n onSelect(percent: number): void;\n}) {\n const offsetPercent = tickOffsetPercent(percent);\n return (\n <button\n type=\"button\"\n data-slot=\"abstraction-controls-tick\"\n aria-pressed={active}\n onClick={() => onSelect(percent)}\n style={{ left: `${offsetPercent}%` }}\n className={cn(\n \"focus-ring absolute top-0 rounded-sm text-meta text-muted-foreground transition-colors duration-fast ease-standard hover:text-foreground\",\n offsetPercent <= 0\n ? \"translate-x-0\"\n : offsetPercent >= 100\n ? \"-translate-x-full\"\n : \"-translate-x-1/2\",\n active && \"font-semibold text-foreground\",\n )}\n >\n {percent}%\n </button>\n );\n}\n\nexport const AbstractionControls = forwardRef<HTMLDivElement, AbstractionControlsProps>(\n function AbstractionControls(\n {\n abstraction,\n onAbstractionChange,\n graph,\n hiddenCounts,\n autoMaxActivities = 25,\n label,\n className,\n ...props\n },\n ref,\n ) {\n const { t } = useLocale();\n const activitiesId = useId();\n const pathsId = useId();\n\n const activitiesPercent = toPercent(abstraction.activities);\n const pathsPercent = toPercent(abstraction.paths);\n\n const setActivitiesPercent = useCallback(\n (percent: number) => onAbstractionChange({ activities: fromPercent(percent) }),\n [onAbstractionChange],\n );\n const setPathsPercent = useCallback(\n (percent: number) => onAbstractionChange({ paths: fromPercent(percent) }),\n [onAbstractionChange],\n );\n\n const handleAuto = useCallback(() => {\n const totalActivities = graph.activities.length + hiddenCounts.activities;\n const result = computeAutoAbstraction(totalActivities, {\n maxActivities: autoMaxActivities,\n });\n onAbstractionChange({\n activities: result.activities,\n paths: Math.min(1, result.activities + AUTO_PATHS_OFFSET),\n });\n }, [graph.activities.length, hiddenCounts.activities, autoMaxActivities, onAbstractionChange]);\n\n // Two independently-pluralized fragments, joined — `t()` selects its plural category\n // from a single `count` var, so one activities count and one paths count cannot share\n // a single PluralMessage. Composing two already-localized fragments is the standard\n // way around that (see `resolveMessage` in locale-provider.tsx).\n const hiddenSummary = `${t(\"process.abstractionControls.hiddenActivities\", {\n count: hiddenCounts.activities,\n })} · ${t(\"process.abstractionControls.hiddenPaths\", { count: hiddenCounts.paths })}`;\n\n return (\n <div\n ref={ref}\n data-slot=\"abstraction-controls\"\n role=\"group\"\n aria-label={label ?? t(\"process.abstractionControls.label\")}\n className={cn(\"flex flex-col gap-4\", className)}\n {...props}\n >\n <div\n data-slot=\"abstraction-controls-activities\"\n role=\"group\"\n aria-labelledby={`${activitiesId}-label`}\n className=\"flex flex-col gap-1.5\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <Label id={`${activitiesId}-label`} htmlFor={activitiesId} className=\"text-body\">\n {t(\"process.abstractionControls.activities\")}\n </Label>\n <span className=\"text-meta text-muted-foreground tabular-nums\">\n {activitiesPercent}%\n </span>\n </div>\n <Slider\n id={activitiesId}\n min={SLIDER_MIN}\n max={SLIDER_MAX}\n step={1}\n value={[activitiesPercent]}\n onValueChange={([next]) => setActivitiesPercent(next ?? activitiesPercent)}\n aria-label={t(\"process.abstractionControls.activities\")}\n />\n <div className=\"relative h-4\">\n {TICKS.map((percent) => (\n <Tick\n key={percent}\n percent={percent}\n active={activitiesPercent === percent}\n onSelect={setActivitiesPercent}\n />\n ))}\n </div>\n </div>\n\n <div\n data-slot=\"abstraction-controls-paths\"\n role=\"group\"\n aria-labelledby={`${pathsId}-label`}\n className=\"flex flex-col gap-1.5\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <Label id={`${pathsId}-label`} htmlFor={pathsId} className=\"text-body\">\n {t(\"process.abstractionControls.paths\")}\n </Label>\n <span className=\"text-meta text-muted-foreground tabular-nums\">{pathsPercent}%</span>\n </div>\n <Slider\n id={pathsId}\n min={SLIDER_MIN}\n max={SLIDER_MAX}\n step={1}\n value={[pathsPercent]}\n onValueChange={([next]) => setPathsPercent(next ?? pathsPercent)}\n aria-label={t(\"process.abstractionControls.paths\")}\n />\n <div className=\"relative h-4\">\n {TICKS.map((percent) => (\n <Tick\n key={percent}\n percent={percent}\n active={pathsPercent === percent}\n onSelect={setPathsPercent}\n />\n ))}\n </div>\n </div>\n\n <div\n data-slot=\"abstraction-controls-footer\"\n className=\"flex items-center justify-between gap-3\"\n >\n <div className=\"flex items-center gap-2\">\n <Switch\n id={`${activitiesId}-invert`}\n checked={abstraction.invert ?? false}\n onCheckedChange={(checked) => onAbstractionChange({ invert: checked })}\n />\n <Label htmlFor={`${activitiesId}-invert`} className=\"text-body\">\n {t(\"process.abstractionControls.invert\")}\n </Label>\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleAuto}\n data-slot=\"abstraction-controls-auto\"\n >\n <Sparkles aria-hidden=\"true\" />\n {t(\"process.abstractionControls.auto\")}\n </Button>\n </div>\n\n <p\n data-slot=\"abstraction-controls-hidden-summary\"\n role=\"status\"\n aria-live=\"polite\"\n className=\"text-meta text-muted-foreground\"\n >\n {hiddenSummary}\n </p>\n </div>\n );\n },\n);\n","/**\n * Auto abstraction heuristic — RM-052 (issue #227).\n *\n * The \"Auto\" button on {@link AbstractionControls} picks an activities fraction that fits\n * a node budget, so a reader opens a busy graph and gets a readable one in one click\n * instead of hand-dragging a slider. This module is pure and framework-free on purpose —\n * it is a small, independently testable piece of judgment, not a UI concern.\n *\n * ## The heuristic\n *\n * \"Smallest activities % whose graph fits the budget\" is the phrase this feature is\n * usually described by, but taken literally it is degenerate: a fraction near 0 always\n * fits any budget, so the \"smallest fraction\" reading is trivially satisfied by hiding\n * almost everything. What a reader actually wants from \"Auto\" is the OPPOSITE end of\n * that: the LARGEST fraction — the most detail — that still fits the budget. This module\n * implements that reading.\n *\n * ## Bounded and terminating, by construction\n *\n * The search is a fixed-iteration binary search over the fraction range `[minFraction,\n * 1]` — `maxSteps` (default 8) iterations, no early-exit convergence check, no\n * data-dependent loop bound. That makes termination a property of the `for` loop itself,\n * not of the input: `computeAutoAbstraction` always does at most `maxSteps` probes of\n * `keptAt(fraction)`, regardless of how many activities the graph has. See\n * `auto-abstraction.test.ts` for the explicit bound assertion.\n *\n * ## Known limitation: this heuristic is topology-blind (RM-052 round 2, #227, F2)\n *\n * `keptActivityCount` predicts how many activities a fraction keeps from the COUNT alone —\n * `round(total * fraction)`. It has no visibility into which activities `abstractGraph`\n * would actually drop, or how they connect. `abstractGraph`'s default `keepConnected: true`\n * adds activities back in to restore reachability to a start/end activity that truncation\n * would otherwise strand — and on a graph with no bypass edges (a strict, unbranched chain,\n * where every activity's only path to the end activity runs through every activity after\n * it), dropping ANY suffix forces the ENTIRE remainder back in. The naive prediction and the\n * real, reconnected result can then diverge by the graph's full size: a 30-activity chain\n * with `maxActivities: 10` predicts a fraction that keeps 10 activities, but\n * `abstractGraph(graph, { activities: fraction, keepConnected: true })` actually keeps all\n * 30 — see `auto-abstraction.test.ts`'s \"known limitation\" describe block, which measures\n * this exact case and isolates `keepConnected` as the sole cause (turning it off makes the\n * real result match the naive prediction exactly).\n *\n * This is accepted as a best-effort gap, not fixed here: fixing it precisely would mean this\n * module accepting a real `ProcessGraph` and searching against `abstractGraph`'s actual\n * output rather than a plain count — a signature change that would also require updating\n * `AbstractionControls`'s call site (`handleAuto`), which sits outside this round's write-set\n * (RM-052-fix-brief.md / RM-052-tristate-decision.md §10). A future round that wants an exact\n * search should route that signature change through `brand-ui-design-system-architect` (it\n * changes this module's public contract) rather than patching around it here.\n */\nexport interface AutoAbstractionOptions {\n /** Largest number of activities the resulting view should keep. Default `25`. */\n maxActivities?: number;\n /** Maximum fraction candidates probed before returning the best found so far. Default `8`. */\n maxSteps?: number;\n /** The smallest fraction ever offered — \"Auto\" never hides everything. Default `0.05`. */\n minFraction?: number;\n}\n\nexport interface AutoAbstractionResult {\n /** The activities fraction to hand to `setAbstraction`. */\n activities: number;\n /** How many fraction candidates were actually probed — always `<= maxSteps`. */\n steps: number;\n}\n\nconst DEFAULT_MAX_ACTIVITIES = 25;\nconst DEFAULT_MAX_STEPS = 8;\nconst DEFAULT_MIN_FRACTION = 0.05;\n\n/**\n * Mirrors `abstractGraph`'s own kept-activity count: `round(total * fraction)`, floored at\n * one activity — `abstractGraph` never hides every activity, so this heuristic must not\n * search for a fraction that would.\n */\nfunction keptActivityCount(total: number, fraction: number): number {\n return Math.max(1, Math.round(total * fraction));\n}\n\n/**\n * Picks the activities fraction \"Auto\" applies, from a plain activity COUNT — not a graph.\n * Paths are left to `abstractGraph`'s own connectivity repair, and duration/statistic\n * fields never enter the search, so this heuristic's result cannot be perturbed by the\n * discovery layer's own reservoir-sampling nondeterminism. Taking a count rather than a\n * `ProcessGraph` also lets a caller pass the graph's PRE-abstraction total — e.g.\n * `graph.activities.length + hiddenCounts.activities` — without building a throwaway array.\n */\nexport function computeAutoAbstraction(\n totalActivities: number,\n opts: AutoAbstractionOptions = {},\n): AutoAbstractionResult {\n const maxActivities = opts.maxActivities ?? DEFAULT_MAX_ACTIVITIES;\n const maxSteps = opts.maxSteps ?? DEFAULT_MAX_STEPS;\n const minFraction = opts.minFraction ?? DEFAULT_MIN_FRACTION;\n const total = totalActivities;\n\n // Already within budget at full detail (or nothing to abstract) — nothing to search for.\n if (total === 0 || keptActivityCount(total, 1) <= maxActivities) {\n return { activities: 1, steps: 0 };\n }\n\n // Even the floor doesn't fit — hand back the floor. Still bounded (one probe), still\n // the best answer this heuristic can offer for a graph this large.\n if (keptActivityCount(total, minFraction) > maxActivities) {\n return { activities: minFraction, steps: 1 };\n }\n\n let lo = minFraction; // largest fraction CONFIRMED to fit the budget so far\n let hi = 1; // smallest fraction confirmed NOT to fit\n let best = minFraction;\n\n for (let step = 1; step <= maxSteps; step += 1) {\n const mid = (lo + hi) / 2;\n if (keptActivityCount(total, mid) <= maxActivities) {\n best = mid;\n lo = mid;\n } else {\n hi = mid;\n }\n if (step === maxSteps) {\n return { activities: best, steps: step };\n }\n }\n\n // Unreachable — maxSteps is always >= 1 when opts allow it — but keeps the function\n // total for TypeScript's control-flow analysis.\n return { activities: best, steps: maxSteps };\n}\n","\"use client\";\n\n/**\n * MetricLayerSwitch — the \"what number is this graph drawing\" control (RM-052, issue\n * #227). A `ProcessMap` always shows SOME number per activity/transition; this component\n * is how a reader picks it.\n *\n * ## Three layers, not just two selects\n *\n * The Frequency/Performance/Rework `ToggleGroup` picks a FAMILY of readings before the\n * two `Select`s narrow to one member of it:\n * - **Frequency** — counts and shares (`ActivityFrequencyMode` / `TransitionFrequencyMode`).\n * - **Performance** — duration aggregates (`PerformanceAgg`) — the same 7-value domain on\n * both node and edge, so locking is always safe here.\n * - **Rework** — there is no frequency/performance READING for \"how much rework happened\n * at this activity\"; that overlay is `ProcessMap`'s own `rework` prop, driven by\n * `detectRework`, not a `ProcessMetric`. Selecting this layer disables both `Select`s\n * (and the lock, which has nothing to lock) rather than inventing a metric value that\n * does not exist.\n *\n * ## The lock keeps node and edge metrics in sync — and keeps the domain honest\n *\n * `ActivityFrequencyMode` only ever has 4 members (a node has no \"antecedent\"/\n * \"consequent\" side); `TransitionFrequencyMode` has 6. So the LOCKED edge `Select` is\n * restricted to the 4-member intersection — every option it offers is guaranteed valid\n * for the node too — and widens back to all 6 the moment the lock comes off. In the\n * Performance layer both sides already share one domain, so locking never restricts\n * anything there.\n *\n * ## Metric-value labels are REUSED from `ProcessMap`, not re-localized\n *\n * `nodeMetricLabel`/`edgeMetricLabel` (`process-map/map-model.ts`) already resolve every\n * `ProcessMetric` to its correct, audience-specific text — \"Occurrences\" for a node's\n * `absolute` vs \"Transitions\" for an edge's, \"Share of events\" vs \"Share of transitions\"\n * for `relative`, and so on. Minting a SECOND, shared `process.metricLayerSwitch.metric.*`\n * locale key per value would either duplicate that resolution or (worse) collapse the\n * node/edge distinction those two functions exist to preserve — so this component calls\n * them directly instead, per the reuse-first rule in `.claude/rules/quality-gates.md`.\n */\nimport { forwardRef, useCallback, useId, useState, type HTMLAttributes } from \"react\";\nimport { Lock, LockOpen } from \"lucide-react\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Toggle,\n ToggleGroup,\n ToggleGroupItem,\n useLocale,\n} from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport {\n edgeMetricLabel,\n isPerformanceMetric,\n nodeMetricLabel,\n type ProcessMetric,\n} from \"../process-map/map-model\";\n\nexport type MetricLayer = \"frequency\" | \"performance\" | \"rework\";\n\nexport interface MetricLayerSwitchMetric {\n node: ProcessMetric;\n edge: ProcessMetric;\n}\n\nexport interface MetricLayerSwitchProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n layer: MetricLayer;\n onLayerChange(layer: MetricLayer): void;\n metric: MetricLayerSwitchMetric;\n onMetricChange(next: Partial<MetricLayerSwitchMetric>): void;\n /** Controlled lock state. Omit to let the component manage it (`defaultLocked`). */\n locked?: boolean;\n /** Initial lock state when uncontrolled. Default `true`. */\n defaultLocked?: boolean;\n onLockedChange?(locked: boolean): void;\n label?: string;\n}\n\nconst FREQUENCY_NODE_OPTIONS: readonly ProcessMetric[] = [\n \"absolute\",\n \"absolute_case\",\n \"relative\",\n \"relative_case\",\n];\nconst FREQUENCY_EDGE_OPTIONS_LOCKED: readonly ProcessMetric[] = FREQUENCY_NODE_OPTIONS;\nconst FREQUENCY_EDGE_OPTIONS_UNLOCKED: readonly ProcessMetric[] = [\n \"absolute\",\n \"absolute_case\",\n \"relative\",\n \"relative_case\",\n \"relative_antecedent\",\n \"relative_consequent\",\n];\nconst PERFORMANCE_OPTIONS: readonly ProcessMetric[] = [\n \"median\",\n \"mean\",\n \"min\",\n \"max\",\n \"sum\",\n \"p90\",\n \"trimmed_mean\",\n];\n\nexport const MetricLayerSwitch = forwardRef<HTMLDivElement, MetricLayerSwitchProps>(\n function MetricLayerSwitch(\n {\n layer,\n onLayerChange,\n metric,\n onMetricChange,\n locked: lockedProp,\n defaultLocked = true,\n onLockedChange,\n label,\n className,\n ...props\n },\n ref,\n ) {\n const { t } = useLocale();\n const nodeId = useId();\n const edgeId = useId();\n\n const [uncontrolledLocked, setUncontrolledLocked] = useState(defaultLocked);\n const locked = lockedProp ?? uncontrolledLocked;\n const setLocked = useCallback(\n (next: boolean) => {\n if (lockedProp === undefined) setUncontrolledLocked(next);\n onLockedChange?.(next);\n },\n [lockedProp, onLockedChange],\n );\n\n const isRework = layer === \"rework\";\n const isPerformance = layer === \"performance\";\n\n const nodeOptions = isPerformance ? PERFORMANCE_OPTIONS : FREQUENCY_NODE_OPTIONS;\n const edgeOptions = isPerformance\n ? PERFORMANCE_OPTIONS\n : locked\n ? FREQUENCY_EDGE_OPTIONS_LOCKED\n : FREQUENCY_EDGE_OPTIONS_UNLOCKED;\n\n const handleLayerChange = useCallback(\n (next: string) => {\n if (!next) return; // Radix ToggleGroup type=\"single\" emits \"\" when re-clicking the active item\n const nextLayer = next as MetricLayer;\n onLayerChange(nextLayer);\n if (nextLayer === \"performance\" && !isPerformanceMetric(metric.node)) {\n onMetricChange({ node: \"median\", edge: \"median\" });\n } else if (nextLayer === \"frequency\" && isPerformanceMetric(metric.node)) {\n onMetricChange({ node: \"absolute\", edge: \"absolute\" });\n }\n },\n [onLayerChange, onMetricChange, metric.node],\n );\n\n const handleNodeChange = useCallback(\n (value: string) => {\n const next = value as ProcessMetric;\n onMetricChange(locked ? { node: next, edge: next } : { node: next });\n },\n [onMetricChange, locked],\n );\n const handleEdgeChange = useCallback(\n (value: string) => {\n const next = value as ProcessMetric;\n onMetricChange(locked ? { node: next, edge: next } : { edge: next });\n },\n [onMetricChange, locked],\n );\n\n const handleLockToggle = useCallback(\n (pressed: boolean) => {\n setLocked(pressed);\n // Turning the lock ON syncs the edge metric to the node's — the node domain is\n // always the intersection-safe one, so this is always a valid edge value.\n if (pressed && metric.edge !== metric.node) {\n onMetricChange({ edge: metric.node });\n }\n },\n [setLocked, metric.edge, metric.node, onMetricChange],\n );\n\n return (\n <div\n ref={ref}\n data-slot=\"metric-layer-switch\"\n role=\"group\"\n aria-label={label ?? t(\"process.metricLayerSwitch.label\")}\n className={cn(\"flex flex-col gap-3\", className)}\n {...props}\n >\n <ToggleGroup\n type=\"single\"\n variant=\"segmented\"\n value={layer}\n onValueChange={handleLayerChange}\n aria-label={t(\"process.metricLayerSwitch.layer\")}\n >\n <ToggleGroupItem value=\"frequency\">\n {t(\"process.metricLayerSwitch.frequency\")}\n </ToggleGroupItem>\n <ToggleGroupItem value=\"performance\">\n {t(\"process.metricLayerSwitch.performance\")}\n </ToggleGroupItem>\n <ToggleGroupItem value=\"rework\">{t(\"process.metricLayerSwitch.rework\")}</ToggleGroupItem>\n </ToggleGroup>\n\n {/* `min-w-0` on both columns: without it a flex child refuses to shrink below its\n content width, so in a narrow rail (a `SplitPanel` side, an inspector column)\n this row overflows and the edge `Select` is clipped by the pane instead of the\n two selects sharing what space there is. */}\n <div className=\"flex items-end gap-2\">\n <div\n data-slot=\"metric-layer-switch-node\"\n className=\"flex min-w-0 flex-1 flex-col gap-1.5\"\n >\n <label htmlFor={nodeId} className=\"text-meta text-muted-foreground\">\n {t(\"process.metricLayerSwitch.node\")}\n </label>\n <Select value={metric.node} onValueChange={handleNodeChange} disabled={isRework}>\n <SelectTrigger id={nodeId} aria-label={t(\"process.metricLayerSwitch.node\")}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {nodeOptions.map((option) => (\n <SelectItem key={option} value={option}>\n {nodeMetricLabel(option)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <Toggle\n pressed={locked}\n onPressedChange={handleLockToggle}\n disabled={isRework}\n aria-label={\n locked\n ? t(\"process.metricLayerSwitch.lockOn\")\n : t(\"process.metricLayerSwitch.lockOff\")\n }\n data-slot=\"metric-layer-switch-lock\"\n >\n {locked ? <Lock aria-hidden=\"true\" /> : <LockOpen aria-hidden=\"true\" />}\n </Toggle>\n\n <div\n data-slot=\"metric-layer-switch-edge\"\n className=\"flex min-w-0 flex-1 flex-col gap-1.5\"\n >\n <label htmlFor={edgeId} className=\"text-meta text-muted-foreground\">\n {t(\"process.metricLayerSwitch.edge\")}\n </label>\n <Select value={metric.edge} onValueChange={handleEdgeChange} disabled={isRework}>\n <SelectTrigger id={edgeId} aria-label={t(\"process.metricLayerSwitch.edge\")}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {edgeOptions.map((option) => (\n <SelectItem key={option} value={option}>\n {edgeMetricLabel(option)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n </div>\n );\n },\n);\n","\"use client\";\n\n/**\n * ProcessKpiStrip — the six numbers every process-mining session opens with (RM-052,\n * issue #227): cases, events, variants, median throughput, rework rate, and conformance.\n *\n * Built entirely on `MetricGrid`/`MetricCard` (`@elabs-ai/components-charts` /\n * `@elabs-ai/components-ui`) — this file authors no grid, no live region and no\n * loading skeleton of its own. `MetricGrid`'s own `loading` prop already forwards\n * `loading`/`announceLoading={false}` to every `MetricCard` child and owns the single\n * region-level `role=\"status\"` announcement (loading-states.md §a11y, \"one live region\n * per region, not per box\") — reusing it is what keeps this component from re-deriving\n * that convention badly.\n *\n * ## Conformance never fabricates a number\n *\n * `conformance` is `number | null | undefined` on purpose: a fraction (0..1) once a\n * conformance model has actually been fitted against the log, or `null`/`undefined`\n * when none has. Rendering `0` in the gap would read as \"0% conformant\" — a real,\n * alarming measurement — when the honest state is \"not measured\". The unavailable\n * state renders a muted icon + word instead of a number, and carries its own\n * `description` explaining why.\n *\n * ## The conformance tile never reflows across states (RM-052 round 2, #227, F3)\n *\n * `MetricCard` only reserves a description line when it is GIVEN a `description` — so\n * passing `description={hasConformance ? undefined : hint}` (the naive reading) made the\n * tile's own box height depend on the very state it's rendering: measured shorter with a\n * conformance score, taller in the \"not available\" state. The fix keeps the description\n * SLOT present in every state — loading, measured, and unavailable all pass a `description`\n * — but only the unavailable state's text is visible; the measured state's copy of the same\n * string is rendered `invisible` (`visibility: hidden` + `aria-hidden`), which reserves the\n * identical line height without showing misleading or duplicated copy to a sighted or\n * screen-reader user.\n */\nimport { type HTMLAttributes } from \"react\";\nimport { CircleSlash2 } from \"lucide-react\";\nimport { MetricCard, type LocaleContextValue } from \"@elabs-ai/components-ui\";\nimport { useLocale } from \"@elabs-ai/components-ui\";\nimport { MetricGrid } from \"@elabs-ai/components-charts\";\nimport { Sparkline } from \"@elabs-ai/components-charts\";\nimport { formatDurationMs } from \"../process-map/map-model\";\n\nexport interface ProcessKpiStripKpis {\n cases: number;\n events: number;\n variants: number;\n medianThroughput: number;\n reworkRate: number;\n}\n\nexport type ProcessKpiStripTrendKey = keyof ProcessKpiStripKpis | \"conformance\";\n\nexport interface ProcessKpiStripProps extends HTMLAttributes<HTMLDivElement> {\n kpis: ProcessKpiStripKpis;\n /** `0..1`, or `null`/`undefined` when no conformance model has been fitted. */\n conformance?: number | null;\n /** Optional per-tile trend series (oldest -> newest), keyed by KPI. */\n trends?: Partial<Record<ProcessKpiStripTrendKey, number[]>>;\n loading?: boolean;\n}\n\n/** How a trend's two endpoints are formatted — mirrors each tile's own headline format. */\ntype TrendValueFormat = \"count\" | \"duration\" | \"percent\";\n\n/** The trend's direction, as the locale key for its word (\"rising\"/\"falling\"/\"steady\"). */\nfunction trendDirectionKey(\n first: number,\n last: number,\n):\n | \"process.kpiStrip.trendRising\"\n | \"process.kpiStrip.trendFalling\"\n | \"process.kpiStrip.trendSteady\" {\n if (last > first) return \"process.kpiStrip.trendRising\";\n if (last < first) return \"process.kpiStrip.trendFalling\";\n return \"process.kpiStrip.trendSteady\";\n}\n\n/** Format one trend endpoint the same way the tile's own headline value is formatted. */\nfunction formatTrendValue(\n formatNumber: LocaleContextValue[\"formatNumber\"],\n value: number,\n format: TrendValueFormat,\n): string {\n switch (format) {\n case \"duration\":\n return formatDurationMs(value);\n case \"percent\":\n return formatNumber(value, { style: \"percent\", maximumFractionDigits: 1 });\n case \"count\":\n default:\n return formatNumber(value);\n }\n}\n\n/**\n * A sparkline's text alternative (#359): the subject plus what the picture shows, not the\n * subject alone — the tile's own label already names the subject, and `Sparkline`'s own\n * default (which describes the series) is strictly more informative than repeating it.\n * Numbers are formatted the same way the tile's own headline value is, so the visible and\n * accessible readings never disagree (a raw 0.18 beside a tile printing 18% would).\n */\nfunction trendVisual(\n values: number[] | undefined,\n subject: string,\n format: TrendValueFormat,\n t: LocaleContextValue[\"t\"],\n formatNumber: LocaleContextValue[\"formatNumber\"],\n) {\n if (!values || values.length === 0) return undefined;\n const first = values[0]!;\n const last = values[values.length - 1]!;\n const label = t(\"process.kpiStrip.trendAlt\", {\n subject,\n periods: values.length,\n direction: t(trendDirectionKey(first, last)),\n first: formatTrendValue(formatNumber, first, format),\n last: formatTrendValue(formatNumber, last, format),\n });\n return <Sparkline values={values} label={label} />;\n}\n\nexport function ProcessKpiStrip({\n kpis,\n conformance,\n trends,\n loading = false,\n className,\n ...props\n}: ProcessKpiStripProps) {\n const { t, formatNumber } = useLocale();\n\n const hasConformance = conformance !== null && conformance !== undefined;\n const conformanceHint = t(\"process.kpiStrip.conformanceUnavailableHint\");\n\n return (\n <div data-slot=\"process-kpi-strip\" className={className} {...props}>\n <MetricGrid columns={3} loading={loading}>\n <MetricCard\n label={t(\"process.kpiStrip.cases\")}\n value={kpis.cases}\n announceLoading={false}\n visual={trendVisual(trends?.cases, t(\"process.kpiStrip.cases\"), \"count\", t, formatNumber)}\n />\n <MetricCard\n label={t(\"process.kpiStrip.events\")}\n value={kpis.events}\n announceLoading={false}\n visual={trendVisual(\n trends?.events,\n t(\"process.kpiStrip.events\"),\n \"count\",\n t,\n formatNumber,\n )}\n />\n <MetricCard\n label={t(\"process.kpiStrip.variants\")}\n value={kpis.variants}\n announceLoading={false}\n visual={trendVisual(\n trends?.variants,\n t(\"process.kpiStrip.variants\"),\n \"count\",\n t,\n formatNumber,\n )}\n />\n <MetricCard\n label={t(\"process.kpiStrip.medianThroughput\")}\n value={formatDurationMs(kpis.medianThroughput)}\n announceLoading={false}\n visual={trendVisual(\n trends?.medianThroughput,\n t(\"process.kpiStrip.medianThroughput\"),\n \"duration\",\n t,\n formatNumber,\n )}\n />\n <MetricCard\n label={t(\"process.kpiStrip.reworkRate\")}\n value={kpis.reworkRate}\n valueFormat=\"percent\"\n announceLoading={false}\n visual={trendVisual(\n trends?.reworkRate,\n t(\"process.kpiStrip.reworkRate\"),\n \"percent\",\n t,\n formatNumber,\n )}\n />\n <MetricCard\n label={t(\"process.kpiStrip.conformance\")}\n value={\n hasConformance ? (\n (conformance as number)\n ) : (\n <span\n data-slot=\"process-kpi-strip-conformance-unavailable\"\n className=\"text-title inline-flex items-center gap-1.5 text-muted-foreground\"\n >\n <CircleSlash2 aria-hidden=\"true\" className=\"size-5\" />\n {t(\"process.kpiStrip.conformanceUnavailable\")}\n </span>\n )\n }\n valueFormat={hasConformance ? \"percent\" : undefined}\n description={\n hasConformance ? (\n // Same slot, same height, every state (F3) — invisible rather than absent, so\n // the tile's box never reflows depending on whether conformance was measured.\n <span aria-hidden=\"true\" className=\"invisible\">\n {conformanceHint}\n </span>\n ) : (\n conformanceHint\n )\n }\n announceLoading={false}\n visual={\n hasConformance\n ? trendVisual(\n trends?.conformance,\n t(\"process.kpiStrip.conformance\"),\n \"percent\",\n t,\n formatNumber,\n )\n : undefined\n }\n />\n </MetricGrid>\n </div>\n );\n}\n","\"use client\";\n\n/**\n * `useProcessExplorer` — the coordinating hook every process-mining view is driven from\n * (RM-052, issue #227).\n *\n * This is the tri-state contract analysis §5.3 calls \"the most important [API decision]\n * ... because it is what makes the set usable both in a standalone prototype and embedded\n * in a BI platform's mashup\": `ProcessMap` and the filter menu it exposes never call\n * `filterLog`/`discoverGraph` themselves — they render `selection` and emit\n * `onFilterIntent`/`onSelect`, and THIS hook is what turns those intents into a\n * recomputed graph. A host with its own associative-selection engine swaps this hook out\n * entirely and drives the exact same components (R22, RM-058).\n *\n * ## What recomputes, and when\n *\n * - `filteredLog` — `filterLog(log, intents)`, always synchronous (a linear scan; nothing\n * here is expensive enough to move off-thread).\n * - **Two independent discoveries when they genuinely differ, one when they don't**\n * (RM-052 round 2, #227, Invariant F — filtering re-inks, it never removes): the FULL\n * `log` and the `filteredLog` are each discovered on their own sync-or-worker path with\n * their own `loading` flag and request-id ref, because either one can independently\n * cross `workerThreshold`. With no intent active `filteredLog === log`, and the\n * filtered role REUSES the full discovery instead of recomputing it — running two\n * identical discoveries in that state was a round-2 regression against `4a1a244`, fixed\n * in round 3 (G1); see `useLogDiscovery`'s own docblock for the skip mechanism.\n * - `graph` (the PUBLIC field) — abstraction runs on the FULL graph FIRST\n * (`abstractGraph(fullGraph, abstraction)`), and the FILTERED graph is reconciled onto that\n * result SECOND (`reconcileGraph`). This order is load-bearing, not incidental: reversing\n * it would let a filter-ghosted element (zeroed to look unused) be mistaken by\n * `abstractGraph`'s own least-frequent heuristic for a genuinely rare one and hidden by\n * abstraction instead of merely dimmed by the filter. So a reader always sees the SAME\n * node set regardless of which intents are active — filtering re-inks elements as\n * `\"excluded\"` (via `selectionStates`), it never shrinks the rendered graph. Abstraction is\n * still the only thing that removes a node from the render (RM-050's \"sliders never change\n * statistics\" property, now paired with \"intents never change the node set either\").\n * - `variants` stays sourced from the FILTERED log alone (unlike `graph`, it narrows rather\n * than ghosts — a variant list has no per-row \"excluded but still there\" concept to draw).\n * - `kpis`/`rework` — derived from `filteredLog` directly (case count, event count, variant\n * count, `durationStats` over each case's own throughput time, `detectRework`), NOT from the\n * abstracted/reconciled `graph` and NOT from either `useLogDiscovery` instance (#347) — all\n * five figures are synchronous derivations of `filteredLog`, so they always agree with one\n * another and never lag behind a still-in-flight discovery. Abstraction and filtering both\n * change what is DRAWN, never what the KPI strip reports for the cases actually in scope.\n *\n * ## Race safety\n *\n * Each of the two discoveries above owns its OWN request-id ref — a worker request in flight\n * for the full log and one in flight for the filtered log are superseded independently the\n * moment their respective input changes again, so a slow full-log import can never clobber a\n * fresher filtered-log result (or vice versa).\n */\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n abstractGraph,\n type AbstractedGraph,\n type AbstractionOptions,\n} from \"../core/abstract-graph\";\nimport { asNormalizedLog, type NormalizedLog } from \"../core/event-log\";\nimport { detectRework, type ReworkStats } from \"../core/detect-rework\";\nimport { discoverGraph } from \"../core/discover-graph\";\nimport { durationStats } from \"../core/duration-stats\";\nimport { extractVariants, variantKey } from \"../core/extract-variants\";\nimport type { FilterSpec } from \"../core/filter-log\";\nimport { filterLog } from \"../core/filter-log\";\nimport { reconcileGraph } from \"../core\";\nimport type { EventLog, FrequencyMode, PerformanceAgg, ProcessGraph, Variant } from \"../core/types\";\nimport {\n createProcessWorker,\n type CreateProcessWorkerOptions,\n} from \"../core/worker/create-process-worker\";\nimport type { MetricLayer } from \"../metric-layer-switch\";\nimport type {\n ProcessFilterIntent,\n ProcessSelection,\n ProcessSelectionStates,\n} from \"../process-map/map-model\";\n\n/** What a node or an edge is painted with — the same domain `ProcessMap`'s metric reads. */\nexport type ProcessExplorerMetric = FrequencyMode | PerformanceAgg;\n\n/** The two metric slots a process explorer coordinates. */\nexport interface ProcessExplorerMetricSpec {\n node: ProcessExplorerMetric;\n edge: ProcessExplorerMetric;\n}\n\n/**\n * Every intent this hook's `applyIntent` accepts — wider than `ProcessMap`'s own menu.\n *\n * `ProcessFilterIntent` (RM-051) is the four kinds `ProcessMap`'s filter-intent menu\n * offers (`with`/`without`/`startsWith`/`endsWith`) and stays scoped to exactly that menu.\n * This hook's `FilterIntent` widens it with `{ kind: \"variant\" }` (RM-052 round 2, #227) so\n * a variant-explorer view can drive the SAME `applyIntent`/`intents` pair to filter by a\n * variant selection — a case a click on the process map can never produce, but a click on a\n * variant row can. The widening is local to this hook's own type alias; it does not touch\n * `ProcessFilterIntent` or `ProcessMap`'s menu, which still only ever emits the original four.\n */\nexport type FilterIntent = ProcessFilterIntent | Extract<FilterSpec, { kind: \"variant\" }>;\n\n/** Options for {@link useProcessExplorer}. */\nexport interface ProcessExplorerOptions {\n /** Initial abstraction sliders. Default: `{ activities: 1, paths: 1 }` — the identity. */\n abstraction?: Partial<AbstractionOptions>;\n /** Initial metric choice. Default: `{ node: \"absolute\", edge: \"absolute\" }`. */\n metric?: Partial<ProcessExplorerMetricSpec>;\n /** Initial metric layer — feed straight into `MetricLayerSwitch`'s `layer` prop. Default `\"frequency\"`. */\n layer?: MetricLayer;\n /**\n * Event count above which discovery and variant extraction move to a worker (RM-050's\n * own stated figure). Default `50_000`. Lower it in a test that wants to exercise the\n * worker path without a 50k-row fixture.\n */\n workerThreshold?: number;\n /**\n * Forwarded verbatim to `createProcessWorker` — override `forceInline`/`createWorker`\n * for a test or a host with its own worker construction. The handle is created lazily,\n * on the first request that crosses `workerThreshold`, so passing this costs nothing in\n * a session that never does.\n */\n worker?: CreateProcessWorkerOptions;\n}\n\n/** What {@link useProcessExplorer} returns. See the module docblock for the recompute rules. */\nexport interface UseProcessExplorerResult {\n /** The graph to render — already abstracted. Superset of `ProcessGraph`; see `hidden`. */\n graph: AbstractedGraph;\n /** Variants of the FILTERED (not abstracted — variants have no node/edge concept) log. */\n variants: Variant[];\n abstraction: AbstractionOptions;\n setAbstraction(next: Partial<AbstractionOptions>): void;\n metric: ProcessExplorerMetricSpec;\n setMetric(next: Partial<ProcessExplorerMetricSpec>): void;\n /** The single explicit selection — `ProcessMap`'s own `selection` prop shape. */\n selection: ProcessSelection | null;\n /** Pass straight through as `ProcessMap`'s `onSelect`. */\n onSelect(next: ProcessSelection | null): void;\n applyIntent(intent: FilterIntent): void;\n clearIntent(index: number): void;\n intents: FilterIntent[];\n filteredLog: EventLog;\n /**\n * Per-element states the active filter contributes — pass straight into `ProcessMap`'s\n * `selectionStates` prop (RM-052 round 2, #227, Invariant F). Every activity/transition an\n * intent excluded is marked `\"excluded\"` here; nothing is ever removed from `graph` itself.\n * `variants` (RM-052 round 3, #227, G2) marks every id named by an active\n * `{ kind: \"variant\" }` intent `\"selected\"` — read by `VariantExplorer` (RM-054), not by\n * `ProcessMap`, which has no variant nodes.\n */\n selectionStates: ProcessSelectionStates;\n /** Activities/paths abstraction is currently hiding — sourced from `abstractGraph`'s own `hidden` field. */\n hiddenCounts: { activities: number; paths: number };\n /**\n * Activities/transitions the active FILTER excluded — rendered, dimmed, never removed.\n * Disjoint from `hiddenCounts` by construction: `hiddenCounts` is what abstraction removed\n * from the render entirely, `excludedCounts` is what the filter re-inked but kept drawn.\n */\n excludedCounts: { activities: number; paths: number };\n /** The active metric layer — feed straight into `MetricLayerSwitch`'s `layer` prop. */\n layer: MetricLayer;\n /** Pass straight through as `MetricLayerSwitch`'s `onLayerChange` — a plain setter; the\n * frequency/performance metric coercion lives in `MetricLayerSwitch` itself, not here. */\n setLayer(next: MetricLayer): void;\n kpis: {\n cases: number;\n events: number;\n variants: number;\n /** Median case throughput time, in milliseconds. */\n medianThroughput: number;\n /** Fraction of cases carrying at least one repeated activity. */\n reworkRate: number;\n };\n /** Full rework tallies — feed straight into `ProcessMap`'s `rework` prop. */\n rework: ReworkStats;\n /**\n * `true` while a discovery/variant request for the CURRENT `filteredLog` is running off\n * a worker. Always `false` when `filteredLog` stays at or under `workerThreshold` — the\n * synchronous path has no gap to express. See loading-states.md: this is `loading`, not\n * `isStreaming` — a settled recomputation, not token-by-token output.\n */\n loading: boolean;\n}\n\nconst DEFAULT_ABSTRACTION: AbstractionOptions = {\n activities: 1,\n paths: 1,\n invert: false,\n keepConnected: true,\n};\n\nconst DEFAULT_METRIC: ProcessExplorerMetricSpec = { node: \"absolute\", edge: \"absolute\" };\n\n/** RM-050's own stated figure for when discovery moves off-thread. */\nconst DEFAULT_WORKER_THRESHOLD = 50_000;\n\nconst EMPTY_GRAPH: ProcessGraph = {\n activities: [],\n transitions: [],\n startActivities: {},\n endActivities: {},\n totals: { cases: 0, events: 0, variants: 0 },\n};\n\ninterface DiscoveryResult {\n graph: ProcessGraph;\n variants: Variant[];\n}\n\nfunction discoverInline(log: EventLog): DiscoveryResult {\n return { graph: discoverGraph(log), variants: extractVariants(log) };\n}\n\n/**\n * The KPI strip's own variant COUNT — the number of distinct activity sequences in\n * `normalized`, without paying for `extractVariants`' full pipeline (a second\n * normalization pass, sorting groups by frequency, hashing a `variantId` per group, and a\n * `durationStats` call per group) that only a variant EXPLORER needs the shape of (PR #413\n * review, P1). `kpis.variants` is a synchronous derivation of `filteredLog` alone, like\n * every other KPI figure (see the module docblock), so above `workerThreshold` it must\n * never re-run the exact extraction `handle.variants()` already dispatches off-thread —\n * that was the main-thread block the worker path exists to avoid. `normalized` is the same\n * value `kpis` already computed for `cases`/`events`/`medianThroughput`, so this adds no\n * second normalization pass either.\n */\nfunction countDistinctVariants(normalized: NormalizedLog): number {\n const keys = new Set<string>();\n for (const kase of normalized.cases) {\n keys.add(variantKey(kase.events.map((event) => event.activity)));\n }\n return keys.size;\n}\n\ninterface DiscoveryState {\n result: DiscoveryResult;\n loading: boolean;\n settled: boolean;\n}\n\n/**\n * One log's own discovery/variants — sync inline below `workerThreshold`, off-thread above\n * it, its OWN request-id ref so it can be superseded independently of any other log this\n * hook is also discovering (RM-052 round 2, #227 — the full log and the filtered log each\n * get one of these; see the module docblock's \"Race safety\").\n *\n * `targetLog === null` means \"skip — a sibling {@link useLogDiscovery} instance already\n * covers this role\" (RM-052 round 3, #227, G1). It exists so the full-log and\n * filtered-log derivations can share one discovery when `filteredLog === log` (no intent\n * active) without calling this hook conditionally, which the Rules of Hooks forbid. A\n * skipped instance runs no memo and posts no request, and its `result` stays the\n * `EMPTY_GRAPH` placeholder for as long as it is skipped — but the caller must NOT read\n * that placeholder as real data the moment the skip ends. `settled` (RM-052 round 4, #227,\n * H1) is `false` until this instance has produced a result of its own — synchronously, in\n * the same render, for a log at or under `workerThreshold`; only once the worker request\n * resolves, above it — so a caller that keeps reusing the sibling discovery while\n * `!settled` never shows this instance's empty placeholder as if it were a settled answer.\n *\n * `settled` means settled **for the log this instance is currently targeting**, never \"has\n * ever produced a result\" (#347). The async result is stored alongside the exact log it was\n * computed for; the moment `targetLog` moves on — to a different log OR to `null` (skip) —\n * a stored result for the log left behind stops counting as `settled` even though the state\n * update that produced it already landed. And `requestIdRef` is bumped on EVERY effect run,\n * skip included: a request that was in flight when this instance transitioned into skip is\n * thereby superseded on the spot, so its response — however late it lands — can never be\n * stored. Without both halves, a filter cleared while its worker request is still in flight\n * can leave that filter's stale, ghosted result sitting in `asyncState` where a later,\n * unrelated filter's own still-unsettled round-trip would read it back out as if it were a\n * real (if outdated) answer for the WRONG log — see `use-process-explorer.test.ts`'s\n * \"settled means settled for the CURRENT target\" lock.\n */\nfunction useLogDiscovery(\n targetLog: EventLog | null,\n workerThreshold: number,\n getHandle: () => ReturnType<typeof createProcessWorker>,\n): DiscoveryState {\n const useWorkerPath = targetLog !== null && targetLog.events.length > workerThreshold;\n\n // Synchronous path: computed directly during render, so a caller never observes a\n // `loading` gap for a log that never crosses the threshold.\n const syncResult = useMemo<DiscoveryResult | null>(\n () => (targetLog === null || useWorkerPath ? null : discoverInline(targetLog)),\n [targetLog, useWorkerPath],\n );\n\n // Tied to the exact `EventLog` it was computed for (#347) — never read back for a\n // DIFFERENT `targetLog`, whether that is a new log or `null` (skip). Reference equality\n // is enough: `targetLog` only ever changes when the caller hands this instance a new\n // memoized reference (a fresh `filterLog(...)` result, or `log` itself).\n const [asyncState, setAsyncState] = useState<{ log: EventLog; result: DiscoveryResult } | null>(\n null,\n );\n const [loading, setLoading] = useState(false);\n const requestIdRef = useRef(0);\n\n useEffect(() => {\n // Bumped on EVERY run, skip included (#347): a request already in flight when this\n // instance transitions into skip (or moves on to a different `targetLog`) is thereby\n // superseded on the spot, so its response — however late it lands — fails the\n // `requestIdRef.current !== requestId` check below and is never stored.\n const requestId = (requestIdRef.current += 1);\n if (targetLog === null || !useWorkerPath) {\n // Skipped, or superseded by (or never needed) the async path — never leave a stale\n // `true` behind from a request that crossed the threshold before this one didn't,\n // or from a filtered request the caller stopped needing when the filter cleared.\n setLoading(false);\n return;\n }\n setLoading(true);\n const handle = getHandle();\n Promise.all([handle.discover(targetLog), handle.variants(targetLog)])\n .then(([graph, variants]) => {\n if (requestIdRef.current !== requestId) return; // a later request already answered\n setAsyncState({ log: targetLog, result: { graph, variants } });\n setLoading(false);\n })\n .catch(() => {\n if (requestIdRef.current !== requestId) return;\n // Degrade to the inline computation rather than getting stuck loading forever —\n // `createProcessWorker` already degrades internally; this catch is the belt for\n // an error the handle itself could not absorb (e.g. a `variants` call after a\n // `terminate()` this hook did not initiate).\n setAsyncState({ log: targetLog, result: discoverInline(targetLog) });\n setLoading(false);\n });\n // `getHandle` is intentionally excluded — it is a stable ref-backed accessor, not\n // reactive state; including it would re-run this effect on every render.\n }, [targetLog, useWorkerPath]);\n\n // A result stored for a log this instance is no longer targeting reads as absent, not as\n // a stale answer (#347) — this is what makes `settled` mean \"settled for the CURRENT\n // target\" rather than \"has ever produced a result\".\n const asyncResult =\n asyncState !== null && asyncState.log === targetLog ? asyncState.result : null;\n\n // A worker-path target this instance has no async result FOR YET is loading, whether or\n // not the passive effect above has had a chance to run (PR #413 review, P2): `targetLog`\n // changes synchronously in render, but the `loading` STATE variable stays at whatever the\n // PREVIOUS target left it — often `false`, once settled — until the effect's\n // `setLoading(true)` commits. Without this, the render that first carries a NEW\n // above-threshold target reports `loading: false` alongside the `EMPTY_GRAPH` placeholder\n // (`asyncResult` above is `null` because `asyncState` still names the old target), which a\n // consumer reads as a settled, genuinely empty answer for one paint. Deriving the extra bit\n // from `asyncState`/`targetLog` themselves needs no effect round trip, so this render\n // already reports `loading: true`. Always `false` while skipped (`useWorkerPath` requires\n // `targetLog !== null`) and a no-op once `asyncState` catches up to `targetLog`.\n const targetUnsettled = useWorkerPath && (asyncState === null || asyncState.log !== targetLog);\n\n return {\n result: syncResult ?? asyncResult ?? { graph: EMPTY_GRAPH, variants: [] },\n loading: loading || targetUnsettled,\n settled: syncResult !== null || asyncResult !== null,\n };\n}\n\n/**\n * Coordinate abstraction, metric choice, selection and filter intents over one event log\n * into everything `ProcessMap` / `AbstractionControls` / `MetricLayerSwitch` /\n * `ProcessKpiStrip` need. See the module docblock.\n */\nexport function useProcessExplorer(\n log: EventLog,\n opts: ProcessExplorerOptions = {},\n): UseProcessExplorerResult {\n const workerThreshold = opts.workerThreshold ?? DEFAULT_WORKER_THRESHOLD;\n\n // The worker options a caller passes are read once per request, never used to decide\n // whether to re-create the handle — the handle is a long-lived resource for the life of\n // this hook, matching `createProcessWorker`'s own \"construct lazily, reuse\" contract.\n // Both discoveries below share this one handle — `createProcessWorker`'s handle answers\n // concurrent requests independently, so the full-log and filtered-log discoveries never\n // block one another.\n const workerOptionsRef = useRef(opts.worker);\n workerOptionsRef.current = opts.worker;\n const handleRef = useRef<ReturnType<typeof createProcessWorker> | null>(null);\n function getHandle() {\n if (handleRef.current === null) {\n handleRef.current = createProcessWorker(workerOptionsRef.current);\n }\n return handleRef.current;\n }\n useEffect(\n () => () => {\n handleRef.current?.terminate();\n },\n [],\n );\n\n const [abstraction, setAbstractionState] = useState<AbstractionOptions>(() => ({\n ...DEFAULT_ABSTRACTION,\n ...opts.abstraction,\n }));\n const [metric, setMetricState] = useState<ProcessExplorerMetricSpec>(() => ({\n ...DEFAULT_METRIC,\n ...opts.metric,\n }));\n const [layer, setLayerState] = useState<MetricLayer>(opts.layer ?? \"frequency\");\n const [selection, setSelection] = useState<ProcessSelection | null>(null);\n const [intents, setIntents] = useState<FilterIntent[]>([]);\n\n const setAbstraction = useCallback((next: Partial<AbstractionOptions>) => {\n setAbstractionState((prev) => ({ ...prev, ...next }));\n }, []);\n const setMetric = useCallback((next: Partial<ProcessExplorerMetricSpec>) => {\n setMetricState((prev) => ({ ...prev, ...next }));\n }, []);\n // A plain setter — the frequency/performance metric coercion that goes with a layer\n // switch lives in `MetricLayerSwitch` itself (it already calls `onMetricChange` before\n // `onLayerChange`), not here.\n const setLayer = useCallback((next: MetricLayer) => setLayerState(next), []);\n const onSelect = useCallback((next: ProcessSelection | null) => setSelection(next), []);\n const applyIntent = useCallback((intent: FilterIntent) => {\n setIntents((prev) => [...prev, intent]);\n }, []);\n const clearIntent = useCallback((index: number) => {\n setIntents((prev) => prev.filter((_, i) => i !== index));\n }, []);\n\n const filteredLog = useMemo(\n () => (intents.length === 0 ? log : filterLog(log, intents)),\n [log, intents],\n );\n\n // Two independent discoveries — see the module docblock. `variants`, `kpis` and `rework`\n // read the FILTERED one; `graph` reads BOTH, full first through abstraction, then\n // reconciled against filtered (Invariant F: filtering re-inks, never removes).\n //\n // With no intent active, `filteredLog === log` (see `filteredLog` above), and the\n // filtered role REUSES the full discovery rather than recomputing it from scratch —\n // decision §1.4 step 3 / §4's whole cost argument rests on this: \"the pipeline reuses\n // `fullRaw` for both roles and runs exactly one discovery — identical to today\" (RM-052\n // round 3, #227, G1). `useLogDiscovery` cannot be called conditionally (Rules of\n // Hooks), so the second instance is always called, but is told to SKIP (`null`) exactly\n // when its sibling already covers the same log; its own request-id ref never fires in\n // that state, so the two derivations still supersede independently the moment the logs\n // genuinely diverge again.\n const sameLog = filteredLog === log;\n const fullDiscovery = useLogDiscovery(log, workerThreshold, getHandle);\n const filteredOwnDiscovery = useLogDiscovery(\n sameLog ? null : filteredLog,\n workerThreshold,\n getHandle,\n );\n // Keep reading the full discovery until the filtered instance has settled a result of\n // its OWN — not just until the skip ends (RM-052 round 4, #227, H1). The moment the\n // first intent makes `filteredLog !== log`, `filteredOwnDiscovery` starts running but\n // has not resolved yet on a worker-path log; reading it immediately would paint its\n // still-`EMPTY_GRAPH` placeholder (all-ghosted, all-zero) for the whole round-trip.\n const filteredDiscovery =\n sameLog || !filteredOwnDiscovery.settled ? fullDiscovery : filteredOwnDiscovery;\n // `loading` ORs the two RAW instances, not the substituted `filteredDiscovery` above —\n // reading `filteredDiscovery.loading` here would silently drop the real in-flight signal\n // during exactly the window this fix targets: while `filteredOwnDiscovery` is unsettled,\n // `filteredDiscovery` reads as `fullDiscovery` (already resolved, `loading: false`), so\n // ORing its `.loading` would report `false` even though `filteredOwnDiscovery.loading` is\n // genuinely `true`. `filteredOwnDiscovery.loading` is always `false` while skipped\n // (`sameLog`), so this is a no-op change there.\n const loading = fullDiscovery.loading || filteredOwnDiscovery.loading;\n\n const presented = useMemo(\n () => abstractGraph(fullDiscovery.result.graph, abstraction),\n [fullDiscovery.result.graph, abstraction],\n );\n\n const reconciled = useMemo(\n () => reconcileGraph(presented, filteredDiscovery.result.graph),\n [presented, filteredDiscovery.result.graph],\n );\n\n const graph = reconciled.graph;\n\n const selectionStates = useMemo<ProcessSelectionStates>(\n () => ({\n activities: Object.fromEntries(\n reconciled.excludedActivities.map((id) => [id, \"excluded\" as const]),\n ),\n transitions: Object.fromEntries(\n reconciled.excludedTransitions.map((key) => [key, \"excluded\" as const]),\n ),\n // Decision §1.4 step 5 (RM-052 round 3, #227, G2): there is no click channel for a\n // variant, so `\"selected\"` here is intent-derived — every id named by an active\n // `{ kind: \"variant\" }` intent, read by `VariantExplorer` (RM-054), not by\n // `ProcessMap`. This namespace was declared on `ProcessSelectionStates` from round 2\n // onward and never populated until this fix.\n variants: Object.fromEntries(\n intents\n .flatMap((intent) => (intent.kind === \"variant\" ? intent.ids : []))\n .map((id) => [id, \"selected\" as const]),\n ),\n }),\n [reconciled, intents],\n );\n\n const excludedCounts = useMemo(\n () => ({\n activities: reconciled.excludedActivities.length,\n paths: reconciled.excludedTransitions.length,\n }),\n [reconciled],\n );\n\n const rework = useMemo(() => detectRework(filteredLog), [filteredLog]);\n\n // `variants` used to read `filteredDiscovery.result.variants.length` — the substituted,\n // possibly-still-settling discovery — so it could disagree with `cases`/`events` for the\n // whole window a filtered discovery was in flight (#347). It is now, like the other four\n // figures, a synchronous derivation of `filteredLog` alone: all three KPIs always agree.\n const kpis = useMemo(() => {\n const normalized = asNormalizedLog(filteredLog);\n const medianThroughput = durationStats(normalized.cases.map((kase) => kase.duration)).median;\n return {\n cases: normalized.totals.cases,\n events: normalized.totals.events,\n variants: countDistinctVariants(normalized),\n medianThroughput,\n reworkRate: rework.caseReworkRate,\n };\n }, [filteredLog, rework]);\n\n return {\n graph,\n variants: filteredDiscovery.result.variants,\n abstraction,\n setAbstraction,\n metric,\n setMetric,\n layer,\n setLayer,\n selection,\n onSelect,\n applyIntent,\n clearIntent,\n intents,\n filteredLog,\n selectionStates,\n hiddenCounts: graph.hidden,\n excludedCounts,\n kpis,\n rework,\n loading,\n };\n}\n","/**\n * Case-level log filtering — RM-050.\n *\n * Every filter in a process-mining tool is a CASE predicate, not an event predicate:\n * \"cases that contain Reject Order\", \"cases that took longer than a day\". Removing single\n * events would rewrite traces and invent directly-follows relations that never happened,\n * so a filter here only ever keeps or drops a case whole. That is what makes the result\n * safe to hand straight back to `discoverGraph` / `extractVariants`.\n *\n * Specs AND together — an empty list keeps everything.\n */\nimport { asNormalizedLog, type AnyLog, type NormalizedCase, type NormalizedLog } from \"./event-log\";\nimport { variantId } from \"./extract-variants\";\nimport type { EventLog } from \"./types\";\n\n/** One case-level predicate. {@link filterLog} ANDs a list of them. */\nexport type FilterSpec =\n /** The case contains `activity` at least once. */\n | { kind: \"with\"; activity: string }\n /** The case never contains `activity`. */\n | { kind: \"without\"; activity: string }\n /** The case's FIRST activity is `activity`. */\n | { kind: \"startsWith\"; activity: string }\n /** The case's LAST activity is `activity`. */\n | { kind: \"endsWith\"; activity: string }\n /**\n * `b` follows `a` in the case — directly (adjacent) when `direct`, otherwise eventually\n * (anywhere later in the trace).\n */\n | { kind: \"follower\"; a: string; b: string; direct?: boolean }\n /** An attribute comparison — see {@link caseMatchesFilters} for how `key` is resolved. */\n | { kind: \"attribute\"; key: string; op: \"eq\" | \"ne\" | \"gt\" | \"lt\" | \"in\"; value: unknown }\n /** The case's throughput time, in milliseconds. Both bounds are INCLUSIVE. */\n | { kind: \"duration\"; min?: number; max?: number }\n /** The case follows one of these variants (see `variantId`). */\n | { kind: \"variant\"; ids: string[] }\n /** The case is one of these case ids. */\n | { kind: \"cases\"; ids: string[] };\n\n/** The activity sequence of a normalized case. */\nfunction sequenceOf(kase: NormalizedCase): string[] {\n const sequence = new Array<string>(kase.events.length);\n for (let i = 0; i < kase.events.length; i += 1) {\n sequence[i] = (kase.events[i] as { activity: string }).activity;\n }\n return sequence;\n}\n\n/**\n * Every value the case offers for `key`.\n *\n * Case attributes win: when `caseAttributes` defines the key, that single value IS the\n * case's answer. Otherwise the key is looked for on the events — including `resource`,\n * which is a first-class field rather than an entry in `attributes` — and the case offers\n * every distinct value its events carry. So \"cases handled by the credit service\" is\n * expressible without the caller pre-aggregating anything.\n */\nfunction valuesFor(kase: NormalizedCase, key: string): unknown[] {\n const attributes = kase.attributes;\n if (attributes !== undefined && Object.hasOwn(attributes, key)) return [attributes[key]];\n\n const values: unknown[] = [];\n const seen = new Set<unknown>();\n for (const event of kase.events) {\n const value = key === \"resource\" ? event.resource : event.attributes?.[key];\n if (value === undefined) continue;\n if (seen.has(value)) continue;\n seen.add(value);\n values.push(value);\n }\n return values;\n}\n\n/** `>` / `<` over two numbers or two strings; anything else does not compare. */\nfunction ordered(left: unknown, right: unknown, wantGreater: boolean): boolean {\n if (typeof left === \"number\" && typeof right === \"number\") {\n return wantGreater ? left > right : left < right;\n }\n if (typeof left === \"string\" && typeof right === \"string\") {\n return wantGreater ? left > right : left < right;\n }\n return false;\n}\n\nfunction matchesAttribute(\n kase: NormalizedCase,\n spec: Extract<FilterSpec, { kind: \"attribute\" }>,\n): boolean {\n const values = valuesFor(kase, spec.key);\n switch (spec.op) {\n case \"eq\":\n return values.some((value) => Object.is(value, spec.value));\n // `ne` is the negation of `eq`, not \"some value differs\" — otherwise a case offering\n // two resources would satisfy both `eq` and `ne` against the same value.\n case \"ne\":\n return !values.some((value) => Object.is(value, spec.value));\n case \"gt\":\n return values.some((value) => ordered(value, spec.value, true));\n case \"lt\":\n return values.some((value) => ordered(value, spec.value, false));\n case \"in\": {\n if (!Array.isArray(spec.value)) return false;\n const allowed = spec.value as unknown[];\n return values.some((value) => allowed.some((candidate) => Object.is(value, candidate)));\n }\n default:\n return false;\n }\n}\n\nfunction matchesOne(kase: NormalizedCase, sequence: readonly string[], spec: FilterSpec): boolean {\n switch (spec.kind) {\n case \"with\":\n return sequence.includes(spec.activity);\n case \"without\":\n return !sequence.includes(spec.activity);\n case \"startsWith\":\n return sequence[0] === spec.activity;\n case \"endsWith\":\n return sequence[sequence.length - 1] === spec.activity;\n case \"follower\": {\n if (spec.direct === true) {\n for (let i = 0; i + 1 < sequence.length; i += 1) {\n if (sequence[i] === spec.a && sequence[i + 1] === spec.b) return true;\n }\n return false;\n }\n const first = sequence.indexOf(spec.a);\n if (first < 0) return false;\n return sequence.indexOf(spec.b, first + 1) >= 0;\n }\n case \"attribute\":\n return matchesAttribute(kase, spec);\n case \"duration\": {\n if (spec.min === undefined && spec.max === undefined) return true;\n // An unmeasurable case (every timestamp unparseable) cannot satisfy a bound.\n if (!Number.isFinite(kase.duration)) return false;\n if (spec.min !== undefined && kase.duration < spec.min) return false;\n if (spec.max !== undefined && kase.duration > spec.max) return false;\n return true;\n }\n case \"variant\":\n return spec.ids.includes(variantId(sequence));\n case \"cases\":\n return spec.ids.includes(kase.caseId);\n default:\n return true;\n }\n}\n\n/**\n * Does `kase` satisfy every spec?\n *\n * Exported because a case table and a variant list want to HIGHLIGHT what a filter\n * selects as often as they want to remove what it does not.\n */\nexport function caseMatchesFilters(kase: NormalizedCase, specs: readonly FilterSpec[]): boolean {\n if (specs.length === 0) return true;\n const sequence = sequenceOf(kase);\n for (const spec of specs) {\n if (!matchesOne(kase, sequence, spec)) return false;\n }\n return true;\n}\n\n/**\n * Filter a NORMALIZED log — the composable form.\n *\n * This is the one to reach for in a pipeline: it accepts either shape, normalizes at most\n * once, and returns the normalized result, so\n * `discoverGraph(filterNormalizedLog(normalized, specs))` re-parses nothing. `totals` are\n * recomputed over the surviving cases; case order is preserved.\n */\nexport function filterNormalizedLog(log: AnyLog, specs: readonly FilterSpec[]): NormalizedLog {\n const normalized = asNormalizedLog(log);\n if (specs.length === 0) return normalized;\n\n const cases: NormalizedCase[] = [];\n let events = 0;\n for (const kase of normalized.cases) {\n if (!caseMatchesFilters(kase, specs)) continue;\n cases.push(kase);\n events += kase.events.length;\n }\n return { cases, totals: { cases: cases.length, events } };\n}\n\n/**\n * Filter a raw {@link EventLog}, answering a raw {@link EventLog}.\n *\n * Every ROW of a surviving case is kept, in input order, including rows normalization\n * itself discards (an unpaired lifecycle half, say) — so the answer round-trips: it can be\n * re-exported, handed to a different adapter, or re-normalized with different options.\n * `caseAttributes` is narrowed to the surviving cases.\n *\n * Use {@link filterNormalizedLog} instead when the next step is another `/core` derivation;\n * this form necessarily makes the caller normalize again.\n */\nexport function filterLog(log: EventLog, specs: readonly FilterSpec[]): EventLog {\n if (specs.length === 0) return log;\n const kept = new Set<string>();\n for (const kase of filterNormalizedLog(log, specs).cases) kept.add(kase.caseId);\n\n const filtered: EventLog = { events: log.events.filter((row) => kept.has(row?.caseId)) };\n if (log.caseAttributes !== undefined) {\n const caseAttributes: Record<string, Record<string, unknown>> = {};\n for (const caseId of Object.keys(log.caseAttributes)) {\n if (kept.has(caseId)) {\n caseAttributes[caseId] = log.caseAttributes[caseId] as Record<string, unknown>;\n }\n }\n filtered.caseAttributes = caseAttributes;\n }\n return filtered;\n}\n","/**\n * reconcileGraph — merge a FILTERED graph's statistics onto a PRESENTED graph's element set\n * (RM-052 round 2, issue #227, Invariant F).\n *\n * The maintainer's ruling on filtering: keep every process-map step visible, dim the ones a\n * filter excludes — never remove them. Invariant F makes that ruling mechanical: the set of\n * activities and transitions a process map renders is a function of the log and the\n * abstraction alone, never of the active filter intents. Applying or clearing a filter can\n * only change an element's STATE and the statistics it carries; it can never add or remove a\n * node or an edge.\n *\n * `reconcileGraph` is the pure operation that keeps that promise: given the graph a map is\n * about to PRESENT (`presented` — the full, abstracted graph, computed with no filter\n * applied) and the graph the ACTIVE filter actually produced (`filtered`), it returns a graph\n * with `presented`'s exact element set, where every element also present in `filtered` reads\n * `filtered`'s statistics, and every element `filtered` dropped becomes a \"ghost\" — the same\n * id, at zero. A ghost's statistics are the FILTERED ones (zero), not the full-log ones —\n * painting full-log numbers on an excluded element would make filtering cosmetic.\n *\n * `startActivities`, `endActivities` and `totals` come from `filtered` outright (they are\n * graph-level facts about what actually survived the filter, not per-element stats to\n * reconcile). Extra fields the presented graph carries beyond `ProcessGraph` itself (e.g.\n * `AbstractedGraph.hidden`) pass through untouched — `reconcileGraph` never removes what it\n * did not add.\n *\n * Pipeline ordering is load-bearing: abstraction runs on the FULL graph, BEFORE\n * reconciliation with the filtered graph. Reconciling first (running abstraction on an\n * already-filtered graph) would let the zeroed ghosts get treated as least-frequent and\n * deleted by abstraction's own ranking — reinstating the \"filtering removes elements\"\n * behaviour the maintainer ruled against, by a second route.\n */\nimport { emptyDurationStats } from \"./duration-stats\";\nimport { EDGE_KEY_SEPARATOR } from \"./discover-graph\";\nimport type { ProcessGraph, TransitionStats } from \"./types\";\n\n/** The result of {@link reconcileGraph}. */\nexport interface ReconciledGraph<G extends ProcessGraph> {\n /** `presented`'s exact element set, with `filtered`'s statistics where an element survived. */\n graph: G;\n /** Activity ids `presented` carries that `filtered` dropped — rendered as zeroed ghosts. */\n excludedActivities: string[];\n /** Transition keys (`source` + {@link EDGE_KEY_SEPARATOR} + `target`) `filtered` dropped. */\n excludedTransitions: string[];\n}\n\nfunction transitionKey(transition: Pick<TransitionStats, \"source\" | \"target\">): string {\n return `${transition.source}${EDGE_KEY_SEPARATOR}${transition.target}`;\n}\n\n/**\n * Reconcile a presented graph's element set against what a filter actually produced.\n *\n * `G` is generic over `ProcessGraph` so an already-abstracted graph (`AbstractedGraph`, which\n * adds `hidden`) reconciles without losing its extra field — the return type is `G`, and the\n * implementation spreads `presented` before overwriting only the fields this function owns.\n */\nexport function reconcileGraph<G extends ProcessGraph>(\n presented: G,\n filtered: ProcessGraph,\n): ReconciledGraph<G> {\n const filteredActivities = new Map(\n filtered.activities.map((activity) => [activity.id, activity]),\n );\n const filteredTransitions = new Map(\n filtered.transitions.map((transition) => [transitionKey(transition), transition]),\n );\n\n const excludedActivities: string[] = [];\n const activities = presented.activities.map((activity) => {\n const survivor = filteredActivities.get(activity.id);\n if (survivor) return survivor;\n excludedActivities.push(activity.id);\n return {\n ...activity,\n instances: 0,\n cases: 0,\n isStart: false,\n isEnd: false,\n duration: emptyDurationStats(),\n };\n });\n\n const excludedTransitions: string[] = [];\n const transitions = presented.transitions.map((transition) => {\n const key = transitionKey(transition);\n const survivor = filteredTransitions.get(key);\n if (survivor) return survivor;\n excludedTransitions.push(key);\n return {\n ...transition,\n count: 0,\n caseCount: 0,\n duration: emptyDurationStats(),\n };\n });\n\n const graph: G = {\n ...presented,\n activities,\n transitions,\n startActivities: filtered.startActivities,\n endActivities: filtered.endActivities,\n totals: filtered.totals,\n };\n\n return { graph, excludedActivities, excludedTransitions };\n}\n","/**\n * The worker entry point — RM-050.\n *\n * Two things live here, deliberately in one module: the PURE request handler, and the\n * `message` bootstrap that runs it inside a real worker. `createProcessWorker` imports the\n * handler for its inline path, so the code that answers on the main thread and the code\n * that answers off it are the SAME function — parity is structural, not something a test\n * has to keep true.\n *\n * The bootstrap is guarded by a real worker-scope check, so importing this module from the\n * main thread registers nothing.\n */\nimport { discoverGraph, type DiscoverGraphOptions } from \"../discover-graph\";\nimport type { AnyLog } from \"../event-log\";\nimport { extractVariants } from \"../extract-variants\";\nimport type { ProcessGraph, Variant } from \"../types\";\n\n/** What the main thread asks the worker to compute. Structured-cloneable, by construction. */\nexport type ProcessWorkerRequest =\n | { id: number; kind: \"discover\"; log: AnyLog; options?: DiscoverGraphOptions }\n | { id: number; kind: \"variants\"; log: AnyLog };\n\n/** What comes back. `ok: false` carries a message, never an `Error` (which clones poorly). */\nexport type ProcessWorkerResponse =\n | { id: number; ok: true; kind: \"discover\"; graph: ProcessGraph }\n | { id: number; ok: true; kind: \"variants\"; variants: Variant[] }\n | { id: number; ok: false; error: string };\n\n/**\n * Answer one request.\n *\n * Pure and synchronous: the whole point of the worker is that this is the expensive part,\n * and moving it off the main thread is the only thing the worker adds. Throwing is\n * converted to an `ok: false` response so a bad log cannot silently kill the worker and\n * leave every later request hanging.\n */\nexport function handleProcessRequest(request: ProcessWorkerRequest): ProcessWorkerResponse {\n try {\n if (request.kind === \"discover\") {\n return {\n id: request.id,\n ok: true,\n kind: \"discover\",\n graph: discoverGraph(request.log, request.options ?? {}),\n };\n }\n return { id: request.id, ok: true, kind: \"variants\", variants: extractVariants(request.log) };\n } catch (error) {\n return { id: request.id, ok: false, error: describe(error) };\n }\n}\n\nfunction describe(error: unknown): string {\n if (error instanceof Error) return error.message;\n return String(error);\n}\n\n/**\n * Is this module executing INSIDE a worker?\n *\n * `self instanceof WorkerGlobalScope` is the specified test. A duck-type on `postMessage`\n * would be wrong: `window.postMessage` exists on every page, so the main thread would\n * install a listener that answers its own messages.\n */\nfunction inWorkerScope(): boolean {\n const scope = globalThis as { WorkerGlobalScope?: new () => unknown; self?: unknown };\n const constructor = scope.WorkerGlobalScope;\n if (typeof constructor !== \"function\") return false;\n return scope.self instanceof constructor;\n}\n\nif (inWorkerScope()) {\n const scope = self as unknown as {\n addEventListener: (type: \"message\", listener: (event: MessageEvent) => void) => void;\n postMessage: (message: unknown) => void;\n };\n scope.addEventListener(\"message\", (event: MessageEvent) => {\n scope.postMessage(handleProcessRequest(event.data as ProcessWorkerRequest));\n });\n}\n","/**\n * Off-thread discovery — RM-050.\n *\n * Discovering a graph from a raw log is O(events) with a couple of hash lookups per event;\n * on a 200 000-row import that is long enough to drop frames, and a dropped frame during a\n * file import is exactly the moment an app reads as broken. So the derivation moves to a\n * worker — but ONLY as an optimization: the caller never branches on the environment.\n *\n * **Degrade, never fail.** When there is no `Worker` (a server render, a Node test, an\n * embedded webview), when constructing one throws, or when a live one errors, the same\n * functions run inline on the calling thread and the promise still resolves with the same\n * answer. `handleProcessRequest` is literally shared with the worker entry, so \"the inline\n * path agrees with the worker path\" is a property of the code, not of a test.\n *\n * **Normalize once.** Every method takes an `AnyLog`; hand it an already-normalized log\n * (`asNormalizedLog`) and neither side re-parses. A `NormalizedLog` is plain data and\n * clones across `postMessage` unchanged.\n */\nimport type { DiscoverGraphOptions } from \"../discover-graph\";\nimport type { AnyLog } from \"../event-log\";\nimport type { ProcessGraph, Variant } from \"../types\";\nimport {\n handleProcessRequest,\n type ProcessWorkerRequest,\n type ProcessWorkerResponse,\n} from \"./process-worker\";\n\n/**\n * The slice of the `Worker` interface this module uses.\n *\n * Narrow on purpose: it is the seam a test (or a bundler with its own worker construction)\n * substitutes, and demanding the full DOM `Worker` surface for that would be pointless\n * ceremony. A real `Worker` satisfies it.\n */\nexport interface ProcessWorkerLike {\n postMessage(message: unknown): void;\n terminate(): void;\n addEventListener(\n type: \"message\" | \"error\" | \"messageerror\",\n listener: (event: unknown) => void,\n ): void;\n}\n\n/** Options for {@link createProcessWorker}. */\nexport interface CreateProcessWorkerOptions {\n /** Skip the worker entirely and run inline. Useful for benchmarks and for tests. */\n forceInline?: boolean;\n /**\n * Build the worker yourself.\n *\n * The default construction is\n * `new Worker(new URL(\"./process-worker.ts\", import.meta.url), { type: \"module\" })`.\n * A bundler compiling THIS SOURCE (Vite, webpack) rewrites that literal itself; a\n * consumer of the BUILT package gets `dist/core/process-worker.js`, which\n * `tsup.config.ts` emits as its own pass and points the built bundle at — a bundler\n * rewrites the source it compiles, never a `dist` it merely consumes, so that file has\n * to be real. A host that needs its own URL supplies this instead. Throwing from it is\n * safe: the handle falls back to the inline path.\n */\n createWorker?: () => ProcessWorkerLike;\n}\n\n/** What {@link createProcessWorker} hands back. */\nexport interface ProcessWorkerHandle {\n /** Discover a directly-follows graph. */\n discover(log: AnyLog, options?: DiscoverGraphOptions): Promise<ProcessGraph>;\n /** Group the log's cases into variants. */\n variants(log: AnyLog): Promise<Variant[]>;\n /** Stop the worker. Pending promises reject, and so does every later call. */\n terminate(): void;\n /**\n * `true` when work is currently running on the CALLING thread — either because the\n * environment has no worker, or because one failed and the handle degraded.\n */\n readonly inline: boolean;\n}\n\ninterface Pending {\n request: ProcessWorkerRequest;\n settle: (response: ProcessWorkerResponse) => void;\n}\n\nfunction workerConstructible(): boolean {\n return typeof Worker !== \"undefined\" && typeof URL !== \"undefined\";\n}\n\n/**\n * Create a handle that computes off-thread when it can and inline when it cannot.\n *\n * The worker is created LAZILY, on the first request, so constructing a handle costs\n * nothing in an environment that never uses it.\n */\nexport function createProcessWorker(options: CreateProcessWorkerOptions = {}): ProcessWorkerHandle {\n const construct = options.createWorker;\n let inline = options.forceInline === true || (construct === undefined && !workerConstructible());\n let terminated = false;\n let worker: ProcessWorkerLike | undefined;\n let nextId = 0;\n const pending = new Map<number, Pending>();\n\n /** Give up on the worker and answer everything outstanding on this thread. */\n function degrade(): void {\n inline = true;\n const stale = [...pending.values()];\n pending.clear();\n if (worker !== undefined) {\n const dying = worker;\n worker = undefined;\n try {\n dying.terminate();\n } catch {\n // A worker that cannot be terminated is already gone; nothing to recover.\n }\n }\n for (const entry of stale) entry.settle(handleProcessRequest(entry.request));\n }\n\n function onMessage(event: unknown): void {\n const response = (event as { data?: unknown }).data as ProcessWorkerResponse | undefined;\n if (response === undefined || typeof response.id !== \"number\") return;\n const entry = pending.get(response.id);\n if (entry === undefined) return;\n pending.delete(response.id);\n entry.settle(response);\n }\n\n function ensureWorker(): ProcessWorkerLike | undefined {\n if (inline || terminated) return undefined;\n if (worker !== undefined) return worker;\n try {\n worker =\n construct === undefined\n ? (new Worker(new URL(\"./process-worker.ts\", import.meta.url), {\n type: \"module\",\n }) as unknown as ProcessWorkerLike)\n : construct();\n } catch {\n degrade();\n return undefined;\n }\n worker.addEventListener(\"message\", onMessage);\n worker.addEventListener(\"error\", degrade);\n worker.addEventListener(\"messageerror\", degrade);\n return worker;\n }\n\n function send(build: (id: number) => ProcessWorkerRequest): Promise<ProcessWorkerResponse> {\n if (terminated) return Promise.reject(new Error(\"process worker terminated\"));\n nextId += 1;\n const request = build(nextId);\n const active = ensureWorker();\n if (active === undefined) {\n // Inline, but still a microtask later, so the caller's own frame returns first.\n return Promise.resolve().then(() => handleProcessRequest(request));\n }\n return new Promise<ProcessWorkerResponse>((resolve, reject) => {\n pending.set(request.id, { request, settle: resolve });\n try {\n active.postMessage(request);\n } catch {\n pending.delete(request.id);\n degrade();\n try {\n resolve(handleProcessRequest(request));\n } catch (error) {\n reject(error instanceof Error ? error : new Error(String(error)));\n }\n }\n });\n }\n\n function unwrap<T>(response: ProcessWorkerResponse, read: (ok: ProcessWorkerResponse) => T): T {\n if (!response.ok) throw new Error(response.error);\n return read(response);\n }\n\n return {\n async discover(log, discoverOptions) {\n const response = await send((id) =>\n discoverOptions === undefined\n ? { id, kind: \"discover\", log }\n : { id, kind: \"discover\", log, options: discoverOptions },\n );\n return unwrap(response, (ok) => (ok as { graph: ProcessGraph }).graph);\n },\n async variants(log) {\n const response = await send((id) => ({ id, kind: \"variants\", log }));\n return unwrap(response, (ok) => (ok as { variants: Variant[] }).variants);\n },\n terminate() {\n terminated = true;\n const stale = [...pending.values()];\n pending.clear();\n for (const entry of stale) {\n entry.settle({ id: entry.request.id, ok: false, error: \"process worker terminated\" });\n }\n if (worker !== undefined) {\n const dying = worker;\n worker = undefined;\n try {\n dying.terminate();\n } catch {\n // Already gone.\n }\n }\n },\n get inline(): boolean {\n return inline;\n },\n };\n}\n"],"mappings":";;;AAaO,SAAS,UAAU,GAAW,GAAmB;AACtD,SAAO,IAAI;AACb;AAcO,SAAS,OAAO,QAA6C;AAClE,MAAI,MAAM,OAAO;AACjB,MAAI,MAAM,OAAO;AACjB,MAAI,OAAO;AACX,aAAW,KAAK,QAAQ;AACtB,QAAI,CAAC,OAAO,SAAS,CAAC,EAAG;AACzB,WAAO;AACP,QAAI,IAAI,IAAK,OAAM;AACnB,QAAI,IAAI,IAAK,OAAM;AAAA,EACrB;AACA,SAAO,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC;AAClC;AAUO,SAAS,eAAe,QAA2B,GAAmB;AAC3E,QAAM,IAAI,OAAO;AACjB,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,MAAM,EAAG,QAAO,OAAO,CAAC;AAC5B,QAAM,UAAU,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI;AACxC,QAAM,MAAM,WAAW,IAAI;AAC3B,QAAM,KAAK,KAAK,MAAM,GAAG;AACzB,QAAM,KAAK,KAAK,KAAK,GAAG;AACxB,QAAM,UAAU,OAAO,EAAE;AACzB,MAAI,OAAO,GAAI,QAAO;AACtB,QAAM,UAAU,OAAO,EAAE;AACzB,SAAO,WAAW,UAAU,YAAY,MAAM;AAChD;;;AC9CO,IAAM,gBAAgB;AAGtB,IAAM,uBAAgD,OAAO,OAAO;AAAA,EACzE,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,KAAK;AAAA,EACL,aAAa;AACf,CAAC;AAGM,SAAS,qBAAoC;AAClD,SAAO,EAAE,GAAG,qBAAqB;AACnC;AAcO,SAAS,cAAc,SAA2C;AACvE,QAAM,SAAmB,CAAC;AAC1B,aAAW,KAAK,QAAS,KAAI,OAAO,SAAS,CAAC,EAAG,QAAO,KAAK,CAAC;AAC9D,QAAM,IAAI,OAAO;AACjB,MAAI,MAAM,EAAG,QAAO,mBAAmB;AACvC,SAAO,KAAK,SAAS;AAErB,MAAI,MAAM;AACV,aAAW,KAAK,OAAQ,QAAO;AAE/B,QAAM,OAAO,KAAK,MAAM,IAAI,aAAa;AACzC,QAAM,KAAK;AACX,QAAM,KAAK,IAAI;AACf,MAAI;AACJ,MAAI,KAAK,MAAM,GAAG;AAChB,kBAAc,MAAM;AAAA,EACtB,OAAO;AACL,QAAI,aAAa;AACjB,aAAS,IAAI,IAAI,IAAI,IAAI,KAAK,EAAG,eAAc,OAAO,CAAC;AACvD,kBAAc,cAAc,KAAK;AAAA,EACnC;AAEA,SAAO;AAAA,IACL,KAAK,OAAO,CAAC;AAAA,IACb,KAAK,OAAO,IAAI,CAAC;AAAA,IACjB,MAAM,MAAM;AAAA,IACZ,QAAQ,eAAe,QAAQ,GAAG;AAAA,IAClC,KAAK,eAAe,QAAQ,GAAG;AAAA,IAC/B;AAAA,IACA;AAAA,EACF;AACF;AAGO,IAAM,sBAAsB;AAOnC,SAAS,WAAW,MAA4B;AAC9C,MAAI,IAAI,SAAS;AACjB,SAAO,MAAM;AACX,QAAK,IAAI,eAAgB;AACzB,QAAI,IAAI;AACR,QAAI,KAAK,KAAK,IAAK,MAAM,IAAK,IAAI,CAAC;AACnC,SAAK,IAAI,KAAK,KAAK,IAAK,MAAM,GAAI,IAAI,EAAE;AACxC,aAAS,IAAK,MAAM,QAAS,KAAK;AAAA,EACpC;AACF;AAcO,IAAM,kBAAN,MAAsB;AAAA,EACV;AAAA,EACA;AAAA,EACA,YAAsB,CAAC;AAAA;AAAA,EAEhC,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS,OAAO;AAAA,EAChB,UAAU,OAAO;AAAA,EAEzB,YAAY,OAAO,YAAY,WAAmB,qBAAqB;AACrE,SAAK,WAAW,WAAW,IAAI,WAAW;AAC1C,SAAK,SAAS,WAAW,IAAI;AAAA,EAC/B;AAAA;AAAA,EAGA,IAAI,QAAsB;AACxB,QAAI,CAAC,OAAO,SAAS,MAAM,EAAG;AAC9B,SAAK,SAAS;AACd,QAAI,SAAS,KAAK,OAAQ,MAAK,SAAS;AACxC,QAAI,SAAS,KAAK,QAAS,MAAK,UAAU;AAC1C,QAAI,KAAK,UAAU,SAAS,KAAK,UAAU;AACzC,WAAK,UAAU,KAAK,MAAM;AAAA,IAC5B,OAAO;AACL,YAAM,IAAI,KAAK,MAAM,KAAK,OAAO,KAAK,KAAK,OAAO,EAAE;AACpD,UAAI,IAAI,KAAK,SAAU,MAAK,UAAU,CAAC,IAAI;AAAA,IAC7C;AACA,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA,EAGA,IAAI,OAAe;AACjB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAuB;AACrB,QAAI,KAAK,SAAS,EAAG,QAAO,mBAAmB;AAC/C,UAAM,gBAAgB,cAAc,KAAK,SAAS;AAClD,WAAO;AAAA,MACL,GAAG;AAAA,MACH,KAAK,KAAK;AAAA,MACV,KAAK,KAAK;AAAA,MACV,KAAK,KAAK;AAAA,MACV,MAAM,KAAK,QAAQ,KAAK;AAAA,IAC1B;AAAA,EACF;AACF;;;ACpGO,SAAS,gBAAgB,KAAmC;AACjE,SAAO,MAAM,QAAS,IAAsB,KAAK;AACnD;AAGO,SAAS,gBAAgB,KAA4B;AAC1D,SAAO,gBAAgB,GAAG,IAAI,MAAM,aAAa,GAAG;AACtD;AASO,SAAS,UAAU,OAA0D;AAClF,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO,OAAO;AACzD,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,SAAS,KAAK,IAAI,QAAQ,OAAO;AAC9E,MAAI,iBAAiB,KAAM,QAAO,MAAM,QAAQ;AAChD,QAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,MAAI,CAAC,OAAO,MAAM,MAAM,EAAG,QAAO;AAGlC,QAAM,WAAW,OAAO,KAAK;AAC7B,SAAO,OAAO,SAAS,QAAQ,KAAK,MAAM,KAAK,MAAM,KAAK,WAAW,OAAO;AAC9E;AAGA,SAAS,YAAY,GAAmB,GAA2B;AACjE,MAAI,OAAO,MAAM,EAAE,EAAE,KAAK,OAAO,MAAM,EAAE,EAAE,EAAG,QAAO;AACrD,SAAO,EAAE,KAAK,EAAE;AAClB;AAiCO,SAAS,aAAa,KAA8B;AACzD,QAAM,SAAS,oBAAI,IAAyB;AAC5C,QAAM,QAAkB,CAAC;AAEzB,aAAW,OAAO,IAAI,QAAQ;AAC5B,QAAI,CAAC,OAAO,OAAO,IAAI,WAAW,YAAY,IAAI,WAAW,GAAI;AACjE,QAAI,OAAO,IAAI,aAAa,YAAY,IAAI,aAAa,GAAI;AAC7D,QAAI,SAAS,OAAO,IAAI,IAAI,MAAM;AAClC,QAAI,WAAW,QAAW;AACxB,eAAS,CAAC;AACV,aAAO,IAAI,IAAI,QAAQ,MAAM;AAC7B,YAAM,KAAK,IAAI,MAAM;AAAA,IACvB;AACA,WAAO,KAAK,EAAE,IAAI,UAAU,IAAI,SAAS,GAAG,IAAI,CAAC;AAAA,EACnD;AAEA,QAAM,QAA0B,CAAC;AACjC,MAAI,SAAS;AAEb,aAAW,UAAU,OAAO;AAC1B,UAAM,SAAS,OAAO,IAAI,MAAM;AAEhC,WAAO,KAAK,WAAW;AAEvB,UAAM,YAA+B,CAAC;AAEtC,UAAM,OAAO,oBAAI,IAAsB;AAEvC,eAAW,EAAE,IAAI,IAAI,KAAK,QAAQ;AAChC,UAAI,IAAI,cAAc,SAAS;AAC7B,cAAMA,SAAQ,UAAU;AACxB,kBAAU,KAAK,aAAa,KAAK,IAAI,IAAI,IAAI,CAAC;AAC9C,cAAM,QAAQ,KAAK,IAAI,IAAI,QAAQ;AACnC,YAAI,UAAU,OAAW,MAAK,IAAI,IAAI,UAAU,CAACA,MAAK,CAAC;AAAA,YAClD,OAAM,KAAKA,MAAK;AACrB;AAAA,MACF;AAEA,UAAI,IAAI,cAAc,YAAY;AAChC,cAAM,QAAQ,KAAK,IAAI,IAAI,QAAQ;AACnC,cAAMA,SAAQ,OAAO,MAAM;AAC3B,YAAIA,WAAU,QAAW;AACvB,gBAAM,WAAW,UAAUA,MAAK;AAChC,mBAAS,MAAM;AACf,mBAAS,WAAW,WAAW,SAAS,OAAO,EAAE;AACjD,mBAAS,SAAS;AAClB,cAAI,SAAS,aAAa,UAAa,IAAI,aAAa,QAAW;AACjE,qBAAS,WAAW,IAAI;AAAA,UAC1B;AACA,cAAI,SAAS,eAAe,UAAa,IAAI,eAAe,QAAW;AACrE,qBAAS,aAAa,IAAI;AAAA,UAC5B;AACA;AAAA,QACF;AAAA,MACF;AAEA,YAAM,MAAM;AACZ,YAAM,gBAAgB,UAAU,IAAI,cAAc;AAClD,YAAM,QAAQ,OAAO,MAAM,aAAa,IAAI,MAAM;AAClD,gBAAU,KAAK,aAAa,KAAK,OAAO,KAAK,KAAK,CAAC;AAAA,IACrD;AAEA,QAAI,UAAU,WAAW,EAAG;AAK5B,cAAU,KAAK,OAAO;AAEtB,QAAI,YAAY,OAAO;AACvB,QAAI,UAAU,OAAO;AACrB,eAAW,YAAY,WAAW;AAChC,UAAI,OAAO,SAAS,SAAS,KAAK,KAAK,SAAS,QAAQ,UAAW,aAAY,SAAS;AACxF,UAAI,OAAO,SAAS,SAAS,GAAG,KAAK,SAAS,MAAM,QAAS,WAAU,SAAS;AAAA,IAClF;AACA,UAAM,YACJ,cAAc,OAAO,qBAAqB,YAAY,OAAO;AAE/D,UAAM,iBAAiC;AAAA,MACrC;AAAA,MACA,QAAQ;AAAA,MACR,OAAO,YAAY,YAAY,OAAO;AAAA,MACtC,KAAK,YAAY,UAAU,OAAO;AAAA,MAClC,UAAU,YAAY,WAAW,WAAW,OAAO,IAAI,OAAO;AAAA,IAChE;AACA,UAAM,iBAAiB,IAAI,iBAAiB,MAAM;AAClD,QAAI,mBAAmB,OAAW,gBAAe,aAAa;AAE9D,UAAM,KAAK,cAAc;AACzB,cAAU,UAAU;AAAA,EACtB;AAEA,SAAO,EAAE,OAAO,QAAQ,EAAE,OAAO,MAAM,QAAQ,OAAO,EAAE;AAC1D;AAEA,SAAS,QAAQ,GAAoB,GAA4B;AAC/D,MAAI,OAAO,MAAM,EAAE,KAAK,KAAK,OAAO,MAAM,EAAE,KAAK,EAAG,QAAO;AAC3D,SAAO,EAAE,QAAQ,EAAE;AACrB;AAEA,SAAS,WAAW,OAAe,KAAqB;AACtD,QAAM,QAAQ,MAAM;AACpB,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO,OAAO;AAC3C,SAAO,QAAQ,IAAI,IAAI;AACzB;AAEA,SAAS,aAAa,KAAe,OAAe,KAAa,QAAkC;AACjG,QAAM,WAA4B;AAAA,IAChC,UAAU,IAAI;AAAA,IACd;AAAA,IACA;AAAA,IACA,UAAU,WAAW,OAAO,GAAG;AAAA,IAC/B;AAAA,EACF;AACA,MAAI,IAAI,aAAa,OAAW,UAAS,WAAW,IAAI;AACxD,MAAI,IAAI,eAAe,OAAW,UAAS,aAAa,IAAI;AAC5D,SAAO;AACT;;;AC5NO,IAAM,wBAAwB;AAG9B,SAAS,WAAW,UAAqC;AAC9D,SAAO,SAAS,KAAK,qBAAqB;AAC5C;AAGA,SAAS,QAAQ,MAAc,OAAuB;AACpD,MAAI,OAAO,UAAU;AACrB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,YAAQ,KAAK,WAAW,CAAC;AACzB,WAAO,KAAK,KAAK,MAAM,QAAU,MAAM;AAAA,EACzC;AACA,SAAO,SAAS;AAClB;AAEA,SAAS,KAAK,OAAuB;AACnC,UAAQ,UAAU,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AACnD;AAWO,SAAS,UAAU,UAAqC;AAC7D,QAAM,MAAM,WAAW,QAAQ;AAC/B,SAAO,IAAI,KAAK,QAAQ,KAAK,UAAU,CAAC,CAAC,GAAG,KAAK,QAAQ,KAAK,UAAU,CAAC,CAAC;AAC5E;AAsBO,SAAS,gBAAgB,KAAwB;AACtD,QAAM,aAAa,gBAAgB,GAAG;AACtC,QAAM,aAAa,WAAW,MAAM;AACpC,MAAI,eAAe,EAAG,QAAO,CAAC;AAE9B,QAAM,SAAS,oBAAI,IAAgC;AACnD,aAAW,QAAQ,WAAW,OAAO;AACnC,UAAM,WAAW,WAAW,IAAI;AAChC,UAAM,MAAM,WAAW,QAAQ;AAC/B,QAAI,QAAQ,OAAO,IAAI,GAAG;AAC1B,QAAI,UAAU,QAAW;AACvB,cAAQ,EAAE,KAAK,UAAU,SAAS,CAAC,GAAG,WAAW,CAAC,EAAE;AACpD,aAAO,IAAI,KAAK,KAAK;AAAA,IACvB;AACA,UAAM,QAAQ,KAAK,KAAK,MAAM;AAC9B,UAAM,UAAU,KAAK,KAAK,QAAQ;AAAA,EACpC;AAEA,QAAM,SAAS,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE;AAAA,IAClC,CAAC,GAAG,MAAM,EAAE,QAAQ,SAAS,EAAE,QAAQ,WAAW,EAAE,MAAM,EAAE,MAAM,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI;AAAA,EAC7F;AAEA,QAAM,QAAQ,oBAAI,IAAY;AAC9B,QAAM,WAAsB,CAAC;AAC7B,MAAI,kBAAkB;AAEtB,aAAW,SAAS,QAAQ;AAC1B,UAAM,QAAQ,MAAM,QAAQ;AAC5B,uBAAmB;AAKnB,QAAI,KAAK,UAAU,MAAM,QAAQ;AACjC,QAAI,MAAM,IAAI,EAAE,GAAG;AACjB,UAAI,SAAS;AACb,aAAO,MAAM,IAAI,GAAG,EAAE,IAAI,MAAM,EAAE,EAAG,WAAU;AAC/C,WAAK,GAAG,EAAE,IAAI,MAAM;AAAA,IACtB;AACA,UAAM,IAAI,EAAE;AAEZ,aAAS,KAAK;AAAA,MACZ;AAAA,MACA,UAAU,MAAM;AAAA,MAChB;AAAA,MACA,OAAO,QAAQ;AAAA,MACf,iBAAiB,kBAAkB;AAAA,MACnC,SAAS,MAAM;AAAA,MACf,UAAU,cAAc,MAAM,SAAS;AAAA,IACzC,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAEA,SAAS,WAAW,MAAgC;AAClD,QAAM,WAAW,IAAI,MAAc,KAAK,OAAO,MAAM;AACrD,WAAS,IAAI,GAAG,IAAI,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC9C,aAAS,CAAC,IAAK,KAAK,OAAO,CAAC,EAA2B;AAAA,EACzD;AACA,SAAO;AACT;;;ACpHO,IAAM,qBAAqB;AA4C3B,SAAS,cAAc,KAAa,UAAgC,CAAC,GAAiB;AAC3F,QAAM,WAAqB,QAAQ,YAAY;AAC/C,QAAM,WAAW,QAAQ,sBAAsB;AAC/C,QAAM,aAAa,gBAAgB,GAAG;AAEtC,QAAM,aAAa,oBAAI,IAAiC;AACxD,QAAM,cAAc,oBAAI,IAAmC;AAC3D,QAAM,kBAAkB,oBAAI,IAAoB;AAChD,QAAM,gBAAgB,oBAAI,IAAoB;AAC9C,QAAM,cAAc,oBAAI,IAAY;AAKpC,MAAI,eAAe;AACnB,QAAM,WAAW,MAAc;AAC7B,oBAAgB;AAChB,YAAQ,aAAa,KAAK,KAAK,cAAc,UAAU,OAAO;AAAA,EAChE;AAEA,MAAI,SAAS;AACb,QAAM,iBAAiB,oBAAI,IAAY;AACvC,QAAM,YAAY,oBAAI,IAAY;AAElC,aAAW,QAAQ,WAAW,OAAO;AACnC,UAAM,QAAQ,KAAK;AACnB,QAAI,MAAM,WAAW,EAAG;AACxB,cAAU,MAAM;AAChB,mBAAe,MAAM;AACrB,cAAU,MAAM;AAEhB,UAAM,WAAW,IAAI,MAAc,MAAM,MAAM;AAE/C,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AACxC,YAAM,QAAQ,MAAM,CAAC;AACrB,YAAM,OAAO,MAAM;AACnB,eAAS,CAAC,IAAI;AAEd,UAAI,WAAW,WAAW,IAAI,IAAI;AAClC,UAAI,aAAa,QAAW;AAC1B,mBAAW,EAAE,WAAW,GAAG,OAAO,GAAG,UAAU,IAAI,gBAAgB,SAAS,GAAG,QAAQ,EAAE;AACzF,mBAAW,IAAI,MAAM,QAAQ;AAAA,MAC/B;AACA,eAAS,aAAa;AACtB,eAAS,SAAS,IAAI,MAAM,QAAQ;AACpC,UAAI,CAAC,eAAe,IAAI,IAAI,GAAG;AAC7B,uBAAe,IAAI,IAAI;AACvB,iBAAS,SAAS;AAAA,MACpB;AAEA,UAAI,MAAM,EAAG;AACb,YAAM,WAAW,MAAM,IAAI,CAAC;AAC5B,YAAM,SAAS,SAAS;AACxB,YAAM,MAAM,GAAG,MAAM,GAAG,kBAAkB,GAAG,IAAI;AACjD,UAAI,OAAO,YAAY,IAAI,GAAG;AAC9B,UAAI,SAAS,QAAW;AACtB,eAAO;AAAA,UACL;AAAA,UACA,QAAQ;AAAA,UACR,OAAO;AAAA,UACP,WAAW;AAAA,UACX,UAAU,IAAI,gBAAgB,SAAS,GAAG,QAAQ;AAAA,QACpD;AACA,oBAAY,IAAI,KAAK,IAAI;AAAA,MAC3B;AACA,WAAK,SAAS;AACd,WAAK,SAAS;AAAA,QACZ,aAAa,qBAAqB,MAAM,QAAQ,SAAS,QAAQ,MAAM,QAAQ,SAAS;AAAA,MAC1F;AACA,UAAI,CAAC,UAAU,IAAI,GAAG,GAAG;AACvB,kBAAU,IAAI,GAAG;AACjB,aAAK,aAAa;AAAA,MACpB;AAAA,IACF;AAEA,UAAM,QAAQ,MAAM,CAAC;AACrB,UAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,oBAAgB,IAAI,MAAM,WAAW,gBAAgB,IAAI,MAAM,QAAQ,KAAK,KAAK,CAAC;AAClF,kBAAc,IAAI,KAAK,WAAW,cAAc,IAAI,KAAK,QAAQ,KAAK,KAAK,CAAC;AAC5E,gBAAY,IAAI,SAAS,KAAK,qBAAqB,CAAC;AAAA,EACtD;AAEA,QAAM,eAAgC,CAAC;AACvC,aAAW,CAAC,IAAI,WAAW,KAAK,YAAY;AAC1C,iBAAa,KAAK;AAAA,MAChB;AAAA,MACA,OAAO;AAAA,MACP,WAAW,YAAY;AAAA,MACvB,OAAO,YAAY;AAAA,MACnB,SAAS,gBAAgB,IAAI,EAAE;AAAA,MAC/B,OAAO,cAAc,IAAI,EAAE;AAAA,MAC3B,UAAU,YAAY,SAAS,MAAM;AAAA,IACvC,CAAC;AAAA,EACH;AAGA,eAAa,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,aAAa,eAAe,EAAE,IAAI,EAAE,EAAE,CAAC;AAEnF,QAAM,iBAAoC,CAAC;AAC3C,aAAW,eAAe,YAAY,OAAO,GAAG;AAC9C,mBAAe,KAAK;AAAA,MAClB,QAAQ,YAAY;AAAA,MACpB,QAAQ,YAAY;AAAA,MACpB,OAAO,YAAY;AAAA,MACnB,WAAW,YAAY;AAAA,MACvB,UAAU,YAAY,SAAS,MAAM;AAAA,MACrC,YAAY,YAAY,WAAW,YAAY;AAAA,MAC/C,YAAY;AAAA,IACd,CAAC;AAAA,EACH;AACA,iBAAe;AAAA,IACb,CAAC,GAAG,MACF,EAAE,QAAQ,EAAE,SAAS,eAAe,EAAE,QAAQ,EAAE,MAAM,KAAK,eAAe,EAAE,QAAQ,EAAE,MAAM;AAAA,EAChG;AAEA,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,iBAAiB,eAAe,eAAe;AAAA,IAC/C,eAAe,eAAe,aAAa;AAAA,IAC3C,QAAQ,EAAE,OAAO,WAAW,MAAM,QAAQ,QAAQ,UAAU,YAAY,KAAK;AAAA,EAC/E;AACF;AAGA,SAAS,eAAe,GAAW,GAAmB;AACpD,SAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI;AAClC;AAGA,SAAS,eAAe,QAAqD;AAC3E,QAAM,MAA8B,CAAC;AACrC,aAAW,OAAO,CAAC,GAAG,OAAO,KAAK,CAAC,EAAE,KAAK,EAAG,KAAI,GAAG,IAAI,OAAO,IAAI,GAAG;AACtE,SAAO;AACT;;;AC1KO,IAAM,mBAA2D,OAAO,OAAO;AAAA,EACpF,IAAI;AAAA,EACJ,GAAG;AAAA,EACH,KAAK;AAAA,EACL,GAAG;AAAA,EACH,GAAG;AACL,CAAC;AAuDM,SAAS,iBAAiB,OAAsB,KAA6B;AAClF,UAAQ,KAAK;AAAA,IACX,KAAK;AACH,aAAO,MAAM;AAAA,IACf,KAAK;AACH,aAAO,MAAM;AAAA,IACf,KAAK;AACH,aAAO,MAAM;AAAA,IACf,KAAK;AACH,aAAO,MAAM;AAAA,IACf,KAAK;AACH,aAAO,MAAM;AAAA,IACf,KAAK;AACH,aAAO,MAAM;AAAA,IACf,KAAK;AAAA,IACL;AACE,aAAO,MAAM;AAAA,EACjB;AACF;;;ACdO,IAAM,8BAA8B,CAAC,QAAQ,WAAW,cAAc,UAAU;AAGhF,IAAM,+BACX,OAAO,OAAO;AAAA,EACZ,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,UAAU;AACZ,CAAC;AAUI,IAAM,qCAET,OAAO,OAAO;AAAA,EAChB,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,UAAU;AACZ,CAAC;AAkCM,IAAM,gBAAgB;AAwItB,SAAS,6BAA6B,MAA4C;AACvF,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL;AACE,aAAO;AAAA,EACX;AACF;AAWO,SAAS,+BAA+B,MAA8C;AAC3F,SAAO,SAAS,oBAAoB,aAAa;AACnD;AAEA,IAAM,mBAAmB,oBAAI,IAAY;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,SAAS,oBAAoB,QAAiD;AACnF,SAAO,iBAAiB,IAAI,MAAM;AACpC;AAEA,IAAM,yBAAmE,OAAO,OAAO;AAAA,EACrF,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,cAAc;AAChB,CAAC;AAED,IAAM,4BAA6E,OAAO,OAAO;AAAA,EAC/F,UAAU;AAAA,EACV,eAAe;AAAA,EACf,UAAU;AAAA,EACV,eAAe;AACjB,CAAC;AAED,IAAM,8BACJ,OAAO,OAAO;AAAA,EACZ,UAAU;AAAA,EACV,eAAe;AAAA,EACf,UAAU;AAAA,EACV,eAAe;AAAA,EACf,qBAAqB;AAAA,EACrB,qBAAqB;AACvB,CAAC;AAGI,SAAS,gBAAgB,QAA+B;AAC7D,SAAO,oBAAoB,MAAM,IAC7B,uBAAuB,MAAM,IAC7B,0BAA0B,6BAA6B,MAAM,CAAC;AACpE;AAGO,SAAS,gBAAgB,QAA+B;AAC7D,SAAO,oBAAoB,MAAM,IAC7B,uBAAuB,MAAM,IAC7B,4BAA4B,+BAA+B,MAAM,CAAC;AACxE;AAGA,SAAS,QAAQ,QAAgC;AAC/C,MAAI,oBAAoB,MAAM,EAAG,QAAO;AACxC,QAAM,WAAW,+BAA+B,MAAM;AACtD,SAAO,aAAa,cAAc,aAAa;AACjD;AAKO,SAAS,oBACd,UACA,QACA,QACQ;AACR,MAAI,oBAAoB,MAAM,EAAG,QAAO,iBAAiB,SAAS,UAAU,MAAM;AAClF,UAAQ,6BAA6B,MAAM,GAAG;AAAA,IAC5C,KAAK;AACH,aAAO,SAAS;AAAA,IAClB,KAAK;AACH,aAAO,OAAO,WAAW,IAAI,IAAI,SAAS,YAAY,OAAO;AAAA,IAC/D,KAAK;AACH,aAAO,OAAO,UAAU,IAAI,IAAI,SAAS,QAAQ,OAAO;AAAA,IAC1D,KAAK;AAAA,IACL;AACE,aAAO,SAAS;AAAA,EACpB;AACF;AAaO,SAAS,wBACd,aACyB;AACzB,QAAM,WAAW,oBAAI,IAAoB;AACzC,QAAM,WAAW,oBAAI,IAAoB;AACzC,MAAI,QAAQ;AACZ,aAAW,KAAK,aAAa;AAC3B,aAAS,EAAE;AACX,aAAS,IAAI,EAAE,SAAS,SAAS,IAAI,EAAE,MAAM,KAAK,KAAK,EAAE,KAAK;AAC9D,aAAS,IAAI,EAAE,SAAS,SAAS,IAAI,EAAE,MAAM,KAAK,KAAK,EAAE,KAAK;AAAA,EAChE;AACA,SAAO,EAAE,OAAO,UAAU,SAAS;AACrC;AAEA,SAAS,MAAM,WAAmB,aAA6B;AAC7D,SAAO,gBAAgB,IAAI,IAAI,YAAY;AAC7C;AAGO,SAAS,sBACd,YACA,QACA,QACA,cACQ;AACR,MAAI,oBAAoB,MAAM,EAAG,QAAO,iBAAiB,WAAW,UAAU,MAAM;AACpF,UAAQ,+BAA+B,MAAM,GAAG;AAAA,IAC9C,KAAK;AACH,aAAO,WAAW;AAAA,IACpB,KAAK;AACH,aAAO,MAAM,WAAW,OAAO,aAAa,KAAK;AAAA,IACnD,KAAK;AACH,aAAO,MAAM,WAAW,WAAW,OAAO,KAAK;AAAA,IACjD,KAAK;AACH,aAAO,MAAM,WAAW,OAAO,aAAa,SAAS,IAAI,WAAW,MAAM,KAAK,CAAC;AAAA,IAClF,KAAK;AACH,aAAO,MAAM,WAAW,OAAO,aAAa,SAAS,IAAI,WAAW,MAAM,KAAK,CAAC;AAAA,IAClF,KAAK;AAAA,IACL;AACE,aAAO,WAAW;AAAA,EACtB;AACF;AAKA,IAAM,kBAA0D;AAAA,EAC9D,CAAC,OAAY,GAAG;AAAA,EAChB,CAAC,MAAW,GAAG;AAAA,EACf,CAAC,KAAQ,KAAK;AAAA,EACd,CAAC,KAAO,GAAG;AACb;AAWO,SAAS,iBAAiB,IAAoB;AACnD,MAAI,CAAC,OAAO,SAAS,EAAE,KAAK,KAAK,EAAG,QAAO;AAC3C,MAAI,OAAO,EAAG,QAAO;AACrB,aAAW,CAAC,MAAM,IAAI,KAAK,iBAAiB;AAC1C,QAAI,MAAM,MAAM;AACd,YAAM,SAAS,KAAK;AACpB,aAAO,GAAG,SAAS,KAAK,OAAO,QAAQ,CAAC,IAAI,KAAK,MAAM,MAAM,CAAC,IAAI,IAAI;AAAA,IACxE;AAAA,EACF;AACA,SAAO,GAAG,KAAK,MAAM,EAAE,CAAC;AAC1B;AAGO,SAAS,kBAAkB,OAAe,QAA+B;AAC9E,MAAI,oBAAoB,MAAM,EAAG,QAAO,iBAAiB,KAAK;AAC9D,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,MAAI,QAAQ,MAAM,EAAG,QAAO,IAAI,QAAQ,KAAK,QAAQ,CAAC,CAAC;AACvD,SAAO,MAAM,eAAe;AAC9B;AASO,SAAS,cAAc,QAAgB,QAAwB;AACpE,SAAO,GAAG,MAAM,GAAG,kBAAkB,GAAG,MAAM;AAChD;AAOO,SAAS,yBAAyB,OAA6B;AACpE,QAAM,aAAa,MAAM,WAAW,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK;AAC1D,QAAM,cAAc,MAAM,YAAY,IAAI,CAAC,MAAM,cAAc,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,KAAK;AACzF,SAAO,GAAG,WAAW,KAAK,kBAAkB,CAAC,IAAI,YAAY,KAAK,kBAAkB,CAAC;AACvF;AAkBO,SAAS,uBACd,OACA,WACsC;AACtC,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,aAAa,oBAAI,IAAY;AACnC,QAAM,cAAc,oBAAI,IAAY;AACpC,MAAI,UAAU,SAAS,YAAY;AACjC,eAAW,IAAI,UAAU,EAAE;AAC3B,eAAW,KAAK,MAAM,aAAa;AACjC,UAAI,EAAE,WAAW,UAAU,MAAM,EAAE,WAAW,UAAU,GAAI;AAC5D,kBAAY,IAAI,cAAc,EAAE,QAAQ,EAAE,MAAM,CAAC;AACjD,iBAAW,IAAI,EAAE,MAAM;AACvB,iBAAW,IAAI,EAAE,MAAM;AAAA,IACzB;AACA,WAAO,EAAE,YAAY,YAAY;AAAA,EACnC;AACA,aAAW,KAAK,MAAM,aAAa;AACjC,QAAI,cAAc,EAAE,QAAQ,EAAE,MAAM,MAAM,UAAU,GAAI;AACxD,gBAAY,IAAI,UAAU,EAAE;AAC5B,eAAW,IAAI,EAAE,MAAM;AACvB,eAAW,IAAI,EAAE,MAAM;AAAA,EACzB;AACA,SAAO,EAAE,YAAY,YAAY;AACnC;AAsBO,SAAS,sBACd,IACA,MACA,WACA,eACA,QACuB;AACvB,MAAI,aAAa,UAAU,SAAS,QAAQ,UAAU,OAAO,GAAI,QAAO;AAExE,QAAM,YAAY,SAAS,aAAa,QAAQ,aAAa,QAAQ;AACrE,QAAM,cAAc,YAAY,EAAE;AAElC,MAAI,gBAAgB,WAAY,QAAO;AAEvC,MAAI,aAAa,eAAe;AAC9B,UAAM,OAAO,SAAS,aAAa,cAAc,aAAa,cAAc;AAC5E,QAAI,CAAC,KAAK,IAAI,EAAE,EAAG,QAAO;AAAA,EAC5B;AAEA,MAAI,gBAAgB,OAAW,QAAO;AAEtC,SAAO;AACT;AA6BO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAiD;AAC/C,QAAM,gBAAgB,uBAAuB,OAAO,SAAS;AAC7D,QAAM,eAAe,wBAAwB,MAAM,WAAW;AAE9D,QAAM,aAAa,MAAM,WAAW,IAAI,CAAC,MAAM,oBAAoB,GAAG,OAAO,MAAM,MAAM,MAAM,CAAC;AAChG,QAAM,aAAa,OAAO,UAAU;AACpC,QAAM,aAAa,MAAM,YAAY;AAAA,IAAI,CAAC,MACxC,sBAAsB,GAAG,OAAO,MAAM,MAAM,QAAQ,YAAY;AAAA,EAClE;AACA,QAAM,aAAa,OAAO,UAAU;AAEpC,QAAM,0BAA0B,gBAAgB,OAAO,IAAI;AAC3D,QAAM,0BAA0B,gBAAgB,OAAO,IAAI;AAC3D,QAAM,WAAW,WAAW,CAAC,IAAI,WAAW,CAAC;AAE7C,QAAM,QAA0B,MAAM,WAAW,IAAI,CAAC,UAAUC,WAAU;AACxE,UAAM,eAAe,WAAWA,MAAK;AACrC,UAAM,iBACJ,OAAO,cAAc,SACjB,SACA;AAAA,MACE,oBAAoB,UAAU,OAAO,WAAW,MAAM,MAAM;AAAA,MAC5D,OAAO;AAAA,IACT;AACN,UAAM,cAAc,QAAQ,YAAY,SAAS,EAAE;AACnD,UAAM,cACJ,gBAAgB,SAAY,SAAY,YAAY,YAAY,YAAY;AAC9E,UAAM,iBAAiB;AAAA,MACrB,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,UAAM,OAAgC;AAAA,MACpC,OAAO,SAAS,SAAS,SAAS;AAAA,MAClC,aAAa;AAAA,MACb,cAAc,kBAAkB,cAAc,OAAO,IAAI;AAAA,MACzD;AAAA,MACA;AAAA;AAAA;AAAA;AAAA,MAIA,YAAY,aAAa,IAAI,OAAO,eAAe,WAAW,CAAC,KAAK;AAAA,MACpE,SAAS,SAAS;AAAA,MAClB,OAAO,SAAS;AAAA,MAChB;AAAA,MACA;AAAA,IACF;AACA,WAAO;AAAA,MACL,IAAI,SAAS;AAAA,MACb,MAAM;AAAA,MACN,UAAU,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,MACvB;AAAA,MACA,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,MAKX,WAAW,kBAAkB,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMjC,eAAe;AAAA,QACb,kBAAkB;AAAA,QAClB,iBAAiB,SAAS;AAAA,MAC5B;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,QAA0B,MAAM,YAAY,IAAI,CAAC,YAAYA,WAAU;AAC3E,UAAM,QAAQ,WAAWA,MAAK;AAC9B,UAAM,KAAK,cAAc,WAAW,QAAQ,WAAW,MAAM;AAC7D,UAAM,iBACJ,OAAO,cAAc,SACjB,SACA;AAAA,MACE,sBAAsB,YAAY,OAAO,WAAW,MAAM,QAAQ,YAAY;AAAA,MAC9E,OAAO;AAAA,IACT;AACN,UAAM,iBAAiB;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,UAAM,OAAkC;AAAA,MACtC,QAAQ,WAAW;AAAA,MACnB,QAAQ,WAAW;AAAA,MACnB,QAAQ;AAAA,MACR;AAAA,MACA,aAAa;AAAA,MACb,OAAO,kBAAkB,OAAO,OAAO,IAAI;AAAA,MAC3C;AAAA,MACA,YAAY,WAAW;AAAA,MACvB,YAAY,aAAa,IAAI,EAAE,KAAK,WAAW;AAAA,MAC/C;AAAA,IACF;AACA,WAAO;AAAA,MACL;AAAA,MACA,QAAQ,WAAW;AAAA,MACnB,QAAQ,WAAW;AAAA,MACnB,MAAM;AAAA,MACN;AAAA,MACA,WAAW,oBAAoB,MAAM,uBAAuB;AAAA,IAC9D;AAAA,EACF,CAAC;AAED,QAAM,eAAqC,MAAM,IAAI,CAAC,UAAU;AAAA,IAC9D,IAAI,KAAK;AAAA,IACT,OAAO,KAAK,KAAK;AAAA,IACjB,cAAc,KAAK,KAAK;AAAA,IACxB,gBAAgB,KAAK,KAAK;AAAA,IAC1B,aAAa,KAAK,KAAK;AAAA,IACvB,MAAM,aAAa,KAAK,IAAI;AAAA,IAC5B,gBAAgB,KAAK,KAAK;AAAA,EAC5B,EAAE;AAEF,QAAM,iBAAyC,MAAM,IAAI,CAAC,SAAS;AACjE,UAAM,OAAO,KAAK;AAClB,WAAO;AAAA,MACL,IAAI,KAAK;AAAA,MACT,QAAQ,KAAK;AAAA,MACb,QAAQ,KAAK;AAAA,MACb,cAAc,KAAK;AAAA,MACnB,gBAAgB,KAAK;AAAA,MACrB,OAAO,gBAAgB,IAAI;AAAA,MAC3B,gBAAgB,KAAK;AAAA,IACvB;AAAA,EACF,CAAC;AAED,QAAM,iBAA2C;AAAA,IAC/C,YAAY,MAAM,OAAO,CAAC,GAAG,SAAS,KAAK,KAAK,KAAK,mBAAmB,aAAa,IAAI,IAAI,CAAC;AAAA,IAC9F,iBAAiB,MAAM;AAAA,IACvB,aAAa,MAAM;AAAA,MACjB,CAAC,GAAG,SACF,KAAM,KAAK,KAAmC,mBAAmB,aAAa,IAAI;AAAA,MACpF;AAAA,IACF;AAAA,IACA,kBAAkB,MAAM;AAAA,EAC1B;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,iBAAiB,CAAC,UAAkB,kBAAkB,OAAO,OAAO,IAAI;AAAA,IACxE;AAAA,EACF;AACF;AAOO,SAAS,aAAa,MAAuC;AAClE,MAAI,KAAK,WAAW,KAAK,MAAO,QAAO;AACvC,MAAI,KAAK,QAAS,QAAO;AACzB,MAAI,KAAK,MAAO,QAAO;AACvB,SAAO;AACT;AAGO,SAAS,gBAAgB,MAAyC;AACvE,MAAI,KAAK,WAAY,QAAO;AAC5B,MAAI,KAAK,WAAY,QAAO;AAC5B,SAAO;AACT;AAYO,SAAS,oBAAoB,OAAsC;AACxE,MAAI,UAAU,WAAY,QAAO;AACjC,MAAI,UAAU,WAAY,QAAO;AACjC,SAAO;AACT;AAUO,IAAM,uCAET,OAAO,OAAO;AAAA,EAChB,UAAU;AAAA,EACV,UAAU;AACZ,CAAC;AAGM,SAAS,kBAAkB,MAAuC;AACvE,QAAM,QAAQ,CAAC,GAAG,KAAK,KAAK,WAAM,aAAa,IAAI,EAAE,YAAY,CAAC,EAAE;AACpE,QAAM,KAAK,GAAG,KAAK,WAAW,IAAI,KAAK,YAAY,EAAE;AACrD,MAAI,KAAK,eAAgB,OAAM,KAAK,KAAK,cAAc;AACvD,MAAI,KAAK,YAAa,OAAM,KAAK,GAAG,KAAK,WAAW,sBAAsB;AAC1E,MAAI,KAAK,mBAAmB,aAAc,OAAM,KAAK,KAAK,cAAc;AACxE,SAAO,MAAM,KAAK,IAAI;AACxB;AAGO,SAAS,oBAAoB,MAAiC,aAA6B;AAChG,QAAM,QAAQ;AAAA,IACZ,KAAK,aACD,gBAAgB,KAAK,MAAM,KAC3B,GAAG,KAAK,aAAa,cAAc,YAAY,SAAS,KAAK,MAAM,OAAO,KAAK,MAAM;AAAA,IACzF,GAAG,WAAW,IAAI,KAAK,KAAK;AAAA,EAC9B;AACA,MAAI,KAAK,eAAgB,OAAM,KAAK,KAAK,cAAc;AACvD,MAAI,KAAK,mBAAmB,aAAc,OAAM,KAAK,KAAK,cAAc;AACxE,SAAO,MAAM,KAAK,IAAI;AACxB;;;ACtzBA;AAAA,EACE,eAAAC;AAAA,EACA,aAAAC;AAAA,EACA,WAAAC;AAAA,EACA,UAAAC;AAAA,EACA,YAAAC;AAAA,OAIK;AACP,SAAS,cAAc;AAEvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,MAAAC,WAAU;AACnB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;;;ACpCP,SAAS,QAAQ,QAAgB,QAAwB;AACvD,SAAO,GAAG,MAAM,GAAG,kBAAkB,GAAG,MAAM;AAChD;AAGA,SAASC,gBAAe,GAAW,GAAmB;AACpD,SAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI;AAClC;AAGA,SAAS,cAAc,OAAuB;AAC5C,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,SAAO,QAAQ,IAAI,IAAI,QAAQ,IAAI,IAAI;AACzC;AAWA,SAAS,YAAY,OAAe,UAAkB,SAAyB;AAC7E,MAAI,UAAU,EAAG,QAAO;AACxB,QAAM,OAAO,KAAK,MAAM,QAAQ,QAAQ;AACxC,SAAO,OAAO,UAAU,KAAK,IAAI,SAAS,KAAK,IAAI;AACrD;AAGA,IAAM,UAAN,MAAc;AAAA,EACK,QAAkB,CAAC;AAAA,EACnB,QAAkB,CAAC;AAAA,EAEpC,IAAI,OAAe;AACjB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,KAAK,MAAc,MAAoB;AACrC,SAAK,MAAM,KAAK,IAAI;AACpB,SAAK,MAAM,KAAK,IAAI;AACpB,QAAI,IAAI,KAAK,MAAM,SAAS;AAC5B,WAAO,IAAI,GAAG;AACZ,YAAM,SAAU,IAAI,KAAM;AAC1B,UAAK,KAAK,MAAM,MAAM,KAAiB,KAAK,MAAM,CAAC,EAAc;AACjE,WAAK,KAAK,GAAG,MAAM;AACnB,UAAI;AAAA,IACN;AAAA,EACF;AAAA,EAEA,MAAkD;AAChD,QAAI,KAAK,MAAM,WAAW,EAAG,QAAO;AACpC,UAAM,OAAO,KAAK,MAAM,CAAC;AACzB,UAAM,OAAO,KAAK,MAAM,CAAC;AACzB,UAAM,WAAW,KAAK,MAAM,IAAI;AAChC,UAAM,WAAW,KAAK,MAAM,IAAI;AAChC,QAAI,KAAK,MAAM,SAAS,GAAG;AACzB,WAAK,MAAM,CAAC,IAAI;AAChB,WAAK,MAAM,CAAC,IAAI;AAChB,UAAI,IAAI;AACR,iBAAS;AACP,cAAM,OAAO,IAAI,IAAI;AACrB,cAAM,QAAQ,OAAO;AACrB,YAAI,WAAW;AACf,cAAM,OAAO,KAAK,MAAM;AACxB,YAAI,OAAO,QAAS,KAAK,MAAM,IAAI,IAAgB,KAAK,MAAM,QAAQ,GAAc;AAClF,qBAAW;AAAA,QACb;AACA,YAAI,QAAQ,QAAS,KAAK,MAAM,KAAK,IAAgB,KAAK,MAAM,QAAQ,GAAc;AACpF,qBAAW;AAAA,QACb;AACA,YAAI,aAAa,EAAG;AACpB,aAAK,KAAK,GAAG,QAAQ;AACrB,YAAI;AAAA,MACN;AAAA,IACF;AACA,WAAO,EAAE,MAAM,KAAK;AAAA,EACtB;AAAA,EAEQ,KAAK,GAAW,GAAiB;AACvC,UAAM,OAAO,KAAK,MAAM,CAAC;AACzB,SAAK,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC;AAC5B,SAAK,MAAM,CAAC,IAAI;AAChB,UAAM,OAAO,KAAK,MAAM,CAAC;AACzB,SAAK,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC;AAC5B,SAAK,MAAM,CAAC,IAAI;AAAA,EAClB;AACF;AAsBO,SAAS,cAAc,OAAqB,MAA2C;AAC5F,QAAM,gBAAgB,KAAK,iBAAiB;AAC5C,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,mBAAmB,cAAc,KAAK,UAAU;AACtD,QAAM,eAAe,cAAc,KAAK,KAAK;AAE7C,QAAM,mBAAmB,CAAC,GAAG,MAAM,UAAU,EAAE;AAAA,IAC7C,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,aAAaA,gBAAe,EAAE,IAAI,EAAE,EAAE;AAAA,EACvF;AACA,QAAM,oBAAoB,YAAY,iBAAiB,QAAQ,kBAAkB,CAAC;AAClF,QAAM,iBAAiB,IAAI;AAAA,KACxB,SACG,iBAAiB,MAAM,iBAAiB,SAAS,iBAAiB,IAClE,iBAAiB,MAAM,GAAG,iBAAiB,GAC7C,IAAI,CAAC,aAAa,SAAS,EAAE;AAAA,EACjC;AAEA,QAAM,iBAAiB,MAAM,YAAY;AAAA,IACvC,CAAC,SAAS,eAAe,IAAI,KAAK,MAAM,KAAK,eAAe,IAAI,KAAK,MAAM;AAAA,EAC7E;AACA,QAAM,cAAc,CAAC,GAAG,cAAc,EAAE;AAAA,IACtC,CAAC,GAAG,MACF,EAAE,QAAQ,EAAE,SACZ,EAAE,YAAY,EAAE,aAChBA,gBAAe,EAAE,QAAQ,EAAE,MAAM,KACjCA,gBAAe,EAAE,QAAQ,EAAE,MAAM;AAAA,EACrC;AACA,QAAM,gBAAgB,YAAY,YAAY,QAAQ,cAAc,CAAC;AACrE,QAAM,YAAY,IAAI;AAAA,KACnB,SACG,YAAY,MAAM,YAAY,SAAS,aAAa,IACpD,YAAY,MAAM,GAAG,aAAa,GACpC,IAAI,CAAC,SAAS,QAAQ,KAAK,QAAQ,KAAK,MAAM,CAAC;AAAA,EACnD;AAEA,MAAI,iBAAiB,eAAe,OAAO,GAAG;AAC5C,cAAU,OAAO,gBAAgB,SAAS;AAAA,EAC5C;AAEA,QAAM,aAA8B,MAAM,WAAW;AAAA,IAAO,CAAC,aAC3D,eAAe,IAAI,SAAS,EAAE;AAAA,EAChC;AACA,QAAM,cAAiC,MAAM,YAAY;AAAA,IAAO,CAAC,SAC/D,UAAU,IAAI,QAAQ,KAAK,QAAQ,KAAK,MAAM,CAAC;AAAA,EACjD;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,iBAAiB,KAAK,MAAM,iBAAiB,cAAc;AAAA,IAC3D,eAAe,KAAK,MAAM,eAAe,cAAc;AAAA;AAAA,IAEvD,QAAQ,MAAM;AAAA,IACd,QAAQ;AAAA,MACN,YAAY,MAAM,WAAW,SAAS,WAAW;AAAA,MACjD,OAAO,MAAM,YAAY,SAAS,YAAY;AAAA,IAChD;AAAA,EACF;AACF;AAGA,SAAS,KAAK,QAAgC,MAAmD;AAC/F,QAAM,MAA8B,CAAC;AACrC,aAAW,QAAQ,OAAO,KAAK,MAAM,GAAG;AACtC,QAAI,KAAK,IAAI,IAAI,EAAG,KAAI,IAAI,IAAI,OAAO,IAAI;AAAA,EAC7C;AACA,SAAO;AACT;AA0BA,SAAS,UAAU,OAAqB,gBAA6B,WAA8B;AACjG,QAAM,UAAU,oBAAI,IAA+B;AACnD,QAAM,WAAW,oBAAI,IAA+B;AACpD,MAAI,WAAW;AACf,aAAW,QAAQ,MAAM,aAAa;AACpC,QAAI,KAAK,QAAQ,SAAU,YAAW,KAAK;AAC3C,UAAM,SAAS,KAAK,QAAQ,IAAI;AAChC,UAAM,UAAU,KAAK,QAAQ,IAAI;AAAA,EACnC;AACA,QAAM,SAAS,KAAK,IAAI,QAAQ;AAChC,QAAM,OAAO,CAAC,UAA2B,QAAQ,IAAI,SAAS,KAAK,IAAI,KAAK,IAAI;AAOhF,QAAM,QAAQ,MAAM,WAAW,SAAS,MAAM,YAAY,SAAS;AACnE,WAAS,QAAQ,GAAG,QAAQ,OAAO,SAAS,GAAG;AAC7C,UAAM,SAAS,eAAe,OAAO,UAAU;AAC/C,oBAAgB,MAAM,iBAAiB,SAAS,IAAI;AACpD,oBAAgB,MAAM,eAAe,UAAU,KAAK;AACpD,QAAI,eAAe,OAAO,UAAU,SAAS,OAAQ;AAAA,EACvD;AAEA,WAAS,gBACP,YACA,WACA,YACM;AACN,UAAM,WAAW,OAAO,KAAK,UAAU;AACvC,QAAI,SAAS,WAAW,EAAG;AAC3B,QAAI,CAAC,SAAS,KAAK,CAAC,OAAO,eAAe,IAAI,EAAE,CAAC,EAAG,QAAO,YAAY,cAAc;AACrF,UAAM,YAAY,SAAS,OAAO,CAAC,OAAO,eAAe,IAAI,EAAE,CAAC;AAChE,QAAI,UAAU,WAAW,EAAG;AAC5B,UAAM,UAAU,OAAO,WAAW,WAAW,UAAU;AAGvD,eAAW,YAAY,MAAM,YAAY;AACvC,UAAI,CAAC,eAAe,IAAI,SAAS,EAAE,KAAK,QAAQ,IAAI,SAAS,EAAE,EAAG;AAMlE,YAAM,QACJ,cAAc,WAAW,SAAS,IAAI,WAAW,YAAY,IAAI,KACjE,cAAc,WAAW,SAAS,IAAI,WAAW,YAAY,KAAK,KAClE,cAAc,UAAU,SAAS,IAAI,WAAW,YAAY,KAAK;AACnE,UAAI,UAAU,OAAW;AACzB,iBAAW,MAAM,MAAM,WAAY,gBAAe,IAAI,EAAE;AACxD,iBAAW,OAAO,MAAM,MAAO,WAAU,IAAI,GAAG;AAChD,iBAAW,MAAM,OAAO,CAAC,GAAG,MAAM,UAAU,GAAG,WAAW,UAAU,EAAG,SAAQ,IAAI,EAAE;AAAA,IACvF;AAAA,EACF;AAGA,WAAS,OACP,OACA,WACA,YACa;AACb,UAAM,OAAO,IAAI,IAAY,MAAM,OAAO,CAAC,OAAO,eAAe,IAAI,EAAE,CAAC,CAAC;AACzE,UAAM,QAAQ,CAAC,GAAG,IAAI;AACtB,aAAS,OAAO,GAAG,OAAO,MAAM,QAAQ,QAAQ,GAAG;AACjD,YAAM,OAAO,MAAM,IAAI;AACvB,iBAAW,QAAQ,UAAU,IAAI,IAAI,KAAK,CAAC,GAAG;AAC5C,cAAM,OAAO,aAAa,KAAK,SAAS,KAAK;AAC7C,YAAI,CAAC,eAAe,IAAI,IAAI,EAAG;AAC/B,YAAI,CAAC,UAAU,IAAI,QAAQ,KAAK,QAAQ,KAAK,MAAM,CAAC,EAAG;AACvD,YAAI,KAAK,IAAI,IAAI,EAAG;AACpB,aAAK,IAAI,IAAI;AACb,cAAM,KAAK,IAAI;AAAA,MACjB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAOA,WAAS,cACP,OACA,QACA,WACA,YACA,UACmB;AACnB,UAAM,WAAW,oBAAI,IAAoB;AACzC,UAAM,WAAW,oBAAI,IAA6B;AAClD,UAAM,UAAU,oBAAI,IAAY;AAChC,UAAM,OAAO,IAAI,QAAQ;AACzB,eAAW,QAAQ,OAAO;AACxB,eAAS,IAAI,MAAM,CAAC;AACpB,WAAK,KAAK,MAAM,CAAC;AAAA,IACnB;AAEA,WAAO,KAAK,OAAO,GAAG;AACpB,YAAM,MAAM,KAAK,IAAI;AACrB,UAAI,QAAQ,IAAI,IAAI,IAAI,EAAG;AAC3B,cAAQ,IAAI,IAAI,IAAI;AACpB,UAAI,IAAI,SAAS,OAAQ;AACzB,iBAAW,QAAQ,UAAU,IAAI,IAAI,IAAI,KAAK,CAAC,GAAG;AAChD,cAAM,OAAO,aAAa,KAAK,SAAS,KAAK;AAC7C,YAAI,SAAS,IAAI,KAAM;AACvB,YAAI,YAAY,CAAC,eAAe,IAAI,IAAI,EAAG;AAC3C,cAAM,YAAY,IAAI,OAAO,KAAK,KAAK,KAAK;AAC5C,cAAM,QAAQ,SAAS,IAAI,IAAI;AAG/B,YAAI,UAAU,UAAa,SAAS,UAAW;AAC/C,iBAAS,IAAI,MAAM,SAAS;AAC5B,iBAAS,IAAI,MAAM,IAAI;AACvB,aAAK,KAAK,MAAM,SAAS;AAAA,MAC3B;AAAA,IACF;AAEA,QAAI,CAAC,QAAQ,IAAI,MAAM,EAAG,QAAO;AACjC,UAAM,QAAe,EAAE,YAAY,oBAAI,IAAI,CAAC,MAAM,CAAC,GAAG,OAAO,oBAAI,IAAI,EAAE;AACvE,QAAI,SAAS;AACb,eAAS;AACP,YAAM,OAAO,SAAS,IAAI,MAAM;AAChC,UAAI,SAAS,OAAW;AACxB,YAAM,MAAM,IAAI,QAAQ,KAAK,QAAQ,KAAK,MAAM,CAAC;AACjD,YAAM,WAAW,IAAI,KAAK,MAAM;AAChC,YAAM,WAAW,IAAI,KAAK,MAAM;AAChC,eAAS,aAAa,KAAK,SAAS,KAAK;AAAA,IAC3C;AACA,WAAO;AAAA,EACT;AAGA,WAAS,OAAO,QAAgC,MAAyB;AACvE,UAAM,QAAQ,OAAO,KAAK,MAAM;AAChC,QAAI,MAAM,WAAW,EAAG;AACxB,QAAI,MAAM,KAAK,CAAC,SAAS,KAAK,IAAI,IAAI,CAAC,EAAG;AAC1C,QAAI,OAAO,MAAM,CAAC;AAClB,eAAW,QAAQ,OAAO;AACxB,YAAM,QAAQ,OAAO,IAAI;AACzB,YAAM,YAAY,OAAO,IAAI;AAC7B,UAAI,QAAQ,aAAc,UAAU,aAAaA,gBAAe,MAAM,IAAI,IAAI,EAAI,QAAO;AAAA,IAC3F;AACA,SAAK,IAAI,IAAI;AAAA,EACf;AACF;AAEA,SAAS,MAAM,KAAqC,KAAa,MAA6B;AAC5F,QAAM,SAAS,IAAI,IAAI,GAAG;AAC1B,MAAI,WAAW,OAAW,KAAI,IAAI,KAAK,CAAC,IAAI,CAAC;AAAA,MACxC,QAAO,KAAK,IAAI;AACvB;;;ACtWO,SAAS,aAAa,KAA0B;AACrD,QAAM,aAAa,gBAAgB,GAAG;AAEtC,QAAM,UAAU,oBAAI,IAA4B;AAChD,MAAI,YAAY;AAChB,MAAI,QAAQ;AACZ,MAAI,kBAAkB;AAEtB,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,QAAQ,WAAW,OAAO;AACnC,SAAK,MAAM;AACX,QAAI,gBAAgB;AACpB,QAAI;AAEJ,eAAW,SAAS,KAAK,QAAQ;AAC/B,YAAM,OAAO,MAAM;AACnB,UAAI,QAAQ,QAAQ,IAAI,IAAI;AAC5B,UAAI,UAAU,QAAW;AACvB,gBAAQ,EAAE,WAAW,GAAG,OAAO,EAAE;AACjC,gBAAQ,IAAI,MAAM,KAAK;AAAA,MACzB;AAEA,UAAI,KAAK,IAAI,IAAI,GAAG;AAClB,wBAAgB;AAChB,YAAI,SAAS,UAAU;AACrB,gBAAM,aAAa;AACnB,uBAAa;AAAA,QACf,OAAO;AACL,gBAAM,SAAS;AACf,mBAAS;AAAA,QACX;AAAA,MACF,OAAO;AACL,aAAK,IAAI,IAAI;AAAA,MACf;AACA,iBAAW;AAAA,IACb;AAEA,QAAI,cAAe,oBAAmB;AAAA,EACxC;AAEA,QAAM,cAA8C,CAAC;AACrD,aAAW,QAAQ,CAAC,GAAG,QAAQ,KAAK,CAAC,EAAE,KAAK,GAAG;AAC7C,gBAAY,IAAI,IAAI,QAAQ,IAAI,IAAI;AAAA,EACtC;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,gBAAgB,WAAW,MAAM,WAAW,IAAI,IAAI,kBAAkB,WAAW,MAAM;AAAA,IACvF;AAAA,EACF;AACF;;;ACnCA,SAAS,eAAmC;AAC5C,SAAS,WAAW,MAAM,MAAM,iBAAiB;AACjD,SAAS,aAAa;AACtB,SAAS,UAAU;AACnB,SAAS,gBAAmC;;;ACnD5C,SAAS,eAAe,WAAqD;AAWtE,IAAM,0BAAgD,OAAO,OAAO;AAAA,EACzE,YAAY;AAAA,EACZ,iBAAiB,oBAAI,IAAY;AACnC,CAAC;AAOM,IAAM,yBAAyB,cAAoC,uBAAuB;AAG1F,SAAS,qBAA2C;AACzD,SAAO,IAAI,sBAAsB;AACnC;AAkBA,IAAM,wBAAkD,MAAM;AAAC;AAGxD,IAAM,2BACX,cAAwC,qBAAqB;AAGxD,SAAS,wBAAkD;AAChE,SAAO,IAAI,wBAAwB;AACrC;;;AD8DuB,cA0Db,YA1Da;AA/CvB,IAAM,oBAAoB;AAAA,EACxB,YAAY;AAAA,EACZ,eAAe;AACjB;AASA,SAAS,UAAU,YAA4B;AAC7C,QAAM,UAAU,KAAK,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,UAAU,CAAC,IAAI,GAAG;AACrE,SAAO,sCAAsC,OAAO;AACtD;AAGA,SAAS,SAAS,SAAkB,OAAgB;AAClD,MAAI,WAAW,MAAO,QAAO;AAC7B,MAAI,QAAS,QAAO;AACpB,MAAI,MAAO,QAAO;AAClB,SAAO;AACT;AAQO,SAAS,oBAAoB,OAAkC;AACpE,QAAM,EAAE,KAAK,IAAI;AACjB,QAAM,QAAQ,mBAAmB;AACjC,QAAM,WAAW,SAAS,KAAK,SAAS,KAAK,KAAK;AAClD,QAAM,YAAY,MAAM,eAAe,MAAM;AAC7C,QAAM,WAAW,KAAK,mBAAmB;AAEzC,QAAM,UAAU,KAAK,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,UAAU,CAAC,IAAI,GAAG;AAE1E,QAAM,WAAW;AAAA,IACf,OAAO;AAAA,MACL,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,UAAU,KAAK,iBACX,GAAG,KAAK,YAAY,SAAM,KAAK,cAAc,KAC7C,KAAK;AAAA,MACT,MAAM,WAAW,oBAAC,YAAS,eAAY,QAAO,IAAK;AAAA;AAAA;AAAA;AAAA,MAInD,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,MAKN,QACE;AAAA,QAAC;AAAA;AAAA,UACC,eAAY;AAAA,UACZ,aAAU;AAAA,UACV,gBAAc;AAAA,UACd,WAAU;AAAA,UACV,OAAO,WAAW,EAAE,SAAS,cAAc,IAAI;AAAA,UAE/C;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,OAAO,EAAE,OAAO,GAAG,OAAO,KAAK,YAAY,UAAU,KAAK,UAAU,EAAE;AAAA;AAAA,UACxE;AAAA;AAAA,MACF;AAAA,IAEJ;AAAA,IACA;AAAA,MACE,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,kBAAgB,KAAK;AAAA,MACrB,aAAW,aAAa,IAAI,EAAE,YAAY;AAAA,MAC1C,cAAY,YAAY,SAAS;AAAA,MAOjC,WAAW,GAAG,YAAY,aAAa,MAAM;AAAA,MAE5C;AAAA,aAAK,cACJ;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,aAAU;AAAA,YACV,WAAU;AAAA,YAEV;AAAA,kCAAC,aAAU,eAAY,QAAO,WAAU,UAAS;AAAA,cACjD,oBAAC,UAAK,eAAY,QAAQ,eAAK,aAAY;AAAA,cAC3C,qBAAC,UAAK,WAAU,WAAW;AAAA,qBAAK;AAAA,gBAAY;AAAA,iBAAoB;AAAA;AAAA;AAAA,QAClE,IACE;AAAA,QAEJ,oBAAC,SAAI,aAAU,+BAA8B,OAAO,WAAW,oBAAoB,QACjF,8BAAC,YAAU,GAAG,OAAO,MAAK,SAAQ,MAAM,UAAU,GACpD;AAAA;AAAA;AAAA,EACF;AAEJ;;;AErJA,SAAS,WAAAC,gBAAe;AAExB;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAuFC,gBAAAC,YAAA;AA9ED,IAAM,+BAA+B;AAG5C,IAAM,kBAAkB;AAExB,IAAM,oBAAoB;AAI1B,IAAM,uBAAuB,EAAE,WAAW,iBAAiB,kBAAkB,WAAW;AAOjF,SAAS,sBAAsB,OAAkC;AACtE,QAAM,EAAE,KAAK,IAAI;AACjB,QAAM,QAAQ,mBAAmB;AACjC,QAAM,YAAY,sBAAsB;AACxC,QAAM,aAAa,MAAM,mBAAmB;AAC5C,QAAM,aAAa,aAAa,uBAAuB;AAEvD,QAAM,eAAeC;AAAA,IACnB,OAAO;AAAA,MACL,QAAQ,MAAM;AAAA,MACd,YAAY;AAAA,MACZ,OAAO,MAAM;AAAA,MACb,aAAa,MAAM;AAAA,MACnB,OAAO,MAAM;AAAA,MACb,gBAAgB,MAAM;AAAA,MACtB,SAAS,MAAM,aAAa,SAAS;AAAA,MACrC;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAeA;AAAA,IACnB,OAAO;AAAA,MACL,QAAQ,MAAM;AAAA,MACd,YAAY;AAAA,MACZ,OAAO,MAAM;AAAA,MACb,gBAAgB,MAAM;AAAA,MACtB;AAAA,IACF;AAAA,IACA,CAAC,MAAM,QAAQ,MAAM,OAAO,MAAM,gBAAgB,UAAU;AAAA,EAC9D;AAEA,QAAM,UAAU,aACZ,gBACA,MAAM,eAAe,QAAQ,CAAC,MAAM,gBAAgB,IAAI,MAAM,EAAE,IAC9D,oBACA;AAEN,SACE,gBAAAD;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,cAAY,MAAM,aAAa,cAAc,MAAM,aAAa,SAAS;AAAA,MACzE,kBAAgB,MAAM;AAAA,MACtB,iBAAe,MAAM,gBAAgB,IAAI,MAAM,EAAE,IAAI,SAAS;AAAA,MAC9D,WAAU;AAAA,MACV,OAAO,EAAE,QAAQ;AAAA,MAMjB,WAAW,CAAC,UAAU,UAAU,MAAM,IAAI,KAAK;AAAA,MAE9C,gBAAM,aACL,gBAAAA,KAAC,oBAAkB,GAAG,OAAO,MAAK,aAAY,MAAM,cAAc,IAElE,gBAAAA,KAAC,oBAAkB,GAAG,OAAO,MAAK,YAAW,MAAM,cAAc;AAAA;AAAA,EAErE;AAEJ;;;AC/GA,SAAS,aAAa,WAAW,WAAAE,UAAS,QAAQ,gBAAgB;AAElE,SAAS,kBAA4C;AAYrD,IAAM,eAAe;AA0BrB,IAAM,eAAoD,EAAE,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI;AAGtF,IAAM,6BAA6B;AAmCnC,IAAM,gCACX;AA+CF,IAAM,YAAiC,oBAAI,IAAY;AAGvD,SAAS,WACP,QACA,YACuB;AACvB,QAAM,YAAwC,CAAC;AAC/C,QAAM,iBAA0D,CAAC;AACjE,QAAM,iBAA0D,CAAC;AACjE,aAAW,QAAQ,OAAO,OAAO;AAC/B,cAAU,KAAK,EAAE,IAAI,KAAK;AAC1B,mBAAe,KAAK,EAAE,IAAI,KAAK;AAC/B,mBAAe,KAAK,EAAE,IAAI,KAAK;AAAA,EACjC;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,OAAO;AAAA,IAClB,WAAW,OAAO;AAAA,IAClB;AAAA,EACF;AACF;AAGO,SAAS,oBACd,OACA,UACkB;AAClB,SAAO,MAAM,IAAI,CAAC,SAAS;AACzB,UAAM,WAAW,SAAS,UAAU,KAAK,EAAE;AAC3C,QAAI,CAAC,SAAU,QAAO;AACtB,WAAO;AAAA,MACL,GAAG;AAAA,MACH;AAAA,MACA,gBAAgB,SAAS,eAAe,KAAK,EAAE;AAAA,MAC/C,gBAAgB,SAAS,eAAe,KAAK,EAAE;AAAA,IACjD;AAAA,EACF,CAAC;AACH;AAQO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AACf,GAAoD;AAClD,QAAM,WAAW,GAAG,YAAY,KAAK,SAAS;AAC9C,QAAM,QAAQ,OAAO,oBAAI,IAAmC,CAAC;AAC7D,QAAM,OAAO,OAAO,CAAC;AAIrB,QAAM,CAAC,SAAS,UAAU,IAAI;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,UAAU;AAAA,IACd,CAAC,KAAa,cAAgC,iBAAmC;AAC/E,YAAM,UAAU,YAAY,IAAI;AAChC,YAAM,SAAS,WAA2C,cAAc,cAAc;AAAA,QACpF;AAAA,QACA,aAAa;AAAA,QACb,aAAa,aAAa,SAAS;AAAA,MACrC,CAAC;AACD,YAAMC,YAAW,WAAW,QAAQ,YAAY,IAAI,IAAI,OAAO;AAC/D,WAAK,WAAW;AAChB,YAAM,QAAQ,IAAI,KAAKA,SAAQ;AAC/B,iBAAW,EAAE,KAAK,UAAAA,UAAS,CAAC;AAAA,IAC9B;AAAA,IACA,CAAC,SAAS;AAAA,EACZ;AAKA,QAAM,SAAS,OAAO,EAAE,OAAO,MAAM,CAAC;AACtC,SAAO,UAAU,EAAE,OAAO,MAAM;AAEhC,QAAM,eAAe,MAAM,QAAQ,IAAI,QAAQ;AAC/C,QAAM,YAAY,SAAS,QAAQ,YAAY,iBAAiB;AAEhE,YAAU,MAAM;AACd,UAAM,SAAS,MAAM,QAAQ,IAAI,QAAQ;AACzC,QAAI,QAAQ;AACV;AAAA,QAAW,CAAC,YACV,SAAS,QAAQ,WAAW,UAAU,EAAE,KAAK,UAAU,UAAU,OAAO;AAAA,MAC1E;AACA;AAAA,IACF;AACA,QAAI,OAAO,QAAQ,MAAM,WAAW,GAAG;AACrC,iBAAW;AAAA,QACT,KAAK;AAAA,QACL,UAAU;AAAA,UACR,WAAW,CAAC;AAAA,UACZ,gBAAgB,CAAC;AAAA,UACjB,gBAAgB,CAAC;AAAA,UACjB,WAAW,CAAC;AAAA,UACZ,WAAW,CAAC;AAAA,UACZ,YAAY;AAAA,QACd;AAAA,MACF,CAAC;AACD;AAAA,IACF;AAGA,QAAI,KAAK,YAAY,KAAK,cAAc,GAAG;AACzC,cAAQ,UAAU,OAAO,QAAQ,OAAO,OAAO,QAAQ,KAAK;AAC5D;AAAA,IACF;AACA,UAAM,QAAQ;AAAA,MACZ,MAAM,QAAQ,UAAU,OAAO,QAAQ,OAAO,OAAO,QAAQ,KAAK;AAAA,MAClE;AAAA,IACF;AACA,WAAO,MAAM,aAAa,KAAK;AAAA,EACjC,GAAG,CAAC,UAAU,SAAS,UAAU,CAAC;AAElC,QAAM,WAAW,SAAS,QAAQ,WAAW,QAAQ,WAAW;AAEhE,QAAM,kBAAkBD;AAAA,IACtB,MAAO,WAAW,oBAAoB,OAAO,QAAQ,IAAI;AAAA,IACzD,CAAC,OAAO,QAAQ;AAAA,EAClB;AAEA,QAAM,cAAcA;AAAA,IAClB,MAAO,WAAW,IAAI,IAAI,SAAS,SAAS,IAAI;AAAA,IAChD,CAAC,QAAQ;AAAA,EACX;AACA,QAAM,cAAcA;AAAA,IAClB,MAAO,WAAW,IAAI,IAAI,SAAS,SAAS,IAAI;AAAA,IAChD,CAAC,QAAQ;AAAA,EACX;AAEA,SAAO;AAAA,IACL,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,YAAY,KAAK;AAAA,IACjB,cAAc,UAAU,cAAc;AAAA,IACtC,SAAS,CAAC,aAAa,MAAM,SAAS;AAAA,EACxC;AACF;;;AN8PQ,gBAAAE,MAmCA,QAAAC,aAnCA;AA1bR,IAAM,aAAa,EAAE,oBAAoB,oBAAoB;AAC7D,IAAM,aAAa,EAAE,sBAAsB,sBAAsB;AAgB1D,IAAM,2BAA2B;AAUxC,IAAM,mBAAmB;AAAA,EACvB,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AACX;AAaA,IAAM,WAAW;AAiDjB,SAAS,iBACP,OACA,KACA,aAC0B;AAC1B,QAAM,aAAaC;AAAA,IACjB,MAAO,QAAQ,SAAY,MAAM,cAAc,GAAG,IAAI;AAAA,IACtD,CAAC,OAAO,GAAG;AAAA,EACb;AACA,QAAM,OAAO,SAAS;AACtB,QAAM,aAAa,aAAa;AAChC,QAAM,QAAQ,aAAa;AAC3B,SAAOA,SAAQ,MAAM;AACnB,QAAI,CAAC,KAAM,QAAO;AAClB,QAAI,eAAe,UAAa,UAAU,OAAW,QAAO;AAC5D,WAAO,cAAc,MAAM,EAAE,YAAY,cAAc,GAAG,OAAO,SAAS,EAAE,CAAC;AAAA,EAC/E,GAAG,CAAC,MAAM,YAAY,KAAK,CAAC;AAC9B;AAUO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAoB;AAIlB,QAAM,EAAE,EAAE,IAAI,UAAU;AACxB,QAAM,WAAW,SAAS,EAAE,mBAAmB;AAM/C,QAAM,qBAAqBC;AAAA,IACzB,CAAC,UAAyC;AACxC,YAAM,MAAM,qCAAqC,KAAK;AACtD,aAAO,MAAM,EAAE,GAAG,IAAI;AAAA,IACxB;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AACA,QAAM,WAAW,iBAAiB,OAAO,KAAK,WAAW;AACzD,QAAM,gBAAgBD;AAAA,IACpB,MAAO,SAAS,SAAY,MAAM,aAAa,GAAG,IAAI;AAAA,IACtD,CAAC,QAAQ,GAAG;AAAA,EACd;AACA,QAAM,eAAe,UAAU;AAG/B,QAAM,eAAe,cAAc;AACnC,QAAM,CAAC,cAAc,eAAe,IAAIE,UAAkC,IAAI;AAC9E,QAAM,kBAAkB,eAAgB,aAAa,OAAQ;AAE7D,QAAM,iBAAiBD;AAAA,IACrB,CAAC,SAAkC;AACjC,UAAI,CAAC,aAAc,iBAAgB,IAAI;AACvC,iBAAW,IAAI;AAAA,IACjB;AAAA,IACA,CAAC,cAAc,QAAQ;AAAA,EACzB;AAEA,QAAM,eAAeD;AAAA,IACnB,MAAO,WAAW,yBAAyB,QAAQ,IAAI;AAAA,IACvD,CAAC,QAAQ;AAAA,EACX;AAGA,QAAM,YAAYA;AAAA,IAChB,MACE,WACI,qBAAqB;AAAA,MACnB,OAAO;AAAA,MACP;AAAA,MACA,QAAQ;AAAA,MACR,WAAW;AAAA,MACX;AAAA,IACF,CAAC,IACD;AAAA,IACN,CAAC,UAAU,QAAQ,cAAc,iBAAiB,eAAe;AAAA,EACnE;AAUA,QAAM,CAAC,WAAW,YAAY,IAAIE,UAA4D,CAAC,CAAC;AAChG,QAAM,oBAAoBD,aAAY,CAAC,YAA0C;AAC/E,iBAAa,CAAC,YAAY;AACxB,UAAI,OAAiE;AACrE,iBAAW,UAAU,SAAS;AAC5B,YAAI,OAAO,SAAS,gBAAgB,CAAC,OAAO,WAAY;AACxD,cAAM,WAAW,QAAQ,OAAO,EAAE;AAClC,YACE,UAAU,UAAU,OAAO,WAAW,SACtC,SAAS,WAAW,OAAO,WAAW,QACtC;AACA;AAAA,QACF;AACA,iBAAS,EAAE,GAAG,QAAQ;AACtB,aAAK,OAAO,EAAE,IAAI,EAAE,OAAO,OAAO,WAAW,OAAO,QAAQ,OAAO,WAAW,OAAO;AAAA,MACvF;AACA,aAAO,QAAQ;AAAA,IACjB,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,QAAM,cAAcD,SAAQ,MAAM;AAChC,UAAM,SAAS,WAAW,SAAS;AACnC,QAAI,UAAU;AACd,UAAM,OAAO,OAAO,IAAI,CAAC,SAAS;AAChC,YAAM,WAAW,UAAU,KAAK,EAAE;AAClC,UAAI,CAAC,SAAU,QAAO;AACtB,gBAAU;AACV,aAAO,EAAE,GAAG,MAAM,SAAS;AAAA,IAC7B,CAAC;AACD,WAAO,UAAU,OAAO;AAAA,EAC1B,GAAG,CAAC,WAAW,SAAS,CAAC;AAWzB,QAAM,UAAUA,SAAQ,MAAM;AAC5B,QAAI,QAAQ;AACZ,QAAI,SAAS;AACb,eAAW,QAAQ,OAAO,OAAO,SAAS,GAAG;AAC3C,UAAI,KAAK,QAAQ,MAAO,SAAQ,KAAK;AACrC,UAAI,KAAK,SAAS,OAAQ,UAAS,KAAK;AAAA,IAC1C;AACA,QAAI,UAAU,KAAK,WAAW,EAAG,QAAO;AACxC,WAAO,GAAG,KAAK,MAAM,QAAQ,CAAC,CAAC,IAAI,KAAK,MAAM,SAAS,CAAC,CAAC;AAAA,EAC3D,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,YAAY,GAAG,YAAY,KAAK,OAAO;AAE7C,QAAM,SAAiC,iBAAiB;AAAA,IACtD,OAAO;AAAA,IACP,OAAO,WAAW,SAAS;AAAA,IAC3B,cAAc;AAAA,IACd;AAAA,EACF,CAAC;AAKD,QAAM,QAAgCA;AAAA,IACpC,MACE,WACI,qBAAqB;AAAA,MACnB,OAAO;AAAA,MACP;AAAA,MACA,QAAQ;AAAA,MACR,WAAW;AAAA,MACX;AAAA,MACA,aAAa,OAAO;AAAA,IACtB,CAAC,IACD;AAAA,IACN,CAAC,UAAU,QAAQ,cAAc,iBAAiB,iBAAiB,OAAO,WAAW;AAAA,EACvF;AAEA,QAAM,kBAAkBA;AAAA,IACtB,MAAO,QAAQ,eAAe,OAAO,QAAQ,SAAS,IAAI;AAAA,IAC1D,CAAC,OAAO,QAAQ,SAAS;AAAA,EAC3B;AAGA,QAAM,CAAC,OAAO,QAAQ,IAAIE,UAA+B,uBAAuB;AAChF,QAAM,WAAWC,QAAyB,CAAC,CAAC;AAC5C,WAAS,UAAU,OAAO,SAAS,CAAC;AAEpC,QAAM,kBAAkBF,aAAY,CAAC,QAAyB,SAAyB;AACrF,UAAM,WAAW,oBAAI,IAAY;AACjC,eAAW,QAAQ,SAAS,SAAS;AACnC,UAAI,KAAK,WAAW,KAAK,MAAM,KAAK,WAAW,KAAK,GAAI,UAAS,IAAI,KAAK,EAAE;AAAA,IAC9E;AACA,aAAS,EAAE,YAAY,KAAK,IAAI,iBAAiB,SAAS,CAAC;AAAA,EAC7D,GAAG,CAAC,CAAC;AACL,QAAM,kBAAkBA,aAAY,MAAM,SAAS,uBAAuB,GAAG,CAAC,CAAC;AAG/E,QAAM,CAAC,YAAY,aAAa,IAAIC,UAAkC,IAAI;AAC1E,QAAM,WAAW,eAAe;AAYhC,QAAM,iBAAiBC,QAA4B,IAAI;AAEvD,QAAM,cAAcF;AAAA,IAClB,CAAC,QAAiC,eAAoC,SAAS;AAC7E,UAAI,CAAC,OAAQ;AACb,qBAAe,UAAU;AACzB,oBAAc,MAAM;AAAA,IACtB;AAAA,IACA,CAAC;AAAA,EACH;AAGA,QAAM,iBAAiBD,SAAQ,MAAM;AACnC,QAAI,CAAC,WAAY,QAAO,CAAC;AACzB,QAAI,WAAW,SAAS,WAAY,QAAO,CAAC,WAAW,EAAE;AACzD,UAAM,QAAQ,OAAO,SAAS,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,OAAO,WAAW,EAAE;AACpE,QAAI,CAAC,KAAM,QAAO,CAAC;AACnB,WAAO,KAAK,WAAW,KAAK,SAAS,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,QAAQ,KAAK,MAAM;AAAA,EAChF,GAAG,CAAC,YAAY,KAAK,CAAC;AAGtB,QAAM,gBAAgBC;AAAA,IACpB,CAAC,YAAyD;AAGxD,YAAM,YAAY,SAAS,QAAqB,WAAW,GAAG,QAAQ;AACtE,UAAI,CAAC,UAAW,QAAO;AACvB,aAAO,OAAO,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,SAAS,IACpD,EAAE,MAAM,cAAc,IAAI,UAAU,IACpC,EAAE,MAAM,YAAY,IAAI,UAAU;AAAA,IACxC;AAAA,IACA,CAAC,iBAAiB,KAAK;AAAA,EACzB;AAWA,QAAM,mBAAmBA;AAAA,IACvB,CAAC,OAA2B,WAA6C;AACvE,UAAI,CAAC,OAAQ,QAAO;AACpB,UAAI,MAAM,WAAW,MAAM,WAAW,MAAM,OAAQ,QAAO;AAO3D,UAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAAK;AAC9C;AAAA,UACE,iBAAiB,SAAS,OAAO,QAAQ,gBAAgB,OAAO,OAAO,KAAK,OAAO;AAAA,QACrF;AACA,eAAO;AAAA,MACT;AAEA,UAAI,MAAM,QAAQ,OAAO,MAAM,QAAQ,IAAK,QAAO;AACnD,YAAM,eAAe;AAErB,YAAM,SAAU,MAAM,QAA+B,cAAc;AACnE,kBAAY,QAAQ,kBAAkB,cAAc,cAAc,MAAM,IAAI,IAAI;AAChF,aAAO;AAAA,IACT;AAAA,IACA,CAAC,iBAAiB,gBAAgB,WAAW;AAAA,EAC/C;AAEA,QAAM,gBAAgBA;AAAA,IACpB,CAAC,UAA8C;AAC7C,YAAM,UAAU,MAAM;AAEtB,UAAI,SAAS,QAAQ,2CAA2C,EAAG;AAGnE,YAAM,gBAAgB,QAAQ,SAAS,QAAqB,WAAW,CAAC;AACxE,WAAK,MAAM,QAAQ,WAAW,MAAM,QAAQ,QAAQ,CAAC,cAAe;AACpE,uBAAiB,OAAO,cAAc,OAAO,CAAC;AAAA,IAChD;AAAA,IACA,CAAC,kBAAkB,aAAa;AAAA,EAClC;AAUA,QAAM,gBAAgBA;AAAA,IACpB,CAAC,QAAgB,UAA8B;AAC7C,UAAI,iBAAiB,OAAO,EAAE,MAAM,cAAc,IAAI,OAAO,CAAC,EAAG,OAAM,gBAAgB;AAAA,IACzF;AAAA,IACA,CAAC,gBAAgB;AAAA,EACnB;AAEA,QAAM,aAAaA;AAAA,IACjB,CAAC,MAAmC,aAAqB;AACvD,uBAAiB,EAAE,MAAM,SAAS,CAAwB;AAC1D,oBAAc,IAAI;AAAA,IACpB;AAAA,IACA,CAAC,cAAc;AAAA,EACjB;AAIA,EAAAG,WAAU,MAAM;AACd,QAAI,CAAC,cAAc,CAAC,MAAO;AAC3B,UAAM,aACJ,WAAW,SAAS,aAChB,MAAM,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,WAAW,EAAE,IAC9C,MAAM,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,WAAW,EAAE;AACpD,QAAI,CAAC,WAAY,eAAc,IAAI;AAAA,EACrC,GAAG,CAAC,YAAY,KAAK,CAAC;AAEtB,MAAI,SAAS;AACX,WACE,gBAAAN;AAAA,MAAC;AAAA;AAAA,QACC,aAAU;AAAA,QACV,cAAW;AAAA,QACX,WAAWO,IAAG,+BAA+B,SAAS;AAAA,QACrD,GAAG;AAAA,QAEJ,0BAAAP,KAAC,cAAW,MAAK,WAAU,OAAO,EAAE,qBAAqB,GAAG;AAAA;AAAA,IAC9D;AAAA,EAEJ;AAEA,MAAI,CAAC,SAAS,MAAM,MAAM,WAAW,GAAG;AACtC,WACE,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,aAAU;AAAA,QACV,cAAW;AAAA,QACX,WAAWO,IAAG,+BAA+B,SAAS;AAAA,QACrD,GAAG;AAAA,QAEJ,0BAAAP;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,OAAO,EAAE,mBAAmB;AAAA,YAC5B,aAAa,EAAE,uBAAuB;AAAA;AAAA,QACxC;AAAA;AAAA,IACF;AAAA,EAEJ;AAEA,QAAM,aACJ,gBAAAC;AAAA,IAAC;AAAA;AAAA,MAKC,OAAO;AAAA,MACP,MAAM;AAAA,MACN,cAAc,CAAC,SAAS;AACtB,YAAI,CAAC,KAAM,eAAc,IAAI;AAAA,MAC/B;AAAA,MAEA;AAAA,wBAAAD,KAAC,uBAAoB,SAAO,MAC1B,0BAAAC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,aAAU;AAAA,YACV,qBAAkB;AAAA,YAClB,SAAS,MAAM;AACb,kBAAI,SAAU;AACd,0BAAY,mBAAmB,EAAE,MAAM,YAAY,IAAI,MAAM,MAAM,CAAC,EAAG,GAAG,CAAC;AAAA,YAC7E;AAAA,YAEA;AAAA,8BAAAD,KAAC,UAAO,eAAY,QAAO,WAAU,UAAS;AAAA,cAC7C,EAAE,oBAAoB;AAAA;AAAA;AAAA,QACzB,GACF;AAAA,QACA,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAM;AAAA,YACN,aAAU;AAAA,YAKV,kBAAkB,CAAC,UAAU;AAC3B,oBAAM,UAAU,eAAe;AAC/B,6BAAe,UAAU;AACzB,kBAAI,CAAC,QAAS;AACd,oBAAM,eAAe;AACrB,sBAAQ;AAAA,YACV;AAAA,YAEC,yBAAe,IAAI,CAAC,UAAUQ,WAC7B,gBAAAP,MAAC,SACE;AAAA,cAAAO,SAAQ,IAAI,gBAAAR,KAAC,yBAAsB,IAAK;AAAA,cACzC,gBAAAA,KAAC,qBAAmB,oBAAS;AAAA,cAC5B,4BAA4B,IAAI,CAAC,SAChC,gBAAAA;AAAA,gBAAC;AAAA;AAAA,kBAMC,cAAY,EAAE,mCAAmC,IAAI,GAAG,EAAE,SAAS,CAAC;AAAA,kBACpE,UAAU,MAAM,WAAW,MAAM,QAAQ;AAAA,kBAExC,uCAA6B,IAAI;AAAA;AAAA,gBAR7B;AAAA,cASP,CACD;AAAA,iBAfO,QAgBV,CACD;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EACF;AAiBF,QAAM,wBAAwB,MAAM,aACjC,OAAO,CAAC,QAAQ,IAAI,mBAAmB,UAAU,EACjD,IAAI,CAAC,QAAQ,IAAI,KAAK,EACtB,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;AACpC,QAAM,0BAA0B,MAAM,eACnC,OAAO,CAAC,QAAQ,IAAI,mBAAmB,UAAU,EACjD,IAAI,CAAC,QAAQ,GAAG,IAAI,MAAM,WAAM,IAAI,MAAM,EAAE,EAC5C,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;AACpC,QAAM,mBAAmB;AAAA,IACvB,EAAE,kCAAkC;AAAA,MAClC,OAAO,MAAM,eAAe;AAAA,MAC5B,OAAO,MAAM,eAAe;AAAA,IAC9B,CAAC;AAAA,IACD,sBAAsB,SAAS,IAC3B,EAAE,qCAAqC,EAAE,OAAO,sBAAsB,KAAK,IAAI,EAAE,CAAC,IAClF;AAAA,IACJ,EAAE,mCAAmC;AAAA,MACnC,OAAO,MAAM,eAAe;AAAA,MAC5B,OAAO,MAAM,eAAe;AAAA,IAC9B,CAAC;AAAA,IACD,wBAAwB,SAAS,IAC7B,EAAE,uCAAuC,EAAE,OAAO,wBAAwB,KAAK,IAAI,EAAE,CAAC,IACtF;AAAA,EACN,EACG,OAAO,CAAC,SAAyB,QAAQ,IAAI,CAAC,EAC9C,KAAK,QAAK;AAEb,MAAI,WAAW;AACb,WACE,gBAAAC;AAAA,MAAC;AAAA;AAAA,QACC,aAAU;AAAA,QACV,aAAU;AAAA,QACV,WAAWM,IAAG,iCAAiC,SAAS;AAAA,QACvD,GAAG;AAAA,QAEJ;AAAA,0BAAAP;AAAA,YAAC;AAAA;AAAA,cACC,aAAU;AAAA,cACV,MAAK;AAAA,cACL,aAAU;AAAA,cACV,WAAU;AAAA,cAET;AAAA;AAAA,UACH;AAAA,UACA,gBAAAA,KAAC,SAAI,WAAU,iCAAiC,sBAAW;AAAA,UAC3D,gBAAAC,MAAC,SAAM,aAAU,8BACf;AAAA,4BAAAD,KAAC,gBACE,YAAE,+BAA+B,EAAE,QAAQ,MAAM,gBAAgB,YAAY,EAAE,CAAC,GACnF;AAAA,YACA,gBAAAA,KAAC,eACC,0BAAAC,MAAC,YACC;AAAA,8BAAAD,KAAC,aAAU,OAAM,OAAO,YAAE,4BAA4B,GAAE;AAAA,cACxD,gBAAAA,KAAC,aAAU,OAAM,OAAO,YAAE,wBAAwB,GAAE;AAAA,cACpD,gBAAAA,KAAC,aAAU,OAAM,OAAO,gBAAM,iBAAgB;AAAA,cAC9C,gBAAAA,KAAC,aAAU,OAAM,OAAO,YAAE,0BAA0B,GAAE;AAAA,cACtD,gBAAAA,KAAC,aAAU,OAAM,OAAO,YAAE,yBAAyB,GAAE;AAAA,eACvD,GACF;AAAA,YACA,gBAAAA,KAAC,aACE,gBAAM,aAAa,IAAI,CAAC,QACvB,gBAAAC;AAAA,cAAC;AAAA;AAAA,gBAEC,kBAAgB,IAAI;AAAA,gBAIpB,cAAY,IAAI,mBAAmB,aAAa,aAAa;AAAA,gBAE7D;AAAA,kCAAAD,KAAC,aAAW,cAAI,OAAM;AAAA,kBACtB,gBAAAA,KAAC,aAAW,cAAI,MAAK;AAAA,kBACrB,gBAAAA,KAAC,aAAU,WAAU,gBAClB,cAAI,iBACD,GAAG,IAAI,YAAY,SAAM,IAAI,cAAc,KAC3C,IAAI,cACV;AAAA,kBACA,gBAAAA,KAAC,aAAU,WAAU,gBAAgB,cAAI,eAAe,GAAE;AAAA,kBAC1D,gBAAAA,KAAC,aAAW,6BAAmB,IAAI,cAAc,GAAE;AAAA;AAAA;AAAA,cAf9C,IAAI;AAAA,YAgBX,CACD,GACH;AAAA,aACF;AAAA,UACA,gBAAAC,MAAC,SAAM,aAAU,gCACf;AAAA,4BAAAD,KAAC,gBACE,YAAE,iCAAiC;AAAA,cAClC,QAAQ,MAAM,gBAAgB,YAAY;AAAA,YAC5C,CAAC,GACH;AAAA,YACA,gBAAAA,KAAC,eACC,0BAAAC,MAAC,YACC;AAAA,8BAAAD,KAAC,aAAU,OAAM,OAAO,YAAE,wBAAwB,GAAE;AAAA,cACpD,gBAAAA,KAAC,aAAU,OAAM,OAAO,YAAE,sBAAsB,GAAE;AAAA,cAClD,gBAAAA,KAAC,aAAU,OAAM,OAAO,YAAE,yBAAyB,GAAE;AAAA,cACrD,gBAAAA,KAAC,aAAU,OAAM,OAAO,gBAAM,iBAAgB;AAAA,cAC9C,gBAAAA,KAAC,aAAU,OAAM,OAAO,YAAE,yBAAyB,GAAE;AAAA,eACvD,GACF;AAAA,YACA,gBAAAA,KAAC,aACE,gBAAM,eAAe,IAAI,CAAC,QACzB,gBAAAC;AAAA,cAAC;AAAA;AAAA,gBAEC,kBAAgB,IAAI;AAAA,gBACpB,cAAY,IAAI,mBAAmB,aAAa,aAAa;AAAA,gBAE7D;AAAA,kCAAAD,KAAC,aAAW,cAAI,QAAO;AAAA,kBACvB,gBAAAA,KAAC,aAAW,cAAI,QAAO;AAAA,kBACvB,gBAAAA,KAAC,aAAW,cAAI,OAAM;AAAA,kBACtB,gBAAAA,KAAC,aAAU,WAAU,gBAClB,cAAI,iBACD,GAAG,IAAI,YAAY,SAAM,IAAI,cAAc,KAC3C,IAAI,cACV;AAAA,kBACA,gBAAAA,KAAC,aAAW,6BAAmB,IAAI,cAAc,GAAE;AAAA;AAAA;AAAA,cAZ9C,IAAI;AAAA,YAaX,CACD,GACH;AAAA,aACF;AAAA;AAAA;AAAA,IACF;AAAA,EAEJ;AAEA,SACE,gBAAAC;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,aAAU;AAAA,MACV,kBAAgB;AAAA,MAChB,WAAWM,IAAG,+BAA+B,SAAS;AAAA,MACtD,WAAW;AAAA,MACV,GAAG;AAAA,MAEJ;AAAA,wBAAAP;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,MAAK;AAAA,YACL,aAAU;AAAA,YACV,WAAU;AAAA,YAET;AAAA;AAAA,QACH;AAAA,QACA,gBAAAA,KAAC,0BAAuB,OAAO,OAC7B,0BAAAA,KAAC,4BAAyB,OAAO,eAC/B,0BAAAC;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,YACP,OAAO,MAAM;AAAA,YACb,WAAW;AAAA,YACX,WAAW;AAAA,YASX,YAAY,GAAG,SAAS,KAAK,SAAS,KAAK,OAAO,UAAU;AAAA,YAC5D,mBAAmB;AAAA,YACnB,SAAS;AAAA,YACT,gBAAgB;AAAA,YAChB,kBAAkB;AAAA,YAMlB,gBAAgB;AAAA,YAChB,WAAW;AAAA,YACX,cAAY;AAAA,YACZ,aAAa,CAAC,QAAQ,SACpB;AAAA,cACE,iBAAiB,SAAS,cAAc,gBAAgB,OAAO,KAAK,KAChE,OACA,EAAE,MAAM,YAAY,IAAI,KAAK,GAAG;AAAA,YACtC;AAAA,YAEF,aAAa,CAAC,QAAQ,SACpB;AAAA,cACE,iBAAiB,SAAS,gBAAgB,gBAAgB,OAAO,KAAK,KAClE,OACA,EAAE,MAAM,cAAc,IAAI,KAAK,GAAG;AAAA,YACxC;AAAA,YAEF,kBAAkB;AAAA,YAClB,kBAAkB;AAAA,YAClB,mBAAmB,CAAC,OAAO,SAAS;AAClC,oBAAM,eAAe;AACrB,0BAAY,EAAE,MAAM,YAAY,IAAI,KAAK,GAAG,CAAC;AAAA,YAC/C;AAAA,YACA,mBAAmB,CAAC,OAAO,SAAS;AAClC,oBAAM,eAAe;AACrB,0BAAY,EAAE,MAAM,cAAc,IAAI,KAAK,GAAG,CAAC;AAAA,YACjD;AAAA,YACA,aAAa,MAAM,eAAe,IAAI;AAAA,YACtC,eAAe;AAAA,YAKf;AAAA,8BAAAD,KAAC,gBAAa,UAAS,eAAc;AAAA,cACpC,cAAc,gBAAAA,KAAC,eAAY,UAAQ,MAAC,UAAQ,MAAC,IAAK;AAAA;AAAA;AAAA,QACrD,GACF,GACF;AAAA,QAEA,gBAAAC;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAU;AAAA,YAET;AAAA,2BACC,gBAAAD;AAAA,gBAAC;AAAA;AAAA,kBACC,SAAQ;AAAA,kBACR,MAAK;AAAA,kBACL,QAAQ,MAAM;AAAA,kBACd,QAAQ,MAAM;AAAA,kBACd,OAAO,MAAM;AAAA,kBACb,WAAU;AAAA;AAAA,cACZ,IAEA,gBAAAA,KAAC,UAAK;AAAA,cAER,gBAAAA,KAAC,SAAI,WAAU,uBAAuB,sBAAW;AAAA;AAAA;AAAA,QACnD;AAAA;AAAA;AAAA,EACF;AAEJ;AAaA,SAAS,cAAc,SAAkC;AACvD,QAAM,SAAS,QAAQ,QAAqB,WAAW,GAAG,QAAQ;AAClE,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,QAAQ,QAAQ,QAAqB,iCAAiC;AAC5E,QAAM,QACJ,QAAQ,QAAQ,CAAC,GAAG,MAAM,iBAA8B,eAAe,IAAI,IAAI,CAAC,IAAI,CAAC;AACvF,QAAMQ,SAAQ,MAAM,QAAQ,OAAO;AACnC,QAAM,OAAO,QAAQ,QAAqB,2BAA2B;AAErE,SAAO,MAAM;AACX,QAAI,QAAQ,aAAa;AACvB,cAAQ,MAAM;AACd;AAAA,IACF;AACA,QAAI,UAAU,MAAM;AAClB,WAAK,cAA2B,aAAa,IAAI,OAAO,MAAM,CAAC,IAAI,GAAG,MAAM;AAC5E;AAAA,IACF;AACA,QAAI,QAAQA,UAAS,KAAK,OAAO,aAAa;AAC5C,YAAM,iBAA8B,eAAe,IAAI,IAAI,EAAEA,MAAK,GAAG,MAAM;AAAA,IAC7E;AAAA,EACF;AACF;AAGA,IAAM,cAAwC,CAAC;AAC/C,IAAM,cAAwC,CAAC;AAoB/C,SAAS,eACP,OACA,QACA,WACA;AACA,QAAM,OAAO,IAAI,IAAI,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;AAChE,QAAM,aAAa,MAAM,MAAM,IAAI,CAAC,SAAS;AAC3C,UAAM,UAAU,KAAK,IAAI,KAAK,EAAE;AAChC,QAAI,CAAC,QAAS,QAAO;AACrB,WAAO;AAAA,MACL,GAAG;AAAA,MACH,UAAU,QAAQ;AAAA,MAClB,gBAAgB,QAAQ;AAAA,MACxB,gBAAgB,QAAQ;AAAA,IAC1B;AAAA,EACF,CAAC;AAED,QAAM,YAAY,cAAc,QAAQ,cAAc,OAAO,MAAM;AACnE,QAAM,aAAa,cAAc,MAAM,MAAM;AAG7C,QAAM,OAAO,cAAc,QAAQ,cAAc,OAAO,KAAK;AAC7D,SAAO,WAAW,KAAK,CAAC,GAAG,MAAM;AAC/B,UAAM,QAAQ,QAAQ,EAAE,SAAS,SAAS,IAAI,EAAE,SAAS,SAAS;AAClE,QAAI,UAAU,EAAG,QAAO;AACxB,UAAM,SAAS,EAAE,SAAS,UAAU,IAAI,EAAE,SAAS,UAAU;AAC7D,QAAI,WAAW,EAAG,QAAO;AACzB,WAAO,EAAE,GAAG,cAAc,EAAE,EAAE;AAAA,EAChC,CAAC;AACH;;;AOx7BA,SAAS,YAAY,eAAAC,cAAa,aAAkC;AACpE,SAAS,gBAAgB;AACzB,SAAS,UAAAC,SAAQ,OAAO,QAAQ,QAAQ,aAAAC,kBAAiB;AACzD,SAAS,MAAAC,WAAU;;;ACqCnB,IAAM,yBAAyB;AAC/B,IAAM,oBAAoB;AAC1B,IAAM,uBAAuB;AAO7B,SAAS,kBAAkB,OAAe,UAA0B;AAClE,SAAO,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,QAAQ,CAAC;AACjD;AAUO,SAAS,uBACd,iBACA,OAA+B,CAAC,GACT;AACvB,QAAM,gBAAgB,KAAK,iBAAiB;AAC5C,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,cAAc,KAAK,eAAe;AACxC,QAAM,QAAQ;AAGd,MAAI,UAAU,KAAK,kBAAkB,OAAO,CAAC,KAAK,eAAe;AAC/D,WAAO,EAAE,YAAY,GAAG,OAAO,EAAE;AAAA,EACnC;AAIA,MAAI,kBAAkB,OAAO,WAAW,IAAI,eAAe;AACzD,WAAO,EAAE,YAAY,aAAa,OAAO,EAAE;AAAA,EAC7C;AAEA,MAAI,KAAK;AACT,MAAI,KAAK;AACT,MAAI,OAAO;AAEX,WAAS,OAAO,GAAG,QAAQ,UAAU,QAAQ,GAAG;AAC9C,UAAM,OAAO,KAAK,MAAM;AACxB,QAAI,kBAAkB,OAAO,GAAG,KAAK,eAAe;AAClD,aAAO;AACP,WAAK;AAAA,IACP,OAAO;AACL,WAAK;AAAA,IACP;AACA,QAAI,SAAS,UAAU;AACrB,aAAO,EAAE,YAAY,MAAM,OAAO,KAAK;AAAA,IACzC;AAAA,EACF;AAIA,SAAO,EAAE,YAAY,MAAM,OAAO,SAAS;AAC7C;;;ADxBI,SAsFQ,OAAAC,MAtFR,QAAAC,aAAA;AArEJ,IAAM,QAAQ,CAAC,IAAI,IAAI,IAAI,GAAG;AAS9B,IAAM,aAAa;AACnB,IAAM,aAAa;AAGnB,SAAS,kBAAkB,SAAyB;AAClD,UAAS,UAAU,eAAe,aAAa,cAAe;AAChE;AAWA,IAAM,oBAAoB;AAE1B,SAAS,UAAU,UAA0B;AAC3C,SAAO,KAAK,MAAM,WAAW,GAAG;AAClC;AAEA,SAAS,YAAY,SAAyB;AAC5C,SAAO,UAAU;AACnB;AAwBA,SAAS,KAAK;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,gBAAgB,kBAAkB,OAAO;AAC/C,SACE,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,SAAS,MAAM,SAAS,OAAO;AAAA,MAC/B,OAAO,EAAE,MAAM,GAAG,aAAa,IAAI;AAAA,MACnC,WAAWC;AAAA,QACT;AAAA,QACA,iBAAiB,IACb,kBACA,iBAAiB,MACf,sBACA;AAAA,QACN,UAAU;AAAA,MACZ;AAAA,MAEC;AAAA;AAAA,QAAQ;AAAA;AAAA;AAAA,EACX;AAEJ;AAEO,IAAM,sBAAsB;AAAA,EACjC,SAASC,qBACP;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,oBAAoB;AAAA,IACpB;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,GACA,KACA;AACA,UAAM,EAAE,EAAE,IAAIC,WAAU;AACxB,UAAM,eAAe,MAAM;AAC3B,UAAM,UAAU,MAAM;AAEtB,UAAM,oBAAoB,UAAU,YAAY,UAAU;AAC1D,UAAM,eAAe,UAAU,YAAY,KAAK;AAEhD,UAAM,uBAAuBC;AAAA,MAC3B,CAAC,YAAoB,oBAAoB,EAAE,YAAY,YAAY,OAAO,EAAE,CAAC;AAAA,MAC7E,CAAC,mBAAmB;AAAA,IACtB;AACA,UAAM,kBAAkBA;AAAA,MACtB,CAAC,YAAoB,oBAAoB,EAAE,OAAO,YAAY,OAAO,EAAE,CAAC;AAAA,MACxE,CAAC,mBAAmB;AAAA,IACtB;AAEA,UAAM,aAAaA,aAAY,MAAM;AACnC,YAAM,kBAAkB,MAAM,WAAW,SAAS,aAAa;AAC/D,YAAM,SAAS,uBAAuB,iBAAiB;AAAA,QACrD,eAAe;AAAA,MACjB,CAAC;AACD,0BAAoB;AAAA,QAClB,YAAY,OAAO;AAAA,QACnB,OAAO,KAAK,IAAI,GAAG,OAAO,aAAa,iBAAiB;AAAA,MAC1D,CAAC;AAAA,IACH,GAAG,CAAC,MAAM,WAAW,QAAQ,aAAa,YAAY,mBAAmB,mBAAmB,CAAC;AAM7F,UAAM,gBAAgB,GAAG,EAAE,gDAAgD;AAAA,MACzE,OAAO,aAAa;AAAA,IACtB,CAAC,CAAC,SAAM,EAAE,2CAA2C,EAAE,OAAO,aAAa,MAAM,CAAC,CAAC;AAEnF,WACE,gBAAAJ;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,aAAU;AAAA,QACV,MAAK;AAAA,QACL,cAAY,SAAS,EAAE,mCAAmC;AAAA,QAC1D,WAAWC,IAAG,uBAAuB,SAAS;AAAA,QAC7C,GAAG;AAAA,QAEJ;AAAA,0BAAAD;AAAA,YAAC;AAAA;AAAA,cACC,aAAU;AAAA,cACV,MAAK;AAAA,cACL,mBAAiB,GAAG,YAAY;AAAA,cAChC,WAAU;AAAA,cAEV;AAAA,gCAAAA,MAAC,SAAI,WAAU,2CACb;AAAA,kCAAAD,KAAC,SAAM,IAAI,GAAG,YAAY,UAAU,SAAS,cAAc,WAAU,aAClE,YAAE,wCAAwC,GAC7C;AAAA,kBACA,gBAAAC,MAAC,UAAK,WAAU,gDACb;AAAA;AAAA,oBAAkB;AAAA,qBACrB;AAAA,mBACF;AAAA,gBACA,gBAAAD;AAAA,kBAAC;AAAA;AAAA,oBACC,IAAI;AAAA,oBACJ,KAAK;AAAA,oBACL,KAAK;AAAA,oBACL,MAAM;AAAA,oBACN,OAAO,CAAC,iBAAiB;AAAA,oBACzB,eAAe,CAAC,CAAC,IAAI,MAAM,qBAAqB,QAAQ,iBAAiB;AAAA,oBACzE,cAAY,EAAE,wCAAwC;AAAA;AAAA,gBACxD;AAAA,gBACA,gBAAAA,KAAC,SAAI,WAAU,gBACZ,gBAAM,IAAI,CAAC,YACV,gBAAAA;AAAA,kBAAC;AAAA;AAAA,oBAEC;AAAA,oBACA,QAAQ,sBAAsB;AAAA,oBAC9B,UAAU;AAAA;AAAA,kBAHL;AAAA,gBAIP,CACD,GACH;AAAA;AAAA;AAAA,UACF;AAAA,UAEA,gBAAAC;AAAA,YAAC;AAAA;AAAA,cACC,aAAU;AAAA,cACV,MAAK;AAAA,cACL,mBAAiB,GAAG,OAAO;AAAA,cAC3B,WAAU;AAAA,cAEV;AAAA,gCAAAA,MAAC,SAAI,WAAU,2CACb;AAAA,kCAAAD,KAAC,SAAM,IAAI,GAAG,OAAO,UAAU,SAAS,SAAS,WAAU,aACxD,YAAE,mCAAmC,GACxC;AAAA,kBACA,gBAAAC,MAAC,UAAK,WAAU,gDAAgD;AAAA;AAAA,oBAAa;AAAA,qBAAC;AAAA,mBAChF;AAAA,gBACA,gBAAAD;AAAA,kBAAC;AAAA;AAAA,oBACC,IAAI;AAAA,oBACJ,KAAK;AAAA,oBACL,KAAK;AAAA,oBACL,MAAM;AAAA,oBACN,OAAO,CAAC,YAAY;AAAA,oBACpB,eAAe,CAAC,CAAC,IAAI,MAAM,gBAAgB,QAAQ,YAAY;AAAA,oBAC/D,cAAY,EAAE,mCAAmC;AAAA;AAAA,gBACnD;AAAA,gBACA,gBAAAA,KAAC,SAAI,WAAU,gBACZ,gBAAM,IAAI,CAAC,YACV,gBAAAA;AAAA,kBAAC;AAAA;AAAA,oBAEC;AAAA,oBACA,QAAQ,iBAAiB;AAAA,oBACzB,UAAU;AAAA;AAAA,kBAHL;AAAA,gBAIP,CACD,GACH;AAAA;AAAA;AAAA,UACF;AAAA,UAEA,gBAAAC;AAAA,YAAC;AAAA;AAAA,cACC,aAAU;AAAA,cACV,WAAU;AAAA,cAEV;AAAA,gCAAAA,MAAC,SAAI,WAAU,2BACb;AAAA,kCAAAD;AAAA,oBAAC;AAAA;AAAA,sBACC,IAAI,GAAG,YAAY;AAAA,sBACnB,SAAS,YAAY,UAAU;AAAA,sBAC/B,iBAAiB,CAAC,YAAY,oBAAoB,EAAE,QAAQ,QAAQ,CAAC;AAAA;AAAA,kBACvE;AAAA,kBACA,gBAAAA,KAAC,SAAM,SAAS,GAAG,YAAY,WAAW,WAAU,aACjD,YAAE,oCAAoC,GACzC;AAAA,mBACF;AAAA,gBACA,gBAAAC;AAAA,kBAACK;AAAA,kBAAA;AAAA,oBACC,MAAK;AAAA,oBACL,SAAQ;AAAA,oBACR,MAAK;AAAA,oBACL,SAAS;AAAA,oBACT,aAAU;AAAA,oBAEV;AAAA,sCAAAN,KAAC,YAAS,eAAY,QAAO;AAAA,sBAC5B,EAAE,kCAAkC;AAAA;AAAA;AAAA,gBACvC;AAAA;AAAA;AAAA,UACF;AAAA,UAEA,gBAAAA;AAAA,YAAC;AAAA;AAAA,cACC,aAAU;AAAA,cACV,MAAK;AAAA,cACL,aAAU;AAAA,cACV,WAAU;AAAA,cAET;AAAA;AAAA,UACH;AAAA;AAAA;AAAA,IACF;AAAA,EAEJ;AACF;;;AExPA,SAAS,cAAAO,aAAY,eAAAC,cAAa,SAAAC,QAAO,YAAAC,iBAAqC;AAC9E,SAAS,MAAM,gBAAgB;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAAC;AAAA,OACK;AACP,SAAS,MAAAC,WAAU;AA+IX,SAOE,OAAAC,MAPF,QAAAC,aAAA;AAnHR,IAAM,yBAAmD;AAAA,EACvD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,IAAM,gCAA0D;AAChE,IAAM,kCAA4D;AAAA,EAChE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,IAAM,sBAAgD;AAAA,EACpD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,oBAAoBC;AAAA,EAC/B,SAASC,mBACP;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,GACA,KACA;AACA,UAAM,EAAE,EAAE,IAAIC,WAAU;AACxB,UAAM,SAASC,OAAM;AACrB,UAAM,SAASA,OAAM;AAErB,UAAM,CAAC,oBAAoB,qBAAqB,IAAIC,UAAS,aAAa;AAC1E,UAAM,SAAS,cAAc;AAC7B,UAAM,YAAYC;AAAA,MAChB,CAAC,SAAkB;AACjB,YAAI,eAAe,OAAW,uBAAsB,IAAI;AACxD,yBAAiB,IAAI;AAAA,MACvB;AAAA,MACA,CAAC,YAAY,cAAc;AAAA,IAC7B;AAEA,UAAM,WAAW,UAAU;AAC3B,UAAM,gBAAgB,UAAU;AAEhC,UAAM,cAAc,gBAAgB,sBAAsB;AAC1D,UAAM,cAAc,gBAChB,sBACA,SACE,gCACA;AAEN,UAAM,oBAAoBA;AAAA,MACxB,CAAC,SAAiB;AAChB,YAAI,CAAC,KAAM;AACX,cAAM,YAAY;AAClB,sBAAc,SAAS;AACvB,YAAI,cAAc,iBAAiB,CAAC,oBAAoB,OAAO,IAAI,GAAG;AACpE,yBAAe,EAAE,MAAM,UAAU,MAAM,SAAS,CAAC;AAAA,QACnD,WAAW,cAAc,eAAe,oBAAoB,OAAO,IAAI,GAAG;AACxE,yBAAe,EAAE,MAAM,YAAY,MAAM,WAAW,CAAC;AAAA,QACvD;AAAA,MACF;AAAA,MACA,CAAC,eAAe,gBAAgB,OAAO,IAAI;AAAA,IAC7C;AAEA,UAAM,mBAAmBA;AAAA,MACvB,CAAC,UAAkB;AACjB,cAAM,OAAO;AACb,uBAAe,SAAS,EAAE,MAAM,MAAM,MAAM,KAAK,IAAI,EAAE,MAAM,KAAK,CAAC;AAAA,MACrE;AAAA,MACA,CAAC,gBAAgB,MAAM;AAAA,IACzB;AACA,UAAM,mBAAmBA;AAAA,MACvB,CAAC,UAAkB;AACjB,cAAM,OAAO;AACb,uBAAe,SAAS,EAAE,MAAM,MAAM,MAAM,KAAK,IAAI,EAAE,MAAM,KAAK,CAAC;AAAA,MACrE;AAAA,MACA,CAAC,gBAAgB,MAAM;AAAA,IACzB;AAEA,UAAM,mBAAmBA;AAAA,MACvB,CAAC,YAAqB;AACpB,kBAAU,OAAO;AAGjB,YAAI,WAAW,OAAO,SAAS,OAAO,MAAM;AAC1C,yBAAe,EAAE,MAAM,OAAO,KAAK,CAAC;AAAA,QACtC;AAAA,MACF;AAAA,MACA,CAAC,WAAW,OAAO,MAAM,OAAO,MAAM,cAAc;AAAA,IACtD;AAEA,WACE,gBAAAN;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,aAAU;AAAA,QACV,MAAK;AAAA,QACL,cAAY,SAAS,EAAE,iCAAiC;AAAA,QACxD,WAAWO,IAAG,uBAAuB,SAAS;AAAA,QAC7C,GAAG;AAAA,QAEJ;AAAA,0BAAAP;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,OAAO;AAAA,cACP,eAAe;AAAA,cACf,cAAY,EAAE,iCAAiC;AAAA,cAE/C;AAAA,gCAAAD,KAAC,mBAAgB,OAAM,aACpB,YAAE,qCAAqC,GAC1C;AAAA,gBACA,gBAAAA,KAAC,mBAAgB,OAAM,eACpB,YAAE,uCAAuC,GAC5C;AAAA,gBACA,gBAAAA,KAAC,mBAAgB,OAAM,UAAU,YAAE,kCAAkC,GAAE;AAAA;AAAA;AAAA,UACzE;AAAA,UAMA,gBAAAC,MAAC,SAAI,WAAU,wBACb;AAAA,4BAAAA;AAAA,cAAC;AAAA;AAAA,gBACC,aAAU;AAAA,gBACV,WAAU;AAAA,gBAEV;AAAA,kCAAAD,KAAC,WAAM,SAAS,QAAQ,WAAU,mCAC/B,YAAE,gCAAgC,GACrC;AAAA,kBACA,gBAAAC,MAAC,UAAO,OAAO,OAAO,MAAM,eAAe,kBAAkB,UAAU,UACrE;AAAA,oCAAAD,KAAC,iBAAc,IAAI,QAAQ,cAAY,EAAE,gCAAgC,GACvE,0BAAAA,KAAC,eAAY,GACf;AAAA,oBACA,gBAAAA,KAAC,iBACE,sBAAY,IAAI,CAAC,WAChB,gBAAAA,KAAC,cAAwB,OAAO,QAC7B,0BAAgB,MAAM,KADR,MAEjB,CACD,GACH;AAAA,qBACF;AAAA;AAAA;AAAA,YACF;AAAA,YAEA,gBAAAA;AAAA,cAAC;AAAA;AAAA,gBACC,SAAS;AAAA,gBACT,iBAAiB;AAAA,gBACjB,UAAU;AAAA,gBACV,cACE,SACI,EAAE,kCAAkC,IACpC,EAAE,mCAAmC;AAAA,gBAE3C,aAAU;AAAA,gBAET,mBAAS,gBAAAA,KAAC,QAAK,eAAY,QAAO,IAAK,gBAAAA,KAAC,YAAS,eAAY,QAAO;AAAA;AAAA,YACvE;AAAA,YAEA,gBAAAC;AAAA,cAAC;AAAA;AAAA,gBACC,aAAU;AAAA,gBACV,WAAU;AAAA,gBAEV;AAAA,kCAAAD,KAAC,WAAM,SAAS,QAAQ,WAAU,mCAC/B,YAAE,gCAAgC,GACrC;AAAA,kBACA,gBAAAC,MAAC,UAAO,OAAO,OAAO,MAAM,eAAe,kBAAkB,UAAU,UACrE;AAAA,oCAAAD,KAAC,iBAAc,IAAI,QAAQ,cAAY,EAAE,gCAAgC,GACvE,0BAAAA,KAAC,eAAY,GACf;AAAA,oBACA,gBAAAA,KAAC,iBACE,sBAAY,IAAI,CAAC,WAChB,gBAAAA,KAAC,cAAwB,OAAO,QAC7B,0BAAgB,MAAM,KADR,MAEjB,CACD,GACH;AAAA,qBACF;AAAA;AAAA;AAAA,YACF;AAAA,aACF;AAAA;AAAA;AAAA,IACF;AAAA,EAEJ;AACF;;;AChPA,OAAoC;AACpC,SAAS,oBAAoB;AAC7B,SAAS,kBAA2C;AACpD,SAAS,aAAAS,kBAAiB;AAC1B,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB;AA+EjB,gBAAAC,MAgFK,QAAAC,aAhFL;AArDT,SAAS,kBACP,OACA,MAIiC;AACjC,MAAI,OAAO,MAAO,QAAO;AACzB,MAAI,OAAO,MAAO,QAAO;AACzB,SAAO;AACT;AAGA,SAAS,iBACP,cACA,OACA,QACQ;AACR,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,iBAAiB,KAAK;AAAA,IAC/B,KAAK;AACH,aAAO,aAAa,OAAO,EAAE,OAAO,WAAW,uBAAuB,EAAE,CAAC;AAAA,IAC3E,KAAK;AAAA,IACL;AACE,aAAO,aAAa,KAAK;AAAA,EAC7B;AACF;AASA,SAAS,YACP,QACA,SACA,QACA,GACA,cACA;AACA,MAAI,CAAC,UAAU,OAAO,WAAW,EAAG,QAAO;AAC3C,QAAM,QAAQ,OAAO,CAAC;AACtB,QAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,QAAM,QAAQ,EAAE,6BAA6B;AAAA,IAC3C;AAAA,IACA,SAAS,OAAO;AAAA,IAChB,WAAW,EAAE,kBAAkB,OAAO,IAAI,CAAC;AAAA,IAC3C,OAAO,iBAAiB,cAAc,OAAO,MAAM;AAAA,IACnD,MAAM,iBAAiB,cAAc,MAAM,MAAM;AAAA,EACnD,CAAC;AACD,SAAO,gBAAAD,KAAC,aAAU,QAAgB,OAAc;AAClD;AAEO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA,GAAG;AACL,GAAyB;AACvB,QAAM,EAAE,GAAG,aAAa,IAAIE,WAAU;AAEtC,QAAM,iBAAiB,gBAAgB,QAAQ,gBAAgB;AAC/D,QAAM,kBAAkB,EAAE,6CAA6C;AAEvE,SACE,gBAAAF,KAAC,SAAI,aAAU,qBAAoB,WAAuB,GAAG,OAC3D,0BAAAC,MAAC,cAAW,SAAS,GAAG,SACtB;AAAA,oBAAAD;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,wBAAwB;AAAA,QACjC,OAAO,KAAK;AAAA,QACZ,iBAAiB;AAAA,QACjB,QAAQ,YAAY,QAAQ,OAAO,EAAE,wBAAwB,GAAG,SAAS,GAAG,YAAY;AAAA;AAAA,IAC1F;AAAA,IACA,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,yBAAyB;AAAA,QAClC,OAAO,KAAK;AAAA,QACZ,iBAAiB;AAAA,QACjB,QAAQ;AAAA,UACN,QAAQ;AAAA,UACR,EAAE,yBAAyB;AAAA,UAC3B;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA;AAAA,IACF;AAAA,IACA,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,2BAA2B;AAAA,QACpC,OAAO,KAAK;AAAA,QACZ,iBAAiB;AAAA,QACjB,QAAQ;AAAA,UACN,QAAQ;AAAA,UACR,EAAE,2BAA2B;AAAA,UAC7B;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA;AAAA,IACF;AAAA,IACA,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,mCAAmC;AAAA,QAC5C,OAAO,iBAAiB,KAAK,gBAAgB;AAAA,QAC7C,iBAAiB;AAAA,QACjB,QAAQ;AAAA,UACN,QAAQ;AAAA,UACR,EAAE,mCAAmC;AAAA,UACrC;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA;AAAA,IACF;AAAA,IACA,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,6BAA6B;AAAA,QACtC,OAAO,KAAK;AAAA,QACZ,aAAY;AAAA,QACZ,iBAAiB;AAAA,QACjB,QAAQ;AAAA,UACN,QAAQ;AAAA,UACR,EAAE,6BAA6B;AAAA,UAC/B;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA;AAAA,IACF;AAAA,IACA,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,8BAA8B;AAAA,QACvC,OACE,iBACG,cAED,gBAAAC;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAU;AAAA,YAEV;AAAA,8BAAAD,KAAC,gBAAa,eAAY,QAAO,WAAU,UAAS;AAAA,cACnD,EAAE,yCAAyC;AAAA;AAAA;AAAA,QAC9C;AAAA,QAGJ,aAAa,iBAAiB,YAAY;AAAA,QAC1C,aACE;AAAA;AAAA;AAAA,UAGE,gBAAAA,KAAC,UAAK,eAAY,QAAO,WAAU,aAChC,2BACH;AAAA,YAEA;AAAA,QAGJ,iBAAiB;AAAA,QACjB,QACE,iBACI;AAAA,UACE,QAAQ;AAAA,UACR,EAAE,8BAA8B;AAAA,UAChC;AAAA,UACA;AAAA,UACA;AAAA,QACF,IACA;AAAA;AAAA,IAER;AAAA,KACF,GACF;AAEJ;;;ACxLA,SAAS,eAAAG,cAAa,aAAAC,YAAW,WAAAC,UAAS,UAAAC,SAAQ,YAAAC,iBAAgB;;;ACZlE,SAASC,YAAW,MAAgC;AAClD,QAAM,WAAW,IAAI,MAAc,KAAK,OAAO,MAAM;AACrD,WAAS,IAAI,GAAG,IAAI,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC9C,aAAS,CAAC,IAAK,KAAK,OAAO,CAAC,EAA2B;AAAA,EACzD;AACA,SAAO;AACT;AAWA,SAAS,UAAU,MAAsB,KAAwB;AAC/D,QAAM,aAAa,KAAK;AACxB,MAAI,eAAe,UAAa,OAAO,OAAO,YAAY,GAAG,EAAG,QAAO,CAAC,WAAW,GAAG,CAAC;AAEvF,QAAM,SAAoB,CAAC;AAC3B,QAAM,OAAO,oBAAI,IAAa;AAC9B,aAAW,SAAS,KAAK,QAAQ;AAC/B,UAAM,QAAQ,QAAQ,aAAa,MAAM,WAAW,MAAM,aAAa,GAAG;AAC1E,QAAI,UAAU,OAAW;AACzB,QAAI,KAAK,IAAI,KAAK,EAAG;AACrB,SAAK,IAAI,KAAK;AACd,WAAO,KAAK,KAAK;AAAA,EACnB;AACA,SAAO;AACT;AAGA,SAAS,QAAQ,MAAe,OAAgB,aAA+B;AAC7E,MAAI,OAAO,SAAS,YAAY,OAAO,UAAU,UAAU;AACzD,WAAO,cAAc,OAAO,QAAQ,OAAO;AAAA,EAC7C;AACA,MAAI,OAAO,SAAS,YAAY,OAAO,UAAU,UAAU;AACzD,WAAO,cAAc,OAAO,QAAQ,OAAO;AAAA,EAC7C;AACA,SAAO;AACT;AAEA,SAAS,iBACP,MACA,MACS;AACT,QAAM,SAAS,UAAU,MAAM,KAAK,GAAG;AACvC,UAAQ,KAAK,IAAI;AAAA,IACf,KAAK;AACH,aAAO,OAAO,KAAK,CAAC,UAAU,OAAO,GAAG,OAAO,KAAK,KAAK,CAAC;AAAA;AAAA;AAAA,IAG5D,KAAK;AACH,aAAO,CAAC,OAAO,KAAK,CAAC,UAAU,OAAO,GAAG,OAAO,KAAK,KAAK,CAAC;AAAA,IAC7D,KAAK;AACH,aAAO,OAAO,KAAK,CAAC,UAAU,QAAQ,OAAO,KAAK,OAAO,IAAI,CAAC;AAAA,IAChE,KAAK;AACH,aAAO,OAAO,KAAK,CAAC,UAAU,QAAQ,OAAO,KAAK,OAAO,KAAK,CAAC;AAAA,IACjE,KAAK,MAAM;AACT,UAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,EAAG,QAAO;AACvC,YAAM,UAAU,KAAK;AACrB,aAAO,OAAO,KAAK,CAAC,UAAU,QAAQ,KAAK,CAAC,cAAc,OAAO,GAAG,OAAO,SAAS,CAAC,CAAC;AAAA,IACxF;AAAA,IACA;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,WAAW,MAAsB,UAA6B,MAA2B;AAChG,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AACH,aAAO,SAAS,SAAS,KAAK,QAAQ;AAAA,IACxC,KAAK;AACH,aAAO,CAAC,SAAS,SAAS,KAAK,QAAQ;AAAA,IACzC,KAAK;AACH,aAAO,SAAS,CAAC,MAAM,KAAK;AAAA,IAC9B,KAAK;AACH,aAAO,SAAS,SAAS,SAAS,CAAC,MAAM,KAAK;AAAA,IAChD,KAAK,YAAY;AACf,UAAI,KAAK,WAAW,MAAM;AACxB,iBAAS,IAAI,GAAG,IAAI,IAAI,SAAS,QAAQ,KAAK,GAAG;AAC/C,cAAI,SAAS,CAAC,MAAM,KAAK,KAAK,SAAS,IAAI,CAAC,MAAM,KAAK,EAAG,QAAO;AAAA,QACnE;AACA,eAAO;AAAA,MACT;AACA,YAAM,QAAQ,SAAS,QAAQ,KAAK,CAAC;AACrC,UAAI,QAAQ,EAAG,QAAO;AACtB,aAAO,SAAS,QAAQ,KAAK,GAAG,QAAQ,CAAC,KAAK;AAAA,IAChD;AAAA,IACA,KAAK;AACH,aAAO,iBAAiB,MAAM,IAAI;AAAA,IACpC,KAAK,YAAY;AACf,UAAI,KAAK,QAAQ,UAAa,KAAK,QAAQ,OAAW,QAAO;AAE7D,UAAI,CAAC,OAAO,SAAS,KAAK,QAAQ,EAAG,QAAO;AAC5C,UAAI,KAAK,QAAQ,UAAa,KAAK,WAAW,KAAK,IAAK,QAAO;AAC/D,UAAI,KAAK,QAAQ,UAAa,KAAK,WAAW,KAAK,IAAK,QAAO;AAC/D,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AACH,aAAO,KAAK,IAAI,SAAS,UAAU,QAAQ,CAAC;AAAA,IAC9C,KAAK;AACH,aAAO,KAAK,IAAI,SAAS,KAAK,MAAM;AAAA,IACtC;AACE,aAAO;AAAA,EACX;AACF;AAQO,SAAS,mBAAmB,MAAsB,OAAuC;AAC9F,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,WAAWA,YAAW,IAAI;AAChC,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,WAAW,MAAM,UAAU,IAAI,EAAG,QAAO;AAAA,EAChD;AACA,SAAO;AACT;AAUO,SAAS,oBAAoB,KAAa,OAA6C;AAC5F,QAAM,aAAa,gBAAgB,GAAG;AACtC,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,QAA0B,CAAC;AACjC,MAAI,SAAS;AACb,aAAW,QAAQ,WAAW,OAAO;AACnC,QAAI,CAAC,mBAAmB,MAAM,KAAK,EAAG;AACtC,UAAM,KAAK,IAAI;AACf,cAAU,KAAK,OAAO;AAAA,EACxB;AACA,SAAO,EAAE,OAAO,QAAQ,EAAE,OAAO,MAAM,QAAQ,OAAO,EAAE;AAC1D;AAaO,SAAS,UAAU,KAAe,OAAwC;AAC/E,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,QAAQ,oBAAoB,KAAK,KAAK,EAAE,MAAO,MAAK,IAAI,KAAK,MAAM;AAE9E,QAAM,WAAqB,EAAE,QAAQ,IAAI,OAAO,OAAO,CAAC,QAAQ,KAAK,IAAI,KAAK,MAAM,CAAC,EAAE;AACvF,MAAI,IAAI,mBAAmB,QAAW;AACpC,UAAM,iBAA0D,CAAC;AACjE,eAAW,UAAU,OAAO,KAAK,IAAI,cAAc,GAAG;AACpD,UAAI,KAAK,IAAI,MAAM,GAAG;AACpB,uBAAe,MAAM,IAAI,IAAI,eAAe,MAAM;AAAA,MACpD;AAAA,IACF;AACA,aAAS,iBAAiB;AAAA,EAC5B;AACA,SAAO;AACT;;;ACzKA,SAAS,cAAc,YAAgE;AACrF,SAAO,GAAG,WAAW,MAAM,GAAG,kBAAkB,GAAG,WAAW,MAAM;AACtE;AASO,SAAS,eACd,WACA,UACoB;AACpB,QAAM,qBAAqB,IAAI;AAAA,IAC7B,SAAS,WAAW,IAAI,CAAC,aAAa,CAAC,SAAS,IAAI,QAAQ,CAAC;AAAA,EAC/D;AACA,QAAM,sBAAsB,IAAI;AAAA,IAC9B,SAAS,YAAY,IAAI,CAAC,eAAe,CAAC,cAAc,UAAU,GAAG,UAAU,CAAC;AAAA,EAClF;AAEA,QAAM,qBAA+B,CAAC;AACtC,QAAM,aAAa,UAAU,WAAW,IAAI,CAAC,aAAa;AACxD,UAAM,WAAW,mBAAmB,IAAI,SAAS,EAAE;AACnD,QAAI,SAAU,QAAO;AACrB,uBAAmB,KAAK,SAAS,EAAE;AACnC,WAAO;AAAA,MACL,GAAG;AAAA,MACH,WAAW;AAAA,MACX,OAAO;AAAA,MACP,SAAS;AAAA,MACT,OAAO;AAAA,MACP,UAAU,mBAAmB;AAAA,IAC/B;AAAA,EACF,CAAC;AAED,QAAM,sBAAgC,CAAC;AACvC,QAAM,cAAc,UAAU,YAAY,IAAI,CAAC,eAAe;AAC5D,UAAM,MAAM,cAAc,UAAU;AACpC,UAAM,WAAW,oBAAoB,IAAI,GAAG;AAC5C,QAAI,SAAU,QAAO;AACrB,wBAAoB,KAAK,GAAG;AAC5B,WAAO;AAAA,MACL,GAAG;AAAA,MACH,OAAO;AAAA,MACP,WAAW;AAAA,MACX,UAAU,mBAAmB;AAAA,IAC/B;AAAA,EACF,CAAC;AAED,QAAM,QAAW;AAAA,IACf,GAAG;AAAA,IACH;AAAA,IACA;AAAA,IACA,iBAAiB,SAAS;AAAA,IAC1B,eAAe,SAAS;AAAA,IACxB,QAAQ,SAAS;AAAA,EACnB;AAEA,SAAO,EAAE,OAAO,oBAAoB,oBAAoB;AAC1D;;;ACtEO,SAAS,qBAAqB,SAAsD;AACzF,MAAI;AACF,QAAI,QAAQ,SAAS,YAAY;AAC/B,aAAO;AAAA,QACL,IAAI,QAAQ;AAAA,QACZ,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,OAAO,cAAc,QAAQ,KAAK,QAAQ,WAAW,CAAC,CAAC;AAAA,MACzD;AAAA,IACF;AACA,WAAO,EAAE,IAAI,QAAQ,IAAI,IAAI,MAAM,MAAM,YAAY,UAAU,gBAAgB,QAAQ,GAAG,EAAE;AAAA,EAC9F,SAAS,OAAO;AACd,WAAO,EAAE,IAAI,QAAQ,IAAI,IAAI,OAAO,OAAO,SAAS,KAAK,EAAE;AAAA,EAC7D;AACF;AAEA,SAAS,SAAS,OAAwB;AACxC,MAAI,iBAAiB,MAAO,QAAO,MAAM;AACzC,SAAO,OAAO,KAAK;AACrB;AASA,SAAS,gBAAyB;AAChC,QAAM,QAAQ;AACd,QAAM,cAAc,MAAM;AAC1B,MAAI,OAAO,gBAAgB,WAAY,QAAO;AAC9C,SAAO,MAAM,gBAAgB;AAC/B;AAEA,IAAI,cAAc,GAAG;AACnB,QAAM,QAAQ;AAId,QAAM,iBAAiB,WAAW,CAAC,UAAwB;AACzD,UAAM,YAAY,qBAAqB,MAAM,IAA4B,CAAC;AAAA,EAC5E,CAAC;AACH;;;ACGA,SAAS,sBAA+B;AACtC,SAAO,OAAO,WAAW,eAAe,OAAO,QAAQ;AACzD;AAQO,SAAS,oBAAoB,UAAsC,CAAC,GAAwB;AACjG,QAAM,YAAY,QAAQ;AAC1B,MAAI,SAAS,QAAQ,gBAAgB,QAAS,cAAc,UAAa,CAAC,oBAAoB;AAC9F,MAAI,aAAa;AACjB,MAAI;AACJ,MAAI,SAAS;AACb,QAAM,UAAU,oBAAI,IAAqB;AAGzC,WAAS,UAAgB;AACvB,aAAS;AACT,UAAM,QAAQ,CAAC,GAAG,QAAQ,OAAO,CAAC;AAClC,YAAQ,MAAM;AACd,QAAI,WAAW,QAAW;AACxB,YAAM,QAAQ;AACd,eAAS;AACT,UAAI;AACF,cAAM,UAAU;AAAA,MAClB,QAAQ;AAAA,MAER;AAAA,IACF;AACA,eAAW,SAAS,MAAO,OAAM,OAAO,qBAAqB,MAAM,OAAO,CAAC;AAAA,EAC7E;AAEA,WAAS,UAAU,OAAsB;AACvC,UAAM,WAAY,MAA6B;AAC/C,QAAI,aAAa,UAAa,OAAO,SAAS,OAAO,SAAU;AAC/D,UAAM,QAAQ,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAI,UAAU,OAAW;AACzB,YAAQ,OAAO,SAAS,EAAE;AAC1B,UAAM,OAAO,QAAQ;AAAA,EACvB;AAEA,WAAS,eAA8C;AACrD,QAAI,UAAU,WAAY,QAAO;AACjC,QAAI,WAAW,OAAW,QAAO;AACjC,QAAI;AACF,eACE,cAAc,SACT,IAAI,OAAO,IAAI,IAAI,uBAAuB,YAAY,GAAG,GAAG;AAAA,QAC3D,MAAM;AAAA,MACR,CAAC,IACD,UAAU;AAAA,IAClB,QAAQ;AACN,cAAQ;AACR,aAAO;AAAA,IACT;AACA,WAAO,iBAAiB,WAAW,SAAS;AAC5C,WAAO,iBAAiB,SAAS,OAAO;AACxC,WAAO,iBAAiB,gBAAgB,OAAO;AAC/C,WAAO;AAAA,EACT;AAEA,WAAS,KAAK,OAA6E;AACzF,QAAI,WAAY,QAAO,QAAQ,OAAO,IAAI,MAAM,2BAA2B,CAAC;AAC5E,cAAU;AACV,UAAM,UAAU,MAAM,MAAM;AAC5B,UAAM,SAAS,aAAa;AAC5B,QAAI,WAAW,QAAW;AAExB,aAAO,QAAQ,QAAQ,EAAE,KAAK,MAAM,qBAAqB,OAAO,CAAC;AAAA,IACnE;AACA,WAAO,IAAI,QAA+B,CAAC,SAAS,WAAW;AAC7D,cAAQ,IAAI,QAAQ,IAAI,EAAE,SAAS,QAAQ,QAAQ,CAAC;AACpD,UAAI;AACF,eAAO,YAAY,OAAO;AAAA,MAC5B,QAAQ;AACN,gBAAQ,OAAO,QAAQ,EAAE;AACzB,gBAAQ;AACR,YAAI;AACF,kBAAQ,qBAAqB,OAAO,CAAC;AAAA,QACvC,SAAS,OAAO;AACd,iBAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAAA,QAClE;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,WAAS,OAAU,UAAiC,MAA2C;AAC7F,QAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,SAAS,KAAK;AAChD,WAAO,KAAK,QAAQ;AAAA,EACtB;AAEA,SAAO;AAAA,IACL,MAAM,SAAS,KAAK,iBAAiB;AACnC,YAAM,WAAW,MAAM;AAAA,QAAK,CAAC,OAC3B,oBAAoB,SAChB,EAAE,IAAI,MAAM,YAAY,IAAI,IAC5B,EAAE,IAAI,MAAM,YAAY,KAAK,SAAS,gBAAgB;AAAA,MAC5D;AACA,aAAO,OAAO,UAAU,CAAC,OAAQ,GAA+B,KAAK;AAAA,IACvE;AAAA,IACA,MAAM,SAAS,KAAK;AAClB,YAAM,WAAW,MAAM,KAAK,CAAC,QAAQ,EAAE,IAAI,MAAM,YAAY,IAAI,EAAE;AACnE,aAAO,OAAO,UAAU,CAAC,OAAQ,GAA+B,QAAQ;AAAA,IAC1E;AAAA,IACA,YAAY;AACV,mBAAa;AACb,YAAM,QAAQ,CAAC,GAAG,QAAQ,OAAO,CAAC;AAClC,cAAQ,MAAM;AACd,iBAAW,SAAS,OAAO;AACzB,cAAM,OAAO,EAAE,IAAI,MAAM,QAAQ,IAAI,IAAI,OAAO,OAAO,4BAA4B,CAAC;AAAA,MACtF;AACA,UAAI,WAAW,QAAW;AACxB,cAAM,QAAQ;AACd,iBAAS;AACT,YAAI;AACF,gBAAM,UAAU;AAAA,QAClB,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAAA,IACA,IAAI,SAAkB;AACpB,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AJ3BA,IAAM,sBAA0C;AAAA,EAC9C,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,eAAe;AACjB;AAEA,IAAM,iBAA4C,EAAE,MAAM,YAAY,MAAM,WAAW;AAGvF,IAAM,2BAA2B;AAEjC,IAAM,cAA4B;AAAA,EAChC,YAAY,CAAC;AAAA,EACb,aAAa,CAAC;AAAA,EACd,iBAAiB,CAAC;AAAA,EAClB,eAAe,CAAC;AAAA,EAChB,QAAQ,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,EAAE;AAC7C;AAOA,SAAS,eAAe,KAAgC;AACtD,SAAO,EAAE,OAAO,cAAc,GAAG,GAAG,UAAU,gBAAgB,GAAG,EAAE;AACrE;AAcA,SAAS,sBAAsB,YAAmC;AAChE,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,QAAQ,WAAW,OAAO;AACnC,SAAK,IAAI,WAAW,KAAK,OAAO,IAAI,CAAC,UAAU,MAAM,QAAQ,CAAC,CAAC;AAAA,EACjE;AACA,SAAO,KAAK;AACd;AAuCA,SAAS,gBACP,WACA,iBACA,WACgB;AAChB,QAAM,gBAAgB,cAAc,QAAQ,UAAU,OAAO,SAAS;AAItE,QAAM,aAAaC;AAAA,IACjB,MAAO,cAAc,QAAQ,gBAAgB,OAAO,eAAe,SAAS;AAAA,IAC5E,CAAC,WAAW,aAAa;AAAA,EAC3B;AAMA,QAAM,CAAC,YAAY,aAAa,IAAIC;AAAA,IAClC;AAAA,EACF;AACA,QAAM,CAAC,SAAS,UAAU,IAAIA,UAAS,KAAK;AAC5C,QAAM,eAAeC,QAAO,CAAC;AAE7B,EAAAC,WAAU,MAAM;AAKd,UAAM,YAAa,aAAa,WAAW;AAC3C,QAAI,cAAc,QAAQ,CAAC,eAAe;AAIxC,iBAAW,KAAK;AAChB;AAAA,IACF;AACA,eAAW,IAAI;AACf,UAAM,SAAS,UAAU;AACzB,YAAQ,IAAI,CAAC,OAAO,SAAS,SAAS,GAAG,OAAO,SAAS,SAAS,CAAC,CAAC,EACjE,KAAK,CAAC,CAAC,OAAO,QAAQ,MAAM;AAC3B,UAAI,aAAa,YAAY,UAAW;AACxC,oBAAc,EAAE,KAAK,WAAW,QAAQ,EAAE,OAAO,SAAS,EAAE,CAAC;AAC7D,iBAAW,KAAK;AAAA,IAClB,CAAC,EACA,MAAM,MAAM;AACX,UAAI,aAAa,YAAY,UAAW;AAKxC,oBAAc,EAAE,KAAK,WAAW,QAAQ,eAAe,SAAS,EAAE,CAAC;AACnE,iBAAW,KAAK;AAAA,IAClB,CAAC;AAAA,EAGL,GAAG,CAAC,WAAW,aAAa,CAAC;AAK7B,QAAM,cACJ,eAAe,QAAQ,WAAW,QAAQ,YAAY,WAAW,SAAS;AAa5E,QAAM,kBAAkB,kBAAkB,eAAe,QAAQ,WAAW,QAAQ;AAEpF,SAAO;AAAA,IACL,QAAQ,cAAc,eAAe,EAAE,OAAO,aAAa,UAAU,CAAC,EAAE;AAAA,IACxE,SAAS,WAAW;AAAA,IACpB,SAAS,eAAe,QAAQ,gBAAgB;AAAA,EAClD;AACF;AAOO,SAAS,mBACd,KACA,OAA+B,CAAC,GACN;AAC1B,QAAM,kBAAkB,KAAK,mBAAmB;AAQhD,QAAM,mBAAmBD,QAAO,KAAK,MAAM;AAC3C,mBAAiB,UAAU,KAAK;AAChC,QAAM,YAAYA,QAAsD,IAAI;AAC5E,WAAS,YAAY;AACnB,QAAI,UAAU,YAAY,MAAM;AAC9B,gBAAU,UAAU,oBAAoB,iBAAiB,OAAO;AAAA,IAClE;AACA,WAAO,UAAU;AAAA,EACnB;AACA,EAAAC;AAAA,IACE,MAAM,MAAM;AACV,gBAAU,SAAS,UAAU;AAAA,IAC/B;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,CAAC,aAAa,mBAAmB,IAAIF,UAA6B,OAAO;AAAA,IAC7E,GAAG;AAAA,IACH,GAAG,KAAK;AAAA,EACV,EAAE;AACF,QAAM,CAAC,QAAQ,cAAc,IAAIA,UAAoC,OAAO;AAAA,IAC1E,GAAG;AAAA,IACH,GAAG,KAAK;AAAA,EACV,EAAE;AACF,QAAM,CAAC,OAAO,aAAa,IAAIA,UAAsB,KAAK,SAAS,WAAW;AAC9E,QAAM,CAAC,WAAW,YAAY,IAAIA,UAAkC,IAAI;AACxE,QAAM,CAAC,SAAS,UAAU,IAAIA,UAAyB,CAAC,CAAC;AAEzD,QAAM,iBAAiBG,aAAY,CAAC,SAAsC;AACxE,wBAAoB,CAAC,UAAU,EAAE,GAAG,MAAM,GAAG,KAAK,EAAE;AAAA,EACtD,GAAG,CAAC,CAAC;AACL,QAAM,YAAYA,aAAY,CAAC,SAA6C;AAC1E,mBAAe,CAAC,UAAU,EAAE,GAAG,MAAM,GAAG,KAAK,EAAE;AAAA,EACjD,GAAG,CAAC,CAAC;AAIL,QAAM,WAAWA,aAAY,CAAC,SAAsB,cAAc,IAAI,GAAG,CAAC,CAAC;AAC3E,QAAM,WAAWA,aAAY,CAAC,SAAkC,aAAa,IAAI,GAAG,CAAC,CAAC;AACtF,QAAM,cAAcA,aAAY,CAAC,WAAyB;AACxD,eAAW,CAAC,SAAS,CAAC,GAAG,MAAM,MAAM,CAAC;AAAA,EACxC,GAAG,CAAC,CAAC;AACL,QAAM,cAAcA,aAAY,CAACC,WAAkB;AACjD,eAAW,CAAC,SAAS,KAAK,OAAO,CAAC,GAAG,MAAM,MAAMA,MAAK,CAAC;AAAA,EACzD,GAAG,CAAC,CAAC;AAEL,QAAM,cAAcL;AAAA,IAClB,MAAO,QAAQ,WAAW,IAAI,MAAM,UAAU,KAAK,OAAO;AAAA,IAC1D,CAAC,KAAK,OAAO;AAAA,EACf;AAeA,QAAM,UAAU,gBAAgB;AAChC,QAAM,gBAAgB,gBAAgB,KAAK,iBAAiB,SAAS;AACrE,QAAM,uBAAuB;AAAA,IAC3B,UAAU,OAAO;AAAA,IACjB;AAAA,IACA;AAAA,EACF;AAMA,QAAM,oBACJ,WAAW,CAAC,qBAAqB,UAAU,gBAAgB;AAQ7D,QAAM,UAAU,cAAc,WAAW,qBAAqB;AAE9D,QAAM,YAAYA;AAAA,IAChB,MAAM,cAAc,cAAc,OAAO,OAAO,WAAW;AAAA,IAC3D,CAAC,cAAc,OAAO,OAAO,WAAW;AAAA,EAC1C;AAEA,QAAM,aAAaA;AAAA,IACjB,MAAM,eAAe,WAAW,kBAAkB,OAAO,KAAK;AAAA,IAC9D,CAAC,WAAW,kBAAkB,OAAO,KAAK;AAAA,EAC5C;AAEA,QAAM,QAAQ,WAAW;AAEzB,QAAM,kBAAkBA;AAAA,IACtB,OAAO;AAAA,MACL,YAAY,OAAO;AAAA,QACjB,WAAW,mBAAmB,IAAI,CAAC,OAAO,CAAC,IAAI,UAAmB,CAAC;AAAA,MACrE;AAAA,MACA,aAAa,OAAO;AAAA,QAClB,WAAW,oBAAoB,IAAI,CAAC,QAAQ,CAAC,KAAK,UAAmB,CAAC;AAAA,MACxE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,UAAU,OAAO;AAAA,QACf,QACG,QAAQ,CAAC,WAAY,OAAO,SAAS,YAAY,OAAO,MAAM,CAAC,CAAE,EACjE,IAAI,CAAC,OAAO,CAAC,IAAI,UAAmB,CAAC;AAAA,MAC1C;AAAA,IACF;AAAA,IACA,CAAC,YAAY,OAAO;AAAA,EACtB;AAEA,QAAM,iBAAiBA;AAAA,IACrB,OAAO;AAAA,MACL,YAAY,WAAW,mBAAmB;AAAA,MAC1C,OAAO,WAAW,oBAAoB;AAAA,IACxC;AAAA,IACA,CAAC,UAAU;AAAA,EACb;AAEA,QAAM,SAASA,SAAQ,MAAM,aAAa,WAAW,GAAG,CAAC,WAAW,CAAC;AAMrE,QAAM,OAAOA,SAAQ,MAAM;AACzB,UAAM,aAAa,gBAAgB,WAAW;AAC9C,UAAM,mBAAmB,cAAc,WAAW,MAAM,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC,EAAE;AACtF,WAAO;AAAA,MACL,OAAO,WAAW,OAAO;AAAA,MACzB,QAAQ,WAAW,OAAO;AAAA,MAC1B,UAAU,sBAAsB,UAAU;AAAA,MAC1C;AAAA,MACA,YAAY,OAAO;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,aAAa,MAAM,CAAC;AAExB,SAAO;AAAA,IACL;AAAA,IACA,UAAU,kBAAkB,OAAO;AAAA,IACnC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc,MAAM;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":["index","index","useCallback","useEffect","useMemo","useRef","useState","cn","compareStrings","useMemo","jsx","useMemo","useMemo","snapshot","jsx","jsxs","useMemo","useCallback","useState","useRef","useEffect","cn","index","useCallback","Button","useLocale","cn","jsx","jsxs","cn","AbstractionControls","useLocale","useCallback","Button","forwardRef","useCallback","useId","useState","useLocale","cn","jsx","jsxs","forwardRef","MetricLayerSwitch","useLocale","useId","useState","useCallback","cn","useLocale","jsx","jsxs","useLocale","useCallback","useEffect","useMemo","useRef","useState","sequenceOf","useMemo","useState","useRef","useEffect","useCallback","index"]}
|