chrono_forge-dashboard 0.2.0 → 0.3.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +36 -15
  4. data/app/assets/chrono_forge/dashboard/dashboard.css +1 -1
  5. data/app/assets/chrono_forge/dashboard/dashboard.js +86 -80
  6. data/app/assets/chrono_forge/dashboard/turbo.min.js +34 -0
  7. data/app/controllers/chrono_forge/dashboard/actions_controller.rb +41 -21
  8. data/app/controllers/chrono_forge/dashboard/assets_controller.rb +1 -0
  9. data/app/controllers/chrono_forge/dashboard/definitions_controller.rb +3 -3
  10. data/app/controllers/chrono_forge/dashboard/overview_controller.rb +45 -0
  11. data/app/controllers/chrono_forge/dashboard/stranded_controller.rb +29 -0
  12. data/app/helpers/chrono_forge/dashboard/dashboard_helper.rb +135 -1
  13. data/app/jobs/chrono_forge/dashboard/bulk_reap_job.rb +11 -0
  14. data/app/jobs/chrono_forge/dashboard/bulk_retry_job.rb +24 -0
  15. data/app/queries/chrono_forge/dashboard/overview_query.rb +62 -0
  16. data/app/queries/chrono_forge/dashboard/workflows_query.rb +6 -1
  17. data/app/views/chrono_forge/dashboard/analytics/index.html.erb +26 -7
  18. data/app/views/chrono_forge/dashboard/branch_children/show.html.erb +8 -3
  19. data/app/views/chrono_forge/dashboard/overview/_card_skeleton.html.erb +5 -0
  20. data/app/views/chrono_forge/dashboard/overview/_stat_card.html.erb +5 -0
  21. data/app/views/chrono_forge/dashboard/overview/blocked.html.erb +5 -0
  22. data/app/views/chrono_forge/dashboard/overview/classes.html.erb +65 -0
  23. data/app/views/chrono_forge/dashboard/overview/in_flight.html.erb +4 -0
  24. data/app/views/chrono_forge/dashboard/overview/index.html.erb +36 -0
  25. data/app/views/chrono_forge/dashboard/overview/processed.html.erb +4 -0
  26. data/app/views/chrono_forge/dashboard/repetitions/index.html.erb +30 -8
  27. data/app/views/chrono_forge/dashboard/stranded/index.html.erb +73 -0
  28. data/app/views/chrono_forge/dashboard/wait_states/index.html.erb +32 -8
  29. data/app/views/chrono_forge/dashboard/workflows/_error_card.html.erb +1 -1
  30. data/app/views/chrono_forge/dashboard/workflows/_filters.html.erb +9 -3
  31. data/app/views/chrono_forge/dashboard/workflows/_timeline.html.erb +66 -18
  32. data/app/views/chrono_forge/dashboard/workflows/_workflow_row.html.erb +18 -1
  33. data/app/views/chrono_forge/dashboard/workflows/index.html.erb +10 -3
  34. data/app/views/chrono_forge/dashboard/workflows/show.html.erb +28 -1
  35. data/app/views/layouts/chrono_forge/dashboard/application.html.erb +12 -5
  36. data/config/routes.rb +15 -1
  37. data/lib/chrono_forge/dashboard/version.rb +1 -1
  38. metadata +16 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ebe7cacdd958bfd807d5fe695f44f8d46fde5bc5a672ffafc02578134f549f9
4
- data.tar.gz: d4ac11de2827303f91d54c5bade906c4dd78cebf0357eb68ea61d56af2b4a95c
3
+ metadata.gz: 438e4a4604f97d5b13ea22ffa01c5f066802461d42ed106608f95a44f728cf34
4
+ data.tar.gz: 25f0e76f594725353c04045609d911fd67db99b5bad4333713a96c0db078a573
5
5
  SHA512:
6
- metadata.gz: 010b3752d94b172520326e68d2893f83fe424198970c2c6a66023bff1420080b421b8d3f86825f776d61103f93d143386fe5bf86f045927c66071a9cf6266eb2
7
- data.tar.gz: 238f10e0646cf3bd6a927d10debe3fb15e42edd33c807c1dd82e3baf809c8adcfd3eb601d7a97d7f2a98770790dd272529df605e7a93c2c439cc600e7b72df25
6
+ metadata.gz: 1fd586c3fdeddc42e48accbc7d172a3df5c94b194f8fa7caf0e960fbd9f0c2d6aeba335ce308d824f7bfa1865fc9889855c38620ddbae5526a9ca1aad54847c3
7
+ data.tar.gz: b75134e0c88f6b42308ec555377b4f5d65daee020e03147d08df10a4eeaf62ad3ad9bfc9bdb7e7c3753100c275a41dbaca43c2a0d05aaea862cf69ee0418e4d1
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to `chrono_forge-dashboard` are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/).
4
4
 
