diff --git a/CHANGELOG.md b/CHANGELOG.md index b6204d0..04620a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ This project follows [Semantic Versioning](https://semver.org/). From **v1.0.0** ### Added -- **Web UI (`flightdeck serve`):** **`/#/settings`** for appearance (Light / Dark / System, **`flightdeck-theme`**); collapsible sidebar (**`flightdeck-sidebar-collapsed`**); **offline system font stack** (no remote font CSS); sidebar + favicon use **bundled** **`/assets/flightdeck-icon-*.png`** with stable **`GET /flightdeck-icon.png`** fallback; **`html[data-theme="dark"]`** tokens and Playwright **`web/e2e/`** (`smoke` icon checks, `theme.spec.ts`, `sidebar.spec.ts`). +- **Web UI (`flightdeck serve`):** **Theme** (Light / Dark / System icon radios, **`flightdeck-theme`**) in the **sidebar Settings** popover; legacy **`/#/settings`** redirects to **`#/`**; collapsible sidebar (**`flightdeck-sidebar-collapsed`**); **offline system font stack** (no remote font CSS); sidebar + favicon use **bundled** **`/assets/flightdeck-icon-*.png`** with stable **`GET /flightdeck-icon.png`** fallback; **`html[data-theme="dark"]`** tokens and Playwright **`web/e2e/`** (`smoke` icon checks, `theme.spec.ts`, `sidebar.spec.ts`). - **`flightdeck pricing check`** — reports **`flightdeck-bundled-*`** snapshot age vs **`--max-age-days`** (default **90**); **`--fail`** for CI. **`release diff`** / **`POST /v1/diff`** append **`pricing.warnings`** when bundled snapshots exceed the same age threshold. - **`flightdeck.integrations.telemetry.configure_otel_tracing()`** — optional OTLP HTTP **`TracerProvider`** wiring when the **`telemetry`** extra is installed (see **`docs/sdk-integrations.md`**). - **SDK:** **`flightdeck.sdk.http_common`** shared serializers and retry policy; parity tests keep sync/async clients aligned. **`pytest-cov`** no longer omits **`sdk/client.py`**. @@ -17,6 +17,7 @@ This project follows [Semantic Versioning](https://semver.org/). From **v1.0.0** - **`[project.optional-dependencies] dev`:** **`ruff`** is **`>=0.15,<0.16`** (was an exact patch pin) so **`pip install`** / shared venvs can resolve alongside other tools; **`uv sync --frozen`** still follows **`uv.lock`**. **`docs/troubleshooting.md`** notes checking **`uv.lock`** for the resolved **`0.15.x`** wheel. - **Docs / positioning:** README local-first and ICP copy; bundled pricing cadence, vendor pricing URLs in YAML comments, and **`docs/pricing-catalog.md`** / **ROADMAP** / **RELEASE_NOTES** staleness commitments. +- **Web UI (`flightdeck serve`):** Sidebar **Settings** popover shows a **Settings** heading and **Theme** as sun / moon / monitor icon radios (replacing text-only appearance controls in that surface). ## 1.2.0 - 2026-05-03 diff --git a/artifacts/flightdeck-demo-share/01-overview-loopback-chips.png b/artifacts/flightdeck-demo-share/01-overview-loopback-chips.png new file mode 100644 index 0000000..c391c09 Binary files /dev/null and b/artifacts/flightdeck-demo-share/01-overview-loopback-chips.png differ diff --git a/artifacts/flightdeck-demo-share/02-overview-ledger-metrics.png b/artifacts/flightdeck-demo-share/02-overview-ledger-metrics.png new file mode 100644 index 0000000..1a11a90 Binary files /dev/null and b/artifacts/flightdeck-demo-share/02-overview-ledger-metrics.png differ diff --git a/artifacts/flightdeck-demo-share/03-diff-compute.png b/artifacts/flightdeck-demo-share/03-diff-compute.png new file mode 100644 index 0000000..7d682a0 Binary files /dev/null and b/artifacts/flightdeck-demo-share/03-diff-compute.png differ diff --git a/artifacts/flightdeck-demo-share/04-runs-query.png b/artifacts/flightdeck-demo-share/04-runs-query.png new file mode 100644 index 0000000..643541c Binary files /dev/null and b/artifacts/flightdeck-demo-share/04-runs-query.png differ diff --git a/artifacts/flightdeck-demo-share/05-actions-promote.png b/artifacts/flightdeck-demo-share/05-actions-promote.png new file mode 100644 index 0000000..3248f9a Binary files /dev/null and b/artifacts/flightdeck-demo-share/05-actions-promote.png differ diff --git a/artifacts/flightdeck-demo-share/06-settings-dark-theme.png b/artifacts/flightdeck-demo-share/06-settings-dark-theme.png new file mode 100644 index 0000000..ca9911f Binary files /dev/null and b/artifacts/flightdeck-demo-share/06-settings-dark-theme.png differ diff --git a/artifacts/flightdeck-demo-share/README.md b/artifacts/flightdeck-demo-share/README.md new file mode 100644 index 0000000..097c3f7 --- /dev/null +++ b/artifacts/flightdeck-demo-share/README.md @@ -0,0 +1,19 @@ +# Demo capture (UI marketing assets) + +Generated with: + +```bash +# from repo root, after: uv sync --frozen --extra dev +node web/scripts/capture-demo-artifacts.mjs +``` + +- **flightdeck-ui-demo.mp4** — short screen walkthrough (H.264, LinkedIn-friendly). +- **flightdeck-ui-demo.webm** — same session, WebM source. +- **01-overview-loopback-chips.png** — Overview with security strip (loopback). +- **02-overview-ledger-metrics.png** — Overview with ledger metrics expanded. +- **03-diff-compute.png** — Run diff page. +- **04-runs-query.png** — Run events page. +- **05-actions-promote.png** — Promote & rollback page. +- **06-settings-dark-theme.png** — Sidebar Settings popover → Theme icons (dark selected). + +Safe to delete this directory before a squash if you do not want binaries in history. diff --git a/artifacts/flightdeck-demo-share/flightdeck-ui-demo.mp4 b/artifacts/flightdeck-demo-share/flightdeck-ui-demo.mp4 new file mode 100644 index 0000000..5a8efca Binary files /dev/null and b/artifacts/flightdeck-demo-share/flightdeck-ui-demo.mp4 differ diff --git a/artifacts/flightdeck-demo-share/flightdeck-ui-demo.webm b/artifacts/flightdeck-demo-share/flightdeck-ui-demo.webm new file mode 100644 index 0000000..11bf34f Binary files /dev/null and b/artifacts/flightdeck-demo-share/flightdeck-ui-demo.webm differ diff --git a/docs/web-ui.md b/docs/web-ui.md index 5dd4df4..4fd3f0e 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -20,7 +20,7 @@ The **[README product overview](../README.md#product-overview)** image is a **ma | Art direction | Application in this repo | |---------------|---------------------------| -| Dark navy / near-black shell | **`html[data-theme="dark"]`** in `web/src/index.css` mirrors semantic tokens; **Appearance** control in the sidebar defaults to **Light** (stored under **`localStorage`** key **`flightdeck-theme`**). | +| Dark navy / near-black shell | **`html[data-theme="dark"]`** in `web/src/index.css` mirrors semantic tokens; **Theme** (sun / moon / monitor icons) in the sidebar **Settings** popover defaults to **Light** (stored under **`localStorage`** key **`flightdeck-theme`**). | | Cyan → purple gradient | CSS variables (for example `--fd-accent-gradient`) for **active nav**, **primary buttons**, and **focus-visible** accents—used sparingly so trust/safety UI stays calm. | | High-contrast titles | Tune `--fd-type-*` and weights under dark mode; avoid shrinking body text for density. | | “Neon” feel | Reserve for **interactive** states, not large background fills. | @@ -30,7 +30,7 @@ The **[README product overview](../README.md#product-overview)** image is a **ma 1. **Token foundation** — Extend `:root` with any missing semantics (`--fd-surface-elevated`, gradient stops, optional `--fd-bg-subtle`). Replace scattered literals in `web/src/index.css` (for example warning callout backgrounds) with variables so dark mode does not require hunting hex values. 2. **`[data-theme="dark"]` block** — Mirror every semantic token used by `.fd-shell`, sidebar, cards, tables, `Badge`, drawers, and `JsonPanel`; set `color-scheme: dark` on `html` when active. Validate **WCAG AA** for body text and links. -3. **Preference UI** — **`/#/settings`** (and room for more prefs later): **Light** / **Dark** / **System**; listen to `prefers-color-scheme` when System is selected. Persist `localStorage` key **`flightdeck-theme`** (`light` \| `dark` \| `system`). +3. **Preference UI** — **Sidebar Settings** popover (and room for more prefs later): **Theme** as sun / moon / monitor icon radios for **Light** / **Dark** / **System**; listen to `prefers-color-scheme` when System is selected. Persist `localStorage` key **`flightdeck-theme`** (`light` \| `dark` \| `system`). Legacy **`/#/settings`** redirects to **`#/`**. 4. **Brand accents** — Apply the gradient token to **active** `.fd-nav__link--active` (left rail) and primary submit-style buttons; keep destructive actions on existing red semantics. 5. **Light theme polish** — Even before dark ships: align spacing rhythm and card shadows with the same tokens so both themes stay maintainable. 6. **Verification** — From `web/`: **`npm ci`**, **`npm run build`**, commit **`src/flightdeck/server/static/`**; **`npm run test:e2e`** (includes **`e2e/theme.spec.ts`**: default light, dark persistence, system / `prefers-color-scheme`, overview smoke in dark). Manually smoke **Diff** and **Actions** in both themes (policy panels, JSON drawer, rollback affordances). @@ -57,7 +57,7 @@ The app uses **HashRouter** (`react-router-dom`) so all navigation stays within | `#/` | `OverviewPage` | `GET /v1/releases`, `GET /v1/promoted`, `GET /v1/actions`, `GET /v1/metrics` (parallel where applicable) | Ledger metrics (read-only); short per-counter hints; skeleton on first load; **auto-refresh** every 30s when the tab is visible + on timeline **`generation`** bump; links to Diff/Runs | | `#/diff` | `DiffPage` | `POST /v1/diff` | Sections: policy gate (incl. `evaluated_at`), evidence window, pricing/catalog/hints (incl. provider/version skew callout when sides differ), per-1k prices when present, cost/quality rollups; raw JSON panel | | `#/runs` | `RunsPage` | `GET /v1/releases` (for datalist), `GET /v1/runs`, `GET /v1/runs/export` | Forensics: filters, table (trace/status, trace band rows or **Group by trace_id**), **View** drawer (focus trap, session/span ids), typed **run-query error** card with **Retry**, empty/offset/truncation hints, NDJSON download | -| `#/settings` | `SettingsPage` | *(none)* | **Color theme** (Light / Dark / System) via `ThemeToggle`; more preferences later. | +| `#/settings` | *(redirect)* | — | Redirects to **`#/`**; **Theme** (Light / Dark / System) lives in the **sidebar Settings** dialog (`SidebarSettingsMenu` + icon `ThemeToggle`). | | `#/actions` | `ActionsPage` | `GET /v1/workspace`, `GET /v1/promotion-requests` (when `promotion_requires_approval`), `POST /v1/promote` **or** `POST /v1/promote/request` + `POST /v1/promote/confirm`, `POST /v1/rollback` | Workspace skeleton then strip; approval path: numbered steps, pending **Refresh list** / **Use for confirm**; **Rollback** danger-styled; see **ActionsPage** below | | `#/*` (any other) | — | Redirects to `#/` | | @@ -81,17 +81,17 @@ ThemePreferenceProvider (`App.tsx`) ├── aside.fd-sidebar (brand, collapse chevron, primary nav, footer nav → Settings) └── div.fd-shell__content ├── SecurityStatusBar - └── main#main-content → OverviewPage | DiffPage | RunsPage | ActionsPage | SettingsPage + └── main#main-content → OverviewPage | DiffPage | RunsPage | ActionsPage ``` --- ## `AppShell` (`web/src/components/AppShell.tsx`) -Renders a fixed-width **left sidebar** (`aside.fd-sidebar`) with brand (gradient **FlightDeck** wordmark, mark in a **raised tile**), a **collapse** control (SVG chevrons, `localStorage` **`flightdeck-sidebar-collapsed`**), a **primary** nav (inline SVG icons + labels; icon-only when collapsed), and a **footer** nav pinned to the bottom of the rail with **Settings** → `#/settings`. Then a **`fd-shell__content`** column with `SecurityStatusBar` and +Renders a fixed-width **left sidebar** (`aside.fd-sidebar`) with brand (gradient **FlightDeck** wordmark, mark in a **raised tile**), a **collapse** control (SVG chevrons, `localStorage` **`flightdeck-sidebar-collapsed`**), a **primary** nav (inline SVG icons + labels; icon-only when collapsed), and a **footer** **Settings** control that opens a compact **Settings** popover (portal, **Theme** row with sun / moon / monitor icon radios). Legacy `#/settings` redirects to `#/`. Then a **`fd-shell__content`** column with `SecurityStatusBar` and `
` wrapping an `` for the active page. On narrow viewports the sidebar stacks above the content with a horizontal nav row; a **collapsed** rail is expanded back to full labels in that breakpoint. Wraps the subtree in `TimelineRefreshProvider` -so any descendant can access the refresh context. `ThemePreferenceProvider` (from `App.tsx`) wraps the router so `ThemeToggle` on **Settings** can read and update **`flightdeck-theme`**; `main.tsx` applies the effective theme before the first paint to avoid a flash of the wrong scheme. +so any descendant can access the refresh context. `ThemePreferenceProvider` (from `App.tsx`) wraps the router so `ThemeToggle` in the popover can read and update **`flightdeck-theme`**; `main.tsx` applies the effective theme before the first paint to avoid a flash of the wrong scheme. A **Skip to main content** link (class `fd-skip-link`) appears first in the shell; it uses `preventDefault` + `focus()` on `#main-content` so **HashRouter** hash URLs (`#/…`) are not diff --git a/src/flightdeck/server/static/assets/index-Cmx_W8JU.js b/src/flightdeck/server/static/assets/index-Cmx_W8JU.js deleted file mode 100644 index 0bf36d8..0000000 --- a/src/flightdeck/server/static/assets/index-Cmx_W8JU.js +++ /dev/null @@ -1,11 +0,0 @@ -(function(){const c=document.createElement("link").relList;if(c&&c.supports&&c.supports("modulepreload"))return;for(const m of document.querySelectorAll('link[rel="modulepreload"]'))f(m);new MutationObserver(m=>{for(const h of m)if(h.type==="childList")for(const g of h.addedNodes)g.tagName==="LINK"&&g.rel==="modulepreload"&&f(g)}).observe(document,{childList:!0,subtree:!0});function o(m){const h={};return m.integrity&&(h.integrity=m.integrity),m.referrerPolicy&&(h.referrerPolicy=m.referrerPolicy),m.crossOrigin==="use-credentials"?h.credentials="include":m.crossOrigin==="anonymous"?h.credentials="omit":h.credentials="same-origin",h}function f(m){if(m.ep)return;m.ep=!0;const h=o(m);fetch(m.href,h)}})();var Kc={exports:{}},Fn={};var Nm;function Xp(){if(Nm)return Fn;Nm=1;var s=Symbol.for("react.transitional.element"),c=Symbol.for("react.fragment");function o(f,m,h){var g=null;if(h!==void 0&&(g=""+h),m.key!==void 0&&(g=""+m.key),"key"in m){h={};for(var j in m)j!=="key"&&(h[j]=m[j])}else h=m;return m=h.ref,{$$typeof:s,type:f,key:g,ref:m!==void 0?m:null,props:h}}return Fn.Fragment=c,Fn.jsx=o,Fn.jsxs=o,Fn}var Em;function Vp(){return Em||(Em=1,Kc.exports=Xp()),Kc.exports}var u=Vp(),Jc={exports:{}},ue={};var Tm;function Zp(){if(Tm)return ue;Tm=1;var s=Symbol.for("react.transitional.element"),c=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),f=Symbol.for("react.strict_mode"),m=Symbol.for("react.profiler"),h=Symbol.for("react.consumer"),g=Symbol.for("react.context"),j=Symbol.for("react.forward_ref"),x=Symbol.for("react.suspense"),p=Symbol.for("react.memo"),O=Symbol.for("react.lazy"),N=Symbol.for("react.activity"),q=Symbol.iterator;function Y(b){return b===null||typeof b!="object"?null:(b=q&&b[q]||b["@@iterator"],typeof b=="function"?b:null)}var V={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},G=Object.assign,U={};function Z(b,M,Q){this.props=b,this.context=M,this.refs=U,this.updater=Q||V}Z.prototype.isReactComponent={},Z.prototype.setState=function(b,M){if(typeof b!="object"&&typeof b!="function"&&b!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,b,M,"setState")},Z.prototype.forceUpdate=function(b){this.updater.enqueueForceUpdate(this,b,"forceUpdate")};function I(){}I.prototype=Z.prototype;function k(b,M,Q){this.props=b,this.context=M,this.refs=U,this.updater=Q||V}var me=k.prototype=new I;me.constructor=k,G(me,Z.prototype),me.isPureReactComponent=!0;var se=Array.isArray;function Ne(){}var $={H:null,A:null,T:null,S:null},ae=Object.prototype.hasOwnProperty;function _e(b,M,Q){var K=Q.ref;return{$$typeof:s,type:b,key:M,ref:K!==void 0?K:null,props:Q}}function Ee(b,M){return _e(b.type,M,b.props)}function ne(b){return typeof b=="object"&&b!==null&&b.$$typeof===s}function Ce(b){var M={"=":"=0",":":"=2"};return"$"+b.replace(/[=:]/g,function(Q){return M[Q]})}var je=/\/+/g;function fe(b,M){return typeof b=="object"&&b!==null&&b.key!=null?Ce(""+b.key):M.toString(36)}function H(b){switch(b.status){case"fulfilled":return b.value;case"rejected":throw b.reason;default:switch(typeof b.status=="string"?b.then(Ne,Ne):(b.status="pending",b.then(function(M){b.status==="pending"&&(b.status="fulfilled",b.value=M)},function(M){b.status==="pending"&&(b.status="rejected",b.reason=M)})),b.status){case"fulfilled":return b.value;case"rejected":throw b.reason}}throw b}function R(b,M,Q,K,te){var ie=typeof b;(ie==="undefined"||ie==="boolean")&&(b=null);var xe=!1;if(b===null)xe=!0;else switch(ie){case"bigint":case"string":case"number":xe=!0;break;case"object":switch(b.$$typeof){case s:case c:xe=!0;break;case O:return xe=b._init,R(xe(b._payload),M,Q,K,te)}}if(xe)return te=te(b),xe=K===""?"."+fe(b,0):K,se(te)?(Q="",xe!=null&&(Q=xe.replace(je,"$&/")+"/"),R(te,M,Q,"",function(B){return B})):te!=null&&(ne(te)&&(te=Ee(te,Q+(te.key==null||b&&b.key===te.key?"":(""+te.key).replace(je,"$&/")+"/")+xe)),M.push(te)),1;xe=0;var Ke=K===""?".":K+":";if(se(b))for(var qe=0;qe>>1,ye=R[pe];if(0>>1;pem(Q,F))Km(te,Q)?(R[pe]=te,R[K]=F,pe=K):(R[pe]=Q,R[M]=F,pe=M);else if(Km(te,F))R[pe]=te,R[K]=F,pe=K;else break e}}return L}function m(R,L){var F=R.sortIndex-L.sortIndex;return F!==0?F:R.id-L.id}if(s.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var h=performance;s.unstable_now=function(){return h.now()}}else{var g=Date,j=g.now();s.unstable_now=function(){return g.now()-j}}var x=[],p=[],O=1,N=null,q=3,Y=!1,V=!1,G=!1,U=!1,Z=typeof setTimeout=="function"?setTimeout:null,I=typeof clearTimeout=="function"?clearTimeout:null,k=typeof setImmediate<"u"?setImmediate:null;function me(R){for(var L=o(p);L!==null;){if(L.callback===null)f(p);else if(L.startTime<=R)f(p),L.sortIndex=L.expirationTime,c(x,L);else break;L=o(p)}}function se(R){if(G=!1,me(R),!V)if(o(x)!==null)V=!0,Ne||(Ne=!0,Ce());else{var L=o(p);L!==null&&H(se,L.startTime-R)}}var Ne=!1,$=-1,ae=5,_e=-1;function Ee(){return U?!0:!(s.unstable_now()-_eR&&Ee());){var pe=N.callback;if(typeof pe=="function"){N.callback=null,q=N.priorityLevel;var ye=pe(N.expirationTime<=R);if(R=s.unstable_now(),typeof ye=="function"){N.callback=ye,me(R),L=!0;break t}N===o(x)&&f(x),me(R)}else f(x);N=o(x)}if(N!==null)L=!0;else{var b=o(p);b!==null&&H(se,b.startTime-R),L=!1}}break e}finally{N=null,q=F,Y=!1}L=void 0}}finally{L?Ce():Ne=!1}}}var Ce;if(typeof k=="function")Ce=function(){k(ne)};else if(typeof MessageChannel<"u"){var je=new MessageChannel,fe=je.port2;je.port1.onmessage=ne,Ce=function(){fe.postMessage(null)}}else Ce=function(){Z(ne,0)};function H(R,L){$=Z(function(){R(s.unstable_now())},L)}s.unstable_IdlePriority=5,s.unstable_ImmediatePriority=1,s.unstable_LowPriority=4,s.unstable_NormalPriority=3,s.unstable_Profiling=null,s.unstable_UserBlockingPriority=2,s.unstable_cancelCallback=function(R){R.callback=null},s.unstable_forceFrameRate=function(R){0>R||125pe?(R.sortIndex=F,c(p,R),o(x)===null&&R===o(p)&&(G?(I($),$=-1):G=!0,H(se,F-pe))):(R.sortIndex=ye,c(x,R),V||Y||(V=!0,Ne||(Ne=!0,Ce()))),R},s.unstable_shouldYield=Ee,s.unstable_wrapCallback=function(R){var L=q;return function(){var F=q;q=L;try{return R.apply(this,arguments)}finally{q=F}}}})(Fc)),Fc}var Cm;function Jp(){return Cm||(Cm=1,$c.exports=Kp()),$c.exports}var Wc={exports:{}},ct={};var Om;function kp(){if(Om)return ct;Om=1;var s=or();function c(x){var p="https://react.dev/errors/"+x;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(s)}catch(c){console.error(c)}}return s(),Wc.exports=kp(),Wc.exports}var Dm;function Fp(){if(Dm)return Wn;Dm=1;var s=Jp(),c=or(),o=$p();function f(e){var t="https://react.dev/errors/"+e;if(1ye||(e.current=pe[ye],pe[ye]=null,ye--)}function Q(e,t){ye++,pe[ye]=e.current,e.current=t}var K=b(null),te=b(null),ie=b(null),xe=b(null);function Ke(e,t){switch(Q(ie,t),Q(te,e),Q(K,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Jd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Jd(t),e=kd(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}M(K),Q(K,e)}function qe(){M(K),M(te),M(ie)}function B(e){e.memoizedState!==null&&Q(xe,e);var t=K.current,l=kd(t,e.type);t!==l&&(Q(te,e),Q(K,l))}function oe(e){te.current===e&&(M(K),M(te)),xe.current===e&&(M(xe),Kn._currentValue=F)}var Ue,X;function le(e){if(Ue===void 0)try{throw Error()}catch(l){var t=l.stack.trim().match(/\n( *(at )?)/);Ue=t&&t[1]||"",X=-1)":-1n||y[a]!==T[n]){var z=` -`+y[a].replace(" at new "," at ");return e.displayName&&z.includes("")&&(z=z.replace("",e.displayName)),z}while(1<=a&&0<=n);break}}}finally{de=!1,Error.prepareStackTrace=l}return(l=e?e.displayName||e.name:"")?le(l):""}function Ct(e,t){switch(e.tag){case 26:case 27:case 5:return le(e.type);case 16:return le("Lazy");case 13:return e.child!==t&&t!==null?le("Suspense Fallback"):le("Suspense");case 19:return le("SuspenseList");case 0:case 15:return st(e.type,!1);case 11:return st(e.type.render,!1);case 1:return st(e.type,!0);case 31:return le("Activity");default:return""}}function Pe(e){try{var t="",l=null;do t+=Ct(e,l),l=e,e=e.return;while(e);return t}catch(a){return` -Error generating stack: `+a.message+` -`+a.stack}}var ln=Object.prototype.hasOwnProperty,Du=s.unstable_scheduleCallback,Mu=s.unstable_cancelCallback,xh=s.unstable_shouldYield,Sh=s.unstable_requestPaint,yt=s.unstable_now,jh=s.unstable_getCurrentPriorityLevel,jr=s.unstable_ImmediatePriority,Nr=s.unstable_UserBlockingPriority,ni=s.unstable_NormalPriority,Nh=s.unstable_LowPriority,Er=s.unstable_IdlePriority,Eh=s.log,Th=s.unstable_setDisableYieldValue,an=null,gt=null;function yl(e){if(typeof Eh=="function"&&Th(e),gt&&typeof gt.setStrictMode=="function")try{gt.setStrictMode(an,e)}catch{}}var bt=Math.clz32?Math.clz32:Ch,Rh=Math.log,Ah=Math.LN2;function Ch(e){return e>>>=0,e===0?32:31-(Rh(e)/Ah|0)|0}var ii=256,ui=262144,si=4194304;function Kl(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function ci(e,t,l){var a=e.pendingLanes;if(a===0)return 0;var n=0,i=e.suspendedLanes,r=e.pingedLanes;e=e.warmLanes;var d=a&134217727;return d!==0?(a=d&~i,a!==0?n=Kl(a):(r&=d,r!==0?n=Kl(r):l||(l=d&~e,l!==0&&(n=Kl(l))))):(d=a&~i,d!==0?n=Kl(d):r!==0?n=Kl(r):l||(l=a&~e,l!==0&&(n=Kl(l)))),n===0?0:t!==0&&t!==n&&(t&i)===0&&(i=n&-n,l=t&-t,i>=l||i===32&&(l&4194048)!==0)?t:n}function nn(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function Oh(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Tr(){var e=si;return si<<=1,(si&62914560)===0&&(si=4194304),e}function wu(e){for(var t=[],l=0;31>l;l++)t.push(e);return t}function un(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function zh(e,t,l,a,n,i){var r=e.pendingLanes;e.pendingLanes=l,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=l,e.entangledLanes&=l,e.errorRecoveryDisabledLanes&=l,e.shellSuspendCounter=0;var d=e.entanglements,y=e.expirationTimes,T=e.hiddenUpdates;for(l=r&~l;0"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var qh=/[\n"\\]/g;function zt(e){return e.replace(qh,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function Yu(e,t,l,a,n,i,r,d){e.name="",r!=null&&typeof r!="function"&&typeof r!="symbol"&&typeof r!="boolean"?e.type=r:e.removeAttribute("type"),t!=null?r==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+Ot(t)):e.value!==""+Ot(t)&&(e.value=""+Ot(t)):r!=="submit"&&r!=="reset"||e.removeAttribute("value"),t!=null?Gu(e,r,Ot(t)):l!=null?Gu(e,r,Ot(l)):a!=null&&e.removeAttribute("value"),n==null&&i!=null&&(e.defaultChecked=!!i),n!=null&&(e.checked=n&&typeof n!="function"&&typeof n!="symbol"),d!=null&&typeof d!="function"&&typeof d!="symbol"&&typeof d!="boolean"?e.name=""+Ot(d):e.removeAttribute("name")}function Br(e,t,l,a,n,i,r,d){if(i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"&&(e.type=i),t!=null||l!=null){if(!(i!=="submit"&&i!=="reset"||t!=null)){Bu(e);return}l=l!=null?""+Ot(l):"",t=t!=null?""+Ot(t):l,d||t===e.value||(e.value=t),e.defaultValue=t}a=a??n,a=typeof a!="function"&&typeof a!="symbol"&&!!a,e.checked=d?e.checked:!!a,e.defaultChecked=!!a,r!=null&&typeof r!="function"&&typeof r!="symbol"&&typeof r!="boolean"&&(e.name=r),Bu(e)}function Gu(e,t,l){t==="number"&&oi(e.ownerDocument)===e||e.defaultValue===""+l||(e.defaultValue=""+l)}function ba(e,t,l,a){if(e=e.options,t){t={};for(var n=0;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Ku=!1;if(Pt)try{var fn={};Object.defineProperty(fn,"passive",{get:function(){Ku=!0}}),window.addEventListener("test",fn,fn),window.removeEventListener("test",fn,fn)}catch{Ku=!1}var bl=null,Ju=null,mi=null;function Kr(){if(mi)return mi;var e,t=Ju,l=t.length,a,n="value"in bl?bl.value:bl.textContent,i=n.length;for(e=0;e=mn),Ir=" ",Pr=!1;function ef(e,t){switch(e){case"keyup":return ov.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function tf(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var ja=!1;function mv(e,t){switch(e){case"compositionend":return tf(t);case"keypress":return t.which!==32?null:(Pr=!0,Ir);case"textInput":return e=t.data,e===Ir&&Pr?null:e;default:return null}}function hv(e,t){if(ja)return e==="compositionend"||!Iu&&ef(e,t)?(e=Kr(),mi=Ju=bl=null,ja=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:l,offset:t-e};e=a}e:{for(;l;){if(l.nextSibling){l=l.nextSibling;break e}l=l.parentNode}l=void 0}l=ff(l)}}function df(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?df(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function mf(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=oi(e.document);t instanceof e.HTMLIFrameElement;){try{var l=typeof t.contentWindow.location.href=="string"}catch{l=!1}if(l)e=t.contentWindow;else break;t=oi(e.document)}return t}function ts(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var Sv=Pt&&"documentMode"in document&&11>=document.documentMode,Na=null,ls=null,yn=null,as=!1;function hf(e,t,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;as||Na==null||Na!==oi(a)||(a=Na,"selectionStart"in a&&ts(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),yn&&pn(yn,a)||(yn=a,a=uu(ls,"onSelect"),0>=r,n-=r,Jt=1<<32-bt(t)+n|l<re?(be=W,W=null):be=W.sibling;var Re=A(S,W,E[re],D);if(Re===null){W===null&&(W=be);break}e&&W&&Re.alternate===null&&t(S,W),_=i(Re,_,re),Te===null?P=Re:Te.sibling=Re,Te=Re,W=be}if(re===E.length)return l(S,W),Se&&tl(S,re),P;if(W===null){for(;rere?(be=W,W=null):be=W.sibling;var Gl=A(S,W,Re.value,D);if(Gl===null){W===null&&(W=be);break}e&&W&&Gl.alternate===null&&t(S,W),_=i(Gl,_,re),Te===null?P=Gl:Te.sibling=Gl,Te=Gl,W=be}if(Re.done)return l(S,W),Se&&tl(S,re),P;if(W===null){for(;!Re.done;re++,Re=E.next())Re=w(S,Re.value,D),Re!==null&&(_=i(Re,_,re),Te===null?P=Re:Te.sibling=Re,Te=Re);return Se&&tl(S,re),P}for(W=a(W);!Re.done;re++,Re=E.next())Re=C(W,S,re,Re.value,D),Re!==null&&(e&&Re.alternate!==null&&W.delete(Re.key===null?re:Re.key),_=i(Re,_,re),Te===null?P=Re:Te.sibling=Re,Te=Re);return e&&W.forEach(function(Qp){return t(S,Qp)}),Se&&tl(S,re),P}function we(S,_,E,D){if(typeof E=="object"&&E!==null&&E.type===G&&E.key===null&&(E=E.props.children),typeof E=="object"&&E!==null){switch(E.$$typeof){case Y:e:{for(var P=E.key;_!==null;){if(_.key===P){if(P=E.type,P===G){if(_.tag===7){l(S,_.sibling),D=n(_,E.props.children),D.return=S,S=D;break e}}else if(_.elementType===P||typeof P=="object"&&P!==null&&P.$$typeof===ae&&aa(P)===_.type){l(S,_.sibling),D=n(_,E.props),jn(D,E),D.return=S,S=D;break e}l(S,_);break}else t(S,_);_=_.sibling}E.type===G?(D=Il(E.props.children,S.mode,D,E.key),D.return=S,S=D):(D=ji(E.type,E.key,E.props,null,S.mode,D),jn(D,E),D.return=S,S=D)}return r(S);case V:e:{for(P=E.key;_!==null;){if(_.key===P)if(_.tag===4&&_.stateNode.containerInfo===E.containerInfo&&_.stateNode.implementation===E.implementation){l(S,_.sibling),D=n(_,E.children||[]),D.return=S,S=D;break e}else{l(S,_);break}else t(S,_);_=_.sibling}D=fs(E,S.mode,D),D.return=S,S=D}return r(S);case ae:return E=aa(E),we(S,_,E,D)}if(H(E))return J(S,_,E,D);if(Ce(E)){if(P=Ce(E),typeof P!="function")throw Error(f(150));return E=P.call(E),ee(S,_,E,D)}if(typeof E.then=="function")return we(S,_,Oi(E),D);if(E.$$typeof===k)return we(S,_,Ti(S,E),D);zi(S,E)}return typeof E=="string"&&E!==""||typeof E=="number"||typeof E=="bigint"?(E=""+E,_!==null&&_.tag===6?(l(S,_.sibling),D=n(_,E),D.return=S,S=D):(l(S,_),D=rs(E,S.mode,D),D.return=S,S=D),r(S)):l(S,_)}return function(S,_,E,D){try{Sn=0;var P=we(S,_,E,D);return Ua=null,P}catch(W){if(W===wa||W===Ai)throw W;var Te=xt(29,W,null,S.mode);return Te.lanes=D,Te.return=S,Te}}}var ia=qf(!0),Lf=qf(!1),Nl=!1;function Ss(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function js(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function El(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Tl(e,t,l){var a=e.updateQueue;if(a===null)return null;if(a=a.shared,(Ae&2)!==0){var n=a.pending;return n===null?t.next=t:(t.next=n.next,n.next=t),a.pending=t,t=Si(e),xf(e,null,l),t}return xi(e,a,t,l),Si(e)}function Nn(e,t,l){if(t=t.updateQueue,t!==null&&(t=t.shared,(l&4194048)!==0)){var a=t.lanes;a&=e.pendingLanes,l|=a,t.lanes=l,Ar(e,l)}}function Ns(e,t){var l=e.updateQueue,a=e.alternate;if(a!==null&&(a=a.updateQueue,l===a)){var n=null,i=null;if(l=l.firstBaseUpdate,l!==null){do{var r={lane:l.lane,tag:l.tag,payload:l.payload,callback:null,next:null};i===null?n=i=r:i=i.next=r,l=l.next}while(l!==null);i===null?n=i=t:i=i.next=t}else n=i=t;l={baseState:a.baseState,firstBaseUpdate:n,lastBaseUpdate:i,shared:a.shared,callbacks:a.callbacks},e.updateQueue=l;return}e=l.lastBaseUpdate,e===null?l.firstBaseUpdate=t:e.next=t,l.lastBaseUpdate=t}var Es=!1;function En(){if(Es){var e=Ma;if(e!==null)throw e}}function Tn(e,t,l,a){Es=!1;var n=e.updateQueue;Nl=!1;var i=n.firstBaseUpdate,r=n.lastBaseUpdate,d=n.shared.pending;if(d!==null){n.shared.pending=null;var y=d,T=y.next;y.next=null,r===null?i=T:r.next=T,r=y;var z=e.alternate;z!==null&&(z=z.updateQueue,d=z.lastBaseUpdate,d!==r&&(d===null?z.firstBaseUpdate=T:d.next=T,z.lastBaseUpdate=y))}if(i!==null){var w=n.baseState;r=0,z=T=y=null,d=i;do{var A=d.lane&-536870913,C=A!==d.lane;if(C?(ge&A)===A:(a&A)===A){A!==0&&A===Da&&(Es=!0),z!==null&&(z=z.next={lane:0,tag:d.tag,payload:d.payload,callback:null,next:null});e:{var J=e,ee=d;A=t;var we=l;switch(ee.tag){case 1:if(J=ee.payload,typeof J=="function"){w=J.call(we,w,A);break e}w=J;break e;case 3:J.flags=J.flags&-65537|128;case 0:if(J=ee.payload,A=typeof J=="function"?J.call(we,w,A):J,A==null)break e;w=N({},w,A);break e;case 2:Nl=!0}}A=d.callback,A!==null&&(e.flags|=64,C&&(e.flags|=8192),C=n.callbacks,C===null?n.callbacks=[A]:C.push(A))}else C={lane:A,tag:d.tag,payload:d.payload,callback:d.callback,next:null},z===null?(T=z=C,y=w):z=z.next=C,r|=A;if(d=d.next,d===null){if(d=n.shared.pending,d===null)break;C=d,d=C.next,C.next=null,n.lastBaseUpdate=C,n.shared.pending=null}}while(!0);z===null&&(y=w),n.baseState=y,n.firstBaseUpdate=T,n.lastBaseUpdate=z,i===null&&(n.shared.lanes=0),zl|=r,e.lanes=r,e.memoizedState=w}}function Bf(e,t){if(typeof e!="function")throw Error(f(191,e));e.call(t)}function Yf(e,t){var l=e.callbacks;if(l!==null)for(e.callbacks=null,e=0;ei?i:8;var r=R.T,d={};R.T=d,Vs(e,!1,t,l);try{var y=n(),T=R.S;if(T!==null&&T(d,y),y!==null&&typeof y=="object"&&typeof y.then=="function"){var z=zv(y,a);Cn(e,t,z,Tt(e))}else Cn(e,t,a,Tt(e))}catch(w){Cn(e,t,{then:function(){},status:"rejected",reason:w},Tt())}finally{L.p=i,r!==null&&d.types!==null&&(r.types=d.types),R.T=r}}function qv(){}function Qs(e,t,l,a){if(e.tag!==5)throw Error(f(476));var n=bo(e).queue;go(e,n,t,F,l===null?qv:function(){return _o(e),l(a)})}function bo(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:F,baseState:F,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:il,lastRenderedState:F},next:null};var l={};return t.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:il,lastRenderedState:l},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function _o(e){var t=bo(e);t.next===null&&(t=e.alternate.memoizedState),Cn(e,t.next.queue,{},Tt())}function Xs(){return nt(Kn)}function xo(){return Ze().memoizedState}function So(){return Ze().memoizedState}function Lv(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var l=Tt();e=El(l);var a=Tl(t,e,l);a!==null&&(pt(a,t,l),Nn(a,t,l)),t={cache:gs()},e.payload=t;return}t=t.return}}function Bv(e,t,l){var a=Tt();l={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},Gi(e)?No(t,l):(l=ss(e,t,l,a),l!==null&&(pt(l,e,a),Eo(l,t,a)))}function jo(e,t,l){var a=Tt();Cn(e,t,l,a)}function Cn(e,t,l,a){var n={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null};if(Gi(e))No(t,n);else{var i=e.alternate;if(e.lanes===0&&(i===null||i.lanes===0)&&(i=t.lastRenderedReducer,i!==null))try{var r=t.lastRenderedState,d=i(r,l);if(n.hasEagerState=!0,n.eagerState=d,_t(d,r))return xi(e,t,n,0),He===null&&_i(),!1}catch{}if(l=ss(e,t,n,a),l!==null)return pt(l,e,a),Eo(l,t,a),!0}return!1}function Vs(e,t,l,a){if(a={lane:2,revertLane:Sc(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},Gi(e)){if(t)throw Error(f(479))}else t=ss(e,l,a,2),t!==null&&pt(t,e,2)}function Gi(e){var t=e.alternate;return e===ce||t!==null&&t===ce}function No(e,t){qa=wi=!0;var l=e.pending;l===null?t.next=t:(t.next=l.next,l.next=t),e.pending=t}function Eo(e,t,l){if((l&4194048)!==0){var a=t.lanes;a&=e.pendingLanes,l|=a,t.lanes=l,Ar(e,l)}}var On={readContext:nt,use:qi,useCallback:Qe,useContext:Qe,useEffect:Qe,useImperativeHandle:Qe,useLayoutEffect:Qe,useInsertionEffect:Qe,useMemo:Qe,useReducer:Qe,useRef:Qe,useState:Qe,useDebugValue:Qe,useDeferredValue:Qe,useTransition:Qe,useSyncExternalStore:Qe,useId:Qe,useHostTransitionStatus:Qe,useFormState:Qe,useActionState:Qe,useOptimistic:Qe,useMemoCache:Qe,useCacheRefresh:Qe};On.useEffectEvent=Qe;var To={readContext:nt,use:qi,useCallback:function(e,t){return rt().memoizedState=[e,t===void 0?null:t],e},useContext:nt,useEffect:co,useImperativeHandle:function(e,t,l){l=l!=null?l.concat([e]):null,Bi(4194308,4,mo.bind(null,t,e),l)},useLayoutEffect:function(e,t){return Bi(4194308,4,e,t)},useInsertionEffect:function(e,t){Bi(4,2,e,t)},useMemo:function(e,t){var l=rt();t=t===void 0?null:t;var a=e();if(ua){yl(!0);try{e()}finally{yl(!1)}}return l.memoizedState=[a,t],a},useReducer:function(e,t,l){var a=rt();if(l!==void 0){var n=l(t);if(ua){yl(!0);try{l(t)}finally{yl(!1)}}}else n=t;return a.memoizedState=a.baseState=n,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:n},a.queue=e,e=e.dispatch=Bv.bind(null,ce,e),[a.memoizedState,e]},useRef:function(e){var t=rt();return e={current:e},t.memoizedState=e},useState:function(e){e=qs(e);var t=e.queue,l=jo.bind(null,ce,t);return t.dispatch=l,[e.memoizedState,l]},useDebugValue:Ys,useDeferredValue:function(e,t){var l=rt();return Gs(l,e,t)},useTransition:function(){var e=qs(!1);return e=go.bind(null,ce,e.queue,!0,!1),rt().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,l){var a=ce,n=rt();if(Se){if(l===void 0)throw Error(f(407));l=l()}else{if(l=t(),He===null)throw Error(f(349));(ge&127)!==0||Kf(a,t,l)}n.memoizedState=l;var i={value:l,getSnapshot:t};return n.queue=i,co(kf.bind(null,a,i,e),[e]),a.flags|=2048,Ba(9,{destroy:void 0},Jf.bind(null,a,i,l,t),null),l},useId:function(){var e=rt(),t=He.identifierPrefix;if(Se){var l=kt,a=Jt;l=(a&~(1<<32-bt(a)-1)).toString(32)+l,t="_"+t+"R_"+l,l=Ui++,0<\/script>",i=i.removeChild(i.firstChild);break;case"select":i=typeof a.is=="string"?r.createElement("select",{is:a.is}):r.createElement("select"),a.multiple?i.multiple=!0:a.size&&(i.size=a.size);break;default:i=typeof a.is=="string"?r.createElement(n,{is:a.is}):r.createElement(n)}}i[lt]=t,i[ft]=a;e:for(r=t.child;r!==null;){if(r.tag===5||r.tag===6)i.appendChild(r.stateNode);else if(r.tag!==4&&r.tag!==27&&r.child!==null){r.child.return=r,r=r.child;continue}if(r===t)break e;for(;r.sibling===null;){if(r.return===null||r.return===t)break e;r=r.return}r.sibling.return=r.return,r=r.sibling}t.stateNode=i;e:switch(ut(i,n,a),n){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break e;case"img":a=!0;break e;default:a=!1}a&&sl(t)}}return Be(t),nc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,l),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==a&&sl(t);else{if(typeof a!="string"&&t.stateNode===null)throw Error(f(166));if(e=ie.current,Oa(t)){if(e=t.stateNode,l=t.memoizedProps,a=null,n=at,n!==null)switch(n.tag){case 27:case 5:a=n.memoizedProps}e[lt]=t,e=!!(e.nodeValue===l||a!==null&&a.suppressHydrationWarning===!0||Zd(e.nodeValue,l)),e||Sl(t,!0)}else e=su(e).createTextNode(a),e[lt]=t,t.stateNode=e}return Be(t),null;case 31:if(l=t.memoizedState,e===null||e.memoizedState!==null){if(a=Oa(t),l!==null){if(e===null){if(!a)throw Error(f(318));if(e=t.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(f(557));e[lt]=t}else Pl(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Be(t),e=!1}else l=hs(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=l),e=!0;if(!e)return t.flags&256?(jt(t),t):(jt(t),null);if((t.flags&128)!==0)throw Error(f(558))}return Be(t),null;case 13:if(a=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(n=Oa(t),a!==null&&a.dehydrated!==null){if(e===null){if(!n)throw Error(f(318));if(n=t.memoizedState,n=n!==null?n.dehydrated:null,!n)throw Error(f(317));n[lt]=t}else Pl(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Be(t),n=!1}else n=hs(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),n=!0;if(!n)return t.flags&256?(jt(t),t):(jt(t),null)}return jt(t),(t.flags&128)!==0?(t.lanes=l,t):(l=a!==null,e=e!==null&&e.memoizedState!==null,l&&(a=t.child,n=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(n=a.alternate.memoizedState.cachePool.pool),i=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(i=a.memoizedState.cachePool.pool),i!==n&&(a.flags|=2048)),l!==e&&l&&(t.child.flags|=8192),Ki(t,t.updateQueue),Be(t),null);case 4:return qe(),e===null&&Tc(t.stateNode.containerInfo),Be(t),null;case 10:return al(t.type),Be(t),null;case 19:if(M(Ve),a=t.memoizedState,a===null)return Be(t),null;if(n=(t.flags&128)!==0,i=a.rendering,i===null)if(n)Dn(a,!1);else{if(Xe!==0||e!==null&&(e.flags&128)!==0)for(e=t.child;e!==null;){if(i=Mi(e),i!==null){for(t.flags|=128,Dn(a,!1),e=i.updateQueue,t.updateQueue=e,Ki(t,e),t.subtreeFlags=0,e=l,l=t.child;l!==null;)Sf(l,e),l=l.sibling;return Q(Ve,Ve.current&1|2),Se&&tl(t,a.treeForkCount),t.child}e=e.sibling}a.tail!==null&&yt()>Wi&&(t.flags|=128,n=!0,Dn(a,!1),t.lanes=4194304)}else{if(!n)if(e=Mi(i),e!==null){if(t.flags|=128,n=!0,e=e.updateQueue,t.updateQueue=e,Ki(t,e),Dn(a,!0),a.tail===null&&a.tailMode==="hidden"&&!i.alternate&&!Se)return Be(t),null}else 2*yt()-a.renderingStartTime>Wi&&l!==536870912&&(t.flags|=128,n=!0,Dn(a,!1),t.lanes=4194304);a.isBackwards?(i.sibling=t.child,t.child=i):(e=a.last,e!==null?e.sibling=i:t.child=i,a.last=i)}return a.tail!==null?(e=a.tail,a.rendering=e,a.tail=e.sibling,a.renderingStartTime=yt(),e.sibling=null,l=Ve.current,Q(Ve,n?l&1|2:l&1),Se&&tl(t,a.treeForkCount),e):(Be(t),null);case 22:case 23:return jt(t),Rs(),a=t.memoizedState!==null,e!==null?e.memoizedState!==null!==a&&(t.flags|=8192):a&&(t.flags|=8192),a?(l&536870912)!==0&&(t.flags&128)===0&&(Be(t),t.subtreeFlags&6&&(t.flags|=8192)):Be(t),l=t.updateQueue,l!==null&&Ki(t,l.retryQueue),l=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(l=e.memoizedState.cachePool.pool),a=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(a=t.memoizedState.cachePool.pool),a!==l&&(t.flags|=2048),e!==null&&M(la),null;case 24:return l=null,e!==null&&(l=e.memoizedState.cache),t.memoizedState.cache!==l&&(t.flags|=2048),al(Je),Be(t),null;case 25:return null;case 30:return null}throw Error(f(156,t.tag))}function Vv(e,t){switch(ds(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return al(Je),qe(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return oe(t),null;case 31:if(t.memoizedState!==null){if(jt(t),t.alternate===null)throw Error(f(340));Pl()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(jt(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(f(340));Pl()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return M(Ve),null;case 4:return qe(),null;case 10:return al(t.type),null;case 22:case 23:return jt(t),Rs(),e!==null&&M(la),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return al(Je),null;case 25:return null;default:return null}}function Fo(e,t){switch(ds(t),t.tag){case 3:al(Je),qe();break;case 26:case 27:case 5:oe(t);break;case 4:qe();break;case 31:t.memoizedState!==null&&jt(t);break;case 13:jt(t);break;case 19:M(Ve);break;case 10:al(t.type);break;case 22:case 23:jt(t),Rs(),e!==null&&M(la);break;case 24:al(Je)}}function Mn(e,t){try{var l=t.updateQueue,a=l!==null?l.lastEffect:null;if(a!==null){var n=a.next;l=n;do{if((l.tag&e)===e){a=void 0;var i=l.create,r=l.inst;a=i(),r.destroy=a}l=l.next}while(l!==n)}}catch(d){ze(t,t.return,d)}}function Cl(e,t,l){try{var a=t.updateQueue,n=a!==null?a.lastEffect:null;if(n!==null){var i=n.next;a=i;do{if((a.tag&e)===e){var r=a.inst,d=r.destroy;if(d!==void 0){r.destroy=void 0,n=t;var y=l,T=d;try{T()}catch(z){ze(n,y,z)}}}a=a.next}while(a!==i)}}catch(z){ze(t,t.return,z)}}function Wo(e){var t=e.updateQueue;if(t!==null){var l=e.stateNode;try{Yf(t,l)}catch(a){ze(e,e.return,a)}}}function Io(e,t,l){l.props=sa(e.type,e.memoizedProps),l.state=e.memoizedState;try{l.componentWillUnmount()}catch(a){ze(e,t,a)}}function wn(e,t){try{var l=e.ref;if(l!==null){switch(e.tag){case 26:case 27:case 5:var a=e.stateNode;break;case 30:a=e.stateNode;break;default:a=e.stateNode}typeof l=="function"?e.refCleanup=l(a):l.current=a}}catch(n){ze(e,t,n)}}function $t(e,t){var l=e.ref,a=e.refCleanup;if(l!==null)if(typeof a=="function")try{a()}catch(n){ze(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof l=="function")try{l(null)}catch(n){ze(e,t,n)}else l.current=null}function Po(e){var t=e.type,l=e.memoizedProps,a=e.stateNode;try{e:switch(t){case"button":case"input":case"select":case"textarea":l.autoFocus&&a.focus();break e;case"img":l.src?a.src=l.src:l.srcSet&&(a.srcset=l.srcSet)}}catch(n){ze(e,e.return,n)}}function ic(e,t,l){try{var a=e.stateNode;dp(a,e.type,l,t),a[ft]=t}catch(n){ze(e,e.return,n)}}function ed(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Hl(e.type)||e.tag===4}function uc(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||ed(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Hl(e.type)||e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function sc(e,t,l){var a=e.tag;if(a===5||a===6)e=e.stateNode,t?(l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l).insertBefore(e,t):(t=l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l,t.appendChild(e),l=l._reactRootContainer,l!=null||t.onclick!==null||(t.onclick=It));else if(a!==4&&(a===27&&Hl(e.type)&&(l=e.stateNode,t=null),e=e.child,e!==null))for(sc(e,t,l),e=e.sibling;e!==null;)sc(e,t,l),e=e.sibling}function Ji(e,t,l){var a=e.tag;if(a===5||a===6)e=e.stateNode,t?l.insertBefore(e,t):l.appendChild(e);else if(a!==4&&(a===27&&Hl(e.type)&&(l=e.stateNode),e=e.child,e!==null))for(Ji(e,t,l),e=e.sibling;e!==null;)Ji(e,t,l),e=e.sibling}function td(e){var t=e.stateNode,l=e.memoizedProps;try{for(var a=e.type,n=t.attributes;n.length;)t.removeAttributeNode(n[0]);ut(t,a,l),t[lt]=e,t[ft]=l}catch(i){ze(e,e.return,i)}}var cl=!1,Fe=!1,cc=!1,ld=typeof WeakSet=="function"?WeakSet:Set,tt=null;function Zv(e,t){if(e=e.containerInfo,Cc=hu,e=mf(e),ts(e)){if("selectionStart"in e)var l={start:e.selectionStart,end:e.selectionEnd};else e:{l=(l=e.ownerDocument)&&l.defaultView||window;var a=l.getSelection&&l.getSelection();if(a&&a.rangeCount!==0){l=a.anchorNode;var n=a.anchorOffset,i=a.focusNode;a=a.focusOffset;try{l.nodeType,i.nodeType}catch{l=null;break e}var r=0,d=-1,y=-1,T=0,z=0,w=e,A=null;t:for(;;){for(var C;w!==l||n!==0&&w.nodeType!==3||(d=r+n),w!==i||a!==0&&w.nodeType!==3||(y=r+a),w.nodeType===3&&(r+=w.nodeValue.length),(C=w.firstChild)!==null;)A=w,w=C;for(;;){if(w===e)break t;if(A===l&&++T===n&&(d=r),A===i&&++z===a&&(y=r),(C=w.nextSibling)!==null)break;w=A,A=w.parentNode}w=C}l=d===-1||y===-1?null:{start:d,end:y}}else l=null}l=l||{start:0,end:0}}else l=null;for(Oc={focusedElem:e,selectionRange:l},hu=!1,tt=t;tt!==null;)if(t=tt,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,tt=e;else for(;tt!==null;){switch(t=tt,i=t.alternate,e=t.flags,t.tag){case 0:if((e&4)!==0&&(e=t.updateQueue,e=e!==null?e.events:null,e!==null))for(l=0;l title"))),ut(i,a,l),i[lt]=e,et(i),a=i;break e;case"link":var r=cm("link","href",n).get(a+(l.href||""));if(r){for(var d=0;dwe&&(r=we,we=ee,ee=r);var S=of(d,ee),_=of(d,we);if(S&&_&&(C.rangeCount!==1||C.anchorNode!==S.node||C.anchorOffset!==S.offset||C.focusNode!==_.node||C.focusOffset!==_.offset)){var E=w.createRange();E.setStart(S.node,S.offset),C.removeAllRanges(),ee>we?(C.addRange(E),C.extend(_.node,_.offset)):(E.setEnd(_.node,_.offset),C.addRange(E))}}}}for(w=[],C=d;C=C.parentNode;)C.nodeType===1&&w.push({element:C,left:C.scrollLeft,top:C.scrollTop});for(typeof d.focus=="function"&&d.focus(),d=0;dl?32:l,R.T=null,l=vc,vc=null;var i=Ml,r=ml;if(We=0,Va=Ml=null,ml=0,(Ae&6)!==0)throw Error(f(331));var d=Ae;if(Ae|=4,md(i.current),fd(i,i.current,r,l),Ae=d,Yn(0,!1),gt&&typeof gt.onPostCommitFiberRoot=="function")try{gt.onPostCommitFiberRoot(an,i)}catch{}return!0}finally{L.p=n,R.T=a,zd(e,t)}}function Md(e,t,l){t=Mt(l,t),t=ks(e.stateNode,t,2),e=Tl(e,t,2),e!==null&&(un(e,2),Ft(e))}function ze(e,t,l){if(e.tag===3)Md(e,e,l);else for(;t!==null;){if(t.tag===3){Md(t,e,l);break}else if(t.tag===1){var a=t.stateNode;if(typeof t.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(Dl===null||!Dl.has(a))){e=Mt(l,e),l=wo(2),a=Tl(t,l,2),a!==null&&(Uo(l,a,t,e),un(a,2),Ft(a));break}}t=t.return}}function bc(e,t,l){var a=e.pingCache;if(a===null){a=e.pingCache=new kv;var n=new Set;a.set(t,n)}else n=a.get(t),n===void 0&&(n=new Set,a.set(t,n));n.has(l)||(oc=!0,n.add(l),e=Pv.bind(null,e,t,l),t.then(e,e))}function Pv(e,t,l){var a=e.pingCache;a!==null&&a.delete(t),e.pingedLanes|=e.suspendedLanes&l,e.warmLanes&=~l,He===e&&(ge&l)===l&&(Xe===4||Xe===3&&(ge&62914560)===ge&&300>yt()-Fi?(Ae&2)===0&&Za(e,0):dc|=l,Xa===ge&&(Xa=0)),Ft(e)}function wd(e,t){t===0&&(t=Tr()),e=Wl(e,t),e!==null&&(un(e,t),Ft(e))}function ep(e){var t=e.memoizedState,l=0;t!==null&&(l=t.retryLane),wd(e,l)}function tp(e,t){var l=0;switch(e.tag){case 31:case 13:var a=e.stateNode,n=e.memoizedState;n!==null&&(l=n.retryLane);break;case 19:a=e.stateNode;break;case 22:a=e.stateNode._retryCache;break;default:throw Error(f(314))}a!==null&&a.delete(t),wd(e,l)}function lp(e,t){return Du(e,t)}var au=null,Ja=null,_c=!1,nu=!1,xc=!1,Ul=0;function Ft(e){e!==Ja&&e.next===null&&(Ja===null?au=Ja=e:Ja=Ja.next=e),nu=!0,_c||(_c=!0,np())}function Yn(e,t){if(!xc&&nu){xc=!0;do for(var l=!1,a=au;a!==null;){if(e!==0){var n=a.pendingLanes;if(n===0)var i=0;else{var r=a.suspendedLanes,d=a.pingedLanes;i=(1<<31-bt(42|e)+1)-1,i&=n&~(r&~d),i=i&201326741?i&201326741|1:i?i|2:0}i!==0&&(l=!0,Ld(a,i))}else i=ge,i=ci(a,a===He?i:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(i&3)===0||nn(a,i)||(l=!0,Ld(a,i));a=a.next}while(l);xc=!1}}function ap(){Ud()}function Ud(){nu=_c=!1;var e=0;Ul!==0&&hp()&&(e=Ul);for(var t=yt(),l=null,a=au;a!==null;){var n=a.next,i=Hd(a,t);i===0?(a.next=null,l===null?au=n:l.next=n,n===null&&(Ja=l)):(l=a,(e!==0||(i&3)!==0)&&(nu=!0)),a=n}We!==0&&We!==5||Yn(e),Ul!==0&&(Ul=0)}function Hd(e,t){for(var l=e.suspendedLanes,a=e.pingedLanes,n=e.expirationTimes,i=e.pendingLanes&-62914561;0d)break;var z=y.transferSize,w=y.initiatorType;z&&Kd(w)&&(y=y.responseEnd,r+=z*(y"u"?null:document;function nm(e,t,l){var a=ka;if(a&&typeof t=="string"&&t){var n=zt(t);n='link[rel="'+e+'"][href="'+n+'"]',typeof l=="string"&&(n+='[crossorigin="'+l+'"]'),am.has(n)||(am.add(n),e={rel:e,crossOrigin:l,href:t},a.querySelector(n)===null&&(t=a.createElement("link"),ut(t,"link",e),et(t),a.head.appendChild(t)))}}function jp(e){hl.D(e),nm("dns-prefetch",e,null)}function Np(e,t){hl.C(e,t),nm("preconnect",e,t)}function Ep(e,t,l){hl.L(e,t,l);var a=ka;if(a&&e&&t){var n='link[rel="preload"][as="'+zt(t)+'"]';t==="image"&&l&&l.imageSrcSet?(n+='[imagesrcset="'+zt(l.imageSrcSet)+'"]',typeof l.imageSizes=="string"&&(n+='[imagesizes="'+zt(l.imageSizes)+'"]')):n+='[href="'+zt(e)+'"]';var i=n;switch(t){case"style":i=$a(e);break;case"script":i=Fa(e)}Bt.has(i)||(e=N({rel:"preload",href:t==="image"&&l&&l.imageSrcSet?void 0:e,as:t},l),Bt.set(i,e),a.querySelector(n)!==null||t==="style"&&a.querySelector(Vn(i))||t==="script"&&a.querySelector(Zn(i))||(t=a.createElement("link"),ut(t,"link",e),et(t),a.head.appendChild(t)))}}function Tp(e,t){hl.m(e,t);var l=ka;if(l&&e){var a=t&&typeof t.as=="string"?t.as:"script",n='link[rel="modulepreload"][as="'+zt(a)+'"][href="'+zt(e)+'"]',i=n;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":i=Fa(e)}if(!Bt.has(i)&&(e=N({rel:"modulepreload",href:e},t),Bt.set(i,e),l.querySelector(n)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(l.querySelector(Zn(i)))return}a=l.createElement("link"),ut(a,"link",e),et(a),l.head.appendChild(a)}}}function Rp(e,t,l){hl.S(e,t,l);var a=ka;if(a&&e){var n=ya(a).hoistableStyles,i=$a(e);t=t||"default";var r=n.get(i);if(!r){var d={loading:0,preload:null};if(r=a.querySelector(Vn(i)))d.loading=5;else{e=N({rel:"stylesheet",href:e,"data-precedence":t},l),(l=Bt.get(i))&&qc(e,l);var y=r=a.createElement("link");et(y),ut(y,"link",e),y._p=new Promise(function(T,z){y.onload=T,y.onerror=z}),y.addEventListener("load",function(){d.loading|=1}),y.addEventListener("error",function(){d.loading|=2}),d.loading|=4,ru(r,t,a)}r={type:"stylesheet",instance:r,count:1,state:d},n.set(i,r)}}}function Ap(e,t){hl.X(e,t);var l=ka;if(l&&e){var a=ya(l).hoistableScripts,n=Fa(e),i=a.get(n);i||(i=l.querySelector(Zn(n)),i||(e=N({src:e,async:!0},t),(t=Bt.get(n))&&Lc(e,t),i=l.createElement("script"),et(i),ut(i,"link",e),l.head.appendChild(i)),i={type:"script",instance:i,count:1,state:null},a.set(n,i))}}function Cp(e,t){hl.M(e,t);var l=ka;if(l&&e){var a=ya(l).hoistableScripts,n=Fa(e),i=a.get(n);i||(i=l.querySelector(Zn(n)),i||(e=N({src:e,async:!0,type:"module"},t),(t=Bt.get(n))&&Lc(e,t),i=l.createElement("script"),et(i),ut(i,"link",e),l.head.appendChild(i)),i={type:"script",instance:i,count:1,state:null},a.set(n,i))}}function im(e,t,l,a){var n=(n=ie.current)?cu(n):null;if(!n)throw Error(f(446));switch(e){case"meta":case"title":return null;case"style":return typeof l.precedence=="string"&&typeof l.href=="string"?(t=$a(l.href),l=ya(n).hoistableStyles,a=l.get(t),a||(a={type:"style",instance:null,count:0,state:null},l.set(t,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(l.rel==="stylesheet"&&typeof l.href=="string"&&typeof l.precedence=="string"){e=$a(l.href);var i=ya(n).hoistableStyles,r=i.get(e);if(r||(n=n.ownerDocument||n,r={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},i.set(e,r),(i=n.querySelector(Vn(e)))&&!i._p&&(r.instance=i,r.state.loading=5),Bt.has(e)||(l={rel:"preload",as:"style",href:l.href,crossOrigin:l.crossOrigin,integrity:l.integrity,media:l.media,hrefLang:l.hrefLang,referrerPolicy:l.referrerPolicy},Bt.set(e,l),i||Op(n,e,l,r.state))),t&&a===null)throw Error(f(528,""));return r}if(t&&a!==null)throw Error(f(529,""));return null;case"script":return t=l.async,l=l.src,typeof l=="string"&&t&&typeof t!="function"&&typeof t!="symbol"?(t=Fa(l),l=ya(n).hoistableScripts,a=l.get(t),a||(a={type:"script",instance:null,count:0,state:null},l.set(t,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(f(444,e))}}function $a(e){return'href="'+zt(e)+'"'}function Vn(e){return'link[rel="stylesheet"]['+e+"]"}function um(e){return N({},e,{"data-precedence":e.precedence,precedence:null})}function Op(e,t,l,a){e.querySelector('link[rel="preload"][as="style"]['+t+"]")?a.loading=1:(t=e.createElement("link"),a.preload=t,t.addEventListener("load",function(){return a.loading|=1}),t.addEventListener("error",function(){return a.loading|=2}),ut(t,"link",l),et(t),e.head.appendChild(t))}function Fa(e){return'[src="'+zt(e)+'"]'}function Zn(e){return"script[async]"+e}function sm(e,t,l){if(t.count++,t.instance===null)switch(t.type){case"style":var a=e.querySelector('style[data-href~="'+zt(l.href)+'"]');if(a)return t.instance=a,et(a),a;var n=N({},l,{"data-href":l.href,"data-precedence":l.precedence,href:null,precedence:null});return a=(e.ownerDocument||e).createElement("style"),et(a),ut(a,"style",n),ru(a,l.precedence,e),t.instance=a;case"stylesheet":n=$a(l.href);var i=e.querySelector(Vn(n));if(i)return t.state.loading|=4,t.instance=i,et(i),i;a=um(l),(n=Bt.get(n))&&qc(a,n),i=(e.ownerDocument||e).createElement("link"),et(i);var r=i;return r._p=new Promise(function(d,y){r.onload=d,r.onerror=y}),ut(i,"link",a),t.state.loading|=4,ru(i,l.precedence,e),t.instance=i;case"script":return i=Fa(l.src),(n=e.querySelector(Zn(i)))?(t.instance=n,et(n),n):(a=l,(n=Bt.get(i))&&(a=N({},l),Lc(a,n)),e=e.ownerDocument||e,n=e.createElement("script"),et(n),ut(n,"link",a),e.head.appendChild(n),t.instance=n);case"void":return null;default:throw Error(f(443,t.type))}else t.type==="stylesheet"&&(t.state.loading&4)===0&&(a=t.instance,t.state.loading|=4,ru(a,l.precedence,e));return t.instance}function ru(e,t,l){for(var a=l.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),n=a.length?a[a.length-1]:null,i=n,r=0;r title"):null)}function zp(e,t,l){if(l===1||t.itemProp!=null)return!1;switch(e){case"meta":case"title":return!0;case"style":if(typeof t.precedence!="string"||typeof t.href!="string"||t.href==="")break;return!0;case"link":if(typeof t.rel!="string"||typeof t.href!="string"||t.href===""||t.onLoad||t.onError)break;return t.rel==="stylesheet"?(e=t.disabled,typeof t.precedence=="string"&&e==null):!0;case"script":if(t.async&&typeof t.async!="function"&&typeof t.async!="symbol"&&!t.onLoad&&!t.onError&&t.src&&typeof t.src=="string")return!0}return!1}function fm(e){return!(e.type==="stylesheet"&&(e.state.loading&3)===0)}function Dp(e,t,l,a){if(l.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(l.state.loading&4)===0){if(l.instance===null){var n=$a(a.href),i=t.querySelector(Vn(n));if(i){t=i._p,t!==null&&typeof t=="object"&&typeof t.then=="function"&&(e.count++,e=ou.bind(e),t.then(e,e)),l.state.loading|=4,l.instance=i,et(i);return}i=t.ownerDocument||t,a=um(a),(n=Bt.get(n))&&qc(a,n),i=i.createElement("link"),et(i);var r=i;r._p=new Promise(function(d,y){r.onload=d,r.onerror=y}),ut(i,"link",a),l.instance=i}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(l,t),(t=l.state.preload)&&(l.state.loading&3)===0&&(e.count++,l=ou.bind(e),t.addEventListener("load",l),t.addEventListener("error",l))}}var Bc=0;function Mp(e,t){return e.stylesheets&&e.count===0&&mu(e,e.stylesheets),0Bc?50:800)+t);return e.unsuspend=l,function(){e.unsuspend=null,clearTimeout(a),clearTimeout(n)}}:null}function ou(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)mu(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var du=null;function mu(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,du=new Map,t.forEach(wp,e),du=null,ou.call(e))}function wp(e,t){if(!(t.state.loading&4)){var l=du.get(e);if(l)var a=l.get(null);else{l=new Map,du.set(e,l);for(var n=e.querySelectorAll("link[data-precedence],style[data-precedence]"),i=0;i"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(s)}catch(c){console.error(c)}}return s(),kc.exports=Fp(),kc.exports}var Ip=Wp();var wm="popstate";function Um(s){return typeof s=="object"&&s!=null&&"pathname"in s&&"search"in s&&"hash"in s&&"state"in s&&"key"in s}function Pp(s={}){function c(m,h){let{pathname:g="/",search:j="",hash:x=""}=da(m.location.hash.substring(1));return!g.startsWith("/")&&!g.startsWith(".")&&(g="/"+g),ir("",{pathname:g,search:j,hash:x},h.state&&h.state.usr||null,h.state&&h.state.key||"default")}function o(m,h){let g=m.document.querySelector("base"),j="";if(g&&g.getAttribute("href")){let x=m.location.href,p=x.indexOf("#");j=p===-1?x:x.slice(0,p)}return j+"#"+(typeof h=="string"?h:ti(h))}function f(m,h){Rt(m.pathname.charAt(0)==="/",`relative pathnames are not supported in hash history.push(${JSON.stringify(h)})`)}return ty(c,o,f,s)}function Ge(s,c){if(s===!1||s===null||typeof s>"u")throw new Error(c)}function Rt(s,c){if(!s){typeof console<"u"&&console.warn(c);try{throw new Error(c)}catch{}}}function ey(){return Math.random().toString(36).substring(2,10)}function Hm(s,c){return{usr:s.state,key:s.key,idx:c,masked:s.unstable_mask?{pathname:s.pathname,search:s.search,hash:s.hash}:void 0}}function ir(s,c,o=null,f,m){return{pathname:typeof s=="string"?s:s.pathname,search:"",hash:"",...typeof c=="string"?da(c):c,state:o,key:c&&c.key||f||ey(),unstable_mask:m}}function ti({pathname:s="/",search:c="",hash:o=""}){return c&&c!=="?"&&(s+=c.charAt(0)==="?"?c:"?"+c),o&&o!=="#"&&(s+=o.charAt(0)==="#"?o:"#"+o),s}function da(s){let c={};if(s){let o=s.indexOf("#");o>=0&&(c.hash=s.substring(o),s=s.substring(0,o));let f=s.indexOf("?");f>=0&&(c.search=s.substring(f),s=s.substring(0,f)),s&&(c.pathname=s)}return c}function ty(s,c,o,f={}){let{window:m=document.defaultView,v5Compat:h=!1}=f,g=m.history,j="POP",x=null,p=O();p==null&&(p=0,g.replaceState({...g.state,idx:p},""));function O(){return(g.state||{idx:null}).idx}function N(){j="POP";let U=O(),Z=U==null?null:U-p;p=U,x&&x({action:j,location:G.location,delta:Z})}function q(U,Z){j="PUSH";let I=Um(U)?U:ir(G.location,U,Z);o&&o(I,U),p=O()+1;let k=Hm(I,p),me=G.createHref(I.unstable_mask||I);try{g.pushState(k,"",me)}catch(se){if(se instanceof DOMException&&se.name==="DataCloneError")throw se;m.location.assign(me)}h&&x&&x({action:j,location:G.location,delta:1})}function Y(U,Z){j="REPLACE";let I=Um(U)?U:ir(G.location,U,Z);o&&o(I,U),p=O();let k=Hm(I,p),me=G.createHref(I.unstable_mask||I);g.replaceState(k,"",me),h&&x&&x({action:j,location:G.location,delta:0})}function V(U){return ly(U)}let G={get action(){return j},get location(){return s(m,g)},listen(U){if(x)throw new Error("A history only accepts one active listener");return m.addEventListener(wm,N),x=U,()=>{m.removeEventListener(wm,N),x=null}},createHref(U){return c(m,U)},createURL:V,encodeLocation(U){let Z=V(U);return{pathname:Z.pathname,search:Z.search,hash:Z.hash}},push:q,replace:Y,go(U){return g.go(U)}};return G}function ly(s,c=!1){let o="http://localhost";typeof window<"u"&&(o=window.location.origin!=="null"?window.location.origin:window.location.href),Ge(o,"No window.location.(origin|href) available to create URL");let f=typeof s=="string"?s:ti(s);return f=f.replace(/ $/,"%20"),!c&&f.startsWith("//")&&(f=o+f),new URL(f,o)}function Km(s,c,o="/"){return ay(s,c,o,!1)}function ay(s,c,o,f){let m=typeof c=="string"?da(c):c,h=pl(m.pathname||"/",o);if(h==null)return null;let g=Jm(s);ny(g);let j=null;for(let x=0;j==null&&x{let O={relativePath:p===void 0?g.path||"":p,caseSensitive:g.caseSensitive===!0,childrenIndex:j,route:g};if(O.relativePath.startsWith("/")){if(!O.relativePath.startsWith(f)&&x)return;Ge(O.relativePath.startsWith(f),`Absolute route path "${O.relativePath}" nested under path "${f}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),O.relativePath=O.relativePath.slice(f.length)}let N=Vt([f,O.relativePath]),q=o.concat(O);g.children&&g.children.length>0&&(Ge(g.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${N}".`),Jm(g.children,c,q,N,x)),!(g.path==null&&!g.index)&&c.push({path:N,score:oy(N,g.index),routesMeta:q})};return s.forEach((g,j)=>{if(g.path===""||!g.path?.includes("?"))h(g,j);else for(let x of km(g.path))h(g,j,!0,x)}),c}function km(s){let c=s.split("/");if(c.length===0)return[];let[o,...f]=c,m=o.endsWith("?"),h=o.replace(/\?$/,"");if(f.length===0)return m?[h,""]:[h];let g=km(f.join("/")),j=[];return j.push(...g.map(x=>x===""?h:[h,x].join("/"))),m&&j.push(...g),j.map(x=>s.startsWith("/")&&x===""?"/":x)}function ny(s){s.sort((c,o)=>c.score!==o.score?o.score-c.score:dy(c.routesMeta.map(f=>f.childrenIndex),o.routesMeta.map(f=>f.childrenIndex)))}var iy=/^:[\w-]+$/,uy=3,sy=2,cy=1,ry=10,fy=-2,qm=s=>s==="*";function oy(s,c){let o=s.split("/"),f=o.length;return o.some(qm)&&(f+=fy),c&&(f+=sy),o.filter(m=>!qm(m)).reduce((m,h)=>m+(iy.test(h)?uy:h===""?cy:ry),f)}function dy(s,c){return s.length===c.length&&s.slice(0,-1).every((f,m)=>f===c[m])?s[s.length-1]-c[c.length-1]:0}function my(s,c,o=!1){let{routesMeta:f}=s,m={},h="/",g=[];for(let j=0;j{if(O==="*"){let V=j[q]||"";g=h.slice(0,h.length-V.length).replace(/(.)\/+$/,"$1")}const Y=j[q];return N&&!Y?p[O]=void 0:p[O]=(Y||"").replace(/%2F/g,"/"),p},{}),pathname:h,pathnameBase:g,pattern:s}}function hy(s,c=!1,o=!0){Rt(s==="*"||!s.endsWith("*")||s.endsWith("/*"),`Route path "${s}" will be treated as if it were "${s.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${s.replace(/\*$/,"/*")}".`);let f=[],m="^"+s.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(g,j,x,p,O)=>{if(f.push({paramName:j,isOptional:x!=null}),x){let N=O.charAt(p+g.length);return N&&N!=="/"?"/([^\\/]*)":"(?:/([^\\/]*))?"}return"/([^\\/]+)"}).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return s.endsWith("*")?(f.push({paramName:"*"}),m+=s==="*"||s==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):o?m+="\\/*$":s!==""&&s!=="/"&&(m+="(?:(?=\\/|$))"),[new RegExp(m,c?void 0:"i"),f]}function vy(s){try{return s.split("/").map(c=>decodeURIComponent(c).replace(/\//g,"%2F")).join("/")}catch(c){return Rt(!1,`The URL path "${s}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${c}).`),s}}function pl(s,c){if(c==="/")return s;if(!s.toLowerCase().startsWith(c.toLowerCase()))return null;let o=c.endsWith("/")?c.length-1:c.length,f=s.charAt(o);return f&&f!=="/"?null:s.slice(o)||"/"}var py=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;function yy(s,c="/"){let{pathname:o,search:f="",hash:m=""}=typeof s=="string"?da(s):s,h;return o?(o=$m(o),o.startsWith("/")?h=Lm(o.substring(1),"/"):h=Lm(o,c)):h=c,{pathname:h,search:_y(f),hash:xy(m)}}function Lm(s,c){let o=Ru(c).split("/");return s.split("/").forEach(m=>{m===".."?o.length>1&&o.pop():m!=="."&&o.push(m)}),o.length>1?o.join("/"):"/"}function Ic(s,c,o,f){return`Cannot include a '${s}' character in a manually specified \`to.${c}\` field [${JSON.stringify(f)}]. Please separate it out to the \`to.${o}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function gy(s){return s.filter((c,o)=>o===0||c.route.path&&c.route.path.length>0)}function dr(s){let c=gy(s);return c.map((o,f)=>f===c.length-1?o.pathname:o.pathnameBase)}function Au(s,c,o,f=!1){let m;typeof s=="string"?m=da(s):(m={...s},Ge(!m.pathname||!m.pathname.includes("?"),Ic("?","pathname","search",m)),Ge(!m.pathname||!m.pathname.includes("#"),Ic("#","pathname","hash",m)),Ge(!m.search||!m.search.includes("#"),Ic("#","search","hash",m)));let h=s===""||m.pathname==="",g=h?"/":m.pathname,j;if(g==null)j=o;else{let N=c.length-1;if(!f&&g.startsWith("..")){let q=g.split("/");for(;q[0]==="..";)q.shift(),N-=1;m.pathname=q.join("/")}j=N>=0?c[N]:"/"}let x=yy(m,j),p=g&&g!=="/"&&g.endsWith("/"),O=(h||g===".")&&o.endsWith("/");return!x.pathname.endsWith("/")&&(p||O)&&(x.pathname+="/"),x}var $m=s=>s.replace(/\/\/+/g,"/"),Vt=s=>$m(s.join("/")),Ru=s=>s.replace(/\/+$/,""),by=s=>Ru(s).replace(/^\/*/,"/"),_y=s=>!s||s==="?"?"":s.startsWith("?")?s:"?"+s,xy=s=>!s||s==="#"?"":s.startsWith("#")?s:"#"+s,Sy=class{constructor(s,c,o,f=!1){this.status=s,this.statusText=c||"",this.internal=f,o instanceof Error?(this.data=o.toString(),this.error=o):this.data=o}};function jy(s){return s!=null&&typeof s.status=="number"&&typeof s.statusText=="string"&&typeof s.internal=="boolean"&&"data"in s}function Ny(s){let c=s.map(o=>o.route.path).filter(Boolean);return Vt(c)||"/"}var Fm=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Wm(s,c){let o=s;if(typeof o!="string"||!py.test(o))return{absoluteURL:void 0,isExternal:!1,to:o};let f=o,m=!1;if(Fm)try{let h=new URL(window.location.href),g=o.startsWith("//")?new URL(h.protocol+o):new URL(o),j=pl(g.pathname,c);g.origin===h.origin&&j!=null?o=j+g.search+g.hash:m=!0}catch{Rt(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:f,isExternal:m,to:o}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var Im=["POST","PUT","PATCH","DELETE"];new Set(Im);var Ey=["GET",...Im];new Set(Ey);var en=v.createContext(null);en.displayName="DataRouter";var Cu=v.createContext(null);Cu.displayName="DataRouterState";var Pm=v.createContext(!1);function Ty(){return v.useContext(Pm)}var eh=v.createContext({isTransitioning:!1});eh.displayName="ViewTransition";var Ry=v.createContext(new Map);Ry.displayName="Fetchers";var Ay=v.createContext(null);Ay.displayName="Await";var At=v.createContext(null);At.displayName="Navigation";var li=v.createContext(null);li.displayName="Location";var Zt=v.createContext({outlet:null,matches:[],isDataRoute:!1});Zt.displayName="Route";var mr=v.createContext(null);mr.displayName="RouteError";var th="REACT_ROUTER_ERROR",Cy="REDIRECT",Oy="ROUTE_ERROR_RESPONSE";function zy(s){if(s.startsWith(`${th}:${Cy}:{`))try{let c=JSON.parse(s.slice(28));if(typeof c=="object"&&c&&typeof c.status=="number"&&typeof c.statusText=="string"&&typeof c.location=="string"&&typeof c.reloadDocument=="boolean"&&typeof c.replace=="boolean")return c}catch{}}function Dy(s){if(s.startsWith(`${th}:${Oy}:{`))try{let c=JSON.parse(s.slice(40));if(typeof c=="object"&&c&&typeof c.status=="number"&&typeof c.statusText=="string")return new Sy(c.status,c.statusText,c.data)}catch{}}function My(s,{relative:c}={}){Ge(tn(),"useHref() may be used only in the context of a component.");let{basename:o,navigator:f}=v.useContext(At),{hash:m,pathname:h,search:g}=ai(s,{relative:c}),j=h;return o!=="/"&&(j=h==="/"?o:Vt([o,h])),f.createHref({pathname:j,search:g,hash:m})}function tn(){return v.useContext(li)!=null}function Kt(){return Ge(tn(),"useLocation() may be used only in the context of a component."),v.useContext(li).location}var lh="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function ah(s){v.useContext(At).static||v.useLayoutEffect(s)}function hr(){let{isDataRoute:s}=v.useContext(Zt);return s?ky():wy()}function wy(){Ge(tn(),"useNavigate() may be used only in the context of a component.");let s=v.useContext(en),{basename:c,navigator:o}=v.useContext(At),{matches:f}=v.useContext(Zt),{pathname:m}=Kt(),h=JSON.stringify(dr(f)),g=v.useRef(!1);return ah(()=>{g.current=!0}),v.useCallback((x,p={})=>{if(Rt(g.current,lh),!g.current)return;if(typeof x=="number"){o.go(x);return}let O=Au(x,JSON.parse(h),m,p.relative==="path");s==null&&c!=="/"&&(O.pathname=O.pathname==="/"?c:Vt([c,O.pathname])),(p.replace?o.replace:o.push)(O,p.state,p)},[c,o,h,m,s])}var Uy=v.createContext(null);function Hy(s){let c=v.useContext(Zt).outlet;return v.useMemo(()=>c&&v.createElement(Uy.Provider,{value:s},c),[c,s])}function ai(s,{relative:c}={}){let{matches:o}=v.useContext(Zt),{pathname:f}=Kt(),m=JSON.stringify(dr(o));return v.useMemo(()=>Au(s,JSON.parse(m),f,c==="path"),[s,m,f,c])}function qy(s,c){return nh(s,c)}function nh(s,c,o){Ge(tn(),"useRoutes() may be used only in the context of a component.");let{navigator:f}=v.useContext(At),{matches:m}=v.useContext(Zt),h=m[m.length-1],g=h?h.params:{},j=h?h.pathname:"/",x=h?h.pathnameBase:"/",p=h&&h.route;{let U=p&&p.path||"";uh(j,!p||U.endsWith("*")||U.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${j}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. - -Please change the parent to .`)}let O=Kt(),N;if(c){let U=typeof c=="string"?da(c):c;Ge(x==="/"||U.pathname?.startsWith(x),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${x}" but pathname "${U.pathname}" was given in the \`location\` prop.`),N=U}else N=O;let q=N.pathname||"/",Y=q;if(x!=="/"){let U=x.replace(/^\//,"").split("/");Y="/"+q.replace(/^\//,"").split("/").slice(U.length).join("/")}let V=Km(s,{pathname:Y});Rt(p||V!=null,`No routes matched location "${N.pathname}${N.search}${N.hash}" `),Rt(V==null||V[V.length-1].route.element!==void 0||V[V.length-1].route.Component!==void 0||V[V.length-1].route.lazy!==void 0,`Matched leaf route at location "${N.pathname}${N.search}${N.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let G=Qy(V&&V.map(U=>Object.assign({},U,{params:Object.assign({},g,U.params),pathname:Vt([x,f.encodeLocation?f.encodeLocation(U.pathname.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:U.pathname]),pathnameBase:U.pathnameBase==="/"?x:Vt([x,f.encodeLocation?f.encodeLocation(U.pathnameBase.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:U.pathnameBase])})),m,o);return c&&G?v.createElement(li.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",unstable_mask:void 0,...N},navigationType:"POP"}},G):G}function Ly(){let s=Jy(),c=jy(s)?`${s.status} ${s.statusText}`:s instanceof Error?s.message:JSON.stringify(s),o=s instanceof Error?s.stack:null,f="rgba(200,200,200, 0.5)",m={padding:"0.5rem",backgroundColor:f},h={padding:"2px 4px",backgroundColor:f},g=null;return console.error("Error handled by React Router default ErrorBoundary:",s),g=v.createElement(v.Fragment,null,v.createElement("p",null,"💿 Hey developer 👋"),v.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",v.createElement("code",{style:h},"ErrorBoundary")," or"," ",v.createElement("code",{style:h},"errorElement")," prop on your route.")),v.createElement(v.Fragment,null,v.createElement("h2",null,"Unexpected Application Error!"),v.createElement("h3",{style:{fontStyle:"italic"}},c),o?v.createElement("pre",{style:m},o):null,g)}var By=v.createElement(Ly,null),ih=class extends v.Component{constructor(s){super(s),this.state={location:s.location,revalidation:s.revalidation,error:s.error}}static getDerivedStateFromError(s){return{error:s}}static getDerivedStateFromProps(s,c){return c.location!==s.location||c.revalidation!=="idle"&&s.revalidation==="idle"?{error:s.error,location:s.location,revalidation:s.revalidation}:{error:s.error!==void 0?s.error:c.error,location:c.location,revalidation:s.revalidation||c.revalidation}}componentDidCatch(s,c){this.props.onError?this.props.onError(s,c):console.error("React Router caught the following error during render",s)}render(){let s=this.state.error;if(this.context&&typeof s=="object"&&s&&"digest"in s&&typeof s.digest=="string"){const o=Dy(s.digest);o&&(s=o)}let c=s!==void 0?v.createElement(Zt.Provider,{value:this.props.routeContext},v.createElement(mr.Provider,{value:s,children:this.props.component})):this.props.children;return this.context?v.createElement(Yy,{error:s},c):c}};ih.contextType=Pm;var Pc=new WeakMap;function Yy({children:s,error:c}){let{basename:o}=v.useContext(At);if(typeof c=="object"&&c&&"digest"in c&&typeof c.digest=="string"){let f=zy(c.digest);if(f){let m=Pc.get(c);if(m)throw m;let h=Wm(f.location,o);if(Fm&&!Pc.get(c))if(h.isExternal||f.reloadDocument)window.location.href=h.absoluteURL||h.to;else{const g=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(h.to,{replace:f.replace}));throw Pc.set(c,g),g}return v.createElement("meta",{httpEquiv:"refresh",content:`0;url=${h.absoluteURL||h.to}`})}}return s}function Gy({routeContext:s,match:c,children:o}){let f=v.useContext(en);return f&&f.static&&f.staticContext&&(c.route.errorElement||c.route.ErrorBoundary)&&(f.staticContext._deepestRenderedBoundaryId=c.route.id),v.createElement(Zt.Provider,{value:s},o)}function Qy(s,c=[],o){let f=o?.state;if(s==null){if(!f)return null;if(f.errors)s=f.matches;else if(c.length===0&&!f.initialized&&f.matches.length>0)s=f.matches;else return null}let m=s,h=f?.errors;if(h!=null){let O=m.findIndex(N=>N.route.id&&h?.[N.route.id]!==void 0);Ge(O>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(h).join(",")}`),m=m.slice(0,Math.min(m.length,O+1))}let g=!1,j=-1;if(o&&f){g=f.renderFallback;for(let O=0;O=0?m=m.slice(0,j+1):m=[m[0]];break}}}}let x=o?.onError,p=f&&x?(O,N)=>{x(O,{location:f.location,params:f.matches?.[0]?.params??{},unstable_pattern:Ny(f.matches),errorInfo:N})}:void 0;return m.reduceRight((O,N,q)=>{let Y,V=!1,G=null,U=null;f&&(Y=h&&N.route.id?h[N.route.id]:void 0,G=N.route.errorElement||By,g&&(j<0&&q===0?(uh("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),V=!0,U=null):j===q&&(V=!0,U=N.route.hydrateFallbackElement||null)));let Z=c.concat(m.slice(0,q+1)),I=()=>{let k;return Y?k=G:V?k=U:N.route.Component?k=v.createElement(N.route.Component,null):N.route.element?k=N.route.element:k=O,v.createElement(Gy,{match:N,routeContext:{outlet:O,matches:Z,isDataRoute:f!=null},children:k})};return f&&(N.route.ErrorBoundary||N.route.errorElement||q===0)?v.createElement(ih,{location:f.location,revalidation:f.revalidation,component:G,error:Y,children:I(),routeContext:{outlet:null,matches:Z,isDataRoute:!0},onError:p}):I()},null)}function vr(s){return`${s} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function Xy(s){let c=v.useContext(en);return Ge(c,vr(s)),c}function Vy(s){let c=v.useContext(Cu);return Ge(c,vr(s)),c}function Zy(s){let c=v.useContext(Zt);return Ge(c,vr(s)),c}function pr(s){let c=Zy(s),o=c.matches[c.matches.length-1];return Ge(o.route.id,`${s} can only be used on routes that contain a unique "id"`),o.route.id}function Ky(){return pr("useRouteId")}function Jy(){let s=v.useContext(mr),c=Vy("useRouteError"),o=pr("useRouteError");return s!==void 0?s:c.errors?.[o]}function ky(){let{router:s}=Xy("useNavigate"),c=pr("useNavigate"),o=v.useRef(!1);return ah(()=>{o.current=!0}),v.useCallback(async(m,h={})=>{Rt(o.current,lh),o.current&&(typeof m=="number"?await s.navigate(m):await s.navigate(m,{fromRouteId:c,...h}))},[s,c])}var Bm={};function uh(s,c,o){!c&&!Bm[s]&&(Bm[s]=!0,Rt(!1,o))}v.memo($y);function $y({routes:s,future:c,state:o,isStatic:f,onError:m}){return nh(s,void 0,{state:o,isStatic:f,onError:m})}function Fy({to:s,replace:c,state:o,relative:f}){Ge(tn()," may be used only in the context of a component.");let{static:m}=v.useContext(At);Rt(!m," must not be used on the initial render in a . This is a no-op, but you should modify your code so the is only ever rendered in response to some user interaction or state change.");let{matches:h}=v.useContext(Zt),{pathname:g}=Kt(),j=hr(),x=Au(s,dr(h),g,f==="path"),p=JSON.stringify(x);return v.useEffect(()=>{j(JSON.parse(p),{replace:c,state:o,relative:f})},[j,p,f,c,o]),null}function Wy(s){return Hy(s.context)}function Vl(s){Ge(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function Iy({basename:s="/",children:c=null,location:o,navigationType:f="POP",navigator:m,static:h=!1,unstable_useTransitions:g}){Ge(!tn(),"You cannot render a inside another . You should never have more than one in your app.");let j=s.replace(/^\/*/,"/"),x=v.useMemo(()=>({basename:j,navigator:m,static:h,unstable_useTransitions:g,future:{}}),[j,m,h,g]);typeof o=="string"&&(o=da(o));let{pathname:p="/",search:O="",hash:N="",state:q=null,key:Y="default",unstable_mask:V}=o,G=v.useMemo(()=>{let U=pl(p,j);return U==null?null:{location:{pathname:U,search:O,hash:N,state:q,key:Y,unstable_mask:V},navigationType:f}},[j,p,O,N,q,Y,f,V]);return Rt(G!=null,` is not able to match the URL "${p}${O}${N}" because it does not start with the basename, so the won't render anything.`),G==null?null:v.createElement(At.Provider,{value:x},v.createElement(li.Provider,{children:c,value:G}))}function Py({children:s,location:c}){return qy(ur(s),c)}function ur(s,c=[]){let o=[];return v.Children.forEach(s,(f,m)=>{if(!v.isValidElement(f))return;let h=[...c,m];if(f.type===v.Fragment){o.push.apply(o,ur(f.props.children,h));return}Ge(f.type===Vl,`[${typeof f.type=="string"?f.type:f.type.name}] is not a component. All component children of must be a or `),Ge(!f.props.index||!f.props.children,"An index route cannot have child routes.");let g={id:f.props.id||h.join("-"),caseSensitive:f.props.caseSensitive,element:f.props.element,Component:f.props.Component,index:f.props.index,path:f.props.path,middleware:f.props.middleware,loader:f.props.loader,action:f.props.action,hydrateFallbackElement:f.props.hydrateFallbackElement,HydrateFallback:f.props.HydrateFallback,errorElement:f.props.errorElement,ErrorBoundary:f.props.ErrorBoundary,hasErrorBoundary:f.props.hasErrorBoundary===!0||f.props.ErrorBoundary!=null||f.props.errorElement!=null,shouldRevalidate:f.props.shouldRevalidate,handle:f.props.handle,lazy:f.props.lazy};f.props.children&&(g.children=ur(f.props.children,h)),o.push(g)}),o}var Su="get",ju="application/x-www-form-urlencoded";function Ou(s){return typeof HTMLElement<"u"&&s instanceof HTMLElement}function eg(s){return Ou(s)&&s.tagName.toLowerCase()==="button"}function tg(s){return Ou(s)&&s.tagName.toLowerCase()==="form"}function lg(s){return Ou(s)&&s.tagName.toLowerCase()==="input"}function ag(s){return!!(s.metaKey||s.altKey||s.ctrlKey||s.shiftKey)}function ng(s,c){return s.button===0&&(!c||c==="_self")&&!ag(s)}function sr(s=""){return new URLSearchParams(typeof s=="string"||Array.isArray(s)||s instanceof URLSearchParams?s:Object.keys(s).reduce((c,o)=>{let f=s[o];return c.concat(Array.isArray(f)?f.map(m=>[o,m]):[[o,f]])},[]))}function ig(s,c){let o=sr(s);return c&&c.forEach((f,m)=>{o.has(m)||c.getAll(m).forEach(h=>{o.append(m,h)})}),o}var xu=null;function ug(){if(xu===null)try{new FormData(document.createElement("form"),0),xu=!1}catch{xu=!0}return xu}var sg=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function er(s){return s!=null&&!sg.has(s)?(Rt(!1,`"${s}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${ju}"`),null):s}function cg(s,c){let o,f,m,h,g;if(tg(s)){let j=s.getAttribute("action");f=j?pl(j,c):null,o=s.getAttribute("method")||Su,m=er(s.getAttribute("enctype"))||ju,h=new FormData(s)}else if(eg(s)||lg(s)&&(s.type==="submit"||s.type==="image")){let j=s.form;if(j==null)throw new Error('Cannot submit a diff --git a/web/src/components/Button.tsx b/web/src/components/Button.tsx new file mode 100644 index 0000000..d426379 --- /dev/null +++ b/web/src/components/Button.tsx @@ -0,0 +1,59 @@ +import type { ButtonHTMLAttributes, ReactNode } from "react"; + +type ButtonVariant = "default" | "primary" | "ghost" | "danger"; +type ButtonSize = "default" | "sm"; + +export type ButtonProps = ButtonHTMLAttributes & { + variant?: ButtonVariant; + size?: ButtonSize; + /** Shows spinner and disables the control; use `loadingLabel` for visible text while busy. */ + loading?: boolean; + loadingLabel?: ReactNode; +}; + +function variantClass(variant: ButtonVariant): string { + switch (variant) { + case "primary": + return "fd-btn--primary"; + case "ghost": + return "fd-btn--ghost"; + case "danger": + return "fd-btn--danger"; + default: + return ""; + } +} + +export function Button({ + variant = "default", + size = "default", + loading = false, + loadingLabel, + className, + children, + disabled, + type = "button", + ...rest +}: ButtonProps) { + const { "aria-busy": ariaBusyFromCaller, ...domRest } = rest; + + const classes = [ + "fd-btn", + variantClass(variant), + size === "sm" ? "fd-btn--sm" : "", + loading ? "fd-btn--loading" : "", + className ?? "", + ] + .filter(Boolean) + .join(" "); + + const label = loading && loadingLabel !== undefined ? loadingLabel : children; + const ariaBusy = loading ? true : ariaBusyFromCaller; + + return ( + + ); +} diff --git a/web/src/components/SecurityStatusBar.tsx b/web/src/components/SecurityStatusBar.tsx index c60b1c6..e750362 100644 --- a/web/src/components/SecurityStatusBar.tsx +++ b/web/src/components/SecurityStatusBar.tsx @@ -1,39 +1,47 @@ import { useEffect, useState } from "react"; import { fetchHealth, type HealthPayload } from "../api"; import { clientMutationTokenConfigured, UI_READ_ONLY } from "../uiConfig"; +import { StatusChip } from "./StatusChip"; -function resolveMutationAuth(data: HealthPayload): "bearer" | "loopback" | null { +type MutationAuthResolved = "bearer" | "loopback" | "unknown"; +type ReadAuthResolved = "bearer" | "open" | "unknown"; + +function resolveMutationAuth(data: HealthPayload): MutationAuthResolved { const m = data.mutation_auth; if (m === "bearer" || m === "loopback") return m; - return null; + return "unknown"; } -function resolveReadAuth(data: HealthPayload): "bearer" | "open" | null { +function resolveReadAuth(data: HealthPayload): ReadAuthResolved { const r = data.read_auth; if (r === "bearer" || r === "open") return r; - return null; + return "unknown"; } export function SecurityStatusBar() { - const [auth, setAuth] = useState<"bearer" | "loopback" | null>(null); - const [readAuth, setReadAuth] = useState<"bearer" | "open" | null>(null); + const [mutationAuth, setMutationAuth] = useState(null); + const [readAuth, setReadAuth] = useState(null); const [fetchErr, setFetchErr] = useState(null); const [healthLoading, setHealthLoading] = useState(true); useEffect(() => { + if (UI_READ_ONLY) { + setHealthLoading(false); + return; + } let cancelled = false; void (async () => { setHealthLoading(true); try { const h = await fetchHealth(); if (!cancelled) { - setAuth(resolveMutationAuth(h)); + setMutationAuth(resolveMutationAuth(h)); setReadAuth(resolveReadAuth(h)); setFetchErr(null); } } catch (e) { if (!cancelled) { - setAuth(null); + setMutationAuth(null); setReadAuth(null); setFetchErr(String(e)); } @@ -47,13 +55,16 @@ export function SecurityStatusBar() { }, []); const hasClient = clientMutationTokenConfigured(); - const mismatch = auth === "bearer" && !hasClient; + const mismatch = mutationAuth === "bearer" && !hasClient; if (UI_READ_ONLY) { return ( -
-

- Read-only UI: navigation to promote and rollback is disabled ( +

+
+ +
+

+ Promote and rollback navigation is disabled ( VITE_FLIGHTDECK_UI_READ_ONLY).

@@ -62,11 +73,11 @@ export function SecurityStatusBar() { if (healthLoading) { return ( -
+
Checking server security -

- Checking /health for API security… -

+
+ +
); @@ -74,60 +85,67 @@ export function SecurityStatusBar() { if (fetchErr) { return ( -
+

- Could not load server security mode from /health - : {fetchErr} + Could not load security mode from /health: {fetchErr}

); } - if (auth === null) { - return null; - } - - const readLine = - readAuth === "bearer" - ? "GET /v1/* read APIs require the same Bearer token." - : "GET /v1/* read APIs are open (no Bearer) while the server has no API token configured."; - - const serverLine = - auth === "bearer" - ? "Server requires an Authorization: Bearer token for ledger writes (ingest, promote, rollback)." - : "Server allows ledger writes from loopback without a Bearer token."; - - const clientLine = hasClient - ? "This UI build sends a client token (VITE_FLIGHTDECK_LOCAL_API_TOKEN is set)." - : "This UI build does not send a client token (VITE_FLIGHTDECK_LOCAL_API_TOKEN unset)."; - - if (auth === "bearer" && hasClient) { + if (mutationAuth === null || readAuth === null) { return ( -
-

- Bearer API: VITE_FLIGHTDECK_LOCAL_API_TOKEN is set — - confirm it matches the server's{" "} - FLIGHTDECK_LOCAL_API_TOKEN (writes and{" "} - GET /v1/* when the server uses a token). +

+

+ Security mode from /health is unavailable.

); } + const writeValue = + mutationAuth === "bearer" ? "Bearer required" : mutationAuth === "loopback" ? "Loopback open" : "Unknown"; + const writeTone = + mutationAuth === "bearer" ? "info" : mutationAuth === "loopback" ? "pass" : "warn"; + + const readValue = + readAuth === "bearer" ? "Bearer required" : readAuth === "open" ? "Open" : "Unknown"; + const readTone = readAuth === "bearer" ? "info" : readAuth === "open" ? "neutral" : "warn"; + + const clientTone = hasClient ? "pass" : mismatch ? "warn" : "neutral"; + + const contractDrift = mutationAuth === "unknown" || readAuth === "unknown"; + return ( -
- {mismatch ? ( -

- Server expects a Bearer token for writes and read APIs, but this UI is not configured with{" "} - VITE_FLIGHTDECK_LOCAL_API_TOKEN. Requests will be rejected - until the token matches{" "} - FLIGHTDECK_LOCAL_API_TOKEN on the server. +

+
+ + + +
+ {contractDrift ? ( +

+ /health omitted or returned an unexpected{" "} + mutation_auth / read_auth{" "} + value. Confirm the server version matches this UI. +

+ ) : mismatch ? ( +

+ Server expects Authorization: Bearer for writes and reads, but{" "} + VITE_FLIGHTDECK_LOCAL_API_TOKEN is unset in this UI build. Set it + to match FLIGHTDECK_LOCAL_API_TOKEN on the server. +

+ ) : mutationAuth === "bearer" && hasClient ? ( +

+ Confirm the UI token matches the server's FLIGHTDECK_LOCAL_API_TOKEN.

) : ( -

- {serverLine}{" "} - {readLine}{" "} - {clientLine} +

+ Ingest, promote, and rollback follow the write mode above. Diff stays unauthenticated.

)}
diff --git a/web/src/components/SidebarSettingsMenu.tsx b/web/src/components/SidebarSettingsMenu.tsx new file mode 100644 index 0000000..c8159ce --- /dev/null +++ b/web/src/components/SidebarSettingsMenu.tsx @@ -0,0 +1,133 @@ +import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; +import { ThemeToggle } from "./ThemeToggle"; +import { IconSettings } from "./sidebarIcons"; + +const POPOVER_WIDTH = 248; +const POPOVER_ESTIMATE_H = 220; + +function positionPopover(trigger: HTMLElement) { + const r = trigger.getBoundingClientRect(); + const gap = 8; + let left = r.right + gap; + if (left + POPOVER_WIDTH > window.innerWidth - gap) { + left = Math.max(gap, r.left - POPOVER_WIDTH - gap); + } + let top = r.top; + if (top + POPOVER_ESTIMATE_H > window.innerHeight - gap) { + top = Math.max(gap, window.innerHeight - POPOVER_ESTIMATE_H - gap); + } + top = Math.max(gap, Math.min(top, window.innerHeight - gap)); + return { top, left }; +} + +export function SidebarSettingsMenu({ sidebarCollapsed }: { sidebarCollapsed: boolean }) { + const [open, setOpen] = useState(false); + const triggerRef = useRef(null); + const panelRef = useRef(null); + const titleId = useId(); + const [coords, setCoords] = useState({ top: 0, left: 0 }); + + const close = useCallback(() => { + setOpen(false); + queueMicrotask(() => triggerRef.current?.focus()); + }, []); + + const reposition = useCallback(() => { + const el = triggerRef.current; + if (!el) return; + setCoords(positionPopover(el)); + }, []); + + useLayoutEffect(() => { + if (!open) return; + reposition(); + }, [open, sidebarCollapsed, reposition]); + + useEffect(() => { + if (!open) return; + const onResize = () => reposition(); + window.addEventListener("resize", onResize); + return () => window.removeEventListener("resize", onResize); + }, [open, reposition]); + + useEffect(() => { + if (!open) return; + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") close(); + }; + document.addEventListener("keydown", onKey, true); + return () => document.removeEventListener("keydown", onKey, true); + }, [open, close]); + + useEffect(() => { + if (!open) return; + const onPointer = (e: MouseEvent | PointerEvent) => { + const t = e.target as Node; + if (panelRef.current?.contains(t) || triggerRef.current?.contains(t)) return; + close(); + }; + document.addEventListener("pointerdown", onPointer, true); + return () => document.removeEventListener("pointerdown", onPointer, true); + }, [open, close]); + + useEffect(() => { + if (!open) return; + const t = window.setTimeout(() => { + const first = panelRef.current?.querySelector( + 'input[type="radio"], button:not([disabled])', + ); + first?.focus(); + }, 0); + return () => window.clearTimeout(t); + }, [open]); + + const portal = + open && typeof document !== "undefined" + ? createPortal( + <> + ); } diff --git a/web/src/components/sidebarIcons.tsx b/web/src/components/sidebarIcons.tsx index d9d99d0..a8b5f55 100644 --- a/web/src/components/sidebarIcons.tsx +++ b/web/src/components/sidebarIcons.tsx @@ -98,6 +98,36 @@ export function IconSettings({ size = 20, ...props }: SVGProps & ); } +/** Light theme (sun) */ +export function IconSun({ size = 18, ...props }: SVGProps & { size?: number }) { + return shell( + size, + <> + + + , + props, + ); +} + +/** Dark theme (moon) */ +export function IconMoon({ size = 18, ...props }: SVGProps & { size?: number }) { + return shell(size, , props); +} + +/** System / prefers-color-scheme (monitor) */ +export function IconMonitor({ size = 18, ...props }: SVGProps & { size?: number }) { + return shell( + size, + <> + + + + , + props, + ); +} + export function IconChevronLeft({ size = 18, ...props }: SVGProps & { size?: number }) { return shell(size, , props); } diff --git a/web/src/index.css b/web/src/index.css index eeb2129..cd67946 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -49,7 +49,7 @@ --fd-radius: 10px; --fd-radius-sm: 6px; - --fd-shadow: 0 1px 2px rgba(15, 23, 42, 0.06); + --fd-shadow: 0 1px 2px rgba(15, 23, 42, 0.05), 0 1px 3px rgba(15, 23, 42, 0.08); /* Offline-first: system UI stack (no remote font download). */ --fd-font: system-ui, "Segoe UI Variable", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", Arial, sans-serif; @@ -122,7 +122,7 @@ html[data-theme="dark"] { --fd-input-focus-glow: rgba(34, 211, 238, 0.35); --fd-checkbox-focus-ring: rgba(34, 211, 238, 0.55); - --fd-shadow: 0 1px 2px rgba(0, 0, 0, 0.35); + --fd-shadow: 0 1px 2px rgba(0, 0, 0, 0.28), 0 1px 3px rgba(0, 0, 0, 0.22); --fd-focus-ring: 0 0 0 2px var(--fd-surface), 0 0 0 4px rgba(34, 211, 238, 0.45); } @@ -145,7 +145,7 @@ body { align-items: stretch; } -/* Langfuse-style: fixed left rail + scrollable main column */ +/* App shell: fixed left rail + scrollable main column */ .fd-sidebar { position: sticky; top: 0; @@ -383,85 +383,91 @@ html[data-theme="dark"] .fd-sidebar__logo-wrap { min-width: 0; } -.fd-theme-toggle__legend { - padding: 0; - margin: 0 0 0.4rem; - font-size: 0.68rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--fd-muted); -} - -.fd-theme-toggle__options { - display: flex; - flex-direction: column; - gap: 0.2rem; -} - -.fd-theme-toggle__label { +/* Sidebar settings popover: “Theme” label + sun / moon / monitor icon radios */ +.fd-theme-toggle--icons { display: flex; + flex-direction: row; + flex-wrap: wrap; align-items: center; - gap: 0.45rem; - font-size: 0.82rem; - font-weight: 500; - color: var(--fd-text); - cursor: pointer; - user-select: none; - padding: 0.28rem 0.15rem; - border-radius: var(--fd-radius-sm); + gap: 0.55rem 0.65rem; } -.fd-theme-toggle__label:hover { - background: var(--fd-surface-2); -} - -.fd-theme-toggle__input { - width: 0.95rem; - height: 0.95rem; +.fd-theme-toggle__theme-label { margin: 0; - flex-shrink: 0; - accent-color: var(--fd-accent); -} - -.fd-theme-toggle__text { - line-height: 1.3; -} - -.fd-theme-toggle--settings .fd-theme-toggle__legend { - font-size: 0.75rem; - margin-bottom: 0.65rem; + font-size: 0.84rem; + font-weight: 600; + letter-spacing: -0.01em; + color: var(--fd-text); } -.fd-theme-toggle--settings .fd-theme-toggle__options { +.fd-theme-toggle__icon-row { + display: flex; flex-direction: row; - flex-wrap: wrap; - gap: 0.5rem 0.75rem; + align-items: center; + gap: 0.35rem; + flex: 1; + min-width: 0; + justify-content: flex-end; } -.fd-theme-toggle--settings .fd-theme-toggle__label { - padding: 0.45rem 0.75rem; - border: 1px solid var(--fd-border); +.fd-theme-toggle__icon-option { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 2.35rem; + height: 2.35rem; + margin: 0; + padding: 0; border-radius: var(--fd-radius-sm); + border: 1px solid var(--fd-border); background: var(--fd-surface-2); + color: var(--fd-muted); + cursor: pointer; + user-select: none; + transition: + background 0.12s ease, + border-color 0.12s ease, + color 0.12s ease; } -.fd-theme-toggle--settings .fd-theme-toggle__label:hover { +.fd-theme-toggle__icon-option:hover { background: var(--fd-surface-elevated); + color: var(--fd-text); } -.fd-theme-toggle--settings .fd-theme-toggle__label:has(.fd-theme-toggle__input:checked) { +.fd-theme-toggle__icon-option:has(.fd-theme-toggle__input:focus-visible) { + outline: 2px solid var(--fd-accent); + outline-offset: 2px; +} + +.fd-theme-toggle__icon-option:has(.fd-theme-toggle__input:checked) { border-color: var(--fd-gradient-start); background: var(--fd-surface); + color: var(--fd-text); box-shadow: 0 0 0 1px rgba(8, 145, 178, 0.35); } -html[data-theme="dark"] .fd-theme-toggle--settings .fd-theme-toggle__label:has(.fd-theme-toggle__input:checked) { +html[data-theme="dark"] .fd-theme-toggle__icon-option:has(.fd-theme-toggle__input:checked) { box-shadow: 0 0 0 1px rgba(34, 211, 238, 0.4); } -.fd-settings-appearance { - margin-top: 0.35rem; +.fd-theme-toggle__icon-option .fd-theme-toggle__input { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + margin: 0; + opacity: 0; + cursor: pointer; + accent-color: var(--fd-accent); +} + +.fd-theme-toggle__icon-wrap { + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; } .fd-shell__content { @@ -517,6 +523,16 @@ html[data-theme="dark"] .fd-theme-toggle--settings .fd-theme-toggle__label:has(. box-shadow: inset 3px 0 0 0 var(--fd-nav-active-indicator); } +button.fd-nav__link--button { + width: 100%; + margin: 0; + border: 0; + background: transparent; + font: inherit; + text-align: inherit; + appearance: none; +} + .fd-sidebar--collapsed .fd-nav__link { justify-content: center; gap: 0; @@ -753,6 +769,19 @@ html[data-theme="dark"] .fd-theme-toggle--settings .fd-theme-toggle__label:has(. scrollbar-gutter: stable; } +.fd-table-wrap--sticky { + max-height: min(28rem, 70vh); + overflow: auto; +} + +.fd-table-wrap--sticky .fd-table thead th { + position: sticky; + top: 0; + z-index: 1; + background: var(--fd-surface); + box-shadow: 0 1px 0 var(--fd-border); +} + .fd-table { width: 100%; border-collapse: collapse; @@ -790,6 +819,26 @@ html[data-theme="dark"] .fd-theme-toggle--settings .fd-theme-toggle__label:has(. } } +.fd-table--striped tbody tr:nth-child(even) td { + background: color-mix(in srgb, var(--fd-surface-2) 65%, var(--fd-surface)); +} + +@media (prefers-reduced-motion: no-preference) { + .fd-table--striped tbody tr:nth-child(even) td { + transition: background 0.12s ease; + } +} + +.fd-table--striped.fd-table--hover tbody tr:hover td { + background: color-mix(in srgb, var(--fd-surface-2) 88%, var(--fd-accent) 12%); +} + +@media (prefers-reduced-motion: no-preference) { + .fd-table--striped.fd-table--hover tbody tr:hover td { + transition: background 0.12s ease; + } +} + .fd-th-narrow { width: 4.5rem; } @@ -1022,6 +1071,45 @@ code.fd-mono--sm { padding: 0.45rem 0.95rem; font-size: 0.9rem; font-weight: 500; + min-height: 2.25rem; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.35rem; + line-height: 1.25; +} + +.fd-btn__label { + display: inline-flex; + align-items: center; + gap: 0.35rem; +} + +.fd-btn__spinner { + width: 0.95rem; + height: 0.95rem; + flex-shrink: 0; + border: 2px solid color-mix(in srgb, currentColor 28%, transparent); + border-top-color: currentColor; + border-radius: 50%; + animation: fd-btn-spin 0.65s linear infinite; +} + +.fd-btn--loading .fd-btn__label { + opacity: 0.92; +} + +@keyframes fd-btn-spin { + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: reduce) { + .fd-btn__spinner { + animation: none; + border-top-color: color-mix(in srgb, currentColor 55%, transparent); + } } @media (prefers-reduced-motion: no-preference) { @@ -1113,6 +1201,12 @@ code.fd-mono--sm { color: var(--fd-muted); } +.fd-field__required { + color: var(--fd-fail-fg); + font-weight: 700; + margin-left: 0.15rem; +} + .fd-input { font: inherit; padding: 0.45rem 0.55rem; @@ -1120,6 +1214,7 @@ code.fd-mono--sm { border-radius: var(--fd-radius-sm); background: var(--fd-surface); color: var(--fd-text); + min-height: 2.25rem; } @media (prefers-reduced-motion: no-preference) { @@ -1139,6 +1234,39 @@ code.fd-mono--sm { box-shadow: 0 0 0 1px var(--fd-surface), 0 0 0 3px var(--fd-input-focus-glow); } +.fd-input--invalid { + border-color: var(--fd-fail-border); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--fd-fail-fg) 25%, transparent); +} + +.fd-select { + appearance: none; + font: inherit; + padding: 0.45rem 2rem 0.45rem 0.55rem; + border: 1px solid var(--fd-border-strong); + border-radius: var(--fd-radius-sm); + background-color: var(--fd-surface); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23525a63' d='M2.5 4.5 6 8l3.5-3.5'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.55rem center; + color: var(--fd-text); + cursor: pointer; + min-height: 2.25rem; +} + +html[data-theme="dark"] .fd-select { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238b949e' d='M2.5 4.5 6 8l3.5-3.5'/%3E%3C/svg%3E"); +} + +.fd-select:focus { + outline: none; +} + +.fd-select:focus-visible { + border-color: var(--fd-accent); + box-shadow: 0 0 0 1px var(--fd-surface), 0 0 0 3px var(--fd-input-focus-glow); +} + .fd-actions { display: flex; flex-wrap: wrap; @@ -1375,6 +1503,87 @@ code.fd-mono--sm { margin-top: 0; } +.fd-status-chip-row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.45rem 0.55rem; +} + +.fd-status-chip { + display: inline-flex; + align-items: baseline; + gap: 0.35rem; + padding: 0.28rem 0.55rem; + border-radius: 999px; + border: 1px solid var(--fd-border); + background: var(--fd-surface); + font-size: 0.78rem; + line-height: 1.3; +} + +.fd-status-chip__label { + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--fd-muted); + font-size: 0.68rem; +} + +.fd-status-chip__value { + font-weight: 600; + color: var(--fd-text); +} + +.fd-status-chip--neutral { + /* Explicit default tone; base .fd-status-chip already styles the shell. */ + border-color: var(--fd-border); + background: var(--fd-surface); +} + +.fd-status-chip--pass { + border-color: color-mix(in srgb, var(--fd-pass-fg) 35%, var(--fd-border)); + background: var(--fd-pass-bg); +} + +.fd-status-chip--pass .fd-status-chip__value { + color: var(--fd-pass-fg); +} + +.fd-status-chip--warn { + border-color: var(--fd-warn-border); + background: var(--fd-warn-bg); +} + +.fd-status-chip--warn .fd-status-chip__value { + color: var(--fd-warn-fg); +} + +.fd-status-chip--fail { + border-color: var(--fd-fail-border); + background: var(--fd-fail-bg); +} + +.fd-status-chip--fail .fd-status-chip__value { + color: var(--fd-fail-fg); +} + +.fd-status-chip--info { + border-color: var(--fd-info-border); + background: var(--fd-info-bg); +} + +.fd-status-chip--info .fd-status-chip__value { + color: var(--fd-info-fg); +} + +.fd-security-strip__detail { + margin: 0.45rem 0 0; + font-size: 0.82rem; + line-height: 1.45; + max-width: 72rem; +} + .fd-security-strip__msg { margin: 0; text-decoration: none; @@ -1657,10 +1866,33 @@ button.fd-skip-link { } .fd-btn--sm { + min-height: 2rem; padding: 0.28rem 0.55rem; font-size: 0.82rem; } +.fd-link { + color: var(--fd-accent); + font-weight: 600; + text-decoration: none; +} + +.fd-link:hover { + color: var(--fd-accent-hover); + text-decoration: underline; + text-decoration-thickness: 1px; + text-underline-offset: 0.14em; +} + +.fd-link:focus { + outline: none; +} + +.fd-link:focus-visible { + border-radius: 2px; + box-shadow: var(--fd-focus-ring); +} + .fd-card--hint { background: var(--fd-surface-2); border-style: dashed; @@ -1783,6 +2015,42 @@ button.fd-skip-link { min-height: 0; } +.fd-settings-popover-backdrop { + position: fixed; + inset: 0; + z-index: 80; + background: transparent; +} + +.fd-settings-popover { + position: fixed; + z-index: 85; + padding: 1rem 1.1rem 1.15rem; + border-radius: var(--fd-radius); + border: 1px solid var(--fd-border-strong); + background: var(--fd-surface); + box-shadow: var(--fd-drawer-shadow); +} + +.fd-settings-popover__head { + margin: 0 0 0.35rem; + font-size: 0.95rem; + font-weight: 650; + letter-spacing: -0.01em; + color: var(--fd-text); +} + +.fd-settings-popover__hint { + margin: 0 0 0.75rem; + font-size: 0.78rem; + color: var(--fd-muted); + line-height: 1.4; +} + +.fd-settings-popover__body { + margin: 0 -0.25rem; +} + .fd-diff-stack { display: flex; flex-direction: column; diff --git a/web/src/pages/ActionsPage.tsx b/web/src/pages/ActionsPage.tsx index cc152c4..b4a0c1a 100644 --- a/web/src/pages/ActionsPage.tsx +++ b/web/src/pages/ActionsPage.tsx @@ -4,8 +4,10 @@ import type { ActionOutcomePayload, PromotionRequestListItem, WorkspacePublicPay import { fetchHealth, fetchJson, fetchPromotionRequests, fetchWorkspace } from "../api"; import { clientMutationTokenConfigured } from "../uiConfig"; import { Badge } from "../components/Badge"; +import { Button } from "../components/Button"; import { JsonPanel } from "../components/JsonPanel"; import { useTimelineRefresh } from "../context/TimelineRefreshContext"; +import { useDocumentTitle } from "../useDocumentTitle"; function shortId(id: string, keepStart = 10, keepEnd = 6) { if (id.length <= keepStart + keepEnd + 1) return id; @@ -57,7 +59,11 @@ function pickOutcome(data: unknown): ActionOutcomePayload | null { type Busy = null | "promote" | "rollback" | "request" | "confirm"; +/** Client-side validation highlight for promote/confirm forms (not API errors). */ +type ActionsFieldError = null | "step1_reason" | "confirm_request" | "confirm_approval"; + export function ActionsPage() { + useDocumentTitle("Promote & rollback"); const [searchParams] = useSearchParams(); const { notifyTimelineMutated } = useTimelineRefresh(); const [workspace, setWorkspace] = useState(null); @@ -75,6 +81,7 @@ export function ActionsPage() { const [actOutcome, setActOutcome] = useState(null); const [actRaw, setActRaw] = useState(null); const [actErr, setActErr] = useState(null); + const [actFieldError, setActFieldError] = useState(null); const [busy, setBusy] = useState(null); const [confirmRequestId, setConfirmRequestId] = useState(""); @@ -155,11 +162,13 @@ export function ActionsPage() { const runAction = async (path: "/v1/promote" | "/v1/rollback") => { setActErr(null); + setActFieldError(null); setActOutcome(null); setActRaw(null); setRequestRaw(null); const reason = actReason.trim(); if (!reason) { + setActFieldError("step1_reason"); setActErr("Reason is required."); return; } @@ -190,6 +199,7 @@ export function ActionsPage() { setActRaw(JSON.stringify(data, null, 2)); notifyTimelineMutated(); } catch (e) { + setActFieldError(null); setActErr(String(e)); } finally { setBusy(null); @@ -198,11 +208,13 @@ export function ActionsPage() { const runRequestPromotion = async () => { setActErr(null); + setActFieldError(null); setActOutcome(null); setActRaw(null); setRequestRaw(null); const reason = actReason.trim(); if (!reason) { + setActFieldError("step1_reason"); setActErr("Reason is required for the promotion request."); return; } @@ -229,6 +241,7 @@ export function ActionsPage() { setListNonce((n) => n + 1); notifyTimelineMutated(); } catch (e) { + setActFieldError(null); setActErr(String(e)); } finally { setBusy(null); @@ -237,15 +250,18 @@ export function ActionsPage() { const runConfirmPromotion = async () => { setActErr(null); + setActFieldError(null); setActOutcome(null); setActRaw(null); const rid = confirmRequestId.trim(); const ar = confirmReason.trim(); if (!rid) { + setActFieldError("confirm_request"); setActErr("Request ID is required to confirm."); return; } if (!ar) { + setActFieldError("confirm_approval"); setActErr("Approval reason is required."); return; } @@ -272,6 +288,7 @@ export function ActionsPage() { setListNonce((n) => n + 1); notifyTimelineMutated(); } catch (e) { + setActFieldError(null); setActErr(String(e)); } finally { setBusy(null); @@ -349,7 +366,7 @@ export function ActionsPage() { ) : null} -
+
{approvalOn ? (

1. Request a promotion

@@ -393,18 +410,33 @@ export function ActionsPage() { />
- {!canMutate ? ( + {workspaceLoading ? ( +

+ Loading workspace… +

+ ) : workspaceErr ? (

- Loading workspace mode… + Workspace unavailable — resolve the error above before promoting or rolling back.

) : (

@@ -414,32 +446,35 @@ export function ActionsPage() { )}

{approvalOn ? ( - + Request promotion + ) : ( - + Promote + )} - + Rollback + {showBearerTokenHint ? ( Server uses Bearer for mutations — set{" "} @@ -459,21 +494,22 @@ export function ActionsPage() { Open requests waiting for an approver. Use Use for confirm to copy an ID into step 3.

- + Refresh list +
{pendingErr ?

{pendingErr}

: null} {pendingList.length === 0 ? (

No pending requests. After you request a promotion, it appears here.

) : ( -
- +
+
@@ -497,15 +533,15 @@ export function ActionsPage() { ))} @@ -527,31 +563,40 @@ export function ActionsPage() {
- + Confirm promotion +
diff --git a/web/src/pages/DiffPage.tsx b/web/src/pages/DiffPage.tsx index 3c2ddae..32979bc 100644 --- a/web/src/pages/DiffPage.tsx +++ b/web/src/pages/DiffPage.tsx @@ -7,6 +7,7 @@ import { DiffDecisionCard } from "../components/diff/DiffDecisionCard"; import { DiffPolicyPanel } from "../components/diff/DiffPolicyPanel"; import { DiffReleaseTwin } from "../components/diff/DiffReleaseTwin"; import { DiffVerdictStack } from "../components/diff/DiffVerdictStack"; +import { Button } from "../components/Button"; import { type DiffJson, isRecord, @@ -15,8 +16,10 @@ import { } from "../components/diff/diffPayload"; import { UI_READ_ONLY } from "../uiConfig"; import { pickTrimmedSearch, searchParamsFromRecord } from "../urlSearch"; +import { useDocumentTitle } from "../useDocumentTitle"; export function DiffPage() { + useDocumentTitle("Run diff"); const [searchParams, setSearchParams] = useSearchParams(); const [diffResultSeq, setDiffResultSeq] = useState(0); const [diffBaseline, setDiffBaseline] = useState(""); @@ -138,14 +141,9 @@ export function DiffPage() {
- - {busy ? ( - - Computing diff - - ) : null} +
diff --git a/web/src/pages/OverviewPage.tsx b/web/src/pages/OverviewPage.tsx index 0a1573d..3cacc1d 100644 --- a/web/src/pages/OverviewPage.tsx +++ b/web/src/pages/OverviewPage.tsx @@ -9,6 +9,7 @@ import { JsonPanel } from "../components/JsonPanel"; import { ReleaseLifecycleStrip } from "../components/ReleaseLifecycleStrip"; import { UI_READ_ONLY } from "../uiConfig"; import { searchParamsFromRecord } from "../urlSearch"; +import { useDocumentTitle } from "../useDocumentTitle"; const OVERVIEW_POLL_MS = 30_000; @@ -38,12 +39,13 @@ function TableShell({ {description ?

{description}

: null} {toolbar ?
{toolbar}
: null} -
{children}
+
{children}
); } export function OverviewPage() { + useDocumentTitle("Overview"); const [searchParams, setSearchParams] = useSearchParams(); const focusReleaseId = (searchParams.get("release") ?? "").trim(); @@ -271,7 +273,7 @@ export function OverviewPage() { title="Promoted releases" description="Current promoted release ID per agent and environment — compare with newer registrations below." > -
Request ID{row.environment} {row.created_at} - +
+
@@ -299,6 +301,7 @@ export function OverviewPage() {
Agent{p.environment} @@ -345,7 +348,7 @@ export function OverviewPage() {