From 34f3c2888fc7d45d190d93e1ea42501b6cd3e474 Mon Sep 17 00:00:00 2001 From: kami Date: Sat, 29 Aug 2026 02:44:43 +0400 Subject: [PATCH] Rebuild the operator console on the ethos shell Nine screens against orchestra-ui-spec.md and the accepted mockups, on the ethos design system: signal violet #8F7AE5, the routing fork motif, the 64px rail and 56px top bar, mono for every machine value and sans for every human one. Each screen was built by its own agent against a fixed foundation, so the shell, tokens and primitives have one author and the screens cannot drift into nine dialects. Real data only. Where no endpoint exists the screen says which one it needs instead of inventing a value. That is most of what was learned here: Steer / Correct is disabled because nothing records a human decision from the web, take-control is disabled because nothing forwards keystrokes to a pane, context occupancy is missing from three screens, and projects can show no repo, remote, quality gate or verification policy because those live only in config.jsonc. Three bugs the render caught that no computed value would have: The previous stylesheet fought every shared class name and leaked properties the new rules never mention, which is how position:fixed survived on .topbar. It is now scoped under .legacy and applies only to the login route, which also stops its green accent and its backdrop-filter from reaching the console. Go marshals a zero time.Time as "0001-01-01T00:00:00Z" and omitempty does not omit a struct, so absent timestamps arrived populated-looking and rendered as "739855d ago". Stripped once at the API boundary every screen reads through, with a test. Long machine ids overflowed their cards and painted under the next one. Verified by rendering: chromium screenshots of the dashboard, tasks, task detail, terminal, workers and review at 1440px, and the dashboard at 390px. Geist is still not on disk, so both stacks fall back to the system faces. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CVbaKucEYBjMqVeUgJUsc1 --- .gitignore | 1 + .../webui/assets/assets/index-B0BkAbwp.css | 1 - .../webui/assets/assets/index-BAiY1a7C.js | 61 ++ .../webui/assets/assets/index-BUuDvtvX.css | 1 + .../webui/assets/assets/index-C0H-zYgt.js | 65 -- internal/webui/assets/ethos-icons.svg | 107 ++ internal/webui/assets/index.html | 16 +- web/index.html | 13 +- web/public/ethos-icons.svg | 107 ++ web/src/Console.tsx | 60 ++ web/src/api/client.test.ts | 20 + web/src/api/client.ts | 30 +- web/src/api/types.ts | 68 ++ web/src/components/Icon.tsx | 19 + web/src/components/Primitives.tsx | 133 +++ web/src/main.tsx | 8 +- web/src/screens/Dashboard.css | 126 +++ web/src/screens/Dashboard.tsx | 361 +++++++ web/src/screens/Decisions.css | 206 ++++ web/src/screens/Decisions.tsx | 458 ++++++++ web/src/screens/Projects.css | 16 + web/src/screens/Projects.tsx | 296 ++++++ web/src/screens/Review.css | 195 ++++ web/src/screens/Review.tsx | 426 ++++++++ web/src/screens/Settings.css | 164 +++ web/src/screens/Settings.tsx | 256 +++++ web/src/screens/TaskDetail.css | 293 ++++++ web/src/screens/TaskDetail.tsx | 454 ++++++++ web/src/screens/Tasks.css | 202 ++++ web/src/screens/Tasks.tsx | 329 ++++++ web/src/screens/Terminal.css | 192 ++++ web/src/screens/Terminal.tsx | 320 ++++++ web/src/screens/Workers.css | 198 ++++ web/src/screens/Workers.tsx | 362 +++++++ web/src/shell/CommandPalette.tsx | 62 ++ web/src/shell/Shell.tsx | 94 ++ web/src/style.css | 996 +++++++++--------- web/src/styles/ethos.tokens.css | 136 +++ web/src/styles/orchestra.css | 679 ++++++++++++ 39 files changed, 6958 insertions(+), 573 deletions(-) delete mode 100644 internal/webui/assets/assets/index-B0BkAbwp.css create mode 100644 internal/webui/assets/assets/index-BAiY1a7C.js create mode 100644 internal/webui/assets/assets/index-BUuDvtvX.css delete mode 100644 internal/webui/assets/assets/index-C0H-zYgt.js create mode 100644 internal/webui/assets/ethos-icons.svg create mode 100644 web/public/ethos-icons.svg create mode 100644 web/src/Console.tsx create mode 100644 web/src/components/Icon.tsx create mode 100644 web/src/components/Primitives.tsx create mode 100644 web/src/screens/Dashboard.css create mode 100644 web/src/screens/Dashboard.tsx create mode 100644 web/src/screens/Decisions.css create mode 100644 web/src/screens/Decisions.tsx create mode 100644 web/src/screens/Projects.css create mode 100644 web/src/screens/Projects.tsx create mode 100644 web/src/screens/Review.css create mode 100644 web/src/screens/Review.tsx create mode 100644 web/src/screens/Settings.css create mode 100644 web/src/screens/Settings.tsx create mode 100644 web/src/screens/TaskDetail.css create mode 100644 web/src/screens/TaskDetail.tsx create mode 100644 web/src/screens/Tasks.css create mode 100644 web/src/screens/Tasks.tsx create mode 100644 web/src/screens/Terminal.css create mode 100644 web/src/screens/Terminal.tsx create mode 100644 web/src/screens/Workers.css create mode 100644 web/src/screens/Workers.tsx create mode 100644 web/src/shell/CommandPalette.tsx create mode 100644 web/src/shell/Shell.tsx create mode 100644 web/src/styles/ethos.tokens.css create mode 100644 web/src/styles/orchestra.css diff --git a/.gitignore b/.gitignore index c6fb17c..d2a3e01 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ node_modules/ .node_modules/ web/dist/ web/tsconfig.tsbuildinfo +build/ diff --git a/internal/webui/assets/assets/index-B0BkAbwp.css b/internal/webui/assets/assets/index-B0BkAbwp.css deleted file mode 100644 index 1c9cb25..0000000 --- a/internal/webui/assets/assets/index-B0BkAbwp.css +++ /dev/null @@ -1 +0,0 @@ -:root{color-scheme:dark;font-family:Inter,ui-sans-serif,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:#eef2f0;background:#0b0f12;font-synthesis:none;--bg: #0b0f12;--sidebar: #0d1215;--surface-1: #11171b;--surface-2: #151d21;--surface-3: #1a2429;--surface-hover: #1e2a2f;--line: rgba(221, 236, 231, .09);--line-strong: rgba(221, 236, 231, .15);--text: #eef2f0;--text-soft: #a9b4b1;--text-muted: #6f7c79;--accent: #8ee0b2;--accent-strong: #b4f0cf;--accent-wash: rgba(86, 200, 139, .12);--accent-line: rgba(86, 200, 139, .3);--blue: #88bdec;--blue-wash: rgba(96, 164, 224, .12);--amber: #efbd72;--amber-wash: rgba(229, 164, 76, .12);--red: #ec8f85;--red-wash: rgba(226, 105, 92, .12);--violet: #b5a2ef;--violet-wash: rgba(154, 126, 226, .12);--mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;--radius-sm: 8px;--radius: 12px;--radius-lg: 18px;--shadow: 0 20px 65px rgba(0, 0, 0, .42);--ease: cubic-bezier(.2, 0, 0, 1)}*{box-sizing:border-box}html{min-width:320px;min-height:100%;background:var(--bg)}body{min-width:320px;min-height:100vh;margin:0;background:radial-gradient(circle at 82% -10%,rgba(73,129,109,.09),transparent 31rem),var(--bg)}body.modal-open{overflow:hidden}button,input,textarea,select,summary{font:inherit}button,.button-link{display:inline-flex;min-height:38px;align-items:center;justify-content:center;gap:8px;padding:8px 13px;border:1px solid var(--accent-line);border-radius:var(--radius-sm);color:#092016;background:var(--accent);font-size:12px;font-weight:700;text-decoration:none;cursor:pointer;transition:transform .14s var(--ease),background .14s var(--ease),border-color .14s var(--ease),color .14s var(--ease)}button:hover:not(:disabled),.button-link:hover{border-color:var(--accent-strong);background:var(--accent-strong);transform:translateY(-1px)}button:active:not(:disabled),.button-link:active{transform:translateY(0)}button:disabled{opacity:.42;cursor:not-allowed}button:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible,summary:focus-visible,a:focus-visible{outline:2px solid var(--accent);outline-offset:2px}a{color:inherit}input,textarea,select{width:100%;border:1px solid var(--line-strong);border-radius:var(--radius-sm);outline:0;color:var(--text);background:#0d1316;transition:border-color .14s var(--ease),box-shadow .14s var(--ease),background .14s var(--ease)}input,select{min-height:42px;padding:9px 11px}textarea{min-height:116px;padding:11px;line-height:1.55;resize:vertical}input:hover,textarea:hover,select:hover{border-color:#ddece73b}input:focus,textarea:focus,select:focus{border-color:var(--accent-line);background:#0f1619;box-shadow:0 0 0 3px #56c88b14}input::placeholder,textarea::placeholder{color:#53615e}select{color:var(--text-soft);cursor:pointer}kbd,.mono,.task-id,.task-short-id,pre,code,time{font-family:var(--mono);font-feature-settings:"tnum" 1,"zero" 1}.icon{display:block;flex:none}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.app-shell{min-height:100vh;padding-left:224px}.sidebar{position:fixed;inset:0 auto 0 0;z-index:20;display:flex;width:224px;flex-direction:column;padding:0 14px 15px;border-right:1px solid var(--line);background:#0d1215f7;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.brand{display:flex;height:64px;align-items:center;gap:11px;margin:0 -14px;padding:0 18px;border-bottom:1px solid var(--line);text-decoration:none}.logo-symbol{position:relative;display:block;width:25px;height:25px;flex:none}.logo-symbol i{position:absolute;display:block;width:7px;height:7px;border:2px solid var(--accent);border-radius:50%;box-shadow:0 0 13px #8ee0b233}.logo-symbol i:nth-child(1){top:0;left:0}.logo-symbol i:nth-child(2){right:0;top:9px}.logo-symbol i:nth-child(3){bottom:0;left:0}.logo-symbol:before,.logo-symbol:after{position:absolute;left:6px;width:14px;height:1px;transform-origin:left center;background:var(--accent);content:""}.logo-symbol:before{top:6px;transform:rotate(30deg)}.logo-symbol:after{bottom:6px;transform:rotate(-30deg)}.brand>span:last-child,.login-brand>span:last-child{display:grid;gap:1px}.brand b,.login-brand b{color:var(--text);font-size:14px;letter-spacing:-.02em}.brand small,.login-brand small{color:var(--text-muted);font:9px var(--mono);letter-spacing:.13em;text-transform:uppercase}.primary-nav{display:grid;gap:5px;margin-top:24px}.nav-heading,.menu-label{margin:0 10px 8px;color:var(--text-muted);font-size:9px;font-weight:700;letter-spacing:.13em;text-transform:uppercase}.primary-nav a{position:relative;display:grid;min-height:42px;grid-template-columns:20px minmax(0,1fr) auto;align-items:center;gap:10px;padding:8px 10px;border:1px solid transparent;border-radius:var(--radius-sm);color:var(--text-muted);font-size:12px;font-weight:600;text-decoration:none;transition:color .14s var(--ease),background .14s var(--ease),border-color .14s var(--ease)}.primary-nav a:hover{color:var(--text-soft);background:var(--surface-2)}.primary-nav a.active{border-color:var(--accent-line);color:var(--accent-strong);background:var(--accent-wash)}.primary-nav a.active:before{position:absolute;left:-15px;width:3px;height:22px;border-radius:0 4px 4px 0;background:var(--accent);content:""}.nav-count{min-width:24px;padding:2px 5px;border-radius:999px;color:var(--text-muted);background:var(--surface-3);font:9px var(--mono);text-align:center}.nav-count.attention{color:var(--amber);background:var(--amber-wash)}.sidebar-status{display:flex;align-items:flex-start;gap:10px;margin-top:auto;padding:12px 10px;border:1px solid var(--line);border-radius:var(--radius);background:var(--surface-1)}.sidebar-status>span:last-child{display:grid;min-width:0;gap:3px}.sidebar-status b{color:var(--text-soft);font-size:10px}.sidebar-status small{color:var(--text-muted);font-size:9px}.signal{display:inline-block;width:7px;height:7px;flex:none;margin-top:3px;border-radius:50%;background:var(--red);box-shadow:0 0 0 4px var(--red-wash)}.signal.online{background:var(--accent);box-shadow:0 0 0 4px var(--accent-wash)}.sidebar-foot{padding:13px 9px 0;color:#4e5a57;font:8px var(--mono);letter-spacing:.1em}.topbar{position:fixed;inset:0 0 auto 224px;z-index:18;display:flex;height:64px;align-items:center;gap:14px;padding:0 28px;border-bottom:1px solid var(--line);background:#0b0f12e0;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.topbar-context{display:flex;min-width:0;align-items:center;gap:6px;color:var(--text-muted);font-size:11px}.topbar-context b{overflow:hidden;color:var(--text-soft);font-weight:600;text-overflow:ellipsis;white-space:nowrap}.command-trigger,.account-trigger,.button-secondary,.icon-button{border-color:var(--line-strong);color:var(--text-soft);background:var(--surface-1)}.command-trigger:hover:not(:disabled),.account-trigger:hover:not(:disabled),.button-secondary:hover:not(:disabled),.icon-button:hover:not(:disabled){border-color:#ddece73d;color:var(--text);background:var(--surface-3)}.command-trigger{min-height:34px;margin-left:auto;padding:6px 8px 6px 10px;color:var(--text-muted);font-weight:500}.command-trigger kbd,.page-actions kbd{padding:3px 5px;border:1px solid var(--line-strong);border-radius:5px;color:var(--text-muted);background:#00000021;font-size:9px;font-weight:400}.account{position:relative}.account-trigger{min-height:34px;padding:3px 8px 3px 4px;font-weight:600}.avatar{display:grid;width:26px;height:26px;place-items:center;border:1px solid var(--accent-line);border-radius:7px;color:var(--accent-strong);background:var(--accent-wash);font:9px var(--mono)}.account-menu{position:absolute;top:calc(100% + 9px);right:0;display:grid;width:196px;gap:9px;padding:11px;border:1px solid var(--line-strong);border-radius:var(--radius);background:var(--surface-2);box-shadow:var(--shadow)}.account-menu-user{display:flex;align-items:center;gap:9px;padding:3px 4px 10px;border-bottom:1px solid var(--line)}.account-menu-user>span:last-child{display:grid;min-width:0;gap:2px}.account-menu-user b{overflow:hidden;color:var(--text);font-size:11px;text-overflow:ellipsis}.account-menu-user small{color:var(--text-muted);font-size:9px}.account-menu>a{display:flex;min-height:33px;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--radius-sm);color:var(--text-soft);background:var(--surface-3);font-size:11px;font-weight:650;text-decoration:none}.account-menu>a:hover{color:var(--text);background:var(--surface-hover)}.account-menu button{width:100%;min-height:33px;justify-content:flex-start;color:var(--text-soft);background:var(--surface-3)}.content{min-height:100vh;padding-top:64px}.page{width:min(1500px,100%);margin:0 auto;padding:38px 32px 68px}.page-header{display:flex;align-items:flex-end;justify-content:space-between;gap:28px;margin-bottom:28px}.page-header h1,.task-header h1,.empty-state h1{margin:0;font-size:clamp(32px,4vw,48px);font-weight:650;line-height:1.02;letter-spacing:-.055em}.page-header>div>p{max-width:580px;margin:10px 0 0;color:var(--text-soft);font-size:14px;line-height:1.55}.eyebrow{display:block;margin-bottom:8px;color:var(--accent);font-size:9px;font-weight:800;letter-spacing:.15em;text-transform:uppercase}.page-actions{display:flex;align-items:center;gap:9px}.back-link{display:inline-flex;align-items:center;gap:5px;margin-bottom:24px;color:var(--text-muted);font-size:11px;font-weight:600;text-decoration:none}.back-link:hover{color:var(--accent-strong)}.section-header,.panel-header{display:flex;align-items:center;justify-content:space-between;gap:18px}.section-header{margin-bottom:13px}.section-header h2,.panel-header h2{margin:0;font-size:14px;letter-spacing:-.015em}.section-header p,.panel-header p{margin:4px 0 0;color:var(--text-muted);font-size:11px}.sync-label{display:inline-flex;align-items:center;gap:8px;color:var(--text-muted);font:9px var(--mono);text-transform:uppercase}.sync-label .signal{margin:0}.panel{padding:20px;border:1px solid var(--line);border-radius:var(--radius);background:linear-gradient(145deg,#151d21e6,#10171be6)}.panel-intro{margin:7px 0 0;color:var(--text-muted);font-size:11px;line-height:1.5}.empty-state{display:grid;min-height:300px;place-items:center;align-content:center;padding:38px;border:1px dashed var(--line-strong);border-radius:var(--radius-lg);text-align:center;background:#11171b73}.empty-state.compact{min-height:220px}.empty-state .empty-icon{display:grid;width:44px;height:44px;place-items:center;margin-bottom:14px;border:1px solid var(--accent-line);border-radius:13px;color:var(--accent);background:var(--accent-wash)}.empty-state h1,.empty-state h2{margin:0;font-size:20px;letter-spacing:-.03em}.empty-state p{max-width:390px;margin:8px 0 17px;color:var(--text-muted);font-size:12px;line-height:1.55}.error-state .empty-icon{border-color:#ec8f8540;color:var(--red);background:var(--red-wash)}.skeleton{min-height:180px;border-radius:var(--radius);background:linear-gradient(100deg,var(--surface-1) 30%,var(--surface-2) 50%,var(--surface-1) 70%);background-size:250% 100%;animation:shimmer 1.5s infinite}.skeleton-title{width:300px;min-height:44px;margin-top:10px}.skeleton-subtitle{width:420px;max-width:75%;min-height:18px;margin-top:13px}.skeleton-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:50px}@keyframes shimmer{to{background-position-x:-250%}}.attention-banner{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:13px;margin-bottom:15px;padding:13px 14px;border:1px solid rgba(239,189,114,.26);border-radius:var(--radius);background:var(--amber-wash)}.attention-icon{display:grid;width:35px;height:35px;place-items:center;border:1px solid rgba(239,189,114,.25);border-radius:10px;color:var(--amber);background:#efbd7214}.attention-banner b{font-size:12px}.attention-banner p{margin:3px 0 0;color:var(--text-soft);font-size:11px}.attention-banner>a{display:inline-flex;align-items:center;gap:3px;color:var(--amber);font-size:11px;font-weight:700;text-decoration:none}.metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-bottom:32px}.metric{display:grid;min-width:0;grid-template-columns:auto minmax(0,1fr);gap:10px;padding:14px;border:1px solid var(--line);border-radius:var(--radius);background:#11171bd1}.metric-icon{display:grid;width:34px;height:34px;grid-row:span 2;place-items:center;border:1px solid var(--line);border-radius:9px;color:var(--text-muted);background:var(--surface-2)}.metric-copy{display:flex;min-width:0;align-items:baseline;justify-content:space-between;gap:8px}.metric-copy small{overflow:hidden;color:var(--text-soft);font-size:10px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.metric-copy b{color:var(--text);font:600 19px var(--mono)}.metric-detail{overflow:hidden;color:var(--text-muted);font-size:10px;text-overflow:ellipsis;white-space:nowrap}.metric.tone-good .metric-icon{border-color:var(--accent-line);color:var(--accent);background:var(--accent-wash)}.metric.tone-warning .metric-icon{border-color:#efbd7242;color:var(--amber);background:var(--amber-wash)}.board-toolbar{display:flex;align-items:center;gap:9px;margin-bottom:13px}.segmented{display:flex;gap:3px;margin-right:auto;padding:3px;border:1px solid var(--line);border-radius:10px;background:var(--surface-1)}.segmented button{min-height:31px;padding:5px 10px;border-color:transparent;color:var(--text-muted);background:transparent;font-size:10px;font-weight:650}.segmented button:hover:not(:disabled){border-color:transparent;color:var(--text-soft);background:var(--surface-3);transform:none}.segmented button.selected{border-color:var(--line-strong);color:var(--text);background:var(--surface-3);box-shadow:0 2px 7px #0000002e}.segmented button span{margin-left:5px;color:var(--text-muted);font:9px var(--mono)}.search-field{position:relative;display:flex;width:min(280px,28vw);align-items:center}.search-field>.icon{position:absolute;left:11px;z-index:1;color:var(--text-muted)}.search-field input{min-height:38px;padding:7px 34px;color:var(--text-soft);background:var(--surface-1);font-size:11px}.search-field button{position:absolute;right:5px;min-width:27px;min-height:27px;padding:0;border-color:transparent;color:var(--text-muted);background:transparent}.select-field{width:155px}.select-field select{min-height:38px;padding:7px 29px 7px 10px;background-color:var(--surface-1);font-size:11px}.task-board{display:grid;gap:10px;overflow-x:auto;padding-bottom:6px;scrollbar-color:var(--surface-3) transparent}.task-board.lanes-2{grid-template-columns:repeat(2,minmax(270px,1fr))}.task-board.lanes-3{grid-template-columns:repeat(3,minmax(255px,1fr))}.task-board.lanes-5{grid-template-columns:repeat(5,minmax(235px,1fr))}.task-board.lanes-7{grid-template-columns:repeat(7,minmax(225px,1fr))}.task-lane{min-height:310px;padding:10px;border:1px solid var(--line);border-radius:var(--radius);background:#11171b9e}.task-lane>header{display:flex;height:29px;align-items:center;justify-content:space-between;padding:0 3px 9px}.task-lane>header>span{display:inline-flex;align-items:center;gap:7px;color:var(--text-soft);font-size:10px;font-weight:750;letter-spacing:.09em;text-transform:uppercase}.task-lane>header i,.status-pill i,.health-badge i{display:block;width:6px;height:6px;border-radius:50%;background:var(--text-muted)}.lane-queued>header i,.status-pill.state-queued i{background:var(--blue);box-shadow:0 0 0 3px var(--blue-wash)}.lane-leased>header i,.status-pill.state-leased i{background:var(--accent);box-shadow:0 0 0 3px var(--accent-wash)}.lane-blocked>header i,.lane-needs_attention>header i,.status-pill.state-blocked i{background:var(--amber);box-shadow:0 0 0 3px var(--amber-wash)}.status-pill.state-needs_attention i{background:var(--amber);box-shadow:0 0 0 3px var(--amber-wash)}.lane-in_review>header i,.status-pill.state-in_review i{background:var(--violet);box-shadow:0 0 0 3px var(--violet-wash)}.lane-completed>header i,.status-pill.state-completed i{background:#8fcfc5;box-shadow:0 0 0 3px #8fcfc51a}.lane-failed>header i,.status-pill.state-failed i{background:var(--red);box-shadow:0 0 0 3px var(--red-wash)}.task-lane>header b{display:grid;min-width:20px;height:20px;place-items:center;border:1px solid var(--line);border-radius:999px;color:var(--text-muted);background:var(--surface-2);font:9px var(--mono)}.lane-cards{display:grid;align-content:start;gap:8px}.empty-lane{margin:0;padding:30px 9px;color:#53615e;font-size:10px;text-align:center}.task-card{position:relative;display:block;min-width:0;padding:12px;border:1px solid var(--line);border-radius:10px;color:inherit;background:var(--surface-2);text-decoration:none;box-shadow:0 5px 18px #00000014;transition:border-color .15s var(--ease),background .15s var(--ease),transform .15s var(--ease),box-shadow .15s var(--ease)}.task-card:hover{z-index:1;border-color:#8ee0b24d;background:var(--surface-hover);box-shadow:0 9px 22px #0000002b;transform:translateY(-2px)}.task-card.state-blocked{border-color:#efbd722e}.task-card.state-needs_attention{border-color:#efbd7240;background:linear-gradient(145deg,rgba(62,46,27,.28),var(--surface-2))}.task-card.state-in_review{border-color:#b5a2ef33}.task-card.state-failed{border-color:#ec8f852e}.task-card-top{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:9px}.status-pill{display:inline-flex;min-height:22px;align-items:center;gap:6px;padding:3px 7px;border:1px solid var(--line);border-radius:999px;color:var(--text-muted);background:#0b0f1259;font-size:8px;font-weight:750;letter-spacing:.05em;text-transform:uppercase}.status-pill.state-leased,.status-pill.state-completed{color:var(--accent-strong)}.status-pill.state-blocked,.status-pill.state-needs_attention{color:var(--amber)}.status-pill.state-in_review{color:var(--violet)}.status-pill.state-failed{color:var(--red)}.task-short-id{color:#5c6865;font-size:8px}.task-card h3{display:-webkit-box;overflow:hidden;margin:0 0 6px;font-size:13px;font-weight:650;line-height:1.4;letter-spacing:-.012em;-webkit-box-orient:vertical;-webkit-line-clamp:2}.task-card>p{display:-webkit-box;overflow:hidden;min-height:30px;margin:0 0 10px;color:var(--text-muted);font-size:11px;line-height:1.5;-webkit-box-orient:vertical;-webkit-line-clamp:2}.approval-chip,.reason-chip{display:inline-flex;align-items:center;gap:5px;margin:0 0 9px;padding:4px 6px;border-radius:5px;color:var(--amber);background:var(--amber-wash);font-size:9px;font-weight:650}.reason-chip{color:var(--text-soft);background:var(--surface-3)}.capability-list,.fact-tags>div,.worker-projects>div{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:9px}.capability-list span,.fact-tags b,.worker-projects b{padding:3px 5px;border:1px solid var(--line);border-radius:5px;color:var(--text-muted);background:#0b0f1247;font:9px var(--mono);font-weight:500}.task-card footer{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:9px;padding-top:9px;border-top:1px solid var(--line);color:#66736f;font-size:9px}.task-card footer>span:first-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.task-card footer time{color:var(--text-muted);white-space:nowrap}.overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:100;display:grid;padding:20px;background:#040708c7;-webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px)}.modal-overlay{place-items:center}.modal{width:min(740px,100%);max-height:calc(100vh - 40px);overflow:auto;border:1px solid var(--line-strong);border-radius:var(--radius-lg);background:#11181c;box-shadow:var(--shadow)}.modal-header{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;padding:22px 23px 18px;border-bottom:1px solid var(--line)}.modal-header h2{margin:0;font-size:22px;letter-spacing:-.035em}.modal-header p{margin:6px 0 0;color:var(--text-muted);font-size:11px}.icon-button{width:34px;min-height:34px;padding:0}.eligibility{display:flex;align-items:flex-start;gap:10px;margin:16px 23px 0;padding:10px 11px;border:1px solid var(--accent-line);border-radius:var(--radius-sm);background:var(--accent-wash)}.eligibility>span:last-child{display:grid;gap:2px}.eligibility b{color:var(--accent-strong);font-size:11px}.eligibility small{color:var(--text-muted);font-size:10px}.eligibility.warning{border-color:#efbd7247;background:var(--amber-wash)}.eligibility.warning .signal{background:var(--amber);box-shadow:0 0 0 4px var(--amber-wash)}.eligibility.warning b{color:var(--amber)}.create-modal form{display:grid;gap:14px;padding:18px 23px 23px}.create-modal label{display:grid;gap:6px}.create-modal label>span{display:flex;justify-content:space-between;color:var(--text-soft);font-size:11px;font-weight:650}.create-modal label>span b{color:var(--text-muted);font-size:8px;font-weight:600;letter-spacing:.05em;text-transform:uppercase}.create-modal label>small{color:var(--text-muted);font-size:10px}.form-row{display:grid;grid-template-columns:1fr 1fr;gap:12px}.short-textarea{min-height:82px}.advanced-fields{border-top:1px solid var(--line);padding-top:4px}.advanced-fields summary{color:var(--text-soft);font-size:10px;font-weight:650;cursor:pointer}.advanced-fields p{margin:8px 0;color:var(--text-muted);font-size:10px}.advanced-fields textarea{min-height:80px}.modal-actions{display:flex;justify-content:flex-end;gap:8px;padding-top:3px}.form-error,.notice{display:flex;align-items:flex-start;gap:7px;margin:0;padding:9px 10px;border:1px solid rgba(236,143,133,.28);border-radius:var(--radius-sm);color:var(--red);background:var(--red-wash);font-size:11px;line-height:1.45}.notice{border-color:var(--accent-line);color:var(--accent-strong);background:var(--accent-wash)}.palette-overlay{place-items:start center;padding-top:12vh}.palette{width:min(590px,100%);overflow:hidden;border:1px solid var(--line-strong);border-radius:15px;background:#11181c;box-shadow:var(--shadow)}.palette-input{position:relative;display:flex;align-items:center;border-bottom:1px solid var(--line)}.palette-input .icon{position:absolute;left:17px;color:var(--text-muted)}.palette-input input{min-height:57px;padding:14px 17px 14px 48px;border:0;border-radius:0;background:transparent;font-size:14px}.palette-list{display:grid;gap:3px;padding:10px}.palette-list .menu-label{margin:3px 8px 5px}.palette-list button{width:100%;min-height:42px;justify-content:space-between;padding:8px 10px;border-color:transparent;color:var(--text-soft);background:transparent;font-weight:550}.palette-list button>span{display:flex;align-items:center;gap:9px}.palette-list button.selected,.palette-list button:hover:not(:disabled){border-color:var(--line);color:var(--text);background:var(--surface-3);transform:none}.palette-list kbd,.palette-footer kbd{color:var(--text-muted);font-size:9px}.palette-empty{padding:24px 8px;color:var(--text-muted);font-size:11px;text-align:center}.palette-footer{display:flex;gap:15px;padding:9px 17px;border-top:1px solid var(--line);color:var(--text-muted);font-size:8px}.task-header{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;margin-bottom:20px}.task-heading-meta{display:flex;align-items:center;gap:8px;margin-bottom:11px;color:var(--text-muted);font-size:9px}.task-heading-meta>span:not(.status-pill)+span:before{margin-right:8px;color:#45514e;content:"/"}.task-header h1{max-width:850px;font-size:clamp(29px,4vw,44px)}.task-id{margin:9px 0 0;color:var(--text-muted);font-size:9px}.diagnosis{display:grid;grid-template-columns:minmax(0,1fr) minmax(420px,.8fr);align-items:center;gap:24px;margin-bottom:14px;padding:18px;border:1px solid var(--accent-line);border-radius:var(--radius);background:linear-gradient(110deg,var(--accent-wash),rgba(17,23,27,.65))}.diagnosis.state-blocked,.diagnosis.state-needs_attention,.diagnosis.state-failed{border-color:#efbd723b;background:linear-gradient(110deg,var(--amber-wash),rgba(17,23,27,.65))}.diagnosis.state-in_review{border-color:#b5a2ef42;background:linear-gradient(110deg,var(--violet-wash),rgba(17,23,27,.65))}.diagnosis-copy{display:flex;min-width:0;align-items:flex-start;gap:13px}.diagnosis-symbol{display:grid;width:38px;height:38px;flex:none;place-items:center;border:1px solid var(--accent-line);border-radius:10px;color:var(--accent);background:var(--accent-wash)}.diagnosis-symbol.state-blocked,.diagnosis-symbol.state-needs_attention,.diagnosis-symbol.state-failed{border-color:#efbd7240;color:var(--amber);background:var(--amber-wash)}.diagnosis-symbol.state-in_review{border-color:#b5a2ef4d;color:var(--violet);background:var(--violet-wash)}.diagnosis h2{margin:0;font-size:17px;letter-spacing:-.025em}.diagnosis-copy p{max-width:630px;margin:5px 0 0;color:var(--text-soft);font-size:10px;line-height:1.5}.diagnosis dl{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));margin:0;overflow:hidden;border:1px solid var(--line);border-radius:9px;background:#090e104d}.diagnosis dl>div{min-width:0;padding:9px;border-right:1px solid var(--line)}.diagnosis dl>div:last-child{border:0}.diagnosis dt,.facts dt,.worker-stats dt{margin-bottom:5px;color:var(--text-muted);font-size:9px;font-weight:700;letter-spacing:.09em;text-transform:uppercase}.diagnosis dd{overflow:hidden;margin:0;color:var(--text-soft);font-size:10px;text-overflow:ellipsis;white-space:nowrap}.approval-panel{display:grid;gap:12px;margin-bottom:14px;padding:18px;border:1px solid rgba(239,189,114,.3);border-radius:var(--radius);background:linear-gradient(135deg,#44301461,#151d21eb)}.approval-heading{display:flex;align-items:flex-start;gap:13px}.approval-heading h2{margin:0;font-size:18px}.approval-heading p{margin:5px 0 0;color:var(--text-soft);font-size:10px}.approval-meta{display:flex;flex-wrap:wrap;gap:6px}.approval-meta span{padding:4px 6px;border:1px solid var(--line);border-radius:5px;color:var(--text-muted);background:#0a0e104d;font:9px var(--mono)}.approval-meta b{color:var(--text-soft);font-weight:500}.approval-code,.artifact-content{margin:0;padding:14px;overflow:auto;border:1px solid var(--line-strong);border-radius:var(--radius-sm);color:#d4dcd9;background:#080c0e;font-size:11px;line-height:1.6;white-space:pre-wrap}.approval-note{margin:0;color:var(--text-soft);font-size:11px;line-height:1.5}.approval-actions{display:flex;gap:8px}.detail-layout{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:14px;align-items:start}.detail-main,.detail-side{display:grid;gap:14px}.immutable-badge{padding:4px 6px;border:1px solid var(--line);border-radius:5px;color:var(--text-muted);font:9px var(--mono);letter-spacing:.05em;text-transform:uppercase}.task-description{margin:14px 0 0;color:#c1cac7;font-size:12px;line-height:1.7;white-space:pre-wrap}.acceptance-list,.quality-gate{margin-top:18px;padding-top:15px;border-top:1px solid var(--line)}.acceptance-list h3{margin:0 0 9px;color:var(--text-soft);font-size:11px}.acceptance-list ul{display:grid;gap:7px;margin:0;padding:0;list-style:none}.acceptance-list li{display:flex;align-items:flex-start;gap:8px;color:var(--text-soft);font-size:11px;line-height:1.45}.acceptance-list li>span{display:grid;width:18px;height:18px;flex:none;place-items:center;border:1px solid var(--accent-line);border-radius:5px;color:var(--accent);background:var(--accent-wash)}.quality-gate{display:grid;gap:6px}.quality-gate>span{color:var(--text-muted);font-size:9px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.quality-gate code{display:block;padding:8px 10px;border:1px solid var(--line);border-radius:6px;color:var(--accent-strong);background:#0b1113;font-size:10px}.capture-panel{overflow:hidden;border:1px solid var(--line-strong);border-radius:var(--radius);background:#090d0f}.capture-panel>header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:11px 12px;border-bottom:1px solid var(--line);background:var(--surface-1)}.capture-title,.capture-actions{display:flex;align-items:center;gap:8px}.capture-title>span{display:grid;width:30px;height:30px;place-items:center;border:1px solid var(--line);border-radius:7px;color:var(--accent);background:var(--accent-wash)}.capture-title h2{margin:0;font-size:11px}.capture-title p{margin:3px 0 0;color:var(--text-muted);font:9px var(--mono)}.small-button{min-height:32px;padding:5px 9px;font-size:10px}.capture-actions .icon-button{min-height:32px;width:32px}.terminal-filter{position:relative;display:flex;align-items:center;margin:10px 11px 0}.terminal-filter .icon{position:absolute;left:9px;color:#55625e}.terminal-filter input{min-height:31px;padding:5px 9px 5px 30px;border-color:var(--line);background:#0c1113;font-size:10px}.terminal-output{max-height:530px;margin:0;padding:15px;overflow:auto;color:#aabcb5;font-size:11px;line-height:1.65;white-space:pre;scrollbar-color:var(--surface-3) transparent}.capture-panel>footer{padding:7px 12px;border-top:1px solid var(--line);color:var(--text-muted);background:var(--surface-1);font-size:9px}.capture-empty{display:flex;align-items:flex-start;gap:11px;padding:17px;border:1px dashed var(--line-strong);border-radius:var(--radius);background:#11171b80}.capture-empty>span{color:var(--text-muted)}.capture-empty b{font-size:11px}.capture-empty p{margin:4px 0 0;color:var(--text-muted);font-size:10px;line-height:1.5}.action-panel .panel-header{margin-bottom:0}.action-list{display:grid;gap:8px;margin-top:13px}.action-card{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:11px;margin:0;padding:11px;border:1px solid var(--line);border-radius:9px;background:#0a0f1152}.action-icon{display:grid;width:31px;height:31px;place-items:center;border:1px solid var(--line);border-radius:8px;color:var(--text-muted);background:var(--surface-2)}.action-card h3{margin:0;font-size:11px}.action-card p{margin:3px 0 0;color:var(--text-muted);font-size:10px;line-height:1.5}.action-form{align-items:start}.action-copy{min-width:0}.action-copy input,.action-copy textarea{min-height:36px;margin-top:9px;padding:7px 9px;font-size:10px}.action-copy textarea{min-height:78px}.action-buttons{display:flex;align-self:end;gap:6px}.action-card button{min-height:32px;padding:6px 9px;font-size:10px}.action-card.confirming{border-color:#efbd7247;background:var(--amber-wash)}.button-danger,.button-danger:hover:not(:disabled){border-color:#ec8f8566;color:#27100e;background:var(--red)}.inline-error{color:var(--red)!important}.unavailable-panel{padding:0}.unavailable-panel summary{display:flex;align-items:center;justify-content:space-between;padding:13px 15px;color:var(--text-muted);font-size:10px;font-weight:650;cursor:pointer}.unavailable-panel summary span{display:grid;width:20px;height:20px;place-items:center;border-radius:50%;background:var(--surface-3);font:8px var(--mono)}.unavailable-list{display:grid;gap:8px;padding:0 15px 14px}.unavailable-list>div{display:grid;gap:3px;padding:9px;border:1px solid var(--line);border-radius:7px}.unavailable-list b{color:var(--text-soft);font-size:10px}.unavailable-list span{color:var(--text-muted);font-size:9px}.facts{display:grid;gap:0;margin:10px 0 0}.facts>div{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:10px;padding:9px 0;border-bottom:1px solid var(--line)}.facts>div:last-child{border:0}.facts dt{margin:0}.facts dd{overflow:hidden;margin:0;color:var(--text-soft);font-size:10px;text-align:right;text-overflow:ellipsis;white-space:nowrap}.fact-tags{display:grid;gap:7px;margin-top:9px;padding-top:12px;border-top:1px solid var(--line)}.fact-tags>span,.worker-projects>span,.worker-current>span{color:var(--text-muted);font-size:9px;font-weight:700;letter-spacing:.09em;text-transform:uppercase}.fact-tags>div{margin:0}.artifacts-panel{display:grid;gap:7px}.artifacts-panel>a{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:9px;padding:9px;border:1px solid var(--line);border-radius:8px;color:var(--text-muted);text-decoration:none}.artifacts-panel>a:hover{border-color:var(--accent-line);color:var(--accent);background:var(--accent-wash)}.artifacts-panel a>span{display:grid;min-width:0;gap:2px}.artifacts-panel b{color:var(--text-soft);font-size:10px}.artifacts-panel small{color:var(--text-muted);font:9px var(--mono)}.timeline-panel{overflow:hidden}.timeline{margin:12px 0 0;padding:0;list-style:none}.timeline li{position:relative;display:grid;grid-template-columns:12px minmax(0,1fr);gap:6px;padding-bottom:13px}.timeline li:not(:last-child):before{position:absolute;top:11px;bottom:0;left:3px;width:1px;background:var(--line-strong);content:""}.event-dot{position:relative;z-index:1;display:block;width:7px;height:7px;margin-top:4px;border:2px solid var(--surface-2);border-radius:50%;background:var(--text-muted);box-shadow:0 0 0 1px var(--line-strong)}.timeline li.latest .event-dot{background:var(--accent);box-shadow:0 0 0 3px var(--accent-wash)}.event-copy{min-width:0}.event-copy>button{display:grid;width:100%;min-height:0;justify-content:stretch;gap:2px;padding:0;border:0;color:inherit;background:transparent;text-align:left}.event-copy>button:hover:not(:disabled){border:0;background:transparent;transform:none}.event-copy>button:disabled{opacity:1;cursor:default}.event-copy b{overflow:hidden;color:var(--text-soft);font-size:10px;text-overflow:ellipsis;white-space:nowrap}.event-copy span,.event-copy time{color:var(--text-muted);font-size:9px}.event-copy time{display:block;margin-top:3px}.event-copy pre{max-height:260px;margin:8px 0 0;padding:8px;overflow:auto;border:1px solid var(--line);border-radius:6px;color:var(--text-muted);background:#0b1012;font-size:9px;line-height:1.5;white-space:pre-wrap}.worker-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(350px,1fr));gap:11px}.worker-card{padding:17px;border:1px solid var(--line);border-radius:var(--radius);background:linear-gradient(145deg,var(--surface-2),var(--surface-1))}.worker-card.offline{border-color:#ec8f8533}.worker-card>header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.worker-identity{display:flex;min-width:0;align-items:center;gap:10px}.worker-glyph{display:grid;width:39px;height:39px;flex:none;place-items:center;border:1px solid var(--line);border-radius:10px;color:var(--accent);background:var(--accent-wash)}.worker-card.offline .worker-glyph{color:var(--red);background:var(--red-wash)}.worker-card h2{margin:0;font-size:13px}.worker-card header p{overflow:hidden;max-width:240px;margin:4px 0 0;color:var(--text-muted);font:9px var(--mono);text-overflow:ellipsis;white-space:nowrap}.health-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 7px;border:1px solid var(--accent-line);border-radius:999px;color:var(--accent-strong);background:var(--accent-wash);font-size:9px;font-weight:700;text-transform:uppercase}.health-badge i{background:var(--accent)}.health-badge.unhealthy{border-color:#ec8f8540;color:var(--red);background:var(--red-wash)}.health-badge.unhealthy i{background:var(--red)}.worker-stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));margin:15px 0 0;overflow:hidden;border:1px solid var(--line);border-radius:8px;background:#090e103d}.worker-stats>div{min-width:0;padding:9px;border-right:1px solid var(--line)}.worker-stats>div:last-child{border:0}.worker-stats dd{overflow:hidden;margin:0;color:var(--text-soft);font-size:10px;text-overflow:ellipsis;text-transform:capitalize;white-space:nowrap}.worker-stats dd.health-reachable{color:var(--accent)}.worker-stats dd.health-unreachable{color:var(--red)}.worker-current,.worker-projects{display:grid;gap:7px;margin-top:14px}.worker-current>a{display:flex;align-items:center;justify-content:space-between;gap:9px;padding:9px;border:1px solid var(--line);border-radius:8px;color:var(--text-muted);background:#090e103d;text-decoration:none}.worker-current>a:hover{border-color:var(--accent-line);color:var(--accent)}.worker-current>a>span{display:grid;min-width:0;gap:3px}.worker-current a b{overflow:hidden;color:var(--text-soft);font:10px var(--mono);text-overflow:ellipsis;white-space:nowrap}.worker-current a small{color:var(--text-muted);font:9px var(--mono)}.worker-current>p{margin:0;padding:9px;border:1px dashed var(--line);border-radius:8px;color:var(--text-muted);font-size:10px}.worker-projects>div{margin:0}.worker-error{display:flex;align-items:flex-start;gap:8px;margin-top:13px;padding:9px;border:1px solid rgba(236,143,133,.2);border-radius:8px;color:var(--red);background:var(--red-wash)}.worker-error>span{display:grid;gap:3px}.worker-error b,.worker-error small{font-size:9px;font-weight:500}.worker-error small{color:var(--text-muted)}.worker-card>footer{display:flex;justify-content:space-between;gap:10px;margin-top:14px;padding-top:11px;border-top:1px solid var(--line);color:var(--text-muted);font:9px var(--mono)}.settings-page{max-width:1160px}.settings-layout{display:grid;grid-template-columns:300px minmax(0,1fr);align-items:start;gap:16px}.profile-card,.settings-card{border:1px solid var(--line);border-radius:var(--radius-lg);background:linear-gradient(145deg,#151d21f0,#0e1417f0);box-shadow:0 18px 50px #00000024}.profile-card{padding:24px}.profile-avatar{display:grid;width:58px;height:58px;place-items:center;margin-bottom:16px;border:1px solid var(--accent-line);border-radius:17px;color:var(--accent-strong);background:linear-gradient(145deg,rgba(86,200,139,.2),var(--accent-wash));box-shadow:inset 0 1px #ffffff0d,0 12px 30px #0000002e;font:600 16px var(--mono)}.profile-card h2{margin:0;font-size:20px;letter-spacing:-.035em;overflow-wrap:anywhere}.profile-card>p{margin:5px 0 22px;color:var(--text-muted);font-size:11px}.profile-card dl{display:grid;gap:12px;margin:0;padding:17px 0;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}.profile-card dl>div{display:grid;gap:4px}.profile-card dt{color:var(--text-muted);font-size:9px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.profile-card dd{margin:0;color:var(--text-soft);font:9px var(--mono)}.database-badge{display:flex;align-items:flex-start;gap:10px;margin-top:18px;padding:12px;border:1px solid var(--accent-line);border-radius:10px;color:var(--accent);background:var(--accent-wash)}.database-badge>span{display:grid;gap:3px}.database-badge b{color:var(--accent-strong);font-size:10px}.database-badge small{color:var(--text-soft);font-size:9px;line-height:1.5}.settings-card{overflow:hidden}.settings-card>header{display:flex;align-items:flex-start;gap:13px;padding:22px 24px;border-bottom:1px solid var(--line);background:#11171b8c}.settings-icon{display:grid;width:38px;height:38px;flex:none;place-items:center;border:1px solid var(--accent-line);border-radius:11px;color:var(--accent);background:var(--accent-wash)}.settings-card h2{margin:1px 0 0;font-size:16px;letter-spacing:-.025em}.settings-card header p{margin:5px 0 0;color:var(--text-muted);font-size:10px}.settings-card form{display:grid;gap:9px;padding:24px}.settings-card label,.current-password-block>label{display:grid;gap:7px;color:var(--text-soft);font-size:10px;font-weight:650}.settings-field{display:grid;gap:7px}.settings-divider{display:flex;align-items:center;gap:12px;margin:14px 0 2px;color:var(--text-muted);font-size:9px;font-weight:700;letter-spacing:.1em;text-transform:uppercase}.settings-divider:after{height:1px;flex:1;background:var(--line);content:""}.password-rules{display:flex;flex-wrap:wrap;gap:7px;margin-top:3px}.password-rules span{display:inline-flex;align-items:center;gap:4px;padding:5px 7px;border:1px solid var(--line);border-radius:999px;color:var(--text-muted);background:var(--surface-1);font-size:9px}.password-rules span.met{border-color:var(--accent-line);color:var(--accent);background:var(--accent-wash)}.current-password-block{display:grid;grid-template-columns:minmax(0,1fr) minmax(220px,.65fr);align-items:end;gap:5px 18px;margin-top:14px;padding:15px;border:1px solid var(--line);border-radius:var(--radius);background:var(--surface-1)}.current-password-block label{grid-column:1}.current-password-block p{grid-column:1;margin:0;color:var(--text-muted);font-size:9px}.current-password-block input{grid-column:2;grid-row:1 / span 2}.settings-actions{display:flex;align-items:center;justify-content:space-between;gap:16px;margin:10px -24px -24px;padding:16px 24px;border-top:1px solid var(--line);background:#0a0f1147}.settings-actions>span{color:var(--text-muted);font-size:9px}.artifact-header{align-items:flex-end}.artifact-content{min-height:400px;padding:20px;font-size:11px;line-height:1.7}.login-page{position:relative;display:grid;min-height:100vh;place-items:center;overflow:hidden;padding:24px;background:radial-gradient(circle at 24% 10%,rgba(86,200,139,.17),transparent 34rem),radial-gradient(circle at 88% 92%,rgba(117,96,186,.09),transparent 30rem),#090d0f}.login-grid{position:absolute;top:0;right:0;bottom:0;left:0;opacity:.24;background-image:linear-gradient(rgba(169,180,177,.07) 1px,transparent 1px),linear-gradient(90deg,rgba(169,180,177,.07) 1px,transparent 1px);background-size:46px 46px;-webkit-mask-image:radial-gradient(circle at center,black,transparent 68%);mask-image:radial-gradient(circle at center,black,transparent 68%)}.login-layout{position:relative;display:grid;width:min(1040px,100%);grid-template-columns:minmax(0,1fr) 420px;gap:14px}.login-showcase{position:relative;display:flex;min-height:610px;flex-direction:column;overflow:hidden;padding:36px 40px;border:1px solid var(--line);border-radius:24px;background:linear-gradient(145deg,#192624e6,#0c1215f0),var(--surface-1);box-shadow:0 30px 90px #00000047}.login-showcase:before{position:absolute;width:430px;height:430px;right:-190px;top:-170px;border:1px solid rgba(142,224,178,.08);border-radius:50%;box-shadow:0 0 0 55px #8ee0b206,0 0 0 110px #8ee0b205;content:""}.showcase-brand{display:flex;align-items:center;gap:12px}.showcase-brand>span:last-child{display:grid;gap:2px}.showcase-brand b{font-size:14px;letter-spacing:-.02em}.showcase-brand small{color:var(--text-muted);font:8px var(--mono);letter-spacing:.1em;text-transform:uppercase}.showcase-copy{position:relative;max-width:500px;margin:auto 0 42px}.showcase-copy h2{margin:0;font-size:clamp(38px,4.2vw,58px);font-weight:620;line-height:.98;letter-spacing:-.064em}.showcase-copy p{max-width:460px;margin:20px 0 0;color:var(--text-soft);font-size:12px;line-height:1.7}.showcase-flow{display:grid;grid-template-columns:auto minmax(16px,1fr) auto minmax(16px,1fr) auto minmax(16px,1fr) auto;align-items:center;gap:8px;margin-bottom:36px;padding:14px;border:1px solid var(--line);border-radius:12px;background:#080d0f57}.showcase-flow>span{display:inline-flex;align-items:center;gap:6px;color:var(--text-soft);font:8px var(--mono);letter-spacing:.05em;text-transform:uppercase}.showcase-flow>b{height:1px;background:linear-gradient(90deg,var(--line-strong),var(--accent-line))}.flow-dot{display:block;width:6px;height:6px;border-radius:50%;background:var(--blue);box-shadow:0 0 0 3px var(--blue-wash)}.flow-dot.active,.flow-dot.done{background:var(--accent);box-shadow:0 0 0 3px var(--accent-wash)}.flow-dot.review{background:var(--violet);box-shadow:0 0 0 3px var(--violet-wash)}.login-showcase>footer{display:flex;align-items:center;gap:9px;padding-top:20px;border-top:1px solid var(--line);color:var(--text-muted);font:8px var(--mono);letter-spacing:.09em;text-transform:uppercase}.login-showcase>footer .signal{margin:0}.login-card{position:relative;width:100%;align-self:center;padding:30px;border:1px solid var(--line-strong);border-radius:22px;background:#11181cf2;box-shadow:0 30px 90px #00000080}.login-brand{display:flex;align-items:center;gap:11px;margin-bottom:38px}.login-heading h1{margin:0;font-size:36px;letter-spacing:-.055em}.login-heading>p{margin:8px 0 0;color:var(--text-soft);font-size:12px;line-height:1.55}.login-card form{display:grid;gap:9px;margin-top:24px}.login-card form>label{color:var(--text-soft);font-size:10px;font-weight:650}.password-field{position:relative}.password-field input{padding-right:62px}.password-field button{position:absolute;top:50%;right:5px;min-height:31px;padding:5px 8px;transform:translateY(-50%);border-color:transparent;color:var(--accent);background:transparent;font-size:10px}.password-field button:hover:not(:disabled){border-color:transparent;background:var(--accent-wash);transform:translateY(-50%)}.caps-lock{color:var(--amber);font-size:10px}.login-submit{min-height:42px;margin-top:7px}.login-security{display:flex;gap:9px;margin-top:23px;padding-top:16px;border-top:1px solid var(--line);color:var(--text-muted)}.login-security>span{display:grid;width:24px;height:24px;flex:none;place-items:center;border-radius:7px;color:var(--accent);background:var(--accent-wash)}.login-security p{margin:0;font-size:10px;line-height:1.55}.session-check{position:relative;display:flex;align-items:center;gap:11px;padding:15px 18px;border:1px solid var(--line-strong);border-radius:var(--radius);color:var(--text-soft);background:var(--surface-1);box-shadow:var(--shadow);font-size:11px}.spinner{display:inline-block;width:13px;height:13px;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media(max-width:1180px){.metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.diagnosis{grid-template-columns:1fr}.task-board.lanes-3{grid-template-columns:repeat(3,minmax(235px,1fr))}}@media(max-width:980px){.app-shell{padding-left:72px}.sidebar{width:72px;padding-right:10px;padding-left:10px}.brand{justify-content:center;margin-right:-10px;margin-left:-10px;padding:0}.brand>span:last-child,.nav-heading,.primary-nav a>span,.nav-count,.sidebar-status>span:last-child,.sidebar-foot{display:none}.primary-nav a{display:grid;width:50px;grid-template-columns:1fr;place-items:center;padding:0}.primary-nav a.active:before{left:-11px}.sidebar-status{justify-content:center;padding:11px}.topbar{left:72px}.detail-layout{grid-template-columns:minmax(0,1fr) 300px}.login-layout{grid-template-columns:minmax(0,1fr) 390px}.login-showcase{padding:30px}}@media(max-width:800px){.page{padding-right:20px;padding-left:20px}.detail-layout{grid-template-columns:1fr}.detail-side{grid-template-columns:1fr 1fr;align-items:start}.timeline-panel{grid-column:1 / -1}.board-toolbar{flex-wrap:wrap}.segmented{width:100%;margin-right:0}.segmented button{flex:1}.search-field{width:calc(100% - 164px)}.settings-layout{grid-template-columns:1fr}.profile-card{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:16px}.profile-avatar{grid-row:span 2;margin:0}.profile-card>p{margin:3px 0 16px}.profile-card dl,.database-badge{grid-column:1 / -1}.login-layout{width:min(430px,100%);grid-template-columns:1fr}.login-showcase{display:none}}@media(max-width:680px){.app-shell{padding:0 0 64px}.sidebar{inset:auto 0 0;width:100%;height:64px;padding:6px 10px;border-top:1px solid var(--line-strong);border-right:0}.brand,.sidebar-status{display:none}.primary-nav{display:flex;height:100%;justify-content:space-around;gap:8px;margin:0}.primary-nav a{width:auto;flex:1;grid-template-columns:auto auto;place-content:center;gap:7px}.primary-nav a>span{display:inline}.primary-nav a.active:before{inset:-7px auto auto 50%;width:26px;height:3px;transform:translate(-50%);border-radius:0 0 4px 4px}.topbar{left:0;height:56px;padding:0 15px}.content{padding-top:56px}.topbar-context>span,.topbar-context .icon,.command-trigger span,.command-trigger kbd,.account-label{display:none}.command-trigger{width:34px;margin-left:auto;padding:0}.account-trigger{padding:3px}.page{padding:27px 15px 45px}.page-header,.task-header{align-items:flex-start;flex-direction:column;gap:18px}.page-header h1{font-size:34px}.page-actions{width:100%}.page-actions>button:last-child{flex:1}.metrics{grid-template-columns:1fr 1fr}.metric{grid-template-columns:1fr}.metric-icon{display:none}.attention-banner{grid-template-columns:auto minmax(0,1fr)}.attention-banner>a{grid-column:2}.task-board.lanes-2,.task-board.lanes-3,.task-board.lanes-5,.task-board.lanes-7{grid-template-columns:repeat(var(--mobile-lanes, 5),minmax(255px,82vw))}.task-board.lanes-2{--mobile-lanes: 2}.task-board.lanes-3{--mobile-lanes: 3}.task-board.lanes-5{--mobile-lanes: 5}.task-board.lanes-7{--mobile-lanes: 7}.task-lane{min-height:270px}.form-row,.detail-side{grid-template-columns:1fr}.timeline-panel{grid-column:auto}.diagnosis dl{grid-template-columns:1fr 1fr}.diagnosis dl>div:nth-child(2){border-right:0}.diagnosis dl>div:nth-child(-n+2){border-bottom:1px solid var(--line)}.approval-panel{padding:14px}.approval-actions{align-items:stretch;flex-direction:column}.action-card,.action-form{grid-template-columns:auto minmax(0,1fr)}.action-card>button,.action-buttons{grid-column:2;justify-self:start}.worker-grid{grid-template-columns:1fr}.worker-card>header{align-items:flex-start}.overlay{padding:10px}.modal{max-height:calc(100vh - 20px)}.modal-header,.create-modal form{padding-right:16px;padding-left:16px}.eligibility{margin-right:16px;margin-left:16px}.artifact-header .task-id{overflow-wrap:anywhere}.login-card{padding:22px}.current-password-block{grid-template-columns:1fr}.current-password-block input{grid-column:1;grid-row:auto;margin-top:5px}.settings-actions{align-items:stretch;flex-direction:column}.settings-actions button{width:100%}.skeleton-grid{grid-template-columns:1fr}}@media(max-width:460px){.refresh-button span{display:none}.search-field,.select-field{width:100%}.metrics{grid-template-columns:1fr 1fr;gap:7px}.metric{padding:11px}.worker-stats{grid-template-columns:1fr}.worker-stats>div{border-right:0;border-bottom:1px solid var(--line)}.worker-stats>div:last-child{border-bottom:0}}@media(prefers-reduced-motion:reduce){*,*:before,*:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}} diff --git a/internal/webui/assets/assets/index-BAiY1a7C.js b/internal/webui/assets/assets/index-BAiY1a7C.js new file mode 100644 index 0000000..c5f2827 --- /dev/null +++ b/internal/webui/assets/assets/index-BAiY1a7C.js @@ -0,0 +1,61 @@ +var _p=l=>{throw TypeError(l)};var No=(l,i,o)=>i.has(l)||_p("Cannot "+o);var b=(l,i,o)=>(No(l,i,"read from private field"),o?o.call(l):i.get(l)),ae=(l,i,o)=>i.has(l)?_p("Cannot add the same private member more than once"):i instanceof WeakSet?i.add(l):i.set(l,o),$=(l,i,o,c)=>(No(l,i,"write to private field"),c?c.call(l,o):i.set(l,o),o),me=(l,i,o)=>(No(l,i,"access private method"),o);var qu=(l,i,o,c)=>({set _(d){$(l,i,d,o)},get _(){return b(l,i,c)}});(function(){const i=document.createElement("link").relList;if(i&&i.supports&&i.supports("modulepreload"))return;for(const d of document.querySelectorAll('link[rel="modulepreload"]'))c(d);new MutationObserver(d=>{for(const h of d)if(h.type==="childList")for(const v of h.addedNodes)v.tagName==="LINK"&&v.rel==="modulepreload"&&c(v)}).observe(document,{childList:!0,subtree:!0});function o(d){const h={};return d.integrity&&(h.integrity=d.integrity),d.referrerPolicy&&(h.referrerPolicy=d.referrerPolicy),d.crossOrigin==="use-credentials"?h.credentials="include":d.crossOrigin==="anonymous"?h.credentials="omit":h.credentials="same-origin",h}function c(d){if(d.ep)return;d.ep=!0;const h=o(d);fetch(d.href,h)}})();function A0(l){return l&&l.__esModule&&Object.prototype.hasOwnProperty.call(l,"default")?l.default:l}var Eo={exports:{}},ai={};/** + * @license React + * react-jsx-runtime.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var wp;function R0(){if(wp)return ai;wp=1;var l=Symbol.for("react.transitional.element"),i=Symbol.for("react.fragment");function o(c,d,h){var v=null;if(h!==void 0&&(v=""+h),d.key!==void 0&&(v=""+d.key),"key"in d){h={};for(var g in d)g!=="key"&&(h[g]=d[g])}else h=d;return d=h.ref,{$$typeof:l,type:c,key:v,ref:d!==void 0?d:null,props:h}}return ai.Fragment=i,ai.jsx=o,ai.jsxs=o,ai}var Cp;function M0(){return Cp||(Cp=1,Eo.exports=R0()),Eo.exports}var s=M0(),To={exports:{}},re={};/** + * @license React + * react.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Op;function z0(){if(Op)return re;Op=1;var l=Symbol.for("react.transitional.element"),i=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),c=Symbol.for("react.strict_mode"),d=Symbol.for("react.profiler"),h=Symbol.for("react.consumer"),v=Symbol.for("react.context"),g=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),y=Symbol.for("react.memo"),E=Symbol.for("react.lazy"),x=Symbol.for("react.activity"),w=Symbol.iterator;function O(T){return T===null||typeof T!="object"?null:(T=w&&T[w]||T["@@iterator"],typeof T=="function"?T:null)}var U={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},z=Object.assign,q={};function H(T,Y,F){this.props=T,this.context=Y,this.refs=q,this.updater=F||U}H.prototype.isReactComponent={},H.prototype.setState=function(T,Y){if(typeof T!="object"&&typeof T!="function"&&T!=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,T,Y,"setState")},H.prototype.forceUpdate=function(T){this.updater.enqueueForceUpdate(this,T,"forceUpdate")};function G(){}G.prototype=H.prototype;function K(T,Y,F){this.props=T,this.context=Y,this.refs=q,this.updater=F||U}var Z=K.prototype=new G;Z.constructor=K,z(Z,H.prototype),Z.isPureReactComponent=!0;var W=Array.isArray;function J(){}var te={H:null,A:null,T:null,S:null},ne=Object.prototype.hasOwnProperty;function ce(T,Y,F){var I=F.ref;return{$$typeof:l,type:T,key:Y,ref:I!==void 0?I:null,props:F}}function _e(T,Y){return ce(T.type,Y,T.props)}function He(T){return typeof T=="object"&&T!==null&&T.$$typeof===l}function X(T){var Y={"=":"=0",":":"=2"};return"$"+T.replace(/[=:]/g,function(F){return Y[F]})}var de=/\/+/g;function pe(T,Y){return typeof T=="object"&&T!==null&&T.key!=null?X(""+T.key):Y.toString(36)}function se(T){switch(T.status){case"fulfilled":return T.value;case"rejected":throw T.reason;default:switch(typeof T.status=="string"?T.then(J,J):(T.status="pending",T.then(function(Y){T.status==="pending"&&(T.status="fulfilled",T.value=Y)},function(Y){T.status==="pending"&&(T.status="rejected",T.reason=Y)})),T.status){case"fulfilled":return T.value;case"rejected":throw T.reason}}throw T}function D(T,Y,F,I,oe){var ve=typeof T;(ve==="undefined"||ve==="boolean")&&(T=null);var Ce=!1;if(T===null)Ce=!0;else switch(ve){case"bigint":case"string":case"number":Ce=!0;break;case"object":switch(T.$$typeof){case l:case i:Ce=!0;break;case E:return Ce=T._init,D(Ce(T._payload),Y,F,I,oe)}}if(Ce)return oe=oe(T),Ce=I===""?"."+pe(T,0):I,W(oe)?(F="",Ce!=null&&(F=Ce.replace(de,"$&/")+"/"),D(oe,Y,F,"",function(cs){return cs})):oe!=null&&(He(oe)&&(oe=_e(oe,F+(oe.key==null||T&&T.key===oe.key?"":(""+oe.key).replace(de,"$&/")+"/")+Ce)),Y.push(oe)),1;Ce=0;var pt=I===""?".":I+":";if(W(T))for(var Je=0;Je>>1,ke=D[Me];if(0>>1;Med(F,ue))Id(oe,F)?(D[Me]=oe,D[I]=ue,Me=I):(D[Me]=F,D[Y]=ue,Me=Y);else if(Id(oe,ue))D[Me]=oe,D[I]=ue,Me=I;else break e}}return V}function d(D,V){var ue=D.sortIndex-V.sortIndex;return ue!==0?ue:D.id-V.id}if(l.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var h=performance;l.unstable_now=function(){return h.now()}}else{var v=Date,g=v.now();l.unstable_now=function(){return v.now()-g}}var p=[],y=[],E=1,x=null,w=3,O=!1,U=!1,z=!1,q=!1,H=typeof setTimeout=="function"?setTimeout:null,G=typeof clearTimeout=="function"?clearTimeout:null,K=typeof setImmediate<"u"?setImmediate:null;function Z(D){for(var V=o(y);V!==null;){if(V.callback===null)c(y);else if(V.startTime<=D)c(y),V.sortIndex=V.expirationTime,i(p,V);else break;V=o(y)}}function W(D){if(z=!1,Z(D),!U)if(o(p)!==null)U=!0,J||(J=!0,X());else{var V=o(y);V!==null&&se(W,V.startTime-D)}}var J=!1,te=-1,ne=5,ce=-1;function _e(){return q?!0:!(l.unstable_now()-ceD&&_e());){var Me=x.callback;if(typeof Me=="function"){x.callback=null,w=x.priorityLevel;var ke=Me(x.expirationTime<=D);if(D=l.unstable_now(),typeof ke=="function"){x.callback=ke,Z(D),V=!0;break t}x===o(p)&&c(p),Z(D)}else c(p);x=o(p)}if(x!==null)V=!0;else{var T=o(y);T!==null&&se(W,T.startTime-D),V=!1}}break e}finally{x=null,w=ue,O=!1}V=void 0}}finally{V?X():J=!1}}}var X;if(typeof K=="function")X=function(){K(He)};else if(typeof MessageChannel<"u"){var de=new MessageChannel,pe=de.port2;de.port1.onmessage=He,X=function(){pe.postMessage(null)}}else X=function(){H(He,0)};function se(D,V){te=H(function(){D(l.unstable_now())},V)}l.unstable_IdlePriority=5,l.unstable_ImmediatePriority=1,l.unstable_LowPriority=4,l.unstable_NormalPriority=3,l.unstable_Profiling=null,l.unstable_UserBlockingPriority=2,l.unstable_cancelCallback=function(D){D.callback=null},l.unstable_forceFrameRate=function(D){0>D||125Me?(D.sortIndex=ue,i(y,D),o(p)===null&&D===o(y)&&(z?(G(te),te=-1):z=!0,se(W,ue-Me))):(D.sortIndex=ke,i(p,D),U||O||(U=!0,J||(J=!0,X()))),D},l.unstable_shouldYield=_e,l.unstable_wrapCallback=function(D){var V=w;return function(){var ue=w;w=V;try{return D.apply(this,arguments)}finally{w=ue}}}})(Co)),Co}var Mp;function k0(){return Mp||(Mp=1,wo.exports=U0()),wo.exports}var Oo={exports:{}},ht={};/** + * @license React + * react-dom.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var zp;function q0(){if(zp)return ht;zp=1;var l=lf();function i(p){var y="https://react.dev/errors/"+p;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(l)}catch(i){console.error(i)}}return l(),Oo.exports=q0(),Oo.exports}/** + * @license React + * react-dom-client.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Up;function B0(){if(Up)return ni;Up=1;var l=k0(),i=lf(),o=H0();function c(e){var t="https://react.dev/errors/"+e;if(1ke||(e.current=Me[ke],Me[ke]=null,ke--)}function F(e,t){ke++,Me[ke]=e.current,e.current=t}var I=T(null),oe=T(null),ve=T(null),Ce=T(null);function pt(e,t){switch(F(ve,t),F(oe,e),F(I,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?$m(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=$m(t),e=Fm(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}Y(I),F(I,e)}function Je(){Y(I),Y(oe),Y(ve)}function cs(e){e.memoizedState!==null&&F(Ce,e);var t=I.current,a=Fm(t,e.type);t!==a&&(F(oe,e),F(I,a))}function xi(e){oe.current===e&&(Y(I),Y(oe)),Ce.current===e&&(Y(Ce),Ps._currentValue=ue)}var lr,Tf;function Cn(e){if(lr===void 0)try{throw Error()}catch(a){var t=a.stack.trim().match(/\n( *(at )?)/);lr=t&&t[1]||"",Tf=-1)":-1u||j[n]!==A[u]){var k=` +`+j[n].replace(" at new "," at ");return e.displayName&&k.includes("")&&(k=k.replace("",e.displayName)),k}while(1<=n&&0<=u);break}}}finally{sr=!1,Error.prepareStackTrace=a}return(a=e?e.displayName||e.name:"")?Cn(a):""}function uy(e,t){switch(e.tag){case 26:case 27:case 5:return Cn(e.type);case 16:return Cn("Lazy");case 13:return e.child!==t&&t!==null?Cn("Suspense Fallback"):Cn("Suspense");case 19:return Cn("SuspenseList");case 0:case 15:return ir(e.type,!1);case 11:return ir(e.type.render,!1);case 1:return ir(e.type,!0);case 31:return Cn("Activity");default:return""}}function _f(e){try{var t="",a=null;do t+=uy(e,a),a=e,e=e.return;while(e);return t}catch(n){return` +Error generating stack: `+n.message+` +`+n.stack}}var ur=Object.prototype.hasOwnProperty,rr=l.unstable_scheduleCallback,cr=l.unstable_cancelCallback,ry=l.unstable_shouldYield,cy=l.unstable_requestPaint,Ot=l.unstable_now,oy=l.unstable_getCurrentPriorityLevel,wf=l.unstable_ImmediatePriority,Cf=l.unstable_UserBlockingPriority,ji=l.unstable_NormalPriority,fy=l.unstable_LowPriority,Of=l.unstable_IdlePriority,dy=l.log,hy=l.unstable_setDisableYieldValue,os=null,At=null;function Xa(e){if(typeof dy=="function"&&hy(e),At&&typeof At.setStrictMode=="function")try{At.setStrictMode(os,e)}catch{}}var Rt=Math.clz32?Math.clz32:vy,my=Math.log,py=Math.LN2;function vy(e){return e>>>=0,e===0?32:31-(my(e)/py|0)|0}var bi=256,Si=262144,Ni=4194304;function On(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 Ei(e,t,a){var n=e.pendingLanes;if(n===0)return 0;var u=0,r=e.suspendedLanes,f=e.pingedLanes;e=e.warmLanes;var m=n&134217727;return m!==0?(n=m&~r,n!==0?u=On(n):(f&=m,f!==0?u=On(f):a||(a=m&~e,a!==0&&(u=On(a))))):(m=n&~r,m!==0?u=On(m):f!==0?u=On(f):a||(a=n&~e,a!==0&&(u=On(a)))),u===0?0:t!==0&&t!==u&&(t&r)===0&&(r=u&-u,a=t&-t,r>=a||r===32&&(a&4194048)!==0)?t:u}function fs(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function yy(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 Af(){var e=Ni;return Ni<<=1,(Ni&62914560)===0&&(Ni=4194304),e}function or(e){for(var t=[],a=0;31>a;a++)t.push(e);return t}function ds(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function gy(e,t,a,n,u,r){var f=e.pendingLanes;e.pendingLanes=a,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=a,e.entangledLanes&=a,e.errorRecoveryDisabledLanes&=a,e.shellSuspendCounter=0;var m=e.entanglements,j=e.expirationTimes,A=e.hiddenUpdates;for(a=f&~a;0"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var Ey=/[\n"\\]/g;function Gt(e){return e.replace(Ey,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function vr(e,t,a,n,u,r,f,m){e.name="",f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"?e.type=f:e.removeAttribute("type"),t!=null?f==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+Yt(t)):e.value!==""+Yt(t)&&(e.value=""+Yt(t)):f!=="submit"&&f!=="reset"||e.removeAttribute("value"),t!=null?yr(e,f,Yt(t)):a!=null?yr(e,f,Yt(a)):n!=null&&e.removeAttribute("value"),u==null&&r!=null&&(e.defaultChecked=!!r),u!=null&&(e.checked=u&&typeof u!="function"&&typeof u!="symbol"),m!=null&&typeof m!="function"&&typeof m!="symbol"&&typeof m!="boolean"?e.name=""+Yt(m):e.removeAttribute("name")}function Gf(e,t,a,n,u,r,f,m){if(r!=null&&typeof r!="function"&&typeof r!="symbol"&&typeof r!="boolean"&&(e.type=r),t!=null||a!=null){if(!(r!=="submit"&&r!=="reset"||t!=null)){pr(e);return}a=a!=null?""+Yt(a):"",t=t!=null?""+Yt(t):a,m||t===e.value||(e.value=t),e.defaultValue=t}n=n??u,n=typeof n!="function"&&typeof n!="symbol"&&!!n,e.checked=m?e.checked:!!n,e.defaultChecked=!!n,f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"&&(e.name=f),pr(e)}function yr(e,t,a){t==="number"&&wi(e.ownerDocument)===e||e.defaultValue===""+a||(e.defaultValue=""+a)}function ol(e,t,a,n){if(e=e.options,t){t={};for(var u=0;u"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Sr=!1;if(ja)try{var vs={};Object.defineProperty(vs,"passive",{get:function(){Sr=!0}}),window.addEventListener("test",vs,vs),window.removeEventListener("test",vs,vs)}catch{Sr=!1}var Za=null,Nr=null,Oi=null;function Ff(){if(Oi)return Oi;var e,t=Nr,a=t.length,n,u="value"in Za?Za.value:Za.textContent,r=u.length;for(e=0;e=xs),ad=" ",nd=!1;function ld(e,t){switch(e){case"keyup":return Py.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function sd(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var ml=!1;function eg(e,t){switch(e){case"compositionend":return sd(t);case"keypress":return t.which!==32?null:(nd=!0,ad);case"textInput":return e=t.data,e===ad&&nd?null:e;default:return null}}function tg(e,t){if(ml)return e==="compositionend"||!Cr&&ld(e,t)?(e=Ff(),Oi=Nr=Za=null,ml=!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:a,offset:t-e};e=n}e:{for(;a;){if(a.nextSibling){a=a.nextSibling;break e}a=a.parentNode}a=void 0}a=hd(a)}}function pd(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?pd(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function vd(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=wi(e.document);t instanceof e.HTMLIFrameElement;){try{var a=typeof t.contentWindow.location.href=="string"}catch{a=!1}if(a)e=t.contentWindow;else break;t=wi(e.document)}return t}function Rr(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 cg=ja&&"documentMode"in document&&11>=document.documentMode,pl=null,Mr=null,Ns=null,zr=!1;function yd(e,t,a){var n=a.window===a?a.document:a.nodeType===9?a:a.ownerDocument;zr||pl==null||pl!==wi(n)||(n=pl,"selectionStart"in n&&Rr(n)?n={start:n.selectionStart,end:n.selectionEnd}:(n=(n.ownerDocument&&n.ownerDocument.defaultView||window).getSelection(),n={anchorNode:n.anchorNode,anchorOffset:n.anchorOffset,focusNode:n.focusNode,focusOffset:n.focusOffset}),Ns&&Ss(Ns,n)||(Ns=n,n=Su(Mr,"onSelect"),0>=f,u-=f,oa=1<<32-Rt(t)+u|a<he?(je=ee,ee=null):je=ee.sibling;var Te=R(_,ee,C[he],L);if(Te===null){ee===null&&(ee=je);break}e&&ee&&Te.alternate===null&&t(_,ee),N=r(Te,N,he),Ee===null?le=Te:Ee.sibling=Te,Ee=Te,ee=je}if(he===C.length)return a(_,ee),be&&Sa(_,he),le;if(ee===null){for(;hehe?(je=ee,ee=null):je=ee.sibling;var pn=R(_,ee,Te.value,L);if(pn===null){ee===null&&(ee=je);break}e&&ee&&pn.alternate===null&&t(_,ee),N=r(pn,N,he),Ee===null?le=pn:Ee.sibling=pn,Ee=pn,ee=je}if(Te.done)return a(_,ee),be&&Sa(_,he),le;if(ee===null){for(;!Te.done;he++,Te=C.next())Te=Q(_,Te.value,L),Te!==null&&(N=r(Te,N,he),Ee===null?le=Te:Ee.sibling=Te,Ee=Te);return be&&Sa(_,he),le}for(ee=n(ee);!Te.done;he++,Te=C.next())Te=M(ee,_,he,Te.value,L),Te!==null&&(e&&Te.alternate!==null&&ee.delete(Te.key===null?he:Te.key),N=r(Te,N,he),Ee===null?le=Te:Ee.sibling=Te,Ee=Te);return e&&ee.forEach(function(O0){return t(_,O0)}),be&&Sa(_,he),le}function Ue(_,N,C,L){if(typeof C=="object"&&C!==null&&C.type===z&&C.key===null&&(C=C.props.children),typeof C=="object"&&C!==null){switch(C.$$typeof){case O:e:{for(var le=C.key;N!==null;){if(N.key===le){if(le=C.type,le===z){if(N.tag===7){a(_,N.sibling),L=u(N,C.props.children),L.return=_,_=L;break e}}else if(N.elementType===le||typeof le=="object"&&le!==null&&le.$$typeof===ne&&Ln(le)===N.type){a(_,N.sibling),L=u(N,C.props),Os(L,C),L.return=_,_=L;break e}a(_,N);break}else t(_,N);N=N.sibling}C.type===z?(L=Un(C.props.children,_.mode,L,C.key),L.return=_,_=L):(L=Bi(C.type,C.key,C.props,null,_.mode,L),Os(L,C),L.return=_,_=L)}return f(_);case U:e:{for(le=C.key;N!==null;){if(N.key===le)if(N.tag===4&&N.stateNode.containerInfo===C.containerInfo&&N.stateNode.implementation===C.implementation){a(_,N.sibling),L=u(N,C.children||[]),L.return=_,_=L;break e}else{a(_,N);break}else t(_,N);N=N.sibling}L=Lr(C,_.mode,L),L.return=_,_=L}return f(_);case ne:return C=Ln(C),Ue(_,N,C,L)}if(se(C))return P(_,N,C,L);if(X(C)){if(le=X(C),typeof le!="function")throw Error(c(150));return C=le.call(C),ie(_,N,C,L)}if(typeof C.then=="function")return Ue(_,N,Zi(C),L);if(C.$$typeof===K)return Ue(_,N,Yi(_,C),L);Vi(_,C)}return typeof C=="string"&&C!==""||typeof C=="number"||typeof C=="bigint"?(C=""+C,N!==null&&N.tag===6?(a(_,N.sibling),L=u(N,C),L.return=_,_=L):(a(_,N),L=Br(C,_.mode,L),L.return=_,_=L),f(_)):a(_,N)}return function(_,N,C,L){try{Cs=0;var le=Ue(_,N,C,L);return _l=null,le}catch(ee){if(ee===Tl||ee===Xi)throw ee;var Ee=zt(29,ee,null,_.mode);return Ee.lanes=L,Ee.return=_,Ee}finally{}}}var Yn=Ld(!0),Qd=Ld(!1),Wa=!1;function Pr(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ir(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 Pa(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Ia(e,t,a){var n=e.updateQueue;if(n===null)return null;if(n=n.shared,(we&2)!==0){var u=n.pending;return u===null?t.next=t:(t.next=u.next,u.next=t),n.pending=t,t=Hi(e),Ed(e,null,a),t}return qi(e,n,t,a),Hi(e)}function As(e,t,a){if(t=t.updateQueue,t!==null&&(t=t.shared,(a&4194048)!==0)){var n=t.lanes;n&=e.pendingLanes,a|=n,t.lanes=a,Mf(e,a)}}function ec(e,t){var a=e.updateQueue,n=e.alternate;if(n!==null&&(n=n.updateQueue,a===n)){var u=null,r=null;if(a=a.firstBaseUpdate,a!==null){do{var f={lane:a.lane,tag:a.tag,payload:a.payload,callback:null,next:null};r===null?u=r=f:r=r.next=f,a=a.next}while(a!==null);r===null?u=r=t:r=r.next=t}else u=r=t;a={baseState:n.baseState,firstBaseUpdate:u,lastBaseUpdate:r,shared:n.shared,callbacks:n.callbacks},e.updateQueue=a;return}e=a.lastBaseUpdate,e===null?a.firstBaseUpdate=t:e.next=t,a.lastBaseUpdate=t}var tc=!1;function Rs(){if(tc){var e=El;if(e!==null)throw e}}function Ms(e,t,a,n){tc=!1;var u=e.updateQueue;Wa=!1;var r=u.firstBaseUpdate,f=u.lastBaseUpdate,m=u.shared.pending;if(m!==null){u.shared.pending=null;var j=m,A=j.next;j.next=null,f===null?r=A:f.next=A,f=j;var k=e.alternate;k!==null&&(k=k.updateQueue,m=k.lastBaseUpdate,m!==f&&(m===null?k.firstBaseUpdate=A:m.next=A,k.lastBaseUpdate=j))}if(r!==null){var Q=u.baseState;f=0,k=A=j=null,m=r;do{var R=m.lane&-536870913,M=R!==m.lane;if(M?(xe&R)===R:(n&R)===R){R!==0&&R===Nl&&(tc=!0),k!==null&&(k=k.next={lane:0,tag:m.tag,payload:m.payload,callback:null,next:null});e:{var P=e,ie=m;R=t;var Ue=a;switch(ie.tag){case 1:if(P=ie.payload,typeof P=="function"){Q=P.call(Ue,Q,R);break e}Q=P;break e;case 3:P.flags=P.flags&-65537|128;case 0:if(P=ie.payload,R=typeof P=="function"?P.call(Ue,Q,R):P,R==null)break e;Q=x({},Q,R);break e;case 2:Wa=!0}}R=m.callback,R!==null&&(e.flags|=64,M&&(e.flags|=8192),M=u.callbacks,M===null?u.callbacks=[R]:M.push(R))}else M={lane:R,tag:m.tag,payload:m.payload,callback:m.callback,next:null},k===null?(A=k=M,j=Q):k=k.next=M,f|=R;if(m=m.next,m===null){if(m=u.shared.pending,m===null)break;M=m,m=M.next,M.next=null,u.lastBaseUpdate=M,u.shared.pending=null}}while(!0);k===null&&(j=Q),u.baseState=j,u.firstBaseUpdate=A,u.lastBaseUpdate=k,r===null&&(u.shared.lanes=0),ln|=f,e.lanes=f,e.memoizedState=Q}}function Yd(e,t){if(typeof e!="function")throw Error(c(191,e));e.call(t)}function Gd(e,t){var a=e.callbacks;if(a!==null)for(e.callbacks=null,e=0;er?r:8;var f=D.T,m={};D.T=m,jc(e,!1,t,a);try{var j=u(),A=D.S;if(A!==null&&A(m,j),j!==null&&typeof j=="object"&&typeof j.then=="function"){var k=gg(j,n);Us(e,t,k,Ht(e))}else Us(e,t,n,Ht(e))}catch(Q){Us(e,t,{then:function(){},status:"rejected",reason:Q},Ht())}finally{V.p=r,f!==null&&m.types!==null&&(f.types=m.types),D.T=f}}function Eg(){}function gc(e,t,a,n){if(e.tag!==5)throw Error(c(476));var u=bh(e).queue;jh(e,u,t,ue,a===null?Eg:function(){return Sh(e),a(n)})}function bh(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:ue,baseState:ue,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:_a,lastRenderedState:ue},next:null};var a={};return t.next={memoizedState:a,baseState:a,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:_a,lastRenderedState:a},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Sh(e){var t=bh(e);t.next===null&&(t=e.alternate.memoizedState),Us(e,t.next.queue,{},Ht())}function xc(){return ot(Ps)}function Nh(){return Fe().memoizedState}function Eh(){return Fe().memoizedState}function Tg(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var a=Ht();e=Pa(a);var n=Ia(t,e,a);n!==null&&(_t(n,t,a),As(n,t,a)),t={cache:Jr()},e.payload=t;return}t=t.return}}function _g(e,t,a){var n=Ht();a={lane:n,revertLane:0,gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},nu(e)?_h(t,a):(a=qr(e,t,a,n),a!==null&&(_t(a,e,n),wh(a,t,n)))}function Th(e,t,a){var n=Ht();Us(e,t,a,n)}function Us(e,t,a,n){var u={lane:n,revertLane:0,gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null};if(nu(e))_h(t,u);else{var r=e.alternate;if(e.lanes===0&&(r===null||r.lanes===0)&&(r=t.lastRenderedReducer,r!==null))try{var f=t.lastRenderedState,m=r(f,a);if(u.hasEagerState=!0,u.eagerState=m,Mt(m,f))return qi(e,t,u,0),qe===null&&ki(),!1}catch{}finally{}if(a=qr(e,t,u,n),a!==null)return _t(a,e,n),wh(a,t,n),!0}return!1}function jc(e,t,a,n){if(n={lane:2,revertLane:Pc(),gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},nu(e)){if(t)throw Error(c(479))}else t=qr(e,a,n,2),t!==null&&_t(t,e,2)}function nu(e){var t=e.alternate;return e===fe||t!==null&&t===fe}function _h(e,t){Cl=Fi=!0;var a=e.pending;a===null?t.next=t:(t.next=a.next,a.next=t),e.pending=t}function wh(e,t,a){if((a&4194048)!==0){var n=t.lanes;n&=e.pendingLanes,a|=n,t.lanes=a,Mf(e,a)}}var ks={readContext:ot,use:Ii,useCallback:Ke,useContext:Ke,useEffect:Ke,useImperativeHandle:Ke,useLayoutEffect:Ke,useInsertionEffect:Ke,useMemo:Ke,useReducer:Ke,useRef:Ke,useState:Ke,useDebugValue:Ke,useDeferredValue:Ke,useTransition:Ke,useSyncExternalStore:Ke,useId:Ke,useHostTransitionStatus:Ke,useFormState:Ke,useActionState:Ke,useOptimistic:Ke,useMemoCache:Ke,useCacheRefresh:Ke};ks.useEffectEvent=Ke;var Ch={readContext:ot,use:Ii,useCallback:function(e,t){return vt().memoizedState=[e,t===void 0?null:t],e},useContext:ot,useEffect:fh,useImperativeHandle:function(e,t,a){a=a!=null?a.concat([e]):null,tu(4194308,4,ph.bind(null,t,e),a)},useLayoutEffect:function(e,t){return tu(4194308,4,e,t)},useInsertionEffect:function(e,t){tu(4,2,e,t)},useMemo:function(e,t){var a=vt();t=t===void 0?null:t;var n=e();if(Gn){Xa(!0);try{e()}finally{Xa(!1)}}return a.memoizedState=[n,t],n},useReducer:function(e,t,a){var n=vt();if(a!==void 0){var u=a(t);if(Gn){Xa(!0);try{a(t)}finally{Xa(!1)}}}else u=t;return n.memoizedState=n.baseState=u,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:u},n.queue=e,e=e.dispatch=_g.bind(null,fe,e),[n.memoizedState,e]},useRef:function(e){var t=vt();return e={current:e},t.memoizedState=e},useState:function(e){e=hc(e);var t=e.queue,a=Th.bind(null,fe,t);return t.dispatch=a,[e.memoizedState,a]},useDebugValue:vc,useDeferredValue:function(e,t){var a=vt();return yc(a,e,t)},useTransition:function(){var e=hc(!1);return e=jh.bind(null,fe,e.queue,!0,!1),vt().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,a){var n=fe,u=vt();if(be){if(a===void 0)throw Error(c(407));a=a()}else{if(a=t(),qe===null)throw Error(c(349));(xe&127)!==0||$d(n,t,a)}u.memoizedState=a;var r={value:a,getSnapshot:t};return u.queue=r,fh(Wd.bind(null,n,r,e),[e]),n.flags|=2048,Al(9,{destroy:void 0},Fd.bind(null,n,r,a,t),null),a},useId:function(){var e=vt(),t=qe.identifierPrefix;if(be){var a=fa,n=oa;a=(n&~(1<<32-Rt(n)-1)).toString(32)+a,t="_"+t+"R_"+a,a=Wi++,0<\/script>",r=r.removeChild(r.firstChild);break;case"select":r=typeof n.is=="string"?f.createElement("select",{is:n.is}):f.createElement("select"),n.multiple?r.multiple=!0:n.size&&(r.size=n.size);break;default:r=typeof n.is=="string"?f.createElement(u,{is:n.is}):f.createElement(u)}}r[rt]=t,r[jt]=n;e:for(f=t.child;f!==null;){if(f.tag===5||f.tag===6)r.appendChild(f.stateNode);else if(f.tag!==4&&f.tag!==27&&f.child!==null){f.child.return=f,f=f.child;continue}if(f===t)break e;for(;f.sibling===null;){if(f.return===null||f.return===t)break e;f=f.return}f.sibling.return=f.return,f=f.sibling}t.stateNode=r;e:switch(dt(r,u,n),u){case"button":case"input":case"select":case"textarea":n=!!n.autoFocus;break e;case"img":n=!0;break e;default:n=!1}n&&Ca(t)}}return Qe(t),Dc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,a),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==n&&Ca(t);else{if(typeof n!="string"&&t.stateNode===null)throw Error(c(166));if(e=ve.current,bl(t)){if(e=t.stateNode,a=t.memoizedProps,n=null,u=ct,u!==null)switch(u.tag){case 27:case 5:n=u.memoizedProps}e[rt]=t,e=!!(e.nodeValue===a||n!==null&&n.suppressHydrationWarning===!0||Vm(e.nodeValue,a)),e||$a(t,!0)}else e=Nu(e).createTextNode(n),e[rt]=t,t.stateNode=e}return Qe(t),null;case 31:if(a=t.memoizedState,e===null||e.memoizedState!==null){if(n=bl(t),a!==null){if(e===null){if(!n)throw Error(c(318));if(e=t.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(c(557));e[rt]=t}else kn(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Qe(t),e=!1}else a=Xr(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),e=!0;if(!e)return t.flags&256?(Ut(t),t):(Ut(t),null);if((t.flags&128)!==0)throw Error(c(558))}return Qe(t),null;case 13:if(n=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(u=bl(t),n!==null&&n.dehydrated!==null){if(e===null){if(!u)throw Error(c(318));if(u=t.memoizedState,u=u!==null?u.dehydrated:null,!u)throw Error(c(317));u[rt]=t}else kn(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Qe(t),u=!1}else u=Xr(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=u),u=!0;if(!u)return t.flags&256?(Ut(t),t):(Ut(t),null)}return Ut(t),(t.flags&128)!==0?(t.lanes=a,t):(a=n!==null,e=e!==null&&e.memoizedState!==null,a&&(n=t.child,u=null,n.alternate!==null&&n.alternate.memoizedState!==null&&n.alternate.memoizedState.cachePool!==null&&(u=n.alternate.memoizedState.cachePool.pool),r=null,n.memoizedState!==null&&n.memoizedState.cachePool!==null&&(r=n.memoizedState.cachePool.pool),r!==u&&(n.flags|=2048)),a!==e&&a&&(t.child.flags|=8192),ru(t,t.updateQueue),Qe(t),null);case 4:return Je(),e===null&&ao(t.stateNode.containerInfo),Qe(t),null;case 10:return Ea(t.type),Qe(t),null;case 19:if(Y($e),n=t.memoizedState,n===null)return Qe(t),null;if(u=(t.flags&128)!==0,r=n.rendering,r===null)if(u)Hs(n,!1);else{if(Ze!==0||e!==null&&(e.flags&128)!==0)for(e=t.child;e!==null;){if(r=$i(e),r!==null){for(t.flags|=128,Hs(n,!1),e=r.updateQueue,t.updateQueue=e,ru(t,e),t.subtreeFlags=0,e=a,a=t.child;a!==null;)Td(a,e),a=a.sibling;return F($e,$e.current&1|2),be&&Sa(t,n.treeForkCount),t.child}e=e.sibling}n.tail!==null&&Ot()>hu&&(t.flags|=128,u=!0,Hs(n,!1),t.lanes=4194304)}else{if(!u)if(e=$i(r),e!==null){if(t.flags|=128,u=!0,e=e.updateQueue,t.updateQueue=e,ru(t,e),Hs(n,!0),n.tail===null&&n.tailMode==="hidden"&&!r.alternate&&!be)return Qe(t),null}else 2*Ot()-n.renderingStartTime>hu&&a!==536870912&&(t.flags|=128,u=!0,Hs(n,!1),t.lanes=4194304);n.isBackwards?(r.sibling=t.child,t.child=r):(e=n.last,e!==null?e.sibling=r:t.child=r,n.last=r)}return n.tail!==null?(e=n.tail,n.rendering=e,n.tail=e.sibling,n.renderingStartTime=Ot(),e.sibling=null,a=$e.current,F($e,u?a&1|2:a&1),be&&Sa(t,n.treeForkCount),e):(Qe(t),null);case 22:case 23:return Ut(t),nc(),n=t.memoizedState!==null,e!==null?e.memoizedState!==null!==n&&(t.flags|=8192):n&&(t.flags|=8192),n?(a&536870912)!==0&&(t.flags&128)===0&&(Qe(t),t.subtreeFlags&6&&(t.flags|=8192)):Qe(t),a=t.updateQueue,a!==null&&ru(t,a.retryQueue),a=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(a=e.memoizedState.cachePool.pool),n=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(n=t.memoizedState.cachePool.pool),n!==a&&(t.flags|=2048),e!==null&&Y(Bn),null;case 24:return a=null,e!==null&&(a=e.memoizedState.cache),t.memoizedState.cache!==a&&(t.flags|=2048),Ea(Pe),Qe(t),null;case 25:return null;case 30:return null}throw Error(c(156,t.tag))}function Rg(e,t){switch(Yr(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Ea(Pe),Je(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return xi(t),null;case 31:if(t.memoizedState!==null){if(Ut(t),t.alternate===null)throw Error(c(340));kn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(Ut(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(c(340));kn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return Y($e),null;case 4:return Je(),null;case 10:return Ea(t.type),null;case 22:case 23:return Ut(t),nc(),e!==null&&Y(Bn),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Ea(Pe),null;case 25:return null;default:return null}}function Ph(e,t){switch(Yr(t),t.tag){case 3:Ea(Pe),Je();break;case 26:case 27:case 5:xi(t);break;case 4:Je();break;case 31:t.memoizedState!==null&&Ut(t);break;case 13:Ut(t);break;case 19:Y($e);break;case 10:Ea(t.type);break;case 22:case 23:Ut(t),nc(),e!==null&&Y(Bn);break;case 24:Ea(Pe)}}function Bs(e,t){try{var a=t.updateQueue,n=a!==null?a.lastEffect:null;if(n!==null){var u=n.next;a=u;do{if((a.tag&e)===e){n=void 0;var r=a.create,f=a.inst;n=r(),f.destroy=n}a=a.next}while(a!==u)}}catch(m){Re(t,t.return,m)}}function an(e,t,a){try{var n=t.updateQueue,u=n!==null?n.lastEffect:null;if(u!==null){var r=u.next;n=r;do{if((n.tag&e)===e){var f=n.inst,m=f.destroy;if(m!==void 0){f.destroy=void 0,u=t;var j=a,A=m;try{A()}catch(k){Re(u,j,k)}}}n=n.next}while(n!==r)}}catch(k){Re(t,t.return,k)}}function Ih(e){var t=e.updateQueue;if(t!==null){var a=e.stateNode;try{Gd(t,a)}catch(n){Re(e,e.return,n)}}}function em(e,t,a){a.props=Xn(e.type,e.memoizedProps),a.state=e.memoizedState;try{a.componentWillUnmount()}catch(n){Re(e,t,n)}}function Ls(e,t){try{var a=e.ref;if(a!==null){switch(e.tag){case 26:case 27:case 5:var n=e.stateNode;break;case 30:n=e.stateNode;break;default:n=e.stateNode}typeof a=="function"?e.refCleanup=a(n):a.current=n}}catch(u){Re(e,t,u)}}function da(e,t){var a=e.ref,n=e.refCleanup;if(a!==null)if(typeof n=="function")try{n()}catch(u){Re(e,t,u)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof a=="function")try{a(null)}catch(u){Re(e,t,u)}else a.current=null}function tm(e){var t=e.type,a=e.memoizedProps,n=e.stateNode;try{e:switch(t){case"button":case"input":case"select":case"textarea":a.autoFocus&&n.focus();break e;case"img":a.src?n.src=a.src:a.srcSet&&(n.srcset=a.srcSet)}}catch(u){Re(e,e.return,u)}}function Uc(e,t,a){try{var n=e.stateNode;Ig(n,e.type,a,t),n[jt]=t}catch(u){Re(e,e.return,u)}}function am(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&on(e.type)||e.tag===4}function kc(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||am(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&&on(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 qc(e,t,a){var n=e.tag;if(n===5||n===6)e=e.stateNode,t?(a.nodeType===9?a.body:a.nodeName==="HTML"?a.ownerDocument.body:a).insertBefore(e,t):(t=a.nodeType===9?a.body:a.nodeName==="HTML"?a.ownerDocument.body:a,t.appendChild(e),a=a._reactRootContainer,a!=null||t.onclick!==null||(t.onclick=xa));else if(n!==4&&(n===27&&on(e.type)&&(a=e.stateNode,t=null),e=e.child,e!==null))for(qc(e,t,a),e=e.sibling;e!==null;)qc(e,t,a),e=e.sibling}function cu(e,t,a){var n=e.tag;if(n===5||n===6)e=e.stateNode,t?a.insertBefore(e,t):a.appendChild(e);else if(n!==4&&(n===27&&on(e.type)&&(a=e.stateNode),e=e.child,e!==null))for(cu(e,t,a),e=e.sibling;e!==null;)cu(e,t,a),e=e.sibling}function nm(e){var t=e.stateNode,a=e.memoizedProps;try{for(var n=e.type,u=t.attributes;u.length;)t.removeAttributeNode(u[0]);dt(t,n,a),t[rt]=e,t[jt]=a}catch(r){Re(e,e.return,r)}}var Oa=!1,tt=!1,Hc=!1,lm=typeof WeakSet=="function"?WeakSet:Set,it=null;function Mg(e,t){if(e=e.containerInfo,so=Au,e=vd(e),Rr(e)){if("selectionStart"in e)var a={start:e.selectionStart,end:e.selectionEnd};else e:{a=(a=e.ownerDocument)&&a.defaultView||window;var n=a.getSelection&&a.getSelection();if(n&&n.rangeCount!==0){a=n.anchorNode;var u=n.anchorOffset,r=n.focusNode;n=n.focusOffset;try{a.nodeType,r.nodeType}catch{a=null;break e}var f=0,m=-1,j=-1,A=0,k=0,Q=e,R=null;t:for(;;){for(var M;Q!==a||u!==0&&Q.nodeType!==3||(m=f+u),Q!==r||n!==0&&Q.nodeType!==3||(j=f+n),Q.nodeType===3&&(f+=Q.nodeValue.length),(M=Q.firstChild)!==null;)R=Q,Q=M;for(;;){if(Q===e)break t;if(R===a&&++A===u&&(m=f),R===r&&++k===n&&(j=f),(M=Q.nextSibling)!==null)break;Q=R,R=Q.parentNode}Q=M}a=m===-1||j===-1?null:{start:m,end:j}}else a=null}a=a||{start:0,end:0}}else a=null;for(io={focusedElem:e,selectionRange:a},Au=!1,it=t;it!==null;)if(t=it,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,it=e;else for(;it!==null;){switch(t=it,r=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(a=0;a title"))),dt(r,n,a),r[rt]=e,st(r),n=r;break e;case"link":var f=op("link","href",u).get(n+(a.href||""));if(f){for(var m=0;mUe&&(f=Ue,Ue=ie,ie=f);var _=md(m,ie),N=md(m,Ue);if(_&&N&&(M.rangeCount!==1||M.anchorNode!==_.node||M.anchorOffset!==_.offset||M.focusNode!==N.node||M.focusOffset!==N.offset)){var C=Q.createRange();C.setStart(_.node,_.offset),M.removeAllRanges(),ie>Ue?(M.addRange(C),M.extend(N.node,N.offset)):(C.setEnd(N.node,N.offset),M.addRange(C))}}}}for(Q=[],M=m;M=M.parentNode;)M.nodeType===1&&Q.push({element:M,left:M.scrollLeft,top:M.scrollTop});for(typeof m.focus=="function"&&m.focus(),m=0;ma?32:a,D.T=null,a=Kc,Kc=null;var r=un,f=Da;if(at=0,Ul=un=null,Da=0,(we&6)!==0)throw Error(c(331));var m=we;if(we|=4,pm(r.current),dm(r,r.current,f,a),we=m,Zs(0,!1),At&&typeof At.onPostCommitFiberRoot=="function")try{At.onPostCommitFiberRoot(os,r)}catch{}return!0}finally{V.p=u,D.T=n,zm(e,t)}}function Um(e,t,a){t=Kt(a,t),t=Ec(e.stateNode,t,2),e=Ia(e,t,2),e!==null&&(ds(e,2),ha(e))}function Re(e,t,a){if(e.tag===3)Um(e,e,a);else for(;t!==null;){if(t.tag===3){Um(t,e,a);break}else if(t.tag===1){var n=t.stateNode;if(typeof t.type.getDerivedStateFromError=="function"||typeof n.componentDidCatch=="function"&&(sn===null||!sn.has(n))){e=Kt(a,e),a=kh(2),n=Ia(t,a,2),n!==null&&(qh(a,n,t,e),ds(n,2),ha(n));break}}t=t.return}}function $c(e,t,a){var n=e.pingCache;if(n===null){n=e.pingCache=new Ug;var u=new Set;n.set(t,u)}else u=n.get(t),u===void 0&&(u=new Set,n.set(t,u));u.has(a)||(Qc=!0,u.add(a),e=Lg.bind(null,e,t,a),t.then(e,e))}function Lg(e,t,a){var n=e.pingCache;n!==null&&n.delete(t),e.pingedLanes|=e.suspendedLanes&a,e.warmLanes&=~a,qe===e&&(xe&a)===a&&(Ze===4||Ze===3&&(xe&62914560)===xe&&300>Ot()-du?(we&2)===0&&kl(e,0):Yc|=a,Dl===xe&&(Dl=0)),ha(e)}function km(e,t){t===0&&(t=Af()),e=Dn(e,t),e!==null&&(ds(e,t),ha(e))}function Qg(e){var t=e.memoizedState,a=0;t!==null&&(a=t.retryLane),km(e,a)}function Yg(e,t){var a=0;switch(e.tag){case 31:case 13:var n=e.stateNode,u=e.memoizedState;u!==null&&(a=u.retryLane);break;case 19:n=e.stateNode;break;case 22:n=e.stateNode._retryCache;break;default:throw Error(c(314))}n!==null&&n.delete(t),km(e,a)}function Gg(e,t){return rr(e,t)}var xu=null,Hl=null,Fc=!1,ju=!1,Wc=!1,cn=0;function ha(e){e!==Hl&&e.next===null&&(Hl===null?xu=Hl=e:Hl=Hl.next=e),ju=!0,Fc||(Fc=!0,Kg())}function Zs(e,t){if(!Wc&&ju){Wc=!0;do for(var a=!1,n=xu;n!==null;){if(e!==0){var u=n.pendingLanes;if(u===0)var r=0;else{var f=n.suspendedLanes,m=n.pingedLanes;r=(1<<31-Rt(42|e)+1)-1,r&=u&~(f&~m),r=r&201326741?r&201326741|1:r?r|2:0}r!==0&&(a=!0,Lm(n,r))}else r=xe,r=Ei(n,n===qe?r:0,n.cancelPendingCommit!==null||n.timeoutHandle!==-1),(r&3)===0||fs(n,r)||(a=!0,Lm(n,r));n=n.next}while(a);Wc=!1}}function Xg(){qm()}function qm(){ju=Fc=!1;var e=0;cn!==0&&t0()&&(e=cn);for(var t=Ot(),a=null,n=xu;n!==null;){var u=n.next,r=Hm(n,t);r===0?(n.next=null,a===null?xu=u:a.next=u,u===null&&(Hl=a)):(a=n,(e!==0||(r&3)!==0)&&(ju=!0)),n=u}at!==0&&at!==5||Zs(e),cn!==0&&(cn=0)}function Hm(e,t){for(var a=e.suspendedLanes,n=e.pingedLanes,u=e.expirationTimes,r=e.pendingLanes&-62914561;0m)break;var k=j.transferSize,Q=j.initiatorType;k&&Jm(Q)&&(j=j.responseEnd,f+=k*(j"u"?null:document;function ip(e,t,a){var n=Bl;if(n&&typeof t=="string"&&t){var u=Gt(t);u='link[rel="'+e+'"][href="'+u+'"]',typeof a=="string"&&(u+='[crossorigin="'+a+'"]'),sp.has(u)||(sp.add(u),e={rel:e,crossOrigin:a,href:t},n.querySelector(u)===null&&(t=n.createElement("link"),dt(t,"link",e),st(t),n.head.appendChild(t)))}}function o0(e){Ua.D(e),ip("dns-prefetch",e,null)}function f0(e,t){Ua.C(e,t),ip("preconnect",e,t)}function d0(e,t,a){Ua.L(e,t,a);var n=Bl;if(n&&e&&t){var u='link[rel="preload"][as="'+Gt(t)+'"]';t==="image"&&a&&a.imageSrcSet?(u+='[imagesrcset="'+Gt(a.imageSrcSet)+'"]',typeof a.imageSizes=="string"&&(u+='[imagesizes="'+Gt(a.imageSizes)+'"]')):u+='[href="'+Gt(e)+'"]';var r=u;switch(t){case"style":r=Ll(e);break;case"script":r=Ql(e)}Wt.has(r)||(e=x({rel:"preload",href:t==="image"&&a&&a.imageSrcSet?void 0:e,as:t},a),Wt.set(r,e),n.querySelector(u)!==null||t==="style"&&n.querySelector(Fs(r))||t==="script"&&n.querySelector(Ws(r))||(t=n.createElement("link"),dt(t,"link",e),st(t),n.head.appendChild(t)))}}function h0(e,t){Ua.m(e,t);var a=Bl;if(a&&e){var n=t&&typeof t.as=="string"?t.as:"script",u='link[rel="modulepreload"][as="'+Gt(n)+'"][href="'+Gt(e)+'"]',r=u;switch(n){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":r=Ql(e)}if(!Wt.has(r)&&(e=x({rel:"modulepreload",href:e},t),Wt.set(r,e),a.querySelector(u)===null)){switch(n){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(a.querySelector(Ws(r)))return}n=a.createElement("link"),dt(n,"link",e),st(n),a.head.appendChild(n)}}}function m0(e,t,a){Ua.S(e,t,a);var n=Bl;if(n&&e){var u=rl(n).hoistableStyles,r=Ll(e);t=t||"default";var f=u.get(r);if(!f){var m={loading:0,preload:null};if(f=n.querySelector(Fs(r)))m.loading=5;else{e=x({rel:"stylesheet",href:e,"data-precedence":t},a),(a=Wt.get(r))&&mo(e,a);var j=f=n.createElement("link");st(j),dt(j,"link",e),j._p=new Promise(function(A,k){j.onload=A,j.onerror=k}),j.addEventListener("load",function(){m.loading|=1}),j.addEventListener("error",function(){m.loading|=2}),m.loading|=4,Tu(f,t,n)}f={type:"stylesheet",instance:f,count:1,state:m},u.set(r,f)}}}function p0(e,t){Ua.X(e,t);var a=Bl;if(a&&e){var n=rl(a).hoistableScripts,u=Ql(e),r=n.get(u);r||(r=a.querySelector(Ws(u)),r||(e=x({src:e,async:!0},t),(t=Wt.get(u))&&po(e,t),r=a.createElement("script"),st(r),dt(r,"link",e),a.head.appendChild(r)),r={type:"script",instance:r,count:1,state:null},n.set(u,r))}}function v0(e,t){Ua.M(e,t);var a=Bl;if(a&&e){var n=rl(a).hoistableScripts,u=Ql(e),r=n.get(u);r||(r=a.querySelector(Ws(u)),r||(e=x({src:e,async:!0,type:"module"},t),(t=Wt.get(u))&&po(e,t),r=a.createElement("script"),st(r),dt(r,"link",e),a.head.appendChild(r)),r={type:"script",instance:r,count:1,state:null},n.set(u,r))}}function up(e,t,a,n){var u=(u=ve.current)?Eu(u):null;if(!u)throw Error(c(446));switch(e){case"meta":case"title":return null;case"style":return typeof a.precedence=="string"&&typeof a.href=="string"?(t=Ll(a.href),a=rl(u).hoistableStyles,n=a.get(t),n||(n={type:"style",instance:null,count:0,state:null},a.set(t,n)),n):{type:"void",instance:null,count:0,state:null};case"link":if(a.rel==="stylesheet"&&typeof a.href=="string"&&typeof a.precedence=="string"){e=Ll(a.href);var r=rl(u).hoistableStyles,f=r.get(e);if(f||(u=u.ownerDocument||u,f={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},r.set(e,f),(r=u.querySelector(Fs(e)))&&!r._p&&(f.instance=r,f.state.loading=5),Wt.has(e)||(a={rel:"preload",as:"style",href:a.href,crossOrigin:a.crossOrigin,integrity:a.integrity,media:a.media,hrefLang:a.hrefLang,referrerPolicy:a.referrerPolicy},Wt.set(e,a),r||y0(u,e,a,f.state))),t&&n===null)throw Error(c(528,""));return f}if(t&&n!==null)throw Error(c(529,""));return null;case"script":return t=a.async,a=a.src,typeof a=="string"&&t&&typeof t!="function"&&typeof t!="symbol"?(t=Ql(a),a=rl(u).hoistableScripts,n=a.get(t),n||(n={type:"script",instance:null,count:0,state:null},a.set(t,n)),n):{type:"void",instance:null,count:0,state:null};default:throw Error(c(444,e))}}function Ll(e){return'href="'+Gt(e)+'"'}function Fs(e){return'link[rel="stylesheet"]['+e+"]"}function rp(e){return x({},e,{"data-precedence":e.precedence,precedence:null})}function y0(e,t,a,n){e.querySelector('link[rel="preload"][as="style"]['+t+"]")?n.loading=1:(t=e.createElement("link"),n.preload=t,t.addEventListener("load",function(){return n.loading|=1}),t.addEventListener("error",function(){return n.loading|=2}),dt(t,"link",a),st(t),e.head.appendChild(t))}function Ql(e){return'[src="'+Gt(e)+'"]'}function Ws(e){return"script[async]"+e}function cp(e,t,a){if(t.count++,t.instance===null)switch(t.type){case"style":var n=e.querySelector('style[data-href~="'+Gt(a.href)+'"]');if(n)return t.instance=n,st(n),n;var u=x({},a,{"data-href":a.href,"data-precedence":a.precedence,href:null,precedence:null});return n=(e.ownerDocument||e).createElement("style"),st(n),dt(n,"style",u),Tu(n,a.precedence,e),t.instance=n;case"stylesheet":u=Ll(a.href);var r=e.querySelector(Fs(u));if(r)return t.state.loading|=4,t.instance=r,st(r),r;n=rp(a),(u=Wt.get(u))&&mo(n,u),r=(e.ownerDocument||e).createElement("link"),st(r);var f=r;return f._p=new Promise(function(m,j){f.onload=m,f.onerror=j}),dt(r,"link",n),t.state.loading|=4,Tu(r,a.precedence,e),t.instance=r;case"script":return r=Ql(a.src),(u=e.querySelector(Ws(r)))?(t.instance=u,st(u),u):(n=a,(u=Wt.get(r))&&(n=x({},a),po(n,u)),e=e.ownerDocument||e,u=e.createElement("script"),st(u),dt(u,"link",n),e.head.appendChild(u),t.instance=u);case"void":return null;default:throw Error(c(443,t.type))}else t.type==="stylesheet"&&(t.state.loading&4)===0&&(n=t.instance,t.state.loading|=4,Tu(n,a.precedence,e));return t.instance}function Tu(e,t,a){for(var n=a.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),u=n.length?n[n.length-1]:null,r=u,f=0;f title"):null)}function g0(e,t,a){if(a===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;switch(t.rel){case"stylesheet":return e=t.disabled,typeof t.precedence=="string"&&e==null;default:return!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 dp(e){return!(e.type==="stylesheet"&&(e.state.loading&3)===0)}function x0(e,t,a,n){if(a.type==="stylesheet"&&(typeof n.media!="string"||matchMedia(n.media).matches!==!1)&&(a.state.loading&4)===0){if(a.instance===null){var u=Ll(n.href),r=t.querySelector(Fs(u));if(r){t=r._p,t!==null&&typeof t=="object"&&typeof t.then=="function"&&(e.count++,e=wu.bind(e),t.then(e,e)),a.state.loading|=4,a.instance=r,st(r);return}r=t.ownerDocument||t,n=rp(n),(u=Wt.get(u))&&mo(n,u),r=r.createElement("link"),st(r);var f=r;f._p=new Promise(function(m,j){f.onload=m,f.onerror=j}),dt(r,"link",n),a.instance=r}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(a,t),(t=a.state.preload)&&(a.state.loading&3)===0&&(e.count++,a=wu.bind(e),t.addEventListener("load",a),t.addEventListener("error",a))}}var vo=0;function j0(e,t){return e.stylesheets&&e.count===0&&Ou(e,e.stylesheets),0vo?50:800)+t);return e.unsuspend=a,function(){e.unsuspend=null,clearTimeout(n),clearTimeout(u)}}:null}function wu(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Ou(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Cu=null;function Ou(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Cu=new Map,t.forEach(b0,e),Cu=null,wu.call(e))}function b0(e,t){if(!(t.state.loading&4)){var a=Cu.get(e);if(a)var n=a.get(null);else{a=new Map,Cu.set(e,a);for(var u=e.querySelectorAll("link[data-precedence],style[data-precedence]"),r=0;r"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(l)}catch(i){console.error(i)}}return l(),_o.exports=B0(),_o.exports}var Q0=L0();/** + * react-router v7.18.1 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */var sf=/^(?:[a-z][a-z0-9+.-]*:|[\\/]{2})/i,bv=/^[\\/]{2}/;function Y0(l,i){return i+l.replace(/\\/g,"/")}var qp="popstate";function Hp(l){return typeof l=="object"&&l!=null&&"pathname"in l&&"search"in l&&"hash"in l&&"state"in l&&"key"in l}function G0(l={}){function i(c,d){var y;let h=(y=d.state)==null?void 0:y.masked,{pathname:v,search:g,hash:p}=h||c.location;return ko("",{pathname:v,search:g,hash:p},d.state&&d.state.usr||null,d.state&&d.state.key||"default",h?{pathname:c.location.pathname,search:c.location.search,hash:c.location.hash}:void 0)}function o(c,d){return typeof d=="string"?d:ci(d)}return K0(i,o,null,l)}function Xe(l,i){if(l===!1||l===null||typeof l>"u")throw new Error(i)}function ya(l,i){if(!l){typeof console<"u"&&console.warn(i);try{throw new Error(i)}catch{}}}function X0(){return Math.random().toString(36).substring(2,10)}function Bp(l,i){return{usr:l.state,key:l.key,idx:i,masked:l.mask?{pathname:l.pathname,search:l.search,hash:l.hash}:void 0}}function ko(l,i,o=null,c,d){return{pathname:typeof l=="string"?l:l.pathname,search:"",hash:"",...typeof i=="string"?ss(i):i,state:o,key:i&&i.key||c||X0(),mask:d}}function ci({pathname:l="/",search:i="",hash:o=""}){return i&&i!=="?"&&(l+=i.charAt(0)==="?"?i:"?"+i),o&&o!=="#"&&(l+=o.charAt(0)==="#"?o:"#"+o),l}function ss(l){let i={};if(l){let o=l.indexOf("#");o>=0&&(i.hash=l.substring(o),l=l.substring(0,o));let c=l.indexOf("?");c>=0&&(i.search=l.substring(c),l=l.substring(0,c)),l&&(i.pathname=l)}return i}function K0(l,i,o,c={}){let{window:d=document.defaultView,v5Compat:h=!1}=c,v=d.history,g="POP",p=null,y=E();y==null&&(y=0,v.replaceState({...v.state,idx:y},""));function E(){return(v.state||{idx:null}).idx}function x(){g="POP";let q=E(),H=q==null?null:q-y;y=q,p&&p({action:g,location:z.location,delta:H})}function w(q,H){g="PUSH";let G=Hp(q)?q:ko(z.location,q,H);y=E()+1;let K=Bp(G,y),Z=z.createHref(G.mask||G);try{v.pushState(K,"",Z)}catch(W){if(W instanceof DOMException&&W.name==="DataCloneError")throw W;d.location.assign(Z)}h&&p&&p({action:g,location:z.location,delta:1})}function O(q,H){g="REPLACE";let G=Hp(q)?q:ko(z.location,q,H);y=E();let K=Bp(G,y),Z=z.createHref(G.mask||G);v.replaceState(K,"",Z),h&&p&&p({action:g,location:z.location,delta:0})}function U(q){return Z0(d,q)}let z={get action(){return g},get location(){return l(d,v)},listen(q){if(p)throw new Error("A history only accepts one active listener");return d.addEventListener(qp,x),p=q,()=>{d.removeEventListener(qp,x),p=null}},createHref(q){return i(d,q)},createURL:U,encodeLocation(q){let H=U(q);return{pathname:H.pathname,search:H.search,hash:H.hash}},push:w,replace:O,go(q){return v.go(q)}};return z}function Z0(l,i,o=!1){let c="http://localhost";l&&(c=l.location.origin!=="null"?l.location.origin:l.location.href),Xe(c,"No window.location.(origin|href) available to create URL");let d=typeof i=="string"?i:ci(i);return d=d.replace(/ $/,"%20"),!o&&bv.test(d)&&(d=c+d),new URL(d,c)}function Sv(l,i,o="/"){return V0(l,i,o,!1)}function V0(l,i,o,c,d){let h=typeof i=="string"?ss(i):i,v=Ya(h.pathname||"/",o);if(v==null)return null;let g=J0(l),p=null,y=sx(v);for(let E=0;p==null&&E{let E={relativePath:y===void 0?v.path||"":y,caseSensitive:v.caseSensitive===!0,childrenIndex:g,route:v};if(E.relativePath.startsWith("/")){if(!E.relativePath.startsWith(c)&&p)return;Xe(E.relativePath.startsWith(c),`Absolute route path "${E.relativePath}" nested under path "${c}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),E.relativePath=E.relativePath.slice(c.length)}let x=ra([c,E.relativePath]),w=o.concat(E);v.children&&v.children.length>0&&(Xe(v.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${x}".`),Nv(v.children,i,w,x,p)),!(v.path==null&&!v.index)&&i.push({path:x,score:ax(x,v.index),routesMeta:w.map((O,U)=>{let[z,q]=_v(O.relativePath,O.caseSensitive,U===w.length-1);return{...O,matcher:z,compiledParams:q}})})};return l.forEach((v,g)=>{var p;if(v.path===""||!((p=v.path)!=null&&p.includes("?")))h(v,g);else for(let y of Ev(v.path))h(v,g,!0,y)}),i}function Ev(l){let i=l.split("/");if(i.length===0)return[];let[o,...c]=i,d=o.endsWith("?"),h=o.replace(/\?$/,"");if(c.length===0)return d?[h,""]:[h];let v=Ev(c.join("/")),g=[];return g.push(...v.map(p=>p===""?h:[h,p].join("/"))),d&&g.push(...v),g.map(p=>l.startsWith("/")&&p===""?"/":p)}function $0(l){l.sort((i,o)=>i.score!==o.score?o.score-i.score:nx(i.routesMeta.map(c=>c.childrenIndex),o.routesMeta.map(c=>c.childrenIndex)))}var F0=/^:[\w-]+$/,W0=3,P0=2,I0=1,ex=10,tx=-2,Lp=l=>l==="*";function ax(l,i){let o=l.split("/"),c=o.length;return o.some(Lp)&&(c+=tx),i&&(c+=P0),o.filter(d=>!Lp(d)).reduce((d,h)=>d+(F0.test(h)?W0:h===""?I0:ex),c)}function nx(l,i){return l.length===i.length&&l.slice(0,-1).every((c,d)=>c===i[d])?l[l.length-1]-i[i.length-1]:0}function lx(l,i,o=!1){let{routesMeta:c}=l,d={},h="/",v=[];for(let g=0;g{if(E==="*"){let U=g[w]||"";v=h.slice(0,h.length-U.length).replace(/(.)\/+$/,"$1")}const O=g[w];return x&&!O?y[E]=void 0:y[E]=(O||"").replace(/%2F/g,"/"),y},{}),pathname:h,pathnameBase:v,pattern:l}}function _v(l,i=!1,o=!0){ya(l==="*"||!l.endsWith("*")||l.endsWith("/*"),`Route path "${l}" will be treated as if it were "${l.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${l.replace(/\*$/,"/*")}".`);let c=[],d="^"+l.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(v,g,p,y,E)=>{if(c.push({paramName:g,isOptional:p!=null}),p){let x=E.charAt(y+v.length);return x&&x!=="/"?"/([^\\/]*)":"(?:/([^\\/]*))?"}return"/([^\\/]+)"}).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return l.endsWith("*")?(c.push({paramName:"*"}),d+=l==="*"||l==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):o?d+="\\/*$":l!==""&&l!=="/"&&(d+="(?:(?=\\/|$))"),[new RegExp(d,i?void 0:"i"),c]}function sx(l){try{return l.split("/").map(i=>decodeURIComponent(i).replace(/\//g,"%2F")).join("/")}catch(i){return ya(!1,`The URL path "${l}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${i}).`),l}}function Ya(l,i){if(i==="/")return l;if(!l.toLowerCase().startsWith(i.toLowerCase()))return null;let o=i.endsWith("/")?i.length-1:i.length,c=l.charAt(o);return c&&c!=="/"?null:l.slice(o)||"/"}function ix(l,i="/"){let{pathname:o,search:c="",hash:d=""}=typeof l=="string"?ss(l):l,h;return o?(o=Cv(o),o.startsWith("/")?h=Qp(o.substring(1),"/"):h=Qp(o,i)):h=i,{pathname:h,search:cx(c),hash:ox(d)}}function Qp(l,i){let o=Ju(i).split("/");return l.split("/").forEach(d=>{d===".."?o.length>1&&o.pop():d!=="."&&o.push(d)}),o.length>1?o.join("/"):"/"}function Ao(l,i,o,c){return`Cannot include a '${l}' character in a manually specified \`to.${i}\` field [${JSON.stringify(c)}]. 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 ux(l){return l.filter((i,o)=>o===0||i.route.path&&i.route.path.length>0)}function wv(l){let i=ux(l);return i.map((o,c)=>c===i.length-1?o.pathname:o.pathnameBase)}function uf(l,i,o,c=!1){let d;typeof l=="string"?d=ss(l):(d={...l},Xe(!d.pathname||!d.pathname.includes("?"),Ao("?","pathname","search",d)),Xe(!d.pathname||!d.pathname.includes("#"),Ao("#","pathname","hash",d)),Xe(!d.search||!d.search.includes("#"),Ao("#","search","hash",d)));let h=l===""||d.pathname==="",v=h?"/":d.pathname,g;if(v==null)g=o;else{let x=i.length-1;if(!c&&v.startsWith("..")){let w=v.split("/");for(;w[0]==="..";)w.shift(),x-=1;d.pathname=w.join("/")}g=x>=0?i[x]:"/"}let p=ix(d,g),y=v&&v!=="/"&&v.endsWith("/"),E=(h||v===".")&&o.endsWith("/");return!p.pathname.endsWith("/")&&(y||E)&&(p.pathname+="/"),p}var Cv=l=>l.replace(/[\\/]{2,}/g,"/"),ra=l=>Cv(l.join("/")),Ju=l=>l.replace(/\/+$/,""),rx=l=>Ju(l).replace(/^\/*/,"/"),cx=l=>!l||l==="?"?"":l.startsWith("?")?l:"?"+l,ox=l=>!l||l==="#"?"":l.startsWith("#")?l:"#"+l,fx=class{constructor(l,i,o,c=!1){this.status=l,this.statusText=i||"",this.internal=c,o instanceof Error?(this.data=o.toString(),this.error=o):this.data=o}};function dx(l){return l!=null&&typeof l.status=="number"&&typeof l.statusText=="string"&&typeof l.internal=="boolean"&&"data"in l}function hx(l){let i=l.map(o=>o.route.path).filter(Boolean);return ra(i)||"/"}var Ov=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Av(l,i){let o=l;if(typeof o!="string"||!sf.test(o))return{absoluteURL:void 0,isExternal:!1,to:o};let c=o,d=!1;if(Ov)try{let h=new URL(window.location.href),v=bv.test(o)?new URL(Y0(o,h.protocol)):new URL(o),g=Ya(v.pathname,i);v.origin===h.origin&&g!=null?o=g+v.search+v.hash:d=!0}catch{ya(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:c,isExternal:d,to:o}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var Rv=["POST","PUT","PATCH","DELETE"];new Set(Rv);var mx=["GET",...Rv];new Set(mx);var px=["about:","blob:","chrome:","chrome-untrusted:","content:","data:","devtools:","file:","filesystem:","javascript:"];function vx(l){try{return px.includes(new URL(l).protocol)}catch{return!1}}var is=S.createContext(null);is.displayName="DataRouter";var er=S.createContext(null);er.displayName="DataRouterState";var Mv=S.createContext(!1);function yx(){return S.useContext(Mv)}var zv=S.createContext({isTransitioning:!1});zv.displayName="ViewTransition";var gx=S.createContext(new Map);gx.displayName="Fetchers";var xx=S.createContext(null);xx.displayName="Await";var aa=S.createContext(null);aa.displayName="Navigation";var vi=S.createContext(null);vi.displayName="Location";var ca=S.createContext({outlet:null,matches:[],isDataRoute:!1});ca.displayName="Route";var rf=S.createContext(null);rf.displayName="RouteError";var Dv="REACT_ROUTER_ERROR",jx="REDIRECT",bx="ROUTE_ERROR_RESPONSE";function Sx(l){if(l.startsWith(`${Dv}:${jx}:{`))try{let i=JSON.parse(l.slice(28));if(typeof i=="object"&&i&&typeof i.status=="number"&&typeof i.statusText=="string"&&typeof i.location=="string"&&typeof i.reloadDocument=="boolean"&&typeof i.replace=="boolean")return i}catch{}}function Nx(l){if(l.startsWith(`${Dv}:${bx}:{`))try{let i=JSON.parse(l.slice(40));if(typeof i=="object"&&i&&typeof i.status=="number"&&typeof i.statusText=="string")return new fx(i.status,i.statusText,i.data)}catch{}}function Ex(l,{relative:i}={}){Xe(yi(),"useHref() may be used only in the context of a component.");let{basename:o,navigator:c}=S.useContext(aa),{hash:d,pathname:h,search:v}=gi(l,{relative:i}),g=h;return o!=="/"&&(g=h==="/"?o:ra([o,h])),c.createHref({pathname:g,search:v,hash:d})}function yi(){return S.useContext(vi)!=null}function Ga(){return Xe(yi(),"useLocation() may be used only in the context of a component."),S.useContext(vi).location}var Uv="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function kv(l){S.useContext(aa).static||S.useLayoutEffect(l)}function qv(){let{isDataRoute:l}=S.useContext(ca);return l?Bx():Tx()}function Tx(){Xe(yi(),"useNavigate() may be used only in the context of a component.");let l=S.useContext(is),{basename:i,navigator:o}=S.useContext(aa),{matches:c}=S.useContext(ca),{pathname:d}=Ga(),h=JSON.stringify(wv(c)),v=S.useRef(!1);return kv(()=>{v.current=!0}),S.useCallback((p,y={})=>{if(ya(v.current,Uv),!v.current)return;if(typeof p=="number"){o.go(p);return}let E=uf(p,JSON.parse(h),d,y.relative==="path");l==null&&i!=="/"&&(E.pathname=E.pathname==="/"?i:ra([i,E.pathname])),(y.replace?o.replace:o.push)(E,y.state,y)},[i,o,h,d,l])}var _x=S.createContext(null);function wx(l){let i=S.useContext(ca).outlet;return S.useMemo(()=>i&&S.createElement(_x.Provider,{value:l},i),[i,l])}function Hv(){let{matches:l}=S.useContext(ca),i=l[l.length-1];return(i==null?void 0:i.params)??{}}function gi(l,{relative:i}={}){let{matches:o}=S.useContext(ca),{pathname:c}=Ga(),d=JSON.stringify(wv(o));return S.useMemo(()=>uf(l,JSON.parse(d),c,i==="path"),[l,d,c,i])}function Cx(l,i){return Bv(l,i)}function Bv(l,i,o){var q;Xe(yi(),"useRoutes() may be used only in the context of a component.");let{navigator:c}=S.useContext(aa),{matches:d}=S.useContext(ca),h=d[d.length-1],v=h?h.params:{},g=h?h.pathname:"/",p=h?h.pathnameBase:"/",y=h&&h.route;{let H=y&&y.path||"";Qv(g,!y||H.endsWith("*")||H.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${g}" (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 E=Ga(),x;if(i){let H=typeof i=="string"?ss(i):i;Xe(p==="/"||((q=H.pathname)==null?void 0:q.startsWith(p)),`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 "${p}" but pathname "${H.pathname}" was given in the \`location\` prop.`),x=H}else x=E;let w=x.pathname||"/",O=w;if(p!=="/"){let H=p.replace(/^\//,"").split("/");O="/"+w.replace(/^\//,"").split("/").slice(H.length).join("/")}let U=o&&o.state.matches.length?o.state.matches.map(H=>Object.assign(H,{route:o.manifest[H.route.id]||H.route})):Sv(l,{pathname:O});ya(y||U!=null,`No routes matched location "${x.pathname}${x.search}${x.hash}" `),ya(U==null||U[U.length-1].route.element!==void 0||U[U.length-1].route.Component!==void 0||U[U.length-1].route.lazy!==void 0,`Matched leaf route at location "${x.pathname}${x.search}${x.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 z=zx(U&&U.map(H=>Object.assign({},H,{params:Object.assign({},v,H.params),pathname:ra([p,c.encodeLocation?c.encodeLocation(H.pathname.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:H.pathname]),pathnameBase:H.pathnameBase==="/"?p:ra([p,c.encodeLocation?c.encodeLocation(H.pathnameBase.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:H.pathnameBase])})),d,o);return i&&z?S.createElement(vi.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",mask:void 0,...x},navigationType:"POP"}},z):z}function Ox(){let l=Hx(),i=dx(l)?`${l.status} ${l.statusText}`:l instanceof Error?l.message:JSON.stringify(l),o=l instanceof Error?l.stack:null,c="rgba(200,200,200, 0.5)",d={padding:"0.5rem",backgroundColor:c},h={padding:"2px 4px",backgroundColor:c},v=null;return console.error("Error handled by React Router default ErrorBoundary:",l),v=S.createElement(S.Fragment,null,S.createElement("p",null,"💿 Hey developer 👋"),S.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",S.createElement("code",{style:h},"ErrorBoundary")," or"," ",S.createElement("code",{style:h},"errorElement")," prop on your route.")),S.createElement(S.Fragment,null,S.createElement("h2",null,"Unexpected Application Error!"),S.createElement("h3",{style:{fontStyle:"italic"}},i),o?S.createElement("pre",{style:d},o):null,v)}var Ax=S.createElement(Ox,null),Lv=class extends S.Component{constructor(l){super(l),this.state={location:l.location,revalidation:l.revalidation,error:l.error}}static getDerivedStateFromError(l){return{error:l}}static getDerivedStateFromProps(l,i){return i.location!==l.location||i.revalidation!=="idle"&&l.revalidation==="idle"?{error:l.error,location:l.location,revalidation:l.revalidation}:{error:l.error!==void 0?l.error:i.error,location:i.location,revalidation:l.revalidation||i.revalidation}}componentDidCatch(l,i){this.props.onError?this.props.onError(l,i):console.error("React Router caught the following error during render",l)}render(){let l=this.state.error;if(this.context&&typeof l=="object"&&l&&"digest"in l&&typeof l.digest=="string"){const o=Nx(l.digest);o&&(l=o)}let i=l!==void 0?S.createElement(ca.Provider,{value:this.props.routeContext},S.createElement(rf.Provider,{value:l,children:this.props.component})):this.props.children;return this.context?S.createElement(Rx,{error:l},i):i}};Lv.contextType=Mv;var Ro=new WeakMap;function Rx({children:l,error:i}){let{basename:o}=S.useContext(aa);if(typeof i=="object"&&i&&"digest"in i&&typeof i.digest=="string"){let c=Sx(i.digest);if(c){let d=Ro.get(i);if(d)throw d;let h=Av(c.location,o),v=h.absoluteURL||h.to;if(vx(v))throw new Error("Invalid redirect location");if(Ov&&!Ro.get(i))if(h.isExternal||c.reloadDocument)window.location.href=v;else{const g=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(h.to,{replace:c.replace}));throw Ro.set(i,g),g}return S.createElement("meta",{httpEquiv:"refresh",content:`0;url=${v}`})}}return l}function Mx({routeContext:l,match:i,children:o}){let c=S.useContext(is);return c&&c.static&&c.staticContext&&(i.route.errorElement||i.route.ErrorBoundary)&&(c.staticContext._deepestRenderedBoundaryId=i.route.id),S.createElement(ca.Provider,{value:l},o)}function zx(l,i=[],o){let c=o==null?void 0:o.state;if(l==null){if(!c)return null;if(c.errors)l=c.matches;else if(i.length===0&&!c.initialized&&c.matches.length>0)l=c.matches;else return null}let d=l,h=c==null?void 0:c.errors;if(h!=null){let E=d.findIndex(x=>x.route.id&&(h==null?void 0:h[x.route.id])!==void 0);Xe(E>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(h).join(",")}`),d=d.slice(0,Math.min(d.length,E+1))}let v=!1,g=-1;if(o&&c){v=c.renderFallback;for(let E=0;E=0?d=d.slice(0,g+1):d=[d[0]];break}}}}let p=o==null?void 0:o.onError,y=c&&p?(E,x)=>{var w,O;p(E,{location:c.location,params:((O=(w=c.matches)==null?void 0:w[0])==null?void 0:O.params)??{},pattern:hx(c.matches),errorInfo:x})}:void 0;return d.reduceRight((E,x,w)=>{let O,U=!1,z=null,q=null;c&&(O=h&&x.route.id?h[x.route.id]:void 0,z=x.route.errorElement||Ax,v&&(g<0&&w===0?(Qv("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),U=!0,q=null):g===w&&(U=!0,q=x.route.hydrateFallbackElement||null)));let H=i.concat(d.slice(0,w+1)),G=()=>{let K;return O?K=z:U?K=q:x.route.Component?K=S.createElement(x.route.Component,null):x.route.element?K=x.route.element:K=E,S.createElement(Mx,{match:x,routeContext:{outlet:E,matches:H,isDataRoute:c!=null},children:K})};return c&&(x.route.ErrorBoundary||x.route.errorElement||w===0)?S.createElement(Lv,{location:c.location,revalidation:c.revalidation,component:z,error:O,children:G(),routeContext:{outlet:null,matches:H,isDataRoute:!0},onError:y}):G()},null)}function cf(l){return`${l} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function Dx(l){let i=S.useContext(is);return Xe(i,cf(l)),i}function Ux(l){let i=S.useContext(er);return Xe(i,cf(l)),i}function kx(l){let i=S.useContext(ca);return Xe(i,cf(l)),i}function of(l){let i=kx(l),o=i.matches[i.matches.length-1];return Xe(o.route.id,`${l} can only be used on routes that contain a unique "id"`),o.route.id}function qx(){return of("useRouteId")}function Hx(){var c;let l=S.useContext(rf),i=Ux("useRouteError"),o=of("useRouteError");return l!==void 0?l:(c=i.errors)==null?void 0:c[o]}function Bx(){let{router:l}=Dx("useNavigate"),i=of("useNavigate"),o=S.useRef(!1);return kv(()=>{o.current=!0}),S.useCallback(async(d,h={})=>{ya(o.current,Uv),o.current&&(typeof d=="number"?await l.navigate(d):await l.navigate(d,{fromRouteId:i,...h}))},[l,i])}var Yp={};function Qv(l,i,o){!i&&!Yp[l]&&(Yp[l]=!0,ya(!1,o))}S.memo(Lx);function Lx({routes:l,manifest:i,future:o,state:c,isStatic:d,onError:h}){return Bv(l,void 0,{manifest:i,state:c,isStatic:d,onError:h})}function Qx(l){return wx(l.context)}function ia(l){Xe(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function Yx({basename:l="/",children:i=null,location:o,navigationType:c="POP",navigator:d,static:h=!1,useTransitions:v}){Xe(!yi(),"You cannot render a inside another . You should never have more than one in your app.");let g=l.replace(/^\/*/,"/"),p=S.useMemo(()=>({basename:g,navigator:d,static:h,useTransitions:v,future:{}}),[g,d,h,v]);typeof o=="string"&&(o=ss(o));let{pathname:y="/",search:E="",hash:x="",state:w=null,key:O="default",mask:U}=o,z=S.useMemo(()=>{let q=Ya(y,g);return q==null?null:{location:{pathname:q,search:E,hash:x,state:w,key:O,mask:U},navigationType:c}},[g,y,E,x,w,O,c,U]);return ya(z!=null,` is not able to match the URL "${y}${E}${x}" because it does not start with the basename, so the won't render anything.`),z==null?null:S.createElement(aa.Provider,{value:p},S.createElement(vi.Provider,{children:i,value:z}))}function Gx({children:l,location:i}){return Cx(qo(l),i)}function qo(l,i=[]){let o=[];return S.Children.forEach(l,(c,d)=>{if(!S.isValidElement(c))return;let h=[...i,d];if(c.type===S.Fragment){o.push.apply(o,qo(c.props.children,h));return}Xe(c.type===ia,`[${typeof c.type=="string"?c.type:c.type.name}] is not a component. All component children of must be a or `),Xe(!c.props.index||!c.props.children,"An index route cannot have child routes.");let v={id:c.props.id||h.join("-"),caseSensitive:c.props.caseSensitive,element:c.props.element,Component:c.props.Component,index:c.props.index,path:c.props.path,middleware:c.props.middleware,loader:c.props.loader,action:c.props.action,hydrateFallbackElement:c.props.hydrateFallbackElement,HydrateFallback:c.props.HydrateFallback,errorElement:c.props.errorElement,ErrorBoundary:c.props.ErrorBoundary,hasErrorBoundary:c.props.hasErrorBoundary===!0||c.props.ErrorBoundary!=null||c.props.errorElement!=null,shouldRevalidate:c.props.shouldRevalidate,handle:c.props.handle,lazy:c.props.lazy};c.props.children&&(v.children=qo(c.props.children,h)),o.push(v)}),o}var Gu="get",Xu="application/x-www-form-urlencoded";function tr(l){return typeof HTMLElement<"u"&&l instanceof HTMLElement}function Xx(l){return tr(l)&&l.tagName.toLowerCase()==="button"}function Kx(l){return tr(l)&&l.tagName.toLowerCase()==="form"}function Zx(l){return tr(l)&&l.tagName.toLowerCase()==="input"}function Vx(l){return!!(l.metaKey||l.altKey||l.ctrlKey||l.shiftKey)}function Jx(l,i){return l.button===0&&(!i||i==="_self")&&!Vx(l)}var Hu=null;function $x(){if(Hu===null)try{new FormData(document.createElement("form"),0),Hu=!1}catch{Hu=!0}return Hu}var Fx=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function Mo(l){return l!=null&&!Fx.has(l)?(ya(!1,`"${l}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${Xu}"`),null):l}function Wx(l,i){let o,c,d,h,v;if(Kx(l)){let g=l.getAttribute("action");c=g?Ya(g,i):null,o=l.getAttribute("method")||Gu,d=Mo(l.getAttribute("enctype"))||Xu,h=new FormData(l)}else if(Xx(l)||Zx(l)&&(l.type==="submit"||l.type==="image")){let g=l.form;if(g==null)throw new Error('Cannot submit a + )} + + + {(item.kind === 'pr' || item.kind === 'findings') && ( +
+ {t.review && ( + + reviewed sha {shortSha(t.review.result_sha)} + + )} + {t.submission && ( + + submitted sha{' '} + {shortSha(t.submission.result_sha)} + + )} + {t.submission?.gate_ref && ( + + gate {t.submission.gate_ref} + + )} + {t.submission?.review_ref && ( + + review {t.submission.review_ref} + + )} +
+ )} + + {item.kind === 'trajectory' && t.decision_request?.options?.length ? ( +
+

+ + agent-supplied · proposed options +

+ {t.decision_request.options.map((o) => ( +

+ {o.id} {o.description} + {o.tradeoff && — {o.tradeoff}} +

+ ))} +
+ ) : null} + + {open && item.kind === 'findings' && t.review && ( + + )} + + ) +} + +export function Review() { + const overview = useQuery({ + queryKey: ['overview'], + queryFn: api.overview, + refetchInterval: 5000, + }) + const [kind, setKind] = useState('all') + + const items = (overview.data?.tasks ?? []).flatMap(itemsFor) + // Most urgent first: high inherent priority, then oldest wait. + const order = KINDS.map((k) => k.kind) + items.sort( + (a, b) => + (b.task.inherent_priority ?? 0) - (a.task.inherent_priority ?? 0) || + Date.parse(a.at ?? '') - Date.parse(b.at ?? '') || + order.indexOf(a.kind) - order.indexOf(b.kind), + ) + const shown = kind === 'all' ? items : items.filter((i) => i.kind === kind) + const count = (k: Kind) => items.filter((i) => i.kind === k).length + const blocking = items.reduce((n, i) => n + (i.kind === 'findings' ? (i.task.review?.blocking ?? 0) : 0), 0) + + return ( +
+
+

Review

+

Items that need your attention and judgement.

+ + {overview.data ? ( + <> + read {new Date(overview.data.updated_at).toISOString().slice(11, 19)} UTC + + ) : overview.isError ? ( + overview unreachable — {String(overview.error)} + ) : ( + 'reading the first snapshot' + )} + +
+ +
+
+ Needs you + {items.length} + + across {new Set(items.map((i) => i.task.id)).size} tasks + +
+
+ Blocking findings + {blocking} + claimed by sealed reviews, not yet accepted +
+
+ Pull requests open + {count('pr')} + recorded on a submission +
+
+ + +
+ + {KINDS.map((k) => ( + + ))} +
+ + {shown.length === 0 ? ( + +

+ Trajectory gates, human decisions, operator-required blocks, blocking review findings + and open pull requests land here. +

+
+ ) : ( +
+ {shown.map((i) => ( + + ))} +
+ )} +
+ + +
+ ) +} diff --git a/web/src/screens/Settings.css b/web/src/screens/Settings.css new file mode 100644 index 0000000..51779ba --- /dev/null +++ b/web/src/screens/Settings.css @@ -0,0 +1,164 @@ +/* Settings. Only what the shared shell does not carry: the tab strip, the + credential form geometry, and the fact list. */ + +.set-tabs { + display: flex; + gap: 2px; + flex-wrap: wrap; + border-bottom: 1px solid var(--line); +} + +.set-tab { + background: none; + border: 0; + border-bottom: 2px solid transparent; + margin-bottom: -1px; + padding: 8px 12px; + color: var(--text-mid); + cursor: pointer; + transition: color var(--fast) var(--ease), border-color var(--fast) var(--ease); +} + +.set-tab:hover { + color: var(--text-hi); +} + +/* Accent marks the section you are in, and the save button. Nothing else. */ +.set-tab[aria-selected='true'] { + color: var(--accent-hi); + border-bottom-color: var(--accent); +} + +.set-account { + display: grid; + grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); + gap: 20px; + align-items: start; +} + +.set-form { + display: flex; + flex-direction: column; + gap: 16px; +} + +.set-note { + margin: 0; + color: var(--text-mid); + font-size: 13px; +} + +.set-field { + display: flex; + flex-direction: column; + gap: 6px; + min-width: 0; +} + +.set-split { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 14px; +} + +.set-input { + height: 34px; + padding: 0 11px; + background: var(--bg-2); + border: 1px solid var(--line); + border-radius: var(--r-sm); + color: var(--text-hi); + font: inherit; + outline: none; + width: 100%; + transition: border-color var(--fast) var(--ease); +} + +.set-input::placeholder { + color: var(--text-lo); +} + +.set-input:focus { + border-color: var(--accent-line); +} + +.set-reveal { + position: relative; + display: flex; +} + +.set-reveal-btn { + position: absolute; + right: 4px; + top: 4px; + height: 26px; + padding: 0 9px; + border: 0; + border-radius: 6px; + background: var(--bg-3); + color: var(--text-mid); + font-size: 12px; + cursor: pointer; +} + +.set-rules { + display: flex; + flex-wrap: wrap; + gap: 14px; + font-size: 12px; + color: var(--text-lo); +} + +.set-rules span { + display: inline-flex; + align-items: center; + gap: 5px; +} + +.set-rules span[data-met='true'] { + color: #5fbf82; +} + +.set-error { + display: flex; + align-items: center; + gap: 8px; + margin: 0; + font-size: 13px; + color: #e06c60; +} + +.set-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; + flex-wrap: wrap; + border-top: 1px solid var(--line); + padding-top: 14px; +} + +.set-facts { + display: flex; + flex-direction: column; + gap: 12px; +} + +.set-facts > div { + display: flex; + flex-direction: column; + gap: 3px; + min-width: 0; + word-break: break-word; +} + +.set-signout { + align-self: flex-start; + margin-top: 4px; +} + +@media (max-width: 900px) { + .set-account { + grid-template-columns: minmax(0, 1fr); + } +} diff --git a/web/src/screens/Settings.tsx b/web/src/screens/Settings.tsx new file mode 100644 index 0000000..6973a30 --- /dev/null +++ b/web/src/screens/Settings.tsx @@ -0,0 +1,256 @@ +import { useState } from 'react' +import { useMutation } from '@tanstack/react-query' +import { api } from '../api/client' +import type { Account } from '../api/types' +import { Chip, EndpointGap, M, Panel } from '../components/Primitives' +import { Icon } from '../components/Icon' +import './Settings.css' + +/** The spec asks for eight tabs. The coordinator has exactly one settings + * mutation — PUT /v1/ui/account — so exactly one tab holds controls. Every + * other tab names the endpoint that would supply it rather than showing a + * form that cannot save. Law 5. */ +type TabKey = + | 'account' + | 'general' + | 'appearance' + | 'notifications' + | 'access' + | 'integrations' + | 'agents' + | 'policies' + | 'advanced' + +const TABS: { key: TabKey; label: string }[] = [ + { key: 'account', label: 'Account' }, + { key: 'general', label: 'General' }, + { key: 'appearance', label: 'Appearance' }, + { key: 'notifications', label: 'Notifications' }, + { key: 'access', label: 'Access & security' }, + { key: 'integrations', label: 'Integrations' }, + { key: 'agents', label: 'Agents & tools' }, + { key: 'policies', label: 'Policies' }, + { key: 'advanced', label: 'Advanced' }, +] + +const GAPS: Record, { path: string; what: string }> = { + general: { + path: 'GET/PUT /v1/ui/settings/general', + what: 'Instance name and environment, time display, default task preferences, default workflow policy and event/artifact retention. The coordinator keeps these in config.jsonc and the container environment; nothing reads or writes them over the API.', + }, + appearance: { + path: 'GET/PUT /v1/ui/settings/appearance', + what: 'Theme and reduced motion are the only appearance choices ethos permits, and neither is stored server-side today. The console follows the operating system.', + }, + notifications: { + path: 'GET/PUT /v1/ui/settings/notifications', + what: 'Notification sinks and per-event routing. ntfy credentials live in the compose .env file and are never returned by the API.', + }, + access: { + path: 'GET /v1/ui/settings/access', + what: 'Web tokens and their status, trusted and ignored review actors, live operator sessions, agent-surface auth and the source each secret is loaded from. Only the operator credential below is exposed today.', + }, + integrations: { + path: 'GET /v1/ui/settings/integrations', + what: 'Gitea and Vikunja endpoints, notification sinks and source reconciliation state, each with its identity and last successful interaction. No endpoint reports integration health.', + }, + agents: { + path: 'GET /v1/ui/settings/harnesses', + what: 'Harness definitions, adapter runtime configuration, herdr and tmux ownership, default launch behaviour and context occupancy thresholds. The registry is read from config.jsonc at start-up and is not served.', + }, + policies: { + path: 'GET/PUT /v1/ui/settings/policies', + what: 'Work phase defaults, the trajectory gate, the bounded decision request budget, retry policy, reconcile failure handoff threshold and review/submission policy. These are compiled-in defaults, not stored settings.', + }, + advanced: { + path: 'GET /v1/ui/settings/diagnostics', + what: 'Event-store diagnostics, snapshots and replay, raw configuration, migration status and destructive operator actions. Destructive actions must not be built before the coordinator can report what they would touch.', + }, +} + +function stamp(at?: string) { + if (!at) return '—' + const ms = Date.parse(at) + return Number.isFinite(ms) ? new Date(ms).toISOString().replace('T', ' ').slice(0, 19) + ' UTC' : at +} + +function message(error: unknown) { + return error instanceof Error ? error.message : String(error) +} + +export function Settings({ account, onLogout, onCredentialsChanged }: { + account: Account + onLogout: () => void + onCredentialsChanged: (username: string) => void +}) { + const [tab, setTab] = useState('account') + return ( +
+
+

Settings

+

One operator credential is stored here. Everything else Orchestra runs on comes from config and environment.

+
+ +
+ {TABS.map((t) => ( + + ))} +
+ + {tab === 'account' ? ( + + ) : ( + t.key === tab)?.label}> + + + )} +
+ ) +} + +function AccountTab({ account, onLogout, onCredentialsChanged }: { + account: Account + onLogout: () => void + onCredentialsChanged: (username: string) => void +}) { + const [username, setUsername] = useState(account.username) + const [currentPassword, setCurrentPassword] = useState('') + const [newPassword, setNewPassword] = useState('') + const [confirmation, setConfirmation] = useState('') + const [visible, setVisible] = useState(false) + const [formError, setFormError] = useState('') + + const mutation = useMutation({ + mutationFn: () => + api.updateAccount({ + current_password: currentPassword, + username: username.trim(), + ...(newPassword ? { new_password: newPassword } : {}), + }), + onSuccess: (updated) => onCredentialsChanged(updated.username), + }) + + const changed = username.trim() !== account.username || !!newPassword + const longEnough = newPassword.length >= 10 + const withinLimit = new TextEncoder().encode(newPassword).length <= 72 + const matches = newPassword === confirmation + + const submit = (event: React.FormEvent) => { + event.preventDefault() + setFormError('') + if (!username.trim()) return setFormError('Username cannot be empty.') + if (!changed) return setFormError('Change the username or enter a new password first.') + if (!currentPassword) return setFormError('Enter your current password to authorize this change.') + if (newPassword && (!longEnough || !withinLimit || !matches)) { + return setFormError(matches ? 'Use a password between 10 and 72 bytes.' : 'The new passwords do not match.') + } + mutation.mutate() + } + + return ( +
+ + +

+ Changing either field signs out every browser using this account. Your current password authorizes the change and is never stored by the console. +

+ +
+ + { setUsername(e.target.value); setFormError('') }} + /> +
+ +
+
+ +
+ { setNewPassword(e.target.value); setFormError('') }} + /> + +
+
+
+ + { setConfirmation(e.target.value); setFormError('') }} + /> +
+
+ + {newPassword && ( +
+ 10 characters or more + 72 bytes or fewer + Passwords match +
+ )} + +
+ + { setCurrentPassword(e.target.value); setFormError('') }} + /> +
+ + {(formError || mutation.error) && ( +

+ {formError || message(mutation.error)} +

+ )} + +
+ You sign in again after saving. + +
+ +
+ + operator}> +
+
Username{account.username}
+
Created{stamp(account.created_at)}
+
Credentials updated{stamp(account.updated_at)}
+
Credential storebcrypt · orchestra data volume
+ +
+
+
+ ) +} diff --git a/web/src/screens/TaskDetail.css b/web/src/screens/TaskDetail.css new file mode 100644 index 0000000..900fe92 --- /dev/null +++ b/web/src/screens/TaskDetail.css @@ -0,0 +1,293 @@ +/* Task detail. Only what orchestra.css does not already carry: the two-column + split, the agent-claim treatment, and the muted superseded register. */ + +.td-top { + display: flex; + align-items: flex-end; + gap: 16px; + flex-wrap: wrap; +} + +.td-top .page-head { + flex: 1; + min-width: 0; +} + +.td-actions { + display: flex; + align-items: center; + gap: 10px; +} + +.td-ids { + display: flex; + align-items: baseline; + gap: 8px 18px; + flex-wrap: wrap; + margin-top: -12px; +} + +.td-columns { + display: grid; + grid-template-columns: minmax(280px, 5fr) minmax(320px, 7fr); + gap: 20px; + align-items: start; +} + +.td-doing > .label { + display: block; + margin: 18px 0 6px; +} + +.td-doing > .label:first-child { + margin-top: 0; +} + +.td-prose { + margin: 0; + color: var(--text-hi); +} + +.td-muted { + color: var(--text-lo); + font-size: 13px; + margin: 6px 0 0; +} + +.td-list { + margin: 0; + padding-left: 18px; + display: flex; + flex-direction: column; + gap: 5px; + color: var(--text-mid); +} + +.td-more > summary { + cursor: pointer; + color: var(--text-lo); + font-size: 13px; + margin-top: 8px; + list-style: none; +} + +.td-more > summary::before { + content: '▸ '; + color: var(--text-lo); +} + +.td-more[open] > summary::before { + content: '▾ '; +} + +.td-more[open] > summary { + margin-bottom: 8px; +} + +/* Operator authority: the one class of content that carries the accent. */ +.td-decisions { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 6px; +} + +.td-decisions li { + display: flex; + align-items: baseline; + gap: 10px; + flex-wrap: wrap; + padding: 8px 10px; + border-radius: var(--r-sm); + background: var(--accent-dim); + border-left: 2px solid var(--accent); +} + +.td-kind { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--accent-hi); +} + +.td-subject { + color: var(--text-mid); +} + +.td-value { + color: var(--text-hi); + flex: 1; + min-width: 0; +} + +/* Superseded: still readable, never dominant, and stripped of the accent. */ +.td-superseded .td-decisions li { + background: var(--bg-2); + border-left-color: var(--line-hi); + opacity: 0.7; +} + +.td-superseded .td-kind { + color: var(--text-lo); +} + +.td-superseded .td-value { + color: var(--text-mid); + text-decoration: line-through; +} + +.td-exec { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 12px; +} + +.td-exec .stat { + gap: 5px; +} + +.td-exec .stat-value { + font-size: 20px; +} + +/* Agent claims read as quotation, not as state: dashed rule, machine text, + and a tag that names the source. */ +.td-claim-head { + display: flex; + align-items: baseline; + gap: 10px; + margin: 18px 0 6px; +} + +.td-claim-tag { + font-family: var(--mono); + font-size: 10px; + letter-spacing: 0; + text-transform: none; + color: var(--text-lo); +} + +.td-claim { + display: flex; + align-items: baseline; + gap: 12px; + margin: 0; + padding: 10px 12px; + background: var(--bg-2); + border: 1px dashed var(--line-hi); + border-radius: var(--r-sm); +} + +.td-claim > .mono:first-child { + flex: 1; + min-width: 0; + overflow-wrap: anywhere; +} + +.td-claim-at { + flex: none; +} + +.td-approval { + margin-top: 16px; + padding: 12px; + border: 1px solid var(--accent-line); + background: var(--accent-dim); + border-radius: var(--r-sm); +} + +.td-approval .td-prose { + margin: 4px 0 8px; +} + +.td-approval-row { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + margin-top: 10px; +} + +.td-approval-row .mono { + flex: 1; + min-width: 0; + font-size: 12px; +} + +.td-links { + display: flex; + gap: 10px; + flex-wrap: wrap; + margin: 16px 0 4px; +} + +.td-flow { + display: flex; + flex-direction: column; + gap: 14px; +} + +.td-flow .phases { + overflow-x: auto; + padding-bottom: 4px; +} + +.td-entries { + display: flex; + gap: 8px 20px; + flex-wrap: wrap; + color: var(--text-lo); + font-size: 13px; +} + +.td-entry-phase { + color: var(--text-mid); +} + +/* Actions menu: a native disclosure, not a popover library. */ +.td-menu { + position: relative; +} + +.td-menu > summary { + list-style: none; + cursor: pointer; +} + +.td-menu > summary::-webkit-details-marker { + display: none; +} + +.td-menu-body { + position: absolute; + right: 0; + top: calc(100% + 6px); + z-index: 10; + min-width: 250px; + background: var(--bg-2); + border: 1px solid var(--line-hi); + border-radius: var(--r-md); + box-shadow: var(--shadow-soft); + overflow: hidden; +} + +.td-menu-body .row { + gap: 8px; +} + +.td-menu-body .row:disabled { + cursor: not-allowed; + opacity: 0.55; +} + +.td-menu-body .row:disabled:hover { + background: none; +} + +@media (max-width: 900px) { + .td-columns { + grid-template-columns: 1fr; + } +} diff --git a/web/src/screens/TaskDetail.tsx b/web/src/screens/TaskDetail.tsx new file mode 100644 index 0000000..ea79f59 --- /dev/null +++ b/web/src/screens/TaskDetail.tsx @@ -0,0 +1,454 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { Link, useParams } from 'react-router-dom' +import { api } from '../api/client' +import type { Action, Detail, Event, Task } from '../api/types' +import { Chip, EndpointGap, M, Panel, PhasePath } from '../components/Primitives' +import { Icon } from '../components/Icon' +import './TaskDetail.css' + +/** work_phase is on the wire (domain.Task) but not yet in api/types.ts, which + * another screen owns. Read it through a local widening rather than editing a + * shared file out from under someone. */ +type Phased = Task & { work_phase?: string; last_lease_epoch?: string } + +const time = (iso?: string) => (iso ? new Date(iso).toISOString().slice(11, 19) : '—') + +function ago(iso?: string) { + if (!iso) return '—' + const s = Math.max(0, Math.round((Date.now() - Date.parse(iso)) / 1000)) + if (s < 60) return `${s}s ago` + if (s < 3600) return `${Math.floor(s / 60)}m ago` + return `${Math.floor(s / 3600)}h ago` +} + +/** ponytail: recomputed on each 5s poll rather than ticking per second. The + * operator reads a lease in minutes; a second-accurate countdown would need a + * timer for no decision it changes. */ +function remaining(iso?: string) { + if (!iso) return undefined + const s = Math.round((Date.parse(iso) - Date.now()) / 1000) + if (s <= 0) return 'expired' + return `${String(Math.floor(s / 60)).padStart(2, '0')}:${String(s % 60).padStart(2, '0')}` +} + +type Decision = { + id: string + kind: string + subject: string + value: string + at: string + standing: boolean +} + +/** The standing authority for this task, reduced from its own log. Mirrors + * domain.ReduceIntent: a decision is retired only by being named, never by a + * newer decision on the same subject. Superseded records are kept so the + * screen can show them muted instead of pretending they never existed. */ +function decisionsOf(events: Event[]): Decision[] { + const out: Decision[] = [] + const retired = new Set() + for (const e of events) { + const p = (e.payload ?? {}) as { + decision_id?: string + kind?: string + subject?: string + value?: string + supersedes?: string[] + } + if (!p.decision_id) continue + if (e.type === 'HumanDecisionRecorded') { + out.push({ + id: p.decision_id, + kind: p.kind ?? 'decision', + subject: p.subject ?? '', + value: p.value ?? '', + at: e.at, + standing: true, + }) + p.supersedes?.forEach((id) => retired.add(id)) + } else if (e.type === 'HumanDecisionSuperseded') { + retired.add(p.decision_id) + } + } + return out.map((d) => ({ ...d, standing: !retired.has(d.id) })) +} + +/** When each phase was entered, from the events Orchestra itself appended. + * Verified state, not a claim. */ +function phaseEntries(events: Event[]) { + return events + .filter((e) => e.type === 'WorkPhaseChanged') + .map((e) => ({ phase: (e.payload as { phase?: string })?.phase ?? '', at: e.at })) + .filter((p) => p.phase) +} + +/** The last thing the pane actually showed. Agent-authored text: it is + * rendered as a claim, never as orchestra state. */ +function lastLine(text?: string) { + const lines = (text ?? '').split('\n').map((l) => l.trimEnd()).filter((l) => l.trim()) + return lines[lines.length - 1] +} + +function stateTone(task: Task) { + if (task.state === 'blocked' || task.state === 'failed' || task.state === 'needs_attention') { + return 'fault' as const + } + if (task.state === 'completed') return 'done' as const + if (task.state === 'leased') return 'accent' as const + return undefined +} + +export function TaskDetail() { + const { id = '' } = useParams() + const client = useQueryClient() + const query = useQuery({ + queryKey: ['task', id], + queryFn: () => api.detail(id), + refetchInterval: 5000, + }) + const act = useMutation({ + mutationFn: (action: string) => api.action(id, action), + onSuccess: () => client.invalidateQueries({ queryKey: ['task', id] }), + }) + + if (query.isError) { + return ( +
+
+

Task

+
+ +
+

{String((query.error as Error).message)}

+
+
+
+ ) + } + if (!query.data) { + return ( +
+

reading /v1/ui/tasks/{id}…

+
+ ) + } + + const detail: Detail = query.data + const task = detail.task as Phased + const session = detail.session + const capture = session?.capture + const approval = session?.pending_approval + const decisions = decisionsOf(detail.events) + const standing = decisions.filter((d) => d.standing) + const constraints = standing.filter((d) => d.kind === 'constraint') + const effective = standing.filter((d) => d.kind !== 'constraint') + const superseded = decisions.filter((d) => !d.standing) + const acceptance = task.acceptance ?? [] + const shown = acceptance.slice(0, 4) + const lease = session?.lease_until ?? task.lease?.until + const activity = lastLine(capture?.text) + const entries = phaseEntries(detail.events) + + return ( +
+ {/* ── top area ──────────────────────────────────────────────────── */} +
+
+

{task.title || task.external_id || 'untitled task'}

+ {task.work_phase || 'frame'} phase + {task.state} +
+
+ + +
+
+ +
+ task id + {task.id} + epoch + {task.lease?.epoch || task.last_lease_epoch || '—'} + project + {task.project} + version + {task.version} +
+ + {act.isError &&

{String((act.error as Error).message)}

} + +
+ {/* ── what we're doing ────────────────────────────────────────── */} + +
+

goal

+

{task.description || task.title || 'No goal recorded.'}

+ +

+ acceptance {acceptance.length} +

+ {acceptance.length === 0 ? ( +

No acceptance criteria on the contract.

+ ) : ( + <> +
    + {shown.map((c) => ( +
  • {c}
  • + ))} +
+ {acceptance.length > shown.length && ( +
+ + +{acceptance.length - shown.length} more criteria + +
    + {acceptance.slice(shown.length).map((c) => ( +
  • {c}
  • + ))} +
+
+ )} + + )} + {task.quality_gate && ( +

+ quality gate {task.quality_gate} +

+ )} + + {effective.length > 0 && ( + <> +

effective human decisions

+
    + {effective.map((d) => ( +
  • + {d.kind} + {d.subject} + {d.value} + {time(d.at)} +
  • + ))} +
+ + )} + + {constraints.length > 0 && ( + <> +

active constraints

+
    + {constraints.map((d) => ( +
  • + constraint + {d.subject} + {d.value} + {time(d.at)} +
  • + ))} +
+ + )} + + {superseded.length > 0 && ( +
+ + {superseded.length} superseded {superseded.length === 1 ? 'decision' : 'decisions'} + +
    + {superseded.map((d) => ( +
  • + {d.kind} + {d.subject} + {d.value} + {time(d.at)} +
  • + ))} +
+
+ )} +
+
+ + {/* ── live execution ──────────────────────────────────────────── */} + + {session.agent_status} + + ) : ( + no session + ) + } + > +
+
+
+ worker / harness + {session?.harness_id || task.lease?.harness_id || task.last_harness_id || '—'} +
+
+ pane + {session?.pane_id || task.last_pane_id || '—'} + + {task.pane_state || 'unknown'} + +
+
+ lease remaining + {remaining(lease) ?? '—'} + + until {time(lease)} + +
+
+ last progress + {ago(capture?.at)} + + capture rev {capture?.revision ?? '—'} + +
+
+ + {/* Context occupancy is the one number this panel is supposed to + carry and the read model does not produce it. Say so rather + than compute a plausible-looking figure. */} + + +