5
+ ## [0.3.0] - 2026-07-13
6
+
7
+ ### Features
8
+
9
+ - Turbo-driven refresh + triage-first redesign + reap action ([#14](https://github.com/radioactive-labs/chrono_forge/issues/14))
10
+
5
11
  ## [0.2.0] - 2026-07-04
6
12
 
7
13
  ### Bug Fixes
data/README.md CHANGED
@@ -36,25 +36,39 @@ mount ChronoForge::Dashboard::Engine, at: "/chrono_forge"
36
36
 
37
37
  ## Screenshots
38
38
 
39
+ | Overview |
40
+ | --- |
41
+ | [![Overview](docs/screenshots/overview.png)](docs/screenshots/overview.png) |
42
+ | A fleet summary — total **processed / in-flight / blocked**, then one row per workflow class with a share-of-processed bar. Every count drills into the matching filtered list; the class name opens its per-class Analytics. Each card and the table load in their own turbo-frame. |
43
+
39
44
  | Workflow list | Analytics |
40
45
  | --- | --- |
41
46
  | [![Workflow list](docs/screenshots/workflows.png)](docs/screenshots/workflows.png) | [![Analytics](docs/screenshots/analytics.png)](docs/screenshots/analytics.png) |
42
- | Filter by state/class/key, keyset pagination, capped state counts. | Completion/failure rate, throughput, top errors, queue health — per class. |
47
+ | Filter by state/class/key, keyset pagination, capped state counts, per-row duration meter (rose when a run is stranded). | Completion/failure rate with a window trend, throughput bars carrying a per-day completion-rate pill (amber-flagged on a failure spike), top errors, queue health. |
43
48
 
44
- | Waiting | Repetitions |
49
+ | Waiting | Stranded |
45
50
  | --- | --- |
46
- | [![Waiting workflows](docs/screenshots/waiting.png)](docs/screenshots/waiting.png) | [![Repetitions](docs/screenshots/repetitions.png)](docs/screenshots/repetitions.png) |
47
- | Oldest unresolved `continue_if` (event) wait per class — the silent stall. | A `durably_repeat` step's per-iteration runs, with catch-up skips (per-tick tombstones or a "caught up ×N" summary) and lateness. |
51
+ | [![Waiting workflows](docs/screenshots/waiting.png)](docs/screenshots/waiting.png) | [![Stranded workflows](docs/screenshots/stranded.png)](docs/screenshots/stranded.png) |
52
+ | Leads with a banner for stalled `continue_if` (event) waits — the silent stall — with event/poll pills and per-row age meters. | Workflows stuck in `running` with a stale lock — the reaper's own set. Lock-age meters, the dead worker named, per-row **Reap** and a **Reap all** background sweep. |
48
53
 
49
- | Branches | Branch children |
54
+ | Repetitions | Branches |
50
55
  | --- | --- |
51
- | [![Branches panel](docs/screenshots/branches.png)](docs/screenshots/branches.png) | [![Branch children](docs/screenshots/branch-children.png)](docs/screenshots/branch-children.png) |
52
- | Fan-out branches with exact **spawned / pending / never-started** counts (recorded by the poller — the immutable spawned total is counted once and cached when the branch seals) plus blocked count and merge state, and in-flight merges showing **live throughput (children/s) and ETA** while draining. | One branch's children with a **live stats header** (throughput/ETA, spawned, pending, never-started, dropped-child recovery) — blocked-first triage, capped state filters, retry per child. |
56
+ | [![Repetitions](docs/screenshots/repetitions.png)](docs/screenshots/repetitions.png) | [![Branches panel](docs/screenshots/branches.png)](docs/screenshots/branches.png) |
57
+ | A `durably_repeat` step's per-iteration runs as status pills, with duration/lateness meters, attempts in words, and catch-up skips (per-tick tombstones or a "caught up ×N" summary). | Fan-out branches with exact **spawned / pending / never-started** counts (recorded by the poller — the immutable spawned total is cached when the branch seals) plus blocked count and merge state, and in-flight merges showing **live throughput (children/s) and ETA** while draining. |
58
+
59
+ | Branch children |
60
+ | --- |
61
+ | [![Branch children](docs/screenshots/branch-children.png)](docs/screenshots/branch-children.png) |
62
+ | One branch's children with a **live stats header** (throughput/ETA, spawned, pending, never-started, dropped-child recovery) — blocked-first triage, capped state filters, retry per child. |
53
63
 
54
- **Workflow detail** — step-replay timeline with errors inlined on the step that failed, periodic-task health, and arguments/context:
64
+ **Workflow detail** — step-replay timeline with errors inlined on the step that failed, periodic-task health, and arguments/context. Durations get a proportional meter (long steps stand out), attempts read in words per step kind, and a summary banner names where a blocked run stopped:
55
65
 
56
66
  [![Workflow detail](docs/screenshots/workflow-detail.png)](docs/screenshots/workflow-detail.png)
57
67
 
68
+ A workflow whose lock has gone stale is flagged **stranded** — its worker was hard-killed mid-pass, so nothing is driving the run. The banner offers **Reap**, which re-enqueues it to steal the stale lock and replay:
69
+
70
+ [![Stranded workflow with reap](docs/screenshots/workflow-reap.png)](docs/screenshots/workflow-reap.png)
71
+
58
72
  **Definition graph** — a per-run static DAG of the durable steps a workflow *will* run (parsed from `perform` with Prism, never executed), with the run's status painted on each node — done / in progress / failed / not-yet-reached — plus guarded edges, early-`return` exits, and unmapped steps. Tap a node or edge to inspect its step name / guard:
59
73
 
60
74
  [![Definition graph](docs/screenshots/definition-graph.png)](docs/screenshots/definition-graph.png)
@@ -119,25 +133,30 @@ end
119
133
 
120
134
  | Option | Default | Notes |
121
135
  | --- | --- | --- |
122
- | `polling_interval` | `15` | Seconds between auto-refreshes (the default). Most pages refresh in place (preserving filter text, focus, and scroll), so a draining fan-out's live throughput/ETA and counts update without a manual reload. The definition graph opts out: its interactive Cytoscape canvas can't survive an in-place swap, so it reloads fully instead and hides the refresh control. A viewer can override the interval with the nav "refresh" control (stored in a cookie). `0` disables. |
136
+ | `polling_interval` | `15` | Seconds between auto-refreshes (the default). Most pages refresh in place (preserving filter text, focus, and scroll), so a draining fan-out's live throughput/ETA and counts update without a manual reload. The definition graph opts out: its interactive Cytoscape canvas can't survive an in-place morph, so it reloads fully instead and hides the refresh control. A viewer can override the interval with the nav "refresh" control (stored in a cookie). `0` disables. |
123
137
  | `polling_interval_options` | `[0, 5, 10, 15, 30, 60, 300]` | Intervals (seconds; `0` = off) offered by the nav refresh control. |
124
138
  | `page_size` | `50` | Workflows per page on the index. |
125
139
  | `long_wait_threshold` | `3600` | Wait-state age in seconds above which a warning is shown. |
126
140
 
141
+ > **Stranded detection has no dashboard config.** A workflow is flagged as stranded (and listed on the **Stranded** page) purely by its lock going stale — `running` with `locked_at` older than the gem's own [`ChronoForge.config.reap_stale_after`](https://github.com/radioactive-labs/chrono_forge#readme) (default `3× max_duration`). That is exactly the criterion `ChronoForge::Workflow.reap_stalled` uses, so the dashboard flags precisely what the reaper reaps. Elapsed *runtime* is deliberately never a signal — a healthy workflow may legitimately run for months.
142
+
127
143
  ## Features
128
144
 
129
- - **Workflow list**: state badges, filter by state/job class/workflow key, stats header showing counts by state
130
- - **Workflow detail**: step replay timeline showing every `durably_execute`, `wait`, `continue_if`, and `durably_repeat` run; repetitions from `durably_repeat` appear nested under their coordination step
145
+ - **Overview**: a fleet summary total workflows **processed / in flight / blocked** across every class, then one row per workflow class sorted by volume with a share-of-processed bar. It answers "how much has run through, and by what": every count drills into the matching filtered list, and each class name opens its per-class Analytics. Blocked keeps the rose triage flag so a problem class still stands out. Each card and the table load in their own turbo-frame, so the page paints instantly and the heavy per-class scan never blocks the cheap counts
146
+ - **Workflow list**: state badges, filter by state/job class/workflow key, stats header showing counts by state, and a per-row duration meter (proportional across the visible page) that turns rose when a run is stranded (its lock has gone stale)
147
+ - **Workflow detail**: step replay timeline showing every `durably_execute`, `wait`, `continue_if`, and `durably_repeat` run; repetitions from `durably_repeat` appear nested under their coordination step. The timeline is ranked for scanning — a summary banner names where a blocked run stopped and why, durations get a proportional meter (long steps stand out), attempts read in words per step kind (a retried execution vs. a polled wait, hidden when there's nothing to say), and a step or run still going past its threshold turns amber/rose so a stuck flow is visible at a glance
131
148
  - **Definition graph**: a per-run static DAG of the durable steps a workflow *will* run — parsed from the `perform` method source with [Prism](https://github.com/ruby/prism) (never executed, never touches the DB) — with the run's live status overlaid on each node (done / in progress / pending / not-yet-reached / failed / unmapped, with per-node repeat counts and fan-out child tallies). Rendered client-side with [Cytoscape](https://js.cytoscape.org) (dagre layout): pan/zoom, and tap a node or edge to inspect its step name / guard. Reached from a "Definition graph" link on the workflow detail page. The analysis is deliberately *conservative*: `if`/`unless`/`case`/`continue_if` become guarded edges, an early `return` a dashed exit, `branch`/`spawn_each` a fan-out node, `durably_repeat` a loop node, and anything it can't resolve statically (a computed step name, a data-dependent loop, a durable call behind an unknown method) becomes a `dynamic` node with a warning rather than a confident-but-wrong graph. It also follows durable calls into helper methods in the same class, assignments, `&&`/`||`, and `case`/`in`, so a step one expression deep isn't missed. A workflow whose source can't be analyzed, or whose `perform` has no durable steps, degrades to a note, never an error.
132
149
  - **Context inspector**: JSON tree view of the workflow's persistent context
133
150
  - **Per-step error logs**: errors attributed to the step and attempt that raised them
134
151
  - **Periodic-task health**: summary of each `durably_repeat` task (last run, next run, missed executions)
135
- - **Wait-states view**: lists workflows in a wait state, with age flagged if above `long_wait_threshold`
136
- - **Recovery actions**: retry a stalled or failed workflow, force-unlock a stuck running workflow (with a duplicate-execution warning), bulk retry all failed workflows
152
+ - **Wait-states view**: lists workflows in a wait state, leading with a banner when a `continue_if` event wait (no timeout, never self-resumes) blows past `long_wait_threshold` — the silent stall. Event vs poll waits read as pills, and each row's age is a meter that escalates to rose (over-threshold event) or amber (long poll)
153
+ - **Stranded view**: workflows stuck in `:running` because their lock went stale (worker hard-killed mid-pass, so nothing is left to wake them) — the same set `Workflow.reap_stalled` re-enqueues. Per-row lock-age meters, the dead worker named, one-click **Reap**, and a **Reap all stranded** background sweep. Elapsed runtime is never the signal (a healthy workflow can run for months); only a stale lock is
154
+ - **Analytics**: completion/failure-rate cards with a window trend (newer half vs older half), a daily throughput chart where each day carries a completion-rate pill and the row is flagged when its failure rate spikes (so a bad day stands out from a busy one), top error classes, and per-class queue health
155
+ - **Recovery actions**: retry a stalled or failed workflow; **reap** a workflow stranded in `:running` by a hard-killed worker (re-enqueues it so the executor steals the stale lock and replays completed steps as no-ops — the single-workflow form of `Workflow.reap_stalled`, offered in the stranded banner); force-unlock a stuck running workflow (with a duplicate-execution warning); bulk retry all blocked workflows, or bulk reap all stranded ones (both fanned out by a background job, so the request stays fast with a large backlog)
137
156
 
138
157
  ## Frontend
139
158
 
140
- The dashboard is server-rendered. It serves one CSS file and one JS file directly from the engine. **The host needs no npm, no build step, and no asset-pipeline configuration** — the compiled stylesheet ships with the gem. The JS is dependency-free vanilla. CSP-compatible (no external hosts or inline handlers).
159
+ The dashboard is server-rendered and driven by [Turbo](https://turbo.hotwired.dev) (~94 KB, Turbo 8, vendored and served from the engine — no external host). **The host needs no npm, no build step, and no asset-pipeline configuration** — the compiled stylesheet ships with the gem. Turbo Drive handles all navigation and form submits as in-app visits (no full reloads, history-aware), so the interactive glue is a small delegated-listener vanilla JS file (`dashboard.js`). Action POSTs redirect with `303 See Other` so Turbo follows them; the auto-refresh uses Turbo's morph stream (see below). CSP-compatible (no external hosts or inline handlers).
141
160
 
142
161
  Styles are written with [Tailwind CSS](https://tailwindcss.com) and precompiled into the shipped `dashboard.css`. Contributors editing views or styles rebuild it with the standalone compiler (no Node required):
143
162
 
@@ -147,7 +166,9 @@ bundle exec rake tailwind:build
147
166
 
148
167
  Assets are cache-busted by a content digest, so a gem upgrade is picked up without a hard refresh.
149
168
 
150
- **One exception:** the **Definition graph** page loads [Cytoscape](https://js.cytoscape.org) + [dagre](https://github.com/dagrejs/dagre) to lay out the DAG client-side (~670 KB total, loaded only on that page). All three libraries plus the init module (`definition_graph.js`) are vendored into the gem and served from the engine — no external host / CDN, and no inline `<script>` (the init is an external file), so the page stays CSP-friendly. The graph is passed as JSON in a `data-` attribute (ERB-escaped in, `JSON.parse`d out) and Cytoscape renders labels onto a canvas, so the graph itself has no HTML-injection surface. Unlike the old Mermaid text grammar, guards containing `()`, `<`, and `&&` round-trip untouched. The one place author-controlled text (labels, step names, guards) reaches the DOM is the tap-to-inspect detail panel, which HTML-escapes it before insertion. Every other page remains dependency-free vanilla JS.
169
+ **One exception:** the **Definition graph** page loads [Cytoscape](https://js.cytoscape.org) + [dagre](https://github.com/dagrejs/dagre) to lay out the DAG client-side (~670 KB total, loaded only on that page). All three libraries plus the init module (`definition_graph.js`) are vendored into the gem and served from the engine — no external host / CDN, and no inline `<script>` (the init is an external file), so the page stays CSP-friendly. The graph is passed as JSON in a `data-` attribute (ERB-escaped in, `JSON.parse`d out) and Cytoscape renders labels onto a canvas, so the graph itself has no HTML-injection surface. Unlike the old Mermaid text grammar, guards containing `()`, `<`, and `&&` round-trip untouched. The one place author-controlled text (labels, step names, guards) reaches the DOM is the tap-to-inspect detail panel, which HTML-escapes it before insertion. Cytoscape and dagre load only on that page.
170
+
171
+ **Auto-refresh:** the polling refresh updates the list/stats region with a Turbo **morph** stream (`<turbo-stream action="update" method="morph">`) rather than replacing its `innerHTML`. idiomorph mutates the existing nodes in place, so a table's horizontal scroll and the filter inputs' value, caret, and focus all survive a tick with no manual bookkeeping. The filter text inputs carry `data-cf-poll-preserve`; a `turbo:before-morph-element` listener skips them so an in-progress query is never reset to the last-submitted value. The refresh is scoped to the list/stats region (gated on the `data-poll-region` attribute), so the header and flash toasts are left alone — and the definition graph opts out entirely, since morphing would wipe its live Cytoscape canvas.
151
172
 
152
173
  ## Development
153
174
 
@@ -1,2 +1,2 @@
1
1
  /*! tailwindcss v4.3.1 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--font-mono:ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-sky-50:oklch(97.7% .013 236.62);--color-sky-200:oklch(90.1% .058 230.902);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-700:oklch(50% .134 242.749);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-700:oklch(49.1% .27 292.581);--color-rose-50:oklch(96.9% .015 12.422);--color-rose-200:oklch(89.2% .058 10.001);--color-rose-300:oklch(81% .117 11.638);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-600:oklch(58.6% .253 17.585);--color-rose-700:oklch(51.4% .222 16.935);--color-rose-800:oklch(45.5% .188 13.697);--color-zinc-50:oklch(98.5% 0 0);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-white:#fff;--spacing:.25rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in:cubic-bezier(.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components{.cf-pill{align-items:center;gap:calc(var(--spacing) * 1.5);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);font-family:var(--font-mono);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-width:1px;border-radius:3.40282e38px;display:inline-flex}.cf-pill-idle{border-color:var(--color-zinc-200);background-color:var(--color-zinc-100);color:var(--color-zinc-600)}.cf-pill-running{border-color:var(--color-sky-200);background-color:var(--color-sky-50);color:var(--color-sky-700)}.cf-pill-completed{border-color:var(--color-emerald-200);background-color:var(--color-emerald-50);color:var(--color-emerald-700)}.cf-pill-failed{border-color:var(--color-rose-200);background-color:var(--color-rose-50);color:var(--color-rose-700)}.cf-pill-stalled{border-color:var(--color-amber-200);background-color:var(--color-amber-50);color:var(--color-amber-700)}.cf-pill-scheduled{border-color:var(--color-violet-200);background-color:var(--color-violet-50);color:var(--color-violet-700)}.cf-dot{height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);border-radius:3.40282e38px;display:inline-block}.cf-dot-idle{background-color:var(--color-zinc-400)}.cf-dot-running{background-color:var(--color-sky-500)}.cf-dot-completed{background-color:var(--color-emerald-500)}.cf-dot-failed{background-color:var(--color-rose-500)}.cf-dot-stalled{background-color:var(--color-amber-500)}.cf-dot-scheduled{background-color:var(--color-violet-500)}.cf-dot-pending{background-color:var(--color-zinc-300)}.cf-btn{align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-zinc-300);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-zinc-800);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:inline-flex}@media (hover:hover){.cf-btn:hover{background-color:var(--color-zinc-50)}}.cf-btn:focus{--tw-outline-style:none;outline-style:none}.cf-btn:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#18181b26}@supports (color:color-mix(in lab, red, red)){.cf-btn:focus-visible{--tw-ring-color:color-mix(in oklab, var(--color-zinc-900) 15%, transparent)}}.cf-btn-primary{border-color:var(--color-zinc-900);background-color:var(--color-zinc-900);color:var(--color-white)}@media (hover:hover){.cf-btn-primary:hover{background-color:var(--color-zinc-700)}}.cf-btn-danger{border-color:var(--color-rose-300);color:var(--color-rose-700)}@media (hover:hover){.cf-btn-danger:hover{background-color:var(--color-rose-50)}}.cf-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-zinc-200);background-color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.cf-bar{height:100%}.cf-bar-0{width:0%}.cf-bar-5{width:5%}.cf-bar-10{width:10%}.cf-bar-15{width:15%}.cf-bar-20{width:20%}.cf-bar-25{width:25%}.cf-bar-30{width:30%}.cf-bar-35{width:35%}.cf-bar-40{width:40%}.cf-bar-45{width:45%}.cf-bar-50{width:50%}.cf-bar-55{width:55%}.cf-bar-60{width:60%}.cf-bar-65{width:65%}.cf-bar-70{width:70%}.cf-bar-75{width:75%}.cf-bar-80{width:80%}.cf-bar-85{width:85%}.cf-bar-90{width:90%}.cf-bar-95{width:95%}.cf-bar-100{width:100%}}@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.top-1\.5{top:calc(var(--spacing) * 1.5)}.top-4{top:calc(var(--spacing) * 4)}.right-4{right:calc(var(--spacing) * 4)}.-left-\[5px\]{left:-5px}.z-50{z-index:50}.order-99{order:99}.order-1000{order:1000}.order-1001{order:1001}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.-ml-2{margin-left:calc(var(--spacing) * -2)}.ml-1{margin-left:var(--spacing)}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-\[72vh\]{height:72vh}.min-h-\[3rem\]{min-height:3rem}.min-h-screen{min-height:100vh}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-16{width:calc(var(--spacing) * 16)}.w-24{width:calc(var(--spacing) * 24)}.w-32{width:calc(var(--spacing) * 32)}.w-64{width:calc(var(--spacing) * 64)}.w-80{width:calc(var(--spacing) * 80)}.w-full{width:100%}.max-w-6xl{max-width:var(--container-6xl)}.max-w-\[16rem\]{max-width:16rem}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-full{max-width:100%}.min-w-0{min-width:0}.min-w-\[36rem\]{min-width:36rem}.min-w-\[40rem\]{min-width:40rem}.min-w-\[44rem\]{min-width:44rem}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-help{cursor:help}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-disc{list-style-type:disc}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-1{row-gap:var(--spacing)}.gap-y-1\.5{row-gap:calc(var(--spacing) * 1.5)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.gap-y-3{row-gap:calc(var(--spacing) * 3)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-amber-100>:not(:last-child)){border-color:var(--color-amber-100)}:where(.divide-zinc-100>:not(:last-child)){border-color:var(--color-zinc-100)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-amber-200{border-color:var(--color-amber-200)}.border-amber-300{border-color:var(--color-amber-300)}.border-emerald-200{border-color:var(--color-emerald-200)}.border-rose-200{border-color:var(--color-rose-200)}.border-zinc-100{border-color:var(--color-zinc-100)}.border-zinc-200{border-color:var(--color-zinc-200)}.border-zinc-300{border-color:var(--color-zinc-300)}.border-zinc-900{border-color:var(--color-zinc-900)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-50\/40{background-color:#fffbeb66}@supports (color:color-mix(in lab, red, red)){.bg-amber-50\/40{background-color:color-mix(in oklab, var(--color-amber-50) 40%, transparent)}}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-emerald-400{background-color:var(--color-emerald-400)}.bg-rose-50{background-color:var(--color-rose-50)}.bg-rose-300{background-color:var(--color-rose-300)}.bg-rose-400{background-color:var(--color-rose-400)}.bg-white{background-color:var(--color-white)}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-100{background-color:var(--color-zinc-100)}.bg-zinc-900{background-color:var(--color-zinc-900)}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-12{padding-block:calc(var(--spacing) * 12)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pr-4{padding-right:calc(var(--spacing) * 4)}.pb-5{padding-bottom:calc(var(--spacing) * 5)}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-6{padding-left:calc(var(--spacing) * 6)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[11px\]{font-size:11px}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.text-wrap{text-wrap:wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-amber-800{color:var(--color-amber-800)}.text-amber-900{color:var(--color-amber-900)}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-800{color:var(--color-emerald-800)}.text-rose-500{color:var(--color-rose-500)}.text-rose-600{color:var(--color-rose-600)}.text-rose-700{color:var(--color-rose-700)}.text-rose-800{color:var(--color-rose-800)}.text-white{color:var(--color-white)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-700{color:var(--color-zinc-700)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-900{color:var(--color-zinc-900)}.lowercase{text-transform:lowercase}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.overline{text-decoration-line:overline}.underline{text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-4{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-amber-200{--tw-ring-color:var(--color-amber-200)}.ring-white{--tw-ring-color:var(--color-white)}.invert{--tw-invert:invert(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.placeholder\:text-zinc-400::placeholder{color:var(--color-zinc-400)}.last\:border-l-transparent:last-child{border-left-color:#0000}.last\:pb-0:last-child{padding-bottom:0}@media (hover:hover){.hover\:bg-rose-50:hover{background-color:var(--color-rose-50)}.hover\:bg-zinc-50:hover{background-color:var(--color-zinc-50)}.hover\:text-zinc-900:hover{color:var(--color-zinc-900)}.hover\:underline:hover{text-decoration-line:underline}}@media (min-width:40rem){.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--font-mono:ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-sky-50:oklch(97.7% .013 236.62);--color-sky-200:oklch(90.1% .058 230.902);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-700:oklch(50% .134 242.749);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-700:oklch(49.1% .27 292.581);--color-rose-50:oklch(96.9% .015 12.422);--color-rose-200:oklch(89.2% .058 10.001);--color-rose-300:oklch(81% .117 11.638);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-600:oklch(58.6% .253 17.585);--color-rose-700:oklch(51.4% .222 16.935);--color-rose-800:oklch(45.5% .188 13.697);--color-zinc-50:oklch(98.5% 0 0);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-white:#fff;--spacing:.25rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in:cubic-bezier(.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components{.cf-pill{align-items:center;gap:calc(var(--spacing) * 1.5);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);font-family:var(--font-mono);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-width:1px;border-radius:3.40282e38px;display:inline-flex}.cf-pill-idle{border-color:var(--color-zinc-200);background-color:var(--color-zinc-100);color:var(--color-zinc-600)}.cf-pill-running{border-color:var(--color-sky-200);background-color:var(--color-sky-50);color:var(--color-sky-700)}.cf-pill-completed{border-color:var(--color-emerald-200);background-color:var(--color-emerald-50);color:var(--color-emerald-700)}.cf-pill-failed{border-color:var(--color-rose-200);background-color:var(--color-rose-50);color:var(--color-rose-700)}.cf-pill-stalled{border-color:var(--color-amber-200);background-color:var(--color-amber-50);color:var(--color-amber-700)}.cf-pill-scheduled{border-color:var(--color-violet-200);background-color:var(--color-violet-50);color:var(--color-violet-700)}.cf-dot{height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);border-radius:3.40282e38px;display:inline-block}.cf-dot-idle{background-color:var(--color-zinc-400)}.cf-dot-running{background-color:var(--color-sky-500)}.cf-dot-completed{background-color:var(--color-emerald-500)}.cf-dot-failed{background-color:var(--color-rose-500)}.cf-dot-stalled{background-color:var(--color-amber-500)}.cf-dot-scheduled{background-color:var(--color-violet-500)}.cf-dot-pending{background-color:var(--color-zinc-300)}.cf-btn{align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-zinc-300);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-zinc-800);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:inline-flex}@media (hover:hover){.cf-btn:hover{background-color:var(--color-zinc-50)}}.cf-btn:focus{--tw-outline-style:none;outline-style:none}.cf-btn:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#18181b26}@supports (color:color-mix(in lab, red, red)){.cf-btn:focus-visible{--tw-ring-color:color-mix(in oklab, var(--color-zinc-900) 15%, transparent)}}.cf-btn-primary{border-color:var(--color-zinc-900);background-color:var(--color-zinc-900);color:var(--color-white)}@media (hover:hover){.cf-btn-primary:hover{background-color:var(--color-zinc-700)}}.cf-btn-danger{border-color:var(--color-rose-300);color:var(--color-rose-700)}@media (hover:hover){.cf-btn-danger:hover{background-color:var(--color-rose-50)}}.cf-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-zinc-200);background-color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.cf-bar{height:100%}.cf-bar-0{width:0%}.cf-bar-5{width:5%}.cf-bar-10{width:10%}.cf-bar-15{width:15%}.cf-bar-20{width:20%}.cf-bar-25{width:25%}.cf-bar-30{width:30%}.cf-bar-35{width:35%}.cf-bar-40{width:40%}.cf-bar-45{width:45%}.cf-bar-50{width:50%}.cf-bar-55{width:55%}.cf-bar-60{width:60%}.cf-bar-65{width:65%}.cf-bar-70{width:70%}.cf-bar-75{width:75%}.cf-bar-80{width:80%}.cf-bar-85{width:85%}.cf-bar-90{width:90%}.cf-bar-95{width:95%}.cf-bar-100{width:100%}}@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.top-1\.5{top:calc(var(--spacing) * 1.5)}.top-4{top:calc(var(--spacing) * 4)}.right-4{right:calc(var(--spacing) * 4)}.-left-\[5px\]{left:-5px}.z-50{z-index:50}.order-99{order:99}.order-1000{order:1000}.order-1001{order:1001}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.-mx-2{margin-inline:calc(var(--spacing) * -2)}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-auto{margin-top:auto}.mt-px{margin-top:1px}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.-ml-2\.5{margin-left:calc(var(--spacing) * -2.5)}.ml-1{margin-left:var(--spacing)}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-7{height:calc(var(--spacing) * 7)}.h-\[72vh\]{height:72vh}.max-h-64{max-height:calc(var(--spacing) * 64)}.min-h-\[3rem\]{min-height:3rem}.min-h-screen{min-height:100vh}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-10{width:calc(var(--spacing) * 10)}.w-11{width:calc(var(--spacing) * 11)}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-28{width:calc(var(--spacing) * 28)}.w-40{width:calc(var(--spacing) * 40)}.w-64{width:calc(var(--spacing) * 64)}.w-80{width:calc(var(--spacing) * 80)}.w-full{width:100%}.max-w-6xl{max-width:var(--container-6xl)}.max-w-\[16rem\]{max-width:16rem}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-full{max-width:100%}.min-w-0{min-width:0}.min-w-\[36rem\]{min-width:36rem}.min-w-\[40rem\]{min-width:40rem}.min-w-\[44rem\]{min-width:44rem}.min-w-\[46rem\]{min-width:46rem}.min-w-\[48rem\]{min-width:48rem}.flex-1{flex:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-pulse{animation:var(--animate-pulse)}.cursor-help{cursor:help}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-disc{list-style-type:disc}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-1{row-gap:var(--spacing)}.gap-y-1\.5{row-gap:calc(var(--spacing) * 1.5)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.gap-y-3{row-gap:calc(var(--spacing) * 3)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-amber-100>:not(:last-child)){border-color:var(--color-amber-100)}:where(.divide-zinc-100>:not(:last-child)){border-color:var(--color-zinc-100)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-amber-200{border-color:var(--color-amber-200)}.border-amber-300{border-color:var(--color-amber-300)}.border-emerald-200{border-color:var(--color-emerald-200)}.border-rose-200{border-color:var(--color-rose-200)}.border-zinc-100{border-color:var(--color-zinc-100)}.border-zinc-200{border-color:var(--color-zinc-200)}.border-zinc-300{border-color:var(--color-zinc-300)}.border-zinc-900{border-color:var(--color-zinc-900)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-50\/40{background-color:#fffbeb66}@supports (color:color-mix(in lab, red, red)){.bg-amber-50\/40{background-color:color-mix(in oklab, var(--color-amber-50) 40%, transparent)}}.bg-amber-400{background-color:var(--color-amber-400)}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-emerald-400{background-color:var(--color-emerald-400)}.bg-rose-50{background-color:var(--color-rose-50)}.bg-rose-50\/40{background-color:#fff1f266}@supports (color:color-mix(in lab, red, red)){.bg-rose-50\/40{background-color:color-mix(in oklab, var(--color-rose-50) 40%, transparent)}}.bg-rose-300{background-color:var(--color-rose-300)}.bg-rose-400{background-color:var(--color-rose-400)}.bg-white{background-color:var(--color-white)}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-100{background-color:var(--color-zinc-100)}.bg-zinc-200{background-color:var(--color-zinc-200)}.bg-zinc-400{background-color:var(--color-zinc-400)}.bg-zinc-900{background-color:var(--color-zinc-900)}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-12{padding-block:calc(var(--spacing) * 12)}.pt-1{padding-top:var(--spacing)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pr-4{padding-right:calc(var(--spacing) * 4)}.pb-5{padding-bottom:calc(var(--spacing) * 5)}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-6{padding-left:calc(var(--spacing) * 6)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.text-wrap{text-wrap:wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-amber-800{color:var(--color-amber-800)}.text-amber-900{color:var(--color-amber-900)}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-800{color:var(--color-emerald-800)}.text-rose-500{color:var(--color-rose-500)}.text-rose-600{color:var(--color-rose-600)}.text-rose-700{color:var(--color-rose-700)}.text-rose-800{color:var(--color-rose-800)}.text-white{color:var(--color-white)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-700{color:var(--color-zinc-700)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-900{color:var(--color-zinc-900)}.lowercase{text-transform:lowercase}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.overline{text-decoration-line:overline}.underline{text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-4{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-white{--tw-ring-color:var(--color-white)}.invert{--tw-invert:invert(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.placeholder\:text-zinc-400::placeholder{color:var(--color-zinc-400)}.last\:border-l-transparent:last-child{border-left-color:#0000}.last\:pb-0:last-child{padding-bottom:0}@media (hover:hover){.hover\:border-zinc-300:hover{border-color:var(--color-zinc-300)}.hover\:bg-rose-50:hover{background-color:var(--color-rose-50)}.hover\:bg-zinc-50:hover{background-color:var(--color-zinc-50)}.hover\:text-zinc-900:hover{color:var(--color-zinc-900)}.hover\:underline:hover{text-decoration-line:underline}}@media (min-width:40rem){.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes pulse{50%{opacity:.5}}
@@ -1,21 +1,27 @@
1
1
  (function () {
2
2
  "use strict";
3
3
 
4
- // Interactive behaviors are delegated to `document` and attached ONCE. Per-row
5
- // listeners are lost whenever the DOM is replaced the back/forward cache,
6
- // a Hotwire/Turbo body swap, or the polling innerHTML refresh — which is why
7
- // row-click stopped working after navigating back. A single document-level
8
- // listener (document is never swapped) keeps matching current and future rows.
4
+ // Turbo Drive owns navigation and form submits, so the page is never fully
5
+ // reloaded on a normal click. Two consequences shape this file:
6
+ //
7
+ // 1. Interactive behaviors are delegated to `document` and attached ONCE.
8
+ // Per-element listeners would be lost on every body swap; `document` is
9
+ // never swapped, so one delegated listener keeps matching current and
10
+ // future rows.
11
+ // 2. Per-page setup (the poll timer, flash auto-dismiss) must re-run after
12
+ // every visit, so it lives in a `turbo:load` handler rather than at the
13
+ // top level — top-level code runs only when the script first executes.
9
14
  if (!window.__chronoForgeDashboard) {
10
15
  window.__chronoForgeDashboard = true;
11
16
 
12
17
  document.addEventListener("click", function (e) {
13
18
  // Timestamp display toggle (relative vs absolute), persisted in a cookie.
19
+ // Re-visit the current URL so the server re-renders every timestamp.
14
20
  var timeSet = e.target.closest("[data-time-set]");
15
21
  if (timeSet) {
16
22
  document.cookie = "cf_time_format=" + timeSet.getAttribute("data-time-set") +
17
23
  ";path=/;max-age=31536000;samesite=lax";
18
- window.location.reload();
24
+ Turbo.visit(window.location.href, { action: "replace" });
19
25
  return;
20
26
  }
21
27
  // Collapsible context tree
@@ -25,102 +31,102 @@
25
31
  if (li) li.classList.toggle("cf-collapsed");
26
32
  return;
27
33
  }
28
- // Whole-row navigation, except when the click landed on an interactive child.
34
+ // Whole-row navigation, except when the click landed on an interactive
35
+ // child. Turbo.visit keeps it an in-app visit (history + no full reload).
29
36
  var row = e.target.closest("tr[data-href]");
30
37
  if (row && !e.target.closest("a, button, input, form, summary")) {
31
- window.location = row.getAttribute("data-href");
38
+ Turbo.visit(row.getAttribute("data-href"));
32
39
  }
33
40
  });
34
41
 
35
- // Auto-submit a filter control (e.g. the state select) on change.
36
42
  document.addEventListener("change", function (e) {
37
- // Auto-refresh interval control: persist and reload so the server re-renders
38
- // the body's data-poll-interval.
43
+ // Auto-refresh interval control: persist and re-visit so the server
44
+ // re-renders the body's data-poll-interval (picked up in setupPage).
39
45
  var poll = e.target.closest("[data-poll-select]");
40
46
  if (poll) {
41
47
  document.cookie = "cf_poll_interval=" + poll.value + ";path=/;max-age=31536000;samesite=lax";
42
- window.location.reload();
48
+ Turbo.visit(window.location.href, { action: "replace" });
43
49
  return;
44
50
  }
45
51
  var el = e.target.closest("[data-autosubmit]");
46
- if (el && el.form) el.form.requestSubmit();
52
+ if (el && el.form) {
53
+ // A checkbox paired with a same-name hidden field (the "unchecked
54
+ // submits 0" trick) would otherwise put `name=0&name=1` in the query
55
+ // string when checked. Disable the hidden twin while checked so the URL
56
+ // carries a single value.
57
+ if (el.type === "checkbox") {
58
+ el.form.querySelectorAll('input[type="hidden"][name="' + el.name + '"]').forEach(function (h) {
59
+ h.disabled = el.checked;
60
+ });
61
+ }
62
+ el.form.requestSubmit();
63
+ }
47
64
  });
48
65
 
49
- // Confirm destructive actions: any form with data-confirm.
66
+ // Confirm destructive actions: any form with data-confirm. preventDefault
67
+ // stops both the native submit and Turbo's.
50
68
  document.addEventListener("submit", function (e) {
51
69
  var form = e.target.closest("form[data-confirm]");
52
70
  if (form && !window.confirm(form.getAttribute("data-confirm"))) e.preventDefault();
53
71
  });
54
- }
55
72
 
56
- // Auto-dismiss floating flash toasts after a few seconds (fade, then remove).
57
- document.querySelectorAll("[data-flash]").forEach(function (el, i) {
58
- setTimeout(function () {
59
- el.classList.add("opacity-0");
60
- setTimeout(function () { el.remove(); }, 300);
61
- }, 4000 + i * 150);
62
- });
73
+ // Leave the filter inputs untouched during the polling morph refresh. Skipping
74
+ // the element (rather than letting idiomorph reconcile it) keeps it in place —
75
+ // so a value the user is typing, its caret, and focus all survive a tick
76
+ // instead of being reset to the last-submitted server value.
77
+ document.addEventListener("turbo:before-morph-element", function (e) {
78
+ if (e.target.hasAttribute && e.target.hasAttribute("data-cf-poll-preserve")) e.preventDefault();
79
+ });
63
80
 
64
- // Polling refresh of the list/stats region. Keep a single timer and re-resolve
65
- // the region each tick so it survives a swapped body.
66
- if (window.__chronoForgePoll) clearInterval(window.__chronoForgePoll);
67
- var body = document.body, interval = parseInt(body.getAttribute("data-poll-interval") || "0", 10) * 1000;
68
- if (interval > 0 && document.querySelector("[data-poll-region]") && !body.hasAttribute("data-poll-paused")) {
69
- window.__chronoForgePoll = setInterval(function () {
70
- var region = document.querySelector("[data-poll-region]");
71
- if (!region) return;
72
- fetch(window.location.href, { headers: { "X-Requested-With": "XMLHttpRequest" } })
73
- .then(function (r) { return r.text(); })
74
- .then(function (html) {
75
- var doc = new DOMParser().parseFromString(html, "text/html");
76
- var fresh = doc.querySelector("[data-poll-region]");
77
- if (!fresh) return;
78
- // Preserve horizontal scroll of any scroll containers across the swap,
79
- // so polling doesn't yank a table back while it's being scrolled.
80
- var scrolls = Array.prototype.map.call(
81
- region.querySelectorAll(".overflow-x-auto"), function (el) { return el.scrollLeft; });
81
+ // Runs on the initial load and after every Turbo visit render.
82
+ document.addEventListener("turbo:load", setupPage);
83
+ }
82
84
 
83
- // Preserve in-progress text in the filter boxes. The swap replaces the
84
- // inputs with server-rendered ones reflecting only the LAST SUBMITTED
85
- // query, so without this every poll tick wipes whatever is being typed
86
- // and drops focus. Capture each named text field's value, plus the caret
87
- // of the focused one, and reapply them after the swap.
88
- var isTextEntry = function (el) {
89
- if (!el) return false;
90
- if (el.tagName === "TEXTAREA") return true;
91
- if (el.tagName !== "INPUT") return false;
92
- return /^(text|search|email|url|tel|number|password)$/i.test(el.type || "text");
93
- };
94
- var values = {};
95
- region.querySelectorAll("input, textarea").forEach(function (el) {
96
- if (el.name && isTextEntry(el)) values[el.name] = el.value;
97
- });
98
- var active = document.activeElement;
99
- var activeName = (active && region.contains(active) && isTextEntry(active) && active.name) ? active.name : null;
100
- var caretStart = null, caretEnd = null;
101
- if (activeName) {
102
- try { caretStart = active.selectionStart; caretEnd = active.selectionEnd; } catch (e) {}
103
- }
85
+ // Per-page setup: (re)arm the flash toasts and the polling timer for whatever
86
+ // page Turbo just rendered.
87
+ function setupPage() {
88
+ // Auto-dismiss floating flash toasts after a few seconds (fade, then remove).
89
+ document.querySelectorAll("[data-flash]").forEach(function (el, i) {
90
+ setTimeout(function () {
91
+ el.classList.add("opacity-0");
92
+ setTimeout(function () { el.remove(); }, 300);
93
+ }, 4000 + i * 150);
94
+ });
104
95
 
105
- region.innerHTML = fresh.innerHTML;
96
+ // Polling refresh of the list/stats region. Keep a single timer; clear any
97
+ // previous one so a navigation doesn't leave two running. Gate on the
98
+ // [data-poll-region] attribute, not the #cf-poll-region id: the id is always
99
+ // on <main> (it's the morph target), but the attribute is present only on
100
+ // pages that opt into polling — the definition graph opts out so its live
101
+ // Cytoscape canvas isn't morphed away.
102
+ if (window.__chronoForgePoll) clearInterval(window.__chronoForgePoll);
103
+ var body = document.body, interval = parseInt(body.getAttribute("data-poll-interval") || "0", 10) * 1000;
104
+ if (interval > 0 && document.querySelector("[data-poll-region]") && !body.hasAttribute("data-poll-paused")) {
105
+ window.__chronoForgePoll = setInterval(pollTick, interval);
106
+ }
107
+ }
106
108
 
107
- region.querySelectorAll(".overflow-x-auto").forEach(function (el, i) {
108
- if (scrolls[i]) el.scrollLeft = scrolls[i];
109
- });
110
- // Reapply preserved field values, then restore focus + caret.
111
- region.querySelectorAll("input, textarea").forEach(function (el) {
112
- if (el.name && isTextEntry(el) && Object.prototype.hasOwnProperty.call(values, el.name)) {
113
- el.value = values[el.name];
114
- }
115
- });
116
- if (activeName) {
117
- var refocus = region.querySelector("[name='" + activeName + "']");
118
- if (refocus) {
119
- refocus.focus();
120
- try { refocus.setSelectionRange(caretStart, caretEnd); } catch (e) {}
121
- }
122
- }
123
- }).catch(function () {});
124
- }, interval);
109
+ // One polling tick: fetch the current page and morph the list/stats region.
110
+ //
111
+ // The refresh is a Turbo morph stream, not an innerHTML swap: idiomorph mutates
112
+ // the existing nodes in place instead of recreating them, so horizontal scroll,
113
+ // focus, caret, and in-progress filter text all survive the update for free —
114
+ // no manual preservation. `update` with method="morph" morphs the region's
115
+ // contents while leaving the <main id> wrapper (and its data-poll-region hook)
116
+ // untouched. Scoped to the region (not a whole-page refresh) so the header and
117
+ // flash toasts are left alone.
118
+ function pollTick() {
119
+ if (!document.querySelector("[data-poll-region]")) return;
120
+ fetch(window.location.href, { headers: { "X-Requested-With": "XMLHttpRequest" } })
121
+ .then(function (r) { return r.text(); })
122
+ .then(function (html) {
123
+ var doc = new DOMParser().parseFromString(html, "text/html");
124
+ var fresh = doc.getElementById("cf-poll-region");
125
+ if (!fresh || !window.Turbo) return;
126
+ Turbo.renderStreamMessage(
127
+ '<turbo-stream action="update" method="morph" target="cf-poll-region">' +
128
+ "<template>" + fresh.innerHTML + "</template></turbo-stream>"
129
+ );
130
+ }).catch(function () {});
125
131
  }
126
132
  })();