diff --git a/README.md b/README.md index 71d8a11..f802e56 100644 --- a/README.md +++ b/README.md @@ -251,7 +251,7 @@ actually returns. - The `codex` CLI installed and available on `PATH`. - A current ChatGPT login in Codex. - A modern terminal with ANSI color and Unicode support. -- On macOS, version 13 (Ventura) or later for builds using Go 1.27.1. +- On macOS, version 13 (Ventura) or later for builds using Go 1.27. The v0.19.0 release was built with Go 1.26.6; this newer minimum applies to subsequent Go 1.27-based builds. @@ -586,7 +586,8 @@ codexometer --codex /path/to/codex | `r` | Refresh account history in Usage; otherwise refresh quota data | | `v` | Cycle views within Quota or Usage | | `s` | Reset the Sessions baseline, or open Benchmark Scope | -| `h` | Reset all Sessions rows to graph-only / split detail-and-graph, closing full detail and clearing individual row choices | +| `g` | Cycle available trend periods in Quota → Pace or Zone | +| `h` | Toggle the trace in Quota → Pace or Zone; in Sessions, reset all rows to graph-only / split detail-and-graph, closing full detail and clearing individual row choices | | `Left` / `Right` | In Sessions, less / more detail for the selected session: graph ↔ split ↔ wide ↔ full screen; stops at either end | | `b` | Run the selected benchmark scope (Benchmark view only) | | `a` | Arm, then confirm, Run All (Benchmark view only) | @@ -608,7 +609,7 @@ codexometer --codex /path/to/codex The responsive top rail below the account status selects Quota, Sessions, Usage, or Benchmark by mouse, `Tab`, or `Shift+Tab`. Quota adds a second rail for Bars, -Consumption Pace, Pie, Fuel Tank, and Resets; select these with the mouse or cycle them +Pace, Zone, Pie, Fuel Tank, and Resets; select these with the mouse or cycle them with `v`. Codexometer remembers the selected Quota view when you leave and return. Both rails condense automatically as the terminal narrows. The footer presents the remaining actions as clickable buttons, including View @@ -868,24 +869,46 @@ The default remains the original green hacker-terminal presentation. ## Views and quota presentations The top-level tabs are **Quota**, **Sessions**, **Usage**, and **Benchmark**. Within Quota, -choose one of these five views with its sub-tab or `v`: +choose one of these six views with its sub-tab or `v`: 1. **Bars** — chunky quota bars, with one full-width rate-limit window per row. -2. **Consumption Pace** — a signed horizontal scale comparing elapsed window - time with quota consumed. Positive headroom means consumption is behind - elapsed time; a negative deficit means quota is being used too quickly. A - clearly labelled linear projection reports `SAFE THROUGH RESET` or estimates - how long remains until exhaustion and how early that is relative to reset. -3. **Pie** — clockwise-filled circles rendered on a 2×4 sub-cell Braille canvas +2. **Pace** — elapsed-window time on X, consumed percentage minus elapsed + percentage on Y (−100 to +100 percentage points). Above the horizontal safe + line means consumption is ahead of time; below it means headroom. Dark colour + bands fade from amber at safety toward red above and green below. +3. **Zone** — elapsed-window time on X and 0–100% consumption on Y, with a + diagonal steady-consumption line and a red/amber/green background. Above + the diagonal means over pace; below it means headroom. +4. **Pie** — clockwise-filled circles rendered on a 2×4 sub-cell Braille canvas for clean curves at any size. -4. **Fuel Tank** — a reverse gauge whose bright segment shows remaining range +5. **Fuel Tank** — a reverse gauge whose bright segment shows remaining range and whose dark segment shows consumed capacity, labelled from Empty to Full; one full-width tank appears per row. Its reset-cycle comparison also drains backward and aligns exactly with the tank's first and last inner cells. -5. **Resets** — available reset credits, grant dates, expiry dates and backend +6. **Resets** — available reset credits, grant dates, expiry dates and backend descriptions. Expiring credits appear first, non-expiring credits last. Scroll with Up/Down or Page Up/Page Down when necessary. +Pace and Zone use terminal-cell backgrounds and densely plotted, continuous +Braille strokes for traces, trends and the safety reference. The +current dot is bright; the observed trace is pale for contrast against the dark +background. Click **Trend** (`g`) to cycle **Window Start**, **Last 30 Minutes**, +**Last Hour**, **Last 24 Hours**, or **Off**; recent intervals are skipped until +at least one quota window has enough uninterrupted observations. A window +without that coverage reports `TREND UNAVAILABLE` rather than using another +interval. **Window Start** is the default and uses the current dot and origin, +so it needs no earlier observations. Click **Trace** (`h`) to hide/show the +path; it is on by default. The continuous trend starts at the selected interval's +beginning, passes through the dot and continues to the plot boundary. It is +green if projected consumption at reset is at most 100%, dark red otherwise. +These are coarse linear estimates, not OpenAI forecasts. History is bounded, +in memory only, collected at each quota refresh even on other tabs, and cleared +when the account or quota cycle changes. Failed refreshes leave gaps rather +than connecting an invented path. Graphs share rows when width permits, add +rows for extra windows, and fall back to compact readouts when too small to plot. +Unknown window timing cannot be graphed. The browser offers the same variants +and trend choices with smoother SVG lines, directional arrows and backgrounds. + The reset shortcut opens Resets and asks for confirmation before redeeming. When individual credit details are supplied, Codexometer sends the ID of the soonest-expiring available quota-reset credit it can identify. That ID remains @@ -1137,7 +1160,7 @@ The other top-level views are: The layout responds to both terminal dimensions and the number of rate limits returned by Codex. Header, status, errors, footer, and meter grid divide the -available rectangle proportionally. Bars, Consumption Pace, and Fuel Tank flow +available rectangle proportionally. Bars and Fuel Tank flow one meter per row. Codexometer does not hardcode the currently returned window set: it renders every primary and secondary window from every limit bucket, including a 300-minute window as `5 HOURS`, plus an effective monthly credit @@ -1148,20 +1171,17 @@ Meter rows always use identical heights; indivisible spare rows become quiet space above the footer instead of stretching one quota block more than another. Pie uses at least two columns when multiple limits exist, adding rows when that preserves more radial detail and adding columns when the terminal is wide -enough. Consumption Pace calculates `elapsed window % - quota used %`, placing -under-budget consumption on the positive side and over-budget consumption on -the negative side. Its linear projection assumes the average burn observed -since the calculated cycle start continues unchanged: remaining time is -`elapsed time × (1 - U) / U`. It reports safe when the resulting exhaustion -time falls at or after reset, and hides the projection when timing is -insufficient. This is a trend estimate, not a backend forecast. Every Quota view -also shows a `RESET CYCLE` comparison: +enough. Pace and Zone use as many graph columns as remain readable and scale +both axes to each card's remaining space. Bars, Pie and Fuel Tank +also show a `RESET CYCLE` comparison: its label and countdown occupy one line, while its progress bar occupies a separate line with the same width and active colour as the main visualization. Its percentage is elapsed time from the calculated window start (`reset - duration`) to the next reset. When Codex supplies a monthly reset but not a cycle start, the card says `CYCLE START UNAVAILABLE`, shows the known -countdown, and leaves the comparison bar unfilled. Every visualization +countdown, and leaves the comparison bar unfilled. Pace and Zone instead +show elapsed time on their X axes and retain a separate reset countdown. +Every visualization receives its card's remaining width and height, and resizing the terminal immediately reflows and rescales it. The underlying values and reset information never change with presentation. @@ -2541,33 +2561,65 @@ codexometer --web --web-port 8765 5. Press Ctrl+C in the launching terminal to stop the server and invalidate access. This preview is **read-only by default and UK-English-only**, not feature parity with -the terminal. It includes Bars, Consumption Pace, Consumption Zone, Pie and Fuel Tank quota +the terminal. It includes Bars, Pace, Zone, Pie and Fuel Tank quota presentations; reset inventory with disclosed expiry information; local session telemetry with expandable/full-page context and synchronised activity graphs; and account history with a daily heatmap, monthly/cumulative bars, a 6/12-month selector and an accessible data table. Five browser themes are available. `CODEXOMETER_LANG` continues to configure the terminal, not this preview. -**Consumption Zone** plots each window's elapsed quota period horizontally and +**Zone** plots each window's elapsed quota period horizontally and 0–100% consumption vertically. The bottom-left to top-right diagonal represents steady consumption: above it means usage is outpacing elapsed time, below it means headroom. The background fades from red at the top left through amber to green at the bottom right, and a high-contrast dot marks the current position (last known -consumption against the current elapsed time). A white trail connects successful -quota observations, with an open circle marking the first observation. This is -the quota window's observed path, **not usage attributed to an individual Codex -session**, a reconstruction of earlier history, or a prediction of future use. -Windows without a known duration and reset date cannot be plotted. -An expandable, keyboard-accessible **OBSERVATION TABLE** supplies the same -retained history as text: observation time, elapsed period, consumed percentage -and breaks between segments. It updates alongside the plotted trail. +consumption against the current elapsed time). **TRACE PATH** is enabled by +default and can be unchecked; it draws a near-black line through successful +quota observations, with a subtly outlined circle marking the first observation. +This is the quota window's observed path, +**not usage attributed to an individual Codex session**, a reconstruction of +earlier history, or a prediction of future use. +Windows without a known duration and reset date cannot be plotted. A separate, +expandable, keyboard-accessible **OBSERVATION DATA** disclosure +supplies the same retained history as text: observation time, elapsed period, +consumed percentage and breaks between segments. It updates alongside the +plotted trail. + +**Pace** is a separate quota view using the same graph and controls. +It replaces the former horizontal Consumption Pace gauge in both interfaces, +keeps the elapsed-period X axis and shows +consumed percentage minus elapsed-period percentage on Y, in percentage points +from −100 to +100. Zero is the horizontal safe line: positive values mean usage +is ahead of time, negative values mean headroom. Its background is uniform +across each row, fading from amber at safety through orange to red above, and +from amber to green below. The dot, observed path and projection all use the +same transformed coordinates. + +The **TREND** selector defaults to **FROM WINDOW START** and can be switched +off. When enough uninterrupted observations exist, it can instead fit recent +velocity over the last 30 minutes, last hour or last 24 hours. +**FROM WINDOW START** uses the whole-window +average: current consumption divided by elapsed quota-period percentage. Its +line starts at the origin, passes through the current dot and extends to the +graph edge; it works immediately without retained history once some time has +elapsed in a known quota window. +The dotted projection starts at the beginning of the selected trend period, +passes through the current dot and continues to the graph edge. It uses +periodic arrows to show direction; its caption estimates consumption at reset or +warns when exhaustion is projected first. Both presentations colour it green +when projected consumption at reset is at most 100%, dark red otherwise. +In Pace, the selected trend period controls the slope relative to safety: +slower-than-steady consumption slopes downward, faster consumption upward. +Recent periods without enough observed +history remain visible but disabled. This is a linear extrapolation of coarse +whole-percentage observations, not an OpenAI forecast. Trails are held only in the web server's memory, survive browser reloads and tab changes, and restart when the server stops. A changed account, reset date or window duration, a lower consumption reading, a backwards clock or a removed window starts a fresh trail. Failed reads add no points and leave a break before -the next observation. Each window retains at most 720 points: the original start -plus the latest 719; the omitted interval is shown as a gap. Readings between +the next observation. Each window retains at most 1,500 points: the original start +plus the latest 1,499; the omitted interval is shown as a gap. Readings between polls are not known. Account-change isolation depends on the account identity available from the existing reader. @@ -2623,7 +2675,7 @@ session IDs. Navigation and layout controls never send actions to Codex. The browser uses a compact dashboard layout: quota plots share the available width and height below the tabs. Pie charts retain their circular shape, while -Consumption Zone scales each axis independently and keeps text legible. On short +Zone and Pace scale each axis independently and keep text legible. On short windows or with many quota windows, content scrolls without hiding the footer controls or shrinking plots below a readable minimum. @@ -3175,7 +3227,7 @@ virtual clock rather than depending on millisecond wall-clock scheduling. Codexometer uses: -- Go 1.27.1+ +- Go 1.27.2+ - Bubble Tea v2 for the terminal event loop and declarative terminal modes - Lip Gloss v2 for adaptive ANSI styling and layout - Starlark for deterministic, hermetic benchmark-code evaluation diff --git a/go.mod b/go.mod index df6575e..aefaa07 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/merefield/codexometer -go 1.27.1 +go 1.27.2 require ( charm.land/bubbles/v2 v2.2.1 diff --git a/internal/i18n/locales/da.json b/internal/i18n/locales/da.json index 7e13342..2b668e9 100644 --- a/internal/i18n/locales/da.json +++ b/internal/i18n/locales/da.json @@ -1,4 +1,25 @@ { + "PACE": "TEMPO", + "ZONE": "ZONE", + "WINDOW START": "VINDUESSTART", + "LAST 30 MINUTES": "SENESTE 30 MINUTTER", + "LAST HOUR": "SENESTE TIME", + "LAST 24 HOURS": "SENESTE 24 TIMER", + "OFF": "FRA", + "ON": "TIL", + "GRAPH DATA UNAVAILABLE": "GRAFDATA MANGLER", + "CONSUMPTION": "FORBRUG", + "DISTANCE FROM SAFETY (PP)": "AFSTAND TIL SIKKERHED (PP)", + "TIME ELAPSED": "FORLØBET TID", + "[ (G)TREND // %s ]": "[ (G)TENDENS // %s ]", + "[ (H)TRACE // %s ]": "[ (H)SPOR // %s ]", + "%+.1f PP FROM SAFETY": "%+.1f PP FRA SIKKERHED", + "TREND // %.1f%% AT RESET": "TENDENS // %.1f%% VED NULSTILLING", + "TREND // %+.1f PP AT RESET": "TENDENS // %+.1f PP VED NULSTILLING", + "TREND UNAVAILABLE": "TENDENS MANGLER", + "%.0f%% USED // %.1f%% TIME": "%.0f%% BRUGT // %.1f%% TID", + "╭ PACE ╮": "╭ TEMPO ╮", + "╭ ZONE ╮": "╭ ZONE ╮", "PREVIOUS TURN": "FORRIGE TUR", "LIVE SESSION": "LIVESESSION", " // %d OF %d": " // %d AF %d", diff --git a/internal/i18n/locales/de.json b/internal/i18n/locales/de.json index 6a884d1..9b89cb9 100644 --- a/internal/i18n/locales/de.json +++ b/internal/i18n/locales/de.json @@ -1,4 +1,25 @@ { + "PACE": "TEMPO", + "ZONE": "ZONE", + "WINDOW START": "FENSTERBEGINN", + "LAST 30 MINUTES": "LETZTE 30 MINUTEN", + "LAST HOUR": "LETZTE STUNDE", + "LAST 24 HOURS": "LETZTE 24 STUNDEN", + "OFF": "AUS", + "ON": "AN", + "GRAPH DATA UNAVAILABLE": "KEINE DIAGRAMMDATEN", + "CONSUMPTION": "VERBRAUCH", + "DISTANCE FROM SAFETY (PP)": "ABSTAND ZUR SICHERHEIT (PP)", + "TIME ELAPSED": "VERGANGENE ZEIT", + "[ (G)TREND // %s ]": "[ (G)TREND // %s ]", + "[ (H)TRACE // %s ]": "[ (H)SPUR // %s ]", + "%+.1f PP FROM SAFETY": "%+.1f PP ZUR SICHERHEIT", + "TREND // %.1f%% AT RESET": "TREND // %.1f%% BEIM RESET", + "TREND // %+.1f PP AT RESET": "TREND // %+.1f PP BEIM RESET", + "TREND UNAVAILABLE": "TREND NICHT VERFÜGBAR", + "%.0f%% USED // %.1f%% TIME": "%.0f%% VERBRAUCHT // %.1f%% ZEIT", + "╭ PACE ╮": "╭ TEMPO ╮", + "╭ ZONE ╮": "╭ ZONE ╮", "PREVIOUS TURN": "VORHERIGER DURCHGANG", "LIVE SESSION": "LIVE-SITZUNG", " // %d OF %d": " // %d VON %d", diff --git a/internal/i18n/locales/en-GB.json b/internal/i18n/locales/en-GB.json index 9b859a8..23668d1 100644 --- a/internal/i18n/locales/en-GB.json +++ b/internal/i18n/locales/en-GB.json @@ -1,4 +1,25 @@ { + "PACE": "PACE", + "ZONE": "ZONE", + "WINDOW START": "WINDOW START", + "LAST 30 MINUTES": "LAST 30 MINUTES", + "LAST HOUR": "LAST HOUR", + "LAST 24 HOURS": "LAST 24 HOURS", + "OFF": "OFF", + "ON": "ON", + "GRAPH DATA UNAVAILABLE": "GRAPH DATA UNAVAILABLE", + "CONSUMPTION": "CONSUMPTION", + "DISTANCE FROM SAFETY (PP)": "DISTANCE FROM SAFETY (PP)", + "TIME ELAPSED": "TIME ELAPSED", + "[ (G)TREND // %s ]": "[ (G)TREND // %s ]", + "[ (H)TRACE // %s ]": "[ (H)TRACE // %s ]", + "%+.1f PP FROM SAFETY": "%+.1f PP FROM SAFETY", + "TREND // %.1f%% AT RESET": "TREND // %.1f%% AT RESET", + "TREND // %+.1f PP AT RESET": "TREND // %+.1f PP AT RESET", + "TREND UNAVAILABLE": "TREND UNAVAILABLE", + "%.0f%% USED // %.1f%% TIME": "%.0f%% USED // %.1f%% TIME", + "╭ PACE ╮": "╭ PACE ╮", + "╭ ZONE ╮": "╭ ZONE ╮", "PREVIOUS TURN": "PREVIOUS TURN", "LIVE SESSION": "LIVE SESSION", " // %d OF %d": " // %d OF %d", diff --git a/internal/i18n/locales/es.json b/internal/i18n/locales/es.json index 65e863f..2e16b1c 100644 --- a/internal/i18n/locales/es.json +++ b/internal/i18n/locales/es.json @@ -1,4 +1,25 @@ { + "PACE": "RITMO", + "ZONE": "ZONA", + "WINDOW START": "INICIO DE VENTANA", + "LAST 30 MINUTES": "ÚLTIMOS 30 MINUTOS", + "LAST HOUR": "ÚLTIMA HORA", + "LAST 24 HOURS": "ÚLTIMAS 24 HORAS", + "OFF": "NO", + "ON": "SÍ", + "GRAPH DATA UNAVAILABLE": "DATOS DEL GRÁFICO NO DISPONIBLES", + "CONSUMPTION": "CONSUMO", + "DISTANCE FROM SAFETY (PP)": "DISTANCIA A LA SEGURIDAD (PP)", + "TIME ELAPSED": "TIEMPO TRANSCURRIDO", + "[ (G)TREND // %s ]": "[ (G)TENDENCIA // %s ]", + "[ (H)TRACE // %s ]": "[ (H)TRAZA // %s ]", + "%+.1f PP FROM SAFETY": "%+.1f PP DE LA SEGURIDAD", + "TREND // %.1f%% AT RESET": "TENDENCIA // %.1f%% AL REINICIAR", + "TREND // %+.1f PP AT RESET": "TENDENCIA // %+.1f PP AL REINICIAR", + "TREND UNAVAILABLE": "TENDENCIA NO DISPONIBLE", + "%.0f%% USED // %.1f%% TIME": "%.0f%% USADO // %.1f%% TIEMPO", + "╭ PACE ╮": "╭ RITMO ╮", + "╭ ZONE ╮": "╭ ZONA ╮", "PREVIOUS TURN": "TURNO ANTERIOR", "LIVE SESSION": "SESIÓN EN DIRECTO", " // %d OF %d": " // %d DE %d", diff --git a/internal/i18n/locales/et.json b/internal/i18n/locales/et.json index a26ab8c..97df30d 100644 --- a/internal/i18n/locales/et.json +++ b/internal/i18n/locales/et.json @@ -1,4 +1,25 @@ { + "PACE": "TEMPO", + "ZONE": "ALA", + "WINDOW START": "AKNA ALGUS", + "LAST 30 MINUTES": "VIIMASED 30 MINUTIT", + "LAST HOUR": "VIIMANE TUND", + "LAST 24 HOURS": "VIIMASED 24 TUNDI", + "OFF": "VÄLJAS", + "ON": "SEES", + "GRAPH DATA UNAVAILABLE": "GRAAFIKUANDMED PUUDUVAD", + "CONSUMPTION": "TARBIMINE", + "DISTANCE FROM SAFETY (PP)": "KAUGUS TURVAPIIRIST (PP)", + "TIME ELAPSED": "MÖÖDUNUD AEG", + "[ (G)TREND // %s ]": "[ (G)TREND // %s ]", + "[ (H)TRACE // %s ]": "[ (H)JÄLG // %s ]", + "%+.1f PP FROM SAFETY": "%+.1f PP TURVAPIIRIST", + "TREND // %.1f%% AT RESET": "TREND // %.1f%% LÄHTESTAMISEL", + "TREND // %+.1f PP AT RESET": "TREND // %+.1f PP LÄHTESTAMISEL", + "TREND UNAVAILABLE": "TREND PUUDUB", + "%.0f%% USED // %.1f%% TIME": "%.0f%% KASUTATUD // %.1f%% AEGA", + "╭ PACE ╮": "╭ TEMPO ╮", + "╭ ZONE ╮": "╭ ALA ╮", "PREVIOUS TURN": "EELMINE VOOR", "LIVE SESSION": "REAALAJASEANSS", " // %d OF %d": " // %d / %d", diff --git a/internal/i18n/locales/fi.json b/internal/i18n/locales/fi.json index 2fa5331..ff207d7 100644 --- a/internal/i18n/locales/fi.json +++ b/internal/i18n/locales/fi.json @@ -1,4 +1,25 @@ { + "PACE": "TAHTI", + "ZONE": "ALUE", + "WINDOW START": "JAKSON ALKU", + "LAST 30 MINUTES": "VIIMEISET 30 MINUUTTIA", + "LAST HOUR": "VIIMEINEN TUNTI", + "LAST 24 HOURS": "VIIMEISET 24 TUNTIA", + "OFF": "POIS", + "ON": "PÄÄLLÄ", + "GRAPH DATA UNAVAILABLE": "KAAVION TIEDOT PUUTTUVAT", + "CONSUMPTION": "KULUTUS", + "DISTANCE FROM SAFETY (PP)": "ETÄISYYS TURVARAJASTA (PP)", + "TIME ELAPSED": "KULUNUT AIKA", + "[ (G)TREND // %s ]": "[ (G)TRENDI // %s ]", + "[ (H)TRACE // %s ]": "[ (H)JÄLKI // %s ]", + "%+.1f PP FROM SAFETY": "%+.1f PP TURVARAJASTA", + "TREND // %.1f%% AT RESET": "TRENDI // %.1f%% NOLLAUKSESSA", + "TREND // %+.1f PP AT RESET": "TRENDI // %+.1f PP NOLLAUKSESSA", + "TREND UNAVAILABLE": "TRENDI PUUTTUU", + "%.0f%% USED // %.1f%% TIME": "%.0f%% KÄYTETTY // %.1f%% AJASTA", + "╭ PACE ╮": "╭ TAHTI ╮", + "╭ ZONE ╮": "╭ ALUE ╮", "PREVIOUS TURN": "EDELLINEN VUORO", "LIVE SESSION": "REAALIAIKAINEN ISTUNTO", " // %d OF %d": " // %d / %d", diff --git a/internal/i18n/locales/fr.json b/internal/i18n/locales/fr.json index b9d4dfd..57c81c2 100644 --- a/internal/i18n/locales/fr.json +++ b/internal/i18n/locales/fr.json @@ -1,4 +1,25 @@ { + "PACE": "RYTHME", + "ZONE": "ZONE", + "WINDOW START": "DÉBUT DE FENÊTRE", + "LAST 30 MINUTES": "30 DERNIÈRES MINUTES", + "LAST HOUR": "DERNIÈRE HEURE", + "LAST 24 HOURS": "24 DERNIÈRES HEURES", + "OFF": "NON", + "ON": "OUI", + "GRAPH DATA UNAVAILABLE": "DONNÉES DU GRAPHIQUE INDISPONIBLES", + "CONSUMPTION": "CONSOMMATION", + "DISTANCE FROM SAFETY (PP)": "ÉCART À LA SÉCURITÉ (PP)", + "TIME ELAPSED": "TEMPS ÉCOULÉ", + "[ (G)TREND // %s ]": "[ (G)TENDANCE // %s ]", + "[ (H)TRACE // %s ]": "[ (H)TRACE // %s ]", + "%+.1f PP FROM SAFETY": "%+.1f PP DE LA SÉCURITÉ", + "TREND // %.1f%% AT RESET": "TENDANCE // %.1f%% AU RESET", + "TREND // %+.1f PP AT RESET": "TENDANCE // %+.1f PP AU RESET", + "TREND UNAVAILABLE": "TENDANCE INDISPONIBLE", + "%.0f%% USED // %.1f%% TIME": "%.0f%% UTILISÉ // %.1f%% TEMPS", + "╭ PACE ╮": "╭ RYTHME ╮", + "╭ ZONE ╮": "╭ ZONE ╮", "PREVIOUS TURN": "TOUR PRÉCÉDENT", "LIVE SESSION": "SESSION EN DIRECT", " // %d OF %d": " // %d SUR %d", diff --git a/internal/i18n/locales/it.json b/internal/i18n/locales/it.json index 680b2dd..e54449e 100644 --- a/internal/i18n/locales/it.json +++ b/internal/i18n/locales/it.json @@ -1,4 +1,25 @@ { + "PACE": "RITMO", + "ZONE": "ZONA", + "WINDOW START": "INIZIO FINESTRA", + "LAST 30 MINUTES": "ULTIMI 30 MINUTI", + "LAST HOUR": "ULTIMA ORA", + "LAST 24 HOURS": "ULTIME 24 ORE", + "OFF": "NO", + "ON": "SÌ", + "GRAPH DATA UNAVAILABLE": "DATI DEL GRAFICO NON DISPONIBILI", + "CONSUMPTION": "CONSUMO", + "DISTANCE FROM SAFETY (PP)": "DISTANZA DALLA SICUREZZA (PP)", + "TIME ELAPSED": "TEMPO TRASCORSO", + "[ (G)TREND // %s ]": "[ (G)TENDENZA // %s ]", + "[ (H)TRACE // %s ]": "[ (H)TRACCIA // %s ]", + "%+.1f PP FROM SAFETY": "%+.1f PP DALLA SICUREZZA", + "TREND // %.1f%% AT RESET": "TENDENZA // %.1f%% AL RESET", + "TREND // %+.1f PP AT RESET": "TENDENZA // %+.1f PP AL RESET", + "TREND UNAVAILABLE": "TENDENZA NON DISPONIBILE", + "%.0f%% USED // %.1f%% TIME": "%.0f%% USATO // %.1f%% TEMPO", + "╭ PACE ╮": "╭ RITMO ╮", + "╭ ZONE ╮": "╭ ZONA ╮", "PREVIOUS TURN": "TURNO PRECEDENTE", "LIVE SESSION": "SESSIONE IN DIRETTA", " // %d OF %d": " // %d DI %d", diff --git a/internal/i18n/locales/ja.json b/internal/i18n/locales/ja.json index 4173181..107e457 100644 --- a/internal/i18n/locales/ja.json +++ b/internal/i18n/locales/ja.json @@ -1,4 +1,25 @@ { + "PACE": "ペース", + "ZONE": "ゾーン", + "WINDOW START": "期間の開始", + "LAST 30 MINUTES": "直近30分", + "LAST HOUR": "直近1時間", + "LAST 24 HOURS": "直近24時間", + "OFF": "オフ", + "ON": "オン", + "GRAPH DATA UNAVAILABLE": "グラフデータなし", + "CONSUMPTION": "消費量", + "DISTANCE FROM SAFETY (PP)": "安全線からの差 (PP)", + "TIME ELAPSED": "経過時間", + "[ (G)TREND // %s ]": "[ (G)傾向 // %s ]", + "[ (H)TRACE // %s ]": "[ (H)軌跡 // %s ]", + "%+.1f PP FROM SAFETY": "安全線との差 %+.1f PP", + "TREND // %.1f%% AT RESET": "傾向 // リセット時 %.1f%%", + "TREND // %+.1f PP AT RESET": "傾向 // リセット時 %+.1f PP", + "TREND UNAVAILABLE": "傾向データなし", + "%.0f%% USED // %.1f%% TIME": "使用 %.0f%% // 時間 %.1f%%", + "╭ PACE ╮": "╭ ペース ╮", + "╭ ZONE ╮": "╭ ゾーン ╮", "PREVIOUS TURN": "前のターン", "LIVE SESSION": "ライブセッション", " // %d OF %d": " // %d / %d", diff --git a/internal/i18n/locales/nb.json b/internal/i18n/locales/nb.json index b75bab5..455a3f2 100644 --- a/internal/i18n/locales/nb.json +++ b/internal/i18n/locales/nb.json @@ -1,4 +1,25 @@ { + "PACE": "TEMPO", + "ZONE": "SONE", + "WINDOW START": "VINDUSSTART", + "LAST 30 MINUTES": "SISTE 30 MINUTTER", + "LAST HOUR": "SISTE TIME", + "LAST 24 HOURS": "SISTE 24 TIMER", + "OFF": "AV", + "ON": "PÅ", + "GRAPH DATA UNAVAILABLE": "GRAFDATA MANGLER", + "CONSUMPTION": "FORBRUK", + "DISTANCE FROM SAFETY (PP)": "AVSTAND TIL TRYGGHET (PP)", + "TIME ELAPSED": "FORLØPT TID", + "[ (G)TREND // %s ]": "[ (G)TREND // %s ]", + "[ (H)TRACE // %s ]": "[ (H)SPOR // %s ]", + "%+.1f PP FROM SAFETY": "%+.1f PP FRA TRYGGHET", + "TREND // %.1f%% AT RESET": "TREND // %.1f%% VED NULLSTILLING", + "TREND // %+.1f PP AT RESET": "TREND // %+.1f PP VED NULLSTILLING", + "TREND UNAVAILABLE": "TREND MANGLER", + "%.0f%% USED // %.1f%% TIME": "%.0f%% BRUKT // %.1f%% TID", + "╭ PACE ╮": "╭ TEMPO ╮", + "╭ ZONE ╮": "╭ SONE ╮", "PREVIOUS TURN": "FORRIGE TUR", "LIVE SESSION": "DIREKTEØKT", " // %d OF %d": " // %d AV %d", diff --git a/internal/i18n/locales/nl.json b/internal/i18n/locales/nl.json index 408a756..3c30868 100644 --- a/internal/i18n/locales/nl.json +++ b/internal/i18n/locales/nl.json @@ -1,4 +1,25 @@ { + "PACE": "TEMPO", + "ZONE": "ZONE", + "WINDOW START": "VENSTERSTART", + "LAST 30 MINUTES": "LAATSTE 30 MINUTEN", + "LAST HOUR": "LAATSTE UUR", + "LAST 24 HOURS": "LAATSTE 24 UUR", + "OFF": "UIT", + "ON": "AAN", + "GRAPH DATA UNAVAILABLE": "GEEN GRAFIEKGEGEVENS", + "CONSUMPTION": "VERBRUIK", + "DISTANCE FROM SAFETY (PP)": "AFSTAND TOT VEILIGHEID (PP)", + "TIME ELAPSED": "VERSTREKEN TIJD", + "[ (G)TREND // %s ]": "[ (G)TREND // %s ]", + "[ (H)TRACE // %s ]": "[ (H)SPOOR // %s ]", + "%+.1f PP FROM SAFETY": "%+.1f PP VAN VEILIGHEID", + "TREND // %.1f%% AT RESET": "TREND // %.1f%% BIJ RESET", + "TREND // %+.1f PP AT RESET": "TREND // %+.1f PP BIJ RESET", + "TREND UNAVAILABLE": "TREND NIET BESCHIKBAAR", + "%.0f%% USED // %.1f%% TIME": "%.0f%% GEBRUIKT // %.1f%% TIJD", + "╭ PACE ╮": "╭ TEMPO ╮", + "╭ ZONE ╮": "╭ ZONE ╮", "PREVIOUS TURN": "VORIGE BEURT", "LIVE SESSION": "LIVESESSIE", " // %d OF %d": " // %d VAN %d", diff --git a/internal/i18n/locales/pt-BR.json b/internal/i18n/locales/pt-BR.json index 3ee8cd1..ceece19 100644 --- a/internal/i18n/locales/pt-BR.json +++ b/internal/i18n/locales/pt-BR.json @@ -1,4 +1,25 @@ { + "PACE": "RITMO", + "ZONE": "ZONA", + "WINDOW START": "INÍCIO DA JANELA", + "LAST 30 MINUTES": "ÚLTIMOS 30 MINUTOS", + "LAST HOUR": "ÚLTIMA HORA", + "LAST 24 HOURS": "ÚLTIMAS 24 HORAS", + "OFF": "DESLIGADO", + "ON": "LIGADO", + "GRAPH DATA UNAVAILABLE": "DADOS DO GRÁFICO INDISPONÍVEIS", + "CONSUMPTION": "CONSUMO", + "DISTANCE FROM SAFETY (PP)": "DISTÂNCIA DA SEGURANÇA (PP)", + "TIME ELAPSED": "TEMPO DECORRIDO", + "[ (G)TREND // %s ]": "[ (G)TENDÊNCIA // %s ]", + "[ (H)TRACE // %s ]": "[ (H)RASTRO // %s ]", + "%+.1f PP FROM SAFETY": "%+.1f PP DA SEGURANÇA", + "TREND // %.1f%% AT RESET": "TENDÊNCIA // %.1f%% NA REDEFINIÇÃO", + "TREND // %+.1f PP AT RESET": "TENDÊNCIA // %+.1f PP NA REDEFINIÇÃO", + "TREND UNAVAILABLE": "TENDÊNCIA INDISPONÍVEL", + "%.0f%% USED // %.1f%% TIME": "%.0f%% USADO // %.1f%% TEMPO", + "╭ PACE ╮": "╭ RITMO ╮", + "╭ ZONE ╮": "╭ ZONA ╮", "PREVIOUS TURN": "TURNO ANTERIOR", "LIVE SESSION": "SESSÃO AO VIVO", " // %d OF %d": " // %d DE %d", diff --git a/internal/i18n/locales/pt-PT.json b/internal/i18n/locales/pt-PT.json index 3642f39..5219b7e 100644 --- a/internal/i18n/locales/pt-PT.json +++ b/internal/i18n/locales/pt-PT.json @@ -1,4 +1,25 @@ { + "PACE": "RITMO", + "ZONE": "ZONA", + "WINDOW START": "INÍCIO DA JANELA", + "LAST 30 MINUTES": "ÚLTIMOS 30 MINUTOS", + "LAST HOUR": "ÚLTIMA HORA", + "LAST 24 HOURS": "ÚLTIMAS 24 HORAS", + "OFF": "DESLIGADO", + "ON": "LIGADO", + "GRAPH DATA UNAVAILABLE": "DADOS DO GRÁFICO INDISPONÍVEIS", + "CONSUMPTION": "CONSUMO", + "DISTANCE FROM SAFETY (PP)": "DISTÂNCIA DA SEGURANÇA (PP)", + "TIME ELAPSED": "TEMPO DECORRIDO", + "[ (G)TREND // %s ]": "[ (G)TENDÊNCIA // %s ]", + "[ (H)TRACE // %s ]": "[ (H)RASTO // %s ]", + "%+.1f PP FROM SAFETY": "%+.1f PP DA SEGURANÇA", + "TREND // %.1f%% AT RESET": "TENDÊNCIA // %.1f%% NA REPOSIÇÃO", + "TREND // %+.1f PP AT RESET": "TENDÊNCIA // %+.1f PP NA REPOSIÇÃO", + "TREND UNAVAILABLE": "TENDÊNCIA INDISPONÍVEL", + "%.0f%% USED // %.1f%% TIME": "%.0f%% UTILIZADO // %.1f%% TEMPO", + "╭ PACE ╮": "╭ RITMO ╮", + "╭ ZONE ╮": "╭ ZONA ╮", "PREVIOUS TURN": "TURNO ANTERIOR", "LIVE SESSION": "SESSÃO EM DIRETO", " // %d OF %d": " // %d DE %d", diff --git a/internal/i18n/locales/ru.json b/internal/i18n/locales/ru.json index a541712..916a7f7 100644 --- a/internal/i18n/locales/ru.json +++ b/internal/i18n/locales/ru.json @@ -1,4 +1,25 @@ { + "PACE": "ТЕМП", + "ZONE": "ЗОНА", + "WINDOW START": "НАЧАЛО ОКНА", + "LAST 30 MINUTES": "ПОСЛЕДНИЕ 30 МИНУТ", + "LAST HOUR": "ПОСЛЕДНИЙ ЧАС", + "LAST 24 HOURS": "ПОСЛЕДНИЕ 24 ЧАСА", + "OFF": "ВЫКЛ", + "ON": "ВКЛ", + "GRAPH DATA UNAVAILABLE": "ДАННЫЕ ГРАФИКА НЕДОСТУПНЫ", + "CONSUMPTION": "РАСХОД", + "DISTANCE FROM SAFETY (PP)": "ОТКЛОНЕНИЕ ОТ БЕЗОПАСНОГО УРОВНЯ (ПП)", + "TIME ELAPSED": "ПРОШЕДШЕЕ ВРЕМЯ", + "[ (G)TREND // %s ]": "[ (G)ТРЕНД // %s ]", + "[ (H)TRACE // %s ]": "[ (H)СЛЕД // %s ]", + "%+.1f PP FROM SAFETY": "%+.1f ПП ОТ БЕЗОПАСНОГО УРОВНЯ", + "TREND // %.1f%% AT RESET": "ТРЕНД // %.1f%% К СБРОСУ", + "TREND // %+.1f PP AT RESET": "ТРЕНД // %+.1f ПП К СБРОСУ", + "TREND UNAVAILABLE": "ТРЕНД НЕДОСТУПЕН", + "%.0f%% USED // %.1f%% TIME": "%.0f%% ИСПОЛЬЗОВАНО // %.1f%% ВРЕМЕНИ", + "╭ PACE ╮": "╭ ТЕМП ╮", + "╭ ZONE ╮": "╭ ЗОНА ╮", "PREVIOUS TURN": "ПРЕДЫДУЩИЙ ХОД", "LIVE SESSION": "ТЕКУЩИЙ СЕАНС", " // %d OF %d": " // %d ИЗ %d", diff --git a/internal/i18n/locales/sv.json b/internal/i18n/locales/sv.json index 560b867..b9ea029 100644 --- a/internal/i18n/locales/sv.json +++ b/internal/i18n/locales/sv.json @@ -1,4 +1,25 @@ { + "PACE": "TAKT", + "ZONE": "ZON", + "WINDOW START": "FÖNSTERSTART", + "LAST 30 MINUTES": "SENASTE 30 MINUTERNA", + "LAST HOUR": "SENASTE TIMMEN", + "LAST 24 HOURS": "SENASTE 24 TIMMARNA", + "OFF": "AV", + "ON": "PÅ", + "GRAPH DATA UNAVAILABLE": "GRAFDATA SAKNAS", + "CONSUMPTION": "FÖRBRUKNING", + "DISTANCE FROM SAFETY (PP)": "AVSTÅND TILL SÄKERHET (PP)", + "TIME ELAPSED": "FÖRFLUTEN TID", + "[ (G)TREND // %s ]": "[ (G)TREND // %s ]", + "[ (H)TRACE // %s ]": "[ (H)SPÅR // %s ]", + "%+.1f PP FROM SAFETY": "%+.1f PP FRÅN SÄKERHET", + "TREND // %.1f%% AT RESET": "TREND // %.1f%% VID ÅTERSTÄLLNING", + "TREND // %+.1f PP AT RESET": "TREND // %+.1f PP VID ÅTERSTÄLLNING", + "TREND UNAVAILABLE": "TREND SAKNAS", + "%.0f%% USED // %.1f%% TIME": "%.0f%% ANVÄNT // %.1f%% TID", + "╭ PACE ╮": "╭ TAKT ╮", + "╭ ZONE ╮": "╭ ZON ╮", "PREVIOUS TURN": "FÖREGÅENDE TUR", "LIVE SESSION": "LIVESESSION", " // %d OF %d": " // %d AV %d", diff --git a/internal/i18n/locales/tr.json b/internal/i18n/locales/tr.json index 7620e03..0a760d5 100644 --- a/internal/i18n/locales/tr.json +++ b/internal/i18n/locales/tr.json @@ -1,4 +1,25 @@ { + "PACE": "HIZ", + "ZONE": "BÖLGE", + "WINDOW START": "PENCERE BAŞLANGICI", + "LAST 30 MINUTES": "SON 30 DAKİKA", + "LAST HOUR": "SON SAAT", + "LAST 24 HOURS": "SON 24 SAAT", + "OFF": "KAPALI", + "ON": "AÇIK", + "GRAPH DATA UNAVAILABLE": "GRAFİK VERİSİ YOK", + "CONSUMPTION": "TÜKETİM", + "DISTANCE FROM SAFETY (PP)": "GÜVENLİK ÇİZGİSİNDEN UZAKLIK (YP)", + "TIME ELAPSED": "GEÇEN SÜRE", + "[ (G)TREND // %s ]": "[ (G)EĞİLİM // %s ]", + "[ (H)TRACE // %s ]": "[ (H)İZ // %s ]", + "%+.1f PP FROM SAFETY": "GÜVENLİK ÇİZGİSİNDEN %+.1f YP", + "TREND // %.1f%% AT RESET": "EĞİLİM // SIFIRLAMADA %.1f%%", + "TREND // %+.1f PP AT RESET": "EĞİLİM // SIFIRLAMADA %+.1f YP", + "TREND UNAVAILABLE": "EĞİLİM YOK", + "%.0f%% USED // %.1f%% TIME": "%.0f%% KULLANILDI // %.1f%% SÜRE", + "╭ PACE ╮": "╭ HIZ ╮", + "╭ ZONE ╮": "╭ BÖLGE ╮", "PREVIOUS TURN": "ÖNCEKİ TUR", "LIVE SESSION": "CANLI OTURUM", " // %d OF %d": " // %d / %d", diff --git a/internal/i18n/locales/zh-Hans.json b/internal/i18n/locales/zh-Hans.json index 151d8af..b22be49 100644 --- a/internal/i18n/locales/zh-Hans.json +++ b/internal/i18n/locales/zh-Hans.json @@ -1,4 +1,25 @@ { + "PACE": "速率", + "ZONE": "区域", + "WINDOW START": "窗口开始", + "LAST 30 MINUTES": "最近30分钟", + "LAST HOUR": "最近1小时", + "LAST 24 HOURS": "最近24小时", + "OFF": "关", + "ON": "开", + "GRAPH DATA UNAVAILABLE": "图表数据不可用", + "CONSUMPTION": "消耗", + "DISTANCE FROM SAFETY (PP)": "距安全线的差值(百分点)", + "TIME ELAPSED": "已过时间", + "[ (G)TREND // %s ]": "[ (G)趋势 // %s ]", + "[ (H)TRACE // %s ]": "[ (H)轨迹 // %s ]", + "%+.1f PP FROM SAFETY": "距安全线 %+.1f 个百分点", + "TREND // %.1f%% AT RESET": "趋势 // 重置时 %.1f%%", + "TREND // %+.1f PP AT RESET": "趋势 // 重置时 %+.1f 个百分点", + "TREND UNAVAILABLE": "趋势不可用", + "%.0f%% USED // %.1f%% TIME": "已用 %.0f%% // 时间 %.1f%%", + "╭ PACE ╮": "╭ 速率 ╮", + "╭ ZONE ╮": "╭ 区域 ╮", "PREVIOUS TURN": "上一轮", "LIVE SESSION": "实时会话", " // %d OF %d": " // %d / %d", diff --git a/internal/quotagraph/graph.go b/internal/quotagraph/graph.go new file mode 100644 index 0000000..0188cce --- /dev/null +++ b/internal/quotagraph/graph.go @@ -0,0 +1,248 @@ +// Package quotagraph owns bounded, process-local quota observations and trend +// geometry independently of either presentation. +package quotagraph + +import ( + "math" + "time" + + "github.com/merefield/codexometer/internal/codex" +) + +const MaxPoints = 1500 + +type Point struct { + At time.Time `json:"at"` + Elapsed float64 `json:"elapsed"` + Used int `json:"used"` + Break bool `json:"break"` +} + +func Elapsed(window codex.Window, now time.Time) (float64, bool) { + if window.WindowDurationMins == nil || *window.WindowDurationMins <= 0 || window.ResetsAt == nil || *window.ResetsAt <= 0 { + return 0, false + } + return max(0, min(100, 100*(1-(float64(*window.ResetsAt)-float64(now.UnixMilli())/1000)/(float64(*window.WindowDurationMins)*60)))), true +} + +func Record(window, previous codex.Window, points []Point, now time.Time, gap bool) []Point { + elapsed, ok := Elapsed(window, now) + if !ok { + return nil + } + if len(points) > 0 { + last := points[len(points)-1] + if previous.WindowDurationMins == nil || previous.ResetsAt == nil || *previous.WindowDurationMins != *window.WindowDurationMins || *previous.ResetsAt != *window.ResetsAt || !now.After(last.At) || window.UsedPercent < last.Used || elapsed < last.Elapsed { + points = nil + } + } + // Observers may still hold the previous snapshot; never mutate its trail. + recorded := make([]Point, len(points)+1) + copy(recorded, points) + recorded[len(points)] = Point{At: now, Elapsed: elapsed, Used: max(0, min(100, window.UsedPercent)), Break: gap} + points = recorded + if len(points) > MaxPoints { + points = append(points[:1], points[len(points)-(MaxPoints-1):]...) + points[1].Break = true + } + return points +} + +type Key struct { + Limit string + Slot int + Kind codex.MeterKind +} + +// Keys follows Meters' ordering without identifying windows by mutable labels. +func Keys(snapshot codex.Snapshot) []Key { + meters := snapshot.Meters() + keys := make([]Key, len(meters)) + slots := map[string]int{} + for i, meter := range meters { + key := Key{Limit: meter.LimitID, Kind: meter.Kind} + if meter.Kind == codex.MeterQuotaWindow { + bucket := snapshot.RateLimits + if len(snapshot.RateLimitsByLimitID) > 0 { + bucket = snapshot.RateLimitsByLimitID[meter.LimitID] + } + key.Slot = slots[meter.LimitID] + if bucket.Primary == nil { + key.Slot++ + } + slots[meter.LimitID]++ + } + keys[i] = key + } + return keys +} + +type Series struct { + Window codex.Window + Points []Point +} +type History struct { + Account string + Series map[Key]Series + gap bool +} + +func (h *History) Observe(snapshot codex.Snapshot, failed bool, at time.Time) { + if failed { + h.gap = true + return + } + if snapshot.AccountFingerprint != h.Account { + h.Series = nil + } + h.Account = snapshot.AccountFingerprint + keys := Keys(snapshot) + next := make(map[Key]Series, len(keys)) + for i, meter := range snapshot.Meters() { + old := h.Series[keys[i]] + window := meter.Window + // Retain values, not pointers into a caller's mutable snapshot. + if window.WindowDurationMins != nil { + duration := *window.WindowDurationMins + window.WindowDurationMins = &duration + } + if window.ResetsAt != nil { + reset := *window.ResetsAt + window.ResetsAt = &reset + } + next[keys[i]] = Series{Window: window, Points: Record(window, old.Window, old.Points, at, h.gap)} + } + h.Series, h.gap = next, false +} + +type Mode int + +const ( + WindowStart Mode = iota + HalfHour + Hour + Day + Off + ModeCount +) + +func (m Mode) Minutes() float64 { + switch m { + case HalfHour: + return 30 + case Hour: + return 60 + case Day: + return 1440 + } + return 0 +} +func (m Mode) Label() string { + switch m { + case HalfHour: + return "LAST 30 MINUTES" + case Hour: + return "LAST HOUR" + case Day: + return "LAST 24 HOURS" + case Off: + return "OFF" + } + return "WINDOW START" +} + +func Recent(points []Point, mode Mode) []Point { + if len(points) < 2 || mode.Minutes() == 0 { + return nil + } + start := 0 + for i := len(points) - 1; i > 0; i-- { + if points[i].Break { + start = i + break + } + } + points = points[start:] + span := time.Duration(mode.Minutes()) * time.Minute + last := points[len(points)-1].At + if last.Sub(points[0].At) < span { + return nil + } + cutoff := last.Add(-span) + for len(points) > 0 && points[0].At.Before(cutoff) { + points = points[1:] + } + if len(points) < 2 { + return nil + } + return points +} + +func Available(mode Mode, window codex.Window, points []Point, now time.Time) bool { + if mode == Off { + return true + } + elapsed, ok := Elapsed(window, now) + if !ok { + return false + } + if mode == WindowStart { + return elapsed > 0 + } + return len(Recent(points, mode)) >= 2 +} + +type Trend struct{ Slope, Intercept, Projected, Start float64 } + +func Project(mode Mode, window codex.Window, points []Point, now time.Time) (Trend, bool) { + if mode == Off || !Available(mode, window, points, now) { + return Trend{}, false + } + elapsed, _ := Elapsed(window, now) + used := float64(max(0, min(100, window.UsedPercent))) + slope := used / elapsed + if mode != WindowStart { + recent := Recent(points, mode) + var xMean, yMean float64 + for _, p := range recent { + xMean += p.Elapsed + yMean += float64(p.Used) + } + xMean /= float64(len(recent)) + yMean /= float64(len(recent)) + var covariance, variance float64 + for _, p := range recent { + covariance += (p.Elapsed - xMean) * (float64(p.Used) - yMean) + variance += math.Pow(p.Elapsed-xMean, 2) + } + if variance <= 0 { + return Trend{}, false + } + slope = max(0, covariance/variance) + } + intercept := used - slope*elapsed + start := max(0, elapsed-mode.Minutes()/float64(*window.WindowDurationMins)*100) + if mode == WindowStart { + intercept, start = 0, 0 + } + return Trend{Slope: slope, Intercept: intercept, Projected: used + slope*(100-elapsed), Start: start}, true +} + +type Segment struct{ X1, Y1, X2, Y2 float64 } + +func (t Trend) Segment(pace bool) (Segment, bool) { + slope, lower, upper := t.Slope, 0.0, 100.0 + if pace { + slope-- + lower = -100 + } + x1, x2 := t.Start, 100.0 + if slope != 0 { + a, b := (lower-t.Intercept)/slope, (upper-t.Intercept)/slope + x1 = max(x1, min(a, b)) + x2 = min(x2, max(a, b)) + } + y1, y2 := t.Intercept+slope*x1, t.Intercept+slope*x2 + valid := x1 <= x2 && y1 >= lower-1e-9 && y1 <= upper+1e-9 && y2 >= lower-1e-9 && y2 <= upper+1e-9 + return Segment{X1: x1, Y1: max(lower, min(upper, y1)), X2: x2, Y2: max(lower, min(upper, y2))}, valid +} diff --git a/internal/quotagraph/graph_test.go b/internal/quotagraph/graph_test.go new file mode 100644 index 0000000..b7c95f6 --- /dev/null +++ b/internal/quotagraph/graph_test.go @@ -0,0 +1,172 @@ +package quotagraph + +import ( + "math" + "testing" + "time" + + "github.com/merefield/codexometer/internal/codex" +) + +func testWindow(now time.Time, used int) codex.Window { + duration, reset := int64(100), now.Add(50*time.Minute).Unix() + return codex.Window{UsedPercent: used, WindowDurationMins: &duration, ResetsAt: &reset} +} + +func TestWindowStartUsesSnapshotWithoutHistory(t *testing.T) { + now := time.Unix(1800000000, 0) + w := testWindow(now, 25) + trend, ok := Project(WindowStart, w, nil, now) + if !ok || trend.Slope != .5 || trend.Projected != 50 || trend.Start != 0 || trend.Intercept != 0 { + t.Fatalf("origin projection: %+v %v", trend, ok) + } + zone, ok := trend.Segment(false) + if !ok || zone != (Segment{0, 0, 100, 50}) { + t.Fatalf("zone segment: %+v %v", zone, ok) + } + pace, ok := trend.Segment(true) + if !ok || pace != (Segment{0, 0, 100, -50}) { + t.Fatalf("pace segment: %+v %v", pace, ok) + } + w.UsedPercent = 0 + if trend, ok = Project(WindowStart, w, nil, now); !ok || trend.Projected != 0 { + t.Fatal("zero consumption should have a safe projection") + } + if _, ok := Project(WindowStart, w, nil, now.Add(-50*time.Minute)); ok { + t.Fatal("zero elapsed time cannot establish velocity") + } + if _, ok := Project(Off, w, nil, now); ok { + t.Fatal("Off should not draw a trend") + } +} + +func TestWindowStartProjectionAtQuotaBoundary(t *testing.T) { + now := time.Unix(1800000000, 0) + for used, want := range map[int]float64{25: 50, 50: 100, 75: 150, 100: 200} { + trend, ok := Project(WindowStart, testWindow(now, used), nil, now) + if !ok || trend.Projected != want { + t.Fatalf("used=%d projection=%v valid=%v, want %v", used, trend.Projected, ok, want) + } + } + if _, ok := Project(WindowStart, codex.Window{UsedPercent: 25}, nil, now); ok { + t.Fatal("unknown cycle must not produce a projection") + } +} + +func TestRecentRegressionRequiresContinuousCoverage(t *testing.T) { + now := time.Unix(1800000000, 0) + w := testWindow(now, 40) + points := []Point{{At: now.Add(-30 * time.Minute), Elapsed: 20, Used: 10}, {At: now.Add(-15 * time.Minute), Elapsed: 35, Used: 25}, {At: now, Elapsed: 50, Used: 40}} + trend, ok := Project(HalfHour, w, points, now) + if !ok || math.Abs(trend.Slope-1) > 1e-10 || trend.Projected != 90 || trend.Start != 20 || trend.Intercept != -10 { + t.Fatalf("recent regression: %+v %v", trend, ok) + } + if Available(Hour, w, points, now) || Available(Day, w, points, now) { + t.Fatal("unobserved windows should not be available") + } + points[1].Break = true + if Available(HalfHour, w, points, now) { + t.Fatal("a refresh failure cannot be bridged by a trend") + } + for i := 1; i <= 2; i++ { + points = append(points, Point{At: now.Add(time.Duration(i) * 15 * time.Minute), Elapsed: float64(50 + i*15), Used: 40 + i*10}) + } + if !Available(HalfHour, w, points, now.Add(30*time.Minute)) { + t.Fatal("coverage should recover after the gap") + } +} + +func TestTrendClipping(t *testing.T) { + for _, test := range []struct { + trend Trend + pace bool + want Segment + }{ + {Trend{Slope: 2}, false, Segment{0, 0, 50, 100}}, + {Trend{Slope: 3}, true, Segment{0, 0, 50, 100}}, + {Trend{Slope: 1, Intercept: -20, Start: 10}, false, Segment{20, 0, 100, 80}}, + } { + got, ok := test.trend.Segment(test.pace) + if !ok || got != test.want { + t.Fatalf("clip %+v: %+v %v", test, got, ok) + } + } + if _, ok := (Trend{Intercept: 200}).Segment(false); ok { + t.Fatal("horizontal line outside plot should be omitted") + } +} + +func TestHistoryIdentityGapsAndResets(t *testing.T) { + now := time.Unix(1800000000, 0) + primary, secondary := testWindow(now, 10), testWindow(now, 20) + q := codex.Snapshot{AccountFingerprint: "A", RateLimitsByLimitID: map[string]codex.RateLimitSnapshot{"a": {Primary: &primary, Secondary: &secondary}, "b": {Primary: &primary}}} + var h History + h.Observe(q, false, now) + h.Observe(q, true, now.Add(time.Minute)) + h.Observe(q, false, now.Add(2*time.Minute)) + for _, series := range h.Series { + if len(series.Points) != 2 || !series.Points[1].Break { + t.Fatal("failed refresh must preserve history and mark a gap") + } + } + bucket := q.RateLimitsByLimitID["a"] + bucket.Primary = nil + q.RateLimitsByLimitID["a"] = bucket + h.Observe(q, false, now.Add(3*time.Minute)) + if len(h.Series) != 2 || h.Series[Keys(q)[0]].Points[0].Used != 20 { + t.Fatal("secondary must not inherit primary history") + } + delete(q.RateLimitsByLimitID, "a") + h.Observe(q, false, now.Add(4*time.Minute)) + if len(h.Series) != 1 { + t.Fatal("removed limits retained history") + } + q.AccountFingerprint = "B" + h.Observe(q, false, now.Add(5*time.Minute)) + if len(h.Series[Keys(q)[0]].Points) != 1 { + t.Fatal("account changed without clearing history") + } + primary.UsedPercent = 5 + h.Observe(q, false, now.Add(6*time.Minute)) + if len(h.Series[Keys(q)[0]].Points) != 1 { + t.Fatal("decreasing consumption must start a new trace") + } + *primary.ResetsAt += 3600 + h.Observe(q, false, now.Add(7*time.Minute)) + if len(h.Series[Keys(q)[0]].Points) != 1 { + t.Fatal("reset identity changed without clearing history") + } + h.Observe(q, false, now.Add(6*time.Minute)) + if len(h.Series[Keys(q)[0]].Points) != 1 { + t.Fatal("clock rewind must start a new trace") + } +} + +func TestHistoryBoundPreservesOriginAndBreak(t *testing.T) { + now := time.Unix(1800000000, 0) + w := testWindow(now, 10) + var points []Point + for i := 0; i < MaxPoints+20; i++ { + points = Record(w, w, points, now.Add(time.Duration(i)*time.Second), false) + } + if len(points) != MaxPoints || points[0].At != now || !points[1].Break || points[len(points)-1].At != now.Add((MaxPoints+19)*time.Second) { + t.Fatal("history is not bounded with a preserved origin and discontinuity") + } + if _, ok := Elapsed(codex.Window{}, now); ok { + t.Fatal("unknown cycle cannot position a dot") + } +} + +func TestRecordDoesNotMutatePreviousSnapshot(t *testing.T) { + now := time.Unix(1800000000, 0) + w := testWindow(now, 10) + previous := make([]Point, MaxPoints, MaxPoints+1) + for i := range previous { + previous[i] = Point{At: now.Add(time.Duration(i) * time.Second), Elapsed: 50, Used: 10} + } + second := previous[1] + Record(w, w, previous, now.Add(MaxPoints*time.Second), false) + if previous[1] != second { + t.Fatal("bounding mutated a previous snapshot's trace") + } +} diff --git a/internal/ui/english_snapshot_test.go b/internal/ui/english_snapshot_test.go index 3d1310f..67fcf66 100644 --- a/internal/ui/english_snapshot_test.go +++ b/internal/ui/english_snapshot_test.go @@ -36,9 +36,9 @@ func TestEnglishPresentationSnapshot(t *testing.T) { } } got := fmt.Sprintf("%x", hash.Sum(nil)) - // Baseline includes Usage summary metrics and report controls, - // plus main's current pricing footer; every theme, view and size is covered. - const want = "d76336a3d8c675014741f20d9205b5d19521a107a738b95a8f699b042bdd4592" + // Baseline includes the renamed Pace tab, new Zone view and graph controls; + // every theme, view and size remains covered. + const want = "dfadcb7e2554e56fb4002a7dc2b72d1bf03b561e1a481d499a80a638f9b346e3" if got != want { t.Fatalf("English presentation changed: got %s, want %s", got, want) } diff --git a/internal/ui/meters.go b/internal/ui/meters.go index 97da1ac..801cb7e 100644 --- a/internal/ui/meters.go +++ b/internal/ui/meters.go @@ -16,14 +16,14 @@ import ( func usesMeterGrid(view meterViewID) bool { switch view { - case viewBars, viewPie, viewConsumptionPace, viewFuel: + case viewBars, viewPie, viewPace, viewZone, viewFuel: return true default: return false } } -func renderMeterGrid(width, height int, meters []codex.Meter, view meterViewID, colors palette) string { +func renderMeterGrid(width, height int, meters []codex.Meter, view meterViewID, colors palette, graphs ...quotaPlotOptions) string { if len(meters) == 0 { return "" } @@ -51,7 +51,11 @@ func renderMeterGrid(width, height int, meters []codex.Meter, view meterViewID, pieces = append(pieces, strings.Repeat(" ", columnGap)) } if meterIndex < len(meters) { - pieces = append(pieces, renderMeterArea(columnWidths[column], rowHeight, meters[meterIndex], view, colors)) + var options []quotaPlotOptions + if meterIndex < len(graphs) { + options = graphs[meterIndex : meterIndex+1] + } + pieces = append(pieces, renderMeterArea(columnWidths[column], rowHeight, meters[meterIndex], view, colors, options...)) } else { pieces = append(pieces, strings.Repeat(" ", columnWidths[column])) } @@ -66,9 +70,12 @@ func renderMeterGrid(width, height int, meters []codex.Meter, view meterViewID, } func meterGridColumns(width, height, meterCount int, view meterViewID) int { - if view == viewBars || view == viewConsumptionPace || view == viewFuel { + if view == viewBars || view == viewFuel { return 1 } + if isQuotaGraph(view) { + return max(1, min(meterCount, width/36)) + } minimumColumns := 1 maximumColumns := meterCount if isRadialView(view) && meterCount > 1 { @@ -123,7 +130,7 @@ func renderMeter(width int, meter codex.Meter, view meterViewID, colors palette) return renderMeterArea(width, 0, meter, view, colors) } -func renderMeterArea(width, height int, meter codex.Meter, view meterViewID, colors palette) string { +func renderMeterArea(width, height int, meter codex.Meter, view meterViewID, colors palette, graphs ...quotaPlotOptions) string { used := min(max(meter.Window.UsedPercent, 0), 100) free := 100 - used color := meterColor(used, colors) @@ -162,18 +169,32 @@ func renderMeterArea(width, height int, meter codex.Meter, view meterViewID, col bodyHeight := 0 visualHeight := 0 detailLines := meterDetailLines(meter.Details) + if isQuotaGraph(view) && height > 0 { + // Preserve the plot before optional API-equivalent telemetry. + detailLines = detailLines[:min(len(detailLines), max(height-10, 0))] + } if height > 0 { bodyHeight = max(height-2, 1) chromeHeight := 3 + len(detailLines) + if isQuotaGraph(view) { + chromeHeight = 2 + len(detailLines) + } visualHeight = max(bodyHeight-chromeHeight, 1) } now := time.Now() visual := renderVisualizationSized(innerWidth, visualHeight, used, view, color, colors) - if view == viewConsumptionPace { - visual = renderConsumptionPaceMeterSized(innerWidth, visualHeight, meter.Window, now, colors) + if isQuotaGraph(view) { + options := quotaPlotOptions{} + if len(graphs) > 0 { + options = graphs[0] + } + visual = renderQuotaPlot(innerWidth, visualHeight, meter.Window, now, view == viewPace, options, colors) } gaugeWidth := min(max(lipgloss.Width(visual), 1), innerWidth) resetGauge := renderResetGauge(innerWidth, gaugeWidth, meter.Window, now, reset, color, colors) + if isQuotaGraph(view) { + resetGauge = colors.dimmed().Render(reset) + } if view == viewFuel { gaugeWidth = max(innerWidth-6, 1) resetGauge = renderReverseResetGauge(innerWidth, gaugeWidth, meter.Window, now, reset, color, colors) @@ -274,18 +295,6 @@ func resetProgress(window codex.Window, now time.Time) (int, bool) { return min(max(progress, 0), 100), true } -// consumptionPace compares how much of the window has elapsed with how much -// quota has been consumed. Positive headroom means consumption is behind time; -// negative headroom means quota is being consumed too quickly. -func consumptionPace(window codex.Window, now time.Time) (int, bool) { - elapsed, ok := resetProgress(window, now) - if !ok { - return 0, false - } - used := min(max(window.UsedPercent, 0), 100) - return min(max(elapsed-used, -100), 100), true -} - func meterColor(used int, colors palette) imagecolor.Color { if used >= 90 { return colors.danger @@ -304,8 +313,10 @@ func renderVisualizationSized(width, height, used int, view meterViewID, color i switch view { case viewPie: return renderPieSized(width, height, used, color, colors) - case viewConsumptionPace: - return renderConsumptionPaceSized(width, height, 0, false, colors) + case viewPace: + return renderQuotaPlot(width, height, codex.Window{}, time.Now(), true, quotaPlotOptions{}, colors) + case viewZone: + return renderQuotaPlot(width, height, codex.Window{}, time.Now(), false, quotaPlotOptions{}, colors) case viewFuel: return renderFuelTankSized(width, height, used, color, colors) case viewBars: @@ -433,234 +444,6 @@ func radialCanvasSize(width, height, legendWidth int) (int, int) { return cellWidth, cellHeight } -func renderConsumptionPaceSized(width, height, pace int, available bool, colors palette) string { - return renderConsumptionPaceWithProjectionSized(width, height, pace, available, "", colors) -} - -func renderConsumptionPaceMeterSized(width, height int, window codex.Window, now time.Time, colors palette) string { - pace, available := consumptionPace(window, now) - projection := formatConsumptionProjection(consumptionProjectionFor(window, now)) - return renderConsumptionPaceWithProjectionSized(width, height, pace, available, projection, colors) -} - -func renderConsumptionPaceWithProjectionSized(width, height, pace int, available bool, projection string, colors palette) string { - width = max(width, 1) - pace = min(max(pace, -100), 100) - center := (width - 1) / 2 - marker := center - if available && width > 1 { - marker = int(math.Round(float64(pace+100) * float64(width-1) / 200)) - } - - markerColor := colors.accent - if pace > 0 { - markerColor = colors.primary - } else if pace < 0 { - markerColor = colors.danger - } - axis := paceAxis(width, center, marker, pace, available, markerColor, colors) - - status := i18n.Text("PACE DATA UNAVAILABLE") - if available { - switch { - case pace > 0: - status = i18n.Format("HEADROOM %+d POINTS // UNDER PACE", pace) - case pace < 0: - status = i18n.Format("DEFICIT %+d POINTS // OVER PACE", pace) - default: - status = i18n.Text("BALANCED +0 POINTS // ON PACE") - } - } - status = lipgloss.NewStyle().Bold(true).Foreground(markerColor).Render(ansi.Truncate(status, width, "")) - caption := colors.dimmed().Render(ansi.Truncate(i18n.Text("CONSUMPTION PACE // TIME - USAGE"), width, "")) - labels := colors.dimmed().Render(paceScaleLabels(width)) - - lines := []string{caption, labels, axis, status} - if projection != "" { - lines = append(lines, colors.dimmed().Render(ansi.Truncate(projection, width, ""))) - } - if height <= 0 { - height = len(lines) - } - switch height { - case 1: - lines = lines[2:3] - case 2: - lines = lines[2:4] - case 3: - if projection != "" { - lines = lines[2:] - } else { - lines = lines[1:] - } - case 4: - if projection != "" { - lines = lines[1:] - } - } - return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center, strings.Join(lines, "\n")) -} - -type consumptionProjectionKind int - -const ( - consumptionProjectionUnavailable consumptionProjectionKind = iota - consumptionProjectionNoBurn - consumptionProjectionSafe - consumptionProjectionEarly - consumptionProjectionExhausted -) - -type consumptionProjection struct { - kind consumptionProjectionKind - timeToExhaustion time.Duration - earlyBy time.Duration - projectedRemaining int -} - -func consumptionProjectionFor(window codex.Window, now time.Time) consumptionProjection { - if window.WindowDurationMins == nil || window.ResetsAt == nil || *window.WindowDurationMins <= 0 { - return consumptionProjection{kind: consumptionProjectionUnavailable} - } - duration := time.Duration(*window.WindowDurationMins) * time.Minute - resetAt := time.Unix(*window.ResetsAt, 0) - elapsed := now.Sub(resetAt.Add(-duration)) - if elapsed <= 0 || elapsed >= duration { - return consumptionProjection{kind: consumptionProjectionUnavailable} - } - used := min(max(window.UsedPercent, 0), 100) - if used == 0 { - return consumptionProjection{kind: consumptionProjectionNoBurn} - } - if used >= 100 { - return consumptionProjection{kind: consumptionProjectionExhausted} - } - timeToExhaustion := time.Duration(float64(elapsed) * float64(100-used) / float64(used)) - exhaustsAt := now.Add(timeToExhaustion) - if !exhaustsAt.Before(resetAt) { - projectedUsed := float64(used) * float64(duration) / float64(elapsed) - projectedRemaining := int(math.Round(100 - projectedUsed)) - return consumptionProjection{ - kind: consumptionProjectionSafe, - timeToExhaustion: timeToExhaustion, - projectedRemaining: min(max(projectedRemaining, 0), 100), - } - } - return consumptionProjection{ - kind: consumptionProjectionEarly, - timeToExhaustion: timeToExhaustion, - earlyBy: resetAt.Sub(exhaustsAt), - } -} - -func formatConsumptionProjection(projection consumptionProjection) string { - switch projection.kind { - case consumptionProjectionNoBurn: - return "LINEAR PROJECTION // NO BURN YET" - case consumptionProjectionSafe: - return i18n.Format("LINEAR PROJECTION // SAFE THROUGH RESET // ~%d%% LEFT", projection.projectedRemaining) - case consumptionProjectionEarly: - return i18n.Format("LINEAR PROJECTION // LIMIT IN ~%s // %s EARLY", - projectionDuration(projection.timeToExhaustion), projectionDuration(projection.earlyBy)) - case consumptionProjectionExhausted: - return "LINEAR PROJECTION // LIMIT REACHED" - default: - return "" - } -} - -func projectionDuration(duration time.Duration) string { - if duration < time.Minute { - return "<1M" - } - minutes := int(math.Round(duration.Minutes())) - days := minutes / (24 * 60) - hours := minutes / 60 % 24 - remainingMinutes := minutes % 60 - switch { - case days > 0 && hours > 0: - return fmt.Sprintf("%dD %dH", days, hours) - case days > 0: - return fmt.Sprintf("%dD", days) - case hours > 0 && remainingMinutes > 0: - return fmt.Sprintf("%dH %dM", hours, remainingMinutes) - case hours > 0: - return fmt.Sprintf("%dH", hours) - default: - return fmt.Sprintf("%dM", remainingMinutes) - } -} - -func paceAxis(width, center, marker, pace int, available bool, markerColor imagecolor.Color, colors palette) string { - if width < 3 { - value := "?" - color := colors.warning - if available { - value = "▲" - color = markerColor - } - return lipgloss.PlaceHorizontal(width, lipgloss.Center, lipgloss.NewStyle().Bold(true).Foreground(color).Render(value)) - } - - carriageStart := min(max(marker-1, 0), width-3) - carriageEnd := carriageStart + 2 - carriage := "[▲]" - if !available { - carriage = "[?]" - markerColor = colors.warning - } - carriageRunes := []rune(carriage) - carriageStyle := lipgloss.NewStyle().Bold(true).Foreground(colors.background).Background(markerColor) - trailStyle := lipgloss.NewStyle().Foreground(markerColor) - dimmed := colors.dimmed() - - var axis strings.Builder - for column := 0; column < width; column++ { - if column >= carriageStart && column <= carriageEnd { - axis.WriteString(carriageStyle.Render(string(carriageRunes[column-carriageStart]))) - continue - } - r := '─' - switch column { - case 0: - r = '├' - case center: - r = '┼' - case width - 1: - r = '┤' - } - inTrail := available && ((pace > 0 && column > center && column < carriageStart) || - (pace < 0 && column < center && column > carriageEnd)) - if inTrail { - axis.WriteString(trailStyle.Render("━")) - continue - } - axis.WriteString(dimmed.Render(string(r))) - } - return axis.String() -} - -func paceScaleLabels(width int) string { - if width < 11 { - return ansi.Truncate(lipgloss.PlaceHorizontal(width, lipgloss.Center, "- 0 +"), width, "") - } - labels := make([]rune, width) - for index := range labels { - labels[index] = ' ' - } - writeAt := func(offset int, value string) { - for index, r := range []rune(value) { - if offset+index >= 0 && offset+index < len(labels) { - labels[offset+index] = r - } - } - } - writeAt(0, "-100") - writeAt((width-1)/2, "0") - writeAt(width-4, "+100") - return string(labels) -} - func verticallyCenterLines(lines []string, height int) []string { if height <= len(lines) { return lines[:height] diff --git a/internal/ui/meters_test.go b/internal/ui/meters_test.go index 97ecfd0..f9b0918 100644 --- a/internal/ui/meters_test.go +++ b/internal/ui/meters_test.go @@ -12,7 +12,7 @@ import ( "github.com/merefield/codexometer/internal/codex" ) -var quotaMeterViews = []meterViewID{viewBars, viewConsumptionPace, viewPie, viewFuel} +var quotaMeterViews = []meterViewID{viewBars, viewPace, viewZone, viewPie, viewFuel} func TestEveryMeterViewHasDistinctiveOutput(t *testing.T) { colors := paletteFor(themeHacker) @@ -22,7 +22,8 @@ func TestEveryMeterViewHasDistinctiveOutput(t *testing.T) { }{ {viewBars, "█"}, {viewPie, "BRAILLE PIE"}, - {viewConsumptionPace, "PACE DATA UNAVAILABLE"}, + {viewPace, "GRAPH DATA UNAVAILABLE"}, + {viewZone, "GRAPH DATA UNAVAILABLE"}, {viewFuel, "RANGE"}, } for _, test := range tests { @@ -55,7 +56,7 @@ func TestEveryViewIncludesResetCycleGauge(t *testing.T) { Name: "1 HOUR", Window: codex.Window{UsedPercent: 40, WindowDurationMins: &duration, ResetsAt: &reset}, } - for _, view := range quotaMeterViews { + for _, view := range []meterViewID{viewBars, viewPie, viewFuel} { output := ansi.Strip(renderMeterArea(80, 18, meter, view, colors)) if !strings.Contains(output, "RESET CYCLE 50%") { t.Errorf("%s missing reset-cycle comparison gauge:\n%s", view.name(), output) @@ -152,55 +153,6 @@ func TestResetProgressUsesWindowStartAndClamps(t *testing.T) { } } -func TestConsumptionPaceComparesElapsedTimeWithUsage(t *testing.T) { - duration := int64(100) - reset := time.Unix(20_000, 0) - now := reset.Add(-50 * time.Minute) - window := codex.Window{UsedPercent: 25, WindowDurationMins: &duration, ResetsAt: ptr(reset.Unix())} - if got, ok := consumptionPace(window, now); !ok || got != 25 { - t.Fatalf("under-pace consumption = %d, %v; want +25, true", got, ok) - } - window.UsedPercent = 75 - if got, ok := consumptionPace(window, now); !ok || got != -25 { - t.Fatalf("over-pace consumption = %d, %v; want -25, true", got, ok) - } - if _, ok := consumptionPace(codex.Window{UsedPercent: 25}, now); ok { - t.Fatal("missing window timing unexpectedly produced a consumption pace") - } -} - -func TestConsumptionProjectionDistinguishesSafeEarlyAndUnavailable(t *testing.T) { - duration := int64(100) - reset := time.Unix(20_000, 0) - now := reset.Add(-50 * time.Minute) - window := codex.Window{UsedPercent: 25, WindowDurationMins: &duration, ResetsAt: ptr(reset.Unix())} - projection := consumptionProjectionFor(window, now) - if projection.kind != consumptionProjectionSafe || projection.projectedRemaining != 50 { - t.Fatalf("safe projection = %#v, want 50%% remaining", projection) - } - if label := formatConsumptionProjection(projection); !strings.Contains(label, "SAFE THROUGH RESET") || !strings.Contains(label, "~50% LEFT") { - t.Fatalf("safe projection label = %q", label) - } - - window.UsedPercent = 75 - projection = consumptionProjectionFor(window, now) - if projection.kind != consumptionProjectionEarly || projection.timeToExhaustion < 16*time.Minute || projection.timeToExhaustion > 17*time.Minute || - projection.earlyBy < 33*time.Minute || projection.earlyBy > 34*time.Minute { - t.Fatalf("early projection = %#v, want limit in about 17m and 33m early", projection) - } - if label := formatConsumptionProjection(projection); !strings.Contains(label, "LIMIT IN ~17M") || !strings.Contains(label, "33M EARLY") { - t.Fatalf("early projection label = %q", label) - } - - window.UsedPercent = 0 - if label := formatConsumptionProjection(consumptionProjectionFor(window, now)); !strings.Contains(label, "NO BURN YET") { - t.Fatalf("zero-burn projection label = %q", label) - } - if label := formatConsumptionProjection(consumptionProjectionFor(codex.Window{UsedPercent: 25}, now)); label != "" { - t.Fatalf("unavailable projection was displayed: %q", label) - } -} - func TestMonthlyCreditMeterShowsDetailsWithoutInventingCycleProgress(t *testing.T) { colors := paletteFor(themeHacker) reset := time.Now().Add(14 * 24 * time.Hour).Unix() @@ -311,46 +263,6 @@ func TestFuelTankStatsFollowReverseGaugeDirection(t *testing.T) { } } -func TestConsumptionPaceGaugeUsesSignedHorizontalAxis(t *testing.T) { - colors := paletteFor(themeHacker) - positive := ansi.Strip(renderConsumptionPaceSized(41, 4, 25, true, colors)) - negative := ansi.Strip(renderConsumptionPaceSized(41, 4, -25, true, colors)) - for _, want := range []string{"-100", "+100", "HEADROOM +25 POINTS", "CONSUMPTION PACE"} { - if !strings.Contains(positive, want) { - t.Fatalf("positive pace gauge missing %q: %q", want, positive) - } - } - if !strings.Contains(negative, "DEFICIT -25 POINTS") { - t.Fatalf("negative pace gauge missing deficit: %q", negative) - } - if !strings.Contains(positive, "[▲]") || !strings.Contains(negative, "[▲]") { - t.Fatalf("pace gauges do not use the highlighted sliding carriage: positive=%q negative=%q", positive, negative) - } - if !strings.Contains(positive, "━") || !strings.Contains(negative, "━") { - t.Fatalf("pace gauges do not show a trail from zero: positive=%q negative=%q", positive, negative) - } - positiveMarker := runeColumn(lineContainingRune(positive, '▲'), '▲') - negativeMarker := runeColumn(lineContainingRune(negative, '▲'), '▲') - if positiveMarker <= 20 || negativeMarker >= 20 { - t.Fatalf("pace markers did not straddle zero: negative=%d positive=%d", negativeMarker, positiveMarker) - } -} - -func TestConsumptionPaceCarriageStaysInsideResponsiveAxis(t *testing.T) { - colors := paletteFor(themeNightshade) - for _, width := range []int{1, 2, 3, 4, 9, 20, 41, 80} { - for _, pace := range []int{-100, -25, 0, 25, 100} { - output := renderConsumptionPaceSized(width, 1, pace, true, colors) - if got := lipgloss.Width(output); got != width { - t.Errorf("width %d pace %d rendered width %d: %q", width, pace, got, ansi.Strip(output)) - } - if got := lipgloss.Height(output); got != 1 { - t.Errorf("width %d pace %d rendered height %d", width, pace, got) - } - } - } -} - func TestRadialGraphicsUseLargeCircularCanvases(t *testing.T) { colors := paletteFor(themeUltraviolet) plain := ansi.Strip(renderPie(52, 62, colors.primary, colors)) diff --git a/internal/ui/model.go b/internal/ui/model.go index b906b71..6d1c330 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -13,6 +13,7 @@ import ( "github.com/merefield/codexometer/internal/codex" "github.com/merefield/codexometer/internal/i18n" + "github.com/merefield/codexometer/internal/quotagraph" "github.com/merefield/codexometer/internal/version" ) @@ -105,6 +106,9 @@ type Model struct { theme themeID meterView meterViewID quotaMeterView meterViewID + quotaGraphs quotagraph.History + quotaGraphMode quotagraph.Mode + quotaGraphHideTrace bool hoveredMainTab mainTabID mainTabHovered bool hoveredView meterViewID @@ -392,6 +396,8 @@ const ( footerButtonBenchmarkDone footerButtonBenchmarkScope footerButtonBenchmarkStop + footerButtonQuotaTrend + footerButtonQuotaTrace ) type footerButton struct { @@ -608,6 +614,14 @@ func (m Model) Update(message tea.Msg) (tea.Model, tea.Cmd) { } return m, nil case tea.KeyPressMsg: + if isQuotaGraph(m.meterView) { + switch strings.ToLower(message.String()) { + case "g": + return m.pressFooterButton(footerButtonQuotaTrend) + case "h": + return m.pressFooterButton(footerButtonQuotaTrace) + } + } if message.IsRepeat && m.meterView == viewMonitor && (strings.EqualFold(message.String(), "c") || len(message.String()) == 1 && message.String()[0] >= '1' && message.String()[0] <= '8') { return m, nil } @@ -1032,6 +1046,11 @@ func (m Model) Update(message tea.Msg) (tea.Model, tea.Cmd) { } m.loading = false m.err = message.err + observedAt := message.at + if observedAt.IsZero() { + observedAt = time.Now() + } + m.quotaGraphs.Observe(message.snapshot, message.err != nil, observedAt) var quotaStepCommand tea.Cmd if message.err == nil { if m.snapshot.AccountFingerprint != message.snapshot.AccountFingerprint { @@ -1356,6 +1375,14 @@ func (m Model) pressMonitorSessionDismiss(id string) (tea.Model, tea.Cmd) { func (m Model) activateFooterButton(button footerButtonID) (Model, tea.Cmd) { switch button { + case footerButtonQuotaTrend: + if isQuotaGraph(m.meterView) { + m.cycleQuotaTrend() + } + case footerButtonQuotaTrace: + if isQuotaGraph(m.meterView) { + m.quotaGraphHideTrace = !m.quotaGraphHideTrace + } case footerButtonTheme: m.theme = m.theme.next() m.prepareBenchmarkDetailTranscript() @@ -1486,6 +1513,9 @@ func (m Model) footerButtonAt(x, y int) footerButtonID { if m.meterView != viewUsage && m.loading && len(m.snapshot.Meters()) == 0 { return footerButtonNone } + if button := m.quotaGraphButtonAt(x, y); button != footerButtonNone { + return button + } if m.meterView == viewMonitor { if button := m.monitorButtonAt(x, y); button != footerButtonNone { return button @@ -1543,6 +1573,9 @@ func (m Model) dashboardLayout() dashboardGeometry { if m.meterView.isQuota() { tabsHeight++ } + if isQuotaGraph(m.meterView) { + tabsHeight++ + } tabsHeight += m.resetControlsLayout(contentWidth).extraRows const framedErrorHeight = 3 const footerHeight = 2 @@ -1555,8 +1588,9 @@ func (m Model) dashboardLayout() dashboardGeometry { if len(meters) == 0 && m.meterView != viewUsage && m.meterView != viewResets && m.meterView != viewThresholds { extraHeight += framedErrorHeight } - if (m.meterView == viewBars || m.meterView == viewConsumptionPace || m.meterView == viewFuel) && len(meters) > 0 { - minimumMeterHeight := len(meters) * 3 + if (m.meterView == viewBars || m.meterView == viewFuel || isQuotaGraph(m.meterView)) && len(meters) > 0 { + columns := meterGridColumns(contentWidth, contentHeight, len(meters), m.meterView) + minimumMeterHeight := ((len(meters) + columns - 1) / columns) * 3 meterHeightWithSpacer := contentHeight - headerHeight - statusHeight - tabsHeight - extraHeight - footerHeight meterHeightWithoutSpacer := meterHeightWithSpacer + statusHeight if meterHeightWithSpacer < minimumMeterHeight && meterHeightWithoutSpacer >= minimumMeterHeight { diff --git a/internal/ui/model_test.go b/internal/ui/model_test.go index e61cac5..ff728b3 100644 --- a/internal/ui/model_test.go +++ b/internal/ui/model_test.go @@ -271,7 +271,7 @@ func TestFooterButtonsSupportHoverAndMouseClicks(t *testing.T) { updated, command = model.Update(footerMouseMessage(t, model, footerButtonView, true)) model = updated.(Model) - if command == nil || model.meterView != viewConsumptionPace || model.flashedButton != footerButtonView { + if command == nil || model.meterView != viewPace || model.flashedButton != footerButtonView { t.Fatalf("view click selected view=%d flash=%d", model.meterView, model.flashedButton) } diff --git a/internal/ui/preferences.go b/internal/ui/preferences.go index eaaefd5..562ccb7 100644 --- a/internal/ui/preferences.go +++ b/internal/ui/preferences.go @@ -145,7 +145,7 @@ func reverseThemePreferences(values map[themeID]string) map[string]themeID { } var quotaViewPreferenceNames = map[meterViewID]string{ - viewBars: "bars", viewPie: "pie", viewConsumptionPace: "consumption-pace", viewFuel: "fuel-tank", viewResets: "resets", viewThresholds: "thresholds", + viewBars: "bars", viewPie: "pie", viewPace: "pace", viewZone: "zone", viewFuel: "fuel-tank", viewResets: "resets", viewThresholds: "thresholds", } var quotaViewPreferenceIDs = reverseViewPreferences(quotaViewPreferenceNames) @@ -155,6 +155,10 @@ func reverseViewPreferences(values map[meterViewID]string) map[string]meterViewI for id, value := range values { reversed[value] = id } + // Preserve the previous Consumption Pace preference after the rename. + if _, ok := values[viewPace]; ok { + reversed["consumption-pace"] = viewPace + } return reversed } diff --git a/internal/ui/quota_graph.go b/internal/ui/quota_graph.go new file mode 100644 index 0000000..4711743 --- /dev/null +++ b/internal/ui/quota_graph.go @@ -0,0 +1,295 @@ +package ui + +import ( + "fmt" + "image/color" + "math" + "strings" + "time" + + "charm.land/lipgloss/v2" + "github.com/charmbracelet/x/ansi" + "github.com/merefield/codexometer/internal/codex" + "github.com/merefield/codexometer/internal/i18n" + "github.com/merefield/codexometer/internal/quotagraph" +) + +func isQuotaGraph(view meterViewID) bool { return view == viewPace || view == viewZone } + +type quotaPlotOptions struct { + points []quotagraph.Point + mode quotagraph.Mode + hideTrace bool +} + +func (m Model) quotaPlotOptions() []quotaPlotOptions { + keys := quotagraph.Keys(m.snapshot) + options := make([]quotaPlotOptions, len(keys)) + for i, key := range keys { + options[i] = quotaPlotOptions{points: m.quotaGraphs.Series[key].Points, mode: m.quotaGraphMode, hideTrace: m.quotaGraphHideTrace} + } + return options +} + +func (m *Model) cycleQuotaTrend() { + options := m.quotaPlotOptions() + meters := m.snapshot.Meters() + for step := 1; step <= int(quotagraph.ModeCount); step++ { + candidate := (m.quotaGraphMode + quotagraph.Mode(step)) % quotagraph.ModeCount + available := candidate == quotagraph.WindowStart || candidate == quotagraph.Off + for i, meter := range meters { + available = available || quotagraph.Available(candidate, meter.Window, options[i].points, time.Now()) + } + if available { + m.quotaGraphMode = candidate + return + } + } +} + +// Rendering and hit testing consume the same labels and widths. +func (m Model) quotaGraphButtons(width int) []footerButton { + trace := "ON" + if m.quotaGraphHideTrace { + trace = "OFF" + } + trend := i18n.Text(m.quotaGraphMode.Label()) + buttons := []footerButton{ + {id: footerButtonQuotaTrend, label: i18n.Format("[ (G)TREND // %s ]", trend), compact: fmt.Sprintf("[G:%s]", trend)}, + {id: footerButtonQuotaTrace, label: i18n.Format("[ (H)TRACE // %s ]", i18n.Text(trace)), compact: fmt.Sprintf("[H:%s]", i18n.Text(trace))}, + } + if lipgloss.Width(buttons[0].label)+lipgloss.Width(buttons[1].label)+1 > width { + for i := range buttons { + buttons[i].label = buttons[i].compact + } + } + if lipgloss.Width(buttons[0].label)+lipgloss.Width(buttons[1].label)+1 > width { + buttons[0].label, buttons[1].label = "[G]", "[H]" + } + if width < 7 { + return buttons[:0] + } + return buttons +} + +func (m Model) renderQuotaGraphControls(width int, colors palette) string { + var parts []string + for _, button := range m.quotaGraphButtons(width) { + parts = append(parts, footerButtonAppearance(colors, m.hoveredButton == button.id, m.flashedButton == button.id).Render(button.label)) + } + return strings.Join(parts, " ") +} + +func (m Model) quotaGraphButtonAt(x, y int) footerButtonID { + if !isQuotaGraph(m.meterView) || y != m.dashboardLayout().quotaTabsY+1 { + return footerButtonNone + } + x -= 2 + start := 0 + for _, button := range m.quotaGraphButtons(m.dashboardLayout().contentWidth) { + width := lipgloss.Width(button.label) + if x >= start && x < start+width { + return button.id + } + start += width + 1 + } + return footerButtonNone +} + +type quotaPlotCell struct { + mask int + ink color.Color + priority int + symbol rune +} + +type quotaCanvas struct { + width, height int + pace bool + cells []quotaPlotCell +} + +func (c quotaCanvas) position(x, y float64) (int, int) { + if c.pace { + y = (y + 100) / 2 + } + return int(math.Round(x / 100 * float64(c.width*2-1))), int(math.Round((1 - y/100) * float64(c.height*4-1))) +} + +func (c *quotaCanvas) pixel(x, y int, ink color.Color, priority int) { + if x < 0 || y < 0 || x >= c.width*2 || y >= c.height*4 { + return + } + cell := &c.cells[(y/4)*c.width+x/2] + if priority < cell.priority { + return + } + if priority > cell.priority { + cell.mask, cell.symbol = 0, 0 + } + cell.priority, cell.ink = priority, ink + cell.mask |= brailleDot(x%2, y%4) +} + +func (c *quotaCanvas) line(x1, y1, x2, y2 float64, ink color.Color, priority, dash int) { + xa, ya := c.position(x1, y1) + xb, yb := c.position(x2, y2) + steps := max(int(math.Abs(float64(xb-xa))), int(math.Abs(float64(yb-ya))), 1) + for i := 0; i <= steps; i++ { + if dash > 0 && (i/dash)%2 != 0 { + continue + } + x := xa + int(math.Round(float64(xb-xa)*float64(i)/float64(steps))) + y := ya + int(math.Round(float64(yb-ya)*float64(i)/float64(steps))) + c.pixel(x, y, ink, priority) + } +} + +func (c *quotaCanvas) mark(x, y float64, symbol rune, ink color.Color, priority int) { + px, py := c.position(x, y) + if px < 0 || py < 0 || px >= c.width*2 || py >= c.height*4 { + return + } + cell := &c.cells[(py/4)*c.width+px/2] + if priority >= cell.priority { + cell.symbol, cell.ink, cell.priority = symbol, ink, priority + } +} + +func quotaFieldColor(risk float64) color.Color { + amber := color.RGBA{R: 83, G: 66, B: 25, A: 255} + end := color.RGBA{R: 66, G: 27, B: 37, A: 255} + if risk < 0 { + end = color.RGBA{R: 17, G: 52, B: 32, A: 255} + } + weight := min(math.Abs(risk), 1) + return color.RGBA{R: uint8(float64(amber.R)*(1-weight) + float64(end.R)*weight), G: uint8(float64(amber.G)*(1-weight) + float64(end.G)*weight), B: uint8(float64(amber.B)*(1-weight) + float64(end.B)*weight), A: 255} +} + +func renderQuotaPlot(width, height int, window codex.Window, now time.Time, pace bool, options quotaPlotOptions, colors palette) string { + width = max(width, 1) + if height <= 0 { + height = 10 + } + elapsed, known := quotagraph.Elapsed(window, now) + if !known { + return quotaPlotFit([]string{colors.dimmed().Render(i18n.Text("GRAPH DATA UNAVAILABLE"))}, width, height) + } + used := float64(max(0, min(100, window.UsedPercent))) + status := i18n.Format("%.0f%% USED // %.1f%% TIME", used, elapsed) + if pace { + status = i18n.Format("%+.1f PP FROM SAFETY", used-elapsed) + } + trend, projected := quotagraph.Project(options.mode, window, options.points, now) + trendInk := colors.dim + if projected { + trendInk = lipgloss.Color("#9D2537") + if trend.Projected <= 100 { + trendInk = lipgloss.Color("#45DB79") + } + } + projection := "" + if projected { + projection = i18n.Format("TREND // %.1f%% AT RESET", trend.Projected) + if pace { + projection = i18n.Format("TREND // %+.1f PP AT RESET", trend.Projected-100) + } + } else if options.mode != quotagraph.Off { + projection = i18n.Text("TREND UNAVAILABLE") + } + status = colors.label().Render(ansi.Truncate(status, width, "")) + projection = lipgloss.NewStyle().Foreground(trendInk).Render(ansi.Truncate(projection, width, "")) + if width < 18 || height < 6 { + return quotaPlotFit([]string{status, projection}, width, height) + } + plotWidth, plotHeight := width-6, height-4 + canvas := quotaCanvas{width: plotWidth, height: plotHeight, pace: pace, cells: make([]quotaPlotCell, plotWidth*plotHeight)} + value := func(x, y float64) float64 { + if pace { + return y - x + } + return y + } + guide := lipgloss.Color("#708276") + canvas.line(0, 0, 100, value(100, 100), guide, 1, 0) + if !options.hideTrace { + for i, point := range options.points { + if i > 0 && !point.Break { + before := options.points[i-1] + canvas.line(before.Elapsed, value(before.Elapsed, float64(before.Used)), point.Elapsed, value(point.Elapsed, float64(point.Used)), lipgloss.Color("#BAD4C7"), 2, 0) + } + } + if len(options.points) > 0 { + p := options.points[0] + canvas.mark(p.Elapsed, value(p.Elapsed, float64(p.Used)), '○', guide, 2) + } + } + if projected { + if segment, ok := trend.Segment(pace); ok { + canvas.line(segment.X1, segment.Y1, segment.X2, segment.Y2, trendInk, 3, 0) + } + } + canvas.mark(elapsed, value(elapsed, used), '●', lipgloss.Color("#FFFFFF"), 4) + caption := i18n.Text("CONSUMPTION") + if pace { + caption = i18n.Text("DISTANCE FROM SAFETY (PP)") + } + lines := []string{colors.dimmed().Render(ansi.Truncate(caption, width, ""))} + for row := 0; row < plotHeight; row++ { + label := " " + if row == 0 { + if pace { + label = "+100" + } else { + label = "100%" + } + } else if row == plotHeight-1 { + if pace { + label = "-100" + } else { + label = " 0%" + } + } else if row == plotHeight/2 && pace { + label = " 0" + } + var line strings.Builder + line.WriteString(colors.dimmed().Render(label + "│")) + for col := 0; col < plotWidth; col++ { + cell := canvas.cells[row*plotWidth+col] + x := float64(col) / float64(max(plotWidth-1, 1)) + y := 1 - float64(row)/float64(max(plotHeight-1, 1)) + risk := y - x + if pace { + risk = 2*y - 1 + } + style := lipgloss.NewStyle().Background(quotaFieldColor(risk)) + r := ' ' + if cell.mask != 0 { + r = rune(0x2800 + cell.mask) + } + if cell.symbol != 0 { + r = cell.symbol + } + if cell.ink != nil { + style = style.Foreground(cell.ink) + } + line.WriteString(style.Render(string(r))) + } + lines = append(lines, line.String()) + } + lines = append(lines, colors.dimmed().Render(" 0%"+lipgloss.PlaceHorizontal(max(plotWidth-6, 0), lipgloss.Center, ansi.Truncate(i18n.Text("TIME ELAPSED"), max(plotWidth-6, 0), ""))+"100%"), status, projection) + return quotaPlotFit(lines, width, height) +} + +func quotaPlotFit(lines []string, width, height int) string { + for len(lines) < height { + lines = append(lines, "") + } + if len(lines) > height { + lines = lines[:height] + } + for i := range lines { + lines[i] = ansi.Truncate(lines[i], width, "") + } + return strings.Join(lines, "\n") +} diff --git a/internal/ui/quota_graph_test.go b/internal/ui/quota_graph_test.go new file mode 100644 index 0000000..0275671 --- /dev/null +++ b/internal/ui/quota_graph_test.go @@ -0,0 +1,259 @@ +package ui + +import ( + "errors" + "fmt" + "strings" + "testing" + "time" + + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" + "github.com/charmbracelet/x/ansi" + "github.com/merefield/codexometer/internal/codex" + "github.com/merefield/codexometer/internal/quotagraph" +) + +func graphWindow(now time.Time, used int) codex.Window { + duration, reset := int64(100), now.Add(50*time.Minute).Unix() + return codex.Window{UsedPercent: used, WindowDurationMins: &duration, ResetsAt: &reset} +} + +func TestQuotaPlotsFitResponsiveRectangles(t *testing.T) { + now := time.Unix(1800000000, 0) + for _, pace := range []bool{false, true} { + for _, width := range []int{1, 9, 17, 18, 30, 80, 140} { + for _, height := range []int{1, 2, 5, 6, 12, 30} { + for _, used := range []int{0, 25, 75, 100} { + out := renderQuotaPlot(width, height, graphWindow(now, used), now, pace, quotaPlotOptions{}, paletteFor(themeHacker)) + if lipgloss.Width(out) > width || lipgloss.Height(out) != height { + t.Fatalf("pace=%v %dx%d used=%d rendered %dx%d", pace, width, height, used, lipgloss.Width(out), lipgloss.Height(out)) + } + if width >= 18 && height >= 6 && !strings.Contains(out, "●") { + t.Fatal("current dot missing") + } + } + } + } + } +} + +func TestQuotaPlotAxesAndProjection(t *testing.T) { + now := time.Unix(1800000000, 0) + w := graphWindow(now, 25) + zone := ansi.Strip(renderQuotaPlot(60, 15, w, now, false, quotaPlotOptions{}, paletteFor(themeHacker))) + pace := ansi.Strip(renderQuotaPlot(60, 15, w, now, true, quotaPlotOptions{}, paletteFor(themeHacker))) + for _, want := range []string{"100%", "TIME ELAPSED", "25% USED // 50.0% TIME", "50.0% AT RESET"} { + if !strings.Contains(zone, want) { + t.Fatalf("Zone missing %q:\n%s", want, zone) + } + } + for _, want := range []string{"+100", "-100", "-25.0 PP FROM SAFETY", "-50.0 PP AT RESET"} { + if !strings.Contains(pace, want) { + t.Fatalf("Pace missing %q:\n%s", want, pace) + } + } + for _, graph := range []string{zone, pace} { + if strings.ContainsAny(graph, "↗↘▸") { + t.Fatal("terminal graph still contains trend arrows") + } + } + off := ansi.Strip(renderQuotaPlot(60, 15, w, now, false, quotaPlotOptions{mode: quotagraph.Off}, paletteFor(themeHacker))) + if strings.Join(strings.Split(zone, "\n")[1:12], "\n") == strings.Join(strings.Split(off, "\n")[1:12], "\n") { + t.Fatal("removing arrows also removed the trend line") + } + if strings.Contains(renderQuotaPlot(60, 15, w, now, true, quotaPlotOptions{mode: quotagraph.Off}, paletteFor(themeHacker)), "AT RESET") { + t.Fatal("Off still rendered projection") + } +} + +func TestQuotaUnavailableTrendIsMuted(t *testing.T) { + now := time.Unix(1800000000, 0) + for theme := themeHacker; theme < themeCount; theme++ { + colors := paletteFor(theme) + for _, pace := range []bool{false, true} { + for _, options := range []quotaPlotOptions{{mode: quotagraph.HalfHour}, {mode: quotagraph.Hour}, {mode: quotagraph.Day}} { + out := renderQuotaPlot(60, 15, graphWindow(now, 25), now, pace, options, colors) + if got, want := strings.Split(out, "\n")[14], colors.dimmed().Render("TREND UNAVAILABLE"); got != want { + t.Fatalf("theme=%v pace=%v mode=%v unavailable trend is not muted: %q", theme, pace, options.mode, got) + } + } + for _, test := range []struct { + used int + ink string + projected float64 + }{ + {25, "#45DB79", 50}, + {50, "#45DB79", 100}, + {75, "#9D2537", 150}, + } { + out := renderQuotaPlot(60, 15, graphWindow(now, test.used), now, pace, quotaPlotOptions{}, colors) + text := fmt.Sprintf("TREND // %.1f%% AT RESET", test.projected) + if pace { + text = fmt.Sprintf("TREND // %+.1f PP AT RESET", test.projected-100) + } + want := lipgloss.NewStyle().Foreground(lipgloss.Color(test.ink)).Render(text) + if got := strings.Split(out, "\n")[14]; got != want { + t.Fatalf("valid projection lost its endpoint colour: %q", got) + } + } + } + } +} + +func TestQuotaLinesUseDenseBrailleWithoutDashGaps(t *testing.T) { + now := time.Unix(1800000000, 0) + w := graphWindow(now, 75) + const width, height = 60, 15 + for _, pace := range []bool{false, true} { + out := ansi.Strip(renderQuotaPlot(width, height, w, now, pace, quotaPlotOptions{hideTrace: true}, paletteFor(themeHacker))) + if strings.ContainsAny(out, "╭╮╰╯↗↘▸") { + t.Fatal("dense Braille graph contains box-drawing elbows or arrows") + } + trend, ok := quotagraph.Project(quotagraph.WindowStart, w, nil, now) + if !ok { + t.Fatal("missing test projection") + } + segment, ok := trend.Segment(pace) + if !ok { + t.Fatal("missing test segment") + } + canvas := quotaCanvas{width: width - 6, height: height - 4, pace: pace, cells: make([]quotaPlotCell, (width-6)*(height-4))} + guideEnd := 100.0 + if pace { + guideEnd = 0 + } + canvas.line(0, 0, 100, guideEnd, nil, 1, 0) + canvas.line(segment.X1, segment.Y1, segment.X2, segment.Y2, nil, 3, 0) + elapsed, _ := quotagraph.Elapsed(w, now) + y := float64(w.UsedPercent) + if pace { + y -= elapsed + } + canvas.mark(elapsed, y, '●', nil, 4) + lines := strings.Split(out, "\n") + for row := 0; row < canvas.height; row++ { + runes := []rune(lines[row+1]) + for col := 0; col < canvas.width; col++ { + cell := canvas.cells[row*canvas.width+col] + if cell.priority > 0 && cell.priority < 4 && runes[col+5] != rune(0x2800+cell.mask) { + t.Fatalf("pace=%v line priority=%d has a gap at %d,%d", pace, cell.priority, col, row) + } + } + } + } +} + +func TestQuotaCanvasPreservesDotAndDirection(t *testing.T) { + c := quotaCanvas{width: 20, height: 10, pace: true, cells: make([]quotaPlotCell, 200)} + _, above := c.position(50, 25) + _, below := c.position(50, -25) + if above >= below { + t.Fatal("positive pace must appear above safety") + } + c.mark(50, 0, '●', nil, 4) + c.line(0, 0, 100, 0, nil, 3, 0) + px, py := c.position(50, 0) + if c.cells[(py/4)*c.width+px/2].symbol != '●' { + t.Fatal("trend overwrote the current dot") + } + if quotaFieldColor(1) == quotaFieldColor(-1) { + t.Fatal("danger and safety backgrounds match") + } +} + +func TestQuotaGraphButtonsTrackRenderedSurfaces(t *testing.T) { + for _, view := range []meterViewID{viewPace, viewZone} { + for _, width := range []int{24, 40, 80, 120} { + m := Model{snapshot: codex.DemoSnapshot(), meterView: view, width: width, height: 30} + layout := m.dashboardLayout() + lines := strings.Split(ansi.Strip(m.render()), "\n") + y, x := layout.quotaTabsY+1, 2 + buttons := m.quotaGraphButtons(layout.contentWidth) + for _, b := range buttons { + if !strings.Contains(lines[y], b.label) { + t.Fatalf("button %q missing from hit-test row %d", b.label, y) + } + for col := x; col < x+lipgloss.Width(b.label); col++ { + if m.footerButtonAt(col, y) != b.id { + t.Fatalf("button %q missed at %d,%d", b.label, col, y) + } + } + if m.footerButtonAt(x, y+1) != footerButtonNone { + t.Fatal("graph body is a control surface") + } + x += lipgloss.Width(b.label) + if m.footerButtonAt(x, y) != footerButtonNone { + t.Fatal("button padding is clickable") + } + x++ + } + mouse := tea.MouseMotionMsg{X: 2, Y: y, Button: tea.MouseLeft} + updated, _ := m.Update(mouse) + m = updated.(Model) + if m.hoveredButton != footerButtonQuotaTrend { + t.Fatal("trend hover missing") + } + updated, cmd := m.Update(tea.MouseClickMsg(mouse)) + m = updated.(Model) + if cmd == nil || m.flashedButton != footerButtonQuotaTrend || m.quotaGraphMode != quotagraph.Off { + t.Fatal("trend did not skip unavailable observation intervals or flash") + } + updated, _ = m.Update(key('h')) + if !updated.(Model).quotaGraphHideTrace { + t.Fatal("H did not toggle trace") + } + } + } + for _, view := range []meterViewID{viewBars, viewPie, viewFuel, viewUsage, viewBenchmark} { + m := Model{meterView: view} + updated, _ := m.Update(key('g')) + if updated.(Model).quotaGraphMode != quotagraph.WindowStart { + t.Fatal("G leaked into another view") + } + updated, _ = m.Update(key('h')) + if updated.(Model).quotaGraphHideTrace { + t.Fatal("H leaked into another view") + } + } +} + +func TestQuotaGraphRefreshCollectionIsIndependentOfSelectedTab(t *testing.T) { + now := time.Unix(1800000000, 0) + w := graphWindow(now, 20) + q := codex.Snapshot{AccountFingerprint: "A", RateLimits: codex.RateLimitSnapshot{Primary: &w}} + m := Model{meterView: viewMonitor} + updated, _ := m.Update(fetchedMsg{snapshot: q, at: now}) + m = updated.(Model) + updated, _ = m.Update(fetchedMsg{snapshot: q, at: now.Add(time.Minute), err: errors.New("offline")}) + m = updated.(Model) + updated, _ = m.Update(fetchedMsg{snapshot: q, at: now.Add(2 * time.Minute)}) + m = updated.(Model) + points := m.quotaPlotOptions()[0].points + if len(points) != 2 || !points[1].Break { + t.Fatal("quota observations were not retained while viewing Sessions") + } + m.resetRevision++ + updated, _ = m.Update(fetchedMsg{snapshot: q, at: now.Add(3 * time.Minute)}) + if len(updated.(Model).quotaPlotOptions()[0].points) != 2 { + t.Fatal("pre-reset result changed the trace") + } +} + +func TestQuotaGraphPreferencesPreserveRenameAndNewView(t *testing.T) { + for saved, want := range map[string]meterViewID{"consumption-pace": viewPace, "pace": viewPace, "zone": viewZone} { + m := Model{} + m.applyPreferences(Preferences{QuotaView: saved}) + if m.meterView != want { + t.Fatalf("saved %q restored %v", saved, m.meterView) + } + } +} + +func TestQuotaGraphsUseMultipleColumnsOnlyWhenReadable(t *testing.T) { + for _, view := range []meterViewID{viewPace, viewZone} { + if meterGridColumns(36, 24, 4, view) != 1 || meterGridColumns(80, 24, 4, view) != 2 || meterGridColumns(160, 30, 4, view) != 4 { + t.Fatal("graph grid does not adapt to available width and window count") + } + } +} diff --git a/internal/ui/tabs.go b/internal/ui/tabs.go index ec10a9a..d23fd0a 100644 --- a/internal/ui/tabs.go +++ b/internal/ui/tabs.go @@ -92,10 +92,10 @@ func mainTabLayout(width int, showMonitorLight bool) ([]mainTab, string) { func quotaViewTabLayout(width int, thresholds ...bool) ([]viewTab, string) { order := append([]meterViewID(nil), quotaViewOrder[:]...) tiers := [][]string{ - {i18n.Text("╭ BARS ╮"), i18n.Text("╭ CONSUMPTION PACE ╮"), i18n.Text("╭ PIE ╮"), i18n.Text("╭ FUEL TANK ╮"), i18n.Text("╭ RESETS ╮")}, - {"╭BAR╮", "╭PACE╮", "╭PIE╮", "╭FUEL╮", "╭RST╮"}, - {"[B]", "[C]", "[P]", "[F]", "[R]"}, - {"B", "C", "P", "F", "R"}, + {i18n.Text("╭ BARS ╮"), i18n.Text("╭ PACE ╮"), i18n.Text("╭ ZONE ╮"), i18n.Text("╭ PIE ╮"), i18n.Text("╭ FUEL TANK ╮"), i18n.Text("╭ RESETS ╮")}, + {"╭BAR╮", "╭PACE╮", "╭ZONE╮", "╭PIE╮", "╭FUEL╮", "╭RST╮"}, + {"[B]", "[C]", "[Z]", "[P]", "[F]", "[R]"}, + {"B", "C", "Z", "P", "F", "R"}, } if len(thresholds) > 0 && thresholds[0] { order = append(order, viewThresholds) diff --git a/internal/ui/tabs_test.go b/internal/ui/tabs_test.go index 9e587fd..334533a 100644 --- a/internal/ui/tabs_test.go +++ b/internal/ui/tabs_test.go @@ -44,7 +44,7 @@ func TestMainTabsChooseResponsiveLabels(t *testing.T) { func TestThresholdTabOnlyAppearsWithConfiguredSteps(t *testing.T) { without, _ := quotaViewTabLayout(160, false) with, _ := quotaViewTabLayout(160, true) - if len(without) != 5 || len(with) != 6 || with[5].view != viewThresholds || with[4].view != viewResets { + if len(without) != 6 || len(with) != 7 || with[6].view != viewThresholds || with[5].view != viewResets { t.Fatal("Thresholds must follow Resets only when configured") } m := Model{meterView: viewResets, quotaSteps: []codex.QuotaStep{{Threshold: 80}}} @@ -90,10 +90,10 @@ func TestQuotaViewTabsChooseResponsiveLabels(t *testing.T) { width int want string }{ - {width: 100, want: "CONSUMPTION PACE"}, + {width: 100, want: "PACE"}, {width: 34, want: "PACE"}, - {width: 15, want: "[C]"}, - {width: 5, want: "C"}, + {width: 18, want: "[C]"}, + {width: 6, want: "C"}, } { t.Run(test.want, func(t *testing.T) { tabs, _ := quotaViewTabLayout(test.width) @@ -110,7 +110,7 @@ func TestQuotaViewTabsChooseResponsiveLabels(t *testing.T) { if !strings.Contains(labels.String(), test.want) { t.Fatalf("width %d labels %q do not contain %q", test.width, labels.String(), test.want) } - if len(tabs) >= 3 && (tabs[1].view != viewConsumptionPace || !strings.Contains(tabs[1].label, test.want) || tabs[2].view != viewPie) { + if len(tabs) >= 4 && (tabs[1].view != viewPace || !strings.Contains(tabs[1].label, test.want) || tabs[2].view != viewZone || tabs[3].view != viewPie) { t.Fatalf("width %d tab order/labels do not match views: %#v", test.width, tabs) } }) @@ -156,16 +156,16 @@ func TestQuotaViewTabsSupportHoverClickAndPulse(t *testing.T) { } updated, command := model.Update(mouse) model = updated.(Model) - if command != nil || !model.viewHovered || model.hoveredView != viewConsumptionPace { + if command != nil || !model.viewHovered || model.hoveredView != viewPace { t.Fatalf("consumption pace hover was not recorded: hovered=%v view=%d", model.viewHovered, model.hoveredView) } updated, command = model.Update(tea.MouseClickMsg(mouse)) model = updated.(Model) - if command == nil || model.meterView != viewConsumptionPace || model.quotaMeterView != viewConsumptionPace || !model.viewFlashing || model.flashedView != viewConsumptionPace { + if command == nil || model.meterView != viewPace || model.quotaMeterView != viewPace || !model.viewFlashing || model.flashedView != viewPace { t.Fatalf("consumption pace click did not select, remember, and pulse: view=%d remembered=%d flashing=%v", model.meterView, model.quotaMeterView, model.viewFlashing) } - updated, _ = model.Update(viewTabFlashExpiredMsg{view: viewConsumptionPace, sequence: model.viewSequence}) + updated, _ = model.Update(viewTabFlashExpiredMsg{view: viewPace, sequence: model.viewSequence}) model = updated.(Model) if model.viewFlashing { t.Fatal("current tab pulse did not expire") @@ -226,7 +226,7 @@ func TestQuotaStyleIsRememberedAcrossMainTabNavigation(t *testing.T) { func TestVSelectsQuotaViewAndMonitorShortcutsStayScoped(t *testing.T) { model := Model{meterView: viewBars} - for _, want := range []meterViewID{viewConsumptionPace, viewPie, viewFuel, viewResets, viewBars} { + for _, want := range []meterViewID{viewPace, viewZone, viewPie, viewFuel, viewResets, viewBars} { updated, command := model.Update(key('v')) model = updated.(Model) if command == nil || model.meterView != want || model.quotaMeterView != want || model.flashedButton != footerButtonView { @@ -269,12 +269,12 @@ func TestQuotaSubTabsOnlyRenderAndHitTestWithinQuota(t *testing.T) { output := ansi.Strip(model.render()) layout := model.dashboardLayout() if view.isQuota() { - if layout.quotaTabsY < 0 || !strings.Contains(output, "CONSUMPTION PACE") { + if layout.quotaTabsY < 0 || !strings.Contains(output, "╭ PACE ╮") { t.Fatalf("Quota did not render its sub-tab rail:\n%s", output) } continue } - if layout.quotaTabsY != -1 || strings.Contains(output, "CONSUMPTION PACE") { + if layout.quotaTabsY != -1 || strings.Contains(output, "╭ PACE ╮") { t.Fatalf("%s exposed Quota sub-tabs", view.name()) } for y := 0; y < model.height; y++ { diff --git a/internal/ui/theme.go b/internal/ui/theme.go index d54af83..a472bbe 100644 --- a/internal/ui/theme.go +++ b/internal/ui/theme.go @@ -29,18 +29,20 @@ const ( viewBars meterViewID = iota viewMonitor viewPie - viewConsumptionPace + viewPace viewFuel viewBenchmark viewUsage viewResets viewThresholds + viewZone viewCount ) var quotaViewOrder = [...]meterViewID{ viewBars, - viewConsumptionPace, + viewPace, + viewZone, viewPie, viewFuel, viewResets, @@ -75,12 +77,13 @@ func (s meterViewID) name() string { i18n.Text("BARS"), i18n.Text("SESSIONS"), i18n.Text("PIE"), - i18n.Text("CONSUMPTION PACE"), + i18n.Text("PACE"), i18n.Text("FUEL TANK"), i18n.Text("BENCHMARK"), i18n.Text("USAGE"), i18n.Text("RESETS"), i18n.Text("THRESHOLDS"), + i18n.Text("ZONE"), }[s] } diff --git a/internal/ui/view.go b/internal/ui/view.go index cf91c34..08e6bf0 100644 --- a/internal/ui/view.go +++ b/internal/ui/view.go @@ -45,6 +45,9 @@ func (m Model) render() string { if m.meterView.isQuota() { parts = append(parts, m.renderQuotaViewTabs(contentWidth, colors)) } + if isQuotaGraph(m.meterView) { + parts = append(parts, m.renderQuotaGraphControls(contentWidth, colors)) + } if m.err != nil && m.meterView != viewUsage { errorView := renderError(contentWidth, m.err, colors) parts = append(parts, errorView) @@ -61,6 +64,10 @@ func (m Model) render() string { parts = append(parts, emptyView) } footer := m.renderFooter(contentWidth, colors) + var graphOptions []quotaPlotOptions + if isQuotaGraph(m.meterView) { + graphOptions = m.quotaPlotOptions() + } if m.meterView == viewThresholds { parts = append(parts, m.renderThresholds(contentWidth, layout.meterHeight, colors)) } else if m.meterView == viewResets { @@ -72,9 +79,9 @@ func (m Model) render() string { } else if m.meterView == viewBenchmark { parts = append(parts, m.renderBenchmarkArea(contentWidth, layout.meterHeight, colors)) } else if len(meters) > 1 { - parts = append(parts, renderMeterGrid(contentWidth, layout.meterHeight, meters, m.meterView, colors)) + parts = append(parts, renderMeterGrid(contentWidth, layout.meterHeight, meters, m.meterView, colors, graphOptions...)) } else if len(meters) == 1 { - parts = append(parts, renderMeterArea(contentWidth, layout.meterHeight, meters[0], m.meterView, colors)) + parts = append(parts, renderMeterArea(contentWidth, layout.meterHeight, meters[0], m.meterView, colors, graphOptions...)) } if layout.footerSpacer { parts = append(parts, strings.Repeat(" ", contentWidth)) diff --git a/internal/ui/view_test.go b/internal/ui/view_test.go index fec5b1c..e8e0600 100644 --- a/internal/ui/view_test.go +++ b/internal/ui/view_test.go @@ -318,7 +318,7 @@ func TestPieViewPlacesQuotaWindowsSideBySide(t *testing.T) { } func TestGaugeGridViewClassification(t *testing.T) { - for _, view := range []meterViewID{viewBars, viewPie, viewConsumptionPace, viewFuel} { + for _, view := range []meterViewID{viewBars, viewPie, viewPace, viewZone, viewFuel} { if got := usesMeterGrid(view); !got { t.Errorf("usesMeterGrid(%s) = false, want true", view.name()) } @@ -376,7 +376,7 @@ func TestGridRespondsToFourRateLimitsAndTerminalShape(t *testing.T) { func TestHorizontalStylesAlwaysFlowOneMeterPerRow(t *testing.T) { meters := codex.DemoSnapshot().Meters() - for _, view := range []meterViewID{viewBars, viewConsumptionPace, viewFuel} { + for _, view := range []meterViewID{viewBars, viewFuel} { if columns := meterGridColumns(160, 30, len(meters), view); columns != 1 { t.Fatalf("%s used %d columns, want one", view.name(), columns) } @@ -420,7 +420,7 @@ func TestMeterGridKeepsRowHeightsEqualAndPadsAboveFooter(t *testing.T) { func TestMeterGridUsesAllocatedHeightWithoutInternalOverflow(t *testing.T) { meters := codex.DemoSnapshot().Meters() - for _, view := range []meterViewID{viewPie, viewConsumptionPace} { + for _, view := range []meterViewID{viewPie, viewPace} { output := ansi.Strip(renderMeterGrid(116, 24, meters, view, paletteFor(themeHacker))) height := strings.Count(output, "\n") + 1 if height != 24 { diff --git a/internal/web/dist/assets/index-4j3sNB-h.css b/internal/web/dist/assets/index-4j3sNB-h.css new file mode 100644 index 0000000..0e4c2d8 --- /dev/null +++ b/internal/web/dist/assets/index-4j3sNB-h.css @@ -0,0 +1 @@ +.observation-details.svelte-1wk8llw{font-size:12px}summary.svelte-1wk8llw{cursor:pointer;color:var(--accent)}.observation-table.svelte-1wk8llw{max-height:240px;overflow:auto}.zone-canvas.svelte-1wk8llw{flex:1;min-height:170px;margin-top:6px;position:relative}svg.svelte-1wk8llw{width:100%;height:100%;display:block;position:absolute;inset:0}text.svelte-1wk8llw{fill:var(--ink);font:14px Cascadia Code,Consolas,monospace}.axis-title.svelte-1wk8llw{letter-spacing:.04em;font-size:12px}.safe-label.svelte-1wk8llw{fill:#fff;font-size:11px}.grid.svelte-1wk8llw{stroke:#fff;stroke-opacity:.14;stroke-width:1px}.axes.svelte-1wk8llw{stroke:var(--ink);stroke-width:1.5px;fill:none}.pace-line.svelte-1wk8llw{stroke:#fff;stroke-width:2px;stroke-dasharray:6 4}.position-halo.svelte-1wk8llw{fill:#101820;stroke:#fff;stroke-width:1.5px}.position-dot.svelte-1wk8llw{fill:#fff}.observation-trail.svelte-1wk8llw{fill:none;stroke:#101820;stroke-width:2.5px;stroke-linejoin:round}.observation-trail-casing.svelte-1wk8llw{fill:none;stroke:#ffffff73;stroke-width:4.5px;stroke-linejoin:round}.trail-start.svelte-1wk8llw{fill:#101820;stroke:#ffffffb3;stroke-width:2px}.trend-line.svelte-1wk8llw{fill:none;stroke:var(--trend-color);stroke-width:2px;stroke-dasharray:3 6}.trend-arrow.svelte-1wk8llw{fill:var(--trend-color);stroke:#ffffff73;stroke-width:.5px}.trend-casing.svelte-1wk8llw{fill:none;stroke:#ffffff73;stroke-width:4px;stroke-dasharray:3 6}.zone-caption.svelte-1wk8llw{min-width:0;color:var(--accent);flex:auto;margin:0}.trend-summary.svelte-1wk8llw{color:var(--ink)}.zone-footer.svelte-1wk8llw{justify-content:space-between;align-items:flex-start;gap:12px;margin-top:4px;display:flex}.zone-controls.svelte-1wk8llw{color:var(--muted);text-align:right;flex-direction:column;align-items:flex-end;gap:5px;font-size:11px;display:flex}.trace-control.svelte-1wk8llw,.trend-control.svelte-1wk8llw{cursor:pointer;white-space:nowrap}.trace-control.svelte-1wk8llw input:where(.svelte-1wk8llw){accent-color:var(--accent)}.trend-control.svelte-1wk8llw select:where(.svelte-1wk8llw){border:1px solid var(--edge);background:var(--panel);max-width:min(230px,45vw);color:var(--ink);font:inherit}.trend-control.svelte-1wk8llw option:where(.svelte-1wk8llw):disabled{color:var(--muted)}.zone-controls.svelte-1wk8llw small:where(.svelte-1wk8llw){font-size:10px}@container (width<=560px){.zone-footer.svelte-1wk8llw{flex-direction:column}.zone-controls.svelte-1wk8llw{align-self:stretch;align-items:flex-end}}.slash-commands.svelte-7bfwr9{margin:.4rem 0;position:relative}.command-popup.svelte-7bfwr9{z-index:5;box-sizing:border-box;border:1px solid var(--accent);background:var(--panel);width:min(40rem,100%);padding:.35rem;position:absolute;bottom:100%;left:0;box-shadow:0 .3rem 1rem #0006}.command-popup.svelte-7bfwr9 p:where(.svelte-7bfwr9){margin:.3rem 0;font-size:.8em}.command-options.vertical.svelte-7bfwr9{flex-flow:column;gap:.15rem;display:flex}.vertical.svelte-7bfwr9 button:where(.svelte-7bfwr9){text-align:left;white-space:nowrap;width:100%;min-width:0;color:var(--muted);background:var(--panel);border-color:#0000;flex:none;align-items:baseline;gap:.4rem;display:flex}.vertical.svelte-7bfwr9 .command-name:where(.svelte-7bfwr9),.vertical.svelte-7bfwr9 .command-tail:where(.svelte-7bfwr9){flex:none}.vertical.svelte-7bfwr9 .command-name:where(.svelte-7bfwr9){text-overflow:ellipsis;max-width:100%;overflow:hidden}.vertical.svelte-7bfwr9 button.suggested:where(.svelte-7bfwr9){outline:none}.vertical.svelte-7bfwr9 button.suggested:where(.svelte-7bfwr9) .command-name:where(.svelte-7bfwr9),.vertical.svelte-7bfwr9 button:where(.svelte-7bfwr9):hover .command-name:where(.svelte-7bfwr9){background:var(--accent);color:var(--bg)}.vertical.svelte-7bfwr9 .command-help:where(.svelte-7bfwr9){min-width:0;max-width:none;color:var(--muted);flex:1}.command-options.svelte-7bfwr9{flex-wrap:wrap;gap:.35rem;max-height:35vh;display:flex;overflow:auto}.command-options.svelte-7bfwr9 .suggested:where(.svelte-7bfwr9){outline:1px solid var(--accent)}.command-help.svelte-7bfwr9{text-overflow:ellipsis;white-space:nowrap;opacity:.75;max-width:32ch;font-size:.8em;display:block;overflow:hidden}pre.svelte-7bfwr9{white-space:pre-wrap;overflow-wrap:anywhere;max-height:35vh;overflow:auto}.file-diff.svelte-x4gtb6{border:1px solid var(--muted);max-height:60vh;padding:.5rem;font-family:monospace;overflow:auto}.diff-line.svelte-x4gtb6{white-space:pre;min-height:1.3em;display:flex}.line-number.svelte-x4gtb6{text-align:right;opacity:.75;-webkit-user-select:none;user-select:none;flex:0 0 5ch;padding-right:1ch;display:inline-block}.added.svelte-x4gtb6{color:#67d391;background:#67d39112}.removed.svelte-x4gtb6{color:#ff6b83;background:#ff6b8312}.heading.svelte-x4gtb6{padding:.5rem 0;font-weight:700}.saved-prompt.svelte-1oupzfc{white-space:pre-wrap;overflow-wrap:anywhere}fieldset.svelte-1oupzfc{border:1px solid;margin:.5rem 0;padding:.35rem .6rem}.decision.svelte-1oupzfc,.answer.svelte-1oupzfc{margin:.3rem 0;display:block}.decision.svelte-1oupzfc code:where(.svelte-1oupzfc),.decision.svelte-1oupzfc span:where(.svelte-1oupzfc){overflow-wrap:anywhere;margin:.3rem 0 .3rem 1.5rem;display:block}textarea.svelte-1oupzfc,.answer.svelte-1oupzfc input:where(.svelte-1oupzfc),.answer.svelte-1oupzfc select:where(.svelte-1oupzfc){box-sizing:border-box;background:var(--bg);width:100%;color:inherit;font:inherit;border:1px solid;margin-top:.4rem;padding:.5rem;display:block}textarea.svelte-1oupzfc{resize:vertical;background:color-mix(in srgb, currentColor 8%, var(--bg))}button.svelte-1oupzfc{margin:.3rem .5rem .3rem 0}h3.svelte-1oupzfc,p.svelte-1oupzfc{margin-block:.4rem}.sent.svelte-1oupzfc{color:var(--accent)}.status-line.svelte-gprptn{align-items:center;gap:.5rem;min-width:0;margin-top:.5rem;display:flex}.status-line.svelte-gprptn p:where(.svelte-gprptn){white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;margin:0;font-size:.8rem;overflow:hidden}.configure.svelte-gprptn{padding:.15rem .35rem;font-size:.75rem}.status-picker.svelte-gprptn{border:1px solid var(--edge);margin:.5rem 0;padding:.6rem}.fields.svelte-gprptn{max-height:35vh;overflow:auto}.field.svelte-gprptn{align-items:center;gap:.35rem;display:flex}.field.svelte-gprptn label:where(.svelte-gprptn){flex:1;align-items:center;gap:.4rem;display:flex}.field.svelte-gprptn label:where(.svelte-gprptn) span:where(.svelte-gprptn){font-size:.75rem}.field.svelte-gprptn button:where(.svelte-gprptn){padding:.1rem .35rem}.session-copy.svelte-543j00{justify-content:flex-end;align-items:center;gap:8px;margin-top:auto;padding-top:6px;display:flex}span.svelte-543j00{color:var(--muted);overflow-wrap:anywhere;font-size:12px}button.svelte-543j00{color:var(--accent);flex-shrink:0;padding:3px 7px}button.svelte-543j00:hover,button.flashed.svelte-543j00{color:var(--bg);background:var(--accent)}h3.svelte-1fbui7h,.window-dates.svelte-1fbui7h,.report-value.svelte-1fbui7h{color:var(--accent);font-weight:700}.quality-warning.svelte-1fbui7h,.quota-near.svelte-1fbui7h{color:var(--approval)}.quota-full.svelte-1fbui7h{color:#ff6680}.alternate.svelte-1fbui7h .report-fill:where(.svelte-1fbui7h){background:radial-gradient(circle, var(--panel) .7px, transparent .9px) 0 0 / 3px 3px, var(--accent)}.unknown.svelte-1fbui7h,.unknown.svelte-1fbui7h .report-value:where(.svelte-1fbui7h){color:var(--muted)}.unknown.svelte-1fbui7h .report-fill:where(.svelte-1fbui7h){background:var(--muted);opacity:1}.report-bars.svelte-1fbui7h{grid-template-columns:minmax(5rem,1fr) minmax(2rem,3fr) auto;gap:.6rem;display:grid}.report-row.svelte-1fbui7h,.report-head.svelte-1fbui7h{grid-column:1/-1;grid-template-columns:subgrid;align-items:center;gap:.6rem;display:grid}.report-head.svelte-1fbui7h{color:var(--muted);font-size:.85rem}.report-head.svelte-1fbui7h>span:where(.svelte-1fbui7h):last-child,.report-value.svelte-1fbui7h{text-align:right}.report-row.svelte-1fbui7h>span:where(.svelte-1fbui7h):first-child{overflow-wrap:anywhere}.report-track.svelte-1fbui7h{background:var(--panel);height:1rem}.report-fill.svelte-1fbui7h{background:var(--accent);height:100%}:root{font-synthesis:none;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;background:#080e0c;font-family:Cascadia Code,SFMono-Regular,Consolas,monospace}*{box-sizing:border-box}body{margin:0}.shell{--accent:#80edac;--approval:#ffca58;--muted:#98aaa0;--edge:#31473a;--panel:#101c16;--bg:#080e0c;--ink:#e2eee5;background:var(--bg);height:100dvh;color:var(--ink);grid-template-rows:auto auto minmax(0,1fr) auto;padding:clamp(10px,1.2vw,20px);display:grid}.shell[data-theme=rust]{--approval:#ff8a3d;--accent:#ffbc75;--muted:#bfac95;--edge:#59412c;--panel:#261b14;--bg:#140e0a}.shell[data-theme=blue-steel]{--approval:#e8c46a;--accent:#85c8ff;--muted:#9cacc4;--edge:#334d69;--panel:#142235;--bg:#0b1421}.shell[data-theme=ultraviolet]{--approval:#f9a8d4;--accent:#c5adff;--muted:#b3a6c9;--edge:#4b3c68;--panel:#241a35;--bg:#140d22}.shell[data-theme=nightshade]{--approval:#8f7cff;--accent:#e59bff;--muted:#c6a7cf;--edge:#673976;--panel:#301739;--bg:#1c0b24}header,.spread,footer,.controls{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:10px;display:flex}header{margin-bottom:10px}.brand{letter-spacing:.08em;color:var(--accent);text-shadow:0 0 22px color-mix(in srgb, var(--accent) 20%, transparent);font-size:clamp(22px,2.5vw,32px);font-weight:800;text-decoration:none}header p{color:var(--muted);margin:3px 0 0;font-size:13px}.connection{color:var(--accent);text-align:right}small,.eyebrow{letter-spacing:.04em;font-size:12px}.connection small{color:var(--muted);margin-top:4px;display:block}.lamp{background:var(--muted);border-radius:50%;width:9px;height:9px;margin-right:9px;display:inline-block}.lamp.lit{background:var(--accent);box-shadow:0 0 8px color-mix(in srgb, var(--accent) 40%, transparent)}.working{animation:1.5s ease-in-out infinite pulse}@keyframes pulse{50%{opacity:.3}}nav{flex-wrap:wrap;gap:6px;margin-bottom:10px;display:flex}nav a,button,.button,select{font:inherit;color:var(--accent);border:1px solid var(--edge);background:var(--panel);cursor:pointer;border-radius:3px;padding:7px 11px;font-size:13px;text-decoration:none}button:hover,.button:hover,nav a:hover{border-color:var(--accent)}nav a.active,nav button.active{background:var(--accent);color:var(--bg);border-color:var(--accent)}:focus-visible{outline:2px solid var(--accent);outline-offset:4px}.secondary{margin-bottom:8px}.secondary a,.secondary button{padding:6px 10px;font-size:12px}main{scrollbar-gutter:stable;flex-direction:column;min-width:0;min-height:0;display:flex;overflow:auto}main>*{flex-shrink:0}.panel{border:1px solid var(--edge);border-top:2px solid var(--accent);background:var(--panel);border-radius:4px;min-width:0;padding:clamp(10px,1vw,16px)}h1{color:var(--accent);font-size:20px}h2{letter-spacing:.04em;color:var(--accent);overflow-wrap:anywhere;margin:0 0 10px;font-size:13px}h3{overflow-wrap:anywhere;font-size:13px}p{margin:6px 0;font-size:13px;line-height:1.45}a{color:var(--accent)}.muted,.eyebrow{color:var(--muted)}.notice{color:#ffe0aa;background:#332611;border-left:3px solid #ffc26e;padding:12px 16px}.empty{color:var(--muted);padding:35px 0}.quota-grid{flex:1 0 auto;grid-auto-rows:minmax(220px,1fr);gap:10px;display:grid}.quota-card{flex-direction:column;min-height:0;display:flex;container-type:inline-size}.quota-card>:not(.meter-graphic){flex-shrink:0}.meter-graphic{flex-direction:column;flex:1;min-height:100px;display:flex}.bar-graphic{min-height:65px}.quota-card .gauge{flex:2;height:auto;min-height:16px;margin:7px 0}.quota-card .timeline{flex:1;min-height:8px}.quota-grid.zone{grid-template-columns:repeat(auto-fit,minmax(min(100%,420px),1fr));grid-auto-rows:minmax(330px,1fr)}.quota-grid.radial{grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr));grid-auto-rows:minmax(260px,1fr)}.gauge{background:var(--edge);height:35px;margin:15px 0;overflow:hidden}.gauge>div{background:repeating-linear-gradient(90deg, var(--accent) 0, var(--accent) 9px, transparent 9px, transparent 12px);height:100%;transition:width .5s}.gauge.timeline{height:14px}.pie-wrap{flex:1;min-height:100px;margin:6px 0;position:relative}.pie-wrap svg{width:100%;height:100%;position:absolute;inset:0}.pie-base{fill:var(--edge);stroke:var(--accent);stroke-width:1px}.pie-fill{fill:var(--accent)}.readout{overflow-wrap:anywhere;color:var(--accent);margin:18px 0;font-size:clamp(22px,3vw,34px)}.credit+.credit{border-top:1px solid var(--edge);padding-top:20px}.credit{margin-top:24px}.session-row{grid-template-columns:minmax(210px,1.1fr) minmax(0,1.2fr) minmax(0,1.5fr);align-items:stretch;gap:14px;margin:18px 0;display:grid}.session-totals{grid-template-columns:repeat(auto-fit,minmax(min(145px,100%),1fr));gap:8px;margin:8px 0;display:grid}.session-totals>div{border:1px solid var(--edge);min-width:0;padding:10px}.session-totals dt{color:var(--muted);font-size:11px}.session-totals dd{color:var(--accent);overflow-wrap:anywhere;margin:6px 0 0;font-size:24px}.session-totals.stale dd{color:var(--muted)}.session-row.selected{outline:1px solid var(--accent);outline-offset:4px}.session-row.wide .context{grid-column:2/-1}.detail-controls{flex-wrap:wrap;gap:6px;display:flex}.attention-summary{flex-wrap:wrap;gap:8px;margin-block:8px;display:flex}.attention-summary .approval{color:var(--approval);border-color:var(--approval)}.attention-summary .approval:hover{background:color-mix(in srgb, var(--approval) 12%, var(--panel))}.attention-summary .approval:focus-visible{outline-color:var(--approval)}.attention-note,.attention-badge{color:var(--accent);border-left:3px solid;padding-left:8px}.attention-note.inferred{color:var(--muted)}.attention-badge{font-size:12px}.session-select{text-align:left;overflow-wrap:anywhere;max-width:100%}.status-detail{color:inherit;text-decoration:none}.status-detail:hover{text-decoration:underline}button:disabled{opacity:.4;cursor:default}.session-row .graph-panel{flex-direction:column;grid-column:2/-1;display:flex}.session-row.split .graph-panel{grid-column:auto}.session-row .button,.session-row button{margin-top:8px;display:inline-block}.context pre{max-height:230px;overflow:auto}.session-row .context{flex-direction:column;display:flex}pre{font:inherit;white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px;line-height:1.7}.full-detail{margin-top:6px}.detail-heading{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:6px 12px;display:flex}.detail-heading h2{flex:250px;min-width:0;margin:0}.detail-metadata{overflow-wrap:anywhere}.detail-workspace{border-top:1px solid var(--edge);flex-direction:column;gap:10px;margin-top:8px;padding-top:4px;display:flex}.detail-context,.detail-workspace .session-actions{min-width:0}.detail-workspace .session-actions{border-top:1px solid var(--edge);padding-top:6px}.full-detail h3{margin-block:8px}.full-detail pre{margin-block:8px;line-height:1.5}.full-detail hr{margin-block:12px}.full-detail .command,.full-detail .notice{padding:8px 10px}.command{background:var(--bg);border-left:3px solid var(--accent);padding:16px}hr{border:0;border-top:1px solid var(--edge);margin:24px 0}.chart{border-bottom:1px solid var(--muted);align-items:flex-end;gap:2px;height:clamp(120px,22vh,320px);margin-top:18px;display:flex}.chart-bar{background:var(--accent);flex:1;min-width:0;max-height:100%}.controls{justify-content:flex-start;margin:20px 0}.summary-grid{grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:16px;margin:20px 0;display:grid}.thresholds-panel{padding:clamp(14px,2vw,22px)}.thresholds-panel h1{margin-top:0}.threshold-list{gap:8px;margin-top:16px;display:grid}.threshold-list article{border:1px solid var(--edge);grid-template-columns:minmax(64px,.5fr) minmax(220px,3fr) minmax(64px,.6fr) minmax(120px,1.2fr);align-items:center;gap:12px;padding:10px 12px;display:grid}.threshold-list article.active{border-color:var(--accent);box-shadow:inset 3px 0 0 var(--accent)}.threshold-list article.next{border-color:var(--approval);box-shadow:inset 3px 0 0 var(--approval)}.threshold-list p{color:var(--muted);margin:3px 0 0;font-size:12px}.threshold-trigger{color:var(--accent);font-size:22px;font-weight:800}.threshold-mode,.threshold-state{color:var(--muted);font-size:12px;font-weight:700}.threshold-list article.active .threshold-state{color:var(--accent)}.threshold-list article.next .threshold-state{color:var(--approval)}.heat-scroll{overflow-x:auto}.heatmap{grid-template-rows:repeat(7,13px);grid-auto-columns:minmax(9px,1fr);grid-auto-flow:column;gap:4px;min-width:620px;margin:25px 0;display:grid}.heat-cell{background:var(--accent);border-radius:2px}.heat-cell.zero{background:var(--edge)}.table-scroll{max-height:280px;overflow:auto}table{border-collapse:collapse;width:100%;margin-top:15px}th,td{text-align:left;border-bottom:1px solid var(--edge);padding:8px}summary{cursor:pointer;color:var(--accent);padding-top:14px}footer{border-top:1px solid var(--edge);color:var(--muted);margin-top:8px;padding-top:8px;font-size:11px}footer select{padding:6px}@media (width<=850px){.session-row{grid-template-columns:minmax(0,1fr)}.session-row.wide .context,.session-row .graph-panel{grid-column:auto}.connection{text-align:left}.threshold-list article{grid-template-columns:64px minmax(0,1fr)}.threshold-mode,.threshold-state{grid-column:2}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition:none!important;animation:none!important}} diff --git a/internal/web/dist/assets/index-C14IM-bC.js b/internal/web/dist/assets/index-C14IM-bC.js new file mode 100644 index 0000000..3944c5e --- /dev/null +++ b/internal/web/dist/assets/index-C14IM-bC.js @@ -0,0 +1,43 @@ +(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=Array.isArray,t=Array.prototype.indexOf,n=Array.prototype.includes,r=Array.from,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyDescriptors,s=Object.prototype,c=Array.prototype,l=Object.getPrototypeOf,u=Object.isExtensible;function d(e){return typeof e==`function`}var f=()=>{};function p(e){return e()}function m(e){for(var t=0;t{e=n,t=r}),resolve:e,reject:t}}function g(e,t,n=!1){return e===void 0?n?t():t:e}function _(e,t){if(Array.isArray(e))return e;if(t===void 0||!(Symbol.iterator in e))return Array.from(e);let n=[];for(let r of e)if(n.push(r),n.length===t)break;return n}var v=1024,y=2048,b=4096,x=8192,S=16384,C=32768,w=1<<25,T=65536,ee=1<<19,te=1<<20,E=1<<25,D=1<<21,ne=1<<22,O=1<<23,re=Symbol(`$state`),ie=Symbol(`component`),ae=Symbol(`legacy props`),oe=Symbol(``),se=Symbol(`attributes`),ce=Symbol(`class`),le=Symbol(`style`),ue=Symbol(`text`),de=Symbol(`form reset`),fe=new class extends Error{name=`StaleReactionError`;message="The reaction that called `getAbortSignal()` was re-run or destroyed"},pe=!!globalThis.document?.contentType&&globalThis.document.contentType.includes(`xml`),me={},he=Symbol(`uninitialized`),ge=`http://www.w3.org/1999/xhtml`;function _e(){console.warn(`https://svelte.dev/e/derived_inert`)}function ve(e){console.warn(`https://svelte.dev/e/hydration_mismatch`)}function ye(){console.warn(`https://svelte.dev/e/select_multiple_invalid_value`)}function be(){console.warn(`https://svelte.dev/e/svelte_boundary_reset_noop`)}var k=!1;function xe(e){k=e}var A;function Se(e){if(e===null)throw ve(),me;return A=e}function Ce(){return Se(cn(A))}function j(e){if(k){if(cn(A)!==null)throw ve(),me;A=e}}function we(e=1){if(k){for(var t=e,n=A;t--;)n=cn(n);A=n}}function Te(e=!0){for(var t=0,n=A;;){if(n.nodeType===8){var r=n.data;if(r===`]`){if(t===0)return n;--t}else(r===`[`||r===`[!`||r[0]===`[`&&!isNaN(Number(r.slice(1))))&&(t+=1)}var i=cn(n);e&&n.remove(),n=i}}function Ee(e){if(!e||e.nodeType!==8)throw ve(),me;return e.data}function De(e){return e===this.v}function Oe(e,t){return e==e?e!==t||typeof e==`object`&&!!e||typeof e==`function`:t==t}function ke(e){return!Oe(e,this.v)}function Ae(e){throw Error(`https://svelte.dev/e/lifecycle_outside_component`)}function je(){throw Error(`https://svelte.dev/e/async_derived_orphan`)}function Me(e,t,n){throw Error(`https://svelte.dev/e/each_key_duplicate`)}function Ne(e){throw Error(`https://svelte.dev/e/effect_in_teardown`)}function Pe(){throw Error(`https://svelte.dev/e/effect_in_unowned_derived`)}function Fe(e){throw Error(`https://svelte.dev/e/effect_orphan`)}function Ie(){throw Error(`https://svelte.dev/e/effect_update_depth_exceeded`)}function Le(e){throw Error(`https://svelte.dev/e/props_invalid_value`)}function Re(){throw Error(`https://svelte.dev/e/state_descriptors_fixed`)}function ze(){throw Error(`https://svelte.dev/e/state_prototype_fixed`)}function Be(){throw Error(`https://svelte.dev/e/state_unsafe_mutation`)}function Ve(){throw Error(`https://svelte.dev/e/svelte_boundary_reset_onerror`)}var He=!1;function Ue(){He=!0}var M=null;function We(e){M=e}function Ge(e,t=!1,n){M={p:M,i:!1,c:null,e:null,s:e,x:null,r:U,l:He&&!t?{s:null,u:null,$:[]}:null}}function Ke(e){var t=M,n=t.e;if(n!==null){t.e=null;for(var r of n)xn(r)}return e!==void 0&&(t.x=e),t.i=!0,M=t.p,qe(e)}function qe(e={}){return i(e,ie,{value:!0}),e}function Je(){return!He||M!==null&&M.l===null}var Ye=[];function Xe(){var e=Ye;Ye=[],m(e)}function Ze(e){if(Ye.length===0&&!Tt){var t=Ye;queueMicrotask(()=>{t===Ye&&Xe()})}Ye.push(e)}function Qe(){for(;Ye.length>0;)Xe()}var $e=~(y|b|v);function et(e,t){e.f=e.f&$e|t}function tt(e){e.f&512||e.deps===null?et(e,v):et(e,b)}function nt(e,t,n){e.f&2048?t.add(e):e.f&4096&&n.add(e),et(e,v)}function rt(e){k&&sn(e)!==null&&ln(e)}var it=!1;function at(){it||(it=!0,document.addEventListener(`reset`,e=>{Promise.resolve().then(()=>{if(!e.defaultPrevented)for(let t of e.target.elements)t[de]?.()})},{capture:!0}))}function ot(e){var t=H,n=U;Gn(null),Kn(null);try{return e()}finally{Gn(t),Kn(n)}}function st(e,t,n,r=n){e.addEventListener(t,()=>ot(n));let i=e[de];e[de]=i?()=>{i(),r(!0)}:()=>r(!0),at()}function ct(e,t,n,r){let i=Je()?ft:ht;var a=e.filter(e=>!e.settled),o=t.map(i);if(n.length===0&&a.length===0){r(o);return}var s=U,c=lt(),l=a.length===1?a[0].promise:a.length>1?Promise.all(a.map(e=>e.promise)):null;function u(e){if(!(s.f&16384)){c();try{r([...o,...e])}catch(e){mn(e,s)}ut()}}var d=dt();if(n.length===0){l.then(()=>u([])).finally(d);return}function f(){Promise.all(n.map(e=>mt(e))).then(u).catch(e=>mn(e,s)).finally(d)}l?l.then(()=>{c(),f(),ut()}):f()}function lt(){var e=U,t=H,n=M,r=P;return function(i=!0){Kn(e),Gn(t),We(n),i&&!(e.f&16384)&&(r?.activate(),r?.apply())}}function ut(e=!0){Kn(null),Gn(null),We(null),e&&P?.deactivate()}function dt(){var e=U,t=e.b,n=P,r=!!t?.is_rendered();return t?.update_pending_count(1,n),n.increment(r,e),()=>{t?.update_pending_count(-1,n),n.decrement(r,e)}}function ft(e){var t=2|y;return U!==null&&(U.f|=ee),{ctx:M,deps:null,effects:null,equals:De,f:t,fn:e,reactions:null,rv:0,v:he,wv:0,parent:U,ac:null}}var pt=Symbol(`obsolete`);function mt(e,t,n){let r=U;r===null&&je();var i=void 0,a=Ht(he),o=!H,s=new Set;return Tn(()=>{var t=U,n=h();i=n.promise;try{Promise.resolve(e()).then(n.resolve,e=>{e!==fe&&n.reject(e)}).finally(ut)}catch(e){n.reject(e),ut()}var c=P;if(o){if(t.f&32768)var l=dt();if(r.b?.is_rendered())c.async_deriveds.get(t)?.reject(pt);else for(let e of s.values())e.reject(pt);s.add(n),c.async_deriveds.set(t,n)}let u=(e,t=void 0)=>{l?.(),s.delete(n),t!==pt&&(c.activate(),t?(a.f|=O,Kt(a,t)):(a.f&8388608&&(a.f^=O),Kt(a,e)),c.deactivate())};n.promise.then(u,e=>u(null,e||`unknown`))}),yn(()=>{for(let e of s)e.reject(pt)}),new Promise(e=>{function t(n){function r(){n===i?e(a):t(i)}n.then(r,r)}t(i)})}function N(e){let t=ft(e);return Jn(t),t}function ht(e){let t=ft(e);return t.equals=ke,t}function gt(e){var t=e.effects;if(t!==null){e.effects=null;for(var n=0;n{t.ac.abort(fe),t.ac=null}),t.fn!==null&&(t.teardown=f),lr(t,0),An(t))}function bt(e){if(e.effects!==null)for(let t of e.effects)t.teardown&&t.fn!==null&&ur(t)}var xt=null,P=null,St=null,Ct=null,wt=null,Tt=!1,Et=!1,Dt=null,Ot=null,kt=0,At=1,jt=class e{id=At++;#e=!1;linked=!0;#t=null;#n=null;async_deriveds=new Map;current=new Map;previous=new Map;#r=new Set;#i=new Set;#a=0;#o=new Map;#s=null;#c=[];#l=[];#u=new Set;#d=new Set;#f=new Map;#p=new Set;is_fork=!1;#m=!1;constructor(){xt===null?xt=this:(xt.#n=this,this.#t=xt),xt=this}#h(){if(this.is_fork)return!0;for(let n of this.#o.keys()){for(var e=n,t=!1;e.parent!==null;){if(this.#f.has(e)){t=!0;break}e=e.parent}if(!t)return!0}return!1}skip_effect(e){this.#f.has(e)||this.#f.set(e,{d:[],m:[]}),this.#p.delete(e)}unskip_effect(e,t=e=>this.schedule(e)){var n=this.#f.get(e);if(n){this.#f.delete(e);for(var r of n.d)et(r,y),t(r);for(r of n.m)et(r,b),t(r)}this.#p.add(e)}#g(){var e=[];for(let i of this.#c)if(!(i.f&16384||!(i.f&6144))){for(var t=i,n=!1;t.parent!==null;){t=t.parent;var r=t.f;if(r&96){if(!(r&1024)){n=!0;break}t.f^=v}}n||e.push(t)}return this.#c=[],e}#_(){this.#e=!0;for(let e of this.#u)this.#d.delete(e),et(e,y),this.schedule(e);for(let e of this.#d)et(e,b),this.schedule(e);this.apply();for(var t=Dt=[],n=[],r=Ot=[];this.#c.length>0;){kt++>1e3&&(this.#S(),Nt());for(let e of this.#g())try{this.#v(e,t,n)}catch(t){throw Rt(e),this.#h()||this.discard(),t}}if(P=null,r.length>0){var i=e.ensure();for(let e of r)i.schedule(e)}if(Dt=null,Ot=null,this.#h()){this.#x(n),this.#x(t);for(let[e,t]of this.#f)Lt(e,t);r.length>0&&P.#_();return}let a=this.#y();if(a){this.#x(n),this.#x(t),a.#b(this);return}this.#u.clear(),this.#d.clear();for(let e of this.#r)e(this);this.#r.clear(),St=this,Ft(n),Ft(t),St=null,this.#s?.resolve();var o=P;if(this.#a===0&&(this.#c.length===0||o!==null)&&this.#S(),this.#c.length>0){if(o!==null){for(let e of this.#c)o.#c.push(e);this.#c=[]}else o=this}o!==null&&(Bt.clear(),o.#_())}#v(e,t,n){e.f^=v;for(var r=e.first;r!==null;){var i=r.f,a=!!(i&96);if(!(a&&i&1024||i&8192||this.#f.has(r))&&r.fn!==null){a?r.f^=v:i&4?t.push(r):ir(r)&&(i&16&&this.#d.add(r),ur(r));var o=r.first;if(o!==null){r=o;continue}}for(;r!==null;){var s=r.next;if(s!==null){r=s;break}r=r.parent}}}#y(){for(var e=this.#t;e!==null;){if(!e.is_fork){for(let[t,[,n]]of this.current)if(e.current.has(t)&&!n)return e}e=e.#t}return null}#b(e){for(let[t,n]of e.current)!this.previous.has(t)&&e.previous.has(t)&&this.previous.set(t,e.previous.get(t)),this.current.set(t,n);for(let[t,n]of e.async_deriveds){let e=this.async_deriveds.get(t);e&&n.promise.then(e.resolve).catch(e.reject)}e.async_deriveds.clear(),this.transfer_effects(e.#u,e.#d);let t=e=>{var n=e.reactions;if(n!==null&&!(e.f&2&&!(e.f&6144)))for(let e of n){var r=e.f;if(r&2)t(e);else{var i=e;r&4194320&&!this.async_deriveds.has(i)&&(this.#d.delete(i),et(i,y),this.schedule(i))}}};for(let e of this.current.keys())t(e);this.oncommit(()=>e.discard()),e.#S(),P=this,this.#_()}#x(e){for(var t=0;t{this.#m=!1,this.linked&&this.flush()}))}transfer_effects(e,t){for(let t of e)this.#u.add(t);for(let e of t)this.#d.add(e);e.clear(),t.clear()}oncommit(e){this.#r.add(e)}ondiscard(e){this.#i.add(e)}settled(){return(this.#s??=h()).promise}static ensure(){if(P===null){let t=P=new e;!Et&&!Tt&&Ze(()=>{t.#e||t.flush()})}return P}apply(){Ct=null}schedule(e){if(wt=e,e.b?.is_pending&&e.f&16777228&&!(e.f&32768)){e.b.defer_effect(e);return}this.#c.push(e)}#S(){if(this.linked){var e=this.#t,t=this.#n;e===null||(e.#n=t),t===null?xt=e:t.#t=e,this.linked=!1}}};function Mt(e){var t=Tt;Tt=!0;try{var n;for(e&&(P!==null&&!P.is_fork&&P.flush(),n=e());;){if(Qe(),P===null)return n;P.flush()}}finally{Tt=t}}function Nt(){try{Ie()}catch(e){mn(e,wt)}}var Pt=null;function Ft(e){var t=e.length;if(t!==0){for(var n=0;n0)){Bt.clear();for(let e of Pt){if(e.f&24576)continue;let t=[e],n=e.parent;for(;n!==null;)Pt.has(n)&&(Pt.delete(n),t.push(n)),n=n.parent;for(let e=t.length-1;e>=0;e--){let n=t[e];n.f&24576||ur(n)}}Pt.clear()}}Pt=null}}function It(e){P.schedule(e)}function Lt(e,t){if(!(e.f&32&&e.f&1024)){e.f&2048?t.d.push(e):e.f&4096&&t.m.push(e),et(e,v);for(var n=e.first;n!==null;)Lt(n,t),n=n.next}}function Rt(e){et(e,v);for(var t=e.first;t!==null;)Rt(t),t=t.next}var zt=new Set,Bt=new Map,Vt=!1;function Ht(e,t){return{f:0,v:e,reactions:null,equals:De,rv:0,wv:0}}function F(e,t){let n=Ht(e,t);return Jn(n),n}function Ut(e,t=!1,n=!0){let r=Ht(e);return t||(r.equals=ke),He&&n&&M!==null&&M.l!==null&&(M.l.s??=[]).push(r),r}function I(e,t,n=!1){return H!==null&&(!Wn||H.f&131072)&&Je()&&H.f&4325394&&(qn===null||!qn.has(e))&&Be(),Kt(e,n?Zt(t):t,Ot)}var Wt=null,Gt=0;function Kt(e,t,n=null){if(!e.equals(t)){Hn?Bt.set(e,t):Bt.has(e)||Bt.set(e,e.v);var r=jt.ensure();if(r.capture(e,t),e.f&2){let t=e;e.f&2048&&_t(t),Ct===null&&tt(t)}e.wv=rr(),Wt=null,Gt=0,Xt(e,y,n),Wt=null,Je()&&U!==null&&U.f&1024&&!(U.f&96)&&(Zn===null?Qn([e]):Zn.push(e)),!r.is_fork&&zt.size>0&&!Vt&&qt()}return t}function qt(){Vt=!1;for(let e of zt){e.f&1024&&et(e,b);let t;try{t=ir(e)}catch{t=!0}t&&ur(e)}zt.clear()}function Jt(e,t=1){var n=W(e),r=t===1?n++:n--;return I(e,n),r}function Yt(e){I(e,e.v+1)}function Xt(e,t,n){var r=e.reactions;if(r!==null){var i=Je(),a=r.length;if(Gt+=a,Gt>1e5&&Wt===null&&(Wt=new Set),Wt!==null){if(Wt.has(e))return;Wt.add(e)}for(var o=0;o{if(tr===d)return e();var t=H,n=tr;Gn(null),nr(d);var r=e();return Gn(t),nr(n),r};return i&&r.set(`length`,F(t.length,u)),new Proxy(t,{defineProperty(e,t,n){(!(`value`in n)||n.configurable===!1||n.enumerable===!1||n.writable===!1)&&Re();var i=r.get(t);return i===void 0?f(()=>{var e=F(n.value,u);return r.set(t,e),e}):I(i,n.value,!0),!0},deleteProperty(e,t){var n=r.get(t);if(n===void 0){if(t in e){let e=f(()=>F(he,u));r.set(t,e),Yt(o)}}else I(n,he),Yt(o);return!0},get(e,n,i){if(n===re)return t;var o=r.get(n),s=n in e;if(o===void 0&&(!s||a(e,n)?.writable)&&(o=f(()=>F(Zt(s?e[n]:he),u)),r.set(n,o)),o!==void 0){var c=W(o);return c===he?void 0:c}return Reflect.get(e,n,i)},getOwnPropertyDescriptor(e,t){this.has?.(e,t);var n=Reflect.getOwnPropertyDescriptor(e,t),i=r.get(t);if(i!==void 0){var a=W(i);if(a===he)return;if(n&&`value`in n)n.value=a;else return{enumerable:!0,configurable:!0,value:a,writable:!0}}return n},has(e,t){if(t===re)return!0;var n=r.get(t),i=n!==void 0&&n.v!==he||Reflect.has(e,t);return(n!==void 0||U!==null&&(!i||a(e,t)?.writable))&&(n===void 0&&(n=f(()=>F(i?Zt(e[t]):he,u)),r.set(t,n)),W(n)===he)?!1:i},set(e,t,n,s){var c=r.get(t),l=t in e;if(i&&t===`length`)for(var d=n;dF(he,u)),r.set(d+``,p)):I(p,he)}if(c===void 0)(!l||a(e,t)?.writable)&&(c=f(()=>F(void 0,u)),I(c,Zt(n)),r.set(t,c));else{l=c.v!==he;var m=f(()=>Zt(n));I(c,m)}var h=Reflect.getOwnPropertyDescriptor(e,t);if(h?.set&&h.set.call(s,n),!l){if(i&&typeof t==`string`){var g=r.get(`length`),_=Number(t);Number.isInteger(_)&&_>=g.v&&I(g,_+1)}Yt(o)}return!0},ownKeys(e){W(o);var t=Reflect.ownKeys(e).filter(e=>{var t=r.get(e);return t===void 0||t.v!==he});for(var[n,i]of r)i.v!==he&&!(n in e)&&t.push(n);return t},setPrototypeOf(){ze()}})}function Qt(e){try{if(typeof e==`object`&&e&&re in e)return e[re]}catch{}return e}function $t(e,t){return Object.is(Qt(e),Qt(t))}var en,tn,nn,rn;function an(){if(en===void 0){en=window,tn=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,n=Text.prototype;nn=a(t,`firstChild`).get,rn=a(t,`nextSibling`).get,u(e)&&(e[ce]=void 0,e[se]=null,e[le]=void 0,e.__e=void 0),u(n)&&(n[ue]=void 0)}}function on(e=``){return document.createTextNode(e)}function sn(e){return nn.call(e)}function cn(e){return rn.call(e)}function L(e,t){if(!k)return sn(e);var n=sn(A);if(n===null)n=A.appendChild(on());else if(t&&n.nodeType!==3){var r=on();return n?.before(r),Se(r),r}return t&&fn(n),Se(n),n}function R(e,t=!1){if(!k){var n=sn(e);return n instanceof Comment&&n.data===``?cn(n):n}if(t){if(A?.nodeType!==3){var r=on();return A?.before(r),Se(r),r}fn(A)}return A}function z(e,t=!1){if(!k)return sn(e);var n=L(e,t);return j(e),n}function B(e,t=1,n=!1){let r=k?A:e;for(var i;t--;)i=r,r=cn(r);if(!k)return r;if(n){if(r?.nodeType!==3){var a=on();return r===null?i?.after(a):r.before(a),Se(a),a}fn(r)}return Se(r),r}function ln(e){e.textContent=``}function un(){return!1}function dn(e,t,n){return t==null||t===`http://www.w3.org/1999/xhtml`?n?document.createElement(e,{is:n}):document.createElement(e):n?document.createElementNS(t,e,{is:n}):document.createElementNS(t,e)}function fn(e){if(e.nodeValue.length<65536)return;let t=e.nextSibling;for(;t!==null&&t.nodeType===3;)t.remove(),e.nodeValue+=t.nodeValue,t=e.nextSibling}function pn(e){var t=U;if(t===null)return H.f|=O,e;if(!(t.f&32768)&&!(t.f&4))throw e;mn(e,t)}function mn(e,t){if(!(t!==null&&t.f&16384)){for(;t!==null;){if(t.f&128&&!(t.f&33570816)){if(!(t.f&32768))throw e;try{t.b.error(e);return}catch(t){e=t}}t=t.parent}throw e}}function hn(e){U===null&&(H===null&&Fe(e),Pe()),Hn&&Ne(e)}function gn(e,t){var n=t.last;n===null?t.last=t.first=e:(n.next=e,e.prev=n,t.last=e)}function _n(e,t){var n=U;n!==null&&n.f&8192&&(e|=x);var r={ctx:M,deps:null,nodes:null,f:e|y|512,first:null,fn:t,last:null,next:null,parent:n,b:n&&n.b,prev:null,teardown:null,wv:0,ac:null};P?.register_created_effect(r);var i=r;if(e&4)Dt===null?jt.ensure().schedule(r):Dt.push(r);else if(t!==null){try{ur(r)}catch(e){throw Mn(r),e}i.deps===null&&i.teardown===null&&i.nodes===null&&i.first===i.last&&!(i.f&524288)&&(i=i.first,e&16&&e&65536&&i!==null&&(i.f|=T))}if(i!==null&&(i.parent=n,n!==null&&gn(i,n),H!==null&&H.f&2&&!(e&64))){var a=H;(a.effects??=[]).push(i)}return r}function vn(){return H!==null&&!Wn}function yn(e){let t=_n(8,null);return et(t,v),t.teardown=e,t}function bn(e){hn(`$effect`);var t=U.f;if(!H&&t&32&&M!==null&&!M.i){var n=M;(n.e??=[]).push(e)}else return xn(e)}function xn(e){return _n(4|te,e)}function Sn(e){return hn(`$effect.pre`),_n(8|te,e)}function Cn(e){jt.ensure();let t=_n(64|ee,e);return(e={})=>new Promise(n=>{e.outro?Fn(t,()=>{Mn(t),n(void 0)}):(Mn(t),n(void 0))})}function wn(e){return _n(4,e)}function Tn(e){return _n(ne|ee,e)}function En(e,t=0){return _n(8|t,e)}function V(e,t=[],n=[],r=[]){ct(r,t,n,t=>{_n(8,()=>{e(...t.map(W))})})}function Dn(e,t=0){return _n(16|t,e)}function On(e){return _n(32|ee,e)}function kn(e){var t=e.teardown;if(t!==null){let n=Hn,r=H;Un(!0),Gn(null);try{t.call(null)}catch(t){mn(t,e.parent)}finally{Un(n),Gn(r)}}}function An(e,t=!1){var n=e.first;for(e.first=e.last=null;n!==null;){let e=n.ac;e!==null&&ot(()=>{e.abort(fe)});var r=n.next;n.f&64?n.parent=null:Mn(n,t),n=r}}function jn(e){for(var t=e.first;t!==null;){var n=t.next;t.f&32||Mn(t),t=n}}function Mn(e,t=!0){var n=!1;(t||e.f&262144)&&e.nodes!==null&&e.nodes.end!==null&&(Nn(e.nodes.start,e.nodes.end),n=!0),e.f|=w,An(e,t&&!n),lr(e,0);var r=e.nodes&&e.nodes.t;if(r!==null)for(let e of r)e.stop();kn(e),e.f^=w,e.f|=S;var i=e.parent;i!==null&&i.first!==null&&Pn(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes=e.ac=e.b=null}function Nn(e,t){for(;e!==null;){var n=e===t?null:cn(e);e.remove(),e=n}}function Pn(e){var t=e.parent,n=e.prev,r=e.next;n!==null&&(n.next=r),r!==null&&(r.prev=n),t!==null&&(t.first===e&&(t.first=r),t.last===e&&(t.last=n))}function Fn(e,t,n=!0){var r=[];e.f|=256,In(e,r,!0);var i=()=>{n&&Mn(e),t&&t()},a=r.length;if(a>0){var o=()=>--a||i();for(var s of r)s.out(o)}else i()}function In(e,t,n){if(!(e.f&8192)){e.f^=x;var r=e.nodes&&e.nodes.t;if(r!==null)for(let e of r)(e.is_global||n)&&t.push(e);for(var i=e.first;i!==null;){var a=i.next;if(!(i.f&64)){var o=!!(i.f&65536)||!!(i.f&32)&&!!(e.f&16);In(i,t,o?n:!1)}i=a}}}function Ln(e){e.f&=-257,Rn(e,!0)}function Rn(e,t){if(!(e.f&256)&&e.f&8192){e.f^=x,e.f&1024||(et(e,y),jt.ensure().schedule(e));for(var n=e.first;n!==null;){var r=n.next,i=!!(n.f&65536)||!!(n.f&32);Rn(n,i?t:!1),n=r}var a=e.nodes&&e.nodes.t;if(a!==null)for(let e of a)(e.is_global||t)&&e.in()}}function zn(e,t){if(e.nodes)for(var n=e.nodes.start,r=e.nodes.end;n!==null;){var i=n===r?null:cn(n);t.append(n),n=i}}var Bn=null,Vn=!1,Hn=!1;function Un(e){Hn=e}var H=null,Wn=!1;function Gn(e){H=e}var U=null;function Kn(e){U=e}var qn=null;function Jn(e){H!==null&&(H.f&2097152||H.f&2)&&(qn??=new Set).add(e)}var Yn=null,Xn=0,Zn=null;function Qn(e){Zn=e}var $n=1,er=0,tr=er;function nr(e){tr=e}function rr(){return++$n}function ir(e){var t=e.f;if(t&2048)return!0;if(t&4096){for(var n=e.deps,r=n.length,i=0;ie.wv)return!0}t&512&&Ct===null&&et(e,v)}return!1}function ar(e,t,n=!0){var r=e.reactions;if(r!==null&&!(qn!==null&&qn.has(e)))for(var i=0;i{e.ac.abort(fe)}),e.ac=null);try{e.f|=D;var u=e.fn,d=u();e.f|=C;var f=sr(e);if(Je()&&Zn!==null&&!Wn&&f!==null&&!(e.f&6146))for(var p=0;p0)for(t.length=Xn+Yn.length,r=0;r{s.ac.abort(fe),s.ac=null,et(s,y)}),yt(s),lr(s,0)}}function lr(e,t){var n=e.deps;if(n!==null)for(var r=t;rn?.call(this,e))}return e.startsWith(`pointer`)||e.startsWith(`touch`)||e===`wheel`?(i.__removed=!1,Ze(()=>{i.__removed||t.addEventListener(e,i,r)})):t.addEventListener(e,i,r),i}function Cr(e,t,n,r,i){var a={capture:r,passive:i},o=Sr(e,t,n,a);(t===document.body||t===window||t===document||t instanceof HTMLMediaElement)&&yn(()=>{o.__removed=!0,t.removeEventListener(e,o,a)})}function G(e,t,n){(t[yr]??={})[e]=n}function wr(e){for(var t=0;t{Er=!1,Tr=null}));var s=0,c=Tr===e&&e[yr];if(c){var l=a.indexOf(c);if(l!==-1&&(t===document||t===window)){e[yr]=t;return}var u=a.indexOf(t);if(u===-1)return;l<=u&&(s=l)}if(o=a[s]||e.target,o!==t){i(e,`currentTarget`,{configurable:!0,get(){return o||n}});var d=H,f=U;Gn(null),Kn(null);try{for(var p,m=[];o!==null&&o!==t;){try{var h=o[yr]?.[r];h!=null&&(!o.disabled||e.target===o)&&h.call(o,e)}catch(e){p?m.push(e):p=e}if(e.cancelBubble)break;s++,o=s{throw e});throw p}}finally{e[yr]=t,delete e.currentTarget,Gn(d),Kn(f)}}}var Or=globalThis?.window?.trustedTypes&&globalThis.window.trustedTypes.createPolicy(`svelte-trusted-html`,{createHTML:e=>e});function kr(e){return Or?.createHTML(e)??e}function Ar(e){var t=dn(`template`);return t.innerHTML=kr(e.replaceAll(``,``)),t.content}function jr(e,t){var n=U;n.nodes===null&&(n.nodes={start:e,end:t,a:null,t:null})}function K(e,t){var n=!!(t&1),r=!!(t&2),i,a=!e.startsWith(``);return()=>{if(k)return jr(A,null),A;i===void 0&&(i=Ar(a?e:``+e),n||(i=sn(i)));var t=r||tn?document.importNode(i,!0):i.cloneNode(!0);if(n){var o=sn(t),s=t.lastChild;jr(o,s)}else jr(t,t);return t}}function Mr(e,t,n=`svg`){var r=!e.startsWith(``),i=!!(t&1),a=`<${n}>${r?e:``+e}`,o;return()=>{if(k)return jr(A,null),A;if(!o){var e=sn(Ar(a));if(i)for(o=document.createDocumentFragment();sn(e);)o.appendChild(sn(e));else o=sn(e)}var t=o.cloneNode(!0);if(i){var n=sn(t),r=t.lastChild;jr(n,r)}else jr(t,t);return t}}function Nr(e,t){return Mr(e,t,`svg`)}function Pr(e=``){if(!k){var t=on(e+``);return jr(t,t),t}var n=A;return n.nodeType===3?fn(n):(n.before(n=on()),Se(n)),jr(n,n),n}function Fr(){if(k)return jr(A,null),A;var e=document.createDocumentFragment(),t=document.createComment(``),n=on();return e.append(t,n),jr(t,n),e}function q(e,t){if(k){var n=U;(!(n.f&32768)||n.nodes.end===null)&&(n.nodes.end=A),Ce();return}e!==null&&e.before(t)}function Ir(){if(k&&A&&A.nodeType===8&&A.textContent?.startsWith(`$`)){let e=A.textContent.substring(1);return Ce(),e}return(window.__svelte??={}).uid??=1,`c${window.__svelte.uid++}`}function Lr(e){let t=0,n=Ht(0),r;return()=>{vn()&&(W(n),En(()=>(t===0&&(r=mr(()=>e(()=>Yt(n)))),t+=1,()=>{Ze(()=>{--t,t===0&&(r?.(),r=void 0,Yt(n))})})))}}var Rr=T|ee;function zr(e,t,n,r){new Br(e,t,n,r)}var Br=class{parent;is_pending=!1;transform_error;#e;#t=k?A:null;#n;#r;#i;#a=null;#o=null;#s=null;#c=null;#l=0;#u=0;#d=!1;#f=new Set;#p=new Set;#m=null;#h=Lr(()=>(this.#m=Ht(this.#l),()=>{this.#m=null}));constructor(e,t,n,r){this.#e=e,this.#n=t,this.#r=e=>{var t=U;t.b=this,t.f|=128,n(e)},this.parent=U.b,this.transform_error=r??this.parent?.transform_error??(e=>e),this.#i=Dn(()=>{if(k){let e=this.#t;Ce();let t=e.data===`[!`;if(e.data.startsWith(`[?`)){let t=JSON.parse(e.data.slice(2));this.#_(t)}else t?this.#y():this.#g()}else this.#b()},Rr),k&&(this.#e=A)}#g(){try{this.#a=On(()=>this.#r(this.#e))}catch(e){this.error(e)}}#_(e){let t=this.#n.failed,{reset:n,invoke_onerror:r}=this.#v(e);Ze(r),t&&(this.#s=On(()=>{t(this.#e,()=>e,()=>n)}))}#v(e){var t=!1,n=!1;let r=()=>{if(t){be();return}t=!0,n&&Ve(),this.#s!==null&&Fn(this.#s,()=>{this.#s=null}),this.#S(()=>{this.#b()})};return{reset:r,invoke_onerror:()=>{try{n=!0,this.#n.onerror?.(e,r),n=!1}catch(e){mn(e,this.#i&&this.#i.parent)}}}}#y(){let e=this.#n.pending;e&&(this.is_pending=!0,this.#o=On(()=>e(this.#e)),Ze(()=>{var e=this.#c=document.createDocumentFragment(),t=on(),n=!1;if(e.append(t),this.#a=this.#S(()=>{try{return On(()=>this.#r(t))}catch(e){try{this.error(e),n=!0}catch(e){mn(e,this.#i.parent)}return null}}),this.#a===null){this.#c=null,n&&this.#x(P);return}this.#u===0&&(this.#e.before(e),this.#c=null,Fn(this.#o,()=>{this.#o=null}),this.#x(P))}))}#b(){try{if(this.is_pending=this.has_pending_snippet(),this.#u=0,this.#l=0,this.#a=On(()=>{this.#r(this.#e)}),this.#u>0){var e=this.#c=document.createDocumentFragment();zn(this.#a,e);let t=this.#n.pending;this.#o=On(()=>t(this.#e))}else this.#x(P)}catch(e){this.error(e)}}#x(e){this.is_pending=!1,e.transfer_effects(this.#f,this.#p)}defer_effect(e){nt(e,this.#f,this.#p)}is_rendered(){return!this.is_pending&&(!this.parent||this.parent.is_rendered())}has_pending_snippet(){return!!this.#n.pending}#S(e){var t=U,n=H,r=M;Kn(this.#i),Gn(this.#i),We(this.#i.ctx);try{return jt.ensure(),e()}finally{Kn(t),Gn(n),We(r)}}#C(e,t){if(!this.has_pending_snippet()){this.parent&&this.parent.#C(e,t);return}this.#u+=e,this.#u===0&&(this.#x(t),this.#o&&Fn(this.#o,()=>{this.#o=null}),this.#c&&=(this.#e.before(this.#c),null))}update_pending_count(e,t){this.#C(e,t),this.#l+=e,!(!this.#m||this.#d)&&(this.#d=!0,Ze(()=>{this.#d=!1,this.#m&&Kt(this.#m,this.#l)}))}get_effect_pending(){return this.#h(),W(this.#m)}error(e){if(!this.#n.onerror&&!this.#n.failed)throw e;P?.is_fork?(this.#a&&P.skip_effect(this.#a),this.#o&&P.skip_effect(this.#o),this.#s&&P.skip_effect(this.#s),P.oncommit(()=>{this.#w(e)})):this.#w(e)}#w(e){this.#a&&=(Mn(this.#a),null),this.#o&&=(Mn(this.#o),null),this.#s&&=(Mn(this.#s),null),k&&(Se(this.#t),we(),Se(Te()));let t=this.#n.failed,n=e=>{let{reset:n,invoke_onerror:r}=this.#v(e);r(),t&&(this.#s=this.#S(()=>{try{return On(()=>{var r=U;r.b=this,r.f|=128,t(this.#e,()=>e,()=>n)})}catch(e){return mn(e,this.#i.parent),null}}))};Ze(()=>{var t;try{t=this.transform_error(e)}catch(e){mn(e,this.#i&&this.#i.parent);return}typeof t==`object`&&t&&typeof t.then==`function`?t.then(n,e=>mn(e,this.#i&&this.#i.parent)):n(t)})}};function J(e,t){var n=t==null?``:typeof t==`object`?`${t}`:t;n!==(e[ue]??=e.nodeValue)&&(e[ue]=n,e.nodeValue=`${n}`)}function Vr(e,t){return Ur(e,t)}var Hr=new Map;function Ur(e,{target:t,anchor:n,props:i={},events:a,context:o,intro:s=!0,transformError:c}){an();var l=void 0,u=Cn(()=>{var s=n??t.appendChild(on());zr(s,{pending:()=>{}},t=>{Ge({});var n=M;if(o&&(n.c=o),a&&(i.$$events=a),k&&jr(t,null),l=e(t,i)||qe(),k&&(U.nodes.end=A,A===null||A.nodeType!==8||A.data!==`]`))throw ve(),me;Ke()},c);var u=new Set,d=e=>{for(var n=0;n{for(var e of u)for(let n of[t,document]){var r=Hr.get(n),i=r.get(e);--i==0?(n.removeEventListener(e,Dr),r.delete(e),r.size===0&&Hr.delete(n)):r.set(e,i)}xr.delete(d),s!==n&&s.parentNode?.removeChild(s)}});return Wr.set(l,u),l}var Wr=new WeakMap,Gr=class{anchor;#e=new Map;#t=new Map;#n=new Map;#r=new Set;#i=!0;constructor(e,t=!0){this.anchor=e,this.#i=t}#a=e=>{if(this.#e.has(e)){var t=this.#e.get(e),n=this.#t.get(t);if(n)Ln(n),this.#r.delete(t);else{var r=this.#n.get(t);r&&(Ln(r.effect),this.#t.set(t,r.effect),this.#n.delete(t),r.fragment.lastChild.remove(),this.anchor.before(r.fragment),n=r.effect)}for(let[t,n]of this.#e){if(this.#e.delete(t),t===e)break;let r=this.#n.get(n);r&&(Mn(r.effect),this.#n.delete(n))}for(let[e,r]of this.#t){if(e===t||this.#r.has(e))continue;let i=()=>{if(Array.from(this.#e.values()).includes(e)){var t=document.createDocumentFragment();zn(r,t),t.append(on()),this.#n.set(e,{effect:r,fragment:t})}else Mn(r);this.#r.delete(e),this.#t.delete(e)};this.#i||!n?(this.#r.add(e),Fn(r,i,!1)):i()}}};#o=e=>{this.#e.delete(e);let t=Array.from(this.#e.values());for(let[e,n]of this.#n)t.includes(e)||(Mn(n.effect),this.#n.delete(e))};ensure(e,t){var n=P,r=un();if(t&&!this.#t.has(e)&&!this.#n.has(e)){if(r){var i=document.createDocumentFragment(),a=on();i.append(a),this.#n.set(e,{effect:On(()=>t(a)),fragment:i})}else this.#t.set(e,On(()=>t(this.anchor)))}if(this.#e.set(n,e),r){for(let[t,r]of this.#t)t===e?n.unskip_effect(r):n.skip_effect(r);for(let[t,r]of this.#n)t===e?n.unskip_effect(r.effect):n.skip_effect(r.effect);n.oncommit(this.#a),n.ondiscard(this.#o)}else k&&(this.anchor=A),this.#a(n)}};function Y(e,t,n=!1){var r;k&&(r=A,Ce());var i=new Gr(e),a=n?T:0;function o(e,t){if(k){var n=Ee(r);if(e!==parseInt(n.substring(1))){var a=Te();Se(a),i.anchor=a,xe(!1),i.ensure(e,t),xe(!0);return}}i.ensure(e,t)}Dn(()=>{var e=!1;t((t,n=0)=>{e=!0,o(n,t)}),e||o(-1,null)},a)}var Kr=Symbol(`NaN`);function qr(e,t,n){k&&Ce();var r=new Gr(e),i=!Je();Dn(()=>{var e=t();e!==e&&(e=Kr),i&&typeof e==`object`&&e&&(e={}),r.ensure(e,n)})}function X(e,t){return t}function Jr(e,t,n){for(var i=[],a=t.length,o,s=t.length,c=0;c{if(o){if(o.pending.delete(n),o.done.add(n),o.pending.size===0){var t=e.outrogroups;Yr(e,r(o.done)),t.delete(o),t.size===0&&(e.outrogroups=null)}}else--s},!1)}if(s===0){var l=i.length===0&&n!==null&&e.pending.size===0;if(l){var u=n,d=u.parentNode;ln(d),d.append(u),e.items.clear()}Yr(e,t,!l)}else o={pending:new Set(t),done:new Set},(e.outrogroups??=new Set).add(o)}function Yr(e,t,n=!0){var r;if(e.pending.size>0){r=new Set;for(let t of e.pending.values())for(let n of t)r.add(e.items.get(n).e)}for(var i=0;i{var t=i();return e(t)?t:t==null?[]:r(t)}),p,m=new Map,h=!0;function g(e){v.effect.f&16384||(v.pending.delete(e),v.fallback=d,Qr(v,p,c,n,a),d!==null&&(p.length===0?d.f&33554432?(d.f^=E,ei(d,null,c)):Ln(d):Fn(d,()=>{d=null})))}function _(e){v.pending.delete(e)}var v={effect:Dn(()=>{p=W(f);var e=p.length;let t=!1;k&&Ee(c)===`[!`!=(e===0)&&(c=Te(),Se(c),xe(!1),t=!0);for(var r=new Set,u=P,v=un(),y=0;ys(c)):(d=On(()=>s(Xr??=on())),d.f|=E)),e>r.size&&Me(``,``,``),k&&e>0&&Se(Te()),!h){if(m.set(u,r),v){for(let[e,t]of l)r.has(e)||u.skip_effect(t.e);u.oncommit(g),u.ondiscard(_)}else g(u)}t&&xe(!0),W(f)}),flags:n,items:l,pending:m,outrogroups:null,fallback:d};h=!1,k&&(c=A)}function Zr(e){for(;e!==null&&!(e.f&32);)e=e.next;return e}function Qr(e,t,n,i,a){var o=!!(i&8),s=t.length,c=e.items,l=Zr(e.effect.first),u,d=null,f,p=[],m=[],h,g,_,v;if(o)for(v=0;v0){var ee=i&4&&s===0?n:null;if(o){for(v=0;v{if(f!==void 0)for(_ of f)_.nodes?.a?.apply()})}function $r(e,t,n,r,i,a,o,s){var c=o&1?o&16?Ht(n):Ut(n,!1,!1):null,l=o&2?Ht(i):null;return{v:c,i:l,e:On(()=>(a(t,c??n,l??i,s),()=>{e.delete(r)}))}}function ei(e,t,n){if(e.nodes)for(var r=e.nodes.start,i=e.nodes.end,a=t&&!(t.f&33554432)?t.nodes.start:n;r!==null;){var o=cn(r);if(a.before(r),r===i)return;r=o}}function ti(e,t,n){t===null?e.effect.first=n:t.next=n,n===null?e.effect.last=t:n.prev=t}function ni(e,t,n){var r;k&&(r=A,Ce());var i=new Gr(e);Dn(()=>{var e=t()??null;if(k&&Ee(r)===`[`!=(e!==null)){var a=Te();Se(a),i.anchor=a,xe(!1),i.ensure(e,e&&(t=>n(t,e))),xe(!0);return}i.ensure(e,e&&(t=>n(t,e)))},T)}var ri=[...` +\r\f\xA0\v`];function ii(e,t,n){var r=e==null?``:``+e;if(t&&(r=r?r+` `+t:t),n){for(var i of Object.keys(n))if(n[i])r=r?r+` `+i:i;else if(r.length)for(var a=i.length,o=0;(o=r.indexOf(i,o))>=0;){var s=o+a;(o===0||ri.includes(r[o-1]))&&(s===r.length||ri.includes(r[s]))?r=(o===0?``:r.substring(0,o))+r.substring(s+1):o=s}}return r===``?null:r}function ai(e,t=!1){var n=t?` !important;`:`;`,r=``;for(var i of Object.keys(e)){var a=e[i];a!=null&&a!==``&&(r+=` `+i+`: `+a+n)}return r}function oi(e){return e[0]!==`-`||e[1]!==`-`?e.toLowerCase():e}function si(e,t){if(t){var n=``,r,i;if(Array.isArray(t)?(r=t[0],i=t[1]):r=t,e){e=String(e).replaceAll(/\/\*.*?\*\//g,``).trim();var a=!1,o=0,s=!1,c=[];r&&c.push(...Object.keys(r).map(oi)),i&&c.push(...Object.keys(i).map(oi));var l=0,u=-1;let t=e.length;for(var d=0;d{t.every(_i)||(`__defaultValue`in e&&fi(e,!1),`__value`in e&&pi(e,e.__value))});t.observe(e,{childList:!0,subtree:!0,attributes:!0,attributeFilter:[`value`]}),yn(()=>{t.disconnect()})}function hi(e,t,n=t){var r=new WeakSet,i=!0;st(e,`change`,t=>{var i=t?`[selected]`:`:checked`,a;if(e.multiple)a=[].map.call(e.querySelectorAll(i),gi);else{var o=e.querySelector(i)??e.querySelector(`option:not([disabled])`);a=o&&gi(o)}n(a),e.__value=a,P!==null&&r.add(P)}),wn(()=>{var a=t();if(e===document.activeElement){var o=P;if(r.has(o))return}if(pi(e,a,i),i&&a===void 0){var s=e.querySelector(`:checked`);s!==null&&(a=gi(s),n(a))}e.__value=a,i=!1})}function gi(e){return`__value`in e?e.__value:e.value}function _i(e){if(e.target.closest(`selectedcontent`)!==null)return!0;if(e.type===`childList`){var t=[...e.addedNodes,...e.removedNodes];return t.length>0&&t.every(e=>e.nodeName===`SELECTEDCONTENT`)}return!1}var vi=Symbol(`is custom element`),yi=Symbol(`is html`),bi=pe?`link`:`LINK`;function xi(e){if(k){var t=!1,n=()=>{if(!t){if(t=!0,e.hasAttribute(`value`)){var n=e.value;Q(e,`value`,null),e.value=n}if(e.hasAttribute(`checked`)){var r=e.checked;Q(e,`checked`,null),e.checked=r}}};e[de]=n,Ze(n),at()}}function Si(e,t){var n=Ci(e);n.checked!==(n.checked=t??void 0)&&(e.checked=t)}function Q(e,t,n,r){var i=Ci(e);k&&(i[t]=e.getAttribute(t),t===`src`||t===`srcset`||t===`href`&&e.nodeName===bi)||i[t]!==(i[t]=n)&&(t===`loading`&&(e[oe]=n),n==null?e.removeAttribute(t):typeof n!=`string`&&Ti(e).has(t)?e[t]=n:e.setAttribute(t,n))}function Ci(e){return e[se]??={[vi]:e.nodeName.includes(`-`),[yi]:e.namespaceURI===ge}}var wi=new Map;function Ti(e){var t=e.getAttribute(`is`)||e.nodeName,n=wi.get(t);if(n)return n;wi.set(t,n=new Set);for(var r,i=e,a=Element.prototype;a!==i;){for(var s in r=o(i),r)r[s].set&&s!==`innerHTML`&&s!==`textContent`&&s!==`innerText`&&n.add(s);i=l(i)}return n}function Ei(e,t,n=t){var r=new WeakSet;st(e,`input`,async i=>{var a=i?e.defaultValue:e.value;if(a=ji(e)?Mi(a):a,n(a),P!==null&&r.add(P),await dr(),a!==(a=t())){var o=e.selectionStart,s=e.selectionEnd,c=e.value.length;if(e.value=a??``,s!==null){var l=e.value.length;o===s&&s===c&&l>c?(e.selectionStart=l,e.selectionEnd=l):(e.selectionStart=o,e.selectionEnd=Math.min(s,l))}}}),(k&&e.defaultValue!==e.value||mr(t)==null&&e.value)&&(n(ji(e)?Mi(e.value):e.value),P!==null&&r.add(P)),En(()=>{var n=t();if(e===document.activeElement){var i=P;if(r.has(i))return}ji(e)&&n===Mi(e.value)||(e.type!==`date`||n||e.value)&&n!==e.value&&(e.value=n??``)})}var Di=new Set;function Oi(e,t,n,r,i=r){var a=n.getAttribute(`type`)===`checkbox`,o=e;let s=!1;if(t!==null)for(var c of t)o=o[c]??=[];o.push(n),st(n,`change`,()=>{var e=n.__value;a&&(e=Ai(o,e,n.checked)),i(e)},()=>i(a?[]:null)),En(()=>{var e=r();if(k&&n.defaultChecked!==n.checked){s=!0;return}a?(e||=[],n.checked=e.includes(n.__value)):n.checked=$t(n.__value,e)}),yn(()=>{var e=o.indexOf(n);e!==-1&&o.splice(e,1)}),Di.has(o)||(Di.add(o),Ze(()=>{o.sort((e,t)=>e.compareDocumentPosition(t)===4?-1:1),Di.delete(o)})),Ze(()=>{if(s){var e=a?Ai(o,e,n.checked):o.find(e=>e.checked)?.__value;i(e)}})}function ki(e,t,n=t){st(e,`change`,t=>{n(t?e.defaultChecked:e.checked)}),(k&&e.defaultChecked!==e.checked||mr(t)==null)&&n(e.checked),En(()=>{e.checked=!!t()})}function Ai(e,t,n){for(var r=new Set,i=0;i{var n=this.#e.get(e);n.delete(t),n.size===0&&(this.#e.delete(e),this.#t.unobserve(e))}}#r(){return this.#t??=new ResizeObserver(t=>{for(var n of t){e.entries.set(n.target,n);for(var r of this.#e.get(n.target)||[])r(n)}})}}({box:`border-box`});function Pi(e,t,n){var r=Ni.observe(e,()=>n(e[t]));wn(()=>(mr(()=>n(e[t])),r))}function Fi(e,t){return e===t||e?.[re]===t}function Ii(e=qe(),t,n,r){var i=M.r,a=U;return wn(()=>{var o,s;return En(()=>{o=s,s=r?.()||[],mr(()=>{Fi(n(...s),e)||(t(e,...s),o&&Fi(n(...o),e)&&t(null,...o))})}),()=>{let r=a;for(;r!==i&&r.parent!==null&&r.parent.f&33554432;)r=r.parent;let o=()=>{s&&Fi(n(...s),e)&&t(null,...s)},c=r.teardown;r.teardown=()=>{o(),c?.()}}}),e}function Li(e,t,n,r,i){var a=()=>{r(n[e])};n.addEventListener(t,a),i?En(()=>{n[e]=i()}):a(),(n===document.body||n===window||n===document)&&yn(()=>{n.removeEventListener(t,a)})}function Ri(e=!1){let t=M,n=t.l.u;if(!n)return;let r=()=>hr(t.s);if(e){let e=0,n={},i=ft(()=>{let r=!1,i=t.s;for(let e in i)i[e]!==n[e]&&(n[e]=i[e],r=!0);return r&&e++,e});r=()=>W(i)}n.b.length&&Sn(()=>{zi(t,r),m(n.b)}),bn(()=>{let e=mr(()=>n.m.map(p));return()=>{for(let t of e)typeof t==`function`&&t()}}),n.a.length&&bn(()=>{zi(t,r),m(n.a)})}function zi(e,t){if(e.l.s)for(let t of e.l.s)W(t);t()}var Bi=!1;function Vi(e){var t=Bi;try{return Bi=!1,[e(),Bi]}finally{Bi=t}}var Hi={get(e,t){let n=e.props.length;for(;n--;){let r=e.props[n];if(d(r)&&(r=r()),typeof r==`object`&&r&&t in r)return r[t]}},set(e,t,n){let r=e.props.length;for(;r--;){let i=e.props[r];d(i)&&(i=i());let o=a(i,t);if(o&&o.set)return o.set(n),!0}return!1},getOwnPropertyDescriptor(e,t){let n=e.props.length;for(;n--;){let r=e.props[n];if(d(r)&&(r=r()),typeof r==`object`&&r&&t in r){let e=a(r,t);return e&&!e.configurable&&(e.configurable=!0),e}}},has(e,t){if(t===re||t===ae)return!1;for(let n of e.props)if(d(n)&&(n=n()),n!=null&&t in n)return!0;return!1},ownKeys(e){let t=[];for(let n of e.props)if(d(n)&&(n=n()),n){for(let e in n)t.includes(e)||t.push(e);for(let e of Object.getOwnPropertySymbols(n))t.includes(e)||t.push(e)}return t}};function Ui(...e){return new Proxy({props:e},Hi)}function Wi(e,t,n,r){var i=!He||!!(n&2),o=!!(n&8),s=!!(n&16),c=r,l=!0,u=void 0,d=()=>s&&i?(u??=ft(r),W(u)):(l&&(l=!1,c=s?mr(r):r),c);let f;if(o){var p=re in e||ae in e;f=a(e,t)?.set??(p&&t in e?n=>e[t]=n:void 0)}var m,h=!1;o?[m,h]=Vi(()=>e[t]):m=e[t],m===void 0&&r!==void 0&&(m=d(),f&&(i&&Le(t),f(m)));var g=i?()=>{var n=e[t];return n===void 0?d():(l=!0,n)}:()=>{var n=e[t];return n!==void 0&&(c=void 0),n===void 0?c:n};if(i&&!(n&4))return g;if(f){var _=e.$$legacy;return(function(e,t){return arguments.length>0?((!i||!t||_||h)&&f(t?g():e),e):g()})}var v=!1,y=(n&1?ft:ht)(()=>(v=!1,g()));o&&W(y);var b=U;return(function(e,t){if(arguments.length>0){let n=t?W(y):i&&o?Zt(e):e;return I(y,n),v=!0,c!==void 0&&(c=n),e}return Hn&&v||b.f&16384?y.v:W(y)})}function Gi(e){M===null&&Ae(`onMount`),He&&M.l!==null?qi(M).m.push(e):bn(()=>{let t=mr(e);if(typeof t==`function`)return t})}function Ki(e){M===null&&Ae(`onDestroy`),Gi(()=>()=>mr(e))}function qi(e){var t=e.l;return t.u??={a:[],b:[],m:[]}}typeof window<`u`&&((window.__svelte??={}).v??=new Set).add(`5`);function Ji(e,t){if(e instanceof RegExp)return{keys:!1,pattern:e};var n,r,i,a,o=[],s=``,c=e.split(`/`);for(c[0]||c.shift();i=c.shift();)n=i[0],n===`*`?(o.push(`wild`),s+=`/(.*)`):n===`:`?(r=i.indexOf(`?`,1),a=i.indexOf(`.`,1),o.push(i.substring(1,~r?r:~a?a:i.length)),s+=~r&&!~a?`(?:/([^/]+?))?`:`/([^/]+?)`,~a&&(s+=(~r?`?`:``)+`\\`+i.substring(a))):s+=`/`+i;return{keys:o,pattern:RegExp(`^`+s+(t?`(?=$|/)`:`/?$`),`i`)}}var Yi=new class{#e=F(Xi());get _loc(){return W(this.#e)}set _loc(e){I(this.#e,e)}#t=N(()=>this._loc.location);get _location(){return W(this.#t)}set _location(e){I(this.#t,e)}#n=N(()=>this._loc.querystring);get _querystring(){return W(this.#n)}set _querystring(e){I(this.#n,e)}#r=F(void 0);get _params(){return W(this.#r)}set _params(e){I(this.#r,e)}get loc(){return this._loc}get location(){return this._location}get querystring(){return this._querystring}get params(){return this._params}constructor(){typeof window<`u`?window.addEventListener(`hashchange`,()=>{this._loc=Xi()}):console.warn(`[svelte-spa-router] window 'window' is not defined, skipping initiation`)}};function Xi(){let e=typeof window<`u`?window.location.href:``,t=e.indexOf(`#/`),n=t>-1?e.substr(t+1):`/`,r=n.indexOf(`?`),i=``;return r>-1&&(i=n.substr(r+1),n=n.substr(0,r)),{location:n,querystring:i}}function Zi(e){e?window.scrollTo(e.__svelte_spa_router_scrollX||0,e.__svelte_spa_router_scrollY||0):window.scrollTo(0,0)}function Qi(e,t){Ge(t,!0);let n=Wi(t,`routes`,19,()=>({})),r=Wi(t,`prefix`,3,``),i=Wi(t,`restoreScrollState`,3,!1),a=Wi(t,`onConditionsFailed`,3,()=>{}),o=Wi(t,`onRouteLoaded`,3,()=>{}),s=Wi(t,`onRouteLoading`,3,()=>{}),c=Wi(t,`onRouteEvent`,3,()=>{});class l{path;component;conditions;userData;props;_pattern;_keys;constructor(e,t){let n=e=>typeof e==`object`&&!!e&&e._sveltesparouter===!0;if(!t||typeof t!=`function`&&!n(t))throw Error(`Invalid component object`);if(!e||typeof e==`string`&&(e.length<1||e.charAt(0)!=`/`&&e.charAt(0)!=`*`)||typeof e==`object`&&!(e instanceof RegExp))throw Error(`Invalid value for "path" argument - strings must start with / or *`);let r=Ji(e);if(this.path=e,n(t)){let e=t;this.component=e.component,this.conditions=e.conditions||[],this.userData=e.userData,this.props=e.props||{}}else{let e=t;this.component=()=>Promise.resolve(e),this.conditions=[],this.props={}}this._pattern=r.pattern,this._keys=r.keys}match(e){if(r()){if(typeof r()==`string`){if(e.startsWith(r()))e=e.substr(r().length)||`/`;else return null}else if(r()instanceof RegExp){let t=e.match(r());if(t&&t[0])e=e.substr(t[0].length)||`/`;else return null}}let t=this._pattern.exec(e);if(t===null)return null;if(this._keys===!1)return t;let n={},i=0;for(;i{u.push(new l(t,e))}):Object.keys(n()).forEach(e=>{let t=n();u.push(new l(e,t[e]))});let d=F(null),f=F(null),p=F({}),m=null,h=null;bn(()=>{history.scrollRestoration=i()?`manual`:`auto`}),bn(()=>{if(!i())return;let e=e=>{m=e.state&&(e.state.__svelte_spa_router_scrollY||e.state.__svelte_spa_router_scrollX)?e.state:null};return window.addEventListener(`popstate`,e),()=>window.removeEventListener(`popstate`,e)});async function g(e,t){await dr(),e(t)}bn(()=>{let e=Yi.loc,t=!1;return mr(async()=>{let n=0;for(;n{t=!0}});function _(e){return e&&typeof e==`object`&&Object.keys(e).length?e:null}var v=Fr(),y=R(v),b=e=>{let t=N(()=>W(d));var n=Fr(),r=R(n),i=e=>{var n=Fr();ni(R(n),()=>W(t),(e,t)=>{t(e,Ui({get params(){return W(f)},get onRouteEvent(){return c()}},()=>W(p)))}),q(e,n)},a=e=>{var n=Fr();ni(R(n),()=>W(t),(e,t)=>{t(e,Ui({get onRouteEvent(){return c()}},()=>W(p)))}),q(e,n)};Y(r,e=>{W(f)?e(i):e(a,-1)}),q(e,n)};Y(y,e=>{W(d)&&e(b)}),q(e,v),Ke()}var $i=[`bars`,`pace`,`zone`,`pie`,`fuel`,`resets`,`thresholds`],ea=`codexometer.web.preferences.v1`,ta={statusLine:null,tab:`quota`,view:`bars`,selected:``,defaultDetail:0,layouts:[]};function na(){try{let e=JSON.parse(localStorage.getItem(ea)||`null`);return!e||typeof e!=`object`?ta:{statusLine:Array.isArray(e.statusLine)?[...new Set(e.statusLine.filter(e=>typeof e==`string`&&e.length<64))].slice(0,32):null,tab:[`quota`,`sessions`,`usage`].includes(e.tab)?e.tab:`quota`,view:$i.includes(e.view)?e.view:`bars`,selected:typeof e.selected==`string`?e.selected.slice(0,256):``,defaultDetail:+(e.defaultDetail===1),layouts:Array.isArray(e.layouts)?e.layouts.filter(e=>{if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.id==`string`&&t.id.length<=256&&[0,1,2].includes(t.level)}).slice(-100):[]}}catch{return ta}}var ra=Zt(na()),ia=Zt({open:!1});function aa(){let e=JSON.stringify(ra);try{localStorage.setItem(ea,e)}catch{}}function oa(){return ra.tab===`quota`?`/quota/`+ra.view:`/`+ra.tab}function sa(e){return ra.layouts.find(t=>t.id===e)?.level??ra.defaultDetail}function ca(e){ra.defaultDetail=e,ra.layouts=[]}function la(e,t){ra.layouts=[...ra.layouts.filter(t=>t.id!==e),{id:e,level:Math.max(0,Math.min(2,t))}].slice(-100)}var $=Zt({data:null,connected:!1,error:``}),ua=e=>e==null?`Unavailable`:e.toLocaleString(`en-GB`),da=e=>!e||String(e).startsWith(`0001-`)?`Not yet available`:new Date(typeof e==`number`?e*1e3:e).toLocaleString(`en-GB`),fa=`codexometer.web.session`,pa=class extends Error{},ma;async function ha(e,t,n){if(!ma||!$.connected||!$.data?.control||$.data.sessionsError)throw Error(`Session controls unavailable. Check the live connection.`);return await ma(e,t,n)}function ga(){let e=new AbortController,t,n=``;ma=async(t,r,i)=>{let a=await fetch(`/api/control/`+t,{method:`POST`,headers:{Authorization:`Bearer ${n}`,"Content-Type":`application/json`},body:JSON.stringify(r),signal:AbortSignal.any([e.signal,AbortSignal.timeout(8e3),...i?[i]:[]]),cache:`no-store`});if(!a.ok)throw a.status===502?Error(`Outcome uncertain. Check Codex before taking another action; nothing was retried.`):new pa(`Action rejected, expired or changed. Refresh and check Codex; nothing was retried.`);return a.json()};try{n=sessionStorage.getItem(fa)||``}catch{}let r=location.hash,i=r.startsWith(`#pair=`)?r.slice(6):``;(i||!r||r===`#`)&&(history.replaceState(null,``,`/#`+oa()),window.dispatchEvent(new HashChangeEvent(`hashchange`)));async function a(){if(i)try{let t=await fetch(`/api/pair`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({secret:i}),signal:e.signal});if(!t.ok)throw Error(`Pairing link expired or already used. Restart --web for a fresh link.`);n=(await t.json()).token;try{sessionStorage.setItem(fa,n)}catch{}}catch(t){e.signal.aborted||($.error=t instanceof Error?t.message:`Pairing failed`);return}if(!n){$.error=`Open the private pairing link printed by codexometer --web in your terminal.`;return}await o()}async function o(){try{let t=await fetch(`/api/events`,{headers:{Authorization:`Bearer ${n}`},signal:e.signal,cache:`no-store`});if(t.status===401){try{sessionStorage.removeItem(fa)}catch{}$.error=`Browser session expired. Restart --web and open its new pairing link.`;return}if(!t.ok||!t.body)throw Error(`Live connection unavailable`);let r=t.body.getReader(),i=new TextDecoder,a=``;for(;!e.signal.aborted;){let{done:e,value:t}=await r.read();if(e)break;a+=i.decode(t,{stream:!0});let n;for(;(n=a.indexOf(` + +`))>=0;){let e=a.slice(0,n);a=a.slice(n+2),e.startsWith(`data: `)&&($.data=JSON.parse(e.slice(6)),$.connected=!0,$.error=``)}}}catch{}finally{$.connected=!1}e.signal.aborted||($.error=`Connection lost — showing last observation. Reconnecting…`,t=setTimeout(o,2500))}return a(),()=>{ma=void 0,e.abort(),clearTimeout(t),$.connected=!1}}Ue();var _a=K(`

Quota refresh failed. Policy state is based on the last successful + observation.

`),va=K(`

`),ya=K(`

The longest Codex quota window selects one active model profile. ASK + creates a per-session review; AUTO applies the profile on the next + eligible check.

`,1),ba=K(`

No threshold-based model steps were configured at launch.

`);function xa(e,t){Ge(t,!1),Ri();var n=Fr(),r=R(n),i=e=>{var t=ya(),n=R(t),r=z(n),i=B(n,2),a=e=>{q(e,_a())};Y(i,e=>{$.data.quotaError&&e(a)});var o=B(i,2),s=L(o),c=z(s),l=B(s,4);Z(l,5,()=>$.data.thresholds,X,(e,t)=>{var n=va();let r;var i=L(n),a=z(i),o=B(i,2),s=L(o),c=z(s,!0),l=z(B(s,2));j(o);var u=B(o,2),d=z(u,!0),f=z(B(u,2));j(n),V((e,i)=>{r=ci(n,1,``,null,r,{active:W(t).state===`ACTIVE`,next:W(t).state===`NEXT`}),J(a,`${W(t).threshold??``}%`),J(c,W(t).model),J(l,`${e??``} REASONING // ${i??``} + SPEED`),J(d,W(t).mode),J(f,`${W(t).state??``}${W(t).state===`NEXT`?` // ${W(t).remaining||0} PP TO GO`:``}`)},[()=>W(t).effort.toUpperCase(),()=>W(t).speed.toUpperCase()]),q(e,n)}),j(l),j(o),V(e=>{J(r,`MODEL STEP POLICY // OBSERVED ${e??``}`),J(c,`THRESHOLDS // ${$.data.thresholds.length??``} CONFIGURED`)},[()=>da($.data.quotaAt)]),q(e,t)},a=e=>{q(e,ba())};Y(r,e=>{$.data?.thresholds?.length?e(i):e(a,-1)}),q(e,n),Ke()}var Sa=Nr(``),Ca=Nr(` `,1),wa=Nr(` `,1),Ta=Nr(`SAFE`),Ea=Nr(` `,1),Da=Nr(` `,1),Oa=K(`
`,1),ka=K(` `),Aa=K(`
Quota observations
Observed atPeriod elapsedConsumedTrail segment
`),ja=K(`
OBSERVATION DATA
`),Ma=K(`
QUOTA PERIOD ELAPSED
`,1);function Na(e,t){let n=Ir();Ge(t,!0);let r=Wi(t,`trail`,19,()=>[]),i=Wi(t,`paceView`,3,!1),a=F(!1),o=F(!0),s=F(`window`),c=[0,25,50,75,100],l={off:0,halfHour:30,hour:60,day:1440,window:0},u=N(()=>i()?[-100,-50,0,50,100]:c),d=N(()=>i()?-100:0),f=N(()=>i()?200:100),p=F(400),m=F(240),h=N(()=>W(p)-24),g=N(()=>W(m)-48),_=N(()=>Math.max(1,W(h)-48)),v=N(()=>Math.max(1,W(g)-20)),y=N(()=>48+Math.max(0,Math.min(100,t.elapsed))/100*W(_));function b(e){return W(g)-(e-W(d))/W(f)*W(v)}function x(e,t){return i()?e-t:e}let S=N(()=>b(x(t.used,t.elapsed))),C=N(()=>t.used-t.elapsed),w=N(()=>r().map((e,t)=>`${t===0||e.break?`M`:`L`}${48+e.elapsed/100*W(_)} ${b(x(e.used,e.elapsed))}`).join(` `)),T=N(()=>{let e=0;for(let t=r().length-1;t>0;t--)if(r()[t].break){e=t;break}return r().slice(e)}),ee=N(()=>W(T).length<2?0:Math.max(0,Date.parse(W(T).at(-1).at)-Date.parse(W(T)[0].at)));function te(e){if(W(ee)Date.parse(e.at)>=t).length>=2}let E=N(()=>({off:!0,halfHour:te(18e5),hour:te(36e5),day:te(864e5),window:Number.isFinite(t.elapsed)&&t.elapsed>0&&Number.isFinite(t.used)}));bn(()=>{W(s)!==`window`&&!W(E)[W(s)]&&I(s,`window`)});function D(e){if(e===`off`||!W(E)[e])return[];let t=l[e]*6e4,n=Date.parse(W(T).at(-1).at)-t,r=W(T).findIndex(e=>Date.parse(e.at)>=n);return r<0?[]:W(T).slice(r)}function ne(e){if(e.length<2)return null;let t=e.reduce((e,t)=>e+t.elapsed,0)/e.length,n=e.reduce((e,t)=>e+t.used,0)/e.length,r=0,i=0;for(let a of e)r+=(a.elapsed-t)*(a.used-n),i+=(a.elapsed-t)**2;return i>0?Math.max(0,r/i):null}let O=N(()=>{let e=W(s)===`window`&&W(E).window?t.used/t.elapsed:ne(D(W(s)));if(e===null||!t.duration)return null;let n=t.used+e*(100-t.elapsed),r=W(s)===`window`?0:t.used-e*t.elapsed,a=e-+!!i(),o=Math.max(0,t.elapsed-l[W(s)]/t.duration*100);W(s)===`window`&&(o=0);let c=100;if(a!==0){let e=(W(d)-r)/a,t=(W(d)+W(f)-r)/a;o=Math.max(o,Math.min(e,t)),c=Math.min(100,Math.max(e,t))}if(o>c)return null;let u=r+a*o,p=r+a*c,m=Math.hypot((c-o)/100*W(_),(p-u)/W(f)*W(v)),h=Math.max(3,Math.min(9,Math.floor(m/65)+2));return{path:`M${Array.from({length:h},(e,t)=>{let n=o+(c-o)*t/(h-1),r=u+(p-u)*t/(h-1);return`${48+n/100*W(_)} ${b(r)}`}).join(` L`)}`,projected:n,safe:n<=100}}),re=N(()=>{if(W(T).length===0)return`NO OBSERVATIONS`;if(W(T).length===1)return`ONE OBSERVATION`;let e=Math.floor(W(ee)/6e4);if(e<60)return`${e} MIN CONTINUOUS`;let t=Math.floor(e/60);return t<24?`${t}H CONTINUOUS`:`${Math.floor(t/24)}D ${t%24}H CONTINUOUS`});var ie=Ma(),ae=R(ie),oe=L(ae);let se;var ce=L(oe),le=L(ce),ue=B(L(le)),de=e=>{q(e,Sa())};Y(ue,e=>{i()&&e(de)});var fe=B(ue);we(),j(le);var pe=B(le);j(ce);var me=B(ce),he=B(me);Z(he,17,()=>c,X,(e,t)=>{var n=Ca(),r=R(n),i=B(r),a=z(i);V(()=>{Q(r,`x1`,48+W(t)/100*W(_)),Q(r,`x2`,48+W(t)/100*W(_)),Q(r,`y2`,W(g)),Q(i,`x`,48+W(t)/100*W(_)),Q(i,`y`,W(g)+20),J(a,`${W(t)??``}%`)}),q(e,n)});var ge=B(he);Z(ge,17,()=>W(u),X,(e,t)=>{var n=wa(),r=R(n),a=B(r),o=z(a,!0);V((e,n,s)=>{Q(r,`y1`,e),Q(r,`x2`,W(h)),Q(r,`y2`,n),Q(a,`y`,s),J(o,i()?`${W(t)>0?`+`:``}${W(t)}`:`${W(t)}%`)},[()=>b(W(t)),()=>b(W(t)),()=>b(W(t))+4]),q(e,n)});var _e=B(ge),ve=B(_e),ye=B(ve),be=e=>{var t=Ta();V(e=>{Q(t,`x`,W(h)-5),Q(t,`y`,e)},[()=>b(0)-6]),q(e,t)};Y(ye,e=>{i()&&e(be)});var k=B(ye),xe=e=>{var t=Ea(),n=R(t),i=B(n),a=B(i),o=z(L(a));j(a),V((e,t)=>{Q(n,`d`,W(w)),Q(i,`d`,W(w)),Q(a,`cx`,48+r()[0].elapsed/100*W(_)),Q(a,`cy`,e),J(o,`First observation: ${t??``}`)},[()=>b(x(r()[0].used,r()[0].elapsed)),()=>da(r()[0].at)]),q(e,t)};Y(k,e=>{W(o)&&r().length&&e(xe)});var A=B(k),Se=e=>{var t=Da(),r=R(t),i=B(r),a=z(L(i));j(i),V(e=>{Q(r,`d`,W(O).path),Q(i,`d`,W(O).path),Q(i,`marker-mid`,`url(#${n}-trend-arrow)`),J(a,`${W(s)===`window`?`Window-average pace`:`Recent consumption trajectory`} projects ${e??``}% at reset if continued`)},[()=>W(O).projected.toFixed(1)]),q(e,t)};Y(A,e=>{W(O)&&e(Se)});var Ce=B(A),Te=z(Ce,!0),Ee=B(Ce),De=B(Ee),Oe=B(De),ke=z(L(Oe));j(Oe),j(oe),j(ae);var Ae=B(ae,2),je=L(Ae),Me=L(je),Ne=e=>{var t=Pr();V(e=>J(t,`${W(C)>0?`+`:``}${e??``} PP FROM SAFETY`),[()=>W(C).toFixed(1)]),q(e,t)},Pe=e=>{var n=Pr();V(()=>J(n,`${t.used??``}% + USED`)),q(e,n)};Y(Me,e=>{i()?e(Ne):e(Pe,-1)});var Fe=B(Me),Ie=B(Fe,3),Le=z(Ie,!0),Re=B(Ie,2),ze=e=>{var t=Oa(),n=z(B(R(t)));V(e=>J(n,`TREND // ${e??``}`),[()=>i()?`${W(O).projected-100>0?`+`:``}${(W(O).projected-100).toFixed(1)} PP AT RESET`:W(O).projected>=100?`QUOTA EXHAUSTION PROJECTED`:`${W(O).projected.toFixed(1)}% PROJECTED AT RESET`]),q(e,t)};Y(Re,e=>{W(O)&&e(ze)}),j(je);var Be=B(je,2),Ve=L(Be),He=L(Ve);xi(He),we(),j(Ve);var Ue=B(Ve,2),M=B(L(Ue),2),We=L(M);We.value=We.__value=`off`;var qe=B(We),Je=z(qe);qe.value=qe.__value=`halfHour`;var Ye=B(qe),Xe=z(Ye);Ye.value=Ye.__value=`hour`;var Ze=B(Ye),Qe=z(Ze);Ze.value=Ze.__value=`day`;var $e=B(Ze),et=z($e);$e.value=$e.__value=`window`,j(M),mi(M),j(Ue);var tt=z(B(Ue,2));j(Be),j(Ae);var nt=B(Ae,2),rt=e=>{var t=ja(),n=B(L(t),2),i=e=>{var t=Aa(),n=L(t),i=B(L(n),2);Z(i,21,r,X,(e,t,n)=>{var r=ka(),i=L(r),a=L(i),o=z(a,!0);j(i);var s=B(i),c=z(s),l=B(s),u=z(l),d=z(B(l),!0);j(r),V((e,r)=>{Q(a,`datetime`,W(t).at),J(o,e),J(c,`${r??``}%`),J(u,`${W(t).used??``}%`),J(d,n===0?`First observation`:W(t).break?`Gap before this observation`:`Connected to previous observation`)},[()=>da(W(t).at),()=>W(t).elapsed.toFixed(1)]),q(e,r)}),j(i),j(n),j(t),q(e,t)};Y(n,e=>{W(a)&&e(i)}),j(t),Li(`open`,`toggle`,t,e=>I(a,e),()=>W(a)),q(e,t)};Y(nt,e=>{r().length&&e(rt)}),V((e,r,a,o,s,c,l)=>{Q(oe,`viewBox`,`0 0 ${W(p)} ${W(m)}`),Q(oe,`aria-label`,e),se=ui(oe,``,se,{"--trend-color":W(O)?.safe?`#115b35`:`#7f1825`}),Q(le,`id`,n),Q(le,`x2`,i()?`0%`:`100%`),Q(fe,`stop-color`,i()?`#ae903b`:`#8c793d`),Q(pe,`id`,n+`-trend-arrow`),Q(me,`width`,W(_)),Q(me,`height`,W(v)),Q(me,`fill`,`url(#${n})`),Q(_e,`d`,`M48 20 V${W(g)} H${W(h)}`),Q(ve,`y1`,r),Q(ve,`x2`,W(h)),Q(ve,`y2`,a),J(Te,i()?`DISTANCE FROM SAFETY (PP)`:`CONSUMPTION`),Q(Ee,`x`,48+W(_)/2),Q(Ee,`y`,W(m)-5),Q(De,`cx`,W(y)),Q(De,`cy`,W(S)),Q(Oe,`cx`,W(y)),Q(Oe,`cy`,W(S)),J(ke,`${t.used??``}% consumed // ${o??``}% of period elapsed${s??``}`),J(Fe,` // ${c??``}% TIME ELAPSED`),J(Le,l),qe.disabled=!W(E).halfHour,J(Je,`LAST 30 MINUTES${W(E).halfHour?``:` — UNAVAILABLE`}`),Ye.disabled=!W(E).hour,J(Xe,`LAST HOUR${W(E).hour?``:` — UNAVAILABLE`}`),Ze.disabled=!W(E).day,J(Qe,`LAST 24 HOURS${W(E).day?``:` — UNAVAILABLE`}`),$e.disabled=!W(E).window,J(et,`FROM WINDOW START${W(E).window?``:` — NO TIME ELAPSED`}`),J(tt,`HISTORY // ${W(re)??``}`)},[()=>`${i()?`Pace zone`:`Consumption zone`}: ${t.used}% consumed, ${t.elapsed.toFixed(1)}% of quota period elapsed. ${i()?`${W(C).toFixed(1)} percentage points from safety.`:`${W(C)>0?`Above`:W(C)<0?`Below`:`On`} the steady-consumption line.`}${W(o)&&r().length?` Trace path contains ${r().length} ${r().length===1?`observation`:`observations`}; gaps are not interpolated.`:``}`,()=>b(0),()=>i()?b(0):b(100),()=>t.elapsed.toFixed(1),()=>i()?` // ${W(C).toFixed(1)} PP from safety`:``,()=>t.elapsed.toFixed(1),()=>Math.abs(W(C))<.05?`ON PACE`:W(C)>0?i()?`ABOVE SAFETY — CONSUMPTION AHEAD OF TIME`:`ABOVE THE LINE — CONSUMING FASTER THAN TIME`:i()?`BELOW SAFETY — QUOTA HEADROOM`:`BELOW THE LINE — WITHIN PACE`]),Pi(ae,`clientWidth`,e=>I(p,e)),Pi(ae,`clientHeight`,e=>I(m,e)),ki(He,()=>W(o),e=>I(o,e)),hi(M,()=>W(s),e=>I(s,e)),q(e,ie),Ke()}var Pa=K(` `),Fa=K(`

Quota refresh failed. Values below are the last successful observation.

`),Ia=K(`

Expiry details unavailable. No listed expiry does not mean no expiry.

`),La=K(`

`),Ra=K(`

Read-only preview. Use the terminal to redeem a reset.

The backend may return only some credits. This list does not establish + redemption order.

`),za=K(` `,1),Ba=Nr(``),Va=Nr(``),Ha=K(`
`),Ua=K(`

Cycle duration or reset date unavailable — position cannot be + plotted.

`),Wa=K(`
EMPTYFULL
`),Ga=K(`

`,1),Ka=K(`
`,1),qa=K(`

`),Ja=K(`

`),Ya=K(`

No quota windows reported yet.

`),Xa=K(`
`,1),Za=K(`

All reported windows are shown. API-equivalent learning and quota status + scoring remain in the terminal for this first preview.

`,1),Qa=K(` `,1);function $a(e,t){Ge(t,!0);let n=Wi(t,`params`,19,()=>({})),r=N(()=>$i.filter(e=>e!==`thresholds`||!!$.data?.thresholds?.length)),i=F(Zt(Date.now())),a=N(()=>W(r).includes(n().view||``)?n().view:`bars`);Gi(()=>{let e=setInterval(()=>I(i,Date.now(),!0),1e3);return()=>clearInterval(e)});function o(e){return!e.duration||e.duration<=0||!e.reset?null:Math.max(0,Math.min(100,100*(1-(e.reset*1e3-W(i))/(e.duration*6e4))))}bn(()=>{ra.view=W(a)});function s(e){let t=e/100*Math.PI*2;return`M60 60 L60 14 A46 46 0 ${+(e>50)} 1 ${60+46*Math.sin(t)} ${60-46*Math.cos(t)} Z`}var c=Qa(),l=R(c);Z(l,21,()=>W(r),X,(e,t)=>{var n=Pa();let r;var i=z(n,!0);V(e=>{Q(n,`href`,`#/quota/`+W(t)),Q(n,`aria-current`,W(a)===W(t)?`page`:void 0),r=ci(n,1,``,null,r,{active:W(a)===W(t)}),J(i,e)},[()=>W(t)===`fuel`?`FUEL TANK`:W(t).toUpperCase()]),q(e,n)}),j(l);var u=B(l,2),d=e=>{var t=Za(),n=R(t),r=e=>{q(e,Fa())};Y(n,e=>{$.data.quotaError&&e(r)});var i=B(n,2),c=z(i),l=B(i,2),u=e=>{xa(e,{})},d=e=>{var t=Ra(),n=L(t),r=z(n),i=B(n,4),a=e=>{q(e,Ia())};Y(i,e=>{$.data.credits.length||e(a)}),Z(B(i,2),17,()=>$.data.credits,X,(e,t)=>{var n=La(),r=L(n),i=z(r),a=z(B(r,2),!0);j(n),V(e=>{J(i,`${(W(t).title||`Quota reset`)??``} // ${W(t).status??``}`),J(a,e)},[()=>W(t).expiryKnown?W(t).expires?`EXPIRES `+da(W(t).expires):`Does not expire`:`Expiry information unavailable`]),q(e,n)}),we(2),j(t),V(()=>J(r,`RESET INVENTORY // ${$.data.creditCount??``} AVAILABLE`)),q(e,t)},f=e=>{var t=Xa(),n=R(t);let r;Z(n,21,()=>$.data.meters,X,(e,t)=>{let n=N(()=>o(W(t)));var r=Ja(),i=L(r),c=z(i,!0),l=B(i,2),u=L(l),d=e=>{var n=za(),r=R(n),i=z(r),a=z(B(r));V(()=>{J(i,`FREE ${100-W(t).used}%`),J(a,`USED ${W(t).used??``}%`)}),q(e,n)},f=e=>{var n=za(),r=R(n),i=z(r),a=z(B(r));V(()=>{J(i,`USED ${W(t).used??``}%`),J(a,`FREE ${100-W(t).used}%`)}),q(e,n)};Y(u,e=>{W(a)===`fuel`?e(d):e(f,-1)}),j(l);var p=B(l,2);let m;var h=L(p),g=e=>{var n=Ha(),r=L(n),i=B(L(r)),a=e=>{q(e,Ba())},o=e=>{var n=Va();V(e=>Q(n,`d`,e),[()=>s(W(t).used)]),q(e,n)};Y(i,e=>{W(t).used>=100?e(a):W(t).used>0&&e(o,1)}),j(r),j(n),V(()=>Q(r,`aria-label`,`${W(t).used}% quota used`)),q(e,n)},_=e=>{var r=Fr(),i=R(r),o=e=>{{let r=N(()=>W(t).trail||[]),i=N(()=>W(a)===`pace`);Na(e,{get used(){return W(t).used},get elapsed(){return W(n)},get trail(){return W(r)},get duration(){return W(t).duration},get paceView(){return W(i)}})}},s=e=>{q(e,Ua())};Y(i,e=>{W(n)===null?e(s,-1):e(o)}),q(e,r)},v=e=>{var r=Ka(),i=R(r),o=L(i);let s;j(i);var c=B(i,2),l=e=>{q(e,Wa())};Y(c,e=>{W(a)===`fuel`&&e(l)});var u=B(c,2),d=e=>{var t=Ga(),r=R(t),i=z(r),o=B(r,2),s=L(o);let c;j(o),V(e=>{J(i,`RESET CYCLE // ${e??``}% ELAPSED`),c=ui(s,``,c,{width:`${W(a)===`fuel`?100-W(n):W(n)}%`})},[()=>Math.floor(W(n))]),q(e,t)};Y(u,e=>{W(n)!==null&&e(d)}),V(()=>{Q(i,`aria-label`,W(a)===`fuel`?`Fuel remaining`:`Quota used`),Q(i,`aria-valuenow`,W(a)===`fuel`?100-W(t).used:W(t).used),s=ui(o,``,s,{width:`${W(a)===`fuel`?100-W(t).used:W(t).used}%`})}),q(e,r)};Y(h,e=>{W(a)===`pie`?e(g):W(a)===`zone`||W(a)===`pace`?e(_,1):e(v,-1)}),j(p);var y=B(p,2),b=z(y),x=B(y,2),S=e=>{var n=qa(),r=z(n,!0);V(()=>J(r,W(t).details)),q(e,n)};Y(x,e=>{W(t).details&&e(S)}),j(r),V(e=>{J(c,W(t).name),m=ci(p,1,`meter-graphic`,null,m,{"bar-graphic":W(a)===`bars`||W(a)===`fuel`}),J(b,`RESETS // ${e??``}`)},[()=>da(W(t).reset)]),q(e,r)}),j(n);var i=B(n,2),c=e=>{q(e,Ya())};Y(i,e=>{$.data.meters.length||e(c)}),V(()=>r=ci(n,1,`quota-grid`,null,r,{radial:W(a)===`pie`,zone:W(a)===`zone`||W(a)===`pace`})),q(e,t)};Y(l,e=>{W(a)===`thresholds`?e(u):W(a)===`resets`?e(d,1):e(f,-1)}),we(2),V(e=>J(c,`QUOTA // OBSERVED ${e??``}`),[()=>da($.data.quotaAt)]),q(e,t)};Y(u,e=>{$.data&&e(d)}),q(e,c),Ke()}var eo=K(`
`),to=K(`

`,1);function no(e,t){Ge(t,!0);let n=Wi(t,`values`,19,()=>[]),r=Wi(t,`label`,3,`Token activity`),i=Wi(t,`capacity`,3,0),a=N(()=>Math.max(0,...n())),o=N(()=>i()>n().length?[...Array(i()-n().length).fill(0),...n()]:n());var s=to(),c=R(s),l=z(c),u=B(c,2);Z(u,21,()=>W(o),X,(e,t)=>{var n=eo();let r;V((e,t)=>{Q(n,`title`,e),r=ui(n,``,r,{height:t})},[()=>W(t).toLocaleString(`en-GB`)+` tokens`,()=>`${100*W(t)/Math.max(1,W(a))}%`]),q(e,n)}),j(u),V((e,t)=>{J(l,`SCALE // 0 — ${e??``} TOKENS`),Q(u,`aria-label`,t)},[()=>W(a).toLocaleString(`en-GB`),()=>`${r()}. Peak ${W(a).toLocaleString(`en-GB`)} tokens.`]),q(e,s),Ke()}var ro=K(`

`),io=K(`

`,1),ao=K(`

`),oo=K(`

Loading / sending…

`),so=K(`

Confirmation expired. Reopen the option.

`),co=K(`

`,1),lo=K(`

 
`,1),uo=K(` `),fo=K(``),po=K(`

No available matching commands.

`),mo=K(`

↑/↓ select · Tab complete · Enter options · Escape dismiss

`),ho=K(`
`,1),go=K(` `,1),_o=K(`
`),vo=K(`
`);function yo(e,t){Ge(t,!0);let n=Wi(t,`query`,3,``),r=Wi(t,`onchange`,3,()=>{}),i=Wi(t,`oncomplete`,3,e=>{}),a=F(!1),o=F(!1),s=F(``),c=F(null),l=F(null),u=F(``),d=F(0),f=F(Zt(Date.now())),p=0,m=F(0),h=F(null),g=F(void 0),_=setInterval(()=>{I(f,Date.now(),!0)},1e3);Ki(()=>{clearInterval(_),p++});let v=N(()=>n().trim().startsWith(`/`)&&!n().trim().startsWith(`//`)),y=N(()=>W(c)?.path===``&&W(v)?n().trim().slice(1).toLowerCase():``),b=N(()=>(W(c)?.choices||[]).filter(e=>!W(y)||e.label.toLowerCase().startsWith(`/`+W(y)))),x=N(()=>!$.connected||!!$.data?.sessionsError),S=N(()=>W(a)&&W(v)&&(!W(c)||W(c).path===``)&&!W(l));bn(()=>{n(),I(m,0)}),bn(()=>{let e=W(m);W(b),W(S)&&dr().then(()=>{let t=W(g)?.children[e];if(!t||!W(g))return;let n=W(g).getBoundingClientRect(),r=t.getBoundingClientRect();r.topn.bottom&&(W(g).scrollTop+=r.bottom-n.bottom)})});function C(e){if(!W(v)||!W(a)||W(c)&&W(c).path!==``||W(l)||W(x)||e.isComposing||e.ctrlKey||e.metaKey||e.altKey||![`ArrowUp`,`ArrowDown`,`Tab`,`Enter`,`Escape`].includes(e.key)||e.key===`Tab`&&e.shiftKey)return;if(e.preventDefault(),e.key===`Escape`){I(a,!1),I(h,n()),I(u,``);return}if(e.key===`ArrowUp`||e.key===`ArrowDown`){I(m,Math.min(Math.max(W(m)+(e.key===`ArrowUp`?-1:1),0),Math.max(W(b).length-1,0)),!0);return}let t=W(b)[W(m)];t&&!W(o)&&(e.key===`Tab`?i()(t.label):T(t))}bn(()=>{W(x)&&(p++,I(o,!1),I(u,``),W(a)&&(I(a,!1),I(h,n())),I(c,null),I(l,null))}),bn(()=>{W(v)&&!W(a)&&n()!==W(h)&&!W(x)&&(I(a,!0),w(``))});async function w(e){let n=++p;I(o,!0),I(l,null),I(u,``),I(s,``);try{let r=await ha(`commands`,{session:t.session,command:{mode:`list`,path:e}});n===p&&((e===``||e===`help`)&&r.choices.push({id:`local-statusline`,label:`/statusline`,help:`Choose multiple fields for Codexometer’s detail footer.`,next:`statusline`}),I(c,r,!0))}catch{n===p&&(I(s,`Command catalogue unavailable. Use Codex.`),(e===``||e===`help`)&&I(c,{title:`/ COMMANDS`,help:`Local display settings remain available.`,path:``,revision:``,choices:[{id:`local-statusline`,label:`/statusline`,help:`Choose multiple fields for Codexometer’s detail footer.`,next:`statusline`}]},!0))}finally{n===p&&I(o,!1)}}async function T(e){if(e.next===`statusline`){ia.open=!0,I(a,!1),I(h,n());return}if(e.next){await w(e.next);return}if(I(l,e,!0),I(u,``),I(s,``),!e.action||!W(c))return;let r=++p;I(o,!0);try{let n=await ha(`commands`,{session:t.session,command:{mode:`prepare`,path:W(c).path,revision:W(c).revision,choice:e.id}});r===p&&(I(u,n.confirmation,!0),I(d,Date.parse(n.expires),!0))}catch{r===p&&I(s,`Option changed or unavailable. Refresh before retrying.`)}finally{r===p&&I(o,!1)}}async function ee(){if(!W(u)||W(x)||W(o)||W(f)>=W(d)||!W(c))return;let e=W(u);I(u,``),I(o,!0);let n=++p;try{let i=await ha(`commands`,{session:t.session,confirmation:e,command:{mode:`commit`,path:W(c).path}});n===p&&(I(s,i.message,!0),I(a,!1),I(c,null),I(l,null),I(d,0),I(h,null),r()())}catch{n===p&&I(s,`Change unconfirmed. Check Codex before retrying.`)}finally{n===p&&I(o,!1)}}var te={handleKey:C},E=vo(),D=L(E),ne=B(D,2),O=e=>{var t=ro(),n=z(t,!0);V(()=>J(n,W(s))),q(e,t)};Y(ne,e=>{W(s)&&!W(a)&&e(O)});var re=B(ne,2),ie=e=>{var n=_o();let r;var i=L(n),a=e=>{var t=io(),n=R(t),r=z(n,!0),i=z(B(n,2),!0);V(()=>{J(r,W(c)?.title||`/ COMMANDS`),J(i,W(c)?.help||`Loading live options…`)}),q(e,t)};Y(i,e=>{W(S)||e(a)});var p=B(i,2),h=e=>{var t=ao(),n=z(t,!0);V(()=>J(n,W(s))),q(e,t)};Y(p,e=>{W(s)&&e(h)});var _=B(p,2),y=e=>{q(e,oo())};Y(_,e=>{W(o)&&e(y)});var C=B(_,2),te=e=>{var n=lo(),r=R(n),i=z(r,!0),a=B(r,2),s=z(a,!0),c=B(a,2),p=e=>{var n=co(),r=R(n),i=z(r),a=B(r,2),s=B(a,2),c=e=>{q(e,so())};Y(s,e=>{W(u)&&W(f)>=W(d)&&e(c)}),V(()=>{J(i,`TARGET // ${t.session??``}. Changes affect subsequent turns of this + session, not global defaults. Automatic quota thresholds may later + supersede model settings.`),a.disabled=W(o)||W(x)||!W(u)||W(f)>=W(d)}),G(`click`,a,ee),q(e,n)};Y(c,e=>{W(l).action&&e(p)});var m=B(c,2);V(()=>{J(i,W(l).label),J(s,W(l).help||`No additional help supplied by Codex.`),m.disabled=W(o)}),G(`click`,m,()=>{I(l,null),I(u,``)}),q(e,n)},E=e=>{var t=ho(),n=R(t);let r;Z(n,23,()=>W(b),e=>e.id,(e,t,n)=>{var r=fo();let i;var a=L(r),s=z(a,!0),l=B(a),u=z(l,!0),d=B(l),f=e=>{var n=uo(),r=z(n,!0);V(()=>J(r,W(t).help)),q(e,n)};Y(d,e=>{W(v)&&W(c)?.path===``&&e(f)}),j(r),V(()=>{r.disabled=W(o)||W(x),Q(r,`title`,W(t).help),Q(r,`aria-label`,W(t).label+(W(t).next?` →`:W(t).action?``:` // HELP`)),i=ci(r,1,`svelte-7bfwr9`,null,i,{suggested:W(v)&&W(c)?.path===``&&W(m)===W(n)}),J(s,W(t).label),J(u,W(t).next?` →`:W(t).action?``:` // HELP`)}),G(`click`,r,()=>T(W(t))),q(e,r)}),j(n),Ii(n,e=>I(g,e),()=>W(g));var i=B(n,2),a=e=>{q(e,po())};Y(i,e=>{!W(o)&&!W(b).length&&e(a)});var s=B(i,2),l=e=>{q(e,mo())};Y(s,e=>{W(v)&&W(c)?.path===``&&e(l)}),V(()=>r=ci(n,1,`command-options svelte-7bfwr9`,null,r,{vertical:W(S)})),q(e,t)};Y(C,e=>{W(l)?e(te):e(E,-1)});var D=B(C,2),ne=e=>{var t=go(),n=R(t),r=B(n,2);V(()=>{n.disabled=W(o),r.disabled=W(o)}),G(`click`,n,()=>w(``)),G(`click`,r,()=>w(W(c)?.path||``)),q(e,t)};Y(D,e=>{W(S)||e(ne)}),j(n),V(()=>{Q(n,`aria-label`,W(S)?`Slash command suggestions`:void 0),r=ci(n,1,`svelte-7bfwr9`,null,r,{"command-popup":W(S)})}),q(e,n)};return Y(re,e=>{W(a)&&e(ie)}),j(E),V(()=>D.disabled=W(x)||W(o)),G(`click`,D,()=>{I(a,!W(a)),I(h,W(a)?null:n(),!0),I(u,``),W(a)&&w(``)}),q(e,E),Ke(te)}wr([`click`]);var bo=K(` `,1),xo=K(`
`),So=K(`
/
`);function Co(e,t){Ge(t,!0);let n=N(()=>t.lines.filter(e=>e.kind===`addition`).length),r=N(()=>t.lines.filter(e=>e.kind===`removal`).length);var i=So(),a=L(i),o=L(a),s=z(o),c=z(B(o,2));j(a),Z(B(a,2),17,()=>t.lines,X,(e,t)=>{var n=xo();let r;var i=L(n),a=e=>{var n=bo(),r=R(n),i=z(r,!0),a=z(B(r),!0);V(()=>{J(i,W(t).old||``),J(a,W(t).new||``)}),q(e,n)};Y(i,e=>{W(t).kind!==`heading`&&W(t).kind!==`metadata`&&e(a)});var o=z(B(i),!0);j(n),V(()=>{r=ci(n,1,`diff-line svelte-x4gtb6`,null,r,{added:W(t).kind===`addition`,removed:W(t).kind===`removal`,heading:W(t).kind===`heading`}),J(o,W(t).text)}),q(e,n)}),j(i),V(()=>{Q(a,`aria-label`,`${W(n)} lines added, ${W(r)} lines removed`),J(s,`+${W(n)??``}`),J(c,`−${W(r)??``}`)}),q(e,i),Ke()}var wo=K(`

`),To=K(`

Send this saved prompt now? This starts Codex work and consumes quota.

`,1),Eo=K(``),Do=K(`

 

In memory only. Follows session model, reasoning and speed. Closing + Codexometer cancels pending work.

`),Oo=K(`

CURRENT PROFILE

MODEL / REASONING LEVEL / SPEED

 

PROPOSED PROFILE

MODEL / REASONING LEVEL / SPEED

 

Applied settings remain after Codexometer closes.

`,1),ko=K(`

`),Ao=K(`

 
`,1),jo=K(`

Command unavailable from this observation. Open Codex to inspect the + request.

`),Mo=K(`

Session controls temporarily unavailable. Check Codex for current state.

`),No=K(`

Checking session controls…

`),Po=K(`

`),Fo=K(`
About browser controls

Controls require a supported live request from a connected shared + app-server session. Local observations alone cannot provide them.

`),Io=K(` `,1),Lo=K(` `),Ro=K(`Grants permission beyond this one action. Check the scope + carefully.`),zo=K(``),Bo=K(``),Vo=K(``),Ho=K(``),Uo=K(`

Not before this time; when quota and idle allow.

`),Wo=K(`

`,1),Go=K(`

The existing trigger remains active until saved or deleted. If + it changes or sends while you edit, saving is blocked.

`),Ko=K(` `,1),qo=K(``),Jo=K(``),Yo=K(``),Xo=K(` `,1),Zo=K(`
  • `),Qo=K(`
    View fixed choices

    Type one of these choices exactly. Your answer stays masked.

      `),$o=K(` `,1),es=K(`

      `),ts=K(`

      `,1),ns=K(` `,1),rs=K(``),is=K(`

      `,1),as=K(`

      `);function os(e,t){Ge(t,!0);let n=[],r=Wi(t,`observedCommand`,3,``),i=Wi(t,`observedFileChanges`,19,()=>[]),a=Wi(t,`review`,3,``),o=Wi(t,`suspended`,3,!1),s=Wi(t,`openScheduler`,3,!1),c=Wi(t,`onProtectedChange`,3,e=>{}),l=F(null),u=F(Zt([])),d=F(null),f=F(``),p=F(0),m=F(Zt(Date.now())),h=F(!1),g=F(``),_=F(!1),v=F(!1),y=F(``),b=F(!1),x=F(``),S=F(!1),C=F(``),w=F(`now`),T=F(60),ee=F(``),te=F(!1),E=F(``),D=F(Zt([])),ne=N(()=>W(D).find(e=>e.status!==`sent`)),O=F(void 0),re=F(void 0);function ie(e){if(W(b)){dr().then(()=>W(O)?.focus());return}if(e.status!==`pending`||W(h)||!W(l)?.id||W(l).kind!==`prompt`||W(l).questions?.length)return;I(b,!0),I(x,e.id,!0),dr().then(()=>W(O)?.focus()),I(u,[e.text],!0),I(w,e.trigger===`quota`?`quota`:`at`,!0),I(f,``),I(C,``);let t=new Date(e.at);e.trigger!==`quota`&&I(ee,new Date(t.getTime()-t.getTimezoneOffset()*6e4).toISOString().slice(0,16),!0)}bn(()=>{s()&&W(ne)&&!W(S)&&W(l)?.id&&W(l).kind===`prompt`&&!W(l).questions?.length&&(I(S,!0),ie(W(ne)))});async function ae(){if(!a())try{I(D,await ha(`schedules`,{session:t.session},be.signal),!0),W(b)&&!W(D).some(e=>e.id===W(x)&&e.status===`pending`)&&I(f,``),W(C)&&!W(D).some(e=>e.id===W(C)&&e.status===`pending`)&&(I(f,``),I(C,``))}catch{}}async function oe(e){try{I(D,await ha(`schedules`,{session:t.session,cancelId:e},be.signal),!0),I(b,!1),I(f,``),I(C,``),I(u,[],!0)}catch(e){I(g,e instanceof Error?e.message:`Unable to cancel trigger.`,!0)}}bn(()=>(c()(W(h)||W(u).some(e=>e.length>0)),()=>c()(!1)));let se=N(()=>$.data?.sessions.find(e=>e.id===t.session)?.status),ce=N(()=>!$.connected||!!$.data?.sessionsError),le=N(()=>a()!==`profile`&&W(l)?.kind===`prompt`&&!W(l).questions?.length&&!!$.data?.profiles?.some(e=>e.session===t.session&&e.pending)),ue=N(()=>!W(ce)&&!W(v)&&!!W(l)?.id&&W(l).kind===`approval`&&W(y)!==W(l).id),de=N(()=>W(ue)?W(l).command:r()),fe=N(()=>!!W(f)&&W(m)W(l)?.questions?.length?W(l).questions:[{text:`Follow-up message`,secret:!1,freeText:!0,options:[]}]),me=N(()=>W(l)?.kind===`approval`||W(l)?.kind===`profile`?W(d)!==null:W(u).length===W(pe).length&&W(u).every((e,t)=>e.trim().length>0&&(W(pe)[t].freeText||W(pe)[t].options?.includes(e)))),he=N(()=>W(l)?.kind===`prompt`&&!W(l).questions?.length&&W(w)!==`now`),ge=N(()=>W(w)===`delay`?W(m)+W(T)*6e4:Date.parse(W(ee))),_e=N(()=>{if(!W(he))return``;if(W(b)&&!W(D).some(e=>e.id===W(x)&&e.status===`pending`))return`Trigger changed, removed or already sent. Go back and reopen it; your draft has not been saved.`;if(!W(u)[0]?.trim())return`Enter a message to schedule.`;if(W(w)===`quota`)return``;if(!Number.isFinite(W(ge))||W(ge)<=W(m))return`Choose a future date/time or a delay greater than zero.`;let e=new Date(W(m));return e.setFullYear(e.getFullYear()+1),W(ge)>=e.getTime()?`Choose a time within one year.`:``});function ve(e){return new Date(e).toLocaleString([],{timeZoneName:`short`})}function ye(e){if(e.status!==`pending`)return e.status;if(e.trigger===`at`&&Date.parse(e.at)>W(m))return`Due in `+Math.ceil((Date.parse(e.at)-W(m))/6e4)+` min`;let t=W(ce)?`waiting for a fresh connection`:e.waitingReason||(e.canSend?`ready; awaiting dispatch`:`waiting for quota and an eligible idle session`);return(e.trigger===`at`?`Time reached — `:``)+t}bn(()=>{(W(ce)||o()||W(le)||W(m)>=W(p))&&I(f,``)});let be=new AbortController;Gi(()=>{let e=e=>{e.ctrlKey&&e.key.toLowerCase()===`s`&&!a()&&W(ne)&&(e.preventDefault(),ie(W(ne)))};window.addEventListener(`keydown`,e);let n,r=setInterval(()=>{I(m,Date.now(),!0)},1e3);async function i(){await ae();try{let e=await ha(`offer`,{session:t.session,...a()?{review:a()}:{}},be.signal);if(be.signal.aborted)return;I(v,!1),W(l)?.id!==e.id&&(I(f,``),I(d,null),I(u,Array(Math.max(1,e.questions?.length||0)).fill(``),!0)),I(l,e,!0),W(_)&&e.id&&e.id!==W(y)&&(I(g,``),I(_,!1))}catch{be.signal.aborted||(I(l,null),I(v,!0),I(f,``))}finally{be.signal.aborted||(n=setTimeout(i,2e3))}}return i(),()=>{window.removeEventListener(`keydown`,e),be.abort(),clearTimeout(n),clearInterval(r)}});async function k(e){if(!e&&W(l)?.kind===`prompt`&&!W(l).questions?.length&&W(u)[0]?.trim().startsWith(`/`)&&!W(u)[0]?.trim().startsWith(`//`)){I(g,`Choose a slash command from COMMANDS; use // for literal slash text.`);return}if(!W(l)?.id||W(h)||W(ce)||o()||W(le)||(e?!e.canSend:!W(me)||W(_e)))return;let n=W(l).id;I(h,!0),I(g,``),I(_,!1),I(f,``),I(C,e?.id||``,!0);try{let r=!e&&W(l).kind===`prompt`&&!W(l).questions?.length&&W(w)!==`now`?{trigger:W(w)===`quota`?`quota`:`at`,zone:Intl.DateTimeFormat().resolvedOptions().timeZone,...W(w)===`quota`?{}:{at:(W(w)===`delay`?new Date(Date.now()+W(T)*6e4):new Date(W(ee))).toISOString()}}:void 0;I(te,!!r),I(E,r?r.trigger===`quota`?`After fresh quota becomes available, when the session is idle`:ve(r.at)+` — not before this time; when quota and idle allow`:``,!0);let i=await ha(`prepare`,{session:t.session,...a()?{review:a()}:{},offer:n,...e?{sendId:e.id}:{},...r?{schedule:r}:{},...r&&W(b)?{editId:W(x)}:{},...W(l).kind===`approval`||W(l).kind===`profile`?{choice:W(d)}:{answers:e?[e.text]:[...W(u)]}},be.signal);W(l)?.id===n&&!be.signal.aborted&&!W(ce)&&!o()&&!W(le)&&(I(f,i.confirmation,!0),I(p,Date.parse(i.expires),!0))}catch(e){be.signal.aborted||I(g,e instanceof Error?e.message:`Unable to prepare action.`,!0)}finally{I(h,!1)}}async function xe(){if(!W(l)?.id||W(h)||o()||W(le)||!W(fe))return;let e=W(l).id,n=W(l).kind,r=W(f);I(f,``),I(h,!0),I(y,e,!0);try{await ha(`commit`,{session:t.session,...a()?{review:a()}:{},offer:e,confirmation:r},be.signal),I(_,!0),I(b,!1),W(te)&&(I(y,``),await ae()),I(g,W(te)?`Trigger set. Closing Codexometer cancels it.`:n===`profile`?`Profile decision completed.`:n===`approval`?`Decision sent.`:`Text sent.`,!0)}catch(e){I(g,e instanceof pa?e.message:`Outcome uncertain. Check Codex before taking another action; nothing was retried.`,!0)}finally{I(h,!1),I(C,``),await ae(),I(u,[],!0),I(d,null)}}var A=Io(),Se=R(A),Ce=e=>{var t=Fr();Z(R(t),17,()=>W(D).filter(e=>e.status!==`sent`),e=>e.id,(e,t)=>{var n=Do(),r=L(n),i=z(r,!0),a=B(r,2),s=z(a),c=B(a,2),u=z(c,!0),d=B(c,2),p=z(d,!0),m=B(d,2),_=z(m,!0),v=B(m,2),y=e=>{var t=wo(),n=z(t,!0);V(()=>J(n,W(g))),q(e,t)};Y(v,e=>{W(g)&&e(y)});var b=B(v,4),x=B(b,2),S=B(x,2),w=e=>{var n=To(),r=B(R(n),2),i=B(r,2);V(()=>r.disabled=W(h)||W(ce)||!W(t).canSend),G(`click`,r,xe),G(`click`,i,()=>{I(f,``),I(C,``)}),q(e,n)},T=e=>{var n=Eo();V(()=>n.disabled=W(h)||W(ce)||!W(t).canSend||o()),G(`click`,n,()=>k(W(t))),q(e,n)};Y(S,e=>{W(fe)&&W(C)===W(t).id?e(w):e(T,-1)}),j(n),V((e,n)=>{J(i,W(t).status===`uncertain`?`CHECK TRIGGER — outcome uncertain; not retried`:`SCHEDULED FOLLOW-UP`),J(s,`TARGET // ${W(t).session??``}`),J(u,e),J(p,n),J(_,W(t).text),b.disabled=W(h)||!W(l)?.id||W(l).kind!==`prompt`||!!W(l).questions?.length||W(t).status!==`pending`,x.disabled=W(h)||W(t).status===`sending`},[()=>W(t).trigger===`quota`?`After confirmed quota recovery, when idle`:`Not before ${ve(W(t).at)}`,()=>ye(W(t))]),G(`click`,b,()=>ie(W(t))),G(`click`,x,()=>oe(W(t).id)),q(e,n)}),q(e,t)},Te=N(()=>!a()&&W(l)?.kind!==`approval`&&!W(l)?.questions?.length&&![`APPROVAL NEEDED`,`INPUT NEEDED`].includes(W(se)||``));Y(Se,e=>{W(Te)&&e(Ce)});var Ee=B(Se,2),De=e=>{var r=as(),o=L(r),s=z(o,!0),c=B(o,2),x=e=>{var t=Oo(),n=R(t),r=z(n),i=B(n,6),a=z(i,!0),o=z(B(i,6),!0);we(2),V(()=>{J(r,`Your ${W(l).profile.threshold??``}% quota threshold has been reached. Review + the profile for subsequent turns.`),J(a,W(l).profile.current),J(o,W(l).profile.proposed)}),q(e,t)};Y(c,e=>{W(l)?.profile&&!W(ce)&&!W(v)&&e(x)});var S=B(c,2),C=e=>{var t=ko();let n;var r=z(t,!0);V(()=>{n=ci(t,1,`svelte-1oupzfc`,null,n,{notice:!W(_),sent:W(_)}),J(r,W(g))}),q(e,t)};Y(S,e=>{W(g)&&e(C)});var D=B(S,2),ne=e=>{Co(e,{get lines(){return W(l).fileChanges}})},ie=e=>{Co(e,{get lines(){return i()}})},ae=e=>{var t=Ao(),n=R(t),r=z(n,!0),i=z(B(n,2),!0);V(()=>{J(r,W(ue)?`EXACT COMMAND`:`LAST OBSERVED COMMAND`),J(i,W(de))}),q(e,t)},oe=e=>{q(e,jo())};Y(D,e=>{W(l)?.fileChanges?.length&&!W(ce)&&!W(v)?e(ne):i().length?e(ie,1):W(de)?e(ae,2):a()!==`profile`&&W(se)===`APPROVAL NEEDED`&&!W(_)&&e(oe,3)});var le=B(D,2),he=e=>{q(e,Mo())},ye=e=>{q(e,No())},be=e=>{var t=Io(),n=R(t),r=e=>{var t=Po(),n=z(t,!0);V(e=>J(n,e),[()=>W(se)===`WORKING`?`Codex is working — nothing to respond to.`:[`APPROVAL NEEDED`,`INPUT NEEDED`].includes(W(se)||``)?`Respond in Codex for this request.`:`Nothing needs a response right now.`]),q(e,t)};Y(n,e=>{(W(se)===`WORKING`||!W(_)&&!W(h))&&e(r)});var i=B(n,2),a=e=>{q(e,Fo())},o=N(()=>[`APPROVAL NEEDED`,`INPUT NEEDED`].includes(W(se)||``)&&!W(_));Y(i,e=>{W(o)&&e(a)}),q(e,t)},A=e=>{var r=is(),i=R(r),o=z(i),s=B(i,2),c=L(s),g=z(c,!0),_=B(c,2),v=e=>{var r=Fr();Z(R(r),17,()=>W(l).choices||[],X,(e,r,i)=>{var o=zo(),s=L(o);xi(s),s.value=s.__value=i;var c=B(s),l=B(c),u=e=>{var t=Lo(),n=z(t,!0);V(()=>J(n,W(r).detail)),q(e,t)};Y(l,e=>{W(r).detail&&e(u)});var f=B(l,2),p=e=>{q(e,Ro())};Y(f,e=>{W(r).persistent&&e(p)}),j(o),V(()=>{Q(s,`name`,`decision-`+t.session+`-`+a()),J(c,` ${W(r).label??``} `)}),Oi(n,[],s,()=>W(d),e=>I(d,e)),q(e,o)}),q(e,r)},y=e=>{var n=Io(),r=R(n),i=e=>{var t=Ko(),n=R(t),r=B(L(n)),i=L(r),a=e=>{var t=Bo();t.value=t.__value=`now`,q(e,t)};Y(i,e=>{W(b)||e(a)});var o=B(i);o.value=o.__value=`quota`;var s=B(o);s.value=s.__value=`delay`;var c=B(s);c.value=c.__value=`at`,j(r),mi(r),j(n);var l=B(n,2),u=e=>{var t=Vo(),n=B(L(t));xi(n),j(t),Ei(n,()=>W(T),e=>I(T,e)),q(e,t)};Y(l,e=>{W(w)===`delay`&&e(u)});var d=B(l,2),f=e=>{var t=Ho(),n=B(L(t));xi(n),j(t),Ei(n,()=>W(ee),e=>I(ee,e)),q(e,t)};Y(d,e=>{W(w)===`at`&&e(f)});var p=B(d,2),m=e=>{var t=Wo(),n=R(t),r=z(n),i=B(n,2),a=e=>{q(e,Uo())};Y(i,e=>{W(w)!==`quota`&&e(a)});var o=B(i,2);o.textContent=`Timezone: ${Intl.DateTimeFormat().resolvedOptions().timeZone??``}. + One pending request per session; saving replaces it. Memory + only; Codexometer must stay running. Uses session model, + reasoning and speed.`,V(e=>J(r,`Will send: ${e??``}`),[()=>W(w)===`quota`?`when quota is available and this session is idle`:Number.isFinite(W(ge))?ve(W(ge)):`choose a date and time`]),q(e,t)};Y(p,e=>{W(w)!==`now`&&e(m)});var h=B(p,2),g=e=>{q(e,Go())};Y(h,e=>{W(b)&&e(g)}),hi(r,()=>W(w),e=>I(w,e)),q(e,t)};Y(r,e=>{W(l).questions?.length||e(i)}),Z(B(r,2),17,()=>W(pe),X,(e,n,r)=>{var i=$o(),o=R(i),s=e=>{{let n=N(()=>W(u)[0]||``);Ii(yo(e,{get session(){return t.session},get query(){return W(n)},oncomplete:e=>{I(u,[e],!0),dr().then(()=>W(O)?.focus())},onchange:()=>{I(u,[``],!0),dr().then(()=>W(O)?.focus())}}),e=>I(re,e,!0),()=>W(re))}};Y(o,e=>{r===0&&a()!==`profile`&&W(l).kind===`prompt`&&!W(l).questions?.length&&e(s)});var c=B(o,2),d=L(c),f=B(d),p=e=>{var t=qo();xi(t),Ei(t,()=>W(u)[r],e=>W(u)[r]=e),q(e,t)},m=e=>{var t=Yo(),i=L(t);i.value=i.__value=``,Z(B(i),17,()=>W(n).options||[],X,(e,t)=>{var n=Jo(),r=z(n,!0),i={};V(()=>{J(r,W(t)),i!==(i=W(t))&&(n.value=(n.__value=i)??``)}),q(e,n)}),j(t),mi(t),hi(t,()=>W(u)[r],e=>W(u)[r]=e),q(e,t)},h=e=>{var t=Xo(),i=R(t);rt(i),Ii(i,e=>I(O,e),()=>W(O));var a=B(i,2),o=e=>{var t=Po(),r=z(t);V(e=>J(r,`Suggested answers: ${e??``}`),[()=>W(n).options.join(` · `)]),q(e,t)};Y(a,e=>{W(n).options?.length&&e(o)}),G(`keydown`,i,e=>{W(l)?.kind===`prompt`&&!W(l).questions?.length&&W(re)?.handleKey(e)}),Ei(i,()=>W(u)[r],e=>W(u)[r]=e),q(e,t)};Y(f,e=>{W(n).secret?e(p):W(n).freeText?e(h,-1):e(m,1)}),j(c);var g=B(c,2),_=e=>{var t=Qo(),r=B(L(t),4);Z(r,21,()=>W(n).options||[],X,(e,t)=>{var n=Zo(),r=z(n,!0);V(()=>J(r,W(t))),q(e,n)}),j(r),j(t),q(e,t)};Y(g,e=>{W(n).secret&&!W(n).freeText&&e(_)}),V(()=>J(d,`${W(n).text??``} `)),q(e,i)}),q(e,n)};Y(_,e=>{W(l).kind===`approval`||W(l).kind===`profile`?e(v):e(y,-1)}),j(s);var x=B(s,2),S=e=>{var t=ts(),n=R(t),r=e=>{var t=es(),n=z(t);V(()=>J(n,`TRIGGER // ${W(E)??``}`)),q(e,t)};Y(n,e=>{W(te)&&e(r)});var i=B(n,2),a=z(i),o=B(i,2),s=z(o),c=B(o,2);V(e=>{J(a,`Check the target and ${W(l).kind===`profile`?`profile above`:W(l).kind===`approval`?W(l).fileChanges?.length?`proposed file changes above`:`exact command and permission scope`:`message above`}. ${W(te)?`This schedules a follow-up that will send automatically when eligible`:`This will send to Codex`}; it may start work using your quota. + Confirmation expires in ${e??``}s.`),o.disabled=W(h)||W(ce),J(s,`CONFIRM ${(W(l).kind===`approval`||W(l).kind===`profile`?W(l).choices?.[W(d)]?.label:W(te)?W(b)?`SAVE CHANGES`:`SCHEDULE`:`SEND`)??``}`)},[()=>Math.max(0,Math.ceil((W(p)-W(m))/1e3))]),G(`click`,o,xe),G(`click`,c,()=>{I(f,``)}),q(e,t)},C=e=>{var t=ns(),n=R(t),r=e=>{var t=wo(),n=z(t,!0);V(()=>J(n,W(_e))),q(e,t)};Y(n,e=>{W(_e)&&e(r)});var i=B(n,2),a=z(i,!0);V(()=>{i.disabled=W(h)||W(ce)||!W(me)||!!W(_e),J(a,W(h)?`SENDING…`:W(b)?`REVIEW CHANGES`:`REVIEW BEFORE SENDING`)}),G(`click`,i,()=>k()),q(e,t)};Y(x,e=>{W(fe)?e(S):e(C,-1)});var D=B(x,2),ne=e=>{var t=rs();G(`click`,t,()=>{I(b,!1),I(f,``),I(u,[],!0)}),q(e,t)};Y(D,e=>{W(b)&&e(ne)}),V(()=>{J(o,`TARGET // ${W(l).thread??``} // ${(W(l).directory||`Directory unavailable`)??``}`),s.disabled=W(h)||W(fe)||W(ce),J(g,W(l).kind===`approval`||W(l).kind===`profile`?`Choose a decision`:W(b)?`EDIT SCHEDULED FOLLOW-UP`:`Reply to this session`)}),q(e,r)};Y(le,e=>{W(ce)||W(v)?e(he):W(l)?W(l).id?W(y)!==W(l).id&&e(A,3):e(be,2):e(ye,1)}),j(r),V(()=>J(s,a()===`profile`?`QUOTA THRESHOLD`:`SESSION CONTROL // EXPERIMENTAL`)),q(e,r)};Y(Ee,e=>{!W(le)&&(!W(ne)||W(b)||a()||W(l)?.kind!==`prompt`||W(l).questions?.length)&&e(De)}),q(e,A),Ke()}wr([`click`,`keydown`]);var ss=K(` `,1),cs=K(`
      `),ls=K(`

      STATUS LINE // CODEXOMETER

      Choose multiple fields and their order. These display preferences do not + change Codex CLI configuration. Missing values are omitted.

      `),us=K(`

      `,1);function ds(e,t){Ge(t,!0);let n=Wi(t,`stale`,3,!1),r=F(Zt([])),i=F(!1),a=N(()=>$.data?.statusLineFields||[]),o=e=>[...new Set(e??W(a).filter(e=>e.default).map(e=>e.id))].filter(e=>W(a).some(t=>t.id===e)),s=e=>o(e).map(e=>e===`run-state`&&(n()||!$.connected||$.data?.sessionsError)?`STALE`:t.session.statusLine?.[e]).filter(Boolean).join(` · `),c=N(()=>[...W(r).map(e=>W(a).find(t=>t.id===e)).filter(Boolean),...W(a).filter(e=>!W(r).includes(e.id))]);bn(()=>{ia.open&&!W(i)?(I(r,o(ra.statusLine),!0),I(i,!0)):ia.open||I(i,!1)});function l(e,t){I(r,t?[...W(r),e]:W(r).filter(t=>t!==e),!0)}function u(e,t){let n=W(r).indexOf(e),i=n+t;if(n<0||i<0||i>=W(r).length)return;let a=[...W(r)];[a[n],a[i]]=[a[i],a[n]],I(r,a,!0)}var d=us(),f=R(d),p=e=>{var t=ls(),n=B(L(t),4);Z(n,21,()=>W(c),e=>e.id,(e,t)=>{var n=cs(),i=L(n),a=L(i);xi(a);var o=B(a,1,!0),s=z(B(o),!0);j(i);var c=B(i,2),d=e=>{var n=ss(),i=R(n),a=B(i,2);V((e,n)=>{Q(i,`aria-label`,`Move `+W(t).label+` earlier`),i.disabled=e,Q(a,`aria-label`,`Move `+W(t).label+` later`),a.disabled=n},[()=>W(r).indexOf(W(t).id)===0,()=>W(r).indexOf(W(t).id)===W(r).length-1]),G(`click`,i,()=>u(W(t).id,-1)),G(`click`,a,()=>u(W(t).id,1)),q(e,n)},f=N(()=>W(r).includes(W(t).id));Y(c,e=>{W(f)&&e(d)}),j(n),V(e=>{Q(i,`title`,W(t).help),Si(a,e),J(o,W(t).label),J(s,W(t).help)},[()=>W(r).includes(W(t).id)]),G(`change`,a,e=>l(W(t).id,e.currentTarget.checked)),q(e,n)}),j(n);var i=B(n,2),a=z(i),o=B(i,2),d=B(o,2);j(t),V(e=>J(a,`Preview // ${e??``}`),[()=>s(W(r))||`—`]),G(`click`,o,()=>{ra.statusLine=[...W(r)],aa(),ia.open=!1}),G(`click`,d,()=>{ia.open=!1}),q(e,t)};Y(f,e=>{ia.open&&e(p)});var m=B(f,2),h=L(m),g=z(h,!0),_=B(h,2);j(m),V((e,t)=>{Q(h,`title`,e),J(g,t)},[()=>s(ra.statusLine),()=>s(ra.statusLine)]),G(`click`,_,()=>{ia.open=!ia.open}),q(e,d),Ke()}wr([`change`,`click`]);var fs=K(`
      `);function ps(e,t){Ge(t,!0);let n=Wi(t,`active`,3,!1),r=N(()=>t.session.text||t.session.contextKind===`LAST ACTIVITY`&&t.session.workingCommand||``),i=N(()=>[`LAST REPLY`,`LAST ACTIVITY`].includes(t.session.contextKind)&&!!W(r).trim()),a=F(!1),o=F(``),s=F(!1);bn(()=>{if(!W(s))return;let e=setTimeout(()=>{I(s,!1)},150);return()=>clearTimeout(e)}),bn(()=>{W(r),t.session.status,I(o,``)});async function c(){if(!W(i)||W(a))return;let e=W(r);I(a,!0),I(s,!0),I(o,``);try{await navigator.clipboard.writeText(e),W(r)===e&&I(o,`Copied.`)}catch{W(r)===e&&I(o,`Clipboard unavailable. Select the visible text and copy it manually.`)}finally{I(a,!1)}}function l(e){!n()||!W(i)||e.defaultPrevented||e.repeat||e.ctrlKey||e.metaKey||e.altKey||e.key.toLowerCase()!==`c`||e.target instanceof Element&&e.target.closest(`input, textarea, select, [contenteditable]`)||(e.preventDefault(),c())}var u=Fr();Cr(`keydown`,en,l);var d=R(u),f=e=>{var t=fs(),n=L(t),r=z(n,!0),i=B(n,2);let l;j(t),V(()=>{J(r,W(o)),i.disabled=W(a),l=ci(i,1,`svelte-543j00`,null,l,{flashed:W(s)})}),G(`click`,i,c),q(e,t)};Y(d,e=>{W(i)&&e(f)}),q(e,u),Ke()}wr([`click`]);var ms=K(`

       
      `,1),hs=K(`

      LATEST GUIDANCE

       
      `,1),gs=K(`

      CONTEXT

       

      `,1),_s=K(`

      `),vs=K(`

      `),ys=K(`
       
      `),bs=K(`

       
      `,1),xs=K(`
       
      `),Ss=K(`

      Command unavailable from this observation. Open Codex to inspect the + request.

      `),Cs=K(`

      `,1),ws=K(`

      `),Ts=K(` `,1),Es=K(`
      `),Ds=K(`

      Session connection or refresh unavailable. Context and telemetry may be + stale.

      `),Os=K(`

      Some quota profile checks are unavailable. Only sessions with freshly + verified quota and settings can be updated; previous outcome notices remain + visible.

      `),ks=K(` `),As=K(` `),js=K(` `),Ms=K(``),Ns=K(`

      Read only — reply or approve in Codex.

      `),Ps=K(`

      `),Fs=K(`QUOTA THRESHOLD // REVIEW PROFILE ↗`),Is=K(`
      `),Ls=K(`
      `),Rs=K(`

      This session is no longer in the current observation. Return to sessions.

      `),zs=K(`

      `),Bs=K(``),Vs=K(`

      `),Hs=K(` `),Us=K(`

      `),Ws=K(`QUOTA THRESHOLD // REVIEW PROFILE ↗`),Gs=K(` `,1),Ks=K(`

      `),qs=K(`

      TOKEN ACTIVITY // 30 SECOND SAMPLES

      `),Js=K(`

      TOKENS

      FULL DETAIL →
      `),Ys=K(`

      No locally observed sessions yet. Keep Codex running alongside + Codexometer.

      `),Xs=K(`

      ↑ ↓ SELECT SESSION // ← LESS DETAIL // → MORE DETAIL // ESC BACK

      `,1),Zs=K(`

      SESSION TOTALS

      `,1);function Qs(e,t){Ge(t,!0);let n=(e,t=f,n,r)=>{let i=ht(()=>g(n?.(),!0)),a=ht(()=>g(r?.(),!1));var o=Ts(),s=R(o),c=e=>{var n=ms(),r=R(n),i=z(r,!0),a=z(B(r,2),!0);V(()=>{J(i,t().contextKind===`LAST REPLY`&&!t().streaming?`TASK`:`CURRENT TASK`),J(a,t().currentTask)}),q(e,n)};Y(s,e=>{t().currentTask&&e(c)});var l=B(s,2),u=e=>{var n=hs(),r=z(B(R(n),2),!0);V(()=>J(r,t().latestGuidance)),q(e,n)};Y(l,e=>{t().latestGuidance&&e(u)});var d=B(l,2),p=e=>{var n=gs(),r=z(B(R(n),2),!0);we(2),V(()=>J(r,t().approvalContext)),q(e,n)};Y(d,e=>{W(a)&&t().contextKind===`APPROVAL REQUEST`&&t().approvalContext&&e(p)});var m=B(d,2),_=e=>{var n=_s();let r;var i=z(n,!0);V(e=>{r=ci(n,1,`attention-note`,null,r,{inferred:t().status===`CHECK SESSION`}),J(i,e)},[()=>C(t())]),q(e,n)},v=N(()=>C(t())&&(!W(a)||W(h)||t().status===`CHECK SESSION`));Y(m,e=>{W(v)&&e(_)});var y=B(m,2),b=e=>{var n=vs(),r=z(n,!0);V(e=>J(r,e),[()=>w(t())]),q(e,n)};Y(y,e=>{W(i)&&e(b)});var x=B(y,2),S=e=>{var n=ys(),r=z(n,!0);V(()=>J(r,t().text||`No session context available.`)),q(e,n)};Y(x,e=>{(t().text||!t().workingCommand)&&e(S)});var T=B(x,2),ee=e=>{var n=bs(),r=B(R(n),2),i=L(r),a=B(i),o=e=>{var n=Pr();V(()=>J(n,`// +${(t().runningCommands||0)-+(t().commandStatus===`running`)} RUNNING`)),q(e,n)};Y(a,e=>{!W(h)&&(t().runningCommands||0)>+(t().commandStatus===`running`)&&e(o)});var s=B(a,2),c=e=>{q(e,Pr(`+`))};Y(s,e=>{t().runningLimited&&e(c)}),j(r);var l=z(B(r,2),!0);V(e=>{J(i,`COMMAND // ${e??``} `),J(l,t().workingCommand)},[()=>W(h)?`UNKNOWN`:(t().commandStatus||`unknown`).toUpperCase()]),q(e,n)};Y(T,e=>{t().workingCommand&&e(ee)});var te=B(T,2),E=e=>{Co(e,{get lines(){return t().fileChanges}})},D=e=>{var n=Cs(),r=B(R(n),2),i=z(r,!0),a=B(r,2),o=e=>{var n=xs(),r=z(n,!0);V(()=>J(r,t().command)),q(e,n)},s=e=>{q(e,Ss())};Y(a,e=>{t().command?e(o):e(s,-1)}),V(()=>J(i,W(h)||t().status!==`APPROVAL NEEDED`?`LAST OBSERVED COMMAND`:$.data?.control?`COMMAND REQUEST`:`COMMAND TO APPROVE IN CODEX`)),q(e,n)};Y(te,e=>{(!W(a)||!$.data?.control)&&t().fileChanges?.length?e(E):(!W(a)||!$.data?.control)&&(t().command||t().status===`APPROVAL NEEDED`)&&e(D,1)});var ne=B(te,2),O=e=>{var n=ws(),r=z(n);V(()=>J(r,`CONTEXT SOURCE // ${(t().source||`LOCAL`)??``} // OBSERVATION`)),q(e,n)};Y(ne,e=>{W(a)||e(O)}),q(e,o)},r=Wi(t,`params`,19,()=>({})),i=N(()=>$.data?.sessions||[]),a=N(()=>$.data?.control&&$.data.profiles||[]),o=N(()=>($.data?.triggers||[]).filter(e=>W(i).some(t=>t.id===e.session)));function s(e,t){if(r().id&&r().id!==t&&W(l)){e.preventDefault();return}b(t)}function c(e){return[`TURN COMPLETE`,`IDLE`,`ACTIVE`].includes(e.status)&&W(o).some(t=>t.session===e.id)?`TRIGGER SET`:e.status}let l=F(!1);function u(e,t){if(r().id&&r().id!==t&&W(l)){e.preventDefault();return}b(t)}bn(()=>{let e=r().id;e&&mr(()=>{ra.selected=e,la(e,2)})});let d=N(()=>W(i).find(e=>e.id===r().id)),p=N(()=>new URLSearchParams(Yi.querystring).get(`review`)===`profile`&&W(a).some(e=>e.session===r().id&&e.pending)),m=N(()=>W(i).some(e=>e.id===ra.selected)?ra.selected:W(i)[0]?.id),h=N(()=>!$.connected||!!$.data?.sessionsError),v=N(()=>[[`OBSERVED TOKENS`,ua(W(i).reduce((e,t)=>e+t.tokens,0))],[`LISTED SESSIONS`,ua(W(i).length)],...[[`WORKING`,`WORKING`],[`AWAITING APPROVAL`,`APPROVAL NEEDED`],[`AWAITING INPUT`,`INPUT NEEDED`],[`CHECK · INFERRED`,`CHECK SESSION`]].map(([e,t])=>[e,W(h)?`—`:ua(W(i).filter(e=>e.status===t).length)])]),y=N(()=>W(i).filter(e=>[`INPUT NEEDED`,`APPROVAL NEEDED`,`CHECK SESSION`].includes(e.status)));function b(e){ra.selected=e,dr().then(()=>document.querySelector(`.session-row.selected`)?.scrollIntoView({block:`nearest`}))}function x(e,t){if(b(e),r().id){t<0&&(la(e,2),location.hash=`/sessions`);return}let n=sa(e)+t;n>2?location.hash=`/sessions/`+encodeURIComponent(e):la(e,n)}function S(e){let t=e.target;if(!(e.altKey||e.ctrlKey||e.metaKey||e.shiftKey||t.closest(`input, textarea, select, [contenteditable="true"]`))){if(e.key===`Escape`&&r().id)e.preventDefault(),x(r().id,-1);else if([`ArrowLeft`,`ArrowRight`].includes(e.key)&&(r().id||W(m)))e.preventDefault(),x(r().id||W(m),e.key===`ArrowLeft`?-1:1);else if(!r().id&&[`ArrowUp`,`ArrowDown`].includes(e.key)&&W(i).length){e.preventDefault();let t=W(i).findIndex(e=>e.id===W(m));b(W(i)[Math.max(0,Math.min(W(i).length-1,t+(e.key===`ArrowUp`?-1:1)))].id)}}}function C(e){return W(h)?`Last observation only — refresh unavailable. Check Codex for current state.`:e.status===`CHECK SESSION`?`INFERRED INACTIVITY — a quiet session, not a confirmed input or approval request. Local tools may still be running.`:$.data?.control&&[`APPROVAL NEEDED`,`INPUT NEEDED`].includes(e.status)?`Open full detail for supported live controls; otherwise reply in Codex.`:e.status===`APPROVAL NEEDED`?`OBSERVED APPROVAL SIGNAL — approve or decline in Codex.`:e.status===`INPUT NEEDED`?`OBSERVED INPUT SIGNAL — reply in Codex.`:e.status===`TURN COMPLETE`?`OBSERVED TURN COMPLETION — informational, not an approval request.`:``}function w(e){return e.streaming&&e.contextKind===`LAST REPLY`?`REPLY // STREAMING`:e.contextKind||`LAST ACTIVITY`}var T=Zs();Cr(`keydown`,en,S);var ee=R(T),te=z(B(L(ee),2));j(ee);var E=B(ee,2);let D;Z(E,21,()=>W(v),X,(e,t)=>{var n=N(()=>_(W(t),2));let r=()=>W(n)[0],i=()=>W(n)[1];var a=Es(),o=L(a),s=z(o,!0),c=z(B(o,2),!0);j(a),V(()=>{J(s,r()),J(c,i())}),q(e,a)}),j(E);var ne=B(E,2),O=z(ne),re=B(ne,2),ie=e=>{q(e,Ds())};Y(re,e=>{W(h)&&e(ie)});var ae=B(re,2),oe=e=>{q(e,Os())};Y(ae,e=>{$.data?.profileError&&e(oe)});var se=B(ae,2),ce=e=>{var t=Ms(),n=L(t);Z(n,17,()=>W(y),X,(e,t)=>{var n=ks();let r;var i=z(n);V((e,a)=>{r=ci(n,1,`button`,null,r,{approval:W(t).status===`APPROVAL NEEDED`}),Q(n,`href`,e),J(i,`${W(t).status??``} + ${a??``} // ${(W(t).name||W(t).directory||W(t).id)??``}`)},[()=>`#/sessions/`+encodeURIComponent(W(t).id),()=>Array.from(W(t).id).slice(-5).join(``).toUpperCase()]),G(`click`,n,()=>b(W(t).id)),q(e,n)});var c=B(n,2);Z(c,17,()=>W(a).filter(e=>e.pending&&W(i).some(t=>t.id===e.session)),X,(e,t)=>{var n=As(),a=z(n);V((e,i,o)=>{Q(n,`title`,r().id&&r().id!==W(t).session&&W(l)?`Finish sending or clear your current draft before switching sessions.`:`Review this session’s quota threshold`),Q(n,`href`,e),J(a,`QUOTA THRESHOLD ${i??``} // ${o??``}`)},[()=>`#/sessions/`+encodeURIComponent(W(t).session)+`?review=profile`,()=>Array.from(W(t).session).slice(-5).join(``).toUpperCase(),()=>W(i).find(e=>e.id===W(t).session)?.name||W(i).find(e=>e.id===W(t).session)?.directory||W(t).session]),G(`click`,n,e=>u(e,W(t).session)),q(e,n)}),Z(B(c,2),17,()=>W(o),X,(e,t)=>{var n=js(),r=z(n);V((e,t,i)=>{Q(n,`href`,e),J(r,`TRIGGER SET ${t??``} // ${i??``}`)},[()=>`#/sessions/`+encodeURIComponent(W(t).session),()=>Array.from(W(t).session).slice(-5).join(``).toUpperCase(),()=>W(i).find(e=>e.id===W(t).session)?.name||W(i).find(e=>e.id===W(t).session)?.directory]),G(`click`,n,e=>s(e,W(t).session)),q(e,n)}),j(t),q(e,t)},le=N(()=>(W(y).length||W(a).some(e=>e.pending)||W(o).length)&&!W(h));Y(se,e=>{W(le)&&e(ce)});var ue=B(se,2),de=e=>{var t=Fr(),i=R(t),o=e=>{var t=Ls(),i=L(t),o=L(i),s=L(o);let u;var f=B(s);j(o);var m=B(o,2);j(i);var g=B(i,2),_=e=>{var t=ws(),n=z(t,!0);V(()=>J(n,W(d).directory)),q(e,t)};Y(g,e=>{W(d).name&&W(d).directory&&e(_)});var v=B(g,2),y=z(v),x=B(v,2);let S;var C=L(x),w=L(C);n(w,()=>W(d),()=>!0,()=>!0),j(C);var T=B(C,2),ee=e=>{var t=Fr();qr(R(t),()=>W(d).id,e=>{{let t=N(()=>new URLSearchParams(Yi.querystring).get(`schedule`)===`edit`),n=N(()=>W(d).fileChanges||[]);os(e,{get session(){return W(d).id},get openScheduler(){return W(t)},get observedCommand(){return W(d).command},get observedFileChanges(){return W(n)},get suspended(){return W(p)},onProtectedChange:e=>{I(l,e,!0)}})}}),q(e,t)},te=e=>{q(e,Ns())};Y(T,e=>{$.data?.control?e(ee):e(te,-1)}),j(x);var E=B(x,2);Z(E,17,()=>W(a).filter(e=>e.session===W(d).id),e=>e.session,(e,t)=>{var n=Is(),r=L(n),i=e=>{var n=Ps(),r=z(n,!0);V(()=>J(r,W(t).notice)),q(e,n)};Y(r,e=>{W(t).notice&&e(i)});var a=B(r,2),o=e=>{os(e,{get session(){return W(d).id},review:`profile`})},s=e=>{var t=Fs();V(e=>Q(t,`href`,e),[()=>`#/sessions/`+encodeURIComponent(W(d).id)+`?review=profile`]),q(e,t)};Y(a,e=>{W(t).pending&&W(p)?e(o):W(t).pending&&e(s,1)}),j(n),V(()=>Q(n,`id`,`quota-profile-`+W(d).id)),q(e,n)});var D=B(E,2),ne=e=>{var t=Fr();qr(R(t),()=>W(d).id,e=>{ps(e,{get session(){return W(d)},active:!0})}),q(e,t)};Y(D,e=>{W(p)||e(ne)}),qr(B(D,2),()=>W(d).id,e=>{ds(e,{get session(){return W(d)},get stale(){return W(h)}})}),j(t),V((e,t)=>{u=ci(s,1,`lamp lit`,null,u,{working:W(d).status===`WORKING`&&!W(h)}),J(f,`${e??``} // ${(W(d).name||W(d).directory)??``}`),J(y,`${t??``} TOKENS // ${W(d).id??``} // CONTEXT SOURCE // ${(W(d).source||`LOCAL`)??``}`),S=ui(x,``,S,{display:W(p)?`none`:void 0})},[()=>W(h)?`STALE`:W(p)?`QUOTA THRESHOLD`:c(W(d)),()=>ua(W(d).tokens)]),G(`click`,m,()=>{b(r().id),la(r().id,2)}),q(e,t)},s=e=>{q(e,Rs())};Y(i,e=>{W(d)?e(o):e(s,-1)}),q(e,t)},fe=e=>{var t=Xs(),r=R(t),o=B(L(r),2),s=L(o),l=B(s,2);j(o),j(r);var d=B(r,2);Z(d,17,()=>W(i),e=>e.id,(e,t)=>{let r=N(()=>sa(W(t).id));var i=Js();let o;var s=L(i),l=L(s),d=e=>{var n=zs(),r=L(n),i=z(r);j(n),V(e=>{Q(r,`aria-pressed`,W(m)===W(t).id),J(i,`${e??``} // ${W(t).name??``}`)},[()=>Array.from(W(t).id).slice(-5).join(``).toUpperCase()]),G(`click`,r,()=>b(W(t).id)),q(e,n)};Y(l,e=>{W(t).name&&e(d)});var f=B(l,2),p=L(f),g=L(p);let _;var v=B(g,1,!0);j(p),j(f);var y=B(f,2),S=e=>{var n=Bs(),r=z(n,!0);V(()=>{Q(n,`aria-pressed`,W(m)===W(t).id),J(r,W(t).directory||W(t).id)}),G(`click`,n,()=>b(W(t).id)),q(e,n)};Y(y,e=>{W(t).name||e(S)});var C=B(y,2),T=L(C);we(),j(C);var ee=B(C,2),te=e=>{var n=ws(),r=z(n,!0);V(()=>J(r,W(t).directory)),q(e,n)};Y(ee,e=>{W(t).name&&W(t).directory&&e(te)});var E=B(ee,2),D=z(E),ne=B(E,2),O=z(ne),re=B(ne,2),ie=L(re),ae=B(ie,2),oe=z(ae,!0),se=B(ae,2);j(re);var ce=B(re,2),le=B(ce,2),ue=e=>{var n=Vs(),r=z(n,!0);V(()=>J(r,W(t).status===`CHECK SESSION`?`INFERRED INACTIVITY`:$.data?.control?`OPEN FULL DETAIL OR REPLY IN CODEX`:W(t).status===`APPROVAL NEEDED`?`APPROVE OR DECLINE IN CODEX`:`REPLY IN CODEX`)),q(e,n)},de=N(()=>!W(h)&&[`APPROVAL NEEDED`,`INPUT NEEDED`,`CHECK SESSION`].includes(W(t).status));Y(le,e=>{W(de)&&e(ue)}),j(s);var fe=B(s,2),pe=e=>{var r=Ks(),i=L(r),o=L(i),s=z(o,!0),c=B(o,2),l=e=>{var n=Hs(),r=z(n,!0);V(e=>{Q(n,`href`,e),J(r,$.data?.control?`REVIEW IN FULL DETAIL ↗`:`APPROVAL IN CODEX ↗`)},[()=>`#/sessions/`+encodeURIComponent(W(t).id)]),q(e,n)};Y(c,e=>{!W(h)&&W(t).status===`APPROVAL NEEDED`&&e(l)}),j(i);var d=B(i,2);n(d,()=>W(t),()=>!1);var f=B(d,2);Z(f,17,()=>W(a).filter(e=>e.session===W(t).id),X,(e,n)=>{var r=Gs(),i=R(r),a=e=>{var t=Us(),r=z(t,!0);V(()=>J(r,W(n).notice)),q(e,t)};Y(i,e=>{W(n).notice&&e(a)});var o=B(i,2),s=e=>{var n=Ws();V(e=>Q(n,`href`,e),[()=>`#/sessions/`+encodeURIComponent(W(t).id)+`?review=profile`]),G(`click`,n,e=>u(e,W(t).id)),q(e,n)};Y(o,e=>{W(n).pending&&e(s)}),q(e,r)});var p=B(f,2);{let e=N(()=>W(m)===W(t).id);ps(p,{get session(){return W(t)},get active(){return W(e)}})}j(r),V(e=>J(s,e),[()=>w(W(t))]),q(e,r)};Y(fe,e=>{W(r)>0&&e(pe)});var me=B(fe,2),he=e=>{var n=qs(),r=B(L(n),2);{let e=N(()=>(W(t).samples||[]).map(e=>e.tokens));no(r,{get values(){return W(e)},capacity:120})}var i=z(B(r,2));j(n),V(()=>J(i,`LAST ${(W(t).samples?.length||0)??``} SAMPLES // AUTO SCALE`)),q(e,n)};Y(me,e=>{W(r)<2&&e(he)}),j(i),V((e,n,a,s,c)=>{o=ci(i,1,`session-row`,null,o,{selected:W(m)===W(t).id,wide:W(r)===2,split:W(r)===1}),Q(i,`aria-label`,`Session `+(W(t).name||W(t).directory||W(t).id)),Q(p,`href`,e),_=ci(g,1,`lamp lit`,null,_,{working:W(t).status===`WORKING`&&!W(h)}),J(v,n),J(T,`${a??``} `),J(D,`${W(t).agents??``} LINKED AGENTS`),J(O,`ACTIVE // ${s??``}`),ie.disabled=W(r)===0,Q(ae,`aria-expanded`,W(r)>0),J(oe,W(r)?`HIDE DETAIL`:`SHOW DETAIL`),Q(ce,`href`,c)},[()=>`#/sessions/`+encodeURIComponent(W(t).id),()=>W(h)?`STALE`:c(W(t)),()=>ua(W(t).tokens),()=>da(W(t).activity),()=>`#/sessions/`+encodeURIComponent(W(t).id)]),G(`click`,p,()=>b(W(t).id)),G(`click`,ie,()=>x(W(t).id,-1)),G(`click`,ae,()=>{b(W(t).id),la(W(t).id,+!W(r))}),G(`click`,se,()=>x(W(t).id,1)),G(`click`,ce,()=>b(W(t).id)),q(e,i)});var f=B(d,2),p=e=>{q(e,Ys())};Y(f,e=>{W(i).length||e(p)}),G(`click`,s,()=>ca(1)),G(`click`,l,()=>ca(0)),q(e,t)};Y(ue,e=>{r().id?e(de):e(fe,-1)}),V(e=>{J(te,`OBSERVED ${e??``}`),D=ci(E,1,`session-totals`,null,D,{stale:W(h)}),J(O,`${W(h)?`LAST KNOWN TOTALS — live state counts unavailable. `:``}Tokens + observed since this server started for currently listed sessions; linked + agents are already included. Totals can decrease when a session leaves the + list. History samples every 30 seconds. Not account-wide totals.`)},[()=>da($.data?.sessionsAt)]),q(e,T),Ke()}wr([`click`]);var $s=K(`

      `),ec=K(`

      Relative usage/credits are not tokens or quota percentages. Groupings + describe the same usage, not additional consumption.

      `,1),tc=K(`// APPROXIMATE`),nc=K(`

      Partial accounting: totals may still change.

      `),rc=K(`

      Percentage of this period's historical allowance, not today's limit. May + exceed 100%. This is not a manual/automatic reset log.

      `,1),ic=K(`

      `,1),ac=K(`

      This optional account report is unavailable. Token history remains + available; missing data does not mean zero usage.

      `),oc=K(`
      `),sc=K(`
      `),cc=K(`

      No usage reported for this day.

      `),lc=K(`

      Selected breakdown unavailable; missing does not mean zero.

      `),uc=K(`

      `,1);function dc(e,t){Ge(t,!0);let n=F(`surface`),r=F(0),i=N(()=>t.reports?.daily),a=N(()=>t.reports?.plan),o=N(()=>t.mode===`breakdown`?W(i)?.days.length||0:W(a)?.periods.length||0),s=N(()=>Math.min(W(r),Math.max(0,W(o)-1))),c=N(()=>W(i)?.days[(W(i)?.days.length||0)-1-W(s)]),l=N(()=>W(a)?.periods[W(s)]),u=N(()=>t.mode===`breakdown`?W(i)?.units||``:`%`),d=N(()=>{let e=Object.create(null);if(t.mode===`breakdown`)e=W(c)?.groups[W(n)]||{};else{let t=W(n)===`feature`?`thread_source`:W(n)===`task start`?`turn_trigger`:W(n);for(let n of W(l)?.breakdowns||[])if(n.dimension===t)for(let t of n.rows)e[t.key]=(e[t.key]||0)+t.basis_points/100}return Object.entries(e).sort((e,t)=>t[1]-e[1]||e[0].localeCompare(t[0]))}),f=N(()=>t.mode===`breakdown`?W(c)?.total:W(l)?.used_basis_points==null?null:W(l).used_basis_points/100),p=N(()=>Math.max(1,W(f)||0,...W(d).map(([,e])=>e))),m=e=>e==null?`UNKNOWN`:e.toLocaleString(`en-GB`,{maximumFractionDigits:2});var h=uc(),g=R(h),v=L(g),y=B(L(v)),b=L(y);b.value=b.__value=`surface`;var x=B(b);x.value=x.__value=`model`;var S=B(x);S.value=S.__value=`feature`;var C=B(S);C.value=C.__value=`task start`,j(y),mi(y),j(v);var w=B(v,2),T=B(w,2),ee=z(B(T,2),!0);j(g);var te=B(g,2),E=L(te),D=z(E,!0),ne=B(E,2),O=z(ne),re=B(ne,2),ie=e=>{var t=ec(),n=R(t),r=z(n),a=B(n,2),o=e=>{var t=$s(),n=z(t);V(()=>J(n,`Data as of ${W(i).dataAsOf??``}`)),q(e,t)};Y(a,e=>{W(i).dataAsOf&&e(o)});var s=z(B(a,2));we(2),V((e,t)=>{J(r,`Latest fetch ${e??``} // queried ${W(i).from??``} → ${W(i).through??``}. + Reports are fetched from OpenAI and are not saved between runs.`),J(s,`${(W(c)?.date||`No daily buckets reported`)??``} // ${t??``} + ${W(u)??``}`)},[()=>da(W(i).fetchedAt),()=>m(W(f))]),q(e,t)},ae=e=>{var t=ic(),n=R(t),r=L(n);let i;var o=z(r,!0),s=B(r),c=e=>{q(e,tc())};Y(s,e=>{W(a).approximate&&e(c)});var u=B(s);j(n);var d=B(n,2),p=L(d),h=B(p),g=e=>{var t=Pr();V(()=>J(t,`// Boundary tolerance ${W(a).boundary_tolerance_seconds??``}s`)),q(e,t)};Y(h,e=>{W(a).boundary_tolerance_seconds!=null&&e(g)}),j(d);var _=B(d,2),v=e=>{var t=rc(),n=R(t),r=L(n),i=B(r);let a;var o=z(i);j(n);var s=B(n,2),c=z(s),u=B(s,2),d=e=>{q(e,nc())};Y(u,e=>{W(l).accounting_complete||e(d)}),we(2),V(e=>{J(r,`${W(l).window_minutes??``} MIN // ${W(l).plan_type??``} // `),a=ci(i,1,`svelte-1fbui7h`,null,a,{muted:W(f)==null,"quota-near":W(f)!=null&&W(f)>=80&&W(f)<100,"quota-full":W(f)!=null&&W(f)>=100}),J(o,`USED ${e??``}${W(f)==null?``:`%`}`),J(c,`${W(l).starts_at??``} → ${W(l).ends_at??``}`)},[()=>m(W(f))]),q(e,t)};Y(_,e=>{W(l)&&e(v)}),V(e=>{i=ci(r,1,`svelte-1fbui7h`,null,i,{"quality-warning":!W(a).coverage_complete}),J(o,W(a).coverage_complete?`Reported range complete`:`Partial coverage`),J(u,` // Latest fetch ${e??``}`),J(p,`Coverage starts ${(W(a).coverage_start||`unknown`)??``} // Data as of ${(W(a).data_as_of||`unknown`)??``}`)},[()=>da(W(a).fetchedAt)]),q(e,t)},oe=e=>{q(e,ac())};Y(re,e=>{t.mode===`breakdown`&&W(i)?e(ie):t.mode===`periods`&&W(a)?e(ae,1):e(oe,-1)});var se=B(re,2),ce=e=>{var n=sc(),r=L(n),i=z(B(L(r),2),!0);j(r),Z(B(r,2),17,()=>W(d),X,(e,n,r)=>{var i=N(()=>_(W(n),2));let a=()=>W(i)[0],o=()=>W(i)[1];var s=oc();let c;var l=L(s),u=z(l,!0),d=B(l,2),f=L(d);let h;j(d);var g=z(B(d,2));j(s),V((e,n,i)=>{c=ci(s,1,`report-row svelte-1fbui7h`,null,c,{unknown:e,alternate:r%2==1}),Q(l,`title`,a()),J(u,a()),h=ui(f,``,h,{width:n}),J(g,`${i??``}${t.mode===`periods`?`%`:``}`)},[()=>a().trim().toLowerCase()===`unknown`,()=>`${Math.max(0,100*o()/W(p))}%`,()=>m(o())]),q(e,s)}),j(n),V(()=>J(i,t.mode===`breakdown`?W(u):`QUOTA USED`)),q(e,n)},le=e=>{q(e,cc())},ue=e=>{q(e,lc())};Y(se,e=>{W(d).length?e(ce):t.mode===`breakdown`&&W(c)?.total===0&&W(c)?.groups[W(n)]!=null?e(le,1):(W(i)&&t.mode===`breakdown`||W(a)&&t.mode===`periods`)&&e(ue,2)}),j(te),V(()=>{w.disabled=W(s)>=W(o)-1,T.disabled=W(s)===0,J(ee,W(o)?`${W(s)+1} / ${W(o)}`:`NO DATA`),J(D,t.mode===`breakdown`?`DAILY BREAKDOWN`:`QUOTA WINDOWS`),J(O,`${(t.mode===`breakdown`?t.reports?.dailyStatus||`UNAVAILABLE`:t.reports?.planStatus||`UNAVAILABLE`)??``} // UTC`)}),hi(y,()=>W(n),e=>I(n,e)),G(`click`,w,()=>I(r,W(s)+1)),G(`click`,T,()=>I(r,W(s)-1)),q(e,h),Ke()}wr([`click`]);var fc=864e5;function pc(e,t){let n=new Date(e);n.setUTCDate(1),n.setUTCMonth(n.getUTCMonth()+t);let r=new Date(Date.UTC(n.getUTCFullYear(),n.getUTCMonth()+1,0)).getUTCDate();return n.setUTCDate(Math.min(e.getUTCDate(),r)),n}function mc(e,t,n){let r=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()));for(let e=0;;e++){let i=pc(r,-t),a=new Date(i.getTime()+fc);if(e===n)return{start:a,end:r};r=i}}var hc=K(``),gc=K(``,1),_c=K(`

      History refresh failed. Any displayed history is the last successful + observation.

      `),vc=K(`

      Token history unavailable; other reports may still be available.

      `),yc=K(``),bc=K(`
      `),xc=K(`

      LESS ░ ▒ ▓ █ MORE // HOVER FOR DATE AND TOKENS

      `,1),Sc=K(`
      `,1),Cc=K(` `),wc=K(`

      LIFETIME TOKENS

      PEAK DAY

      CURRENT STREAK

      DAYS

      LONGEST TURN

      SECONDS

      LONGEST STREAK

      DAYS

      OPENAI // UTC

      Accessible data table
      Date (UTC)Tokens

      `,1),Tc=K(`

      History unavailable or awaiting a matching account observation. Missing + history is not treated as zero usage.

      `),Ec=K(`

      USAGE // ACCOUNT HISTORY

      Account-wide history reported by Codex, not the local Sessions counter. Dates + use UTC. Allowance history is approximate where indicated; reset causes are + not provided.

      `,1);function Dc(e,t){Ge(t,!0);let n=F(`daily`),r=F(12),i=F(0),a=N(()=>{let e=$.data?.usage?.dailyUsageBuckets;if(!e)return[];let{start:t,end:n}=mc(new Date,W(r),W(i)),a=new Map;for(let t of e)!Number.isFinite(t.tokens)||t.tokens<0||a.set(t.startDate,(a.get(t.startDate)||0)+t.tokens);let o=[];for(let e=t;e<=n;e=new Date(e.getTime()+864e5)){let t=e.toISOString().slice(0,10);o.push({date:t,tokens:a.get(t)||0})}return o}),o=N(()=>Math.max(1,...W(a).map(e=>e.tokens))),s=N(()=>W(a).length?new Date(W(a)[0].date+`T00:00:00Z`).getUTCDay():0),c=N(()=>{if(W(n)===`monthly`){let e=new Map;for(let t of W(a))e.set(t.date.slice(0,7),(e.get(t.date.slice(0,7))||0)+t.tokens);return[...e].map(([e,t])=>({date:e,tokens:t}))}let e=0;return W(a).map(t=>({date:t.date,tokens:e+=t.tokens}))});var l=Ec(),u=B(R(l),4);Z(u,20,()=>[[`daily`,`Daily`],[`monthly`,`Monthly`],[`cumulative`,`Cumulative`],[`breakdown`,`Breakdown`],[`periods`,`Windows`]],X,(e,t)=>{var r=N(()=>_(t,2));let a=()=>W(r)[0],o=()=>W(r)[1];var s=hc();let c;var l=z(s,!0);V(()=>{Q(s,`aria-pressed`,W(n)===a()),c=ci(s,1,``,null,c,{active:W(n)===a()}),J(l,o())}),G(`click`,s,()=>{I(n,a(),!0),I(i,0)}),q(e,s)}),j(u);var d=B(u,2),f=L(d),p=e=>{var t=gc(),n=R(t),a=B(L(n)),o=L(a);o.value=o.__value=6;var s=B(o);s.value=s.__value=12,j(a),mi(a),j(n);var c=B(n),l=B(c);V(()=>l.disabled=W(i)===0),G(`change`,a,()=>I(i,0)),hi(a,()=>W(r),e=>I(r,e)),G(`click`,c,()=>Jt(i)),G(`click`,l,()=>Jt(i,-1)),q(e,t)};Y(f,e=>{W(n)!==`breakdown`&&W(n)!==`periods`&&e(p)}),j(d);var m=B(d,2),h=e=>{q(e,_c())};Y(m,e=>{$.data?.usageError&&e(h)});var g=B(m,2),v=e=>{q(e,vc())};Y(g,e=>{$.data?.usage?.tokenStatus===`UNAVAILABLE`&&W(n)!==`breakdown`&&W(n)!==`periods`&&e(v)});var y=B(g,2),b=e=>{var t=Fr();qr(R(t),()=>W(n),e=>{{let t=N(()=>$.data?.usage?.reports);dc(e,{get reports(){return W(t)},get mode(){return W(n)}})}}),q(e,t)},x=e=>{var t=wc(),r=R(t),i=L(r),l=z(B(L(i),2),!0);j(i);var u=B(i,2),d=z(B(L(u),2),!0);j(u);var f=B(u,2),p=B(L(f),2),m=L(p);we(),j(p),j(f);var h=B(f,2),g=B(L(h),2),_=L(g);we(),j(g),j(h);var v=B(h,2),y=B(L(v),2),b=L(y);we(),j(y),j(v),j(r);var x=B(r,4),S=L(x),C=z(S),w=B(S,2),T=e=>{var t=xc(),n=R(t),r=L(n),i=L(r);Z(i,17,()=>Array(W(s)),X,(e,t)=>{q(e,yc())}),Z(B(i,2),17,()=>W(a),X,(e,t)=>{var n=bc();let r,i;V(e=>{r=ci(n,1,`heat-cell`,null,r,{zero:W(t).tokens===0}),Q(n,`title`,e),i=ui(n,``,i,{opacity:W(t).tokens?.25+.75*W(t).tokens/W(o):1})},[()=>`${W(t).date}: ${ua(W(t).tokens)} tokens`]),q(e,n)}),j(r),j(n),we(2),q(e,t)},ee=e=>{var t=Sc(),r=R(t);{let e=N(()=>W(c).map(e=>e.tokens)),t=N(()=>W(n)+` usage`);no(r,{get values(){return W(e)},get label(){return W(t)}})}var i=B(r,2),a=L(i),o=z(a,!0),s=z(B(a),!0);j(i),V(e=>{J(o,W(c)[0]?.date),J(s,e)},[()=>W(c).at(-1)?.date]),q(e,t)};Y(w,e=>{W(n)===`daily`?e(T):e(ee,-1)});var te=B(w,2),E=B(L(te),2),D=L(E),ne=B(L(D));Z(ne,21,()=>W(n)===`daily`?W(a):W(c),X,(e,t)=>{var n=Cc(),r=L(n),i=z(r,!0),a=z(B(r),!0);j(n),V(e=>{J(i,W(t).date),J(a,e)},[()=>ua(W(t).tokens)]),q(e,n)}),j(ne),j(D),j(E),j(te),j(x);var O=z(B(x,2));V((e,t,n,r,i,o,s)=>{J(l,e),J(d,t),J(m,`${n??``} `),J(_,`${r??``} `),J(b,`${i??``} `),J(C,`${W(a)[0]?.date??``} — ${o??``}`),J(O,`OBSERVED ${s??``} // CUMULATIVE IS FOR THE SELECTED PERIOD`)},[()=>ua($.data.usage.summary.lifetimeTokens),()=>ua($.data.usage.summary.peakDailyTokens),()=>ua($.data.usage.summary.currentStreakDays),()=>ua($.data.usage.summary.longestRunningTurnSec),()=>ua($.data.usage.summary.longestStreakDays),()=>W(a).at(-1)?.date,()=>da($.data.usageAt)]),q(e,t)},S=e=>{q(e,Tc())};Y(y,e=>{W(n)===`breakdown`||W(n)===`periods`?e(b):$.data?.usage&&$.data.usage.dailyUsageBuckets!==null?e(x,1):e(S,-1)}),q(e,l),Ke()}wr([`click`,`change`]);var Oc=K(`

      Page not found

      Return to Quota

      `,1);function kc(e){var t=Oc();we(2),q(e,t)}var Ac=K(` `),jc=K(`

      `),Mc=K(`

      Connecting to your local Codexometer…

      `),Nc=K(``),Pc=K(`
      CODEXOMETER

      Your quota. Your sessions. Your command centre.

      `);function Fc(e,t){Ge(t,!0);let n={"/":$a,"/quota/:view?":$a,"/sessions/:id?":Qs,"/usage":Dc,"*":kc},r={quota:/^(?:\/|\/quota(?:\/[^/]+)?\/?)$/,sessions:/^\/sessions(?:\/[^/]+)?\/?$/,usage:/^\/usage\/?$/},i=F(`hacker`),a=[`hacker`,`rust`,`blue-steel`,`ultraviolet`,`nightshade`];bn(()=>{aa()}),bn(()=>{if($.data)for(let[e,t]of Object.entries(r))t.test(Yi.location)&&(ra.tab=e)}),Gi(()=>{try{let e=localStorage.getItem(`codexometer.web.theme`);e&&a.includes(e)&&I(i,e,!0)}catch{}return ga()});function o(){try{localStorage.setItem(`codexometer.web.theme`,W(i))}catch{}}var s=Pc(),c=L(s),l=B(L(c),2),u=L(l);let d;var f=B(u,1,!0),p=z(B(f));j(l),j(c);var m=B(c,2);Z(m,21,()=>Object.entries(r),X,(e,t)=>{var n=N(()=>_(W(t),2));let r=()=>W(n)[0],i=()=>W(n)[1],a=N(()=>i().test(Yi.location));var o=Ac();let s;var c=z(o,!0);V(e=>{Q(o,`href`,r()===`quota`?`#/quota/`+ra.view:`#/`+r()),Q(o,`aria-current`,W(a)?`page`:void 0),s=ci(o,1,``,null,s,{active:W(a)}),J(c,e)},[()=>r().toUpperCase()]),q(e,o)}),j(m);var h=B(m,2),g=L(h),v=e=>{var t=jc(),n=z(t,!0);V(()=>J(n,$.error)),q(e,t)};Y(g,e=>{$.error&&e(v)});var y=B(g,2),b=e=>{Qi(e,{get routes(){return n}})},x=e=>{q(e,Mc())};Y(y,e=>{$.data?e(b):$.error||e(x,1)}),j(h);var S=B(h,2),C=L(S),w=z(C),T=B(C,2),ee=z(T,!0),te=B(T,2),E=B(L(te));Z(E,21,()=>a,X,(e,t)=>{var n=Nc(),r=z(n,!0),i={};V(e=>{J(r,e),i!==(i=W(t))&&(n.value=(n.__value=i)??``)},[()=>W(t).replace(`-`,` `).toUpperCase()]),q(e,n)}),j(E),mi(E),j(te),j(S),j(s),V(()=>{Q(s,`data-theme`,W(i)),d=ci(u,1,`lamp`,null,d,{lit:$.connected}),J(f,$.connected?`CONNECTED`:`OFFLINE`),J(p,`EXPERIMENTAL // ${$.data?.control?`SESSION CONTROL`:`READ ONLY`}`),J(w,`v${($.data?.version||`…`)??``} // LOCAL ONLY`),J(ee,$.data?.control?`Session control enabled. Stop the server to revoke access.`:`No actions can be sent from this preview.`)}),G(`change`,E,o),hi(E,()=>W(i),e=>I(i,e)),q(e,s),Ke()}wr([`change`]),Vr(Fc,{target:document.getElementById(`app`)}); \ No newline at end of file diff --git a/internal/web/dist/assets/index-CQ2rxUyW.js b/internal/web/dist/assets/index-CQ2rxUyW.js deleted file mode 100644 index 4fa4764..0000000 --- a/internal/web/dist/assets/index-CQ2rxUyW.js +++ /dev/null @@ -1,45 +0,0 @@ -(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=Array.isArray,t=Array.prototype.indexOf,n=Array.prototype.includes,r=Array.from,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyDescriptors,s=Object.prototype,c=Array.prototype,l=Object.getPrototypeOf,u=Object.isExtensible;function d(e){return typeof e==`function`}var f=()=>{};function p(e){return e()}function m(e){for(var t=0;t{e=n,t=r}),resolve:e,reject:t}}function g(e,t,n=!1){return e===void 0?n?t():t:e}function _(e,t){if(Array.isArray(e))return e;if(t===void 0||!(Symbol.iterator in e))return Array.from(e);let n=[];for(let r of e)if(n.push(r),n.length===t)break;return n}var v=1024,y=2048,b=4096,x=8192,S=16384,C=32768,w=1<<25,T=65536,ee=1<<19,E=1<<20,D=1<<25,O=1<<21,te=1<<22,ne=1<<23,re=Symbol(`$state`),ie=Symbol(`component`),ae=Symbol(`legacy props`),oe=Symbol(``),se=Symbol(`attributes`),ce=Symbol(`class`),le=Symbol(`style`),ue=Symbol(`text`),de=Symbol(`form reset`),fe=new class extends Error{name=`StaleReactionError`;message="The reaction that called `getAbortSignal()` was re-run or destroyed"},pe=!!globalThis.document?.contentType&&globalThis.document.contentType.includes(`xml`),me={},he=Symbol(`uninitialized`),ge=`http://www.w3.org/1999/xhtml`;function _e(){console.warn(`https://svelte.dev/e/derived_inert`)}function ve(e){console.warn(`https://svelte.dev/e/hydration_mismatch`)}function ye(){console.warn(`https://svelte.dev/e/select_multiple_invalid_value`)}function be(){console.warn(`https://svelte.dev/e/svelte_boundary_reset_noop`)}var k=!1;function xe(e){k=e}var A;function Se(e){if(e===null)throw ve(),me;return A=e}function Ce(){return Se(cn(A))}function j(e){if(k){if(cn(A)!==null)throw ve(),me;A=e}}function we(e=1){if(k){for(var t=e,n=A;t--;)n=cn(n);A=n}}function Te(e=!0){for(var t=0,n=A;;){if(n.nodeType===8){var r=n.data;if(r===`]`){if(t===0)return n;--t}else(r===`[`||r===`[!`||r[0]===`[`&&!isNaN(Number(r.slice(1))))&&(t+=1)}var i=cn(n);e&&n.remove(),n=i}}function Ee(e){if(!e||e.nodeType!==8)throw ve(),me;return e.data}function De(e){return e===this.v}function Oe(e,t){return e==e?e!==t||typeof e==`object`&&!!e||typeof e==`function`:t==t}function ke(e){return!Oe(e,this.v)}function Ae(e){throw Error(`https://svelte.dev/e/lifecycle_outside_component`)}function je(){throw Error(`https://svelte.dev/e/async_derived_orphan`)}function Me(e,t,n){throw Error(`https://svelte.dev/e/each_key_duplicate`)}function Ne(e){throw Error(`https://svelte.dev/e/effect_in_teardown`)}function Pe(){throw Error(`https://svelte.dev/e/effect_in_unowned_derived`)}function Fe(e){throw Error(`https://svelte.dev/e/effect_orphan`)}function Ie(){throw Error(`https://svelte.dev/e/effect_update_depth_exceeded`)}function Le(e){throw Error(`https://svelte.dev/e/props_invalid_value`)}function Re(){throw Error(`https://svelte.dev/e/state_descriptors_fixed`)}function ze(){throw Error(`https://svelte.dev/e/state_prototype_fixed`)}function Be(){throw Error(`https://svelte.dev/e/state_unsafe_mutation`)}function Ve(){throw Error(`https://svelte.dev/e/svelte_boundary_reset_onerror`)}var He=!1;function Ue(){He=!0}var M=null;function We(e){M=e}function Ge(e,t=!1,n){M={p:M,i:!1,c:null,e:null,s:e,x:null,r:U,l:He&&!t?{s:null,u:null,$:[]}:null}}function Ke(e){var t=M,n=t.e;if(n!==null){t.e=null;for(var r of n)xn(r)}return e!==void 0&&(t.x=e),t.i=!0,M=t.p,qe(e)}function qe(e={}){return i(e,ie,{value:!0}),e}function Je(){return!He||M!==null&&M.l===null}var Ye=[];function Xe(){var e=Ye;Ye=[],m(e)}function Ze(e){if(Ye.length===0&&!Tt){var t=Ye;queueMicrotask(()=>{t===Ye&&Xe()})}Ye.push(e)}function Qe(){for(;Ye.length>0;)Xe()}var $e=~(y|b|v);function et(e,t){e.f=e.f&$e|t}function tt(e){e.f&512||e.deps===null?et(e,v):et(e,b)}function nt(e,t,n){e.f&2048?t.add(e):e.f&4096&&n.add(e),et(e,v)}function rt(e){k&&sn(e)!==null&&ln(e)}var it=!1;function at(){it||(it=!0,document.addEventListener(`reset`,e=>{Promise.resolve().then(()=>{if(!e.defaultPrevented)for(let t of e.target.elements)t[de]?.()})},{capture:!0}))}function ot(e){var t=H,n=U;Gn(null),Kn(null);try{return e()}finally{Gn(t),Kn(n)}}function st(e,t,n,r=n){e.addEventListener(t,()=>ot(n));let i=e[de];e[de]=i?()=>{i(),r(!0)}:()=>r(!0),at()}function ct(e,t,n,r){let i=Je()?ft:ht;var a=e.filter(e=>!e.settled),o=t.map(i);if(n.length===0&&a.length===0){r(o);return}var s=U,c=lt(),l=a.length===1?a[0].promise:a.length>1?Promise.all(a.map(e=>e.promise)):null;function u(e){if(!(s.f&16384)){c();try{r([...o,...e])}catch(e){mn(e,s)}ut()}}var d=dt();if(n.length===0){l.then(()=>u([])).finally(d);return}function f(){Promise.all(n.map(e=>mt(e))).then(u).catch(e=>mn(e,s)).finally(d)}l?l.then(()=>{c(),f(),ut()}):f()}function lt(){var e=U,t=H,n=M,r=P;return function(i=!0){Kn(e),Gn(t),We(n),i&&!(e.f&16384)&&(r?.activate(),r?.apply())}}function ut(e=!0){Kn(null),Gn(null),We(null),e&&P?.deactivate()}function dt(){var e=U,t=e.b,n=P,r=!!t?.is_rendered();return t?.update_pending_count(1,n),n.increment(r,e),()=>{t?.update_pending_count(-1,n),n.decrement(r,e)}}function ft(e){var t=2|y;return U!==null&&(U.f|=ee),{ctx:M,deps:null,effects:null,equals:De,f:t,fn:e,reactions:null,rv:0,v:he,wv:0,parent:U,ac:null}}var pt=Symbol(`obsolete`);function mt(e,t,n){let r=U;r===null&&je();var i=void 0,a=Ht(he),o=!H,s=new Set;return Tn(()=>{var t=U,n=h();i=n.promise;try{Promise.resolve(e()).then(n.resolve,e=>{e!==fe&&n.reject(e)}).finally(ut)}catch(e){n.reject(e),ut()}var c=P;if(o){if(t.f&32768)var l=dt();if(r.b?.is_rendered())c.async_deriveds.get(t)?.reject(pt);else for(let e of s.values())e.reject(pt);s.add(n),c.async_deriveds.set(t,n)}let u=(e,t=void 0)=>{l?.(),s.delete(n),t!==pt&&(c.activate(),t?(a.f|=ne,Kt(a,t)):(a.f&8388608&&(a.f^=ne),Kt(a,e)),c.deactivate())};n.promise.then(u,e=>u(null,e||`unknown`))}),yn(()=>{for(let e of s)e.reject(pt)}),new Promise(e=>{function t(n){function r(){n===i?e(a):t(i)}n.then(r,r)}t(i)})}function N(e){let t=ft(e);return Jn(t),t}function ht(e){let t=ft(e);return t.equals=ke,t}function gt(e){var t=e.effects;if(t!==null){e.effects=null;for(var n=0;n{t.ac.abort(fe),t.ac=null}),t.fn!==null&&(t.teardown=f),lr(t,0),An(t))}function bt(e){if(e.effects!==null)for(let t of e.effects)t.teardown&&t.fn!==null&&ur(t)}var xt=null,P=null,St=null,Ct=null,wt=null,Tt=!1,Et=!1,Dt=null,Ot=null,kt=0,At=1,jt=class e{id=At++;#e=!1;linked=!0;#t=null;#n=null;async_deriveds=new Map;current=new Map;previous=new Map;#r=new Set;#i=new Set;#a=0;#o=new Map;#s=null;#c=[];#l=[];#u=new Set;#d=new Set;#f=new Map;#p=new Set;is_fork=!1;#m=!1;constructor(){xt===null?xt=this:(xt.#n=this,this.#t=xt),xt=this}#h(){if(this.is_fork)return!0;for(let n of this.#o.keys()){for(var e=n,t=!1;e.parent!==null;){if(this.#f.has(e)){t=!0;break}e=e.parent}if(!t)return!0}return!1}skip_effect(e){this.#f.has(e)||this.#f.set(e,{d:[],m:[]}),this.#p.delete(e)}unskip_effect(e,t=e=>this.schedule(e)){var n=this.#f.get(e);if(n){this.#f.delete(e);for(var r of n.d)et(r,y),t(r);for(r of n.m)et(r,b),t(r)}this.#p.add(e)}#g(){var e=[];for(let i of this.#c)if(!(i.f&16384||!(i.f&6144))){for(var t=i,n=!1;t.parent!==null;){t=t.parent;var r=t.f;if(r&96){if(!(r&1024)){n=!0;break}t.f^=v}}n||e.push(t)}return this.#c=[],e}#_(){this.#e=!0;for(let e of this.#u)this.#d.delete(e),et(e,y),this.schedule(e);for(let e of this.#d)et(e,b),this.schedule(e);this.apply();for(var t=Dt=[],n=[],r=Ot=[];this.#c.length>0;){kt++>1e3&&(this.#S(),Nt());for(let e of this.#g())try{this.#v(e,t,n)}catch(t){throw Rt(e),this.#h()||this.discard(),t}}if(P=null,r.length>0){var i=e.ensure();for(let e of r)i.schedule(e)}if(Dt=null,Ot=null,this.#h()){this.#x(n),this.#x(t);for(let[e,t]of this.#f)Lt(e,t);r.length>0&&P.#_();return}let a=this.#y();if(a){this.#x(n),this.#x(t),a.#b(this);return}this.#u.clear(),this.#d.clear();for(let e of this.#r)e(this);this.#r.clear(),St=this,Ft(n),Ft(t),St=null,this.#s?.resolve();var o=P;if(this.#a===0&&(this.#c.length===0||o!==null)&&this.#S(),this.#c.length>0){if(o!==null){for(let e of this.#c)o.#c.push(e);this.#c=[]}else o=this}o!==null&&(Bt.clear(),o.#_())}#v(e,t,n){e.f^=v;for(var r=e.first;r!==null;){var i=r.f,a=!!(i&96);if(!(a&&i&1024||i&8192||this.#f.has(r))&&r.fn!==null){a?r.f^=v:i&4?t.push(r):ir(r)&&(i&16&&this.#d.add(r),ur(r));var o=r.first;if(o!==null){r=o;continue}}for(;r!==null;){var s=r.next;if(s!==null){r=s;break}r=r.parent}}}#y(){for(var e=this.#t;e!==null;){if(!e.is_fork){for(let[t,[,n]]of this.current)if(e.current.has(t)&&!n)return e}e=e.#t}return null}#b(e){for(let[t,n]of e.current)!this.previous.has(t)&&e.previous.has(t)&&this.previous.set(t,e.previous.get(t)),this.current.set(t,n);for(let[t,n]of e.async_deriveds){let e=this.async_deriveds.get(t);e&&n.promise.then(e.resolve).catch(e.reject)}e.async_deriveds.clear(),this.transfer_effects(e.#u,e.#d);let t=e=>{var n=e.reactions;if(n!==null&&!(e.f&2&&!(e.f&6144)))for(let e of n){var r=e.f;if(r&2)t(e);else{var i=e;r&4194320&&!this.async_deriveds.has(i)&&(this.#d.delete(i),et(i,y),this.schedule(i))}}};for(let e of this.current.keys())t(e);this.oncommit(()=>e.discard()),e.#S(),P=this,this.#_()}#x(e){for(var t=0;t{this.#m=!1,this.linked&&this.flush()}))}transfer_effects(e,t){for(let t of e)this.#u.add(t);for(let e of t)this.#d.add(e);e.clear(),t.clear()}oncommit(e){this.#r.add(e)}ondiscard(e){this.#i.add(e)}settled(){return(this.#s??=h()).promise}static ensure(){if(P===null){let t=P=new e;!Et&&!Tt&&Ze(()=>{t.#e||t.flush()})}return P}apply(){Ct=null}schedule(e){if(wt=e,e.b?.is_pending&&e.f&16777228&&!(e.f&32768)){e.b.defer_effect(e);return}this.#c.push(e)}#S(){if(this.linked){var e=this.#t,t=this.#n;e===null||(e.#n=t),t===null?xt=e:t.#t=e,this.linked=!1}}};function Mt(e){var t=Tt;Tt=!0;try{var n;for(e&&(P!==null&&!P.is_fork&&P.flush(),n=e());;){if(Qe(),P===null)return n;P.flush()}}finally{Tt=t}}function Nt(){try{Ie()}catch(e){mn(e,wt)}}var Pt=null;function Ft(e){var t=e.length;if(t!==0){for(var n=0;n0)){Bt.clear();for(let e of Pt){if(e.f&24576)continue;let t=[e],n=e.parent;for(;n!==null;)Pt.has(n)&&(Pt.delete(n),t.push(n)),n=n.parent;for(let e=t.length-1;e>=0;e--){let n=t[e];n.f&24576||ur(n)}}Pt.clear()}}Pt=null}}function It(e){P.schedule(e)}function Lt(e,t){if(!(e.f&32&&e.f&1024)){e.f&2048?t.d.push(e):e.f&4096&&t.m.push(e),et(e,v);for(var n=e.first;n!==null;)Lt(n,t),n=n.next}}function Rt(e){et(e,v);for(var t=e.first;t!==null;)Rt(t),t=t.next}var zt=new Set,Bt=new Map,Vt=!1;function Ht(e,t){return{f:0,v:e,reactions:null,equals:De,rv:0,wv:0}}function F(e,t){let n=Ht(e,t);return Jn(n),n}function Ut(e,t=!1,n=!0){let r=Ht(e);return t||(r.equals=ke),He&&n&&M!==null&&M.l!==null&&(M.l.s??=[]).push(r),r}function I(e,t,n=!1){return H!==null&&(!Wn||H.f&131072)&&Je()&&H.f&4325394&&(qn===null||!qn.has(e))&&Be(),Kt(e,n?Zt(t):t,Ot)}var Wt=null,Gt=0;function Kt(e,t,n=null){if(!e.equals(t)){Hn?Bt.set(e,t):Bt.has(e)||Bt.set(e,e.v);var r=jt.ensure();if(r.capture(e,t),e.f&2){let t=e;e.f&2048&&_t(t),Ct===null&&tt(t)}e.wv=rr(),Wt=null,Gt=0,Xt(e,y,n),Wt=null,Je()&&U!==null&&U.f&1024&&!(U.f&96)&&(Zn===null?Qn([e]):Zn.push(e)),!r.is_fork&&zt.size>0&&!Vt&&qt()}return t}function qt(){Vt=!1;for(let e of zt){e.f&1024&&et(e,b);let t;try{t=ir(e)}catch{t=!0}t&&ur(e)}zt.clear()}function Jt(e,t=1){var n=W(e),r=t===1?n++:n--;return I(e,n),r}function Yt(e){I(e,e.v+1)}function Xt(e,t,n){var r=e.reactions;if(r!==null){var i=Je(),a=r.length;if(Gt+=a,Gt>1e5&&Wt===null&&(Wt=new Set),Wt!==null){if(Wt.has(e))return;Wt.add(e)}for(var o=0;o{if(tr===d)return e();var t=H,n=tr;Gn(null),nr(d);var r=e();return Gn(t),nr(n),r};return i&&r.set(`length`,F(t.length,u)),new Proxy(t,{defineProperty(e,t,n){(!(`value`in n)||n.configurable===!1||n.enumerable===!1||n.writable===!1)&&Re();var i=r.get(t);return i===void 0?f(()=>{var e=F(n.value,u);return r.set(t,e),e}):I(i,n.value,!0),!0},deleteProperty(e,t){var n=r.get(t);if(n===void 0){if(t in e){let e=f(()=>F(he,u));r.set(t,e),Yt(o)}}else I(n,he),Yt(o);return!0},get(e,n,i){if(n===re)return t;var o=r.get(n),s=n in e;if(o===void 0&&(!s||a(e,n)?.writable)&&(o=f(()=>F(Zt(s?e[n]:he),u)),r.set(n,o)),o!==void 0){var c=W(o);return c===he?void 0:c}return Reflect.get(e,n,i)},getOwnPropertyDescriptor(e,t){this.has?.(e,t);var n=Reflect.getOwnPropertyDescriptor(e,t),i=r.get(t);if(i!==void 0){var a=W(i);if(a===he)return;if(n&&`value`in n)n.value=a;else return{enumerable:!0,configurable:!0,value:a,writable:!0}}return n},has(e,t){if(t===re)return!0;var n=r.get(t),i=n!==void 0&&n.v!==he||Reflect.has(e,t);return(n!==void 0||U!==null&&(!i||a(e,t)?.writable))&&(n===void 0&&(n=f(()=>F(i?Zt(e[t]):he,u)),r.set(t,n)),W(n)===he)?!1:i},set(e,t,n,s){var c=r.get(t),l=t in e;if(i&&t===`length`)for(var d=n;dF(he,u)),r.set(d+``,p)):I(p,he)}if(c===void 0)(!l||a(e,t)?.writable)&&(c=f(()=>F(void 0,u)),I(c,Zt(n)),r.set(t,c));else{l=c.v!==he;var m=f(()=>Zt(n));I(c,m)}var h=Reflect.getOwnPropertyDescriptor(e,t);if(h?.set&&h.set.call(s,n),!l){if(i&&typeof t==`string`){var g=r.get(`length`),_=Number(t);Number.isInteger(_)&&_>=g.v&&I(g,_+1)}Yt(o)}return!0},ownKeys(e){W(o);var t=Reflect.ownKeys(e).filter(e=>{var t=r.get(e);return t===void 0||t.v!==he});for(var[n,i]of r)i.v!==he&&!(n in e)&&t.push(n);return t},setPrototypeOf(){ze()}})}function Qt(e){try{if(typeof e==`object`&&e&&re in e)return e[re]}catch{}return e}function $t(e,t){return Object.is(Qt(e),Qt(t))}var en,tn,nn,rn;function an(){if(en===void 0){en=window,tn=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,n=Text.prototype;nn=a(t,`firstChild`).get,rn=a(t,`nextSibling`).get,u(e)&&(e[ce]=void 0,e[se]=null,e[le]=void 0,e.__e=void 0),u(n)&&(n[ue]=void 0)}}function on(e=``){return document.createTextNode(e)}function sn(e){return nn.call(e)}function cn(e){return rn.call(e)}function L(e,t){if(!k)return sn(e);var n=sn(A);if(n===null)n=A.appendChild(on());else if(t&&n.nodeType!==3){var r=on();return n?.before(r),Se(r),r}return t&&fn(n),Se(n),n}function R(e,t=!1){if(!k){var n=sn(e);return n instanceof Comment&&n.data===``?cn(n):n}if(t){if(A?.nodeType!==3){var r=on();return A?.before(r),Se(r),r}fn(A)}return A}function z(e,t=!1){if(!k)return sn(e);var n=L(e,t);return j(e),n}function B(e,t=1,n=!1){let r=k?A:e;for(var i;t--;)i=r,r=cn(r);if(!k)return r;if(n){if(r?.nodeType!==3){var a=on();return r===null?i?.after(a):r.before(a),Se(a),a}fn(r)}return Se(r),r}function ln(e){e.textContent=``}function un(){return!1}function dn(e,t,n){return t==null||t===`http://www.w3.org/1999/xhtml`?n?document.createElement(e,{is:n}):document.createElement(e):n?document.createElementNS(t,e,{is:n}):document.createElementNS(t,e)}function fn(e){if(e.nodeValue.length<65536)return;let t=e.nextSibling;for(;t!==null&&t.nodeType===3;)t.remove(),e.nodeValue+=t.nodeValue,t=e.nextSibling}function pn(e){var t=U;if(t===null)return H.f|=ne,e;if(!(t.f&32768)&&!(t.f&4))throw e;mn(e,t)}function mn(e,t){if(!(t!==null&&t.f&16384)){for(;t!==null;){if(t.f&128&&!(t.f&33570816)){if(!(t.f&32768))throw e;try{t.b.error(e);return}catch(t){e=t}}t=t.parent}throw e}}function hn(e){U===null&&(H===null&&Fe(e),Pe()),Hn&&Ne(e)}function gn(e,t){var n=t.last;n===null?t.last=t.first=e:(n.next=e,e.prev=n,t.last=e)}function _n(e,t){var n=U;n!==null&&n.f&8192&&(e|=x);var r={ctx:M,deps:null,nodes:null,f:e|y|512,first:null,fn:t,last:null,next:null,parent:n,b:n&&n.b,prev:null,teardown:null,wv:0,ac:null};P?.register_created_effect(r);var i=r;if(e&4)Dt===null?jt.ensure().schedule(r):Dt.push(r);else if(t!==null){try{ur(r)}catch(e){throw Mn(r),e}i.deps===null&&i.teardown===null&&i.nodes===null&&i.first===i.last&&!(i.f&524288)&&(i=i.first,e&16&&e&65536&&i!==null&&(i.f|=T))}if(i!==null&&(i.parent=n,n!==null&&gn(i,n),H!==null&&H.f&2&&!(e&64))){var a=H;(a.effects??=[]).push(i)}return r}function vn(){return H!==null&&!Wn}function yn(e){let t=_n(8,null);return et(t,v),t.teardown=e,t}function bn(e){hn(`$effect`);var t=U.f;if(!H&&t&32&&M!==null&&!M.i){var n=M;(n.e??=[]).push(e)}else return xn(e)}function xn(e){return _n(4|E,e)}function Sn(e){return hn(`$effect.pre`),_n(8|E,e)}function Cn(e){jt.ensure();let t=_n(64|ee,e);return(e={})=>new Promise(n=>{e.outro?Fn(t,()=>{Mn(t),n(void 0)}):(Mn(t),n(void 0))})}function wn(e){return _n(4,e)}function Tn(e){return _n(te|ee,e)}function En(e,t=0){return _n(8|t,e)}function V(e,t=[],n=[],r=[]){ct(r,t,n,t=>{_n(8,()=>{e(...t.map(W))})})}function Dn(e,t=0){return _n(16|t,e)}function On(e){return _n(32|ee,e)}function kn(e){var t=e.teardown;if(t!==null){let n=Hn,r=H;Un(!0),Gn(null);try{t.call(null)}catch(t){mn(t,e.parent)}finally{Un(n),Gn(r)}}}function An(e,t=!1){var n=e.first;for(e.first=e.last=null;n!==null;){let e=n.ac;e!==null&&ot(()=>{e.abort(fe)});var r=n.next;n.f&64?n.parent=null:Mn(n,t),n=r}}function jn(e){for(var t=e.first;t!==null;){var n=t.next;t.f&32||Mn(t),t=n}}function Mn(e,t=!0){var n=!1;(t||e.f&262144)&&e.nodes!==null&&e.nodes.end!==null&&(Nn(e.nodes.start,e.nodes.end),n=!0),e.f|=w,An(e,t&&!n),lr(e,0);var r=e.nodes&&e.nodes.t;if(r!==null)for(let e of r)e.stop();kn(e),e.f^=w,e.f|=S;var i=e.parent;i!==null&&i.first!==null&&Pn(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes=e.ac=e.b=null}function Nn(e,t){for(;e!==null;){var n=e===t?null:cn(e);e.remove(),e=n}}function Pn(e){var t=e.parent,n=e.prev,r=e.next;n!==null&&(n.next=r),r!==null&&(r.prev=n),t!==null&&(t.first===e&&(t.first=r),t.last===e&&(t.last=n))}function Fn(e,t,n=!0){var r=[];e.f|=256,In(e,r,!0);var i=()=>{n&&Mn(e),t&&t()},a=r.length;if(a>0){var o=()=>--a||i();for(var s of r)s.out(o)}else i()}function In(e,t,n){if(!(e.f&8192)){e.f^=x;var r=e.nodes&&e.nodes.t;if(r!==null)for(let e of r)(e.is_global||n)&&t.push(e);for(var i=e.first;i!==null;){var a=i.next;if(!(i.f&64)){var o=!!(i.f&65536)||!!(i.f&32)&&!!(e.f&16);In(i,t,o?n:!1)}i=a}}}function Ln(e){e.f&=-257,Rn(e,!0)}function Rn(e,t){if(!(e.f&256)&&e.f&8192){e.f^=x,e.f&1024||(et(e,y),jt.ensure().schedule(e));for(var n=e.first;n!==null;){var r=n.next,i=!!(n.f&65536)||!!(n.f&32);Rn(n,i?t:!1),n=r}var a=e.nodes&&e.nodes.t;if(a!==null)for(let e of a)(e.is_global||t)&&e.in()}}function zn(e,t){if(e.nodes)for(var n=e.nodes.start,r=e.nodes.end;n!==null;){var i=n===r?null:cn(n);t.append(n),n=i}}var Bn=null,Vn=!1,Hn=!1;function Un(e){Hn=e}var H=null,Wn=!1;function Gn(e){H=e}var U=null;function Kn(e){U=e}var qn=null;function Jn(e){H!==null&&(H.f&2097152||H.f&2)&&(qn??=new Set).add(e)}var Yn=null,Xn=0,Zn=null;function Qn(e){Zn=e}var $n=1,er=0,tr=er;function nr(e){tr=e}function rr(){return++$n}function ir(e){var t=e.f;if(t&2048)return!0;if(t&4096){for(var n=e.deps,r=n.length,i=0;ie.wv)return!0}t&512&&Ct===null&&et(e,v)}return!1}function ar(e,t,n=!0){var r=e.reactions;if(r!==null&&!(qn!==null&&qn.has(e)))for(var i=0;i{e.ac.abort(fe)}),e.ac=null);try{e.f|=O;var u=e.fn,d=u();e.f|=C;var f=sr(e);if(Je()&&Zn!==null&&!Wn&&f!==null&&!(e.f&6146))for(var p=0;p0)for(t.length=Xn+Yn.length,r=0;r{s.ac.abort(fe),s.ac=null,et(s,y)}),yt(s),lr(s,0)}}function lr(e,t){var n=e.deps;if(n!==null)for(var r=t;rn?.call(this,e))}return e.startsWith(`pointer`)||e.startsWith(`touch`)||e===`wheel`?(i.__removed=!1,Ze(()=>{i.__removed||t.addEventListener(e,i,r)})):t.addEventListener(e,i,r),i}function Cr(e,t,n,r,i){var a={capture:r,passive:i},o=Sr(e,t,n,a);(t===document.body||t===window||t===document||t instanceof HTMLMediaElement)&&yn(()=>{o.__removed=!0,t.removeEventListener(e,o,a)})}function G(e,t,n){(t[yr]??={})[e]=n}function wr(e){for(var t=0;t{Er=!1,Tr=null}));var s=0,c=Tr===e&&e[yr];if(c){var l=a.indexOf(c);if(l!==-1&&(t===document||t===window)){e[yr]=t;return}var u=a.indexOf(t);if(u===-1)return;l<=u&&(s=l)}if(o=a[s]||e.target,o!==t){i(e,`currentTarget`,{configurable:!0,get(){return o||n}});var d=H,f=U;Gn(null),Kn(null);try{for(var p,m=[];o!==null&&o!==t;){try{var h=o[yr]?.[r];h!=null&&(!o.disabled||e.target===o)&&h.call(o,e)}catch(e){p?m.push(e):p=e}if(e.cancelBubble)break;s++,o=s{throw e});throw p}}finally{e[yr]=t,delete e.currentTarget,Gn(d),Kn(f)}}}var Or=globalThis?.window?.trustedTypes&&globalThis.window.trustedTypes.createPolicy(`svelte-trusted-html`,{createHTML:e=>e});function kr(e){return Or?.createHTML(e)??e}function Ar(e){var t=dn(`template`);return t.innerHTML=kr(e.replaceAll(``,``)),t.content}function jr(e,t){var n=U;n.nodes===null&&(n.nodes={start:e,end:t,a:null,t:null})}function K(e,t){var n=!!(t&1),r=!!(t&2),i,a=!e.startsWith(``);return()=>{if(k)return jr(A,null),A;i===void 0&&(i=Ar(a?e:``+e),n||(i=sn(i)));var t=r||tn?document.importNode(i,!0):i.cloneNode(!0);if(n){var o=sn(t),s=t.lastChild;jr(o,s)}else jr(t,t);return t}}function Mr(e,t,n=`svg`){var r=!e.startsWith(``),i=!!(t&1),a=`<${n}>${r?e:``+e}`,o;return()=>{if(k)return jr(A,null),A;if(!o){var e=sn(Ar(a));if(i)for(o=document.createDocumentFragment();sn(e);)o.appendChild(sn(e));else o=sn(e)}var t=o.cloneNode(!0);if(i){var n=sn(t),r=t.lastChild;jr(n,r)}else jr(t,t);return t}}function Nr(e,t){return Mr(e,t,`svg`)}function Pr(e=``){if(!k){var t=on(e+``);return jr(t,t),t}var n=A;return n.nodeType===3?fn(n):(n.before(n=on()),Se(n)),jr(n,n),n}function Fr(){if(k)return jr(A,null),A;var e=document.createDocumentFragment(),t=document.createComment(``),n=on();return e.append(t,n),jr(t,n),e}function q(e,t){if(k){var n=U;(!(n.f&32768)||n.nodes.end===null)&&(n.nodes.end=A),Ce();return}e!==null&&e.before(t)}function Ir(){if(k&&A&&A.nodeType===8&&A.textContent?.startsWith(`$`)){let e=A.textContent.substring(1);return Ce(),e}return(window.__svelte??={}).uid??=1,`c${window.__svelte.uid++}`}function Lr(e){let t=0,n=Ht(0),r;return()=>{vn()&&(W(n),En(()=>(t===0&&(r=mr(()=>e(()=>Yt(n)))),t+=1,()=>{Ze(()=>{--t,t===0&&(r?.(),r=void 0,Yt(n))})})))}}var Rr=T|ee;function zr(e,t,n,r){new Br(e,t,n,r)}var Br=class{parent;is_pending=!1;transform_error;#e;#t=k?A:null;#n;#r;#i;#a=null;#o=null;#s=null;#c=null;#l=0;#u=0;#d=!1;#f=new Set;#p=new Set;#m=null;#h=Lr(()=>(this.#m=Ht(this.#l),()=>{this.#m=null}));constructor(e,t,n,r){this.#e=e,this.#n=t,this.#r=e=>{var t=U;t.b=this,t.f|=128,n(e)},this.parent=U.b,this.transform_error=r??this.parent?.transform_error??(e=>e),this.#i=Dn(()=>{if(k){let e=this.#t;Ce();let t=e.data===`[!`;if(e.data.startsWith(`[?`)){let t=JSON.parse(e.data.slice(2));this.#_(t)}else t?this.#y():this.#g()}else this.#b()},Rr),k&&(this.#e=A)}#g(){try{this.#a=On(()=>this.#r(this.#e))}catch(e){this.error(e)}}#_(e){let t=this.#n.failed,{reset:n,invoke_onerror:r}=this.#v(e);Ze(r),t&&(this.#s=On(()=>{t(this.#e,()=>e,()=>n)}))}#v(e){var t=!1,n=!1;let r=()=>{if(t){be();return}t=!0,n&&Ve(),this.#s!==null&&Fn(this.#s,()=>{this.#s=null}),this.#S(()=>{this.#b()})};return{reset:r,invoke_onerror:()=>{try{n=!0,this.#n.onerror?.(e,r),n=!1}catch(e){mn(e,this.#i&&this.#i.parent)}}}}#y(){let e=this.#n.pending;e&&(this.is_pending=!0,this.#o=On(()=>e(this.#e)),Ze(()=>{var e=this.#c=document.createDocumentFragment(),t=on(),n=!1;if(e.append(t),this.#a=this.#S(()=>{try{return On(()=>this.#r(t))}catch(e){try{this.error(e),n=!0}catch(e){mn(e,this.#i.parent)}return null}}),this.#a===null){this.#c=null,n&&this.#x(P);return}this.#u===0&&(this.#e.before(e),this.#c=null,Fn(this.#o,()=>{this.#o=null}),this.#x(P))}))}#b(){try{if(this.is_pending=this.has_pending_snippet(),this.#u=0,this.#l=0,this.#a=On(()=>{this.#r(this.#e)}),this.#u>0){var e=this.#c=document.createDocumentFragment();zn(this.#a,e);let t=this.#n.pending;this.#o=On(()=>t(this.#e))}else this.#x(P)}catch(e){this.error(e)}}#x(e){this.is_pending=!1,e.transfer_effects(this.#f,this.#p)}defer_effect(e){nt(e,this.#f,this.#p)}is_rendered(){return!this.is_pending&&(!this.parent||this.parent.is_rendered())}has_pending_snippet(){return!!this.#n.pending}#S(e){var t=U,n=H,r=M;Kn(this.#i),Gn(this.#i),We(this.#i.ctx);try{return jt.ensure(),e()}finally{Kn(t),Gn(n),We(r)}}#C(e,t){if(!this.has_pending_snippet()){this.parent&&this.parent.#C(e,t);return}this.#u+=e,this.#u===0&&(this.#x(t),this.#o&&Fn(this.#o,()=>{this.#o=null}),this.#c&&=(this.#e.before(this.#c),null))}update_pending_count(e,t){this.#C(e,t),this.#l+=e,!(!this.#m||this.#d)&&(this.#d=!0,Ze(()=>{this.#d=!1,this.#m&&Kt(this.#m,this.#l)}))}get_effect_pending(){return this.#h(),W(this.#m)}error(e){if(!this.#n.onerror&&!this.#n.failed)throw e;P?.is_fork?(this.#a&&P.skip_effect(this.#a),this.#o&&P.skip_effect(this.#o),this.#s&&P.skip_effect(this.#s),P.oncommit(()=>{this.#w(e)})):this.#w(e)}#w(e){this.#a&&=(Mn(this.#a),null),this.#o&&=(Mn(this.#o),null),this.#s&&=(Mn(this.#s),null),k&&(Se(this.#t),we(),Se(Te()));let t=this.#n.failed,n=e=>{let{reset:n,invoke_onerror:r}=this.#v(e);r(),t&&(this.#s=this.#S(()=>{try{return On(()=>{var r=U;r.b=this,r.f|=128,t(this.#e,()=>e,()=>n)})}catch(e){return mn(e,this.#i.parent),null}}))};Ze(()=>{var t;try{t=this.transform_error(e)}catch(e){mn(e,this.#i&&this.#i.parent);return}typeof t==`object`&&t&&typeof t.then==`function`?t.then(n,e=>mn(e,this.#i&&this.#i.parent)):n(t)})}};function J(e,t){var n=t==null?``:typeof t==`object`?`${t}`:t;n!==(e[ue]??=e.nodeValue)&&(e[ue]=n,e.nodeValue=`${n}`)}function Vr(e,t){return Ur(e,t)}var Hr=new Map;function Ur(e,{target:t,anchor:n,props:i={},events:a,context:o,intro:s=!0,transformError:c}){an();var l=void 0,u=Cn(()=>{var s=n??t.appendChild(on());zr(s,{pending:()=>{}},t=>{Ge({});var n=M;if(o&&(n.c=o),a&&(i.$$events=a),k&&jr(t,null),l=e(t,i)||qe(),k&&(U.nodes.end=A,A===null||A.nodeType!==8||A.data!==`]`))throw ve(),me;Ke()},c);var u=new Set,d=e=>{for(var n=0;n{for(var e of u)for(let n of[t,document]){var r=Hr.get(n),i=r.get(e);--i==0?(n.removeEventListener(e,Dr),r.delete(e),r.size===0&&Hr.delete(n)):r.set(e,i)}xr.delete(d),s!==n&&s.parentNode?.removeChild(s)}});return Wr.set(l,u),l}var Wr=new WeakMap,Gr=class{anchor;#e=new Map;#t=new Map;#n=new Map;#r=new Set;#i=!0;constructor(e,t=!0){this.anchor=e,this.#i=t}#a=e=>{if(this.#e.has(e)){var t=this.#e.get(e),n=this.#t.get(t);if(n)Ln(n),this.#r.delete(t);else{var r=this.#n.get(t);r&&(Ln(r.effect),this.#t.set(t,r.effect),this.#n.delete(t),r.fragment.lastChild.remove(),this.anchor.before(r.fragment),n=r.effect)}for(let[t,n]of this.#e){if(this.#e.delete(t),t===e)break;let r=this.#n.get(n);r&&(Mn(r.effect),this.#n.delete(n))}for(let[e,r]of this.#t){if(e===t||this.#r.has(e))continue;let i=()=>{if(Array.from(this.#e.values()).includes(e)){var t=document.createDocumentFragment();zn(r,t),t.append(on()),this.#n.set(e,{effect:r,fragment:t})}else Mn(r);this.#r.delete(e),this.#t.delete(e)};this.#i||!n?(this.#r.add(e),Fn(r,i,!1)):i()}}};#o=e=>{this.#e.delete(e);let t=Array.from(this.#e.values());for(let[e,n]of this.#n)t.includes(e)||(Mn(n.effect),this.#n.delete(e))};ensure(e,t){var n=P,r=un();if(t&&!this.#t.has(e)&&!this.#n.has(e)){if(r){var i=document.createDocumentFragment(),a=on();i.append(a),this.#n.set(e,{effect:On(()=>t(a)),fragment:i})}else this.#t.set(e,On(()=>t(this.anchor)))}if(this.#e.set(n,e),r){for(let[t,r]of this.#t)t===e?n.unskip_effect(r):n.skip_effect(r);for(let[t,r]of this.#n)t===e?n.unskip_effect(r.effect):n.skip_effect(r.effect);n.oncommit(this.#a),n.ondiscard(this.#o)}else k&&(this.anchor=A),this.#a(n)}};function Y(e,t,n=!1){var r;k&&(r=A,Ce());var i=new Gr(e),a=n?T:0;function o(e,t){if(k){var n=Ee(r);if(e!==parseInt(n.substring(1))){var a=Te();Se(a),i.anchor=a,xe(!1),i.ensure(e,t),xe(!0);return}}i.ensure(e,t)}Dn(()=>{var e=!1;t((t,n=0)=>{e=!0,o(n,t)}),e||o(-1,null)},a)}var Kr=Symbol(`NaN`);function qr(e,t,n){k&&Ce();var r=new Gr(e),i=!Je();Dn(()=>{var e=t();e!==e&&(e=Kr),i&&typeof e==`object`&&e&&(e={}),r.ensure(e,n)})}function X(e,t){return t}function Jr(e,t,n){for(var i=[],a=t.length,o,s=t.length,c=0;c{if(o){if(o.pending.delete(n),o.done.add(n),o.pending.size===0){var t=e.outrogroups;Yr(e,r(o.done)),t.delete(o),t.size===0&&(e.outrogroups=null)}}else--s},!1)}if(s===0){var l=i.length===0&&n!==null&&e.pending.size===0;if(l){var u=n,d=u.parentNode;ln(d),d.append(u),e.items.clear()}Yr(e,t,!l)}else o={pending:new Set(t),done:new Set},(e.outrogroups??=new Set).add(o)}function Yr(e,t,n=!0){var r;if(e.pending.size>0){r=new Set;for(let t of e.pending.values())for(let n of t)r.add(e.items.get(n).e)}for(var i=0;i{var t=i();return e(t)?t:t==null?[]:r(t)}),p,m=new Map,h=!0;function g(e){v.effect.f&16384||(v.pending.delete(e),v.fallback=d,Qr(v,p,c,n,a),d!==null&&(p.length===0?d.f&33554432?(d.f^=D,ei(d,null,c)):Ln(d):Fn(d,()=>{d=null})))}function _(e){v.pending.delete(e)}var v={effect:Dn(()=>{p=W(f);var e=p.length;let t=!1;k&&Ee(c)===`[!`!=(e===0)&&(c=Te(),Se(c),xe(!1),t=!0);for(var r=new Set,u=P,v=un(),y=0;ys(c)):(d=On(()=>s(Xr??=on())),d.f|=D)),e>r.size&&Me(``,``,``),k&&e>0&&Se(Te()),!h){if(m.set(u,r),v){for(let[e,t]of l)r.has(e)||u.skip_effect(t.e);u.oncommit(g),u.ondiscard(_)}else g(u)}t&&xe(!0),W(f)}),flags:n,items:l,pending:m,outrogroups:null,fallback:d};h=!1,k&&(c=A)}function Zr(e){for(;e!==null&&!(e.f&32);)e=e.next;return e}function Qr(e,t,n,i,a){var o=!!(i&8),s=t.length,c=e.items,l=Zr(e.effect.first),u,d=null,f,p=[],m=[],h,g,_,v;if(o)for(v=0;v0){var ee=i&4&&s===0?n:null;if(o){for(v=0;v{if(f!==void 0)for(_ of f)_.nodes?.a?.apply()})}function $r(e,t,n,r,i,a,o,s){var c=o&1?o&16?Ht(n):Ut(n,!1,!1):null,l=o&2?Ht(i):null;return{v:c,i:l,e:On(()=>(a(t,c??n,l??i,s),()=>{e.delete(r)}))}}function ei(e,t,n){if(e.nodes)for(var r=e.nodes.start,i=e.nodes.end,a=t&&!(t.f&33554432)?t.nodes.start:n;r!==null;){var o=cn(r);if(a.before(r),r===i)return;r=o}}function ti(e,t,n){t===null?e.effect.first=n:t.next=n,n===null?e.effect.last=t:n.prev=t}function ni(e,t,n){var r;k&&(r=A,Ce());var i=new Gr(e);Dn(()=>{var e=t()??null;if(k&&Ee(r)===`[`!=(e!==null)){var a=Te();Se(a),i.anchor=a,xe(!1),i.ensure(e,e&&(t=>n(t,e))),xe(!0);return}i.ensure(e,e&&(t=>n(t,e)))},T)}var ri=[...` -\r\f\xA0\v`];function ii(e,t,n){var r=e==null?``:``+e;if(t&&(r=r?r+` `+t:t),n){for(var i of Object.keys(n))if(n[i])r=r?r+` `+i:i;else if(r.length)for(var a=i.length,o=0;(o=r.indexOf(i,o))>=0;){var s=o+a;(o===0||ri.includes(r[o-1]))&&(s===r.length||ri.includes(r[s]))?r=(o===0?``:r.substring(0,o))+r.substring(s+1):o=s}}return r===``?null:r}function ai(e,t=!1){var n=t?` !important;`:`;`,r=``;for(var i of Object.keys(e)){var a=e[i];a!=null&&a!==``&&(r+=` `+i+`: `+a+n)}return r}function oi(e){return e[0]!==`-`||e[1]!==`-`?e.toLowerCase():e}function si(e,t){if(t){var n=``,r,i;if(Array.isArray(t)?(r=t[0],i=t[1]):r=t,e){e=String(e).replaceAll(/\/\*.*?\*\//g,``).trim();var a=!1,o=0,s=!1,c=[];r&&c.push(...Object.keys(r).map(oi)),i&&c.push(...Object.keys(i).map(oi));var l=0,u=-1;let t=e.length;for(var d=0;d{t.every(_i)||(`__defaultValue`in e&&fi(e,!1),`__value`in e&&pi(e,e.__value))});t.observe(e,{childList:!0,subtree:!0,attributes:!0,attributeFilter:[`value`]}),yn(()=>{t.disconnect()})}function hi(e,t,n=t){var r=new WeakSet,i=!0;st(e,`change`,t=>{var i=t?`[selected]`:`:checked`,a;if(e.multiple)a=[].map.call(e.querySelectorAll(i),gi);else{var o=e.querySelector(i)??e.querySelector(`option:not([disabled])`);a=o&&gi(o)}n(a),e.__value=a,P!==null&&r.add(P)}),wn(()=>{var a=t();if(e===document.activeElement){var o=P;if(r.has(o))return}if(pi(e,a,i),i&&a===void 0){var s=e.querySelector(`:checked`);s!==null&&(a=gi(s),n(a))}e.__value=a,i=!1})}function gi(e){return`__value`in e?e.__value:e.value}function _i(e){if(e.target.closest(`selectedcontent`)!==null)return!0;if(e.type===`childList`){var t=[...e.addedNodes,...e.removedNodes];return t.length>0&&t.every(e=>e.nodeName===`SELECTEDCONTENT`)}return!1}var vi=Symbol(`is custom element`),yi=Symbol(`is html`),bi=pe?`link`:`LINK`;function xi(e){if(k){var t=!1,n=()=>{if(!t){if(t=!0,e.hasAttribute(`value`)){var n=e.value;Q(e,`value`,null),e.value=n}if(e.hasAttribute(`checked`)){var r=e.checked;Q(e,`checked`,null),e.checked=r}}};e[de]=n,Ze(n),at()}}function Si(e,t){var n=Ci(e);n.checked!==(n.checked=t??void 0)&&(e.checked=t)}function Q(e,t,n,r){var i=Ci(e);k&&(i[t]=e.getAttribute(t),t===`src`||t===`srcset`||t===`href`&&e.nodeName===bi)||i[t]!==(i[t]=n)&&(t===`loading`&&(e[oe]=n),n==null?e.removeAttribute(t):typeof n!=`string`&&Ti(e).has(t)?e[t]=n:e.setAttribute(t,n))}function Ci(e){return e[se]??={[vi]:e.nodeName.includes(`-`),[yi]:e.namespaceURI===ge}}var wi=new Map;function Ti(e){var t=e.getAttribute(`is`)||e.nodeName,n=wi.get(t);if(n)return n;wi.set(t,n=new Set);for(var r,i=e,a=Element.prototype;a!==i;){for(var s in r=o(i),r)r[s].set&&s!==`innerHTML`&&s!==`textContent`&&s!==`innerText`&&n.add(s);i=l(i)}return n}function Ei(e,t,n=t){var r=new WeakSet;st(e,`input`,async i=>{var a=i?e.defaultValue:e.value;if(a=Ai(e)?ji(a):a,n(a),P!==null&&r.add(P),await dr(),a!==(a=t())){var o=e.selectionStart,s=e.selectionEnd,c=e.value.length;if(e.value=a??``,s!==null){var l=e.value.length;o===s&&s===c&&l>c?(e.selectionStart=l,e.selectionEnd=l):(e.selectionStart=o,e.selectionEnd=Math.min(s,l))}}}),(k&&e.defaultValue!==e.value||mr(t)==null&&e.value)&&(n(Ai(e)?ji(e.value):e.value),P!==null&&r.add(P)),En(()=>{var n=t();if(e===document.activeElement){var i=P;if(r.has(i))return}Ai(e)&&n===ji(e.value)||(e.type!==`date`||n||e.value)&&n!==e.value&&(e.value=n??``)})}var Di=new Set;function Oi(e,t,n,r,i=r){var a=n.getAttribute(`type`)===`checkbox`,o=e;let s=!1;if(t!==null)for(var c of t)o=o[c]??=[];o.push(n),st(n,`change`,()=>{var e=n.__value;a&&(e=ki(o,e,n.checked)),i(e)},()=>i(a?[]:null)),En(()=>{var e=r();if(k&&n.defaultChecked!==n.checked){s=!0;return}a?(e||=[],n.checked=e.includes(n.__value)):n.checked=$t(n.__value,e)}),yn(()=>{var e=o.indexOf(n);e!==-1&&o.splice(e,1)}),Di.has(o)||(Di.add(o),Ze(()=>{o.sort((e,t)=>e.compareDocumentPosition(t)===4?-1:1),Di.delete(o)})),Ze(()=>{if(s){var e=a?ki(o,e,n.checked):o.find(e=>e.checked)?.__value;i(e)}})}function ki(e,t,n){for(var r=new Set,i=0;i{var n=this.#e.get(e);n.delete(t),n.size===0&&(this.#e.delete(e),this.#t.unobserve(e))}}#r(){return this.#t??=new ResizeObserver(t=>{for(var n of t){e.entries.set(n.target,n);for(var r of this.#e.get(n.target)||[])r(n)}})}}({box:`border-box`});function Ni(e,t,n){var r=Mi.observe(e,()=>n(e[t]));wn(()=>(mr(()=>n(e[t])),r))}function Pi(e,t){return e===t||e?.[re]===t}function Fi(e=qe(),t,n,r){var i=M.r,a=U;return wn(()=>{var o,s;return En(()=>{o=s,s=r?.()||[],mr(()=>{Pi(n(...s),e)||(t(e,...s),o&&Pi(n(...o),e)&&t(null,...o))})}),()=>{let r=a;for(;r!==i&&r.parent!==null&&r.parent.f&33554432;)r=r.parent;let o=()=>{s&&Pi(n(...s),e)&&t(null,...s)},c=r.teardown;r.teardown=()=>{o(),c?.()}}}),e}function Ii(e,t,n,r,i){var a=()=>{r(n[e])};n.addEventListener(t,a),i?En(()=>{n[e]=i()}):a(),(n===document.body||n===window||n===document)&&yn(()=>{n.removeEventListener(t,a)})}function Li(e=!1){let t=M,n=t.l.u;if(!n)return;let r=()=>hr(t.s);if(e){let e=0,n={},i=ft(()=>{let r=!1,i=t.s;for(let e in i)i[e]!==n[e]&&(n[e]=i[e],r=!0);return r&&e++,e});r=()=>W(i)}n.b.length&&Sn(()=>{Ri(t,r),m(n.b)}),bn(()=>{let e=mr(()=>n.m.map(p));return()=>{for(let t of e)typeof t==`function`&&t()}}),n.a.length&&bn(()=>{Ri(t,r),m(n.a)})}function Ri(e,t){if(e.l.s)for(let t of e.l.s)W(t);t()}var zi=!1;function Bi(e){var t=zi;try{return zi=!1,[e(),zi]}finally{zi=t}}var Vi={get(e,t){let n=e.props.length;for(;n--;){let r=e.props[n];if(d(r)&&(r=r()),typeof r==`object`&&r&&t in r)return r[t]}},set(e,t,n){let r=e.props.length;for(;r--;){let i=e.props[r];d(i)&&(i=i());let o=a(i,t);if(o&&o.set)return o.set(n),!0}return!1},getOwnPropertyDescriptor(e,t){let n=e.props.length;for(;n--;){let r=e.props[n];if(d(r)&&(r=r()),typeof r==`object`&&r&&t in r){let e=a(r,t);return e&&!e.configurable&&(e.configurable=!0),e}}},has(e,t){if(t===re||t===ae)return!1;for(let n of e.props)if(d(n)&&(n=n()),n!=null&&t in n)return!0;return!1},ownKeys(e){let t=[];for(let n of e.props)if(d(n)&&(n=n()),n){for(let e in n)t.includes(e)||t.push(e);for(let e of Object.getOwnPropertySymbols(n))t.includes(e)||t.push(e)}return t}};function Hi(...e){return new Proxy({props:e},Vi)}function Ui(e,t,n,r){var i=!He||!!(n&2),o=!!(n&8),s=!!(n&16),c=r,l=!0,u=void 0,d=()=>s&&i?(u??=ft(r),W(u)):(l&&(l=!1,c=s?mr(r):r),c);let f;if(o){var p=re in e||ae in e;f=a(e,t)?.set??(p&&t in e?n=>e[t]=n:void 0)}var m,h=!1;o?[m,h]=Bi(()=>e[t]):m=e[t],m===void 0&&r!==void 0&&(m=d(),f&&(i&&Le(t),f(m)));var g=i?()=>{var n=e[t];return n===void 0?d():(l=!0,n)}:()=>{var n=e[t];return n!==void 0&&(c=void 0),n===void 0?c:n};if(i&&!(n&4))return g;if(f){var _=e.$$legacy;return(function(e,t){return arguments.length>0?((!i||!t||_||h)&&f(t?g():e),e):g()})}var v=!1,y=(n&1?ft:ht)(()=>(v=!1,g()));o&&W(y);var b=U;return(function(e,t){if(arguments.length>0){let n=t?W(y):i&&o?Zt(e):e;return I(y,n),v=!0,c!==void 0&&(c=n),e}return Hn&&v||b.f&16384?y.v:W(y)})}function Wi(e){M===null&&Ae(`onMount`),He&&M.l!==null?Ki(M).m.push(e):bn(()=>{let t=mr(e);if(typeof t==`function`)return t})}function Gi(e){M===null&&Ae(`onDestroy`),Wi(()=>()=>mr(e))}function Ki(e){var t=e.l;return t.u??={a:[],b:[],m:[]}}typeof window<`u`&&((window.__svelte??={}).v??=new Set).add(`5`);function qi(e,t){if(e instanceof RegExp)return{keys:!1,pattern:e};var n,r,i,a,o=[],s=``,c=e.split(`/`);for(c[0]||c.shift();i=c.shift();)n=i[0],n===`*`?(o.push(`wild`),s+=`/(.*)`):n===`:`?(r=i.indexOf(`?`,1),a=i.indexOf(`.`,1),o.push(i.substring(1,~r?r:~a?a:i.length)),s+=~r&&!~a?`(?:/([^/]+?))?`:`/([^/]+?)`,~a&&(s+=(~r?`?`:``)+`\\`+i.substring(a))):s+=`/`+i;return{keys:o,pattern:RegExp(`^`+s+(t?`(?=$|/)`:`/?$`),`i`)}}var Ji=new class{#e=F(Yi());get _loc(){return W(this.#e)}set _loc(e){I(this.#e,e)}#t=N(()=>this._loc.location);get _location(){return W(this.#t)}set _location(e){I(this.#t,e)}#n=N(()=>this._loc.querystring);get _querystring(){return W(this.#n)}set _querystring(e){I(this.#n,e)}#r=F(void 0);get _params(){return W(this.#r)}set _params(e){I(this.#r,e)}get loc(){return this._loc}get location(){return this._location}get querystring(){return this._querystring}get params(){return this._params}constructor(){typeof window<`u`?window.addEventListener(`hashchange`,()=>{this._loc=Yi()}):console.warn(`[svelte-spa-router] window 'window' is not defined, skipping initiation`)}};function Yi(){let e=typeof window<`u`?window.location.href:``,t=e.indexOf(`#/`),n=t>-1?e.substr(t+1):`/`,r=n.indexOf(`?`),i=``;return r>-1&&(i=n.substr(r+1),n=n.substr(0,r)),{location:n,querystring:i}}function Xi(e){e?window.scrollTo(e.__svelte_spa_router_scrollX||0,e.__svelte_spa_router_scrollY||0):window.scrollTo(0,0)}function Zi(e,t){Ge(t,!0);let n=Ui(t,`routes`,19,()=>({})),r=Ui(t,`prefix`,3,``),i=Ui(t,`restoreScrollState`,3,!1),a=Ui(t,`onConditionsFailed`,3,()=>{}),o=Ui(t,`onRouteLoaded`,3,()=>{}),s=Ui(t,`onRouteLoading`,3,()=>{}),c=Ui(t,`onRouteEvent`,3,()=>{});class l{path;component;conditions;userData;props;_pattern;_keys;constructor(e,t){let n=e=>typeof e==`object`&&!!e&&e._sveltesparouter===!0;if(!t||typeof t!=`function`&&!n(t))throw Error(`Invalid component object`);if(!e||typeof e==`string`&&(e.length<1||e.charAt(0)!=`/`&&e.charAt(0)!=`*`)||typeof e==`object`&&!(e instanceof RegExp))throw Error(`Invalid value for "path" argument - strings must start with / or *`);let r=qi(e);if(this.path=e,n(t)){let e=t;this.component=e.component,this.conditions=e.conditions||[],this.userData=e.userData,this.props=e.props||{}}else{let e=t;this.component=()=>Promise.resolve(e),this.conditions=[],this.props={}}this._pattern=r.pattern,this._keys=r.keys}match(e){if(r()){if(typeof r()==`string`){if(e.startsWith(r()))e=e.substr(r().length)||`/`;else return null}else if(r()instanceof RegExp){let t=e.match(r());if(t&&t[0])e=e.substr(t[0].length)||`/`;else return null}}let t=this._pattern.exec(e);if(t===null)return null;if(this._keys===!1)return t;let n={},i=0;for(;i{u.push(new l(t,e))}):Object.keys(n()).forEach(e=>{let t=n();u.push(new l(e,t[e]))});let d=F(null),f=F(null),p=F({}),m=null,h=null;bn(()=>{history.scrollRestoration=i()?`manual`:`auto`}),bn(()=>{if(!i())return;let e=e=>{m=e.state&&(e.state.__svelte_spa_router_scrollY||e.state.__svelte_spa_router_scrollX)?e.state:null};return window.addEventListener(`popstate`,e),()=>window.removeEventListener(`popstate`,e)});async function g(e,t){await dr(),e(t)}bn(()=>{let e=Ji.loc,t=!1;return mr(async()=>{let n=0;for(;n{t=!0}});function _(e){return e&&typeof e==`object`&&Object.keys(e).length?e:null}var v=Fr(),y=R(v),b=e=>{let t=N(()=>W(d));var n=Fr(),r=R(n),i=e=>{var n=Fr();ni(R(n),()=>W(t),(e,t)=>{t(e,Hi({get params(){return W(f)},get onRouteEvent(){return c()}},()=>W(p)))}),q(e,n)},a=e=>{var n=Fr();ni(R(n),()=>W(t),(e,t)=>{t(e,Hi({get onRouteEvent(){return c()}},()=>W(p)))}),q(e,n)};Y(r,e=>{W(f)?e(i):e(a,-1)}),q(e,n)};Y(y,e=>{W(d)&&e(b)}),q(e,v),Ke()}var Qi=[`bars`,`pace`,`zone`,`pie`,`fuel`,`resets`,`thresholds`],$i=`codexometer.web.preferences.v1`,ea={statusLine:null,tab:`quota`,view:`bars`,selected:``,defaultDetail:0,layouts:[]};function ta(){try{let e=JSON.parse(localStorage.getItem($i)||`null`);return!e||typeof e!=`object`?ea:{statusLine:Array.isArray(e.statusLine)?[...new Set(e.statusLine.filter(e=>typeof e==`string`&&e.length<64))].slice(0,32):null,tab:[`quota`,`sessions`,`usage`].includes(e.tab)?e.tab:`quota`,view:Qi.includes(e.view)?e.view:`bars`,selected:typeof e.selected==`string`?e.selected.slice(0,256):``,defaultDetail:+(e.defaultDetail===1),layouts:Array.isArray(e.layouts)?e.layouts.filter(e=>{if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.id==`string`&&t.id.length<=256&&[0,1,2].includes(t.level)}).slice(-100):[]}}catch{return ea}}var na=Zt(ta()),ra=Zt({open:!1});function ia(){let e=JSON.stringify(na);try{localStorage.setItem($i,e)}catch{}}function aa(){return na.tab===`quota`?`/quota/`+na.view:`/`+na.tab}function oa(e){return na.layouts.find(t=>t.id===e)?.level??na.defaultDetail}function sa(e){na.defaultDetail=e,na.layouts=[]}function ca(e,t){na.layouts=[...na.layouts.filter(t=>t.id!==e),{id:e,level:Math.max(0,Math.min(2,t))}].slice(-100)}var $=Zt({data:null,connected:!1,error:``}),la=e=>e==null?`Unavailable`:e.toLocaleString(`en-GB`),ua=e=>!e||String(e).startsWith(`0001-`)?`Not yet available`:new Date(typeof e==`number`?e*1e3:e).toLocaleString(`en-GB`),da=`codexometer.web.session`,fa=class extends Error{},pa;async function ma(e,t,n){if(!pa||!$.connected||!$.data?.control||$.data.sessionsError)throw Error(`Session controls unavailable. Check the live connection.`);return await pa(e,t,n)}function ha(){let e=new AbortController,t,n=``;pa=async(t,r,i)=>{let a=await fetch(`/api/control/`+t,{method:`POST`,headers:{Authorization:`Bearer ${n}`,"Content-Type":`application/json`},body:JSON.stringify(r),signal:AbortSignal.any([e.signal,AbortSignal.timeout(8e3),...i?[i]:[]]),cache:`no-store`});if(!a.ok)throw a.status===502?Error(`Outcome uncertain. Check Codex before taking another action; nothing was retried.`):new fa(`Action rejected, expired or changed. Refresh and check Codex; nothing was retried.`);return a.json()};try{n=sessionStorage.getItem(da)||``}catch{}let r=location.hash,i=r.startsWith(`#pair=`)?r.slice(6):``;(i||!r||r===`#`)&&(history.replaceState(null,``,`/#`+aa()),window.dispatchEvent(new HashChangeEvent(`hashchange`)));async function a(){if(i)try{let t=await fetch(`/api/pair`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({secret:i}),signal:e.signal});if(!t.ok)throw Error(`Pairing link expired or already used. Restart --web for a fresh link.`);n=(await t.json()).token;try{sessionStorage.setItem(da,n)}catch{}}catch(t){e.signal.aborted||($.error=t instanceof Error?t.message:`Pairing failed`);return}if(!n){$.error=`Open the private pairing link printed by codexometer --web in your terminal.`;return}await o()}async function o(){try{let t=await fetch(`/api/events`,{headers:{Authorization:`Bearer ${n}`},signal:e.signal,cache:`no-store`});if(t.status===401){try{sessionStorage.removeItem(da)}catch{}$.error=`Browser session expired. Restart --web and open its new pairing link.`;return}if(!t.ok||!t.body)throw Error(`Live connection unavailable`);let r=t.body.getReader(),i=new TextDecoder,a=``;for(;!e.signal.aborted;){let{done:e,value:t}=await r.read();if(e)break;a+=i.decode(t,{stream:!0});let n;for(;(n=a.indexOf(` - -`))>=0;){let e=a.slice(0,n);a=a.slice(n+2),e.startsWith(`data: `)&&($.data=JSON.parse(e.slice(6)),$.connected=!0,$.error=``)}}}catch{}finally{$.connected=!1}e.signal.aborted||($.error=`Connection lost — showing last observation. Reconnecting…`,t=setTimeout(o,2500))}return a(),()=>{pa=void 0,e.abort(),clearTimeout(t),$.connected=!1}}Ue();var ga=K(`

      Quota refresh failed. Policy state is based on the last successful - observation.

      `),_a=K(`

      `),va=K(`

      The longest Codex quota window selects one active model profile. ASK - creates a per-session review; AUTO applies the profile on the next - eligible check.

      `,1),ya=K(`

      No threshold-based model steps were configured at launch.

      `);function ba(e,t){Ge(t,!1),Li();var n=Fr(),r=R(n),i=e=>{var t=va(),n=R(t),r=z(n),i=B(n,2),a=e=>{q(e,ga())};Y(i,e=>{$.data.quotaError&&e(a)});var o=B(i,2),s=L(o),c=z(s),l=B(s,4);Z(l,5,()=>$.data.thresholds,X,(e,t)=>{var n=_a();let r;var i=L(n),a=z(i),o=B(i,2),s=L(o),c=z(s,!0),l=z(B(s,2));j(o);var u=B(o,2),d=z(u,!0),f=z(B(u,2));j(n),V((e,i)=>{r=ci(n,1,``,null,r,{active:W(t).state===`ACTIVE`,next:W(t).state===`NEXT`}),J(a,`${W(t).threshold??``}%`),J(c,W(t).model),J(l,`${e??``} REASONING // ${i??``} - SPEED`),J(d,W(t).mode),J(f,`${W(t).state??``}${W(t).state===`NEXT`?` // ${W(t).remaining||0} PP TO GO`:``}`)},[()=>W(t).effort.toUpperCase(),()=>W(t).speed.toUpperCase()]),q(e,n)}),j(l),j(o),V(e=>{J(r,`MODEL STEP POLICY // OBSERVED ${e??``}`),J(c,`THRESHOLDS // ${$.data.thresholds.length??``} CONFIGURED`)},[()=>ua($.data.quotaAt)]),q(e,t)},a=e=>{q(e,ya())};Y(r,e=>{$.data?.thresholds?.length?e(i):e(a,-1)}),q(e,n),Ke()}var xa=Nr(` `,1),Sa=Nr(` `,1),Ca=K(` `),wa=K(`
      Quota observations
      Observed atPeriod elapsedConsumedTrail segment
      `),Ta=K(`


      Observed quota - path, not individual session usage. Gaps are not interpolated. Expand the - observation table for times, positions and gaps.

      OBSERVATION TABLE
      `,1),Ea=K(`
      CONSUMPTIONQUOTA PERIOD ELAPSED


      `,1);function Da(e,t){let n=Ir();Ge(t,!0);let r=Ui(t,`trail`,19,()=>[]),i=F(!1),a=[0,25,50,75,100],o=F(400),s=F(240),c=N(()=>W(o)-24),l=N(()=>W(s)-48),u=N(()=>Math.max(1,W(c)-48)),d=N(()=>Math.max(1,W(l)-20)),f=N(()=>48+Math.max(0,Math.min(100,t.elapsed))/100*W(u)),p=N(()=>W(l)-Math.max(0,Math.min(100,t.used))/100*W(d)),m=N(()=>t.used-t.elapsed),h=N(()=>r().map((e,t)=>`${t===0||e.break?`M`:`L`}${48+e.elapsed/100*W(u)} ${W(l)-e.used/100*W(d)}`).join(` `));var g=Ea(),_=R(g),v=L(_),y=L(v),b=z(y),x=B(y),S=B(x);Z(S,17,()=>a,X,(e,t)=>{var n=xa(),r=R(n),i=B(r),a=B(i),o=z(a),s=B(a),f=z(s);V(()=>{Q(r,`x1`,48+W(t)/100*W(u)),Q(r,`x2`,48+W(t)/100*W(u)),Q(r,`y2`,W(l)),Q(i,`y1`,W(l)-W(t)/100*W(d)),Q(i,`x2`,W(c)),Q(i,`y2`,W(l)-W(t)/100*W(d)),Q(a,`x`,48+W(t)/100*W(u)),Q(a,`y`,W(l)+20),J(o,`${W(t)??``}%`),Q(s,`y`,W(l)+4-W(t)/100*W(d)),J(f,`${W(t)??``}%`)}),q(e,n)});var C=B(S),w=B(C),T=B(w),ee=e=>{var t=Sa(),n=R(t),i=B(n),a=z(L(i));j(i),V(e=>{Q(n,`d`,W(h)),Q(i,`cx`,48+r()[0].elapsed/100*W(u)),Q(i,`cy`,W(l)-r()[0].used/100*W(d)),J(a,`First observation: ${e??``}`)},[()=>ua(r()[0].at)]),q(e,t)};Y(T,e=>{r().length&&e(ee)});var E=B(T,2),D=B(E),O=B(D),te=z(L(O));j(O),j(v),j(_);var ne=B(_,2),re=L(ne),ie=z(B(re,3),!0);j(ne);var ae=B(ne,2),oe=e=>{var t=Ta(),a=R(t),o=L(a);we(2),j(a);var s=B(a,2),c=B(L(s),2),l=e=>{var t=wa(),n=L(t),i=B(L(n),2);Z(i,21,r,X,(e,t,n)=>{var r=Ca(),i=L(r),a=L(i),o=z(a,!0);j(i);var s=B(i),c=z(s),l=B(s),u=z(l),d=z(B(l),!0);j(r),V((e,r)=>{Q(a,`datetime`,W(t).at),J(o,e),J(c,`${r??``}%`),J(u,`${W(t).used??``}%`),J(d,n===0?`First observation`:W(t).break?`Gap before this observation`:`Connected to previous observation`)},[()=>ua(W(t).at),()=>W(t).elapsed.toFixed(1)]),q(e,r)}),j(i),j(n),j(t),q(e,t)};Y(c,e=>{W(i)&&e(l)}),j(s),V(e=>{Q(a,`id`,n+`-trail-summary`),J(o,`○ START ${e??``} // ${r().length??``} - ${r().length===1?`OBSERVATION`:`OBSERVATIONS`}`)},[()=>ua(r()[0].at)]),Ii(`open`,`toggle`,s,e=>I(i,e),()=>W(i)),q(e,t)};Y(ae,e=>{r().length&&e(oe)}),V((e,i,a,m)=>{Q(v,`viewBox`,`0 0 ${W(o)} ${W(s)}`),Q(v,`aria-describedby`,r().length?n+`-trail-summary`:void 0),Q(v,`aria-label`,e),Q(b,`id`,n),Q(x,`width`,W(u)),Q(x,`height`,W(d)),Q(x,`fill`,`url(#${n})`),Q(C,`d`,`M48 20 V${W(l)} H${W(c)}`),Q(w,`y1`,W(l)),Q(w,`x2`,W(c)),Q(E,`x`,48+W(u)/2),Q(E,`y`,W(s)-5),Q(D,`cx`,W(f)),Q(D,`cy`,W(p)),Q(O,`cx`,W(f)),Q(O,`cy`,W(p)),J(te,`${t.used??``}% consumed // ${i??``}% of period elapsed`),J(re,`${t.used??``}% USED // ${a??``}% TIME ELAPSED`),J(ie,m)},[()=>`Consumption zone: ${t.used}% consumed, ${t.elapsed.toFixed(1)}% of quota period elapsed. ${W(m)>0?`Above`:W(m)<0?`Below`:`On`} the steady-consumption line.`,()=>t.elapsed.toFixed(1),()=>t.elapsed.toFixed(1),()=>Math.abs(W(m))<.05?`ON PACE`:W(m)>0?`ABOVE THE LINE — CONSUMING FASTER THAN TIME`:`BELOW THE LINE — WITHIN PACE`]),Ni(_,`clientWidth`,e=>I(o,e)),Ni(_,`clientHeight`,e=>I(s,e)),q(e,g),Ke()}var Oa=K(` `),ka=K(`

      Quota refresh failed. Values below are the last successful observation.

      `),Aa=K(`

      Expiry details unavailable. No listed expiry does not mean no expiry.

      `),ja=K(`

      `),Ma=K(`

      Read-only preview. Use the terminal to redeem a reset.

      The backend may return only some credits. This list does not establish - redemption order.

      `),Na=K(` `,1),Pa=Nr(``),Fa=Nr(``),Ia=K(`
      `),La=K(`

      Cycle duration or reset date unavailable — position cannot be - plotted.

      `),Ra=K(`
      ▼
      −100 // OVER BUDGET+100 // HEADROOM

      `,1),za=K(`

      Cycle duration unavailable — pace cannot be calculated.

      `),Ba=K(`
      EMPTYFULL
      `),Va=K(`

      `,1),Ha=K(`
      `,1),Ua=K(`

      `),Wa=K(`

      `),Ga=K(`

      No quota windows reported yet.

      `),Ka=K(`
      `,1),qa=K(`

      All reported windows are shown. API-equivalent learning and quota status - scoring remain in the terminal for this first preview.

      `,1),Ja=K(` `,1);function Ya(e,t){Ge(t,!0);let n=Ui(t,`params`,19,()=>({})),r=N(()=>Qi.filter(e=>e!==`thresholds`||!!$.data?.thresholds?.length)),i=F(Zt(Date.now())),a=N(()=>W(r).includes(n().view||``)?n().view:`bars`);Wi(()=>{let e=setInterval(()=>I(i,Date.now(),!0),1e3);return()=>clearInterval(e)});function o(e){return!e.duration||e.duration<=0||!e.reset?null:Math.max(0,Math.min(100,100*(1-(e.reset*1e3-W(i))/(e.duration*6e4))))}bn(()=>{na.view=W(a)});function s(e){let t=e/100*Math.PI*2;return`M60 60 L60 14 A46 46 0 ${+(e>50)} 1 ${60+46*Math.sin(t)} ${60-46*Math.cos(t)} Z`}var c=Ja(),l=R(c);Z(l,21,()=>W(r),X,(e,t)=>{var n=Oa();let r;var i=z(n,!0);V(e=>{Q(n,`href`,`#/quota/`+W(t)),Q(n,`aria-current`,W(a)===W(t)?`page`:void 0),r=ci(n,1,``,null,r,{active:W(a)===W(t)}),J(i,e)},[()=>W(t)===`pace`?`CONSUMPTION PACE`:W(t)===`zone`?`CONSUMPTION ZONE`:W(t)===`fuel`?`FUEL TANK`:W(t).toUpperCase()]),q(e,n)}),j(l);var u=B(l,2),d=e=>{var t=qa(),n=R(t),r=e=>{q(e,ka())};Y(n,e=>{$.data.quotaError&&e(r)});var i=B(n,2),c=z(i),l=B(i,2),u=e=>{ba(e,{})},d=e=>{var t=Ma(),n=L(t),r=z(n),i=B(n,4),a=e=>{q(e,Aa())};Y(i,e=>{$.data.credits.length||e(a)}),Z(B(i,2),17,()=>$.data.credits,X,(e,t)=>{var n=ja(),r=L(n),i=z(r),a=z(B(r,2),!0);j(n),V(e=>{J(i,`${(W(t).title||`Quota reset`)??``} // ${W(t).status??``}`),J(a,e)},[()=>W(t).expiryKnown?W(t).expires?`EXPIRES `+ua(W(t).expires):`Does not expire`:`Expiry information unavailable`]),q(e,n)}),we(2),j(t),V(()=>J(r,`RESET INVENTORY // ${$.data.creditCount??``} AVAILABLE`)),q(e,t)},f=e=>{var t=Ka(),n=R(t);let r;Z(n,21,()=>$.data.meters,X,(e,t)=>{let n=N(()=>o(W(t))),r=N(()=>W(n)===null?null:W(n)-W(t).used);var i=Wa(),c=L(i),l=z(c,!0),u=B(c,2),d=L(u),f=e=>{var n=Na(),r=R(n),i=z(r),a=z(B(r));V(()=>{J(i,`FREE ${100-W(t).used}%`),J(a,`USED ${W(t).used??``}%`)}),q(e,n)},p=e=>{var n=Na(),r=R(n),i=z(r),a=z(B(r));V(()=>{J(i,`USED ${W(t).used??``}%`),J(a,`FREE ${100-W(t).used}%`)}),q(e,n)};Y(d,e=>{W(a)===`fuel`?e(f):e(p,-1)}),j(u);var m=B(u,2);let h;var g=L(m),_=e=>{var n=Ia(),r=L(n),i=B(L(r)),a=e=>{q(e,Pa())},o=e=>{var n=Fa();V(e=>Q(n,`d`,e),[()=>s(W(t).used)]),q(e,n)};Y(i,e=>{W(t).used>=100?e(a):W(t).used>0&&e(o,1)}),j(r),j(n),V(()=>Q(r,`aria-label`,`${W(t).used}% quota used`)),q(e,n)},v=e=>{var r=Fr(),i=R(r),a=e=>{{let r=N(()=>W(t).trail||[]);Da(e,{get used(){return W(t).used},get elapsed(){return W(n)},get trail(){return W(r)}})}},o=e=>{q(e,La())};Y(i,e=>{W(n)===null?e(o,-1):e(a)}),q(e,r)},y=e=>{var t=Fr(),n=R(t),i=e=>{var t=Ra(),n=R(t),i=B(L(n),2);let a;j(n);var o=B(n,4),s=L(o),c=z(B(s),!0);j(o),V(e=>{a=ui(i,``,a,{left:`${(W(r)+100)/2}%`}),J(s,`${W(r)>=0?`+`:``}${e??``} PP `),J(c,W(r)>=0?`WITHIN PACE`:`USING FASTER THAN TIME`)},[()=>W(r).toFixed(1)]),q(e,t)},a=e=>{q(e,za())};Y(n,e=>{W(r)===null?e(a,-1):e(i)}),q(e,t)},b=e=>{var r=Ha(),i=R(r),o=L(i);let s;j(i);var c=B(i,2),l=e=>{q(e,Ba())};Y(c,e=>{W(a)===`fuel`&&e(l)});var u=B(c,2),d=e=>{var t=Va(),r=R(t),i=z(r),o=B(r,2),s=L(o);let c;j(o),V(e=>{J(i,`RESET CYCLE // ${e??``}% ELAPSED`),c=ui(s,``,c,{width:`${W(a)===`fuel`?100-W(n):W(n)}%`})},[()=>Math.floor(W(n))]),q(e,t)};Y(u,e=>{W(n)!==null&&e(d)}),V(()=>{Q(i,`aria-label`,W(a)===`fuel`?`Fuel remaining`:`Quota used`),Q(i,`aria-valuenow`,W(a)===`fuel`?100-W(t).used:W(t).used),s=ui(o,``,s,{width:`${W(a)===`fuel`?100-W(t).used:W(t).used}%`})}),q(e,r)};Y(g,e=>{W(a)===`pie`?e(_):W(a)===`zone`?e(v,1):W(a)===`pace`?e(y,2):e(b,-1)}),j(m);var x=B(m,2),S=z(x),C=B(x,2),w=e=>{var n=Ua(),r=z(n,!0);V(()=>J(r,W(t).details)),q(e,n)};Y(C,e=>{W(t).details&&e(w)}),j(i),V(e=>{J(l,W(t).name),h=ci(m,1,`meter-graphic`,null,h,{"bar-graphic":W(a)===`bars`||W(a)===`fuel`}),J(S,`RESETS // ${e??``}`)},[()=>ua(W(t).reset)]),q(e,i)}),j(n);var i=B(n,2),c=e=>{q(e,Ga())};Y(i,e=>{$.data.meters.length||e(c)}),V(()=>r=ci(n,1,`quota-grid`,null,r,{radial:W(a)===`pie`,zone:W(a)===`zone`})),q(e,t)};Y(l,e=>{W(a)===`thresholds`?e(u):W(a)===`resets`?e(d,1):e(f,-1)}),we(2),V(e=>J(c,`QUOTA // OBSERVED ${e??``}`),[()=>ua($.data.quotaAt)]),q(e,t)};Y(u,e=>{$.data&&e(d)}),q(e,c),Ke()}var Xa=K(`
      `),Za=K(`

      `,1);function Qa(e,t){Ge(t,!0);let n=Ui(t,`values`,19,()=>[]),r=Ui(t,`label`,3,`Token activity`),i=Ui(t,`capacity`,3,0),a=N(()=>Math.max(0,...n())),o=N(()=>i()>n().length?[...Array(i()-n().length).fill(0),...n()]:n());var s=Za(),c=R(s),l=z(c),u=B(c,2);Z(u,21,()=>W(o),X,(e,t)=>{var n=Xa();let r;V((e,t)=>{Q(n,`title`,e),r=ui(n,``,r,{height:t})},[()=>W(t).toLocaleString(`en-GB`)+` tokens`,()=>`${100*W(t)/Math.max(1,W(a))}%`]),q(e,n)}),j(u),V((e,t)=>{J(l,`SCALE // 0 — ${e??``} TOKENS`),Q(u,`aria-label`,t)},[()=>W(a).toLocaleString(`en-GB`),()=>`${r()}. Peak ${W(a).toLocaleString(`en-GB`)} tokens.`]),q(e,s),Ke()}var $a=K(`

      `),eo=K(`

      `,1),to=K(`

      `),no=K(`

      Loading / sending…

      `),ro=K(`

      Confirmation expired. Reopen the option.

      `),io=K(`

      `,1),ao=K(`

       
      `,1),oo=K(` `),so=K(``),co=K(`

      No available matching commands.

      `),lo=K(`

      ↑/↓ select · Tab complete · Enter options · Escape dismiss

      `),uo=K(`
      `,1),fo=K(` `,1),po=K(`
      `),mo=K(`
      `);function ho(e,t){Ge(t,!0);let n=Ui(t,`query`,3,``),r=Ui(t,`onchange`,3,()=>{}),i=Ui(t,`oncomplete`,3,e=>{}),a=F(!1),o=F(!1),s=F(``),c=F(null),l=F(null),u=F(``),d=F(0),f=F(Zt(Date.now())),p=0,m=F(0),h=F(null),g=F(void 0),_=setInterval(()=>{I(f,Date.now(),!0)},1e3);Gi(()=>{clearInterval(_),p++});let v=N(()=>n().trim().startsWith(`/`)&&!n().trim().startsWith(`//`)),y=N(()=>W(c)?.path===``&&W(v)?n().trim().slice(1).toLowerCase():``),b=N(()=>(W(c)?.choices||[]).filter(e=>!W(y)||e.label.toLowerCase().startsWith(`/`+W(y)))),x=N(()=>!$.connected||!!$.data?.sessionsError),S=N(()=>W(a)&&W(v)&&(!W(c)||W(c).path===``)&&!W(l));bn(()=>{n(),I(m,0)}),bn(()=>{let e=W(m);W(b),W(S)&&dr().then(()=>{let t=W(g)?.children[e];if(!t||!W(g))return;let n=W(g).getBoundingClientRect(),r=t.getBoundingClientRect();r.topn.bottom&&(W(g).scrollTop+=r.bottom-n.bottom)})});function C(e){if(!W(v)||!W(a)||W(c)&&W(c).path!==``||W(l)||W(x)||e.isComposing||e.ctrlKey||e.metaKey||e.altKey||![`ArrowUp`,`ArrowDown`,`Tab`,`Enter`,`Escape`].includes(e.key)||e.key===`Tab`&&e.shiftKey)return;if(e.preventDefault(),e.key===`Escape`){I(a,!1),I(h,n()),I(u,``);return}if(e.key===`ArrowUp`||e.key===`ArrowDown`){I(m,Math.min(Math.max(W(m)+(e.key===`ArrowUp`?-1:1),0),Math.max(W(b).length-1,0)),!0);return}let t=W(b)[W(m)];t&&!W(o)&&(e.key===`Tab`?i()(t.label):T(t))}bn(()=>{W(x)&&(p++,I(o,!1),I(u,``),W(a)&&(I(a,!1),I(h,n())),I(c,null),I(l,null))}),bn(()=>{W(v)&&!W(a)&&n()!==W(h)&&!W(x)&&(I(a,!0),w(``))});async function w(e){let n=++p;I(o,!0),I(l,null),I(u,``),I(s,``);try{let r=await ma(`commands`,{session:t.session,command:{mode:`list`,path:e}});n===p&&((e===``||e===`help`)&&r.choices.push({id:`local-statusline`,label:`/statusline`,help:`Choose multiple fields for Codexometer’s detail footer.`,next:`statusline`}),I(c,r,!0))}catch{n===p&&(I(s,`Command catalogue unavailable. Use Codex.`),(e===``||e===`help`)&&I(c,{title:`/ COMMANDS`,help:`Local display settings remain available.`,path:``,revision:``,choices:[{id:`local-statusline`,label:`/statusline`,help:`Choose multiple fields for Codexometer’s detail footer.`,next:`statusline`}]},!0))}finally{n===p&&I(o,!1)}}async function T(e){if(e.next===`statusline`){ra.open=!0,I(a,!1),I(h,n());return}if(e.next){await w(e.next);return}if(I(l,e,!0),I(u,``),I(s,``),!e.action||!W(c))return;let r=++p;I(o,!0);try{let n=await ma(`commands`,{session:t.session,command:{mode:`prepare`,path:W(c).path,revision:W(c).revision,choice:e.id}});r===p&&(I(u,n.confirmation,!0),I(d,Date.parse(n.expires),!0))}catch{r===p&&I(s,`Option changed or unavailable. Refresh before retrying.`)}finally{r===p&&I(o,!1)}}async function ee(){if(!W(u)||W(x)||W(o)||W(f)>=W(d)||!W(c))return;let e=W(u);I(u,``),I(o,!0);let n=++p;try{let i=await ma(`commands`,{session:t.session,confirmation:e,command:{mode:`commit`,path:W(c).path}});n===p&&(I(s,i.message,!0),I(a,!1),I(c,null),I(l,null),I(d,0),I(h,null),r()())}catch{n===p&&I(s,`Change unconfirmed. Check Codex before retrying.`)}finally{n===p&&I(o,!1)}}var E={handleKey:C},D=mo(),O=L(D),te=B(O,2),ne=e=>{var t=$a(),n=z(t,!0);V(()=>J(n,W(s))),q(e,t)};Y(te,e=>{W(s)&&!W(a)&&e(ne)});var re=B(te,2),ie=e=>{var n=po();let r;var i=L(n),a=e=>{var t=eo(),n=R(t),r=z(n,!0),i=z(B(n,2),!0);V(()=>{J(r,W(c)?.title||`/ COMMANDS`),J(i,W(c)?.help||`Loading live options…`)}),q(e,t)};Y(i,e=>{W(S)||e(a)});var p=B(i,2),h=e=>{var t=to(),n=z(t,!0);V(()=>J(n,W(s))),q(e,t)};Y(p,e=>{W(s)&&e(h)});var _=B(p,2),y=e=>{q(e,no())};Y(_,e=>{W(o)&&e(y)});var C=B(_,2),E=e=>{var n=ao(),r=R(n),i=z(r,!0),a=B(r,2),s=z(a,!0),c=B(a,2),p=e=>{var n=io(),r=R(n),i=z(r),a=B(r,2),s=B(a,2),c=e=>{q(e,ro())};Y(s,e=>{W(u)&&W(f)>=W(d)&&e(c)}),V(()=>{J(i,`TARGET // ${t.session??``}. Changes affect subsequent turns of this - session, not global defaults. Automatic quota thresholds may later - supersede model settings.`),a.disabled=W(o)||W(x)||!W(u)||W(f)>=W(d)}),G(`click`,a,ee),q(e,n)};Y(c,e=>{W(l).action&&e(p)});var m=B(c,2);V(()=>{J(i,W(l).label),J(s,W(l).help||`No additional help supplied by Codex.`),m.disabled=W(o)}),G(`click`,m,()=>{I(l,null),I(u,``)}),q(e,n)},D=e=>{var t=uo(),n=R(t);let r;Z(n,23,()=>W(b),e=>e.id,(e,t,n)=>{var r=so();let i;var a=L(r),s=z(a,!0),l=B(a),u=z(l,!0),d=B(l),f=e=>{var n=oo(),r=z(n,!0);V(()=>J(r,W(t).help)),q(e,n)};Y(d,e=>{W(v)&&W(c)?.path===``&&e(f)}),j(r),V(()=>{r.disabled=W(o)||W(x),Q(r,`title`,W(t).help),Q(r,`aria-label`,W(t).label+(W(t).next?` →`:W(t).action?``:` // HELP`)),i=ci(r,1,`svelte-7bfwr9`,null,i,{suggested:W(v)&&W(c)?.path===``&&W(m)===W(n)}),J(s,W(t).label),J(u,W(t).next?` →`:W(t).action?``:` // HELP`)}),G(`click`,r,()=>T(W(t))),q(e,r)}),j(n),Fi(n,e=>I(g,e),()=>W(g));var i=B(n,2),a=e=>{q(e,co())};Y(i,e=>{!W(o)&&!W(b).length&&e(a)});var s=B(i,2),l=e=>{q(e,lo())};Y(s,e=>{W(v)&&W(c)?.path===``&&e(l)}),V(()=>r=ci(n,1,`command-options svelte-7bfwr9`,null,r,{vertical:W(S)})),q(e,t)};Y(C,e=>{W(l)?e(E):e(D,-1)});var O=B(C,2),te=e=>{var t=fo(),n=R(t),r=B(n,2);V(()=>{n.disabled=W(o),r.disabled=W(o)}),G(`click`,n,()=>w(``)),G(`click`,r,()=>w(W(c)?.path||``)),q(e,t)};Y(O,e=>{W(S)||e(te)}),j(n),V(()=>{Q(n,`aria-label`,W(S)?`Slash command suggestions`:void 0),r=ci(n,1,`svelte-7bfwr9`,null,r,{"command-popup":W(S)})}),q(e,n)};return Y(re,e=>{W(a)&&e(ie)}),j(D),V(()=>O.disabled=W(x)||W(o)),G(`click`,O,()=>{I(a,!W(a)),I(h,W(a)?null:n(),!0),I(u,``),W(a)&&w(``)}),q(e,D),Ke(E)}wr([`click`]);var go=K(` `,1),_o=K(`
      `),vo=K(`
      /
      `);function yo(e,t){Ge(t,!0);let n=N(()=>t.lines.filter(e=>e.kind===`addition`).length),r=N(()=>t.lines.filter(e=>e.kind===`removal`).length);var i=vo(),a=L(i),o=L(a),s=z(o),c=z(B(o,2));j(a),Z(B(a,2),17,()=>t.lines,X,(e,t)=>{var n=_o();let r;var i=L(n),a=e=>{var n=go(),r=R(n),i=z(r,!0),a=z(B(r),!0);V(()=>{J(i,W(t).old||``),J(a,W(t).new||``)}),q(e,n)};Y(i,e=>{W(t).kind!==`heading`&&W(t).kind!==`metadata`&&e(a)});var o=z(B(i),!0);j(n),V(()=>{r=ci(n,1,`diff-line svelte-x4gtb6`,null,r,{added:W(t).kind===`addition`,removed:W(t).kind===`removal`,heading:W(t).kind===`heading`}),J(o,W(t).text)}),q(e,n)}),j(i),V(()=>{Q(a,`aria-label`,`${W(n)} lines added, ${W(r)} lines removed`),J(s,`+${W(n)??``}`),J(c,`−${W(r)??``}`)}),q(e,i),Ke()}var bo=K(`

      `),xo=K(`

      Send this saved prompt now? This starts Codex work and consumes quota.

      `,1),So=K(``),Co=K(`

       

      In memory only. Follows session model, reasoning and speed. Closing - Codexometer cancels pending work.

      `),wo=K(`

      CURRENT PROFILE

      MODEL / REASONING LEVEL / SPEED

       

      PROPOSED PROFILE

      MODEL / REASONING LEVEL / SPEED

       

      Applied settings remain after Codexometer closes.

      `,1),To=K(`

      `),Eo=K(`

       
      `,1),Do=K(`

      Command unavailable from this observation. Open Codex to inspect the - request.

      `),Oo=K(`

      Session controls temporarily unavailable. Check Codex for current state.

      `),ko=K(`

      Checking session controls…

      `),Ao=K(`

      `),jo=K(`
      About browser controls

      Controls require a supported live request from a connected shared - app-server session. Local observations alone cannot provide them.

      `),Mo=K(` `,1),No=K(` `),Po=K(`Grants permission beyond this one action. Check the scope - carefully.`),Fo=K(``),Io=K(``),Lo=K(``),Ro=K(``),zo=K(`

      Not before this time; when quota and idle allow.

      `),Bo=K(`

      `,1),Vo=K(`

      The existing trigger remains active until saved or deleted. If - it changes or sends while you edit, saving is blocked.

      `),Ho=K(` `,1),Uo=K(``),Wo=K(``),Go=K(``),Ko=K(` `,1),qo=K(`
    • `),Jo=K(`
      View fixed choices

      Type one of these choices exactly. Your answer stays masked.

        `),Yo=K(` `,1),Xo=K(`

        `),Zo=K(`

        `,1),Qo=K(` `,1),$o=K(``),es=K(`

        `,1),ts=K(`

        `);function ns(e,t){Ge(t,!0);let n=[],r=Ui(t,`observedCommand`,3,``),i=Ui(t,`observedFileChanges`,19,()=>[]),a=Ui(t,`review`,3,``),o=Ui(t,`suspended`,3,!1),s=Ui(t,`openScheduler`,3,!1),c=Ui(t,`onProtectedChange`,3,e=>{}),l=F(null),u=F(Zt([])),d=F(null),f=F(``),p=F(0),m=F(Zt(Date.now())),h=F(!1),g=F(``),_=F(!1),v=F(!1),y=F(``),b=F(!1),x=F(``),S=F(!1),C=F(``),w=F(`now`),T=F(60),ee=F(``),E=F(!1),D=F(``),O=F(Zt([])),te=N(()=>W(O).find(e=>e.status!==`sent`)),ne=F(void 0),re=F(void 0);function ie(e){if(W(b)){dr().then(()=>W(ne)?.focus());return}if(e.status!==`pending`||W(h)||!W(l)?.id||W(l).kind!==`prompt`||W(l).questions?.length)return;I(b,!0),I(x,e.id,!0),dr().then(()=>W(ne)?.focus()),I(u,[e.text],!0),I(w,e.trigger===`quota`?`quota`:`at`,!0),I(f,``),I(C,``);let t=new Date(e.at);e.trigger!==`quota`&&I(ee,new Date(t.getTime()-t.getTimezoneOffset()*6e4).toISOString().slice(0,16),!0)}bn(()=>{s()&&W(te)&&!W(S)&&W(l)?.id&&W(l).kind===`prompt`&&!W(l).questions?.length&&(I(S,!0),ie(W(te)))});async function ae(){if(!a())try{I(O,await ma(`schedules`,{session:t.session},be.signal),!0),W(b)&&!W(O).some(e=>e.id===W(x)&&e.status===`pending`)&&I(f,``),W(C)&&!W(O).some(e=>e.id===W(C)&&e.status===`pending`)&&(I(f,``),I(C,``))}catch{}}async function oe(e){try{I(O,await ma(`schedules`,{session:t.session,cancelId:e},be.signal),!0),I(b,!1),I(f,``),I(C,``),I(u,[],!0)}catch(e){I(g,e instanceof Error?e.message:`Unable to cancel trigger.`,!0)}}bn(()=>(c()(W(h)||W(u).some(e=>e.length>0)),()=>c()(!1)));let se=N(()=>$.data?.sessions.find(e=>e.id===t.session)?.status),ce=N(()=>!$.connected||!!$.data?.sessionsError),le=N(()=>a()!==`profile`&&W(l)?.kind===`prompt`&&!W(l).questions?.length&&!!$.data?.profiles?.some(e=>e.session===t.session&&e.pending)),ue=N(()=>!W(ce)&&!W(v)&&!!W(l)?.id&&W(l).kind===`approval`&&W(y)!==W(l).id),de=N(()=>W(ue)?W(l).command:r()),fe=N(()=>!!W(f)&&W(m)W(l)?.questions?.length?W(l).questions:[{text:`Follow-up message`,secret:!1,freeText:!0,options:[]}]),me=N(()=>W(l)?.kind===`approval`||W(l)?.kind===`profile`?W(d)!==null:W(u).length===W(pe).length&&W(u).every((e,t)=>e.trim().length>0&&(W(pe)[t].freeText||W(pe)[t].options?.includes(e)))),he=N(()=>W(l)?.kind===`prompt`&&!W(l).questions?.length&&W(w)!==`now`),ge=N(()=>W(w)===`delay`?W(m)+W(T)*6e4:Date.parse(W(ee))),_e=N(()=>{if(!W(he))return``;if(W(b)&&!W(O).some(e=>e.id===W(x)&&e.status===`pending`))return`Trigger changed, removed or already sent. Go back and reopen it; your draft has not been saved.`;if(!W(u)[0]?.trim())return`Enter a message to schedule.`;if(W(w)===`quota`)return``;if(!Number.isFinite(W(ge))||W(ge)<=W(m))return`Choose a future date/time or a delay greater than zero.`;let e=new Date(W(m));return e.setFullYear(e.getFullYear()+1),W(ge)>=e.getTime()?`Choose a time within one year.`:``});function ve(e){return new Date(e).toLocaleString([],{timeZoneName:`short`})}function ye(e){if(e.status!==`pending`)return e.status;if(e.trigger===`at`&&Date.parse(e.at)>W(m))return`Due in `+Math.ceil((Date.parse(e.at)-W(m))/6e4)+` min`;let t=W(ce)?`waiting for a fresh connection`:e.waitingReason||(e.canSend?`ready; awaiting dispatch`:`waiting for quota and an eligible idle session`);return(e.trigger===`at`?`Time reached — `:``)+t}bn(()=>{(W(ce)||o()||W(le)||W(m)>=W(p))&&I(f,``)});let be=new AbortController;Wi(()=>{let e=e=>{e.ctrlKey&&e.key.toLowerCase()===`s`&&!a()&&W(te)&&(e.preventDefault(),ie(W(te)))};window.addEventListener(`keydown`,e);let n,r=setInterval(()=>{I(m,Date.now(),!0)},1e3);async function i(){await ae();try{let e=await ma(`offer`,{session:t.session,...a()?{review:a()}:{}},be.signal);if(be.signal.aborted)return;I(v,!1),W(l)?.id!==e.id&&(I(f,``),I(d,null),I(u,Array(Math.max(1,e.questions?.length||0)).fill(``),!0)),I(l,e,!0),W(_)&&e.id&&e.id!==W(y)&&(I(g,``),I(_,!1))}catch{be.signal.aborted||(I(l,null),I(v,!0),I(f,``))}finally{be.signal.aborted||(n=setTimeout(i,2e3))}}return i(),()=>{window.removeEventListener(`keydown`,e),be.abort(),clearTimeout(n),clearInterval(r)}});async function k(e){if(!e&&W(l)?.kind===`prompt`&&!W(l).questions?.length&&W(u)[0]?.trim().startsWith(`/`)&&!W(u)[0]?.trim().startsWith(`//`)){I(g,`Choose a slash command from COMMANDS; use // for literal slash text.`);return}if(!W(l)?.id||W(h)||W(ce)||o()||W(le)||(e?!e.canSend:!W(me)||W(_e)))return;let n=W(l).id;I(h,!0),I(g,``),I(_,!1),I(f,``),I(C,e?.id||``,!0);try{let r=!e&&W(l).kind===`prompt`&&!W(l).questions?.length&&W(w)!==`now`?{trigger:W(w)===`quota`?`quota`:`at`,zone:Intl.DateTimeFormat().resolvedOptions().timeZone,...W(w)===`quota`?{}:{at:(W(w)===`delay`?new Date(Date.now()+W(T)*6e4):new Date(W(ee))).toISOString()}}:void 0;I(E,!!r),I(D,r?r.trigger===`quota`?`After fresh quota becomes available, when the session is idle`:ve(r.at)+` — not before this time; when quota and idle allow`:``,!0);let i=await ma(`prepare`,{session:t.session,...a()?{review:a()}:{},offer:n,...e?{sendId:e.id}:{},...r?{schedule:r}:{},...r&&W(b)?{editId:W(x)}:{},...W(l).kind===`approval`||W(l).kind===`profile`?{choice:W(d)}:{answers:e?[e.text]:[...W(u)]}},be.signal);W(l)?.id===n&&!be.signal.aborted&&!W(ce)&&!o()&&!W(le)&&(I(f,i.confirmation,!0),I(p,Date.parse(i.expires),!0))}catch(e){be.signal.aborted||I(g,e instanceof Error?e.message:`Unable to prepare action.`,!0)}finally{I(h,!1)}}async function xe(){if(!W(l)?.id||W(h)||o()||W(le)||!W(fe))return;let e=W(l).id,n=W(l).kind,r=W(f);I(f,``),I(h,!0),I(y,e,!0);try{await ma(`commit`,{session:t.session,...a()?{review:a()}:{},offer:e,confirmation:r},be.signal),I(_,!0),I(b,!1),W(E)&&(I(y,``),await ae()),I(g,W(E)?`Trigger set. Closing Codexometer cancels it.`:n===`profile`?`Profile decision completed.`:n===`approval`?`Decision sent.`:`Text sent.`,!0)}catch(e){I(g,e instanceof fa?e.message:`Outcome uncertain. Check Codex before taking another action; nothing was retried.`,!0)}finally{I(h,!1),I(C,``),await ae(),I(u,[],!0),I(d,null)}}var A=Mo(),Se=R(A),Ce=e=>{var t=Fr();Z(R(t),17,()=>W(O).filter(e=>e.status!==`sent`),e=>e.id,(e,t)=>{var n=Co(),r=L(n),i=z(r,!0),a=B(r,2),s=z(a),c=B(a,2),u=z(c,!0),d=B(c,2),p=z(d,!0),m=B(d,2),_=z(m,!0),v=B(m,2),y=e=>{var t=bo(),n=z(t,!0);V(()=>J(n,W(g))),q(e,t)};Y(v,e=>{W(g)&&e(y)});var b=B(v,4),x=B(b,2),S=B(x,2),w=e=>{var n=xo(),r=B(R(n),2),i=B(r,2);V(()=>r.disabled=W(h)||W(ce)||!W(t).canSend),G(`click`,r,xe),G(`click`,i,()=>{I(f,``),I(C,``)}),q(e,n)},T=e=>{var n=So();V(()=>n.disabled=W(h)||W(ce)||!W(t).canSend||o()),G(`click`,n,()=>k(W(t))),q(e,n)};Y(S,e=>{W(fe)&&W(C)===W(t).id?e(w):e(T,-1)}),j(n),V((e,n)=>{J(i,W(t).status===`uncertain`?`CHECK TRIGGER — outcome uncertain; not retried`:`SCHEDULED FOLLOW-UP`),J(s,`TARGET // ${W(t).session??``}`),J(u,e),J(p,n),J(_,W(t).text),b.disabled=W(h)||!W(l)?.id||W(l).kind!==`prompt`||!!W(l).questions?.length||W(t).status!==`pending`,x.disabled=W(h)||W(t).status===`sending`},[()=>W(t).trigger===`quota`?`After confirmed quota recovery, when idle`:`Not before ${ve(W(t).at)}`,()=>ye(W(t))]),G(`click`,b,()=>ie(W(t))),G(`click`,x,()=>oe(W(t).id)),q(e,n)}),q(e,t)},Te=N(()=>!a()&&W(l)?.kind!==`approval`&&!W(l)?.questions?.length&&![`APPROVAL NEEDED`,`INPUT NEEDED`].includes(W(se)||``));Y(Se,e=>{W(Te)&&e(Ce)});var Ee=B(Se,2),De=e=>{var r=ts(),o=L(r),s=z(o,!0),c=B(o,2),x=e=>{var t=wo(),n=R(t),r=z(n),i=B(n,6),a=z(i,!0),o=z(B(i,6),!0);we(2),V(()=>{J(r,`Your ${W(l).profile.threshold??``}% quota threshold has been reached. Review - the profile for subsequent turns.`),J(a,W(l).profile.current),J(o,W(l).profile.proposed)}),q(e,t)};Y(c,e=>{W(l)?.profile&&!W(ce)&&!W(v)&&e(x)});var S=B(c,2),C=e=>{var t=To();let n;var r=z(t,!0);V(()=>{n=ci(t,1,`svelte-1oupzfc`,null,n,{notice:!W(_),sent:W(_)}),J(r,W(g))}),q(e,t)};Y(S,e=>{W(g)&&e(C)});var O=B(S,2),te=e=>{yo(e,{get lines(){return W(l).fileChanges}})},ie=e=>{yo(e,{get lines(){return i()}})},ae=e=>{var t=Eo(),n=R(t),r=z(n,!0),i=z(B(n,2),!0);V(()=>{J(r,W(ue)?`EXACT COMMAND`:`LAST OBSERVED COMMAND`),J(i,W(de))}),q(e,t)},oe=e=>{q(e,Do())};Y(O,e=>{W(l)?.fileChanges?.length&&!W(ce)&&!W(v)?e(te):i().length?e(ie,1):W(de)?e(ae,2):a()!==`profile`&&W(se)===`APPROVAL NEEDED`&&!W(_)&&e(oe,3)});var le=B(O,2),he=e=>{q(e,Oo())},ye=e=>{q(e,ko())},be=e=>{var t=Mo(),n=R(t),r=e=>{var t=Ao(),n=z(t,!0);V(e=>J(n,e),[()=>W(se)===`WORKING`?`Codex is working — nothing to respond to.`:[`APPROVAL NEEDED`,`INPUT NEEDED`].includes(W(se)||``)?`Respond in Codex for this request.`:`Nothing needs a response right now.`]),q(e,t)};Y(n,e=>{(W(se)===`WORKING`||!W(_)&&!W(h))&&e(r)});var i=B(n,2),a=e=>{q(e,jo())},o=N(()=>[`APPROVAL NEEDED`,`INPUT NEEDED`].includes(W(se)||``)&&!W(_));Y(i,e=>{W(o)&&e(a)}),q(e,t)},A=e=>{var r=es(),i=R(r),o=z(i),s=B(i,2),c=L(s),g=z(c,!0),_=B(c,2),v=e=>{var r=Fr();Z(R(r),17,()=>W(l).choices||[],X,(e,r,i)=>{var o=Fo(),s=L(o);xi(s),s.value=s.__value=i;var c=B(s),l=B(c),u=e=>{var t=No(),n=z(t,!0);V(()=>J(n,W(r).detail)),q(e,t)};Y(l,e=>{W(r).detail&&e(u)});var f=B(l,2),p=e=>{q(e,Po())};Y(f,e=>{W(r).persistent&&e(p)}),j(o),V(()=>{Q(s,`name`,`decision-`+t.session+`-`+a()),J(c,` ${W(r).label??``} `)}),Oi(n,[],s,()=>W(d),e=>I(d,e)),q(e,o)}),q(e,r)},y=e=>{var n=Mo(),r=R(n),i=e=>{var t=Ho(),n=R(t),r=B(L(n)),i=L(r),a=e=>{var t=Io();t.value=t.__value=`now`,q(e,t)};Y(i,e=>{W(b)||e(a)});var o=B(i);o.value=o.__value=`quota`;var s=B(o);s.value=s.__value=`delay`;var c=B(s);c.value=c.__value=`at`,j(r),mi(r),j(n);var l=B(n,2),u=e=>{var t=Lo(),n=B(L(t));xi(n),j(t),Ei(n,()=>W(T),e=>I(T,e)),q(e,t)};Y(l,e=>{W(w)===`delay`&&e(u)});var d=B(l,2),f=e=>{var t=Ro(),n=B(L(t));xi(n),j(t),Ei(n,()=>W(ee),e=>I(ee,e)),q(e,t)};Y(d,e=>{W(w)===`at`&&e(f)});var p=B(d,2),m=e=>{var t=Bo(),n=R(t),r=z(n),i=B(n,2),a=e=>{q(e,zo())};Y(i,e=>{W(w)!==`quota`&&e(a)});var o=B(i,2);o.textContent=`Timezone: ${Intl.DateTimeFormat().resolvedOptions().timeZone??``}. - One pending request per session; saving replaces it. Memory - only; Codexometer must stay running. Uses session model, - reasoning and speed.`,V(e=>J(r,`Will send: ${e??``}`),[()=>W(w)===`quota`?`when quota is available and this session is idle`:Number.isFinite(W(ge))?ve(W(ge)):`choose a date and time`]),q(e,t)};Y(p,e=>{W(w)!==`now`&&e(m)});var h=B(p,2),g=e=>{q(e,Vo())};Y(h,e=>{W(b)&&e(g)}),hi(r,()=>W(w),e=>I(w,e)),q(e,t)};Y(r,e=>{W(l).questions?.length||e(i)}),Z(B(r,2),17,()=>W(pe),X,(e,n,r)=>{var i=Yo(),o=R(i),s=e=>{{let n=N(()=>W(u)[0]||``);Fi(ho(e,{get session(){return t.session},get query(){return W(n)},oncomplete:e=>{I(u,[e],!0),dr().then(()=>W(ne)?.focus())},onchange:()=>{I(u,[``],!0),dr().then(()=>W(ne)?.focus())}}),e=>I(re,e,!0),()=>W(re))}};Y(o,e=>{r===0&&a()!==`profile`&&W(l).kind===`prompt`&&!W(l).questions?.length&&e(s)});var c=B(o,2),d=L(c),f=B(d),p=e=>{var t=Uo();xi(t),Ei(t,()=>W(u)[r],e=>W(u)[r]=e),q(e,t)},m=e=>{var t=Go(),i=L(t);i.value=i.__value=``,Z(B(i),17,()=>W(n).options||[],X,(e,t)=>{var n=Wo(),r=z(n,!0),i={};V(()=>{J(r,W(t)),i!==(i=W(t))&&(n.value=(n.__value=i)??``)}),q(e,n)}),j(t),mi(t),hi(t,()=>W(u)[r],e=>W(u)[r]=e),q(e,t)},h=e=>{var t=Ko(),i=R(t);rt(i),Fi(i,e=>I(ne,e),()=>W(ne));var a=B(i,2),o=e=>{var t=Ao(),r=z(t);V(e=>J(r,`Suggested answers: ${e??``}`),[()=>W(n).options.join(` · `)]),q(e,t)};Y(a,e=>{W(n).options?.length&&e(o)}),G(`keydown`,i,e=>{W(l)?.kind===`prompt`&&!W(l).questions?.length&&W(re)?.handleKey(e)}),Ei(i,()=>W(u)[r],e=>W(u)[r]=e),q(e,t)};Y(f,e=>{W(n).secret?e(p):W(n).freeText?e(h,-1):e(m,1)}),j(c);var g=B(c,2),_=e=>{var t=Jo(),r=B(L(t),4);Z(r,21,()=>W(n).options||[],X,(e,t)=>{var n=qo(),r=z(n,!0);V(()=>J(r,W(t))),q(e,n)}),j(r),j(t),q(e,t)};Y(g,e=>{W(n).secret&&!W(n).freeText&&e(_)}),V(()=>J(d,`${W(n).text??``} `)),q(e,i)}),q(e,n)};Y(_,e=>{W(l).kind===`approval`||W(l).kind===`profile`?e(v):e(y,-1)}),j(s);var x=B(s,2),S=e=>{var t=Zo(),n=R(t),r=e=>{var t=Xo(),n=z(t);V(()=>J(n,`TRIGGER // ${W(D)??``}`)),q(e,t)};Y(n,e=>{W(E)&&e(r)});var i=B(n,2),a=z(i),o=B(i,2),s=z(o),c=B(o,2);V(e=>{J(a,`Check the target and ${W(l).kind===`profile`?`profile above`:W(l).kind===`approval`?W(l).fileChanges?.length?`proposed file changes above`:`exact command and permission scope`:`message above`}. ${W(E)?`This schedules a follow-up that will send automatically when eligible`:`This will send to Codex`}; it may start work using your quota. - Confirmation expires in ${e??``}s.`),o.disabled=W(h)||W(ce),J(s,`CONFIRM ${(W(l).kind===`approval`||W(l).kind===`profile`?W(l).choices?.[W(d)]?.label:W(E)?W(b)?`SAVE CHANGES`:`SCHEDULE`:`SEND`)??``}`)},[()=>Math.max(0,Math.ceil((W(p)-W(m))/1e3))]),G(`click`,o,xe),G(`click`,c,()=>{I(f,``)}),q(e,t)},C=e=>{var t=Qo(),n=R(t),r=e=>{var t=bo(),n=z(t,!0);V(()=>J(n,W(_e))),q(e,t)};Y(n,e=>{W(_e)&&e(r)});var i=B(n,2),a=z(i,!0);V(()=>{i.disabled=W(h)||W(ce)||!W(me)||!!W(_e),J(a,W(h)?`SENDING…`:W(b)?`REVIEW CHANGES`:`REVIEW BEFORE SENDING`)}),G(`click`,i,()=>k()),q(e,t)};Y(x,e=>{W(fe)?e(S):e(C,-1)});var O=B(x,2),te=e=>{var t=$o();G(`click`,t,()=>{I(b,!1),I(f,``),I(u,[],!0)}),q(e,t)};Y(O,e=>{W(b)&&e(te)}),V(()=>{J(o,`TARGET // ${W(l).thread??``} // ${(W(l).directory||`Directory unavailable`)??``}`),s.disabled=W(h)||W(fe)||W(ce),J(g,W(l).kind===`approval`||W(l).kind===`profile`?`Choose a decision`:W(b)?`EDIT SCHEDULED FOLLOW-UP`:`Reply to this session`)}),q(e,r)};Y(le,e=>{W(ce)||W(v)?e(he):W(l)?W(l).id?W(y)!==W(l).id&&e(A,3):e(be,2):e(ye,1)}),j(r),V(()=>J(s,a()===`profile`?`QUOTA THRESHOLD`:`SESSION CONTROL // EXPERIMENTAL`)),q(e,r)};Y(Ee,e=>{!W(le)&&(!W(te)||W(b)||a()||W(l)?.kind!==`prompt`||W(l).questions?.length)&&e(De)}),q(e,A),Ke()}wr([`click`,`keydown`]);var rs=K(` `,1),is=K(`
        `),as=K(`

        STATUS LINE // CODEXOMETER

        Choose multiple fields and their order. These display preferences do not - change Codex CLI configuration. Missing values are omitted.

        `),os=K(`

        `,1);function ss(e,t){Ge(t,!0);let n=Ui(t,`stale`,3,!1),r=F(Zt([])),i=F(!1),a=N(()=>$.data?.statusLineFields||[]),o=e=>[...new Set(e??W(a).filter(e=>e.default).map(e=>e.id))].filter(e=>W(a).some(t=>t.id===e)),s=e=>o(e).map(e=>e===`run-state`&&(n()||!$.connected||$.data?.sessionsError)?`STALE`:t.session.statusLine?.[e]).filter(Boolean).join(` · `),c=N(()=>[...W(r).map(e=>W(a).find(t=>t.id===e)).filter(Boolean),...W(a).filter(e=>!W(r).includes(e.id))]);bn(()=>{ra.open&&!W(i)?(I(r,o(na.statusLine),!0),I(i,!0)):ra.open||I(i,!1)});function l(e,t){I(r,t?[...W(r),e]:W(r).filter(t=>t!==e),!0)}function u(e,t){let n=W(r).indexOf(e),i=n+t;if(n<0||i<0||i>=W(r).length)return;let a=[...W(r)];[a[n],a[i]]=[a[i],a[n]],I(r,a,!0)}var d=os(),f=R(d),p=e=>{var t=as(),n=B(L(t),4);Z(n,21,()=>W(c),e=>e.id,(e,t)=>{var n=is(),i=L(n),a=L(i);xi(a);var o=B(a,1,!0),s=z(B(o),!0);j(i);var c=B(i,2),d=e=>{var n=rs(),i=R(n),a=B(i,2);V((e,n)=>{Q(i,`aria-label`,`Move `+W(t).label+` earlier`),i.disabled=e,Q(a,`aria-label`,`Move `+W(t).label+` later`),a.disabled=n},[()=>W(r).indexOf(W(t).id)===0,()=>W(r).indexOf(W(t).id)===W(r).length-1]),G(`click`,i,()=>u(W(t).id,-1)),G(`click`,a,()=>u(W(t).id,1)),q(e,n)},f=N(()=>W(r).includes(W(t).id));Y(c,e=>{W(f)&&e(d)}),j(n),V(e=>{Q(i,`title`,W(t).help),Si(a,e),J(o,W(t).label),J(s,W(t).help)},[()=>W(r).includes(W(t).id)]),G(`change`,a,e=>l(W(t).id,e.currentTarget.checked)),q(e,n)}),j(n);var i=B(n,2),a=z(i),o=B(i,2),d=B(o,2);j(t),V(e=>J(a,`Preview // ${e??``}`),[()=>s(W(r))||`—`]),G(`click`,o,()=>{na.statusLine=[...W(r)],ia(),ra.open=!1}),G(`click`,d,()=>{ra.open=!1}),q(e,t)};Y(f,e=>{ra.open&&e(p)});var m=B(f,2),h=L(m),g=z(h,!0),_=B(h,2);j(m),V((e,t)=>{Q(h,`title`,e),J(g,t)},[()=>s(na.statusLine),()=>s(na.statusLine)]),G(`click`,_,()=>{ra.open=!ra.open}),q(e,d),Ke()}wr([`change`,`click`]);var cs=K(`
        `);function ls(e,t){Ge(t,!0);let n=Ui(t,`active`,3,!1),r=N(()=>t.session.text||t.session.contextKind===`LAST ACTIVITY`&&t.session.workingCommand||``),i=N(()=>[`LAST REPLY`,`LAST ACTIVITY`].includes(t.session.contextKind)&&!!W(r).trim()),a=F(!1),o=F(``),s=F(!1);bn(()=>{if(!W(s))return;let e=setTimeout(()=>{I(s,!1)},150);return()=>clearTimeout(e)}),bn(()=>{W(r),t.session.status,I(o,``)});async function c(){if(!W(i)||W(a))return;let e=W(r);I(a,!0),I(s,!0),I(o,``);try{await navigator.clipboard.writeText(e),W(r)===e&&I(o,`Copied.`)}catch{W(r)===e&&I(o,`Clipboard unavailable. Select the visible text and copy it manually.`)}finally{I(a,!1)}}function l(e){!n()||!W(i)||e.defaultPrevented||e.repeat||e.ctrlKey||e.metaKey||e.altKey||e.key.toLowerCase()!==`c`||e.target instanceof Element&&e.target.closest(`input, textarea, select, [contenteditable]`)||(e.preventDefault(),c())}var u=Fr();Cr(`keydown`,en,l);var d=R(u),f=e=>{var t=cs(),n=L(t),r=z(n,!0),i=B(n,2);let l;j(t),V(()=>{J(r,W(o)),i.disabled=W(a),l=ci(i,1,`svelte-543j00`,null,l,{flashed:W(s)})}),G(`click`,i,c),q(e,t)};Y(d,e=>{W(i)&&e(f)}),q(e,u),Ke()}wr([`click`]);var us=K(`

         
        `,1),ds=K(`

        LATEST GUIDANCE

         
        `,1),fs=K(`

        CONTEXT

         

        `,1),ps=K(`

        `),ms=K(`

        `),hs=K(`
         
        `),gs=K(`

         
        `,1),_s=K(`
         
        `),vs=K(`

        Command unavailable from this observation. Open Codex to inspect the - request.

        `),ys=K(`

        `,1),bs=K(`

        `),xs=K(` `,1),Ss=K(`
        `),Cs=K(`

        Session connection or refresh unavailable. Context and telemetry may be - stale.

        `),ws=K(`

        Some quota profile checks are unavailable. Only sessions with freshly - verified quota and settings can be updated; previous outcome notices remain - visible.

        `),Ts=K(` `),Es=K(` `),Ds=K(` `),Os=K(``),ks=K(`

        Read only — reply or approve in Codex.

        `),As=K(`

        `),js=K(`QUOTA THRESHOLD // REVIEW PROFILE ↗`),Ms=K(`
        `),Ns=K(`
        `),Ps=K(`

        This session is no longer in the current observation. Return to sessions.

        `),Fs=K(`

        `),Is=K(``),Ls=K(`

        `),Rs=K(` `),zs=K(`

        `),Bs=K(`QUOTA THRESHOLD // REVIEW PROFILE ↗`),Vs=K(` `,1),Hs=K(`

        `),Us=K(`

        TOKEN ACTIVITY // 30 SECOND SAMPLES

        `),Ws=K(`

        TOKENS

        FULL DETAIL →
        `),Gs=K(`

        No locally observed sessions yet. Keep Codex running alongside - Codexometer.

        `),Ks=K(`

        ↑ ↓ SELECT SESSION // ← LESS DETAIL // → MORE DETAIL // ESC BACK

        `,1),qs=K(`

        SESSION TOTALS

        `,1);function Js(e,t){Ge(t,!0);let n=(e,t=f,n,r)=>{let i=ht(()=>g(n?.(),!0)),a=ht(()=>g(r?.(),!1));var o=xs(),s=R(o),c=e=>{var n=us(),r=R(n),i=z(r,!0),a=z(B(r,2),!0);V(()=>{J(i,t().contextKind===`LAST REPLY`&&!t().streaming?`TASK`:`CURRENT TASK`),J(a,t().currentTask)}),q(e,n)};Y(s,e=>{t().currentTask&&e(c)});var l=B(s,2),u=e=>{var n=ds(),r=z(B(R(n),2),!0);V(()=>J(r,t().latestGuidance)),q(e,n)};Y(l,e=>{t().latestGuidance&&e(u)});var d=B(l,2),p=e=>{var n=fs(),r=z(B(R(n),2),!0);we(2),V(()=>J(r,t().approvalContext)),q(e,n)};Y(d,e=>{W(a)&&t().contextKind===`APPROVAL REQUEST`&&t().approvalContext&&e(p)});var m=B(d,2),_=e=>{var n=ps();let r;var i=z(n,!0);V(e=>{r=ci(n,1,`attention-note`,null,r,{inferred:t().status===`CHECK SESSION`}),J(i,e)},[()=>C(t())]),q(e,n)},v=N(()=>C(t())&&(!W(a)||W(h)||t().status===`CHECK SESSION`));Y(m,e=>{W(v)&&e(_)});var y=B(m,2),b=e=>{var n=ms(),r=z(n,!0);V(e=>J(r,e),[()=>w(t())]),q(e,n)};Y(y,e=>{W(i)&&e(b)});var x=B(y,2),S=e=>{var n=hs(),r=z(n,!0);V(()=>J(r,t().text||`No session context available.`)),q(e,n)};Y(x,e=>{(t().text||!t().workingCommand)&&e(S)});var T=B(x,2),ee=e=>{var n=gs(),r=B(R(n),2),i=L(r),a=B(i),o=e=>{var n=Pr();V(()=>J(n,`// +${(t().runningCommands||0)-+(t().commandStatus===`running`)} RUNNING`)),q(e,n)};Y(a,e=>{!W(h)&&(t().runningCommands||0)>+(t().commandStatus===`running`)&&e(o)});var s=B(a,2),c=e=>{q(e,Pr(`+`))};Y(s,e=>{t().runningLimited&&e(c)}),j(r);var l=z(B(r,2),!0);V(e=>{J(i,`COMMAND // ${e??``} `),J(l,t().workingCommand)},[()=>W(h)?`UNKNOWN`:(t().commandStatus||`unknown`).toUpperCase()]),q(e,n)};Y(T,e=>{t().workingCommand&&e(ee)});var E=B(T,2),D=e=>{yo(e,{get lines(){return t().fileChanges}})},O=e=>{var n=ys(),r=B(R(n),2),i=z(r,!0),a=B(r,2),o=e=>{var n=_s(),r=z(n,!0);V(()=>J(r,t().command)),q(e,n)},s=e=>{q(e,vs())};Y(a,e=>{t().command?e(o):e(s,-1)}),V(()=>J(i,W(h)||t().status!==`APPROVAL NEEDED`?`LAST OBSERVED COMMAND`:$.data?.control?`COMMAND REQUEST`:`COMMAND TO APPROVE IN CODEX`)),q(e,n)};Y(E,e=>{(!W(a)||!$.data?.control)&&t().fileChanges?.length?e(D):(!W(a)||!$.data?.control)&&(t().command||t().status===`APPROVAL NEEDED`)&&e(O,1)});var te=B(E,2),ne=e=>{var n=bs(),r=z(n);V(()=>J(r,`CONTEXT SOURCE // ${(t().source||`LOCAL`)??``} // OBSERVATION`)),q(e,n)};Y(te,e=>{W(a)||e(ne)}),q(e,o)},r=Ui(t,`params`,19,()=>({})),i=N(()=>$.data?.sessions||[]),a=N(()=>$.data?.control&&$.data.profiles||[]),o=N(()=>($.data?.triggers||[]).filter(e=>W(i).some(t=>t.id===e.session)));function s(e,t){if(r().id&&r().id!==t&&W(l)){e.preventDefault();return}b(t)}function c(e){return[`TURN COMPLETE`,`IDLE`,`ACTIVE`].includes(e.status)&&W(o).some(t=>t.session===e.id)?`TRIGGER SET`:e.status}let l=F(!1);function u(e,t){if(r().id&&r().id!==t&&W(l)){e.preventDefault();return}b(t)}bn(()=>{let e=r().id;e&&mr(()=>{na.selected=e,ca(e,2)})});let d=N(()=>W(i).find(e=>e.id===r().id)),p=N(()=>new URLSearchParams(Ji.querystring).get(`review`)===`profile`&&W(a).some(e=>e.session===r().id&&e.pending)),m=N(()=>W(i).some(e=>e.id===na.selected)?na.selected:W(i)[0]?.id),h=N(()=>!$.connected||!!$.data?.sessionsError),v=N(()=>[[`OBSERVED TOKENS`,la(W(i).reduce((e,t)=>e+t.tokens,0))],[`LISTED SESSIONS`,la(W(i).length)],...[[`WORKING`,`WORKING`],[`AWAITING APPROVAL`,`APPROVAL NEEDED`],[`AWAITING INPUT`,`INPUT NEEDED`],[`CHECK · INFERRED`,`CHECK SESSION`]].map(([e,t])=>[e,W(h)?`—`:la(W(i).filter(e=>e.status===t).length)])]),y=N(()=>W(i).filter(e=>[`INPUT NEEDED`,`APPROVAL NEEDED`,`CHECK SESSION`].includes(e.status)));function b(e){na.selected=e,dr().then(()=>document.querySelector(`.session-row.selected`)?.scrollIntoView({block:`nearest`}))}function x(e,t){if(b(e),r().id){t<0&&(ca(e,2),location.hash=`/sessions`);return}let n=oa(e)+t;n>2?location.hash=`/sessions/`+encodeURIComponent(e):ca(e,n)}function S(e){let t=e.target;if(!(e.altKey||e.ctrlKey||e.metaKey||e.shiftKey||t.closest(`input, textarea, select, [contenteditable="true"]`))){if(e.key===`Escape`&&r().id)e.preventDefault(),x(r().id,-1);else if([`ArrowLeft`,`ArrowRight`].includes(e.key)&&(r().id||W(m)))e.preventDefault(),x(r().id||W(m),e.key===`ArrowLeft`?-1:1);else if(!r().id&&[`ArrowUp`,`ArrowDown`].includes(e.key)&&W(i).length){e.preventDefault();let t=W(i).findIndex(e=>e.id===W(m));b(W(i)[Math.max(0,Math.min(W(i).length-1,t+(e.key===`ArrowUp`?-1:1)))].id)}}}function C(e){return W(h)?`Last observation only — refresh unavailable. Check Codex for current state.`:e.status===`CHECK SESSION`?`INFERRED INACTIVITY — a quiet session, not a confirmed input or approval request. Local tools may still be running.`:$.data?.control&&[`APPROVAL NEEDED`,`INPUT NEEDED`].includes(e.status)?`Open full detail for supported live controls; otherwise reply in Codex.`:e.status===`APPROVAL NEEDED`?`OBSERVED APPROVAL SIGNAL — approve or decline in Codex.`:e.status===`INPUT NEEDED`?`OBSERVED INPUT SIGNAL — reply in Codex.`:e.status===`TURN COMPLETE`?`OBSERVED TURN COMPLETION — informational, not an approval request.`:``}function w(e){return e.streaming&&e.contextKind===`LAST REPLY`?`REPLY // STREAMING`:e.contextKind||`LAST ACTIVITY`}var T=qs();Cr(`keydown`,en,S);var ee=R(T),E=z(B(L(ee),2));j(ee);var D=B(ee,2);let O;Z(D,21,()=>W(v),X,(e,t)=>{var n=N(()=>_(W(t),2));let r=()=>W(n)[0],i=()=>W(n)[1];var a=Ss(),o=L(a),s=z(o,!0),c=z(B(o,2),!0);j(a),V(()=>{J(s,r()),J(c,i())}),q(e,a)}),j(D);var te=B(D,2),ne=z(te),re=B(te,2),ie=e=>{q(e,Cs())};Y(re,e=>{W(h)&&e(ie)});var ae=B(re,2),oe=e=>{q(e,ws())};Y(ae,e=>{$.data?.profileError&&e(oe)});var se=B(ae,2),ce=e=>{var t=Os(),n=L(t);Z(n,17,()=>W(y),X,(e,t)=>{var n=Ts();let r;var i=z(n);V((e,a)=>{r=ci(n,1,`button`,null,r,{approval:W(t).status===`APPROVAL NEEDED`}),Q(n,`href`,e),J(i,`${W(t).status??``} - ${a??``} // ${(W(t).name||W(t).directory||W(t).id)??``}`)},[()=>`#/sessions/`+encodeURIComponent(W(t).id),()=>Array.from(W(t).id).slice(-5).join(``).toUpperCase()]),G(`click`,n,()=>b(W(t).id)),q(e,n)});var c=B(n,2);Z(c,17,()=>W(a).filter(e=>e.pending&&W(i).some(t=>t.id===e.session)),X,(e,t)=>{var n=Es(),a=z(n);V((e,i,o)=>{Q(n,`title`,r().id&&r().id!==W(t).session&&W(l)?`Finish sending or clear your current draft before switching sessions.`:`Review this session’s quota threshold`),Q(n,`href`,e),J(a,`QUOTA THRESHOLD ${i??``} // ${o??``}`)},[()=>`#/sessions/`+encodeURIComponent(W(t).session)+`?review=profile`,()=>Array.from(W(t).session).slice(-5).join(``).toUpperCase(),()=>W(i).find(e=>e.id===W(t).session)?.name||W(i).find(e=>e.id===W(t).session)?.directory||W(t).session]),G(`click`,n,e=>u(e,W(t).session)),q(e,n)}),Z(B(c,2),17,()=>W(o),X,(e,t)=>{var n=Ds(),r=z(n);V((e,t,i)=>{Q(n,`href`,e),J(r,`TRIGGER SET ${t??``} // ${i??``}`)},[()=>`#/sessions/`+encodeURIComponent(W(t).session),()=>Array.from(W(t).session).slice(-5).join(``).toUpperCase(),()=>W(i).find(e=>e.id===W(t).session)?.name||W(i).find(e=>e.id===W(t).session)?.directory]),G(`click`,n,e=>s(e,W(t).session)),q(e,n)}),j(t),q(e,t)},le=N(()=>(W(y).length||W(a).some(e=>e.pending)||W(o).length)&&!W(h));Y(se,e=>{W(le)&&e(ce)});var ue=B(se,2),de=e=>{var t=Fr(),i=R(t),o=e=>{var t=Ns(),i=L(t),o=L(i),s=L(o);let u;var f=B(s);j(o);var m=B(o,2);j(i);var g=B(i,2),_=e=>{var t=bs(),n=z(t,!0);V(()=>J(n,W(d).directory)),q(e,t)};Y(g,e=>{W(d).name&&W(d).directory&&e(_)});var v=B(g,2),y=z(v),x=B(v,2);let S;var C=L(x),w=L(C);n(w,()=>W(d),()=>!0,()=>!0),j(C);var T=B(C,2),ee=e=>{var t=Fr();qr(R(t),()=>W(d).id,e=>{{let t=N(()=>new URLSearchParams(Ji.querystring).get(`schedule`)===`edit`),n=N(()=>W(d).fileChanges||[]);ns(e,{get session(){return W(d).id},get openScheduler(){return W(t)},get observedCommand(){return W(d).command},get observedFileChanges(){return W(n)},get suspended(){return W(p)},onProtectedChange:e=>{I(l,e,!0)}})}}),q(e,t)},E=e=>{q(e,ks())};Y(T,e=>{$.data?.control?e(ee):e(E,-1)}),j(x);var D=B(x,2);Z(D,17,()=>W(a).filter(e=>e.session===W(d).id),e=>e.session,(e,t)=>{var n=Ms(),r=L(n),i=e=>{var n=As(),r=z(n,!0);V(()=>J(r,W(t).notice)),q(e,n)};Y(r,e=>{W(t).notice&&e(i)});var a=B(r,2),o=e=>{ns(e,{get session(){return W(d).id},review:`profile`})},s=e=>{var t=js();V(e=>Q(t,`href`,e),[()=>`#/sessions/`+encodeURIComponent(W(d).id)+`?review=profile`]),q(e,t)};Y(a,e=>{W(t).pending&&W(p)?e(o):W(t).pending&&e(s,1)}),j(n),V(()=>Q(n,`id`,`quota-profile-`+W(d).id)),q(e,n)});var O=B(D,2),te=e=>{var t=Fr();qr(R(t),()=>W(d).id,e=>{ls(e,{get session(){return W(d)},active:!0})}),q(e,t)};Y(O,e=>{W(p)||e(te)}),qr(B(O,2),()=>W(d).id,e=>{ss(e,{get session(){return W(d)},get stale(){return W(h)}})}),j(t),V((e,t)=>{u=ci(s,1,`lamp lit`,null,u,{working:W(d).status===`WORKING`&&!W(h)}),J(f,`${e??``} // ${(W(d).name||W(d).directory)??``}`),J(y,`${t??``} TOKENS // ${W(d).id??``} // CONTEXT SOURCE // ${(W(d).source||`LOCAL`)??``}`),S=ui(x,``,S,{display:W(p)?`none`:void 0})},[()=>W(h)?`STALE`:W(p)?`QUOTA THRESHOLD`:c(W(d)),()=>la(W(d).tokens)]),G(`click`,m,()=>{b(r().id),ca(r().id,2)}),q(e,t)},s=e=>{q(e,Ps())};Y(i,e=>{W(d)?e(o):e(s,-1)}),q(e,t)},fe=e=>{var t=Ks(),r=R(t),o=B(L(r),2),s=L(o),l=B(s,2);j(o),j(r);var d=B(r,2);Z(d,17,()=>W(i),e=>e.id,(e,t)=>{let r=N(()=>oa(W(t).id));var i=Ws();let o;var s=L(i),l=L(s),d=e=>{var n=Fs(),r=L(n),i=z(r);j(n),V(e=>{Q(r,`aria-pressed`,W(m)===W(t).id),J(i,`${e??``} // ${W(t).name??``}`)},[()=>Array.from(W(t).id).slice(-5).join(``).toUpperCase()]),G(`click`,r,()=>b(W(t).id)),q(e,n)};Y(l,e=>{W(t).name&&e(d)});var f=B(l,2),p=L(f),g=L(p);let _;var v=B(g,1,!0);j(p),j(f);var y=B(f,2),S=e=>{var n=Is(),r=z(n,!0);V(()=>{Q(n,`aria-pressed`,W(m)===W(t).id),J(r,W(t).directory||W(t).id)}),G(`click`,n,()=>b(W(t).id)),q(e,n)};Y(y,e=>{W(t).name||e(S)});var C=B(y,2),T=L(C);we(),j(C);var ee=B(C,2),E=e=>{var n=bs(),r=z(n,!0);V(()=>J(r,W(t).directory)),q(e,n)};Y(ee,e=>{W(t).name&&W(t).directory&&e(E)});var D=B(ee,2),O=z(D),te=B(D,2),ne=z(te),re=B(te,2),ie=L(re),ae=B(ie,2),oe=z(ae,!0),se=B(ae,2);j(re);var ce=B(re,2),le=B(ce,2),ue=e=>{var n=Ls(),r=z(n,!0);V(()=>J(r,W(t).status===`CHECK SESSION`?`INFERRED INACTIVITY`:$.data?.control?`OPEN FULL DETAIL OR REPLY IN CODEX`:W(t).status===`APPROVAL NEEDED`?`APPROVE OR DECLINE IN CODEX`:`REPLY IN CODEX`)),q(e,n)},de=N(()=>!W(h)&&[`APPROVAL NEEDED`,`INPUT NEEDED`,`CHECK SESSION`].includes(W(t).status));Y(le,e=>{W(de)&&e(ue)}),j(s);var fe=B(s,2),pe=e=>{var r=Hs(),i=L(r),o=L(i),s=z(o,!0),c=B(o,2),l=e=>{var n=Rs(),r=z(n,!0);V(e=>{Q(n,`href`,e),J(r,$.data?.control?`REVIEW IN FULL DETAIL ↗`:`APPROVAL IN CODEX ↗`)},[()=>`#/sessions/`+encodeURIComponent(W(t).id)]),q(e,n)};Y(c,e=>{!W(h)&&W(t).status===`APPROVAL NEEDED`&&e(l)}),j(i);var d=B(i,2);n(d,()=>W(t),()=>!1);var f=B(d,2);Z(f,17,()=>W(a).filter(e=>e.session===W(t).id),X,(e,n)=>{var r=Vs(),i=R(r),a=e=>{var t=zs(),r=z(t,!0);V(()=>J(r,W(n).notice)),q(e,t)};Y(i,e=>{W(n).notice&&e(a)});var o=B(i,2),s=e=>{var n=Bs();V(e=>Q(n,`href`,e),[()=>`#/sessions/`+encodeURIComponent(W(t).id)+`?review=profile`]),G(`click`,n,e=>u(e,W(t).id)),q(e,n)};Y(o,e=>{W(n).pending&&e(s)}),q(e,r)});var p=B(f,2);{let e=N(()=>W(m)===W(t).id);ls(p,{get session(){return W(t)},get active(){return W(e)}})}j(r),V(e=>J(s,e),[()=>w(W(t))]),q(e,r)};Y(fe,e=>{W(r)>0&&e(pe)});var me=B(fe,2),he=e=>{var n=Us(),r=B(L(n),2);{let e=N(()=>(W(t).samples||[]).map(e=>e.tokens));Qa(r,{get values(){return W(e)},capacity:120})}var i=z(B(r,2));j(n),V(()=>J(i,`LAST ${(W(t).samples?.length||0)??``} SAMPLES // AUTO SCALE`)),q(e,n)};Y(me,e=>{W(r)<2&&e(he)}),j(i),V((e,n,a,s,c)=>{o=ci(i,1,`session-row`,null,o,{selected:W(m)===W(t).id,wide:W(r)===2,split:W(r)===1}),Q(i,`aria-label`,`Session `+(W(t).name||W(t).directory||W(t).id)),Q(p,`href`,e),_=ci(g,1,`lamp lit`,null,_,{working:W(t).status===`WORKING`&&!W(h)}),J(v,n),J(T,`${a??``} `),J(O,`${W(t).agents??``} LINKED AGENTS`),J(ne,`ACTIVE // ${s??``}`),ie.disabled=W(r)===0,Q(ae,`aria-expanded`,W(r)>0),J(oe,W(r)?`HIDE DETAIL`:`SHOW DETAIL`),Q(ce,`href`,c)},[()=>`#/sessions/`+encodeURIComponent(W(t).id),()=>W(h)?`STALE`:c(W(t)),()=>la(W(t).tokens),()=>ua(W(t).activity),()=>`#/sessions/`+encodeURIComponent(W(t).id)]),G(`click`,p,()=>b(W(t).id)),G(`click`,ie,()=>x(W(t).id,-1)),G(`click`,ae,()=>{b(W(t).id),ca(W(t).id,+!W(r))}),G(`click`,se,()=>x(W(t).id,1)),G(`click`,ce,()=>b(W(t).id)),q(e,i)});var f=B(d,2),p=e=>{q(e,Gs())};Y(f,e=>{W(i).length||e(p)}),G(`click`,s,()=>sa(1)),G(`click`,l,()=>sa(0)),q(e,t)};Y(ue,e=>{r().id?e(de):e(fe,-1)}),V(e=>{J(E,`OBSERVED ${e??``}`),O=ci(D,1,`session-totals`,null,O,{stale:W(h)}),J(ne,`${W(h)?`LAST KNOWN TOTALS — live state counts unavailable. `:``}Tokens - observed since this server started for currently listed sessions; linked - agents are already included. Totals can decrease when a session leaves the - list. History samples every 30 seconds. Not account-wide totals.`)},[()=>ua($.data?.sessionsAt)]),q(e,T),Ke()}wr([`click`]);var Ys=K(`

        `),Xs=K(`

        Relative usage/credits are not tokens or quota percentages. Groupings - describe the same usage, not additional consumption.

        `,1),Zs=K(`// APPROXIMATE`),Qs=K(`

        Partial accounting: totals may still change.

        `),$s=K(`

        Percentage of this period's historical allowance, not today's limit. May - exceed 100%. This is not a manual/automatic reset log.

        `,1),ec=K(`

        `,1),tc=K(`

        This optional account report is unavailable. Token history remains - available; missing data does not mean zero usage.

        `),nc=K(`
        `),rc=K(`
        `),ic=K(`

        No usage reported for this day.

        `),ac=K(`

        Selected breakdown unavailable; missing does not mean zero.

        `),oc=K(`

        `,1);function sc(e,t){Ge(t,!0);let n=F(`surface`),r=F(0),i=N(()=>t.reports?.daily),a=N(()=>t.reports?.plan),o=N(()=>t.mode===`breakdown`?W(i)?.days.length||0:W(a)?.periods.length||0),s=N(()=>Math.min(W(r),Math.max(0,W(o)-1))),c=N(()=>W(i)?.days[(W(i)?.days.length||0)-1-W(s)]),l=N(()=>W(a)?.periods[W(s)]),u=N(()=>t.mode===`breakdown`?W(i)?.units||``:`%`),d=N(()=>{let e=Object.create(null);if(t.mode===`breakdown`)e=W(c)?.groups[W(n)]||{};else{let t=W(n)===`feature`?`thread_source`:W(n)===`task start`?`turn_trigger`:W(n);for(let n of W(l)?.breakdowns||[])if(n.dimension===t)for(let t of n.rows)e[t.key]=(e[t.key]||0)+t.basis_points/100}return Object.entries(e).sort((e,t)=>t[1]-e[1]||e[0].localeCompare(t[0]))}),f=N(()=>t.mode===`breakdown`?W(c)?.total:W(l)?.used_basis_points==null?null:W(l).used_basis_points/100),p=N(()=>Math.max(1,W(f)||0,...W(d).map(([,e])=>e))),m=e=>e==null?`UNKNOWN`:e.toLocaleString(`en-GB`,{maximumFractionDigits:2});var h=oc(),g=R(h),v=L(g),y=B(L(v)),b=L(y);b.value=b.__value=`surface`;var x=B(b);x.value=x.__value=`model`;var S=B(x);S.value=S.__value=`feature`;var C=B(S);C.value=C.__value=`task start`,j(y),mi(y),j(v);var w=B(v,2),T=B(w,2),ee=z(B(T,2),!0);j(g);var E=B(g,2),D=L(E),O=z(D,!0),te=B(D,2),ne=z(te),re=B(te,2),ie=e=>{var t=Xs(),n=R(t),r=z(n),a=B(n,2),o=e=>{var t=Ys(),n=z(t);V(()=>J(n,`Data as of ${W(i).dataAsOf??``}`)),q(e,t)};Y(a,e=>{W(i).dataAsOf&&e(o)});var s=z(B(a,2));we(2),V((e,t)=>{J(r,`Latest fetch ${e??``} // queried ${W(i).from??``} → ${W(i).through??``}. - Reports are fetched from OpenAI and are not saved between runs.`),J(s,`${(W(c)?.date||`No daily buckets reported`)??``} // ${t??``} - ${W(u)??``}`)},[()=>ua(W(i).fetchedAt),()=>m(W(f))]),q(e,t)},ae=e=>{var t=ec(),n=R(t),r=L(n);let i;var o=z(r,!0),s=B(r),c=e=>{q(e,Zs())};Y(s,e=>{W(a).approximate&&e(c)});var u=B(s);j(n);var d=B(n,2),p=L(d),h=B(p),g=e=>{var t=Pr();V(()=>J(t,`// Boundary tolerance ${W(a).boundary_tolerance_seconds??``}s`)),q(e,t)};Y(h,e=>{W(a).boundary_tolerance_seconds!=null&&e(g)}),j(d);var _=B(d,2),v=e=>{var t=$s(),n=R(t),r=L(n),i=B(r);let a;var o=z(i);j(n);var s=B(n,2),c=z(s),u=B(s,2),d=e=>{q(e,Qs())};Y(u,e=>{W(l).accounting_complete||e(d)}),we(2),V(e=>{J(r,`${W(l).window_minutes??``} MIN // ${W(l).plan_type??``} // `),a=ci(i,1,`svelte-1fbui7h`,null,a,{muted:W(f)==null,"quota-near":W(f)!=null&&W(f)>=80&&W(f)<100,"quota-full":W(f)!=null&&W(f)>=100}),J(o,`USED ${e??``}${W(f)==null?``:`%`}`),J(c,`${W(l).starts_at??``} → ${W(l).ends_at??``}`)},[()=>m(W(f))]),q(e,t)};Y(_,e=>{W(l)&&e(v)}),V(e=>{i=ci(r,1,`svelte-1fbui7h`,null,i,{"quality-warning":!W(a).coverage_complete}),J(o,W(a).coverage_complete?`Reported range complete`:`Partial coverage`),J(u,` // Latest fetch ${e??``}`),J(p,`Coverage starts ${(W(a).coverage_start||`unknown`)??``} // Data as of ${(W(a).data_as_of||`unknown`)??``}`)},[()=>ua(W(a).fetchedAt)]),q(e,t)},oe=e=>{q(e,tc())};Y(re,e=>{t.mode===`breakdown`&&W(i)?e(ie):t.mode===`periods`&&W(a)?e(ae,1):e(oe,-1)});var se=B(re,2),ce=e=>{var n=rc(),r=L(n),i=z(B(L(r),2),!0);j(r),Z(B(r,2),17,()=>W(d),X,(e,n,r)=>{var i=N(()=>_(W(n),2));let a=()=>W(i)[0],o=()=>W(i)[1];var s=nc();let c;var l=L(s),u=z(l,!0),d=B(l,2),f=L(d);let h;j(d);var g=z(B(d,2));j(s),V((e,n,i)=>{c=ci(s,1,`report-row svelte-1fbui7h`,null,c,{unknown:e,alternate:r%2==1}),Q(l,`title`,a()),J(u,a()),h=ui(f,``,h,{width:n}),J(g,`${i??``}${t.mode===`periods`?`%`:``}`)},[()=>a().trim().toLowerCase()===`unknown`,()=>`${Math.max(0,100*o()/W(p))}%`,()=>m(o())]),q(e,s)}),j(n),V(()=>J(i,t.mode===`breakdown`?W(u):`QUOTA USED`)),q(e,n)},le=e=>{q(e,ic())},ue=e=>{q(e,ac())};Y(se,e=>{W(d).length?e(ce):t.mode===`breakdown`&&W(c)?.total===0&&W(c)?.groups[W(n)]!=null?e(le,1):(W(i)&&t.mode===`breakdown`||W(a)&&t.mode===`periods`)&&e(ue,2)}),j(E),V(()=>{w.disabled=W(s)>=W(o)-1,T.disabled=W(s)===0,J(ee,W(o)?`${W(s)+1} / ${W(o)}`:`NO DATA`),J(O,t.mode===`breakdown`?`DAILY BREAKDOWN`:`QUOTA WINDOWS`),J(ne,`${(t.mode===`breakdown`?t.reports?.dailyStatus||`UNAVAILABLE`:t.reports?.planStatus||`UNAVAILABLE`)??``} // UTC`)}),hi(y,()=>W(n),e=>I(n,e)),G(`click`,w,()=>I(r,W(s)+1)),G(`click`,T,()=>I(r,W(s)-1)),q(e,h),Ke()}wr([`click`]);var cc=864e5;function lc(e,t){let n=new Date(e);n.setUTCDate(1),n.setUTCMonth(n.getUTCMonth()+t);let r=new Date(Date.UTC(n.getUTCFullYear(),n.getUTCMonth()+1,0)).getUTCDate();return n.setUTCDate(Math.min(e.getUTCDate(),r)),n}function uc(e,t,n){let r=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()));for(let e=0;;e++){let i=lc(r,-t),a=new Date(i.getTime()+cc);if(e===n)return{start:a,end:r};r=i}}var dc=K(``),fc=K(``,1),pc=K(`

        History refresh failed. Any displayed history is the last successful - observation.

        `),mc=K(`

        Token history unavailable; other reports may still be available.

        `),hc=K(``),gc=K(`
        `),_c=K(`

        LESS ░ ▒ ▓ █ MORE // HOVER FOR DATE AND TOKENS

        `,1),vc=K(`
        `,1),yc=K(` `),bc=K(`

        LIFETIME TOKENS

        PEAK DAY

        CURRENT STREAK

        DAYS

        LONGEST TURN

        SECONDS

        LONGEST STREAK

        DAYS

        OPENAI // UTC

        Accessible data table
        Date (UTC)Tokens

        `,1),xc=K(`

        History unavailable or awaiting a matching account observation. Missing - history is not treated as zero usage.

        `),Sc=K(`

        USAGE // ACCOUNT HISTORY

        Account-wide history reported by Codex, not the local Sessions counter. Dates - use UTC. Allowance history is approximate where indicated; reset causes are - not provided.

        `,1);function Cc(e,t){Ge(t,!0);let n=F(`daily`),r=F(12),i=F(0),a=N(()=>{let e=$.data?.usage?.dailyUsageBuckets;if(!e)return[];let{start:t,end:n}=uc(new Date,W(r),W(i)),a=new Map;for(let t of e)!Number.isFinite(t.tokens)||t.tokens<0||a.set(t.startDate,(a.get(t.startDate)||0)+t.tokens);let o=[];for(let e=t;e<=n;e=new Date(e.getTime()+864e5)){let t=e.toISOString().slice(0,10);o.push({date:t,tokens:a.get(t)||0})}return o}),o=N(()=>Math.max(1,...W(a).map(e=>e.tokens))),s=N(()=>W(a).length?new Date(W(a)[0].date+`T00:00:00Z`).getUTCDay():0),c=N(()=>{if(W(n)===`monthly`){let e=new Map;for(let t of W(a))e.set(t.date.slice(0,7),(e.get(t.date.slice(0,7))||0)+t.tokens);return[...e].map(([e,t])=>({date:e,tokens:t}))}let e=0;return W(a).map(t=>({date:t.date,tokens:e+=t.tokens}))});var l=Sc(),u=B(R(l),4);Z(u,20,()=>[[`daily`,`Daily`],[`monthly`,`Monthly`],[`cumulative`,`Cumulative`],[`breakdown`,`Breakdown`],[`periods`,`Windows`]],X,(e,t)=>{var r=N(()=>_(t,2));let a=()=>W(r)[0],o=()=>W(r)[1];var s=dc();let c;var l=z(s,!0);V(()=>{Q(s,`aria-pressed`,W(n)===a()),c=ci(s,1,``,null,c,{active:W(n)===a()}),J(l,o())}),G(`click`,s,()=>{I(n,a(),!0),I(i,0)}),q(e,s)}),j(u);var d=B(u,2),f=L(d),p=e=>{var t=fc(),n=R(t),a=B(L(n)),o=L(a);o.value=o.__value=6;var s=B(o);s.value=s.__value=12,j(a),mi(a),j(n);var c=B(n),l=B(c);V(()=>l.disabled=W(i)===0),G(`change`,a,()=>I(i,0)),hi(a,()=>W(r),e=>I(r,e)),G(`click`,c,()=>Jt(i)),G(`click`,l,()=>Jt(i,-1)),q(e,t)};Y(f,e=>{W(n)!==`breakdown`&&W(n)!==`periods`&&e(p)}),j(d);var m=B(d,2),h=e=>{q(e,pc())};Y(m,e=>{$.data?.usageError&&e(h)});var g=B(m,2),v=e=>{q(e,mc())};Y(g,e=>{$.data?.usage?.tokenStatus===`UNAVAILABLE`&&W(n)!==`breakdown`&&W(n)!==`periods`&&e(v)});var y=B(g,2),b=e=>{var t=Fr();qr(R(t),()=>W(n),e=>{{let t=N(()=>$.data?.usage?.reports);sc(e,{get reports(){return W(t)},get mode(){return W(n)}})}}),q(e,t)},x=e=>{var t=bc(),r=R(t),i=L(r),l=z(B(L(i),2),!0);j(i);var u=B(i,2),d=z(B(L(u),2),!0);j(u);var f=B(u,2),p=B(L(f),2),m=L(p);we(),j(p),j(f);var h=B(f,2),g=B(L(h),2),_=L(g);we(),j(g),j(h);var v=B(h,2),y=B(L(v),2),b=L(y);we(),j(y),j(v),j(r);var x=B(r,4),S=L(x),C=z(S),w=B(S,2),T=e=>{var t=_c(),n=R(t),r=L(n),i=L(r);Z(i,17,()=>Array(W(s)),X,(e,t)=>{q(e,hc())}),Z(B(i,2),17,()=>W(a),X,(e,t)=>{var n=gc();let r,i;V(e=>{r=ci(n,1,`heat-cell`,null,r,{zero:W(t).tokens===0}),Q(n,`title`,e),i=ui(n,``,i,{opacity:W(t).tokens?.25+.75*W(t).tokens/W(o):1})},[()=>`${W(t).date}: ${la(W(t).tokens)} tokens`]),q(e,n)}),j(r),j(n),we(2),q(e,t)},ee=e=>{var t=vc(),r=R(t);{let e=N(()=>W(c).map(e=>e.tokens)),t=N(()=>W(n)+` usage`);Qa(r,{get values(){return W(e)},get label(){return W(t)}})}var i=B(r,2),a=L(i),o=z(a,!0),s=z(B(a),!0);j(i),V(e=>{J(o,W(c)[0]?.date),J(s,e)},[()=>W(c).at(-1)?.date]),q(e,t)};Y(w,e=>{W(n)===`daily`?e(T):e(ee,-1)});var E=B(w,2),D=B(L(E),2),O=L(D),te=B(L(O));Z(te,21,()=>W(n)===`daily`?W(a):W(c),X,(e,t)=>{var n=yc(),r=L(n),i=z(r,!0),a=z(B(r),!0);j(n),V(e=>{J(i,W(t).date),J(a,e)},[()=>la(W(t).tokens)]),q(e,n)}),j(te),j(O),j(D),j(E),j(x);var ne=z(B(x,2));V((e,t,n,r,i,o,s)=>{J(l,e),J(d,t),J(m,`${n??``} `),J(_,`${r??``} `),J(b,`${i??``} `),J(C,`${W(a)[0]?.date??``} — ${o??``}`),J(ne,`OBSERVED ${s??``} // CUMULATIVE IS FOR THE SELECTED PERIOD`)},[()=>la($.data.usage.summary.lifetimeTokens),()=>la($.data.usage.summary.peakDailyTokens),()=>la($.data.usage.summary.currentStreakDays),()=>la($.data.usage.summary.longestRunningTurnSec),()=>la($.data.usage.summary.longestStreakDays),()=>W(a).at(-1)?.date,()=>ua($.data.usageAt)]),q(e,t)},S=e=>{q(e,xc())};Y(y,e=>{W(n)===`breakdown`||W(n)===`periods`?e(b):$.data?.usage&&$.data.usage.dailyUsageBuckets!==null?e(x,1):e(S,-1)}),q(e,l),Ke()}wr([`click`,`change`]);var wc=K(`

        Page not found

        Return to Quota

        `,1);function Tc(e){var t=wc();we(2),q(e,t)}var Ec=K(` `),Dc=K(`

        `),Oc=K(`

        Connecting to your local Codexometer…

        `),kc=K(``),Ac=K(`
        CODEXOMETER

        Your quota. Your sessions. Your command centre.

        `);function jc(e,t){Ge(t,!0);let n={"/":Ya,"/quota/:view?":Ya,"/sessions/:id?":Js,"/usage":Cc,"*":Tc},r={quota:/^(?:\/|\/quota(?:\/[^/]+)?\/?)$/,sessions:/^\/sessions(?:\/[^/]+)?\/?$/,usage:/^\/usage\/?$/},i=F(`hacker`),a=[`hacker`,`rust`,`blue-steel`,`ultraviolet`,`nightshade`];bn(()=>{ia()}),bn(()=>{if($.data)for(let[e,t]of Object.entries(r))t.test(Ji.location)&&(na.tab=e)}),Wi(()=>{try{let e=localStorage.getItem(`codexometer.web.theme`);e&&a.includes(e)&&I(i,e,!0)}catch{}return ha()});function o(){try{localStorage.setItem(`codexometer.web.theme`,W(i))}catch{}}var s=Ac(),c=L(s),l=B(L(c),2),u=L(l);let d;var f=B(u,1,!0),p=z(B(f));j(l),j(c);var m=B(c,2);Z(m,21,()=>Object.entries(r),X,(e,t)=>{var n=N(()=>_(W(t),2));let r=()=>W(n)[0],i=()=>W(n)[1],a=N(()=>i().test(Ji.location));var o=Ec();let s;var c=z(o,!0);V(e=>{Q(o,`href`,r()===`quota`?`#/quota/`+na.view:`#/`+r()),Q(o,`aria-current`,W(a)?`page`:void 0),s=ci(o,1,``,null,s,{active:W(a)}),J(c,e)},[()=>r().toUpperCase()]),q(e,o)}),j(m);var h=B(m,2),g=L(h),v=e=>{var t=Dc(),n=z(t,!0);V(()=>J(n,$.error)),q(e,t)};Y(g,e=>{$.error&&e(v)});var y=B(g,2),b=e=>{Zi(e,{get routes(){return n}})},x=e=>{q(e,Oc())};Y(y,e=>{$.data?e(b):$.error||e(x,1)}),j(h);var S=B(h,2),C=L(S),w=z(C),T=B(C,2),ee=z(T,!0),E=B(T,2),D=B(L(E));Z(D,21,()=>a,X,(e,t)=>{var n=kc(),r=z(n,!0),i={};V(e=>{J(r,e),i!==(i=W(t))&&(n.value=(n.__value=i)??``)},[()=>W(t).replace(`-`,` `).toUpperCase()]),q(e,n)}),j(D),mi(D),j(E),j(S),j(s),V(()=>{Q(s,`data-theme`,W(i)),d=ci(u,1,`lamp`,null,d,{lit:$.connected}),J(f,$.connected?`CONNECTED`:`OFFLINE`),J(p,`EXPERIMENTAL // ${$.data?.control?`SESSION CONTROL`:`READ ONLY`}`),J(w,`v${($.data?.version||`…`)??``} // LOCAL ONLY`),J(ee,$.data?.control?`Session control enabled. Stop the server to revoke access.`:`No actions can be sent from this preview.`)}),G(`change`,D,o),hi(D,()=>W(i),e=>I(i,e)),q(e,s),Ke()}wr([`change`]),Vr(jc,{target:document.getElementById(`app`)}); \ No newline at end of file diff --git a/internal/web/dist/assets/index-f6yvkeCn.css b/internal/web/dist/assets/index-f6yvkeCn.css deleted file mode 100644 index ad4be38..0000000 --- a/internal/web/dist/assets/index-f6yvkeCn.css +++ /dev/null @@ -1 +0,0 @@ -.observation-details.svelte-1wk8llw{font-size:12px}summary.svelte-1wk8llw{cursor:pointer;color:var(--accent)}.observation-table.svelte-1wk8llw{max-height:240px;overflow:auto}.zone-canvas.svelte-1wk8llw{flex:1;min-height:170px;margin-top:6px;position:relative}svg.svelte-1wk8llw{width:100%;height:100%;display:block;position:absolute;inset:0}text.svelte-1wk8llw{fill:var(--ink);font:14px Cascadia Code,Consolas,monospace}.axis-title.svelte-1wk8llw{letter-spacing:.04em;font-size:12px}.grid.svelte-1wk8llw{stroke:#fff;stroke-opacity:.14;stroke-width:1px}.axes.svelte-1wk8llw{stroke:var(--ink);stroke-width:1.5px;fill:none}.pace-line.svelte-1wk8llw{stroke:#fff;stroke-width:2px;stroke-dasharray:6 4}.position-halo.svelte-1wk8llw{fill:#101820;stroke:#fff;stroke-width:1.5px}.position-dot.svelte-1wk8llw{fill:#fff}.observation-trail.svelte-1wk8llw{fill:none;stroke:#fff;stroke-width:2.5px;stroke-linejoin:round}.trail-start.svelte-1wk8llw{fill:none;stroke:#fff;stroke-width:2px}.trail-caption.svelte-1wk8llw{text-align:center;font-size:11px}.zone-caption.svelte-1wk8llw{color:var(--accent);text-align:center}.slash-commands.svelte-7bfwr9{margin:.4rem 0;position:relative}.command-popup.svelte-7bfwr9{z-index:5;box-sizing:border-box;border:1px solid var(--accent);background:var(--panel);width:min(40rem,100%);padding:.35rem;position:absolute;bottom:100%;left:0;box-shadow:0 .3rem 1rem #0006}.command-popup.svelte-7bfwr9 p:where(.svelte-7bfwr9){margin:.3rem 0;font-size:.8em}.command-options.vertical.svelte-7bfwr9{flex-flow:column;gap:.15rem;display:flex}.vertical.svelte-7bfwr9 button:where(.svelte-7bfwr9){text-align:left;white-space:nowrap;width:100%;min-width:0;color:var(--muted);background:var(--panel);border-color:#0000;flex:none;align-items:baseline;gap:.4rem;display:flex}.vertical.svelte-7bfwr9 .command-name:where(.svelte-7bfwr9),.vertical.svelte-7bfwr9 .command-tail:where(.svelte-7bfwr9){flex:none}.vertical.svelte-7bfwr9 .command-name:where(.svelte-7bfwr9){text-overflow:ellipsis;max-width:100%;overflow:hidden}.vertical.svelte-7bfwr9 button.suggested:where(.svelte-7bfwr9){outline:none}.vertical.svelte-7bfwr9 button.suggested:where(.svelte-7bfwr9) .command-name:where(.svelte-7bfwr9),.vertical.svelte-7bfwr9 button:where(.svelte-7bfwr9):hover .command-name:where(.svelte-7bfwr9){background:var(--accent);color:var(--bg)}.vertical.svelte-7bfwr9 .command-help:where(.svelte-7bfwr9){min-width:0;max-width:none;color:var(--muted);flex:1}.command-options.svelte-7bfwr9{flex-wrap:wrap;gap:.35rem;max-height:35vh;display:flex;overflow:auto}.command-options.svelte-7bfwr9 .suggested:where(.svelte-7bfwr9){outline:1px solid var(--accent)}.command-help.svelte-7bfwr9{text-overflow:ellipsis;white-space:nowrap;opacity:.75;max-width:32ch;font-size:.8em;display:block;overflow:hidden}pre.svelte-7bfwr9{white-space:pre-wrap;overflow-wrap:anywhere;max-height:35vh;overflow:auto}.file-diff.svelte-x4gtb6{border:1px solid var(--muted);max-height:60vh;padding:.5rem;font-family:monospace;overflow:auto}.diff-line.svelte-x4gtb6{white-space:pre;min-height:1.3em;display:flex}.line-number.svelte-x4gtb6{text-align:right;opacity:.75;-webkit-user-select:none;user-select:none;flex:0 0 5ch;padding-right:1ch;display:inline-block}.added.svelte-x4gtb6{color:#67d391;background:#67d39112}.removed.svelte-x4gtb6{color:#ff6b83;background:#ff6b8312}.heading.svelte-x4gtb6{padding:.5rem 0;font-weight:700}.saved-prompt.svelte-1oupzfc{white-space:pre-wrap;overflow-wrap:anywhere}fieldset.svelte-1oupzfc{border:1px solid;margin:.5rem 0;padding:.35rem .6rem}.decision.svelte-1oupzfc,.answer.svelte-1oupzfc{margin:.3rem 0;display:block}.decision.svelte-1oupzfc code:where(.svelte-1oupzfc),.decision.svelte-1oupzfc span:where(.svelte-1oupzfc){overflow-wrap:anywhere;margin:.3rem 0 .3rem 1.5rem;display:block}textarea.svelte-1oupzfc,.answer.svelte-1oupzfc input:where(.svelte-1oupzfc),.answer.svelte-1oupzfc select:where(.svelte-1oupzfc){box-sizing:border-box;background:var(--bg);width:100%;color:inherit;font:inherit;border:1px solid;margin-top:.4rem;padding:.5rem;display:block}textarea.svelte-1oupzfc{resize:vertical;background:color-mix(in srgb, currentColor 8%, var(--bg))}button.svelte-1oupzfc{margin:.3rem .5rem .3rem 0}h3.svelte-1oupzfc,p.svelte-1oupzfc{margin-block:.4rem}.sent.svelte-1oupzfc{color:var(--accent)}.status-line.svelte-gprptn{align-items:center;gap:.5rem;min-width:0;margin-top:.5rem;display:flex}.status-line.svelte-gprptn p:where(.svelte-gprptn){white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;margin:0;font-size:.8rem;overflow:hidden}.configure.svelte-gprptn{padding:.15rem .35rem;font-size:.75rem}.status-picker.svelte-gprptn{border:1px solid var(--edge);margin:.5rem 0;padding:.6rem}.fields.svelte-gprptn{max-height:35vh;overflow:auto}.field.svelte-gprptn{align-items:center;gap:.35rem;display:flex}.field.svelte-gprptn label:where(.svelte-gprptn){flex:1;align-items:center;gap:.4rem;display:flex}.field.svelte-gprptn label:where(.svelte-gprptn) span:where(.svelte-gprptn){font-size:.75rem}.field.svelte-gprptn button:where(.svelte-gprptn){padding:.1rem .35rem}.session-copy.svelte-543j00{justify-content:flex-end;align-items:center;gap:8px;margin-top:auto;padding-top:6px;display:flex}span.svelte-543j00{color:var(--muted);overflow-wrap:anywhere;font-size:12px}button.svelte-543j00{color:var(--accent);flex-shrink:0;padding:3px 7px}button.svelte-543j00:hover,button.flashed.svelte-543j00{color:var(--bg);background:var(--accent)}h3.svelte-1fbui7h,.window-dates.svelte-1fbui7h,.report-value.svelte-1fbui7h{color:var(--accent);font-weight:700}.quality-warning.svelte-1fbui7h,.quota-near.svelte-1fbui7h{color:var(--approval)}.quota-full.svelte-1fbui7h{color:#ff6680}.alternate.svelte-1fbui7h .report-fill:where(.svelte-1fbui7h){background:radial-gradient(circle, var(--panel) .7px, transparent .9px) 0 0 / 3px 3px, var(--accent)}.unknown.svelte-1fbui7h,.unknown.svelte-1fbui7h .report-value:where(.svelte-1fbui7h){color:var(--muted)}.unknown.svelte-1fbui7h .report-fill:where(.svelte-1fbui7h){background:var(--muted);opacity:1}.report-bars.svelte-1fbui7h{grid-template-columns:minmax(5rem,1fr) minmax(2rem,3fr) auto;gap:.6rem;display:grid}.report-row.svelte-1fbui7h,.report-head.svelte-1fbui7h{grid-column:1/-1;grid-template-columns:subgrid;align-items:center;gap:.6rem;display:grid}.report-head.svelte-1fbui7h{color:var(--muted);font-size:.85rem}.report-head.svelte-1fbui7h>span:where(.svelte-1fbui7h):last-child,.report-value.svelte-1fbui7h{text-align:right}.report-row.svelte-1fbui7h>span:where(.svelte-1fbui7h):first-child{overflow-wrap:anywhere}.report-track.svelte-1fbui7h{background:var(--panel);height:1rem}.report-fill.svelte-1fbui7h{background:var(--accent);height:100%}:root{font-synthesis:none;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;background:#080e0c;font-family:Cascadia Code,SFMono-Regular,Consolas,monospace}*{box-sizing:border-box}body{margin:0}.shell{--accent:#80edac;--approval:#ffca58;--muted:#98aaa0;--edge:#31473a;--panel:#101c16;--bg:#080e0c;--ink:#e2eee5;background:var(--bg);height:100dvh;color:var(--ink);grid-template-rows:auto auto minmax(0,1fr) auto;padding:clamp(10px,1.2vw,20px);display:grid}.shell[data-theme=rust]{--approval:#ff8a3d;--accent:#ffbc75;--muted:#bfac95;--edge:#59412c;--panel:#261b14;--bg:#140e0a}.shell[data-theme=blue-steel]{--approval:#e8c46a;--accent:#85c8ff;--muted:#9cacc4;--edge:#334d69;--panel:#142235;--bg:#0b1421}.shell[data-theme=ultraviolet]{--approval:#f9a8d4;--accent:#c5adff;--muted:#b3a6c9;--edge:#4b3c68;--panel:#241a35;--bg:#140d22}.shell[data-theme=nightshade]{--approval:#8f7cff;--accent:#e59bff;--muted:#c6a7cf;--edge:#673976;--panel:#301739;--bg:#1c0b24}header,.spread,footer,.controls{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:10px;display:flex}header{margin-bottom:10px}.brand{letter-spacing:.08em;color:var(--accent);text-shadow:0 0 22px color-mix(in srgb, var(--accent) 20%, transparent);font-size:clamp(22px,2.5vw,32px);font-weight:800;text-decoration:none}header p{color:var(--muted);margin:3px 0 0;font-size:13px}.connection{color:var(--accent);text-align:right}small,.eyebrow{letter-spacing:.04em;font-size:12px}.connection small{color:var(--muted);margin-top:4px;display:block}.lamp{background:var(--muted);border-radius:50%;width:9px;height:9px;margin-right:9px;display:inline-block}.lamp.lit{background:var(--accent);box-shadow:0 0 8px color-mix(in srgb, var(--accent) 40%, transparent)}.working{animation:1.5s ease-in-out infinite pulse}@keyframes pulse{50%{opacity:.3}}nav{flex-wrap:wrap;gap:6px;margin-bottom:10px;display:flex}nav a,button,.button,select{font:inherit;color:var(--accent);border:1px solid var(--edge);background:var(--panel);cursor:pointer;border-radius:3px;padding:7px 11px;font-size:13px;text-decoration:none}button:hover,.button:hover,nav a:hover{border-color:var(--accent)}nav a.active,nav button.active{background:var(--accent);color:var(--bg);border-color:var(--accent)}:focus-visible{outline:2px solid var(--accent);outline-offset:4px}.secondary{margin-bottom:8px}.secondary a,.secondary button{padding:6px 10px;font-size:12px}main{scrollbar-gutter:stable;flex-direction:column;min-width:0;min-height:0;display:flex;overflow:auto}main>*{flex-shrink:0}.panel{border:1px solid var(--edge);border-top:2px solid var(--accent);background:var(--panel);border-radius:4px;min-width:0;padding:clamp(10px,1vw,16px)}h1{color:var(--accent);font-size:20px}h2{letter-spacing:.04em;color:var(--accent);overflow-wrap:anywhere;margin:0 0 10px;font-size:13px}h3{overflow-wrap:anywhere;font-size:13px}p{margin:6px 0;font-size:13px;line-height:1.45}a{color:var(--accent)}.muted,.eyebrow{color:var(--muted)}.notice{color:#ffe0aa;background:#332611;border-left:3px solid #ffc26e;padding:12px 16px}.empty{color:var(--muted);padding:35px 0}.quota-grid{flex:1 0 auto;grid-auto-rows:minmax(220px,1fr);gap:10px;display:grid}.quota-card{flex-direction:column;min-height:0;display:flex}.quota-card>:not(.meter-graphic){flex-shrink:0}.meter-graphic{flex-direction:column;flex:1;min-height:100px;display:flex}.bar-graphic{min-height:65px}.quota-card .gauge{flex:2;height:auto;min-height:16px;margin:7px 0}.quota-card .timeline{flex:1;min-height:8px}.quota-card .pace{flex:1;height:auto;min-height:20px;margin:20px 14px 8px}.quota-card .readout{margin:6px 0;font-size:clamp(18px,2vw,28px)}.quota-grid.zone{grid-template-columns:repeat(auto-fit,minmax(min(100%,420px),1fr));grid-auto-rows:minmax(330px,1fr)}.quota-grid.radial{grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr));grid-auto-rows:minmax(260px,1fr)}.gauge{background:var(--edge);height:35px;margin:15px 0;overflow:hidden}.gauge>div{background:repeating-linear-gradient(90deg, var(--accent) 0, var(--accent) 9px, transparent 9px, transparent 12px);height:100%;transition:width .5s}.gauge.timeline{height:14px}.pie-wrap{flex:1;min-height:100px;margin:6px 0;position:relative}.pie-wrap svg{width:100%;height:100%;position:absolute;inset:0}.pie-base{fill:var(--edge);stroke:var(--accent);stroke-width:1px}.pie-fill{fill:var(--accent)}.pace{background:linear-gradient(90deg, #6d3838, var(--edge) 50%, #376348);height:28px;margin:28px 14px 15px;position:relative}.pace-mid{border-left:2px solid var(--ink);height:100%;position:absolute;left:50%}.pace-marker{color:var(--accent);font-size:27px;position:absolute;top:-16px;transform:translate(-50%)}.readout{overflow-wrap:anywhere;color:var(--accent);margin:18px 0;font-size:clamp(22px,3vw,34px)}.credit+.credit{border-top:1px solid var(--edge);padding-top:20px}.credit{margin-top:24px}.session-row{grid-template-columns:minmax(210px,1.1fr) minmax(0,1.2fr) minmax(0,1.5fr);align-items:stretch;gap:14px;margin:18px 0;display:grid}.session-totals{grid-template-columns:repeat(auto-fit,minmax(min(145px,100%),1fr));gap:8px;margin:8px 0;display:grid}.session-totals>div{border:1px solid var(--edge);min-width:0;padding:10px}.session-totals dt{color:var(--muted);font-size:11px}.session-totals dd{color:var(--accent);overflow-wrap:anywhere;margin:6px 0 0;font-size:24px}.session-totals.stale dd{color:var(--muted)}.session-row.selected{outline:1px solid var(--accent);outline-offset:4px}.session-row.wide .context{grid-column:2/-1}.detail-controls{flex-wrap:wrap;gap:6px;display:flex}.attention-summary{flex-wrap:wrap;gap:8px;margin-block:8px;display:flex}.attention-summary .approval{color:var(--approval);border-color:var(--approval)}.attention-summary .approval:hover{background:color-mix(in srgb, var(--approval) 12%, var(--panel))}.attention-summary .approval:focus-visible{outline-color:var(--approval)}.attention-note,.attention-badge{color:var(--accent);border-left:3px solid;padding-left:8px}.attention-note.inferred{color:var(--muted)}.attention-badge{font-size:12px}.session-select{text-align:left;overflow-wrap:anywhere;max-width:100%}.status-detail{color:inherit;text-decoration:none}.status-detail:hover{text-decoration:underline}button:disabled{opacity:.4;cursor:default}.session-row .graph-panel{flex-direction:column;grid-column:2/-1;display:flex}.session-row.split .graph-panel{grid-column:auto}.session-row .button,.session-row button{margin-top:8px;display:inline-block}.context pre{max-height:230px;overflow:auto}.session-row .context{flex-direction:column;display:flex}pre{font:inherit;white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px;line-height:1.7}.full-detail{margin-top:6px}.detail-heading{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:6px 12px;display:flex}.detail-heading h2{flex:250px;min-width:0;margin:0}.detail-metadata{overflow-wrap:anywhere}.detail-workspace{border-top:1px solid var(--edge);flex-direction:column;gap:10px;margin-top:8px;padding-top:4px;display:flex}.detail-context,.detail-workspace .session-actions{min-width:0}.detail-workspace .session-actions{border-top:1px solid var(--edge);padding-top:6px}.full-detail h3{margin-block:8px}.full-detail pre{margin-block:8px;line-height:1.5}.full-detail hr{margin-block:12px}.full-detail .command,.full-detail .notice{padding:8px 10px}.command{background:var(--bg);border-left:3px solid var(--accent);padding:16px}hr{border:0;border-top:1px solid var(--edge);margin:24px 0}.chart{border-bottom:1px solid var(--muted);align-items:flex-end;gap:2px;height:clamp(120px,22vh,320px);margin-top:18px;display:flex}.chart-bar{background:var(--accent);flex:1;min-width:0;max-height:100%}.controls{justify-content:flex-start;margin:20px 0}.summary-grid{grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:16px;margin:20px 0;display:grid}.thresholds-panel{padding:clamp(14px,2vw,22px)}.thresholds-panel h1{margin-top:0}.threshold-list{gap:8px;margin-top:16px;display:grid}.threshold-list article{border:1px solid var(--edge);grid-template-columns:minmax(64px,.5fr) minmax(220px,3fr) minmax(64px,.6fr) minmax(120px,1.2fr);align-items:center;gap:12px;padding:10px 12px;display:grid}.threshold-list article.active{border-color:var(--accent);box-shadow:inset 3px 0 0 var(--accent)}.threshold-list article.next{border-color:var(--approval);box-shadow:inset 3px 0 0 var(--approval)}.threshold-list p{color:var(--muted);margin:3px 0 0;font-size:12px}.threshold-trigger{color:var(--accent);font-size:22px;font-weight:800}.threshold-mode,.threshold-state{color:var(--muted);font-size:12px;font-weight:700}.threshold-list article.active .threshold-state{color:var(--accent)}.threshold-list article.next .threshold-state{color:var(--approval)}.heat-scroll{overflow-x:auto}.heatmap{grid-template-rows:repeat(7,13px);grid-auto-columns:minmax(9px,1fr);grid-auto-flow:column;gap:4px;min-width:620px;margin:25px 0;display:grid}.heat-cell{background:var(--accent);border-radius:2px}.heat-cell.zero{background:var(--edge)}.table-scroll{max-height:280px;overflow:auto}table{border-collapse:collapse;width:100%;margin-top:15px}th,td{text-align:left;border-bottom:1px solid var(--edge);padding:8px}summary{cursor:pointer;color:var(--accent);padding-top:14px}footer{border-top:1px solid var(--edge);color:var(--muted);margin-top:8px;padding-top:8px;font-size:11px}footer select{padding:6px}@media (width<=850px){.session-row{grid-template-columns:minmax(0,1fr)}.session-row.wide .context,.session-row .graph-panel{grid-column:auto}.connection{text-align:left}.threshold-list article{grid-template-columns:64px minmax(0,1fr)}.threshold-mode,.threshold-state{grid-column:2}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition:none!important;animation:none!important}} diff --git a/internal/web/dist/index.html b/internal/web/dist/index.html index cc955b1..c02049b 100644 --- a/internal/web/dist/index.html +++ b/internal/web/dist/index.html @@ -5,8 +5,8 @@ Codexometer // Experimental web - - + +
        diff --git a/internal/web/state.go b/internal/web/state.go index 3dc48a1..6468ac3 100644 --- a/internal/web/state.go +++ b/internal/web/state.go @@ -9,6 +9,7 @@ import ( "time" "github.com/merefield/codexometer/internal/codex" + "github.com/merefield/codexometer/internal/quotagraph" "github.com/merefield/codexometer/internal/statusline" "github.com/merefield/codexometer/internal/version" ) @@ -69,37 +70,22 @@ type meterIdentity struct { kind codex.MeterKind } -type zonePoint struct { - At time.Time `json:"at"` - Elapsed float64 `json:"elapsed"` - Used int `json:"used"` - Break bool `json:"break"` -} +type zonePoint = quotagraph.Point // Bound memory and snapshot size while retaining the original observation. -const maxZonePoints = 720 +// At the default one-minute quota cadence this also preserves a full day for +// the optional Consumption Zone trend. +const maxZonePoints = quotagraph.MaxPoints func observeZone(m meter, previous []meter, now time.Time, gap bool) []zonePoint { - if m.Duration == nil || *m.Duration <= 0 || m.Reset == nil || *m.Reset <= 0 { - return nil - } - x := max(0, min(100, 100*(1-(float64(*m.Reset)-float64(now.Unix()))/(float64(*m.Duration)*60)))) - var points []zonePoint + window := codex.Window{UsedPercent: m.Used, WindowDurationMins: m.Duration, ResetsAt: m.Reset} for _, old := range previous { - if old.identity == m.identity && old.Duration != nil && old.Reset != nil && *old.Duration == *m.Duration && *old.Reset == *m.Reset && len(old.Trail) > 0 { - last := old.Trail[len(old.Trail)-1] - if now.After(last.At) && m.Used >= last.Used && x >= last.Elapsed { - points = append(points, old.Trail...) - } - break + if old.identity == m.identity { + before := codex.Window{UsedPercent: old.Used, WindowDurationMins: old.Duration, ResetsAt: old.Reset} + return quotagraph.Record(window, before, old.Trail, now, gap) } } - points = append(points, zonePoint{At: now, Elapsed: x, Used: m.Used, Break: gap}) - if len(points) > maxZonePoints { - points = append(points[:1], points[len(points)-(maxZonePoints-1):]...) - points[1].Break = true - } - return points + return quotagraph.Record(window, codex.Window{}, nil, now, gap) } type credit struct { diff --git a/intro-post.md b/intro-post.md index e11b4a0..df5df44 100644 --- a/intro-post.md +++ b/intro-post.md @@ -24,7 +24,7 @@ The interface runs locally in a terminal, with clickable and hover-highlighted c The interface has four main tabs: -- **Quota** — switch between Bars, Consumption Pace, Pie, and Fuel Tank presentations. Compare consumption directly with elapsed reset-cycle time, see countdowns and reset dates, and get a pace-aware health signal or an early-exhaustion projection. Learned API-equivalent estimates show both current spend and what 100% of a primary quota window might represent, with conservative confidence and a pricing-source/date footer when space permits. Eligible banked resets can be redeemed with a separate confirmation click. +- **Quota** — switch between Bars, Pace, Zone, Pie, and Fuel Tank presentations. Compare consumption directly with elapsed reset-cycle time, see countdowns and reset dates, and get a pace-aware health signal. Zone and Pace graphs in both terminal and browser trace observed history and project consumption trends, showing either total consumption or distance from safety. The terminal uses Braille lines and dark colour bands; the browser uses smoother SVG with directional arrows. Learned API-equivalent estimates show both current spend and what 100% of a primary quota window might represent, with conservative confidence and a pricing-source/date footer when space permits. Eligible banked resets can be redeemed with a separate confirmation click. - **Sessions** — your session command centre, combining per-session context and eligible live reply/approval controls with metrics and scrolling, auto-scaling graphs on a shared 30-second tick. Compare local token shares, model calls, activity, output size, and time to first token; explicitly linked subagents are folded into their parent session. The wider **SESSION TOTALS** readout keeps a clickable Reset beside it. Page through sessions and dismiss finished rows with `[×]` without closing them: fresh activity brings them back. - **Usage** — explore OpenAI account token history in a GitHub-style daily activity grid, weekly bars, or a cumulative graph. Choose 6 or 12 months (26/52 weeks), browse older periods, and see lifetime tokens, peak usage, longest turn, and activity streaks. New **Breakdown** and **Windows** views, in both terminal and web, show OpenAI's available daily model/surface/feature detail and historical allowance windows. Relative usage, credits, tokens and allowance percentages remain clearly separate. History is fetched directly from OpenAI on each run, with no local usage ledger. Unavailable data and approximate periods are labelled rather than guessed. - **Benchmark** — run programmatically checked challenges across selected model and reasoning-level combinations, then compare outcomes, wall time, tokens, estimated API-equivalent cost, and rankings. @@ -119,9 +119,14 @@ Expiry warnings are reminders to review, not instructions to reset immediately. Prefer a browser window? `codexometer --web` now offers an **experimental, read-only by default** Quota, Sessions and Usage dashboard with the same retro spirit: -responsive gauges, live activity graphs and usage heatmaps. Consumption Zone -now traces the observed quota path, with a marked starting point and gaps for -failed observations. Sessions offers graph-only, split, wide and full-page +responsive gauges, live activity graphs and usage heatmaps. Zone +traces the observed quota path, with a marked starting point and gaps for +failed observations. The separate Pace view shows distance from the safe line, +replacing the former horizontal gauge. Both graphs offer the same +trace and trend controls. Arrowed trends use the +whole-window average by default or an observed recent period; green means +projected usage stays within quota at reset, dark red means it exceeds it. +Sessions offers graph-only, split, wide and full-page detail, with a compact totals strip for observed tokens, listed sessions and separate working/approval/input/inferred-check counts. Linked-agent tokens are already included, and stale observations are labelled. Select sessions with ↑/↓, @@ -253,7 +258,7 @@ Remove-Item $installer The execution-policy override applies only to the installer child process, leaving your current shell's policy and persistent machine/user settings unchanged. `-NoProfile` avoids custom profiles; `-UseBasicParsing` avoids Windows PowerShell 5.1's web-content parsing and associated security prompt. Organisational Group Policy, application-control rules and antivirus protections still apply. Inspect the downloaded script before running it if required by your security policy. -Quit the running dashboard and re-run the relevant installer to upgrade or reinstall Codexometer, then check `codexometer --version`. It replaces the executable only after the downloaded artifact passes its checksum and version checks. Windows defaults to `%LOCALAPPDATA%\Programs\codexometer\bin`; add that to `PATH` if needed. New Go 1.27-based macOS builds require macOS 13 (Ventura) or later. Developers who prefer to build from source can use `go install github.com/merefield/codexometer@latest` with Go 1.27.1 or later, ensuring `GOBIN` (or the default Go bin directory) is on `PATH`. +Quit the running dashboard and re-run the relevant installer to upgrade or reinstall Codexometer, then check `codexometer --version`. It replaces the executable only after the downloaded artifact passes its checksum and version checks. Windows defaults to `%LOCALAPPDATA%\Programs\codexometer\bin`; add that to `PATH` if needed. New Go 1.27-based macOS builds require macOS 13 (Ventura) or later. Developers who prefer to build from source can use `go install github.com/merefield/codexometer@latest` with Go 1.27.2 or later, ensuring `GOBIN` (or the default Go bin directory) is on `PATH`. Full installation, authentication, privacy, monitoring, and benchmarking guidance is available in the [README](https://github.com/merefield/codexometer#readme). diff --git a/web/src/ConsumptionZone.svelte b/web/src/ConsumptionZone.svelte index 4fbeeb1..6b56891 100644 --- a/web/src/ConsumptionZone.svelte +++ b/web/src/ConsumptionZone.svelte @@ -5,10 +5,26 @@ used, elapsed, trail = [], - }: { used: number; elapsed: number; trail?: Meter['trail'] } = $props(); + duration, + paceView = false, + }: { + used: number; + elapsed: number; + trail?: Meter['trail']; + duration: number | null; + paceView?: boolean; + } = $props(); + type TrailPoint = NonNullable[number]; + type TrendMode = 'off' | 'halfHour' | 'hour' | 'day' | 'window'; const gradient = $props.id(); let showObservations = $state(false); + let showTrace = $state(true); + let trendMode = $state('window'); const ticks = [0, 25, 50, 75, 100]; + const trendMinutes = { off: 0, halfHour: 30, hour: 60, day: 1440, window: 0 }; + let yTicks = $derived(paceView ? [-100, -50, 0, 50, 100] : ticks); + let minimum = $derived(paceView ? -100 : 0); + let range = $derived(paceView ? 200 : 100); let width = $state(400); let height = $state(240); let right = $derived(width - 24); @@ -18,34 +34,162 @@ let x = $derived( 48 + (Math.max(0, Math.min(100, elapsed)) / 100) * plotWidth, ); - let y = $derived( - bottom - (Math.max(0, Math.min(100, used)) / 100) * plotHeight, - ); + function plotY(value: number): number { + return bottom - ((value - minimum) / range) * plotHeight; + } + function graphValue(consumption: number, time: number): number { + return paceView ? consumption - time : consumption; + } + let y = $derived(plotY(graphValue(used, elapsed))); let difference = $derived(used - elapsed); let path = $derived( trail .map( (p, i) => - `${i === 0 || p.break ? 'M' : 'L'}${48 + (p.elapsed / 100) * plotWidth} ${bottom - (p.used / 100) * plotHeight}`, + `${i === 0 || p.break ? 'M' : 'L'}${48 + (p.elapsed / 100) * plotWidth} ${plotY(graphValue(p.used, p.elapsed))}`, ) .join(' '), ); + let continuous = $derived.by(() => { + let start = 0; + for (let i = trail.length - 1; i > 0; i--) { + if (trail[i].break) { + start = i; + break; + } + } + return trail.slice(start); + }); + let observedMilliseconds = $derived.by(() => { + if (continuous.length < 2) return 0; + return Math.max( + 0, + Date.parse(continuous.at(-1)!.at) - Date.parse(continuous[0].at), + ); + }); + function observedPeriod(span: number): boolean { + if (observedMilliseconds < span || continuous.length < 2) return false; + const cutoff = Date.parse(continuous.at(-1)!.at) - span; + return ( + continuous.filter((point) => Date.parse(point.at) >= cutoff).length >= 2 + ); + } + let availability = $derived({ + off: true, + halfHour: observedPeriod(30 * 60_000), + hour: observedPeriod(60 * 60_000), + day: observedPeriod(24 * 60 * 60_000), + window: Number.isFinite(elapsed) && elapsed > 0 && Number.isFinite(used), + }); + $effect(() => { + if (trendMode !== 'window' && !availability[trendMode]) + trendMode = 'window'; + }); + function trendPoints(mode: TrendMode): TrailPoint[] { + if (mode === 'off' || !availability[mode]) return []; + const span = trendMinutes[mode] * 60_000; + const cutoff = Date.parse(continuous.at(-1)!.at) - span; + const start = continuous.findIndex( + (point) => Date.parse(point.at) >= cutoff, + ); + if (start < 0) return []; + return continuous.slice(start); + } + function regressionSlope(points: TrailPoint[]): number | null { + if (points.length < 2) return null; + const xMean = + points.reduce((sum, point) => sum + point.elapsed, 0) / points.length; + const yMean = + points.reduce((sum, point) => sum + point.used, 0) / points.length; + let covariance = 0; + let variance = 0; + for (const point of points) { + covariance += (point.elapsed - xMean) * (point.used - yMean); + variance += (point.elapsed - xMean) ** 2; + } + return variance > 0 ? Math.max(0, covariance / variance) : null; + } + let trend = $derived.by(() => { + const slope = + trendMode === 'window' && availability.window + ? used / elapsed + : regressionSlope(trendPoints(trendMode)); + if (slope === null || !duration) return null; + const projected = used + slope * (100 - elapsed); + const intercept = trendMode === 'window' ? 0 : used - slope * elapsed; + const graphSlope = slope - (paceView ? 1 : 0); + let x1 = Math.max(0, elapsed - (trendMinutes[trendMode] / duration) * 100); + if (trendMode === 'window') x1 = 0; + let x2 = 100; + if (graphSlope !== 0) { + const lower = (minimum - intercept) / graphSlope; + const upper = (minimum + range - intercept) / graphSlope; + x1 = Math.max(x1, Math.min(lower, upper)); + x2 = Math.min(100, Math.max(lower, upper)); + } + if (x1 > x2) return null; + const y1 = intercept + graphSlope * x1; + const y2 = intercept + graphSlope * x2; + const pixelLength = Math.hypot( + ((x2 - x1) / 100) * plotWidth, + ((y2 - y1) / range) * plotHeight, + ); + const vertices = Math.max(3, Math.min(9, Math.floor(pixelLength / 65) + 2)); + const points = Array.from({ length: vertices }, (_, index) => { + const px = x1 + ((x2 - x1) * index) / (vertices - 1); + const py = y1 + ((y2 - y1) * index) / (vertices - 1); + return `${48 + (px / 100) * plotWidth} ${plotY(py)}`; + }); + return { + path: `M${points.join(' L')}`, + projected, + safe: projected <= 100, + }; + }); + let observedLabel = $derived.by(() => { + if (continuous.length === 0) return 'NO OBSERVATIONS'; + if (continuous.length === 1) return 'ONE OBSERVATION'; + const minutes = Math.floor(observedMilliseconds / 60_000); + if (minutes < 60) return `${minutes} MIN CONTINUOUS`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}H CONTINUOUS`; + return `${Math.floor(hours / 24)}D ${hours % 24}H CONTINUOUS`; + });
        0 ? 'Above' : difference < 0 ? 'Below' : 'On'} the steady-consumption line.`} + aria-label={`${paceView ? 'Pace zone' : 'Consumption zone'}: ${used}% consumed, ${elapsed.toFixed(1)}% of quota period elapsed. ${paceView ? `${difference.toFixed(1)} percentage points from safety.` : `${difference > 0 ? 'Above' : difference < 0 ? 'Below' : 'On'} the steady-consumption line.`}${showTrace && trail.length ? ` Trace path contains ${trail.length} ${trail.length === 1 ? 'observation' : 'observations'}; gaps are not interpolated.` : ''}`} > - + - + {#if paceView}{/if} + + + + - {tick}% - {tick}% + {paceView ? `${tick > 0 ? '+' : ''}${tick}` : `${tick}%`} {/each} - - {#if trail.length} + + {#if paceView}SAFE{/if} + {#if showTrace && trail.length} + First observation: {date(trail[0].at)} {/if} - CONSUMPTION + {#if trend} + + {trendMode === 'window' + ? 'Window-average pace' + : 'Recent consumption trajectory'} projects {trend.projected.toFixed( + 1, + )}% at reset if continued + {/if} + {paceView ? 'DISTANCE FROM SAFETY (PP)' : 'CONSUMPTION'} - {used}% consumed // {elapsed.toFixed(1)}% of period elapsed + {used}% consumed // {elapsed.toFixed(1)}% of period elapsed{paceView + ? ` // ${difference.toFixed(1)} PP from safety` + : ''}
        -

        - {used}% USED // {elapsed.toFixed(1)}% TIME ELAPSED
        - {Math.abs(difference) < 0.05 - ? 'ON PACE' - : difference > 0 - ? 'ABOVE THE LINE — CONSUMING FASTER THAN TIME' - : 'BELOW THE LINE — WITHIN PACE'} -

        -{#if trail.length}

        - ○ START {date(trail[0].at)} // {trail.length} - {trail.length === 1 ? 'OBSERVATION' : 'OBSERVATIONS'}
        Observed quota - path, not individual session usage. Gaps are not interpolated. Expand the - observation table for times, positions and gaps. +

        +{#if trail.length}
        + OBSERVATION DATA {#if showObservations}
        @@ -188,6 +398,10 @@ font-size: 12px; letter-spacing: 0.04em; } + .safe-label { + fill: #fff; + font-size: 11px; + } .grid { stroke: #fff; stroke-opacity: 0.14; @@ -213,21 +427,91 @@ } .observation-trail { fill: none; - stroke: white; + stroke: #101820; stroke-width: 2.5; stroke-linejoin: round; } + .observation-trail-casing { + fill: none; + stroke: rgb(255 255 255 / 45%); + stroke-width: 4.5; + stroke-linejoin: round; + } .trail-start { + fill: #101820; + stroke: rgb(255 255 255 / 70%); + stroke-width: 2; + } + .trend-line { fill: none; - stroke: white; + stroke: var(--trend-color); stroke-width: 2; + stroke-dasharray: 3 6; } - .trail-caption { - font-size: 11px; - text-align: center; + .trend-arrow { + fill: var(--trend-color); + stroke: rgb(255 255 255 / 45%); + stroke-width: 0.5; + } + .trend-casing { + fill: none; + stroke: rgb(255 255 255 / 45%); + stroke-width: 4; + stroke-dasharray: 3 6; } .zone-caption { + flex: 1 1 auto; + min-width: 0; color: var(--accent); - text-align: center; + margin: 0; + } + .trend-summary { + color: var(--ink); + } + .zone-footer { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; + margin-top: 4px; + } + .zone-controls { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 5px; + color: var(--muted); + font-size: 11px; + text-align: right; + } + .trace-control, + .trend-control { + cursor: pointer; + white-space: nowrap; + } + .trace-control input { + accent-color: var(--accent); + } + .trend-control select { + max-width: min(230px, 45vw); + border: 1px solid var(--edge); + background: var(--panel); + color: var(--ink); + font: inherit; + } + .trend-control option:disabled { + color: var(--muted); + } + .zone-controls small { + font-size: 10px; + } + @container (max-width: 560px) { + .zone-footer { + flex-direction: column; + } + .zone-controls { + align-self: stretch; + align-items: flex-end; + } } diff --git a/web/src/Quota.svelte b/web/src/Quota.svelte index 09764b4..9890dc8 100644 --- a/web/src/Quota.svelte +++ b/web/src/Quota.svelte @@ -40,13 +40,7 @@ href={'#/quota/' + item} class:active={view === item} aria-current={view === item ? 'page' : undefined} - >{item === 'pace' - ? 'CONSUMPTION PACE' - : item === 'zone' - ? 'CONSUMPTION ZONE' - : item === 'fuel' - ? 'FUEL TANK' - : item.toUpperCase()}{item === 'fuel' ? 'FUEL TANK' : item.toUpperCase()}{/each} {#if live.data} @@ -81,12 +75,11 @@ {:else}
        {#each live.data.meters as meter} {@const cycle = elapsed(meter)} - {@const pace = cycle === null ? null : cycle - meter.used}

        {meter.name}

        @@ -119,39 +112,19 @@ />{/if}
        - {:else if view === 'zone'} + {:else if view === 'zone' || view === 'pace'} {#if cycle !== null} {:else}

        Cycle duration or reset date unavailable — position cannot be plotted.

        {/if} - {:else if view === 'pace'} - {#if pace !== null} -
        -
        - ▼ -
        -
        - −100 // OVER BUDGET+100 // HEADROOM -
        -

        - {pace >= 0 ? '+' : ''}{pace.toFixed(1)} PP - {pace >= 0 - ? 'WITHIN PACE' - : 'USING FASTER THAN TIME'} -

        - {:else}

        - Cycle duration unavailable — pace cannot be calculated. -

        {/if} {:else}
        :not(.meter-graphic) { flex-shrink: 0; @@ -253,16 +254,6 @@ a { flex: 1; min-height: 8px; } -.quota-card .pace { - flex: 1; - height: auto; - min-height: 20px; - margin: 20px 14px 8px; -} -.quota-card .readout { - margin: 6px 0; - font-size: clamp(18px, 2vw, 28px); -} .quota-grid.zone { grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr)); grid-auto-rows: minmax(330px, 1fr); @@ -311,25 +302,6 @@ a { .pie-fill { fill: var(--accent); } -.pace { - height: 28px; - margin: 28px 14px 15px; - position: relative; - background: linear-gradient(90deg, #6d3838, var(--edge) 50%, #376348); -} -.pace-mid { - position: absolute; - left: 50%; - border-left: 2px solid var(--ink); - height: 100%; -} -.pace-marker { - position: absolute; - transform: translateX(-50%); - top: -16px; - color: var(--accent); - font-size: 27px; -} .readout { overflow-wrap: anywhere; font-size: clamp(22px, 3vw, 34px); diff --git a/web/tests/browser.spec.ts b/web/tests/browser.spec.ts index 1c682d2..7bc31a8 100644 --- a/web/tests/browser.spec.ts +++ b/web/tests/browser.spec.ts @@ -1886,15 +1886,14 @@ test('pairing, all quota views, navigation and refresh', async ({ await expect(page.getByRole('meter').first()).toBeVisible(); await page.getByRole('link', { name: 'PIE', exact: true }).click(); await expect(page.locator('svg')).toHaveCount(2); - await page - .getByRole('link', { name: 'CONSUMPTION PACE', exact: true }) - .click(); - await expect(page.locator('.pace')).toHaveCount(2); - await expect(page.getByText('−100 // OVER BUDGET')).toHaveCount(2); - await expect(page.getByText('+100 // HEADROOM')).toHaveCount(2); - await page - .getByRole('link', { name: 'CONSUMPTION ZONE', exact: true }) - .click(); + await page.getByRole('link', { name: 'PACE', exact: true }).click(); + await expect(page.locator('.consumption-zone')).toHaveCount(2); + await expect( + page.getByText('DISTANCE FROM SAFETY (PP)', { exact: true }), + ).toHaveCount(2); + await expect(page.locator('.pace-marker')).toHaveCount(0); + await expect(page.getByRole('radio')).toHaveCount(0); + await page.getByRole('link', { name: 'ZONE', exact: true }).click(); await expect(page.locator('.consumption-zone')).toHaveCount(2); await expect(page.locator('.position-dot')).toHaveCount(2); await page.getByRole('link', { name: 'FUEL TANK', exact: true }).click(); @@ -2256,9 +2255,7 @@ test('consumption zone plots bounded coordinates and handles missing windows', a }), ); await page.goto(pairingURL); - await page - .getByRole('link', { name: 'CONSUMPTION ZONE', exact: true }) - .click(); + await page.getByRole('link', { name: 'ZONE', exact: true }).click(); await expect(page.locator('.consumption-zone')).toHaveCount(3); expect( await page.locator('.position-dot').evaluateAll((nodes) => @@ -2305,6 +2302,375 @@ test('consumption zone plots bounded coordinates and handles missing windows', a }); }); +test('Zone and Pace distinguish empty, single and continuous observations', async ({ + page, + pairingURL, +}) => { + const now = new Date('2026-09-11T12:00:00Z'); + await page.clock.setFixedTime(now); + const point = (minutesAgo: number, elapsed: number) => ({ + at: new Date(now.getTime() - minutesAgo * 60_000).toISOString(), + elapsed, + used: 25, + break: false, + }); + const snapshot = { + version: 'test', + credits: [], + creditCount: 0, + sessions: [], + usage: null, + quotaAt: now.toISOString(), + sessionsAt: '', + usageAt: '', + quotaError: false, + sessionsError: false, + usageError: false, + meters: [ + { + name: 'Weekly', + used: 25, + duration: 100, + reset: Math.floor(now.getTime() / 1000) + 3000, + details: '', + trail: [] as ReturnType[], + }, + ], + }; + await mockStream(page, snapshot); + await page.goto(pairingURL); + for (const view of ['ZONE', 'PACE']) { + await page.getByRole('link', { name: view, exact: true }).click(); + for (const [trail, label] of [ + [[], 'NO OBSERVATIONS'], + [[point(0, 50)], 'ONE OBSERVATION'], + [[point(30, 20), point(0, 50)], '30 MIN CONTINUOUS'], + [[], 'NO OBSERVATIONS'], + ] as const) { + snapshot.meters[0].trail = [...trail]; + await page.evaluate( + (snapshot) => + window.dispatchEvent( + new CustomEvent('test-snapshot', { detail: snapshot }), + ), + snapshot, + ); + await expect(page.locator('.zone-controls small')).toHaveText( + 'HISTORY // ' + label, + ); + } + } +}); + +test('consumption zone offers trace control and observed trend periods', async ({ + page, + pairingURL, +}) => { + const now = new Date('2026-09-11T12:00:00Z'); + await page.clock.setFixedTime(now); + const end = Math.floor(now.getTime() / 1000); + const point = (hoursAgo: number, elapsed: number, used: number) => ({ + at: new Date(now.getTime() - hoursAgo * 60 * 60 * 1000).toISOString(), + elapsed, + used, + break: false, + }); + const snapshot = { + version: 'test', + credits: [], + creditCount: 0, + sessions: [], + usage: null, + quotaAt: now.toISOString(), + sessionsAt: '', + usageAt: '', + quotaError: false, + sessionsError: false, + usageError: false, + meters: [ + { + name: 'Weekly', + used: 40, + duration: 7 * 24 * 60, + reset: end + 3 * 24 * 60 * 60, + details: '', + trail: [ + point(25, 42, 20), + point(24, 42.6, 21), + point(1, 56.5, 38), + point(0.25, 56.95, 39), + point(0, 57.1, 40), + ], + }, + ], + }; + await mockStream(page, snapshot); + await page.goto(pairingURL); + await page.getByRole('link', { name: 'ZONE', exact: true }).click(); + + const graph = page.locator('.consumption-zone'); + const trend = page.getByLabel('Trend period'); + const trace = page.getByRole('checkbox', { name: 'TRACE PATH' }); + await expect(trace).toBeChecked(); + await expect(page.locator('.observation-trail')).toBeVisible(); + await expect(page.locator('.observation-trail-casing')).toBeVisible(); + await expect(page.locator('.observation-details summary')).toBeVisible(); + await expect(page.locator('.trend-line')).toBeVisible(); + await expect(trend).toHaveValue('window'); + await expect(trend.locator('option[value="halfHour"]')).not.toHaveAttribute( + 'disabled', + ); + await expect(trend.locator('option[value="hour"]')).not.toHaveAttribute( + 'disabled', + ); + await expect(trend.locator('option[value="day"]')).not.toHaveAttribute( + 'disabled', + ); + await expect(trend.locator('option[value="window"]')).not.toHaveAttribute( + 'disabled', + ); + await trace.uncheck(); + await expect(page.locator('.observation-trail')).toHaveCount(0); + await expect(page.locator('.observation-trail-casing')).toHaveCount(0); + await expect(page.locator('.observation-details summary')).toBeVisible(); + await trace.check(); + await expect(page.locator('.observation-trail')).toBeVisible(); + await expect(page.locator('.observation-trail-casing')).toBeVisible(); + await expect(graph).toHaveAttribute('aria-label', /5 observations/); + + await trend.selectOption('off'); + await expect(page.locator('.trend-line')).toHaveCount(0); + async function expectTrendStart(minutes: number) { + const width = Number( + await page.locator('.zone-field').getAttribute('width'), + ); + const expectedX = + 48 + (((4 / 7) * 100 - (minutes / (7 * 24 * 60)) * 100) / 100) * width; + const startX = Number( + (await page.locator('.trend-line').getAttribute('d'))!.match( + /^M([\d.]+)/, + )![1], + ); + expect(startX).toBeCloseTo(expectedX, 5); + } + await trend.selectOption('halfHour'); + await expect(page.locator('.trend-line')).toBeVisible(); + await expectTrendStart(30); + await trend.selectOption('hour'); + await expect(page.locator('.trend-line')).toBeVisible(); + await expectTrendStart(60); + await expect(page.locator('.trend-arrow')).toBeAttached(); + await expect( + page.getByText(/PROJECTED AT RESET|EXHAUSTION PROJECTED/), + ).toBeVisible(); + await trend.selectOption('day'); + await expect(page.locator('.trend-line')).toBeVisible(); + await expectTrendStart(24 * 60); + await page.screenshot({ + path: 'test-results/consumption-zone-trend.png', + fullPage: true, + }); + + // Window-average pace uses the known current dot, even with no history. + snapshot.meters[0].trail = []; + await page.evaluate( + (detail) => + window.dispatchEvent(new CustomEvent('test-snapshot', { detail })), + snapshot, + ); + await expect(trend).toHaveValue('window'); + await expect(trend.locator('option[value="halfHour"]')).toHaveAttribute( + 'disabled', + ); + await trend.selectOption('window'); + const line = page.locator('.trend-line'); + await expect(line).toBeVisible(); + const axes = await page.locator('.axes').getAttribute('d'); + const bottom = /V([\d.]+)/.exec(axes!)![1]; + expect(await line.getAttribute('d')).toMatch(new RegExp(`^M48 ${bottom} L`)); + await expect(page.locator('.trend-summary')).toHaveText( + 'TREND // 70.0% PROJECTED AT RESET', + ); + + // No consumption yields a horizontal line; zero elapsed time has no rate. + snapshot.meters[0].used = 0; + await page.evaluate( + (detail) => + window.dispatchEvent(new CustomEvent('test-snapshot', { detail })), + snapshot, + ); + await expect(page.locator('.trend-summary')).toHaveText( + 'TREND // 0.0% PROJECTED AT RESET', + ); + expect(await line.getAttribute('d')).not.toMatch(/NaN|Infinity/); + snapshot.meters[0].reset = end + 7 * 24 * 60 * 60; + await page.evaluate( + (detail) => + window.dispatchEvent(new CustomEvent('test-snapshot', { detail })), + snapshot, + ); + await expect(trend).toHaveValue('window'); + await expect(trend.locator('option[value="window"]')).toHaveAttribute( + 'disabled', + ); + await expect(trend.locator('option[value="window"]')).toContainText( + 'NO TIME ELAPSED', + ); + await expect(line).toHaveCount(0); + // The default resumes automatically once the new window has elapsed time. + snapshot.meters[0].used = 40; + snapshot.meters[0].reset = end + 3 * 24 * 60 * 60; + await page.evaluate( + (detail) => + window.dispatchEvent(new CustomEvent('test-snapshot', { detail })), + snapshot, + ); + await expect(trend).toHaveValue('window'); + await expect(line).toBeVisible(); +}); + +test('pace zone transforms the dot and trace and colours trends by their endpoint', async ({ + page, + pairingURL, +}) => { + const now = new Date('2026-09-11T12:00:00Z'); + await page.clock.setFixedTime(now); + const elapsed = (4 / 7) * 100; + const meter = { + name: 'Weekly', + used: 80, + duration: 7 * 24 * 60, + reset: now.getTime() / 1000 + 3 * 24 * 60 * 60, + details: '', + trail: [{ at: now.toISOString(), elapsed, used: 80, break: false }], + }; + const snapshot = { + version: 'test', + credits: [], + creditCount: 0, + sessions: [], + usage: null, + quotaAt: now.toISOString(), + sessionsAt: '', + usageAt: '', + quotaError: false, + sessionsError: false, + usageError: false, + meters: [meter], + }; + await mockStream(page, snapshot); + await page.goto(pairingURL); + await page.getByRole('link', { name: 'ZONE', exact: true }).click(); + const consumption = page.getByRole('link', { + name: 'ZONE', + exact: true, + }); + const pace = page.getByRole('link', { name: 'PACE', exact: true }); + const line = page.locator('.trend-line'); + const dot = page.locator('.position-dot'); + const safe = page.locator('.pace-line'); + const trend = page.getByLabel('Trend period'); + await expect(consumption).toHaveAttribute('aria-current', 'page'); + await expect(pace).not.toHaveAttribute('aria-current', 'page'); + await expect(line).toHaveCSS('stroke', 'rgb(127, 24, 37)'); + const originalX = await dot.getAttribute('cx'); + await pace.click(); + await expect(consumption).not.toHaveAttribute('aria-current', 'page'); + await expect(pace).toHaveAttribute('aria-current', 'page'); + await expect(dot).toHaveAttribute('cx', originalX!); + await expect(page.locator('.axis-title').first()).toHaveText( + 'DISTANCE FROM SAFETY (PP)', + ); + const safeY = Number(await safe.getAttribute('y1')); + expect(Number(await safe.getAttribute('y2'))).toBe(safeY); + const bottom = Number( + /V([\d.]+)/.exec((await page.locator('.axes').getAttribute('d'))!)![1], + ); + const expectedY = bottom - ((80 - elapsed + 100) / 200) * (bottom - 20); + expect(Number(await dot.getAttribute('cy'))).toBeCloseTo(expectedY, 5); + expect(expectedY).toBeLessThan(safeY); + expect(await page.locator('.observation-trail').getAttribute('d')).toBe( + `M${originalX} ${expectedY}`, + ); + const gradient = page.locator('linearGradient'); + await expect(gradient).toHaveAttribute('x2', '0%'); + await expect(page.locator('.trend-summary')).toHaveText( + 'TREND // +40.0 PP AT RESET', + ); + await expect(line).toHaveCSS('stroke', 'rgb(127, 24, 37)'); + + async function update() { + await page.evaluate( + (detail) => + window.dispatchEvent(new CustomEvent('test-snapshot', { detail })), + snapshot, + ); + } + function recentSlope(slope: number) { + const delta = (0.5 / (7 * 24)) * 100; + meter.trail = [ + { + at: new Date(now.getTime() - 30 * 60_000).toISOString(), + elapsed: elapsed - delta, + used: 80 - slope * delta, + break: false, + }, + { at: now.toISOString(), elapsed, used: 80, break: false }, + ]; + } + // Improving towards safety is still red if it ends above zero at reset. + recentSlope(0.8); + await update(); + await trend.selectOption('halfHour'); + await expect(page.locator('.trend-summary')).toHaveText( + 'TREND // +14.3 PP AT RESET', + ); + await expect(line).toHaveCSS('stroke', 'rgb(127, 24, 37)'); + let coords = (await line.getAttribute('d'))!.match(/[\d.-]+/g)!.map(Number); + const plotWidth = Number( + await page.locator('.zone-field').getAttribute('width'), + ); + const startX = 48 + ((elapsed - (0.5 / (7 * 24)) * 100) / 100) * plotWidth; + expect(coords[0]).toBeCloseTo(startX, 5); + expect(coords.at(-2)!).toBeCloseTo(48 + plotWidth, 5); + expect(coords.at(-1)!).toBeGreaterThan(coords[1]); + expect(coords.at(-1)!).toBeLessThan(safeY); + + // Slow enough to recover: green, with the endpoint below the safe line. + recentSlope(0.1); + await update(); + await expect(line).toHaveCSS('stroke', 'rgb(17, 91, 53)'); + await expect(page.locator('.trend-arrow')).toHaveCSS( + 'fill', + 'rgb(17, 91, 53)', + ); + await expect(page.locator('.trend-summary')).toHaveText( + 'TREND // -15.7 PP AT RESET', + ); + coords = (await line.getAttribute('d'))!.match(/[\d.-]+/g)!.map(Number); + expect(coords.at(-1)!).toBeGreaterThan(safeY); + await page.screenshot({ + path: 'test-results/consumption-zone-pace.png', + fullPage: true, + }); + await consumption.click(); + await expect(trend).toHaveValue('halfHour'); + await expect(gradient).toHaveAttribute('x2', '100%'); + await expect(line).toHaveCSS('stroke', 'rgb(17, 91, 53)'); + + // Exactly 100% projected consumption is within safety in either view. + meter.used = elapsed; + await update(); + await trend.selectOption('window'); + await expect(line).toHaveCSS('stroke', 'rgb(17, 91, 53)'); + await pace.click(); + await expect(page.locator('.trend-summary')).toHaveText( + 'TREND // 0.0 PP AT RESET', + ); + await expect(line).toHaveCSS('stroke', 'rgb(17, 91, 53)'); + expect(await line.getAttribute('d')).not.toMatch(/NaN|Infinity/); +}); + test('quota graphics use viewport height and keep compact navigation accessible', async ({ page, pairingURL, @@ -2313,8 +2679,8 @@ test('quota graphics use viewport height and keep compact navigation accessible' await page.goto(pairingURL); for (const [name, graphic] of [ ['BARS', '.gauge:not(.timeline)'], - ['CONSUMPTION PACE', '.pace'], - ['CONSUMPTION ZONE', '.zone-canvas'], + ['PACE', '.zone-canvas'], + ['ZONE', '.zone-canvas'], ['PIE', '.pie-wrap'], ['FUEL TANK', '.gauge:not(.timeline)'], ]) { @@ -2343,9 +2709,7 @@ test('quota graphics use viewport height and keep compact navigation accessible' await expect(plot).toBeVisible(); } await page.setViewportSize({ width: 1440, height: 900 }); - await page - .getByRole('link', { name: 'CONSUMPTION ZONE', exact: true }) - .click(); + await page.getByRole('link', { name: 'ZONE', exact: true }).click(); await expect(page.locator('.zone-canvas')).toHaveCount(2); await page.screenshot({ path: 'test-results/responsive-quota.png' }); }); @@ -2821,10 +3185,10 @@ test('zone trail renders start, gap and live updates across navigation and reloa }; await mockStream(page, snapshot); await page.goto(pairingURL); - await page - .getByRole('link', { name: 'CONSUMPTION ZONE', exact: true }) - .click(); + await page.getByRole('link', { name: 'ZONE', exact: true }).click(); const trail = page.locator('.observation-trail'); + const trace = page.getByRole('checkbox', { name: 'TRACE PATH' }); + await expect(trace).toBeChecked(); await expect(page.locator('.trail-start')).toHaveCount(1); expect((await trail.getAttribute('d'))?.match(/M/g)).toHaveLength(2); expect((await trail.getAttribute('d'))?.match(/L/g)).toHaveLength(1); @@ -2848,9 +3212,17 @@ test('zone trail renders start, gap and live updates across navigation and reloa await expect(dataRows.nth(2)).toContainText('Gap before this observation'); await page.getByRole('link', { name: 'SESSIONS', exact: true }).click(); await page.getByRole('link', { name: 'QUOTA', exact: true }).click(); + await expect(trace).toBeChecked(); await expect(trail).toHaveCount(1); await page.reload(); - await expect(page.locator('.trail-caption')).toContainText('3 OBSERVATIONS'); + await expect(trace).toBeChecked(); + await expect(trail).toHaveCount(1); + await expect(page.locator('.consumption-zone')).toHaveAttribute( + 'aria-label', + /3 observations/, + ); + await page.locator('.observation-details summary').click(); + await expect(table).toBeVisible(); snapshot.meters[0].trail.push({ at: '2026-09-10T16:00:00Z', elapsed: 50, @@ -2862,8 +3234,10 @@ test('zone trail renders start, gap and live updates across navigation and reloa window.dispatchEvent(new CustomEvent('test-snapshot', { detail })), snapshot, ); - await expect(page.locator('.trail-caption')).toContainText('4 OBSERVATIONS'); - await page.locator('.observation-details summary').click(); + await expect(page.locator('.consumption-zone')).toHaveAttribute( + 'aria-label', + /4 observations/, + ); await expect(table.locator('tbody tr')).toHaveCount(4); snapshot.meters[0].trail = [snapshot.meters[0].trail[3]]; await page.evaluate( @@ -2871,9 +3245,9 @@ test('zone trail renders start, gap and live updates across navigation and reloa window.dispatchEvent(new CustomEvent('test-snapshot', { detail })), snapshot, ); - await expect(page.locator('.trail-caption')).toContainText('1 OBSERVATION'); - await expect(page.locator('.trail-caption')).not.toContainText( - '1 OBSERVATIONS', + await expect(page.locator('.consumption-zone')).toHaveAttribute( + 'aria-label', + /1 observation;/, ); await expect(table.locator('tbody tr')).toHaveCount(1); });
        Quota observations