@elabs-ai/components-process 4.2.0 → 5.0.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.
Files changed (144) hide show
  1. package/README.md +8 -1
  2. package/dist/core/index.d.ts +801 -3
  3. package/dist/core/index.js +1334 -0
  4. package/dist/core/index.js.map +1 -1
  5. package/dist/index.d.ts +1889 -34
  6. package/dist/index.js +5512 -196
  7. package/dist/index.js.map +1 -1
  8. package/dist/test/index.d.ts +223 -5
  9. package/dist/test/index.js +346 -191
  10. package/dist/test/index.js.map +1 -1
  11. package/package.json +14 -13
  12. package/src/__contract__/case-table.contract.test.tsx +49 -0
  13. package/src/__contract__/compare-kpi-strip.contract.test.tsx +49 -0
  14. package/src/__contract__/conformance-overlay.contract.test.tsx +49 -0
  15. package/src/__contract__/happy-path-editor.contract.test.tsx +49 -0
  16. package/src/__contract__/violation-list.contract.test.tsx +49 -0
  17. package/src/abstraction-controls/abstraction-controls-per-type.test.tsx +80 -0
  18. package/src/abstraction-controls/abstraction-controls.stories.tsx +43 -1
  19. package/src/abstraction-controls/abstraction-controls.tsx +198 -5
  20. package/src/case-table/case-table.stories.tsx +89 -0
  21. package/src/case-table/case-table.test.tsx +148 -0
  22. package/src/case-table/case-table.tsx +144 -0
  23. package/src/case-table/columns.ts +116 -0
  24. package/src/case-table/index.ts +11 -0
  25. package/src/case-timeline/case-timeline-model.test.ts +72 -0
  26. package/src/case-timeline/case-timeline-model.ts +112 -0
  27. package/src/case-timeline/case-timeline.stories.tsx +94 -0
  28. package/src/case-timeline/case-timeline.test.tsx +51 -0
  29. package/src/case-timeline/case-timeline.tsx +109 -0
  30. package/src/case-timeline/index.ts +9 -0
  31. package/src/conformance-overlay/conformance-fixture.ts +59 -0
  32. package/src/conformance-overlay/conformance-legend.tsx +109 -0
  33. package/src/conformance-overlay/conformance-overlay.stories.tsx +116 -0
  34. package/src/conformance-overlay/conformance-overlay.test.tsx +88 -0
  35. package/src/conformance-overlay/conformance-overlay.tsx +107 -0
  36. package/src/conformance-overlay/conformance-state.test.ts +79 -0
  37. package/src/conformance-overlay/conformance-state.ts +220 -0
  38. package/src/conformance-overlay/index.ts +4 -0
  39. package/src/core/activity-color-scale.test.ts +107 -0
  40. package/src/core/activity-color-scale.ts +133 -0
  41. package/src/core/adapters/ocel.test.ts +112 -0
  42. package/src/core/adapters/ocel.ts +359 -0
  43. package/src/core/adapters/xes.test.ts +293 -0
  44. package/src/core/adapters/xes.ts +384 -0
  45. package/src/core/cases-from-log.test.ts +72 -0
  46. package/src/core/cases-from-log.ts +85 -0
  47. package/src/core/conformance.test.ts +80 -0
  48. package/src/core/conformance.ts +91 -0
  49. package/src/core/diff-graphs.test.ts +151 -0
  50. package/src/core/diff-graphs.ts +118 -0
  51. package/src/core/discover-object-centric-graph.test.ts +94 -0
  52. package/src/core/discover-object-centric-graph.ts +296 -0
  53. package/src/core/fixtures/ocel-sample.ts +82 -0
  54. package/src/core/fixtures/sample.xes +68 -0
  55. package/src/core/index.ts +113 -0
  56. package/src/core/reference-model.test.ts +43 -0
  57. package/src/core/reference-model.ts +116 -0
  58. package/src/core/replay-timeline.test.ts +161 -0
  59. package/src/core/replay-timeline.ts +260 -0
  60. package/src/core/segments.test.ts +185 -0
  61. package/src/core/segments.ts +153 -0
  62. package/src/core/token-replay.test.ts +218 -0
  63. package/src/core/token-replay.ts +456 -0
  64. package/src/core/types.ts +2 -2
  65. package/src/dotted-chart/compute-dots.test.ts +176 -0
  66. package/src/dotted-chart/compute-dots.ts +241 -0
  67. package/src/dotted-chart/dotted-chart-labels.ts +93 -0
  68. package/src/dotted-chart/dotted-chart.stories.tsx +182 -0
  69. package/src/dotted-chart/dotted-chart.test.tsx +135 -0
  70. package/src/dotted-chart/dotted-chart.tsx +841 -0
  71. package/src/dotted-chart/index.ts +23 -0
  72. package/src/dotted-chart/use-element-size.ts +33 -0
  73. package/src/happy-path-editor/happy-path-editor-context.ts +81 -0
  74. package/src/happy-path-editor/happy-path-editor.stories.tsx +116 -0
  75. package/src/happy-path-editor/happy-path-editor.test.tsx +142 -0
  76. package/src/happy-path-editor/happy-path-editor.tsx +239 -0
  77. package/src/happy-path-editor/happy-path-step-node.tsx +175 -0
  78. package/src/happy-path-editor/index.ts +4 -0
  79. package/src/index.ts +51 -1
  80. package/src/performance-spectrum/aggregate-segments.test.ts +107 -0
  81. package/src/performance-spectrum/aggregate-segments.ts +174 -0
  82. package/src/performance-spectrum/index.ts +25 -0
  83. package/src/performance-spectrum/performance-spectrum-context.tsx +116 -0
  84. package/src/performance-spectrum/performance-spectrum.stories.tsx +128 -0
  85. package/src/performance-spectrum/performance-spectrum.test.tsx +190 -0
  86. package/src/performance-spectrum/performance-spectrum.tsx +870 -0
  87. package/src/process-compare/compare-kpi-strip.stories.tsx +48 -0
  88. package/src/process-compare/compare-kpi-strip.tsx +94 -0
  89. package/src/process-compare/compare-model.ts +83 -0
  90. package/src/process-compare/compare-side.tsx +42 -0
  91. package/src/process-compare/diff-to-graph.ts +104 -0
  92. package/src/process-compare/index.ts +23 -0
  93. package/src/process-compare/process-compare.stories.tsx +184 -0
  94. package/src/process-compare/process-compare.test.tsx +224 -0
  95. package/src/process-compare/process-compare.tsx +251 -0
  96. package/src/process-explorer.stories.tsx +1 -1
  97. package/src/process-filter-bar/index.ts +2 -0
  98. package/src/process-filter-bar/process-filter-bar.stories.tsx +156 -0
  99. package/src/process-filter-bar/process-filter-bar.test.tsx +201 -0
  100. package/src/process-filter-bar/process-filter-bar.tsx +167 -0
  101. package/src/process-kpi-strip/process-kpi-strip.stories.tsx +47 -0
  102. package/src/process-kpi-strip/process-kpi-strip.test.tsx +67 -0
  103. package/src/process-kpi-strip/process-kpi-strip.tsx +148 -8
  104. package/src/process-map/activity-accent.ts +25 -0
  105. package/src/process-map/index.ts +1 -0
  106. package/src/process-map/map-model.test.ts +16 -0
  107. package/src/process-map/map-model.ts +323 -1
  108. package/src/process-map/object-centric-map.test.tsx +132 -0
  109. package/src/process-map/process-activity-node.tsx +152 -14
  110. package/src/process-map/process-map-object-centric.stories.tsx +219 -0
  111. package/src/process-map/process-map.stories.tsx +64 -0
  112. package/src/process-map/process-map.tsx +240 -16
  113. package/src/process-map/process-transition-edge.test.tsx +47 -0
  114. package/src/process-map/process-transition-edge.tsx +134 -7
  115. package/src/process-map/use-process-layout.ts +30 -9
  116. package/src/process-replay/congestion-heat.tsx +107 -0
  117. package/src/process-replay/index.ts +14 -0
  118. package/src/process-replay/process-replay.stories.tsx +168 -0
  119. package/src/process-replay/process-replay.test.tsx +170 -0
  120. package/src/process-replay/process-replay.tsx +285 -0
  121. package/src/process-replay/replay-controls.tsx +147 -0
  122. package/src/process-replay/replay-format.ts +83 -0
  123. package/src/process-replay/replay-tokens-context.ts +30 -0
  124. package/src/process-replay/use-controllable-value.ts +30 -0
  125. package/src/templates-process-explorer.stories.tsx +1304 -0
  126. package/src/test/contract.test.ts +66 -0
  127. package/src/test/contract.ts +107 -6
  128. package/src/test/doubles.test.tsx +87 -1
  129. package/src/test/doubles.tsx +174 -3
  130. package/src/test/index.ts +25 -1
  131. package/src/use-process-explorer/use-process-explorer.test.ts +44 -0
  132. package/src/use-process-explorer/use-process-explorer.ts +34 -2
  133. package/src/variant-explorer/coverage-bar.tsx +36 -0
  134. package/src/variant-explorer/index.ts +16 -0
  135. package/src/variant-explorer/sequence-chips.tsx +103 -0
  136. package/src/variant-explorer/variant-explorer-model.ts +42 -0
  137. package/src/variant-explorer/variant-explorer.stories.tsx +226 -0
  138. package/src/variant-explorer/variant-explorer.test.tsx +302 -0
  139. package/src/variant-explorer/variant-explorer.tsx +567 -0
  140. package/src/variant-explorer/variant-row.tsx +137 -0
  141. package/src/violation-list/index.ts +2 -0
  142. package/src/violation-list/violation-list.stories.tsx +73 -0
  143. package/src/violation-list/violation-list.test.tsx +84 -0
  144. package/src/violation-list/violation-list.tsx +259 -0
package/dist/index.js.map CHANGED
@@ -1 +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":"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","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"]}
1
+ {"version":3,"sources":["../src/process-map/activity-accent.ts","../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/conformance-overlay/conformance-state.ts","../src/conformance-overlay/conformance-legend.tsx","../src/core/activity-color-scale.ts","../src/core/discover-object-centric-graph.ts","../src/process-map/process-activity-node.tsx","../src/process-map/process-map-context.ts","../src/process-map/process-transition-edge.tsx","../src/process-replay/replay-tokens-context.ts","../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","../src/core/cases-from-log.ts","../src/core/token-replay.ts","../src/core/segments.ts","../src/core/diff-graphs.ts","../src/core/replay-timeline.ts","../src/variant-explorer/variant-explorer.tsx","../src/variant-explorer/variant-explorer-model.ts","../src/variant-explorer/variant-row.tsx","../src/variant-explorer/coverage-bar.tsx","../src/variant-explorer/sequence-chips.tsx","../src/process-filter-bar/process-filter-bar.tsx","../src/case-table/case-table.tsx","../src/case-table/columns.ts","../src/case-timeline/case-timeline.tsx","../src/case-timeline/case-timeline-model.ts","../src/dotted-chart/dotted-chart.tsx","../src/dotted-chart/compute-dots.ts","../src/dotted-chart/dotted-chart-labels.ts","../src/dotted-chart/use-element-size.ts","../src/performance-spectrum/performance-spectrum.tsx","../src/performance-spectrum/aggregate-segments.ts","../src/performance-spectrum/performance-spectrum-context.tsx","../src/conformance-overlay/conformance-overlay.tsx","../src/violation-list/violation-list.tsx","../src/happy-path-editor/happy-path-editor.tsx","../src/happy-path-editor/happy-path-editor-context.ts","../src/happy-path-editor/happy-path-step-node.tsx","../src/process-compare/process-compare.tsx","../src/process-compare/compare-kpi-strip.tsx","../src/process-compare/compare-side.tsx","../src/process-compare/diff-to-graph.ts","../src/process-compare/compare-model.ts","../src/process-replay/process-replay.tsx","../src/process-replay/congestion-heat.tsx","../src/process-replay/replay-format.ts","../src/process-replay/replay-controls.tsx","../src/process-replay/use-controllable-value.ts"],"sourcesContent":["/**\n * How an activity's identity colour is painted (RM-054).\n *\n * The ONE place that turns an {@link ActivityColor} into CSS, shared by\n * `ProcessActivityNode`'s accent swatch and `VariantExplorer`'s sequence chips — so the\n * two views cannot drift into painting the same activity differently. Both endpoints are\n * semantic tokens; no literal colour is authored.\n *\n * The shared \"other\" bucket is a diagonal hatch over a light wash of the same token, so\n * it reads as \"one of many\" in greyscale too, never as a twelfth distinct activity.\n */\nimport type { CSSProperties } from \"react\";\nimport type { ActivityColor } from \"../core/activity-color-scale\";\n\n/** Inline style for an accent swatch painted with `color`. */\nexport function activityAccentStyle(color: ActivityColor): CSSProperties {\n const ink = `var(${color.token})`;\n if (color.pattern === \"other\") {\n return {\n backgroundColor: `color-mix(in oklab, ${ink} 30%, transparent)`,\n backgroundImage: `repeating-linear-gradient(135deg, ${ink} 0 1.5px, transparent 1.5px 3.5px)`,\n };\n }\n return { backgroundColor: ink };\n}\n","/**\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 type { ActivityColor, ActivityColorScale } from \"../core/activity-color-scale\";\nimport type { ConformanceState } from \"../conformance-overlay/conformance-state\";\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 { ObjectCentricGraph } from \"../core/discover-object-centric-graph\";\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 * The activity's identity colour from a shared `ActivityColorScale` (RM-054), painted as\n * a small accent swatch — never the card fill, which would fight the metric reading.\n * Absent when the map was given no `colorScale`.\n */\n accent?: ActivityColor;\n /**\n * Where this activity sits against a reference model (RM-062). Set only when the map\n * was given `conformance`; drives `data-conformance` plus its glyph and dash.\n */\n conformance?: ConformanceState;\n /**\n * Object-centric — RM-066. One entry per object type this activity occurs in, in the\n * graph's type order. Set only in object-centric mode; drives the per-type chips.\n */\n objectTypes?: ProcessObjectTypeCount[];\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 * This transition's composed accessible name ({@link transitionAriaLabel}), duplicated\n * here from the edge object's own `ariaLabel` (#354). React Flow's `EdgeWrapper` reads\n * `ariaLabel` off the edge OBJECT for the outer, non-focusable `<g>` — it never forwards\n * it to the edge COMPONENT via `EdgeProps` (`@xyflow/react`'s `EdgeProps` type has no\n * `ariaLabel` field) — so `ProcessTransitionEdge`, which only receives `data`, has no\n * other channel to reach it. Read this field, never recompute the name locally, or the\n * canvas and the `TableView` twin (`transitionRows`) can drift apart. Always set by\n * {@link buildProcessMapModel} — optional only because it is filled in a step after the\n * rest of this object (it is itself derived from this object).\n */\n ariaLabel?: string;\n /**\n * Where this transition sits against a reference model (RM-062). Set only when the map\n * was given `conformance`; drives `data-conformance` plus its glyph and dash.\n */\n conformance?: ConformanceState;\n /**\n * Object-centric — RM-066. The object type this edge belongs to; per-type edges between\n * the same two activities are drawn side by side (see `parallelIndex`/`parallelCount`).\n */\n objectType?: ProcessObjectTypeMark;\n /** Object-centric — RM-066. This edge's slot among the per-type edges of its pair. */\n parallelIndex?: number;\n /** Object-centric — RM-066. How many per-type edges join this edge's pair. */\n parallelCount?: number;\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 /** Object-centric — RM-066. Per-type counts, as printed in the table twin. */\n objectTypes?: string;\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 /** Object-centric — RM-066. The object type's display name. */\n objectType?: string;\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 * The shared activity colour scale (RM-054). When given, every node carries\n * `data.accent`, so the map and `VariantExplorer` paint an activity identically.\n */\n colorScale?: ActivityColorScale;\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 colorScale,\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 ...(colorScale ? { accent: colorScale.colorFor(activity.id) } : {}),\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 // Computed once, read from two places (#354): React Flow's own `EdgeWrapper` reads\n // `ariaLabel` off THIS edge object for the outer, non-focusable `<g>`; `data.ariaLabel`\n // is what `ProcessTransitionEdge` — which never sees this edge object, only `data` —\n // folds onto the focused label pill. One computation, so the two can never drift.\n data.ariaLabel = transitionAriaLabel(data, resolvedEdgeMetricLabel);\n return {\n id,\n source: transition.source,\n target: transition.target,\n type: \"process-transition\",\n data,\n ariaLabel: data.ariaLabel,\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// ── Object-centric — RM-066 ──────────────────────────────────────────────────\n\n/** How one object type is marked on the map: a name, a text code and a chart colour. */\nexport interface ProcessObjectTypeMark {\n /** The object type's id, as keyed in the `ObjectCentricGraph`. */\n type: string;\n /** Display name. */\n label: string;\n /** Two-character code printed beside every swatch — the non-colour channel. */\n code: string;\n /** A categorical chart token from `objectTypeColorScale` (RM-054's scale, reused). */\n color: ActivityColor;\n}\n\n/** One object type's share of an activity node. */\nexport interface ProcessObjectTypeCount extends ProcessObjectTypeMark {\n instances: number;\n /** Distinct objects of this type the activity touches. */\n cases: number;\n /** The chip's accessible name, e.g. \"order: 2 objects, 3 occurrences\". */\n ariaLabel: string;\n}\n\n/** Every string the object-centric mode composes. Override for a non-English locale. */\nexport interface ObjectCentricMapLabels {\n /** A node chip's accessible name and table text. */\n typeCount: (type: string, objects: number, occurrences: number) => string;\n /** Prefixes a per-type edge's accessible name. */\n edgePrefix: (type: string) => string;\n /** Activity-table column header. */\n columnObjectTypes: string;\n /** Transition-table column header. */\n columnObjectType: string;\n /** The canvas legend's title. */\n legendTitle: string;\n}\n\n/** English defaults for {@link ObjectCentricMapLabels}. */\nexport const OBJECT_CENTRIC_MAP_DEFAULT_LABELS: Readonly<ObjectCentricMapLabels> = Object.freeze({\n typeCount: (type: string, objects: number, occurrences: number) =>\n `${type}: ${objects} ${objects === 1 ? \"object\" : \"objects\"}, ${occurrences} ${\n occurrences === 1 ? \"occurrence\" : \"occurrences\"\n }`,\n edgePrefix: (type: string) => `${type} flow`,\n columnObjectTypes: \"Object types\",\n columnObjectType: \"Object type\",\n legendTitle: \"Object types\",\n});\n\n/** The id of one object type's edge: the merged edge id, the separator, the type. */\nexport function objectCentricEdgeId(source: string, target: string, type: string): string {\n return `${processEdgeId(source, target)}${EDGE_KEY_SEPARATOR}${type}`;\n}\n\n/** Inputs to {@link buildObjectCentricMapModel}. */\nexport interface BuildObjectCentricMapModelOptions extends BuildProcessMapModelOptions {\n /**\n * The object-centric graph `graph` was flattened from (`objectCentricProcessGraph`).\n * Omit — or omit `objectTypeScale` — and this is exactly {@link buildProcessMapModel}.\n */\n objectCentric?: ObjectCentricGraph;\n /** `objectTypeColorScale(objectCentric)`, built once from the FULL graph. */\n objectTypeScale?: ActivityColorScale;\n objectCentricLabels?: ObjectCentricMapLabels;\n}\n\n/**\n * The object-centric process-map model: {@link buildProcessMapModel} over the flattened\n * graph, then\n *\n * - every node gains `data.objectTypes` (one chip per type, each with a text code and a\n * real accessible name folded into the node's own), and\n * - every merged edge is replaced by one edge PER OBJECT TYPE — its weight and printed\n * label read that type's own counts, its label is prefixed with the type's code, and its\n * stroke is the type's chart token. The edge-value colour ramp is never used here.\n *\n * A transition selection may name either a per-type edge or the merged pair; both select\n * the pair's neighbourhood, and only the named per-type edge reads `\"selected\"`.\n */\nexport function buildObjectCentricMapModel({\n objectCentric,\n objectTypeScale,\n objectCentricLabels = OBJECT_CENTRIC_MAP_DEFAULT_LABELS,\n ...options\n}: BuildObjectCentricMapModelOptions): ProcessMapModel {\n if (!objectCentric || !objectTypeScale) return buildProcessMapModel(options);\n const labels = objectCentricLabels;\n const { metric } = options;\n\n const typeEdges = new Map<string, Map<string, TransitionStats>>();\n const mergedIdOf = new Map<string, string>();\n for (const type of objectCentric.objectTypes) {\n for (const transition of objectCentric.transitionsByType[type] ?? []) {\n const mergedId = processEdgeId(transition.source, transition.target);\n mergedIdOf.set(objectCentricEdgeId(transition.source, transition.target, type), mergedId);\n let byType = typeEdges.get(mergedId);\n if (byType === undefined) {\n byType = new Map();\n typeEdges.set(mergedId, byType);\n }\n byType.set(type, transition);\n }\n }\n\n const requested = options.selection;\n const typeSelectionId =\n requested?.kind === \"transition\" && mergedIdOf.has(requested.id) ? requested.id : undefined;\n const model = buildProcessMapModel({\n ...options,\n selection: typeSelectionId\n ? { kind: \"transition\", id: mergedIdOf.get(typeSelectionId) as string }\n : requested,\n });\n\n const marks = new Map<string, ProcessObjectTypeMark>(\n objectCentric.objectTypes.map((type) => [\n type,\n {\n type,\n label: objectTypeScale.labelFor(type),\n code: objectTypeScale.codeFor(type),\n color: objectTypeScale.colorFor(type),\n },\n ]),\n );\n\n const activityById = new Map(objectCentric.activities.map((a) => [a.id, a]));\n const nodes: ProcessMapNode[] = model.nodes.map((node) => {\n const activity = activityById.get(node.id);\n if (!activity) return node;\n const objectTypes: ProcessObjectTypeCount[] = [];\n for (const type of objectCentric.objectTypes) {\n const counts = activity.perType[type];\n const mark = marks.get(type);\n if (!counts || !mark) continue;\n objectTypes.push({\n ...mark,\n instances: counts.instances,\n cases: counts.cases,\n ariaLabel: labels.typeCount(mark.label, counts.cases, counts.instances),\n });\n }\n return {\n ...node,\n data: { ...node.data, objectTypes },\n ariaLabel: [node.ariaLabel, ...objectTypes.map((entry) => entry.ariaLabel)].join(\", \"),\n };\n });\n\n const denominators = new Map(\n objectCentric.objectTypes.map((type) => [\n type,\n processEdgeDenominators(objectCentric.transitionsByType[type] ?? []),\n ]),\n );\n\n const edges: ProcessMapEdge[] = [];\n const values: number[] = [];\n for (const merged of model.edges) {\n const byType = typeEdges.get(merged.id);\n const mergedData = merged.data as ProcessTransitionEdgeData;\n if (!byType) continue;\n let parallelIndex = 0;\n for (const type of objectCentric.objectTypes) {\n const transition = byType.get(type);\n const mark = marks.get(type);\n const typeGraph = objectCentric.graphsByType[type];\n if (!transition || !mark || !typeGraph) continue;\n const typeDenominators = denominators.get(type) as ProcessEdgeDenominators;\n const id = objectCentricEdgeId(transition.source, transition.target, type);\n const value = transitionMetricValue(\n transition,\n metric.edge,\n typeGraph.totals,\n typeDenominators,\n );\n values.push(value);\n const selectionState: ProcessSelectionState =\n typeSelectionId !== undefined &&\n mergedData.selectionState === \"selected\" &&\n typeSelectionId !== id\n ? \"associated\"\n : mergedData.selectionState;\n const data: ProcessTransitionEdgeData = {\n ...mergedData,\n weight: value,\n value,\n label: `${mark.code} ${formatMetricValue(value, metric.edge)}`,\n secondaryLabel:\n metric.secondary === undefined\n ? undefined\n : formatMetricValue(\n transitionMetricValue(\n transition,\n metric.secondary,\n typeGraph.totals,\n typeDenominators,\n ),\n metric.secondary,\n ),\n selectionState,\n objectType: mark,\n parallelIndex,\n parallelCount: byType.size,\n };\n data.ariaLabel = `${labels.edgePrefix(mark.label)}: ${transitionAriaLabel(\n data,\n model.edgeMetricLabel,\n )}`;\n edges.push({\n ...merged,\n id,\n data,\n ariaLabel: data.ariaLabel,\n style: { stroke: `var(${mark.color.token})` },\n });\n parallelIndex += 1;\n }\n }\n\n const edgeDomain = values.length > 0 ? minMax(values) : model.edgeDomain;\n for (const edge of edges) (edge.data as ProcessTransitionEdgeData).valueDomain = edgeDomain;\n\n const activityRows: ProcessActivityRow[] = model.activityRows.map((row, index) => {\n const objectTypes = nodes[index]?.data.objectTypes;\n return objectTypes\n ? {\n ...row,\n objectTypes: objectTypes.map((entry) => entry.ariaLabel).join(\"; \"),\n }\n : row;\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 objectType: data.objectType?.label,\n };\n });\n\n return {\n ...model,\n nodes,\n edges,\n edgeDomain,\n activityRows,\n transitionRows,\n excludedCounts: {\n ...model.excludedCounts,\n transitions: transitionRows.filter((row) => row.selectionState === \"excluded\").length,\n totalTransitions: transitionRows.length,\n },\n };\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 { ActivityColorScale } from \"../core/activity-color-scale\";\nimport type { EventLog, ProcessGraph } from \"../core/types\";\nimport type { ConformanceResult } from \"../core/conformance\";\nimport {\n activityConformance,\n CONFORMANCE_STATE_DEFAULT_LABELS,\n resolveConformanceStates,\n transitionConformance,\n withActivityConformance,\n withTransitionConformance,\n} from \"../conformance-overlay/conformance-state\";\nimport { ConformanceStateMark } from \"../conformance-overlay/conformance-legend\";\nimport {\n abstractObjectCentricGraph,\n objectCentricProcessGraph,\n objectTypeColorScale,\n type ObjectCentricGraph,\n} from \"../core/discover-object-centric-graph\";\nimport {\n buildObjectCentricMapModel,\n OBJECT_CENTRIC_MAP_DEFAULT_LABELS,\n type ObjectCentricMapLabels,\n} from \"./map-model\";\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 * A shared activity colour scale (RM-054, `/core`'s `activityColorScale`). When given,\n * each activity node shows its identity swatch — the same colour `VariantExplorer`\n * paints that activity with. Build it once from the FULL graph and hand the same\n * instance to both views. Omit for today's uncoloured nodes.\n */\n colorScale?: ActivityColorScale;\n /**\n * A replay result (RM-061's `tokenReplay`) to overlay (RM-062). When given, every node\n * and edge carries `data-conformance=\"both\" | \"logOnly\" | \"modelOnly\"` — additive to\n * `data-selection` — painted as a status tone PLUS a glyph and a line style, with the\n * state's word in the accessible name and a Conformance column in the table twin. Omit\n * for today's map, unchanged. Usually reached through `ConformanceOverlay`.\n */\n conformance?: ConformanceResult;\n /**\n * Object-centric — RM-066. An object-centric graph (`discoverObjectCentricGraph`, e.g.\n * over `fromOcel(…).logs`) to draw instead of `graph`/`log` — mutually exclusive with\n * both, and it wins when given together (with a dev-mode console warning). Shared\n * activities merge into one node carrying a chip per object type; edges are drawn once\n * per object type in that type's chart colour, side by side. `abstraction` applies to\n * every type alike; for per-type abstraction pass `abstractObjectCentricGraph(…)`'s\n * result here instead.\n */\n objectCentric?: ObjectCentricGraph;\n /** Object-centric — RM-066. Strings the object-centric mode composes. */\n objectCentricLabels?: ObjectCentricMapLabels;\n /**\n * Accessible name for the canvas region. Defaults to the localized\n * `process.map.label` message.\n */\n label?: string;\n /**\n * The smallest zoom the OPENING fit may use. The default keeps activity names legible\n * and, when the process does not fit at that size, opens on its start. A workspace that\n * would rather open on the whole picture — a wide canvas, a reader who zooms in on what\n * matters — lowers it. The reader can always zoom out further by hand.\n * @default PROCESS_MAP_LEGIBLE_ZOOM\n */\n fitMinZoom?: number;\n /**\n * The fraction of the pane the opening fit keeps clear on every side. The default is\n * generous because transition pills are drawn outside the fitted bounds; a pane that is\n * short on the axis the process runs along can trade some of it for a larger picture.\n * @default 0.15\n */\n fitPadding?: number;\n /**\n * Re-frames the map whenever this value changes. The map already re-fits when its own\n * structure or direction changes; it cannot know that the pane around it just changed\n * shape — a dock opened, a side panel closed. Pass anything that changes when that\n * happens. Unset: no extra re-fits, the reader's viewport is left alone.\n */\n refitKey?: string | number;\n // elkjs adapter — RM-067\n /**\n * The layout engine. `\"elk\"` lays the map out with `@elabs-ai/components-flow`'s\n * `layoutFlowElk` — elkjs, an optional peer, loaded lazily on first use; when it is not\n * installed the map falls back to dagre with a development-only warning. @default \"dagre\"\n */\n layoutEngine?: \"dagre\" | \"elk\";\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/** Object-centric — RM-066. The abstracted graph, its flattened twin and the type scale. */\nfunction useObjectCentricView(\n objectCentric: ObjectCentricGraph | undefined,\n abstraction: Pick<AbstractionOptions, \"activities\" | \"paths\"> | undefined,\n) {\n const scale = useMemo(\n () => (objectCentric ? objectTypeColorScale(objectCentric) : undefined),\n [objectCentric],\n );\n const activities = abstraction?.activities;\n const paths = abstraction?.paths;\n return useMemo(() => {\n if (!objectCentric || !scale) return undefined;\n const graph =\n activities === undefined && paths === undefined\n ? objectCentric\n : abstractObjectCentricGraph(\n objectCentric,\n {},\n { activities: activities ?? 1, paths: paths ?? 1 },\n );\n return { graph, flat: objectCentricProcessGraph(graph), scale };\n }, [objectCentric, scale, 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 colorScale,\n conformance,\n objectCentric,\n objectCentricLabels = OBJECT_CENTRIC_MAP_DEFAULT_LABELS,\n label,\n layoutEngine = \"dagre\",\n fitMinZoom = PROCESS_MAP_LEGIBLE_ZOOM,\n fitPadding = FIT_VIEW_OPTIONS.padding,\n refitKey,\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 // Object-centric — RM-066: abstract per type, flatten for layout, colour from the FULL\n // graph so a type keeps its swatch while the reader abstracts.\n const objectCentricView = useObjectCentricView(objectCentric, abstraction);\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n if (objectCentric && (graph || log)) {\n console.warn(\n \"ProcessMap: `objectCentric` is mutually exclusive with `graph`/`log`; drawing `objectCentric`.\",\n );\n }\n }, [objectCentric, graph, log]);\n const resolved = useResolvedGraph(\n objectCentricView ? objectCentricView.flat : graph,\n objectCentricView ? undefined : log,\n objectCentricView ? undefined : abstraction,\n );\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 colorScale,\n })\n : null,\n [resolved, metric, activeRework, activeSelection, selectionStates, colorScale],\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 layoutEngine,\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 ? buildObjectCentricMapModel({\n graph: resolved,\n metric,\n rework: activeRework,\n selection: activeSelection,\n selectionStates,\n backEdgeIds: layout.backEdgeIds,\n colorScale,\n objectCentric: objectCentricView?.graph,\n objectTypeScale: objectCentricView?.scale,\n objectCentricLabels,\n })\n : null,\n [\n objectCentricView,\n objectCentricLabels,\n resolved,\n metric,\n activeRework,\n activeSelection,\n selectionStates,\n layout.backEdgeIds,\n colorScale,\n ],\n );\n\n const positionedNodes = useMemo(\n () => (model ? applyPositions(model, layout, direction) : EMPTY_NODES),\n [model, layout, direction],\n );\n\n // ── Conformance (RM-062) ──────────────────────────────────────────────────\n // A decoration AFTER the model and the layout, never an input to either: switching the\n // reference model re-inks nodes and edges without re-deriving a metric or moving a node.\n const conformanceStates = useMemo(\n () => (conformance ? resolveConformanceStates(conformance) : null),\n [conformance],\n );\n const canvasNodes = useMemo(\n () =>\n conformanceStates\n ? positionedNodes.map((node) => withActivityConformance(node, conformanceStates))\n : positionedNodes,\n [positionedNodes, conformanceStates],\n );\n // Where the process begins: a clamped opening fit keeps these in view on BOTH axes — in a\n // left-to-right layout the first rank sits at mid-height, not in the top-left corner.\n const startNodeIds = useMemo(\n () => positionedNodes.filter((node) => node.data.isStart).map((node) => node.id),\n [positionedNodes],\n );\n const fitViewOptions = useMemo(\n () => ({ ...FIT_VIEW_OPTIONS, minZoom: fitMinZoom, padding: fitPadding }),\n [fitMinZoom, fitPadding],\n );\n const canvasEdges = useMemo(\n () =>\n model && conformanceStates\n ? model.edges.map((edge) => withTransitionConformance(edge, conformanceStates))\n : (model?.edges ?? EMPTY_EDGES),\n [model, conformanceStates],\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 {conformanceStates ? (\n <TableHead scope=\"col\">{CONFORMANCE_STATE_DEFAULT_LABELS.column}</TableHead>\n ) : null}\n {objectCentricView ? (\n <TableHead scope=\"col\">{objectCentricLabels.columnObjectTypes}</TableHead>\n ) : null}\n </TableRow>\n </TableHeader>\n <TableBody>\n {model.activityRows.map((row) => (\n <TableRow\n key={row.id}\n data-selection={row.selectionState}\n data-conformance={\n conformanceStates ? activityConformance(conformanceStates, row.id) : undefined\n }\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 {conformanceStates ? (\n <TableCell>\n <ConformanceStateMark state={activityConformance(conformanceStates, row.id)} />\n </TableCell>\n ) : null}\n {objectCentricView ? <TableCell>{row.objectTypes}</TableCell> : null}\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 {objectCentricView ? (\n <TableHead scope=\"col\">{objectCentricLabels.columnObjectType}</TableHead>\n ) : null}\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 {conformanceStates ? (\n <TableHead scope=\"col\">{CONFORMANCE_STATE_DEFAULT_LABELS.column}</TableHead>\n ) : null}\n </TableRow>\n </TableHeader>\n <TableBody>\n {model.transitionRows.map((row) => (\n <TableRow\n key={row.id}\n data-selection={row.selectionState}\n data-conformance={\n conformanceStates\n ? transitionConformance(conformanceStates, row.source, row.target)\n : undefined\n }\n data-state={row.selectionState === \"selected\" ? \"selected\" : undefined}\n >\n {objectCentricView ? <TableCell>{row.objectType}</TableCell> : null}\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 {conformanceStates ? (\n <TableCell>\n <ConformanceStateMark\n state={transitionConformance(conformanceStates, row.source, row.target)}\n />\n </TableCell>\n ) : null}\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={canvasNodes}\n edges={canvasEdges}\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}::${refitKey ?? \"\"}`}\n fitViewKeyOptions={fitViewOptions}\n fitViewAnchorNodeIds={startNodeIds}\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 <div className=\"flex items-start gap-3\">\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 {/* Object-centric — RM-066: each type's colour beside its printed code and name. */}\n {objectCentricView ? (\n <Legend\n title={objectCentricLabels.legendTitle}\n items={objectCentricView.scale.legend\n .filter((entry) => objectCentricView.graph.objectTypes.includes(entry.activityId))\n .map((entry) => ({\n label: `${entry.code} · ${entry.label}`,\n color: `var(${entry.token})`,\n }))}\n className=\"pointer-events-auto\"\n />\n ) : null}\n </div>\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","/**\n * Conformance states on the process map — RM-062.\n *\n * Reads a `ConformanceResult` (RM-061's `tokenReplay`) as the three-colour overlay the\n * analysis names (§2): every activity and every observed transition is in the log AND the\n * model (`\"both\"`), in the log only (`\"logOnly\"`), or expected by the model but missing\n * from the log (`\"modelOnly\"`).\n *\n * ## How the state is derived — without the reference model in hand\n *\n * A `ConformanceResult` does not carry the model it was replayed against, and it does not\n * need to: token replay charges an `undesired` deviation to EVERY observed event whose\n * activity the model does not contain, so an observed activity is outside the model\n * exactly when it carries an `undesired` deviation. The rules, in precedence order:\n *\n * | element | state | when |\n * | ---------- | ------------ | ------------------------------------------------------------ |\n * | activity | `logOnly` | at least one `undesired` deviation names it |\n * | activity | `modelOnly` | at least one `skipped` deviation names it (cases jumped it) |\n * | activity | `both` | otherwise |\n * | transition | `logOnly` | deviations are charged to it, or either endpoint is `logOnly` |\n * | transition | `both` | otherwise |\n *\n * A transition is never `modelOnly`: every edge on the map was observed. A model step\n * that NO case ever reached is not on the graph at all, so it cannot be painted here —\n * `ViolationList` still counts the cases that skipped it.\n *\n * ## Never colour alone (WCAG 1.4.1, analysis §5.4)\n *\n * Each state pairs a status tone with a distinct glyph AND a distinct line style —\n * solid circle / hollow triangle / dashed square — so the three read apart in greyscale.\n * {@link CONFORMANCE_STATE_ENCODING} is the one table every surface reads it from.\n */\nimport { Circle, SquareDashed, Triangle, type LucideIcon } from \"lucide-react\";\nimport type { ConformanceResult } from \"../core/conformance\";\nimport { EDGE_KEY_SEPARATOR } from \"../core/discover-graph\";\nimport type { ProcessMapEdge, ProcessMapNode } from \"../process-map/map-model\";\n\n/** Where an element sits relative to the reference model. */\nexport type ConformanceState = \"both\" | \"logOnly\" | \"modelOnly\";\n\n/** Every {@link ConformanceState}, in legend order. */\nexport const CONFORMANCE_STATES: readonly ConformanceState[] = [\"both\", \"logOnly\", \"modelOnly\"];\n\n/** The status tone a state is painted with. */\nexport type ConformanceTone = \"success\" | \"warning\" | \"destructive\";\n\n/** The line style a state is drawn with — the second non-colour channel. */\nexport type ConformanceDash = \"solid\" | \"dotted\" | \"dashed\";\n\n/** Everything that encodes one state, colour included. */\nexport interface ConformanceStateEncoding {\n tone: ConformanceTone;\n /** Stable glyph name, mirrored to `data-glyph` so a test can assert distinctness. */\n glyph: \"circle\" | \"triangle\" | \"square\";\n /** Lucide glyph drawn for the state. */\n icon: LucideIcon;\n /** Whether the glyph is filled (`true`) or an outline. */\n filled: boolean;\n dash: ConformanceDash;\n /** SVG `stroke-dasharray` for a transition stroke; `undefined` keeps the edge's own. */\n strokeDasharray: string | undefined;\n /** Mark (fill-rung) utility for a glyph or swatch. */\n markClass: string;\n /** Ink (`-text` rung) utility for coloured text. */\n textClass: string;\n /** Border utilities: the tone's fill rung plus the line style. */\n borderClass: string;\n /** CSS colour reference for a stroke or a `--border` override. */\n colorVar: string;\n}\n\n/** The single source of truth for how each state looks. */\nexport const CONFORMANCE_STATE_ENCODING: Readonly<\n Record<ConformanceState, ConformanceStateEncoding>\n> = Object.freeze({\n both: {\n tone: \"success\",\n glyph: \"circle\",\n icon: Circle,\n filled: true,\n dash: \"solid\",\n strokeDasharray: undefined,\n markClass: \"text-success fill-success\",\n textClass: \"text-success-text\",\n borderClass: \"border-success border-solid\",\n colorVar: \"var(--success)\",\n },\n logOnly: {\n tone: \"warning\",\n glyph: \"triangle\",\n icon: Triangle,\n filled: false,\n dash: \"dotted\",\n strokeDasharray: \"2 4\",\n markClass: \"text-warning\",\n textClass: \"text-warning-text\",\n borderClass: \"border-warning border-dotted\",\n colorVar: \"var(--warning)\",\n },\n modelOnly: {\n tone: \"destructive\",\n glyph: \"square\",\n icon: SquareDashed,\n filled: false,\n dash: \"dashed\",\n strokeDasharray: \"6 4\",\n markClass: \"text-destructive\",\n textClass: \"text-destructive-text\",\n borderClass: \"border-destructive border-dashed\",\n colorVar: \"var(--destructive)\",\n },\n});\n\n/** The words for each state. Override through a component's `labels` to localize. */\nexport interface ConformanceStateLabels {\n both: string;\n logOnly: string;\n modelOnly: string;\n /** The table twin's column header and the legend's title. */\n column: string;\n}\n\n/** The shipped English words for each state. */\nexport const CONFORMANCE_STATE_DEFAULT_LABELS: Readonly<ConformanceStateLabels> = Object.freeze({\n column: \"Conformance\",\n both: \"Conforms — in log and model\",\n logOnly: \"Log only — not in the model\",\n modelOnly: \"Model only — skipped in the log\",\n});\n\n/** Per-element conformance states for one `ConformanceResult`. */\nexport interface ConformanceStates {\n /** Keyed by activity id. An activity absent here is `\"both\"`. */\n activities: ReadonlyMap<string, ConformanceState>;\n /**\n * Keyed `source + EDGE_KEY_SEPARATOR + target` — the id `discoverGraph` and the map give\n * the transition. A transition absent here is resolved by {@link transitionConformance}.\n */\n transitions: ReadonlyMap<string, ConformanceState>;\n}\n\n/** Derive activity and transition states from a replay result. See this module's docblock. */\nexport function resolveConformanceStates(conformance: ConformanceResult): ConformanceStates {\n const activities = new Map<string, ConformanceState>();\n for (const trace of conformance.traces) {\n for (const deviation of trace.deviations) {\n if (deviation.activity === undefined) continue;\n if (deviation.type === \"undesired\") activities.set(deviation.activity, \"logOnly\");\n else if (deviation.type === \"skipped\" && activities.get(deviation.activity) !== \"logOnly\") {\n activities.set(deviation.activity, \"modelOnly\");\n }\n }\n }\n const transitions = new Map<string, ConformanceState>();\n for (const [key, { deviations }] of Object.entries(conformance.perEdge)) {\n if (deviations > 0) transitions.set(key, \"logOnly\");\n }\n return { activities, transitions };\n}\n\n/** The state of one activity. */\nexport function activityConformance(states: ConformanceStates, activity: string): ConformanceState {\n return states.activities.get(activity) ?? \"both\";\n}\n\n/** The state of one observed transition. */\nexport function transitionConformance(\n states: ConformanceStates,\n source: string,\n target: string,\n): ConformanceState {\n const charged = states.transitions.get(`${source}${EDGE_KEY_SEPARATOR}${target}`);\n if (charged) return charged;\n if (\n activityConformance(states, source) === \"logOnly\" ||\n activityConformance(states, target) === \"logOnly\"\n ) {\n return \"logOnly\";\n }\n return \"both\";\n}\n\n/**\n * A map node with its conformance state folded in: `data.conformance` for the glyph and\n * dash, `data-conformance` on React Flow's own node element (additive to\n * `data-selection`), and the state's word appended to the accessible name — so the state\n * reaches assistive technology as text, never as colour.\n */\nexport function withActivityConformance(\n node: ProcessMapNode,\n states: ConformanceStates,\n labels: ConformanceStateLabels = CONFORMANCE_STATE_DEFAULT_LABELS,\n): ProcessMapNode {\n const state = activityConformance(states, node.id);\n return {\n ...node,\n data: { ...node.data, conformance: state },\n ariaLabel: node.ariaLabel ? `${node.ariaLabel}, ${labels[state]}` : labels[state],\n domAttributes: {\n ...node.domAttributes,\n \"data-conformance\": state,\n } as ProcessMapNode[\"domAttributes\"],\n };\n}\n\n/** A map edge with its conformance state folded in. See {@link withActivityConformance}. */\nexport function withTransitionConformance(\n edge: ProcessMapEdge,\n states: ConformanceStates,\n labels: ConformanceStateLabels = CONFORMANCE_STATE_DEFAULT_LABELS,\n): ProcessMapEdge {\n const state = transitionConformance(states, edge.source, edge.target);\n const ariaLabel = edge.ariaLabel ? `${edge.ariaLabel}, ${labels[state]}` : labels[state];\n return {\n ...edge,\n ariaLabel,\n data: edge.data ? { ...edge.data, conformance: state, ariaLabel } : edge.data,\n };\n}\n","\"use client\";\n\n/**\n * ConformanceLegend — the key for the conformance overlay (RM-062).\n *\n * Follows `@elabs-ai/components-flow`'s `Legend` pattern (a titled swatch list on a\n * raised surface) but cannot BE that `Legend`: its categorical variant keys by colour\n * alone, and analysis §5.4 forbids a bare colour key. Each entry here pairs three\n * channels — the tone, the glyph (circle / triangle / square) and a line sample in the\n * state's dash (solid / dotted / dashed) — plus the state's word, all read from the one\n * {@link CONFORMANCE_STATE_ENCODING} table the map's nodes and edges also read.\n *\n * Built from HTML and Lucide glyphs only; no SVG is authored here.\n */\nimport { forwardRef, type HTMLAttributes } from \"react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport {\n CONFORMANCE_STATE_DEFAULT_LABELS,\n CONFORMANCE_STATE_ENCODING,\n CONFORMANCE_STATES,\n type ConformanceState,\n type ConformanceStateLabels,\n} from \"./conformance-state\";\n\n/** Props for {@link ConformanceStateMark}. */\nexport interface ConformanceStateMarkProps extends HTMLAttributes<HTMLSpanElement> {\n state: ConformanceState;\n /** Override the state's word. */\n labels?: Partial<ConformanceStateLabels>;\n}\n\n/**\n * One state as glyph + coloured word — the text form a table cell prints. The glyph is\n * `aria-hidden`; the word is the accessible reading.\n */\nexport const ConformanceStateMark = forwardRef<HTMLSpanElement, ConformanceStateMarkProps>(\n function ConformanceStateMark({ state, labels, className, ...props }, ref) {\n const encoding = CONFORMANCE_STATE_ENCODING[state];\n const Glyph = encoding.icon;\n const word = labels?.[state] ?? CONFORMANCE_STATE_DEFAULT_LABELS[state];\n return (\n <span\n ref={ref}\n data-slot=\"conformance-state-mark\"\n data-conformance={state}\n data-glyph={encoding.glyph}\n className={cn(\"inline-flex min-w-0 items-center gap-1.5\", className)}\n {...props}\n >\n <Glyph aria-hidden=\"true\" className={cn(\"size-3.5 shrink-0\", encoding.markClass)} />\n <span className={encoding.textClass}>{word}</span>\n </span>\n );\n },\n);\n\n/** Props for {@link ConformanceLegend}. */\nexport interface ConformanceLegendProps extends HTMLAttributes<HTMLDivElement> {\n /** Override any state word or the title. */\n labels?: Partial<ConformanceStateLabels>;\n}\n\n/** The three-state key: tone + glyph + dash + word per entry. */\nexport const ConformanceLegend = forwardRef<HTMLDivElement, ConformanceLegendProps>(\n function ConformanceLegend({ labels: labelOverrides, className, ...props }, ref) {\n const labels = { ...CONFORMANCE_STATE_DEFAULT_LABELS, ...labelOverrides };\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-label={labels.column}\n data-slot=\"conformance-legend\"\n className={cn(\n \"flex flex-col gap-1.5 rounded-lg bg-surface-elevated p-3 shadow-ring-sm\",\n className,\n )}\n {...props}\n >\n <div aria-hidden=\"true\" className=\"text-caption font-medium text-foreground\">\n {labels.column}\n </div>\n <ul className=\"flex flex-wrap gap-x-4 gap-y-1.5\">\n {CONFORMANCE_STATES.map((state) => {\n const encoding = CONFORMANCE_STATE_ENCODING[state];\n const Glyph = encoding.icon;\n return (\n <li\n key={state}\n data-slot=\"conformance-legend-item\"\n data-conformance={state}\n data-glyph={encoding.glyph}\n data-dash={encoding.dash}\n className=\"flex items-center gap-2 text-caption text-muted-foreground\"\n >\n <Glyph aria-hidden=\"true\" className={cn(\"size-3.5 shrink-0\", encoding.markClass)} />\n <span\n aria-hidden=\"true\"\n data-slot=\"conformance-legend-dash\"\n className={cn(\"w-6 shrink-0 border-t-2\", encoding.borderClass)}\n />\n {labels[state]}\n </li>\n );\n })}\n </ul>\n </div>\n );\n },\n);\n","/**\n * Activity colour scale — RM-054.\n *\n * One activity, one colour, across every process view. `ProcessMap` paints the colour as\n * a small accent mark on the activity node; `VariantExplorer` paints it on the sequence\n * chips. Both read the SAME scale instance built from one graph, so \"Create Order\" is the\n * same swatch in the map and in the variant list.\n *\n * ## The colour budget\n *\n * The chart palette ships twelve series tokens (`--chart-1` … `--chart-12`). The eleven\n * most frequent activities (ranked by the number of cases they occur in) take\n * `--chart-1` … `--chart-11`; every remaining activity shares `--chart-12` and is flagged\n * `pattern: \"other\"`, which the views render as a hatch so \"other\" never reads as a\n * twelfth distinct activity. Colour is never the only channel: every view that paints a\n * swatch also prints the activity label (or its two-letter {@link ActivityColorScale.codeFor}\n * code) as text.\n *\n * Pure and framework-free: returns token NAMES, never resolved colours, so a theme switch\n * re-inks every swatch with no recomputation.\n */\nimport type { ProcessGraph } from \"./types\";\n\n/** How many activities get a distinct palette slot before the rest share \"other\". */\nexport const ACTIVITY_COLOR_SLOTS = 11;\n\n/** The token every activity outside the top {@link ACTIVITY_COLOR_SLOTS} shares. */\nexport const ACTIVITY_OTHER_TOKEN = \"--chart-12\";\n\n/** The colour one activity is painted with. */\nexport interface ActivityColor {\n /** A CSS custom-property NAME, e.g. `\"--chart-3\"`. Paint with `var(${token})`. */\n token: string;\n /** Present for the shared \"other\" bucket — render the hatch, not a flat swatch. */\n pattern?: \"other\";\n}\n\n/** One legend entry, in rank order. */\nexport interface ActivityColorLegendEntry extends ActivityColor {\n activityId: string;\n label: string;\n /** A two-character code unique within the scale, for abbreviated (\"DNA strip\") chips. */\n code: string;\n}\n\n/** The shared activity→colour mapping. */\nexport interface ActivityColorScale {\n /** The colour for an activity. An id the graph never contained is \"other\". */\n colorFor(activityId: string): ActivityColor;\n /** The two-character code for an activity; derived from the id when it is unknown. */\n codeFor(activityId: string): string;\n /** The activity's display label; the id itself when it is unknown. */\n labelFor(activityId: string): string;\n /** Every activity in the graph, ranked by case count descending, ties by id. */\n legend: ActivityColorLegendEntry[];\n}\n\nconst OTHER: ActivityColor = Object.freeze({ token: ACTIVITY_OTHER_TOKEN, pattern: \"other\" });\n\n/** Upper-cased letters and digits of a label — the material codes are built from. */\nfunction alphanumerics(label: string): string {\n return label.replace(/[^\\p{L}\\p{N}]/gu, \"\").toUpperCase();\n}\n\n/**\n * Candidate two-character codes for a label, most readable first: initials of the first\n * two words (\"Create Order\" → \"CO\"), the first two letters (\"Approve\" → \"AP\"), then the\n * first letter paired with every later letter, then the first letter plus a digit.\n */\nfunction codeCandidates(label: string): string[] {\n const out: string[] = [];\n const words = label.split(/[^\\p{L}\\p{N}]+/u).filter(Boolean);\n const first = words[0];\n const second = words[1];\n if (first && second) out.push(`${first[0]}${second[0]}`.toUpperCase());\n const letters = alphanumerics(label);\n const head = letters[0] ?? \"?\";\n if (letters.length >= 2) out.push(letters.slice(0, 2));\n for (let i = 2; i < letters.length; i += 1) out.push(`${head}${letters[i]}`);\n for (let digit = 1; digit <= 9; digit += 1) out.push(`${head}${digit}`);\n return out;\n}\n\n/** A code for a label, avoiding every code already in `taken`. */\nfunction uniqueCode(label: string, taken: Set<string>): string {\n for (const candidate of codeCandidates(label)) {\n if (!taken.has(candidate)) return candidate;\n }\n // Pathological: more than ~40 activities sharing one first letter. Fall back to a\n // base-36 counter so the code is still two characters and still unique.\n for (let n = 0; n < 36 * 36; n += 1) {\n const candidate = n.toString(36).toUpperCase().padStart(2, \"0\");\n if (!taken.has(candidate)) return candidate;\n }\n return (alphanumerics(label).slice(0, 2) || \"??\").padEnd(2, \"?\");\n}\n\n/**\n * Build the shared colour scale for a graph.\n *\n * Deterministic: the same graph always yields the same assignment, regardless of the\n * order `graph.activities` arrives in. Build it from the FULL (unfiltered, unabstracted)\n * graph so colours stay put while a reader filters or abstracts.\n */\nexport function activityColorScale(graph: ProcessGraph): ActivityColorScale {\n const ranked = [...graph.activities].sort(\n (a, b) => b.cases - a.cases || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0),\n );\n\n const colors = new Map<string, ActivityColor>();\n const codes = new Map<string, string>();\n const labels = new Map<string, string>();\n const taken = new Set<string>();\n const legend: ActivityColorLegendEntry[] = ranked.map((activity, index) => {\n const color: ActivityColor =\n index < ACTIVITY_COLOR_SLOTS ? { token: `--chart-${index + 1}` } : OTHER;\n const label = activity.label || activity.id;\n const code = uniqueCode(label, taken);\n taken.add(code);\n colors.set(activity.id, color);\n codes.set(activity.id, code);\n labels.set(activity.id, label);\n return { activityId: activity.id, label, code, ...color };\n });\n\n return {\n colorFor: (activityId) => colors.get(activityId) ?? OTHER,\n codeFor: (activityId) =>\n codes.get(activityId) ?? (alphanumerics(activityId).slice(0, 2) || \"??\").padEnd(2, \"?\"),\n labelFor: (activityId) => labels.get(activityId) ?? activityId,\n legend,\n };\n}\n","/**\n * Object-centric directly-follows graph (OC-DFG) discovery — RM-066.\n *\n * An OC-DFG is one directly-follows graph PER OBJECT TYPE, drawn on one canvas: activities\n * shared between types merge into a single node that keeps a per-type breakdown, while\n * edges stay per type — an `order` following and an `item` following are different\n * relations, so they are never summed into one arrow. The merge semantics (per-type\n * projection, shared-activity join, per-type edges) follow pm4js's documented OC-DFG\n * construction; see `ATTRIBUTION.md`. No pm4js code is copied, and nothing here derives\n * from the AGPL Python reference implementation.\n *\n * Built entirely on `discoverGraph` and `abstractGraph`: every per-type number is exactly\n * what the single-case pipeline would print for that type's projection.\n */\nimport { abstractGraph, type AbstractionOptions } from \"./abstract-graph\";\nimport { activityColorScale, type ActivityColorScale } from \"./activity-color-scale\";\nimport { OCEL_EVENT_ID_ATTRIBUTE } from \"./adapters/ocel\";\nimport { discoverGraph, EDGE_KEY_SEPARATOR, type DiscoverGraphOptions } from \"./discover-graph\";\nimport type { ActivityStats, EventLog, ProcessGraph, TransitionStats } from \"./types\";\n\n/** One object type's share of a merged activity. */\nexport interface ObjectTypeActivityCounts {\n /** Occurrences in that type's projection (one per event × referenced object). */\n instances: number;\n /** Distinct objects of that type the activity touches. */\n cases: number;\n}\n\n/** A merged activity: shared across object types, with a per-type breakdown. */\nexport interface ObjectCentricActivityStats extends Omit<ActivityStats, \"instances\" | \"cases\"> {\n /**\n * Distinct OCEL events of this activity across every type — an event that references an\n * order and two items counts once. Falls back to the largest per-type `instances` when\n * the logs carry no `__ocelEventId` (hand-built logs).\n */\n events: number;\n /** Object type → counts, only for the types this activity occurs in. */\n perType: Record<string, ObjectTypeActivityCounts>;\n}\n\n/** An object-centric directly-follows graph. */\nexport interface ObjectCentricGraph {\n /** Merged activities, busiest (by `events`) first, ties by id. */\n activities: ObjectCentricActivityStats[];\n /** Object type → that type's own directly-follows edges. Never merged across types. */\n transitionsByType: Record<string, TransitionStats[]>;\n /** The object types, in the caller's order. */\n objectTypes: string[];\n /** Object type → the full per-type graph the merge was built from. */\n graphsByType: Record<string, ProcessGraph>;\n}\n\n/** {@link abstractObjectCentricGraph}'s result — what each type's abstraction hid. */\nexport interface AbstractedObjectCentricGraph extends ObjectCentricGraph {\n hiddenByType: Record<string, { activities: number; paths: number }>;\n}\n\nfunction compareStrings(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/** Distinct `__ocelEventId`s per activity across every log, or `undefined` if none carry one. */\nfunction countDistinctEvents(logs: readonly EventLog[]): Map<string, number> | undefined {\n const ids = new Map<string, Set<string>>();\n let any = false;\n for (const log of logs) {\n for (const row of log.events) {\n const id = row.attributes?.[OCEL_EVENT_ID_ATTRIBUTE];\n if (typeof id !== \"string\") continue;\n any = true;\n let set = ids.get(row.activity);\n if (set === undefined) {\n set = new Set();\n ids.set(row.activity, set);\n }\n set.add(id);\n }\n }\n if (!any) return undefined;\n const out = new Map<string, number>();\n for (const [activity, set] of ids) out.set(activity, set.size);\n return out;\n}\n\n/**\n * Merge per-type graphs into one {@link ObjectCentricGraph}.\n *\n * `eventCounts` supplies each activity's distinct-event count; an activity it does not\n * name falls back to its largest per-type `instances`. A merged activity's `duration` is\n * the duration of the type with the most instances of it (ties: earlier type), because\n * durations of the same events seen through two projections are not additive.\n */\nexport function mergeObjectCentricGraphs(\n graphsByType: Record<string, ProcessGraph>,\n objectTypes: readonly string[] = Object.keys(graphsByType),\n eventCounts?: ReadonlyMap<string, number>,\n): ObjectCentricGraph {\n const merged = new Map<string, ObjectCentricActivityStats>();\n const bestInstances = new Map<string, number>();\n const transitionsByType: Record<string, TransitionStats[]> = {};\n const types = objectTypes.filter((type) => graphsByType[type] !== undefined);\n\n for (const type of types) {\n const graph = graphsByType[type] as ProcessGraph;\n transitionsByType[type] = graph.transitions;\n for (const activity of graph.activities) {\n let entry = merged.get(activity.id);\n if (entry === undefined) {\n entry = {\n id: activity.id,\n label: activity.label,\n isStart: false,\n isEnd: false,\n duration: activity.duration,\n events: 0,\n perType: {},\n };\n merged.set(activity.id, entry);\n }\n entry.perType[type] = { instances: activity.instances, cases: activity.cases };\n entry.isStart ||= activity.isStart;\n entry.isEnd ||= activity.isEnd;\n if (activity.instances > (bestInstances.get(activity.id) ?? -1)) {\n bestInstances.set(activity.id, activity.instances);\n entry.duration = activity.duration;\n }\n }\n }\n\n const activities = [...merged.values()];\n for (const activity of activities) {\n activity.events = eventCounts?.get(activity.id) ?? bestInstances.get(activity.id) ?? 0;\n }\n activities.sort((a, b) => b.events - a.events || compareStrings(a.id, b.id));\n\n const graphs: Record<string, ProcessGraph> = {};\n for (const type of types) graphs[type] = graphsByType[type] as ProcessGraph;\n\n return { activities, transitionsByType, objectTypes: types, graphsByType: graphs };\n}\n\n/**\n * Discover an {@link ObjectCentricGraph} from per-object-type logs (typically\n * `fromOcel(…).logs`). Runs `discoverGraph` once per type, then merges.\n */\nexport function discoverObjectCentricGraph(\n logsByType: Record<string, EventLog>,\n options: DiscoverGraphOptions & { objectTypes?: string[] } = {},\n): ObjectCentricGraph {\n const { objectTypes = Object.keys(logsByType), ...discoverOptions } = options;\n const types = objectTypes.filter((type) => logsByType[type] !== undefined);\n const graphsByType: Record<string, ProcessGraph> = {};\n for (const type of types) {\n graphsByType[type] = discoverGraph(logsByType[type] as EventLog, discoverOptions);\n }\n const eventCounts = countDistinctEvents(types.map((type) => logsByType[type] as EventLog));\n return mergeObjectCentricGraphs(graphsByType, types, eventCounts);\n}\n\n/**\n * Abstract each object type independently, then re-merge.\n *\n * `perType[type]` wins for a type it names; `fallback` applies to every other type; a type\n * with neither is kept whole. Activity `events` counts are carried over from the input,\n * so abstraction hides nodes and edges without restating a statistic.\n */\nexport function abstractObjectCentricGraph(\n graph: ObjectCentricGraph,\n perType: Readonly<Record<string, AbstractionOptions>> = {},\n fallback?: AbstractionOptions,\n): AbstractedObjectCentricGraph {\n const graphsByType: Record<string, ProcessGraph> = {};\n const hiddenByType: Record<string, { activities: number; paths: number }> = {};\n for (const type of graph.objectTypes) {\n const source = graph.graphsByType[type] as ProcessGraph;\n const options = perType[type] ?? fallback;\n if (options === undefined) {\n graphsByType[type] = source;\n hiddenByType[type] = { activities: 0, paths: 0 };\n continue;\n }\n const { hidden, ...abstracted } = abstractGraph(source, options);\n graphsByType[type] = abstracted;\n hiddenByType[type] = hidden;\n }\n const eventCounts = new Map(graph.activities.map((activity) => [activity.id, activity.events]));\n return {\n ...mergeObjectCentricGraphs(graphsByType, graph.objectTypes, eventCounts),\n hiddenByType,\n };\n}\n\n/**\n * Flatten an {@link ObjectCentricGraph} into one {@link ProcessGraph} — the shape the\n * process map lays out and reads its node metrics from.\n *\n * - an activity's `instances` is its distinct-event count and its `cases` is the number of\n * objects (of any type) it touches;\n * - a pair of activities joined in several types becomes ONE transition whose `count` and\n * `caseCount` sum the types' — used for layout and selection only; the map still draws\n * the per-type edges from `transitionsByType`;\n * - `totals.cases` counts objects, `totals.events` distinct events.\n */\nexport function objectCentricProcessGraph(graph: ObjectCentricGraph): ProcessGraph {\n const activities: ActivityStats[] = graph.activities.map((activity) => {\n let cases = 0;\n for (const counts of Object.values(activity.perType)) cases += counts.cases;\n return {\n id: activity.id,\n label: activity.label,\n instances: activity.events,\n cases,\n isStart: activity.isStart,\n isEnd: activity.isEnd,\n duration: activity.duration,\n };\n });\n\n const transitions = new Map<string, TransitionStats>();\n const startActivities: Record<string, number> = {};\n const endActivities: Record<string, number> = {};\n const totals = { cases: 0, events: 0, variants: 0 };\n for (const activity of graph.activities) totals.events += activity.events;\n\n for (const type of graph.objectTypes) {\n const typeGraph = graph.graphsByType[type] as ProcessGraph;\n totals.cases += typeGraph.totals.cases;\n totals.variants += typeGraph.totals.variants;\n for (const [id, count] of Object.entries(typeGraph.startActivities)) {\n startActivities[id] = (startActivities[id] ?? 0) + count;\n }\n for (const [id, count] of Object.entries(typeGraph.endActivities)) {\n endActivities[id] = (endActivities[id] ?? 0) + count;\n }\n for (const transition of graph.transitionsByType[type] ?? []) {\n const key = `${transition.source}${EDGE_KEY_SEPARATOR}${transition.target}`;\n const existing = transitions.get(key);\n if (existing === undefined) {\n transitions.set(key, { ...transition });\n continue;\n }\n if (transition.count > existing.count) existing.duration = transition.duration;\n existing.count += transition.count;\n existing.caseCount += transition.caseCount;\n }\n }\n\n const transitionList = [...transitions.values()].sort(\n (a, b) =>\n b.count - a.count || compareStrings(a.source, b.source) || compareStrings(a.target, b.target),\n );\n\n const sorted = (record: Record<string, number>): Record<string, number> => {\n const out: Record<string, number> = {};\n for (const key of Object.keys(record).sort()) out[key] = record[key] as number;\n return out;\n };\n\n return {\n activities,\n transitions: transitionList,\n startActivities: sorted(startActivities),\n endActivities: sorted(endActivities),\n totals,\n };\n}\n\n/**\n * The object-type colour scale — RM-054's `activityColorScale`, reused rather than\n * reimplemented: object types are ranked by how many objects each has (ties by name) and\n * take `--chart-1` … `--chart-11` in that order, beyond which they share the hatched\n * \"other\" slot. `codeFor(type)` is the two-character text code painted beside every\n * swatch, so a type is never identified by colour alone.\n */\nexport function objectTypeColorScale(graph: ObjectCentricGraph): ActivityColorScale {\n const zero = { min: 0, max: 0, mean: 0, median: 0, p90: 0, sum: 0, trimmedMean: 0 };\n const activities: ActivityStats[] = graph.objectTypes.map((type) => {\n const typeGraph = graph.graphsByType[type] as ProcessGraph;\n return {\n id: type,\n label: type,\n instances: typeGraph.totals.events,\n cases: typeGraph.totals.cases,\n isStart: false,\n isEnd: false,\n duration: zero,\n };\n });\n return activityColorScale({\n activities,\n transitions: [],\n startActivities: {},\n endActivities: {},\n totals: { cases: 0, events: 0, variants: 0 },\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, type ReactNode } 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 type { ActivityColor } from \"../core/activity-color-scale\";\nimport { activityAccentStyle } from \"./activity-accent\";\nimport { activityRole, GHOST_OPACITY, type ProcessMapNode } from \"./map-model\";\nimport type { ProcessObjectTypeCount } from \"./map-model\";\nimport {\n CONFORMANCE_STATE_ENCODING,\n type ConformanceState,\n} from \"../conformance-overlay/conformance-state\";\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/**\n * The footer row: the meter alone (today's rendering, byte-identical) or, when the map has\n * a shared `colorScale` (RM-054), the activity's identity swatch in front of the meter.\n *\n * The swatch is a small mark, never the card fill, so identity colour and the metric's\n * saturation ramp never compete for the same pixels. It is `aria-hidden`: the activity's\n * identity already reaches the reader as its printed title and accessible name, so the\n * colour is a redundant cross-view cue (WCAG 1.4.1), not a channel of its own.\n */\nfunction accentFooter(accent: ActivityColor | undefined, isDimmed: boolean, meter: ReactNode) {\n if (!accent) return meter;\n return (\n <div className=\"flex items-center gap-1.5\">\n <span\n aria-hidden=\"true\"\n data-slot=\"process-activity-node-accent\"\n data-color-token={accent.token}\n data-pattern={accent.pattern}\n className=\"size-2.5 shrink-0 rounded-sm\"\n style={{ ...activityAccentStyle(accent), ...(isDimmed ? { opacity: GHOST_OPACITY } : {}) }}\n />\n <div className=\"min-w-0 flex-1\">{meter}</div>\n </div>\n );\n}\n\n/**\n * Object-centric — RM-066. One chip per object type above the footer row: a chart-token\n * square, the type's two-character code and its object count. Never colour alone — the\n * code and count are printed, and each chip is a named image (`role=\"img\"` plus the\n * type's own \"order: 2 objects, 3 occurrences\" name, also shown as a `title` tooltip).\n */\nfunction objectTypeFooter(\n objectTypes: ProcessObjectTypeCount[] | undefined,\n isDimmed: boolean,\n footer: ReactNode,\n) {\n if (!objectTypes || objectTypes.length === 0) return footer;\n return (\n <div className=\"flex flex-col gap-1.5\">\n <div data-slot=\"process-activity-node-object-types\" className=\"flex flex-wrap gap-1\">\n {objectTypes.map((entry) => (\n <span\n key={entry.type}\n role=\"img\"\n data-object-type={entry.type}\n data-color-token={entry.color.token}\n aria-label={entry.ariaLabel}\n title={entry.ariaLabel}\n className=\"inline-flex items-center gap-1 rounded-sm text-meta tabular-nums text-muted-foreground\"\n >\n <span\n aria-hidden=\"true\"\n className=\"size-2.5 shrink-0 rounded-sm\"\n style={{\n ...activityAccentStyle(entry.color),\n ...(isDimmed ? { opacity: GHOST_OPACITY } : {}),\n }}\n />\n <span aria-hidden=\"true\" className=\"font-medium text-foreground\">\n {entry.code}\n </span>\n <span aria-hidden=\"true\">{entry.cases}</span>\n </span>\n ))}\n </div>\n {footer}\n </div>\n );\n}\n\n/**\n * The card frame under a conformance state (RM-062): the border token is retargeted to the\n * state's tone and the line style is set on `FlowNode`'s own card through a descendant\n * variant — composition from outside, same as {@link GHOST_FRAME_STYLE}, never a fork.\n * Merged AFTER the ghost style, so an excluded node keeps its quieter fill (selection) and\n * still shows the conformance border and dash (conformance): the two layers compose.\n */\nconst CONFORMANCE_FRAME_CLASS: Record<ConformanceState, string> = {\n both: \"[&_[data-slot=flow-node]]:border-solid\",\n logOnly: \"[&_[data-slot=flow-node]]:border-dotted\",\n modelOnly: \"[&_[data-slot=flow-node]]:border-dashed\",\n};\n\nfunction frameStyle(\n isDimmed: boolean,\n conformance: ConformanceState | undefined,\n): CSSProperties | undefined {\n if (!conformance) return isDimmed ? GHOST_FRAME_STYLE : undefined;\n return {\n ...(isDimmed ? GHOST_FRAME_STYLE : {}),\n \"--border\": CONFORMANCE_STATE_ENCODING[conformance].colorVar,\n } as CSSProperties;\n}\n\n/**\n * The conformance glyph pinned to the card's top-start corner. `aria-hidden`: the state's\n * word is already folded into the node's accessible name by `ProcessMap`, so the glyph is\n * the visible, non-colour channel, not a second announcement.\n */\nfunction ConformanceMarker({ state }: { state: ConformanceState }) {\n const encoding = CONFORMANCE_STATE_ENCODING[state];\n const Glyph = encoding.icon;\n return (\n <span\n aria-hidden=\"true\"\n data-slot=\"process-activity-node-conformance\"\n data-conformance={state}\n data-glyph={encoding.glyph}\n data-dash={encoding.dash}\n className=\"absolute -start-2 -top-2 z-10 flex size-5 items-center justify-center rounded-full bg-flow-node shadow-xs\"\n >\n <Glyph className={cn(\"size-3\", encoding.markClass)} />\n </span>\n );\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: objectTypeFooter(\n data.objectTypes,\n isDimmed,\n accentFooter(\n data.accent,\n isDimmed,\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 [\n data.title,\n data.metricLabel,\n data.primaryLabel,\n data.secondaryLabel,\n data.saturation,\n data.accent,\n data.objectTypes,\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 data-conformance={data.conformance}\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 {data.conformance ? <ConformanceMarker state={data.conformance} /> : null}\n\n <div\n data-slot=\"process-activity-node-frame\"\n className={data.conformance ? CONFORMANCE_FRAME_CLASS[data.conformance] : undefined}\n style={frameStyle(isDimmed, data.conformance)}\n >\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 *\n * ## The label pill is also the only reachable place for the transition's name (#354)\n *\n * The same portal-out-of-the-`<g>` fact applies to `aria-label`: React Flow's `EdgeWrapper`\n * puts `edge.ariaLabel` on the outer `<g>`, but that `<g>` is never a tab stop\n * (`process-map.tsx` sets `edgesFocusable={false}` so the pill is the arrow's one stop —\n * see that file's own docblock). `labelProps` carries `data.ariaLabel` (computed once, in\n * `map-model.ts`'s `transitionAriaLabel`, and read here rather than recomputed, so the\n * canvas and the `TableView` twin cannot drift) onto the pill's `aria-label`, merged with\n * the excluded-state fields above rather than replacing them.\n */\nimport { useMemo, type CSSProperties } from \"react\";\nimport type { EdgeProps } from \"@xyflow/react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport {\n FlowSelfLoopEdge,\n FlowWeightedEdge,\n Position,\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\";\nimport {\n CONFORMANCE_STATE_ENCODING,\n type ConformanceState,\n} from \"../conformance-overlay/conformance-state\";\nimport { useProcessReplayEdgeTokens } from \"../process-replay/replay-tokens-context\";\n\n/**\n * The label pill under a conformance state (RM-062): the state's glyph as a leading\n * `::before` mark (circle / triangle / square — the same shapes as the node marker and the\n * legend) and the tone's fill rung on the pill border. The pill's own `aria-label` already\n * carries the state's word (`ProcessMap` folds it into `data.ariaLabel`), so the glyph is\n * a visible, non-colour channel only. The line style lives on the STROKE, not the pill, so\n * it never fights the excluded state's dashed pill border.\n */\nconst CONFORMANCE_LABEL_CLASS: Record<ConformanceState, string> = {\n both: \"border-success before:text-success before:content-['●']\",\n logOnly: \"border-warning before:text-warning before:content-['△']\",\n modelOnly: \"border-destructive before:text-destructive before:content-['⬚']\",\n};\n\n/**\n * The stroke under a conformance state: the tone as paint plus the state's dash pattern.\n * Omitted while the edge is selected, so React Flow's selection stroke still wins; a\n * `\"both\"` edge keeps its own dash (a back edge stays dashed).\n */\nfunction conformanceStrokeStyle(\n state: ConformanceState | undefined,\n selected: boolean | undefined,\n): CSSProperties | undefined {\n if (!state || selected) return undefined;\n const encoding = CONFORMANCE_STATE_ENCODING[state];\n return {\n stroke: encoding.colorVar,\n ...(encoding.strokeDasharray\n ? { strokeDasharray: encoding.strokeDasharray, strokeLinecap: \"round\" }\n : {}),\n };\n}\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 * Object-centric — RM-066. Per-type edges joining the same two activities are drawn side\n * by side: each one's endpoints shift across the flow axis by its slot, so the strokes and\n * their label pills separate instead of stacking. The spread is capped so every endpoint\n * stays on the card's own face.\n */\nconst PARALLEL_EDGE_GAP = 40;\nconst PARALLEL_EDGE_MAX_SPREAD = 120;\n\nfunction parallelShift(index: number | undefined, count: number | undefined): number {\n if (index === undefined || count === undefined || count < 2) return 0;\n const gap = Math.min(PARALLEL_EDGE_GAP, PARALLEL_EDGE_MAX_SPREAD / (count - 1));\n return (index - (count - 1) / 2) * gap;\n}\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 // The focused element (the label pill's `<button>`) and the named element (React Flow's\n // own edge `<g>`, unreachable — see this file's own docblock) are two different DOM\n // nodes; `data.ariaLabel` (`transitionAriaLabel`, computed once in `map-model.ts`) is the\n // channel that gets the same accessible name onto the one a screen-reader user actually\n // lands on (#354). Merged with, never replacing, the excluded-state fields.\n const conformance = data?.conformance;\n const selected = props.selected;\n const labelProps = useMemo(() => {\n if (!isExcluded && !data?.ariaLabel && !conformance) return undefined;\n return {\n ...(isExcluded ? EXCLUDED_LABEL_PROPS : undefined),\n ...(data?.ariaLabel ? { \"aria-label\": data.ariaLabel } : undefined),\n // Additive to the excluded fields (RM-062): the dashed excluded border stays, the\n // conformance glyph and tone join it. The tone border yields to the selection ring.\n ...(conformance\n ? {\n \"data-conformance\": conformance,\n className: cn(\n isExcluded && EXCLUDED_LABEL_PROPS.className,\n CONFORMANCE_LABEL_CLASS[conformance],\n selected && \"border-ring\",\n ),\n }\n : undefined),\n };\n }, [isExcluded, data?.ariaLabel, conformance, selected]);\n const strokeStyle = conformanceStrokeStyle(conformance, selected);\n const edgeStyle = strokeStyle ? { ...props.style, ...strokeStyle } : props.style;\n // RM-065: tokens from an enclosing `ProcessReplay`. `undefined` outside one, and then the\n // key is not added at all, so a map with no replay builds exactly the data it did before.\n const replayTokens = useProcessReplayEdgeTokens(props.id);\n // Object-centric — RM-066: a per-type edge takes its type's chart stroke (on the edge\n // object's `style`), never the value ramp, and sits in its own parallel slot.\n const isObjectTyped = data?.objectType !== undefined;\n const shift = parallelShift(data?.parallelIndex, data?.parallelCount);\n const acrossX = props.targetPosition === Position.Top || props.targetPosition === Position.Bottom;\n const placed = shift\n ? {\n sourceX: props.sourceX + (acrossX ? shift : 0),\n targetX: props.targetX + (acrossX ? shift : 0),\n sourceY: props.sourceY + (acrossX ? 0 : shift),\n targetY: props.targetY + (acrossX ? 0 : shift),\n }\n : undefined;\n\n const weightedData = useMemo<FlowWeightedEdgeData>(\n () => ({\n weight: data?.weight,\n scaleGroup: PROCESS_MAP_EDGE_SCALE_GROUP,\n value: isObjectTyped ? undefined : data?.value,\n valueDomain: isObjectTyped ? undefined : data?.valueDomain,\n label: data?.label,\n secondaryLabel: data?.secondaryLabel,\n variant: data?.isBackEdge ? \"back\" : \"forward\",\n labelProps,\n ...(replayTokens ? { tokens: replayTokens } : undefined),\n }),\n [\n isObjectTyped,\n data?.weight,\n data?.value,\n data?.valueDomain,\n data?.label,\n data?.secondaryLabel,\n data?.isBackEdge,\n labelProps,\n replayTokens,\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 ...(replayTokens ? { tokens: replayTokens } : undefined),\n }),\n [data?.weight, data?.label, data?.secondaryLabel, labelProps, replayTokens],\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 data-conformance={conformance}\n data-dash={conformance ? CONFORMANCE_STATE_ENCODING[conformance].dash : undefined}\n data-object-type={data?.objectType?.type}\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\n {...props}\n {...placed}\n style={edgeStyle}\n type=\"self-loop\"\n data={selfLoopData}\n />\n ) : (\n <FlowWeightedEdge\n {...props}\n {...placed}\n style={edgeStyle}\n type=\"weighted\"\n data={weightedData}\n />\n )}\n </g>\n );\n}\n","\"use client\";\n\n/**\n * The replay channel into the process map (RM-065).\n *\n * `ProcessMap` fixes its own edge types, so a replay cannot hand tokens to an edge through\n * props. `ProcessReplay` provides this context above the map instead, keyed by edge id\n * (`processEdgeId`); `ProcessTransitionEdge` reads its own entry and passes it on as the flow\n * edge's `data.tokens`. Without a provider the read answers `undefined` and the edge's data\n * is exactly what it was before this channel existed.\n *\n * Kept in its own tiny module so the edge imports no replay component.\n */\nimport { createContext, use } from \"react\";\nimport type { FlowEdgeToken } from \"@elabs-ai/components-flow\";\n\n/** Edge id → the tokens on that edge right now. An edge with no entry carries none. */\nexport type ProcessReplayTokens = ReadonlyMap<string, readonly FlowEdgeToken[]>;\n\n/** Provided by `ProcessReplay`. `null` (the default) means no replay is running. */\nexport const ProcessReplayTokensContext = createContext<ProcessReplayTokens | null>(null);\n\n/** The tokens on one edge, or `undefined` outside a replay. */\nexport function useProcessReplayEdgeTokens(edgeId: string): readonly FlowEdgeToken[] | undefined {\n const tokens = use(ProcessReplayTokensContext);\n if (tokens === null) return undefined;\n return tokens.get(edgeId) ?? EMPTY_TOKENS;\n}\n\nconst EMPTY_TOKENS: readonly FlowEdgeToken[] = Object.freeze([]);\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, layoutFlowElk, 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 // elkjs adapter — RM-067\n /**\n * Which layout engine runs. `\"elk\"` calls `layoutFlowElk` (async, lazily loaded, falls\n * back to dagre when elkjs is not installed) instead of `layoutFlow`. @default \"dagre\"\n */\n layoutEngine?: \"dagre\" | \"elk\";\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 layoutEngine = \"dagre\",\n}: UseProcessLayoutOptions): UseProcessLayoutResult {\n // The engine joins the key only when it is not the default, so dagre keys are unchanged.\n const cacheKey = `${structureKey}::${direction}${layoutEngine === \"elk\" ? \"::elk\" : \"\"}`;\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 layoutOptions = {\n direction,\n nodeSpacing: NODE_SPACING,\n rankSpacing: RANK_SPACING[direction],\n };\n const settle = (result: ReturnType<typeof layoutFlow<ProcessMapNode, ProcessMapEdge>>) => {\n const snapshot = toSnapshot(result, performance.now() - started);\n runs.current += 1;\n cache.current.set(key, snapshot);\n setApplied({ key, snapshot });\n };\n // elkjs adapter — RM-067: async, lazily loaded; `layoutFlowElk` never rejects (it\n // falls back to dagre itself), and a result for a key no longer current still caches.\n if (layoutEngine === \"elk\") {\n void layoutFlowElk<ProcessMapNode, ProcessMapEdge>(\n currentNodes,\n currentEdges,\n layoutOptions,\n ).then(settle);\n return;\n }\n settle(layoutFlow<ProcessMapNode, ProcessMapEdge>(currentNodes, currentEdges, layoutOptions));\n },\n [direction, layoutEngine],\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 { ChevronRight, Link2, Link2Off } from \"lucide-react\";\nimport { Button, Label, Slider, Switch, useLocale } from \"@elabs-ai/components-ui\";\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } 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 * Object-centric — RM-066. One slider pair per object type, keyed by type, rendered as\n * collapsible rows under the global pair. A `linked` type (the default) follows the\n * global sliders proportionally; toggle its link to abstract it on its own. Feed the\n * result to `abstractObjectCentricGraph`.\n */\n perType?: Readonly<Record<string, ObjectTypeAbstraction>>;\n /** Object-centric — RM-066. Called with the whole next per-type record. */\n onPerTypeChange?(next: Record<string, ObjectTypeAbstraction>): void;\n /** Object-centric — RM-066. Strings for the per-type rows. */\n perTypeLabels?: ObjectTypeAbstractionLabels;\n}\n\n/** Object-centric — RM-066. One object type's abstraction fractions. */\nexport interface ObjectTypeAbstraction {\n /** Fraction of this type's activities to keep, `0..1`. */\n activities: number;\n /** Fraction of this type's paths to keep, `0..1`. */\n paths: number;\n /** Follow the global sliders proportionally. @default true */\n linked?: boolean;\n}\n\n/** Object-centric — RM-066. Strings for the per-type rows. */\nexport interface ObjectTypeAbstractionLabels {\n section: string;\n link: (type: string) => string;\n activities: (type: string) => string;\n paths: (type: string) => string;\n}\n\n/** English defaults for {@link ObjectTypeAbstractionLabels}. */\nexport const OBJECT_TYPE_ABSTRACTION_DEFAULT_LABELS: Readonly<ObjectTypeAbstractionLabels> =\n Object.freeze({\n section: \"Per object type\",\n link: (type: string) => `Link ${type} to the global sliders`,\n activities: (type: string) => `${type} activities`,\n paths: (type: string) => `${type} paths`,\n });\n\n/**\n * Scale every linked type by the global pair's own change: a type at 60% whose global\n * slider moves from 100% to 50% lands at 30%. From a global of 0 there is no ratio, so\n * linked types take the new global value as-is.\n */\nfunction scalePerType(\n perType: Readonly<Record<string, ObjectTypeAbstraction>>,\n current: Pick<AbstractionOptions, \"activities\" | \"paths\">,\n patch: Partial<Pick<AbstractionOptions, \"activities\" | \"paths\">>,\n): Record<string, ObjectTypeAbstraction> {\n const next: Record<string, ObjectTypeAbstraction> = {};\n for (const [type, entry] of Object.entries(perType)) {\n if (entry.linked === false) {\n next[type] = entry;\n continue;\n }\n const scaled = { ...entry };\n for (const axis of [\"activities\", \"paths\"] as const) {\n const target = patch[axis];\n if (target === undefined) continue;\n const from = current[axis];\n scaled[axis] = Math.min(1, Math.max(0, from > 0 ? (entry[axis] * target) / from : target));\n }\n next[type] = scaled;\n }\n return next;\n}\n\n/** Object-centric — RM-066. One type's collapsible row: a link toggle and two sliders. */\nfunction ObjectTypeSliders({\n type,\n entry,\n labels,\n onChange,\n}: {\n type: string;\n entry: ObjectTypeAbstraction;\n labels: ObjectTypeAbstractionLabels;\n onChange(next: ObjectTypeAbstraction): void;\n}) {\n const baseId = useId();\n const linked = entry.linked !== false;\n const activitiesPercent = toPercent(entry.activities);\n const pathsPercent = toPercent(entry.paths);\n return (\n <Collapsible\n data-slot=\"abstraction-controls-object-type\"\n data-object-type={type}\n data-linked={linked ? \"true\" : \"false\"}\n className=\"flex flex-col gap-2\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <CollapsibleTrigger asChild>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"group min-w-0 justify-start\">\n <ChevronRight\n aria-hidden=\"true\"\n className=\"transition-transform duration-fast ease-standard group-data-[state=open]:rotate-90 motion-reduce:transition-none\"\n />\n <span className=\"truncate\">{type}</span>\n <span className=\"text-meta text-muted-foreground tabular-nums\">\n {activitiesPercent}% · {pathsPercent}%\n </span>\n </Button>\n </CollapsibleTrigger>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-pressed={linked}\n aria-label={labels.link(type)}\n data-slot=\"abstraction-controls-object-type-link\"\n onClick={() => onChange({ ...entry, linked: !linked })}\n >\n {linked ? <Link2 aria-hidden=\"true\" /> : <Link2Off aria-hidden=\"true\" />}\n </Button>\n </div>\n <CollapsibleContent className=\"flex flex-col gap-3 ps-6\">\n <Slider\n id={`${baseId}-activities`}\n min={SLIDER_MIN}\n max={SLIDER_MAX}\n step={1}\n value={[activitiesPercent]}\n onValueChange={([next]) =>\n onChange({ ...entry, activities: fromPercent(next ?? activitiesPercent) })\n }\n aria-label={labels.activities(type)}\n />\n <Slider\n id={`${baseId}-paths`}\n min={SLIDER_MIN}\n max={SLIDER_MAX}\n step={1}\n value={[pathsPercent]}\n onValueChange={([next]) =>\n onChange({ ...entry, paths: fromPercent(next ?? pathsPercent) })\n }\n aria-label={labels.paths(type)}\n />\n </CollapsibleContent>\n </Collapsible>\n );\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 perType,\n onPerTypeChange,\n perTypeLabels = OBJECT_TYPE_ABSTRACTION_DEFAULT_LABELS,\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 // Object-centric — RM-066: a global change carries every linked type along with it.\n const syncPerType = useCallback(\n (patch: Partial<Pick<AbstractionOptions, \"activities\" | \"paths\">>) => {\n if (perType && onPerTypeChange) {\n onPerTypeChange(scalePerType(perType, abstraction, patch));\n }\n },\n [perType, onPerTypeChange, abstraction],\n );\n\n const setActivitiesPercent = useCallback(\n (percent: number) => {\n onAbstractionChange({ activities: fromPercent(percent) });\n syncPerType({ activities: fromPercent(percent) });\n },\n [onAbstractionChange, syncPerType],\n );\n const setPathsPercent = useCallback(\n (percent: number) => {\n onAbstractionChange({ paths: fromPercent(percent) });\n syncPerType({ paths: fromPercent(percent) });\n },\n [onAbstractionChange, syncPerType],\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 syncPerType({\n activities: result.activities,\n paths: Math.min(1, result.activities + AUTO_PATHS_OFFSET),\n });\n }, [\n graph.activities.length,\n hiddenCounts.activities,\n autoMaxActivities,\n onAbstractionChange,\n syncPerType,\n ]);\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 {/* Object-centric — RM-066 */}\n {perType ? (\n <div\n data-slot=\"abstraction-controls-per-type\"\n role=\"group\"\n aria-label={perTypeLabels.section}\n className=\"flex flex-col gap-1\"\n >\n {Object.entries(perType).map(([type, entry]) => (\n <ObjectTypeSliders\n key={type}\n type={type}\n entry={entry}\n labels={perTypeLabels}\n onChange={(next) => onPerTypeChange?.({ ...perType, [type]: next })}\n />\n ))}\n </div>\n ) : null}\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, type ReactNode } from \"react\";\nimport { CircleSlash2 } from \"lucide-react\";\nimport { MetricCard, Skeleton, type LocaleContextValue } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\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\";\nimport type { ConformanceRatePoint, ConformanceResult } from \"../core/conformance\";\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 /**\n * `0..1`, a replay result (RM-061's `tokenReplay`, whose `overallFitness` the tile\n * prints — RM-062), or `null`/`undefined` when no conformance model has been fitted.\n */\n conformance?: number | ConformanceResult | null;\n /** Optional per-tile trend series (oldest -> newest), keyed by KPI. */\n trends?: Partial<Record<ProcessKpiStripTrendKey, number[]>>;\n /**\n * Conformance rate over time (`conformanceRateSeries`), plotted as the conformance\n * tile's sparkline — the rate-over-time reading (RM-062). Takes precedence over\n * `trends.conformance`.\n */\n conformanceSeries?: ConformanceRatePoint[];\n loading?: boolean;\n /**\n * How the six numbers are laid out. `\"grid\"` (default) is the card grid a dashboard page\n * opens with. `\"inline\"` is one dense ribbon — label, value and sparkline per cell, divided\n * by hairlines, scrolling sideways when it runs out of room — for a workspace whose height\n * belongs to the process map, not to its KPIs. Same numbers, same formats, same sparkline\n * text alternatives in both.\n */\n layout?: \"grid\" | \"inline\";\n}\n\n/** The tile's fraction: a number as given, a replay result's `overallFitness`. */\nfunction conformanceFraction(conformance: number | ConformanceResult | null | undefined) {\n if (conformance === null || conformance === undefined) return undefined;\n return typeof conformance === \"number\" ? conformance : conformance.overallFitness;\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 size?: { width: number; height: number },\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} {...size} />;\n}\n\n/** One cell of the `layout=\"inline\"` ribbon. */\nfunction InlineKpi({\n label,\n value,\n visual,\n loading,\n}: {\n label: string;\n value: ReactNode;\n visual?: ReactNode;\n loading: boolean;\n}) {\n return (\n // A `div` inside a `<dl>` may hold nothing but `dt`/`dd` — so the cell is\n // exactly those two, and the trend rides INSIDE the `dd` beside the number\n // rather than as a third sibling.\n <div data-slot=\"process-kpi-strip-cell\" className=\"flex flex-1 flex-col px-4 py-2\">\n <dt className=\"text-meta whitespace-nowrap text-muted-foreground\">{label}</dt>\n <dd className=\"flex items-center justify-between gap-3\">\n <span className=\"text-subtitle font-semibold tabular-nums whitespace-nowrap\">\n {loading ? <Skeleton className=\"mt-1 h-5 w-14\" /> : value}\n </span>\n {/* The trend is the first thing to go when the ribbon is short of room; the number stays. */}\n {!loading && visual ? (\n <span className=\"hidden shrink-0 text-muted-foreground @4xl:block\">{visual}</span>\n ) : null}\n </dd>\n </div>\n );\n}\n\nexport function ProcessKpiStrip({\n kpis,\n conformance,\n trends,\n conformanceSeries,\n loading = false,\n layout = \"grid\",\n className,\n ...props\n}: ProcessKpiStripProps) {\n const { t, formatNumber } = useLocale();\n\n const fraction = conformanceFraction(conformance);\n const hasConformance = fraction !== undefined;\n const conformanceTrend = conformanceSeries\n ? conformanceSeries.map((point) => point.fitness)\n : trends?.conformance;\n const conformanceHint = t(\"process.kpiStrip.conformanceUnavailableHint\");\n\n if (layout === \"inline\") {\n const percent = (value: number) =>\n formatNumber(value, { style: \"percent\", maximumFractionDigits: 1 });\n const spark = (values: number[] | undefined, subject: string, format: TrendValueFormat) =>\n trendVisual(values, subject, format, t, formatNumber, { width: 64, height: 24 });\n return (\n <div\n data-slot=\"process-kpi-strip\"\n data-layout=\"inline\"\n role={loading ? \"status\" : undefined}\n aria-busy={loading || undefined}\n className={cn(\"@container overflow-x-auto\", className)}\n {...props}\n >\n <dl className=\"flex min-w-max divide-x divide-border\">\n <InlineKpi\n label={t(\"process.kpiStrip.cases\")}\n value={formatNumber(kpis.cases)}\n visual={spark(trends?.cases, t(\"process.kpiStrip.cases\"), \"count\")}\n loading={loading}\n />\n <InlineKpi\n label={t(\"process.kpiStrip.events\")}\n value={formatNumber(kpis.events)}\n visual={spark(trends?.events, t(\"process.kpiStrip.events\"), \"count\")}\n loading={loading}\n />\n <InlineKpi\n label={t(\"process.kpiStrip.variants\")}\n value={formatNumber(kpis.variants)}\n visual={spark(trends?.variants, t(\"process.kpiStrip.variants\"), \"count\")}\n loading={loading}\n />\n <InlineKpi\n label={t(\"process.kpiStrip.medianThroughput\")}\n value={formatDurationMs(kpis.medianThroughput)}\n visual={spark(\n trends?.medianThroughput,\n t(\"process.kpiStrip.medianThroughput\"),\n \"duration\",\n )}\n loading={loading}\n />\n <InlineKpi\n label={t(\"process.kpiStrip.reworkRate\")}\n value={percent(kpis.reworkRate)}\n visual={spark(trends?.reworkRate, t(\"process.kpiStrip.reworkRate\"), \"percent\")}\n loading={loading}\n />\n <InlineKpi\n label={t(\"process.kpiStrip.conformance\")}\n value={\n hasConformance ? (\n percent(fraction)\n ) : (\n <span\n data-slot=\"process-kpi-strip-conformance-unavailable\"\n title={conformanceHint}\n className=\"inline-flex items-center gap-1.5 text-body font-normal text-muted-foreground\"\n >\n <CircleSlash2 aria-hidden=\"true\" className=\"size-4\" />\n {t(\"process.kpiStrip.conformanceUnavailable\")}\n </span>\n )\n }\n visual={\n hasConformance\n ? spark(conformanceTrend, t(\"process.kpiStrip.conformance\"), \"percent\")\n : undefined\n }\n loading={loading}\n />\n </dl>\n </div>\n );\n }\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 fraction\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 conformanceTrend,\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, filterNormalizedLog } 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 *\n * `{ kind: \"cases\" }` joins it for the same reason: `DottedChart` (RM-059) and\n * `PerformanceSpectrum` (RM-060) brush a set of case ids, so their `onFilterIntent` can be\n * wired straight to `applyIntent`.\n */\nexport type FilterIntent =\n | ProcessFilterIntent\n | Extract<FilterSpec, { kind: \"variant\" }>\n | Extract<FilterSpec, { kind: \"cases\" }>;\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 /**\n * How many cases EACH active intent alone excludes — parallel to {@link intents} (same\n * index), for `ProcessFilterBar` (RM-056, #205)'s per-chip \"excluded N\" count. For intent\n * `i`, this is the case count filtered by every intent BEFORE `i` minus the case count\n * filtered by every intent up to and including `i` — so it isolates what `i` itself\n * removes from the chain, not what the whole chain removes. `[]` when no intents are\n * active. Computed with one `filterNormalizedLog` call per prefix (`n + 1` calls for `n`\n * intents, reusing each prefix's count for both the term it ends and the term it starts),\n * never `n²` — additive field, `intents`/`applyIntent`/`clearIntent` are unchanged.\n */\n excludedByIntent: number[];\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 // One `filterNormalizedLog` call per prefix (`0..intents.length`, so `n + 1` for `n`\n // intents) — `prefixCaseCounts[i]` is the case count after applying only the first `i`\n // intents. Each intent's own excluded count is then just the drop between two adjacent\n // prefixes, so nothing here is O(intents²) (RM-056, #205).\n const excludedByIntent = useMemo(() => {\n if (intents.length === 0) return [];\n const prefixCaseCounts: number[] = [];\n for (let i = 0; i <= intents.length; i += 1) {\n prefixCaseCounts.push(filterNormalizedLog(log, intents.slice(0, i)).totals.cases);\n }\n return intents.map((_, i) => prefixCaseCounts[i]! - prefixCaseCounts[i + 1]!);\n }, [log, intents]);\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 excludedByIntent,\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","/**\n * casesFromLog — RM-055.\n *\n * A pure per-case summary over an event log: one {@link CaseRow} per case, shaped for\n * `CaseTable`'s default columns and CSV export (`toCsv`). `variantId` is read off\n * {@link extractVariants} rather than recomputed independently — `extractVariants` is the\n * one place that disambiguates a 64-bit hash collision between two different sequences\n * with a `-1`/`-2` suffix, so recomputing `variantId(sequence)` here directly would\n * silently diverge from a variant explorer over the SAME log on the rare log where that\n * disambiguation fires. Reading it off `extractVariants`'s own `caseIds` keeps the two\n * views byte-identical always, not just usually.\n *\n * Framework-free, deterministic: no React, no `Date.now()`, no randomness. See\n * `.claude/rules/data.md` — nothing under `src/core/` may import React or an\n * `@elabs-ai/components-*` package.\n */\nimport { asNormalizedLog, type NormalizedCase } from \"./event-log\";\nimport { extractVariants } from \"./extract-variants\";\nimport type { EventLog } from \"./types\";\n\n/**\n * One case's own summary — its extent, size and path identity, not its trace. The shape\n * `CaseTable` (case-table/) renders one row per, and `CaseTimeline`'s own model reads a\n * single case's raw `EventRow[]` separately (a summary row has no per-activity detail to\n * build a Gantt row from).\n */\nexport interface CaseRow {\n caseId: string;\n /** ISO 8601. Empty string when the case has no resolvable extent — see `normalizeLog`. */\n start: string;\n end: string;\n durationMs: number;\n eventCount: number;\n /** Identical to the `id` {@link extractVariants} assigns the SAME case over the SAME log. */\n variantId: string;\n /**\n * Never set here: no conformance model lives in `/core` (`ProcessKpiStrip`'s own\n * `conformance` prop treats it the same way — a fitted value the HOST supplies, never a\n * number this package invents). A caller with a conformance result attaches it per row.\n */\n conformance?: \"conforming\" | \"nonConforming\" | \"unknown\";\n /** Carried over from `EventLog.caseAttributes`, untouched. */\n attributes?: Record<string, string | number | boolean | null>;\n}\n\n/** `NaN` (no resolvable extent) becomes `\"\"`, never a thrown `RangeError` or `\"Invalid Date\"`. */\nfunction toIso(ms: number): string {\n return Number.isFinite(ms) ? new Date(ms).toISOString() : \"\";\n}\n\nfunction toCaseRow(kase: NormalizedCase, variantId: string): CaseRow {\n const row: CaseRow = {\n caseId: kase.caseId,\n start: toIso(kase.start),\n end: toIso(kase.end),\n durationMs: Number.isFinite(kase.duration) ? kase.duration : 0,\n eventCount: kase.events.length,\n variantId,\n };\n if (kase.attributes !== undefined) {\n row.attributes = kase.attributes as Record<string, string | number | boolean | null>;\n }\n return row;\n}\n\n/**\n * One summary row per case in `log`, in the same first-appearance order `normalizeLog`\n * produces. `variantId` is sourced from {@link extractVariants} over the identical log —\n * see the module docblock for why that, and not a direct `variantId(sequence)` call, is\n * what keeps a `CaseTable` and a variant explorer over the same log always agreeing on\n * which cases share a path.\n *\n * An empty log answers an empty array.\n */\nexport function casesFromLog(log: EventLog): CaseRow[] {\n const normalized = asNormalizedLog(log);\n if (normalized.cases.length === 0) return [];\n\n const variantIdByCase = new Map<string, string>();\n for (const variant of extractVariants(normalized)) {\n for (const caseId of variant.caseIds) variantIdByCase.set(caseId, variant.id);\n }\n\n return normalized.cases.map((kase) => toCaseRow(kase, variantIdByCase.get(kase.caseId) ?? \"\"));\n}\n","/**\n * Token-based replay — RM-061.\n *\n * Replays each case of an event log against a {@link ReplayModel} (a lifted happy path)\n * and scores it with the classic produced / consumed / missing / remaining token counts:\n *\n * fitness = ½ · (1 − missing / consumed) + ½ · (1 − remaining / produced)\n *\n * The replay procedure — seed the initial marking, fire an enabled transition per event,\n * enable a disabled one through silent transitions first, force-insert missing tokens\n * only when that fails, then consume the final marking and count what is left over —\n * follows the token-based replay algorithm published in pm4js (BSD-3-Clause, credited in\n * `scripts/attributions.sources.json`). It is re-typed in TypeScript for the restricted\n * nets `liftHappyPath` builds; no pm4js code is copied, and nothing here derives from the\n * AGPL Python reference implementation.\n *\n * Deviation typing is this module's own layer on top of the counts: every forced token\n * (and every unreachable final token) is explained by at least one\n * {@link Deviation}, so a violation list can say WHY a case lost fitness.\n *\n * Scope boundary (analysis §9 risk 2): no alignment search. Replay is greedy and\n * single-pass per trace.\n *\n * Framework-free, deterministic: no React, no `@elabs-ai/components-*` import.\n */\nimport type { ConformanceResult } from \"./conformance\";\nimport { EDGE_KEY_SEPARATOR } from \"./discover-graph\";\nimport { asNormalizedLog, normalizeLog, type AnyLog, type NormalizedEvent } from \"./event-log\";\nimport type { ReplayModel, ReplayTransition } from \"./reference-model\";\nimport type { EventRow } from \"./types\";\n\n/**\n * Why a case lost fitness.\n *\n * - `undesired` — the activity is not in the model at all.\n * - `skipped` — a required modelled activity was jumped over and never observed in the case.\n * - `wrongOrder` — the activity is modelled but its preceding place was unmarked, and the\n * gap is not explained by an unobserved step (it ran too early, too late, or again).\n * - `wrongStart` — the first event of the case is not one the initial marking enables.\n * - `incomplete` — the case ended before reaching the model's final marking.\n */\nexport type DeviationType = \"undesired\" | \"skipped\" | \"wrongOrder\" | \"wrongStart\" | \"incomplete\";\n\n/** Every {@link DeviationType}, in a stable display order. */\nexport const DEVIATION_TYPES: readonly DeviationType[] = [\n \"undesired\",\n \"skipped\",\n \"wrongOrder\",\n \"wrongStart\",\n \"incomplete\",\n];\n\n/** One deviation found while replaying a case. */\nexport interface Deviation {\n type: DeviationType;\n /**\n * The activity the deviation is about: the observed event for `undesired`/`wrongOrder`/\n * `wrongStart`, the missing step for `skipped`, the last observed event for `incomplete`.\n */\n activity?: string;\n /** The activity the model expected next at that point, when one is determinable. */\n expected?: string;\n /**\n * Zero-based index into the case's ordered trace of the event where the deviation was\n * detected. `incomplete` uses the trace length (the position after the last event).\n */\n at: number;\n}\n\n/** Replay result for one case. */\nexport interface TraceReplayResult {\n caseId: string;\n produced: number;\n consumed: number;\n missing: number;\n remaining: number;\n /** `½(1 − missing/consumed) + ½(1 − remaining/produced)`, in `[0, 1]`. */\n fitness: number;\n deviations: Deviation[];\n}\n\n// ── Compiled model ────────────────────────────────────────────────────────────\n\ninterface CompiledModel {\n model: ReplayModel;\n /** Visible (`step`/`repeat`) transitions by activity, `step`s first. */\n byActivity: Map<string, ReplayTransition[]>;\n /** place → outgoing single-input transitions, silent ones first. */\n outgoing: Map<string, ReplayTransition[]>;\n /** place → outgoing silent single-input transitions. */\n silentOutgoing: Map<string, ReplayTransition[]>;\n /** `step` transitions in model order — the candidates for `expected`. */\n steps: ReplayTransition[];\n}\n\nconst compiled = new WeakMap<ReplayModel, CompiledModel>();\n\nfunction pushTo<K, V>(map: Map<K, V[]>, key: K, value: V): void {\n const list = map.get(key);\n if (list === undefined) map.set(key, [value]);\n else list.push(value);\n}\n\nfunction compile(model: ReplayModel): CompiledModel {\n const cached = compiled.get(model);\n if (cached !== undefined) return cached;\n\n const byActivity = new Map<string, ReplayTransition[]>();\n const outgoing = new Map<string, ReplayTransition[]>();\n const silentOutgoing = new Map<string, ReplayTransition[]>();\n const steps: ReplayTransition[] = [];\n\n const silent = model.transitions.filter((t) => t.kind === \"skip\");\n const visible = model.transitions.filter((t) => t.kind !== \"skip\");\n\n for (const t of [...visible].sort((a, b) => rank(a) - rank(b))) {\n pushTo(byActivity, t.activity, t);\n }\n for (const t of visible) if (t.kind === \"step\") steps.push(t);\n // Silent first, so a breadth-first search reaches a place over a skip arc before it\n // reaches it over the parallel visible step — an optional step is never reported\n // `skipped`.\n for (const t of [...silent, ...visible]) {\n if (t.consumes.length !== 1 || t.kind === \"repeat\") continue;\n const from = t.consumes[0] as string;\n pushTo(outgoing, from, t);\n if (t.kind === \"skip\") pushTo(silentOutgoing, from, t);\n }\n\n const result: CompiledModel = { model, byActivity, outgoing, silentOutgoing, steps };\n compiled.set(model, result);\n return result;\n}\n\nfunction rank(t: ReplayTransition): number {\n return t.kind === \"step\" ? 0 : 1;\n}\n\n// ── Marking helpers ───────────────────────────────────────────────────────────\n\ntype Marking = Map<string, number>;\n\nfunction tokens(marking: Marking, place: string): number {\n return marking.get(place) ?? 0;\n}\n\nfunction isEnabled(marking: Marking, t: ReplayTransition): boolean {\n const need = new Map<string, number>();\n for (const place of t.consumes) need.set(place, (need.get(place) ?? 0) + 1);\n for (const [place, count] of need) if (tokens(marking, place) < count) return false;\n return true;\n}\n\nfunction fire(marking: Marking, t: ReplayTransition): void {\n for (const place of t.consumes) marking.set(place, tokens(marking, place) - 1);\n for (const place of t.produces) marking.set(place, tokens(marking, place) + 1);\n}\n\n/**\n * Shortest chain of transitions from any marked place to `target`, walking `edges`.\n * Returns `undefined` when no marked place reaches it. A place that is already marked\n * yields an empty chain.\n */\nfunction shortestChain(\n marking: Marking,\n target: string,\n edges: Map<string, ReplayTransition[]>,\n): ReplayTransition[] | undefined {\n if (tokens(marking, target) > 0) return [];\n const parent = new Map<string, ReplayTransition | null>();\n const queue: string[] = [];\n for (const [place, count] of marking) {\n if (count > 0 && !parent.has(place)) {\n parent.set(place, null);\n queue.push(place);\n }\n }\n for (let head = 0; head < queue.length; head += 1) {\n const place = queue[head] as string;\n for (const t of edges.get(place) ?? []) {\n for (const next of t.produces) {\n if (parent.has(next)) continue;\n parent.set(next, t);\n if (next === target) return unwind(parent, target);\n queue.push(next);\n }\n }\n }\n return undefined;\n}\n\nfunction unwind(parent: Map<string, ReplayTransition | null>, target: string): ReplayTransition[] {\n const chain: ReplayTransition[] = [];\n let at = parent.get(target);\n while (at !== null && at !== undefined) {\n chain.unshift(at);\n at = parent.get(at.consumes[0] as string);\n }\n return chain;\n}\n\ninterface Counters {\n produced: number;\n consumed: number;\n missing: number;\n}\n\n/**\n * Try to put a token into every place in `places` by firing silent transitions only.\n * Commits the firings (and counts them) only when every place ends up marked.\n */\nfunction enableSilently(\n cm: CompiledModel,\n marking: Marking,\n places: string[],\n counters: Counters,\n): boolean {\n const trial = new Map(marking);\n let produced = 0;\n let consumed = 0;\n for (const place of places) {\n if (tokens(trial, place) > 0) continue;\n const chain = shortestChain(trial, place, cm.silentOutgoing);\n if (chain === undefined) return false;\n for (const t of chain) {\n if (!isEnabled(trial, t)) return false;\n fire(trial, t);\n consumed += t.consumes.length;\n produced += t.produces.length;\n }\n }\n for (const [place, count] of trial) marking.set(place, count);\n counters.produced += produced;\n counters.consumed += consumed;\n return true;\n}\n\n/** The first step the model would accept next from `marking`, looking through skip arcs. */\nfunction expectedNext(cm: CompiledModel, marking: Marking): string | undefined {\n const reachable = new Set<string>();\n const queue: string[] = [];\n for (const [place, count] of marking) {\n if (count > 0) {\n reachable.add(place);\n queue.push(place);\n }\n }\n for (let head = 0; head < queue.length; head += 1) {\n for (const t of cm.silentOutgoing.get(queue[head] as string) ?? []) {\n for (const next of t.produces) {\n if (reachable.has(next)) continue;\n reachable.add(next);\n queue.push(next);\n }\n }\n }\n for (const t of cm.steps) {\n if (t.consumes.every((place) => reachable.has(place))) return t.activity;\n }\n return undefined;\n}\n\nfunction fitnessOf(c: Counters & { remaining: number }): number {\n const missingTerm = c.consumed > 0 ? 1 - c.missing / c.consumed : 1;\n const remainingTerm = c.produced > 0 ? 1 - c.remaining / c.produced : 1;\n return Math.min(1, Math.max(0, 0.5 * missingTerm + 0.5 * remainingTerm));\n}\n\n// ── Replay ────────────────────────────────────────────────────────────────────\n\n/**\n * Replay an already-ordered activity sequence. The shared engine behind\n * {@link replayTrace}, {@link tokenReplay} and `conformanceRateSeries`.\n */\nexport function replayActivities(\n caseId: string,\n trace: readonly string[],\n model: ReplayModel,\n): TraceReplayResult {\n const cm = compile(model);\n const marking: Marking = new Map();\n const counters: Counters = { produced: 0, consumed: 0, missing: 0 };\n const deviations: Deviation[] = [];\n const observed = new Set(trace);\n\n for (const place of model.initialMarking) {\n marking.set(place, tokens(marking, place) + 1);\n counters.produced += 1;\n }\n\n trace.forEach((activity, at) => {\n const candidates = cm.byActivity.get(activity);\n\n if (candidates === undefined || candidates.length === 0) {\n // Not in the model: a phantom firing that consumes one forced (missing) token.\n deviations.push({ type: \"undesired\", activity, expected: expectedNext(cm, marking), at });\n counters.missing += 1;\n counters.consumed += 1;\n return;\n }\n\n const enabled =\n candidates.find((t) => isEnabled(marking, t)) ??\n candidates.find((t) => enableSilently(cm, marking, t.consumes, counters));\n if (enabled !== undefined) {\n fire(marking, enabled);\n counters.consumed += enabled.consumes.length;\n counters.produced += enabled.produces.length;\n return;\n }\n\n // Force: pick the candidate the current marking is closest to, else the first `step`.\n const expected = expectedNext(cm, marking);\n let chosen = candidates[0] as ReplayTransition;\n let chain: ReplayTransition[] | undefined;\n for (const t of candidates) {\n const gap = t.consumes.find((place) => tokens(marking, place) === 0);\n if (gap === undefined) continue;\n const found = shortestChain(marking, gap, cm.outgoing);\n if (found !== undefined && (chain === undefined || found.length < chain.length)) {\n chain = found;\n chosen = t;\n }\n }\n\n if (at === 0) {\n deviations.push({ type: \"wrongStart\", activity, expected, at });\n } else {\n const unobserved = (chain ?? []).filter(\n (t) => t.kind === \"step\" && !observed.has(t.activity),\n );\n if (unobserved.length > 0) {\n const seen = new Set<string>();\n for (const t of unobserved) {\n if (seen.has(t.activity)) continue;\n seen.add(t.activity);\n deviations.push({ type: \"skipped\", activity: t.activity, expected: t.activity, at });\n }\n } else {\n deviations.push({ type: \"wrongOrder\", activity, expected, at });\n }\n }\n\n for (const place of chosen.consumes) {\n if (tokens(marking, place) === 0) {\n marking.set(place, 1);\n counters.missing += 1;\n }\n }\n fire(marking, chosen);\n counters.consumed += chosen.consumes.length;\n counters.produced += chosen.produces.length;\n });\n\n // Consume the final marking, reaching it through skip arcs where the tail is optional.\n enableSilently(cm, marking, model.finalMarking, counters);\n const expected = expectedNext(cm, marking);\n let reachedEnd = true;\n for (const place of model.finalMarking) {\n if (tokens(marking, place) > 0) marking.set(place, tokens(marking, place) - 1);\n else {\n counters.missing += 1;\n reachedEnd = false;\n }\n counters.consumed += 1;\n }\n if (!reachedEnd) {\n deviations.push({\n type: \"incomplete\",\n activity: trace.length > 0 ? trace[trace.length - 1] : undefined,\n expected,\n at: trace.length,\n });\n }\n\n let remaining = 0;\n for (const count of marking.values()) remaining += count;\n\n const result = { caseId, ...counters, remaining };\n return { ...result, fitness: fitnessOf(result), deviations };\n}\n\nfunction activitiesOf(events: readonly NormalizedEvent[]): string[] {\n return events.map((event) => event.activity);\n}\n\n/**\n * Replay ONE case's raw rows against `model`.\n *\n * Rows are normalized first (ordered in time, lifecycle pairs merged into one instance),\n * exactly as every other `/core` derivation sees them. The rows are expected to share one\n * `caseId`; if they do not, every instance is replayed as one trace in start order under\n * the first row's `caseId`.\n */\nexport function replayTrace(events: EventRow[], model: ReplayModel): TraceReplayResult {\n const normalized = normalizeLog({ events });\n const instances = normalized.cases.flatMap((kase) => kase.events);\n if (normalized.cases.length > 1) instances.sort((a, b) => a.start - b.start);\n const caseId = normalized.cases[0]?.caseId ?? events[0]?.caseId ?? \"\";\n return replayActivities(caseId, activitiesOf(instances), model);\n}\n\n/** A {@link DeviationType}-keyed tally with every type present at zero. */\nexport function emptyDeviationCounts(): Record<DeviationType, number> {\n return { undesired: 0, skipped: 0, wrongOrder: 0, wrongStart: 0, incomplete: 0 };\n}\n\n/**\n * Replay every case of `log` against `model` and fold the results.\n *\n * - `deviationCounts[type]` — total deviations of that type across all cases.\n * - `perActivity[activity].deviations` — every deviation carries an activity, so these sum\n * to the total deviation count.\n * - `perEdge[source + EDGE_KEY_SEPARATOR + target].deviations` — a deviation detected at an\n * event with a predecessor is charged to the OBSERVED directly-follows edge into that\n * event (the key `discoverGraph` uses for the same edge). A deviation at the first event\n * or at the end of the case has no edge, so these sum to at most the total.\n * - `overallFitness` — mean trace fitness; `0` for an empty log.\n */\nexport function tokenReplay(log: AnyLog, model: ReplayModel): ConformanceResult {\n const normalized = asNormalizedLog(log);\n const traces: TraceReplayResult[] = [];\n const deviationCounts = emptyDeviationCounts();\n // Prototype-free, so an activity named `constructor` or `__proto__` is just a key.\n const perActivity = Object.create(null) as Record<string, { deviations: number }>;\n const perEdge = Object.create(null) as Record<string, { deviations: number }>;\n let fitnessSum = 0;\n\n for (const kase of normalized.cases) {\n const trace = activitiesOf(kase.events);\n const result = replayActivities(kase.caseId, trace, model);\n traces.push(result);\n fitnessSum += result.fitness;\n\n for (const deviation of result.deviations) {\n deviationCounts[deviation.type] += 1;\n if (deviation.activity !== undefined) {\n const entry = (perActivity[deviation.activity] ??= { deviations: 0 });\n entry.deviations += 1;\n }\n if (deviation.at > 0 && deviation.at < trace.length) {\n const key = `${trace[deviation.at - 1]}${EDGE_KEY_SEPARATOR}${trace[deviation.at]}`;\n const entry = (perEdge[key] ??= { deviations: 0 });\n entry.deviations += 1;\n }\n }\n }\n\n return {\n overallFitness: traces.length > 0 ? fitnessSum / traces.length : 0,\n traces,\n deviationCounts,\n perActivity,\n perEdge,\n };\n}\n","/**\n * Segment occurrences — RM-060.\n *\n * A performance spectrum (ProM's PSM) draws one line per case through a FIXED, chosen\n * sequence of segments, where a segment is one directly-follows pair `from → to`. That\n * needs something `TransitionStats` does not carry: the individual, time-ordered\n * OCCURRENCES of a pair (which case, when it entered, when it left), not one aggregate\n * across the whole log. Batching, FIFO violations and queue build-up are visible only\n * in the occurrences.\n *\n * ## What an occurrence measures\n *\n * `start` is the moment the `from` event COMPLETES and `end` the moment the `to` event\n * STARTS — the same idle-time reading `discoverGraph` defaults to, so a spectrum row and\n * the map's edge median agree about one pair. For atomic events (the common case) start\n * and completion coincide, so this is simply the two event timestamps. Overlapping\n * (parallel) events would give `end < start`; `end` is clamped to `start`, so `duration`\n * is never negative and a line never runs backwards.\n *\n * Deterministic and framework-free: no React, no `@elabs-ai/components-*`.\n */\n\nimport { EDGE_KEY_SEPARATOR } from \"./discover-graph\";\nimport { asNormalizedLog, type AnyLog, type NormalizedEvent } from \"./event-log\";\nimport { ascending, quantileSorted } from \"./scale\";\nimport type { ProcessGraph, Variant } from \"./types\";\n\n/** One row of a spectrum: the directly-follows pair `from → to`. */\nexport interface SegmentDefinition {\n from: string;\n to: string;\n /** Display label. Defaults to `\"from → to\"` in a view. */\n label?: string;\n}\n\n/** One case passing through one segment. */\nexport interface SegmentOccurrence {\n /** The segment's key — {@link segmentKey}`(from, to)`. */\n segment: string;\n caseId: string;\n /** When the `from` event completed, epoch ms. */\n start: number;\n /** When the `to` event started, epoch ms. Never before `start`. */\n end: number;\n /** `end - start`, in ms. */\n duration: number;\n}\n\n/** A duration quartile, `1` = fastest quarter, `4` = slowest. */\nexport type DurationQuartile = 1 | 2 | 3 | 4;\n\n/** The three cut points (25th, 50th, 75th percentile) a quartile is read against. */\nexport type QuartileThresholds = readonly [number, number, number];\n\n/**\n * The key of a segment — the same `source + separator + target` edge key `discoverGraph`\n * and `ProcessMap` use, so a segment round-trips to a transition selection unchanged.\n */\nexport function segmentKey(from: string, to: string): string {\n return `${from}${EDGE_KEY_SEPARATOR}${to}`;\n}\n\n/**\n * Every occurrence of the segments in `order`, walking each case's normalised sequence\n * once. Pairs not in `order` are skipped — a spectrum shows a chosen sequence, never the\n * whole graph. Output is in log order: case by case, and within a case in trace order.\n * Duplicate definitions in `order` are ignored.\n */\nexport function segmentsFor(log: AnyLog, order: readonly SegmentDefinition[]): SegmentOccurrence[] {\n const wanted = new Set<string>();\n for (const def of order) wanted.add(segmentKey(def.from, def.to));\n const out: SegmentOccurrence[] = [];\n if (wanted.size === 0) return out;\n\n for (const kase of asNormalizedLog(log).cases) {\n const trace = kase.events;\n for (let i = 1; i < trace.length; i += 1) {\n const previous = trace[i - 1] as NormalizedEvent;\n const event = trace[i] as NormalizedEvent;\n const key = segmentKey(previous.activity, event.activity);\n if (!wanted.has(key)) continue;\n const start = previous.end;\n if (!Number.isFinite(start) || !Number.isFinite(event.start)) continue;\n const end = Math.max(start, event.start);\n out.push({ segment: key, caseId: kase.caseId, start, end, duration: end - start });\n }\n }\n return out;\n}\n\n/**\n * The `limit` most frequent directly-follows pairs of `graph`, busiest first. Reads\n * `graph.transitions` in the order `discoverGraph` already ranks them (count descending,\n * ties by source then target), so the two can never disagree about \"the top N\".\n */\nexport function segmentOrderByFrequency(\n graph: ProcessGraph,\n limit: number = Number.POSITIVE_INFINITY,\n): SegmentDefinition[] {\n const n = Math.max(0, Math.floor(limit));\n return graph.transitions.slice(0, n).map((t) => ({ from: t.source, to: t.target }));\n}\n\n/**\n * The consecutive pairs of `variant.sequence`, in path order. A pair repeated by a loop\n * (`A, B, A, B`) appears once, at its first position — a spectrum has one row per segment.\n */\nexport function segmentOrderForVariant(variant: Variant): SegmentDefinition[] {\n const seen = new Set<string>();\n const out: SegmentDefinition[] = [];\n for (let i = 1; i < variant.sequence.length; i += 1) {\n const from = variant.sequence[i - 1] as string;\n const to = variant.sequence[i] as string;\n const key = segmentKey(from, to);\n if (seen.has(key)) continue;\n seen.add(key);\n out.push({ from, to });\n }\n return out;\n}\n\n/**\n * The 25th/50th/75th percentile of `durations` (R-7 interpolation, the same `quantile`\n * every process view uses). Non-finite samples are dropped; an empty input gives zeros.\n * Compute this ONCE per segment and read many occurrences against it with\n * {@link quartileOf} — {@link durationQuartile} sorts on every call.\n */\nexport function durationQuartileThresholds(durations: readonly number[]): QuartileThresholds {\n const sorted: number[] = [];\n for (const d of durations) if (Number.isFinite(d)) sorted.push(d);\n sorted.sort(ascending);\n if (sorted.length === 0) return [0, 0, 0];\n return [quantileSorted(sorted, 0.25), quantileSorted(sorted, 0.5), quantileSorted(sorted, 0.75)];\n}\n\n/** Which quartile `duration` falls in, against precomputed thresholds (upper bounds inclusive). */\nexport function quartileOf(duration: number, thresholds: QuartileThresholds): DurationQuartile {\n if (duration <= thresholds[0]) return 1;\n if (duration <= thresholds[1]) return 2;\n if (duration <= thresholds[2]) return 3;\n return 4;\n}\n\n/**\n * Buckets `occurrence` against ITS OWN segment's duration distribution — not the whole\n * log's — which is PSM's colour convention: a slow line is slow for that segment.\n */\nexport function durationQuartile(\n occurrence: SegmentOccurrence,\n allDurationsForSegment: readonly number[],\n): DurationQuartile {\n return quartileOf(occurrence.duration, durationQuartileThresholds(allDurationsForSegment));\n}\n","/**\n * diffGraphs — RM-064's pure diff between two discovered `ProcessGraph`s, the model\n * `ProcessCompare` renders (side-by-side or superimposed).\n *\n * Activities are matched by `id` (the activity name — the same identity `/core` uses\n * everywhere else); transitions are matched by `source`+{@link EDGE_KEY_SEPARATOR}+`target`,\n * exactly the key `reconcileGraph`'s own `transitionKey` builds, so a transition that moved\n * between the two graphs is never mistaken for two unrelated ones. An element present on\n * both sides is `\"common\"`; present only in `a` is `\"aOnly\"`; present only in `b` is\n * `\"bOnly\"` — every element in the union appears in exactly one of the three states.\n *\n * `delta`/`ratio` are computed once BOTH sides have the element (a `\"common\"` entry) —\n * comparing a real reading against nothing would be a fabricated number, not a diff. The\n * reference metric is each element's plain occurrence count — `instances` for an activity,\n * `count` for a transition — because those are the only two fields every `ActivityStats`/\n * `TransitionStats` always carries (never derived, never optional), so a delta is always\n * well-defined for a `\"common\"` entry, whatever metric a downstream view happens to paint\n * with. `ratio` is omitted rather than `Infinity` when `a`'s value is `0`.\n *\n * Pure, deterministic, framework-free — see the module-level rule in `types.ts`.\n */\nimport { EDGE_KEY_SEPARATOR } from \"./discover-graph\";\nimport type { ActivityStats, ProcessGraph, TransitionStats } from \"./types\";\n\n/** Which side(s) of the diff an activity or transition survives in. */\nexport type DiffState = \"common\" | \"aOnly\" | \"bOnly\";\n\n/** One activity's or transition's diff — its identity, its state, and (when `\"common\"`) its delta. */\nexport interface DiffEntry<Stats> {\n id: string;\n state: DiffState;\n /** Present unless the element is `\"bOnly\"`. */\n a?: Stats;\n /** Present unless the element is `\"aOnly\"`. */\n b?: Stats;\n /** `b`'s reference value minus `a`'s. Only set for a `\"common\"` entry. */\n delta?: number;\n /** `b`'s reference value divided by `a`'s. Only set for a `\"common\"` entry with `a > 0`. */\n ratio?: number;\n}\n\n/** The full diff between two graphs. */\nexport interface ProcessGraphDiff {\n activities: DiffEntry<ActivityStats>[];\n transitions: DiffEntry<TransitionStats>[];\n totals: { a: ProcessGraph[\"totals\"]; b: ProcessGraph[\"totals\"] };\n}\n\n/** `source`+{@link EDGE_KEY_SEPARATOR}+`target` — the same key `reconcileGraph` builds. */\nfunction transitionKey(transition: Pick<TransitionStats, \"source\" | \"target\">): string {\n return `${transition.source}${EDGE_KEY_SEPARATOR}${transition.target}`;\n}\n\n/** Every id in `first`, in order, then every id `second` adds that `first` did not have. */\nfunction unionIds(first: string[], second: string[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const id of first) {\n if (seen.has(id)) continue;\n seen.add(id);\n out.push(id);\n }\n for (const id of second) {\n if (seen.has(id)) continue;\n seen.add(id);\n out.push(id);\n }\n return out;\n}\n\nfunction diffEntries<Stats>(\n aById: ReadonlyMap<string, Stats>,\n bById: ReadonlyMap<string, Stats>,\n ids: string[],\n referenceValue: (stats: Stats) => number,\n): DiffEntry<Stats>[] {\n return ids.map((id) => {\n const a = aById.get(id);\n const b = bById.get(id);\n const state: DiffState = a !== undefined && b !== undefined ? \"common\" : a ? \"aOnly\" : \"bOnly\";\n const entry: DiffEntry<Stats> = { id, state };\n if (a !== undefined) entry.a = a;\n if (b !== undefined) entry.b = b;\n if (a !== undefined && b !== undefined) {\n const aValue = referenceValue(a);\n const bValue = referenceValue(b);\n entry.delta = bValue - aValue;\n if (aValue > 0) entry.ratio = bValue / aValue;\n }\n return entry;\n });\n}\n\n/**\n * Diff two discovered graphs. Neither input is mutated; the entries reference the original\n * `ActivityStats`/`TransitionStats` objects, never copies.\n */\nexport function diffGraphs(a: ProcessGraph, b: ProcessGraph): ProcessGraphDiff {\n const aActivities = new Map(a.activities.map((activity) => [activity.id, activity]));\n const bActivities = new Map(b.activities.map((activity) => [activity.id, activity]));\n const activityIds = unionIds(\n a.activities.map((activity) => activity.id),\n b.activities.map((activity) => activity.id),\n );\n\n const aTransitions = new Map(a.transitions.map((t) => [transitionKey(t), t]));\n const bTransitions = new Map(b.transitions.map((t) => [transitionKey(t), t]));\n const transitionIds = unionIds(\n a.transitions.map((t) => transitionKey(t)),\n b.transitions.map((t) => transitionKey(t)),\n );\n\n return {\n activities: diffEntries(aActivities, bActivities, activityIds, (stats) => stats.instances),\n transitions: diffEntries(aTransitions, bTransitions, transitionIds, (stats) => stats.count),\n totals: { a: a.totals, b: b.totals },\n };\n}\n","/**\n * replayTimeline — the timing model behind `ProcessReplay` (RM-065).\n *\n * Pure, deterministic, framework-free: no DOM, no clock, no randomness. It turns a log into\n * the moves each case makes along the directly-follows edges of its trace, then answers two\n * questions for any playhead `t`:\n *\n * - **Where is every in-flight case?** A token per case on the edge it is travelling, with\n * `progress` 0..1 from the source activity's completion to the target activity's start\n * (the idle time the edge measures). {@link replayFrameAt}.\n * - **How congested is each edge?** The number of distinct cases on it at any moment of the\n * current bucket (`bucketMs` wide). {@link ReplayFrame.congestion}.\n *\n * Edge ids are `/core`'s `source + EDGE_KEY_SEPARATOR + target` — the same string\n * `discoverGraph`'s transitions and the process map's React Flow edges use.\n *\n * Time is RELATIVE to the timeline's `origin`: `t = 0` is the earliest event in wall-clock\n * mode, or each case's own first activity in `synchronizedStart` mode (Disco's\n * \"synchronized start\"), where `origin` is `0` and absolute timestamps are meaningless.\n */\nimport { EDGE_KEY_SEPARATOR } from \"./discover-graph\";\nimport { asNormalizedLog, type AnyLog } from \"./event-log\";\n\n/** One case travelling one edge. Times are relative to {@link ReplayTimeline.origin}. */\nexport interface ReplaySegment {\n caseId: string;\n /** `source + EDGE_KEY_SEPARATOR + target`. */\n edgeId: string;\n source: string;\n target: string;\n /** The source activity's completion. */\n enterAt: number;\n /** The target activity's start — never before {@link enterAt}. */\n exitAt: number;\n}\n\n/** One case's token on one edge at a playhead. */\nexport interface ReplayFrameToken {\n caseId: string;\n edgeId: string;\n /** 0 at the source end, 1 at the target end. */\n progress: number;\n}\n\n/** The replay at one playhead. */\nexport interface ReplayFrame {\n /** Playhead, relative to the timeline origin. */\n t: number;\n /** One token per in-flight case, in segment order. */\n tokens: ReplayFrameToken[];\n /** Edge id → distinct cases on that edge during the bucket starting at `t`. Zero edges are omitted. */\n congestion: Record<string, number>;\n}\n\n/** Options for {@link replayTimeline}. */\nexport interface ReplayTimelineOptions {\n /** Bucket width in ms. Defaults to {@link defaultReplayBucketMs}. Raised when it would exceed {@link REPLAY_MAX_FRAMES}. */\n bucketMs?: number;\n /** Align every case's first activity to `t = 0`. @default false */\n synchronizedStart?: boolean;\n}\n\n/** A replay-ready timeline. */\nexport interface ReplayTimeline {\n /** Epoch ms of `t = 0` in wall-clock mode; `0` in synchronized-start mode. */\n origin: number;\n /** Last relative instant any case reaches. `0` for an empty log. */\n duration: number;\n /** The bucket width actually used. */\n bucketMs: number;\n synchronizedStart: boolean;\n /** Every edge move of every case, ordered by `enterAt` then case order. */\n segments: ReplaySegment[];\n /** One frame per bucket, `frames[i].t === i * bucketMs`, covering `[0, duration]`. */\n frames: ReplayFrame[];\n /** Highest congestion any edge reaches in any bucket. `0` when nothing moves. */\n peakCongestion: number;\n}\n\n/** How many frames {@link defaultReplayBucketMs} aims for. */\nexport const REPLAY_TARGET_FRAMES = 300;\n/** Upper bound on frames; a finer `bucketMs` is widened to respect it. */\nexport const REPLAY_MAX_FRAMES = 5000;\n\n/** The default bucket: the log span over {@link REPLAY_TARGET_FRAMES}, at least 1 ms. */\nexport function defaultReplayBucketMs(duration: number): number {\n if (!Number.isFinite(duration) || duration <= 0) return 1;\n return Math.max(1, duration / REPLAY_TARGET_FRAMES);\n}\n\n/** Every case's edge moves, relative to the chosen origin. */\nfunction buildSegments(\n log: AnyLog,\n synchronizedStart: boolean,\n): { origin: number; duration: number; segments: ReplaySegment[] } {\n const { cases } = asNormalizedLog(log);\n if (cases.length === 0) return { origin: 0, duration: 0, segments: [] };\n const origin = synchronizedStart ? 0 : Math.min(...cases.map((c) => c.start));\n const segments: ReplaySegment[] = [];\n let duration = 0;\n for (const trace of cases) {\n const base = synchronizedStart ? trace.start : origin;\n duration = Math.max(duration, trace.end - base);\n for (let i = 1; i < trace.events.length; i++) {\n const from = trace.events[i - 1]!;\n const to = trace.events[i]!;\n const enterAt = from.end - base;\n segments.push({\n caseId: trace.caseId,\n edgeId: `${from.activity}${EDGE_KEY_SEPARATOR}${to.activity}`,\n source: from.activity,\n target: to.activity,\n enterAt,\n exitAt: Math.max(enterAt, to.start - base),\n });\n }\n }\n // Stable: equal `enterAt` keeps case order.\n segments.sort((a, b) => a.enterAt - b.enterAt);\n return { origin, duration, segments };\n}\n\n/** Tokens at `t`: a segment is in flight over `[enterAt, exitAt)`. */\nfunction tokensAt(segments: readonly ReplaySegment[], t: number): ReplayFrameToken[] {\n const tokens: ReplayFrameToken[] = [];\n for (const segment of segments) {\n if (segment.enterAt > t) break;\n if (t >= segment.exitAt) continue;\n tokens.push({\n caseId: segment.caseId,\n edgeId: segment.edgeId,\n progress: (t - segment.enterAt) / (segment.exitAt - segment.enterAt),\n });\n }\n return tokens;\n}\n\n/** Distinct cases per edge overlapping `[start, end)`. A zero-length move counts where it happens. */\nfunction congestionIn(\n segments: readonly ReplaySegment[],\n start: number,\n end: number,\n): Record<string, number> {\n const casesByEdge = new Map<string, Set<string>>();\n for (const segment of segments) {\n if (segment.enterAt >= end) break;\n const overlaps = segment.exitAt > start || segment.enterAt >= start;\n if (!overlaps) continue;\n let cases = casesByEdge.get(segment.edgeId);\n if (!cases) casesByEdge.set(segment.edgeId, (cases = new Set()));\n cases.add(segment.caseId);\n }\n const congestion: Record<string, number> = {};\n for (const [edgeId, cases] of casesByEdge) congestion[edgeId] = cases.size;\n return congestion;\n}\n\n/**\n * Bucket a log into replay frames.\n *\n * @example\n * ```ts\n * const timeline = replayTimeline(log, { synchronizedStart: true });\n * const frame = replayFrameAt(timeline, timeline.duration / 2);\n * ```\n */\nexport function replayTimeline(log: AnyLog, options: ReplayTimelineOptions = {}): ReplayTimeline {\n const synchronizedStart = options.synchronizedStart ?? false;\n const { origin, duration, segments } = buildSegments(log, synchronizedStart);\n const requested =\n options.bucketMs !== undefined && Number.isFinite(options.bucketMs) && options.bucketMs > 0\n ? options.bucketMs\n : defaultReplayBucketMs(duration);\n const bucketMs = Math.max(requested, duration / (REPLAY_MAX_FRAMES - 1));\n const frameCount = Math.floor(duration / bucketMs) + 1;\n const frames: ReplayFrame[] = [];\n let peakCongestion = 0;\n for (let i = 0; i < frameCount; i++) {\n const t = i * bucketMs;\n const congestion = congestionIn(segments, t, t + bucketMs);\n for (const count of Object.values(congestion)) peakCongestion = Math.max(peakCongestion, count);\n frames.push({ t, tokens: tokensAt(segments, t), congestion });\n }\n return { origin, duration, bucketMs, synchronizedStart, segments, frames, peakCongestion };\n}\n\n/**\n * The replay at any playhead — tokens at exactly `t` (not snapped), congestion from the\n * bucket containing `t`. `t` is clamped to `[0, duration]`.\n */\nexport function replayFrameAt(timeline: ReplayTimeline, t: number): ReplayFrame {\n const clamped = Math.min(Math.max(Number.isFinite(t) ? t : 0, 0), timeline.duration);\n const bucket = Math.min(Math.floor(clamped / timeline.bucketMs), timeline.frames.length - 1);\n return {\n t: clamped,\n tokens: tokensAt(timeline.segments, clamped),\n congestion: timeline.frames[Math.max(bucket, 0)]?.congestion ?? {},\n };\n}\n\n/** One transition in {@link rankReplayCongestion}'s list. */\nexport interface ReplayCongestionEntry {\n edgeId: string;\n source: string;\n target: string;\n /** Most distinct cases on the edge in one bucket. */\n peak: number;\n /** Relative start of the first bucket reaching {@link peak}. */\n peakAt: number;\n /** Mean congestion over every bucket of the timeline. */\n mean: number;\n}\n\n/** Transitions ranked by peak congestion, then mean, then edge id. Edges that never carry a case are omitted. */\nexport function rankReplayCongestion(\n timeline: ReplayTimeline,\n limit = Number.POSITIVE_INFINITY,\n): ReplayCongestionEntry[] {\n const ends = new Map<string, { source: string; target: string }>();\n for (const s of timeline.segments) ends.set(s.edgeId, { source: s.source, target: s.target });\n const byEdge = new Map<string, ReplayCongestionEntry>();\n for (const frame of timeline.frames) {\n for (const [edgeId, count] of Object.entries(frame.congestion)) {\n let entry = byEdge.get(edgeId);\n if (!entry) {\n const { source, target } = ends.get(edgeId)!;\n byEdge.set(edgeId, (entry = { edgeId, source, target, peak: 0, peakAt: 0, mean: 0 }));\n }\n if (count > entry.peak) {\n entry.peak = count;\n entry.peakAt = frame.t;\n }\n entry.mean += count;\n }\n }\n const frameCount = Math.max(timeline.frames.length, 1);\n return [...byEdge.values()]\n .map((entry) => ({ ...entry, mean: entry.mean / frameCount }))\n .sort(\n (a, b) =>\n b.peak - a.peak ||\n b.mean - a.mean ||\n (a.edgeId < b.edgeId ? -1 : a.edgeId > b.edgeId ? 1 : 0),\n )\n .slice(0, limit);\n}\n\n/** Smallest and largest token radius, in px. */\nexport const REPLAY_TOKEN_RADIUS_RANGE = Object.freeze([3, 8] as const);\n\n/**\n * Token radius for an edge's congestion: the radius above the minimum grows with\n * √(count / peak), so a busier edge's blob grows by area rather than by diameter.\n */\nexport function replayTokenRadius(congestion: number, peak: number): number {\n const [min, max] = REPLAY_TOKEN_RADIUS_RANGE;\n if (!(peak > 0) || !(congestion > 0)) return min;\n const share = Math.min(congestion / peak, 1);\n return min + (max - min) * Math.sqrt(share);\n}\n","\"use client\";\n\n/**\n * VariantExplorer — the ranked list of activity sequences (RM-054, issue #203).\n *\n * One row per variant: its activity sequence as colour-keyed chips, how many cases follow\n * it, the share of all cases it covers, and its median end-to-end duration. Multi-select\n * with checkboxes, or let the coverage slider pick \"the fewest paths that explain N % of\n * cases\" in one move.\n *\n * ## It emits; it never filters\n *\n * `onSelect(ids, mode)` is the whole output. A playbook wires it to\n * `useProcessExplorer().applyIntent({ kind: \"variant\", ids })` so the map re-derives from\n * the chosen cases; the explorer itself never calls `filterLog`. The list NARROWS rather\n * than ghosts under a filter (decided 2026-09-05): `selectionStates.variants` carries\n * `selected` and only carries `excluded` when a host engine supplies it.\n *\n * ## Shared colours\n *\n * Chips are painted from the SAME `ActivityColorScale` instance `ProcessMap` receives as\n * `colorScale`, through the same `activityAccentStyle`, so an activity is one colour in\n * both views. Colour is never the only channel: every chip prints its label (or two-letter\n * code) and every row has a full accessible name.\n *\n * ## Scale\n *\n * Rows are virtualized with `@tanstack/react-virtual`, so 2 000 variants mount only the\n * rows in view plus a small overscan. Keyboard: the checkboxes form one roving tab stop;\n * Arrow Up/Down, Page Up/Down, Home and End move between rows, Space toggles the focused\n * row, Enter toggles it too (same as a click).\n */\nimport {\n forwardRef,\n useCallback,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type KeyboardEvent,\n} from \"react\";\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\nimport {\n Button,\n Slider,\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 type { ActivityColorScale } from \"../core/activity-color-scale\";\nimport type { Variant } from \"../core/types\";\nimport {\n formatDurationMs,\n type ProcessSelectionState,\n type ProcessSelectionStates,\n} from \"../process-map/map-model\";\nimport type { FilterIntent } from \"../use-process-explorer\";\nimport {\n fillLabel,\n selectVariantsByCoverage,\n VARIANT_EXPLORER_COLUMNS,\n type VariantExplorerColumn,\n} from \"./variant-explorer-model\";\nimport { VariantRow, type VariantRowText } from \"./variant-row\";\n\n/** Row height in px — fixed, so the virtualizer never has to measure. Matches `h-10`. */\nexport const VARIANT_EXPLORER_ROW_HEIGHT = 40;\n\n/** How a selection change applies: replace the whole selection, or flip these ids. */\nexport type VariantSelectMode = \"replace\" | \"toggle\";\n\n/**\n * Every user-visible string. `{name}` placeholders are filled at render. Override any\n * subset through `labels` to localize.\n */\nexport interface VariantExplorerLabels {\n /** Accessible name of the list. */\n list: string;\n columnVariant: string;\n columnSequence: string;\n columnCases: string;\n columnCoverage: string;\n columnMedianDuration: string;\n columnState: string;\n /** `{rank}`, `{activities}`, `{cases}`, `{coverage}` (a whole percent number). */\n rowSummary: string;\n /** `{activities}` — the sequence joined with commas. */\n sequence: string;\n activitiesOne: string;\n activitiesOther: string;\n casesOne: string;\n casesOther: string;\n selected: string;\n excluded: string;\n coverageTarget: string;\n /** `{percent}`, `{count}`. */\n coverageTargetValue: string;\n filterToSelected: string;\n tableCaption: string;\n loading: string;\n empty: string;\n emptyBody: string;\n}\n\n/** The shipped English labels. */\nexport const VARIANT_EXPLORER_DEFAULT_LABELS: Readonly<VariantExplorerLabels> = Object.freeze({\n list: \"Variants\",\n columnVariant: \"Variant\",\n columnSequence: \"Sequence\",\n columnCases: \"Cases\",\n columnCoverage: \"Coverage\",\n columnMedianDuration: \"Median duration\",\n columnState: \"State\",\n rowSummary: \"Variant {rank}, {activities}, {cases}, {coverage} percent coverage\",\n sequence: \"Sequence: {activities}\",\n activitiesOne: \"{count} activity\",\n activitiesOther: \"{count} activities\",\n casesOne: \"{count} case\",\n casesOther: \"{count} cases\",\n selected: \"selected\",\n excluded: \"excluded\",\n coverageTarget: \"Coverage target\",\n coverageTargetValue: \"{percent} of cases · {count} variants\",\n filterToSelected: \"Show only selected variants\",\n tableCaption: \"Variants — cases, coverage and median duration per activity sequence\",\n loading: \"Extracting variants…\",\n empty: \"No variants\",\n emptyBody: \"No cases match the current filters.\",\n});\n\n/** Props for {@link VariantExplorer}. `onSelect` shadows the DOM handler, so it is omitted. */\nexport interface VariantExplorerProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** Variants in `extractVariants` order (count descending). */\n variants: Variant[];\n /** The shared colour scale — hand `ProcessMap` the same instance. */\n colorScale: ActivityColorScale;\n /** Tri-state per variant id; an absent key means `\"associated\"`. */\n selectionStates?: ProcessSelectionStates;\n /** Fires with the ids to select and how to apply them. The explorer never filters. */\n onSelect: (variantIds: string[], mode: VariantSelectMode) => void;\n /** When given, a button emits `{ kind: \"variant\", ids }` for the selected variants. */\n onFilterIntent?: (intent: FilterIntent) => void;\n /** Numeric columns to show. @default [\"cases\", \"coverage\", \"medianDuration\"] */\n columns?: VariantExplorerColumn[];\n /**\n * Initial coverage target, `0..1`. When given, a slider selects the smallest prefix of\n * variants whose cumulative share reaches the target (mode `\"replace\"`).\n */\n coverageTarget?: number;\n /** \"Variant DNA\" strip: two-character codes instead of full activity labels. */\n abbreviate?: boolean;\n /**\n * `\"swatch\"`: each step is one identity block with no text — the densest strip, for a\n * narrow rail. The strip keeps its accessible name and every block its `title`.\n * @default \"text\"\n */\n sequenceDisplay?: \"text\" | \"swatch\";\n /** Render the accessible table twin instead of the list. @default false */\n tableView?: boolean;\n /** No variants yet. Renders the loading panel rather than an empty list. */\n loading?: boolean;\n /** Override any user-visible string. */\n labels?: Partial<VariantExplorerLabels>;\n}\n\nfunction stateOf(states: ProcessSelectionStates | undefined, id: string): ProcessSelectionState {\n return states?.variants?.[id] ?? \"associated\";\n}\n\n/**\n * The variant explorer.\n *\n * @example\n * ```tsx\n * const scale = useMemo(() => activityColorScale(fullGraph), [fullGraph]);\n * <VariantExplorer\n * variants={explorer.variants}\n * colorScale={scale}\n * selectionStates={explorer.selectionStates}\n * onSelect={(ids) => explorer.applyIntent({ kind: \"variant\", ids })}\n * />\n * ```\n */\nexport const VariantExplorer = forwardRef<HTMLDivElement, VariantExplorerProps>(\n function VariantExplorer(\n {\n variants,\n colorScale,\n selectionStates,\n onSelect,\n onFilterIntent,\n columns = VARIANT_EXPLORER_COLUMNS as VariantExplorerColumn[],\n coverageTarget,\n abbreviate = false,\n sequenceDisplay = \"text\",\n tableView = false,\n loading = false,\n labels: labelOverrides,\n className,\n ...props\n },\n ref,\n ) {\n const { locale, formatNumber } = useLocale();\n const labels = useMemo<VariantExplorerLabels>(\n () => ({ ...VARIANT_EXPLORER_DEFAULT_LABELS, ...labelOverrides }),\n [labelOverrides],\n );\n const pluralRules = useMemo(() => new Intl.PluralRules(locale), [locale]);\n const plural = useCallback(\n (count: number, one: string, other: string) =>\n fillLabel(pluralRules.select(count) === \"one\" ? one : other, {\n count: formatNumber(count),\n }),\n [pluralRules, formatNumber],\n );\n const formatShare = useCallback(\n (share: number) => formatNumber(share, { style: \"percent\", maximumFractionDigits: 1 }),\n [formatNumber],\n );\n\n const selectedIds = useMemo(\n () => variants.filter((v) => stateOf(selectionStates, v.id) === \"selected\").map((v) => v.id),\n [variants, selectionStates],\n );\n\n const toggle = useCallback((id: string) => onSelect([id], \"toggle\"), [onSelect]);\n\n const rowText = useCallback(\n (variant: Variant, rank: number, state: ProcessSelectionState): VariantRowText => {\n const activityNames = variant.sequence.map((id) => colorScale.labelFor(id));\n let summary = fillLabel(labels.rowSummary, {\n rank,\n activities: plural(variant.sequence.length, labels.activitiesOne, labels.activitiesOther),\n cases: plural(variant.count, labels.casesOne, labels.casesOther),\n coverage: formatNumber(Math.round(variant.share * 100)),\n });\n if (state === \"selected\") summary = `${summary}, ${labels.selected}`;\n if (state === \"excluded\") summary = `${summary}, ${labels.excluded}`;\n return {\n summary,\n sequence: fillLabel(labels.sequence, { activities: activityNames.join(\", \") }),\n cases: formatNumber(variant.count),\n coverage: formatShare(variant.share),\n medianDuration: formatDurationMs(variant.duration.median),\n };\n },\n [colorScale, labels, plural, formatNumber, formatShare],\n );\n\n // ── Coverage target ────────────────────────────────────────────────────\n const sliderLabelId = useId();\n const [targetPercent, setTargetPercent] = useState(() =>\n Math.round(Math.min(1, Math.max(0, coverageTarget ?? 0)) * 100),\n );\n const targetCount = useMemo(\n () => selectVariantsByCoverage(variants, targetPercent / 100).length,\n [variants, targetPercent],\n );\n\n // ── Virtualized list + roving focus ────────────────────────────────────\n const scrollRef = useRef<HTMLDivElement>(null);\n const listRef = useRef<HTMLDivElement>(null);\n const virtualizer = useVirtualizer({\n count: variants.length,\n getScrollElement: () => scrollRef.current,\n estimateSize: () => VARIANT_EXPLORER_ROW_HEIGHT,\n overscan: 6,\n });\n const [activeIndex, setActiveIndex] = useState(0);\n const virtualRows = virtualizer.getVirtualItems();\n // The roving tab stop is the last focused row — but only while that row is mounted. Once\n // the reader scrolls it out of the window (wheel, scrollbar), the stop moves to the first\n // row in view, so Tab can always reach the list and never lands on an unmounted row.\n const clampedActive = Math.min(activeIndex, Math.max(0, variants.length - 1));\n const tabIndex = virtualRows.some((item) => item.index === clampedActive)\n ? clampedActive\n : (virtualizer.range?.startIndex ?? virtualRows[0]?.index ?? clampedActive);\n const pendingFocus = useRef<number | null>(null);\n\n // A row scrolled into view by the keyboard mounts on a LATER render than the key press,\n // so focus is handed over whenever the pending row finally exists. No deps on purpose:\n // it has to look after every render until the row is there.\n useLayoutEffect(() => {\n const index = pendingFocus.current;\n if (index === null) return;\n const checkbox = listRef.current?.querySelector<HTMLElement>(\n `[data-index=\"${index}\"] [data-slot=\"checkbox\"]`,\n );\n if (checkbox) {\n pendingFocus.current = null;\n checkbox.focus();\n }\n });\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n if (variants.length === 0) return;\n const last = variants.length - 1;\n const page = Math.max(\n 1,\n Math.floor((scrollRef.current?.clientHeight ?? 0) / VARIANT_EXPLORER_ROW_HEIGHT),\n );\n let next: number | null = null;\n switch (event.key) {\n case \"ArrowDown\":\n next = Math.min(last, tabIndex + 1);\n break;\n case \"ArrowUp\":\n next = Math.max(0, tabIndex - 1);\n break;\n case \"PageDown\":\n next = Math.min(last, tabIndex + page);\n break;\n case \"PageUp\":\n next = Math.max(0, tabIndex - page);\n break;\n case \"Home\":\n next = 0;\n break;\n case \"End\":\n next = last;\n break;\n case \"Enter\": {\n event.preventDefault();\n const variant = variants[tabIndex];\n if (variant) toggle(variant.id);\n return;\n }\n default:\n return;\n }\n event.preventDefault();\n setActiveIndex(next);\n pendingFocus.current = next;\n virtualizer.scrollToIndex(next, { align: \"auto\" });\n },\n [variants, tabIndex, toggle, virtualizer],\n );\n\n if (loading) {\n return (\n <div\n ref={ref}\n data-slot=\"variant-explorer\"\n data-state=\"loading\"\n className={cn(\"relative flex h-96 flex-col\", className)}\n {...props}\n >\n <StatePanel kind=\"loading\" title={labels.loading} />\n </div>\n );\n }\n\n if (variants.length === 0) {\n return (\n <div\n ref={ref}\n data-slot=\"variant-explorer\"\n data-state=\"empty\"\n className={cn(\"relative flex h-96 flex-col\", className)}\n {...props}\n >\n <StatePanel kind=\"empty\" title={labels.empty} description={labels.emptyBody} />\n </div>\n );\n }\n\n const toolbar =\n coverageTarget !== undefined || onFilterIntent ? (\n <div data-slot=\"variant-explorer-toolbar\" className=\"flex flex-wrap items-center gap-3\">\n {coverageTarget !== undefined ? (\n <div\n data-slot=\"variant-explorer-coverage-target\"\n className=\"flex min-w-0 flex-1 items-center gap-3\"\n >\n <span id={sliderLabelId} className=\"shrink-0 text-meta text-muted-foreground\">\n {labels.coverageTarget}\n </span>\n <Slider\n aria-labelledby={sliderLabelId}\n aria-valuetext={fillLabel(labels.coverageTargetValue, {\n percent: formatShare(targetPercent / 100),\n count: formatNumber(targetCount),\n })}\n className=\"min-w-24 max-w-64 flex-1\"\n min={0}\n max={100}\n step={1}\n value={[targetPercent]}\n onValueChange={(value) => setTargetPercent(value[0] ?? 0)}\n onValueCommit={(value) =>\n onSelect(selectVariantsByCoverage(variants, (value[0] ?? 0) / 100), \"replace\")\n }\n />\n <span className=\"shrink-0 text-meta tabular-nums\">\n {fillLabel(labels.coverageTargetValue, {\n percent: formatShare(targetPercent / 100),\n count: formatNumber(targetCount),\n })}\n </span>\n </div>\n ) : null}\n {onFilterIntent ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"ms-auto\"\n disabled={selectedIds.length === 0}\n onClick={() => onFilterIntent({ kind: \"variant\", ids: selectedIds })}\n >\n {labels.filterToSelected}\n </Button>\n ) : null}\n </div>\n ) : null;\n\n if (tableView) {\n return (\n <div\n ref={ref}\n data-slot=\"variant-explorer\"\n data-view=\"table\"\n className={cn(\"flex flex-col gap-3\", className)}\n {...props}\n >\n {toolbar}\n <Table data-slot=\"variant-explorer-table\">\n <TableCaption>{labels.tableCaption}</TableCaption>\n <TableHeader>\n <TableRow>\n <TableHead scope=\"col\">{labels.columnVariant}</TableHead>\n <TableHead scope=\"col\">{labels.columnSequence}</TableHead>\n {columns.includes(\"cases\") ? (\n <TableHead scope=\"col\">{labels.columnCases}</TableHead>\n ) : null}\n {columns.includes(\"coverage\") ? (\n <TableHead scope=\"col\">{labels.columnCoverage}</TableHead>\n ) : null}\n {columns.includes(\"medianDuration\") ? (\n <TableHead scope=\"col\">{labels.columnMedianDuration}</TableHead>\n ) : null}\n <TableHead scope=\"col\">{labels.columnState}</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {variants.map((variant, index) => {\n const state = stateOf(selectionStates, variant.id);\n const text = rowText(variant, index + 1, state);\n return (\n <TableRow\n key={variant.id}\n data-selection={state}\n data-state={state === \"selected\" ? \"selected\" : undefined}\n >\n <TableCell className=\"tabular-nums\">{index + 1}</TableCell>\n <TableCell>\n {variant.sequence.map((id) => colorScale.labelFor(id)).join(\" → \")}\n </TableCell>\n {columns.includes(\"cases\") ? (\n <TableCell className=\"tabular-nums\">{text.cases}</TableCell>\n ) : null}\n {columns.includes(\"coverage\") ? (\n <TableCell className=\"tabular-nums\">{text.coverage}</TableCell>\n ) : null}\n {columns.includes(\"medianDuration\") ? (\n <TableCell className=\"tabular-nums\">{text.medianDuration}</TableCell>\n ) : null}\n <TableCell>\n {state === \"selected\"\n ? labels.selected\n : state === \"excluded\"\n ? labels.excluded\n : \"\"}\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n data-slot=\"variant-explorer\"\n data-view=\"list\"\n className={cn(\"flex h-96 min-h-0 flex-col gap-3\", className)}\n {...props}\n >\n {toolbar}\n {/* header-band-exempt: a table column header row, not shell chrome */}\n <div\n aria-hidden=\"true\"\n data-slot=\"variant-explorer-header\"\n className=\"flex h-8 shrink-0 items-center gap-3 border-b border-border-strong px-3 text-meta text-muted-foreground\"\n >\n <span className=\"w-4 shrink-0\" />\n <span className=\"w-8 shrink-0\">#</span>\n <span className=\"min-w-0 flex-1\">{labels.columnSequence}</span>\n {columns.includes(\"cases\") ? (\n <span className=\"w-16 shrink-0 text-end\">{labels.columnCases}</span>\n ) : null}\n {columns.includes(\"coverage\") ? (\n <span className=\"w-32 shrink-0\">{labels.columnCoverage}</span>\n ) : null}\n {columns.includes(\"medianDuration\") ? (\n <span className=\"w-20 shrink-0 truncate text-end\">{labels.columnMedianDuration}</span>\n ) : null}\n </div>\n <div\n ref={scrollRef}\n data-slot=\"variant-explorer-viewport\"\n className=\"relative min-h-0 flex-1 overflow-auto\"\n onKeyDown={handleKeyDown}\n >\n <div\n ref={listRef}\n role=\"list\"\n aria-label={labels.list}\n data-slot=\"variant-explorer-list\"\n className=\"relative w-full\"\n style={{ height: virtualizer.getTotalSize() }}\n >\n {virtualRows.map((item) => {\n const variant = variants[item.index] as Variant;\n const state = stateOf(selectionStates, variant.id);\n return (\n <VariantRow\n key={variant.id}\n aria-setsize={variants.length}\n aria-posinset={item.index + 1}\n className=\"absolute inset-x-0 top-0\"\n style={{ transform: `translateY(${item.start}px)` }}\n variant={variant}\n rank={item.index + 1}\n index={item.index}\n selectionState={state}\n colorScale={colorScale}\n abbreviate={abbreviate}\n sequenceDisplay={sequenceDisplay}\n columns={columns}\n text={rowText(variant, item.index + 1, state)}\n tabbable={item.index === tabIndex}\n onToggle={toggle}\n onFocusRow={setActiveIndex}\n />\n );\n })}\n </div>\n </div>\n </div>\n );\n },\n);\n","/**\n * Pure helpers behind `VariantExplorer` (RM-054) — no React, so the selection semantics\n * are unit-testable on their own and identical on every render.\n */\nimport type { Variant } from \"../core/types\";\n\n/** The optional numeric columns a variant row can show, in display order. */\nexport type VariantExplorerColumn = \"cases\" | \"coverage\" | \"medianDuration\";\n\n/** Every column, in the order the explorer renders them. */\nexport const VARIANT_EXPLORER_COLUMNS: readonly VariantExplorerColumn[] = Object.freeze([\n \"cases\",\n \"coverage\",\n \"medianDuration\",\n]);\n\n/**\n * The smallest prefix of `variants` whose `cumulativeShare` reaches `target`.\n *\n * `variants` is expected in `extractVariants` order (count descending, fully tie-broken),\n * so the prefix is the set of most frequent paths that together cover `target` of the\n * cases. Deterministic: the same variants and target always yield the same id list. A\n * target of `0` or less selects nothing; a target above the last variant's share selects\n * every variant. A tiny epsilon absorbs float noise in `cumulativeShare` so a `0.5`\n * target is met by a variant whose share prints as exactly 50 %.\n */\nexport function selectVariantsByCoverage(variants: readonly Variant[], target: number): string[] {\n if (!(target > 0)) return [];\n const ids: string[] = [];\n for (const variant of variants) {\n ids.push(variant.id);\n if (variant.cumulativeShare >= target - 1e-9) break;\n }\n return ids;\n}\n\n/** Fill `{name}` placeholders in a labels string. Unknown names are left as written. */\nexport function fillLabel(template: string, vars: Record<string, string | number>): string {\n return template.replace(/\\{(\\w+)\\}/g, (match, key: string) =>\n key in vars ? String(vars[key]) : match,\n );\n}\n","\"use client\";\n\n/**\n * One variant as a list row (RM-054): selection checkbox, rank, sequence chips and the\n * requested numeric columns.\n *\n * The checkbox is the row's ONE focus target. Its accessible name is the whole row in\n * words (\"Variant 3, 6 activities, 214 cases, 12 percent coverage\"), so a keyboard or\n * screen-reader user gets every number without a second tab stop per row. A pointer click\n * anywhere on the row toggles it too — the checkbox already offers the same action from the\n * keyboard, so the row click is a convenience, never the only path.\n */\nimport { forwardRef, type CSSProperties, type HTMLAttributes, type MouseEvent } from \"react\";\nimport { Checkbox } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport type { ActivityColorScale } from \"../core/activity-color-scale\";\nimport type { Variant } from \"../core/types\";\nimport type { ProcessSelectionState } from \"../process-map/map-model\";\nimport { VariantCoverageBar } from \"./coverage-bar\";\nimport { VariantSequenceChips } from \"./sequence-chips\";\nimport type { VariantExplorerColumn } from \"./variant-explorer-model\";\n\n/** Pre-formatted text a row prints — the explorer formats once, with its locale. */\nexport interface VariantRowText {\n /** The checkbox's accessible name — the whole row in words. */\n summary: string;\n /** The sequence strip's accessible name. */\n sequence: string;\n cases: string;\n coverage: string;\n medianDuration: string;\n}\n\nexport interface VariantRowProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onToggle\"> {\n variant: Variant;\n /** 1-based position in the list. */\n rank: number;\n /** 0-based position in the list, stamped as `data-index` for the virtualizer. */\n index: number;\n selectionState: ProcessSelectionState;\n colorScale: ActivityColorScale;\n abbreviate: boolean;\n sequenceDisplay?: \"text\" | \"swatch\";\n columns: readonly VariantExplorerColumn[];\n text: VariantRowText;\n /** Whether this row's checkbox is the list's single tab stop. */\n tabbable: boolean;\n onToggle: (variantId: string) => void;\n onFocusRow: (index: number) => void;\n style?: CSSProperties;\n}\n\nexport const VariantRow = forwardRef<HTMLDivElement, VariantRowProps>(function VariantRow(\n {\n variant,\n rank,\n index,\n selectionState,\n colorScale,\n abbreviate,\n sequenceDisplay,\n columns,\n text,\n tabbable,\n onToggle,\n onFocusRow,\n className,\n ...props\n },\n ref,\n) {\n const selected = selectionState === \"selected\";\n\n const handleClick = (event: MouseEvent<HTMLDivElement>) => {\n // The checkbox handles its own click; toggling here too would undo it.\n if ((event.target as HTMLElement).closest('[data-slot=\"checkbox\"]')) return;\n onToggle(variant.id);\n };\n\n return (\n <div\n ref={ref}\n role=\"listitem\"\n data-slot=\"variant-explorer-row\"\n data-index={index}\n data-variant={variant.id}\n data-selection={selectionState}\n className={cn(\n \"flex h-10 items-center gap-3 border-b border-border-strong px-3 transition-colors duration-fast ease-standard motion-reduce:transition-none\",\n \"hover:bg-muted/60 data-[selection=selected]:bg-accent data-[selection=excluded]:text-muted-foreground\",\n className,\n )}\n onClick={handleClick}\n {...props}\n >\n <Checkbox\n checked={selected}\n aria-label={text.summary}\n tabIndex={tabbable ? 0 : -1}\n onCheckedChange={() => onToggle(variant.id)}\n onFocus={() => onFocusRow(index)}\n />\n <span\n aria-hidden=\"true\"\n className=\"w-8 shrink-0 text-meta text-muted-foreground tabular-nums\"\n >\n {rank}\n </span>\n <VariantSequenceChips\n className=\"flex-1\"\n sequence={variant.sequence}\n colorScale={colorScale}\n abbreviate={abbreviate}\n display={sequenceDisplay}\n label={text.sequence}\n />\n {columns.includes(\"cases\") ? (\n <span aria-hidden=\"true\" className=\"w-16 shrink-0 text-end text-meta tabular-nums\">\n {text.cases}\n </span>\n ) : null}\n {columns.includes(\"coverage\") ? (\n <VariantCoverageBar\n aria-hidden=\"true\"\n className=\"w-32 shrink-0\"\n share={variant.share}\n valueLabel={text.coverage}\n />\n ) : null}\n {columns.includes(\"medianDuration\") ? (\n <span aria-hidden=\"true\" className=\"w-20 shrink-0 text-end text-meta tabular-nums\">\n {text.medianDuration}\n </span>\n ) : null}\n </div>\n );\n});\n","\"use client\";\n\n/**\n * A variant's share of cases as a bar plus its printed percentage (RM-054).\n *\n * Composes `@elabs-ai/components-ui`'s `Progress` rather than drawing a bar. The bar is\n * `aria-hidden`: the same number is printed beside it and is part of the row's accessible\n * name, so a progressbar per row would only add 2 000 redundant announcements.\n */\nimport { forwardRef, type HTMLAttributes } from \"react\";\nimport { Progress } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nexport interface VariantCoverageBarProps extends HTMLAttributes<HTMLDivElement> {\n /** `0..1`. */\n share: number;\n /** The share, already formatted for display. */\n valueLabel: string;\n}\n\nexport const VariantCoverageBar = forwardRef<HTMLDivElement, VariantCoverageBarProps>(\n function VariantCoverageBar({ share, valueLabel, className, ...props }, ref) {\n const percent = Math.min(100, Math.max(0, share * 100));\n return (\n <div\n ref={ref}\n data-slot=\"variant-explorer-coverage\"\n className={cn(\"flex min-w-0 items-center gap-2\", className)}\n {...props}\n >\n <Progress aria-hidden=\"true\" value={percent} className=\"h-1.5 min-w-8 flex-1\" />\n <span className=\"w-12 shrink-0 text-end text-meta tabular-nums\">{valueLabel}</span>\n </div>\n );\n },\n);\n","\"use client\";\n\n/**\n * One variant's activity sequence as a row of chips (RM-054).\n *\n * Each chip is an activity: an identity swatch painted from the shared\n * `ActivityColorScale` (the same `activityAccentStyle` `ProcessActivityNode` uses, so the\n * two views cannot disagree) plus the activity's label — or, in the abbreviated\n * \"DNA strip\", its two-character code. Colour is never the only channel: the text always\n * names the activity, and the whole strip is one `role=\"img\"` whose accessible name lists\n * the full sequence, so an abbreviated code never reaches a screen reader in place of a\n * name. HTML spans, not SVG — nothing here needs a coordinate system.\n */\nimport { forwardRef, type HTMLAttributes } from \"react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport type { ActivityColorScale } from \"../core/activity-color-scale\";\nimport { activityAccentStyle } from \"../process-map/activity-accent\";\n\nexport interface VariantSequenceChipsProps extends HTMLAttributes<HTMLDivElement> {\n sequence: readonly string[];\n colorScale: ActivityColorScale;\n /** Two-character codes instead of full labels. */\n abbreviate?: boolean;\n /**\n * `\"swatch\"` drops the text and draws each activity as one identity block — the densest\n * strip, for a narrow rail where even two-character codes leave room for three steps.\n * The strip's own accessible name (and `title`) still lists the full sequence, and each\n * block carries its activity as `title`, so a name is always one hover away. Wins over\n * `abbreviate`. @default \"text\"\n */\n display?: \"text\" | \"swatch\";\n /** Accessible name for the strip — the full sequence in words. */\n label: string;\n}\n\nexport const VariantSequenceChips = forwardRef<HTMLDivElement, VariantSequenceChipsProps>(\n function VariantSequenceChips(\n { sequence, colorScale, abbreviate = false, display = \"text\", label, className, ...props },\n ref,\n ) {\n return (\n <div\n ref={ref}\n role=\"img\"\n aria-label={label}\n title={label}\n data-slot=\"variant-explorer-sequence\"\n data-abbreviated={abbreviate ? \"true\" : undefined}\n data-display={display === \"swatch\" ? \"swatch\" : undefined}\n className={cn(\n \"flex min-w-0 flex-nowrap items-center overflow-hidden\",\n display === \"swatch\" || abbreviate ? \"gap-0.5\" : \"gap-1\",\n className,\n )}\n {...props}\n >\n {sequence.map((activityId, position) => {\n const color = colorScale.colorFor(activityId);\n if (display === \"swatch\") {\n return (\n <span\n key={`${position}:${activityId}`}\n data-slot=\"variant-explorer-chip\"\n data-activity={activityId}\n data-color-token={color.token}\n data-pattern={color.pattern}\n title={colorScale.labelFor(activityId)}\n className=\"h-4 w-2 shrink-0 rounded-sm\"\n style={activityAccentStyle(color)}\n />\n );\n }\n return (\n <span\n // A sequence may repeat an activity (rework), so the id alone is not unique;\n // the position within an immutable sequence is the stable identity here.\n key={`${position}:${activityId}`}\n data-slot=\"variant-explorer-chip\"\n data-activity={activityId}\n data-color-token={color.token}\n data-pattern={color.pattern}\n className={cn(\n \"inline-flex shrink-0 items-center gap-1 rounded-sm border border-border bg-card text-meta text-foreground\",\n abbreviate ? \"px-1 font-mono tabular-nums\" : \"max-w-40 px-1.5\",\n )}\n >\n <span\n data-slot=\"variant-explorer-chip-swatch\"\n data-color-token={color.token}\n data-pattern={color.pattern}\n className=\"size-2 shrink-0 rounded-sm\"\n style={activityAccentStyle(color)}\n />\n <span className=\"truncate\">\n {abbreviate ? colorScale.codeFor(activityId) : colorScale.labelFor(activityId)}\n </span>\n </span>\n );\n })}\n </div>\n );\n },\n);\n","\"use client\";\n\n/**\n * ProcessFilterBar — filter chain breadcrumbs with excluded-count chips (RM-056, #205).\n *\n * §4 R13 of the process-mining components analysis requires \"filter breadcrumbs showing\n * the active filter chain, removable, with 'excluded' counts\" for every persona — the 2026\n * literature review's first systematic gap is \"no visible reference to what was filtered\n * out\", and this component is that visible reference. It composes `@elabs-ai/components-data`'s\n * `FilterBar`/`FilterChip` — the chip's own `count`/`countLabel`/`onRemove` contract already\n * carries the \"excluded N\" reading (RM-047) — and authors no chip markup of its own\n * (`pnpm check --rule process-reuse`).\n *\n * ## Index keys, not intent ids\n *\n * `useProcessExplorer`'s `intents: FilterIntent[]` carries no id field — `clearIntent` and\n * this bar's own `onRemove` both key by ARRAY INDEX, matching the hook's real contract\n * (`.claude/scratch/process-mining/wave-2/common-brief.md`). `excludedByIntent` is the\n * hook's own additive field (same file, this item's other touch): index `i` there is the\n * case count intent `i` ALONE excludes from the chain, parallel to `intents[i]`.\n *\n * ## The summary line never double-counts\n *\n * `filteredCases`/`totalCases` describe what FILTERING removed; `hiddenCounts.activities`\n * (from `AbstractionControls`, RM-052) describes what ABSTRACTION additionally hides from\n * the picture. The two are disjoint by construction (`useProcessExplorer`'s own module\n * docblock, Invariant F) — this component states them as two independently-omittable\n * clauses of one sentence rather than merging them into one number, the same \"two\n * independently-pluralized fragments, composed at the call site\" shape `AbstractionControls`\n * already uses for its own hidden-count line.\n */\nimport { forwardRef, type HTMLAttributes } from \"react\";\nimport { Button, useLocale } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { FilterBar, FilterChip } from \"@elabs-ai/components-data\";\nimport type { FilterIntent } from \"../use-process-explorer\";\n\n/** One intent, worded as a chip label — label-in-value text, never a bare activity name. */\nfunction intentChipLabel(t: ReturnType<typeof useLocale>[\"t\"], intent: FilterIntent): string {\n switch (intent.kind) {\n case \"with\":\n return t(\"process.filterBar.with\", { activity: intent.activity });\n case \"without\":\n return t(\"process.filterBar.without\", { activity: intent.activity });\n case \"startsWith\":\n return t(\"process.filterBar.startsWith\", { activity: intent.activity });\n case \"endsWith\":\n return t(\"process.filterBar.endsWith\", { activity: intent.activity });\n case \"variant\":\n return t(\"process.filterBar.variant\", { count: intent.ids.length });\n case \"cases\":\n return t(\"process.filterBar.cases\", { count: intent.ids.length });\n default:\n return t(\"process.filterBar.filter\");\n }\n}\n\nexport interface ProcessFilterBarProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** The active filter chain — `useProcessExplorer`'s own `intents`, in order. */\n intents: FilterIntent[];\n /**\n * How many cases EACH intent alone excludes, parallel to {@link intents} (same index) —\n * `useProcessExplorer`'s own additive `excludedByIntent` field.\n */\n excludedByIntent: number[];\n /** The unfiltered case count — the right half of the summary line's \"X of Y\". */\n totalCases: number;\n /** `useProcessExplorer`'s `kpis.cases` — the left half of the summary line's \"X of Y\". */\n filteredCases: number;\n /** What abstraction currently hides — `useProcessExplorer`'s own `hiddenCounts`. */\n hiddenCounts?: { activities: number; paths: number };\n /** Remove one intent by its index into {@link intents} — wire to `clearIntent`. */\n onRemove(index: number): void;\n /** Remove every intent at once. */\n onClearAll(): void;\n /** Accessible name for the chip row. Default from locale. */\n label?: string;\n}\n\n/**\n * Filter chain breadcrumbs: one removable chip per active intent, each carrying how many\n * cases that intent alone excludes, plus a summary line stating what filtering and\n * abstraction currently hide.\n */\nexport const ProcessFilterBar = forwardRef<HTMLDivElement, ProcessFilterBarProps>(\n function ProcessFilterBar(\n {\n intents,\n excludedByIntent,\n totalCases,\n filteredCases,\n hiddenCounts,\n onRemove,\n onClearAll,\n label,\n className,\n ...props\n },\n ref,\n ) {\n const { t, formatNumber } = useLocale();\n\n const hasIntents = intents.length > 0;\n const hasHiddenByAbstraction = (hiddenCounts?.activities ?? 0) > 0;\n\n // Two independently-omittable clauses, joined — mirrors `AbstractionControls`' own\n // hidden-count line (see the module docblock): \"what filtering removed\" and \"what\n // abstraction additionally hides\" are disjoint numbers, never merged into one.\n const casesClause =\n filteredCases === totalCases\n ? t(\"process.filterBar.showingAll\", {\n count: totalCases,\n total: formatNumber(totalCases),\n })\n : t(\"process.filterBar.showing\", {\n filtered: formatNumber(filteredCases),\n total: formatNumber(totalCases),\n });\n const summary = hasHiddenByAbstraction\n ? `${casesClause} · ${t(\"process.filterBar.hiddenByAbstraction\", {\n count: hiddenCounts!.activities,\n })}`\n : casesClause;\n\n return (\n <div\n ref={ref}\n data-slot=\"process-filter-bar\"\n className={cn(\"flex flex-col gap-2\", className)}\n {...props}\n >\n {hasIntents ? (\n // `FilterBar` (data package) has no `role`/`aria-label` of its own to forward —\n // this wrapping `div` is the group's real accessible-name carrier.\n <div role=\"group\" aria-label={label ?? t(\"process.filterBar.label\")}>\n <FilterBar\n actions={\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n data-slot=\"process-filter-bar-clear-all\"\n onClick={onClearAll}\n >\n {t(\"process.filterBar.clearAll\")}\n </Button>\n }\n >\n {intents.map((intent, index) => (\n <FilterChip\n key={`${intent.kind}-${index}`}\n label={intentChipLabel(t, intent)}\n count={excludedByIntent[index] ?? 0}\n countLabel={t(\"process.filterBar.excludedLabel\")}\n onRemove={() => onRemove(index)}\n />\n ))}\n </FilterBar>\n </div>\n ) : null}\n <p data-slot=\"process-filter-bar-summary\" className=\"text-meta text-muted-foreground\">\n {summary}\n </p>\n </div>\n );\n },\n);\n","\"use client\";\n\n/**\n * CaseTable — the case drill-down every auditor persona needs (RM-055, issue #204, §4 R11).\n *\n * A column CONFIGURATION over `@elabs-ai/components-data`'s `DataTable`, not a table\n * implementation (`.claude/rules/data.md` — \"primitives go down, compositions go up\"):\n * this file authors no `<table>` markup of its own. Row activation, keyboard navigation,\n * loading/empty states and the CSV column-order contract all come straight from `DataTable`;\n * the only thing this component adds is the domain reading (which field is a column, how a\n * duration/conformance value prints) and the export button.\n *\n * ## CSV export mirrors the VISIBLE columns\n *\n * `toCsv` is called with the SAME column set the table renders (the default set, or the\n * caller's own `columns` override) — in the same order, with the same header text — so a\n * consumer who narrows or reorders `columns` gets an export that matches what is on screen,\n * attribute columns included. Header/value resolution reads a `ColumnDef`'s own\n * `accessorKey`/`accessorFn` directly rather than re-deriving through a live table instance,\n * which keeps this a pure per-row mapping (no TanStack `Table` needed for the export path).\n */\nimport { forwardRef, useCallback, useMemo, type HTMLAttributes, type ReactNode } from \"react\";\nimport { Download } from \"lucide-react\";\nimport { DataTable, toCsv, type ColumnDef } from \"@elabs-ai/components-data\";\nimport { Button, downloadBlob, useLocale } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport type { CaseRow } from \"../core/cases-from-log\";\nimport { createCaseTableColumns } from \"./columns\";\n\nexport interface CaseTableProps extends HTMLAttributes<HTMLDivElement> {\n cases: CaseRow[];\n /** Column configuration over `DataTable`. Defaults to `createCaseTableColumns`'s set. */\n columns?: ColumnDef<CaseRow>[];\n /** Fired when a row is activated (click, or Enter/Space on its keyboard activation target). */\n onCaseOpen?: (caseId: string) => void;\n /** File name for the CSV export. Default `\"cases.csv\"`. */\n exportFileName?: string;\n loading?: boolean;\n /** Message shown when there are no cases and not loading. */\n emptyMessage?: ReactNode;\n}\n\ntype CaseTableColumn = ColumnDef<CaseRow> & {\n id?: string;\n accessorKey?: string;\n accessorFn?: (row: CaseRow, index: number) => unknown;\n};\n\nfunction csvKey(column: CaseTableColumn): string {\n return column.id ?? column.accessorKey ?? \"\";\n}\n\nfunction csvHeader(column: CaseTableColumn): string {\n return typeof column.header === \"string\" ? column.header : csvKey(column);\n}\n\nfunction csvValue(row: CaseRow, column: CaseTableColumn): string | number | boolean | null {\n const value =\n typeof column.accessorFn === \"function\"\n ? column.accessorFn(row, 0)\n : typeof column.accessorKey === \"string\"\n ? (row as unknown as Record<string, unknown>)[column.accessorKey]\n : undefined;\n if (value === null || value === undefined) return null;\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n return value;\n }\n return String(value);\n}\n\nfunction toCsvRecord(\n row: CaseRow,\n columns: CaseTableColumn[],\n): Record<string, string | number | boolean | null> {\n const record: Record<string, string | number | boolean | null> = {};\n for (const column of columns) {\n const key = csvKey(column);\n if (key) record[key] = csvValue(row, column);\n }\n return record;\n}\n\nexport const CaseTable = forwardRef<HTMLDivElement, CaseTableProps>(function CaseTable(\n {\n cases,\n columns,\n onCaseOpen,\n exportFileName = \"cases.csv\",\n loading = false,\n emptyMessage,\n className,\n ...props\n },\n ref,\n) {\n const { t, formatDate } = useLocale();\n\n const resolvedColumns = useMemo<CaseTableColumn[]>(\n () => columns ?? createCaseTableColumns({ t, formatDate }),\n [columns, t, formatDate],\n );\n\n const handleExport = useCallback(() => {\n const csvColumns = resolvedColumns.map((column) => ({\n key: csvKey(column),\n header: csvHeader(column),\n }));\n const rows = cases.map((row) => toCsvRecord(row, resolvedColumns));\n const csv = toCsv(rows, { columns: csvColumns });\n const blob = new Blob([csv], { type: \"text/csv;charset=utf-8;\" });\n downloadBlob(blob, `${exportFileName.replace(/\\.csv$/i, \"\")}.csv`);\n }, [cases, resolvedColumns, exportFileName]);\n\n return (\n <div\n ref={ref}\n data-slot=\"case-table\"\n className={cn(\"flex flex-col gap-2\", className)}\n {...props}\n >\n <div data-slot=\"case-table-toolbar\" className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleExport}\n disabled={cases.length === 0}\n >\n <Download aria-hidden=\"true\" />\n {t(\"process.caseTable.exportCsv\")}\n </Button>\n </div>\n <DataTable<CaseRow, unknown>\n data={cases}\n columns={resolvedColumns}\n getRowId={(row) => row.caseId}\n loading={loading}\n emptyMessage={emptyMessage ?? t(\"process.caseTable.empty\")}\n onRowClick={onCaseOpen ? (row) => onCaseOpen(row.original.caseId) : undefined}\n caption={t(\"process.caseTable.tableLabel\")}\n />\n </div>\n );\n});\n","/**\n * `CaseTable`'s default column set — RM-055.\n *\n * A column CONFIGURATION over `@elabs-ai/components-data`'s `DataTable`, not a table\n * implementation: every cell either prints a `CaseRow` field as-is or formats it with a\n * helper this package already ships (`formatDurationMs`), plus one `StatusBadge` for the\n * three-state conformance flag. See `.claude/rules/data.md` — \"primitives go down,\n * compositions go up\". Plain `.ts` (no JSX) — the one cell that renders a component\n * (`conformance`) builds it with `createElement`.\n *\n * Message KEYS, not resolved text, at module scope (mirrors `gantt.tsx`'s\n * `VIEW_MODE_LABEL_KEYS` — `t()` needs a hook, unavailable here); `createCaseTableColumns`\n * resolves them at the call site, which always has `useLocale()`.\n */\nimport { createElement } from \"react\";\nimport { CheckCircle2, AlertCircle, type LucideIcon } from \"lucide-react\";\nimport { type ColumnDef } from \"@elabs-ai/components-data\";\nimport { StatusBadge, type LocaleContextValue, type StatusTone } from \"@elabs-ai/components-ui\";\nimport type { CaseRow } from \"../core/cases-from-log\";\nimport { formatDurationMs } from \"../process-map/map-model\";\n\nexport const CASE_TABLE_COLUMN_LABEL_KEYS = {\n caseId: \"process.caseTable.columnCaseId\",\n start: \"process.caseTable.columnStart\",\n end: \"process.caseTable.columnEnd\",\n duration: \"process.caseTable.columnDuration\",\n eventCount: \"process.caseTable.columnEventCount\",\n variantId: \"process.caseTable.columnVariant\",\n conformance: \"process.caseTable.columnConformance\",\n} as const;\n\nconst CONFORMANCE_LABEL_KEYS: Record<NonNullable<CaseRow[\"conformance\"]>, string> = {\n conforming: \"process.caseTable.conformanceConforming\",\n nonConforming: \"process.caseTable.conformanceNonConforming\",\n unknown: \"process.caseTable.conformanceUnknown\",\n};\n\n/**\n * Maps a `CaseRow.conformance` value onto `StatusBadge`'s calm-only escape hatch (#363) —\n * a domain-specific three-state flag, not one of `StatusBadge`'s own canonical 7 statuses.\n */\nfunction conformanceStatus(\n conformance: CaseRow[\"conformance\"],\n t: LocaleContextValue[\"t\"],\n): { label: string; tone: StatusTone; icon?: LucideIcon } {\n switch (conformance) {\n case \"conforming\":\n return { label: t(CONFORMANCE_LABEL_KEYS.conforming), tone: \"success\", icon: CheckCircle2 };\n case \"nonConforming\":\n return {\n label: t(CONFORMANCE_LABEL_KEYS.nonConforming),\n tone: \"destructive\",\n icon: AlertCircle,\n };\n case \"unknown\":\n default:\n return { label: t(CONFORMANCE_LABEL_KEYS.unknown), tone: \"neutral\" };\n }\n}\n\nexport interface CreateCaseTableColumnsOptions {\n t: LocaleContextValue[\"t\"];\n formatDate: LocaleContextValue[\"formatDate\"];\n}\n\n/** The default `CaseTable` column set. Reproduce this shape (or subset it) to customize. */\nexport function createCaseTableColumns({\n t,\n formatDate,\n}: CreateCaseTableColumnsOptions): ColumnDef<CaseRow>[] {\n return [\n {\n accessorKey: \"caseId\",\n header: t(CASE_TABLE_COLUMN_LABEL_KEYS.caseId),\n },\n {\n accessorKey: \"start\",\n header: t(CASE_TABLE_COLUMN_LABEL_KEYS.start),\n cell: ({ getValue }) => {\n const value = getValue<string>();\n return value ? formatDate(new Date(value)) : \"—\";\n },\n },\n {\n accessorKey: \"end\",\n header: t(CASE_TABLE_COLUMN_LABEL_KEYS.end),\n cell: ({ getValue }) => {\n const value = getValue<string>();\n return value ? formatDate(new Date(value)) : \"—\";\n },\n },\n {\n accessorKey: \"durationMs\",\n header: t(CASE_TABLE_COLUMN_LABEL_KEYS.duration),\n meta: { numeric: true },\n cell: ({ getValue }) => formatDurationMs(getValue<number>()),\n },\n {\n accessorKey: \"eventCount\",\n header: t(CASE_TABLE_COLUMN_LABEL_KEYS.eventCount),\n meta: { numeric: true },\n },\n {\n accessorKey: \"variantId\",\n header: t(CASE_TABLE_COLUMN_LABEL_KEYS.variantId),\n },\n {\n accessorKey: \"conformance\",\n header: t(CASE_TABLE_COLUMN_LABEL_KEYS.conformance),\n cell: ({ getValue }) => {\n const status = conformanceStatus(getValue<CaseRow[\"conformance\"]>(), t);\n return createElement(StatusBadge, { status, size: \"sm\" });\n },\n },\n ];\n}\n","\"use client\";\n\n/**\n * CaseTimeline — the single-case Gantt every case drill-down opens into (RM-055, issue\n * #204, §4 R12): \"single-case timeline with activity durations and waiting time\".\n *\n * A THIN wrapper: it computes {@link buildCaseTimelineInstances}'s model and renders\n * `@elabs-ai/components-charts`'s `Gantt` — it does not draw a bar, a gap band or a grid\n * line itself (`.claude/rules/data.md` — \"primitives go down, compositions go up\"). Waiting\n * time reuses RM-047's `GanttTask.gaps` hatched-band rendering directly; there is no second\n * idle-time renderer here.\n *\n * ## The parallel flag carries a text channel, not just a colour\n *\n * Two overlapping instances get `status=\"info\"` (an existing `Gantt` tone — no new color,\n * per the RM's own instruction), but WCAG 1.4.1 forbids colour as the ONLY channel for a\n * status. `name` (a `ReactNode` `Gantt` already supports) carries a visible \"Parallel\" tag\n * plus an `sr-only` explanation for anyone who cannot see the tone at all.\n */\nimport { forwardRef, useMemo, type HTMLAttributes } from \"react\";\nimport { Gantt, type GanttGap, type GanttTask } from \"@elabs-ai/components-charts\";\nimport { Badge, useLocale } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport type { EventRow } from \"../core/types\";\nimport { formatDurationMs } from \"../process-map/map-model\";\nimport {\n buildCaseTimelineInstances,\n type CaseTimelineInstance,\n type CaseTimelineModelOptions,\n} from \"./case-timeline-model\";\n\nexport interface CaseTimelineProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\">, CaseTimelineModelOptions {\n caseId: string;\n /** This case's own events, straight from `core`'s `EventLog.events`. */\n events: EventRow[];\n}\n\nfunction instanceName(\n instance: CaseTimelineInstance,\n t: ReturnType<typeof useLocale>[\"t\"],\n): GanttTask[\"name\"] {\n if (!instance.isParallel) return instance.activity;\n return (\n <span className=\"inline-flex items-center gap-1.5\">\n <span>{instance.activity}</span>\n <Badge variant=\"info\">{t(\"process.caseTimeline.parallelLabel\")}</Badge>\n <span className=\"sr-only\">, {t(\"process.caseTimeline.parallelSuffix\")}</span>\n </span>\n );\n}\n\nfunction instanceGaps(\n instance: CaseTimelineInstance,\n t: ReturnType<typeof useLocale>[\"t\"],\n): GanttGap[] | undefined {\n if (!instance.gap) return undefined;\n return [\n {\n start: instance.gap.start,\n end: instance.gap.end,\n label: t(\"process.caseTimeline.gapLabel\", {\n duration: formatDurationMs(instance.gap.durationMs),\n }),\n },\n ];\n}\n\nexport const CaseTimeline = forwardRef<HTMLDivElement, CaseTimelineProps>(function CaseTimeline(\n { caseId, events, parallelismThreshold, className, ...props },\n ref,\n) {\n const { t } = useLocale();\n\n const instances = useMemo(\n () => buildCaseTimelineInstances(events, { parallelismThreshold }),\n [events, parallelismThreshold],\n );\n\n const tasks = useMemo<GanttTask[]>(\n () =>\n instances.map((instance) => {\n const task: GanttTask = {\n id: `${caseId}-${instance.id}`,\n name: instanceName(instance, t),\n start: instance.start,\n end: instance.end,\n };\n if (instance.isParallel) task.status = \"info\";\n const gaps = instanceGaps(instance, t);\n if (gaps) task.gaps = gaps;\n return task;\n }),\n [instances, caseId, t],\n );\n\n return (\n <Gantt\n ref={ref}\n data-slot=\"case-timeline\"\n aria-label={t(\"process.caseTimeline.label\")}\n tasks={tasks}\n defaultViewMode=\"auto\"\n labelPosition=\"end\"\n className={cn(className)}\n {...props}\n />\n );\n});\n","/**\n * `CaseTimeline`'s model — RM-055.\n *\n * Maps ONE case's raw `EventRow[]` to a flat, chronologically-ordered `CaseTimelineInstance[]`:\n * one entry per activity EXECUTION (not per activity name — a repeated activity gets one\n * instance per occurrence), each carrying the resolved start/end `normalizeLog` (RM-049)\n * already knows how to pair from `lifecycle: \"start\"`/`\"complete\"` rows.\n *\n * Two things this module adds on top of `normalizeLog`'s own trace:\n *\n * - **Waiting-time gaps** — the idle time between the END of one instance and the START of\n * the NEXT in the case's own chronological order (not \"next occurrence of the same\n * activity\" — a case has no natural per-activity lane once instances interleave). Surfaced\n * as `gap` on the LATER instance, ready to become one `Gantt` `row.gaps` entry (RM-047).\n * - **Parallel flags** — any pair of instances whose intervals overlap by more than\n * `parallelismThreshold` (default `0`: any genuine overlap) are both marked `isParallel`.\n * O(n²) over one case's own instance count, which is small enough that this never needs\n * the interval-tree machinery a whole-log analysis would.\n *\n * Plain `.ts` (no JSX, no `@elabs-ai/components-charts` import) — `case-timeline.tsx` is the\n * one place that turns an instance into a `Gantt` `GanttTask`, because that is also where the\n * parallel flag's localized text channel (WCAG 1.4.1 — colour is never the only channel) is\n * built, and `useLocale()` is only available from a component.\n */\nimport { normalizeLog } from \"../core/event-log\";\nimport type { EventRow } from \"../core/types\";\n\nexport interface CaseTimelineGap {\n start: number;\n end: number;\n durationMs: number;\n}\n\n/** One activity EXECUTION in the case's trace. */\nexport interface CaseTimelineInstance {\n /** Stable within one model build — the instance's position in chronological order. */\n id: string;\n activity: string;\n start: number;\n end: number;\n resource?: string;\n /** True for a `lifecycle: \"start\"` row that never got a matching `\"complete\"`. */\n isOpen: boolean;\n /** True when this instance's interval overlaps another by more than `parallelismThreshold`. */\n isParallel: boolean;\n /** Waiting time since the previous instance ended, when there was a gap. */\n gap?: CaseTimelineGap;\n}\n\nexport interface CaseTimelineModelOptions {\n /**\n * Ms of overlap below which two instances are NOT flagged parallel. Default `0` — any\n * genuine overlap (however small) counts.\n */\n parallelismThreshold?: number;\n}\n\n/**\n * One case's trace as `CaseTimelineInstance[]`, in ascending `start` order — empty when\n * `events` resolves to no case (e.g. every row has an empty `caseId`/`activity`).\n */\nexport function buildCaseTimelineInstances(\n events: EventRow[],\n options: CaseTimelineModelOptions = {},\n): CaseTimelineInstance[] {\n const threshold = options.parallelismThreshold ?? 0;\n const normalized = normalizeLog({ events });\n const kase = normalized.cases[0];\n if (!kase) return [];\n\n const instances: CaseTimelineInstance[] = kase.events.map((event, index) => {\n const instance: CaseTimelineInstance = {\n id: `${index}`,\n activity: event.activity,\n start: event.start,\n end: event.end,\n isOpen: event.isOpen,\n isParallel: false,\n };\n if (event.resource !== undefined) instance.resource = event.resource;\n return instance;\n });\n\n // Waiting-time gaps: chronological order, not \"same activity\" — `kase.events` is already\n // sorted by resolved start (`normalizeLog`'s own contract).\n for (let i = 1; i < instances.length; i += 1) {\n const previous = instances[i - 1] as CaseTimelineInstance;\n const current = instances[i] as CaseTimelineInstance;\n if (current.start > previous.end) {\n current.gap = {\n start: previous.end,\n end: current.start,\n durationMs: current.start - previous.end,\n };\n }\n }\n\n // Parallel flags: every pair whose intervals overlap by more than `threshold`.\n for (let i = 0; i < instances.length; i += 1) {\n for (let j = i + 1; j < instances.length; j += 1) {\n const a = instances[i] as CaseTimelineInstance;\n const b = instances[j] as CaseTimelineInstance;\n const overlap = Math.min(a.end, b.end) - Math.max(a.start, b.start);\n if (overlap > threshold) {\n a.isParallel = true;\n b.isParallel = true;\n }\n }\n }\n\n return instances;\n}\n","\"use client\";\n\n/**\n * DottedChart — case rows × time, one dot per event, coloured by activity (RM-059, issue\n * #208, analysis §4 R14; bupaR's `dotted_chart` is the reference).\n *\n * ## It composes; it never draws a primitive of its own\n *\n * The card, toolbar and CSV download are `@elabs-ai/components-charts`' `ChartFrame`; the\n * marks are RM-046's `CanvasLayer` (one canvas for up to ~100k dots, a single keyboard\n * cursor, a spatial-grid hit test, a parallel accessible summary); the legend is the charts\n * `ChartLegend`; the accessible twin is `@elabs-ai/components-ui`'s `Table`. The only thing\n * authored here is the `draw` callback that paints dots into the canvas the layer owns.\n *\n * ## Colour is never the only channel\n *\n * Activities take their hue from the SAME `ActivityColorScale` `ProcessMap` and\n * `VariantExplorer` read, so an activity is one colour across the explorer. Every hue is\n * also named: the legend prints each label, the tooltip and the keyboard cursor speak the\n * activity, and `tableView` lists every case as text. The shared \"other\" bucket is drawn as\n * a hollow ring rather than a filled dot, so it stays distinguishable in greyscale.\n *\n * ## It emits; it never filters\n *\n * Click a dot (or press Enter on a focused row) → `onSelect([caseId])`. Drag a rectangle →\n * `onSelect(ids)` and `onFilterIntent({ kind: \"cases\", ids })` once, on release, with the\n * covered case ids in row order. Shift+Arrow extends a keyboard range the same way; Enter\n * then commits it as the filter intent. The chart itself never calls `filterLog`.\n */\nimport {\n forwardRef,\n useCallback,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n} from \"react\";\nimport {\n CANVAS_LAYER_HIT_RADIUS,\n CanvasLayer,\n CHART_HAIRLINE_WIDTH,\n ChartFrame,\n ChartLegend,\n canvasTokenColor,\n createSpatialGrid,\n type CanvasLayerRect,\n type ChartFrameColumn,\n} from \"@elabs-ai/components-charts\";\nimport {\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 { activityColorScale, type ActivityColorScale } from \"../core/activity-color-scale\";\nimport { discoverGraph } from \"../core/discover-graph\";\nimport type { FilterSpec } from \"../core/filter-log\";\nimport type { EventLog, EventRow } from \"../core/types\";\nimport { formatDurationMs } from \"../process-map/map-model\";\nimport { fillLabel } from \"../variant-explorer/variant-explorer-model\";\nimport {\n casesInBrush,\n computeDots,\n rankCategoryColors,\n type DottedChartDot,\n type DottedChartModel,\n type DottedChartRow,\n type DottedChartSort,\n type DottedChartX,\n} from \"./compute-dots\";\nimport { DOTTED_CHART_DEFAULT_LABELS, type DottedChartLabels } from \"./dotted-chart-labels\";\nimport { useElementSize } from \"./use-element-size\";\n\n/** Dot radius in CSS pixels — a categorical marker, not an area encoding. */\nexport const DOTTED_CHART_DOT_RADIUS = 2;\n\n/** Plot inset in CSS pixels, so a dot on the domain edge is not clipped. */\nconst PLOT_INSET = 6;\n/** Opacity of dots outside the current selection. */\nconst DIMMED_ALPHA = 0.2;\n/** A pointer drag shorter than this (CSS px) is a click, not a brush. */\nconst BRUSH_THRESHOLD = 4;\n/** How many activities the keyboard cursor speaks before summarizing the rest. */\nconst SPOKEN_ACTIVITY_LIMIT = 12;\n\nconst DAY_MS = 86_400_000;\n/** 1970-01-05 00:00 UTC — the first Monday after the epoch. */\nconst FIRST_MONDAY_MS = 4 * DAY_MS;\n\n/** What a brush (or a committed keyboard range) emits. A subset of `/core`'s `FilterSpec`. */\nexport type DottedChartFilterIntent = Extract<FilterSpec, { kind: \"cases\" }>;\n\n/** How dots are coloured. A function returns a CATEGORY KEY, never a colour. */\nexport type DottedChartColor = \"activity\" | \"resource\" | ((row: EventRow) => string);\n\n/** A datum under the cursor: a case row (keyboard) or one event dot (pointer). */\nexport type DottedChartDatum = DottedChartRow | DottedChartDot;\n\nexport interface DottedChartProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onSelect\" | \"title\" | \"color\"\n> {\n log: EventLog;\n /** How the x axis places an event. @default \"absolute\" */\n x?: DottedChartX;\n /** How case rows are ordered, top to bottom. @default \"start\" */\n sort?: DottedChartSort;\n /**\n * What colours a dot. `\"activity\"` reads `colorScale`; `\"resource\"` and a function rank\n * their category keys onto the same `--chart-1 …` budget. @default \"activity\"\n */\n color?: DottedChartColor;\n /** The shared activity colour scale — hand `ProcessMap` the same instance. Built from `log` when omitted. */\n colorScale?: ActivityColorScale;\n /** Case ids to highlight; every other dot dims. */\n selectedCaseIds?: readonly string[];\n /** Fires with the case ids a click, brush or keyboard range selects. */\n onSelect?: (caseIds: string[]) => void;\n /** Fires once when a brush is released (or a keyboard range committed), with ids in row order. */\n onFilterIntent?: (intent: DottedChartFilterIntent) => void;\n /** Card title — write the conclusion, not \"Dotted chart\". */\n title?: ReactNode;\n /** Prose under the title. */\n description?: ReactNode;\n /** Body height in CSS pixels (legend + plot + axis). @default 360 */\n height?: number;\n /** Render the accessible table twin instead of the plot. @default false */\n tableView?: boolean;\n /** No log yet. Renders the frame's skeleton. */\n loading?: boolean;\n /** Override any user-visible string. */\n labels?: Partial<DottedChartLabels>;\n}\n\ninterface CategoryEntry {\n label: string;\n token: string;\n pattern?: \"other\";\n}\n\ninterface ColorBuckets {\n /** Legend entries in rank order, with every \"other\" category collapsed into one. */\n legend: CategoryEntry[];\n /** Per-dot category label (activity / resource / key). */\n dotLabel: string[];\n /** Dot indices grouped by paint token. */\n buckets: { token: string; pattern?: \"other\"; indices: Int32Array }[];\n}\n\nfunction asEventRow(dot: DottedChartDot): EventRow {\n const { event } = dot;\n const row: EventRow = {\n caseId: dot.caseId,\n activity: event.activity,\n timestamp: event.end,\n startTimestamp: event.start,\n };\n if (event.resource !== undefined) row.resource = event.resource;\n if (event.attributes !== undefined) row.attributes = event.attributes;\n return row;\n}\n\nfunction buildColorBuckets(\n model: DottedChartModel,\n color: DottedChartColor,\n scale: ActivityColorScale,\n labels: DottedChartLabels,\n): ColorBuckets {\n const dotLabel = new Array<string>(model.dots.length);\n const tokenOf = new Map<string, { token: string; pattern?: \"other\" }>();\n const legend: CategoryEntry[] = [];\n let hasOther = false;\n\n if (color === \"activity\") {\n for (let i = 0; i < model.dots.length; i += 1) {\n dotLabel[i] = (model.dots[i] as DottedChartDot).event.activity;\n }\n const present = new Set(dotLabel);\n for (const entry of scale.legend) {\n if (!present.has(entry.activityId)) continue;\n tokenOf.set(entry.activityId, scale.colorFor(entry.activityId));\n if (entry.pattern === \"other\") hasOther = true;\n else legend.push({ label: entry.label, token: entry.token });\n }\n for (const id of present) {\n if (!tokenOf.has(id)) {\n tokenOf.set(id, scale.colorFor(id));\n if (scale.colorFor(id).pattern === \"other\") hasOther = true;\n }\n }\n for (let i = 0; i < dotLabel.length; i += 1) {\n dotLabel[i] = scale.labelFor(dotLabel[i] as string);\n }\n } else {\n const keyOf =\n color === \"resource\"\n ? (dot: DottedChartDot) => dot.event.resource ?? labels.noResource\n : (dot: DottedChartDot) => color(asEventRow(dot));\n for (let i = 0; i < model.dots.length; i += 1) {\n dotLabel[i] = keyOf(model.dots[i] as DottedChartDot);\n }\n for (const category of rankCategoryColors(dotLabel)) {\n tokenOf.set(category.key, { token: category.token, pattern: category.pattern });\n if (category.pattern === \"other\") hasOther = true;\n else legend.push({ label: category.key, token: category.token });\n }\n }\n\n const grouped = new Map<string, { token: string; pattern?: \"other\"; indices: number[] }>();\n // Activity mode keys `tokenOf` by activity id, which `dotLabel` no longer holds.\n for (let i = 0; i < model.dots.length; i += 1) {\n const key =\n color === \"activity\" ? (model.dots[i] as DottedChartDot).event.activity : dotLabel[i];\n const paint = tokenOf.get(key as string) ?? { token: \"--chart-12\", pattern: \"other\" as const };\n const bucketKey = `${paint.token}|${paint.pattern ?? \"\"}`;\n let bucket = grouped.get(bucketKey);\n if (!bucket) {\n bucket = { token: paint.token, pattern: paint.pattern, indices: [] };\n grouped.set(bucketKey, bucket);\n }\n bucket.indices.push(i);\n }\n\n if (hasOther) legend.push({ label: labels.other, token: \"--chart-12\", pattern: \"other\" });\n\n return {\n legend,\n dotLabel,\n buckets: [...grouped.values()].map((b) => ({\n token: b.token,\n pattern: b.pattern,\n indices: Int32Array.from(b.indices),\n })),\n };\n}\n\n/** Evenly spaced axis ticks for the model's domain, as `[value, label]` pairs. */\nfunction buildTicks(\n model: DottedChartModel,\n innerWidth: number,\n formatDate: (d: Date | number, opts?: Intl.DateTimeFormatOptions) => string,\n): { value: number; label: string }[] {\n const [d0, d1] = model.domain;\n if (model.x === \"relative_day\") {\n return [0, 6, 12, 18].map((h) => ({\n value: h * 3_600_000,\n label: formatDate(h * 3_600_000, { hour: \"2-digit\", minute: \"2-digit\", timeZone: \"UTC\" }),\n }));\n }\n if (model.x === \"relative_week\") {\n return Array.from({ length: 7 }, (_, day) => ({\n value: day * DAY_MS,\n label: formatDate(FIRST_MONDAY_MS + day * DAY_MS, { weekday: \"short\", timeZone: \"UTC\" }),\n }));\n }\n const count = Math.max(2, Math.min(8, Math.floor(innerWidth / 120)));\n const span = d1 - d0;\n const dateOpts: Intl.DateTimeFormatOptions =\n span > 2 * DAY_MS\n ? { month: \"short\", day: \"numeric\", timeZone: \"UTC\" }\n : { hour: \"2-digit\", minute: \"2-digit\", timeZone: \"UTC\" };\n return Array.from({ length: count }, (_, i) => {\n const value = d0 + (span * i) / (count - 1);\n return {\n value,\n label: model.x === \"relative\" ? formatDurationMs(value) : formatDate(value, dateOpts),\n };\n });\n}\n\n/** Stable per-object numbers, so a `drawSignature` can name \"this data\" cheaply. */\nconst objectIds = new WeakMap<object, number>();\nlet nextObjectId = 1;\nfunction objectId(value: object): number {\n let id = objectIds.get(value);\n if (id === undefined) {\n id = nextObjectId;\n nextObjectId += 1;\n objectIds.set(value, id);\n }\n return id;\n}\n\nconst EMPTY_IDS: readonly string[] = Object.freeze([]);\n\n/**\n * The dotted chart.\n *\n * @example\n * ```tsx\n * const scale = useMemo(() => activityColorScale(fullGraph), [fullGraph]);\n * <DottedChart\n * log={explorer.filteredLog}\n * colorScale={scale}\n * sort=\"duration\"\n * onFilterIntent={explorer.applyIntent}\n * />\n * ```\n */\nexport const DottedChart = forwardRef<HTMLDivElement, DottedChartProps>(function DottedChart(\n {\n log,\n x = \"absolute\",\n sort = \"start\",\n color = \"activity\",\n colorScale,\n selectedCaseIds = EMPTY_IDS,\n onSelect,\n onFilterIntent,\n title,\n description,\n height = 360,\n tableView = false,\n loading = false,\n labels: labelOverrides,\n className,\n ...props\n },\n ref,\n) {\n const { locale, formatNumber, formatDate } = useLocale();\n const labels = useMemo<DottedChartLabels>(\n () => ({ ...DOTTED_CHART_DEFAULT_LABELS, ...labelOverrides }),\n [labelOverrides],\n );\n const pluralRules = useMemo(() => new Intl.PluralRules(locale), [locale]);\n const plural = useCallback(\n (count: number, one: string, other: string) =>\n fillLabel(pluralRules.select(count) === \"one\" ? one : other, {\n count: formatNumber(count),\n }),\n [pluralRules, formatNumber],\n );\n const formatTime = useCallback(\n (ms: number) => formatDate(ms, { dateStyle: \"medium\", timeStyle: \"short\", timeZone: \"UTC\" }),\n [formatDate],\n );\n\n // ── Model ───────────────────────────────────────────────────────────────\n const model = useMemo(() => computeDots(log, { x, sort }), [log, x, sort]);\n const scale = useMemo(\n () => colorScale ?? activityColorScale(discoverGraph(log)),\n [colorScale, log],\n );\n const colors = useMemo(\n () => buildColorBuckets(model, color, scale, labels),\n [model, color, scale, labels],\n );\n const selected = useMemo(() => new Set(selectedCaseIds), [selectedCaseIds]);\n const eventCount = model.dots.length;\n\n // ── Geometry ────────────────────────────────────────────────────────────\n const [plotRef, plotSize] = useElementSize<HTMLDivElement>();\n const { width, height: plotHeight } = plotSize;\n const geometry = useMemo(() => {\n const left = PLOT_INSET;\n const right = Math.max(left + 1, width - PLOT_INSET);\n const top = PLOT_INSET;\n const bottom = Math.max(top + 1, plotHeight - PLOT_INSET);\n const [d0, d1] = model.domain;\n const rowHeight = (bottom - top) / Math.max(1, model.rows.length);\n const xToPx = (value: number) => left + ((value - d0) / (d1 - d0)) * (right - left);\n const pxToX = (px: number) => d0 + ((px - left) / (right - left)) * (d1 - d0);\n const pxToRow = (py: number) =>\n Math.min(model.rows.length - 1, Math.max(0, Math.floor((py - top) / rowHeight)));\n const px = new Float32Array(model.dots.length);\n const py = new Float32Array(model.dots.length);\n const grid = createSpatialGrid<DottedChartDot>(CANVAS_LAYER_HIT_RADIUS);\n for (let i = 0; i < model.dots.length; i += 1) {\n const dot = model.dots[i] as DottedChartDot;\n const dx = xToPx(dot.x);\n const dy = top + (dot.rowIndex + 0.5) * rowHeight;\n px[i] = dx;\n py[i] = dy;\n grid.insert(dx, dy, dot);\n }\n return { left, right, top, bottom, rowHeight, xToPx, pxToX, pxToRow, px, py, grid };\n }, [model, width, plotHeight]);\n\n const ticks = useMemo(\n () => buildTicks(model, geometry.right - geometry.left, formatDate),\n [model, geometry, formatDate],\n );\n\n // ── Paint ───────────────────────────────────────────────────────────────\n const draw = useCallback(\n (ctx: CanvasRenderingContext2D) => {\n const el = ctx.canvas;\n const { px, py, top, bottom } = geometry;\n\n ctx.globalAlpha = 1;\n ctx.lineWidth = CHART_HAIRLINE_WIDTH;\n ctx.strokeStyle = canvasTokenColor(\"--chart-grid\", el, \"GrayText\");\n ctx.beginPath();\n for (const tick of ticks) {\n const tx = Math.round(geometry.xToPx(tick.value)) + 0.5;\n ctx.moveTo(tx, top);\n ctx.lineTo(tx, bottom);\n }\n ctx.stroke();\n\n const dim = selected.size > 0;\n const passes: (boolean | null)[] = dim ? [false, true] : [null];\n for (const pass of passes) {\n ctx.globalAlpha = pass === false ? DIMMED_ALPHA : 1;\n for (const bucket of colors.buckets) {\n const ink = canvasTokenColor(bucket.token, el, \"CanvasText\");\n ctx.beginPath();\n for (const i of bucket.indices) {\n if (pass !== null) {\n const isSelected = selected.has((model.dots[i] as DottedChartDot).caseId);\n if (isSelected !== pass) continue;\n }\n const cx = px[i] as number;\n const cy = py[i] as number;\n ctx.moveTo(cx + DOTTED_CHART_DOT_RADIUS, cy);\n ctx.arc(cx, cy, DOTTED_CHART_DOT_RADIUS, 0, Math.PI * 2);\n }\n if (bucket.pattern === \"other\") {\n ctx.lineWidth = 1;\n ctx.strokeStyle = ink;\n ctx.stroke();\n } else {\n ctx.fillStyle = ink;\n ctx.fill();\n }\n }\n }\n ctx.globalAlpha = 1;\n },\n [geometry, ticks, colors, selected, model],\n );\n const drawSignature = `${objectId(geometry)}:${objectId(colors)}:${objectId(selected)}:${objectId(ticks)}`;\n\n // ── Text channels ───────────────────────────────────────────────────────\n const rowLabel = useCallback(\n (row: DottedChartRow) => {\n const activities: string[] = [];\n const limit = Math.min(row.dotEnd, row.dotStart + SPOKEN_ACTIVITY_LIMIT);\n for (let d = row.dotStart; d < limit; d += 1) {\n activities.push(colors.dotLabel[d] as string);\n }\n let spoken = activities.join(\", \");\n const rest = row.eventCount - activities.length;\n if (rest > 0) spoken = `${spoken}, ${fillLabel(labels.moreActivities, { count: rest })}`;\n let text = fillLabel(labels.row, {\n caseId: row.caseId,\n events: plural(row.eventCount, labels.eventsOne, labels.eventsOther),\n duration: formatDurationMs(row.duration),\n activities: spoken,\n });\n if (selected.has(row.caseId)) text = `${text}, ${labels.selected}`;\n return text;\n },\n [colors, labels, plural, selected],\n );\n\n const dotText = useCallback(\n (dot: DottedChartDot) => {\n const base = fillLabel(labels.dot, {\n activity: dot.event.activity,\n time: formatTime(dot.event.start),\n caseId: dot.caseId,\n });\n return dot.event.resource\n ? `${base}, ${fillLabel(labels.dotResource, { resource: dot.event.resource })}`\n : base;\n },\n [labels, formatTime],\n );\n\n const labelFor = useCallback(\n (datum: DottedChartDatum) => (datum.kind === \"row\" ? rowLabel(datum) : dotText(datum)),\n [rowLabel, dotText],\n );\n\n const summary = useMemo(() => {\n const first = model.rows.reduce((m, r) => Math.min(m, r.start), Number.POSITIVE_INFINITY);\n const last = model.rows.reduce((m, r) => Math.max(m, r.end), Number.NEGATIVE_INFINITY);\n const sortLabel = {\n start: labels.sortStart,\n end: labels.sortEnd,\n duration: labels.sortDuration,\n start_day: labels.sortStartDay,\n }[sort];\n const colorBy =\n color === \"activity\"\n ? labels.colorByActivity\n : color === \"resource\"\n ? labels.colorByResource\n : labels.colorByCustom;\n return fillLabel(labels.summary, {\n cases: plural(model.rows.length, labels.casesOne, labels.casesOther),\n events: plural(eventCount, labels.eventsOne, labels.eventsOther),\n from: Number.isFinite(first) ? formatTime(first) : \"—\",\n to: Number.isFinite(last) ? formatTime(last) : \"—\",\n sort: sortLabel,\n colorBy,\n });\n }, [model, labels, sort, color, plural, eventCount, formatTime]);\n\n // ── Selection: click, brush, keyboard range ─────────────────────────────\n const interactive = Boolean(onSelect || onFilterIntent);\n const suppressClickRef = useRef(false);\n const focusedRowRef = useRef<DottedChartRow | null>(null);\n const anchorRef = useRef<number | null>(null);\n const extendRef = useRef(false);\n const rangeRef = useRef<string[] | null>(null);\n\n const handleActivate = useCallback(\n (datum: DottedChartDatum) => {\n if (suppressClickRef.current) {\n suppressClickRef.current = false;\n return;\n }\n const range = rangeRef.current;\n if (datum.kind === \"row\" && range && range.length > 1) {\n onFilterIntent?.({ kind: \"cases\", ids: range });\n return;\n }\n onSelect?.([datum.caseId]);\n },\n [onSelect, onFilterIntent],\n );\n\n const handleFocus = useCallback(\n (datum: DottedChartDatum | null) => {\n if (datum === null || datum.kind !== \"row\") {\n focusedRowRef.current = null;\n anchorRef.current = null;\n extendRef.current = false;\n rangeRef.current = null;\n return;\n }\n focusedRowRef.current = datum;\n if (extendRef.current && anchorRef.current !== null) {\n extendRef.current = false;\n const lo = Math.min(anchorRef.current, datum.index);\n const hi = Math.max(anchorRef.current, datum.index);\n const ids = model.rows.slice(lo, hi + 1).map((row) => row.caseId);\n rangeRef.current = ids;\n onSelect?.(ids);\n }\n },\n [model, onSelect],\n );\n\n const handleKeyDown = useCallback((event: ReactKeyboardEvent<HTMLDivElement>) => {\n if (![\"ArrowUp\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\", \"Home\", \"End\"].includes(event.key)) {\n return;\n }\n if (event.shiftKey) {\n if (anchorRef.current === null) anchorRef.current = focusedRowRef.current?.index ?? 0;\n extendRef.current = true;\n } else {\n anchorRef.current = null;\n extendRef.current = false;\n rangeRef.current = null;\n }\n }, []);\n\n const brushStartRef = useRef<{ x: number; y: number; pointerId: number } | null>(null);\n const [brush, setBrush] = useState<CanvasLayerRect | null>(null);\n\n const localPoint = (event: ReactPointerEvent<HTMLDivElement>) => {\n const rect = event.currentTarget.getBoundingClientRect();\n return { x: event.clientX - rect.left, y: event.clientY - rect.top };\n };\n\n const handlePointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {\n suppressClickRef.current = false;\n rangeRef.current = null;\n if (!interactive || event.button !== 0 || model.rows.length === 0) return;\n brushStartRef.current = { ...localPoint(event), pointerId: event.pointerId };\n };\n\n const handlePointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {\n const start = brushStartRef.current;\n if (!start || start.pointerId !== event.pointerId) return;\n const point = localPoint(event);\n if (\n !brush &&\n Math.abs(point.x - start.x) < BRUSH_THRESHOLD &&\n Math.abs(point.y - start.y) < BRUSH_THRESHOLD\n ) {\n return;\n }\n if (!brush) event.currentTarget.setPointerCapture?.(event.pointerId);\n setBrush({\n x: Math.min(start.x, point.x),\n y: Math.min(start.y, point.y),\n width: Math.abs(point.x - start.x),\n height: Math.abs(point.y - start.y),\n });\n };\n\n const endBrush = (event: ReactPointerEvent<HTMLDivElement>, commit: boolean) => {\n const start = brushStartRef.current;\n brushStartRef.current = null;\n if (!start || !brush) return;\n setBrush(null);\n if (event.currentTarget.hasPointerCapture?.(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId);\n }\n if (!commit) return;\n suppressClickRef.current = true;\n const ids = casesInBrush(\n model,\n [geometry.pxToRow(brush.y), geometry.pxToRow(brush.y + brush.height)],\n [geometry.pxToX(brush.x), geometry.pxToX(brush.x + brush.width)],\n );\n if (ids.length === 0) return;\n onSelect?.(ids);\n onFilterIntent?.({ kind: \"cases\", ids });\n };\n\n // ── Table twin + CSV rows ───────────────────────────────────────────────\n const csvColumns = useMemo<ChartFrameColumn[]>(\n () => [\n { key: \"caseId\", header: labels.columnCase },\n { key: \"firstActivity\", header: labels.columnFirstActivity },\n { key: \"lastActivity\", header: labels.columnLastActivity },\n { key: \"start\", header: labels.columnStart },\n { key: \"end\", header: labels.columnEnd },\n { key: \"durationMs\", header: labels.columnDuration },\n { key: \"events\", header: labels.columnEvents },\n ],\n [labels],\n );\n const csvRows = useMemo(\n () =>\n model.rows.map((row) => ({\n caseId: row.caseId,\n firstActivity: row.firstActivity,\n lastActivity: row.lastActivity,\n start: new Date(row.start).toISOString(),\n end: new Date(row.end).toISOString(),\n durationMs: row.duration,\n events: row.eventCount,\n })),\n [model],\n );\n\n const axisLabel = {\n absolute: labels.axisAbsolute,\n relative: labels.axisRelative,\n relative_day: labels.axisDay,\n relative_week: labels.axisWeek,\n }[x];\n\n let body: ReactNode;\n if (model.rows.length === 0) {\n body = (\n <StatePanel\n data-slot=\"dotted-chart-empty\"\n kind=\"empty\"\n title={labels.empty}\n titleAs=\"div\"\n description={labels.emptyBody}\n />\n );\n } else if (tableView) {\n const hasSelection = selected.size > 0;\n body = (\n <div data-slot=\"dotted-chart-table\" className=\"size-full overflow-auto\">\n <Table>\n <TableCaption>{labels.tableCaption}</TableCaption>\n <TableHeader>\n <TableRow>\n <TableHead scope=\"col\">{labels.columnCase}</TableHead>\n <TableHead scope=\"col\">{labels.columnFirstActivity}</TableHead>\n <TableHead scope=\"col\">{labels.columnLastActivity}</TableHead>\n <TableHead scope=\"col\">{labels.columnStart}</TableHead>\n <TableHead scope=\"col\">{labels.columnEnd}</TableHead>\n <TableHead scope=\"col\">{labels.columnDuration}</TableHead>\n <TableHead scope=\"col\">{labels.columnEvents}</TableHead>\n {hasSelection ? <TableHead scope=\"col\">{labels.columnState}</TableHead> : null}\n </TableRow>\n </TableHeader>\n <TableBody>\n {model.rows.map((row) => {\n const isSelected = selected.has(row.caseId);\n return (\n <TableRow key={row.caseId} data-state={isSelected ? \"selected\" : undefined}>\n <TableCell>{row.caseId}</TableCell>\n <TableCell>{scale.labelFor(row.firstActivity)}</TableCell>\n <TableCell>{scale.labelFor(row.lastActivity)}</TableCell>\n <TableCell className=\"tabular-nums\">{formatTime(row.start)}</TableCell>\n <TableCell className=\"tabular-nums\">{formatTime(row.end)}</TableCell>\n <TableCell className=\"tabular-nums\">{formatDurationMs(row.duration)}</TableCell>\n <TableCell className=\"tabular-nums\">{formatNumber(row.eventCount)}</TableCell>\n {hasSelection ? <TableCell>{isSelected ? labels.selected : \"\"}</TableCell> : null}\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n </div>\n );\n } else {\n body = (\n <div data-slot=\"dotted-chart-body\" className=\"flex size-full min-h-0 flex-col gap-2\">\n <div data-slot=\"dotted-chart-legend\" className=\"flex shrink-0 items-start gap-3\">\n <ChartLegend\n className=\"max-h-16 min-w-0 flex-1 flex-row flex-wrap gap-x-1 gap-y-0 overflow-y-auto\"\n itemClassName=\"px-1.5 py-0.5\"\n labelClassName=\"text-meta\"\n showValue={false}\n items={colors.legend.map((entry) => ({\n label: entry.label,\n value: 0,\n color: `var(${entry.token})`,\n }))}\n />\n {selected.size > 0 ? (\n <span\n data-slot=\"dotted-chart-selection\"\n className=\"shrink-0 text-meta text-muted-foreground tabular-nums\"\n >\n {fillLabel(labels.selectionCount, {\n cases: plural(selected.size, labels.casesOne, labels.casesOther),\n })}\n </span>\n ) : null}\n </div>\n <div\n ref={plotRef}\n dir=\"ltr\"\n data-slot=\"dotted-chart-plot\"\n className={cn(\"relative min-h-0 flex-1\", interactive && \"touch-none select-none\")}\n onKeyDown={handleKeyDown}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={(event) => endBrush(event, true)}\n onPointerCancel={(event) => endBrush(event, false)}\n >\n {width > 0 && plotHeight > 0 ? (\n <CanvasLayer<DottedChartDatum>\n points={model.rows}\n draw={draw}\n drawSignature={drawSignature}\n hitTest={(hx, hy) => geometry.grid.query(hx, hy, CANVAS_LAYER_HIT_RADIUS)}\n width={width}\n height={plotHeight}\n labelFor={labelFor}\n focusRect={(datum) =>\n datum.kind === \"row\"\n ? {\n x: geometry.left - 2,\n y: geometry.top + datum.index * geometry.rowHeight - 1,\n width: geometry.right - geometry.left + 4,\n height: Math.max(4, geometry.rowHeight + 2),\n }\n : {\n x: geometry.xToPx(datum.x) - 4,\n y: geometry.top + (datum.rowIndex + 0.5) * geometry.rowHeight - 4,\n width: 8,\n height: 8,\n }\n }\n renderTooltip={(datum) => (\n <span className=\"block max-w-64 text-meta\">{labelFor(datum)}</span>\n )}\n onDatapointFocus={handleFocus}\n onDatapointActivate={handleActivate}\n accessibleLabel={labels.chart}\n accessibleDescription={summary}\n />\n ) : null}\n {brush ? (\n <div\n aria-hidden=\"true\"\n data-slot=\"dotted-chart-brush\"\n className=\"pointer-events-none absolute rounded-sm border border-primary bg-primary/10\"\n style={{ left: brush.x, top: brush.y, width: brush.width, height: brush.height }}\n />\n ) : null}\n </div>\n <div\n aria-hidden=\"true\"\n dir=\"ltr\"\n data-slot=\"dotted-chart-axis\"\n className=\"relative h-4 shrink-0 text-meta text-muted-foreground tabular-nums\"\n >\n {ticks.map((tick, i) => (\n <span\n key={tick.value}\n className=\"absolute top-0 whitespace-nowrap\"\n style={{\n left: geometry.xToPx(tick.value),\n transform:\n i === 0\n ? undefined\n : i === ticks.length - 1 && model.x !== \"relative_week\"\n ? \"translateX(-100%)\"\n : \"translateX(-50%)\",\n }}\n >\n {tick.label}\n </span>\n ))}\n </div>\n <div\n aria-hidden=\"true\"\n data-slot=\"dotted-chart-axis-label\"\n className=\"shrink-0 text-center text-meta text-muted-foreground\"\n >\n {axisLabel}\n </div>\n </div>\n );\n }\n\n return (\n <ChartFrame\n ref={ref}\n data-slot=\"dotted-chart\"\n data-view={tableView ? \"table\" : \"plot\"}\n title={title}\n description={description}\n data={model.rows.length > 0 ? csvRows : undefined}\n columns={csvColumns}\n features={[\"expand\", \"download\"]}\n height={height}\n loading={loading}\n className={cn(className)}\n {...props}\n >\n {body}\n </ChartFrame>\n );\n});\n","/**\n * Pure model behind `DottedChart` (RM-059) — no React, no canvas, so the row order, the\n * time transform and the brush enumeration are fixture-testable on their own.\n *\n * A dotted chart puts one ROW per case and one DOT per event. `computeDots` groups a log by\n * case (through `/core`'s `normalizeLog`, so lifecycle pairs and the three timestamp\n * encodings are resolved exactly once), orders the rows by `sort`, and places every event\n * on the x axis according to `x`:\n *\n * - `absolute` — the event's own start time (epoch ms).\n * - `relative` — milliseconds since the case's first event.\n * - `relative_day` — time of day, `0 … 24 h` (UTC), so daily rhythms line up.\n * - `relative_week` — time of week, `0 … 7 d`, weeks starting Monday 00:00 (UTC).\n *\n * The two cyclic modes use UTC on purpose: a chart whose dots move when the reviewer's\n * machine changes time zone is not a deterministic fixture.\n */\nimport {\n ACTIVITY_COLOR_SLOTS,\n ACTIVITY_OTHER_TOKEN,\n type ActivityColor,\n} from \"../core/activity-color-scale\";\nimport { asNormalizedLog, type AnyLog, type NormalizedEvent } from \"../core/event-log\";\n\n/** How the x axis places an event. */\nexport type DottedChartX = \"absolute\" | \"relative\" | \"relative_day\" | \"relative_week\";\n\n/** How case rows are ordered, top to bottom. Every order is ascending and stable. */\nexport type DottedChartSort = \"start\" | \"end\" | \"duration\" | \"start_day\";\n\nconst DAY_MS = 86_400_000;\nconst WEEK_MS = 7 * DAY_MS;\n/** 1970-01-01 was a Thursday; shifting by 3 days makes weeks start on Monday. */\nconst MONDAY_OFFSET_MS = 3 * DAY_MS;\n\n/** One event, placed on the chart. */\nexport interface DottedChartDot {\n kind: \"dot\";\n caseId: string;\n /** Index of this dot's row in {@link DottedChartModel.rows}. */\n rowIndex: number;\n /** The normalized event (activity, start, end, resource, attributes). */\n event: NormalizedEvent;\n /** The event's position on the x axis, in the unit `x` implies (always milliseconds). */\n x: number;\n}\n\n/** One case, placed on the chart. */\nexport interface DottedChartRow {\n kind: \"row\";\n caseId: string;\n /** Position in `rows` — top row is `0`. */\n index: number;\n start: number;\n end: number;\n duration: number;\n eventCount: number;\n firstActivity: string;\n lastActivity: string;\n /** This row's dots are `dots.slice(dotStart, dotEnd)`, in time order. */\n dotStart: number;\n dotEnd: number;\n}\n\n/** What {@link computeDots} returns. */\nexport interface DottedChartModel {\n x: DottedChartX;\n sort: DottedChartSort;\n rows: DottedChartRow[];\n /** Every dot, grouped by row in `rows` order, each row's dots in time order. */\n dots: DottedChartDot[];\n /** The x extent to scale against. Never zero-width. */\n domain: [number, number];\n}\n\nexport interface ComputeDotsOptions {\n x?: DottedChartX;\n sort?: DottedChartSort;\n}\n\nfunction timeOfDay(ms: number): number {\n return ((ms % DAY_MS) + DAY_MS) % DAY_MS;\n}\n\nfunction timeOfWeek(ms: number): number {\n const shifted = ms + MONDAY_OFFSET_MS;\n return ((shifted % WEEK_MS) + WEEK_MS) % WEEK_MS;\n}\n\nfunction placeX(x: DottedChartX, at: number, caseStart: number): number {\n switch (x) {\n case \"relative\":\n return at - caseStart;\n case \"relative_day\":\n return timeOfDay(at);\n case \"relative_week\":\n return timeOfWeek(at);\n default:\n return at;\n }\n}\n\n/**\n * Build the dotted-chart model for a log.\n *\n * Deterministic: the same log and options always yield the same rows, dots and domain.\n * Events whose start could not be parsed are left out (they have no place on a time axis);\n * a case left with no placeable event is left out too.\n */\nexport function computeDots(log: AnyLog, options: ComputeDotsOptions = {}): DottedChartModel {\n const x = options.x ?? \"absolute\";\n const sort = options.sort ?? \"start\";\n const normalized = asNormalizedLog(log);\n\n const cases = normalized.cases\n .map((kase) => ({\n kase,\n events: kase.events.filter((event) => Number.isFinite(event.start)),\n }))\n .filter((entry) => entry.events.length > 0);\n\n const keyOf = (entry: (typeof cases)[number]): number => {\n const first = entry.events[0] as NormalizedEvent;\n const last = entry.events[entry.events.length - 1] as NormalizedEvent;\n const start = first.start;\n const end = Math.max(last.end, entry.kase.end);\n switch (sort) {\n case \"end\":\n return end;\n case \"duration\":\n return end - start;\n case \"start_day\":\n return timeOfDay(start);\n default:\n return start;\n }\n };\n\n // Decorate-sort-undecorate keeps the sort stable and each key computed once.\n const ordered = cases\n .map((entry, position) => ({ entry, position, key: keyOf(entry) }))\n .sort((a, b) => a.key - b.key || a.position - b.position);\n\n const rows: DottedChartRow[] = [];\n const dots: DottedChartDot[] = [];\n let min = Number.POSITIVE_INFINITY;\n let max = Number.NEGATIVE_INFINITY;\n\n for (const { entry } of ordered) {\n const { kase, events } = entry;\n const rowIndex = rows.length;\n const first = events[0] as NormalizedEvent;\n const last = events[events.length - 1] as NormalizedEvent;\n const start = first.start;\n const end = Number.isFinite(kase.end) ? Math.max(kase.end, start) : start;\n const dotStart = dots.length;\n for (const event of events) {\n const value = placeX(x, event.start, start);\n if (value < min) min = value;\n if (value > max) max = value;\n dots.push({ kind: \"dot\", caseId: kase.caseId, rowIndex, event, x: value });\n }\n rows.push({\n kind: \"row\",\n caseId: kase.caseId,\n index: rowIndex,\n start,\n end,\n duration: end - start,\n eventCount: events.length,\n firstActivity: first.activity,\n lastActivity: last.activity,\n dotStart,\n dotEnd: dots.length,\n });\n }\n\n let domain: [number, number];\n if (x === \"relative_day\") domain = [0, DAY_MS];\n else if (x === \"relative_week\") domain = [0, WEEK_MS];\n else if (!Number.isFinite(min)) domain = [0, 1];\n else if (min === max) domain = [min - 1, max + 1];\n else domain = [min, max];\n\n return { x, sort, rows, dots, domain };\n}\n\n/**\n * Case ids of the rows a brush covers, in `rows` order.\n *\n * A row is covered when its index is in `[firstRow, lastRow]` (inclusive, either order) AND\n * at least one of its dots lies in the x range `[x0, x1]` (inclusive, either order). The\n * x test is what makes a brush over an empty stretch of time select nothing.\n */\nexport function casesInBrush(\n model: DottedChartModel,\n rowRange: readonly [number, number],\n xRange: readonly [number, number],\n): string[] {\n const firstRow = Math.max(0, Math.min(rowRange[0], rowRange[1]));\n const lastRow = Math.min(model.rows.length - 1, Math.max(rowRange[0], rowRange[1]));\n const lo = Math.min(xRange[0], xRange[1]);\n const hi = Math.max(xRange[0], xRange[1]);\n const ids: string[] = [];\n for (let r = firstRow; r <= lastRow; r += 1) {\n const row = model.rows[r] as DottedChartRow;\n for (let d = row.dotStart; d < row.dotEnd; d += 1) {\n const value = (model.dots[d] as DottedChartDot).x;\n if (value >= lo && value <= hi) {\n ids.push(row.caseId);\n break;\n }\n }\n }\n return ids;\n}\n\n/** One entry of a {@link rankCategoryColors} legend. */\nexport interface DottedChartCategory extends ActivityColor {\n key: string;\n /** How many dots carry this category. */\n count: number;\n}\n\n/**\n * Colour arbitrary category keys (resources, a caller's own grouping) with the SAME budget\n * `activityColorScale` uses: the {@link ACTIVITY_COLOR_SLOTS} most frequent keys take\n * `--chart-1 …`, every other key shares {@link ACTIVITY_OTHER_TOKEN} and is flagged\n * `pattern: \"other\"`. Ranked by count descending, ties by key. Returns token names only.\n */\nexport function rankCategoryColors(keys: readonly string[]): DottedChartCategory[] {\n const counts = new Map<string, number>();\n for (const key of keys) counts.set(key, (counts.get(key) ?? 0) + 1);\n return [...counts.entries()]\n .sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))\n .map(([key, count], index) =>\n index < ACTIVITY_COLOR_SLOTS\n ? { key, count, token: `--chart-${index + 1}` }\n : { key, count, token: ACTIVITY_OTHER_TOKEN, pattern: \"other\" as const },\n );\n}\n","/**\n * Every user-visible string `DottedChart` (RM-059) renders. `{name}` placeholders are\n * filled at render; override any subset through the component's `labels` prop to localize.\n */\n\nexport interface DottedChartLabels {\n /** Accessible name of the plot and its keyboard cursor. */\n chart: string;\n /** `{cases}`, `{events}`, `{from}`, `{to}`, `{sort}`, `{colorBy}` — the parallel summary. */\n summary: string;\n sortStart: string;\n sortEnd: string;\n sortDuration: string;\n sortStartDay: string;\n colorByActivity: string;\n colorByResource: string;\n colorByCustom: string;\n /** `{caseId}`, `{events}`, `{duration}`, `{activities}` — spoken for a focused case row. */\n row: string;\n /** `{activity}`, `{time}`, `{caseId}` — spoken/shown for one event. */\n dot: string;\n /** `{resource}` — appended to a dot tooltip when the event has a resource. */\n dotResource: string;\n eventsOne: string;\n eventsOther: string;\n casesOne: string;\n casesOther: string;\n /** `{count}` more activities past the spoken prefix of a long trace. */\n moreActivities: string;\n /** `{cases}` — visible count of the current selection. */\n selectionCount: string;\n axisAbsolute: string;\n axisRelative: string;\n axisDay: string;\n axisWeek: string;\n /** The legend entry every category past the colour budget shares. */\n other: string;\n /** Category key for an event that carries no resource. */\n noResource: string;\n tableCaption: string;\n columnCase: string;\n columnFirstActivity: string;\n columnLastActivity: string;\n columnStart: string;\n columnEnd: string;\n columnDuration: string;\n columnEvents: string;\n columnState: string;\n selected: string;\n empty: string;\n emptyBody: string;\n}\n\n/** The shipped English labels. */\nexport const DOTTED_CHART_DEFAULT_LABELS: Readonly<DottedChartLabels> = Object.freeze({\n chart: \"Dotted chart — one row per case, one dot per event\",\n summary:\n \"{cases} and {events}, from {from} to {to}. One row per case, sorted by {sort}; one dot per event, coloured by {colorBy}.\",\n sortStart: \"case start\",\n sortEnd: \"case end\",\n sortDuration: \"case duration\",\n sortStartDay: \"time of day the case started\",\n colorByActivity: \"activity\",\n colorByResource: \"resource\",\n colorByCustom: \"category\",\n row: \"Case {caseId}, {events}, {duration}: {activities}\",\n dot: \"{activity} at {time}, case {caseId}\",\n dotResource: \"by {resource}\",\n eventsOne: \"{count} event\",\n eventsOther: \"{count} events\",\n casesOne: \"{count} case\",\n casesOther: \"{count} cases\",\n moreActivities: \"and {count} more\",\n selectionCount: \"{cases} selected\",\n axisAbsolute: \"Time (UTC)\",\n axisRelative: \"Time since case start\",\n axisDay: \"Time of day (UTC)\",\n axisWeek: \"Time of week (UTC)\",\n other: \"Other\",\n noResource: \"No resource\",\n tableCaption: \"Cases — first and last activity, start, end, duration and event count\",\n columnCase: \"Case\",\n columnFirstActivity: \"First activity\",\n columnLastActivity: \"Last activity\",\n columnStart: \"Start\",\n columnEnd: \"End\",\n columnDuration: \"Duration\",\n columnEvents: \"Events\",\n columnState: \"State\",\n selected: \"selected\",\n empty: \"No events\",\n emptyBody: \"No cases match the current filters.\",\n});\n","\"use client\";\n\nimport { useLayoutEffect, useState } from \"react\";\n\n/**\n * The CSS-pixel size of an element, re-measured whenever it resizes. `0 × 0` until measured.\n *\n * Returns a CALLBACK ref (a state setter), so an element that mounts after the first render\n * (a loading or table view switching to the plot) is still observed.\n */\nexport function useElementSize<E extends HTMLElement>() {\n const [node, setNode] = useState<E | null>(null);\n const [size, setSize] = useState({ width: 0, height: 0 });\n\n useLayoutEffect(() => {\n if (!node) return;\n const measure = () => {\n const rect = node.getBoundingClientRect();\n const width = Math.round(rect.width);\n const height = Math.round(rect.height);\n setSize((current) =>\n current.width === width && current.height === height ? current : { width, height },\n );\n };\n measure();\n if (typeof ResizeObserver !== \"function\") return;\n const observer = new ResizeObserver(measure);\n observer.observe(node);\n return () => observer.disconnect();\n }, [node]);\n\n return [setNode, size] as const;\n}\n","\"use client\";\n\n/**\n * PerformanceSpectrum — segments × time, one line per case (RM-060, issue #209, §4 R15).\n *\n * ProM's performance spectrum: a fixed, chosen sequence of segments (`from → to` pairs)\n * stacked as rows, all sharing one absolute-time x-axis. In `mode=\"lines\"` every\n * occurrence is a line from `(start, row top)` to `(end, row bottom)`, so a steep line is\n * fast, a slanted one slow, parallel lines are FIFO, crossing lines overtake, and a\n * bundle converging on one instant is a batch. `mode=\"aggregated\"` trades the lines for\n * one bar per `binSize` bucket (height = cases entering, fill = median quartile).\n *\n * ## Composition, not re-authoring\n *\n * Marks paint on `@elabs-ai/components-charts`'s `CanvasLayer` (RM-046) — never SVG — one\n * layer per row, which is also what makes the keyboard contract fall out: each row's\n * layer owns ONE tab stop, so `Tab` moves row by row, the arrow keys walk that row's\n * occurrences in time order, and `Enter` activates the focused occurrence's case. Hover\n * hit-testing uses the charts `createSpatialGrid`; the tooltip is `ChartTooltipContent`;\n * the colour key is `Legend`; the ramp is `resolvePalette(\"sequential\")`, the one\n * `HeatmapChart` uses.\n *\n * ## Colour is never the only channel\n *\n * A line's quartile colour is REDUNDANT with its geometry: the horizontal run of a line\n * is its duration. Every occurrence and bar also speaks its quartile in words through the\n * layer's cursor, each row carries a parallel summary (count, cases, median, p90), and\n * `tableView` renders the same numbers as a table. The sequential ramp is\n * lightness-monotonic, so the quartiles also separate in greyscale.\n *\n * ## It emits; it never filters\n *\n * Dragging across the time axis (or Shift+Arrow then Enter on it) calls\n * `onFilterIntent({ kind: \"cases\", ids })` once, with every case that has an occurrence\n * overlapping the range. The spectrum itself never narrows its data (§5.3).\n */\nimport {\n forwardRef,\n useCallback,\n useId,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type KeyboardEvent,\n type PointerEvent,\n} from \"react\";\nimport {\n CanvasLayer,\n canvasTokenColor,\n CHART_HAIRLINE_WIDTH,\n ChartTooltipContent,\n createSpatialGrid,\n Legend,\n LegendItemComponent,\n LegendLabel,\n LegendMarker,\n resolvePalette,\n type CanvasLayerRect,\n type SpatialGrid,\n} from \"@elabs-ai/components-charts\";\nimport {\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 { discoverGraph } from \"../core/discover-graph\";\nimport { asNormalizedLog } from \"../core/event-log\";\nimport { extractVariants } from \"../core/extract-variants\";\nimport type { FilterSpec } from \"../core/filter-log\";\nimport {\n segmentOrderByFrequency,\n segmentOrderForVariant,\n segmentsFor,\n type SegmentDefinition,\n} from \"../core/segments\";\nimport type { EventLog } from \"../core/types\";\nimport { formatDurationMs, type ProcessSelection } from \"../process-map/map-model\";\nimport { fillLabel } from \"../variant-explorer/variant-explorer-model\";\nimport {\n aggregateSegmentBins,\n buildSpectrumRows,\n casesInRange,\n spectrumDomain,\n spectrumTicks,\n type SpectrumBin,\n type SpectrumLine,\n type SpectrumRow,\n} from \"./aggregate-segments\";\nimport {\n PERFORMANCE_SPECTRUM_DEFAULT_LABELS,\n PerformanceSpectrumProvider,\n usePerformanceSpectrum,\n type PerformanceSpectrumContextValue,\n type PerformanceSpectrumLabels,\n} from \"./performance-spectrum-context\";\n\n/** One day in ms — the aggregated mode's default bucket. */\nexport const PERFORMANCE_SPECTRUM_DEFAULT_BIN_SIZE = 86_400_000;\n/** Default row height in CSS px when `height` is not given. */\nexport const PERFORMANCE_SPECTRUM_ROW_HEIGHT = 56;\n/** Default number of segment rows. */\nexport const PERFORMANCE_SPECTRUM_SEGMENT_LIMIT = 12;\n\n/** Which segments a spectrum shows, in row order. */\nexport type PerformanceSpectrumOrder = SegmentDefinition[] | \"frequency\" | { variantId: string };\n\n/** The one intent the spectrum emits — a `/core` `FilterSpec`, straight into `filterLog`. */\nexport type PerformanceSpectrumFilterIntent = Extract<FilterSpec, { kind: \"cases\" }>;\n\n/** Props for {@link PerformanceSpectrum}. */\nexport interface PerformanceSpectrumProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n log: EventLog;\n /** Row order: explicit segments, the busiest transitions, or one variant's path. @default \"frequency\" */\n order?: PerformanceSpectrumOrder;\n /** At most this many rows, whichever `order` is used. @default 12 */\n segmentLimit?: number;\n /** One line per occurrence, or one bar per time bucket. @default \"lines\" */\n mode?: \"lines\" | \"aggregated\";\n /** Bucket width in ms, aggregated mode only. @default 86_400_000 (1 day) */\n binSize?: number;\n /** Rows whose segment is (or touches) the selected transition/activity are marked. */\n selection?: ProcessSelection | null;\n /** When given, the time axis becomes a brush that emits `{ kind: \"cases\", ids }`. */\n onFilterIntent?: (intent: PerformanceSpectrumFilterIntent) => void;\n /** Click or `Enter` on an occurrence (lines) or on a bar's first case (aggregated). */\n onCaseSelect?: (caseId: string, occurrence: SpectrumLine) => void;\n /** Total plot height in CSS px, split evenly across rows (min 24 px a row). */\n height?: number;\n /** Render the accessible table twin instead of the chart. @default false */\n tableView?: boolean;\n /** Data not ready yet — renders the loading panel. */\n loading?: boolean;\n /** Override any user-visible string. */\n labels?: Partial<PerformanceSpectrumLabels>;\n}\n\n/** `var(--chart-seq-3)` → `--chart-seq-3`, the name `canvasTokenColor` resolves. */\nfunction tokenName(reference: string): string {\n return /var\\((--[\\w-]+)\\)/.exec(reference)?.[1] ?? reference;\n}\n\n/** A canvas-safe ink when a token cannot be resolved — a CSS system colour, never a literal. */\nconst INK_FALLBACK = \"CanvasText\";\n\n/** Hover sample spacing along a line, in CSS px; kept under the grid's hit radius. */\nconst LINE_SAMPLE_PX = 6;\nconst LINE_SAMPLE_MAX = 64;\nconst HIT_RADIUS = 8;\n\nfunction isRowSelected(row: SpectrumRow, selection: ProcessSelection | null | undefined): boolean {\n if (!selection) return false;\n if (selection.kind === \"transition\") return selection.id === row.key;\n return row.definition.from === selection.id || row.definition.to === selection.id;\n}\n\ninterface SpectrumRowProps {\n row: SpectrumRow;\n bins: SpectrumBin[] | null;\n maxBinCount: number;\n selected: boolean;\n}\n\n/** One segment row — a `CanvasLayer` painting either lines or bars. */\nfunction PerformanceSpectrumRow({ row, bins, maxBinCount, selected }: SpectrumRowProps) {\n const ctx = usePerformanceSpectrum();\n const { labels, domain, ticks, rowHeight, quartileColors, formatInstant, formatDuration } = ctx;\n const [d0, d1] = domain;\n const span = d1 - d0;\n const widthRef = useRef(0);\n const gridRef = useRef<{ width: number; grid: SpatialGrid<SpectrumLine> } | null>(null);\n\n const xOf = useCallback((t: number, width: number) => ((t - d0) / span) * width, [d0, span]);\n\n const binIndex = useMemo(() => {\n const map = new Map<number, SpectrumBin>();\n if (!bins) return map;\n for (const bin of bins) map.set(Math.round((bin.start - d0) / ctx.binSize), bin);\n return map;\n }, [bins, d0, ctx.binSize]);\n\n const barGeometry = useCallback(\n (bin: SpectrumBin, width: number): CanvasLayerRect => {\n const x = xOf(bin.start, width);\n const w = Math.max(1, xOf(bin.end, width) - x - 1);\n const h = Math.max(1, (bin.count / Math.max(1, maxBinCount)) * (rowHeight - 4));\n return { x, y: rowHeight - h, width: w, height: h };\n },\n [maxBinCount, rowHeight, xOf],\n );\n\n const draw = (c: CanvasRenderingContext2D, scales: { width: number; height: number }) => {\n const { width, height } = scales;\n widthRef.current = width;\n const el = c.canvas;\n\n c.strokeStyle = canvasTokenColor(\"--chart-grid\", el, INK_FALLBACK);\n c.lineWidth = CHART_HAIRLINE_WIDTH;\n c.beginPath();\n for (const tick of ticks) {\n const x = Math.round(xOf(tick, width)) + 0.5;\n c.moveTo(x, 0);\n c.lineTo(x, height);\n }\n c.stroke();\n\n const inks = quartileColors.map((ref) => canvasTokenColor(tokenName(ref), el, INK_FALLBACK));\n\n if (bins) {\n for (const bin of bins) {\n const rect = barGeometry(bin, width);\n c.fillStyle = inks[bin.quartile - 1] as string;\n c.fillRect(rect.x, rect.y, rect.width, rect.height);\n }\n return;\n }\n\n c.lineWidth = 1;\n // Fastest first, slowest last: the slow lines — the ones worth finding — sit on top.\n for (let q = 1; q <= 4; q += 1) {\n c.strokeStyle = inks[q - 1] as string;\n c.beginPath();\n for (const line of row.lines) {\n if (line.quartile !== q) continue;\n c.moveTo(xOf(line.start, width), 1);\n c.lineTo(xOf(line.end, width), height - 1);\n }\n c.stroke();\n }\n };\n\n const lineGrid = (width: number): SpatialGrid<SpectrumLine> => {\n const cached = gridRef.current;\n if (cached && cached.width === width) return cached.grid;\n const grid = createSpatialGrid<SpectrumLine>(HIT_RADIUS);\n for (const line of row.lines) {\n const x0 = xOf(line.start, width);\n const x1 = xOf(line.end, width);\n const steps = Math.min(\n LINE_SAMPLE_MAX,\n Math.max(1, Math.ceil(Math.max(Math.abs(x1 - x0), rowHeight) / LINE_SAMPLE_PX)),\n );\n for (let k = 0; k <= steps; k += 1) {\n const f = k / steps;\n grid.insert(x0 + (x1 - x0) * f, rowHeight * f, line);\n }\n }\n gridRef.current = { width, grid };\n return grid;\n };\n\n const hitTest = (x: number, y: number): SpectrumLine | SpectrumBin | null => {\n const width = widthRef.current;\n if (width <= 0) return null;\n if (bins) {\n const t = d0 + (x / width) * span;\n return binIndex.get(Math.floor((t - d0) / ctx.binSize)) ?? null;\n }\n return lineGrid(width).query(x, y, HIT_RADIUS);\n };\n\n const isBin = (datum: SpectrumLine | SpectrumBin): datum is SpectrumBin => \"count\" in datum;\n\n const focusRect = (datum: SpectrumLine | SpectrumBin): CanvasLayerRect => {\n const width = widthRef.current;\n if (isBin(datum)) {\n const rect = barGeometry(datum, width);\n return {\n x: rect.x - 2,\n y: Math.max(1, rect.y - 2),\n width: rect.width + 4,\n height: rect.height + 1,\n };\n }\n const x0 = xOf(datum.start, width);\n const x1 = xOf(datum.end, width);\n return { x: Math.min(x0, x1) - 3, y: 1, width: Math.abs(x1 - x0) + 6, height: rowHeight - 2 };\n };\n\n const labelFor = (datum: SpectrumLine | SpectrumBin): string =>\n isBin(datum)\n ? fillLabel(labels.bin, {\n start: formatInstant(datum.start),\n end: formatInstant(datum.end),\n count: datum.count,\n median: formatDuration(datum.medianDuration),\n quartile: ctx.quartileName(datum.quartile),\n })\n : fillLabel(labels.occurrence, {\n caseId: datum.caseId,\n start: formatInstant(datum.start),\n end: formatInstant(datum.end),\n duration: formatDuration(datum.duration),\n quartile: ctx.quartileName(datum.quartile),\n });\n\n const segmentLabel =\n row.definition.label ??\n fillLabel(labels.segment, { from: row.definition.from, to: row.definition.to });\n\n const renderTooltip = (datum: SpectrumLine | SpectrumBin) => {\n const color = quartileColors[datum.quartile - 1] as string;\n const quartile = ctx.quartileName(datum.quartile);\n return isBin(datum) ? (\n <ChartTooltipContent\n title={segmentLabel}\n rows={[\n { color, label: labels.tooltipStart, value: formatInstant(datum.start) },\n { color, label: labels.tooltipEnd, value: formatInstant(datum.end) },\n { color, label: labels.tooltipCount, value: String(datum.count) },\n { color, label: labels.tooltipMedian, value: formatDuration(datum.medianDuration) },\n { color, label: labels.tooltipQuartile, value: quartile },\n ]}\n />\n ) : (\n <ChartTooltipContent\n title={datum.caseId}\n rows={[\n { color, label: labels.tooltipSegment, value: segmentLabel },\n { color, label: labels.tooltipStart, value: formatInstant(datum.start) },\n { color, label: labels.tooltipEnd, value: formatInstant(datum.end) },\n { color, label: labels.tooltipDuration, value: formatDuration(datum.duration) },\n { color, label: labels.tooltipQuartile, value: quartile },\n ]}\n />\n );\n };\n\n const activate = (datum: SpectrumLine | SpectrumBin) => {\n if (!ctx.onCaseSelect) return;\n if (isBin(datum)) {\n const first = row.lines.find((l) => l.start >= datum.start && l.start < datum.end);\n if (first) ctx.onCaseSelect(first.caseId, first);\n return;\n }\n ctx.onCaseSelect(datum.caseId, datum);\n };\n\n const points: Array<SpectrumLine | SpectrumBin> = bins ?? row.lines;\n const first = row.lines[0];\n const last = row.lines[row.lines.length - 1];\n const drawSignature = [\n row.key,\n row.lines.length,\n first?.start,\n last?.end,\n bins ? `bins:${bins.length}:${ctx.binSize}:${maxBinCount}` : \"lines\",\n d0,\n d1,\n ticks.length,\n rowHeight,\n ].join(\"|\");\n\n return (\n <div\n data-slot=\"performance-spectrum-row\"\n data-selected={selected ? \"\" : undefined}\n className=\"border-t border-border-strong\"\n >\n <CanvasLayer<SpectrumLine | SpectrumBin>\n accessibleLabel={selected ? `${segmentLabel}, ${labels.selected}` : segmentLabel}\n accessibleDescription={fillLabel(labels.rowSummary, {\n count: row.lines.length,\n cases: row.caseCount,\n median: formatDuration(row.medianDuration),\n p90: formatDuration(row.p90Duration),\n })}\n draw={draw}\n drawSignature={drawSignature}\n focusRect={focusRect}\n height={rowHeight}\n hitTest={hitTest}\n labelFor={labelFor}\n onDatapointActivate={activate}\n points={points}\n renderTooltip={renderTooltip}\n style={{ height: rowHeight }}\n />\n </div>\n );\n}\n\n/**\n * The performance spectrum.\n *\n * @example\n * ```tsx\n * <PerformanceSpectrum\n * log={explorer.filteredLog}\n * order=\"frequency\"\n * onFilterIntent={(intent) => setCaseFilter(intent)}\n * />\n * ```\n */\nexport const PerformanceSpectrum = forwardRef<HTMLDivElement, PerformanceSpectrumProps>(\n function PerformanceSpectrum(\n {\n log,\n order = \"frequency\",\n segmentLimit = PERFORMANCE_SPECTRUM_SEGMENT_LIMIT,\n mode = \"lines\",\n binSize = PERFORMANCE_SPECTRUM_DEFAULT_BIN_SIZE,\n selection,\n onFilterIntent,\n onCaseSelect,\n height,\n tableView = false,\n loading = false,\n labels: labelOverrides,\n className,\n ...props\n },\n ref,\n ) {\n const { formatDate } = useLocale();\n const labels = useMemo<PerformanceSpectrumLabels>(\n () => ({ ...PERFORMANCE_SPECTRUM_DEFAULT_LABELS, ...labelOverrides }),\n [labelOverrides],\n );\n\n const normalized = useMemo(() => asNormalizedLog(log), [log]);\n\n // `order` is often an inline literal (`{ variantId }`), so key the derivation on its\n // CONTENT rather than its identity — re-extracting variants on every render is the\n // most expensive thing this component could do by accident.\n const orderKey = JSON.stringify(order);\n const resolvedOrder = useMemo<SegmentDefinition[]>(() => {\n const limit = Math.max(0, Math.floor(segmentLimit));\n const parsed = JSON.parse(orderKey) as PerformanceSpectrumOrder;\n if (parsed === \"frequency\") return segmentOrderByFrequency(discoverGraph(normalized), limit);\n if (Array.isArray(parsed)) return parsed.slice(0, limit);\n const variant = extractVariants(normalized).find((v) => v.id === parsed.variantId);\n return variant ? segmentOrderForVariant(variant).slice(0, limit) : [];\n }, [normalized, orderKey, segmentLimit]);\n\n const rows = useMemo(\n () => buildSpectrumRows(resolvedOrder, segmentsFor(normalized, resolvedOrder)),\n [normalized, resolvedOrder],\n );\n const domain = useMemo(() => spectrumDomain(rows), [rows]);\n const ticks = useMemo(() => spectrumTicks(domain), [domain]);\n const occurrenceCount = rows.reduce((n, row) => n + row.lines.length, 0);\n\n const binsByRow = useMemo(\n () =>\n mode === \"aggregated\"\n ? rows.map((row) => aggregateSegmentBins(row, binSize, domain[0]))\n : null,\n [binSize, domain, mode, rows],\n );\n const maxBinCount = useMemo(\n () =>\n binsByRow\n ? binsByRow.reduce((m, bins) => bins.reduce((mm, b) => Math.max(mm, b.count), m), 0)\n : 0,\n [binsByRow],\n );\n\n const rowHeight =\n height !== undefined && rows.length > 0\n ? Math.max(24, Math.floor(height / rows.length))\n : PERFORMANCE_SPECTRUM_ROW_HEIGHT;\n\n const spanMs = domain[1] - domain[0];\n const formatInstant = useCallback(\n (ms: number) => formatDate(ms, { dateStyle: \"medium\", timeStyle: \"short\" }),\n [formatDate],\n );\n const formatTick = useCallback(\n (ms: number) =>\n spanMs > 2 * 86_400_000\n ? formatDate(ms, { month: \"short\", day: \"numeric\" })\n : formatDate(ms, { hour: \"2-digit\", minute: \"2-digit\" }),\n [formatDate, spanMs],\n );\n const quartileName = useCallback(\n (q: number) => {\n const base = fillLabel(labels.quartile, { n: q });\n if (q === 1) return `${base} (${labels.quartileFastest})`;\n if (q === 4) return `${base} (${labels.quartileSlowest})`;\n return base;\n },\n [labels],\n );\n\n const quartileColors = useMemo(() => resolvePalette(\"sequential\", 4), []);\n\n const contextValue = useMemo<PerformanceSpectrumContextValue>(\n () => ({\n labels,\n mode,\n binSize: Number.isFinite(binSize) && binSize > 0 ? binSize : 1,\n domain,\n ticks,\n rowHeight,\n quartileColors,\n formatInstant,\n formatDuration: formatDurationMs,\n quartileName,\n onCaseSelect,\n }),\n [\n binSize,\n domain,\n formatInstant,\n labels,\n mode,\n onCaseSelect,\n quartileColors,\n quartileName,\n rowHeight,\n ticks,\n ],\n );\n\n // ── Brush ──────────────────────────────────────────────────────────────────\n const trackRef = useRef<HTMLDivElement | null>(null);\n const hintId = useId();\n const [brush, setBrush] = useState<{ from: number; to: number } | null>(null);\n const [caret, setCaret] = useState<number | null>(null);\n const [trackFocused, setTrackFocused] = useState(false);\n const anchorRef = useRef<number | null>(null);\n const dragRef = useRef<{ pointerId: number; startX: number } | null>(null);\n\n const timeAt = (clientX: number): number | null => {\n const rect = trackRef.current?.getBoundingClientRect();\n if (!rect || rect.width <= 0 || !Number.isFinite(clientX)) return null;\n const f = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));\n return domain[0] + f * spanMs;\n };\n\n const commit = (range: { from: number; to: number } | null) => {\n if (!onFilterIntent || !range) return;\n const lo = Math.min(range.from, range.to);\n const hi = Math.max(range.from, range.to);\n if (hi <= lo) return;\n onFilterIntent({ kind: \"cases\", ids: casesInRange(rows, lo, hi) });\n };\n\n const onPointerDown = (event: PointerEvent<HTMLDivElement>) => {\n const t = timeAt(event.clientX);\n if (t === null) return;\n event.currentTarget.setPointerCapture?.(event.pointerId);\n dragRef.current = { pointerId: event.pointerId, startX: event.clientX };\n anchorRef.current = t;\n setBrush({ from: t, to: t });\n };\n const onPointerMove = (event: PointerEvent<HTMLDivElement>) => {\n if (dragRef.current?.pointerId !== event.pointerId || anchorRef.current === null) return;\n const t = timeAt(event.clientX);\n if (t !== null) setBrush({ from: anchorRef.current, to: t });\n };\n const onPointerUp = (event: PointerEvent<HTMLDivElement>) => {\n const drag = dragRef.current;\n if (drag?.pointerId !== event.pointerId || anchorRef.current === null) return;\n dragRef.current = null;\n event.currentTarget.releasePointerCapture?.(event.pointerId);\n const t = timeAt(event.clientX);\n // A click (under 3 px of travel) clears rather than filtering to an instant.\n if (t === null || Math.abs(event.clientX - drag.startX) < 3) {\n setBrush(null);\n anchorRef.current = null;\n return;\n }\n const range = { from: anchorRef.current, to: t };\n setBrush(range);\n anchorRef.current = null;\n commit(range);\n };\n\n const onTrackKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n const step = spanMs / 48;\n const current = caret ?? domain[0];\n let next: number | null = null;\n switch (event.key) {\n case \"ArrowRight\":\n next = current + step;\n break;\n case \"ArrowLeft\":\n next = current - step;\n break;\n case \"PageUp\":\n next = current + step * 6;\n break;\n case \"PageDown\":\n next = current - step * 6;\n break;\n case \"Home\":\n next = domain[0];\n break;\n case \"End\":\n next = domain[1];\n break;\n case \"Enter\":\n event.preventDefault();\n commit(brush);\n return;\n case \"Escape\":\n event.preventDefault();\n setBrush(null);\n anchorRef.current = null;\n return;\n default:\n return;\n }\n event.preventDefault();\n const clamped = Math.min(domain[1], Math.max(domain[0], next));\n if (event.shiftKey) {\n if (anchorRef.current === null) anchorRef.current = current;\n setBrush({ from: anchorRef.current, to: clamped });\n } else {\n anchorRef.current = null;\n }\n setCaret(clamped);\n };\n\n const pct = (t: number) => `${((t - domain[0]) / spanMs) * 100}%`;\n const brushText =\n brush && brush.from !== brush.to\n ? fillLabel(labels.brushRange, {\n start: formatInstant(Math.min(brush.from, brush.to)),\n end: formatInstant(Math.max(brush.from, brush.to)),\n count: casesInRange(rows, brush.from, brush.to).length,\n })\n : \"\";\n\n // ── States ─────────────────────────────────────────────────────────────────\n if (loading) {\n return (\n <div\n ref={ref}\n data-slot=\"performance-spectrum\"\n data-state=\"loading\"\n className={cn(\"relative flex h-72 flex-col\", className)}\n {...props}\n >\n <StatePanel kind=\"loading\" title={labels.loading} />\n </div>\n );\n }\n\n if (occurrenceCount === 0) {\n return (\n <div\n ref={ref}\n data-slot=\"performance-spectrum\"\n data-state=\"empty\"\n className={cn(\"relative flex h-72 flex-col\", className)}\n {...props}\n >\n <StatePanel kind=\"empty\" title={labels.empty} description={labels.emptyBody} />\n </div>\n );\n }\n\n const segmentLabel = (row: SpectrumRow) =>\n row.definition.label ??\n fillLabel(labels.segment, { from: row.definition.from, to: row.definition.to });\n\n if (tableView) {\n return (\n <div\n ref={ref}\n data-slot=\"performance-spectrum\"\n data-view=\"table\"\n className={cn(\"flex flex-col gap-3\", className)}\n {...props}\n >\n <Table data-slot=\"performance-spectrum-table\">\n <TableCaption>{labels.tableCaption}</TableCaption>\n <TableHeader>\n <TableRow>\n <TableHead scope=\"col\">{labels.columnSegment}</TableHead>\n <TableHead scope=\"col\">{labels.columnCases}</TableHead>\n <TableHead scope=\"col\">{labels.columnMedian}</TableHead>\n <TableHead scope=\"col\">{labels.columnP90}</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {rows.map((row) => {\n const selected = isRowSelected(row, selection);\n return (\n <TableRow key={row.key} data-state={selected ? \"selected\" : undefined}>\n <TableCell>\n {segmentLabel(row)}\n {selected ? <span className=\"sr-only\">, {labels.selected}</span> : null}\n </TableCell>\n <TableCell className=\"tabular-nums\">{row.caseCount}</TableCell>\n <TableCell className=\"tabular-nums\">\n {formatDurationMs(row.medianDuration)}\n </TableCell>\n <TableCell className=\"tabular-nums\">\n {formatDurationMs(row.p90Duration)}\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n </div>\n );\n }\n\n const legendItems = quartileColors.map((color, i) => ({\n label: quartileName(i + 1),\n value: 0,\n color,\n }));\n\n return (\n <PerformanceSpectrumProvider value={contextValue}>\n <div\n ref={ref}\n role=\"group\"\n aria-label={labels.label}\n data-slot=\"performance-spectrum\"\n data-view=\"chart\"\n data-mode={mode}\n className={cn(\"flex min-w-0 flex-col gap-3\", className)}\n {...props}\n >\n <div\n data-slot=\"performance-spectrum-legend\"\n className=\"flex flex-wrap items-center gap-2\"\n >\n <span className=\"text-meta text-muted-foreground\">{labels.legend}</span>\n <Legend items={legendItems} className=\"flex-row flex-wrap gap-1\">\n <LegendItemComponent className=\"flex items-center gap-1.5 px-1.5 py-0.5\">\n <LegendMarker />\n <LegendLabel className=\"text-meta\" />\n </LegendItemComponent>\n </Legend>\n </div>\n\n <div data-slot=\"performance-spectrum-body\" dir=\"ltr\" className=\"flex min-w-0\">\n <div\n aria-hidden=\"true\"\n data-slot=\"performance-spectrum-gutter\"\n className=\"flex w-40 shrink-0 flex-col\"\n >\n {rows.map((row) => {\n const selected = isRowSelected(row, selection);\n return (\n <div\n key={row.key}\n data-selected={selected ? \"\" : undefined}\n className={cn(\n \"flex min-w-0 flex-col justify-between border-t border-s-2 border-t-border-strong py-0.5 ps-2 pe-2\",\n selected ? \"border-s-primary\" : \"border-s-transparent\",\n )}\n style={{ height: rowHeight + 1 }}\n >\n <span className={cn(\"truncate text-meta\", selected && \"font-medium\")}>\n {row.definition.label ?? row.definition.from}\n </span>\n <span className=\"truncate text-meta text-muted-foreground\">\n {row.definition.label ? \"\" : row.definition.to}\n </span>\n </div>\n );\n })}\n </div>\n\n <div data-slot=\"performance-spectrum-plot\" className=\"flex min-w-0 flex-1 flex-col\">\n <div className=\"relative\">\n {rows.map((row, index) => (\n <PerformanceSpectrumRow\n key={row.key}\n row={row}\n bins={binsByRow ? (binsByRow[index] as SpectrumBin[]) : null}\n maxBinCount={maxBinCount}\n selected={isRowSelected(row, selection)}\n />\n ))}\n {brush && brush.from !== brush.to ? (\n <div\n aria-hidden=\"true\"\n data-slot=\"performance-spectrum-brush\"\n className=\"pointer-events-none absolute inset-y-0 border-x border-primary bg-primary/10\"\n style={{\n insetInlineStart: pct(Math.min(brush.from, brush.to)),\n width: `${(Math.abs(brush.to - brush.from) / spanMs) * 100}%`,\n }}\n />\n ) : null}\n {trackFocused && caret !== null ? (\n <div\n aria-hidden=\"true\"\n data-slot=\"performance-spectrum-caret\"\n className=\"pointer-events-none absolute inset-y-0 w-0.5 bg-ring\"\n style={{ insetInlineStart: pct(caret) }}\n />\n ) : null}\n </div>\n\n <div\n ref={trackRef}\n data-slot=\"performance-spectrum-axis\"\n className={cn(\n \"relative h-8 touch-none select-none border-t border-border-strong\",\n onFilterIntent && \"focus-ring-inset cursor-col-resize\",\n )}\n {...(onFilterIntent\n ? {\n role: \"group\",\n tabIndex: 0,\n \"aria-label\": labels.brush,\n \"aria-describedby\": hintId,\n onPointerDown,\n onPointerMove,\n onPointerUp,\n onPointerCancel: () => {\n dragRef.current = null;\n anchorRef.current = null;\n },\n onKeyDown: onTrackKeyDown,\n onFocus: () => {\n setTrackFocused(true);\n setCaret((c) => c ?? domain[0]);\n },\n onBlur: () => setTrackFocused(false),\n }\n : { \"aria-hidden\": true })}\n >\n {ticks.map((tick, i) => (\n <span\n key={tick}\n className={cn(\n \"pointer-events-none absolute top-1 whitespace-nowrap text-meta text-muted-foreground tabular-nums\",\n i === 0\n ? \"\"\n : i === ticks.length - 1\n ? \"-translate-x-full\"\n : \"-translate-x-1/2\",\n )}\n style={{ insetInlineStart: pct(tick) }}\n >\n {formatTick(tick)}\n </span>\n ))}\n </div>\n {onFilterIntent ? (\n <>\n <span id={hintId} className=\"sr-only\">\n {labels.brushHint}\n </span>\n <span\n role=\"status\"\n aria-live=\"polite\"\n data-slot=\"performance-spectrum-brush-status\"\n className=\"sr-only\"\n >\n {brushText}\n </span>\n </>\n ) : null}\n </div>\n </div>\n </div>\n </PerformanceSpectrumProvider>\n );\n },\n);\n","/**\n * The pure model behind `PerformanceSpectrum` — RM-060.\n *\n * Everything here is React-free and unit-tested on its own: grouping occurrences into\n * rows (with each occurrence's quartile read against ITS OWN segment), the aggregated\n * mode's time bins, the shared time domain and ticks, and the brush's case lookup. The\n * component only lays these out and paints them.\n */\nimport {\n durationQuartileThresholds,\n quartileOf,\n segmentKey,\n type DurationQuartile,\n type SegmentDefinition,\n type SegmentOccurrence,\n} from \"../core/segments\";\nimport { ascending, quantileSorted } from \"../core/scale\";\n\n/** One occurrence, ready to paint: its quartile is already resolved. */\nexport interface SpectrumLine extends SegmentOccurrence {\n quartile: DurationQuartile;\n}\n\n/** One `binSize` bucket of one row in aggregated mode. */\nexport interface SpectrumBin {\n segment: string;\n /** Bucket start, epoch ms (inclusive). */\n start: number;\n /** Bucket end, epoch ms (exclusive). */\n end: number;\n /** Occurrences entering the segment inside the bucket. */\n count: number;\n /** Median duration of those occurrences, ms. */\n medianDuration: number;\n /** The median of their quartiles, rounded up — the bar's fill. */\n quartile: DurationQuartile;\n}\n\n/** One spectrum row: a segment and everything drawn in it. */\nexport interface SpectrumRow {\n key: string;\n definition: SegmentDefinition;\n /** Sorted by `start`, then `end`, then `caseId` — the keyboard cursor's walk order. */\n lines: SpectrumLine[];\n /** Distinct cases with at least one occurrence. */\n caseCount: number;\n medianDuration: number;\n p90Duration: number;\n}\n\nfunction compareLines(a: SegmentOccurrence, b: SegmentOccurrence): number {\n return (\n a.start - b.start || a.end - b.end || (a.caseId < b.caseId ? -1 : a.caseId > b.caseId ? 1 : 0)\n );\n}\n\n/** Median of an ascending-sorted array's rounded-up middle — for small integer quartiles. */\nfunction medianQuartile(quartiles: DurationQuartile[]): DurationQuartile {\n const sorted = [...quartiles].sort(ascending);\n return Math.ceil(quantileSorted(sorted, 0.5)) as DurationQuartile;\n}\n\n/**\n * Groups `occurrences` into one row per definition in `order` (duplicates dropped),\n * resolving each occurrence's quartile against its own segment's distribution.\n */\nexport function buildSpectrumRows(\n order: readonly SegmentDefinition[],\n occurrences: readonly SegmentOccurrence[],\n): SpectrumRow[] {\n const byKey = new Map<string, SegmentOccurrence[]>();\n for (const occurrence of occurrences) {\n const bucket = byKey.get(occurrence.segment);\n if (bucket) bucket.push(occurrence);\n else byKey.set(occurrence.segment, [occurrence]);\n }\n\n const rows: SpectrumRow[] = [];\n const seen = new Set<string>();\n for (const definition of order) {\n const key = segmentKey(definition.from, definition.to);\n if (seen.has(key)) continue;\n seen.add(key);\n const own = byKey.get(key) ?? [];\n const durations = own.map((o) => o.duration).sort(ascending);\n const thresholds = durationQuartileThresholds(durations);\n const lines = own\n .map((o) => ({ ...o, quartile: quartileOf(o.duration, thresholds) }))\n .sort(compareLines);\n const cases = new Set<string>();\n for (const o of own) cases.add(o.caseId);\n rows.push({\n key,\n definition,\n lines,\n caseCount: cases.size,\n medianDuration: durations.length ? quantileSorted(durations, 0.5) : 0,\n p90Duration: durations.length ? quantileSorted(durations, 0.9) : 0,\n });\n }\n return rows;\n}\n\n/**\n * The aggregated mode's bars for one row: occurrences bucketed by the time they ENTER the\n * segment, `binSize` ms wide, aligned to `origin`. Only non-empty buckets are returned,\n * in time order, so a sparse year at a one-minute bin costs nothing for the empty minutes.\n */\nexport function aggregateSegmentBins(\n row: SpectrumRow,\n binSize: number,\n origin: number,\n): SpectrumBin[] {\n const size = Number.isFinite(binSize) && binSize > 0 ? binSize : 1;\n const buckets = new Map<number, SpectrumLine[]>();\n for (const line of row.lines) {\n const index = Math.floor((line.start - origin) / size);\n const bucket = buckets.get(index);\n if (bucket) bucket.push(line);\n else buckets.set(index, [line]);\n }\n return [...buckets.keys()].sort(ascending).map((index) => {\n const lines = buckets.get(index) as SpectrumLine[];\n const durations = lines.map((l) => l.duration).sort(ascending);\n return {\n segment: row.key,\n start: origin + index * size,\n end: origin + (index + 1) * size,\n count: lines.length,\n medianDuration: quantileSorted(durations, 0.5),\n quartile: medianQuartile(lines.map((l) => l.quartile)),\n };\n });\n}\n\n/**\n * The shared time domain `[min start, max end]` across every row. A degenerate domain\n * (one instant, or nothing at all) is widened by one second so a scale never divides by 0.\n */\nexport function spectrumDomain(rows: readonly SpectrumRow[]): [number, number] {\n let lo = Number.POSITIVE_INFINITY;\n let hi = Number.NEGATIVE_INFINITY;\n for (const row of rows) {\n for (const line of row.lines) {\n if (line.start < lo) lo = line.start;\n if (line.end > hi) hi = line.end;\n }\n }\n if (!Number.isFinite(lo) || !Number.isFinite(hi)) return [0, 1000];\n return hi > lo ? [lo, hi] : [lo, lo + 1000];\n}\n\n/** `count` evenly spaced tick instants across `domain`, both ends included. */\nexport function spectrumTicks(domain: readonly [number, number], count = 5): number[] {\n const n = Math.max(2, Math.floor(count));\n const [lo, hi] = domain;\n return Array.from({ length: n }, (_, i) => lo + ((hi - lo) * i) / (n - 1));\n}\n\n/**\n * Case ids with at least one occurrence overlapping `[from, to]` (inclusive), in the order\n * they are first met walking rows top to bottom and each row in time order.\n */\nexport function casesInRange(rows: readonly SpectrumRow[], from: number, to: number): string[] {\n const lo = Math.min(from, to);\n const hi = Math.max(from, to);\n const ids = new Set<string>();\n for (const row of rows) {\n for (const line of row.lines) {\n if (line.start <= hi && line.end >= lo) ids.add(line.caseId);\n }\n }\n return [...ids];\n}\n","\"use client\";\n\n/**\n * The context `PerformanceSpectrum` shares with its rows — RM-060.\n *\n * Every row paints against the SAME time domain, ticks, ramp and formatters; lifting\n * them into one provider keeps the row component prop-light and guarantees two rows can\n * never disagree about where an instant sits on the shared x-axis.\n */\nimport { createContext, useContext } from \"react\";\nimport type { SpectrumLine } from \"./aggregate-segments\";\n\n/** Every user-visible string `PerformanceSpectrum` renders. `{name}` placeholders fill. */\nexport interface PerformanceSpectrumLabels {\n /** Accessible name of the whole view. */\n label: string;\n /** `{from}`, `{to}` — a row's accessible name and default visible label. */\n segment: string;\n /** Appended to a selected row's accessible name. */\n selected: string;\n /** `{count}`, `{cases}`, `{median}`, `{p90}` — a row's parallel summary. */\n rowSummary: string;\n /** `{caseId}`, `{start}`, `{end}`, `{duration}`, `{quartile}` — one line, spoken. */\n occurrence: string;\n /** `{start}`, `{end}`, `{count}`, `{median}`, `{quartile}` — one aggregated bar, spoken. */\n bin: string;\n tooltipSegment: string;\n tooltipStart: string;\n tooltipEnd: string;\n tooltipDuration: string;\n tooltipQuartile: string;\n tooltipCount: string;\n tooltipMedian: string;\n /** `{n}` — a quartile's short name. */\n quartile: string;\n quartileFastest: string;\n quartileSlowest: string;\n /** Heading of the colour key. */\n legend: string;\n /** Accessible name of the time-axis brush track. */\n brush: string;\n brushHint: string;\n /** `{start}`, `{end}`, `{count}` — announced when a range is set. */\n brushRange: string;\n columnSegment: string;\n columnCases: string;\n columnMedian: string;\n columnP90: string;\n tableCaption: string;\n loading: string;\n empty: string;\n emptyBody: string;\n}\n\n/** The shipped English labels. */\nexport const PERFORMANCE_SPECTRUM_DEFAULT_LABELS: Readonly<PerformanceSpectrumLabels> =\n Object.freeze({\n label: \"Performance spectrum\",\n segment: \"{from} → {to}\",\n selected: \"selected\",\n rowSummary: \"{count} occurrences across {cases} cases, median {median}, 90th percentile {p90}\",\n occurrence: \"Case {caseId}, {start} to {end}, took {duration}, {quartile}\",\n bin: \"{start} to {end}: {count} cases entered, median {median}, {quartile}\",\n tooltipSegment: \"Segment\",\n tooltipStart: \"Start\",\n tooltipEnd: \"End\",\n tooltipDuration: \"Duration\",\n tooltipQuartile: \"Quartile\",\n tooltipCount: \"Cases entered\",\n tooltipMedian: \"Median duration\",\n quartile: \"Quartile {n}\",\n quartileFastest: \"fastest\",\n quartileSlowest: \"slowest\",\n legend: \"Duration quartile, per segment\",\n brush: \"Time range\",\n brushHint:\n \"Drag across the axis, or use Shift with the arrow keys, to pick a time range. Press Enter to filter to cases in the range, Escape to clear.\",\n brushRange: \"{start} to {end}, {count} cases\",\n columnSegment: \"Segment\",\n columnCases: \"Cases\",\n columnMedian: \"Median duration\",\n columnP90: \"90th percentile duration\",\n tableCaption: \"Performance spectrum — cases and duration per segment\",\n loading: \"Loading segment occurrences…\",\n empty: \"No segment occurrences\",\n emptyBody: \"None of the chosen segments occur in this log.\",\n });\n\n/** What every spectrum row reads. */\nexport interface PerformanceSpectrumContextValue {\n labels: PerformanceSpectrumLabels;\n mode: \"lines\" | \"aggregated\";\n binSize: number;\n domain: readonly [number, number];\n ticks: readonly number[];\n rowHeight: number;\n /** The four quartile inks as `var(--…)` references, quartile 1 first. */\n quartileColors: readonly string[];\n formatInstant: (ms: number) => string;\n formatDuration: (ms: number) => string;\n quartileName: (quartile: number) => string;\n onCaseSelect?: (caseId: string, occurrence: SpectrumLine) => void;\n}\n\nconst PerformanceSpectrumContext = createContext<PerformanceSpectrumContextValue | null>(null);\n\nexport const PerformanceSpectrumProvider = PerformanceSpectrumContext.Provider;\n\n/** Reads the spectrum context. Throws outside a `PerformanceSpectrum`. */\nexport function usePerformanceSpectrum(): PerformanceSpectrumContextValue {\n const value = useContext(PerformanceSpectrumContext);\n if (!value) {\n throw new Error(\"usePerformanceSpectrum must be used inside a PerformanceSpectrum\");\n }\n return value;\n}\n","\"use client\";\n\n/**\n * ConformanceOverlay — the process map read against a reference model (RM-062).\n *\n * A thin composition: `ProcessMap` with its `conformance` prop always set, plus a\n * {@link ConformanceLegend} explaining the three states. It authors no node or edge\n * rendering of its own — the tone, glyph and dash per element come from `ProcessMap`'s own\n * activity node and transition edge, so the overlay and the plain map can never drift\n * (`pnpm check --rule process-reuse`).\n *\n * Every state reaches the reader on three visual channels (tone, glyph, line style) and as\n * text (the accessible name on the canvas, a Conformance column in `tableView`), so the\n * overlay never relies on colour alone (analysis §5.4, WCAG 1.4.1).\n */\nimport { forwardRef, type HTMLAttributes } from \"react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport type { FlowLayoutDirection } from \"@elabs-ai/components-flow\";\nimport type { ConformanceResult } from \"../core/conformance\";\nimport type { ProcessGraph } from \"../core/types\";\nimport type { ProcessMetricSpec, ProcessSelection } from \"../process-map/map-model\";\nimport { ProcessMap, type ProcessMapProps } from \"../process-map/process-map\";\nimport { ConformanceLegend } from \"./conformance-legend\";\nimport type { ConformanceStateLabels } from \"./conformance-state\";\n\n/** The metric the overlay paints when none is given: case frequency on both marks. */\nexport const CONFORMANCE_OVERLAY_DEFAULT_METRIC: ProcessMetricSpec = Object.freeze({\n node: \"absolute_case\",\n edge: \"absolute\",\n}) as ProcessMetricSpec;\n\n/** Props for {@link ConformanceOverlay}. `onSelect` shadows the DOM handler, so it is omitted. */\nexport interface ConformanceOverlayProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** The discovered graph to overlay. */\n graph: ProcessGraph;\n /** The replay result (`tokenReplay`) against the reference model. */\n conformance: ConformanceResult;\n /** Which readings the nodes and edges print. @default case frequency */\n metric?: ProcessMetricSpec;\n /** @default \"TB\" */\n direction?: FlowLayoutDirection;\n /** Controlled selection, passed straight to `ProcessMap`. */\n selection?: ProcessSelection | null;\n onSelect?: ProcessMapProps[\"onSelect\"];\n onFilterIntent?: ProcessMapProps[\"onFilterIntent\"];\n /** Render the accessible table twin instead of the canvas. @default false */\n tableView?: boolean;\n /** No graph or replay yet. Renders the map's loading panel and hides the legend. */\n loading?: boolean;\n /** Override the legend's words. */\n labels?: Partial<ConformanceStateLabels>;\n}\n\n/**\n * The conformance overlay.\n *\n * @example\n * ```tsx\n * const model = useMemo(() => liftHappyPath(happyPath), [happyPath]);\n * const conformance = useMemo(() => tokenReplay(log, model), [log, model]);\n * <ConformanceOverlay graph={graph} conformance={conformance} />\n * ```\n */\nexport const ConformanceOverlay = forwardRef<HTMLDivElement, ConformanceOverlayProps>(\n function ConformanceOverlay(\n {\n graph,\n conformance,\n metric = CONFORMANCE_OVERLAY_DEFAULT_METRIC,\n direction,\n selection,\n onSelect,\n onFilterIntent,\n tableView = false,\n loading = false,\n labels,\n className,\n ...props\n },\n ref,\n ) {\n return (\n <div\n ref={ref}\n data-slot=\"conformance-overlay\"\n data-view={tableView ? \"table\" : \"canvas\"}\n className={cn(\"flex size-full min-h-0 flex-col gap-3\", className)}\n {...props}\n >\n {loading ? null : <ConformanceLegend labels={labels} className=\"self-start\" />}\n <div data-slot=\"conformance-overlay-map\" className=\"min-h-64 flex-1\">\n <ProcessMap\n graph={graph}\n metric={metric}\n conformance={conformance}\n direction={direction}\n selection={selection}\n onSelect={onSelect}\n onFilterIntent={onFilterIntent}\n tableView={tableView}\n loading={loading}\n />\n </div>\n </div>\n );\n },\n);\n","\"use client\";\n\n/**\n * ViolationList — why cases lost fitness, one row per deviation type (RM-062).\n *\n * A `Table` (`@elabs-ai/components-ui`) over a `ConformanceResult` (RM-061): the deviation\n * type, the share of cases carrying it and the number of those cases. Rows with at least\n * one deviation are sorted by `deviationCounts` descending (ties keep `DEVIATION_TYPES`\n * order); a final \"Conforming\" row counts the cases with no deviation at all, so the\n * conforming vs non-conforming pair analysis §2 asks for reads off the same table.\n *\n * Choosing a row emits `{ kind: \"cases\", ids }` — exactly the case ids whose replay found\n * that type — which a host hands straight to `filterLog`. The row's type cell is a real\n * `<button>`, so the same action is keyboard-operable; the component never filters itself.\n */\nimport { forwardRef, useCallback, useMemo, type HTMLAttributes } from \"react\";\nimport { ListFilter } from \"lucide-react\";\nimport {\n Button,\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 type { ConformanceResult } from \"../core/conformance\";\nimport type { FilterSpec } from \"../core/filter-log\";\nimport { DEVIATION_TYPES, type DeviationType } from \"../core/token-replay\";\nimport { fillLabel } from \"../variant-explorer/variant-explorer-model\";\n\n/** What a row stands for: a deviation type, or the cases with none. */\nexport type ViolationRowKind = DeviationType | \"conforming\";\n\n/** One row of {@link ViolationList}. */\nexport interface ViolationRow {\n kind: ViolationRowKind;\n /** Deviations of this type across all cases (`0` for `\"conforming\"`). */\n deviations: number;\n /** Ids of the cases carrying at least one deviation of this type, in log order. */\n caseIds: string[];\n /** `caseIds.length / traces.length`; `0` for an empty log. */\n share: number;\n}\n\n/** The filter a row emits — a strict subset of `/core`'s `FilterSpec`. */\nexport type ViolationFilterIntent = Extract<FilterSpec, { kind: \"cases\" }>;\n\n/**\n * The rows {@link ViolationList} renders, in order: every type with deviations, by count\n * descending, then `\"conforming\"`.\n */\nexport function violationRows(conformance: ConformanceResult): ViolationRow[] {\n const total = conformance.traces.length;\n const byType = new Map<DeviationType, string[]>(DEVIATION_TYPES.map((type) => [type, []]));\n const conforming: string[] = [];\n for (const trace of conformance.traces) {\n if (trace.deviations.length === 0) {\n conforming.push(trace.caseId);\n continue;\n }\n const seen = new Set<DeviationType>();\n for (const deviation of trace.deviations) {\n if (seen.has(deviation.type)) continue;\n seen.add(deviation.type);\n byType.get(deviation.type)?.push(trace.caseId);\n }\n }\n const share = (count: number) => (total === 0 ? 0 : count / total);\n const rows = DEVIATION_TYPES.filter((type) => conformance.deviationCounts[type] > 0)\n .map((type, order) => ({ type, order }))\n .sort(\n (a, b) =>\n conformance.deviationCounts[b.type] - conformance.deviationCounts[a.type] ||\n a.order - b.order,\n )\n .map(({ type }): ViolationRow => {\n const caseIds = byType.get(type) ?? [];\n return {\n kind: type,\n deviations: conformance.deviationCounts[type],\n caseIds,\n share: share(caseIds.length),\n };\n });\n rows.push({\n kind: \"conforming\",\n deviations: 0,\n caseIds: conforming,\n share: share(conforming.length),\n });\n return rows;\n}\n\n/** Every user-visible string. `{name}` placeholders are filled at render. */\nexport interface ViolationListLabels {\n undesired: string;\n skipped: string;\n wrongOrder: string;\n wrongStart: string;\n incomplete: string;\n conforming: string;\n columnType: string;\n columnShare: string;\n columnCases: string;\n /** `{conforming}`, `{total}`. */\n caption: string;\n /** Appended to the caption when rows can be chosen. */\n captionFilter: string;\n loading: string;\n empty: string;\n emptyBody: string;\n}\n\n/** The shipped English labels. */\nexport const VIOLATION_LIST_DEFAULT_LABELS: Readonly<ViolationListLabels> = Object.freeze({\n undesired: \"Undesired activity\",\n skipped: \"Skipped step\",\n wrongOrder: \"Wrong order\",\n wrongStart: \"Wrong start\",\n incomplete: \"Incomplete case\",\n conforming: \"Conforming\",\n columnType: \"Deviation\",\n columnShare: \"Share of cases\",\n columnCases: \"Cases\",\n caption: \"Deviations by type — {conforming} of {total} cases conform.\",\n captionFilter: \"Choose a row to filter the log to its cases.\",\n loading: \"Replaying cases…\",\n empty: \"No cases replayed\",\n emptyBody: \"Replay an event log against a happy path to list its deviations.\",\n});\n\n/** Props for {@link ViolationList}. */\nexport interface ViolationListProps extends HTMLAttributes<HTMLDivElement> {\n /** The replay result (`tokenReplay`). */\n conformance: ConformanceResult;\n /** When given, each row emits `{ kind: \"cases\", ids }` for its cases. */\n onFilterIntent?: (intent: ViolationFilterIntent) => void;\n /** No replay yet. Renders the loading panel rather than an empty table. */\n loading?: boolean;\n /** Override any user-visible string. */\n labels?: Partial<ViolationListLabels>;\n}\n\n/**\n * The violation list.\n *\n * @example\n * ```tsx\n * <ViolationList\n * conformance={tokenReplay(log, liftHappyPath(path))}\n * onFilterIntent={(intent) => explorer.applyIntent(intent)}\n * />\n * ```\n */\nexport const ViolationList = forwardRef<HTMLDivElement, ViolationListProps>(function ViolationList(\n { conformance, onFilterIntent, loading = false, labels: labelOverrides, className, ...props },\n ref,\n) {\n const { formatNumber } = useLocale();\n const labels = useMemo<ViolationListLabels>(\n () => ({ ...VIOLATION_LIST_DEFAULT_LABELS, ...labelOverrides }),\n [labelOverrides],\n );\n const rows = useMemo(() => violationRows(conformance), [conformance]);\n const emit = useCallback(\n (row: ViolationRow) => onFilterIntent?.({ kind: \"cases\", ids: row.caseIds }),\n [onFilterIntent],\n );\n\n if (loading) {\n return (\n <div\n ref={ref}\n data-slot=\"violation-list\"\n data-state=\"loading\"\n className={className}\n {...props}\n >\n <StatePanel kind=\"loading\" title={labels.loading} />\n </div>\n );\n }\n\n const total = conformance.traces.length;\n if (total === 0) {\n return (\n <div ref={ref} data-slot=\"violation-list\" data-state=\"empty\" className={className} {...props}>\n <StatePanel kind=\"empty\" title={labels.empty} description={labels.emptyBody} />\n </div>\n );\n }\n\n const conformingCount = rows[rows.length - 1]?.caseIds.length ?? 0;\n const interactive = onFilterIntent !== undefined;\n\n return (\n <div ref={ref} data-slot=\"violation-list\" className={className} {...props}>\n <Table data-slot=\"violation-list-table\">\n <TableCaption>\n {fillLabel(labels.caption, {\n conforming: formatNumber(conformingCount),\n total: formatNumber(total),\n })}\n {interactive ? ` ${labels.captionFilter}` : null}\n </TableCaption>\n <TableHeader>\n <TableRow>\n <TableHead scope=\"col\">{labels.columnType}</TableHead>\n <TableHead scope=\"col\" className=\"text-end\">\n {labels.columnShare}\n </TableHead>\n <TableHead scope=\"col\" className=\"text-end\">\n {labels.columnCases}\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {rows.map((row) => (\n <TableRow\n key={row.kind}\n data-slot=\"violation-list-row\"\n data-kind={row.kind}\n onClick={interactive ? () => emit(row) : undefined}\n >\n <TableHead scope=\"row\" className=\"font-normal\">\n {interactive ? (\n // The row's click handler receives this button's click (keyboard Enter/Space\n // included) — one action, one handler, reachable without a pointer.\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n data-slot=\"violation-list-filter\"\n className=\"-ms-3 gap-1.5\"\n >\n <ListFilter aria-hidden=\"true\" className=\"size-3.5\" />\n {labels[row.kind]}\n </Button>\n ) : (\n labels[row.kind]\n )}\n </TableHead>\n <TableCell className=\"text-end tabular-nums\">\n {formatNumber(row.share, { style: \"percent\", maximumFractionDigits: 1 })}\n </TableCell>\n <TableCell className=\"text-end tabular-nums\">\n {formatNumber(row.caseIds.length)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n );\n});\n","\"use client\";\n\n/**\n * HappyPathEditor — draw the reference sequence conformance is checked against (RM-062).\n *\n * An editable `CanvasShell` (`@elabs-ai/components-flow`): each step is a\n * {@link HappyPathStepNode} (a `FlowNode` with the step's controls), consecutive steps are\n * chained by `FlowButtonEdge`s whose \"+\" inserts a step between them, and a\n * `FlowPlaceholderNode` at the tail appends one. Removing a step reconnects the chain,\n * because the chain is re-derived from the step list.\n *\n * ## Controlled, and stateless on purpose\n *\n * `value` in, a complete `HappyPath` out through `onChange` on every edit — insert,\n * remove, rename (per keystroke), toggle. The editor keeps no state `HappyPath` does not\n * already model, so a host can run `liftHappyPath` + `tokenReplay` on every change and\n * the canvas can never disagree with the model it drew.\n */\nimport { forwardRef, useCallback, useMemo, type HTMLAttributes } from \"react\";\nimport type { Edge } from \"@xyflow/react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport {\n CanvasShell,\n FlowButtonEdge,\n FlowEdge,\n FlowPlaceholderNode,\n type BrandFlowButtonEdge,\n type BrandFlowPlaceholderNode,\n} from \"@elabs-ai/components-flow\";\nimport type { HappyPath, HappyPathStep } from \"../core/reference-model\";\nimport { fillLabel } from \"../variant-explorer/variant-explorer-model\";\nimport {\n HAPPY_PATH_EDITOR_DEFAULT_LABELS,\n HappyPathEditorContext,\n type HappyPathEditorContextValue,\n type HappyPathEditorLabels,\n} from \"./happy-path-editor-context\";\nimport { HappyPathStepNode, type HappyPathStepFlowNode } from \"./happy-path-step-node\";\n\nconst NODE_TYPES = { \"happy-path-step\": HappyPathStepNode, placeholder: FlowPlaceholderNode };\nconst EDGE_TYPES = { button: FlowButtonEdge, tail: FlowEdge };\n\n/** Vertical distance between consecutive steps, in flow units. */\nconst STEP_SPACING = 260;\n/** Id of the tail placeholder node. */\nconst TAIL_ID = \"happy-path-tail\";\n\n/** Id of the node for the step at `index`. */\nexport function happyPathStepNodeId(index: number): string {\n return `happy-path-step-${index}`;\n}\n\n/**\n * The activity a newly inserted step starts with: the first available activity the path\n * does not use yet, else the first available one, else empty (typed in afterwards).\n */\nexport function nextHappyPathActivity(\n path: HappyPath,\n availableActivities: readonly string[] | undefined,\n): string {\n if (!availableActivities || availableActivities.length === 0) return \"\";\n const used = new Set(path.steps.map((step) => step.activity));\n return availableActivities.find((activity) => !used.has(activity)) ?? availableActivities[0]!;\n}\n\n/** `path` with a new step inserted at `index`. */\nexport function insertHappyPathStep(\n path: HappyPath,\n index: number,\n step: HappyPathStep,\n): HappyPath {\n const steps = [...path.steps];\n steps.splice(index, 0, step);\n return { ...path, steps };\n}\n\n/** `path` without the step at `index`. The neighbours become consecutive. */\nexport function removeHappyPathStep(path: HappyPath, index: number): HappyPath {\n return { ...path, steps: path.steps.filter((_, i) => i !== index) };\n}\n\n/** `path` with the step at `index` patched. `false` flags are dropped, not stored. */\nexport function updateHappyPathStep(\n path: HappyPath,\n index: number,\n patch: Partial<HappyPathStep>,\n): HappyPath {\n return {\n ...path,\n steps: path.steps.map((step, i) => {\n if (i !== index) return step;\n const next: HappyPathStep = { ...step, ...patch };\n if (!next.optional) delete next.optional;\n if (!next.repeatable) delete next.repeatable;\n return next;\n }),\n };\n}\n\n/** Props for {@link HappyPathEditor}. `onChange` carries a path, so the DOM one is omitted. */\nexport interface HappyPathEditorProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n value: HappyPath;\n /** Fires with the complete path after every edit. */\n onChange: (path: HappyPath) => void;\n /** Activities a step may pick from (e.g. the log's). Omit for free-text activities. */\n availableActivities?: string[];\n /** Override any user-visible string. */\n labels?: Partial<HappyPathEditorLabels>;\n}\n\ntype EditorNode = HappyPathStepFlowNode | BrandFlowPlaceholderNode;\ntype EditorEdge = BrandFlowButtonEdge | Edge;\n\n/**\n * The happy-path editor.\n *\n * @example\n * ```tsx\n * const [path, setPath] = useState<HappyPath>({ id: \"p\", label: \"Order\", steps: [] });\n * const conformance = useMemo(() => tokenReplay(log, liftHappyPath(path)), [log, path]);\n * <HappyPathEditor value={path} onChange={setPath} availableActivities={activities} />\n * ```\n */\nexport const HappyPathEditor = forwardRef<HTMLDivElement, HappyPathEditorProps>(\n function HappyPathEditor(\n { value, onChange, availableActivities, labels: labelOverrides, className, ...props },\n ref,\n ) {\n const labels = useMemo<HappyPathEditorLabels>(\n () => ({ ...HAPPY_PATH_EDITOR_DEFAULT_LABELS, ...labelOverrides }),\n [labelOverrides],\n );\n\n const insertAt = useCallback(\n (index: number) =>\n onChange(\n insertHappyPathStep(value, index, {\n activity: nextHappyPathActivity(value, availableActivities),\n }),\n ),\n [value, onChange, availableActivities],\n );\n\n const context = useMemo<HappyPathEditorContextValue>(\n () => ({\n labels,\n availableActivities,\n updateStep: (index, patch) => onChange(updateHappyPathStep(value, index, patch)),\n removeStep: (index) => onChange(removeHappyPathStep(value, index)),\n }),\n [labels, availableActivities, value, onChange],\n );\n\n const nodes = useMemo<EditorNode[]>(() => {\n const stepNodes: EditorNode[] = value.steps.map((step, index) => {\n const flags = [\n step.optional ? labels.optional.toLowerCase() : null,\n step.repeatable ? labels.repeatable.toLowerCase() : null,\n ].filter(Boolean);\n return {\n id: happyPathStepNodeId(index),\n type: \"happy-path-step\",\n position: { x: 0, y: index * STEP_SPACING },\n data: { index, step },\n draggable: false,\n ariaLabel: fillLabel(labels.stepName, {\n n: index + 1,\n activity: step.activity || labels.untitled,\n flags: flags.length > 0 ? `, ${flags.join(\", \")}` : \"\",\n }),\n };\n });\n stepNodes.push({\n id: TAIL_ID,\n type: \"placeholder\",\n position: { x: 0, y: value.steps.length * STEP_SPACING },\n data: { label: labels.addStep, onActivate: () => insertAt(value.steps.length) },\n draggable: false,\n });\n return stepNodes;\n }, [value.steps, labels, insertAt]);\n\n const edges = useMemo<EditorEdge[]>(() => {\n const chain: EditorEdge[] = [];\n value.steps.forEach((step, index) => {\n const next = value.steps[index + 1];\n if (next) {\n chain.push({\n id: `${happyPathStepNodeId(index)}->${happyPathStepNodeId(index + 1)}`,\n source: happyPathStepNodeId(index),\n target: happyPathStepNodeId(index + 1),\n type: \"button\",\n data: {\n label: fillLabel(labels.insertStep, {\n before: step.activity || labels.untitled,\n after: next.activity || labels.untitled,\n }),\n onInsert: () => insertAt(index + 1),\n },\n });\n } else {\n chain.push({\n id: `${happyPathStepNodeId(index)}->${TAIL_ID}`,\n source: happyPathStepNodeId(index),\n target: TAIL_ID,\n type: \"tail\",\n });\n }\n });\n return chain;\n }, [value.steps, labels, insertAt]);\n\n return (\n <div\n ref={ref}\n data-slot=\"happy-path-editor\"\n data-steps={value.steps.length}\n className={cn(\"relative size-full min-h-80\", className)}\n {...props}\n >\n <HappyPathEditorContext value={context}>\n <CanvasShell<EditorNode, EditorEdge>\n nodes={nodes}\n edges={edges}\n nodeTypes={NODE_TYPES}\n edgeTypes={EDGE_TYPES}\n nodesDraggable={false}\n nodesConnectable={false}\n edgesFocusable={false}\n deleteKeyCode={null}\n fitView\n fitViewKey={value.steps.length}\n aria-label={fillLabel(labels.canvas, { path: value.label })}\n />\n </HappyPathEditorContext>\n </div>\n );\n },\n);\n","\"use client\";\n\n/**\n * The seam between `HappyPathEditor` and its step nodes (RM-062).\n *\n * React Flow renders a node from `data` alone, so the edit actions and the labels reach a\n * step through this context rather than through per-node callbacks in `data` — node data\n * stays the step itself, and the actions keep one identity per edit.\n */\nimport { createContext, use } from \"react\";\nimport type { HappyPathStep } from \"../core/reference-model\";\n\n/** Every user-visible string of the editor. `{name}` placeholders are filled at render. */\nexport interface HappyPathEditorLabels {\n /** Accessible name of the canvas. `{path}` — the happy path's label. */\n canvas: string;\n /** Eyebrow above a step. `{n}` — the one-based position. */\n step: string;\n /** Accessible name of a step node. `{n}`, `{activity}`, `{flags}`. */\n stepName: string;\n /** Shown as a step's title while its activity is empty. */\n untitled: string;\n /** Accessible name of a step's activity field. `{n}`. */\n activity: string;\n /** Placeholder of the free-text activity field. */\n activityPlaceholder: string;\n optional: string;\n repeatable: string;\n /** Accessible name of a step's optional switch. `{activity}`. */\n optionalFor: string;\n /** Accessible name of a step's repeatable switch. `{activity}`. */\n repeatableFor: string;\n remove: string;\n /** Accessible name of a step's remove button. `{activity}`. */\n removeFor: string;\n /** Accessible name of an edge's insert button. `{before}`, `{after}`. */\n insertStep: string;\n /** Label of the tail placeholder that appends a step. */\n addStep: string;\n}\n\n/** The shipped English labels. */\nexport const HAPPY_PATH_EDITOR_DEFAULT_LABELS: Readonly<HappyPathEditorLabels> = Object.freeze({\n canvas: \"Happy path editor — {path}\",\n step: \"Step {n}\",\n stepName: \"Step {n}: {activity}{flags}\",\n untitled: \"Untitled step\",\n activity: \"Activity for step {n}\",\n activityPlaceholder: \"Activity name\",\n optional: \"Optional\",\n repeatable: \"Repeatable\",\n optionalFor: \"Optional — {activity}\",\n repeatableFor: \"Repeatable — {activity}\",\n remove: \"Remove\",\n removeFor: \"Remove {activity}\",\n insertStep: \"Insert a step between {before} and {after}\",\n addStep: \"Add step\",\n});\n\n/** What a step node reads from its editor. */\nexport interface HappyPathEditorContextValue {\n labels: HappyPathEditorLabels;\n availableActivities: readonly string[] | undefined;\n updateStep: (index: number, patch: Partial<HappyPathStep>) => void;\n removeStep: (index: number) => void;\n}\n\nconst NOOP_CONTEXT: HappyPathEditorContextValue = {\n labels: HAPPY_PATH_EDITOR_DEFAULT_LABELS,\n availableActivities: undefined,\n updateStep: () => {},\n removeStep: () => {},\n};\n\n/** Provided by `HappyPathEditor`; a step rendered on its own reads inert defaults. */\nexport const HappyPathEditorContext = createContext<HappyPathEditorContextValue>(NOOP_CONTEXT);\n\n/** Read the editor's labels and actions. */\nexport function useHappyPathEditor(): HappyPathEditorContextValue {\n return use(HappyPathEditorContext);\n}\n","\"use client\";\n\n/**\n * HappyPathStepNode — one prescribed step on the happy-path canvas (RM-062).\n *\n * Composes `@elabs-ai/components-flow`'s `FlowNode` (frame, handles, focus treatment) and\n * puts the step's controls in its `footer` slot: the activity (a `Select` over\n * `availableActivities`, or free text), the `optional`/`repeatable` `Switch`es that map\n * one-to-one onto `HappyPathStep`, and a remove button. It holds no state — every control\n * writes straight back through the editor context.\n *\n * A repeatable step shows a repeat glyph and an optional one a skip glyph beside its\n * title, and both words join the node's accessible name, so neither flag is carried by a\n * switch position alone.\n */\nimport { useId, useMemo } from \"react\";\nimport { Redo2, Repeat, Trash2 } from \"lucide-react\";\nimport {\n Button,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Switch,\n} from \"@elabs-ai/components-ui\";\nimport { FlowNode, type FlowNodeData } from \"@elabs-ai/components-flow\";\nimport type { Node, NodeProps } from \"@xyflow/react\";\nimport type { HappyPathStep } from \"../core/reference-model\";\nimport { fillLabel } from \"../variant-explorer/variant-explorer-model\";\nimport { useHappyPathEditor } from \"./happy-path-editor-context\";\n\n/** `data` carried by a {@link HappyPathStepFlowNode}. */\nexport interface HappyPathStepNodeData extends Record<string, unknown> {\n /** Zero-based position in the path. */\n index: number;\n step: HappyPathStep;\n}\n\n/** A happy-path step node. Register as `nodeTypes={{ \"happy-path-step\": … }}`. */\nexport type HappyPathStepFlowNode = Node<HappyPathStepNodeData, \"happy-path-step\">;\n\n/** One step of the happy-path editor. */\nexport function HappyPathStepNode(props: NodeProps<HappyPathStepFlowNode>) {\n const { index, step } = props.data;\n const { labels, availableActivities, updateStep, removeStep } = useHappyPathEditor();\n const optionalId = useId();\n const repeatableId = useId();\n const n = index + 1;\n const activityName = step.activity || labels.untitled;\n\n const options = useMemo(() => {\n if (!availableActivities) return undefined;\n return step.activity && !availableActivities.includes(step.activity)\n ? [step.activity, ...availableActivities]\n : [...availableActivities];\n }, [availableActivities, step.activity]);\n\n const flowData = useMemo<FlowNodeData>(\n () => ({\n title: activityName,\n kind: fillLabel(labels.step, { n }),\n icon:\n step.repeatable || step.optional ? (\n <span className=\"flex items-center gap-0.5\">\n {step.optional ? <Redo2 aria-hidden=\"true\" /> : null}\n {step.repeatable ? <Repeat aria-hidden=\"true\" /> : null}\n </span>\n ) : undefined,\n footer: (\n <div\n data-slot=\"happy-path-step-node-controls\"\n // `nodrag nopan`: typing, toggling and clicking here must never pan the canvas\n // or start a node drag.\n className=\"nodrag nopan flex flex-col gap-2\"\n >\n {options ? (\n <Select\n value={step.activity || undefined}\n onValueChange={(activity) => updateStep(index, { activity })}\n >\n <SelectTrigger\n size=\"sm\"\n aria-label={fillLabel(labels.activity, { n })}\n data-slot=\"happy-path-step-node-activity\"\n className=\"w-full\"\n >\n <SelectValue placeholder={labels.activityPlaceholder} />\n </SelectTrigger>\n <SelectContent>\n {options.map((activity) => (\n <SelectItem key={activity} value={activity}>\n {activity}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : (\n <Input\n value={step.activity}\n onChange={(event) => updateStep(index, { activity: event.target.value })}\n aria-label={fillLabel(labels.activity, { n })}\n placeholder={labels.activityPlaceholder}\n data-slot=\"happy-path-step-node-activity\"\n className=\"h-control-sm\"\n />\n )}\n <div className=\"flex items-center justify-between gap-3\">\n <Label htmlFor={optionalId} className=\"text-caption\">\n {labels.optional}\n </Label>\n <Switch\n id={optionalId}\n data-slot=\"happy-path-step-node-optional\"\n aria-label={fillLabel(labels.optionalFor, { activity: activityName })}\n checked={step.optional === true}\n onCheckedChange={(optional) => updateStep(index, { optional })}\n />\n </div>\n <div className=\"flex items-center justify-between gap-3\">\n <Label htmlFor={repeatableId} className=\"text-caption\">\n {labels.repeatable}\n </Label>\n <Switch\n id={repeatableId}\n data-slot=\"happy-path-step-node-repeatable\"\n aria-label={fillLabel(labels.repeatableFor, { activity: activityName })}\n checked={step.repeatable === true}\n onCheckedChange={(repeatable) => updateStep(index, { repeatable })}\n />\n </div>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n data-slot=\"happy-path-step-node-remove\"\n aria-label={fillLabel(labels.removeFor, { activity: activityName })}\n className=\"self-end gap-1.5\"\n onClick={() => removeStep(index)}\n >\n <Trash2 aria-hidden=\"true\" className=\"size-3.5\" />\n {labels.remove}\n </Button>\n </div>\n ),\n }),\n [\n activityName,\n labels,\n n,\n index,\n step.activity,\n step.optional,\n step.repeatable,\n options,\n optionalId,\n repeatableId,\n updateStep,\n removeStep,\n ],\n );\n\n return (\n <div\n data-slot=\"happy-path-step-node\"\n data-optional={step.optional ? \"true\" : undefined}\n data-repeatable={step.repeatable ? \"true\" : undefined}\n className=\"w-56\"\n >\n <FlowNode {...props} type=\"brand\" data={flowData} />\n </div>\n );\n}\n","\"use client\";\n\n/**\n * ProcessCompare — side-by-side or superimposed diff of two process maps (RM-064).\n *\n * ## What it is\n *\n * A composition, like every other view in this package: `side-by-side` renders two\n * `CompareSide` panels — each a plain `ProcessMap` — with ONE `AbstractionControls` above\n * both (fanned out to both maps, the same \"lift state, drive two children\" shape\n * `useProcessExplorer` already uses for a single map); `superimposed` renders a SINGLE\n * `ProcessMap` over the union of both graphs. Either way, `CompareKpiStrip` reads underneath\n * — cases and median throughput, A vs B, with a delta on B's tile.\n *\n * ## The diff channel is never colour alone\n *\n * Superimposed mode paints each activity's accent swatch with a diff-state colour token\n * (`--success` common, `--chart-1` A-only, `--destructive` B-only — {@link diffColorScale})\n * AND appends the diff state as real text to the activity's own title\n * ({@link withDiffLabels}) — the same text a screen reader announces as part of the node's\n * accessible name, not a decorative glyph a colour-blind or non-visual reader could miss\n * (WCAG 1.4.1). The `Legend` alongside states the same three words. Transitions carry no\n * diff encoding of their own: `ProcessMap` has no per-edge colour hook to repurpose the way\n * `colorScale` lets a node's accent be repurposed, and adding one is a `ProcessMap` change\n * outside this item's scope (see this component's own module test for the deviation this\n * records).\n *\n * ## No new `ProcessMap` prop\n *\n * Both modes compose the SHIPPED `ProcessMap` twice or once — no fork, no new prop on it.\n * One consequence: `ProcessMap` exposes no viewport ref or controlled-viewport prop, so\n * `side-by-side`'s two `CanvasShell`s cannot be zoom/pan-synced from outside it. The\n * roadmap's `onZoomSync` is therefore NOT implemented here — a prop that could only ever be\n * a no-op is a worse API than no prop at all (see `.claude/rules/conventions.md`'s \"no\n * speculative props\"). Two panes with independent pan/zoom, laid out side by side so a\n * reader can still eyeball the same region on both, is what ships.\n */\nimport { forwardRef, useId, useMemo, useState, type HTMLAttributes } from \"react\";\nimport { Table2 } from \"lucide-react\";\nimport { Label, Switch, useLocale } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { Legend, type FlowLayoutDirection } from \"@elabs-ai/components-flow\";\nimport { abstractGraph, type AbstractionOptions } from \"../core/abstract-graph\";\nimport { diffGraphs } from \"../core/diff-graphs\";\nimport type { EventLog, ProcessGraph } from \"../core/types\";\nimport { AbstractionControls } from \"../abstraction-controls/abstraction-controls\";\nimport { ProcessMap, type ProcessMapProps } from \"../process-map/process-map\";\nimport { CompareKpiStrip } from \"./compare-kpi-strip\";\nimport { CompareSide } from \"./compare-side\";\nimport { resolveCompareKpis, withDiffLabels } from \"./compare-model\";\nimport { diffColorScale, diffToProcessGraph, EMPTY_PROCESS_GRAPH } from \"./diff-to-graph\";\n\n/** One side of the comparison. */\nexport interface ProcessCompareSide {\n /** How this side is named in headers, the KPI strip and the diff legend/labels. */\n label: string;\n /** A discovered graph. `undefined` while this side is still loading. */\n graph: ProcessGraph | undefined;\n /** The raw log, for `CompareKpiStrip`'s median-throughput reading. Optional. */\n log?: EventLog;\n}\n\nexport type ProcessCompareMode = \"side-by-side\" | \"superimposed\";\n\nexport interface ProcessCompareProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n a: ProcessCompareSide;\n b: ProcessCompareSide;\n /** @default \"side-by-side\" */\n mode?: ProcessCompareMode;\n metric: ProcessMapProps[\"metric\"];\n /** Shared abstraction, synced across both sides (or the one superimposed map). */\n abstraction: AbstractionOptions;\n onAbstractionChange(next: Partial<AbstractionOptions>): void;\n /** @default \"TB\" */\n direction?: FlowLayoutDirection;\n /** Neither side has a graph yet. Renders every map's own loading panel. @default false */\n loading?: boolean;\n /** Accessible name for the region. Defaults to the localized `process.compare.label`. */\n label?: string;\n}\n\n/** ProcessMap's own narrower abstraction shape — `AbstractionControls` needs the full options. */\nfunction toMapAbstraction(\n abstraction: AbstractionOptions,\n): Pick<AbstractionOptions, \"activities\" | \"paths\"> {\n return { activities: abstraction.activities, paths: abstraction.paths };\n}\n\n/**\n * Side-by-side or superimposed diff of two process maps.\n *\n * @example\n * ```tsx\n * <ProcessCompare\n * a={{ label: \"Before\", graph: beforeGraph }}\n * b={{ label: \"After\", graph: afterGraph }}\n * metric={{ node: \"absolute_case\", edge: \"absolute\" }}\n * abstraction={abstraction}\n * onAbstractionChange={setAbstraction}\n * />\n * ```\n */\nexport const ProcessCompare = forwardRef<HTMLDivElement, ProcessCompareProps>(\n function ProcessCompare(\n {\n a,\n b,\n mode = \"side-by-side\",\n metric,\n abstraction,\n onAbstractionChange,\n direction = \"TB\",\n loading = false,\n label,\n className,\n ...props\n },\n ref,\n ) {\n const { t } = useLocale();\n const tableViewId = useId();\n const [tableView, setTableView] = useState(false);\n\n const mapAbstraction = useMemo(() => toMapAbstraction(abstraction), [abstraction]);\n\n // The union of both sides — read for the ONE shared `AbstractionControls` (both modes)\n // and, in superimposed mode, for the single map's own `graph` prop below. Computed\n // unconditionally: it is a cheap linear scan, and building it once keeps the \"auto\"\n // heuristic's reference graph and the superimposed render byte-identical rather than two\n // independently-maintained unions.\n const diff = useMemo(\n () => diffGraphs(a.graph ?? EMPTY_PROCESS_GRAPH, b.graph ?? EMPTY_PROCESS_GRAPH),\n [a.graph, b.graph],\n );\n const unionGraph = useMemo(() => diffToProcessGraph(diff), [diff]);\n const abstracted = useMemo(\n () => abstractGraph(unionGraph, abstraction),\n [unionGraph, abstraction],\n );\n\n const aKpis = useMemo(() => resolveCompareKpis(a), [a]);\n const bKpis = useMemo(() => resolveCompareKpis(b), [b]);\n\n const superimposedGraph = useMemo(\n () => withDiffLabels(unionGraph, diff, a.label, b.label, t),\n [unionGraph, diff, a.label, b.label, t],\n );\n const superimposedColorScale = useMemo(() => diffColorScale(diff), [diff]);\n\n return (\n <div\n ref={ref}\n data-slot=\"process-compare\"\n role=\"group\"\n aria-label={label ?? t(\"process.compare.label\")}\n className={cn(\"flex flex-col gap-4\", className)}\n {...props}\n >\n <div\n data-slot=\"process-compare-toolbar\"\n className=\"flex flex-wrap items-start justify-between gap-3\"\n >\n <AbstractionControls\n abstraction={abstraction}\n onAbstractionChange={onAbstractionChange}\n graph={abstracted}\n hiddenCounts={abstracted.hidden}\n />\n <div className=\"flex items-center gap-2\">\n <Switch id={tableViewId} checked={tableView} onCheckedChange={setTableView} />\n <Label htmlFor={tableViewId} className=\"inline-flex items-center gap-1.5 text-body\">\n <Table2 aria-hidden=\"true\" className=\"size-4\" />\n {t(\"process.compare.tableView\")}\n </Label>\n </div>\n </div>\n\n {mode === \"superimposed\" ? (\n <div data-slot=\"process-compare-superimposed\" className=\"flex flex-col gap-3\">\n <Legend\n variant=\"categorical\"\n title={t(\"process.compare.legendTitle\")}\n items={[\n { label: t(\"process.compare.diffSuffixCommon\"), color: \"var(--success)\" },\n {\n label: t(\"process.compare.diffSuffixOnly\", { label: a.label }),\n color: \"var(--chart-1)\",\n },\n {\n label: t(\"process.compare.diffSuffixOnly\", { label: b.label }),\n color: \"var(--destructive)\",\n },\n ]}\n />\n <div className=\"min-h-96 flex-1\">\n <ProcessMap\n graph={superimposedGraph}\n metric={metric}\n abstraction={mapAbstraction}\n direction={direction}\n colorScale={superimposedColorScale}\n tableView={tableView}\n loading={loading}\n label={t(\"process.compare.superimposedMapLabel\", {\n aLabel: a.label,\n bLabel: b.label,\n })}\n className=\"size-full\"\n />\n </div>\n </div>\n ) : (\n <div data-slot=\"process-compare-sides\" className=\"grid gap-4 md:grid-cols-2\">\n <CompareSide\n label={a.label}\n map={{\n graph: a.graph,\n log: a.log,\n metric,\n abstraction: mapAbstraction,\n direction,\n tableView,\n loading,\n label: t(\"process.compare.mapLabel\", { label: a.label }),\n }}\n />\n <CompareSide\n label={b.label}\n map={{\n graph: b.graph,\n log: b.log,\n metric,\n abstraction: mapAbstraction,\n direction,\n tableView,\n loading,\n label: t(\"process.compare.mapLabel\", { label: b.label }),\n }}\n />\n </div>\n )}\n\n <CompareKpiStrip\n a={{ label: a.label, kpis: aKpis }}\n b={{ label: b.label, kpis: bKpis }}\n loading={loading}\n />\n </div>\n );\n },\n);\n","\"use client\";\n\n/**\n * CompareKpiStrip — the one shared reading of \"how did B differ from A\" (RM-064): cases and\n * median throughput for each side, paired, with a delta on B's own tile.\n *\n * Built entirely on `MetricCard`/`MetricGrid` (`@elabs-ai/components-ui`/`-charts`) — no\n * local grid, no local delta styling. `MetricCard`'s own `delta`/`deltaDirection` already\n * carries direction as an arrow glyph AND a \"up/down, favorable/unfavorable\" accessible\n * name (never colour alone, see `metric-card.tsx`), so this file adds no channel of its\n * own — it only decides WHICH two numbers to pair and what counts as favourable.\n */\nimport { forwardRef, type HTMLAttributes } from \"react\";\nimport { MetricCard, useLocale } from \"@elabs-ai/components-ui\";\nimport { MetricGrid } from \"@elabs-ai/components-charts\";\nimport { formatDurationMs } from \"../process-map/map-model\";\nimport type { CompareSideKpis } from \"./compare-model\";\n\nexport interface CompareKpiStripSide {\n /** The host-supplied name for this side (\"Before\", \"Q1\", the log's own file name, …). */\n label: string;\n kpis: CompareSideKpis;\n}\n\nexport interface CompareKpiStripProps extends HTMLAttributes<HTMLDivElement> {\n a: CompareKpiStripSide;\n b: CompareKpiStripSide;\n loading?: boolean;\n}\n\ninterface Delta {\n text: string;\n direction: \"up\" | \"down\" | \"neutral\";\n}\n\n/** `b` relative to `a`, as a signed percentage — `null` when `a` is `0` (nothing to divide by). */\nfunction percentDelta(a: number, b: number): Delta | null {\n if (a === 0) return null;\n const fraction = (b - a) / a;\n const percent = Math.round(fraction * 100);\n if (percent === 0) return { text: \"0%\", direction: \"neutral\" };\n return { text: `${percent > 0 ? \"+\" : \"\"}${percent}%`, direction: percent > 0 ? \"up\" : \"down\" };\n}\n\n/** Compact KPI pairs for `ProcessCompare`: cases and median throughput, A vs B. */\nexport const CompareKpiStrip = forwardRef<HTMLDivElement, CompareKpiStripProps>(\n function CompareKpiStrip({ a, b, loading = false, className, ...props }, ref) {\n const { t } = useLocale();\n const casesDelta = percentDelta(a.kpis.cases, b.kpis.cases);\n const hasThroughput =\n a.kpis.medianThroughput !== undefined && b.kpis.medianThroughput !== undefined;\n const throughputDelta = hasThroughput\n ? percentDelta(a.kpis.medianThroughput as number, b.kpis.medianThroughput as number)\n : null;\n\n return (\n <div ref={ref} data-slot=\"compare-kpi-strip\" className={className} {...props}>\n <MetricGrid columns={hasThroughput ? 4 : 2} loading={loading}>\n <MetricCard\n label={t(\"process.compare.cases\", { label: a.label })}\n value={a.kpis.cases}\n announceLoading={false}\n />\n <MetricCard\n label={t(\"process.compare.cases\", { label: b.label })}\n value={b.kpis.cases}\n announceLoading={false}\n delta={casesDelta?.text}\n deltaDirection={casesDelta?.direction}\n />\n {hasThroughput ? (\n <>\n <MetricCard\n label={t(\"process.compare.medianThroughput\", { label: a.label })}\n value={formatDurationMs(a.kpis.medianThroughput as number)}\n announceLoading={false}\n />\n <MetricCard\n label={t(\"process.compare.medianThroughput\", { label: b.label })}\n value={formatDurationMs(b.kpis.medianThroughput as number)}\n announceLoading={false}\n delta={throughputDelta?.text}\n deltaDirection={throughputDelta?.direction}\n // A shorter throughput is the improvement — the inverse of the cases pair\n // above, where more/fewer cases carries no inherent favourability.\n positiveIsGood={false}\n />\n </>\n ) : null}\n </MetricGrid>\n </div>\n );\n },\n);\n","\"use client\";\n\n/**\n * CompareSide — one labeled `ProcessMap` panel inside `ProcessCompare`'s side-by-side mode\n * (RM-064). Owns nothing beyond a heading and the map itself — `ProcessCompare` lifts\n * abstraction, metric and table-view state so both sides stay in lockstep, the same\n * \"lift state into the provider\" convention a compound component follows, applied to a\n * fixed pair rather than an open set of children.\n */\nimport { forwardRef, type HTMLAttributes } from \"react\";\nimport { Heading } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { ProcessMap, type ProcessMapProps } from \"../process-map/process-map\";\n\nexport interface CompareSideProps extends HTMLAttributes<HTMLDivElement> {\n /** The host-supplied name for this side (\"Before\", \"Q1\", the log's own file name, …). */\n label: string;\n /** Forwarded to `ProcessMap` verbatim — everything this side's canvas needs. */\n map: Omit<ProcessMapProps, \"className\">;\n}\n\n/** One heading + one `ProcessMap`, stacked to fill its panel. */\nexport const CompareSide = forwardRef<HTMLDivElement, CompareSideProps>(function CompareSide(\n { label, map, className, ...props },\n ref,\n) {\n return (\n <div\n ref={ref}\n data-slot=\"compare-side\"\n className={cn(\"flex min-h-0 flex-col gap-2\", className)}\n {...props}\n >\n <Heading level={3} size=\"subtitle\" data-slot=\"compare-side-label\">\n {label}\n </Heading>\n <div className=\"min-h-96 flex-1\">\n <ProcessMap {...map} className=\"size-full\" />\n </div>\n </div>\n );\n});\n","/**\n * diffToProcessGraph — turn a `ProcessGraphDiff` (`/core`'s `diffGraphs`, RM-064) into the\n * ONE `ProcessGraph` `ProcessCompare`'s superimposed mode hands to a single `ProcessMap`,\n * plus a synthetic `ActivityColorScale` that paints by diff state instead of by identity.\n *\n * `ProcessMap` (RM-051) is never forked or given a new prop for this: its existing\n * `colorScale` prop (RM-054) is the only per-activity colour hook it exposes, and\n * `ActivityColorScale` is a plain interface — nothing stops a second caller building one\n * keyed by diff state rather than by rank. Pure, no React; lives beside `process-compare.tsx`\n * rather than under `/core` because it is presentation shaping for ONE component (label\n * suffixes, colour tokens), not a reusable process-mining derivation.\n */\nimport type {\n ActivityColor,\n ActivityColorLegendEntry,\n ActivityColorScale,\n} from \"../core/activity-color-scale\";\nimport type { DiffEntry, DiffState, ProcessGraphDiff } from \"../core/diff-graphs\";\nimport type { ActivityStats, ProcessGraph, TransitionStats } from \"../core/types\";\n\n/** Empty graph — the identity input for a side that has not discovered anything yet. */\nexport const EMPTY_PROCESS_GRAPH: ProcessGraph = {\n activities: [],\n transitions: [],\n startActivities: {},\n endActivities: {},\n totals: { cases: 0, events: 0, variants: 0 },\n};\n\n/**\n * The three diff tokens the design asks for (RM-064's spec): `common` reads as success,\n * `aOnly` as the first chart series, `bOnly` as destructive. Semantic tokens only — no\n * literal colour is authored (`.claude/rules/conventions.md`'s styling rule).\n */\nexport const DIFF_STATE_TOKEN: Record<DiffState, string> = {\n common: \"--success\",\n aOnly: \"--chart-1\",\n bOnly: \"--destructive\",\n};\n\n/** `b`'s stats when the element survives into `b` (common or bOnly); `a`'s otherwise. */\nfunction representative<Stats>(entry: DiffEntry<Stats>): Stats {\n return (entry.b ?? entry.a) as Stats;\n}\n\n/**\n * The ONE `ProcessGraph` a superimposed `ProcessMap` renders: every activity/transition\n * either side ever saw, painted with whichever side's stats are the \"current\" reading (`b`\n * when present, `a` otherwise). `startActivities`/`endActivities` are recomputed from the\n * represented activities themselves, so they can never disagree with what `activities`\n * above actually carries. `totals` prefers `b`'s (the \"current\" period) and falls back to\n * `a`'s only when `b` measured nothing at all (e.g. a `b` side still loading).\n */\nexport function diffToProcessGraph(diff: ProcessGraphDiff): ProcessGraph {\n const activities: ActivityStats[] = diff.activities.map(representative);\n const transitions: TransitionStats[] = diff.transitions.map(representative);\n const totals =\n diff.totals.b.cases > 0 || diff.totals.b.events > 0 ? diff.totals.b : diff.totals.a;\n return {\n activities,\n transitions,\n startActivities: Object.fromEntries(\n activities\n .filter((activity) => activity.isStart)\n .map((activity) => [activity.id, activity.cases]),\n ),\n endActivities: Object.fromEntries(\n activities\n .filter((activity) => activity.isEnd)\n .map((activity) => [activity.id, activity.cases]),\n ),\n totals,\n };\n}\n\n/** Diff state per activity id, straight off {@link ProcessGraphDiff.activities}. */\nexport function diffStateByActivity(diff: ProcessGraphDiff): ReadonlyMap<string, DiffState> {\n return new Map(diff.activities.map((entry) => [entry.id, entry.state]));\n}\n\n/**\n * A synthetic `ActivityColorScale` that paints every activity by its DIFF STATE rather\n * than its identity. `colorFor` is the only method `ProcessMap` itself calls (see\n * `map-model.ts`'s `buildProcessMapModel`); `codeFor`/`labelFor`/`legend` exist only to\n * satisfy the shared interface.\n */\nexport function diffColorScale(diff: ProcessGraphDiff): ActivityColorScale {\n const states = diffStateByActivity(diff);\n const colorFor = (activityId: string): ActivityColor => ({\n token: DIFF_STATE_TOKEN[states.get(activityId) ?? \"common\"],\n });\n const legend: ActivityColorLegendEntry[] = diff.activities.map((entry) => ({\n activityId: entry.id,\n label: entry.id,\n code: entry.id.slice(0, 2).toUpperCase(),\n ...colorFor(entry.id),\n }));\n return {\n colorFor,\n codeFor: (activityId) => activityId.slice(0, 2).toUpperCase(),\n labelFor: (activityId) => activityId,\n legend,\n };\n}\n","/**\n * compare-model — the presentation-shaping helpers `ProcessCompare`/`CompareKpiStrip` read\n * from, kept out of `process-compare.tsx` so the render function stays about layout, not\n * derivation (RM-064).\n */\nimport { asNormalizedLog } from \"../core/event-log\";\nimport { durationStats } from \"../core/duration-stats\";\nimport type { DiffState, ProcessGraphDiff } from \"../core/diff-graphs\";\nimport type { EventLog, ProcessGraph } from \"../core/types\";\nimport { diffStateByActivity } from \"./diff-to-graph\";\n\n/** One side's raw input — a discovered graph, an event log, or (loading) neither. */\nexport interface CompareSideInput {\n graph?: ProcessGraph;\n log?: EventLog;\n}\n\n/** The two numbers {@link CompareKpiStrip} reads for one side. */\nexport interface CompareSideKpis {\n cases: number;\n /** Median case throughput, ms. Omitted when the side has no `log` to derive it from. */\n medianThroughput?: number;\n}\n\n/**\n * Cases and median throughput for one side, mirroring `useProcessExplorer`'s own `kpis`\n * derivation (`cases`/`medianThroughput` from a normalized log's per-case durations).\n * `medianThroughput` needs case-level duration samples that a bare `ProcessGraph` does not\n * carry (only per-activity/per-transition aggregates do) — so it is only derivable when the\n * caller also handed this side a `log`; a `graph`-only side reports cases alone.\n */\nexport function resolveCompareKpis(side: CompareSideInput): CompareSideKpis {\n if (side.log) {\n const normalized = asNormalizedLog(side.log);\n const medianThroughput = durationStats(normalized.cases.map((kase) => kase.duration)).median;\n return { cases: normalized.totals.cases, medianThroughput };\n }\n return { cases: side.graph?.totals.cases ?? 0 };\n}\n\n/** Translator shape this module needs — the same signature `useLocale()` returns. */\nexport type Translate = (key: string, vars?: Record<string, string | number>) => string;\n\n/**\n * The diff-state word for one activity, localized. `\"common\"` reads as a plain word; the\n * two single-sided states name the OWNING side by its host-supplied label (\"Before only\",\n * \"After only\") rather than the generic \"A\"/\"B\" — the label is what the reader actually\n * chose to call that side.\n */\nexport function diffStateSuffix(\n state: DiffState,\n aLabel: string,\n bLabel: string,\n t: Translate,\n): string {\n if (state === \"common\") return t(\"process.compare.diffSuffixCommon\");\n return t(\"process.compare.diffSuffixOnly\", { label: state === \"aOnly\" ? aLabel : bLabel });\n}\n\n/**\n * Append the localized diff-state word to every activity's own label — the TEXT channel a\n * superimposed `ProcessMap` reads as its node title (`map-model.ts`'s `buildProcessMapModel`\n * always prints `activity.label`), so diff state reaches the reader through real text and\n * the node's accessible name, not only through {@link diffColorScale}'s accent swatch\n * (WCAG 1.4.1 — colour is never the only channel).\n */\nexport function withDiffLabels(\n graph: ProcessGraph,\n diff: ProcessGraphDiff,\n aLabel: string,\n bLabel: string,\n t: Translate,\n): ProcessGraph {\n const stateById = diffStateByActivity(diff);\n return {\n ...graph,\n activities: graph.activities.map((activity) => {\n const state = stateById.get(activity.id) ?? \"common\";\n const suffix = diffStateSuffix(state, aLabel, bLabel, t);\n return { ...activity, label: `${activity.label || activity.id} · ${suffix}` };\n }),\n };\n}\n","\"use client\";\n\n/**\n * ProcessReplay — cases replayed as tokens moving over the process map (RM-065).\n *\n * A composition, not a new map: `ProcessMap` (read-only — no `onSelect`), with the tokens\n * handed to its own transition edges through {@link ProcessReplayTokensContext}. The edges\n * draw them with `@elabs-ai/components-flow`'s `FlowEdgeTokens`, which owns every SVG mark;\n * this component owns only the clock.\n *\n * - **Time.** `replayTimeline` buckets the log; the playhead is a controllable `time`, driven\n * by a `requestAnimationFrame` loop while `playing`. At 1× the whole timeline plays in\n * {@link REPLAY_PLAYBACK_MS}. Reaching the end pauses.\n * - **Congestion without colour.** One token per in-flight case, so a busy edge carries more\n * tokens; each token's radius grows with its edge's congestion in the current bucket\n * (`replayTokenRadius`). {@link CongestionHeat} states the ranking in words beside the map.\n * The weighted-edge value ramp is deliberately not used.\n * - **Motion.** Never starts on its own under reduced motion: `defaultPlaying` and a\n * parent's `playing` are both held until the reader presses play. The tokens then jump\n * rather than glide (the flow primitive drops its transition).\n * - **Text alternative.** The tokens are `aria-hidden`. One polite live region announces the\n * playhead and the in-flight case count whenever the replay is paused or scrubbed — not\n * frame by frame while it plays.\n */\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n} from \"react\";\nimport { StatePanel } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { useReducedMotion } from \"@elabs-ai/components-tokens\";\nimport type { FlowEdgeToken, FlowLayoutDirection } from \"@elabs-ai/components-flow\";\nimport { replayFrameAt, replayTimeline, replayTokenRadius } from \"../core/replay-timeline\";\nimport type { EventLog, ProcessGraph } from \"../core/types\";\nimport type { ProcessMetricSpec } from \"../process-map/map-model\";\nimport { ProcessMap } from \"../process-map/process-map\";\nimport { fillLabel } from \"../variant-explorer/variant-explorer-model\";\nimport { CongestionHeat } from \"./congestion-heat\";\nimport { ReplayControls } from \"./replay-controls\";\nimport {\n PROCESS_REPLAY_DEFAULT_LABELS,\n useReplayTimeFormatter,\n type ProcessReplayLabels,\n} from \"./replay-format\";\nimport { ProcessReplayTokensContext, type ProcessReplayTokens } from \"./replay-tokens-context\";\nimport { useControllableValue } from \"./use-controllable-value\";\n\n/** Wall-clock length of a full replay at 1×, in ms. */\nexport const REPLAY_PLAYBACK_MS = 30_000;\n\n/** The metric the map paints when none is given: case frequency on both marks. */\nexport const PROCESS_REPLAY_DEFAULT_METRIC: ProcessMetricSpec = Object.freeze({\n node: \"absolute_case\",\n edge: \"absolute\",\n}) as ProcessMetricSpec;\n\n/** Props for {@link ProcessReplay}. `onSelect` is not offered: the map is read-only. */\nexport interface ProcessReplayProps extends HTMLAttributes<HTMLDivElement> {\n /** The discovered graph to replay over. */\n graph: ProcessGraph;\n /** The log the graph came from — tokens need per-case timing, not just the aggregate. */\n log: EventLog;\n /** Align every case's start to `t = 0` instead of the wall clock. @default false */\n synchronizedStart?: boolean;\n /** Bucket width in ms for congestion. @default the log span / 300 */\n bucketMs?: number;\n /** Which readings the nodes and edges print. @default case frequency */\n metric?: ProcessMetricSpec;\n /** @default \"TB\" */\n direction?: FlowLayoutDirection;\n /** Controlled playing state. Held while reduced motion is on until the reader presses play. */\n playing?: boolean;\n /** Initial playing state when uncontrolled. Ignored under reduced motion. @default false */\n defaultPlaying?: boolean;\n onPlayingChange?: (playing: boolean) => void;\n /** Controlled playhead, ms relative to the timeline origin. */\n time?: number;\n /** @default 0 */\n defaultTime?: number;\n onTimeChange?: (time: number) => void;\n /** Controlled playback multiplier. */\n speed?: number;\n /** @default 1 */\n defaultSpeed?: number;\n onSpeedChange?: (speed: number) => void;\n /** How many transitions the congestion list ranks. @default 5 */\n congestionLimit?: number;\n /** No log yet. Renders the loading panel. */\n loading?: boolean;\n /** Override any user-visible string. */\n labels?: Partial<ProcessReplayLabels>;\n}\n\n/**\n * The process replay.\n *\n * @example\n * ```tsx\n * const graph = useMemo(() => discoverGraph(log), [log]);\n * <ProcessReplay graph={graph} log={log} synchronizedStart />\n * ```\n */\nexport const ProcessReplay = forwardRef<HTMLDivElement, ProcessReplayProps>(function ProcessReplay(\n {\n graph,\n log,\n synchronizedStart = false,\n bucketMs,\n metric = PROCESS_REPLAY_DEFAULT_METRIC,\n direction,\n playing: playingProp,\n defaultPlaying = false,\n onPlayingChange,\n time: timeProp,\n defaultTime = 0,\n onTimeChange,\n speed: speedProp,\n defaultSpeed = 1,\n onSpeedChange,\n congestionLimit = 5,\n loading = false,\n labels: labelOverrides,\n className,\n ...props\n },\n ref,\n) {\n const labels = useMemo<ProcessReplayLabels>(\n () => ({ ...PROCESS_REPLAY_DEFAULT_LABELS, ...labelOverrides }),\n [labelOverrides],\n );\n const reducedMotion = useReducedMotion();\n const timeline = useMemo(\n () => replayTimeline(log, { bucketMs, synchronizedStart }),\n [log, bucketMs, synchronizedStart],\n );\n const { duration } = timeline;\n\n const [playingRequested, setPlaying] = useControllableValue(\n playingProp,\n defaultPlaying,\n onPlayingChange,\n );\n const [rawTime, setTime] = useControllableValue(timeProp, defaultTime, onTimeChange);\n const [speed, setSpeed] = useControllableValue(speedProp, defaultSpeed, onSpeedChange);\n const time = Math.min(Math.max(rawTime, 0), duration);\n\n // Reduced motion: nothing moves until the reader asks for it with the play button.\n const [readerStarted, setReaderStarted] = useState(false);\n const playing = playingRequested && duration > 0 && (!reducedMotion || readerStarted);\n\n const timeRef = useRef(time);\n useLayoutEffect(() => {\n timeRef.current = time;\n });\n\n useEffect(() => {\n if (!playing) return;\n let handle = 0;\n let last: number | null = null;\n const tick = (now: number) => {\n if (last !== null) {\n const advance = ((now - last) * duration * speed) / REPLAY_PLAYBACK_MS;\n const next = Math.min(timeRef.current + advance, duration);\n timeRef.current = next;\n setTime(next);\n if (next >= duration) {\n setPlaying(false);\n return;\n }\n }\n last = now;\n handle = requestAnimationFrame(tick);\n };\n handle = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(handle);\n }, [playing, duration, speed, setTime, setPlaying]);\n\n const handlePlayingChange = useCallback(\n (next: boolean) => {\n if (next) {\n setReaderStarted(true);\n if (timeRef.current >= duration) setTime(0);\n }\n setPlaying(next);\n },\n [duration, setPlaying, setTime],\n );\n\n const frame = useMemo(() => replayFrameAt(timeline, time), [timeline, time]);\n const tokensByEdge = useMemo<ProcessReplayTokens>(() => {\n const map = new Map<string, FlowEdgeToken[]>();\n for (const token of frame.tokens) {\n let list = map.get(token.edgeId);\n if (!list) map.set(token.edgeId, (list = []));\n list.push({\n id: token.caseId,\n progress: token.progress,\n radius: replayTokenRadius(frame.congestion[token.edgeId] ?? 0, timeline.peakCongestion),\n });\n }\n return map;\n }, [frame, timeline.peakCongestion]);\n\n const formatTime = useReplayTimeFormatter(timeline, labels.relativeTime);\n const inFlight = frame.tokens.length;\n\n // The map element is memoized on its own inputs, so a playhead tick re-renders only the\n // edges that read the tokens context — never the layout or the model.\n const map = useMemo(\n () => <ProcessMap graph={graph} metric={metric} direction={direction} label={labels.map} />,\n [graph, metric, direction, labels.map],\n );\n\n if (loading) {\n return (\n <div\n ref={ref}\n data-slot=\"process-replay\"\n data-state=\"loading\"\n className={className}\n {...props}\n >\n <StatePanel kind=\"loading\" title={labels.loading} />\n </div>\n );\n }\n\n if (timeline.segments.length === 0 && duration === 0) {\n return (\n <div ref={ref} data-slot=\"process-replay\" data-state=\"empty\" className={className} {...props}>\n <StatePanel kind=\"empty\" title={labels.empty} description={labels.emptyBody} />\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n role=\"region\"\n aria-label={labels.region}\n data-slot=\"process-replay\"\n data-state={playing ? \"playing\" : \"paused\"}\n data-synchronized-start={synchronizedStart ? \"true\" : undefined}\n className={cn(\"flex size-full min-h-0 flex-col gap-3\", className)}\n {...props}\n >\n <ReplayControls\n playing={playing}\n onPlayingChange={handlePlayingChange}\n time={time}\n onTimeChange={setTime}\n duration={duration}\n step={timeline.bucketMs}\n speed={speed}\n onSpeedChange={setSpeed}\n inFlight={inFlight}\n formatTime={formatTime}\n labels={labels}\n />\n <p role=\"status\" aria-live=\"polite\" data-slot=\"process-replay-status\" className=\"sr-only\">\n {playing\n ? labels.playing\n : fillLabel(labels.readout, { time: formatTime(time), count: inFlight })}\n </p>\n <div className=\"flex min-h-0 flex-1 flex-col gap-3 lg:flex-row\">\n <div data-slot=\"process-replay-map\" data-in-flight={inFlight} className=\"min-h-64 flex-1\">\n <ProcessReplayTokensContext value={tokensByEdge}>{map}</ProcessReplayTokensContext>\n </div>\n <CongestionHeat\n timeline={timeline}\n limit={congestionLimit}\n labels={labels}\n className=\"shrink-0 lg:w-72\"\n />\n </div>\n </div>\n );\n});\n","\"use client\";\n\n/**\n * CongestionHeat — the replay's congestion, as a ranked text list (RM-065).\n *\n * The map shows congestion without colour — more tokens on a busy edge, each drawn larger —\n * and this list states it in words: the transitions that held the most cases at once, with\n * the peak count and when it happened, plus a relative bar whose LENGTH (not hue) repeats the\n * peak. It is static (the whole timeline, not the playhead), so it is also the complete\n * reading for anyone who never presses play, including under reduced motion.\n */\nimport { forwardRef, useId, useMemo, type HTMLAttributes } from \"react\";\nimport { useLocale } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { rankReplayCongestion, type ReplayTimeline } from \"../core/replay-timeline\";\nimport { fillLabel } from \"../variant-explorer/variant-explorer-model\";\nimport {\n PROCESS_REPLAY_DEFAULT_LABELS,\n useReplayTimeFormatter,\n type ProcessReplayLabels,\n} from \"./replay-format\";\n\n/** Props for {@link CongestionHeat}. */\nexport interface CongestionHeatProps extends HTMLAttributes<HTMLElement> {\n /** The replay timeline (`replayTimeline`). */\n timeline: ReplayTimeline;\n /** How many transitions to list. @default 5 */\n limit?: number;\n labels?: Partial<ProcessReplayLabels>;\n}\n\n/** The most congested transitions of a replay, ranked. */\nexport const CongestionHeat = forwardRef<HTMLElement, CongestionHeatProps>(function CongestionHeat(\n { timeline, limit = 5, labels: labelOverrides, className, ...props },\n ref,\n) {\n const { formatNumber } = useLocale();\n const labels = useMemo<ProcessReplayLabels>(\n () => ({ ...PROCESS_REPLAY_DEFAULT_LABELS, ...labelOverrides }),\n [labelOverrides],\n );\n const formatTime = useReplayTimeFormatter(timeline, labels.relativeTime);\n const ranked = useMemo(() => rankReplayCongestion(timeline, limit), [timeline, limit]);\n const titleId = useId();\n const top = ranked[0];\n\n return (\n <section\n ref={ref}\n data-slot=\"congestion-heat\"\n aria-labelledby={titleId}\n className={cn(\"flex min-w-0 flex-col gap-2\", className)}\n {...props}\n >\n <h3 id={titleId} data-slot=\"congestion-heat-title\" className=\"text-subtitle\">\n {labels.congestionTitle}\n </h3>\n {top ? (\n <>\n <p data-slot=\"congestion-heat-summary\" className=\"text-caption text-muted-foreground\">\n {fillLabel(labels.congestionSummary, {\n source: top.source,\n target: top.target,\n peak: formatNumber(top.peak),\n time: formatTime(top.peakAt),\n })}\n </p>\n <ol data-slot=\"congestion-heat-list\" className=\"flex flex-col gap-2\">\n {ranked.map((entry) => (\n <li\n key={entry.edgeId}\n data-slot=\"congestion-heat-item\"\n data-edge-id={entry.edgeId}\n data-peak={entry.peak}\n className=\"flex min-w-0 flex-col gap-1\"\n >\n <span className=\"text-body truncate\">\n {fillLabel(labels.congestionTransition, {\n source: entry.source,\n target: entry.target,\n })}\n </span>\n <span className=\"text-meta text-muted-foreground tabular-nums\">\n {fillLabel(labels.congestionPeak, {\n peak: formatNumber(entry.peak),\n time: formatTime(entry.peakAt),\n })}\n </span>\n <span aria-hidden=\"true\" className=\"h-1 w-full rounded-full bg-muted\">\n <span\n data-slot=\"congestion-heat-bar\"\n className=\"block h-full rounded-full bg-primary\"\n style={{ width: `${(entry.peak / top.peak) * 100}%` }}\n />\n </span>\n </li>\n ))}\n </ol>\n </>\n ) : (\n <p data-slot=\"congestion-heat-empty\" className=\"text-caption text-muted-foreground\">\n {labels.congestionEmpty}\n </p>\n )}\n </section>\n );\n});\n","\"use client\";\n\n/**\n * Words and time formatting shared by the replay parts (RM-065).\n */\nimport { useCallback } from \"react\";\nimport { useLocale } from \"@elabs-ai/components-ui\";\nimport type { ReplayTimeline } from \"../core/replay-timeline\";\nimport { formatDurationMs } from \"../process-map/map-model\";\n\n/** Every user-visible string in `ProcessReplay` and its parts. `{…}` are placeholders. */\nexport interface ProcessReplayLabels {\n /** Accessible name of the replay region. */\n region: string;\n /** Accessible name of the map canvas. */\n map: string;\n play: string;\n pause: string;\n /** The time slider. */\n time: string;\n /** The speed select. */\n speed: string;\n /** `{speed}` — one speed option. */\n speedOption: string;\n /** `{time}`, `{count}` — the readout and the paused announcement. */\n readout: string;\n /** Announced while the replay runs (the time is not announced frame by frame). */\n playing: string;\n /** `{duration}` — a playhead in synchronized-start mode. */\n relativeTime: string;\n congestionTitle: string;\n /** `{source}`, `{target}`. */\n congestionTransition: string;\n /** `{peak}`, `{time}` — one ranked transition. */\n congestionPeak: string;\n /** `{source}`, `{target}`, `{peak}`, `{time}` — the one-line summary of the busiest transition. */\n congestionSummary: string;\n congestionEmpty: string;\n loading: string;\n empty: string;\n emptyBody: string;\n}\n\n/** The shipped English labels. */\nexport const PROCESS_REPLAY_DEFAULT_LABELS: Readonly<ProcessReplayLabels> = Object.freeze({\n region: \"Process replay\",\n map: \"Process map with case tokens\",\n play: \"Play replay\",\n pause: \"Pause replay\",\n time: \"Replay time\",\n speed: \"Playback speed\",\n speedOption: \"{speed}×\",\n readout: \"{time} · {count} in flight\",\n playing: \"Replay playing\",\n relativeTime: \"+{duration}\",\n congestionTitle: \"Most congested transitions\",\n congestionTransition: \"{source} → {target}\",\n congestionPeak: \"Peak {peak} cases at {time}\",\n congestionSummary: \"Busiest: {source} → {target}, {peak} cases at {time}\",\n congestionEmpty: \"No case moves along a transition.\",\n loading: \"Preparing the replay…\",\n empty: \"No cases to replay\",\n emptyBody: \"Load an event log with at least one case to replay it.\",\n});\n\n/**\n * Formats a playhead: a medium date and short time in wall-clock mode, `+duration` since each\n * case's start in synchronized-start mode.\n */\nexport function useReplayTimeFormatter(\n timeline: Pick<ReplayTimeline, \"origin\" | \"synchronizedStart\">,\n relativeTemplate: string = PROCESS_REPLAY_DEFAULT_LABELS.relativeTime,\n): (t: number) => string {\n const { formatDate } = useLocale();\n const { origin, synchronizedStart } = timeline;\n return useCallback(\n (t: number) =>\n synchronizedStart\n ? relativeTemplate.replace(\"{duration}\", formatDurationMs(t))\n : formatDate(origin + t, { dateStyle: \"medium\", timeStyle: \"short\" }),\n [formatDate, origin, synchronizedStart, relativeTemplate],\n );\n}\n","\"use client\";\n\n/**\n * ReplayControls — the transport bar of `ProcessReplay` (RM-065).\n *\n * Presentational and fully controlled: a play/pause button, a time slider and a speed select,\n * all native-keyboard components from `@elabs-ai/components-ui` (Space/Enter on the button,\n * arrows/Home/End/PageUp/PageDown on the slider, arrows and typeahead in the select), plus a\n * visible readout of the playhead and the in-flight case count. The readout is NOT a live\n * region — `ProcessReplay` owns the one announcement per region.\n */\nimport { forwardRef, useMemo, type HTMLAttributes } from \"react\";\nimport { Pause, Play } from \"lucide-react\";\nimport {\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Slider,\n useLocale,\n} from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { fillLabel } from \"../variant-explorer/variant-explorer-model\";\nimport { PROCESS_REPLAY_DEFAULT_LABELS, type ProcessReplayLabels } from \"./replay-format\";\n\n/** The speed multipliers the select offers. */\nexport const REPLAY_SPEEDS = Object.freeze([0.5, 1, 2, 4] as const);\n\n/** Props for {@link ReplayControls}. */\nexport interface ReplayControlsProps extends HTMLAttributes<HTMLDivElement> {\n playing: boolean;\n onPlayingChange: (playing: boolean) => void;\n /** Playhead, ms relative to the timeline origin. */\n time: number;\n onTimeChange: (time: number) => void;\n /** Timeline length in ms — the slider's maximum. */\n duration: number;\n /** Slider step in ms (one bucket). */\n step: number;\n speed: number;\n onSpeedChange: (speed: number) => void;\n /** Cases in flight at {@link time}. */\n inFlight: number;\n /** Formats a playhead for the readout and the slider's value text. */\n formatTime: (time: number) => string;\n /** Disable every control (an empty timeline). */\n disabled?: boolean;\n labels?: Partial<ProcessReplayLabels>;\n}\n\n/** Play/pause, time slider, speed select and a readout. */\nexport const ReplayControls = forwardRef<HTMLDivElement, ReplayControlsProps>(\n function ReplayControls(\n {\n playing,\n onPlayingChange,\n time,\n onTimeChange,\n duration,\n step,\n speed,\n onSpeedChange,\n inFlight,\n formatTime,\n disabled = false,\n labels: labelOverrides,\n className,\n ...props\n },\n ref,\n ) {\n const { formatNumber } = useLocale();\n const labels = useMemo<ProcessReplayLabels>(\n () => ({ ...PROCESS_REPLAY_DEFAULT_LABELS, ...labelOverrides }),\n [labelOverrides],\n );\n const timeText = formatTime(time);\n const PlayIcon = playing ? Pause : Play;\n\n return (\n <div\n ref={ref}\n data-slot=\"replay-controls\"\n data-playing={playing ? \"true\" : \"false\"}\n className={cn(\"flex flex-wrap items-center gap-3\", className)}\n {...props}\n >\n <Button\n data-slot=\"replay-controls-play\"\n variant=\"outline\"\n size=\"icon\"\n disabled={disabled}\n aria-label={playing ? labels.pause : labels.play}\n onClick={() => onPlayingChange(!playing)}\n >\n <PlayIcon aria-hidden=\"true\" />\n </Button>\n <Slider\n data-slot=\"replay-controls-time\"\n className=\"min-w-40 flex-1\"\n min={0}\n max={Math.max(duration, step)}\n step={step}\n value={[time]}\n disabled={disabled}\n aria-label={labels.time}\n aria-valuetext={timeText}\n onValueChange={([next]) => {\n if (next !== undefined) onTimeChange(Math.min(next, duration));\n }}\n />\n {/* A `SelectTrigger` is full-width by default, which in this wrapping row claimed a\n line of its own — a speed picker as wide as the map. Its box is the speed, no more. */}\n <div className=\"w-24 shrink-0\">\n <Select\n value={String(speed)}\n disabled={disabled}\n onValueChange={(value) => onSpeedChange(Number(value))}\n >\n <SelectTrigger\n data-slot=\"replay-controls-speed\"\n className=\"min-w-20\"\n aria-label={labels.speed}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {REPLAY_SPEEDS.map((option) => (\n <SelectItem key={option} value={String(option)}>\n {fillLabel(labels.speedOption, { speed: formatNumber(option) })}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <p\n data-slot=\"replay-controls-readout\"\n className=\"text-meta text-muted-foreground tabular-nums\"\n >\n {fillLabel(labels.readout, { time: timeText, count: formatNumber(inFlight) })}\n </p>\n </div>\n );\n },\n);\n","\"use client\";\n\n/**\n * Controlled/uncontrolled state with a STABLE setter (RM-065).\n *\n * Mirrors `useControllableState` in `@elabs-ai/components-ui` (mode locked on mount,\n * `onChange` always called), which that package does not export. The one difference is the\n * point of this copy: the setter's identity never changes, even when a consumer passes an\n * inline `onChange`, so the replay's animation-frame loop does not restart every frame.\n */\nimport { useCallback, useLayoutEffect, useRef, useState } from \"react\";\n\nexport function useControllableValue<T>(\n controlledValue: T | undefined,\n defaultValue: T,\n onChange?: (value: T) => void,\n): [T, (next: T) => void] {\n const isControlledRef = useRef(controlledValue !== undefined);\n const [internal, setInternal] = useState<T>(defaultValue);\n const value = isControlledRef.current ? (controlledValue as T) : internal;\n const onChangeRef = useRef(onChange);\n useLayoutEffect(() => {\n onChangeRef.current = onChange;\n });\n const setValue = useCallback((next: T) => {\n if (!isControlledRef.current) setInternal(next);\n onChangeRef.current?.(next);\n }, []);\n return [value, setValue];\n}\n"],"mappings":"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","names":["index","index","useCallback","useEffect","useMemo","useRef","useState","cn","compareStrings","ConformanceStateMark","ConformanceLegend","index","compareStrings","compareStrings","compareStrings","cn","jsx","jsxs","cn","useMemo","cn","createContext","use","jsx","index","useMemo","cn","useMemo","snapshot","jsx","jsxs","useMemo","useCallback","useEffect","useState","useRef","cn","index","forwardRef","useCallback","Button","useLocale","cn","jsx","jsxs","Button","cn","forwardRef","AbstractionControls","useLocale","useCallback","forwardRef","useCallback","useId","useState","useLocale","cn","jsx","jsxs","forwardRef","MetricLayerSwitch","useLocale","useId","useState","useCallback","cn","cn","useLocale","jsx","jsxs","useLocale","cn","useCallback","useEffect","useMemo","useRef","useState","sequenceOf","variantId","transitionKey","useMemo","useState","useRef","useEffect","useCallback","index","forwardRef","useCallback","useId","useMemo","useRef","useState","Button","Slider","StatePanel","Table","TableBody","TableCaption","TableCell","TableHead","TableHeader","TableRow","useLocale","cn","forwardRef","cn","forwardRef","cn","jsx","jsxs","VariantCoverageBar","forwardRef","cn","jsx","jsxs","forwardRef","VariantSequenceChips","cn","jsx","jsxs","forwardRef","VariantRow","index","cn","jsx","jsxs","forwardRef","VariantExplorer","useLocale","useMemo","useCallback","useId","useState","useRef","index","cn","StatePanel","Slider","Button","Table","TableCaption","TableHeader","TableRow","TableHead","TableBody","TableCell","forwardRef","Button","useLocale","cn","jsx","jsxs","ProcessFilterBar","index","forwardRef","useCallback","useMemo","Button","useLocale","cn","jsx","jsxs","forwardRef","CaseTable","useLocale","useMemo","useCallback","cn","Button","forwardRef","useMemo","Badge","useLocale","cn","index","jsx","jsxs","Badge","forwardRef","CaseTimeline","useLocale","useMemo","cn","forwardRef","useCallback","useMemo","useRef","useState","StatePanel","Table","TableBody","TableCaption","TableCell","TableHead","TableHeader","TableRow","useLocale","cn","ordered","index","useLayoutEffect","useState","jsx","jsxs","DAY_MS","forwardRef","DottedChart","useLocale","useMemo","useCallback","px","py","useRef","useState","StatePanel","Table","TableCaption","TableHeader","TableRow","TableHead","TableBody","TableCell","cn","forwardRef","useCallback","useId","useMemo","useRef","useState","CanvasLayer","canvasTokenColor","CHART_HAIRLINE_WIDTH","createSpatialGrid","Legend","StatePanel","Table","TableBody","TableCaption","TableCell","TableHead","TableHeader","TableRow","useLocale","cn","index","createContext","jsx","jsxs","useRef","useCallback","useMemo","canvasTokenColor","CHART_HAIRLINE_WIDTH","createSpatialGrid","first","CanvasLayer","forwardRef","PerformanceSpectrum","useLocale","useId","useState","cn","StatePanel","Table","TableCaption","TableHeader","TableRow","TableHead","TableBody","TableCell","Legend","index","forwardRef","cn","jsx","jsxs","forwardRef","ConformanceOverlay","cn","forwardRef","useCallback","useMemo","Button","StatePanel","Table","TableBody","TableCaption","TableCell","TableHead","TableHeader","TableRow","useLocale","jsx","jsxs","forwardRef","ViolationList","useLocale","useMemo","useCallback","StatePanel","Table","TableCaption","TableHeader","TableRow","TableHead","TableBody","Button","TableCell","forwardRef","useCallback","useMemo","cn","CanvasShell","createContext","use","useId","useMemo","Button","Label","Select","SelectContent","SelectItem","SelectTrigger","SelectValue","Switch","FlowNode","jsx","jsxs","index","useId","useMemo","Select","SelectTrigger","SelectValue","SelectContent","SelectItem","Label","Switch","Button","FlowNode","jsx","NODE_TYPES","EDGE_TYPES","index","forwardRef","HappyPathEditor","useMemo","useCallback","cn","CanvasShell","forwardRef","useId","useMemo","useState","Table2","Label","Switch","useLocale","cn","Legend","forwardRef","MetricCard","useLocale","MetricGrid","Fragment","jsx","jsxs","forwardRef","CompareKpiStrip","useLocale","MetricGrid","MetricCard","forwardRef","cn","jsx","jsxs","forwardRef","CompareSide","cn","jsx","jsxs","forwardRef","ProcessCompare","useLocale","useId","useState","useMemo","cn","Switch","Label","Table2","Legend","forwardRef","useCallback","useEffect","useLayoutEffect","useMemo","useRef","useState","StatePanel","cn","forwardRef","useId","useMemo","useLocale","cn","useCallback","useLocale","useLocale","useCallback","Fragment","jsx","jsxs","forwardRef","CongestionHeat","useLocale","useMemo","useId","cn","forwardRef","useMemo","Play","Button","Select","SelectContent","SelectItem","SelectTrigger","SelectValue","Slider","useLocale","cn","jsx","jsxs","forwardRef","ReplayControls","useLocale","useMemo","Play","cn","Button","Slider","Select","SelectTrigger","SelectValue","SelectContent","SelectItem","useCallback","useLayoutEffect","useRef","useState","jsx","jsxs","forwardRef","ProcessReplay","useMemo","useState","useRef","useLayoutEffect","useEffect","useCallback","map","StatePanel","cn"]}