+ current activity + + agent-supplied · pane capture{capture?.source ? ` · ${capture.source}` : ''} + +

+ {activity ? ( +

+ {activity} + + {time(capture?.at)} + +

+ ) : ( +

No capture from the owning worker.

+ )} + + {approval && ( +
+ pending approval +

{approval.summary}

+ {approval.command && {approval.command}} +
+ + {approval.pane_id} · rev {approval.capture_revision} + + + +
+
+ )} + +
+ + + Open live pane + + {task.handoff_ref && ( + + + Handoff + + )} + {detail.report_ref && ( + + + Report + + )} +
+ +
+
+
+ + {/* ── workflow ──────────────────────────────────────────────────── */} + ace-fca}> +
+ + {entries.length > 0 ? ( +
+ {entries.map((e) => ( + + {e.phase} entered {time(e.at)} + + ))} +
+ ) : ( +

+ No WorkPhaseChanged event in this task's log — the path shows the current phase only. +

+ )} +
+
+
+ ) +} + +/** Operator actions, exactly as the server declares them. An action that + * needs input the console cannot collect stays disabled and says what it + * wants — offering a button that will 409 is the dishonest option. */ +function ActionsMenu({ + actions, + pending, + run, +}: { + actions: Action[] + pending: boolean + run: (action: string) => void +}) { + return ( +
+ + Actions + +
+ {actions.map((a) => { + const blocked = !a.enabled || (a.needs?.length ?? 0) > 0 + return ( + + ) + })} +
+
+ ) +} diff --git a/web/src/screens/Tasks.css b/web/src/screens/Tasks.css new file mode 100644 index 0000000..09b1b3f --- /dev/null +++ b/web/src/screens/Tasks.css @@ -0,0 +1,202 @@ +/* Tasks list. Only what the shared shell does not already carry: the row + column geometry, the group disclosure, and the filter bar. */ + +.tasks-controls { + display: flex; + align-items: center; + gap: 16px; + flex-wrap: wrap; +} + +.tasks-tabs { + display: flex; + gap: 2px; + flex-wrap: wrap; +} + +.tasks-tab { + background: none; + border: 0; + border-bottom: 2px solid transparent; + padding: 7px 11px; + color: var(--text-mid); + cursor: pointer; + transition: color var(--fast) var(--ease), border-color var(--fast) var(--ease); +} + +.tasks-tab span { + font-size: 12px; + margin-left: 5px; +} + +.tasks-tab:hover { + color: var(--text-hi); +} + +.tasks-tab[aria-selected='true'] { + color: var(--text-hi); + border-bottom-color: var(--accent); +} + +.tasks-filters { + display: flex; + gap: 10px; + margin-left: auto; + flex-wrap: wrap; +} + +.tasks-filter { + display: flex; + align-items: center; + gap: 7px; +} + +.tasks-filter select { + font-family: var(--mono); + font-size: 12px; + color: var(--text-machine); + background: var(--bg-2); + border: 1px solid var(--line); + border-radius: var(--r-sm); + padding: 5px 8px; + cursor: pointer; +} + +.tasks-filter select:hover { + border-color: var(--line-hi); +} + +.page-head .count { + font-size: 12px; + padding: 2px 8px; + border-radius: 999px; + background: var(--bg-2); + border: 1px solid var(--line); +} + +/* ── group disclosure ────────────────────────────────────────────────── */ + +.tasks-group > summary { + cursor: pointer; + list-style: none; +} + +.tasks-group > summary::-webkit-details-marker { + display: none; +} + +.tasks-group:not([open]) > summary { + border-bottom: 0; +} + +.tasks-group > summary > svg:first-child { + color: var(--text-lo); +} + +.tasks-group > summary > svg:last-child { + margin-left: auto; + color: var(--text-lo); + transform: rotate(90deg); + transition: transform var(--fast) var(--ease); +} + +.tasks-group[open] > summary > svg:last-child { + transform: rotate(-90deg); +} + +/* ── row geometry ────────────────────────────────────────────────────── */ + +.task-row { + display: flex; + align-items: center; + gap: 18px; + color: inherit; +} + +.task-row > svg:last-child { + color: var(--text-lo); + flex: none; +} + +.task-row-main { + flex: 1 1 220px; + min-width: 0; + display: flex; + flex-direction: column; +} + +.task-row-main .row-title, +.task-row-main .row-sub { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.task-row-phase { + flex: none; + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; +} + +.task-row-phase-name { + font-size: 10px; + letter-spacing: 0.04em; +} + +/* The compact path carries no labels, so the connector no longer needs to + sit above a name. */ +.tasks-phases .phase-step { + min-width: 0; +} + +.tasks-phases .phase-link { + width: 14px; + margin-bottom: 0; +} + +.task-row-worker { + flex: none; + width: 150px; + display: flex; + flex-direction: column; + font-size: 13px; +} + +.task-row-when { + flex: none; + width: 118px; + display: flex; + flex-direction: column; + font-size: 12px; +} + +.task-row-status { + flex: none; + display: flex; + gap: 6px; + justify-content: flex-end; + min-width: 130px; +} + +@media (max-width: 900px) { + .task-row-phase, + .task-row-when { + display: none; + } + + .task-row-worker { + width: 110px; + } +} + +@media (max-width: 640px) { + .task-row-worker { + display: none; + } + + .tasks-filters { + margin-left: 0; + } +} diff --git a/web/src/screens/Tasks.tsx b/web/src/screens/Tasks.tsx new file mode 100644 index 0000000..45fadef --- /dev/null +++ b/web/src/screens/Tasks.tsx @@ -0,0 +1,329 @@ +import { useMemo, useState } from 'react' +import { Link } from 'react-router-dom' +import { useQuery } from '@tanstack/react-query' +import { api } from '../api/client' +import type { Overview, Task } from '../api/types' +import { Chip, Empty, EndpointGap, M, PHASES, PhasePath } from '../components/Primitives' +import { Icon } from '../components/Icon' +import './Tasks.css' + +type GroupKey = 'attention' | 'running' | 'waiting' | 'completed' | 'failed' + +const GROUPS: { key: GroupKey; title: string; icon: string; open: boolean }[] = [ + { key: 'attention', title: 'Needs attention', icon: 'alert', open: true }, + { key: 'running', title: 'Running', icon: 'play', open: true }, + { key: 'waiting', title: 'Waiting / queued', icon: 'queue', open: true }, + { key: 'completed', title: 'Completed', icon: 'check', open: false }, + { key: 'failed', title: 'Failed', icon: 'x', open: false }, +] + +function groupOf(task: Task): GroupKey { + switch (task.state) { + case 'needs_attention': + case 'blocked': + return 'attention' + case 'leased': + return 'running' + case 'completed': + return 'completed' + case 'failed': + return 'failed' + default: + return 'waiting' + } +} + +/** The harness holding the lease, or the last one that did. Nothing is + * invented: a task that never ran reports no worker. */ +function harnessOf(task: Task) { + return task.lease?.harness_id || task.last_harness_id || '' +} + +function stateTone(task: Task) { + if (task.state === 'failed' || task.state === 'blocked') return 'fault' as const + if (task.state === 'needs_attention') return 'warn' as const + if (task.state === 'completed') return 'done' as const + if (task.state === 'leased') return 'accent' as const + return undefined +} + +function ago(at?: string) { + if (!at) return '' + const ms = Date.now() - Date.parse(at) + if (!Number.isFinite(ms)) return '' + return `${span(ms)} ago` +} + +function span(ms: number) { + const s = Math.max(0, Math.round(ms / 1000)) + if (s < 60) return `${s}s` + if (s < 3600) return `${Math.round(s / 60)}m` + if (s < 86400) return `${Math.round(s / 3600)}h` + return `${Math.round(s / 86400)}d` +} + +/** The one time value a task actually carries per state. There is no + * per-task updated_at on the overview, so this never claims one. */ +function timing(task: Task): { label: string; value: string } | undefined { + if (task.lease?.until) { + const left = Date.parse(task.lease.until) - Date.now() + return left > 0 + ? { label: 'lease', value: `${span(left)} left` } + : { label: 'lease', value: `expired ${span(-left)} ago` } + } + if (task.blocked_at) return { label: 'blocked', value: ago(task.blocked_at) } + if (task.next_retry_at) { + const left = Date.parse(task.next_retry_at) - Date.now() + return { label: 'retry', value: left > 0 ? `in ${span(left)}` : 'due' } + } + if (task.due) return { label: 'due', value: task.due.slice(0, 10) } + return undefined +} + +function unique(values: (string | undefined)[]) { + return [...new Set(values.filter((value): value is string => !!value))].sort() +} + +function Select({ + label, + value, + options, + onChange, +}: { + label: string + value: string + options: string[] + onChange: (next: string) => void +}) { + return ( + + ) +} + +function Row({ task, agentStatus }: { task: Task; agentStatus?: string }) { + const harness = harnessOf(task) + const when = timing(task) + return ( + + + {task.title || task.external_id || 'untitled task'} + + {task.project} · {task.external_id || task.id} + + + + + + {task.lifecycle_phase || '—'} + + + + {harness ? ( + <> + {harness} + {task.last_pane_id && {task.last_pane_id}} + + ) : ( + unassigned + )} + + + + {when && ( + <> + {when.label} + {when.value} + + )} + + + + {agentStatus && {agentStatus}} + {task.block_reason || task.state} + + + + + ) +} + +function Group({ + title, + icon, + open, + tasks, + statusFor, +}: { + title: string + icon: string + open: boolean + tasks: Task[] + statusFor: (task: Task) => string | undefined +}) { + return ( +
0}> + + +

{title}

+ {tasks.length} + +
+ {tasks.length ? ( +
+ {tasks.map((task) => ( + + ))} +
+ ) : ( + + )} +
+ ) +} + +export function Tasks() { + const { data, isLoading, error } = useQuery({ + queryKey: ['overview'], + queryFn: api.overview, + refetchInterval: 5000, + }) + + const [project, setProject] = useState('') + const [phase, setPhase] = useState('') + const [worker, setWorker] = useState('') + const [attention, setAttention] = useState('') + const [group, setGroup] = useState('') + + const tasks = useMemo(() => data?.tasks ?? [], [data]) + + /** Agent status comes from the live session, matched by pane. It is an + * agent claim, so it stays a separate chip from orchestra's own state. */ + const statusFor = useMemo(() => { + const byPane = new Map((data?.sessions ?? []).map((s) => [s.pane_id, s])) + return (task: Task) => + task.state === 'leased' ? byPane.get(task.last_pane_id)?.agent_status : undefined + }, [data]) + + const filtered = tasks.filter((task) => { + if (project && task.project !== project) return false + if (phase && task.lifecycle_phase !== phase) return false + if (worker && harnessOf(task) !== worker) return false + if (attention === 'needs' && groupOf(task) !== 'attention') return false + if (attention === 'clear' && groupOf(task) === 'attention') return false + if (attention && attention !== 'needs' && attention !== 'clear' && task.block_reason !== attention) + return false + return true + }) + + const counts = (key: GroupKey) => filtered.filter((task) => groupOf(task) === key).length + const shown = GROUPS.filter((g) => !group || g.key === group) + + return ( +
+
+

Tasks

+

All work under orchestration.

+ {tasks.length} +
+ +
+
+ + {GROUPS.map((g) => ( + + ))} +
+
+ + task.block_reason))]} + onChange={setAttention} + /> +
+
+ + {error && ( +
+

/v1/ui/overview failed: {(error as Error).message}

+
+ )} + + {isLoading && !data && ( +
+

loading /v1/ui/overview…

+
+ )} + + {data && !tasks.length && ( +
+ +

Orchestra has no work on record. Ingest a task to start.

+
+
+ )} + + {data && + tasks.length > 0 && + shown.map((g) => ( + groupOf(task) === g.key)} + statusFor={statusFor} + /> + ))} + + {data && tasks.length > 0 && ( +
+ +
+ )} +
+ ) +} diff --git a/web/src/screens/Terminal.css b/web/src/screens/Terminal.css new file mode 100644 index 0000000..1619076 --- /dev/null +++ b/web/src/screens/Terminal.css @@ -0,0 +1,192 @@ +/* The live pane. Tokens only — nothing here redefines the ethos ramp. */ + +.term { + min-height: 0; +} + +.term-head { + align-items: center; +} + +.term-identity { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 12px; +} + +.term-sep { + color: var(--text-lo); +} + +.term-controls { + margin-left: auto; + display: flex; + align-items: center; + gap: 8px; +} + +.term-mode { + gap: 6px; +} + +.term-layout { + display: grid; + grid-template-columns: minmax(0, 1fr) 320px; + gap: 16px; + align-items: start; +} + +/* The frame. Depth from a hairline and a surface step, never from glass. */ +.term-frame { + background: var(--bg-0); + border: 1px solid var(--line-hi); + border-radius: var(--r-md); + overflow: hidden; + min-width: 0; +} + +.term-frame[data-lost='true'] { + border-color: rgba(224, 108, 96, 0.42); +} + +.term-bar { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 12px; + background: var(--bg-2); + border-bottom: 1px solid var(--line); + font-size: 11px; +} + +.term-bar-right { + margin-left: auto; + display: flex; + gap: 14px; +} + +/* Exact pane characters: no wrapping, no reflow, no prettifying. */ +.term-text { + margin: 0; + padding: 14px 16px; + height: min(62vh, 720px); + overflow: auto; + white-space: pre; + tab-size: 8; + font-size: 13px; + line-height: 1.35; + color: #cfc6b4; + background: var(--bg-0); +} + +.term-text:focus-visible { + outline-offset: -2px; +} + +.term-foot { + display: flex; + align-items: center; + gap: 12px; + min-height: 0; + padding: 0 12px; +} + +.term-foot:not(:empty) { + padding: 8px 12px; + border-top: 1px solid var(--line); + background: var(--bg-1); +} + +.term-jump { + height: 28px; + font-size: 12px; +} + +.term-warn { + font-size: 12px; + color: #e06c60; +} + +.term-side { + display: flex; + flex-direction: column; + gap: 14px; + min-width: 0; +} + +.term-fields, +.term-lost { + display: flex; + flex-direction: column; + gap: 10px; +} + +.term-lost p { + margin: 0 0 2px; + color: var(--text-mid); +} + +.term-field { + display: flex; + flex-direction: column; + gap: 2px; +} + +.term-field-value { + font-size: 12px; + color: var(--text-mid); + overflow-wrap: anywhere; +} + +.term-health { + display: inline-flex; + align-items: center; + gap: 7px; +} + +.term-link:hover { + color: var(--accent-hi); +} + +.term-resubmit p { + margin: 8px 0 0; + font-size: 12px; + color: var(--text-mid); +} + +.term-events { + display: flex; + flex-direction: column; + gap: 7px; +} + +.term-event { + display: flex; + gap: 10px; + font-size: 12px; + color: var(--text-mid); +} + +.term-muted { + margin: 0; + color: var(--text-lo); +} + +@media (max-width: 1040px) { + .term-layout { + grid-template-columns: minmax(0, 1fr); + } +} + +@media (max-width: 640px) { + .term-controls { + margin-left: 0; + width: 100%; + } + + .term-text { + height: 54vh; + font-size: 12px; + } +} diff --git a/web/src/screens/Terminal.tsx b/web/src/screens/Terminal.tsx new file mode 100644 index 0000000..87c8864 --- /dev/null +++ b/web/src/screens/Terminal.tsx @@ -0,0 +1,320 @@ +import { useEffect, useRef, useState } from 'react' +import { Link, useParams } from 'react-router-dom' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { api } from '../api/client' +import type { Capture, Detail } from '../api/types' +import { Chip, Dot, Empty, EndpointGap, M, Panel } from '../components/Primitives' +import { Icon } from '../components/Icon' +import './Terminal.css' + +/** This is the screen that must not lie. Everything inside the frame is the + * pane's own bytes; everything outside it is state Orchestra can prove. */ + +function ago(at?: string) { + if (!at) return '—' + const seconds = Math.max(0, Math.round((Date.now() - new Date(at).getTime()) / 1000)) + if (seconds < 60) return `${seconds}s ago` + if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago` + return `${Math.floor(seconds / 3600)}h ago` +} + +function clock(at?: string) { + return at ? new Date(at).toISOString().slice(11, 19) : '—' +} + +function until(at?: string) { + if (!at) return '—' + const seconds = Math.round((new Date(at).getTime() - Date.now()) / 1000) + if (seconds <= 0) return 'expired' + const m = Math.floor(seconds / 60) + return `${String(m).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}` +} + +function Field({ label, value }: { label: string; value: React.ReactNode }) { + return ( +
+ {label} + {value} +
+ ) +} + +export function Terminal() { + const { id = '' } = useParams() + const queries = useQueryClient() + const detail = useQuery({ + queryKey: ['detail', id], + queryFn: () => api.detail(id), + refetchInterval: 3000, + }) + + // The last frame we actually received, kept across refreshes so a lost pane + // still shows what was true when it was lost instead of an empty box. + const [frame, setFrame] = useState() + const [lostAt, setLostAt] = useState() + const [copied, setCopied] = useState(false) + const [pinned, setPinned] = useState(true) + const view = useRef(null) + + const task = detail.data?.task + const session = detail.data?.session + const capture = session?.capture + + useEffect(() => { + if (capture) { + setFrame(capture) + setLostAt(undefined) + return + } + // A leased task with no capture is a lost pane, not an empty one. + if (detail.isSuccess && task?.state === 'leased') { + setLostAt((was) => was ?? new Date().toISOString()) + } + }, [capture, detail.isSuccess, task?.state]) + + // Scrollback inspection must not disturb the live view: we only follow the + // tail while the operator is already sitting at the tail. + useEffect(() => { + const el = view.current + if (el && pinned) el.scrollTop = el.scrollHeight + }, [frame?.revision, pinned]) + + const resubmit = useMutation({ + mutationFn: () => api.action(id, 'resubmit'), + onSuccess: (next) => queries.setQueryData(['detail', id], next), + }) + + const canResubmit = detail.data?.actions.find((a) => a.id === 'resubmit')?.enabled ?? false + const lost = Boolean(lostAt) && Boolean(frame) + const status = session?.agent_status || task?.pane_state || 'unknown' + const health = lost ? 'error' : status === 'blocked' ? 'degraded' : status ? 'healthy' : 'unknown' + + if (detail.isLoading) { + return ( +
+
+

Terminal

+
+ +
Reading the pane…
+
+
+ ) + } + + if (detail.isError || !task) { + return ( +
+
+

Terminal

+
+ + +

+ {id} is not in the store, so there is no pane to show. +

+
+
+
+ ) + } + + return ( +
+
+

Terminal — live pane

+ {lost ? Pane lost : capture ? Live : No session} + + {session?.harness_id || task.last_harness_id || 'no worker'} + / + {session?.pane_id || task.last_pane_id || 'no pane'} + +
+ + Read-only + + + +
+
+ +
+
+
+ {session?.pane_id || task.last_pane_id || '—'} + + rev {frame ? frame.revision : '—'} + {frame?.source || '—'} + {clock(frame?.at)} + +
+ {frame ? ( +
 {
+                const el = event.currentTarget
+                setPinned(el.scrollHeight - el.scrollTop - el.clientHeight < 24)
+              }}
+            >
+              {frame.text}
+            
+ ) : ( + +

+ This task holds no leased session, so no worker is publishing pane text for{' '} + {task.id}. +

+
+ )} +
+ {frame?.truncated && frame truncated by the worker} + {!pinned && ( + + )} +
+
+ +
+ {lost && ( + +
+

+ The frame above is the last one Orchestra received. It is history, not the + present. +

+ {clock(frame?.at)} UTC} /> + {clock(lostAt)} UTC} /> + + the lease still runs until {clock(task.lease.until)} UTC; nothing is + rerouted before it expires + + ) : ( + no lease is held, so this task can be routed again immediately + ) + } + /> +
+
+ )} + + +
+ {task.id}} /> + {session?.harness_id || task.last_harness_id || '—'}} /> + {session?.pane_id || task.last_pane_id || '—'}} /> + {task.lease?.epoch || '—'}} /> + + {until(task.lease.until)} left · until {clock(task.lease.until)} UTC + + ) : ( + none + ) + } + /> + {frame?.source || '—'}} /> + {frame ? frame.revision : '—'}} /> + {ago(frame?.at)}} /> + read-only} /> +
+
+ + +
+ + + {status} + + } + /> + {ago(task.last_session?.checked_at || frame?.at)}} /> + {(session?.blocker || task.blocker) && ( + {session?.blocker || task.blocker}} /> + )} +
+
+ + + +
+ +

