spexcode 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/spec-cli/src/anchors.ts +341 -0
- package/spec-cli/src/cli.ts +20 -20
- package/spec-cli/src/gateway.ts +8 -1
- package/spec-cli/src/git.ts +21 -13
- package/spec-cli/src/graph.ts +13 -7
- package/spec-cli/src/guide.ts +63 -15
- package/spec-cli/src/harness.ts +85 -34
- package/spec-cli/src/help.ts +33 -17
- package/spec-cli/src/index.ts +39 -7
- package/spec-cli/src/init.ts +5 -4
- package/spec-cli/src/lint.ts +103 -28
- package/spec-cli/src/localIssues.ts +19 -0
- package/spec-cli/src/migrate-table.ts +27 -16
- package/spec-cli/src/search.bench.mjs +2 -2
- package/spec-cli/src/session-timeline.ts +148 -0
- package/spec-cli/src/sessions.ts +29 -4
- package/spec-cli/src/specs.ts +44 -21
- package/spec-cli/templates/hooks/prepare-commit-msg +6 -1
- package/spec-cli/templates/spec/project/.plugins/{extract → commands/extract}/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/commands/spec.md +16 -0
- package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +8 -0
- package/spec-cli/templates/spec/project/.plugins/core/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/prompts/spec.md +20 -0
- package/spec-cli/templates/spec/project/.plugins/skills/spec.md +17 -0
- package/spec-cli/templates/spec/project/.plugins/spec.md +5 -2
- package/spec-cli/templates/spec/project/spec.md +1 -1
- package/spec-dashboard/dist/assets/{Dashboard-C7Bzsv86.js → Dashboard-C_fGmOKK.js} +3 -3
- package/spec-dashboard/dist/assets/EvalsPage-Cnr1s3bq.js +2 -0
- package/spec-dashboard/dist/assets/{FoldToggle-D5iB4Ac2.js → FoldToggle-x9gtO1OQ.js} +1 -1
- package/spec-dashboard/dist/assets/{IssuesPage-CMFTsQhg.js → IssuesPage-5f_vL-JV.js} +1 -1
- package/spec-dashboard/dist/assets/MobileApp-DEO1jgGM.js +1 -0
- package/spec-dashboard/dist/assets/SessionInterface-CAlbMOFR.js +66 -0
- package/spec-dashboard/dist/assets/SessionWindow-JYbpPwNB.js +13 -0
- package/spec-dashboard/dist/assets/{Settings-BW5f0OaW.js → Settings-DKb5Ji_X.js} +1 -1
- package/spec-dashboard/dist/assets/index-BQu-oJ8J.js +41 -0
- package/spec-dashboard/dist/assets/index-BbMkwuix.css +1 -0
- package/spec-dashboard/dist/assets/launch-BM9GgvkX.js +6 -0
- package/spec-dashboard/dist/index.html +2 -2
- package/spec-eval/src/cli.ts +82 -21
- package/spec-eval/src/evaltab.ts +15 -6
- package/spec-eval/src/humanok.ts +43 -0
- package/spec-eval/src/scenarios.ts +116 -4
- package/spec-eval/src/sidecar.ts +35 -9
- package/spec-cli/templates/presets/careful/.plugins/clarify-before-code/spec.md +0 -11
- package/spec-cli/templates/spec/project/.plugins/supervisor/spec.md +0 -8
- package/spec-dashboard/dist/assets/EvalsPage-DKZZIdHq.js +0 -2
- package/spec-dashboard/dist/assets/MobileApp-DwuTKgdP.js +0 -1
- package/spec-dashboard/dist/assets/SessionInterface-CBS5_cmK.js +0 -71
- package/spec-dashboard/dist/assets/SessionWindow-CqAnjWfI.js +0 -9
- package/spec-dashboard/dist/assets/index-Cc26X4ce.css +0 -1
- package/spec-dashboard/dist/assets/index-Ce0wDyQS.js +0 -41
- /package/spec-cli/templates/spec/project/.plugins/{regroup → commands/regroup}/spec.md +0 -0
- /package/spec-cli/templates/spec/project/.plugins/{tidy → commands/tidy}/spec.md +0 -0
- /package/spec-cli/templates/spec/project/.plugins/{forge-link → prompts/forge-link}/spec.md +0 -0
- /package/spec-cli/templates/spec/project/.plugins/{memory-hygiene → prompts/memory-hygiene}/spec.md +0 -0
- /package/spec-cli/templates/spec/project/.plugins/{reproduce-before-fix → prompts/reproduce-before-fix}/spec.md +0 -0
- /package/spec-cli/templates/spec/project/.plugins/{distill → skills/distill}/digest.mjs +0 -0
- /package/spec-cli/templates/spec/project/.plugins/{distill → skills/distill}/spec.md +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--paper: #fdf6e3;--panel: #f4eeda;--panel2:#eee8d5;--line: #ded7bf;--ink: #586e75;--ink2: #073642;--muted: #93a1a1;--blue: #268bd2;--green: #859900;--orange:#cb4b16;--red: #dc322f;--yellow:#b58900;--magenta:#d33682;--cyan: #2aa198;--term-bg: #0d1117;--mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace}:root[data-theme=dark]{--paper: #0d1117;--panel: #161b22;--panel2:#21262d;--line: #30363d;--ink: #c9d1d9;--ink2: #f0f6fc;--muted: #8b949e;--blue: #58a6ff;--green: #3fb950;--orange:#f0883e;--red: #f85149;--yellow:#d29922;--magenta:#bc8cff;--cyan: #39c5cf;--term-bg: #0d1117}*{box-sizing:border-box}html,body,#root{height:100%;margin:0}body{background:var(--paper);color:var(--ink);font-family:var(--mono);font-size:13px;-webkit-font-smoothing:antialiased}*{scrollbar-color:var(--line) transparent;scrollbar-width:thin}::-webkit-scrollbar{width:9px;height:9px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--line);border-radius:6px;border:2px solid var(--panel)}::-webkit-scrollbar-thumb:hover{background:var(--muted)}::-webkit-scrollbar-corner{background:transparent}.app{display:flex;height:100vh}.app-main{flex:1;min-width:0;display:flex;position:relative}.page-graph{flex:1;min-width:0;display:grid;grid-template-columns:1fr var(--fp-w, 250px);position:relative}.side-rail{flex:0 0 52px;display:flex;flex-direction:column;align-items:center;gap:6px;padding:10px 0;background:var(--panel);border-right:1px solid var(--line)}.rail-spacer{flex:1}.rail-btn{width:36px;height:36px;display:flex;align-items:center;justify-content:center;background:none;border:none;border-radius:9px;cursor:pointer;color:var(--muted)}.rail-btn:hover{background:var(--panel2);color:var(--ink2)}.rail-btn.on{background:color-mix(in srgb,var(--blue) 14%,transparent);color:var(--blue)}.page-pane{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column;background:var(--paper)}.page-evals,.page-issues{overflow:hidden;padding:0 14px 10px 0}.page-settings{overflow:auto;flex-direction:row;justify-content:center}.settings-body{width:min(720px,94%);padding:26px 0 60px}.page-title{font-size:17px;font-weight:700;color:var(--ink2);margin:6px 2px 14px}.pane-resizer{cursor:col-resize;z-index:6;position:relative}.pane-resizer:after{content:"";position:absolute;top:0;bottom:0;left:50%;width:3px;margin-left:-1.5px;background:transparent;border-radius:2px}.pane-resizer:hover:after,body.is-resizing .pane-resizer:after{background:color-mix(in srgb,var(--blue) 55%,transparent)}.page-graph>.pane-resizer{position:absolute;top:0;bottom:0;width:9px;right:calc(var(--fp-w, 250px) - 4px)}.si-resizer{flex:0 0 7px;margin:0 -3.5px}body.is-resizing,body.is-resizing *{cursor:col-resize!important;-webkit-user-select:none!important;user-select:none!important}.app.kbd-mode,.app.kbd-mode *{cursor:none!important}.app.kbd-mode .react-flow,.app.kbd-mode .react-flow *{pointer-events:none!important}.graph{position:relative;background:var(--paper);border-right:1px solid var(--line)}.react-flow__attribution{display:none}.react-flow__edge-path{stroke-dasharray:0}.move-edge .react-flow__edge-path{stroke-linecap:round}.hud{position:absolute;top:16px;left:18px;z-index:5;display:flex;flex-direction:row;align-items:center;gap:8px;pointer-events:none}.brand{color:var(--ink2);font-weight:700;letter-spacing:.5px}.hud .hud-help{align-self:center}.hud-help{pointer-events:auto;align-self:flex-start;cursor:pointer;width:22px;height:22px;display:flex;align-items:center;justify-content:center;font-family:var(--mono);font-size:13px;line-height:1;color:var(--blue);background:var(--paper);border:1px solid var(--line);border-radius:4px}.hud-help:hover{border-color:var(--blue);color:var(--orange)}.lock-hint{position:absolute;top:16px;left:50%;transform:translate(-50%);z-index:6;display:flex;align-items:center;gap:10px;max-width:min(48vw,520px);padding:5px 8px 5px 11px;background:var(--paper);border:1px solid var(--line);border-left:4px solid var(--ov, var(--blue));border-radius:6px;box-shadow:0 6px 22px #657b8329;font-family:var(--mono);font-size:12px;color:var(--ink)}.lock-hint-lead{font-weight:700;flex:none;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lock-hint-body{color:var(--ink2);flex:1;min-width:0}.lock-hint kbd{border:1px solid var(--line);border-radius:3px;padding:0 5px;margin:0 1px;color:var(--blue);font-family:var(--mono);font-size:11px}.lock-hint-sep{color:var(--muted)}.lock-hint-release{flex:none;cursor:pointer;padding:1px 8px;background:var(--paper);border:1px solid var(--line);border-radius:4px;color:var(--muted);font-family:var(--mono);font-size:11px}.lock-hint-release:hover{border-color:var(--blue);color:var(--orange)}.graph-stats{position:absolute;bottom:14px;left:18px;z-index:5;display:flex;align-items:center;gap:5px;flex-wrap:wrap;max-width:calc(100% - 36px);padding:4px 9px;background:var(--paper);border:1px solid var(--line);border-radius:6px;box-shadow:0 4px 18px #657b831f;font-family:var(--mono);font-size:12px;color:var(--ink2)}.bstat-total{font-weight:700;color:var(--ink);padding-right:1px}.bstat{display:inline-flex;align-items:center;gap:3px;padding:1px 4px;border:0;border-radius:4px;background:none;font-family:var(--mono);font-size:12px;line-height:1.4;color:inherit;cursor:pointer}.bstat:hover:not(:disabled){background:var(--panel2);color:var(--ink)}.bstat:disabled{opacity:.38;cursor:default}.bstat-dot{width:7px;height:7px;border-radius:2px;flex:none}.bstat-drift{color:var(--yellow)}.bstat-issue{color:var(--magenta)}.bstat-sep{width:1px;align-self:stretch;margin:2px 3px;background:var(--line)}.graph-stats .score-badge{width:13px;height:13px;font-size:8px}.graph-stats .score-badge.empty{opacity:.6}.react-flow__node{transition:opacity .18s ease,transform .2s ease}.react-flow__node.is-far{opacity:.35}.react-flow__controls{box-shadow:none;border:1px solid var(--line);border-radius:4px;overflow:hidden}.react-flow__controls-button{background:var(--panel2);border-bottom:1px solid var(--line);color:var(--ink2)}.react-flow__controls-button:hover{background:var(--panel)}.react-flow__controls-button svg{fill:var(--ink)}.spec-node{position:relative;display:flex;flex-direction:column;justify-content:center;gap:3px;width:176px;min-height:46px;padding:5px 9px;background:var(--panel);border:1px solid var(--line);border-radius:4px;transition:border-color .12s}.spec-node.focused{border-color:var(--blue);outline:2px solid var(--blue);outline-offset:-1px}.spec-node.active.focused{border-color:var(--orange);outline-color:var(--orange)}.spec-node.drift.focused{border-color:var(--yellow);outline-color:var(--yellow)}.node-row1{display:flex;align-items:center;gap:7px}.node-row2{display:flex;align-items:center;gap:7px;min-height:18px}.node-dot{position:relative;width:7px;height:7px;border-radius:2px;flex:none}.pulse{position:absolute;top:0;right:0;bottom:0;left:0;border-radius:2px;animation:pulse 1.6s ease-out infinite}@keyframes pulse{0%{transform:scale(1);opacity:.6}to{transform:scale(2.6);opacity:0}}.node-title{font-size:12px;font-weight:600;color:var(--ink2);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.node-ago{font-size:10px;color:var(--muted);flex:none;white-space:nowrap}.node-ver{font-size:10px;color:var(--muted);flex:none}.node-expand{position:absolute;right:-7px;top:50%;transform:translateY(-50%);display:flex;align-items:center;gap:1px;font-size:9px;font-weight:700;line-height:1;color:var(--muted);background:var(--paper);border:1px solid var(--line);border-radius:7px;padding:2px 4px;pointer-events:none}.spec-node.focused .node-expand{color:var(--blue);border-color:var(--blue)}.node-editors{display:flex;align-items:center;gap:3px}.av-more{font-size:9px;font-weight:700;color:var(--muted);margin-left:1px}.avatar{display:inline-flex;border-radius:50%;box-shadow:0 0 0 1.5px var(--panel)}.av-face{display:inline-flex;align-items:center;justify-content:center;line-height:1;font-weight:700;overflow:hidden}.av-circle{border-radius:50%}.av-rounded{border-radius:5px}.av-square{border-radius:1px}.av-hex{border-radius:50%/18%;clip-path:polygon(25% 5%,75% 5%,100% 50%,75% 95%,25% 95%,0 50%)}.av-st-working{box-shadow:0 0 0 1.5px var(--panel),0 0 0 3px var(--green)}.av-st-idle{box-shadow:0 0 0 1.5px var(--panel),0 0 0 3px var(--muted)}.av-st-offline{box-shadow:0 0 0 1.5px var(--panel),0 0 0 3px var(--line);opacity:.55}.react-flow .react-flow__handle{opacity:0;pointer-events:none;background:transparent;border:none;width:5px;height:5px;min-width:0;min-height:0;border-radius:1px}.side{padding:18px 18px 24px;overflow-y:auto;background:var(--panel);border-left:1px solid var(--line)}.trail{display:flex;flex-wrap:wrap;align-items:center;gap:2px;margin-bottom:16px;font-size:11px}.trail .sep{color:var(--muted);margin:0 3px}.crumb{background:none;border:none;padding:1px 3px;cursor:pointer;color:var(--muted);font-family:var(--mono);font-size:11px}.crumb:hover{color:var(--blue)}.crumb.here{color:var(--ink2);font-weight:700}.side h1{font-size:17px;color:var(--ink2);margin:0 0 8px}.side h2{font-size:11px;color:var(--muted);margin:24px 0 10px;font-weight:500}.side h2:first-child{margin-top:0}.stats{display:flex;flex-direction:column;border:1px solid var(--line);border-radius:4px;overflow:hidden}.stats div{display:flex;justify-content:space-between;font-size:12px;padding:7px 10px}.stats div:nth-child(odd){background:var(--panel2)}.stats span{color:var(--muted)}.stats b{color:var(--ink2);font-weight:600}.c-merged{color:var(--green)}.c-active{color:var(--orange)}.c-drift{color:var(--yellow)}.c-pending{color:var(--muted)}.badge{display:inline-block;font-size:11px;padding:2px 0;border:none;background:none}.badge.merged{color:var(--green)}.badge.active{color:var(--orange)}.badge.drift{color:var(--yellow)}.badge.pending{color:var(--muted)}.desc{color:var(--ink);line-height:1.55;font-size:12.5px;margin:12px 0 4px}.meta{margin-top:14px;display:flex;flex-direction:column;gap:0;border:1px solid var(--line);border-radius:4px;overflow:hidden}.meta div{display:flex;justify-content:space-between;font-size:12px;padding:7px 10px}.meta div:nth-child(odd){background:var(--panel2)}.meta span{color:var(--muted)}.meta b{color:var(--ink2);font-weight:600}.peek-btn{margin-top:22px;width:100%;padding:9px;background:var(--paper);color:var(--blue);border:1px solid var(--blue);border-radius:4px;font-family:var(--mono);font-size:13px;font-weight:600;cursor:pointer}.peek-btn:hover:not(:disabled){background:var(--blue);color:var(--paper)}.peek-btn:disabled{color:var(--muted);border-color:var(--line);cursor:default}.ov-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:20;background:#657b8340;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center}.ov-panel{width:min(900px,90vw);height:min(600px,84vh);background:var(--paper);border:1px solid var(--blue);border-radius:6px;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 12px 40px #657b8340}.ov-head{display:flex;align-items:center;gap:14px;padding:7px 12px;background:var(--panel2);border-bottom:1px solid var(--line)}.ov-title{font-size:13px;font-weight:700;color:var(--ink2)}.ov-tabs{display:flex;gap:4px}.ov-tab{background:none;border:1px solid transparent;border-radius:4px;padding:3px 8px;cursor:pointer;color:var(--muted);font-family:var(--mono);font-size:12px}.ov-tab:hover{color:var(--ink2)}.ov-tab.on{color:var(--ink2);border-color:var(--blue);background:var(--paper)}.ov-tab kbd{font-family:var(--mono);font-size:10px;color:var(--blue);border:1px solid var(--line);border-radius:2px;padding:0 3px;margin-right:3px;background:var(--paper)}.ov-tab-counts{display:inline-flex;gap:3px;margin-left:5px;vertical-align:baseline}.ovc{font-family:var(--mono);font-size:9px;font-weight:700;line-height:1.5;padding:0 4px;border-radius:3px;border:1px solid var(--line)}.ovc.st-open{color:var(--green);border-color:color-mix(in srgb,var(--green) 45%,transparent)}.ovc.st-closed{color:var(--magenta);border-color:color-mix(in srgb,var(--magenta) 45%,transparent)}.ovc.st-edit{color:var(--blue);border-color:color-mix(in srgb,var(--blue) 45%,transparent)}.ov-hint{margin-left:auto;font-size:11px;color:var(--muted)}.ov-body{flex:1;min-width:0;overflow:hidden;display:flex}.pane-doc{flex:1;padding:22px 26px;overflow:auto}.pane-doc h1{font-size:18px;color:var(--ink2);margin:0 0 14px}.pane-doc blockquote{margin:0 0 16px;padding:8px 14px;border-left:3px solid var(--blue);background:var(--panel2);color:var(--ink);line-height:1.6;font-size:13px}.pane-doc .doc-stat{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin:0 0 16px;padding:6px 10px;background:var(--panel2);border:1px solid var(--line);border-radius:5px}.stat-status{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em}.stat-dot{width:7px;height:7px;border-radius:2px;flex:none;background:var(--muted)}.stat-status.st-merged{color:var(--green)}.stat-status.st-merged .stat-dot{background:var(--green)}.stat-status.st-active{color:var(--orange)}.stat-status.st-active .stat-dot{background:var(--orange)}.stat-status.st-drift{color:var(--yellow)}.stat-status.st-drift .stat-dot{background:var(--yellow)}.stat-status.st-pending{color:var(--muted)}.stat-status.st-pending .stat-dot{background:var(--muted)}.stat-chip{display:inline-flex;align-items:center;gap:4px;font-size:10.5px;color:var(--ink2);background:var(--paper);border:1px solid var(--line);border-radius:3px;padding:1px 6px}.stat-chip.stat-drift{color:var(--yellow);font-weight:700;border-color:color-mix(in srgb,var(--yellow) 45%,transparent)}.stat-sess{margin-left:auto;display:inline-flex;align-items:center;gap:5px;font-size:10.5px;color:var(--muted);min-width:0}.stat-sess b{color:var(--ink2);font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pane-doc .doc-gov{margin:0 0 16px}.doc-gov-h{display:block;font-size:10.5px;color:var(--muted);margin-bottom:6px}.doc-gov-h b{color:var(--ink2);font-weight:700}.doc-gov-files{display:flex;flex-wrap:wrap;gap:5px}.gov-f{font-size:11px;color:var(--blue);background:var(--panel2);border:1px solid color-mix(in srgb,var(--blue) 22%,var(--line));padding:1px 7px;border-radius:3px}.doc-gov.prose .doc-gov-h{font-style:italic}.doc-body{border-top:1px solid var(--line);padding-top:14px;font-size:13px;line-height:1.65;color:var(--ink)}.doc-body p{margin:0 0 10px}.pane-loading{display:flex;align-items:center;justify-content:center;padding:48px 0}.spinner{width:22px;height:22px;border-radius:50%;border:2px solid var(--line);border-top-color:var(--ink2);animation:si-attach-spin .7s linear infinite}.doc-body ul{margin:0 0 10px;padding-left:18px}.doc-body li{margin:2px 0}.doc-body code{font-size:12px;background:var(--panel2);color:var(--ink2);padding:0 4px;border-radius:3px}.doc-body strong{color:var(--ink2)}.doc-h{font-size:12px;color:var(--ink2);font-weight:700;margin:14px 0 6px}.doc-pre{margin:0 0 10px;padding:10px 12px;background:var(--panel2);border-radius:4px;overflow-x:auto;font-size:12px;line-height:1.5}.doc-pre code{background:none;padding:0;color:var(--ink)}.doc-link{color:var(--blue)}.doc-table{width:100%;margin:0 0 12px;border-collapse:collapse;font-size:12px;display:block;overflow-x:auto}.doc-table th,.doc-table td{border:1px solid var(--line);padding:5px 9px;text-align:left;vertical-align:top}.doc-table th{background:var(--panel2);color:var(--ink2);font-weight:700}.doc-table code{white-space:nowrap}.spec-parts{display:flex;flex-direction:column;gap:14px;margin-top:14px}.spec-part{border:1px solid var(--line);border-radius:6px;border-left-width:3px;overflow:hidden}.spec-part.part-raw{border-left-color:var(--green)}.spec-part.part-expanded{border-left-color:var(--blue)}.spec-part.part-state{border-left-color:var(--orange)}.part-head{display:flex;align-items:baseline;gap:10px;padding:8px 12px;background:var(--panel2);flex-wrap:wrap}.part-title{font-size:12px;font-weight:700;color:var(--ink2);text-transform:uppercase;letter-spacing:.04em}.part-owner{font-size:10px;font-weight:700;padding:1px 7px;border-radius:9px;text-transform:uppercase;letter-spacing:.03em}.part-owner.owner-human{background:#8599002e;color:var(--green)}.part-owner.owner-agent{background:#268bd22e;color:var(--blue)}.part-note{font-size:11px;color:var(--muted);margin-left:auto}.part-body{padding:4px 14px 12px}.part-body .doc-body{border-top:none;padding-top:8px}.cs-sub+.cs-sub{margin-top:6px}.cs-label{font-size:11px;font-weight:700;color:var(--orange);text-transform:uppercase;letter-spacing:.04em;margin:12px 0 0}.ver-row{padding:10px 0;border-bottom:1px solid var(--line)}.ver-row.latest{border-left:2px solid var(--blue);padding-left:12px;margin-left:-14px}.rec-toggle{display:block;width:100%;text-align:left;background:none;border:0;padding:0;margin:0;cursor:pointer;font:inherit;color:inherit}.rec-caret{color:var(--muted);font-size:9px;width:9px;flex:none}.rec-head{display:flex;align-items:baseline;gap:10px;font-size:12px}.rec-v{color:var(--ink2);font-weight:700}.rec-hash{color:var(--muted);font-size:11px}.rec-date{color:var(--muted)}.rec-diff{margin-left:auto;display:flex;gap:8px;font-size:12px}.rec-add{color:var(--green)}.rec-del{color:var(--orange)}.rec-msg{color:var(--ink2);font-size:13px;margin-top:5px;word-break:break-word}.rec-sub{color:var(--muted);font-size:11px;margin-top:3px}.rec-msg.muted{color:var(--muted)}.rec-evidence{margin:12px 0 2px;padding:14px 0 0;border-top:1px solid var(--line)}.ev-note{font-size:11px;color:var(--muted);text-align:center;margin-top:12px}.ev-difflabel{font-size:11px;color:var(--muted);margin-bottom:8px}.ev-diff{margin:0;font-family:var(--mono);font-size:11px;line-height:1.55;background:var(--panel2);border:1px solid var(--line);border-radius:5px;padding:8px 0;overflow-x:auto}.ev-diff .dl{padding:0 12px;white-space:pre}.dl-hunk{color:var(--blue);opacity:.75}.dl-add{color:var(--green);background:#8599001a}.dl-del{color:var(--orange);background:#cb4b161a}.dl-ctx{color:var(--muted)}.pane-hist{flex:1;min-width:0;padding:16px 20px;overflow:auto}.pane-hist.empty{color:var(--muted);font-size:13px}.pane-solo{flex:1;min-width:0;display:flex}.spec-node.has-overlay{border-color:var(--ov);box-shadow:0 0 0 2px color-mix(in srgb,var(--ov) 22%,transparent)}.spec-node.has-overlay.ov-dirty{border-style:dashed}.spec-node.has-overlay.focused{outline-color:var(--ov)}.spec-node.ghost{background:transparent;border-style:dashed;opacity:.72}.spec-node.deleted .node-title{text-decoration:line-through;color:var(--muted)}.drift-badge{flex:none;font-size:10px;font-weight:700;color:var(--yellow);border:1px solid var(--yellow);border-radius:3px;padding:0 3px;line-height:1.4}.issue-badge{flex:none;font-size:10px;font-weight:700;color:var(--magenta);border:1px solid var(--magenta);border-radius:3px;padding:0 3px;line-height:1.4;cursor:default}.score-badge{flex:none;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;border-radius:50%;border:1.5px solid currentColor;font-size:9px;font-weight:900;line-height:1;cursor:default}.score-badge.pass{color:var(--green)}.score-badge.fail{color:var(--red)}.score-badge.stalePass,.score-badge.staleFail{color:var(--muted)}.score-badge.empty{color:var(--muted);opacity:.45}.scenario-count{flex:none;font-family:var(--mono);font-size:10px;font-weight:700;line-height:1.4;border:1px solid currentColor;border-radius:3px;padding:0 3px;cursor:default}.scenario-count.pass{color:var(--green)}.scenario-count.fail{color:var(--red)}.scenario-count.stalePass,.scenario-count.staleFail{color:var(--muted)}.scenario-count.empty{color:var(--muted);opacity:.7}.focus-panel{overflow:auto;background:var(--paper);display:flex;flex-direction:column;gap:18px;padding:16px 14px}.fp-head{display:flex;flex-direction:column;gap:3px}.fp-title{font-size:15px;font-weight:700;color:var(--ink2);word-break:break-word;line-height:1.25}.fp-desc{color:var(--ink);font-size:11px;line-height:1.4;margin-top:3px;display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden}.fp-sec{display:flex;flex-direction:column;gap:5px}.fp-sec-head{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);border-bottom:1px solid var(--line);padding-bottom:5px}.fp-count{font-family:var(--mono);font-size:10px}.fp-count.pass{color:var(--green)}.fp-count.fail{color:var(--red)}.fp-count.stalePass,.fp-count.staleFail,.fp-count.empty{color:var(--muted)}.fp-empty{color:var(--muted);font-size:11px;font-style:italic;padding:3px 2px}.fp-scenario{position:relative;display:block;width:100%;padding:6px;text-align:left;background:none;border:none;border-radius:5px;font:inherit;color:inherit;cursor:pointer}.fp-scenario:hover{background:var(--panel2)}.fp-sc-mark{position:absolute;left:6px;top:6px;width:12px;text-align:center;font-weight:900;font-size:11px;line-height:1.5}.fp-scenario.pass .fp-sc-mark{color:var(--green)}.fp-scenario.fail .fp-sc-mark{color:var(--red)}.fp-scenario.stalePass .fp-sc-mark,.fp-scenario.staleFail .fp-sc-mark{color:var(--muted)}.fp-scenario.empty .fp-sc-mark,.fp-scenario.missing .fp-sc-mark{color:var(--muted);opacity:.55}.fp-sc-body{display:flex;flex-direction:column;gap:3px;min-width:0}.fp-sc-name{padding-left:20px;font-family:var(--mono);font-size:11px;font-weight:700;color:var(--ink2)}.fp-sc-expected{color:var(--ink);font-size:11px;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.fp-sc-code{color:var(--muted);font-family:var(--mono);font-size:9px;opacity:.85}.tag-chips{display:inline-flex;flex-wrap:wrap;gap:3px;min-width:0}.tag-chip{font-family:var(--mono);font-size:9px;line-height:1.5;color:var(--ink2);background:var(--paper);border:1px solid var(--line);border-radius:3px;padding:0 4px;white-space:nowrap}.search-item .tag-chips{flex:none}.fp-issue-counts{display:flex;gap:6px;font-size:9px}.focus-panel .issue-group-head{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);margin:8px 2px 3px}.focus-panel .issue-card{padding:6px;border-radius:5px}.issue-card{display:block;max-width:100%;min-width:0;box-sizing:border-box;overflow:hidden;padding:5px 6px;border-radius:5px;text-decoration:none}.issue-card+.issue-card{margin-top:2px}.issue-card:hover{background:var(--panel2)}.issue-card-top{display:flex;align-items:center;gap:6px;min-width:0;margin-bottom:2px}.issue-num{font-size:10px;font-weight:700;color:var(--magenta);flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--mono)}.issue-state{font-size:8px;color:var(--muted);flex:none;text-transform:uppercase;letter-spacing:.04em;border:1px solid var(--line);border-radius:3px;padding:0 3px;line-height:1.5}.issue-state.st-open{color:var(--green);border-color:color-mix(in srgb,var(--green) 45%,transparent)}.issue-state.st-closed{color:var(--magenta);border-color:color-mix(in srgb,var(--magenta) 45%,transparent)}.issue-card-title{display:block;font-size:11px;line-height:1.35;color:var(--ink2);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.pane-issues{flex:1;min-width:0;padding:16px 20px;overflow:auto}.pane-issues.empty{color:var(--muted);font-size:13px}.pane-issues .issue-group-head{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);margin:0 2px 6px}.pane-issues .issue-group-head.closed{margin-top:16px;border-top:1px solid var(--line);padding-top:14px}.pane-issues .issue-card{padding:7px 8px}.pane-issues .issue-card+.issue-card{margin-top:1px}.pane-issues .issue-card-title{font-size:12px}.pane-edit{flex:1;min-width:0;padding:16px 20px;overflow:auto}.pane-edit.empty{color:var(--muted);font-size:13px}.edit-rev{margin:0 0 18px}.edit-by{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--muted);margin-bottom:8px}.edit-by-label{font-weight:600}.edit-state{margin-left:auto;font-family:var(--mono);font-size:10px}.ov-marks{display:flex;gap:3px;flex:none;margin-left:2px}.ov-mark{font-size:11px;font-weight:700;line-height:1;color:var(--ov, var(--blue))}.ov-mark.ov-deleted{color:var(--orange)}.react-flow__node.ov-hot{opacity:1}.react-flow__node.ov-dim{opacity:.22}.sesswin{position:absolute;top:48px;left:16px;z-index:6;width:232px;max-height:min(80vh,calc(100vh - 112px));background:var(--paper);border:1px solid var(--line);border-radius:6px;box-shadow:0 6px 22px #657b8329;overflow-y:auto;overflow-x:hidden;font-size:12px}.sesswin-empty{padding:12px 10px;color:var(--muted);font-size:11px;line-height:1.5}.sesswin-empty kbd{border:1px solid var(--line);border-radius:3px;padding:0 4px;color:var(--blue)}.sess-row{display:flex;flex-wrap:wrap;align-items:center;column-gap:7px;row-gap:2px;width:100%;padding:7px 9px;background:none;border:none;border-bottom:1px solid var(--line);border-left:2px solid var(--ov, transparent);cursor:pointer;text-align:left;font-family:var(--mono);font-size:12px;color:var(--ink2);-webkit-user-select:none;user-select:none;transition:background .12s,border-left-width .12s}.sess-row:last-child{border-bottom:none}.sess-row:hover{background:var(--panel)}.sess-row.locked{background:color-mix(in srgb,var(--ov) 12%,var(--panel2));border-left-width:4px;color:var(--ink);font-weight:600}.sess-id{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sess-lock{font-size:10px;flex:none;line-height:1;filter:saturate(0)}.sess-meta{order:1;flex:0 0 100%;width:100%;min-width:0;margin-top:1px;display:flex;align-items:center;column-gap:7px;font-size:10px;line-height:1.3;opacity:.9;overflow:hidden}.sess-ops{color:var(--ov, var(--blue));font-weight:700;flex:none}.sess-glyph{flex:none;width:12px;text-align:center;font-size:11px;line-height:1}.sess-lead{flex:none;align-self:stretch;display:inline-flex;align-items:stretch;--rail: color-mix(in srgb, var(--muted) 42%, transparent)}.sess-rail{flex:none;width:14px;position:relative;min-height:1.7em}.sess-rail.rail:before,.sess-rail.tee:before,.sess-rail.elbow:before{content:"";position:absolute;left:50%;top:0;bottom:0;border-left:1px solid var(--rail)}.sess-rail.elbow:before{bottom:50%}.sess-rail.tee:after,.sess-rail.elbow:after{content:"";position:absolute;left:50%;right:1px;top:50%;border-top:1px solid var(--rail)}.sess-fold{flex:none;display:inline-flex;align-items:center;justify-content:center;align-self:center;cursor:pointer;-webkit-user-select:none;user-select:none}.sess-fold.pod{min-width:16px;width:auto;height:14px;padding:0 4px;border-radius:999px;border:1px solid;font-size:9.5px;font-weight:700;line-height:1;color:var(--paper)}.sess-fold.pod.open{background:transparent}.sess-fold.pod:hover{filter:brightness(1.25)}.sesswin .sess-row{flex-wrap:nowrap}.sesswin .sess-meta{flex:none;width:auto;min-width:0;margin-top:0;margin-left:auto}.sesswin-zone{--zh: var(--muted);display:flex;align-items:center;gap:7px;padding:8px 9px 2px;font-size:9.5px;font-weight:700;letter-spacing:.9px;text-transform:uppercase;-webkit-user-select:none;user-select:none;color:var(--zh)}.sesswin-zone:after{content:"";flex:1;height:1px;background:var(--zh);opacity:.25}.sesswin-zone-need{--zh: var(--yellow)}.sesswin-zone-run{--zh: var(--green)}.sesswin-zone-offline{--zh: var(--muted)}.sess-menu{position:fixed;z-index:60;min-width:116px;background:var(--paper);border:1px solid var(--line);border-radius:5px;box-shadow:0 6px 22px #657b8338;overflow:hidden;padding:3px}.sess-menu-item{display:block;width:100%;text-align:left;background:none;border:none;border-radius:3px;padding:5px 9px;cursor:pointer;font-family:var(--mono);font-size:12px;color:var(--ink2)}.sess-menu-item:hover{background:var(--blue);color:var(--paper)}.sess-menu-item.danger{color:var(--orange)}.sess-menu-item.danger:hover{background:var(--orange);color:var(--paper)}.sess-menu-sep{height:1px;margin:3px 4px;background:var(--line)}.sess-menu-item.sess-menu-sess{display:flex;align-items:center;gap:6px}.sess-menu-item.sess-menu-sess .sess-glyph{width:11px}.sess-rename-modal{width:min(360px,92vw)}.sess-rename{display:flex;flex-direction:column;gap:10px;padding:4px 2px 2px}.sess-rename-input{font-family:var(--mono);font-size:13px;color:var(--ink2);background:var(--paper);border:1px solid var(--line);border-radius:4px;padding:7px 9px}.sess-rename-input:focus{outline:none;border-color:var(--blue)}.sess-rename-actions{display:flex;justify-content:flex-end;gap:8px}.sess-rename-btn{background:var(--paper);border:1px solid var(--line);color:var(--ink);border-radius:4px;padding:4px 12px;cursor:pointer;font-family:var(--mono);font-size:12px}.sess-rename-btn:hover{background:var(--panel)}.sess-rename-save{border-color:var(--blue);color:var(--blue)}.sess-rename-save:hover{background:var(--blue);color:var(--paper)}.sess-rename-save:disabled{opacity:.5;cursor:default}.sess-confirm{display:flex;flex-direction:column;gap:12px;padding:4px 2px 2px}.sess-confirm-msg{margin:0;color:var(--ink);font-size:12px;line-height:1.4}.sess-rename-btn.danger{border-color:var(--orange);color:var(--orange)}.sess-rename-btn.danger:hover{background:var(--orange);color:var(--paper)}.sess-rename-btn.danger:disabled{opacity:.5;cursor:default}.legend-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:60;background:#657b8340;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center}.legend{position:relative;width:min(660px,92vw);max-height:86vh;overflow-y:auto;background:var(--paper);border:1px solid var(--blue);border-radius:6px;box-shadow:0 12px 40px #657b8340;font-size:12px}.legend-head{position:sticky;top:0;display:flex;align-items:center;justify-content:space-between;padding:6px 9px;background:var(--panel2);border-bottom:1px solid var(--line)}.legend-title{color:var(--muted);font-size:11px;letter-spacing:.5px}.legend-close{background:none;border:none;color:var(--muted);cursor:pointer;display:inline-flex;align-items:center;line-height:1;padding:2px}.legend-close:hover{color:var(--orange)}.legend-body{padding:4px 10px 10px}.legend-sec{padding:8px 0;border-bottom:1px solid var(--line)}.legend-sec:last-child{border-bottom:none}.legend-h{font-size:11px;color:var(--ink2);font-weight:700;margin-bottom:6px}.legend-sub{color:var(--muted);font-weight:400}.keymap-keys{flex:none;width:132px;display:flex;flex-wrap:wrap;gap:4px}.legend kbd,.settings-body kbd{font-family:var(--mono);font-size:11px;color:var(--blue);line-height:1.4;border:1px solid var(--line);border-radius:3px;padding:0 4px;background:var(--paper)}.legend-row{display:flex;align-items:baseline;gap:8px;padding:3px 0}.legend-row .node-dot{position:relative;top:1px}.legend-name{color:var(--ink2);font-weight:600;flex:none;width:52px}.legend-desc{color:var(--ink);flex:1;line-height:1.4}.legend-glyph{color:var(--blue);font-weight:700;flex:none;width:18px;text-align:center}.legend-glyph.legend-ver{font-size:11px;color:var(--muted)}.legend .ov-mark{flex:none;width:18px;text-align:center}.legend .drift-badge{flex:none}.legend-ring{flex:none;width:22px;height:14px;border-radius:3px;background:var(--panel)}.legend-ring.ring-dashed{border:1px dashed var(--blue)}.legend-ring.ring-solid{border:1px solid var(--blue)}.legend-ring.ring-ghost{border:1px dashed var(--line);background:transparent;opacity:.72}.set-langs{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:6px}.set-lang{font-family:var(--mono);font-size:12px;color:var(--ink);cursor:pointer;border:1px solid var(--line);border-radius:4px;padding:4px 12px;background:var(--paper)}.set-lang:hover{border-color:var(--blue);color:var(--blue)}.set-lang.on{border-color:var(--blue);color:var(--blue);background:var(--panel2);font-weight:600}.set-hint{display:block;color:var(--muted);font-size:11px;line-height:1.4}.set-keys{display:flex;flex-direction:column;gap:2px;margin:2px 0 8px}.set-key-row{display:flex;align-items:center;gap:8px;padding:2px 0}.set-key-row .legend-desc{flex:1;font-size:12px}.bind-cell{flex:none;min-width:92px;min-height:24px;display:flex;flex-wrap:wrap;align-items:center;gap:4px;justify-content:center;cursor:pointer;background:var(--paper);border:1px solid var(--line);border-radius:4px;padding:2px 6px}.bind-cell:hover:not(.fixed){border-color:var(--blue)}.bind-cell.capturing{border-color:var(--blue);border-style:dashed;background:var(--panel2)}.bind-cell.custom{border-color:var(--blue)}.bind-cell.fixed{cursor:default;opacity:.6}.bind-cell kbd{border:none;padding:0 2px;background:transparent}.bind-hint{font-size:11px;color:var(--blue)}.set-foot{display:flex;align-items:center;justify-content:space-between;gap:12px}.set-reset{flex:none;font-family:var(--mono);font-size:11px;color:var(--muted);cursor:pointer;border:1px solid var(--line);border-radius:4px;padding:3px 10px;background:var(--paper)}.set-reset:hover{border-color:var(--red);color:var(--red)}.search-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:60;background:#657b8340;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:flex-start;justify-content:center;padding-top:12vh}.search-panel{width:min(92vw,clamp(640px,50vw,1100px));max-height:72vh;display:flex;flex-direction:column;overflow:hidden;background:var(--paper);border:1px solid var(--blue);border-radius:6px;box-shadow:0 14px 44px #657b8347;font-size:12px}.search-bar{display:flex;align-items:center;gap:8px;padding:9px 12px;border-bottom:1px solid var(--line);background:var(--panel2)}.search-icon{color:var(--muted);font-size:14px;flex:none}.search-input{flex:1;min-width:0;background:transparent;border:none;outline:none;color:var(--ink2);font-family:var(--mono);font-size:14px;line-height:1.4}.search-input::placeholder{color:var(--muted)}.search-results{margin:0;padding:4px;list-style:none;overflow-y:auto;overflow-x:hidden}.search-empty{padding:8px 10px;color:var(--muted)}.search-item{display:flex;align-items:baseline;gap:8px;padding:5px 8px;border-radius:3px;cursor:pointer;white-space:nowrap}.search-item.on{background:var(--panel2)}.search-item .node-dot{position:relative;top:1px}.search-kind{flex:none;font-size:9px;line-height:1;letter-spacing:.4px;text-transform:uppercase;padding:2px 4px;border-radius:3px;border:1px solid var(--line);color:var(--muted);position:relative;top:-1px}.search-kind.k-spec{color:var(--blue);border-color:color-mix(in srgb,var(--blue) 40%,var(--line))}.search-kind.k-session{color:var(--orange);border-color:color-mix(in srgb,var(--orange) 40%,var(--line))}.search-kind.k-issue{color:var(--green);border-color:color-mix(in srgb,var(--green) 40%,var(--line))}.search-kind.k-scenario{color:var(--cyan);border-color:color-mix(in srgb,var(--cyan) 40%,var(--line))}.search-title{color:var(--ink2);font-weight:600;flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis}.search-path{color:var(--muted);font-size:11px;flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis}.search-foot{padding:6px 12px;border-top:1px solid var(--line);background:var(--panel);color:var(--muted);font-size:11px}.si-page{flex:1;min-width:0;display:flex}.si-panel{flex:1;min-width:0;background:var(--paper);border:none;border-radius:0;overflow:hidden;display:flex}.si-list{flex:0 0 240px;border-right:1px solid var(--line);background:var(--panel);display:flex;flex-direction:column;overflow-y:auto}.si-list-unfold{flex:0 0 22px;display:flex;align-items:flex-start;justify-content:center;padding:9px 0 0;color:var(--muted);background:var(--panel);border:none;border-right:1px solid var(--line);cursor:pointer;min-height:0}.si-list-unfold:hover{color:var(--blue);background:var(--panel2)}.si-item{display:flex;flex-wrap:wrap;align-items:center;column-gap:7px;row-gap:2px;width:100%;padding:9px 12px;background:none;border:none;border-bottom:1px solid var(--line);border-left:2px solid var(--ov, transparent);cursor:pointer;text-align:left;font-family:var(--mono);font-size:12px;color:var(--ink2)}.si-item:hover{background:var(--panel2)}.si-item.on{background:color-mix(in srgb,var(--blue) 15%,var(--panel2));color:var(--ink2);font-weight:600;border-left-width:3px}.si-item.on:hover{background:color-mix(in srgb,var(--blue) 24%,var(--panel2))}.si-item.on:before{content:"❯";flex:none;color:var(--green);font-weight:700}.si-item{flex-wrap:nowrap;padding:5px 12px;font-size:11px}.si-item.on:before{content:none}.si-item .sess-meta{flex:none;width:auto;min-width:0;margin-top:0;margin-left:auto;opacity:1;overflow:visible}.si-item .sess-lock{color:var(--muted)}.si-item.on{display:block}.si-item.on .sess-meta{float:right;margin:0 0 0 8px}.si-item.on .sess-id{white-space:normal;overflow:visible}.si-zone{--zh: var(--muted);display:flex;align-items:center;gap:8px;padding:10px 12px 4px;font-size:10px;font-weight:700;letter-spacing:1px;text-transform:uppercase;-webkit-user-select:none;user-select:none;color:var(--zh)}.si-zone:after{content:"";flex:1;height:1px;background:var(--zh);opacity:.25}.si-zone-need{--zh: var(--yellow)}.si-zone-run{--zh: var(--green)}.si-zone-offline{--zh: var(--muted)}.si-toprow{display:flex;gap:6px;padding:8px 10px;border-bottom:1px solid var(--line)}.si-selbar{display:flex;align-items:center;gap:6px;padding:8px 10px;border-bottom:1px solid var(--line)}.si-selcount{flex:1;font-family:var(--mono);font-size:11px;color:var(--ink2)}.si-selbar .sess-rename-btn{padding:4px 10px;font-size:11px}.si-check{flex:none;width:13px;height:13px;border:1.5px solid var(--muted);border-radius:3px;box-sizing:border-box}.si-check.on{border-color:var(--blue);background:var(--blue);position:relative}.si-check.on:after{content:"";position:absolute;left:3.5px;top:1px;width:3px;height:6px;border:solid var(--paper);border-width:0 1.5px 1.5px 0;transform:rotate(45deg)}.si-item.picked{background:color-mix(in srgb,var(--blue) 12%,var(--panel2))}.si-item.picked:hover{background:color-mix(in srgb,var(--blue) 20%,var(--panel2))}.si-pill{flex:1;display:flex;align-items:center;justify-content:center;padding:7px 0;background:none;border:1px solid var(--line);border-radius:5px;cursor:pointer;font-family:var(--mono);line-height:1}.si-pill-glyph{font-size:15px;display:flex}.si-pill.new{color:var(--blue)}.si-pill:hover{background:var(--panel2);border-color:currentColor}.si-pill.new.on{background:color-mix(in srgb,var(--blue) 16%,var(--panel2));border-color:var(--blue)}.si-pill.search{color:var(--ink2)}.si-pill.search:hover{color:var(--blue)}.si-pill.forum{color:var(--ink2)}.si-pill.forum:hover{color:var(--blue)}.si-pill.forum.on{color:var(--blue);background:color-mix(in srgb,var(--blue) 16%,var(--panel2));border-color:var(--blue)}.fv-master{flex:1;min-height:0;display:grid;grid-template-columns:minmax(232px,280px) 1fr}.fv-master.folded{grid-template-columns:22px 1fr}.fv-list-col{position:relative;min-height:0;display:flex;flex-direction:column;overflow:hidden;border-right:1px solid var(--line);background:var(--paper)}.fv-list-col>.fv-notice{flex:none}.fv-list-col>.fv-group{flex:1 1 auto;min-height:0;overflow-y:auto;border-top:0}.fv-list-col>.fv-scroll{flex:1 1 auto;min-height:0;overflow-y:auto}.fv-list-col>.fv-scroll .fv-group{border-top:0}.fv-list-col>.fv-scroll .fv-group-head{padding-right:34px}.fv-fold{position:absolute;top:8px;right:6px;z-index:3;display:grid;place-items:center;padding:0;color:var(--muted);background:var(--panel);border:1px solid var(--line);border-radius:6px;width:22px;height:22px;cursor:pointer}.fv-fold:hover{color:var(--blue);border-color:var(--blue)}.fv-fold-inline{display:inline-flex;align-items:center;justify-content:center;flex:none;padding:0;color:var(--muted);background:var(--panel);border:1px solid var(--line);border-radius:6px;width:22px;height:22px;cursor:pointer}.fv-fold-inline:hover{color:var(--blue);border-color:var(--blue)}.fv-unfold{display:flex;align-items:flex-start;justify-content:center;padding:9px 0 0;color:var(--muted);background:var(--panel);border:none;border-right:1px solid var(--line);cursor:pointer;min-height:0}.fv-unfold:hover{color:var(--blue);background:var(--panel2)}.fold-toggle-icon{width:15px;height:15px;display:block;flex:none}.fv-detail{min-height:0;min-width:0;overflow-y:auto;background:var(--paper)}.fv-group{display:flex;flex-direction:column}.fv-group+.fv-group{border-top:1px solid var(--line)}.fv-group-head{position:sticky;top:0;z-index:2;display:flex;align-items:center;gap:6px;flex-wrap:wrap;row-gap:5px;padding:9px 12px;background:var(--panel2);border-bottom:1px solid var(--line)}.fv-group-title{font-size:9px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:var(--ink2)}.fv-head-row{flex-basis:100%;display:flex;align-items:center;gap:6px}.fv-row,.ef-row{display:flex;align-items:center;gap:8px;width:100%;min-width:0;text-align:left;font:inherit;font-size:12px;color:var(--ink2);background:none;border:0;border-bottom:1px solid color-mix(in srgb,var(--line) 55%,transparent);padding:7px 12px;cursor:pointer}.fv-row:hover,.ef-row:hover{background:var(--panel2)}.fv-row.sel,.ef-row.sel{background:color-mix(in srgb,var(--blue) 12%,var(--paper));box-shadow:inset 2px 0 0 var(--blue)}.fv-row .fv-concern{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fv-note{margin:auto;padding:24px;color:var(--muted);font-family:var(--mono);font-size:12.5px;text-align:center}.fv-dot{flex:none;width:8px;height:8px;border-radius:50%;background:var(--muted)}.fv-dot.st-open{background:var(--blue)}.fv-dot.st-landed{background:var(--green)}.fv-dot.st-closed{background:color-mix(in srgb,var(--muted) 55%,transparent)}.fv-replies{flex:none;font-family:var(--mono);font-size:10px;color:var(--muted);background:var(--panel2);border:1px solid var(--line);border-radius:9px;padding:0 6px;line-height:1.4}.fv-store{flex:none;font-family:var(--mono);font-size:9px;letter-spacing:.8px;text-transform:uppercase;color:var(--muted)}.fv-store-local{color:color-mix(in srgb,var(--blue) 65%,var(--muted))}.fv-concern{flex:1 1 auto;min-width:80px;color:var(--ink);font-weight:600;font-size:12px}.fv-status{flex:none;font-family:var(--mono);font-size:9px;border:1px solid var(--line);border-radius:3px;padding:1px 6px;color:var(--muted)}.fv-st-open{color:var(--blue);border-color:color-mix(in srgb,var(--blue) 40%,var(--line))}.fv-st-landed{color:var(--green);border-color:color-mix(in srgb,var(--green) 40%,var(--line))}.fv-by{flex:none;color:var(--muted);font-size:11px}.fv-originator{flex:none;display:inline-flex;align-items:center;gap:5px;font:inherit;font-size:11px;color:var(--muted);background:transparent;border:0;border-radius:4px;padding:1px 3px}.fv-originator-dot{width:7px;height:7px;border-radius:50%;flex:none}.fv-originator-who{color:var(--ink2);font-family:var(--mono);font-size:10.5px}.fv-originator.openable{cursor:pointer;background:color-mix(in srgb,var(--green) 8%,transparent)}.fv-originator.openable:hover{background:color-mix(in srgb,var(--green) 16%,transparent)}.fv-originator.openable:focus-visible{outline:2px solid var(--green);outline-offset:2px}.fv-originator.offline{opacity:.7}.fv-chips{flex:none;display:flex;flex-wrap:wrap;gap:5px}.fvd{height:100%;min-height:0;display:flex;flex-direction:column}.fvd-scroll{flex:1 1 auto;min-height:0;overflow-y:auto;padding:16px 22px 24px}.fvd-scroll>*{max-width:860px}.fvd-compose{flex:none;border-top:1px solid var(--line);padding:8px 16px 12px;background:var(--panel)}.fvd-compose .fv-compose{margin-top:0;padding-top:0;border-top:none;max-width:860px}.fvd-head{margin-bottom:4px}.fvd-concern{font-size:16px;font-weight:700;color:var(--ink);line-height:1.35}.fv-close-issue{flex:none;font-family:var(--mono);font-size:11px;color:var(--orange);background:var(--paper);border:1px solid color-mix(in srgb,var(--orange) 35%,var(--line));padding:4px 12px;border-radius:4px;cursor:pointer}.fv-close-issue:hover{background:var(--orange);color:var(--paper);border-color:var(--orange)}.fv-close-issue:disabled{opacity:.5;cursor:default}.fv-close-issue.fv-life-promote{color:var(--blue);border-color:color-mix(in srgb,var(--blue) 35%,var(--line))}.fv-close-issue.fv-life-promote:hover{background:var(--blue);color:var(--paper);border-color:var(--blue)}.fvd-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:12px}.fvd-body{font-size:13px}.fv-chip{font-family:var(--mono);font-size:10.5px;color:var(--blue);background:var(--panel2);border:1px solid color-mix(in srgb,var(--blue) 22%,var(--line));padding:1px 7px;border-radius:3px;cursor:pointer;line-height:1.5}.fv-chip:hover{background:var(--blue);color:var(--paper);border-color:var(--blue)}.fv-count{color:var(--muted);font-size:10.5px}.fv-link{color:var(--blue);font-size:10.5px;text-decoration:none}.fv-link:hover{text-decoration:underline}.fv-text{font-family:var(--mono);font-size:12px;line-height:1.6;color:var(--ink2);white-space:pre-wrap}.fv-reply{margin-top:10px;padding-left:12px;border-left:2px solid var(--line);border-radius:0 4px 4px 0;transition:background .15s,border-color .15s}.fv-reply.active{border-left-color:var(--green);background:color-mix(in srgb,var(--green) 7%,transparent)}.fv-reply.sel{border-left-color:var(--orange);background:color-mix(in srgb,var(--orange) 9%,transparent)}.fv-reply.remark.open{border-left-color:var(--orange);border-left-width:3px}.fv-reply.remark.resolved{opacity:.62;border-left-color:color-mix(in srgb,var(--green) 45%,var(--line))}.fv-remark-state{font-family:var(--mono);font-size:10px;padding:0 5px;border-radius:3px;margin-left:auto}.fv-remark-state.open{color:var(--orange);background:color-mix(in srgb,var(--orange) 12%,transparent);font-weight:600}.fv-remark-state.resolved{color:var(--green);background:color-mix(in srgb,var(--green) 12%,transparent)}.fv-remark-act{font-family:var(--mono);font-size:10px;padding:0 6px;border-radius:3px;cursor:pointer;line-height:1.6;background:var(--paper)}.fv-remark-act.resolve{color:var(--green);border:1px solid color-mix(in srgb,var(--green) 40%,var(--line))}.fv-remark-act.resolve:hover{background:var(--green);color:var(--paper);border-color:var(--green)}.fv-remark-act.retract{color:var(--muted);border:1px solid var(--line)}.fv-remark-act.retract:hover{background:var(--orange);color:var(--paper);border-color:var(--orange)}.fv-remark-act:disabled{opacity:.5;cursor:default}.fv-reply-meta{display:flex;align-items:center;gap:8px;margin-bottom:3px;font-family:var(--mono);font-size:10.5px}.fv-reply-by{color:var(--blue);font-weight:600}.fv-reply-at{color:var(--muted)}.fv-anchor{font-family:var(--mono);font-size:10.5px;color:var(--orange);background:color-mix(in srgb,var(--orange) 12%,transparent);border:1px solid color-mix(in srgb,var(--orange) 35%,var(--line));border-radius:3px;padding:1px 6px;cursor:pointer}.fv-anchor:hover{background:var(--orange);color:var(--paper)}.fv-anchor.static{cursor:default}.fv-anchor.static:hover{background:color-mix(in srgb,var(--orange) 12%,transparent);color:var(--orange)}.fv-anchor.static.degraded{color:var(--muted);background:transparent;border-style:dashed;border-color:color-mix(in srgb,var(--muted) 45%,var(--line))}.fv-anchor.static.degraded:hover{color:var(--muted);background:transparent}.fv-reply-media{display:flex;flex-direction:column;gap:6px;margin-top:6px}.fv-frames{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}.fv-frames .an-image,.fv-frames .eval-video{height:48px;width:auto;max-width:none}.fv-anchor-btn{display:inline-flex;align-items:center;gap:4px;font-family:var(--mono);font-size:10.5px;color:var(--orange);background:var(--panel2);border:1px solid color-mix(in srgb,var(--orange) 35%,var(--line));border-radius:4px;padding:3px 10px;cursor:pointer}.fv-anchor-btn:hover{background:var(--orange);color:var(--paper)}.fv-new-btn{display:inline-flex;align-items:center;justify-content:center;color:var(--blue);background:color-mix(in srgb,var(--blue) 10%,var(--panel2));border:1px solid color-mix(in srgb,var(--blue) 32%,var(--line));padding:3px 12px;border-radius:6px;cursor:pointer;transition:color .12s,background .12s,border-color .12s}.fv-new-btn:hover{background:var(--blue);color:var(--paper);border-color:var(--blue)}.fv-new-modal{width:min(520px,92vw);overflow:visible}.fv-new-modal .legend-body{padding:0}.fv-hint{font-family:var(--mono);font-size:10.5px;color:var(--muted)}.fv-notice{padding:6px 16px;font-family:var(--mono);font-size:11px;color:var(--green);background:color-mix(in srgb,var(--green) 8%,var(--paper));border-bottom:1px solid var(--line)}.fv-error{margin:4px 0 8px;font-family:var(--mono);font-size:11px;color:var(--red)}.fv-actions .fv-error{margin:0}.fv-compose{margin-top:12px;padding-top:10px;border-top:1px dashed var(--line)}.fv-compose .fv-textarea{resize:none;min-height:56px;max-height:38vh}.fv-new-form{display:flex;flex-direction:column;gap:8px;padding:12px 16px;border-bottom:1px solid var(--line);background:var(--panel2)}.fv-store-pick{display:flex;align-items:center;gap:8px;align-self:flex-start;font-family:var(--mono);font-size:10.5px;color:var(--muted)}.fv-store-pick select{font-family:var(--mono);font-size:11px;color:var(--ink2);background:var(--paper);border:1px solid var(--line);border-radius:4px;padding:3px 7px}.fv-input,.fv-textarea{width:100%;box-sizing:border-box;font-family:var(--mono);font-size:12px;color:var(--ink);background:var(--paper);border:1px solid var(--line);border-radius:4px;padding:6px 8px;resize:vertical}.fv-input:focus,.fv-textarea:focus{outline:none;border-color:var(--blue)}.fv-tawrap{position:relative}.fv-tawrap .fv-textarea{display:block}.fv-actions{display:flex;align-items:center;gap:10px;margin-top:6px}.fv-cancel{margin-left:auto;font-family:var(--mono);font-size:11px;color:var(--muted);background:var(--paper);border:1px solid var(--line);padding:4px 12px;border-radius:4px;cursor:pointer}.fv-cancel:hover{color:var(--orange);border-color:var(--orange)}.fv-cancel:disabled{opacity:.5;cursor:default}.fv-send{margin-left:auto;font-family:var(--mono);font-size:11px;color:var(--paper);background:var(--blue);border:1px solid var(--blue);padding:4px 14px;border-radius:4px;cursor:pointer}.fv-cancel+.fv-send{margin-left:0}.fv-send:disabled{opacity:.5;cursor:default}.si-content{flex:1;min-width:0;display:flex;flex-direction:column;background:var(--paper)}.si-content.is-new{align-items:center;justify-content:center}.si-new-center{width:min(560px,86%);display:flex;flex-direction:column;align-items:center;gap:12px;margin-bottom:12vh;animation:si-rise .18s ease}.si-avatar{font-size:30px;color:var(--blue);letter-spacing:2px}.si-ask{font-size:15px;color:var(--ink2)}.si-inputwrap{position:relative;width:100%;display:flex;border:1px solid var(--blue);border-radius:6px;background:var(--panel);padding:10px 12px;transition:height .12s ease}.mention-menu{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:5;margin:0;padding:4px;list-style:none;max-height:248px;overflow-y:auto;background:var(--paper);border:1px solid var(--blue);border-radius:4px;box-shadow:0 6px 18px #07364229;animation:si-rise .12s ease}.mention-menu.up{top:auto;bottom:calc(100% + 4px)}.mention-menu.fixed{position:fixed;top:auto;z-index:70}.mention-head{padding:3px 8px 5px;font-size:10.5px;color:var(--muted);letter-spacing:.02em;border-bottom:1px solid var(--line);margin-bottom:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mention-item{display:flex;align-items:baseline;gap:8px;padding:4px 8px;border-radius:3px;cursor:pointer;font-size:12px;white-space:nowrap}.mention-item.on{background:var(--panel2)}.mention-item.new .mention-id{color:var(--green);font-style:italic}.mention-dot{width:6px;height:6px;border-radius:50%;flex:none;align-self:center}.mention-id{color:var(--blue);flex:none}.mention-id .mention-hit{font-weight:700}.mention-path{color:var(--muted);font-size:11px;min-width:0;overflow:hidden;text-overflow:ellipsis}.slash-name{color:var(--blue);flex:none}.slash-name .mention-hit{font-weight:700}.slash-desc{color:var(--muted);font-size:11px;min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis}.slash-src{color:var(--muted);font-size:10.5px;flex:none;opacity:.7}.slash-src.src-user{color:var(--green);opacity:.85}.slash-src.src-project{color:var(--cyan);opacity:.85}.slash-src.src-skill{color:var(--magenta);opacity:.85}.slash-name.ui{color:var(--sc);font-weight:700}.slash-src.src-ui{color:var(--sc);opacity:.9}.slash-src.src-mutating{color:var(--yellow);opacity:.85}.slash-src.src-report{color:var(--cyan);opacity:.85}.si-input{flex:1;min-width:0;resize:none;background:transparent;border:none;outline:none;color:var(--ink2);font-family:var(--mono);font-size:13px;line-height:1.5;max-height:180px;overflow-y:hidden;transition:height .12s ease}.si-input::placeholder{color:var(--muted)}.si-hint{font-size:11px;color:var(--muted)}.si-hint code{color:var(--blue);background:var(--panel2);padding:0 4px;border-radius:3px}.si-agent-glyph{width:16px;height:16px;display:block;fill:currentColor}.si-launcher-picker{display:inline-flex;align-items:center;gap:6px}.si-launcher-label{font-size:11px;color:var(--ink2);text-transform:lowercase}.si-launcher-harness{display:inline-flex;align-items:center;color:var(--ink2)}.si-launcher-select{height:28px;padding:0 8px;cursor:pointer;color:var(--ink);background:var(--panel);border:1px solid var(--line);border-radius:999px;font-size:12px;transition:border-color .12s ease}.si-launcher-select:hover{border-color:var(--blue)}.si-content.is-session .si-term-body{animation:si-expand .22s ease}.si-content.is-session{--si-dock-h: 44px}.si-tabbar{flex:none;display:flex;align-items:stretch;gap:8px;padding:0 10px;background:var(--panel);border-bottom:1px solid var(--line)}.si-tabs{flex:none;display:flex;gap:2px}.si-tab{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:none;cursor:pointer;padding:8px 14px;font-family:var(--mono);font-size:12px;color:var(--muted);border-bottom:2px solid transparent;margin-bottom:-1px}.si-tab:hover{color:var(--ink2)}.si-tab.on{color:var(--ink2);border-bottom-color:var(--blue)}.si-th-name{flex:1;min-width:0;align-self:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--mono);font-size:12px;color:var(--ink2)}.si-term-body{flex:1;min-height:0;overflow:hidden;padding:6px 10px 0;margin-bottom:var(--si-dock-h);background:var(--term-bg)}.si-line{font-size:12px;line-height:1.6;color:var(--ink2);white-space:pre-wrap;word-break:break-word}.si-line.muted{color:var(--muted)}.si-op{color:var(--blue);font-weight:700}.si-bottom{position:absolute;left:0;right:0;bottom:0;z-index:2;display:flex;align-items:flex-end;gap:8px;min-height:44px;box-sizing:border-box;padding:11px 14px 12px;border-top:1px solid var(--line);background:var(--panel)}.si-prompt{color:var(--blue);flex:none;line-height:20px}.si-bottom .si-input:disabled{color:var(--muted)}.si-bottom.err{border-color:var(--orange)}.si-send-err{flex:none;color:var(--orange);font-size:11px;white-space:nowrap}.si-attach{flex:none;align-self:flex-end;display:inline-flex;align-items:center;background:transparent;border:none;cursor:pointer;color:var(--muted);line-height:1.5;padding:0 2px}.si-attach:hover:not(:disabled){color:var(--blue)}.si-attach:disabled{opacity:.5;cursor:default}.si-attach-busy{animation:si-attach-spin .8s linear infinite}@keyframes si-attach-spin{to{transform:rotate(360deg)}}.si-inputwrap.dragover,.si-bottom.dragover{border-color:var(--green)}.si-attach-err{flex:none;align-self:center;color:var(--orange);font-size:11px;white-space:nowrap}@keyframes si-rise{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}@keyframes si-expand{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}.st-wrap{position:relative;width:100%;height:100%}.st-host{width:100%;height:100%;overflow:hidden}.st-copyhint{position:absolute;right:8px;bottom:7px;pointer-events:none;z-index:2;font-family:var(--mono);font-size:11px;line-height:1.25;letter-spacing:.2px;color:#cfe8e8;opacity:.85;background:#073642eb;border:1px solid rgba(131,148,150,.35);padding:3px 8px;border-radius:5px;transition:opacity .15s ease,color .15s ease,background .15s ease,border-color .15s ease}.st-wrap:hover .st-copyhint{opacity:1}.st-copyhint.copied{opacity:1;color:#fdf6e3;background:#859900f2;border-color:#859900f2}.st-copyhint.reconnecting{opacity:1;color:#fdf6e3;background:#cb4b16f2;border-color:#cb4b16f2;animation:st-recon-pulse 1.1s ease-in-out infinite}@keyframes st-recon-pulse{0%,to{opacity:1}50%{opacity:.55}}.st-host .xterm{width:100%!important;height:100%!important;background:var(--term-bg)!important}.st-host .xterm-screen{background:var(--term-bg)!important}.st-host .xterm-viewport{overflow-y:auto!important;overflow-x:hidden!important;background:var(--term-bg)!important}.si-bottom .si-input{color:var(--ink2);min-height:20px;line-height:20px;padding:0}.si-bottom .si-attach{height:20px;line-height:20px}.si-actions{margin-left:auto;display:flex;align-items:center;gap:6px}.si-act{background:var(--paper);border:1px solid var(--line);color:var(--ink2);border-radius:4px;padding:1px 9px;cursor:pointer;font-family:var(--mono);font-size:11px}.si-act:hover,.si-act.go{border-color:var(--blue);color:var(--blue)}.si-act.go:hover{background:var(--blue);color:var(--paper)}.si-act.big{padding:8px 18px;font-size:13px;margin-top:8px}.sc-red{--sc: var(--red)}.sc-green{--sc: var(--green)}.sc-yellow{--sc: var(--yellow)}.sc-cyan{--sc: var(--cyan)}.sc-muted{--sc: var(--muted)}.si-act.ui{border-color:var(--sc);color:var(--sc)}.si-act.ui:hover{background:var(--sc);color:var(--paper);border-color:var(--sc)}.si-act.ui.type.on{background:var(--sc);color:var(--paper)}.si-act.ui.type.suggest{animation:si-type-pulse 1.1s ease-in-out infinite}@keyframes si-type-pulse{0%,to{box-shadow:0 0 #b5890080}50%{box-shadow:0 0 0 3px #b5890000}}.si-bottom.type{border-color:var(--yellow);background:var(--panel2);cursor:pointer;gap:10px}.si-type-ind{color:var(--yellow);font-weight:700;flex:none}.si-type-help{color:var(--muted);font-size:11px}.si-offline{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;text-align:center;padding:24px}.si-offline-msg{font-size:15px;color:var(--ink2)}.si-offline-sub{font-size:12px;color:var(--muted);max-width:460px;line-height:1.6}.si-offline-sub code{color:var(--blue);background:var(--panel2);padding:0 4px;border-radius:3px}.si-offline-err{margin-top:10px;font-size:12px;color:var(--yellow);max-width:460px;line-height:1.55;border:1px solid var(--yellow);border-radius:4px;padding:8px 10px;background:var(--panel2)}.m-app{position:fixed;top:0;right:0;bottom:0;left:0;z-index:10;display:flex;flex-direction:column;background:var(--paper);color:var(--ink);font-family:var(--mono);-webkit-tap-highlight-color:transparent}.m-main{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden;overscroll-behavior:contain;padding-top:env(safe-area-inset-top,0)}.m-app .m-timeline,.m-app .m-children,.m-app .m-sesslist,.m-app .m-sessdetail,.m-app .m-pane-host .pane-doc,.m-app .m-pane-host .pane-hist,.m-app .m-pane-host .pane-issues,.m-app .m-pane-host .pane-eval,.m-app .m-pane-host .pane-edit{overflow-x:hidden}.m-app pre,.m-app code,.m-app .diff-line,.m-app .hist-diff pre{white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;max-width:100%}.m-app .ev-diff{overflow-x:hidden}.m-app .ev-diff .dl{white-space:pre-wrap;overflow-wrap:anywhere}.m-app .m-ev-from{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.m-app img,.m-app video{max-width:100%;height:auto}.m-specs{flex:1;min-height:0;display:flex;flex-direction:column}.m-crumbs{flex:none;display:flex;flex-wrap:wrap;align-items:center;gap:2px;padding:7px 10px;border-bottom:1px solid var(--line);background:var(--paper)}.m-crumb{display:inline-flex;align-items:center;gap:2px}.m-crumb-sep{color:var(--muted)}.m-crumb-btn{background:none;border:none;padding:2px 3px;font-family:var(--mono);font-size:12px;color:var(--muted);cursor:pointer}.m-crumb-btn.cur{color:var(--ink2);font-weight:700}.m-node{flex:1;min-height:0;display:flex;flex-direction:column}.m-node-head{flex:none;display:flex;align-items:center;flex-wrap:wrap;gap:6px 8px;padding:12px 14px 8px}.m-dot{flex:none;width:9px;height:9px;border-radius:50%}.m-node-title{font-size:16px;font-weight:700;color:var(--ink2)}.m-node-ver{font-size:12px;color:var(--muted)}.m-node-status{font-size:11px;color:var(--ink);padding:1px 6px;border:1px solid var(--line);border-radius:999px}.m-node-faces{display:inline-flex;gap:2px}.m-node-desc{flex:none;padding:0 14px 10px;color:var(--ink);font-size:13px;line-height:1.5}.m-tabs{flex:none;display:flex;flex-wrap:wrap;gap:6px;padding:0 10px 8px;border-bottom:1px solid var(--line)}.m-tab{flex:none;padding:6px 12px;min-height:32px;cursor:pointer;font-family:var(--mono);font-size:12px;color:var(--ink);background:var(--panel);border:1px solid var(--line);border-radius:999px}.m-tab.on{color:var(--paper);background:var(--blue);border-color:var(--blue)}.m-pane-host{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.m-pane-host>*{flex:1;min-height:0}.m-pane-host.fixed .pane-hist{height:100%}.m-pane-host .pane-doc{overflow:auto;-webkit-overflow-scrolling:touch;padding:14px}.m-pane-host .pane-doc>h1,.m-pane-host .pane-doc>blockquote{display:none}.m-pane-host .pane-issues,.m-pane-host .pane-edit{-webkit-overflow-scrolling:touch}.m-children{overflow:auto;-webkit-overflow-scrolling:touch}.m-row{display:flex;align-items:center;gap:9px;width:100%;min-height:52px;padding:10px 14px;text-align:left;cursor:pointer;background:var(--paper);border:none;border-bottom:1px solid var(--line);font-family:var(--mono);color:var(--ink)}.m-row:active{background:var(--panel)}.m-row[disabled]{opacity:.5}.m-row-title{flex:1;min-width:0;font-size:14px;color:var(--ink2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.m-row-faces{flex:none;display:inline-flex;gap:2px}.m-row-ver{flex:none;font-size:11px;color:var(--muted)}.m-row-kids{flex:none;font-size:11px;color:var(--blue)}.m-row-chev{flex:none;color:var(--muted);font-size:18px}.m-tabbar{flex:none;display:flex;border-top:1px solid var(--line);background:var(--panel);padding-bottom:env(safe-area-inset-bottom,0)}.m-tabbar-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;height:52px;cursor:pointer;background:none;border:none;font-family:var(--mono);font-size:13px;color:var(--muted)}.m-tabbar-btn.on{color:var(--blue);font-weight:700;box-shadow:inset 0 2px 0 var(--blue)}.m-tabbar-ico{font-size:13px}.m-tabbar-badge{min-width:16px;padding:0 4px;border-radius:999px;background:var(--orange);color:var(--paper);font-size:10px;line-height:16px;text-align:center}.m-sesslist{flex:1;min-height:0;overflow:auto;-webkit-overflow-scrolling:touch}.m-sess-row{display:flex;flex-wrap:nowrap;align-items:center;gap:8px;width:100%;padding:12px 14px;text-align:left;cursor:pointer;font-size:14px;color:var(--ink2);background:var(--paper);border:none;border-bottom:1px solid var(--line);font-family:var(--mono)}.m-sess-row:active{background:var(--panel)}.m-sess-row .sess-id{flex:1;min-width:0;font-weight:700;color:var(--ink2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.m-sess-row .sess-meta{flex:none;width:auto;min-width:0;margin-top:0;margin-left:auto;font-size:12px}.m-sess-row .sess-glyph{font-size:13px;width:14px}.m-sess-row .sess-ops{color:var(--muted)}.m-zone{padding:10px 14px 4px;font-size:10px;font-weight:700;letter-spacing:.5px;color:var(--muted);text-transform:uppercase}.m-sessdetail{flex:1;min-height:0;overflow:auto;-webkit-overflow-scrolling:touch}.m-sessdetail.chat{display:flex;flex-direction:column;overflow:hidden}.m-sess-card{flex:none;display:flex;align-items:center;gap:10px;padding:12px 14px 12px 6px;border-bottom:1px solid var(--line)}.m-sess-back{flex:none;width:36px;height:36px;display:flex;align-items:center;justify-content:center;font-size:26px;line-height:1;color:var(--blue);background:none;border:none;cursor:pointer}.m-sess-meta{display:flex;flex-direction:column;gap:2px;min-width:0}.m-sess-name{font-size:16px;font-weight:700;color:var(--ink2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.m-sess-status{font-size:12px;color:var(--muted)}.m-sess-id8{color:var(--muted)}.m-timeline{flex:1;min-height:0;overflow:auto;-webkit-overflow-scrolling:touch;padding:10px 14px}.m-day{text-align:center;margin:10px 0 6px;font-size:10px;letter-spacing:.5px;color:var(--muted);text-transform:uppercase}.m-ev{margin:8px 0}.m-ev-head{display:flex;align-items:baseline;gap:7px;font-family:var(--mono);font-size:12px}.m-ev-glyph{flex:none}.m-ev-word{font-weight:700}.m-ev-time{margin-left:auto;flex:none;font-size:10px;color:var(--muted)}.m-ev-note{margin:4px 0 0 14px;padding:8px 10px;font-size:13px;line-height:1.55;color:var(--ink2);white-space:pre-wrap;overflow-wrap:anywhere;background:var(--panel);border-left:2px solid var(--line);border-radius:0 6px 6px 0}.m-ev-sent .m-ev-from{font-weight:700;color:var(--blue)}.m-ev-text{margin:4px 0 0 14px;padding:8px 10px;font-size:13px;line-height:1.55;color:var(--ink);white-space:pre-wrap;overflow-wrap:anywhere;background:color-mix(in srgb,var(--blue) 7%,var(--paper));border-left:2px solid color-mix(in srgb,var(--blue) 45%,var(--line));border-radius:0 6px 6px 0}.m-ev-prompt summary{font-family:var(--mono);font-size:12px;color:var(--muted);cursor:pointer}.m-offline,.m-senderr{flex:none;padding:6px 14px;font-size:12px}.m-offline{color:var(--muted);background:var(--panel);border-top:1px solid var(--line)}.m-senderr{color:var(--red)}.m-composer{flex:none;padding:8px 14px 10px;border-top:1px solid var(--line);background:var(--panel)}.m-composer-line{display:flex;align-items:flex-end;gap:8px}.m-input{flex:1;min-width:0;resize:none;padding:9px 10px;font-family:var(--mono);font-size:14px;color:var(--ink2);background:var(--paper);border:1px solid var(--line);border-radius:8px;outline:none}.m-input:focus{border-color:var(--blue)}.m-send{flex:none;height:38px;padding:0 16px;font-family:var(--mono);font-size:13px;font-weight:700;color:var(--paper);background:var(--blue);border:none;border-radius:8px;cursor:pointer}.m-send:disabled{opacity:.45;cursor:default}.m-empty{padding:18px 14px;color:var(--muted);font-size:13px}.m-empty.big{flex:1;display:flex;align-items:center;justify-content:center;text-align:center}.m-new-btn{display:flex;align-items:center;gap:8px;width:100%;padding:12px 14px;font-family:var(--mono);font-size:14px;font-weight:700;color:var(--blue);text-align:left;background:var(--paper);border:none;border-bottom:1px solid var(--line);cursor:pointer}.m-new-btn:active{background:var(--panel)}.m-new-btn-plus{flex:none;font-size:16px;line-height:1}.m-new-body{display:flex;flex-direction:column;gap:12px;padding:14px}.m-new-input{min-height:120px}.m-new-launcher{display:flex;align-items:center;gap:10px;font-size:13px;color:var(--muted)}.m-new-launcher-select{flex:1;min-width:0;padding:8px 10px;font-family:var(--mono);font-size:14px;color:var(--ink2);background:var(--paper);border:1px solid var(--line);border-radius:8px}.m-new .m-senderr{padding:0}.m-new-go{height:42px}.pane-eval{flex:1;min-width:0;padding:16px 20px;overflow:auto}.pane-eval.empty{color:var(--muted);font-size:13px}.eval-todo-note{color:var(--muted);font-size:13px;margin:0 0 14px}.eval-declared-row .eval-expected{padding-left:23px;margin:4px 0 0}.eval-tracks{color:var(--muted);font-family:var(--mono);font-size:10px;opacity:.85}.eval-dangling-badge{color:var(--orange);margin-right:6px}.eval-dangling-name{text-decoration:line-through;color:var(--muted)}.eval-dangling-tag{font-family:var(--mono);font-size:10px;color:var(--orange);margin-left:8px;border:1px dashed color-mix(in srgb,var(--orange) 45%,var(--line));border-radius:3px;padding:0 5px}.eval-dangling-remarks{padding-left:23px;margin:6px 0 0}.eval-row{border-bottom:1px solid var(--line);padding:10px 0}.eval-row:last-child{border-bottom:0}.eval-head{display:flex;flex-direction:column;gap:4px;width:100%;text-align:left;background:none;border:0;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer}.eval-top{display:flex;align-items:center;gap:8px}.eval-caret{flex:none;color:var(--muted);font-size:11px}.eval-scenario{font-size:13px;font-weight:600;color:var(--ink2)}.eval-meta{display:flex;align-items:center;gap:10px;font-size:11px;color:var(--muted)}.eval-sha{color:var(--blue);background:var(--panel2);padding:0 4px;border-radius:3px}.eval-verdict{flex:none;font-size:10px;font-weight:700;border-radius:3px;padding:0 4px;line-height:1.5}.eval-verdict.pass{color:var(--green);border:1px solid var(--green)}.eval-verdict.fail{color:var(--orange);border:1px solid var(--orange)}.eval-verdict.note{color:var(--blue);border:1px solid var(--blue);cursor:help}.eval-verdict.legacy{color:var(--muted);border:1px solid var(--line)}.eval-shot{margin:8px 0 2px;padding-left:19px}.eval-shot img,.eval-video{display:block;max-width:100%;background:var(--panel2);border:1px solid var(--line);border-radius:4px}.eval-noimg{font-size:11px;color:var(--muted)}.eval-expected,.eval-note{font-size:12px;color:var(--ink2);margin:0 0 6px}.eval-expected-label{color:var(--muted);font-weight:600}.eval-transcript{font-family:var(--mono);font-size:11px;color:var(--ink2);background:var(--panel2);border:1px solid var(--line);border-radius:4px;padding:8px 10px;margin:0;max-height:360px;overflow:auto;white-space:pre-wrap;word-break:break-word}.eval-transcript.loading{color:var(--muted)}.eval-datawrap{border:1px solid var(--line);border-left:3px solid var(--acc, #6b46c1);border-radius:4px;overflow:hidden}.eval-datahead{font-family:var(--mono);font-size:10px;letter-spacing:.04em;text-transform:uppercase;color:var(--muted);background:var(--panel);padding:4px 10px;border-bottom:1px solid var(--line)}.eval-data{font-family:var(--mono);font-size:11px;color:var(--ink2);background:var(--panel2);padding:8px 10px;margin:0;max-height:360px;overflow:auto;white-space:pre-wrap;word-break:break-word}.eval-data.loading{color:var(--muted)}.ef-chipbar{display:flex;gap:6px;flex-wrap:wrap}.ef-chip{font:inherit;font-size:11px;color:var(--muted);background:var(--panel2);border:1px solid var(--line);border-radius:6px;padding:3px 10px;cursor:pointer;line-height:1.35;transition:color .12s,border-color .12s,background .12s}.ef-chip:hover{color:var(--ink2);border-color:color-mix(in srgb,var(--blue) 45%,var(--line))}.ef-chip.on{color:var(--paper);background:var(--blue);border-color:var(--blue)}.ef-close{font:inherit;color:var(--muted);background:none;border:0;cursor:pointer;padding:2px 6px}.ef-empty,.ef-miss{color:var(--muted);font-size:12px;padding:18px 8px}.ef-scenario{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600;font-size:12px}.ef-node{flex:0 1 auto;max-width:58px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:10.5px;opacity:.9}.ef-kind{flex:none;margin-left:auto;font-size:11px;color:var(--muted)}.ef-time{flex:none;color:var(--muted);font-size:10.5px;min-width:26px;text-align:right}.ef-okd{flex:none;color:var(--green);font-size:11px}.ef-okbtn{flex:none;font-size:10.5px;color:var(--muted);border:1px solid var(--line);border-radius:4px;padding:0 5px;line-height:1.5;opacity:0;transition:opacity .12s,color .12s,border-color .12s}.ef-row:hover .ef-okbtn,.ef-row.sel .ef-okbtn{opacity:1}.ef-okbtn:hover{color:var(--green);border-color:color-mix(in srgb,var(--green) 55%,var(--line))}.an-detail{height:100%;min-height:0;display:flex;flex-direction:column;padding:0}.an-head{flex:none;display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:10px 16px;border-bottom:1px solid var(--line);background:var(--panel2)}.an-work{flex:1 1 auto;min-height:0;display:grid;grid-template-columns:minmax(0,1fr) minmax(280px,340px)}.an-stage-col{min-width:0;min-height:0;overflow-y:auto;padding:12px 16px 20px}.an-rail{min-width:0;min-height:0;display:flex;flex-direction:column;border-left:1px solid var(--line);background:var(--panel)}.an-rail .an-comments-head{flex:none;padding:10px 12px 8px;border-bottom:1px solid var(--line)}.an-rail-list{flex:1 1 auto;min-height:0;overflow-y:auto;padding:4px 12px 8px}.an-rail-list .fv-reply{margin-top:8px}.an-rail-compose{flex:none;border-top:1px solid var(--line);padding:0 12px 10px;background:var(--panel)}.an-rail-compose .fv-compose{margin-top:8px;padding-top:0;border-top:none}@media (max-width: 900px){.an-detail{height:auto}.an-work{display:block}.an-stage-col{overflow:visible}.an-rail{border-left:none;border-top:1px solid var(--line)}.an-rail-list{overflow:visible}.an-rail-compose{position:sticky;bottom:0;z-index:2}}.an-meta{font-size:10.5px;color:var(--muted)}.an-image{display:block;max-width:100%;border:1px solid var(--line);border-radius:6px;background:var(--panel2);cursor:zoom-in}.lightbox{position:fixed;top:0;right:0;bottom:0;left:0;z-index:100;display:flex;align-items:center;justify-content:center;background:#000000d1;cursor:zoom-out}.lightbox img{max-width:96vw;max-height:96vh;object-fit:contain;border-radius:4px;box-shadow:0 8px 48px #0009}.an-prior-note{color:var(--muted)}.an-stale{color:var(--muted);font-family:var(--mono);font-size:11px}.an-stale b{color:var(--ink2);font-weight:700}.an-stale-files{opacity:.85}.an-title{font-weight:700;font-size:14px;color:var(--ink)}.an-node{font-size:11px;color:var(--muted)}.an-expected{font-size:12px;color:var(--ink2);padding:8px 0 0}.an-comments-head{font-size:10px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:var(--ink2)}.an-stage-col>*{max-width:1100px}.an-gallery{display:flex;flex-direction:column;gap:10px;margin:12px 0 0}.an-detail>.an-expected,.an-detail>.an-hint{padding:8px 16px 0}.an-stage{position:relative;margin:12px 0 0;cursor:crosshair}.an-video{display:block;width:100%;background:#000;border:1px solid var(--line);border-radius:6px 6px 0 0}.an-rect{position:absolute;border:2px solid var(--orange);border-radius:3px;background:#ff963c1f;pointer-events:none}.an-rect.live{border-style:dashed}.an-bigplay{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:54px;height:54px;border-radius:50%;background:#080c1280;border:1px solid rgba(255,255,255,.22);display:flex;align-items:center;justify-content:center;font-size:22px;color:#fff;pointer-events:none;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}.an-bar{display:flex;align-items:center;gap:10px;padding:6px 8px;background:var(--panel2);border:1px solid var(--line);border-top:none;border-radius:0 0 6px 6px}.an-play{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:30px;height:26px;color:var(--ink);background:var(--panel);border:1px solid var(--line);border-radius:5px;cursor:pointer}.an-play:hover{border-color:var(--blue)}.an-fs{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:30px;height:26px;color:var(--ink);background:var(--panel);border:1px solid var(--line);border-radius:5px;cursor:pointer}.an-fs:hover{border-color:var(--blue)}.an-player:fullscreen{display:flex;flex-direction:column;align-items:center;justify-content:center;background:#000}.an-player:fullscreen .an-stage{margin:0;max-width:100vw;max-height:calc(100vh - 38px)}.an-player:fullscreen .an-video{width:auto;max-width:100vw;max-height:calc(100vh - 38px);border:none;border-radius:0}.an-player:fullscreen .an-bar{width:min(100vw,1100px);border-radius:0}.an-player:-webkit-full-screen{display:flex;flex-direction:column;align-items:center;justify-content:center;background:#000}.an-player:-webkit-full-screen .an-stage{margin:0;max-width:100vw;max-height:calc(100vh - 38px)}.an-player:-webkit-full-screen .an-video{width:auto;max-width:100vw;max-height:calc(100vh - 38px);border:none;border-radius:0}.an-player:-webkit-full-screen .an-bar{width:min(100vw,1100px);border-radius:0}.an-time{flex:0 0 auto;font-family:var(--mono);font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums}.an-curstep{flex:0 0 auto;font-size:10.5px;color:var(--green);background:color-mix(in srgb,var(--green) 12%,transparent);border:1px solid color-mix(in srgb,var(--green) 35%,var(--line));border-radius:3px;padding:1px 7px;max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.an-seek{flex:1 1 auto;position:relative;height:18px;display:flex;align-items:center;cursor:pointer}.an-seek-trk{position:absolute;left:0;right:0;height:5px;background:var(--line);border-radius:5px}.an-seek-play{position:absolute;left:0;height:5px;background:var(--blue);border-radius:5px;transition:width .12s linear}.an-knob{position:absolute;width:12px;height:12px;background:var(--ink);border:2px solid var(--paper);border-radius:50%;transform:translate(-50%);box-shadow:0 1px 4px #0006;transition:left .12s linear}.an-band{position:absolute;top:2px;width:2px;height:14px;background:color-mix(in srgb,var(--green) 45%,transparent);transform:translate(-50%);pointer-events:none}.an-mk{position:absolute;top:-1px;width:10px;height:20px;padding:0;transform:translate(-50%);background:none;border:none;cursor:pointer}.an-mk:after{content:"";display:block;margin:0 auto;width:3px;height:20px;border-radius:2px;background:var(--orange);box-shadow:0 0 0 1px var(--paper);transition:transform .12s,background .12s}.an-mk:hover:after{transform:scaleX(1.8)}.an-mk.active:after{background:var(--green)}.an-mk.on:after{transform:scaleX(2);background:var(--orange);box-shadow:0 0 0 1px var(--paper),0 0 8px color-mix(in srgb,var(--orange) 60%,transparent)}.an-seek-hov{position:absolute;bottom:22px;transform:translate(-50%);background:var(--panel);border:1px solid var(--line);color:var(--ink);font-family:var(--mono);font-size:10.5px;padding:1px 6px;border-radius:4px;pointer-events:none;white-space:nowrap}.an-keys{font-family:var(--mono);font-size:10px;color:var(--muted);padding:4px 0 0;letter-spacing:.2px}.an-ruler{display:flex;flex-wrap:wrap;gap:4px;padding:8px 0 0}.an-step{font:inherit;font-size:11px;color:var(--ink2);background:var(--panel2);border:1px solid var(--line);border-radius:4px;padding:2px 8px;cursor:pointer}.an-step.on{border-color:var(--green);color:var(--ink)}.an-hint{color:var(--muted);font-size:11px;padding:8px 0 0}.an-flash{font-size:12px;color:var(--green);padding:4px 0 0}.an-verdict-badge{font-size:12px;font-weight:700;line-height:1}.an-verdict-badge.pass{color:var(--green)}.an-verdict-badge.fail{color:var(--orange)}.an-verdict-badge.legacy{color:var(--muted)}.an-okd{font-size:11px;color:var(--green);white-space:nowrap}.an-okbtn{font:inherit;font-size:11px;color:var(--ink2);background:var(--panel);border:1px solid var(--line);border-radius:5px;padding:2px 8px;cursor:pointer;white-space:nowrap;transition:color .12s,border-color .12s}.an-okbtn:hover{color:var(--green);border-color:color-mix(in srgb,var(--green) 55%,var(--line))}.an-ab{display:flex;align-items:center;gap:8px;margin-left:auto}.an-ab-nav{display:inline-flex;align-items:center;justify-content:center;line-height:1;color:var(--ink2);background:var(--panel2);border:1px solid var(--line);border-radius:4px;padding:2px 6px;cursor:pointer}.an-ab-nav:disabled{opacity:.35;cursor:default}.an-ab-track{display:flex;flex-wrap:wrap;gap:3px;align-items:center}.an-ab-pip{font:inherit;font-size:11px;font-weight:700;width:20px;height:20px;display:flex;align-items:center;justify-content:center;background:var(--panel2);border:1px solid var(--line);border-radius:4px;cursor:pointer;color:var(--muted)}.an-ab-pip.pass{color:var(--green)}.an-ab-pip.fail{color:var(--orange)}.an-ab-pip.on{outline:2px solid var(--blue);outline-offset:0;border-color:var(--blue)}.an-ab-pos{font-size:10.5px;color:var(--muted);margin-left:2px}.se-pane{flex:1;min-height:0;display:flex;flex-direction:column;background:var(--paper)}.se-gates{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 14px;border-bottom:1px solid var(--line);background:var(--panel2)}.se-gate{font-family:var(--mono);font-size:10.5px;border:1px solid var(--line);border-radius:3px;padding:1px 7px;color:var(--muted)}.se-gate.ok{color:var(--green);border-color:color-mix(in srgb,var(--green) 40%,var(--line))}.se-gate.bad{color:var(--red);border-color:color-mix(in srgb,var(--red) 40%,var(--line))}.se-export{margin-left:auto;display:inline-flex;align-items:center;color:var(--blue);text-decoration:none}.se-export:hover{color:color-mix(in srgb,var(--blue) 72%,var(--ink))}.se-blind .ef-scenario{color:var(--muted)}.se-warn{font-family:var(--mono);font-size:10px;color:var(--orange)}.ef-insession{color:var(--blue);font-size:10px;flex:none}.se-divider{display:flex;align-items:center;gap:8px;padding:5px 10px 3px;font-family:var(--mono);font-size:9px;letter-spacing:.8px;text-transform:uppercase;color:var(--muted)}.se-divider:before,.se-divider:after{content:"";flex:1;border-top:1px dashed var(--line)}.fv-filter{font-family:var(--mono);font-size:11px;line-height:1.35;color:var(--ink2);background:var(--panel2);border:1px solid var(--line);border-radius:6px;padding:3px 8px;cursor:pointer;transition:border-color .12s}.fv-filter:hover{border-color:color-mix(in srgb,var(--blue) 45%,var(--line))}.loading{display:flex;align-items:center;justify-content:center;gap:10px;height:100%;flex:1;color:var(--muted);font-family:var(--mono);font-size:13px}.loading.load-error{color:var(--red)}.load-retry{font-family:var(--mono);font-size:12px;color:var(--ink2);background:var(--panel2);border:1px solid var(--line);border-radius:4px;padding:3px 10px;cursor:pointer}.load-retry:hover{border-color:var(--muted)}.chrono-tools{display:flex;justify-content:flex-end;margin:0 0 6px}.chrono-all{font-family:var(--mono);font-size:10px;color:var(--muted);background:none;border:1px solid var(--line);border-radius:4px;padding:2px 8px;cursor:pointer}.chrono-all:hover{color:var(--ink2);border-color:var(--muted)}.pane-filter{position:sticky;top:-16px;z-index:2;margin:-8px 0 8px;padding:6px 0;background:var(--panel)}.pane-filter-input{width:100%;box-sizing:border-box;background:var(--panel2);border:1px solid var(--line);border-radius:4px;outline:none;color:var(--ink2);font-family:var(--mono);font-size:11px;padding:4px 8px}.pane-filter-input:focus{border-color:var(--muted)}.pane-filter-input::placeholder{color:var(--muted)}.pane-filter-none{color:var(--muted);font-family:var(--mono);font-size:11px;padding:6px 2px}.ui-tip{position:fixed;z-index:100;pointer-events:none;background:var(--panel2);color:var(--ink2);border:1px solid var(--line);border-radius:6px;padding:4px 9px;font-family:var(--mono);font-size:11px;line-height:1.5;max-width:320px;white-space:pre-line;overflow-wrap:break-word;box-shadow:0 4px 16px #00000038;opacity:0;transition:opacity .14s ease,transform .14s ease}.ui-tip[data-place=top]{transform:translateY(3px)}.ui-tip[data-place=bottom]{transform:translateY(-3px)}.ui-tip.show{opacity:1;transform:translateY(0)}.ui-tip-arrow{position:absolute;width:8px;height:8px;background:var(--panel2);border-right:1px solid var(--line);border-bottom:1px solid var(--line)}.ui-tip[data-place=top] .ui-tip-arrow{bottom:-4.5px;transform:rotate(45deg)}.ui-tip[data-place=bottom] .ui-tip-arrow{top:-4.5px;transform:rotate(225deg)}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{r as f,f as p,h as m}from"./index-BQu-oJ8J.js";import{V as d,W as y}from"./SessionWindow-JYbpPwNB.js";const $=(t,a,n)=>{const s=t.match(/^\/(\S+)\s*([\s\S]*)$/);if(!s)return t;const o=a.find(i=>i.name===s[1]);if(!o)return t;const e=[],r=s[2].replace(d,(i,h)=>(e.push(h),"")).trim(),u=e.length?e.map(i=>{const h=n.find(l=>l.id===i);return h?`- [[${h.id}]] — ${y(h.path)}`:`- [[${i}]]`}).join(`
|
|
2
|
+
`):"(No target was mentioned. If the prompt names the scope, use it; otherwise ask the human to define the scope before proceeding — unless this task needs no scope, in which case proceed.)",c=o.body.includes("{{targets}}")?o.body.replace("{{targets}}",u):`${o.body}
|
|
3
|
+
|
|
4
|
+
${u}`;return r?`${c}
|
|
5
|
+
|
|
6
|
+
${r}`:c};async function b(t,a){try{const n=await fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a?{prompt:t,launcher:a}:{prompt:t})}),s=await n.json().catch(()=>null);return{ok:n.ok,error:s==null?void 0:s.error}}catch{return{ok:!1}}}function k(){const[t,a]=f.useState([]),[n,s]=f.useState(()=>{try{return localStorage.getItem("si.launcher")||""}catch{return""}}),o=e=>{s(e);try{localStorage.setItem("si.launcher",e)}catch{}};return f.useEffect(()=>{p().then(e=>{const r=e==null?void 0:e.launchers;!Array.isArray(r)||!r.length||(a(r),s(u=>r.some(c=>c.name===u)?u:e.default&&r.some(c=>c.name===e.default)?e.default:r[0].name))}).catch(()=>{})},[]),{launchers:t,launcher:n,pickLauncher:o}}function E(){const[t,a]=f.useState([]);return f.useEffect(()=>{m().then(n=>{Array.isArray(n)&&a(n)}).catch(()=>{})},[]),t}export{E as a,$ as b,b as c,k as u};
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
} catch (e) { /* localStorage/matchMedia unavailable — default (light) stands */ }
|
|
20
20
|
})()
|
|
21
21
|
</script>
|
|
22
|
-
<script type="module" crossorigin src="/assets/index-
|
|
23
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
22
|
+
<script type="module" crossorigin src="/assets/index-BQu-oJ8J.js"></script>
|
|
23
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BbMkwuix.css">
|
|
24
24
|
</head>
|
|
25
25
|
<body>
|
|
26
26
|
<div id="root"></div>
|
package/spec-eval/src/cli.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { repoRoot, headSha, driftIndex, stagedFiles, git } from '../../spec-cli/
|
|
|
4
4
|
import { loadSpecs } from '../../spec-cli/src/specs.js'
|
|
5
5
|
import { loadConfig, sourceExtRe } from '../../spec-cli/src/lint.js'
|
|
6
6
|
import { mainBranch, envSessionId, readRawRecord } from '../../spec-cli/src/layout.js'
|
|
7
|
-
import { evalNodes, validateScenarios, resolveEvalNode, scenarioHash, EVAL_FILE, type EvalNode } from './scenarios.js'
|
|
7
|
+
import { evalNodes, validateScenarios, resolveEvalNode, scenarioHash, EVAL_FILE, type EvalNode, type ScenarioTestReference } from './scenarios.js'
|
|
8
8
|
import { readReadings, readSidecar, appendReading, appendRetraction, latestPerScenario, evidenceOf, isJsonBlob, type Reading, type Verdict, type Evidence, type EvidenceKind, type Retraction } from './sidecar.js'
|
|
9
9
|
import { staleAxes, contentProbeFor } from './freshness.js'
|
|
10
10
|
import { scenarioIndex } from './scenariofresh.js'
|
|
@@ -115,7 +115,7 @@ async function scan(args: string[] = []): Promise<number> {
|
|
|
115
115
|
if (y) {
|
|
116
116
|
// schema first: a malformed eval.md is the loudest gap — report each violation, then still scan its
|
|
117
117
|
// (leniently-parsed) scenarios for stale/missing so a typo doesn't mask a real freshness gap.
|
|
118
|
-
for (const e of validateScenarios(readFileSync(join(y.dir, EVAL_FILE), 'utf8'), cfg.scenarioTags)) {
|
|
118
|
+
for (const e of validateScenarios(readFileSync(join(y.dir, EVAL_FILE), 'utf8'), cfg.scenarioTags, root)) {
|
|
119
119
|
malformed++
|
|
120
120
|
findings.push(` • eval-schema: '${s.id}' ${e} — fix ${y.evalPath}`)
|
|
121
121
|
}
|
|
@@ -428,6 +428,51 @@ async function retractCmd(args: string[]): Promise<number> {
|
|
|
428
428
|
return 0
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
+
// `spex eval ok` — the HUMAN sign-off on a scenario's latest reading ([[human-ok]]): appends a monotonic
|
|
432
|
+
// human-ok event bound to that one immutable reading (a newer reading or live-computed staleness brings the
|
|
433
|
+
// scenario back on its own — no un-ok verb exists). CLI parity with the dashboard's affordance (LAW L);
|
|
434
|
+
// identity is the surface's: 'human' here — and a GOVERNED session is REFUSED, because the sign-off is the
|
|
435
|
+
// human's own deliberate judgment and an agent blessing its own reading would hide it from exactly the
|
|
436
|
+
// review the ok certifies (the no-self-resolve analogue). Flag set closed, like every eval verb.
|
|
437
|
+
async function okCmd(args: string[]): Promise<number> {
|
|
438
|
+
const root = repoRoot()
|
|
439
|
+
for (let i = 0; i < args.length; i++) {
|
|
440
|
+
const a = args[i]
|
|
441
|
+
if (!a.startsWith('--')) continue
|
|
442
|
+
if (a === '--scenario') { i++; continue }
|
|
443
|
+
console.error(`spex eval ok: unknown flag '${a}' — accepts --scenario`)
|
|
444
|
+
return 2
|
|
445
|
+
}
|
|
446
|
+
if (envSessionId()) {
|
|
447
|
+
console.error('spex eval ok: refusing under a governed session — human-ok is the HUMAN\'s sign-off, never an agent\'s self-blessing. Ask the human to ok it from the dashboard or their own terminal; an agent\'s judgment on a reading is a remark (`spex remark add`).')
|
|
448
|
+
return 1
|
|
449
|
+
}
|
|
450
|
+
const sel = positional(args)
|
|
451
|
+
if (!sel || sel === '.') { console.error('spex eval ok: name a node — usage: spex eval ok <node> --scenario <name>'); return 2 }
|
|
452
|
+
const node = stripRefSigil(sel)
|
|
453
|
+
const scName = flag(args, 'scenario')
|
|
454
|
+
const res = resolveEvalNode(evalNodes(root), node)
|
|
455
|
+
if (!res.ok) { console.error(`spex eval ok: ${res.error}`); return 1 }
|
|
456
|
+
const declared = res.node.scenarios.map((s) => s.name)
|
|
457
|
+
const scenario = scName ?? (declared.length === 1 ? declared[0] : undefined)
|
|
458
|
+
if (!scenario) {
|
|
459
|
+
console.error(`spex eval ok: '${res.node.id}' declares ${declared.length} scenarios — name one with --scenario <name> (declared: ${declared.join(', ')})`)
|
|
460
|
+
return 1
|
|
461
|
+
}
|
|
462
|
+
const { fileHumanOk } = await import('./humanok.js')
|
|
463
|
+
const r = fileHumanOk(res.node.id, scenario, 'human')
|
|
464
|
+
if (!r.ok) { console.error(`spex eval ok: ${r.error}`); return 1 }
|
|
465
|
+
if (r.already) {
|
|
466
|
+
console.log(`spex eval ok: '${res.node.id}' scenario '${scenario}' reading @ ${r.humanOk.okTs} is already human-ok'd (by ${r.humanOk.by}, ${r.humanOk.ts}) — monotonic, nothing appended`)
|
|
467
|
+
return 0
|
|
468
|
+
}
|
|
469
|
+
console.log(` ☑ '${res.node.id}' scenario '${scenario}' reading @ ${r.humanOk.okTs} (${r.humanOk.okSha.slice(0, 7)}) human-ok'd`)
|
|
470
|
+
console.log(r.landed === 'committed'
|
|
471
|
+
? 'spex eval ok: 1 sign-off filed (committed straight to the trunk)'
|
|
472
|
+
: 'spex eval ok: 1 sign-off filed (an appended event — commit the sidecar so the sign-off lands)')
|
|
473
|
+
return 0
|
|
474
|
+
}
|
|
475
|
+
|
|
431
476
|
async function clean(args: string[]): Promise<number> {
|
|
432
477
|
const root = repoRoot()
|
|
433
478
|
const all = has(args, 'all')
|
|
@@ -467,7 +512,7 @@ function checkStaged(): number {
|
|
|
467
512
|
for (const rel of staged.filter((p) => p === EVAL_FILE || p.endsWith('/' + EVAL_FILE))) {
|
|
468
513
|
const abs = join(root, rel)
|
|
469
514
|
if (!existsSync(abs)) continue // staged deletion — nothing to validate
|
|
470
|
-
const errs = validateScenarios(readFileSync(abs, 'utf8'), tagLibrary)
|
|
515
|
+
const errs = validateScenarios(readFileSync(abs, 'utf8'), tagLibrary, root)
|
|
471
516
|
if (!errs.length) continue
|
|
472
517
|
bad = true
|
|
473
518
|
console.error(`✗ SpexCode eval: ${rel} — invalid scenario schema:`)
|
|
@@ -521,7 +566,8 @@ export function formatTimeline(tl: EvalTimeline): string {
|
|
|
521
566
|
const ev = list.length
|
|
522
567
|
? list.map((e) => e.state === 'miss' ? 'miss original file' : `${e.kind} ${(e.hash ?? '').slice(0, 12)}…`).join(', ')
|
|
523
568
|
: 'no evidence'
|
|
524
|
-
const
|
|
569
|
+
const okTag = r.humanOk ? ` ☑ human-ok (${r.humanOk.by})` : ''
|
|
570
|
+
const head = ` ${r.scenario.padEnd(w)} ${verdictText(r.verdict)} ${badge}${okTag} ${r.codeSha.slice(0, 7)} ${ev} ${r.ts}`
|
|
525
571
|
return r.expected ? [head, ` ${' '.repeat(w)} expected: ${r.expected}`] : [head]
|
|
526
572
|
})
|
|
527
573
|
return [`spex eval ls: '${tl.node}' — ${tl.readings.length} eval(s), newest first`, '', ...lines, ...retractLines].join('\n')
|
|
@@ -532,6 +578,34 @@ export function formatTimeline(tl: EvalTimeline): string {
|
|
|
532
578
|
// (the collection view); a <node>/`.` scopes to one node. --unmeasured keeps only scenarios with NO
|
|
533
579
|
// effective reading — never measured, or every filing retracted — the blind-spot worklist a measuring
|
|
534
580
|
// hand picks from. Flag set closed, like every eval verb.
|
|
581
|
+
export type ScenarioListRow = {
|
|
582
|
+
node: string
|
|
583
|
+
scenario: string
|
|
584
|
+
tags?: string[]
|
|
585
|
+
test?: ScenarioTestReference
|
|
586
|
+
measured: boolean
|
|
587
|
+
latest?: { verdict?: Verdict; ts: string }
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export function scenarioListRows(nodes: EvalNode[], unmeasuredOnly = false): ScenarioListRow[] {
|
|
591
|
+
const rows: ScenarioListRow[] = []
|
|
592
|
+
for (const n of nodes) {
|
|
593
|
+
const latest = latestPerScenario(readReadings(n.sidecarPath))
|
|
594
|
+
for (const sc of n.scenarios) {
|
|
595
|
+
const r = latest.get(sc.name)
|
|
596
|
+
if (unmeasuredOnly && r) continue
|
|
597
|
+
rows.push({
|
|
598
|
+
node: n.id, scenario: sc.name,
|
|
599
|
+
...(sc.tags?.length ? { tags: sc.tags } : {}),
|
|
600
|
+
...(sc.test ? { test: sc.test } : {}),
|
|
601
|
+
measured: !!r,
|
|
602
|
+
...(r ? { latest: { ...(r.verdict ? { verdict: r.verdict } : {}), ts: r.ts } } : {}),
|
|
603
|
+
})
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return rows
|
|
607
|
+
}
|
|
608
|
+
|
|
535
609
|
async function scenarioLs(args: string[]): Promise<number> {
|
|
536
610
|
for (const a of args) {
|
|
537
611
|
if (!a.startsWith('--')) continue
|
|
@@ -552,21 +626,7 @@ async function scenarioLs(args: string[]): Promise<number> {
|
|
|
552
626
|
nodes = [res.node]
|
|
553
627
|
}
|
|
554
628
|
const unmeasuredOnly = has(args, 'unmeasured')
|
|
555
|
-
|
|
556
|
-
const rows: Row[] = []
|
|
557
|
-
for (const n of nodes) {
|
|
558
|
-
const latest = latestPerScenario(readReadings(n.sidecarPath))
|
|
559
|
-
for (const sc of n.scenarios) {
|
|
560
|
-
const r = latest.get(sc.name)
|
|
561
|
-
if (unmeasuredOnly && r) continue
|
|
562
|
-
rows.push({
|
|
563
|
-
node: n.id, scenario: sc.name,
|
|
564
|
-
...(sc.tags?.length ? { tags: sc.tags } : {}),
|
|
565
|
-
measured: !!r,
|
|
566
|
-
...(r ? { latest: { ...(r.verdict ? { verdict: r.verdict } : {}), ts: r.ts } } : {}),
|
|
567
|
-
})
|
|
568
|
-
}
|
|
569
|
-
}
|
|
629
|
+
const rows = scenarioListRows(nodes, unmeasuredOnly)
|
|
570
630
|
if (has(args, 'json')) { console.log(JSON.stringify(rows, null, 2)); return 0 }
|
|
571
631
|
if (!rows.length) {
|
|
572
632
|
console.log(`spex eval scenario ls: ${unmeasuredOnly ? 'no unmeasured scenarios' : 'no scenarios declared'}${sel ? ` on '${nodes[0]?.id ?? sel}'` : ''}`)
|
|
@@ -585,13 +645,14 @@ async function scenarioLs(args: string[]): Promise<number> {
|
|
|
585
645
|
|
|
586
646
|
// the `spex eval` drawer's node-scoped verbs ([[cli-surface]]): add (file a measurement) · ls (a node's
|
|
587
647
|
// reading timeline) · scenario ls (the declared contracts, --unmeasured = blind spots) · lint (the
|
|
588
|
-
// measurement-layer lint — advisory, always exit 0) · retract · clean.
|
|
648
|
+
// measurement-layer lint — advisory, always exit 0) · ok (the human sign-off) · retract · clean.
|
|
589
649
|
// The session-scoped read (`spex eval ls --session <SEL>`) is intercepted in cli.ts before this runs;
|
|
590
650
|
// `check-staged` is hook plumbing, exported separately for `spex internal check-staged`.
|
|
591
651
|
export async function runEval(args: string[]): Promise<number> {
|
|
592
652
|
const sub = args[0]
|
|
593
653
|
if (sub === 'lint') return scan(args.slice(1))
|
|
594
654
|
if (sub === 'add') return evalCmd(args.slice(1))
|
|
655
|
+
if (sub === 'ok') return okCmd(args.slice(1))
|
|
595
656
|
if (sub === 'retract') return retractCmd(args.slice(1))
|
|
596
657
|
if (sub === 'clean') return clean(args.slice(1))
|
|
597
658
|
if (sub === 'ls') return show(args.slice(1))
|
|
@@ -600,7 +661,7 @@ export async function runEval(args: string[]): Promise<number> {
|
|
|
600
661
|
console.error('spex eval scenario: ls [<node>|.] [--unmeasured] [--json] — list declared scenarios (the measurement contracts)')
|
|
601
662
|
return 2
|
|
602
663
|
}
|
|
603
|
-
console.error('spex eval: add [.|<node>] [--scenario <name>] (--pass|--fail) [--note <text>] [--image <path> …repeatable] [--result <path|->] [--video <path>] [--timeline <json>] | ls [.|<node>] [--json] | ls --session <SEL> [--export] | scenario ls [<node>|.] [--unmeasured] [--json] | lint [--changed] | retract [.|<node>] [--scenario <name>] [--last | --ts <iso>] [--note <why>] | clean [--keep-latest|--all]')
|
|
664
|
+
console.error('spex eval: add [.|<node>] [--scenario <name>] (--pass|--fail) [--note <text>] [--image <path> …repeatable] [--result <path|->] [--video <path>] [--timeline <json>] | ls [.|<node>] [--json] | ls --session <SEL> [--export] | scenario ls [<node>|.] [--unmeasured] [--json] | lint [--changed] | ok <node> [--scenario <name>] | retract [.|<node>] [--scenario <name>] [--last | --ts <iso>] [--note <why>] | clean [--keep-latest|--all]')
|
|
604
665
|
return 2
|
|
605
666
|
}
|
|
606
667
|
|
package/spec-eval/src/evaltab.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { relative, dirname } from 'node:path'
|
|
|
2
2
|
import { repoRoot, driftIndex, historyIndex, type DriftIndex, type HistoryIndex } from '../../spec-cli/src/git.js'
|
|
3
3
|
import { loadSpecs } from '../../spec-cli/src/specs.js'
|
|
4
4
|
import { loadEvalRemarkTracks, trackKey, type RemarkTrack, type Issue, type Reply } from '../../spec-cli/src/issues.js'
|
|
5
|
-
import { evalNodes, type EvalNode } from './scenarios.js'
|
|
6
|
-
import { readSidecar, applyRetractions, evidenceOf, isJsonBlob, type Verdict, type EvidenceKind, type Retraction } from './sidecar.js'
|
|
5
|
+
import { evalNodes, type EvalNode, type ScenarioTestReference } from './scenarios.js'
|
|
6
|
+
import { readSidecar, applyRetractions, evidenceOf, isJsonBlob, humanOkFor, type Verdict, type EvidenceKind, type Retraction } from './sidecar.js'
|
|
7
7
|
import { staleAxes, codeDrift, contentProbeFor, type StaleAxis } from './freshness.js'
|
|
8
8
|
import { scenarioIndex, type ScenarioIndex } from './scenariofresh.js'
|
|
9
9
|
import { hasBlob, getBlob, MISS_BLOB } from './cache.js'
|
|
@@ -64,6 +64,10 @@ export type EvalEntry = {
|
|
|
64
64
|
// so the eval detail pane reads its whole comment thread from the reading overlay — the counterpart to
|
|
65
65
|
// splitting eval-remark threads OUT of the issue surfaces (mergedIssues). Absent until the first remark.
|
|
66
66
|
thread?: Issue
|
|
67
|
+
// the human sign-off bound to THIS reading ([[human-ok]]) — present only on the exact reading an ok row
|
|
68
|
+
// anchors (by scenario + ts), so a newer reading arrives unblessed and the feed's hide releases itself.
|
|
69
|
+
// Rides the board fold verbatim (latestPerScenario is a filter, never a projection).
|
|
70
|
+
humanOk?: { by: string; ts: string }
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
// a remark overlaid onto its display host (a reading, above) → the RemarkView the surfaces read.
|
|
@@ -87,7 +91,7 @@ function toRemarkView(rm: Reply, threadId: string, dangling: boolean): RemarkVie
|
|
|
87
91
|
// at NODE level. `scenario` is the orphaned name (rendered struck-through / gone); `remarks` are all dangling.
|
|
88
92
|
export type DanglingTrack = { scenario: string; threadId: string; thread: Issue; remarks: RemarkView[] }
|
|
89
93
|
|
|
90
|
-
export type ScenarioInfo = { name: string; expected: string; tags?: string[]; code?: string[] }
|
|
94
|
+
export type ScenarioInfo = { name: string; expected: string; tags?: string[]; test?: ScenarioTestReference; code?: string[] }
|
|
91
95
|
|
|
92
96
|
// `hasEvalFile` distinguishes a node that declares no scenarios (no eval.md) from one that declares some but
|
|
93
97
|
// has no readings yet — the tab says different things for each. `scenarios` is the declared set; `readings`
|
|
@@ -160,11 +164,12 @@ export async function evalTimeline(id: string, ctx?: EvalContext): Promise<EvalT
|
|
|
160
164
|
const threadFor = (scenario: string): Issue | undefined => tracks.get(trackKey(id, scenario))?.thread
|
|
161
165
|
const scenarios: ScenarioInfo[] = ynode.scenarios.map((s) => ({
|
|
162
166
|
name: s.name, expected: s.expected,
|
|
163
|
-
...(s.tags?.length ? { tags: s.tags } : {}), ...(s.
|
|
167
|
+
...(s.tags?.length ? { tags: s.tags } : {}), ...(s.test ? { test: s.test } : {}),
|
|
168
|
+
...(s.code?.length ? { code: s.code } : {}),
|
|
164
169
|
}))
|
|
165
170
|
// one raw sidecar read: the effective readings feed the scoreboard rows below; the retraction events ride
|
|
166
|
-
// along as the undo trace (newest-first, like the readings).
|
|
167
|
-
const { readings: rawReadings, retractions } = readSidecar(ynode.sidecarPath)
|
|
171
|
+
// along as the undo trace (newest-first, like the readings), the human-ok events as the sign-off overlay.
|
|
172
|
+
const { readings: rawReadings, retractions, oks } = readSidecar(ynode.sidecarPath)
|
|
168
173
|
// the off-history content fallback ([[eval-core]]): fed to both git axes so a rebased/folded-away
|
|
169
174
|
// anchor with byte-identical governed content reads fresh. Lazy — an in-history reading never probes.
|
|
170
175
|
const probe = contentProbeFor(root)
|
|
@@ -183,6 +188,9 @@ export async function evalTimeline(id: string, ctx?: EvalContext): Promise<EvalT
|
|
|
183
188
|
// first) drives the scalar compat fields for single-evidence consumers.
|
|
184
189
|
const evidence: EvidenceView[] = evidenceOf(r).map((e) => ({ hash: e.hash, kind: e.kind, state: hasBlob(e.hash) ? 'present' : 'miss' }))
|
|
185
190
|
const primary = evidence.find((e) => e.kind === 'video') ?? evidence[0]
|
|
191
|
+
// the sign-off join ([[human-ok]]): the ok binds by exact (scenario, ts), so only the very reading the
|
|
192
|
+
// human blessed carries it — a newer or retract-revealed reading reads unblessed.
|
|
193
|
+
const okRow = humanOkFor(oks, r.scenario, r.ts)
|
|
186
194
|
return {
|
|
187
195
|
scenario: r.scenario,
|
|
188
196
|
expected: byName.get(r.scenario)?.expected ?? '',
|
|
@@ -200,6 +208,7 @@ export async function evalTimeline(id: string, ctx?: EvalContext): Promise<EvalT
|
|
|
200
208
|
...(drift.length ? { codeDrift: drift } : {}),
|
|
201
209
|
blobState: primary ? primary.state : 'none',
|
|
202
210
|
...(threadFor(r.scenario) ? { thread: threadFor(r.scenario) } : {}),
|
|
211
|
+
...(okRow ? { humanOk: { by: okRow.by, ts: okRow.ts } } : {}),
|
|
203
212
|
}
|
|
204
213
|
})
|
|
205
214
|
readings.reverse() // newest-first
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { relative } from 'node:path'
|
|
2
|
+
import { repoRoot } from '../../spec-cli/src/git.js'
|
|
3
|
+
import { commitTrunkData } from '../../spec-cli/src/localIssues.js'
|
|
4
|
+
import { evalNodes, resolveEvalNode } from './scenarios.js'
|
|
5
|
+
import { readReadings, readSidecar, appendHumanOk, humanOkFor, type HumanOk } from './sidecar.js'
|
|
6
|
+
|
|
7
|
+
// @@@ human-ok - the human sign-off on an eval reading ([[human-ok]]). One write, both surfaces: the CLI
|
|
8
|
+
// (`spex eval ok`) and the HTTP route (`POST /api/specs/:id/evals/ok`, identity server-derived 'human')
|
|
9
|
+
// call THIS. The ok binds to ONE immutable reading — the scenario's latest effective reading at ok-time,
|
|
10
|
+
// anchored by its (ts, codeSha) — and is MONOTONIC: no un-ok verb exists, because a newer reading is a
|
|
11
|
+
// different object the ok never transfers to, and staleness is computed live; both bring the scenario back
|
|
12
|
+
// on their own. Durability follows the checkout: on the trunk checkout the append is committed straight to
|
|
13
|
+
// trunk (`--no-verify`, path-scoped, under the shared store lock — the [[local-issues]] discipline,
|
|
14
|
+
// commitTrunkData); on a linked worktree the append stays in that tree and the session's own ritual commit
|
|
15
|
+
// carries it, exactly like every other sidecar write.
|
|
16
|
+
export type OkResult =
|
|
17
|
+
| { ok: true; humanOk: HumanOk; already: boolean; landed: 'committed' | 'uncommitted' }
|
|
18
|
+
| { ok: false; error: string }
|
|
19
|
+
|
|
20
|
+
export function fileHumanOk(nodeId: string, scenario: string, by: string): OkResult {
|
|
21
|
+
const root = repoRoot()
|
|
22
|
+
// the same loud resolution every eval verb applies ([[eval-core]]): exact canonical id, else a unique
|
|
23
|
+
// bare leaf; an ambiguous leaf returns the candidate list instead of blessing an arbitrary node.
|
|
24
|
+
const res = resolveEvalNode(evalNodes(root), nodeId)
|
|
25
|
+
if (!res.ok) return { ok: false, error: res.error }
|
|
26
|
+
const node = res.node
|
|
27
|
+
if (!node.scenarios.some((s) => s.name === scenario) &&
|
|
28
|
+
!readSidecar(node.sidecarPath).readings.some((r) => r.scenario === scenario))
|
|
29
|
+
return { ok: false, error: `'${node.id}' has no scenario '${scenario}'` }
|
|
30
|
+
// the ok's one possible target: the latest EFFECTIVE reading — an ok is a judgment on a measurement that
|
|
31
|
+
// exists and currently counts; an unmeasured (or fully-retracted) scenario has nothing to bless.
|
|
32
|
+
const forScenario = readReadings(node.sidecarPath).filter((r) => r.scenario === scenario)
|
|
33
|
+
if (!forScenario.length) return { ok: false, error: `'${node.id}' scenario '${scenario}' has no effective reading — nothing to ok` }
|
|
34
|
+
const latest = forScenario[forScenario.length - 1]
|
|
35
|
+
// a duplicate ok is idempotent success (the store already IS the requested state — the local-issue
|
|
36
|
+
// close's `already` semantics), never an error and never a second appended row.
|
|
37
|
+
const existing = humanOkFor(readSidecar(node.sidecarPath).oks, scenario, latest.ts)
|
|
38
|
+
if (existing) return { ok: true, humanOk: existing, already: true, landed: 'committed' }
|
|
39
|
+
const row: HumanOk = { kind: 'human-ok', scenario, okTs: latest.ts, okSha: latest.codeSha, by, ts: new Date().toISOString() }
|
|
40
|
+
appendHumanOk(node.sidecarPath, row)
|
|
41
|
+
const landed = commitTrunkData(relative(root, node.sidecarPath), `eval(${node.id}): human-ok '${scenario}' @ ${latest.ts} by ${by}`)
|
|
42
|
+
return { ok: true, humanOk: row, already: false, landed: landed === 'not-primary' ? 'uncommitted' : 'committed' }
|
|
43
|
+
}
|