+ Presses Enter once on input Orchestra placed in this pane. It sends no other key and + changes no lifecycle state. +

+ {resubmit.isError && ( +

{(resubmit.error as Error).message}

+ )} +
+
+ + +
+ {(detail.data?.events ?? []).slice(-5).reverse().map((event) => ( +
+ {clock(event.at)} + {event.type} +
+ ))} + {!detail.data?.events.length &&

No events recorded yet.

} +
+
+
+
+
+ ) +} diff --git a/web/src/screens/Workers.css b/web/src/screens/Workers.css new file mode 100644 index 0000000..9bec391 --- /dev/null +++ b/web/src/screens/Workers.css @@ -0,0 +1,198 @@ +/* Workers screen. Only what the shared sheet does not already carry: + the two-column split, the label/value field row, and the observations ring. */ + +.workers-split { + display: grid; + grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); + gap: 14px; + align-items: start; +} + +.workers-detail { + min-width: 0; +} + +.workers .worker-row { + cursor: pointer; +} + +.workers .worker-row[aria-selected='true'] { + background: var(--bg-2); + box-shadow: inset 3px 0 0 var(--accent); +} + +.workers .status-cell { + display: inline-flex; + align-items: center; + gap: 8px; + white-space: nowrap; +} + +.workers .worker-id { + font-size: 15px; + color: var(--accent-hi); +} + +.workers .dim { + color: var(--text-lo); +} + +.workers .task-link { + display: inline-flex; + align-items: baseline; + gap: 8px; + min-width: 0; +} + +.workers .task-link .mono { + color: var(--accent-hi); +} + +.workers .task-link:hover .mono { + text-decoration: underline; +} + +.workers .task-title { + color: var(--text-mid); + font-size: 13px; +} + +/* A human label, a machine value, on one line. Law 1 drawn as geometry. */ +.workers .field { + display: flex; + gap: 16px; + align-items: baseline; + padding: 5px 0; +} + +.workers .field-name { + flex: none; + width: 150px; + color: var(--text-mid); + font-size: 13px; +} + +.workers .field-value { + min-width: 0; + display: flex; + align-items: baseline; + gap: 8px; + flex-wrap: wrap; +} + +.workers .chips { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-bottom: 12px; +} + +.workers .steps { + margin: 0 0 12px; + padding-left: 20px; + color: var(--text-mid); + display: flex; + flex-direction: column; + gap: 4px; +} + +.workers .reg-lead { + display: flex; + align-items: center; + gap: 8px; + margin: 0 0 10px; + color: var(--text-hi); +} + +.workers .reg-lead svg { + color: var(--accent); +} + +/* ── the observations ring ─────────────────────────────────────────── + One distinct failure per row, with its repeat count kept whole. A count + of 41 means something looped 41 times; that number is the finding, so it + gets weight instead of a parenthesis. */ + +.workers .obs-latest { + margin: 0 0 12px; + border-style: solid; + border-color: var(--line-hi); + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +.workers .obs { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 8px; +} + +.workers .obs-item { + background: var(--bg-2); + border: 1px solid var(--line); + border-radius: var(--r-sm); + padding: 10px 12px; +} + +.workers .obs-head { + display: flex; + gap: 10px; + align-items: baseline; +} + +.workers .obs-count { + flex: none; + font-size: 15px; + color: var(--text-hi); + min-width: 42px; + text-align: right; +} + +/* Repeats are the signal that something is stuck rather than merely broken. */ +.workers .obs-count[data-repeat='true'] { + color: var(--accent-hi); +} + +.workers .obs-msg { + min-width: 0; + color: var(--text-machine); + font-size: 12.5px; + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +.workers .obs-when { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 8px; + margin-top: 8px; + padding-left: 52px; + font-size: 12px; +} + +.workers .obs-when .mono { + color: var(--text-lo); +} + +@media (max-width: 1100px) { + .workers-split { + grid-template-columns: 1fr; + } + + .workers .obs-when { + padding-left: 0; + } + + .workers .field { + flex-direction: column; + gap: 2px; + } + + .workers .field-name { + width: auto; + } +} diff --git a/web/src/screens/Workers.tsx b/web/src/screens/Workers.tsx new file mode 100644 index 0000000..989bd56 --- /dev/null +++ b/web/src/screens/Workers.tsx @@ -0,0 +1,362 @@ +import { useState } from 'react' +import { Link } from 'react-router-dom' +import { useQuery } from '@tanstack/react-query' +import { api } from '../api/client' +import type { Task, Worker, WorkerHealth } from '../api/types' +import { Chip, Dot, Empty, EndpointGap, M, Panel } from '../components/Primitives' +import type { Health } from '../components/Primitives' +import { Icon } from '../components/Icon' +import './Workers.css' + +/** The coordinator's Go structs carry `observations` on WorkerHealth; the + * shared type file has not caught up and is owned elsewhere, so the shape is + * narrowed here rather than guessed at the call site. */ +interface Observation { + message: string + count: number + first: string + last: string +} +type Health2 = WorkerHealth & { observations?: Observation[] } + +/** Go marshals a zero time.Time as year 1 — `omitempty` does not omit a + * struct. Treating that as a real timestamp would print "2025y ago". */ +function absent(iso?: string) { + return !iso || iso.startsWith('0001-') +} + +function ago(iso?: string) { + if (absent(iso)) return '—' + const s = Math.max(0, (Date.now() - new Date(iso as string).getTime()) / 1000) + if (s < 60) return `${Math.floor(s)}s ago` + if (s < 3600) return `${Math.floor(s / 60)}m ago` + if (s < 86400) return `${Math.floor(s / 3600)}h ago` + return `${Math.floor(s / 86400)}d ago` +} + +function clock(iso?: string) { + if (absent(iso)) return '—' + return new Date(iso as string).toISOString().slice(11, 19) + ' UTC' +} + +function health(w: Worker): Health { + if (!w.online) return 'unknown' + if (w.health.herdr_status === 'unreachable') return 'error' + if (w.health.herdr_status === 'unknown') return 'unknown' + return w.health.last_error ? 'degraded' : 'healthy' +} + +function statusLabel(w: Worker) { + if (!w.online) return 'Offline' + if (w.health.herdr_status === 'unreachable') return 'Backend unreachable' + if (w.health.active_task_id) return 'Busy' + return w.health.last_error ? 'Degraded' : 'Idle' +} + +/** The ring is the evidence. A message with a count of 41 means a loop ran 41 + * times, and that number is the whole point — it is never collapsed away. */ +function Observations({ items }: { items: Observation[] }) { + return ( +
    + {items.map((o) => ( +
  • +
    + 1} title={`${o.count} occurrences`}> + ×{o.count} + + {o.message} +
    +
    + first + {clock(o.first)} + last + {clock(o.last)} + ({ago(o.last)}) +
    +
  • + ))} +
+ ) +} + +function Field({ name, children }: { name: string; children: React.ReactNode }) { + return ( +
+ {name} + {children} +
+ ) +} + +function Detail({ worker, tasks }: { worker: Worker; tasks: Task[] }) { + const h = worker.health as Health2 + const obs = h.observations ?? [] + const task = tasks.find((t) => t.id === h.active_task_id) + + return ( +
+ +
+

{worker.id}

+ + {statusLabel(worker)} + +
+
+ + {h.backend || 'unknown'} + + + {h.herdr_status} + + + {clock(h.checked_at)} ({ago(h.checked_at)}) + + + {clock(worker.last_seen)}{' '} + ({ago(worker.last_seen)}) + + + {worker.capacity} + + {worker.address && ( + + {worker.address} + + )} +
+
+ + +
+ {h.active_task_id ? ( + <> + + + {h.active_task_id} + {task?.title && {task.title}} + + + {task?.lifecycle_phase && ( + + {task.lifecycle_phase} + + )} + + {h.active_pane_id || '—'} + + + ) : ( +

No task leased to this worker.

+ )} +
+
+ + +
+ + {worker.build?.revision || 'unknown'} + + + {worker.build?.time || 'unknown'} + + {worker.build?.dirty === 'true' && ( + + dirty — built from uncommitted changes + + )} +
+
+ + reported by worker}> +
+ {worker.supported_projects?.length ? ( +
+ {worker.supported_projects.map((p) => ( + + {p} + + ))} +
+ ) : ( +

The worker declared no projects, so nothing routes to it.

+ )} + +
+
+ + bounded ring of distinct failures} + > + {obs.length ? ( +
+ {h.last_error && ( +

+ latest: {h.last_error} + {!absent(h.error_at) && ` — ${clock(h.error_at)}`} +

+ )} + +
+ ) : ( + +

The worker has reported nothing to explain. Repeats would collapse here with a count.

+
+ )} +
+
+ ) +} + +export function Workers() { + const workers = useQuery({ queryKey: ['workers'], queryFn: api.workers, refetchInterval: 5000 }) + const overview = useQuery({ queryKey: ['overview'], queryFn: api.overview, refetchInterval: 5000 }) + const [picked, setPicked] = useState() + + const list = workers.data ?? [] + const selected = list.find((w) => w.id === picked) ?? list[0] + const online = list.filter((w) => w.online) + const busy = online.filter((w) => w.health.active_task_id) + const faulted = list.filter((w) => health(w) === 'error' || health(w) === 'degraded') + const capacity = online.reduce((n, w) => n + (w.capacity || 0), 0) + + return ( +
+
+
+

Workers

+

Where work can run, and whether that machinery is usable.

+
+
+ + {workers.isError ? ( + + +

GET /v1/federation/workers failed. Nothing can be leased until it answers.

+
+
+ ) : ( + <> +
+
+ Online + {online.length} +
+
+ Busy + {busy.length} +
+
+ Offline + {list.length - online.length} +
+
+ Faulted + {faulted.length} +
+
+ Free capacity + {capacity - busy.length} +
+
+ +
+ + {list.length === 0 ? ( + +

+ A worker registers itself: it starts with coordinator credentials, announces its + id, revision, harnesses, projects and capacity, then keeps liveness with a + heartbeat. Nothing here can create one. +

+
+ ) : ( +
+ + + + + + + + + + + + + {list.map((w) => { + const h = w.health as Health2 + return ( + setPicked(w.id)} + > + + + + + + + + ) + })} + +
WorkerStatusBackendCurrent taskPaneLast seen
{w.id} + + {statusLabel(w)} + + + + {h.backend || 'unknown'} + + + {h.active_task_id ? ( + e.stopPropagation()} + > + {h.active_task_id} + + ) : ( + + )} + {h.active_pane_id || '—'}{ago(w.last_seen)}
+
+ )} +
+ +
+ {selected && } +
+
+ + +
+

+ Workers register themselves; the console only reads + what they announce. +

+
    +
  1. The worker starts with coordinator credentials.
  2. +
  3. It announces id, revision, harnesses, projects and capacity.
  4. +
  5. The coordinator records it; heartbeats keep liveness.
  6. +
+ +
+
+ + )} +
+ ) +} diff --git a/web/src/shell/CommandPalette.tsx b/web/src/shell/CommandPalette.tsx new file mode 100644 index 0000000..9eae9c2 --- /dev/null +++ b/web/src/shell/CommandPalette.tsx @@ -0,0 +1,62 @@ +import { useEffect, useMemo, useRef, useState } from 'react' +import { NAV } from './Shell' + +/** The shared command surface. It navigates and nothing else: an operator + * console should not hide state changes behind a search box. */ +export function CommandPalette({ + onClose, + onNavigate, +}: { + onClose: () => void + onNavigate: (to: string) => void +}) { + const [query, setQuery] = useState('') + const [active, setActive] = useState(0) + const input = useRef(null) + + useEffect(() => input.current?.focus(), []) + + const matches = useMemo(() => { + const needle = query.trim().toLowerCase() + return NAV.filter((item) => !needle || item.label.toLowerCase().includes(needle)) + }, [query]) + + useEffect(() => setActive(0), [query]) + + return ( +
+
event.stopPropagation()}> + setQuery(event.target.value)} + onKeyDown={(event) => { + if (event.key === 'ArrowDown') setActive((i) => Math.min(i + 1, matches.length - 1)) + if (event.key === 'ArrowUp') setActive((i) => Math.max(i - 1, 0)) + if (event.key === 'Enter' && matches[active]) onNavigate(matches[active].to) + }} + /> +
+ {matches.map((item, i) => ( + + ))} + {matches.length === 0 && ( +

+ Nothing matches that. +

+ )} +
+
+
+ ) +} diff --git a/web/src/shell/Shell.tsx b/web/src/shell/Shell.tsx new file mode 100644 index 0000000..72ab172 --- /dev/null +++ b/web/src/shell/Shell.tsx @@ -0,0 +1,94 @@ +import { useEffect, useState } from 'react' +import { NavLink, Outlet, useNavigate } from 'react-router-dom' +import { Icon } from '../components/Icon' +import { CommandPalette } from './CommandPalette' + +export const NAV = [ + { to: '/', icon: 'home', label: 'Dashboard', end: true }, + { to: '/tasks', icon: 'task', label: 'Tasks' }, + { to: '/decisions', icon: 'decision', label: 'Decisions' }, + { to: '/workers', icon: 'worker', label: 'Workers' }, + { to: '/projects', icon: 'project', label: 'Projects' }, + { to: '/review', icon: 'review', label: 'Review' }, + { to: '/settings', icon: 'settings', label: 'Settings', secondary: true }, +] + +/** Machine readouts belong in the top bar on desktop. On mobile they relocate + * into the detail screens rather than disappearing: honesty moves, it is + * never deleted. */ +function Clock() { + const [now, setNow] = useState(() => new Date()) + useEffect(() => { + const id = setInterval(() => setNow(new Date()), 1000) + return () => clearInterval(id) + }, []) + return {now.toISOString().slice(11, 19)} UTC +} + +export function Shell({ revision, live }: { revision?: string; live?: boolean }) { + const [palette, setPalette] = useState(false) + const navigate = useNavigate() + + useEffect(() => { + const onShortcut = (event: KeyboardEvent) => { + if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') { + event.preventDefault() + setPalette((open) => !open) + } + if (event.key === 'Escape') setPalette(false) + } + window.addEventListener('keydown', onShortcut) + return () => window.removeEventListener('keydown', onShortcut) + }, []) + + return ( +
+ +
+
+ orchestra + +
+ {revision && {revision.slice(0, 7)}} + + {' '} + {live ? 'Live' : 'Unknown'} + + +
+
+ +
+ {palette && ( + setPalette(false)} + onNavigate={(to) => { + setPalette(false) + navigate(to) + }} + /> + )} +
+ ) +} diff --git a/web/src/style.css b/web/src/style.css index 48e9eae..798176d 100644 --- a/web/src/style.css +++ b/web/src/style.css @@ -1,4 +1,4 @@ -:root { +.legacy { color-scheme: dark; font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: #eef2f0; @@ -54,7 +54,7 @@ body { var(--bg); } -body.modal-open { +.legacy body.modal-open { overflow: hidden; } @@ -62,12 +62,12 @@ button, input, textarea, select, -summary { +.legacy summary { font: inherit; } button, -.button-link { +.legacy .button-link { display: inline-flex; min-height: 38px; align-items: center; @@ -86,18 +86,18 @@ button, } button:hover:not(:disabled), -.button-link:hover { +.legacy .button-link:hover { border-color: var(--accent-strong); background: var(--accent-strong); transform: translateY(-1px); } button:active:not(:disabled), -.button-link:active { +.legacy .button-link:active { transform: translateY(0); } -button:disabled { +.legacy button:disabled { opacity: 0.42; cursor: not-allowed; } @@ -107,18 +107,18 @@ input:focus-visible, textarea:focus-visible, select:focus-visible, summary:focus-visible, -a:focus-visible { +.legacy a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } -a { +.legacy a { color: inherit; } input, textarea, -select { +.legacy select { width: 100%; border: 1px solid var(--line-strong); border-radius: var(--radius-sm); @@ -129,12 +129,12 @@ select { } input, -select { +.legacy select { min-height: 42px; padding: 9px 11px; } -textarea { +.legacy textarea { min-height: 116px; padding: 11px; line-height: 1.55; @@ -143,24 +143,24 @@ textarea { input:hover, textarea:hover, -select:hover { +.legacy select:hover { border-color: rgba(221, 236, 231, 0.23); } input:focus, textarea:focus, -select:focus { +.legacy select:focus { border-color: var(--accent-line); background: #0f1619; box-shadow: 0 0 0 3px rgba(86, 200, 139, 0.08); } input::placeholder, -textarea::placeholder { +.legacy textarea::placeholder { color: #53615e; } -select { +.legacy select { color: var(--text-soft); cursor: pointer; } @@ -171,17 +171,17 @@ kbd, .task-short-id, pre, code, -time { +.legacy time { font-family: var(--mono); font-feature-settings: "tnum" 1, "zero" 1; } -.icon { +.legacy .icon { display: block; flex: none; } -.sr-only { +.legacy .sr-only { position: absolute; width: 1px; height: 1px; @@ -195,12 +195,12 @@ time { /* Shell */ -.app-shell { +.legacy .app-shell { min-height: 100vh; padding-left: 224px; } -.sidebar { +.legacy .sidebar { position: fixed; inset: 0 auto 0 0; z-index: 20; @@ -213,7 +213,7 @@ time { backdrop-filter: blur(18px); } -.brand { +.legacy .brand { display: flex; height: 64px; align-items: center; @@ -224,7 +224,7 @@ time { text-decoration: none; } -.logo-symbol { +.legacy .logo-symbol { position: relative; display: block; width: 25px; @@ -232,7 +232,7 @@ time { flex: none; } -.logo-symbol i { +.legacy .logo-symbol i { position: absolute; display: block; width: 7px; @@ -242,23 +242,23 @@ time { box-shadow: 0 0 13px rgba(142, 224, 178, 0.2); } -.logo-symbol i:nth-child(1) { +.legacy .logo-symbol i:nth-child(1) { top: 0; left: 0; } -.logo-symbol i:nth-child(2) { +.legacy .logo-symbol i:nth-child(2) { right: 0; top: 9px; } -.logo-symbol i:nth-child(3) { +.legacy .logo-symbol i:nth-child(3) { bottom: 0; left: 0; } .logo-symbol::before, -.logo-symbol::after { +.legacy .logo-symbol::after { position: absolute; left: 6px; width: 14px; @@ -268,45 +268,45 @@ time { content: ""; } -.logo-symbol::before { +.legacy .logo-symbol::before { top: 6px; transform: rotate(30deg); } -.logo-symbol::after { +.legacy .logo-symbol::after { bottom: 6px; transform: rotate(-30deg); } .brand > span:last-child, -.login-brand > span:last-child { +.legacy .login-brand > span:last-child { display: grid; gap: 1px; } .brand b, -.login-brand b { +.legacy .login-brand b { color: var(--text); font-size: 14px; letter-spacing: -0.02em; } .brand small, -.login-brand small { +.legacy .login-brand small { color: var(--text-muted); font: 9px var(--mono); letter-spacing: 0.13em; text-transform: uppercase; } -.primary-nav { +.legacy .primary-nav { display: grid; gap: 5px; margin-top: 24px; } .nav-heading, -.menu-label { +.legacy .menu-label { margin: 0 10px 8px; color: var(--text-muted); font-size: 9px; @@ -315,7 +315,7 @@ time { text-transform: uppercase; } -.primary-nav a { +.legacy .primary-nav a { position: relative; display: grid; min-height: 42px; @@ -332,18 +332,18 @@ time { transition: color 140ms var(--ease), background 140ms var(--ease), border-color 140ms var(--ease); } -.primary-nav a:hover { +.legacy .primary-nav a:hover { color: var(--text-soft); background: var(--surface-2); } -.primary-nav a.active { +.legacy .primary-nav a.active { border-color: var(--accent-line); color: var(--accent-strong); background: var(--accent-wash); } -.primary-nav a.active::before { +.legacy .primary-nav a.active::before { position: absolute; left: -15px; width: 3px; @@ -353,7 +353,7 @@ time { content: ""; } -.nav-count { +.legacy .nav-count { min-width: 24px; padding: 2px 5px; border-radius: 999px; @@ -363,12 +363,12 @@ time { text-align: center; } -.nav-count.attention { +.legacy .nav-count.attention { color: var(--amber); background: var(--amber-wash); } -.sidebar-status { +.legacy .sidebar-status { display: flex; align-items: flex-start; gap: 10px; @@ -379,23 +379,23 @@ time { background: var(--surface-1); } -.sidebar-status > span:last-child { +.legacy .sidebar-status > span:last-child { display: grid; min-width: 0; gap: 3px; } -.sidebar-status b { +.legacy .sidebar-status b { color: var(--text-soft); font-size: 10px; } -.sidebar-status small { +.legacy .sidebar-status small { color: var(--text-muted); font-size: 9px; } -.signal { +.legacy .signal { display: inline-block; width: 7px; height: 7px; @@ -406,19 +406,19 @@ time { box-shadow: 0 0 0 4px var(--red-wash); } -.signal.online { +.legacy .signal.online { background: var(--accent); box-shadow: 0 0 0 4px var(--accent-wash); } -.sidebar-foot { +.legacy .sidebar-foot { padding: 13px 9px 0; color: #4e5a57; font: 8px var(--mono); letter-spacing: 0.1em; } -.topbar { +.legacy .topbar { position: fixed; inset: 0 0 auto 224px; z-index: 18; @@ -432,7 +432,7 @@ time { backdrop-filter: blur(18px); } -.topbar-context { +.legacy .topbar-context { display: flex; min-width: 0; align-items: center; @@ -441,7 +441,7 @@ time { font-size: 11px; } -.topbar-context b { +.legacy .topbar-context b { overflow: hidden; color: var(--text-soft); font-weight: 600; @@ -452,7 +452,7 @@ time { .command-trigger, .account-trigger, .button-secondary, -.icon-button { +.legacy .icon-button { border-color: var(--line-strong); color: var(--text-soft); background: var(--surface-1); @@ -461,13 +461,13 @@ time { .command-trigger:hover:not(:disabled), .account-trigger:hover:not(:disabled), .button-secondary:hover:not(:disabled), -.icon-button:hover:not(:disabled) { +.legacy .icon-button:hover:not(:disabled) { border-color: rgba(221, 236, 231, 0.24); color: var(--text); background: var(--surface-3); } -.command-trigger { +.legacy .command-trigger { min-height: 34px; margin-left: auto; padding: 6px 8px 6px 10px; @@ -476,7 +476,7 @@ time { } .command-trigger kbd, -.page-actions kbd { +.legacy .page-actions kbd { padding: 3px 5px; border: 1px solid var(--line-strong); border-radius: 5px; @@ -486,17 +486,17 @@ time { font-weight: 400; } -.account { +.legacy .account { position: relative; } -.account-trigger { +.legacy .account-trigger { min-height: 34px; padding: 3px 8px 3px 4px; font-weight: 600; } -.avatar { +.legacy .avatar { display: grid; width: 26px; height: 26px; @@ -508,7 +508,7 @@ time { font: 9px var(--mono); } -.account-menu { +.legacy .account-menu { position: absolute; top: calc(100% + 9px); right: 0; @@ -522,7 +522,7 @@ time { box-shadow: var(--shadow); } -.account-menu-user { +.legacy .account-menu-user { display: flex; align-items: center; gap: 9px; @@ -530,25 +530,25 @@ time { border-bottom: 1px solid var(--line); } -.account-menu-user > span:last-child { +.legacy .account-menu-user > span:last-child { display: grid; min-width: 0; gap: 2px; } -.account-menu-user b { +.legacy .account-menu-user b { overflow: hidden; color: var(--text); font-size: 11px; text-overflow: ellipsis; } -.account-menu-user small { +.legacy .account-menu-user small { color: var(--text-muted); font-size: 9px; } -.account-menu > a { +.legacy .account-menu > a { display: flex; min-height: 33px; align-items: center; @@ -562,12 +562,12 @@ time { text-decoration: none; } -.account-menu > a:hover { +.legacy .account-menu > a:hover { color: var(--text); background: var(--surface-hover); } -.account-menu button { +.legacy .account-menu button { width: 100%; min-height: 33px; justify-content: flex-start; @@ -575,20 +575,20 @@ time { background: var(--surface-3); } -.content { +.legacy .content { min-height: 100vh; padding-top: 64px; } /* Shared page patterns */ -.page { +.legacy .page { width: min(1500px, 100%); margin: 0 auto; padding: 38px 32px 68px; } -.page-header { +.legacy .page-header { display: flex; align-items: flex-end; justify-content: space-between; @@ -598,7 +598,7 @@ time { .page-header h1, .task-header h1, -.empty-state h1 { +.legacy .empty-state h1 { margin: 0; font-size: clamp(32px, 4vw, 48px); font-weight: 650; @@ -606,7 +606,7 @@ time { letter-spacing: -0.055em; } -.page-header > div > p { +.legacy .page-header > div > p { max-width: 580px; margin: 10px 0 0; color: var(--text-soft); @@ -614,7 +614,7 @@ time { line-height: 1.55; } -.eyebrow { +.legacy .eyebrow { display: block; margin-bottom: 8px; color: var(--accent); @@ -624,13 +624,13 @@ time { text-transform: uppercase; } -.page-actions { +.legacy .page-actions { display: flex; align-items: center; gap: 9px; } -.back-link { +.legacy .back-link { display: inline-flex; align-items: center; gap: 5px; @@ -641,37 +641,37 @@ time { text-decoration: none; } -.back-link:hover { +.legacy .back-link:hover { color: var(--accent-strong); } .section-header, -.panel-header { +.legacy .panel-header { display: flex; align-items: center; justify-content: space-between; gap: 18px; } -.section-header { +.legacy .section-header { margin-bottom: 13px; } .section-header h2, -.panel-header h2 { +.legacy .panel-header h2 { margin: 0; font-size: 14px; letter-spacing: -0.015em; } .section-header p, -.panel-header p { +.legacy .panel-header p { margin: 4px 0 0; color: var(--text-muted); font-size: 11px; } -.sync-label { +.legacy .sync-label { display: inline-flex; align-items: center; gap: 8px; @@ -680,25 +680,25 @@ time { text-transform: uppercase; } -.sync-label .signal { +.legacy .sync-label .signal { margin: 0; } -.panel { +.legacy .panel { padding: 20px; border: 1px solid var(--line); border-radius: var(--radius); background: linear-gradient(145deg, rgba(21, 29, 33, 0.9), rgba(16, 23, 27, 0.9)); } -.panel-intro { +.legacy .panel-intro { margin: 7px 0 0; color: var(--text-muted); font-size: 11px; line-height: 1.5; } -.empty-state { +.legacy .empty-state { display: grid; min-height: 300px; place-items: center; @@ -710,11 +710,11 @@ time { background: rgba(17, 23, 27, 0.45); } -.empty-state.compact { +.legacy .empty-state.compact { min-height: 220px; } -.empty-state .empty-icon { +.legacy .empty-state .empty-icon { display: grid; width: 44px; height: 44px; @@ -727,13 +727,13 @@ time { } .empty-state h1, -.empty-state h2 { +.legacy .empty-state h2 { margin: 0; font-size: 20px; letter-spacing: -0.03em; } -.empty-state p { +.legacy .empty-state p { max-width: 390px; margin: 8px 0 17px; color: var(--text-muted); @@ -741,13 +741,13 @@ time { line-height: 1.55; } -.error-state .empty-icon { +.legacy .error-state .empty-icon { border-color: rgba(236, 143, 133, 0.25); color: var(--red); background: var(--red-wash); } -.skeleton { +.legacy .skeleton { min-height: 180px; border-radius: var(--radius); background: linear-gradient(100deg, var(--surface-1) 30%, var(--surface-2) 50%, var(--surface-1) 70%); @@ -755,20 +755,20 @@ time { animation: shimmer 1.5s infinite; } -.skeleton-title { +.legacy .skeleton-title { width: 300px; min-height: 44px; margin-top: 10px; } -.skeleton-subtitle { +.legacy .skeleton-subtitle { width: 420px; max-width: 75%; min-height: 18px; margin-top: 13px; } -.skeleton-grid { +.legacy .skeleton-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; @@ -781,7 +781,7 @@ time { /* Overview */ -.attention-banner { +.legacy .attention-banner { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; @@ -793,7 +793,7 @@ time { background: var(--amber-wash); } -.attention-icon { +.legacy .attention-icon { display: grid; width: 35px; height: 35px; @@ -804,17 +804,17 @@ time { background: rgba(239, 189, 114, 0.08); } -.attention-banner b { +.legacy .attention-banner b { font-size: 12px; } -.attention-banner p { +.legacy .attention-banner p { margin: 3px 0 0; color: var(--text-soft); font-size: 11px; } -.attention-banner > a { +.legacy .attention-banner > a { display: inline-flex; align-items: center; gap: 3px; @@ -824,14 +824,14 @@ time { text-decoration: none; } -.metrics { +.legacy .metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; margin-bottom: 32px; } -.metric { +.legacy .metric { display: grid; min-width: 0; grid-template-columns: auto minmax(0, 1fr); @@ -842,7 +842,7 @@ time { background: rgba(17, 23, 27, 0.82); } -.metric-icon { +.legacy .metric-icon { display: grid; width: 34px; height: 34px; @@ -854,7 +854,7 @@ time { background: var(--surface-2); } -.metric-copy { +.legacy .metric-copy { display: flex; min-width: 0; align-items: baseline; @@ -862,7 +862,7 @@ time { gap: 8px; } -.metric-copy small { +.legacy .metric-copy small { overflow: hidden; color: var(--text-soft); font-size: 10px; @@ -871,12 +871,12 @@ time { white-space: nowrap; } -.metric-copy b { +.legacy .metric-copy b { color: var(--text); font: 600 19px var(--mono); } -.metric-detail { +.legacy .metric-detail { overflow: hidden; color: var(--text-muted); font-size: 10px; @@ -884,26 +884,26 @@ time { white-space: nowrap; } -.metric.tone-good .metric-icon { +.legacy .metric.tone-good .metric-icon { border-color: var(--accent-line); color: var(--accent); background: var(--accent-wash); } -.metric.tone-warning .metric-icon { +.legacy .metric.tone-warning .metric-icon { border-color: rgba(239, 189, 114, 0.26); color: var(--amber); background: var(--amber-wash); } -.board-toolbar { +.legacy .board-toolbar { display: flex; align-items: center; gap: 9px; margin-bottom: 13px; } -.segmented { +.legacy .segmented { display: flex; gap: 3px; margin-right: auto; @@ -913,7 +913,7 @@ time { background: var(--surface-1); } -.segmented button { +.legacy .segmented button { min-height: 31px; padding: 5px 10px; border-color: transparent; @@ -923,41 +923,41 @@ time { font-weight: 650; } -.segmented button:hover:not(:disabled) { +.legacy .segmented button:hover:not(:disabled) { border-color: transparent; color: var(--text-soft); background: var(--surface-3); transform: none; } -.segmented button.selected { +.legacy .segmented button.selected { border-color: var(--line-strong); color: var(--text); background: var(--surface-3); box-shadow: 0 2px 7px rgba(0, 0, 0, 0.18); } -.segmented button span { +.legacy .segmented button span { margin-left: 5px; color: var(--text-muted); font: 9px var(--mono); } -.search-field { +.legacy .search-field { position: relative; display: flex; width: min(280px, 28vw); align-items: center; } -.search-field > .icon { +.legacy .search-field > .icon { position: absolute; left: 11px; z-index: 1; color: var(--text-muted); } -.search-field input { +.legacy .search-field input { min-height: 38px; padding: 7px 34px; color: var(--text-soft); @@ -965,7 +965,7 @@ time { font-size: 11px; } -.search-field button { +.legacy .search-field button { position: absolute; right: 5px; min-width: 27px; @@ -976,18 +976,18 @@ time { background: transparent; } -.select-field { +.legacy .select-field { width: 155px; } -.select-field select { +.legacy .select-field select { min-height: 38px; padding: 7px 29px 7px 10px; background-color: var(--surface-1); font-size: 11px; } -.task-board { +.legacy .task-board { display: grid; gap: 10px; overflow-x: auto; @@ -995,23 +995,23 @@ time { scrollbar-color: var(--surface-3) transparent; } -.task-board.lanes-2 { +.legacy .task-board.lanes-2 { grid-template-columns: repeat(2, minmax(270px, 1fr)); } -.task-board.lanes-3 { +.legacy .task-board.lanes-3 { grid-template-columns: repeat(3, minmax(255px, 1fr)); } -.task-board.lanes-5 { +.legacy .task-board.lanes-5 { grid-template-columns: repeat(5, minmax(235px, 1fr)); } -.task-board.lanes-7 { +.legacy .task-board.lanes-7 { grid-template-columns: repeat(7, minmax(225px, 1fr)); } -.task-lane { +.legacy .task-lane { min-height: 310px; padding: 10px; border: 1px solid var(--line); @@ -1019,7 +1019,7 @@ time { background: rgba(17, 23, 27, 0.62); } -.task-lane > header { +.legacy .task-lane > header { display: flex; height: 29px; align-items: center; @@ -1027,7 +1027,7 @@ time { padding: 0 3px 9px; } -.task-lane > header > span { +.legacy .task-lane > header > span { display: inline-flex; align-items: center; gap: 7px; @@ -1040,7 +1040,7 @@ time { .task-lane > header i, .status-pill i, -.health-badge i { +.legacy .health-badge i { display: block; width: 6px; height: 6px; @@ -1049,48 +1049,48 @@ time { } .lane-queued > header i, -.status-pill.state-queued i { +.legacy .status-pill.state-queued i { background: var(--blue); box-shadow: 0 0 0 3px var(--blue-wash); } .lane-leased > header i, -.status-pill.state-leased i { +.legacy .status-pill.state-leased i { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-wash); } .lane-blocked > header i, .lane-needs_attention > header i, -.status-pill.state-blocked i { +.legacy .status-pill.state-blocked i { background: var(--amber); box-shadow: 0 0 0 3px var(--amber-wash); } -.status-pill.state-needs_attention i { +.legacy .status-pill.state-needs_attention i { background: var(--amber); box-shadow: 0 0 0 3px var(--amber-wash); } .lane-in_review > header i, -.status-pill.state-in_review i { +.legacy .status-pill.state-in_review i { background: var(--violet); box-shadow: 0 0 0 3px var(--violet-wash); } .lane-completed > header i, -.status-pill.state-completed i { +.legacy .status-pill.state-completed i { background: #8fcfc5; box-shadow: 0 0 0 3px rgba(143, 207, 197, 0.1); } .lane-failed > header i, -.status-pill.state-failed i { +.legacy .status-pill.state-failed i { background: var(--red); box-shadow: 0 0 0 3px var(--red-wash); } -.task-lane > header b { +.legacy .task-lane > header b { display: grid; min-width: 20px; height: 20px; @@ -1102,13 +1102,13 @@ time { font: 9px var(--mono); } -.lane-cards { +.legacy .lane-cards { display: grid; align-content: start; gap: 8px; } -.empty-lane { +.legacy .empty-lane { margin: 0; padding: 30px 9px; color: #53615e; @@ -1116,7 +1116,7 @@ time { text-align: center; } -.task-card { +.legacy .task-card { position: relative; display: block; min-width: 0; @@ -1130,7 +1130,7 @@ time { transition: border-color 150ms var(--ease), background 150ms var(--ease), transform 150ms var(--ease), box-shadow 150ms var(--ease); } -.task-card:hover { +.legacy .task-card:hover { z-index: 1; border-color: rgba(142, 224, 178, 0.3); background: var(--surface-hover); @@ -1138,24 +1138,24 @@ time { transform: translateY(-2px); } -.task-card.state-blocked { +.legacy .task-card.state-blocked { border-color: rgba(239, 189, 114, 0.18); } -.task-card.state-needs_attention { +.legacy .task-card.state-needs_attention { border-color: rgba(239, 189, 114, 0.25); background: linear-gradient(145deg, rgba(62, 46, 27, 0.28), var(--surface-2)); } -.task-card.state-in_review { +.legacy .task-card.state-in_review { border-color: rgba(181, 162, 239, 0.2); } -.task-card.state-failed { +.legacy .task-card.state-failed { border-color: rgba(236, 143, 133, 0.18); } -.task-card-top { +.legacy .task-card-top { display: flex; align-items: center; justify-content: space-between; @@ -1163,7 +1163,7 @@ time { margin-bottom: 9px; } -.status-pill { +.legacy .status-pill { display: inline-flex; min-height: 22px; align-items: center; @@ -1180,32 +1180,32 @@ time { } .status-pill.state-leased, -.status-pill.state-completed { +.legacy .status-pill.state-completed { color: var(--accent-strong); } -.status-pill.state-blocked { +.legacy .status-pill.state-blocked { color: var(--amber); } -.status-pill.state-needs_attention { +.legacy .status-pill.state-needs_attention { color: var(--amber); } -.status-pill.state-in_review { +.legacy .status-pill.state-in_review { color: var(--violet); } -.status-pill.state-failed { +.legacy .status-pill.state-failed { color: var(--red); } -.task-short-id { +.legacy .task-short-id { color: #5c6865; font-size: 8px; } -.task-card h3 { +.legacy .task-card h3 { display: -webkit-box; overflow: hidden; margin: 0 0 6px; @@ -1217,7 +1217,7 @@ time { -webkit-line-clamp: 2; } -.task-card > p { +.legacy .task-card > p { display: -webkit-box; overflow: hidden; min-height: 30px; @@ -1230,7 +1230,7 @@ time { } .approval-chip, -.reason-chip { +.legacy .reason-chip { display: inline-flex; align-items: center; gap: 5px; @@ -1243,14 +1243,14 @@ time { font-weight: 650; } -.reason-chip { +.legacy .reason-chip { color: var(--text-soft); background: var(--surface-3); } .capability-list, .fact-tags > div, -.worker-projects > div { +.legacy .worker-projects > div { display: flex; flex-wrap: wrap; gap: 4px; @@ -1259,7 +1259,7 @@ time { .capability-list span, .fact-tags b, -.worker-projects b { +.legacy .worker-projects b { padding: 3px 5px; border: 1px solid var(--line); border-radius: 5px; @@ -1269,7 +1269,7 @@ time { font-weight: 500; } -.task-card footer { +.legacy .task-card footer { display: flex; min-width: 0; align-items: center; @@ -1281,20 +1281,20 @@ time { font-size: 9px; } -.task-card footer > span:first-child { +.legacy .task-card footer > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.task-card footer time { +.legacy .task-card footer time { color: var(--text-muted); white-space: nowrap; } /* Overlays and forms */ -.overlay { +.legacy .overlay { position: fixed; inset: 0; z-index: 100; @@ -1304,11 +1304,11 @@ time { backdrop-filter: blur(7px); } -.modal-overlay { +.legacy .modal-overlay { place-items: center; } -.modal { +.legacy .modal { width: min(740px, 100%); max-height: calc(100vh - 40px); overflow: auto; @@ -1318,7 +1318,7 @@ time { box-shadow: var(--shadow); } -.modal-header { +.legacy .modal-header { display: flex; align-items: flex-start; justify-content: space-between; @@ -1327,25 +1327,25 @@ time { border-bottom: 1px solid var(--line); } -.modal-header h2 { +.legacy .modal-header h2 { margin: 0; font-size: 22px; letter-spacing: -0.035em; } -.modal-header p { +.legacy .modal-header p { margin: 6px 0 0; color: var(--text-muted); font-size: 11px; } -.icon-button { +.legacy .icon-button { width: 34px; min-height: 34px; padding: 0; } -.eligibility { +.legacy .eligibility { display: flex; align-items: flex-start; gap: 10px; @@ -1356,47 +1356,47 @@ time { background: var(--accent-wash); } -.eligibility > span:last-child { +.legacy .eligibility > span:last-child { display: grid; gap: 2px; } -.eligibility b { +.legacy .eligibility b { color: var(--accent-strong); font-size: 11px; } -.eligibility small { +.legacy .eligibility small { color: var(--text-muted); font-size: 10px; } -.eligibility.warning { +.legacy .eligibility.warning { border-color: rgba(239, 189, 114, 0.28); background: var(--amber-wash); } -.eligibility.warning .signal { +.legacy .eligibility.warning .signal { background: var(--amber); box-shadow: 0 0 0 4px var(--amber-wash); } -.eligibility.warning b { +.legacy .eligibility.warning b { color: var(--amber); } -.create-modal form { +.legacy .create-modal form { display: grid; gap: 14px; padding: 18px 23px 23px; } -.create-modal label { +.legacy .create-modal label { display: grid; gap: 6px; } -.create-modal label > span { +.legacy .create-modal label > span { display: flex; justify-content: space-between; color: var(--text-soft); @@ -1404,7 +1404,7 @@ time { font-weight: 650; } -.create-modal label > span b { +.legacy .create-modal label > span b { color: var(--text-muted); font-size: 8px; font-weight: 600; @@ -1412,44 +1412,44 @@ time { text-transform: uppercase; } -.create-modal label > small { +.legacy .create-modal label > small { color: var(--text-muted); font-size: 10px; } -.form-row { +.legacy .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } -.short-textarea { +.legacy .short-textarea { min-height: 82px; } -.advanced-fields { +.legacy .advanced-fields { border-top: 1px solid var(--line); padding-top: 4px; } -.advanced-fields summary { +.legacy .advanced-fields summary { color: var(--text-soft); font-size: 10px; font-weight: 650; cursor: pointer; } -.advanced-fields p { +.legacy .advanced-fields p { margin: 8px 0; color: var(--text-muted); font-size: 10px; } -.advanced-fields textarea { +.legacy .advanced-fields textarea { min-height: 80px; } -.modal-actions { +.legacy .modal-actions { display: flex; justify-content: flex-end; gap: 8px; @@ -1457,7 +1457,7 @@ time { } .form-error, -.notice { +.legacy .notice { display: flex; align-items: flex-start; gap: 7px; @@ -1471,18 +1471,18 @@ time { line-height: 1.45; } -.notice { +.legacy .notice { border-color: var(--accent-line); color: var(--accent-strong); background: var(--accent-wash); } -.palette-overlay { +.legacy .palette-overlay { place-items: start center; padding-top: 12vh; } -.palette { +.legacy .palette { width: min(590px, 100%); overflow: hidden; border: 1px solid var(--line-strong); @@ -1491,20 +1491,20 @@ time { box-shadow: var(--shadow); } -.palette-input { +.legacy .palette-input { position: relative; display: flex; align-items: center; border-bottom: 1px solid var(--line); } -.palette-input .icon { +.legacy .palette-input .icon { position: absolute; left: 17px; color: var(--text-muted); } -.palette-input input { +.legacy .palette-input input { min-height: 57px; padding: 14px 17px 14px 48px; border: 0; @@ -1513,17 +1513,17 @@ time { font-size: 14px; } -.palette-list { +.legacy .palette-list { display: grid; gap: 3px; padding: 10px; } -.palette-list .menu-label { +.legacy .palette-list .menu-label { margin: 3px 8px 5px; } -.palette-list button { +.legacy .palette-list button { width: 100%; min-height: 42px; justify-content: space-between; @@ -1534,14 +1534,14 @@ time { font-weight: 550; } -.palette-list button > span { +.legacy .palette-list button > span { display: flex; align-items: center; gap: 9px; } .palette-list button.selected, -.palette-list button:hover:not(:disabled) { +.legacy .palette-list button:hover:not(:disabled) { border-color: var(--line); color: var(--text); background: var(--surface-3); @@ -1549,19 +1549,19 @@ time { } .palette-list kbd, -.palette-footer kbd { +.legacy .palette-footer kbd { color: var(--text-muted); font-size: 9px; } -.palette-empty { +.legacy .palette-empty { padding: 24px 8px; color: var(--text-muted); font-size: 11px; text-align: center; } -.palette-footer { +.legacy .palette-footer { display: flex; gap: 15px; padding: 9px 17px; @@ -1572,7 +1572,7 @@ time { /* Task detail */ -.task-header { +.legacy .task-header { display: flex; align-items: flex-end; justify-content: space-between; @@ -1580,7 +1580,7 @@ time { margin-bottom: 20px; } -.task-heading-meta { +.legacy .task-heading-meta { display: flex; align-items: center; gap: 8px; @@ -1589,24 +1589,24 @@ time { font-size: 9px; } -.task-heading-meta > span:not(.status-pill) + span::before { +.legacy .task-heading-meta > span:not(.status-pill) + span::before { margin-right: 8px; color: #45514e; content: "/"; } -.task-header h1 { +.legacy .task-header h1 { max-width: 850px; font-size: clamp(29px, 4vw, 44px); } -.task-id { +.legacy .task-id { margin: 9px 0 0; color: var(--text-muted); font-size: 9px; } -.diagnosis { +.legacy .diagnosis { display: grid; grid-template-columns: minmax(0, 1fr) minmax(420px, 0.8fr); align-items: center; @@ -1620,24 +1620,24 @@ time { .diagnosis.state-blocked, .diagnosis.state-needs_attention, -.diagnosis.state-failed { +.legacy .diagnosis.state-failed { border-color: rgba(239, 189, 114, 0.23); background: linear-gradient(110deg, var(--amber-wash), rgba(17, 23, 27, 0.65)); } -.diagnosis.state-in_review { +.legacy .diagnosis.state-in_review { border-color: rgba(181, 162, 239, 0.26); background: linear-gradient(110deg, var(--violet-wash), rgba(17, 23, 27, 0.65)); } -.diagnosis-copy { +.legacy .diagnosis-copy { display: flex; min-width: 0; align-items: flex-start; gap: 13px; } -.diagnosis-symbol { +.legacy .diagnosis-symbol { display: grid; width: 38px; height: 38px; @@ -1651,25 +1651,25 @@ time { .diagnosis-symbol.state-blocked, .diagnosis-symbol.state-needs_attention, -.diagnosis-symbol.state-failed { +.legacy .diagnosis-symbol.state-failed { border-color: rgba(239, 189, 114, 0.25); color: var(--amber); background: var(--amber-wash); } -.diagnosis-symbol.state-in_review { +.legacy .diagnosis-symbol.state-in_review { border-color: rgba(181, 162, 239, 0.3); color: var(--violet); background: var(--violet-wash); } -.diagnosis h2 { +.legacy .diagnosis h2 { margin: 0; font-size: 17px; letter-spacing: -0.025em; } -.diagnosis-copy p { +.legacy .diagnosis-copy p { max-width: 630px; margin: 5px 0 0; color: var(--text-soft); @@ -1677,7 +1677,7 @@ time { line-height: 1.5; } -.diagnosis dl { +.legacy .diagnosis dl { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 0; @@ -1687,19 +1687,19 @@ time { background: rgba(9, 14, 16, 0.3); } -.diagnosis dl > div { +.legacy .diagnosis dl > div { min-width: 0; padding: 9px; border-right: 1px solid var(--line); } -.diagnosis dl > div:last-child { +.legacy .diagnosis dl > div:last-child { border: 0; } .diagnosis dt, .facts dt, -.worker-stats dt { +.legacy .worker-stats dt { margin-bottom: 5px; color: var(--text-muted); font-size: 9px; @@ -1708,7 +1708,7 @@ time { text-transform: uppercase; } -.diagnosis dd { +.legacy .diagnosis dd { overflow: hidden; margin: 0; color: var(--text-soft); @@ -1717,7 +1717,7 @@ time { white-space: nowrap; } -.approval-panel { +.legacy .approval-panel { display: grid; gap: 12px; margin-bottom: 14px; @@ -1727,30 +1727,30 @@ time { background: linear-gradient(135deg, rgba(68, 48, 20, 0.38), rgba(21, 29, 33, 0.92)); } -.approval-heading { +.legacy .approval-heading { display: flex; align-items: flex-start; gap: 13px; } -.approval-heading h2 { +.legacy .approval-heading h2 { margin: 0; font-size: 18px; } -.approval-heading p { +.legacy .approval-heading p { margin: 5px 0 0; color: var(--text-soft); font-size: 10px; } -.approval-meta { +.legacy .approval-meta { display: flex; flex-wrap: wrap; gap: 6px; } -.approval-meta span { +.legacy .approval-meta span { padding: 4px 6px; border: 1px solid var(--line); border-radius: 5px; @@ -1759,13 +1759,13 @@ time { font: 9px var(--mono); } -.approval-meta b { +.legacy .approval-meta b { color: var(--text-soft); font-weight: 500; } .approval-code, -.artifact-content { +.legacy .artifact-content { margin: 0; padding: 14px; overflow: auto; @@ -1778,19 +1778,19 @@ time { white-space: pre-wrap; } -.approval-note { +.legacy .approval-note { margin: 0; color: var(--text-soft); font-size: 11px; line-height: 1.5; } -.approval-actions { +.legacy .approval-actions { display: flex; gap: 8px; } -.detail-layout { +.legacy .detail-layout { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 14px; @@ -1798,12 +1798,12 @@ time { } .detail-main, -.detail-side { +.legacy .detail-side { display: grid; gap: 14px; } -.immutable-badge { +.legacy .immutable-badge { padding: 4px 6px; border: 1px solid var(--line); border-radius: 5px; @@ -1813,7 +1813,7 @@ time { text-transform: uppercase; } -.task-description { +.legacy .task-description { margin: 14px 0 0; color: #c1cac7; font-size: 12px; @@ -1822,19 +1822,19 @@ time { } .acceptance-list, -.quality-gate { +.legacy .quality-gate { margin-top: 18px; padding-top: 15px; border-top: 1px solid var(--line); } -.acceptance-list h3 { +.legacy .acceptance-list h3 { margin: 0 0 9px; color: var(--text-soft); font-size: 11px; } -.acceptance-list ul { +.legacy .acceptance-list ul { display: grid; gap: 7px; margin: 0; @@ -1842,7 +1842,7 @@ time { list-style: none; } -.acceptance-list li { +.legacy .acceptance-list li { display: flex; align-items: flex-start; gap: 8px; @@ -1851,7 +1851,7 @@ time { line-height: 1.45; } -.acceptance-list li > span { +.legacy .acceptance-list li > span { display: grid; width: 18px; height: 18px; @@ -1863,12 +1863,12 @@ time { background: var(--accent-wash); } -.quality-gate { +.legacy .quality-gate { display: grid; gap: 6px; } -.quality-gate > span { +.legacy .quality-gate > span { color: var(--text-muted); font-size: 9px; font-weight: 700; @@ -1876,7 +1876,7 @@ time { text-transform: uppercase; } -.quality-gate code { +.legacy .quality-gate code { display: block; padding: 8px 10px; border: 1px solid var(--line); @@ -1886,14 +1886,14 @@ time { font-size: 10px; } -.capture-panel { +.legacy .capture-panel { overflow: hidden; border: 1px solid var(--line-strong); border-radius: var(--radius); background: #090d0f; } -.capture-panel > header { +.legacy .capture-panel > header { display: flex; align-items: center; justify-content: space-between; @@ -1904,13 +1904,13 @@ time { } .capture-title, -.capture-actions { +.legacy .capture-actions { display: flex; align-items: center; gap: 8px; } -.capture-title > span { +.legacy .capture-title > span { display: grid; width: 30px; height: 30px; @@ -1921,42 +1921,42 @@ time { background: var(--accent-wash); } -.capture-title h2 { +.legacy .capture-title h2 { margin: 0; font-size: 11px; } -.capture-title p { +.legacy .capture-title p { margin: 3px 0 0; color: var(--text-muted); font: 9px var(--mono); } -.small-button { +.legacy .small-button { min-height: 32px; padding: 5px 9px; font-size: 10px; } -.capture-actions .icon-button { +.legacy .capture-actions .icon-button { min-height: 32px; width: 32px; } -.terminal-filter { +.legacy .terminal-filter { position: relative; display: flex; align-items: center; margin: 10px 11px 0; } -.terminal-filter .icon { +.legacy .terminal-filter .icon { position: absolute; left: 9px; color: #55625e; } -.terminal-filter input { +.legacy .terminal-filter input { min-height: 31px; padding: 5px 9px 5px 30px; border-color: var(--line); @@ -1964,7 +1964,7 @@ time { font-size: 10px; } -.terminal-output { +.legacy .terminal-output { max-height: 530px; margin: 0; padding: 15px; @@ -1976,7 +1976,7 @@ time { scrollbar-color: var(--surface-3) transparent; } -.capture-panel > footer { +.legacy .capture-panel > footer { padding: 7px 12px; border-top: 1px solid var(--line); color: var(--text-muted); @@ -1984,7 +1984,7 @@ time { font-size: 9px; } -.capture-empty { +.legacy .capture-empty { display: flex; align-items: flex-start; gap: 11px; @@ -1994,32 +1994,32 @@ time { background: rgba(17, 23, 27, 0.5); } -.capture-empty > span { +.legacy .capture-empty > span { color: var(--text-muted); } -.capture-empty b { +.legacy .capture-empty b { font-size: 11px; } -.capture-empty p { +.legacy .capture-empty p { margin: 4px 0 0; color: var(--text-muted); font-size: 10px; line-height: 1.5; } -.action-panel .panel-header { +.legacy .action-panel .panel-header { margin-bottom: 0; } -.action-list { +.legacy .action-list { display: grid; gap: 8px; margin-top: 13px; } -.action-card { +.legacy .action-card { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; @@ -2031,7 +2031,7 @@ time { background: rgba(10, 15, 17, 0.32); } -.action-icon { +.legacy .action-icon { display: grid; width: 31px; height: 31px; @@ -2042,71 +2042,71 @@ time { background: var(--surface-2); } -.action-card h3 { +.legacy .action-card h3 { margin: 0; font-size: 11px; } -.action-card p { +.legacy .action-card p { margin: 3px 0 0; color: var(--text-muted); font-size: 10px; line-height: 1.5; } -.action-form { +.legacy .action-form { align-items: start; } -.action-copy { +.legacy .action-copy { min-width: 0; } .action-copy input, -.action-copy textarea { +.legacy .action-copy textarea { min-height: 36px; margin-top: 9px; padding: 7px 9px; font-size: 10px; } -.action-copy textarea { +.legacy .action-copy textarea { min-height: 78px; } -.action-buttons { +.legacy .action-buttons { display: flex; align-self: end; gap: 6px; } -.action-card button { +.legacy .action-card button { min-height: 32px; padding: 6px 9px; font-size: 10px; } -.action-card.confirming { +.legacy .action-card.confirming { border-color: rgba(239, 189, 114, 0.28); background: var(--amber-wash); } .button-danger, -.button-danger:hover:not(:disabled) { +.legacy .button-danger:hover:not(:disabled) { border-color: rgba(236, 143, 133, 0.4); color: #27100e; background: var(--red); } -.inline-error { +.legacy .inline-error { color: var(--red) !important; } -.unavailable-panel { +.legacy .unavailable-panel { padding: 0; } -.unavailable-panel summary { +.legacy .unavailable-panel summary { display: flex; align-items: center; justify-content: space-between; @@ -2117,7 +2117,7 @@ time { cursor: pointer; } -.unavailable-panel summary span { +.legacy .unavailable-panel summary span { display: grid; width: 20px; height: 20px; @@ -2127,13 +2127,13 @@ time { font: 8px var(--mono); } -.unavailable-list { +.legacy .unavailable-list { display: grid; gap: 8px; padding: 0 15px 14px; } -.unavailable-list > div { +.legacy .unavailable-list > div { display: grid; gap: 3px; padding: 9px; @@ -2141,23 +2141,23 @@ time { border-radius: 7px; } -.unavailable-list b { +.legacy .unavailable-list b { color: var(--text-soft); font-size: 10px; } -.unavailable-list span { +.legacy .unavailable-list span { color: var(--text-muted); font-size: 9px; } -.facts { +.legacy .facts { display: grid; gap: 0; margin: 10px 0 0; } -.facts > div { +.legacy .facts > div { display: flex; min-width: 0; align-items: center; @@ -2167,15 +2167,15 @@ time { border-bottom: 1px solid var(--line); } -.facts > div:last-child { +.legacy .facts > div:last-child { border: 0; } -.facts dt { +.legacy .facts dt { margin: 0; } -.facts dd { +.legacy .facts dd { overflow: hidden; margin: 0; color: var(--text-soft); @@ -2185,7 +2185,7 @@ time { white-space: nowrap; } -.fact-tags { +.legacy .fact-tags { display: grid; gap: 7px; margin-top: 9px; @@ -2195,7 +2195,7 @@ time { .fact-tags > span, .worker-projects > span, -.worker-current > span { +.legacy .worker-current > span { color: var(--text-muted); font-size: 9px; font-weight: 700; @@ -2203,16 +2203,16 @@ time { text-transform: uppercase; } -.fact-tags > div { +.legacy .fact-tags > div { margin: 0; } -.artifacts-panel { +.legacy .artifacts-panel { display: grid; gap: 7px; } -.artifacts-panel > a { +.legacy .artifacts-panel > a { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; @@ -2224,39 +2224,39 @@ time { text-decoration: none; } -.artifacts-panel > a:hover { +.legacy .artifacts-panel > a:hover { border-color: var(--accent-line); color: var(--accent); background: var(--accent-wash); } -.artifacts-panel a > span { +.legacy .artifacts-panel a > span { display: grid; min-width: 0; gap: 2px; } -.artifacts-panel b { +.legacy .artifacts-panel b { color: var(--text-soft); font-size: 10px; } -.artifacts-panel small { +.legacy .artifacts-panel small { color: var(--text-muted); font: 9px var(--mono); } -.timeline-panel { +.legacy .timeline-panel { overflow: hidden; } -.timeline { +.legacy .timeline { margin: 12px 0 0; padding: 0; list-style: none; } -.timeline li { +.legacy .timeline li { position: relative; display: grid; grid-template-columns: 12px minmax(0, 1fr); @@ -2264,7 +2264,7 @@ time { padding-bottom: 13px; } -.timeline li:not(:last-child)::before { +.legacy .timeline li:not(:last-child)::before { position: absolute; top: 11px; bottom: 0; @@ -2274,7 +2274,7 @@ time { content: ""; } -.event-dot { +.legacy .event-dot { position: relative; z-index: 1; display: block; @@ -2287,16 +2287,16 @@ time { box-shadow: 0 0 0 1px var(--line-strong); } -.timeline li.latest .event-dot { +.legacy .timeline li.latest .event-dot { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-wash); } -.event-copy { +.legacy .event-copy { min-width: 0; } -.event-copy > button { +.legacy .event-copy > button { display: grid; width: 100%; min-height: 0; @@ -2309,18 +2309,18 @@ time { text-align: left; } -.event-copy > button:hover:not(:disabled) { +.legacy .event-copy > button:hover:not(:disabled) { border: 0; background: transparent; transform: none; } -.event-copy > button:disabled { +.legacy .event-copy > button:disabled { opacity: 1; cursor: default; } -.event-copy b { +.legacy .event-copy b { overflow: hidden; color: var(--text-soft); font-size: 10px; @@ -2329,17 +2329,17 @@ time { } .event-copy span, -.event-copy time { +.legacy .event-copy time { color: var(--text-muted); font-size: 9px; } -.event-copy time { +.legacy .event-copy time { display: block; margin-top: 3px; } -.event-copy pre { +.legacy .event-copy pre { max-height: 260px; margin: 8px 0 0; padding: 8px; @@ -2355,38 +2355,38 @@ time { /* Workers */ -.worker-grid { +.legacy .worker-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 11px; } -.worker-card { +.legacy .worker-card { padding: 17px; border: 1px solid var(--line); border-radius: var(--radius); background: linear-gradient(145deg, var(--surface-2), var(--surface-1)); } -.worker-card.offline { +.legacy .worker-card.offline { border-color: rgba(236, 143, 133, 0.2); } -.worker-card > header { +.legacy .worker-card > header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; } -.worker-identity { +.legacy .worker-identity { display: flex; min-width: 0; align-items: center; gap: 10px; } -.worker-glyph { +.legacy .worker-glyph { display: grid; width: 39px; height: 39px; @@ -2398,17 +2398,17 @@ time { background: var(--accent-wash); } -.worker-card.offline .worker-glyph { +.legacy .worker-card.offline .worker-glyph { color: var(--red); background: var(--red-wash); } -.worker-card h2 { +.legacy .worker-card h2 { margin: 0; font-size: 13px; } -.worker-card header p { +.legacy .worker-card header p { overflow: hidden; max-width: 240px; margin: 4px 0 0; @@ -2418,7 +2418,7 @@ time { white-space: nowrap; } -.health-badge { +.legacy .health-badge { display: inline-flex; align-items: center; gap: 6px; @@ -2432,21 +2432,21 @@ time { text-transform: uppercase; } -.health-badge i { +.legacy .health-badge i { background: var(--accent); } -.health-badge.unhealthy { +.legacy .health-badge.unhealthy { border-color: rgba(236, 143, 133, 0.25); color: var(--red); background: var(--red-wash); } -.health-badge.unhealthy i { +.legacy .health-badge.unhealthy i { background: var(--red); } -.worker-stats { +.legacy .worker-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 15px 0 0; @@ -2456,17 +2456,17 @@ time { background: rgba(9, 14, 16, 0.24); } -.worker-stats > div { +.legacy .worker-stats > div { min-width: 0; padding: 9px; border-right: 1px solid var(--line); } -.worker-stats > div:last-child { +.legacy .worker-stats > div:last-child { border: 0; } -.worker-stats dd { +.legacy .worker-stats dd { overflow: hidden; margin: 0; color: var(--text-soft); @@ -2476,22 +2476,22 @@ time { white-space: nowrap; } -.worker-stats dd.health-reachable { +.legacy .worker-stats dd.health-reachable { color: var(--accent); } -.worker-stats dd.health-unreachable { +.legacy .worker-stats dd.health-unreachable { color: var(--red); } .worker-current, -.worker-projects { +.legacy .worker-projects { display: grid; gap: 7px; margin-top: 14px; } -.worker-current > a { +.legacy .worker-current > a { display: flex; align-items: center; justify-content: space-between; @@ -2504,18 +2504,18 @@ time { text-decoration: none; } -.worker-current > a:hover { +.legacy .worker-current > a:hover { border-color: var(--accent-line); color: var(--accent); } -.worker-current > a > span { +.legacy .worker-current > a > span { display: grid; min-width: 0; gap: 3px; } -.worker-current a b { +.legacy .worker-current a b { overflow: hidden; color: var(--text-soft); font: 10px var(--mono); @@ -2523,12 +2523,12 @@ time { white-space: nowrap; } -.worker-current a small { +.legacy .worker-current a small { color: var(--text-muted); font: 9px var(--mono); } -.worker-current > p { +.legacy .worker-current > p { margin: 0; padding: 9px; border: 1px dashed var(--line); @@ -2537,11 +2537,11 @@ time { font-size: 10px; } -.worker-projects > div { +.legacy .worker-projects > div { margin: 0; } -.worker-error { +.legacy .worker-error { display: flex; align-items: flex-start; gap: 8px; @@ -2553,22 +2553,22 @@ time { background: var(--red-wash); } -.worker-error > span { +.legacy .worker-error > span { display: grid; gap: 3px; } .worker-error b, -.worker-error small { +.legacy .worker-error small { font-size: 9px; font-weight: 500; } -.worker-error small { +.legacy .worker-error small { color: var(--text-muted); } -.worker-card > footer { +.legacy .worker-card > footer { display: flex; justify-content: space-between; gap: 10px; @@ -2581,11 +2581,11 @@ time { /* Account settings */ -.settings-page { +.legacy .settings-page { max-width: 1160px; } -.settings-layout { +.legacy .settings-layout { display: grid; grid-template-columns: 300px minmax(0, 1fr); align-items: start; @@ -2593,18 +2593,18 @@ time { } .profile-card, -.settings-card { +.legacy .settings-card { border: 1px solid var(--line); border-radius: var(--radius-lg); background: linear-gradient(145deg, rgba(21, 29, 33, 0.94), rgba(14, 20, 23, 0.94)); box-shadow: 0 18px 50px rgba(0, 0, 0, 0.14); } -.profile-card { +.legacy .profile-card { padding: 24px; } -.profile-avatar { +.legacy .profile-avatar { display: grid; width: 58px; height: 58px; @@ -2618,20 +2618,20 @@ time { font: 600 16px var(--mono); } -.profile-card h2 { +.legacy .profile-card h2 { margin: 0; font-size: 20px; letter-spacing: -0.035em; overflow-wrap: anywhere; } -.profile-card > p { +.legacy .profile-card > p { margin: 5px 0 22px; color: var(--text-muted); font-size: 11px; } -.profile-card dl { +.legacy .profile-card dl { display: grid; gap: 12px; margin: 0; @@ -2640,12 +2640,12 @@ time { border-bottom: 1px solid var(--line); } -.profile-card dl > div { +.legacy .profile-card dl > div { display: grid; gap: 4px; } -.profile-card dt { +.legacy .profile-card dt { color: var(--text-muted); font-size: 9px; font-weight: 700; @@ -2653,13 +2653,13 @@ time { text-transform: uppercase; } -.profile-card dd { +.legacy .profile-card dd { margin: 0; color: var(--text-soft); font: 9px var(--mono); } -.database-badge { +.legacy .database-badge { display: flex; align-items: flex-start; gap: 10px; @@ -2671,27 +2671,27 @@ time { background: var(--accent-wash); } -.database-badge > span { +.legacy .database-badge > span { display: grid; gap: 3px; } -.database-badge b { +.legacy .database-badge b { color: var(--accent-strong); font-size: 10px; } -.database-badge small { +.legacy .database-badge small { color: var(--text-soft); font-size: 9px; line-height: 1.5; } -.settings-card { +.legacy .settings-card { overflow: hidden; } -.settings-card > header { +.legacy .settings-card > header { display: flex; align-items: flex-start; gap: 13px; @@ -2700,7 +2700,7 @@ time { background: rgba(17, 23, 27, 0.55); } -.settings-icon { +.legacy .settings-icon { display: grid; width: 38px; height: 38px; @@ -2712,26 +2712,26 @@ time { background: var(--accent-wash); } -.settings-card h2 { +.legacy .settings-card h2 { margin: 1px 0 0; font-size: 16px; letter-spacing: -0.025em; } -.settings-card header p { +.legacy .settings-card header p { margin: 5px 0 0; color: var(--text-muted); font-size: 10px; } -.settings-card form { +.legacy .settings-card form { display: grid; gap: 9px; padding: 24px; } .settings-card label, -.current-password-block > label { +.legacy .current-password-block > label { display: grid; gap: 7px; color: var(--text-soft); @@ -2739,12 +2739,12 @@ time { font-weight: 650; } -.settings-field { +.legacy .settings-field { display: grid; gap: 7px; } -.settings-divider { +.legacy .settings-divider { display: flex; align-items: center; gap: 12px; @@ -2756,21 +2756,21 @@ time { text-transform: uppercase; } -.settings-divider::after { +.legacy .settings-divider::after { height: 1px; flex: 1; background: var(--line); content: ""; } -.password-rules { +.legacy .password-rules { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 3px; } -.password-rules span { +.legacy .password-rules span { display: inline-flex; align-items: center; gap: 4px; @@ -2782,13 +2782,13 @@ time { font-size: 9px; } -.password-rules span.met { +.legacy .password-rules span.met { border-color: var(--accent-line); color: var(--accent); background: var(--accent-wash); } -.current-password-block { +.legacy .current-password-block { display: grid; grid-template-columns: minmax(0, 1fr) minmax(220px, 0.65fr); align-items: end; @@ -2800,23 +2800,23 @@ time { background: var(--surface-1); } -.current-password-block label { +.legacy .current-password-block label { grid-column: 1; } -.current-password-block p { +.legacy .current-password-block p { grid-column: 1; margin: 0; color: var(--text-muted); font-size: 9px; } -.current-password-block input { +.legacy .current-password-block input { grid-column: 2; grid-row: 1 / span 2; } -.settings-actions { +.legacy .settings-actions { display: flex; align-items: center; justify-content: space-between; @@ -2827,25 +2827,25 @@ time { background: rgba(10, 15, 17, 0.28); } -.settings-actions > span { +.legacy .settings-actions > span { color: var(--text-muted); font-size: 9px; } /* Artifact and login */ -.artifact-header { +.legacy .artifact-header { align-items: flex-end; } -.artifact-content { +.legacy .artifact-content { min-height: 400px; padding: 20px; font-size: 11px; line-height: 1.7; } -.login-page { +.legacy .login-page { position: relative; display: grid; min-height: 100vh; @@ -2858,7 +2858,7 @@ time { #090d0f; } -.login-grid { +.legacy .login-grid { position: absolute; inset: 0; opacity: 0.24; @@ -2869,7 +2869,7 @@ time { mask-image: radial-gradient(circle at center, black, transparent 68%); } -.login-layout { +.legacy .login-layout { position: relative; display: grid; width: min(1040px, 100%); @@ -2877,7 +2877,7 @@ time { gap: 14px; } -.login-showcase { +.legacy .login-showcase { position: relative; display: flex; min-height: 610px; @@ -2892,7 +2892,7 @@ time { box-shadow: 0 30px 90px rgba(0, 0, 0, 0.28); } -.login-showcase::before { +.legacy .login-showcase::before { position: absolute; width: 430px; height: 430px; @@ -2906,36 +2906,36 @@ time { content: ""; } -.showcase-brand { +.legacy .showcase-brand { display: flex; align-items: center; gap: 12px; } -.showcase-brand > span:last-child { +.legacy .showcase-brand > span:last-child { display: grid; gap: 2px; } -.showcase-brand b { +.legacy .showcase-brand b { font-size: 14px; letter-spacing: -0.02em; } -.showcase-brand small { +.legacy .showcase-brand small { color: var(--text-muted); font: 8px var(--mono); letter-spacing: 0.1em; text-transform: uppercase; } -.showcase-copy { +.legacy .showcase-copy { position: relative; max-width: 500px; margin: auto 0 42px; } -.showcase-copy h2 { +.legacy .showcase-copy h2 { margin: 0; font-size: clamp(38px, 4.2vw, 58px); font-weight: 620; @@ -2943,7 +2943,7 @@ time { letter-spacing: -0.064em; } -.showcase-copy p { +.legacy .showcase-copy p { max-width: 460px; margin: 20px 0 0; color: var(--text-soft); @@ -2951,7 +2951,7 @@ time { line-height: 1.7; } -.showcase-flow { +.legacy .showcase-flow { display: grid; grid-template-columns: auto minmax(16px, 1fr) auto minmax(16px, 1fr) auto minmax(16px, 1fr) auto; align-items: center; @@ -2963,7 +2963,7 @@ time { background: rgba(8, 13, 15, 0.34); } -.showcase-flow > span { +.legacy .showcase-flow > span { display: inline-flex; align-items: center; gap: 6px; @@ -2973,12 +2973,12 @@ time { text-transform: uppercase; } -.showcase-flow > b { +.legacy .showcase-flow > b { height: 1px; background: linear-gradient(90deg, var(--line-strong), var(--accent-line)); } -.flow-dot { +.legacy .flow-dot { display: block; width: 6px; height: 6px; @@ -2988,17 +2988,17 @@ time { } .flow-dot.active, -.flow-dot.done { +.legacy .flow-dot.done { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-wash); } -.flow-dot.review { +.legacy .flow-dot.review { background: var(--violet); box-shadow: 0 0 0 3px var(--violet-wash); } -.login-showcase > footer { +.legacy .login-showcase > footer { display: flex; align-items: center; gap: 9px; @@ -3010,11 +3010,11 @@ time { text-transform: uppercase; } -.login-showcase > footer .signal { +.legacy .login-showcase > footer .signal { margin: 0; } -.login-card { +.legacy .login-card { position: relative; width: 100%; align-self: center; @@ -3025,47 +3025,47 @@ time { box-shadow: 0 30px 90px rgba(0, 0, 0, 0.5); } -.login-brand { +.legacy .login-brand { display: flex; align-items: center; gap: 11px; margin-bottom: 38px; } -.login-heading h1 { +.legacy .login-heading h1 { margin: 0; font-size: 36px; letter-spacing: -0.055em; } -.login-heading > p { +.legacy .login-heading > p { margin: 8px 0 0; color: var(--text-soft); font-size: 12px; line-height: 1.55; } -.login-card form { +.legacy .login-card form { display: grid; gap: 9px; margin-top: 24px; } -.login-card form > label { +.legacy .login-card form > label { color: var(--text-soft); font-size: 10px; font-weight: 650; } -.password-field { +.legacy .password-field { position: relative; } -.password-field input { +.legacy .password-field input { padding-right: 62px; } -.password-field button { +.legacy .password-field button { position: absolute; top: 50%; right: 5px; @@ -3078,23 +3078,23 @@ time { font-size: 10px; } -.password-field button:hover:not(:disabled) { +.legacy .password-field button:hover:not(:disabled) { border-color: transparent; background: var(--accent-wash); transform: translateY(-50%); } -.caps-lock { +.legacy .caps-lock { color: var(--amber); font-size: 10px; } -.login-submit { +.legacy .login-submit { min-height: 42px; margin-top: 7px; } -.login-security { +.legacy .login-security { display: flex; gap: 9px; margin-top: 23px; @@ -3103,7 +3103,7 @@ time { color: var(--text-muted); } -.login-security > span { +.legacy .login-security > span { display: grid; width: 24px; height: 24px; @@ -3114,13 +3114,13 @@ time { background: var(--accent-wash); } -.login-security p { +.legacy .login-security p { margin: 0; font-size: 10px; line-height: 1.55; } -.session-check { +.legacy .session-check { position: relative; display: flex; align-items: center; @@ -3134,7 +3134,7 @@ time { font-size: 11px; } -.spinner { +.legacy .spinner { display: inline-block; width: 13px; height: 13px; @@ -3151,31 +3151,31 @@ time { /* Responsive layout */ @media (max-width: 1180px) { - .metrics { + .legacy .metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .diagnosis { + .legacy .diagnosis { grid-template-columns: 1fr; } - .task-board.lanes-3 { + .legacy .task-board.lanes-3 { grid-template-columns: repeat(3, minmax(235px, 1fr)); } } @media (max-width: 980px) { - .app-shell { + .legacy .app-shell { padding-left: 72px; } - .sidebar { + .legacy .sidebar { width: 72px; padding-right: 10px; padding-left: 10px; } - .brand { + .legacy .brand { justify-content: center; margin-right: -10px; margin-left: -10px; @@ -3187,11 +3187,11 @@ time { .primary-nav a > span, .nav-count, .sidebar-status > span:last-child, - .sidebar-foot { + .legacy .sidebar-foot { display: none; } - .primary-nav a { + .legacy .primary-nav a { display: grid; width: 50px; grid-template-columns: 1fr; @@ -3199,108 +3199,108 @@ time { padding: 0; } - .primary-nav a.active::before { + .legacy .primary-nav a.active::before { left: -11px; } - .sidebar-status { + .legacy .sidebar-status { justify-content: center; padding: 11px; } - .topbar { + .legacy .topbar { left: 72px; } - .detail-layout { + .legacy .detail-layout { grid-template-columns: minmax(0, 1fr) 300px; } - .login-layout { + .legacy .login-layout { grid-template-columns: minmax(0, 1fr) 390px; } - .login-showcase { + .legacy .login-showcase { padding: 30px; } } @media (max-width: 800px) { - .page { + .legacy .page { padding-right: 20px; padding-left: 20px; } - .detail-layout { + .legacy .detail-layout { grid-template-columns: 1fr; } - .detail-side { + .legacy .detail-side { grid-template-columns: 1fr 1fr; align-items: start; } - .timeline-panel { + .legacy .timeline-panel { grid-column: 1 / -1; } - .board-toolbar { + .legacy .board-toolbar { flex-wrap: wrap; } - .segmented { + .legacy .segmented { width: 100%; margin-right: 0; } - .segmented button { + .legacy .segmented button { flex: 1; } - .search-field { + .legacy .search-field { width: calc(100% - 164px); } - .settings-layout { + .legacy .settings-layout { grid-template-columns: 1fr; } - .profile-card { + .legacy .profile-card { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 16px; } - .profile-avatar { + .legacy .profile-avatar { grid-row: span 2; margin: 0; } - .profile-card > p { + .legacy .profile-card > p { margin: 3px 0 16px; } .profile-card dl, - .database-badge { + .legacy .database-badge { grid-column: 1 / -1; } - .login-layout { + .legacy .login-layout { width: min(430px, 100%); grid-template-columns: 1fr; } - .login-showcase { + .legacy .login-showcase { display: none; } } @media (max-width: 680px) { - .app-shell { + .legacy .app-shell { padding: 0 0 64px; } - .sidebar { + .legacy .sidebar { inset: auto 0 0; width: 100%; height: 64px; @@ -3310,11 +3310,11 @@ time { } .brand, - .sidebar-status { + .legacy .sidebar-status { display: none; } - .primary-nav { + .legacy .primary-nav { display: flex; height: 100%; justify-content: space-around; @@ -3322,7 +3322,7 @@ time { margin: 0; } - .primary-nav a { + .legacy .primary-nav a { width: auto; flex: 1; grid-template-columns: auto auto; @@ -3330,11 +3330,11 @@ time { gap: 7px; } - .primary-nav a > span { + .legacy .primary-nav a > span { display: inline; } - .primary-nav a.active::before { + .legacy .primary-nav a.active::before { inset: -7px auto auto 50%; width: 26px; height: 3px; @@ -3342,13 +3342,13 @@ time { border-radius: 0 0 4px 4px; } - .topbar { + .legacy .topbar { left: 0; height: 56px; padding: 0 15px; } - .content { + .legacy .content { padding-top: 56px; } @@ -3356,67 +3356,67 @@ time { .topbar-context .icon, .command-trigger span, .command-trigger kbd, - .account-label { + .legacy .account-label { display: none; } - .command-trigger { + .legacy .command-trigger { width: 34px; margin-left: auto; padding: 0; } - .account-trigger { + .legacy .account-trigger { padding: 3px; } - .page { + .legacy .page { padding: 27px 15px 45px; } .page-header, - .task-header { + .legacy .task-header { align-items: flex-start; flex-direction: column; gap: 18px; } - .page-header h1 { + .legacy .page-header h1 { font-size: 34px; } - .page-actions { + .legacy .page-actions { width: 100%; } - .page-actions > button:last-child { + .legacy .page-actions > button:last-child { flex: 1; } - .metrics { + .legacy .metrics { grid-template-columns: 1fr 1fr; } - .metric { + .legacy .metric { grid-template-columns: 1fr; } - .metric-icon { + .legacy .metric-icon { display: none; } - .attention-banner { + .legacy .attention-banner { grid-template-columns: auto minmax(0, 1fr); } - .attention-banner > a { + .legacy .attention-banner > a { grid-column: 2; } .task-board.lanes-2, .task-board.lanes-3, .task-board.lanes-5, - .task-board.lanes-7 { + .legacy .task-board.lanes-7 { grid-template-columns: repeat(var(--mobile-lanes, 5), minmax(255px, 82vw)); } @@ -3425,142 +3425,142 @@ time { .task-board.lanes-5 { --mobile-lanes: 5; } .task-board.lanes-7 { --mobile-lanes: 7; } - .task-lane { + .legacy .task-lane { min-height: 270px; } .form-row, - .detail-side { + .legacy .detail-side { grid-template-columns: 1fr; } - .timeline-panel { + .legacy .timeline-panel { grid-column: auto; } - .diagnosis dl { + .legacy .diagnosis dl { grid-template-columns: 1fr 1fr; } - .diagnosis dl > div:nth-child(2) { + .legacy .diagnosis dl > div:nth-child(2) { border-right: 0; } - .diagnosis dl > div:nth-child(-n + 2) { + .legacy .diagnosis dl > div:nth-child(-n + 2) { border-bottom: 1px solid var(--line); } - .approval-panel { + .legacy .approval-panel { padding: 14px; } - .approval-actions { + .legacy .approval-actions { align-items: stretch; flex-direction: column; } .action-card, - .action-form { + .legacy .action-form { grid-template-columns: auto minmax(0, 1fr); } .action-card > button, - .action-buttons { + .legacy .action-buttons { grid-column: 2; justify-self: start; } - .worker-grid { + .legacy .worker-grid { grid-template-columns: 1fr; } - .worker-card > header { + .legacy .worker-card > header { align-items: flex-start; } - .overlay { + .legacy .overlay { padding: 10px; } - .modal { + .legacy .modal { max-height: calc(100vh - 20px); } .modal-header, - .create-modal form { + .legacy .create-modal form { padding-right: 16px; padding-left: 16px; } - .eligibility { + .legacy .eligibility { margin-right: 16px; margin-left: 16px; } - .artifact-header .task-id { + .legacy .artifact-header .task-id { overflow-wrap: anywhere; } - .login-card { + .legacy .login-card { padding: 22px; } - .current-password-block { + .legacy .current-password-block { grid-template-columns: 1fr; } - .current-password-block input { + .legacy .current-password-block input { grid-column: 1; grid-row: auto; margin-top: 5px; } - .settings-actions { + .legacy .settings-actions { align-items: stretch; flex-direction: column; } - .settings-actions button { + .legacy .settings-actions button { width: 100%; } - .skeleton-grid { + .legacy .skeleton-grid { grid-template-columns: 1fr; } } @media (max-width: 460px) { - .refresh-button span { + .legacy .refresh-button span { display: none; } - .search-field { + .legacy .search-field { width: 100%; } - .select-field { + .legacy .select-field { width: 100%; } - .metrics { + .legacy .metrics { grid-template-columns: 1fr 1fr; gap: 7px; } - .metric { + .legacy .metric { padding: 11px; } - .worker-stats { + .legacy .worker-stats { grid-template-columns: 1fr; } - .worker-stats > div { + .legacy .worker-stats > div { border-right: 0; border-bottom: 1px solid var(--line); } - .worker-stats > div:last-child { + .legacy .worker-stats > div:last-child { border-bottom: 0; } } diff --git a/web/src/styles/ethos.tokens.css b/web/src/styles/ethos.tokens.css new file mode 100644 index 0000000..db40891 --- /dev/null +++ b/web/src/styles/ethos.tokens.css @@ -0,0 +1,136 @@ +/* ═══════════════════════════════════════════════════════════════ + ETHOS — tokens + Shared design language for the kvmx.ru apps. + + The neutral system (ramp, type, motion, radii, shadow) is shared by + every app and does not change. The only per-app difference is the + accent (one hue) and the motif (one geometric signature). + + NEW APP: do not invent the accent silently. Ask the operator for the + accent name + hex and the motif, then add one [data-app] block at the + bottom using the TEMPLATE. Fonts are self-hosted (subset + woff2, + immutable caching) — no font CDN. + ═══════════════════════════════════════════════════════════════ */ + +@font-face { + font-family: 'Geist'; + src: url('/fonts/Geist-Variable.woff2') format('woff2'); + font-weight: 100 900; font-display: swap; +} +@font-face { + font-family: 'Geist Mono'; + src: url('/fonts/GeistMono-Variable.woff2') format('woff2'); + font-weight: 100 900; font-display: swap; +} + +:root { + /* ── neutral ramp — warm, brown-tinted (dark, default) ── */ + --bg-0: #14110D; /* deepest room */ + --bg-1: #1B1712; /* surface */ + --bg-2: #221D17; /* raised */ + --bg-3: #2C261D; /* hover / raised */ + --bg-4: #372F24; /* pressed / high */ + + --line: rgba(244, 234, 220, 0.09); /* hairline */ + --line-hi: rgba(244, 234, 220, 0.16); /* hairline emphasized */ + + --text-hi: #F4EEE4; /* human primary (sans) */ + --text-mid: #B4AA98; /* human secondary (sans) */ + --text-lo: #756C5C; /* human tertiary / idle */ + --text-machine: #9C917D; /* machine default (mono) */ + + /* ── type ── */ + --sans: 'Geist', -apple-system, system-ui, sans-serif; + --mono: 'Geist Mono', ui-monospace, 'SF Mono', Menlo, monospace; + + /* mono numerics: apply on any element using --mono */ + /* font-feature-settings: "tnum" 1, "zero" 1; letter-spacing: -0.01em; */ + + /* ── motion — mechanical, no bounce ── */ + --ease: cubic-bezier(0.2, 0, 0, 1); + --fast: 130ms; + --med: 170ms; + + /* ── radii ── */ + --r-sm: 8px; + --r-md: 12px; + --r-lg: 18px; + --r-xl: 24px; + + /* ── elevation: depth from light, never blur ── */ + --shadow-soft: 0 2px 8px rgba(0,0,0,0.35), 0 12px 32px rgba(0,0,0,0.28); + --shadow-lift: 0 4px 14px rgba(0,0,0,0.40), 0 20px 48px rgba(0,0,0,0.34); + + /* ── accent slot ── + Neutral fallback so an app with no [data-app] is never unstyled. + Real values come from the per-app block below. Accent is a SIGNAL + (active state, focus ring, primary action, one lit detail) — never a + fill-everything wash. */ + --accent: var(--text-mid); + --accent-hi: var(--text-hi); + --accent-dim: rgba(244, 234, 220, 0.10); + --accent-line: rgba(244, 234, 220, 0.24); + --accent-glow: rgba(244, 234, 220, 0.14); +} + +/* ── light theme — warm, off-white (kept off cream to dodge the AI-cream tell) ── */ +[data-theme="light"] { + --bg-0: #F1ECE3; + --bg-1: #EAE4D8; + --bg-2: #E2DACB; + --bg-3: #D7CDBB; + --bg-4: #C9BDA7; + + --line: rgba(28, 22, 14, 0.10); + --line-hi: rgba(28, 22, 14, 0.18); + + --text-hi: #1B1712; + --text-mid: #544C3E; + --text-lo: #877E6C; + --text-machine: #6B6252; + + --shadow-soft: 0 2px 8px rgba(60,45,25,0.10), 0 12px 32px rgba(60,45,25,0.08); + --shadow-lift: 0 4px 14px rgba(60,45,25,0.12), 0 20px 48px rgba(60,45,25,0.10); +} + +/* ═══════════════════════════════════════════════════════════════ + PER-APP OVERRIDES — one small block each. Accent + optional motif + hooks only; never touch the neutral ramp. + ═══════════════════════════════════════════════════════════════ */ + +/* muzick — reference app · honey amber · waveform */ +[data-app="muzick"] { + --accent: #EDA24E; + --accent-hi: #F5B667; + --accent-dim: rgba(237, 162, 78, 0.14); + --accent-line: rgba(237, 162, 78, 0.32); + --accent-glow: rgba(237, 162, 78, 0.22); + + /* motif hook: cool content-art gradient so art carries color, amber stays signal */ + --np-art: radial-gradient(120% 120% at 22% 14%, #7FB3BC 0%, #2F6E7A 42%, #1C4552 78%, #14262E 100%); +} + +/* ── TEMPLATE — copy for a new app AFTER intake with the operator ── +[data-app="APPNAME"] { + --accent: #RRGGBB; // the operator's chosen hue + --accent-hi: #RRGGBB; // ~ +10–14% lightness + --accent-dim: rgba(R, G, B, 0.14); // active-state backgrounds + --accent-line: rgba(R, G, B, 0.32); // accent hairlines + --accent-glow: rgba(R, G, B, 0.22); // restrained ambient pool + // optional motif hooks (gradients / seeds) go here, app-specific +} +*/ + +/* orchestra — signal violet · routing fork + Accent and motif come from orchestra-ui-spec.md, chosen by the operator. */ +[data-app="orchestra"] { + --accent: #8F7AE5; + --accent-hi: #A291EC; + --accent-dim: rgba(143, 122, 229, 0.14); + --accent-line: rgba(143, 122, 229, 0.32); + --accent-glow: rgba(143, 122, 229, 0.22); + + /* motif hook: the routing fork, one path splitting and rejoining. Used by + the app mark, phase transitions and empty states. */ + --fork-line: rgba(143, 122, 229, 0.55); +} diff --git a/web/src/styles/orchestra.css b/web/src/styles/orchestra.css new file mode 100644 index 0000000..6327329 --- /dev/null +++ b/web/src/styles/orchestra.css @@ -0,0 +1,679 @@ +/* Orchestra operator console — the ethos shell, applied. + Tokens come from ethos.tokens.css and are never redefined here. This file + holds the shared shell geometry and the primitives every screen reuses, so + nine screens cannot drift into nine dialects. */ + +@import './ethos.tokens.css'; + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body, +#root { + height: 100%; +} + +body { + margin: 0; + background: var(--bg-0); + color: var(--text-hi); + font-family: var(--sans); + font-size: 14px; + line-height: 1.45; + -webkit-font-smoothing: antialiased; +} + +/* Law 1. Anything the machine owns is mono, with tabular figures so columns + of ids, durations and percentages line up instead of shimmering. */ +.mono, +code, +kbd, +pre { + font-family: var(--mono); + font-feature-settings: 'tnum' 1, 'zero' 1; + letter-spacing: -0.01em; + color: var(--text-machine); +} + +/* Machine ids are long and unbreakable by default, so a pane id ran out of + its card and under the next one. Ids wrap; tables keep their own scroll. */ +.console .mono { + overflow-wrap: anywhere; +} + +.console .table .mono, +.console .topbar-readouts .mono { + overflow-wrap: normal; +} + +.console .label { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--text-lo); + font-weight: 500; +} + +.console h1 { + font-size: 28px; + line-height: 1.15; + letter-spacing: -0.02em; + margin: 0; + font-weight: 600; +} + +.console h2 { + font-size: 17px; + margin: 0; + font-weight: 600; + letter-spacing: -0.01em; +} + +.console a { + color: inherit; + text-decoration: none; +} + +.console button { + font: inherit; + color: inherit; +} + +/* ── shell ───────────────────────────────────────────────────────────── */ + +.console.shell { + display: grid; + grid-template-columns: 64px 1fr; + height: 100%; +} + +.console .rail { + background: var(--bg-1); + border-right: 1px solid var(--line); + display: flex; + flex-direction: column; + align-items: center; + padding: 14px 0 10px; + gap: 4px; +} + +.console .rail-mark { + color: var(--accent); + margin-bottom: 14px; +} + +.console .rail-item { + position: relative; + width: 44px; + height: 44px; + display: grid; + place-items: center; + border-radius: var(--r-sm); + color: var(--text-lo); + background: none; + border: 0; + cursor: pointer; + transition: color var(--fast) var(--ease), background var(--fast) var(--ease); +} + +.console .rail-item:hover { + color: var(--text-mid); + background: var(--bg-2); +} + +/* Accent marks the one active thing. It never fills the rail. */ +.console .rail-item[aria-current='page'] { + color: var(--accent); + background: var(--accent-dim); +} + +.console .rail-item[aria-current='page']::before { + content: ''; + position: absolute; + left: -10px; + top: 10px; + bottom: 10px; + width: 3px; + border-radius: 0 2px 2px 0; + background: var(--accent); +} + +.console .rail-spacer { + flex: 1; +} + +.console .topbar { + height: 56px; + display: flex; + align-items: center; + gap: 16px; + padding: 0 20px; + border-bottom: 1px solid var(--line); + background: var(--bg-1); +} + +.console .topbar-title { + font-weight: 600; + letter-spacing: -0.01em; +} + +.console .topbar-readouts { + margin-left: auto; + display: flex; + align-items: center; + gap: 18px; + font-family: var(--mono); + font-size: 12px; + color: var(--text-machine); +} + +.console .main { + display: grid; + grid-template-rows: 56px 1fr; + min-width: 0; + height: 100%; +} + +.console .page { + overflow-y: auto; + padding: 22px 24px 40px; + display: flex; + flex-direction: column; + gap: 20px; +} + +.console .page-head { + display: flex; + align-items: flex-end; + gap: 14px; + flex-wrap: wrap; +} + +.console .page-head p { + margin: 0; + color: var(--text-mid); +} + +/* ── command surface ─────────────────────────────────────────────────── */ + +.console .cmdk-trigger { + display: flex; + align-items: center; + gap: 10px; + height: 34px; + min-width: 260px; + max-width: 520px; + flex: 1; + padding: 0 12px; + background: var(--bg-2); + border: 1px solid var(--line); + border-radius: var(--r-sm); + color: var(--text-lo); + cursor: pointer; + transition: border-color var(--fast) var(--ease); +} + +.console .cmdk-trigger:hover { + border-color: var(--line-hi); +} + +kbd { + font-size: 11px; + padding: 2px 6px; + border: 1px solid var(--line); + border-radius: 5px; + background: var(--bg-1); +} + +.console .cmdk-scrim { + position: fixed; + inset: 0; + background: rgba(10, 8, 6, 0.62); + display: grid; + place-items: start center; + padding-top: 12vh; + z-index: 40; +} + +.console .cmdk-panel { + width: min(620px, 92vw); + background: var(--bg-1); + border: 1px solid var(--line-hi); + border-radius: var(--r-md); + box-shadow: var(--shadow-soft); + overflow: hidden; +} + +.console .cmdk-input { + width: 100%; + height: 50px; + padding: 0 16px; + background: none; + border: 0; + border-bottom: 1px solid var(--line); + color: var(--text-hi); + font: inherit; + outline: none; +} + +.console .cmdk-list { + max-height: 46vh; + overflow-y: auto; + padding: 6px; +} + +.console .cmdk-row { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 9px 10px; + border: 0; + border-radius: var(--r-sm); + background: none; + text-align: left; + cursor: pointer; +} + +.console .cmdk-row[data-active='true'] { + background: var(--bg-3); +} + +/* ── panels and tables ───────────────────────────────────────────────── */ + +.console .panel { + background: var(--bg-1); + border: 1px solid var(--line); + border-radius: var(--r-md); +} + +.console .panel-head { + display: flex; + align-items: center; + gap: 10px; + padding: 14px 18px; + border-bottom: 1px solid var(--line); +} + +.console .panel-head .count { + font-family: var(--mono); + font-size: 11px; + padding: 1px 7px; + border-radius: 999px; + background: var(--bg-3); + color: var(--text-machine); +} + +.console .panel-head .trailing { + margin-left: auto; + color: var(--text-lo); + font-size: 13px; +} + +.console .panel-body { + padding: 14px 18px; +} + +.console .rows { + display: flex; + flex-direction: column; +} + +.console .row { + display: flex; + align-items: center; + gap: 16px; + padding: 12px 18px; + border-bottom: 1px solid var(--line); + text-align: left; + background: none; + border-left: 0; + border-right: 0; + border-top: 0; + width: 100%; + cursor: pointer; + transition: background var(--fast) var(--ease); +} + +.console .row:last-child { + border-bottom: 0; +} + +.console .row:hover { + background: var(--bg-2); +} + +.console .row-main { + min-width: 0; + flex: 1; +} + +.console .row-title { + font-weight: 500; +} + +.console .row-sub { + color: var(--text-mid); + font-size: 13px; +} + +.console .table { + width: 100%; + border-collapse: collapse; +} + +.console .table th { + text-align: left; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--text-lo); + font-weight: 500; + padding: 10px 14px; + border-bottom: 1px solid var(--line); + white-space: nowrap; +} + +.console .table td { + padding: 12px 14px; + border-bottom: 1px solid var(--line); + vertical-align: middle; +} + +.console .table tbody tr:last-child td { + border-bottom: 0; +} + +.console .table tbody tr:hover { + background: var(--bg-2); +} + +.console .table-scroll { + overflow-x: auto; +} + +/* ── status, phases, values ──────────────────────────────────────────── */ + +.console .dot { + width: 7px; + height: 7px; + border-radius: 50%; + display: inline-block; + flex: none; +} + +.console .dot[data-health='healthy'] { + background: #5fbf82; +} + +.console .dot[data-health='degraded'] { + background: #d8a657; +} + +.console .dot[data-health='error'] { + background: #e06c60; +} + +.console .dot[data-health='unknown'] { + background: var(--text-lo); +} + +.console .chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 3px 9px; + border-radius: 6px; + font-size: 12px; + border: 1px solid var(--line); + background: var(--bg-2); + color: var(--text-mid); + white-space: nowrap; +} + +.console .chip[data-tone='accent'] { + border-color: var(--accent-line); + background: var(--accent-dim); + color: var(--accent-hi); +} + +.console .chip[data-tone='warn'] { + border-color: rgba(216, 166, 87, 0.32); + background: rgba(216, 166, 87, 0.12); + color: #d8a657; +} + +.console .chip[data-tone='fault'] { + border-color: rgba(224, 108, 96, 0.32); + background: rgba(224, 108, 96, 0.12); + color: #e06c60; +} + +.console .chip[data-tone='done'] { + border-color: rgba(95, 191, 130, 0.28); + background: rgba(95, 191, 130, 0.1); + color: #5fbf82; +} + +/* The routing fork, rendered as the phase path. Completed is quiet green, + the current phase carries the accent, the future stays neutral. */ +.console .phases { + display: flex; + align-items: center; + gap: 0; +} + +.console .phase-step { + display: flex; + flex-direction: column; + align-items: center; + gap: 3px; + min-width: 46px; +} + +.console .phase-node { + width: 9px; + height: 9px; + border-radius: 50%; + border: 1.6px solid var(--text-lo); +} + +.console .phase-step[data-state='done'] .phase-node { + border-color: #5fbf82; + background: rgba(95, 191, 130, 0.35); +} + +.console .phase-step[data-state='current'] .phase-node { + border-color: var(--accent); + background: var(--accent); + box-shadow: 0 0 0 3px var(--accent-dim); +} + +.console .phase-step .phase-name { + font-size: 9px; + color: var(--text-lo); + letter-spacing: 0.04em; +} + +.console .phase-step[data-state='current'] .phase-name { + color: var(--accent-hi); +} + +.console .phase-link { + height: 1.6px; + width: 18px; + background: var(--line-hi); + margin-bottom: 12px; +} + +.console .phase-link[data-state='done'] { + background: rgba(95, 191, 130, 0.4); +} + +.console .meter { + height: 4px; + border-radius: 2px; + background: var(--bg-3); + overflow: hidden; + min-width: 64px; +} + +.console .meter > span { + display: block; + height: 100%; + background: var(--accent); +} + +/* ── actions ─────────────────────────────────────────────────────────── */ + +.console .btn { + display: inline-flex; + align-items: center; + gap: 8px; + height: 32px; + padding: 0 13px; + border-radius: var(--r-sm); + border: 1px solid var(--line); + background: var(--bg-2); + color: var(--text-hi); + cursor: pointer; + transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease); +} + +.console .btn:hover { + background: var(--bg-3); + border-color: var(--line-hi); +} + +.console .btn[data-variant='primary'] { + background: var(--accent-dim); + border-color: var(--accent-line); + color: var(--accent-hi); +} + +.console .btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + border-radius: 4px; +} + +/* ── empty, error, gap ───────────────────────────────────────────────── */ + +.console .empty { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + padding: 44px 20px; + text-align: center; + color: var(--text-mid); +} + +.console .empty .motif { + color: var(--accent); + opacity: 0.55; +} + +.console .empty h3 { + margin: 0; + font-size: 15px; + color: var(--text-hi); + font-weight: 600; +} + +/* A screen with no endpoint says so, with the path it would call. Law 5: an + interface never implies data it does not have. */ +.console .gap-note { + font-family: var(--mono); + font-size: 12px; + color: var(--text-machine); + background: var(--bg-2); + border: 1px dashed var(--line-hi); + border-radius: var(--r-sm); + padding: 8px 12px; +} + +.console .grid { + display: grid; + gap: 14px; +} + +.console .stats { + grid-template-columns: repeat(auto-fit, minmax(184px, 1fr)); +} + +.console .stat { + background: var(--bg-1); + border: 1px solid var(--line); + border-radius: var(--r-md); + padding: 14px 16px; + display: flex; + flex-direction: column; + gap: 8px; +} + +.console .stat-value { + font-family: var(--mono); + font-size: 26px; + color: var(--text-hi); + letter-spacing: -0.02em; +} + +/* ── mobile: rail becomes a bottom tab bar, readouts relocate ────────── */ + +@media (max-width: 640px) { + .console.shell { + grid-template-columns: 1fr; + grid-template-rows: 1fr auto; + } + + .console .rail { + order: 2; + flex-direction: row; + justify-content: space-around; + width: 100%; + padding: 4px 0; + border-right: 0; + border-top: 1px solid var(--line); + } + + .rail-mark, + .rail-spacer, + .console .rail-item[data-secondary='true'] { + display: none; + } + + .console .rail-item[aria-current='page']::before { + left: 10px; + right: 10px; + top: -4px; + bottom: auto; + width: auto; + height: 3px; + border-radius: 0 0 2px 2px; + } + + .console .topbar-readouts { + display: none; + } + + .console .page { + padding: 16px 14px 28px; + } +} + +@media (prefers-reduced-motion: reduce) { + * { + transition: none !important; + animation: none !important; + } +}