Files
obelisk/app/obelisk-ui-mini.html
T
tommy0103 28fa99ac94 feat: improve session sharing and Kimi transcript support
- normalize Kimi thinking parts and omit empty placeholders
- add standalone session-sharing and UI demo artifacts
- add regression coverage and refresh the Kimi index marker
- bump the Electron app and CLI packages to v0.2.1
- ignore generated app build assets
2026-07-21 19:29:14 +08:00

259 lines
140 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<!-- PROTOTYPE: faithful, dependency-free desktop UI model of the current Obelisk app. Mock data only. -->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Obelisk UI Mini</title>
<style>
:root{--bg:#0a0b14;--bg2:#11131f;--surface:rgba(255,255,255,.03);--surface2:rgba(255,255,255,.06);--surface3:rgba(255,255,255,.09);--fg:rgba(255,255,255,.92);--fg2:rgba(255,255,255,.72);--muted:rgba(255,255,255,.48);--muted2:rgba(255,255,255,.28);--line:rgba(255,255,255,.05);--line2:rgba(255,255,255,.08);--accent:#a78bfa;--accent2:#c4b5fd;--accentSoft:rgba(167,139,250,.12);--danger:#f87171;--warn:#fbbf24;--green:#34d399;--sans:-apple-system,BlinkMacSystemFont,"Inter","Segoe UI",system-ui,sans-serif;--mono:ui-monospace,"JetBrains Mono","IBM Plex Mono","SF Mono",Menlo,monospace;--sidebar:220px}
*{box-sizing:border-box;margin:0;padding:0}html,body,#app{height:100%;overflow:hidden}body{color:var(--fg);font:13px/1.4 var(--sans);-webkit-font-smoothing:antialiased;background-color:var(--bg);background-image:radial-gradient(80% 60% at 100% 0%,rgba(236,72,153,.14),transparent 55%),radial-gradient(60% 50% at 50% 40%,rgba(167,139,250,.12),transparent 60%),radial-gradient(70% 60% at 0% 100%,rgba(99,102,241,.16),transparent 60%),linear-gradient(var(--bg),var(--bg2))}
body:before{content:"";position:fixed;inset:0;pointer-events:none;opacity:.22;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 .04 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");mix-blend-mode:overlay}
button,input{font:inherit;color:inherit}button{border:0;background:none;cursor:pointer}button:disabled{cursor:not-allowed;opacity:.35}button:focus-visible,input:focus-visible,[tabindex]:focus-visible{outline:2px solid var(--accent);outline-offset:2px}::-webkit-scrollbar{width:8px}::-webkit-scrollbar-thumb{background:rgba(255,255,255,.1);border:2px solid transparent;background-clip:padding-box;border-radius:9px}
.app{position:relative;z-index:1;height:100%;display:flex;flex-direction:column}.titlebar{height:32px;flex:none;display:flex;align-items:center;justify-content:center;padding-left:72px;background:rgba(0,0,0,.15);border-bottom:1px solid var(--line);backdrop-filter:blur(20px);font-size:12px;font-weight:500;letter-spacing:-.005em;color:var(--muted);user-select:none}.titlebar b{color:var(--fg2);font-weight:600}.titlebar i{font-style:normal;color:var(--muted2);margin:0 6px}.columns{flex:1;min-height:0;display:grid;grid-template-columns:var(--sidebar) 1fr}.main{min-width:0;min-height:0;display:flex;flex-direction:column}
.sidebar{min-height:0;display:flex;flex-direction:column;overflow:hidden;background:rgba(0,0,0,.2);border-right:1px solid var(--line2)}.brand{height:36px;flex:none;padding:0 14px;display:flex;align-items:center;gap:8px;border-bottom:1px solid var(--line);position:relative}.brand-logo{width:18px;height:18px;filter:drop-shadow(0 0 8px rgba(167,139,250,.4))}.brand-name{font-weight:600;color:var(--fg2)}.health{margin-left:auto;padding:5px 6px;display:flex;gap:3px;border-radius:4px}.health:hover{background:var(--surface2)}.dot{width:5px;height:5px;border-radius:50%;display:inline-block}.dot.claude{background:#d97757;box-shadow:0 0 4px rgba(217,119,87,.7)}.dot.codex{background:#10a37f;box-shadow:0 0 4px rgba(16,163,127,.7)}
.popover{position:absolute;top:41px;left:7px;width:260px;padding:6px 0;border:1px solid var(--line2);border-radius:8px;background:rgba(20,22,38,.98);box-shadow:0 12px 40px rgba(0,0,0,.6);z-index:20}.pop-head,.pop-foot{padding:8px 14px;font-size:11.5px;color:var(--muted)}.pop-head{border-bottom:1px solid var(--line)}.pop-foot{border-top:1px solid var(--line)}.pop-foot button{color:var(--accent2)}.source-row{width:100%;padding:8px 14px;display:flex;gap:10px;text-align:left}.source-row:hover{background:var(--surface)}.source-row .dot{width:7px;height:7px;margin-top:5px}.source-name{color:var(--fg);font-weight:500}.source-meta{font:10.5px var(--mono);color:var(--muted);margin-top:2px}
.side-section{padding:8px 6px;flex:none}.side-section+.side-section{border-top:1px solid var(--line)}.side-section.projects{flex:1;min-height:0;display:flex;flex-direction:column}.side-section.bottom{margin-top:auto}.section-title{padding:4px 10px 6px;font-size:10.5px;color:var(--muted);letter-spacing:.04em;display:flex;justify-content:space-between}.side-item{width:100%;height:28px;padding:0 10px;border-radius:5px;display:flex;align-items:center;gap:8px;color:var(--fg2);text-align:left;position:relative}.side-item:hover{background:var(--surface2);color:var(--fg)}.side-item.active{background:var(--accentSoft);color:var(--fg)}.side-item.active:before{content:"";position:absolute;left:-6px;top:4px;bottom:4px;width:2px;background:var(--accent);box-shadow:0 0 8px rgba(167,139,250,.35)}.side-item svg{width:14px;height:14px;color:var(--muted);flex:none}.side-item.active svg{color:var(--accent2);filter:drop-shadow(0 0 4px rgba(167,139,250,.35))}.side-item .label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.badge{font:10.5px var(--mono);color:var(--muted);min-width:22px;text-align:right}.side-item.sub{height:26px;padding-left:30px;font-size:12px}.side-item.sub svg{width:12px}.project-search{padding:0 6px 6px}.project-search input{width:100%;height:24px;padding:0 8px;border:1px solid var(--line);border-radius:4px;background:var(--surface);font-size:12px}.project-list{overflow:auto;min-height:0;flex:1}.project-list .side-item{font-size:12px}
.toolbar{height:44px;flex:none;display:flex;align-items:center;gap:10px;padding:0 14px;border-bottom:1px solid var(--line2);background:rgba(0,0,0,.15);backdrop-filter:blur(20px);position:relative;z-index:10}.breadcrumb{display:flex;align-items:center;gap:6px;min-width:0}.crumb{font-size:14px;color:var(--muted);padding:4px 6px;border-radius:4px;white-space:nowrap}.crumb:not(.current):hover{background:var(--surface2);color:var(--fg2)}.crumb.current{font-weight:600;color:var(--fg);overflow:hidden;text-overflow:ellipsis}.crumb-sep{color:var(--muted2)}.spacer{flex:1}.search{width:220px;position:relative}.search input{width:100%;height:26px;padding:0 30px 0 27px;border:1px solid var(--line);border-radius:5px;background:var(--surface)}.search input:focus{border-color:var(--accent);background:var(--surface2);box-shadow:0 0 0 2px var(--accentSoft);outline:0}.search svg{position:absolute;left:8px;top:7px;width:12px;color:var(--muted)}.kbd{font:9.5px var(--mono);color:var(--muted2);padding:1px 4px;border:1px solid var(--line2);border-radius:3px}.search .kbd{position:absolute;right:6px;top:5px}.sort{height:26px;padding:0 7px;border-radius:5px;font:12px var(--mono);color:var(--muted)}.sort:hover{background:var(--surface2);color:var(--fg2)}.filter,.btn{height:28px;padding:0 11px;border:1px solid var(--line2);border-radius:5px;background:var(--surface);color:var(--fg2);font-size:12px}.filter:hover,.btn:hover{background:var(--surface2);color:var(--fg)}.filter.active,.btn.primary{border-color:rgba(167,139,250,.35);background:var(--accentSoft);color:var(--accent2)}.filter-menu{position:absolute;right:68px;top:36px;width:190px;padding:6px;border:1px solid var(--line2);border-radius:8px;background:rgba(20,22,38,.98);box-shadow:0 12px 40px #0009}.filter-menu button{width:100%;padding:7px 8px;text-align:left;border-radius:5px;color:var(--fg2)}.filter-menu button:hover,.filter-menu button.on{background:var(--accentSoft);color:var(--accent2)}.tabs{display:flex;height:26px;border:1px solid var(--line2);border-radius:5px;overflow:hidden}.tabs button{padding:0 12px;color:var(--muted);border-right:1px solid var(--line2)}.tabs button:last-child{border:0}.tabs button.active{background:var(--accentSoft);color:var(--accent2)}
#content{flex:1;min-height:0;overflow:auto}.list{min-height:100%}.srow,.mrow{position:relative;border-bottom:1px solid var(--line);cursor:pointer;transition:background .08s}.srow:hover,.mrow:hover{background:rgba(255,255,255,.025)}.srow{min-height:64px;padding:12px 16px;display:grid;grid-template-columns:1fr auto;gap:12px}.srow-title{font-size:14px;font-weight:500;color:var(--fg);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.srow-meta{margin-top:4px;display:flex;gap:8px;align-items:center;flex-wrap:wrap;font:11px var(--mono);color:var(--muted)}.meta-dot{width:2px;height:2px;background:var(--muted2);border-radius:50%}.source-pill{font:9.5px var(--mono);padding:2px 5px;border-radius:7px;background:var(--surface2)}.source-pill.claude{color:#e69a7d}.source-pill.codex{color:#57c7a9}.srow-right{font:11px var(--mono);color:var(--fg2);text-align:right}.srow-right small{display:block;color:var(--muted);margin-top:2px}.snippet{margin-top:7px;padding-left:14px;position:relative;color:var(--fg2);font-size:12px}.snippet:before{content:"";position:absolute;left:0;top:1px;bottom:1px;width:2px;background:var(--accentSoft)}mark{padding:0 2px;border-radius:2px;background:var(--accentSoft);color:var(--accent2)}
.mrow{min-height:88px;padding:14px 16px 14px 14px;display:grid;grid-template-columns:22px 1fr auto;gap:12px}.mrow.cursor{background:var(--surface)}.mrow.cursor:before,.mrow.selected:before{content:"";position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--muted2)}.mrow.selected:before{background:var(--accent);box-shadow:0 0 12px rgba(167,139,250,.35)}.mrow.cursor.selected{background:rgba(167,139,250,.16)}.check{width:18px;height:18px;margin-top:1px;border:1.5px solid var(--muted2);border-radius:4px;display:grid;place-items:center;opacity:0}.mrow:hover .check,.mrow.selected .check,.mrow.cursor .check{opacity:1}.check.on{opacity:1;background:var(--accent);border-color:var(--accent);color:var(--bg);box-shadow:0 0 8px rgba(167,139,250,.35)}.check svg{width:10px;height:10px}.mrow.selected{background:var(--accentSoft)}.mrow-body{min-width:0}.mrow-path{font:500 14px/1.4 var(--mono);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mrow-project{color:var(--muted);font-weight:400}.row-status{display:inline-grid;place-items:center;width:14px;height:14px;margin-right:6px;color:var(--muted2);vertical-align:-2px}.row-status svg{width:14px;height:14px}.mrow-summary{margin-top:6px;color:var(--fg2);line-height:1.5;max-width:75ch}.mrow-right{display:flex;flex-direction:column;align-items:flex-end;gap:12px;font:10.5px var(--mono);color:var(--muted)}.row-action{font:12px var(--sans);color:var(--muted);opacity:0}.mrow:hover .row-action{opacity:1}.row-action:hover{color:var(--danger)}.empty{min-height:100%;display:grid;place-content:center;text-align:center;color:var(--muted);gap:8px}.empty b{color:var(--fg2);font-size:14px}.empty small{font-size:11px}
.detail{max-width:920px;margin:auto;padding:34px 44px 90px}.eyebrow{font:10.5px var(--mono);color:var(--muted);text-transform:uppercase;letter-spacing:.08em}.detail h1{font-size:24px;line-height:1.25;margin:10px 0;color:var(--fg);letter-spacing:-.02em}.detail-sub{color:var(--fg2);font-size:14px;max-width:70ch}.detail-meta{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:12px;font:11px var(--mono);color:var(--muted)}.detail-rule{height:1px;background:var(--line);margin:26px 0}.timeline{display:flex;flex-direction:column;gap:18px}.message{display:grid;grid-template-columns:58px 1fr;gap:14px}.role{padding-top:10px;font:10px var(--mono);text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}.bubble{padding:13px 15px;border:1px solid var(--line);border-radius:7px;background:rgba(255,255,255,.022);color:var(--fg2);line-height:1.6}.message.user .bubble{background:rgba(167,139,250,.08);border-color:rgba(167,139,250,.18);color:var(--fg)}.thinking,.tool{margin-top:8px;border:1px solid var(--line);border-radius:5px;background:rgba(0,0,0,.18)}.disclosure{width:100%;padding:8px 10px;text-align:left;font:11px var(--mono);color:var(--muted)}.disclosure:hover{color:var(--fg2);background:var(--surface)}.disclosure-content{display:none;padding:0 11px 10px;color:var(--muted);font:11px/1.55 var(--mono);white-space:pre-wrap}.open .disclosure-content{display:block}.tool-result{padding:10px;border-top:1px solid var(--line);font:11px/1.5 var(--mono);color:var(--fg2)}.msg-nav{position:sticky;bottom:14px;margin:24px auto 0;width:max-content;padding:5px 7px;display:flex;align-items:center;gap:6px;border:1px solid var(--line2);border-radius:7px;background:rgba(10,11,20,.88);backdrop-filter:blur(12px);box-shadow:0 8px 24px #0007}.msg-nav button{width:26px;height:24px;border-radius:4px;color:var(--muted)}.msg-nav button:hover{background:var(--surface2);color:var(--fg)}.msg-pos{font:11px var(--mono);color:var(--muted);min-width:45px;text-align:center}
.settings{max-width:720px;margin:auto;padding:36px 32px 80px}.settings section{margin-bottom:44px}.section-head{padding-bottom:10px;margin-bottom:16px;border-bottom:1px solid var(--line)}.section-head h2{font-size:18px;margin-bottom:2px}.section-head p{color:var(--muted)}.source-card{padding:18px;border:1px solid var(--line);border-radius:8px;background:rgba(0,0,0,.18);margin-bottom:12px}.source-card-head{display:flex;align-items:center;gap:10px;margin-bottom:14px}.source-mark{width:28px;height:28px;border:1px solid var(--line2);border-radius:6px;display:grid;place-items:center;background:#0005}.source-info{flex:1}.source-info b{font-size:14px}.vendor{font-size:11px;color:var(--muted);font-weight:400;margin-left:6px}.status{font:10.5px var(--mono);color:var(--muted);margin-top:3px}.status .ok{color:var(--green)}.path{display:flex;gap:6px}.path input{flex:1;min-width:0;height:28px;padding:0 10px;border:1px solid var(--line2);border-radius:5px;background:#0005;font:12px var(--mono)}.setting-row{display:grid;grid-template-columns:180px 1fr;gap:24px;padding:14px 0}.setting-row+.setting-row{border-top:1px solid var(--line)}.setting-label{color:var(--fg2);font-weight:500;padding-top:6px}.toggle{display:inline-flex;align-items:center;gap:8px;color:var(--fg2)}.track{width:30px;height:16px;border:1px solid var(--line2);border-radius:9px;position:relative;background:var(--surface2)}.track:after{content:"";position:absolute;width:10px;height:10px;top:2px;left:2px;border-radius:50%;background:var(--muted);transition:transform .15s}.track.on{border-color:rgba(167,139,250,.5);background:var(--accentSoft)}.track.on:after{transform:translateX(14px);background:var(--accent2);box-shadow:0 0 6px rgba(167,139,250,.5)}
.toast{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:100;padding:10px 16px;border:1px solid var(--line2);border-radius:8px;background:var(--surface2);box-shadow:0 4px 20px #0006;color:var(--fg2);font-size:12px}.toast button{margin-left:12px;padding:3px 10px;border:1px solid rgba(167,139,250,.3);border-radius:4px;background:var(--accentSoft);color:var(--accent2);font:12px var(--mono)}
/* Fidelity layer: mirrors the installed Electron app, while remaining standalone. */
:root{--sidebar:220px;--serif:"Iowan Old Style",Charter,"Source Serif Pro",Georgia,serif}
.titlebar{position:relative;padding:0 16px 0 78px}.traffic{position:absolute;left:13px;top:10px;display:flex;gap:8px}.traffic span{width:12px;height:12px;border-radius:50%;box-shadow:inset 0 0 0 .5px #0005}.traffic .red{background:#ff5f57}.traffic .yellow{background:#febc2e}.traffic .green{background:#28c840}
.popover{top:36px;left:0;width:260px;border-radius:0 0 7px 0;padding:0}.pop-head{padding:10px 14px}.source-row{padding:9px 14px}.pop-foot{padding:8px 14px;background:#0a0b14cc}.source-name{display:block}.source-meta{display:block}.section-title .show-all{font:10px var(--mono);color:var(--muted);padding:2px 5px;border-radius:3px}.section-title .show-all:hover{background:var(--surface2);color:var(--fg2)}.noise-fold{width:100%;height:28px;padding:0 10px;display:flex;align-items:center;gap:8px;color:var(--muted);font-size:12px}.noise-fold:hover{background:var(--surface2);color:var(--fg2)}
.source-filter-wrap{position:relative}.filter.source-filter{display:flex;align-items:center;gap:6px;height:26px;font:10.5px var(--mono)}.filter.source-filter svg{width:11px}.filter-menu{top:32px;right:0;width:220px;padding:6px}.filter-menu button{display:flex;align-items:center;gap:10px;padding:8px 10px}.filter-menu button.on{background:transparent;color:var(--fg2)}.filter-menu button:hover{background:var(--surface)}.filter-check{width:15px;height:15px;border:1.5px solid var(--muted2);border-radius:3px;display:grid;place-items:center;flex:none}.on .filter-check{background:var(--accent);border-color:var(--accent);color:#090a12}.filter-check svg{width:10px;height:10px}.toolbar .tabs{height:26px}.toolbar .btn.primary{height:26px}
.session-row{position:relative;min-height:61px;padding:12px 16px;display:grid;grid-template-columns:1fr auto;gap:12px;border-bottom:1px solid var(--line);cursor:pointer}.session-row:hover{background:rgba(255,255,255,.025)}.session-row-title{font-size:14px;font-weight:500;color:var(--fg);line-height:1.35;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.session-row-meta{margin-top:4px;font:11px var(--mono);color:var(--muted);display:flex;gap:7px}.session-row-meta .project{color:var(--fg2)}.session-row-time{padding-top:1px;font:11px var(--mono);color:var(--fg2);white-space:nowrap}.session-obelisk{position:absolute;left:0;bottom:0;width:3px;border-radius:2px 2px 0 0;background:linear-gradient(to top,#6d28d9,#a78bfa);box-shadow:0 0 7px rgba(167,139,250,.42)}.quiet-fold{height:39px;padding:0 16px;display:flex;align-items:center;gap:9px;border-bottom:1px solid var(--line);color:var(--muted);font-size:12px}.quiet-fold:hover{background:var(--surface)}.quiet-fold .chev{font-size:15px;color:var(--muted2)}.quiet-fold .quiet-count{font:11px var(--mono);color:var(--fg2)}.quiet-fold .quiet-text{flex:1}.quiet-fold .quiet-action{color:var(--accent2);font-size:11.5px}
.session-reader{width:min(660px,calc(100% - 48px));margin:0 auto;padding:28px 0 90px}.session-head-mark{height:18px;display:flex;align-items:center;gap:10px;margin-bottom:2px}.session-head-mark:before{content:"";width:36px;height:2px;background:linear-gradient(90deg,var(--accent),transparent);box-shadow:0 0 8px rgba(167,139,250,.45)}.tiny-obelisk{width:12px;height:16px;position:relative}.tiny-obelisk:before{content:"";position:absolute;left:4px;top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:5px solid #c4b5fd}.tiny-obelisk:after{content:"";position:absolute;left:5px;top:6px;width:4px;height:9px;background:linear-gradient(90deg,#a78bfa 50%,#4c1d95 50%)}.session-provenance{display:flex;align-items:center;gap:8px;font:10.5px var(--mono);color:var(--muted)}.session-provenance .folder{width:11px}.via{padding:2px 6px;border:1px solid var(--line2);border-radius:3px;color:var(--fg2)}.via.codex:before{content:"";display:inline-block;width:4px;height:4px;margin-right:5px;border-radius:50%;background:#10a37f;box-shadow:0 0 4px #10a37f}.session-reader h1{font-size:23px;line-height:1.25;letter-spacing:-.015em;margin:12px 0 7px}.session-meta-line{display:flex;gap:8px;align-items:center;font:11px var(--mono);color:var(--muted);margin-bottom:42px}.session-timeline{display:flex;flex-direction:column;gap:14px}.session-msg{padding:12px 14px;border:1px solid var(--line2);border-radius:7px;background:rgba(255,255,255,.022);box-shadow:inset 0 1px rgba(255,255,255,.025)}.session-msg.user{background:rgba(167,139,250,.08);border-color:rgba(167,139,250,.18)}.msg-head{display:flex;align-items:center;margin-bottom:9px;font:10px var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--fg2)}.msg-head time{margin-left:auto;color:var(--muted)}.msg-body{color:var(--fg2);font-size:13px;line-height:1.6;white-space:pre-wrap}.user .msg-body{color:var(--fg)}.trace-row{margin-top:8px;border:1px solid var(--line);border-radius:4px;background:rgba(0,0,0,.19);overflow:hidden}.trace-toggle{width:100%;height:29px;padding:0 10px;display:flex;align-items:center;gap:7px;text-align:left;font:10.5px var(--mono);color:var(--muted)}.trace-toggle:hover{background:var(--surface);color:var(--fg2)}.trace-toggle .trace-icon{color:var(--accent2);font-size:9px}.trace-body{display:none;padding:0 11px 10px;font:11px/1.55 var(--mono);color:var(--muted);white-space:pre-wrap}.trace-row.open .trace-body{display:block}.trace-row.open .trace-toggle{color:var(--fg2)}.session-reader .msg-nav{position:sticky;z-index:5;margin-top:-1px;bottom:14px}
.memory-reader{width:min(660px,calc(100% - 48px));margin:0 auto;padding:30px 0 80px}.memory-project{display:flex;align-items:center;gap:7px;font:11px var(--mono);color:var(--muted);margin-bottom:12px}.memory-project svg{width:12px}.memory-path{font:500 17px/1.5 var(--mono);color:var(--fg);word-break:break-all;margin-bottom:16px}.memory-summary{font-size:14px;line-height:1.6;color:var(--fg2);margin-bottom:16px}.memory-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding-bottom:16px;border-bottom:1px solid var(--line);font:12px var(--mono);color:var(--muted)}.memory-meta button{color:var(--accent2);text-decoration:underline;text-decoration-color:#a78bfa40;text-underline-offset:3px}.memory-body-head{display:flex;align-items:center;margin:28px 0 12px;font-size:10.5px;color:var(--muted);letter-spacing:.04em}.memory-body-head span{flex:1}.source-toggle-v2{height:22px;padding:0 8px;border:1px solid var(--line2);border-radius:4px;background:var(--surface);font-size:12px;color:var(--muted)}.source-toggle-v2.active{background:var(--accentSoft);color:var(--accent2)}.memory-article{color:var(--fg2);font-size:13px;line-height:1.75}.memory-article h1{font-size:22px;color:var(--fg);margin:0 0 18px}.memory-article h2{font-size:17px;color:var(--fg);margin:24px 0 9px}.memory-article p{margin-bottom:14px}.memory-article ul,.memory-article ol{padding-left:24px;margin-bottom:14px}.memory-source{padding:14px 16px;border:1px solid var(--line);border-radius:6px;background:#0005;font:12px/1.55 var(--mono);white-space:pre-wrap;color:var(--fg2)}.anchor-head{display:flex;gap:8px;align-items:center;margin-top:28px;padding-bottom:8px;border-bottom:1px solid var(--line);font:10.5px var(--mono);color:var(--muted)}.anchor-v2{width:100%;display:flex;gap:8px;padding:9px 4px;border-bottom:1px solid var(--line);text-align:left;font:11px var(--mono);color:var(--accent2)}.memory-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:32px}
.session-reader{position:relative;width:100%;max-width:720px;padding:32px 32px 90px}.session-progress{position:sticky;top:0;z-index:5;height:2px;margin:-32px -32px 30px;background:var(--line)}.session-progress-fill{height:100%;background:var(--accent);box-shadow:0 0 6px rgba(167,139,250,.35)}.session-reader .session-header{margin-bottom:28px;padding-bottom:20px;border-bottom:1px solid var(--line)}.session-reader .session-eyebrow{display:flex;align-items:center;gap:6px;margin-bottom:12px;flex-wrap:wrap;font-size:11px;color:var(--muted)}.session-reader .session-eyebrow svg{width:13px;height:13px}.session-reader .session-eyebrow .project-name{color:var(--fg2);font-weight:500}.session-reader .session-eyebrow .project-path{font-family:var(--mono)}.session-reader .session-eyebrow .via{display:inline-flex;align-items:center;gap:5px;margin-left:6px;padding:1px 7px;border:1px solid var(--line);border-radius:3px;background:rgba(255,255,255,.04);font:10.5px var(--mono)}.via-dot{width:4px;height:4px;border-radius:50%}.via-dot.claude{background:#d97757;box-shadow:0 0 4px rgba(217,119,87,.5)}.via-dot.codex{background:#10a37f;box-shadow:0 0 4px rgba(16,163,127,.5)}.session-reader .session-title{margin-bottom:14px;font-size:22px;font-weight:600;line-height:1.3;letter-spacing:-.01em}.session-reader .session-meta-line{margin:0;gap:10px}.trace-chev{width:9px;color:var(--muted2);transition:transform .15s}.trace-row.open .trace-chev{transform:rotate(90deg);color:var(--accent2)}
.session-reader .msg-body{font-size:var(--reader-font,14px)}.trace-icon{width:14px;height:14px;display:inline-flex;align-items:center;flex:none}.trace-icon svg{width:14px;height:14px}.section-title .show-all svg{display:none}.section-title .show-all:before{content:"≡";font:11px/1 var(--mono);color:var(--muted2);transform:scaleX(1.15)}.sort{display:flex;align-items:center;gap:4px}.sort svg,.btn svg{width:13px;height:13px;flex:none}.memory-meta button{display:inline-flex;align-items:center;gap:5px}.memory-meta button svg{width:11px;height:11px;flex:none}.anchor-v2 svg{width:12px;height:12px;flex:none}
/* Activity 0.2.0 */
.activity-wrap{height:100%;overflow-y:auto}.activity-wide{max-width:860px;margin:0 auto;padding:28px 32px 60px}.activity-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px}.activity-title{font-size:16px;font-weight:600;letter-spacing:-.01em}.activity-tabs{display:flex}.activity-tab{padding:5px 12px;border:1px solid var(--line);color:var(--muted);font:12px var(--mono)}.activity-tab:first-child{border-radius:4px 0 0 4px}.activity-tab:last-child{border-radius:0 4px 4px 0}.activity-tab+.activity-tab{border-left:0}.activity-tab:hover{background:var(--surface2);color:var(--fg2)}.activity-tab.active{background:var(--accentSoft);border-color:var(--accentSoft);color:var(--fg)}.activity-stats{display:flex;margin-bottom:32px;border:1px solid var(--line);border-radius:8px;background:var(--surface);overflow:hidden}.activity-stat{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;padding:16px 12px;border-right:1px solid var(--line)}.activity-stat:last-child{border-right:0}.activity-stat-value{font-size:18px;font-weight:600;font-variant-numeric:tabular-nums}.activity-stat-label{font:10.5px var(--mono);color:var(--muted);text-align:center}.activity-chart{margin-top:8px}.activity-chart svg{display:block;width:100%;height:auto;max-height:164px}.heat-cell{cursor:pointer;transition:opacity .08s}.heat-cell:hover{opacity:.7}.heat-cell.level-0{fill:var(--surface2)}.heat-cell.level-1{fill:rgba(99,102,241,.3)}.heat-cell.level-2{fill:rgba(99,102,241,.5)}.heat-cell.level-3{fill:rgba(139,92,246,.7)}.heat-cell.level-4{fill:rgba(168,85,247,.9)}.heat-cell.selected{stroke:var(--fg);stroke-width:1.5}.heat-month{font:10px var(--mono);fill:var(--muted2)}.heat-legend{display:flex;justify-content:flex-end;align-items:center;gap:6px;margin-top:12px;color:var(--muted2);font:10px var(--mono)}.weekly-bar{fill:var(--accent);opacity:.8;cursor:pointer}.weekly-bar:hover{opacity:1}.cumulative-area{fill:rgba(99,102,241,.12)}.cumulative-line{fill:none;stroke:var(--accent);stroke-width:1.5}.cumulative-dot{fill:var(--accent);opacity:0;cursor:pointer}.cumulative-dot:hover{opacity:1}.chart-tooltip{position:fixed;z-index:200;padding:5px 10px;border:1px solid var(--line2);border-radius:4px;background:rgba(30,35,50,.95);color:var(--fg2);font:11px var(--mono);pointer-events:none;opacity:0;white-space:nowrap;backdrop-filter:blur(8px);transition:opacity .1s}.chart-tooltip.show{opacity:1}.activity-month{margin-top:28px;margin-bottom:44px}.activity-month-head{display:grid;grid-template-columns:max-content minmax(48px,1fr) max-content;align-items:center;gap:16px;margin-bottom:22px}.activity-month-head h2{font-size:14px;font-weight:600;letter-spacing:-.01em}.activity-month-rule{height:1px;background:var(--line)}.activity-month-count{font:10px/1 var(--mono);color:var(--muted2);letter-spacing:.04em}.activity-ledger{position:relative;margin-left:10px;padding-left:44px}.activity-ledger:before{content:"";position:absolute;top:2px;bottom:4px;left:12px;width:1px;background:var(--line)}.ledger-group{position:relative;padding-bottom:28px}.ledger-group:last-child{padding-bottom:6px}.ledger-node{position:absolute;top:-2px;left:-44px;width:26px;height:26px;display:grid;place-items:center;border:1px solid var(--line2);border-radius:50%;background:var(--surface);color:var(--muted);box-shadow:0 0 0 5px var(--bg)}.ledger-node svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2}.ledger-group.workspace .ledger-node{color:#f59e0b;border-color:rgba(245,158,11,.28);background:rgba(245,158,11,.1)}.ledger-group.started .ledger-node{color:var(--accent2);border-color:rgba(167,139,250,.28);background:var(--accentSoft)}.ledger-group-head{display:flex;align-items:baseline;justify-content:space-between;gap:20px;min-height:24px;margin-bottom:9px}.ledger-group-head h3{font-size:13px;font-weight:500}.ledger-group-head time{font:10px/1 var(--mono);color:var(--muted2);letter-spacing:.05em}.ledger-items{display:grid;gap:2px}.ledger-item{display:block;width:100%;margin-left:-10px;padding:6px 10px;border-radius:5px;text-align:left}.ledger-item:hover{background:var(--surface2)}.ledger-item-title{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--accent2);font-size:13px;font-weight:500;line-height:1.35}.ledger-item.continued .ledger-item-title{color:var(--fg2)}.ledger-item:hover .ledger-item-title{text-decoration:underline;text-underline-offset:2px}.ledger-item-meta{display:flex;align-items:center;gap:8px;min-height:15px;margin-top:4px;color:var(--muted2);font:11px/1.35 var(--mono)}.ledger-item-meta .source{color:var(--fg2);font-weight:500}.ledger-item-meta .project{color:var(--muted)}.ledger-noise{display:flex;align-items:center;gap:8px;width:max-content;margin:4px 0 0 -4px;padding:6px 8px;border-radius:5px;color:var(--muted2);font:11px/1.25 var(--mono)}.ledger-noise:hover{background:var(--surface2);color:var(--muted)}.ledger-noise .chev{width:9px;transition:transform .15s}.ledger-noise.expanded .chev{transform:rotate(90deg)}.show-more-activity{display:block;width:max-content;margin:-18px auto 16px;padding:8px 12px;border:1px solid var(--line);border-radius:6px;color:var(--muted);font:11px/1 var(--mono)}.show-more-activity:hover{background:var(--surface2);border-color:var(--line2);color:var(--fg2)}
.heat-legend svg{width:70px!important;height:11px!important;max-height:none!important;flex:0 0 70px}
.activity-empty{padding:26px 72px;color:var(--muted2);font-size:12px}
/* Recap list and generator */
.recap-list-v2{height:100%;overflow-y:auto;--rl-serif:var(--serif)}.rl-content{max-width:720px;margin:0 auto;padding:32px 32px 80px}.rl-head{display:flex;align-items:baseline;gap:12px;margin-bottom:20px;padding-bottom:10px;border-bottom:1px solid var(--line)}.rl-year{font:500 22px var(--serif);color:var(--fg2)}.rl-count{font:12px var(--mono);color:var(--muted)}.rl-timeline{position:relative}.rl-timeline:before{content:"";position:absolute;left:32px;top:32px;bottom:32px;width:1px;background:linear-gradient(rgba(167,139,250,.55),rgba(167,139,250,.35) 8%,rgba(255,255,255,.12) 30%,rgba(255,255,255,.06))}.rl-row{position:relative;display:grid;grid-template-columns:64px 1fr;align-items:center;gap:18px;padding:12px 0;cursor:pointer;transition:transform .12s}.rl-row:hover{transform:translateX(2px)}.rl-node{width:64px;height:64px;position:relative;z-index:2}.rl-node:before{content:"";position:absolute;inset:-2px;border-radius:50%;background:var(--bg);z-index:-1}.rl-node svg{display:block;width:100%;height:100%;filter:drop-shadow(0 0 6px var(--node-glow))}.rl-row:hover .rl-node svg{filter:drop-shadow(0 0 10px var(--node-glow))}.rl-card{display:grid;grid-template-columns:1fr auto;align-items:center;gap:16px;padding:14px 16px;border:1px solid var(--line);border-radius:8px;background:rgba(255,255,255,.02)}.rl-row:hover .rl-card{background:rgba(255,255,255,.035);border-color:var(--line2)}.rl-body{display:flex;flex-direction:column;gap:4px;min-width:0}.rl-period{display:flex;align-items:center;gap:8px;color:var(--muted);font:12px var(--mono)}.rl-period .dot{width:2px;height:2px;background:var(--muted2)}.rl-archetype{font:500 20px var(--serif);letter-spacing:-.01em}.rl-claim{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:rgba(255,255,255,.55);font:italic 14.5px/1.4 var(--serif)}.rl-stats{display:flex;gap:10px;margin-top:4px;color:var(--muted2);font:11.5px var(--mono)}.rl-arrow{color:var(--muted2);font-size:19px}
.generate-backdrop{position:fixed;inset:0;z-index:500;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(5,6,12,.65);backdrop-filter:blur(8px)}.generate-modal{width:100%;max-width:480px;border:1px solid var(--line2);border-radius:12px;background:linear-gradient(165deg,rgba(20,22,38,.95),rgba(13,15,28,.95));box-shadow:0 30px 80px #0009,0 12px 32px #0006,inset 0 1px rgba(255,255,255,.08);overflow:hidden}.generate-head{display:flex;align-items:baseline;gap:10px;padding:18px 22px 12px;border-bottom:1px solid var(--line)}.generate-head .diamond{width:6px;height:6px;align-self:center;background:var(--accent);transform:rotate(45deg);box-shadow:0 0 8px rgba(167,139,250,.35)}.generate-title{flex:1;font:500 17px var(--serif)}.generate-close{width:24px;height:24px;border-radius:4px;color:var(--muted)}.generate-close:hover{background:var(--surface);color:var(--fg2)}.generate-body{padding:18px 22px 20px}.generate-copy{margin-bottom:14px;color:var(--fg2);font:italic 13.5px/1.6 var(--serif)}.generate-options{display:flex;flex-direction:column;gap:1px;margin-bottom:14px;border:1px solid var(--line);border-radius:6px;background:var(--line);overflow:hidden}.generate-option{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;background:rgba(0,0,0,.2);text-align:left;color:var(--fg2);font:12px var(--mono)}.generate-option:hover{background:rgba(255,255,255,.025)}.generate-option.active{background:var(--accentSoft);color:var(--accent2)}.generate-radio{width:12px;height:12px;border:1.5px solid var(--muted2);border-radius:50%;position:relative}.generate-option.active .generate-radio{border-color:var(--accent)}.generate-option.active .generate-radio:after{content:"";position:absolute;inset:2px;border-radius:50%;background:var(--accent);box-shadow:0 0 6px rgba(167,139,250,.35)}.generate-command{position:relative;margin-bottom:14px;padding:14px 50px 14px 16px;border:1px solid var(--line2);border-radius:6px;background:rgba(0,0,0,.4);font:12.5px var(--mono)}.generate-command .prompt{margin-right:4px;color:var(--accent2)}.generate-command button{position:absolute;right:8px;top:7px;width:32px;height:32px;border-radius:5px;color:var(--muted)}.generate-command button:hover,.generate-command button.copied{background:var(--surface);color:var(--accent2)}.generate-hint{color:var(--muted2);font:10.5px/1.5 var(--mono)}
/* Recap five-card reader */
.recap-app-v2{height:100%;display:grid;grid-template-rows:1fr 64px;position:relative;outline:0;--tc:#a78bfa;--tc2:#c4b5fd;--tg:rgba(167,139,250,.35);--tgmid:rgba(167,139,250,.18);--tgsoft:rgba(167,139,250,.08);--rfg3:rgba(255,255,255,.55)}.rc-stage{position:relative;overflow:hidden;display:flex;align-items:center;justify-content:center;padding:32px 24px}.rc-deck{position:relative;width:100%;max-width:540px;height:100%}.rc-card{position:absolute;inset:0;display:flex;flex-direction:column;overflow:hidden;border:1px solid rgba(255,255,255,.1);border-radius:14px;background:linear-gradient(165deg,rgba(255,255,255,.04),rgba(255,255,255,.015));box-shadow:0 30px 80px #0008,0 12px 32px #0005,inset 0 1px rgba(255,255,255,.08)}.rc-card.themed:before{content:"";position:absolute;right:0;bottom:0;width:60%;height:50%;background:radial-gradient(ellipse at 100% 100%,var(--tgmid),transparent 70%)}.rc-cover{background:radial-gradient(120% 80% at 50% 100%,var(--tg),transparent 55%),radial-gradient(100% 70% at 50% 80%,var(--tgmid),transparent 65%),linear-gradient(180deg,rgba(10,11,20,.4),rgba(10,11,20,.85) 70%)}.rc-stars span{position:absolute;width:2px;height:2px;border-radius:50%;background:#fff;box-shadow:0 0 4px #fff9}.rc-stars span:nth-child(1){top:12%;left:18%}.rc-stars span:nth-child(2){top:8%;left:78%}.rc-stars span:nth-child(3){top:22%;left:88%;opacity:.6}.rc-stars span:nth-child(4){top:32%;left:8%;opacity:.5}.rc-stars span:nth-child(5){top:18%;left:52%;opacity:.7}.rc-eyebrow{display:flex;align-items:center;gap:10px;padding:22px 28px 0;position:relative;z-index:1;color:var(--muted);font:12px var(--mono)}.rc-eyebrow .diamond{width:6px;height:6px;background:var(--tc);transform:rotate(45deg);box-shadow:0 0 8px var(--tg)}.rc-eyebrow .slot{margin-left:auto;color:var(--muted2)}.rc-seal{position:absolute;right:24px;top:22px;width:60px;height:60px}.rc-seal svg{width:100%;height:100%;filter:drop-shadow(0 0 12px var(--tg))}.rc-cover-body{flex:1;display:flex;flex-direction:column;padding:0 36px;position:relative;z-index:1}.rc-cover-title{margin-top:auto;margin-bottom:18px;color:var(--fg);font:500 64px/1.05 var(--serif);letter-spacing:-.02em;text-shadow:0 2px 24px #0006}.rc-cover-claim{max-width:92%;margin-bottom:36px;color:var(--fg2);font:italic 19px/1.5 var(--serif)}.rc-activity{margin-bottom:28px}.rc-activity-bars{display:grid;grid-template-columns:repeat(7,1fr);gap:6px;margin-bottom:8px}.rc-activity-bar{height:32px;border:1px solid var(--line);border-radius:3px;background:rgba(255,255,255,.04);position:relative;overflow:hidden}.rc-activity-bar i{position:absolute;left:0;right:0;bottom:0;background:linear-gradient(var(--tc2),var(--tc));box-shadow:0 0 10px var(--tg)}.rc-day-labels{display:grid;grid-template-columns:repeat(7,1fr);gap:6px;text-align:center;color:var(--muted2);font:10.5px var(--mono)}.rc-footer{padding-bottom:28px;color:var(--muted);font:13px var(--mono)}.rc-title{padding:18px 36px 6px;font:500 30px/1.2 var(--serif);letter-spacing:-.015em;position:relative;z-index:1}.rc-content{flex:1;padding:0 36px 32px;overflow:auto;position:relative;z-index:1}.rc-path{position:relative;padding-left:28px}.rc-path:before{content:"";position:absolute;left:6px;top:14px;bottom:14px;width:1px;background:linear-gradient(var(--tg),rgba(255,255,255,.06))}.rc-path-item{position:relative;padding:8px 0 10px}.rc-path-item:before{content:"";position:absolute;left:-28px;top:16px;width:7px;height:7px;background:var(--tc);transform:rotate(45deg);box-shadow:0 0 8px var(--tg),0 0 0 3px var(--bg)}.rc-day{margin-bottom:4px;color:var(--tc2);font:600 12px var(--mono)}.rc-prompt{margin-bottom:6px;color:var(--fg);font:italic 16px/1.35 var(--serif)}.rc-outcome{display:inline-flex;padding:4px 10px;border:1px solid var(--line);border-left:2px solid var(--tc);border-radius:4px;background:rgba(255,255,255,.025);color:var(--fg2);font:12px var(--mono)}.rc-section-label{margin:8px 0 12px;color:var(--muted);font:italic 13px var(--serif)}.rc-vibe-list{display:flex;flex-direction:column;gap:10px}.rc-vibe-row{display:flex;align-items:baseline;gap:12px;padding:10px 14px;border:1px solid var(--line);border-left:2px solid var(--tgmid);border-radius:4px;background:rgba(255,255,255,.025)}.rc-vibe-text{flex:1;color:var(--fg);font:italic 18px/1.4 var(--serif)}.rc-vibe-meta{white-space:nowrap;color:var(--muted);font:12px var(--mono)}.rc-meter{margin-top:22px}.rc-meter-track{height:10px;border:1px solid var(--line);border-radius:2px;background:rgba(255,255,255,.04);overflow:hidden}.rc-meter-fill{height:100%;width:76%;background:linear-gradient(90deg,var(--tc),var(--tc2));box-shadow:0 0 12px var(--tg)}.rc-meter-row{display:flex;justify-content:space-between;margin-top:8px}.rc-meter-row span:first-child{color:var(--muted);font:italic 14px var(--serif)}.rc-meter-row span:last-child{color:var(--tc2);font:600 11.5px var(--mono)}.rc-quote{margin-top:22px;padding-top:18px;border-top:1px solid var(--line);color:var(--fg);font:500 22px/1.4 var(--serif)}.rc-deck-text{padding:0 36px 22px;color:var(--rfg3);font:italic 15px/1.55 var(--serif);position:relative;z-index:1}.rc-workflow-stat{margin-bottom:18px;color:var(--muted);font:13px var(--mono)}.rc-workflow-list{border:1px solid var(--line);border-radius:6px;overflow:hidden}.rc-workflow-row{padding:14px 16px;background:rgba(10,11,20,.4)}.rc-workflow-row+.rc-workflow-row{border-top:1px solid var(--line)}.rc-workflow-row b{display:block;margin-bottom:6px;font:500 13px var(--mono)}.rc-workflow-row span{color:var(--fg2);font:italic 16px/1.4 var(--serif)}.rc-verdict{margin-top:auto;padding:16px 18px;border:1px solid var(--line2);border-radius:6px;background:rgba(255,255,255,.025)}.rc-verdict small{display:block;margin-bottom:6px;color:var(--muted);font:italic 13px var(--serif)}.rc-verdict strong{font:500 22px var(--serif)}.rc-closing{background:radial-gradient(80% 70% at 50% 30%,var(--tgsoft),transparent 60%),radial-gradient(60% 50% at 50% 50%,var(--tgmid),transparent 70%),linear-gradient(180deg,rgba(10,11,20,.6),rgba(10,11,20,.95))}.rc-closing-body{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:32px;padding:0 40px;text-align:center}.rc-closing-title{font:500 72px/1 var(--serif);letter-spacing:-.02em;text-shadow:0 4px 24px var(--tg)}.rc-closing-stats{display:flex;flex-direction:column;gap:4px;color:var(--muted);font:13px var(--mono)}.rc-closing-quote{max-width:360px;color:var(--fg2);font:italic 19px/1.5 var(--serif)}.rc-closing-quote small{display:block;margin-top:8px;color:var(--muted);font-size:13px}.rc-signoff{color:var(--muted);font:italic 15px var(--serif)}.rc-nav{display:flex;align-items:center;justify-content:center;gap:16px;padding:0 22px;border-top:1px solid var(--line);background:rgba(0,0,0,.18);position:relative}.rc-arrow{width:36px;height:36px;border:1px solid var(--line2);border-radius:50%;background:var(--surface);color:var(--fg2)}.rc-arrow:hover:not(:disabled){background:var(--surface2);color:var(--fg)}.rc-dots{display:flex;gap:8px}.rc-dot{display:flex;flex-direction:column;align-items:center;gap:4px;padding:4px 8px;border-radius:4px}.rc-dot:hover{background:var(--surface)}.rc-glyph{width:24px;height:3px;border-radius:2px;background:rgba(255,255,255,.16)}.rc-dot.active .rc-glyph{width:28px;background:var(--tc);box-shadow:0 0 8px var(--tg)}.rc-label{color:var(--muted2);font:italic 11px var(--serif)}.rc-dot.active .rc-label{color:var(--fg2)}.rc-actions{position:absolute;right:60px;display:flex;gap:6px}.rc-action{width:32px;height:32px;display:grid;place-items:center;border:1px solid var(--line2);border-radius:6px;background:var(--surface);color:var(--fg2)}.rc-action:hover{background:var(--surface2);color:var(--fg)}.rc-action svg{width:14px;height:14px}
.recap-app-v2.p1{--tc:#fbbf24;--tc2:#fde68a;--tg:rgba(251,191,36,.4);--tgmid:rgba(251,191,36,.22);--tgsoft:rgba(251,191,36,.1)}.recap-app-v2.p2{--tc:#f472b6;--tc2:#fda4af;--tg:rgba(244,114,182,.4);--tgmid:rgba(244,114,182,.22);--tgsoft:rgba(244,114,182,.1)}.recap-app-v2.p3{--tc:#67e8f9;--tc2:#a5f3fc;--tg:rgba(103,232,249,.4);--tgmid:rgba(103,232,249,.22);--tgsoft:rgba(103,232,249,.1)}.recap-app-v2.p4{--tc:#fcd34d;--tc2:#fde68a;--tg:rgba(252,211,77,.4);--tgmid:rgba(252,211,77,.22);--tgsoft:rgba(252,211,77,.1)}.recap-app-v2.p5{--tc:#34d399;--tc2:#6ee7b7;--tg:rgba(52,211,153,.4);--tgmid:rgba(52,211,153,.22);--tgsoft:rgba(52,211,153,.1)}.recap-app-v2.p6{--tc:#64748b;--tc2:#94a3b8;--tg:rgba(100,116,139,.45);--tgmid:rgba(100,116,139,.25);--tgsoft:rgba(100,116,139,.12)}.rc-card{animation:rc-enter .22s cubic-bezier(.22,1,.36,1)}@keyframes rc-enter{from{opacity:0;transform:translateY(24px) scale(.97)}}
/* Settings exact copy */
.settings-v2{max-width:720px;margin:0 auto;padding:36px 32px 80px}.settings-v2 section{margin-bottom:44px}.settings-v2 .section-head{margin-bottom:16px}.settings-v2 .section-head h2{font-size:18px;font-weight:600;letter-spacing:-.01em}.settings-v2 .section-head p{font-size:13px}.settings-v2 .source-card{transition:border-color .15s}.settings-v2 .btn{display:inline-flex;align-items:center;gap:6px;font-weight:500;white-space:nowrap}.source-mark .dot{width:8px;height:8px}.source-card-name{display:flex;align-items:baseline;gap:8px;font-size:14px;font-weight:600}.source-card-name .vendor{font-size:11.5px}.source-status{display:flex;align-items:center;gap:8px;margin-top:3px;color:var(--muted);font:10.5px var(--mono)}.source-status .status-dot-v2{position:relative;width:6px;height:6px;border-radius:50%;background:#34d399;box-shadow:0 0 5px rgba(52,211,153,.5)}.source-status .status-dot-v2:before{content:"";position:absolute;inset:-2.5px;border:1px solid #34d399;border-radius:50%;animation:src-pulse 1.6s ease-out infinite}.source-status .connected{color:#34d399}.source-status strong{color:var(--fg2);font-weight:500}.setting-hint{margin-top:4px;color:var(--muted);font-size:11.5px}.setting-hint code{padding:1px 4px;border-radius:3px;background:#0005;color:var(--muted);font:10.5px var(--mono)}.settings-v2 .setting-row{align-items:start}.version-text{padding-top:6px;color:var(--fg2);font:12px var(--mono)}.reset-hint{margin-top:6px;color:var(--muted);font-size:11.5px}@keyframes src-pulse{0%{transform:scale(.8);opacity:.5}100%{transform:scale(1.8);opacity:0}}
/* Subagent detail: the installed app's wider, non-virtualized conversation reader. */
.subagent-wrap{height:100%;overflow-y:auto}.subagent-wide{max-width:860px;margin:0 auto;padding:28px 32px 60px}.subagent-header{margin-bottom:28px;padding-bottom:20px;border-bottom:1px solid var(--line)}.subagent-eyebrow{margin-bottom:12px;color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.05em}.subagent-title{margin-bottom:14px;color:var(--fg);font-size:22px;font-weight:600;line-height:1.3;letter-spacing:-.01em}.subagent-meta{color:var(--muted);font:12px var(--mono)}.sub-timeline{display:flex;flex-direction:column;gap:14px}.sub-msg{padding:12px 14px;border:1px solid rgba(255,255,255,.06);border-radius:8px;background:rgba(255,255,255,.025)}.sub-msg.user{border-color:rgba(167,139,250,.18);background:rgba(167,139,250,.08)}.sub-head{display:flex;align-items:center;gap:8px;margin-bottom:8px;color:var(--muted);font:11px var(--mono)}.sub-role{color:var(--fg2);font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.06em}.sub-msg.user .sub-role{color:var(--accent2)}.sub-when{margin-left:auto;font-variant-numeric:tabular-nums}.markdown-msg{color:var(--fg);font:13px/1.75 "Helvetica Neue","Inter",-apple-system,system-ui,"PingFang SC","Hiragino Sans GB",sans-serif;letter-spacing:.005em;word-wrap:break-word}.markdown-msg p{margin:.5em 0}.markdown-msg p:first-child{margin-top:0}.markdown-msg p:last-child{margin-bottom:0}.markdown-msg h2{margin:1em 0 .4em;color:var(--fg);font-size:15px;font-weight:600;line-height:1.3;letter-spacing:-.01em}.markdown-msg ul,.markdown-msg ol{margin:.5em 0;padding-left:22px}.markdown-msg li{margin:.18em 0}.markdown-msg code{padding:1px 5px;border-radius:3px;background:rgba(255,255,255,.06);color:var(--accent2);font:12px var(--mono)}.sub-disclosure{margin-top:10px;overflow:hidden;border:1px solid var(--line);border-radius:5px;background:rgba(0,0,0,.15)}.sub-toggle{display:flex;align-items:center;gap:8px;width:100%;padding:7px 10px;text-align:left;color:inherit}.sub-toggle:hover{background:rgba(255,255,255,.03)}.sub-chevron{width:8px;height:8px;flex:none;color:var(--muted);transition:transform .15s}.sub-disclosure.open .sub-chevron{transform:rotate(90deg);color:var(--accent2)}.sub-label{flex:none;color:var(--muted);font:600 10.5px var(--mono);text-transform:uppercase;letter-spacing:.05em}.sub-preview{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--muted2);font-size:11px}.sub-body{display:none;padding:8px 14px 12px;border-top:1px solid var(--line)}.sub-disclosure.open .sub-body{display:block}.sub-meta{margin-top:0;background:rgba(0,0,0,.1)}.sub-meta .sub-toggle{padding:5px 10px}.sub-meta .sub-label{color:var(--muted2);font-size:10px}.sub-meta .sub-body{padding:6px 12px 10px}.sub-tool{margin-top:5px;background:rgba(0,0,0,.2)}.sub-tool.is-error{border-color:rgba(248,113,113,.3);background:rgba(248,113,113,.12)}.sub-tool .sub-toggle{padding:6px 10px}.sub-tool-name{flex:none;color:var(--accent2);font:600 11px var(--mono)}.sub-tool-arg{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fg2);font:11px var(--mono)}.sub-tool.is-error .sub-tool-name{color:var(--danger)}.sub-error{flex:none;padding:1px 6px;border-radius:3px;background:rgba(248,113,113,.18);color:var(--danger);font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.04em}.sub-tool .sub-body{max-height:400px;overflow:auto;padding:12px 14px;background:rgba(0,0,0,.32)}.tc-section{margin:0 0 5px;color:var(--muted);font:500 10px var(--mono);text-transform:uppercase;letter-spacing:.05em}.sub-tool pre{margin-bottom:12px;color:var(--fg2);font:11.5px/1.55 var(--mono);white-space:pre-wrap;word-wrap:break-word}.sub-tool pre:last-child{margin-bottom:0}.truncated-btn{display:block;width:100%;margin-top:8px;padding:6px 12px;border:1px solid rgba(167,139,250,.2);border-radius:4px;background:var(--accentSoft);color:var(--accent2);font:11px var(--mono);text-align:center}.truncated-btn:hover{border-color:var(--accent);background:rgba(167,139,250,.2)}.empty-text{color:var(--muted2);font-style:italic}.agent-call{border-color:rgba(245,158,11,.12)!important;border-left:2px solid #f59e0b!important}.agent-call-head{display:flex;align-items:center}.agent-call-head .trace-toggle{min-width:0;flex:1;width:auto}.agent-kind{flex:none;color:#f59e0b;font-weight:600}.agent-nav-btn{margin-right:8px;padding:2px 8px;border:1px solid rgba(245,158,11,.25);border-radius:3px;background:rgba(245,158,11,.12);color:#f59e0b;white-space:nowrap;font:10.5px var(--mono)}.agent-nav-btn:hover{border-color:#f59e0b;background:rgba(245,158,11,.25)}
/* Sessions' hidden branches: first-run/no-source state and quiet-session group. */
.empty .hint{color:var(--muted2);font-size:11px}
.empty-content{display:flex;flex:1;max-width:520px;margin:0 auto;padding:40px;flex-direction:column;justify-content:center;gap:16px}.empty-eyebrow{display:flex;align-items:center;gap:8px;color:var(--muted);font:11px var(--mono);letter-spacing:.04em}.empty-eyebrow .diamond{width:6px;height:6px;flex:none;background:var(--accent);transform:rotate(45deg);box-shadow:0 0 6px rgba(167,139,250,.4)}.empty-title{color:var(--fg);font:500 22px/1.2 var(--serif);letter-spacing:-.015em}.empty-body{max-width:460px;color:rgba(255,255,255,.55);font:italic 14px/1.6 var(--serif)}.empty-body code{padding:1px 6px;border-radius:3px;background:var(--accentSoft);color:var(--accent2);font:12.5px var(--mono);font-style:normal}.empty-body strong{color:var(--fg);font-style:normal;font-weight:600}.empty-body .inline-link{padding-bottom:1px;border-bottom:1px solid rgba(167,139,250,.4);color:var(--accent2)}.empty-actions{display:flex;gap:8px;margin-top:6px}.empty-actions .toolbar-action{display:inline-flex;height:32px;padding:0 14px;align-items:center;gap:6px;border:1px solid rgba(167,139,250,.35);border-radius:5px;background:var(--accentSoft);color:var(--accent2);font-size:12px;font-weight:500}.empty-actions svg{width:13px;height:13px}.empty-divider{width:100%;height:1px;margin:6px 0;background:var(--line)}.empty-help{display:flex;flex-direction:column;gap:6px;color:var(--muted);font:11px var(--mono)}.help-row{display:flex;align-items:baseline;gap:8px}.help-row .label{width:76px;flex:none;color:var(--muted2);letter-spacing:.04em}.empty-help code{padding:1px 6px;border-radius:3px;background:rgba(0,0,0,.3);color:var(--fg2);font-family:var(--mono)}.fold-banner{display:flex;align-items:center;gap:12px;padding:10px 22px;border-top:1px solid var(--line);border-bottom:1px solid var(--line);background:rgba(255,255,255,.015);color:var(--muted);font-size:12.5px;text-align:left}.fold-banner:hover{background:rgba(255,255,255,.03);color:var(--fg2)}.fold-banner .chev{width:10px;height:10px;flex:none;color:var(--muted2);transition:transform .15s}.fold-banner.expanded .chev{transform:rotate(90deg);color:var(--accent2)}.fold-banner .body{flex:1}.fold-banner strong{color:var(--fg2);font:500 11.5px var(--mono)}.fold-banner .reveal-link{padding-bottom:1px;border-bottom:1px solid rgba(167,139,250,.4);color:var(--accent2);font-size:11.5px}.noise-group{border-bottom:1px solid var(--line2);background:rgba(0,0,0,.15)}.noise-group-head{padding:6px 22px;border-bottom:1px solid var(--line);background:rgba(0,0,0,.1);color:var(--muted2);font:10px var(--mono);text-transform:uppercase;letter-spacing:.06em}.session-row.noise{min-height:58px;padding:8px 22px 8px 18px}.session-row.noise .session-row-title{color:var(--muted);font-size:13px;font-style:italic;font-weight:400}.session-row.noise .session-row-meta{color:var(--muted2)}.noise-fold-bottom{display:flex;width:100%;padding:8px 22px;align-items:center;gap:8px;border-top:1px solid var(--line);background:rgba(0,0,0,.2);color:var(--muted);font:11px var(--mono);text-align:left}.noise-fold-bottom:hover{background:rgba(0,0,0,.3);color:var(--fg2)}.noise-fold-bottom .chev{width:9px;height:9px;transform:rotate(-90deg);color:var(--muted2)}
@media(max-width:900px){:root{--sidebar:190px}.search{width:170px}.stats{grid-template-columns:repeat(3,1fr)}.stat:nth-child(n+4){display:none}.detail{padding-inline:28px}.usage{padding-inline:24px}}
@media(prefers-reduced-motion:reduce){*,*:before,*:after{scroll-behavior:auto!important;transition:none!important;animation:none!important}}
/* SessionTimelineRow.vue fidelity: component branches and tool presenters. */
.session-reader{--text-base:var(--reader-font,14px);--text-md:var(--reader-font,14px);max-width:720px;width:100%;padding:32px 32px 90px}
.session-reader .session-progress{margin:-32px -32px 30px}.session-reader .msg-nav{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);margin:0;z-index:10}.session-reader .msg-nav button{width:28px;height:28px}.session-timeline{gap:14px}
.session-timeline .msg{position:relative;padding:12px 14px;border:1px solid;border-radius:8px;background:rgba(255,255,255,.025);transition:border-color .6s,box-shadow .6s}
.session-timeline .msg.user{background:rgba(167,139,250,.08);border-color:rgba(167,139,250,.18)}
.session-timeline .msg.assistant{border-color:rgba(255,255,255,.06)}
.session-timeline .msg-head{display:flex;align-items:center;gap:8px;margin-bottom:8px;color:var(--muted);font:11px var(--mono);letter-spacing:0;text-transform:none}
.session-timeline .msg-head .role{color:var(--fg2);font:600 10px var(--mono);text-transform:uppercase;letter-spacing:.06em}
.session-timeline .msg.user .msg-head .role{color:var(--accent2)}
.session-timeline .msg-head .when{margin-left:auto;color:var(--muted);font-variant-numeric:tabular-nums}
.msg-text{color:var(--fg);font-size:var(--text-base);line-height:1.55;white-space:pre-wrap;overflow-wrap:anywhere}
.msg-text p{margin:.55em 0}.msg-text p:first-child{margin-top:0}.msg-text p:last-child{margin-bottom:0}.msg-text h3{margin:0 0 .55em;font-size:15px}.msg-text code{padding:1px 5px;border-radius:3px;background:rgba(255,255,255,.06);color:var(--accent2);font:calc(var(--text-base) - 1px) var(--mono)}
.msg-thinking{overflow:hidden;border:1px solid var(--line);border-radius:5px;background:rgba(0,0,0,.15)}
.thinking-toggle,.meta-toggle,.summary-toggle,.toolcall-toggle{display:flex;width:100%;align-items:center;gap:8px;border:0;background:transparent;color:inherit;text-align:left}
.thinking-toggle{padding:7px 10px}.thinking-toggle:hover,.meta-toggle:hover,.toolcall-toggle:hover{background:rgba(255,255,255,.03)}
.timeline-chevron{width:8px;height:8px;flex:none;color:var(--muted);transition:transform .15s}.trace-row.open .timeline-chevron{transform:rotate(90deg);color:var(--accent2)}
.thinking-label{flex:none;color:var(--muted);font:600 10.5px var(--mono);text-transform:uppercase;letter-spacing:.05em}
.thinking-body{display:none;padding:8px 14px 12px;border-top:1px solid var(--line)}.msg-thinking.open .thinking-body{display:block}
.msg-tools{display:flex;flex-direction:column;gap:5px;margin-top:10px}.msg-tool{overflow:hidden;border:1px solid var(--line);border-radius:5px;background:rgba(0,0,0,.2);transition:border-color .1s}
.msg-tool.is-error{border-color:rgba(248,113,113,.3);background:rgba(248,113,113,.12)}.toolcall-toggle{padding:6px 10px}
.tool-icon{display:inline-flex;width:14px;height:14px;flex:none;align-items:center;color:var(--accent2)}.tool-icon svg{width:14px;height:14px}.msg-tool.is-error .tool-icon{color:var(--danger)}
.tool-name{flex:none;color:var(--accent2);font:600 11px var(--mono)}.msg-tool.is-error .tool-name{color:var(--danger)}
.tool-arg{min-width:0;flex:1;overflow:hidden;color:var(--fg2);font:11px var(--mono);text-overflow:ellipsis;white-space:nowrap}.tool-error{flex:none;padding:1px 6px;border-radius:3px;background:rgba(248,113,113,.18);color:var(--danger);font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.04em}
.toolcall-body{display:none;border-top:1px solid var(--line);background:rgba(0,0,0,.32)}.msg-tool.open .toolcall-body{display:block}
.toolcall-body-strip{display:flex;align-items:center;gap:8px;padding:6px 10px;border-bottom:1px solid var(--line);background:rgba(0,0,0,.18)}.strip-label{color:var(--muted);font:10px var(--mono);text-transform:uppercase;letter-spacing:.05em}.raw-toggle{padding:2px 7px;border:1px solid var(--line);border-radius:3px;color:var(--muted);font:10px var(--mono)}.raw-toggle:hover{border-color:var(--line2);background:var(--surface2);color:var(--fg2)}.raw-toggle.active{border-color:var(--accentSoft);background:var(--accentSoft);color:var(--accent2)}
.toolcall-pretty{padding:10px 12px}.toolcall-raw{display:none;max-height:400px;overflow:auto;padding:12px 14px}.msg-tool.raw .toolcall-pretty{display:none}.msg-tool.raw .toolcall-raw{display:block}.tc-section{margin:0 0 5px;color:var(--muted);font:500 10px var(--mono);text-transform:uppercase;letter-spacing:.05em}.toolcall-raw .tc-section+pre{margin-bottom:12px}.toolcall-raw pre{color:var(--fg2);font:11.5px/1.55 var(--mono);white-space:pre-wrap;overflow-wrap:anywhere}
.codeact-view{overflow:hidden;border:1px solid var(--line2);border-radius:6px;background:#11121d;color:var(--fg2);font:11.5px/1.55 var(--mono)}.codeact-section+.codeact-section{border-top:1px solid var(--line2)}.codeact-section-head{min-height:32px;display:flex;align-items:center;padding:6px 10px 6px 12px;background:#181a27;color:var(--muted)}.codeact-section-label{color:var(--fg2);font-size:9.5px;font-weight:650;letter-spacing:.09em;text-transform:uppercase}.codeact-code-frame{display:grid;grid-template-columns:max-content minmax(0,1fr);max-height:260px;overflow:auto}.codeact-gutter,.codeact-code,.codeact-result-block{margin:0;font:inherit;white-space:pre}.codeact-gutter{padding:8px 10px 8px 12px;border-right:1px solid var(--line);background:rgba(255,255,255,.015);color:var(--muted2);text-align:right;user-select:none}.codeact-code{padding:8px 12px;color:var(--fg2)}.codeact-token.keyword{color:#c4b5fd}.codeact-token.string{color:#86efac}.codeact-token.global{color:#7dd3fc}.codeact-result{max-height:280px;overflow:auto;background:#0d0e17}.codeact-result-block{padding:10px 12px;white-space:pre-wrap;overflow-wrap:anywhere}.codeact-note{padding:7px 12px;border-top:1px solid var(--line);background:rgba(251,191,36,.07);color:#d6bd82;font:10.5px var(--sans)}
.terminal-view{overflow:hidden;border:1px solid var(--line);border-radius:5px;background:#07090f;color:var(--fg2);font:11.5px/1.55 var(--mono)}.terminal-prompt-line{display:flex;gap:8px;padding:8px 12px;background:rgba(255,255,255,.03)}.prompt-marker{flex:none;color:#4ade80;font-weight:600}.prompt-cmd{color:var(--fg);white-space:pre-wrap;overflow-wrap:anywhere}.terminal-divider{height:1px;background:rgba(255,255,255,.06)}.terminal-output{max-height:300px;overflow:auto;margin-left:10px;padding:8px 12px;border-left:2px solid rgba(255,255,255,.06);color:rgba(255,255,255,.68);white-space:pre-wrap}.terminal-output.is-error{border-left-color:rgba(248,113,113,.3);color:#fca5a5}
.file-content,.diff-view{overflow:hidden;border:1px solid var(--line);border-radius:5px;background:rgba(0,0,0,.4)}.file-content-head,.diff-view-head{display:flex;align-items:center;gap:10px;padding:6px 10px;border-bottom:1px solid var(--line);background:rgba(255,255,255,.02);color:var(--muted);font:10.5px var(--mono)}.file-content-head .label,.diff-view-head .label{color:var(--fg2);font-size:10px;font-weight:500;letter-spacing:.04em;text-transform:uppercase}.file-content-head .meta,.diff-view-head .stats{margin-left:auto}.file-content-body,.diff-body{display:grid;grid-template-columns:max-content 1fr;max-height:320px;overflow:auto;color:var(--fg2);font:11.5px/1.55 var(--mono)}.file-content-body.collapsed{max-height:180px}.file-content-body .gutter,.diff-gutter{padding:6px 10px 6px 12px;border-right:1px solid var(--line);background:rgba(255,255,255,.015);color:var(--muted2);text-align:right;white-space:pre;user-select:none}.file-content-body .code,.diff-line{padding:6px 12px;white-space:pre}.file-content-expand{width:100%;padding:6px;border-top:1px solid var(--line);background:rgba(255,255,255,.02);color:var(--muted);font:10.5px var(--mono)}.file-content-expand:hover{background:var(--surface2);color:var(--fg2)}.diff-body{max-height:380px}.diff-gutter{padding-block:0}.diff-line{padding-block:0}.diff-line.add{background:rgba(99,102,241,.06);color:rgba(165,180,252,.85)}.diff-line.del{background:rgba(236,72,153,.06);color:rgba(249,168,212,.6);text-decoration:line-through}.stat-add{color:rgba(165,180,252,.85)}.stat-del{color:rgba(249,168,212,.7)}
.field-grid{display:grid;grid-template-columns:max-content 1fr;gap:4px 14px;color:var(--fg2);font:11.5px var(--mono)}.field-key{color:var(--muted);font-weight:500}.literal-string{color:var(--accent2)}.literal-bool{color:#4ade80}.literal-num{color:#fcd34d}.result-chip{display:inline-flex;margin-top:10px;padding:5px 10px;border:1px solid rgba(74,222,128,.18);border-radius:4px;background:rgba(74,222,128,.12);color:var(--fg2);font-size:11.5px}.result-chip.error{border-color:rgba(248,113,113,.25);background:rgba(248,113,113,.12)}
.msg-tool.agent-call{border-color:rgba(245,158,11,.12);border-left:2px solid #f59e0b}.msg-tool.agent-call .tool-name{color:#f59e0b}.agent-call-head{display:flex;align-items:center}.agent-call-head .toolcall-toggle{min-width:0;flex:1;width:auto}.agent-nav-btn{margin-left:auto;margin-right:8px}.agent-prompt{margin-bottom:8px;padding:8px 10px;border-radius:4px;background:rgba(0,0,0,.2);color:var(--fg2);font-size:12px;line-height:1.5;white-space:pre-wrap}.agent-result{max-height:400px;overflow:auto;color:var(--fg);font-size:13px;line-height:1.55}
.skill-card{display:flex;align-items:flex-start;gap:12px;padding:12px 14px;border:1px solid rgba(6,182,212,.15);border-left:3px solid rgba(6,182,212,.5);border-radius:8px;background:rgba(6,182,212,.04)}.skill-card-icon{width:28px;height:28px;display:grid;flex:none;place-items:center;border-radius:6px;background:rgba(6,182,212,.1);color:#67e8f9}.skill-card-icon svg{width:14px}.skill-card-body{min-width:0;flex:1}.skill-card-header{display:flex;align-items:center;gap:8px;margin-bottom:4px}.skill-card-badge{padding:1px 6px;border-radius:3px;background:rgba(6,182,212,.15);color:#67e8f9;font:600 10px var(--mono);text-transform:uppercase;letter-spacing:.04em}.skill-card-name{color:var(--fg);font-size:14px;font-weight:500}.skill-card-args{overflow:hidden;color:rgba(255,255,255,.58);font-size:12px;line-height:1.5}.skill-card-md{margin-top:8px}.skill-md-toggle{display:flex;align-items:center;gap:6px;padding:2px 0;color:var(--muted);font:10.5px var(--mono)}.skill-md-body{display:none;max-height:400px;overflow:auto;margin-top:8px;padding:12px;border:1px solid var(--line);border-radius:5px;background:rgba(0,0,0,.3)}.skill-card.open .skill-md-body{display:block}
.msg.meta{padding:4px 10px;border-color:var(--line);background:transparent}.msg-meta-collapsed{overflow:hidden;border:1px solid var(--line);border-radius:4px;background:rgba(0,0,0,.1)}.meta-toggle{padding:5px 10px}.meta-label{flex:none;color:var(--muted2);font:600 10px var(--mono);text-transform:uppercase;letter-spacing:.05em}.meta-preview{min-width:0;flex:1;overflow:hidden;color:var(--muted2);font-size:11px;text-overflow:ellipsis;white-space:nowrap}.meta-body{display:none;padding:6px 12px 10px;border-top:1px solid var(--line)}.msg-meta-collapsed.open .meta-body{display:block}
.msg-summary{overflow:hidden;margin-top:12px;border:1px solid var(--line);border-left:3px solid var(--accentSoft);border-radius:5px;background:rgba(167,139,250,.04)}.summary-toggle{padding:7px 12px}.summary-label{color:var(--accent2);font:600 10.5px var(--mono);text-transform:uppercase;letter-spacing:.05em}.summary-source{margin-left:4px;color:var(--muted);font:10.5px var(--mono)}.summary-body{display:none;padding:8px 14px 12px;border-top:1px solid var(--line)}.msg-summary.open .summary-body{display:block}
.wf-card{overflow:hidden;border:1px solid rgba(245,158,11,.12);border-left:3px solid #f59e0b;border-radius:8px;background:rgba(245,158,11,.04)}.wf-card-header{display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid rgba(245,158,11,.12);background:rgba(245,158,11,.06)}.wf-card-name{color:var(--fg);font:600 14px var(--mono)}.wf-card-count{color:var(--muted);font:11px var(--mono)}.wf-card-status{margin-left:auto;padding:2px 8px;border-radius:3px;background:rgba(74,222,128,.14);color:#4ade80;font:500 10px var(--mono);text-transform:uppercase;letter-spacing:.04em}.wf-card-body{padding:12px 16px}.wf-card-phase-title{margin-bottom:4px;padding-bottom:4px;border-bottom:1px solid var(--line);color:#f59e0b;font:600 11px var(--mono);text-transform:uppercase;letter-spacing:.05em}.wf-card-agent{display:flex;width:100%;align-items:center;gap:10px;padding:7px 10px;border-radius:4px;text-align:left}.wf-card-agent:hover{background:rgba(245,158,11,.08)}.wf-card-agent-label{min-width:0;flex:1;overflow:hidden;color:var(--fg2);font-size:13px;text-overflow:ellipsis;white-space:nowrap}.wf-card-agent-arrow{color:var(--muted2)}
.archived-tag{display:inline-flex;align-items:center;gap:4px;padding:1px 6px;border-radius:3px;background:rgba(248,113,113,.1);color:var(--danger);font:9.5px var(--mono);text-transform:uppercase;letter-spacing:.04em}.archived-tag:before{content:"";width:4px;height:4px;border-radius:50%;background:currentColor}.memory-actions .btn.danger{color:var(--danger)}.memory-actions .btn.danger:hover{border-color:rgba(248,113,113,.3);background:rgba(248,113,113,.12)}
</style>
</head>
<body>
<div id="app"></div>
<div id="modalHost"></div>
<div id="toast" aria-live="polite"></div>
<script>
const sessions=[
{id:'s1',title:'Design the Obelisk session reader',project:'quiet-zero',source:'codex',when:'18 min',created:'Today, 14:32',messages:86,duration:'42m',branch:'codex/session-reader',snippet:'Implement the virtualized timeline and preserve reader position across live reloads.'},
{id:'s2',title:'Fix memory archive undo behavior',project:'quiet-zero',source:'claude',when:'2 hr',created:'Today, 11:06',messages:42,duration:'19m',branch:'main',snippet:'The undo toast should restore the selection and its original list position.'},
{id:'s3',title:'Build benchmark corpus and evaluation notes',project:'obelisk-bench',source:'claude',when:'Yesterday',created:'Yesterday, 16:48',messages:113,duration:'1h 08m',branch:'research/benchmark',snippet:'Compare evidence recall across Claude Code and Codex histories.'},
{id:'s4',title:'Refactor the indexer writer lease',project:'quiet-zero',source:'codex',when:'Jul 18',created:'Jul 18, 09:14',messages:67,duration:'36m',branch:'codex/writer-lease',snippet:'Keep a single writer while readers continue to serve session detail.'},
{id:'s5',title:'Landing page icon direction',project:'obelisk-site',source:'claude',when:'Jul 16',created:'Jul 16, 20:21',messages:29,duration:'14m',branch:'design/icons',snippet:'Use the slab obelisk with a quiet aurora, not a generic sparkle mark.'},
{id:'s6',title:'Package the Obelisk skill artifact',project:'quiet-zero',source:'codex',when:'Jul 15',created:'Jul 15, 10:17',messages:54,duration:'31m',branch:'release/skill',snippet:'Publish readable references without bundling development-only files.'}
];
const memories=[
{id:'m1',project:'quiet-zero',path:'docs/decisions/session-reader-state.md',summary:'Reader state is keyed by session and restores expanded messages, focused item, and scroll anchor after live reload.',when:'12m ago',archived:false},
{id:'m2',project:'quiet-zero',path:'docs/decisions/evidence-before-assertion.md',summary:'Every interpretation in Obelisk must keep a visible path back to the raw session event that supports it.',when:'3h ago',archived:false},
{id:'m3',project:'obelisk-bench',path:'research/benchmark/retrieval-notes.md',summary:'Exact evidence recall improves when queries include both the project boundary and a concrete implementation noun.',when:'Yesterday',archived:false},
{id:'m4',project:'quiet-zero',path:'docs/decisions/two-tier-runtime.md',summary:'The CLI consumes precompiled core output; the desktop renderer communicates through a narrow IPC contract.',when:'Jul 17',archived:true},
{id:'m5',project:'obelisk-site',path:'brand/icon-direction.md',summary:'The approved mark combines a literal stone slab with a restrained violet and magenta aurora.',when:'Jul 12',archived:true},
{id:'m6',project:'quiet-zero',path:'docs/decisions/indexer-migration.md',summary:'Provider adapters emit normalized records through the shared parsing contract while the indexer owns persistence.',when:'Jul 8',archived:true}
];
const projects=[['quiet-zero',9],['obelisk-bench',3],['obelisk-site',2],['accio',2],['codex-pets',1],['docs-lab',1]];
const recaps=[
{id:'recap-2026-W25.json',period:'Jun 15 18',kind:'weekly',title:'The Architect',claim:'You widened the system from schema to UI while keeping every layer inspectable.',sessions:31,tokens:'3.2K',messages:'3.2K'},
{id:'recap-2026-W24.json',period:'Jun 9 15',kind:'weekly',title:'The Shipper',claim:'You moved a desktop app from a small prototype to something ready to package.',sessions:11,tokens:'1.4K',messages:'1.4K'},
{id:'recap-2026-06.json',period:'June 2026',range:'Jun 1 15',kind:'monthly',title:'The Architect',claim:'Gave every kind of knowledge a shell you can browse.',sessions:18,tokens:'7.9M',messages:'14.7K'}
];
const icons={sessions:'<path d="M3 4h10v8a1 1 0 01-1 1H4a1 1 0 01-1-1V4z"/><path d="M5.5 7h5M5.5 9.5h3"/>',memory:'<rect x="2.5" y="2.5" width="11" height="11" rx="2"/><path d="M5 5.5h6M5 8h6M5 10.5h4"/>',activity:'<rect x="2" y="10" width="2.5" height="4"/><rect x="6" y="6" width="2.5" height="8"/><rect x="10" y="3" width="2.5" height="11"/>',recap:'<path d="M3 2h10v12H3z"/><path d="M6 5h4M6 8h4M6 11h2"/>',folder:'<path d="M2 5.5V12a1.5 1.5 0 001.5 1.5h9A1.5 1.5 0 0014 12V6.5A1.5 1.5 0 0012.5 5H8.3L7 3.5H3.5A1.5 1.5 0 002 5z"/>',settings:'<path d="M3 4h10M3 8h10M3 12h10"/><circle cx="9.5" cy="4" r="1.7"/><circle cx="5.5" cy="8" r="1.7"/><circle cx="11" cy="12" r="1.7"/>'};
const svg=k=>`<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${icons[k]}</svg>`;
const fileSvg=`<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round" stroke-linecap="round" aria-hidden="true"><path d="M3.5 2h6l3 3v9a1 1 0 01-1 1h-8a1 1 0 01-1-1V3a1 1 0 011-1z"/><path d="M9.5 2v3h3"/></svg>`;
const logo=`<svg class="brand-logo" viewBox="0 0 40 40" aria-hidden="true"><defs><radialGradient id="a"><stop stop-color="#ec4899" stop-opacity=".8"/><stop offset=".5" stop-color="#a855f7" stop-opacity=".7"/><stop offset="1" stop-color="#6366f1" stop-opacity="0"/></radialGradient><linearGradient id="s" x2="0" y2="1"><stop stop-color="#cbd5e1"/><stop offset="1" stop-color="#475569"/></linearGradient></defs><ellipse cx="20" cy="22" rx="15" ry="11" fill="url(#a)"/><polygon points="20,7 16.5,12 23.5,12" fill="url(#s)"/><polygon points="20,12 16.5,12 17.5,33 20,33" fill="url(#s)"/><polygon points="20,12 23.5,12 22.5,33 20,33" fill="#1e293b"/><rect x="15.5" y="33" width="9" height="1.6" rx=".3" fill="#0f172a"/></svg>`;
const recapSeals={
architect:`<svg viewBox="0 0 110 110" fill="none"><defs><radialGradient id="seal-a"><stop stop-color="#a78bfa" stop-opacity=".5"/><stop offset="1" stop-color="#a78bfa" stop-opacity="0"/></radialGradient></defs><circle cx="55" cy="55" r="50" fill="url(#seal-a)"/><circle cx="55" cy="55" r="42" stroke="rgba(255,255,255,.2)"/><circle cx="55" cy="55" r="38" stroke="#a78bfa" stroke-width="1.4"/><polygon points="55,28 50,38 60,38" fill="#c4b5fd"/><polygon points="50,38 60,38 58,76 52,76" fill="#a78bfa"/><polygon points="55,38 60,38 58,76 55,76" fill="#7c3aed" opacity=".75"/></svg>`,
shipper:`<svg viewBox="0 0 110 110" fill="none"><defs><radialGradient id="seal-s"><stop stop-color="#f472b6" stop-opacity=".5"/><stop offset="1" stop-color="#f472b6" stop-opacity="0"/></radialGradient></defs><circle cx="55" cy="55" r="50" fill="url(#seal-s)"/><circle cx="55" cy="55" r="42" stroke="rgba(255,255,255,.2)"/><circle cx="55" cy="55" r="38" stroke="#f472b6" stroke-width="1.4"/><rect x="32" y="48" width="14" height="14" rx="1.5" fill="#f472b6" opacity=".35"/><rect x="48" y="48" width="14" height="14" rx="1.5" fill="#f472b6" opacity=".65"/><rect x="64" y="48" width="14" height="14" rx="1.5" fill="#fda4af"/></svg>`
};
const S={page:'sessions',project:'all',memoryView:'active',q:'',desc:true,source:'all',sourceMenu:false,health:false,selected:new Set(),detail:null,activity:'daily',day:null,recapKind:'weekly',slide:0,recapPalette:0,auto:true,toastTimer:0,undo:null};
const $=s=>document.querySelector(s), esc=s=>String(s).replace(/\\n/g,'\n').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
function label(p){return ['sessions','sessionDetail','subagentDetail'].includes(p)?'Sessions':p==='memory'||p==='memoryDetail'?'Memory':p==='activity'?'Activity':p==='recap'||p==='recapDetail'?'Recap':'Settings'}
function scope(){if(S.page==='sessionDetail')return`Sessions · ${sessions.find(x=>x.id===S.detail)?.title||''}`;if(S.page==='subagentDetail')return`Sessions · ${sessions.find(x=>x.id===(S.parentSession||'s1'))?.title||''}`;if(S.page==='memoryDetail')return`Memory · ${memories.find(x=>x.id===S.detail)?.path.split('/').pop()||''}`;if(S.page==='recapDetail')return`Recap · ${S.detail}`;if(S.page==='memory')return`Memory · ${S.memoryView==='archived'?'Archived':'Active'}${S.project!=='all'?` · ${S.project}`:''}`;return label(S.page)+(S.project!=='all'&&S.page==='sessions'?` · ${S.project}`:'')}
function sideItem(page,icon,text,badge,sub=''){const active=page==='sessions'?['sessions','sessionDetail','subagentDetail'].includes(S.page)&&S.project==='all':page==='memory'?['memory','memoryDetail'].includes(S.page)&&S.project==='all'&&S.memoryView===(sub||'active'):S.page===page;const click=page==='memory'?`A.memoryView('${sub||'active'}')`:`A.nav('${page}')`;return`<button class="side-item ${sub?'sub ':''}${active?'active':''}" onclick="${click}">${svg(icon)}<span class="label">${text}</span>${badge!==''?`<span class="badge">${badge}</span>`:''}</button>`}
function breadcrumb(){if(S.page==='sessionDetail'){const x=sessions.find(x=>x.id===S.detail);return`<button class="crumb" onclick="A.backSessions()">Sessions</button><span class="crumb-sep">/</span><span class="crumb current">${esc(x?.title||'')}</span>`}if(S.page==='subagentDetail'){const x=sessions.find(x=>x.id===S.parentSession)||sessions[0];return`<button class="crumb" onclick="A.backSessions()">Sessions</button><span class="crumb-sep">/</span><button class="crumb" onclick="A.backSessionDetail()">${esc(x.title)}</button><span class="crumb-sep">/</span><span class="crumb current">${esc(S.detail)}</span>`}if(S.page==='memoryDetail'){const x=memories.find(x=>x.id===S.detail);return`<button class="crumb" onclick="A.backMemory()">Memory</button><span class="crumb-sep">/</span><span class="crumb current" style="font-family:var(--mono)">${esc(x?.path.split('/').pop()||'')}</span>`}if(S.page==='recapDetail')return`<button class="crumb" onclick="A.backRecap()">Recap</button><span class="crumb-sep">/</span><span class="crumb current">${S.detail}</span>`;return S.project!=='all'&&['sessions','memory'].includes(S.page)?`<button class="crumb" onclick="A.project('all')">${label(S.page)}</button><span class="crumb-sep">/</span><span class="crumb current">${esc(S.project)}</span>`:`<span class="crumb current">${label(S.page)}</span>`}
function hi(text){if(!S.q)return esc(text);const q=esc(S.q).replace(/[.*+?^${}()|[\]\\]/g,'\\$&');return esc(text).replace(new RegExp(`(${q})`,'ig'),'<mark>$1</mark>')}
function visibleMemoryRows(){let rows=memories.filter(x=>x.archived===(S.memoryView==='archived')&&(S.project==='all'||x.project===S.project)&&(`${x.path} ${x.summary} ${x.project}`.toLowerCase().includes(S.q.toLowerCase())));return S.desc?rows:[...rows].reverse()}
function memoryList(){const rows=visibleMemoryRows(),check=`<svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M2.5 6.5l2.5 2.5 4.5-5"/></svg>`,archived=`<span class="row-status" title="archived"><svg viewBox="0 0 14 14" fill="currentColor"><circle cx="7" cy="7" r="2.5"/></svg></span>`;return rows.length?`<div class="list">${rows.map(x=>`<article class="mrow ${S.cursor===x.id?'cursor ':''}${S.selected.has(x.id)?'selected':''}" data-id="${x.id}" onclick="A.openMemory(event,'${x.id}')"><button class="check ${S.selected.has(x.id)?'on':''}" aria-label="Select" onclick="event.stopPropagation();A.select('${x.id}',event.shiftKey)">${S.selected.has(x.id)?check:''}</button><div class="mrow-body"><div class="mrow-path">${x.archived?archived:''}<span class="mrow-project">${x.project} / </span>${hi(x.path.split('/').pop())}</div><div class="mrow-summary">${hi(x.summary)}</div></div><div class="mrow-right"><span>${x.when}</span><button class="row-action" onclick="event.stopPropagation();A.archive('${x.id}')">${x.archived?'Restore':'Archive'} <span class="kbd">D</span></button></div></article>`).join('')}</div>`:`<div class="empty"><b>No memories${S.memoryView==='archived'?' archived':''} here.</b><small>${S.q?'Try a different search term.':'Press / to search.'}</small></div>`}
S.showNoise=false;S.quietShown=false;S.debugEmpty=false;S.projectQ='';S.showSource=false;S.generateWindow='this-week';S.msgPos=1;S.months=1;S.fontSize=14;S.cursor=null;S.noiseLedger=false;S.traceOpen={};S.rawTools={};S.traceSeq=0;
const noiseProjects=[['od-conn-test',1],['4d924b',1],['7f8e91',1]];
function projectButtonsV2(){const memoryScope=S.page.startsWith('memory');const memoryProjects=[...new Map(memories.filter(m=>m.archived===(S.memoryView==='archived')).map(m=>[m.project,0])).keys()].map(name=>[name,memories.filter(m=>m.project===name&&m.archived===(S.memoryView==='archived')).length]);const base=memoryScope?memoryProjects:projects;const all=[...base,...(!memoryScope&&S.showNoise?noiseProjects:[])];return all.filter(x=>!S.projectQ||x[0].toLowerCase().includes(S.projectQ)).map(p=>`<button class="side-item ${S.project===p[0]?'active':''} ${noiseProjects.some(n=>n[0]===p[0])?'noise':''}" onclick="A.project('${p[0]}')">${svg('folder')}<span class="label">${p[0]}</span><span class="badge">${p[1]}</span></button>`).join('')}
function sidebarV2(){const showProjects=['sessions','sessionDetail','subagentDetail','memory','memoryDetail'].includes(S.page);const sessionsScope=['sessions','sessionDetail','subagentDetail'].includes(S.page);return`<aside class="sidebar"><div class="brand">${logo}<span class="brand-name">Obelisk</span><button class="health" aria-label="Connected sources" onclick="A.health()"><span class="dot claude"></span><span class="dot codex"></span></button>${S.health?`<div class="popover"><div class="pop-head">Connected sources</div><button class="source-row" onclick="A.sourceSettings()"><span class="dot claude"></span><span><span class="source-name">Claude Code <small style="color:var(--muted);font:10px var(--mono)">76 sessions</small></span><span class="source-meta">Connected</span></span></button><button class="source-row" onclick="A.sourceSettings()"><span class="dot codex"></span><span><span class="source-name">Codex <small style="color:var(--muted);font:10px var(--mono)">244 sessions</small></span><span class="source-meta">Connected</span></span></button><div class="pop-foot"><button onclick="A.manageSettings()">Manage in Settings →</button></div></div>`:''}</div><div class="side-section"><div class="section-title">Library</div>${sideItem('sessions','sessions','Sessions',326)}${sideItem('memory','memory','Memory',memories.length)}${sideItem('memory','memory','Active',memories.filter(x=>!x.archived).length,'active')}${sideItem('memory','memory','Archived',memories.filter(x=>x.archived).length,'archived')}</div><div class="side-section"><div class="section-title">Stats</div>${sideItem('activity','activity','Activity','')}${sideItem('recap','recap','Recap','')}</div>${showProjects?`<div class="side-section projects"><div class="section-title"><span>Projects</span>${sessionsScope?`<button class="show-all" onclick="A.noise()">${svg('sessions')} ${S.showNoise?'hide noise':'show all'}</button>`:''}</div>${sessionsScope?`<div class="project-search"><input aria-label="Filter projects" placeholder="Filter projects…" value="${esc(S.projectQ)}" oninput="A.projectSearchV2(this.value)"></div>`:''}<div class="project-list">${projectButtonsV2()}${sessionsScope&&!S.showNoise?`<button class="noise-fold" onclick="A.noise()"><span></span><span class="label">${noiseProjects.length} test projects hidden</span><span class="badge">${noiseProjects.length}</span></button>`:''}</div></div>`:'<div style="flex:1"></div>'}<div class="side-section bottom">${sideItem('settings','settings','Settings','')}</div></aside>`}
function filterCheckV2(on){return`<span class="filter-check">${on?`<svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M2.5 6l2.2 2.2L9.5 3.5"/></svg>`:''}</span>`}
function toolbarV2(){const list=['sessions','memory'].includes(S.page),isRecap=S.page==='recap';return`<div class="toolbar"><div class="breadcrumb">${breadcrumb()}</div><div class="spacer"></div>${isRecap?`<div class="tabs"><button class="${S.recapKind==='weekly'?'active':''}" onclick="A.recapKind('weekly')">Weekly</button><button class="${S.recapKind==='monthly'?'active':''}" onclick="A.recapKind('monthly')">Monthly</button></div><button class="btn primary" onclick="A.generate()"><span style="font-size:14px;line-height:1;opacity:.8">+</span><span>Generate</span></button>`:''}${S.page==='sessions'?`<div class="source-filter-wrap"><button class="filter source-filter ${S.source!=='all'?'active':''}" onclick="A.sourceMenu()"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 3h8M3.5 6h5M5 9h2"/></svg>${S.source==='all'?'All sources':S.source==='claude'?'Claude Code':'Codex'}</button>${S.sourceMenu?`<div class="filter-menu">${['claude','codex','all'].map(x=>{const on=S.source==='all'||S.source===x;return`<button class="${on?'on':''}" onclick="A.source('${x}')">${filterCheckV2(on)}<span>${x==='all'?'All sources':x==='claude'?'Claude Code':'Codex'}</span></button>`}).join('')}</div>`:''}</div>`:''}${list?`<label class="search"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="7" cy="7" r="5"/><path d="M11 11l3 3"/></svg><input id="search" aria-label="Search" placeholder="Search…" value="${esc(S.q)}" oninput="A.search(this.value)"><span class="kbd">/</span></label><button class="sort ${S.desc?'desc':'asc'}" onclick="A.sort()"><span>${S.desc?'newest':'oldest'}</span><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M5 6l3-3 3 3" opacity="${S.desc?'.25':'1'}"/><path d="M5 10l3 3 3-3" opacity="${S.desc?'1':'.25'}"/></svg></button>`:''}</div>`}
function sessionNoDataV2(){return`<div class="empty-content"><div class="empty-eyebrow"><span class="diamond"></span><span>No data source connected</span></div><div class="empty-title">Obelisk reads your Claude Code session history.</div><div class="empty-body">We didn't find <code>~/.claude</code> on this machine. If you've already used Claude Code, point Obelisk at where its data lives in <button class="inline-link" onclick="A.nav('settings')">Settings</button>. If you haven't, <strong>install Claude Code first</strong> — Obelisk has nothing to read until sessions exist.</div><div class="empty-actions"><button class="toolbar-action" onclick="A.nav('settings')">${svg('folder')}Choose folder…</button></div><div class="empty-divider"></div><div class="empty-help"><div class="help-row"><span class="label">expected</span><code>~/.claude</code></div><div class="help-row"><span class="label">searched</span><code>~</code></div></div></div>`}
function quietSessionsV2(count){const times=['07/19 04:00','07/17 04:35','07/17 04:27','07/17 04:06','07/09 12:52','06/17 01:13','06/15 01:43','1970/01/01 08:00'];return`<button class="fold-banner ${S.quietShown?'expanded':''}" onclick="A.quiet()"><svg class="chev" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M4 2.5l3 3.5-3 3.5"/></svg><span class="body"><strong>${count}</strong> quiet sessions hidden — untitled, likely tests or incomplete runs.</span>${S.quietShown?'':`<span class="reveal-link">Show all</span>`}</button>${S.quietShown?`<div class="noise-group"><div class="noise-group-head">${count} sessions · untitled</div>${Array.from({length:count},(_,i)=>`<article class="session-row noise" onclick="A.openSession('s${i%6+1}')"><div><div class="session-row-title">(untitled)</div><div class="session-row-meta">${S.project==='all'?`<span class="project">${sessions[i%6].project}</span><span>·</span>`:''}<span>${[6,25,11,19,148,6,3,0][i]||0} msg</span></div></div><time class="session-row-time">${times[i]}</time></article>`).join('')}<button class="noise-fold-bottom" onclick="event.stopPropagation();A.quiet()"><svg class="chev" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 2.5l3 3.5-3 3.5"/></svg>Collapse</button></div>`:''}`}
function sessionListV2(){let rows=sessions.filter(x=>(S.project==='all'||x.project===S.project)&&(S.source==='all'||x.source===S.source)&&(`${x.title} ${x.project}`.toLowerCase().includes(S.q.toLowerCase())));if(S.debugEmpty||(!rows.length&&!S.q))return sessionNoDataV2();if(!S.desc)rows=[...rows].reverse();const times=['05:07','05:03','04:10','02:29','02:24','07/19 20:15'];const visible=rows.map((x,i)=>`<article class="session-row" tabindex="0" onclick="A.openSession('${x.id}')" onkeydown="if(event.key==='Enter')A.openSession('${x.id}')"><span class="session-obelisk" style="height:${Math.max(5,Math.min(31,Math.round(x.messages/4)))}px"></span><div><div class="session-row-title">${hi(x.title)}</div><div class="session-row-meta">${S.project==='all'?`<span class="project">${hi(x.project)}</span><span>·</span>`:''}<span>${x.messages} msg</span></div></div><time class="session-row-time">${times[i]||x.when}</time></article>`).join('');return visible?`<div class="list">${visible}${!S.q?quietSessionsV2(8):''}</div>`:`<div class="empty">No sessions here.<span class="hint">Try a different search term.</span></div>`}
/* Mirrors SessionTimelineRow.vue and session-timeline-presentation.mjs. */
const timelineChev=`<svg class="timeline-chevron" viewBox="0 0 8 8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M2.5 1.5l3 2.5-3 2.5"/></svg>`;
const terminalIcon=`<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round"><rect x="2" y="3" width="12" height="10" rx="1.2"/><path d="M5 7l2 1.5-2 1.5M8.5 10.5h2.5"/></svg>`;
const readIcon=`<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round"><path d="M3.5 2h6l3 3v9a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1z"/><path d="M9.5 2v3h3"/></svg>`;
const editIcon=`<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round"><path d="M3.5 2h6l3 3v9a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1z"/><path d="M9.5 2v3h3"/><path d="M6 10l4-4M6.5 10.5l-1.2 1.4 1.4-1.2"/></svg>`;
function toolIconV3(name){return ['Bash','exec'].includes(name)?terminalIcon:name==='Read'?readIcon:['Edit','Write'].includes(name)?editIcon:''}
function disclosureOpenV3(key){return key in S.traceOpen?S.traceOpen[key]:false}
function thinkingV3(text,id='thinking',open=false){const key=`thinking:${id}`;return`<div class="msg-thinking trace-row ${disclosureOpenV3(key,open)?'open':''}" data-trace="${key}"><button class="thinking-toggle" onclick="A.disclose(this)">${timelineChev}<span class="thinking-label">Thinking</span></button><div class="thinking-body msg-text">${text}</div></div>`}
function execPrettyV3(source,result){const lines=source.split('\n');return`<div class="codeact-view" role="group" aria-label="CodeAct execution"><section class="codeact-section"><div class="codeact-section-head"><span class="codeact-section-label">Source</span></div><div class="codeact-code-frame"><pre class="codeact-gutter">${lines.map((_,i)=>i+1).join('\n')}</pre><pre class="codeact-code"><code>${esc(source)}</code></pre></div></section><section class="codeact-section"><div class="codeact-section-head"><span class="codeact-section-label">Result</span></div><div class="codeact-result"><pre class="codeact-result-block">${esc(result)}</pre></div><div class="codeact-note">Indexed output truncated. Open Raw to inspect the captured envelope.</div></section></div>`}
function readPrettyV3(output){const lines=output.split('\n'),collapsed=lines.length>12;return`<div class="file-content"><div class="file-content-head"><span class="label">File contents</span><span class="meta">${lines.length} lines</span></div><div class="file-content-body ${collapsed?'collapsed':''}"><div class="gutter">${lines.map((_,i)=>i+1).join('\n')}</div><div class="code">${esc(output)}</div></div>${collapsed?`<button class="file-content-expand" onclick="A.fileExpand(this)">Show all ${lines.length} lines</button>`:''}</div>`}
function diffPrettyV3(){return`<div class="diff-view"><div class="diff-view-head"><span class="label">Diff</span><div class="stats"><span class="stat-add">+2</span><span class="stat-del">1</span></div></div><div class="diff-body"><div class="diff-gutter"> 1 1\n 2 \n 2\n 3</div><div><div class="diff-line context"> function restoreReaderState(state) {</div><div class="diff-line del">- viewport.scrollTop = state.top;</div><div class="diff-line add">+ restoreFocusedAnchor(state.anchor);</div><div class="diff-line add">+ restoreDisclosures(state.open);</div></div></div></div><div class="result-chip">Updated session-reader-state.mjs</div>`}
function terminalPrettyV3(command,output,error=false){return`<div class="terminal-view"><div class="terminal-prompt-line"><span class="prompt-marker">$</span><span class="prompt-cmd">${esc(command)}</span></div><div class="terminal-divider"></div><div class="terminal-output ${error?'is-error':''}">${esc(output)}</div></div>`}
function genericPrettyV3(){return`<div class="field-grid"><div class="field-key">query</div><div class="field-val"><span class="literal-string">"SessionTimelineRow"</span></div><div class="field-key">limit</div><div class="field-val"><span class="literal-num">20</span></div><div class="field-key">case_sensitive</div><div class="field-val"><span class="literal-bool">false</span></div></div><div class="result-chip">20 matches</div>`}
function prettyToolV3(t){return t.name==='exec'?execPrettyV3(t.source,t.output):t.name==='Read'?readPrettyV3(t.output):t.name==='Edit'?diffPrettyV3():t.name==='Bash'?terminalPrettyV3(t.arg,t.output,t.error):genericPrettyV3()}
function toolV3(t){const key=`tool:${t.id}`,open=disclosureOpenV3(key,t.open),raw=S.rawTools?.[key];return`<div class="msg-tool trace-row ${open?'open':''} ${raw?'raw':''} ${t.error?'is-error':''}" data-trace="${key}"><button class="toolcall-toggle" onclick="A.disclose(this)">${timelineChev}${toolIconV3(t.name)?`<span class="tool-icon">${toolIconV3(t.name)}</span>`:''}<span class="tool-name">${t.name}</span><span class="tool-arg">${esc(t.arg)}</span>${t.error?'<span class="tool-error">error</span>':''}</button><div class="toolcall-body"><div class="toolcall-body-strip"><span class="strip-label">${t.name}</span><span class="spacer"></span><button class="raw-toggle ${raw?'active':''}" onclick="A.rawTool(event,this)">{ } Raw</button></div><div class="toolcall-pretty">${prettyToolV3(t)}</div><div class="toolcall-raw"><div class="tc-section">Input</div><pre>${esc(t.input)}</pre><div class="tc-section">${t.error?'Error':'Output'}</div><pre>${esc(t.output||'(empty)')}</pre></div></div></div>`}
function messageV3(role,time,body,pos){const user=role==='You';return`<section class="msg ${user?'user':'assistant'}" data-pos="${pos}"><div class="msg-head"><span class="role">${role}</span><span class="when">${time}</span></div>${body}</section>`}
function agentToolV3(){const key='tool:agent-a6205b6278fc60f04',open=disclosureOpenV3(key);return`<div class="msg-tool agent-call trace-row ${open?'open':''}" data-trace="${key}"><div class="agent-call-head"><button class="toolcall-toggle" onclick="A.disclose(this)">${timelineChev}<span class="tool-name">general-purpose</span><span class="tool-arg">Audit the mini-app against every UI state</span></button><button class="agent-nav-btn" onclick="event.stopPropagation();A.openSubagent('agent-a6205b6278fc60f04')">View conversation →</button></div><div class="toolcall-body" style="padding:10px 12px"><div class="tc-section">Prompt</div><div class="agent-prompt">Compare the existing UI page tree and report every mismatch.</div><div class="tc-section">Result</div><div class="agent-result">The subagent found a missing route and supplied interaction evidence.</div></div></div>`}
function skillV3(){const key='skill:session-ui',open=disclosureOpenV3(key);return`<section class="skill-card trace-row ${open?'open':''}" data-pos="6" data-trace="${key}"><div class="skill-card-icon">${readIcon}</div><div class="skill-card-body"><div class="skill-card-header"><span class="skill-card-badge">Skill</span><span class="skill-card-name">computer-use</span></div><div class="skill-card-args">Compare the installed Obelisk app and the mini-app state by state.</div><div class="skill-card-md"><button class="skill-md-toggle" onclick="A.disclose(this)">${timelineChev}<span>SKILL.md</span></button><div class="skill-md-body msg-text"><p>Use Computer Use for app interactions that are not exposed through a more specific interface.</p></div></div></div></section>`}
function metaV3(){const key='meta:system-demo',open=disclosureOpenV3(key);return`<section class="msg meta" data-pos="7"><div class="msg-meta-collapsed trace-row ${open?'open':''}" data-trace="${key}"><button class="meta-toggle" onclick="A.disclose(this)">${timelineChev}<span class="meta-label">System</span><span class="meta-preview">Keep evidence separate from inferred presentation state.</span></button><div class="meta-body msg-text">Keep evidence separate from inferred presentation state. Use the installed application as the visual source of truth.</div></div></section>`}
function summaryV3(){const key='summary:session-demo',open=disclosureOpenV3(key);return`<div class="msg-summary trace-row ${open?'open':''}" data-trace="${key}"><button class="summary-toggle" onclick="A.disclose(this)">${timelineChev}<span class="summary-label">Session summary</span><span class="summary-source">compaction</span></button><div class="summary-body msg-text"><p>The reader now restores its focused anchor and disclosure state after live updates.</p></div></div>`}
function workflowV3(){return`<section class="wf-card" data-pos="9"><div class="wf-card-header"><span>⚙</span><span class="wf-card-name">UI fidelity audit</span><span class="wf-card-count">2 agents</span><span class="wf-card-status">completed</span></div><div class="wf-card-body"><div class="wf-card-phase-title">Compare</div><button class="wf-card-agent" onclick="A.openSubagent('agent-a6205b6278fc60f04')"><span class="wf-card-agent-label">Session timeline component audit</span><span class="wf-card-agent-arrow">→</span></button><button class="wf-card-agent" onclick="A.openSubagent('agent-visual-check')"><span class="wf-card-agent-label">Computer Use visual verification</span><span class="wf-card-agent-arrow">→</span></button></div></section>`}
function sessionDetailV3(){const x=sessions.find(x=>x.id===S.detail)||sessions[0],total=9,readOut=`export function captureReaderState(viewport) {\n return { anchor: viewport.focusedItem, open: viewport.disclosures };\n}\n\nexport function restoreReaderState(state) {\n restoreFocusedAnchor(state.anchor);\n restoreDisclosures(state.open);\n}`,execSource=`const r = await tools.exec_command({\n cmd: "sed -n '1,240p' app/src/renderer/src/components/SessionTimelineRow.vue"\n});\ntext(r.output);`;return`<div class="session-reader" style="--reader-font:${S.fontSize}px"><div class="session-progress"><div class="session-progress-fill" style="width:${Math.round(S.msgPos/total*100)}%"></div></div><header class="session-header"><div class="session-eyebrow">${svg('folder')}<span class="project-name">${x.project}</span><span class="sep">·</span><span class="project-path">/Users/tomiya/Code/${x.project}</span><span class="via"><span class="via-dot ${x.source}"></span>via ${x.source==='codex'?'Codex':'Claude Code'}</span></div><div class="session-title">${esc(x.title)}</div><div class="session-meta-line"><span>created 2h ago</span><span class="meta-dot"></span><span>last active 55m ago</span><span class="meta-dot"></span><span>${x.messages} messages</span><span class="meta-dot"></span><span>${x.branch||'main'}</span></div></header><div class="session-timeline">${messageV3('You','02:55',`<div class="msg-text">修改 mini-app,使其忠实复刻 app 的 UI / UX 行为;尤其注意 UI 部件、字体样式和文字内容。</div>`,1)}${messageV3('Assistant','02:55',thinkingV3('<h3>Inspecting the component tree</h3><p>I need to map the real timeline branches before changing the miniature.</p>','inspect')+`<div class="msg-text"><p>我会先对照 <code>SessionTimelineRow.vue</code> 和 tool renderer,再使用 Computer Use 验收。</p></div><div class="msg-tools">`+toolV3({id:'exec-source',name:'exec',arg:execSource,input:JSON.stringify(execSource),source:execSource,output:'---\\nSessionTimelineRow\\nTool presenter branches\\n...',open:true})+`</div>`,2)}${messageV3('Assistant','02:56',`<div class="msg-text"><p>普通工具需要保留独立的 Pretty 和 Raw 展示。</p></div><div class="msg-tools">`+toolV3({id:'read-state',name:'Read',arg:'/app/src/renderer/src/session-reader-state.mjs',input:'{\\n \"file_path\": \"/app/src/renderer/src/session-reader-state.mjs\"\\n}',output:readOut})+toolV3({id:'edit-state',name:'Edit',arg:'/app/src/renderer/src/session-reader-state.mjs',input:'{\\n \"old_string\": \"viewport.scrollTop = state.top\",\\n \"new_string\": \"restoreFocusedAnchor(state.anchor)\"\\n}',output:'Updated session-reader-state.mjs'})+`</div>`,3)}${messageV3('Assistant','02:57',thinkingV3('<p>The viewport follows new events only when the reader is already at the live tail.</p>','tail')+`<div class="msg-tools">`+toolV3({id:'bash-tests',name:'Bash',arg:'npm test -- session-reader-state',input:'{\\n \"command\": \"npm test -- session-reader-state\"\\n}',output:'✓ restores focused item\\n✓ preserves expanded messages\\n✓ follows new events only at live tail'})+toolV3({id:'edit-error',name:'Edit',arg:'/app/obelisk-ui-mini.html',input:'{\\n \"old_string\": \"trace-row\",\\n \"new_string\": \"msg-tool\"\\n}',output:'File has been modified since read. Read it again before attempting to write.',error:true})+`</div>`,4)}${messageV3('Assistant','02:58',`<div class="msg-text"><p>The Agent/Task branch exposes the child conversation directly from the tool header.</p></div><div class="msg-tools">`+agentToolV3()+`</div>`,5)}${skillV3()}${metaV3()}${messageV3('Assistant','03:01',`<div class="msg-text"><p>Implemented source-shaped disclosures and preserved their open state.</p></div>`+summaryV3(),8)}${workflowV3()}</div><div class="msg-nav"><button ${S.msgPos===1?'disabled':''} onclick="A.msgNav('first')" title="First">⇤</button><button ${S.msgPos===1?'disabled':''} onclick="A.msgNav('prev')" title="Previous"></button><span class="msg-pos">${S.msgPos} / ${total}</span><button ${S.msgPos===total?'disabled':''} onclick="A.msgNav('next')" title="Next"></button><button ${S.msgPos===total?'disabled':''} onclick="A.msgNav('last')" title="Last">⇥</button></div></div>`}
const subChev=`<svg class="sub-chevron" viewBox="0 0 8 8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M2.5 1.5l3 2.5-3 2.5"/></svg>`;
function subTool(name,arg,input,output,error=false){return`<div class="sub-disclosure sub-tool ${error?'is-error ':''}"><button class="sub-toggle" onclick="A.subToggle(this)">${subChev}<span class="sub-tool-name">${esc(name)}</span><span class="sub-tool-arg">${esc(arg)}</span>${error?'<span class="sub-error">error</span>':''}</button><div class="sub-body"><div class="tc-section">Input</div><pre>${esc(input)}</pre><div class="tc-section">${error?'Error':'Output'}</div><pre>${esc(output||'(empty)')}</pre></div></div>`}
function subagentDetailV2(){const id=S.detail||'agent-a6205b6278fc60f04';return`<div class="subagent-wrap"><div class="subagent-wide"><header class="subagent-header"><div class="subagent-eyebrow">Subagent</div><div class="subagent-title">${esc(id)}</div><div class="subagent-meta">8 messages</div></header><div class="sub-timeline"><section class="sub-msg user"><div class="sub-head"><span class="sub-role">Prompt</span><time class="sub-when">11:14</time></div><div class="markdown-msg"><p>Audit the current mini-app against the installed Obelisk app. Preserve the real information hierarchy and interaction semantics.</p><h2>What to verify</h2><ul><li>Traverse every route and representative state.</li><li>Match visible copy, punctuation, icons, and typography.</li><li>Keep the result in <code>obelisk-ui-mini.html</code>.</li></ul></div></section><section class="sub-msg"><div class="sub-head"><span class="sub-role">Assistant</span><time class="sub-when">11:14</time></div><div class="markdown-msg"><p>Ill start by reading the real renderer and opening each reachable branch in the installed app.</p></div>${subTool('Read','/app/src/renderer/src/App.vue','{"file_path":"/app/src/renderer/src/App.vue"}','<template>\n <RouterView />\n</template>')}</section><section class="sub-msg"><div class="sub-disclosure"><button class="sub-toggle" onclick="A.subToggle(this)">${subChev}<span class="sub-label">Thinking</span></button><div class="sub-body markdown-msg"><p>The visible route tree includes a child conversation that the miniature does not currently represent.</p></div></div></section><section class="sub-msg"><div class="sub-disclosure sub-meta"><button class="sub-toggle" onclick="A.subToggle(this)">${subChev}<span class="sub-label">System</span><span class="sub-preview">Keep evidence separate from inferred presentation state.</span></button><div class="sub-body markdown-msg"><p>Keep evidence separate from inferred presentation state. Use the installed application as the visual source of truth.</p></div></div></section><section class="sub-msg"><div class="sub-head"><span class="sub-role">Assistant</span><time class="sub-when">11:18</time></div><div class="markdown-msg"><p>Now Ill compare the message components and their disclosure states.</p></div>${subTool('Bash','node --check mini-inline.js','{"command":"node --check mini-inline.js"}','Syntax OK',false,true)}</section><section class="sub-msg"><div class="sub-head"><span class="sub-role">Assistant</span><time class="sub-when">11:19</time></div><div class="markdown-msg"><p>The first edit collided with a newer file version, so I re-read before applying it again.</p></div>${subTool('Edit','/app/obelisk-ui-mini.html','{"old_string":"sessionDetail","new_string":"subagentDetail"}','File has been modified since read. Read it again before attempting to write.',true)}</section><section class="sub-msg"><div class="sub-head"><span class="sub-role">Assistant</span><time class="sub-when">11:20</time></div><div class="markdown-msg sub-message-body"><p>The comparison found the missing Subagent route, its three-part breadcrumb, the wider reader, Prompt and Assistant role labels, and simplified tool disclosures…</p></div><button class="truncated-btn" onclick="A.subFull(this)">Message truncated — click to load full text</button></section><section class="sub-msg"><div class="sub-head"><span class="sub-role">Assistant</span><time class="sub-when">11:21</time></div><div class="markdown-msg empty-text">(no text content)</div></section></div></div></div>`}
function memoryDetailV3(){const x=memories.find(x=>x.id===S.detail)||memories[0],relative=x.path.split('/').slice(-3).join('/');return`<div class="memory-reader"><div class="memory-project">${svg('folder')}<strong>${x.project}</strong>${x.archived?'<span class="archived-tag">archived</span>':''}</div><div class="memory-path">${esc(relative)}</div><div class="memory-summary">${esc(x.summary)}</div><div class="memory-meta"><button onclick="A.openSession('s1')">${svg('sessions')}<span>Design the Obelisk session reader</span></button><span>·</span><span>${x.when}</span><span>·</span><span>codex:01…→ codex:01…</span></div><div class="memory-body-head"><span>Body</span><button class="source-toggle-v2 ${S.showSource?'active':''}" onclick="A.toggleSource()">${S.showSource?'Show rendered':'Show source'}</button></div>${S.showSource?`<pre class="memory-source"># Reader state and evidence\n\n## Decision\nReader state is keyed by session and preserves the focused evidence anchor.\n\n## Constraints\n- Expanded messages remain open.\n- Follow the tail only when already live.</pre>`:`<article class="memory-article"><h1>Reader state and evidence</h1><h2>Decision</h2><p>Reader state is keyed by session and survives live reload without losing the visible evidence anchor.</p><p>The reading surface must remain stable. Incoming events can inform the reader, but they must not steal position unless the reader explicitly follows the live tail.</p><h2>Initial implementation slice</h2><ol><li>Capture the focused timeline item and its offset.</li><li>Restore expanded disclosures before restoring scroll.</li><li>Advance only while the viewport is already at the tail.</li></ol><h2>Scope constraints</h2><p>Observed session events remain distinct from inferred presentation state.</p></article>`}<div class="anchor-head"><span>Anchors</span><span>3</span></div><button class="anchor-v2">${fileSvg} session-reader-state.mjs</button><button class="anchor-v2">${fileSvg} session-live-reload.mjs</button><button class="anchor-v2">${fileSvg} session-reader-state.test.mjs</button><div class="memory-actions"><button class="btn" onclick="A.backMemory()">Back <span class="kbd">Esc</span></button><button class="btn ${x.archived?'primary':'danger'}" onclick="A.archive('${x.id}')">${x.archived?'Restore':'Archive'} <span class="kbd">D</span></button></div></div>`}
function activityDate(i){return new Date(2025,6,20+i)}
function activityLevel(i){const col=Math.floor(i/7);if(col<38)return 0;let v=Math.imul(i+1,-1640531527);v^=v>>>16;v=Math.imul(v,-2048144789);v^=v>>>13;v=(v>>>0)%17;return v<10?0:Math.min(4,1+Math.floor((v-10)/2))}
function ordinal(n){const m=n%100;return n+(m>=11&&m<=13?'th':n%10===1?'st':n%10===2?'nd':n%10===3?'rd':'th')}
function activityHeatSvg(){const cells=Array.from({length:371},(_,i)=>{const col=Math.floor(i/7),row=i%7,level=activityLevel(i),d=activityDate(i),selected=S.day===i?' selected':'',tip=`${level?level*24+'M':'0'} tokens on ${d.toLocaleString('en',{month:'long'})} ${ordinal(d.getDate())}, ${d.getFullYear()}`;return`<rect class="heat-cell level-${level}${selected}" x="${col*13}" y="${row*13}" width="11" height="11" rx="2" onclick="A.day(${i})" onmouseenter="A.chartTip(event,'${tip}')" onmousemove="A.chartTip(event,'${tip}')" onmouseleave="A.chartTipOff()"/>`}).join('');const labels=['Jul','Aug','Sep','Oct','Nov','Dec','Jan','Feb','Mar','Apr','May','Jun','Jul'].map((m,i)=>`<text class="heat-month" x="${i*56}" y="105">${m}</text>`).join('');return`<svg viewBox="0 0 709 112" role="img" aria-label="Daily token activity">${cells}${labels}</svg><div class="heat-legend"><span>Less</span><svg width="70" height="11">${[0,1,2,3,4].map((l,i)=>`<rect x="${i*14}" width="11" height="11" rx="2" class="heat-cell level-${l}"/>`).join('')}</svg><span>More</span></div>`}
function activityChartV2(){if(S.activity==='daily')return activityHeatSvg();if(S.activity==='weekly'){const bars=Array.from({length:53},(_,i)=>{const h=3+((i*17)%105),tip=`Week ${i+1}: ${h*3}M tokens`;return`<rect class="weekly-bar" x="${i*13}" y="${120-h}" width="10" height="${h}" rx="2" onmouseenter="A.chartTip(event,'${tip}')" onmousemove="A.chartTip(event,'${tip}')" onmouseleave="A.chartTipOff()"/>`}).join('');return`<svg viewBox="0 0 709 144" aria-label="Weekly token activity">${bars}<text class="heat-month" x="0" y="138">Jul</text><text class="heat-month" x="170" y="138">Oct</text><text class="heat-month" x="340" y="138">Jan</text><text class="heat-month" x="510" y="138">Apr</text><text class="heat-month" x="675" y="138">Jul</text></svg>`}const dots=[[0,140],[90,137],[210,112],[340,86],[420,72],[560,42],[700,8]].map((p,i)=>`<circle class="cumulative-dot" cx="${p[0]}" cy="${p[1]}" r="6" onmouseenter="A.chartTip(event,'${(i+1)*1.6}B cumulative tokens')" onmousemove="A.chartTip(event,'${(i+1)*1.6}B cumulative tokens')" onmouseleave="A.chartTipOff()"/>`).join('');return`<svg viewBox="0 0 700 164" aria-label="Cumulative token activity"><path class="cumulative-area" d="M0 140 C90 137 140 125 210 112 S340 86 420 72 S560 42 700 8 L700 140 L0 140Z"/><path class="cumulative-line" d="M0 140 C90 137 140 125 210 112 S340 86 420 72 S560 42 700 8"/>${dots}<text class="heat-month" x="0" y="158">Jul</text><text class="heat-month" x="175" y="158">Oct</text><text class="heat-month" x="350" y="158">Jan</text><text class="heat-month" x="525" y="158">Apr</text><text class="heat-month" x="680" y="158">Jul</text></svg>`}
const ledgerRows=[['Created 3 new workspaces','workspace',[['Prototype the evidence reader','Codex','quiet-zero','86 msg'],['Benchmark local retrieval','Claude Code','obelisk-bench','113 msg']]],['Started 5 sessions in 3 projects','started',[['Design the Obelisk session reader','Codex','quiet-zero','86 msg'],['Fix memory archive undo behavior','Claude Code','quiet-zero','42 msg'],['Landing page icon direction','Claude Code','obelisk-site','29 msg']]],['Continued 2 sessions','continued',[['Refactor the indexer writer lease','Codex','quiet-zero','67 msg'],['Package the Obelisk skill artifact','Codex','quiet-zero','54 msg']]]];
function activityLedgerV2(){const d=S.day===null?null:activityDate(S.day),eventDate=d?`${d.toLocaleString('en',{month:'short'}).toUpperCase()} ${d.getDate()}`:'';return`<div class="activity-ledger">${ledgerRows.map((g,gi)=>`<article class="ledger-group ${g[1]}"><div class="ledger-node">${g[1]==='workspace'?svg('folder'):g[1]==='started'?svg('sessions'):'↻'}</div><header class="ledger-group-head"><h3>${g[0]}</h3>${d?`<time>${eventDate}</time>`:''}</header><div class="ledger-items">${g[2].map((r,i)=>`<button class="ledger-item ${g[1]}" onclick="A.openSession('s${(i%6)+1}')"><span class="ledger-item-title">${r[0]}</span><span class="ledger-item-meta"><span class="source">${r[1]}</span><span>·</span>${g[1]!=='workspace'?`<span class="project">${r[2]}</span><span>·</span>`:''}<span>${r[3]}</span></span></button>`).join('')}${gi===1?`<button class="ledger-noise ${S.noiseLedger?'expanded':''}" onclick="A.ledgerNoise()"><svg class="chev" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 2.5l3 3.5-3 3.5"/></svg><span>3 hidden, likely test or throwaway runs</span></button>${S.noiseLedger?`<button class="ledger-item noise"><span class="ledger-item-title">Untitled test run</span><span class="ledger-item-meta">Codex · 3 msg</span></button>`:''}`:''}</div></article>`).join('')}</div>`}
function activityV2(){let months;if(S.day!==null){const d=activityDate(S.day),empty=activityLevel(S.day)===0,count=empty?0:5,eventDate=`${d.toLocaleString('en',{month:'short'}).toUpperCase()} ${d.getDate()}`;months=`<section class="activity-month"><div class="activity-month-head"><h2>${d.toLocaleString('en',{month:'long'})} ${d.getFullYear()}</h2><span class="activity-month-rule"></span><span class="activity-month-count">${count} session${count===1?'':'s'}</span></div>${empty?`<div class="activity-empty">No sessions on ${eventDate}.</div>`:activityLedgerV2()}</section>`}else months=Array.from({length:S.months},(_,i)=>`<section class="activity-month"><div class="activity-month-head"><h2>${i?'June':'July'} 2026</h2><span class="activity-month-rule"></span><span class="activity-month-count">${i?18:10} sessions</span></div>${activityLedgerV2()}</section>`).join('');return`<div class="activity-wrap"><div class="activity-wide"><div class="activity-header"><span class="activity-title">Token activity</span><div class="activity-tabs">${['daily','weekly','cumulative'].map(x=>`<button class="activity-tab ${S.activity===x?'active':''}" onclick="A.activity('${x}')">${x[0].toUpperCase()+x.slice(1)}</button>`).join('')}</div></div><div class="activity-stats"><div class="activity-stat"><span class="activity-stat-value">10.35B</span><span class="activity-stat-label">Lifetime tokens</span></div><div class="activity-stat"><span class="activity-stat-value">679.1M</span><span class="activity-stat-label">Peak tokens</span></div><div class="activity-stat"><span class="activity-stat-value">16h 5m 27s</span><span class="activity-stat-label">Longest task</span></div><div class="activity-stat"><span class="activity-stat-value">14d</span><span class="activity-stat-label">Current streak</span></div><div class="activity-stat"><span class="activity-stat-value">47d</span><span class="activity-stat-label">Longest streak</span></div></div><div class="activity-chart">${activityChartV2()}</div>${months}${S.day===null?`<button class="show-more-activity" onclick="A.moreActivity()">Show more activity</button>`:''}<div id="chartTip" class="chart-tooltip"></div></div></div>`}
function recapListV2(){const list=recaps.filter(x=>x.kind===S.recapKind);return`<div class="recap-list-v2"><div class="rl-content"><div class="rl-head"><span class="rl-year">2026</span><span class="rl-count">${list.length} ${list.length===1?'recap':'recaps'}</span></div><div class="rl-timeline">${list.map((x,i)=>{const arch=i?'shipper':'architect',glow=arch==='shipper'?'rgba(244,114,182,.45)':'rgba(167,139,250,.45)',week=x.id.match(/W(\d+)/)?.[1];return`<article class="rl-row" style="--node-glow:${glow}" onclick="A.openRecap('${x.id}')"><div class="rl-node">${recapSeals[arch]}</div><div class="rl-card"><div class="rl-body"><div class="rl-period"><span>${x.kind==='weekly'?'Week '+week:x.period}</span><span class="dot"></span><span>${x.kind==='weekly'?x.period:x.range}</span></div><div class="rl-archetype">${esc(x.title)}</div><div class="rl-claim">${esc(x.claim)}</div><div class="rl-stats"><span>${x.sessions} sessions</span><span>·</span><span>${x.tokens} tokens</span></div></div><span class="rl-arrow"></span></div></article>`}).join('')}</div></div></div>`}
function recapCardV2(x){
const star=`<div class="rc-stars"><span></span><span></span><span></span><span></span><span></span></div>`,week=x.id.match(/W(\d+)/)?.[1],active=x.kind==='weekly'?4:14;
if(S.slide===0)return`<article class="rc-card rc-cover">${star}<div class="rc-eyebrow"><span class="diamond"></span><span>${x.kind==='weekly'?'Week '+week:x.period}</span></div><div class="rc-seal">${S.recapPalette===2?recapSeals.shipper:recapSeals.architect}</div><div class="rc-cover-body"><div class="rc-cover-title">${esc(x.title)}</div><div class="rc-cover-claim">${esc(x.claim)}</div><div class="rc-activity"><div class="rc-activity-bars">${[.15,.22,.84,.3,0,.5,.9].map(v=>`<span class="rc-activity-bar">${v?`<i style="height:${v*100}%"></i>`:''}</span>`).join('')}</div><div class="rc-day-labels"><span>M</span><span>T</span><span>W</span><span>T</span><span>F</span><span>S</span><span>S</span></div></div><div class="rc-footer">${x.sessions} sessions · ${x.messages} messages · ${active} active days</div></div></article>`;
if(S.slide===1)return`<article class="rc-card themed"><div class="rc-eyebrow"><span class="diamond"></span><span>Your thinking path</span><span class="slot">02 · 05</span></div><div class="rc-title">Five turns, one system wider.</div><div class="rc-content"><div class="rc-path">${[['Mon','“Can the watcher stay narrow?”','Yes, but only around real session roots.'],['Tue','“The old database crashes on open.”','The migration chain was missing, not the query.'],['Wed','“Will live messages steal the reader position?”','Only follow when already at the live tail.'],['Thu','“Can the evidence remain inspectable?”','Keep presentation state separate from observed events.'],['Fri','“Can the prototype stay disposable?”','Keep every interaction, remove every dependency.']].map(r=>`<div class="rc-path-item"><div class="rc-day">${r[0]}</div><div class="rc-prompt">${r[1]}</div><div class="rc-outcome">${r[2]}</div></div>`).join('')}</div></div></article>`;
if(S.slide===2)return`<article class="rc-card themed"><div class="rc-eyebrow"><span class="diamond"></span><span>Your vibe this week</span><span class="slot">03 · 05</span></div><div class="rc-title">Builder with doubts, building anyway.</div><div class="rc-content"><div class="rc-section-label">Things you kept saying</div><div class="rc-vibe-list"><div class="rc-vibe-row"><span class="rc-vibe-text">“Keep the current evidence visible.”</span><span class="rc-vibe-meta">×3 · exacting</span></div><div class="rc-vibe-row"><span class="rc-vibe-text">“Dont invent UI that isnt there.”</span><span class="rc-vibe-meta">pragmatist</span></div><div class="rc-vibe-row"><span class="rc-vibe-text">“Use the actual app as the reference.”</span><span class="rc-vibe-meta">questioning</span></div><div class="rc-vibe-row"><span class="rc-vibe-text">“…still comparing at 4 AM”</span><span class="rc-vibe-meta">night owl · 04:00</span></div></div><div class="rc-meter"><div class="rc-meter-track"><div class="rc-meter-fill"></div></div><div class="rc-meter-row"><span>conviction</span><span>quiet resolve</span></div></div><div class="rc-quote">The UI is evidence too.<div style="margin-top:8px;color:var(--muted);font:italic 13px var(--serif)">— the reason you kept comparing</div></div></div></article>`;
if(S.slide===3)return`<article class="rc-card themed"><div class="rc-eyebrow"><span class="diamond"></span><span>Workflows</span><span class="slot">04 · 05</span></div><div class="rc-title">Three focused implementation loops.</div><div class="rc-deck-text">You compared, corrected, and verified without replacing the source of truth.</div><div class="rc-content" style="display:flex;flex-direction:column"><div class="rc-workflow-stat">3 workflows · 9 focused checks</div><div class="rc-workflow-list"><div class="rc-workflow-row"><b>Source inspection</b><span>“Map the real component before changing the mock.”</span></div><div class="rc-workflow-row"><b>Computer Use comparison</b><span>“Verify each visible state in the installed app.”</span></div><div class="rc-workflow-row"><b>Regression pass</b><span>“Walk every branch again after the fix.”</span></div></div><div class="rc-verdict"><small>Verdict —</small><strong>Evidence-led week.</strong></div></div></article>`;
return`<article class="rc-card rc-closing"><div class="rc-eyebrow"><span class="diamond"></span><span>The week, carved.</span><span class="slot">05 · 05</span></div><div class="rc-closing-body"><div class="rc-closing-title">${active} active days</div><div class="rc-closing-stats"><span>7 projects touched</span><span>8 decisions verified</span></div><div class="rc-closing-quote">“Compare it with the actual app.”<small>— most-said phrase</small></div><div class="rc-signoff">See you next week.</div></div></article>`
}
function recapDetailV2(){const x=recaps.find(x=>x.id===S.detail)||recaps[0],labels=['Cover','Path','Vibe','Workflow','Closing'];return`<div class="recap-app-v2 p${S.recapPalette}" tabindex="0"><div class="rc-stage"><div class="rc-deck">${recapCardV2(x)}</div></div><div class="rc-nav"><button class="rc-arrow" ${S.slide===0?'disabled':''} onclick="A.slide(-1)" aria-label="Previous card"></button><div class="rc-dots">${labels.map((l,i)=>`<button class="rc-dot ${S.slide===i?'active':''}" onclick="A.goSlide(${i})"><span class="rc-glyph"></span><span class="rc-label">${l}</span></button>`).join('')}</div><button class="rc-arrow" ${S.slide===4?'disabled':''} onclick="A.slide(1)" aria-label="Next card"></button><div class="rc-actions"><button class="rc-action" title="Copy image" onclick="A.copyRecap()"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="5" y="5" width="9" height="9" rx="1.5"/><path d="M5 11H3.5A1.5 1.5 0 012 9.5v-7A1.5 1.5 0 013.5 1h7A1.5 1.5 0 0112 2.5V5"/></svg></button><button class="rc-action" title="Export PNG" onclick="A.exportRecap()"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 2v8M5 7l3 3 3-3M3 12v1.5a1 1 0 001 1h8a1 1 0 001-1V12"/></svg></button></div></div></div>`}
function settingsV2(){const sourceData=[['claude','Claude Code','Anthropic','/Users/tomiya/.claude','78','3h ago'],['codex','Codex','OpenAI','/Users/tomiya/.codex','248','1h ago']];return`<div class="settings-v2"> <section><div class="section-head"><h2>Data Sources</h2><p>Where Obelisk reads your agent session history.</p></div>${sourceData.map(x=>`<div class="source-card"><div class="source-card-head"><div class="source-mark"><span class="dot ${x[0]}"></span></div><div class="source-info"><div class="source-card-name">${x[1]} <span class="vendor">by ${x[2]}</span></div><div class="source-status"><span class="status-dot-v2"></span><span class="connected">Connected</span><span>·</span><span>last read <strong>${x[5]}</strong></span><span>·</span><span><strong>${x[4]}</strong> sessions</span></div></div></div><div class="path"><input value="${x[3]}" readonly><button class="btn">${svg('folder')}Browse…</button></div></div>`).join('')}</section><section><div class="section-head"><h2>Index location</h2><p>SQLite database where Obelisk caches the unified session index.</p></div><div class="path" style="max-width:480px"><input value="/Users/tomiya/.obelisk/obelisk.sqlite" readonly><button class="btn">Reveal</button></div></section><section><div class="section-head"><h2>Auto-refresh</h2><p>Obelisk re-reads when new session files appear.</p></div><button class="toggle" onclick="A.auto()"><span class="track ${S.auto?'on':''}"></span>Watch data sources for changes</button></section><section><div class="section-head"><h2>Recap</h2><p>Where generated weekly and monthly recap files live.</p></div><div class="setting-row"><div><div class="setting-label">Recap output directory</div><div class="setting-hint">Watched by Obelisk for new <code>recap-*.json</code> files.</div></div><div class="path"><input value="/Users/tomiya/.obelisk/recap"><button class="btn">Browse…</button></div></div></section><section><div class="section-head"><h2>About</h2><p>The kind of details you don't usually need.</p></div><div class="setting-row"><span class="setting-label">Version</span><span class="version-text">Obelisk 0.1.0</span></div><div class="setting-row"><span class="setting-label">Reset</span><div><button id="rebuild" class="btn" onclick="A.rebuild()">Rebuild index</button><div class="reset-hint">Rebuilding re-reads your coding agent session data. It does not delete memories or recaps.</div></div></div></section></div>`}
function content(){return S.page==='sessions'?sessionListV2():S.page==='memory'?memoryList():S.page==='sessionDetail'?sessionDetailV3():S.page==='subagentDetail'?subagentDetailV2():S.page==='memoryDetail'?memoryDetailV3():S.page==='activity'?activityV2():S.page==='recap'?recapListV2():S.page==='recapDetail'?recapDetailV2():settingsV2()}
function render(){document.title=`Obelisk — ${scope()}`;$('#app').innerHTML=`<div class="app"><div class="titlebar"><div class="traffic" aria-hidden="true"><span class="red"></span><span class="yellow"></span><span class="green"></span></div><b>Obelisk</b><i>—</i><span>${esc(scope())}</span></div><div class="columns">${sidebarV2()}<main class="main">${toolbarV2()}<div id="content">${content()}</div></main></div></div>`;if(S.page==='sessionDetail')$('#content').onscroll=()=>A.readerScroll($('#content'))}
function renderContent(){document.title=`Obelisk — ${scope()}`;$('#content').innerHTML=content()}
function renderToolbar(){document.querySelector('.toolbar').outerHTML=toolbarV2()}
const A=window.A={nav(p){S.page=p;S.detail=null;S.q='';S.sourceMenu=false;S.health=false;S.selected.clear();render()},memoryView(v){S.memoryView=v;S.page='memory';S.project='all';S.detail=null;S.q='';S.selected.clear();render()},project(p){S.project=p;S.detail=null;S.page=S.page.startsWith('memory')?'memory':'sessions';render()},projectSearch(q){S.projectQ=q.toLowerCase();document.querySelector('.project-list').innerHTML=projects.filter(x=>!S.projectQ||x[0].includes(S.projectQ)).map(p=>`<button class="side-item ${S.project===p[0]?'active':''}" onclick="A.project('${p[0]}')">${svg('folder')}<span class="label">${p[0]}</span><span class="badge">${p[1]}</span></button>`).join('')},health(){S.health=!S.health;render()},search(q){S.q=q;renderContent()},sort(){S.desc=!S.desc;renderToolbar();renderContent()},sourceMenu(){S.sourceMenu=!S.sourceMenu;renderToolbar()},source(x){S.source=x;S.sourceMenu=false;renderToolbar();renderContent()},openSession(id){S.page='sessionDetail';S.detail=id;S.q='';render()},openMemory(e,id){if(e.metaKey||e.ctrlKey){A.select(id);return}S.page='memoryDetail';S.detail=id;S.showSource=false;render()},select(id){S.selected.has(id)?S.selected.delete(id):S.selected.add(id);renderContent()},archive(id){const x=memories.find(x=>x.id===id);S.undo={id,archived:x.archived};x.archived=!x.archived;S.selected.delete(id);S.page='memory';render();A.toast(x.archived?'Memory archived':'Memory restored','Undo')},undoAction(){if(!S.undo)return;memories.find(x=>x.id===S.undo.id).archived=S.undo.archived;S.undo=null;render();A.toast('Action undone')},toggleSource(){S.showSource=!S.showSource;renderContent()},disclose(el){el.parentElement.classList.toggle('open');el.textContent=(el.parentElement.classList.contains('open')?'▾':'▸')+el.textContent.slice(1)},activity(x){S.activity=x;renderContent()},day(i){S.day=S.day===i?null:i;renderContent()},recapKind(k){S.recapKind=k;renderToolbar();renderContent()},openRecap(id){S.page='recapDetail';S.detail=id;S.slide=0;render()},slide(d){S.slide=Math.max(0,Math.min(2,S.slide+d));renderContent()},generate(){const cmd=S.recapKind==='weekly'?'obelisk recap --week current':'obelisk recap --month current';$('#modalHost').innerHTML=`<dialog><div class="modal-head">◇ Generate a new recap<button aria-label="Close" onclick="this.closest('dialog').close()">×</button></div><div class="modal-body"><p style="color:var(--fg2)">Choose the window, then run the command in your terminal.</p><div class="modal-options"><button class="modal-option active">Current ${S.recapKind==='weekly'?'week':'month'}</button><button class="modal-option">Previous</button></div><div class="command"><b>$</b><span>${cmd}</span><button onclick="A.copy(this)">Copy</button></div><div class="modal-hint">Generation takes about 30s. New recaps appear automatically.</div></div></dialog>`;$('#modalHost dialog').showModal()},copy(el){el.textContent='Copied';A.toast('Command copied')},auto(){S.auto=!S.auto;renderContent();A.toast(S.auto?'Auto-refresh enabled':'Auto-refresh paused')},rebuild(){const b=$('#rebuild');b.disabled=true;b.textContent='Rebuilding…';setTimeout(()=>{b.disabled=false;b.textContent='Rebuild index';A.toast('Index rebuilt from 131 sessions')},1100)},toast(msg,action){clearTimeout(S.toastTimer);$('#toast').innerHTML=`<div class="toast">${msg}${action?`<button onclick="A.undoAction()">${action}</button>`:''}</div>`;S.toastTimer=setTimeout(()=>$('#toast').innerHTML='',3200)}};
A.dropUndo=()=>{clearInterval(S.undoTimer);S.undo=null;$('#toast').innerHTML=''};
A.nav=p=>{const prior=S.page;A.dropUndo();if(prior==='sessions'&&p!=='sessions')S.debugEmpty=false;if(p==='sessions'&&prior!=='sessions'){S.debugEmpty=false;S.quietShown=false}if(p==='activity'&&prior!=='activity'){S.activity='daily';S.day=null;S.months=1;S.noiseLedger=false}if(p==='recap'){S.recapKind='weekly';S.generateWindow='this-week'}S.page=p;S.detail=null;S.q='';S.sourceMenu=false;S.selected.clear();S.cursor=null;render()};
A.memoryView=v=>{S.memoryView=v;S.page='memory';S.project='all';S.detail=null;S.q='';S.sourceMenu=false;S.selected.clear();S.cursor=null;render()};
A.project=p=>{S.project=p;S.detail=null;S.page=S.page.startsWith('memory')?'memory':'sessions';S.selected.clear();S.cursor=null;render()};
A.backSessions=()=>{S.page='sessions';S.detail=null;S.debugEmpty=false;S.quietShown=false;render()};
A.backSessionDetail=()=>{S.page='sessionDetail';S.detail=S.parentSession||'s1';render();requestAnimationFrame(()=>{$('#content').scrollTop=S.parentScroll||0})};
A.backMemory=()=>{S.page='memory';S.detail=null;render()};
A.backRecap=()=>{S.page='recap';S.detail=null;S.recapKind='weekly';S.generateWindow='this-week';render()};
A.sourceSettings=()=>{A.dropUndo();S.page='settings';S.detail=null;render()};
A.manageSettings=()=>{S.health=false;A.sourceSettings()};
A.openSession=id=>{A.dropUndo();S.debugEmpty=false;S.quietShown=false;S.page='sessionDetail';S.detail=id;S.msgPos=1;S.traceOpen={};S.rawTools={};render()};
A.openSubagent=id=>{S.parentSession=S.detail;S.parentScroll=$('#content')?.scrollTop||0;S.page='subagentDetail';S.detail=id;render()};
A.subToggle=el=>el.closest('.sub-disclosure')?.classList.toggle('open');
A.subFull=el=>{const body=el.parentElement.querySelector('.sub-message-body');if(body)body.innerHTML='<p>The comparison found the missing Subagent route, its three-part breadcrumb, the wider reader, Prompt and Assistant role labels, simplified tool disclosures, explicit error styling, and full-text loading behavior.</p><p>The parent breadcrumb returns to the same Session Detail reading position, preserving the users evidence context.</p>';el.remove()};
A.openMemory=(e,id)=>{if(e.metaKey||e.ctrlKey||e.shiftKey){A.select(id,e.shiftKey);return}S.cursor=id;S.page='memoryDetail';S.detail=id;S.showSource=false;render()};
A.select=(id,range=false)=>{const rows=visibleMemoryRows(),before=S.cursor;if(range&&before){const ids=rows.map(x=>x.id),a=ids.indexOf(before),b=ids.indexOf(id);if(a>=0&&b>=0)for(let i=Math.min(a,b);i<=Math.max(a,b);i++)S.selected.add(ids[i])}else S.selected.has(id)?S.selected.delete(id):S.selected.add(id);S.cursor=id;renderContent()};
A.moveMemory=(direction,extend=false)=>{const rows=visibleMemoryRows();if(!rows.length)return;const before=S.cursor,i=rows.findIndex(x=>x.id===before),next=i<0?0:Math.max(0,Math.min(rows.length-1,i+direction));if(extend&&before){S.selected.add(before);S.selected.add(rows[next].id)}S.cursor=rows[next].id;renderContent();requestAnimationFrame(()=>document.querySelector(`.mrow[data-id="${S.cursor}"]`)?.scrollIntoView({block:'nearest'}))};
A.openCursor=()=>{if(!S.cursor)return;S.page='memoryDetail';S.detail=S.cursor;S.showSource=false;render()};
A.disclose=el=>{const row=el.closest('.trace-row');if(!row)return;row.classList.toggle('open');S.traceOpen[row.dataset.trace]=row.classList.contains('open')};
A.rawTool=(e,el)=>{e.stopPropagation();const row=el.closest('.msg-tool'),key=row?.dataset.trace;if(!row||!key)return;row.classList.toggle('raw');el.classList.toggle('active');S.rawTools[key]=row.classList.contains('raw')};
A.fileExpand=el=>{const body=el.previousElementSibling;body.classList.toggle('collapsed');el.textContent=body.classList.contains('collapsed')?`Show all ${body.querySelector('.gutter').textContent.split('\n').length} lines`:'Collapse'};
A.msgNav=target=>{const total=9;S.msgPos=target==='first'?1:target==='last'?total:target==='prev'?Math.max(1,S.msgPos-1):Math.min(total,S.msgPos+1);renderContent();requestAnimationFrame(()=>document.querySelector(`[data-pos="${S.msgPos}"]`)?.scrollIntoView({behavior:'smooth',block:'center'}))};
A.readerScroll=el=>{cancelAnimationFrame(S.readerFrame);S.readerFrame=requestAnimationFrame(()=>{const rows=[...el.querySelectorAll('[data-pos]')],center=el.scrollTop+el.clientHeight/2;if(!rows.length)return;const row=rows.reduce((best,n)=>Math.abs(n.offsetTop+n.offsetHeight/2-center)<Math.abs(best.offsetTop+best.offsetHeight/2-center)?n:best);S.msgPos=+row.dataset.pos;const nav=el.querySelector('.msg-nav'),pos=nav?.querySelector('.msg-pos'),buttons=nav?.querySelectorAll('button');if(pos)pos.textContent=`${S.msgPos} / 9`;if(buttons?.length===4){buttons[0].disabled=buttons[1].disabled=S.msgPos===1;buttons[2].disabled=buttons[3].disabled=S.msgPos===9}})};
A.slide=d=>{S.slide=Math.max(0,Math.min(4,S.slide+d));renderContent()};
A.goSlide=i=>{S.slide=Math.max(0,Math.min(4,i));renderContent()};
A.openRecap=id=>{S.page='recapDetail';S.detail=id;S.slide=0;S.recapPalette=0;render()};
A.health=()=>{const y=$('#content')?.scrollTop||0;S.health=!S.health;render();requestAnimationFrame(()=>{if($('#content'))$('#content').scrollTop=y})};
A.sourceSettings=()=>{A.dropUndo();const same=S.page==='settings',y=same?$('#content')?.scrollTop||0:0;S.health=false;S.page='settings';S.detail=null;render();if(same)requestAnimationFrame(()=>{if($('#content'))$('#content').scrollTop=y})};
A.manageSettings=A.sourceSettings;
A.browse=()=>{const i=document.createElement('input');i.type='file';i.webkitdirectory=true;i.click()};
A.cardBlob=async()=>{const n=$('.rc-card'),r=n.getBoundingClientRect(),css=[...document.styleSheets].flatMap(s=>{try{return[...s.cssRules].map(x=>x.cssText)}catch{return[]}}).join(''),html=`<div xmlns="http://www.w3.org/1999/xhtml" style="width:${r.width}px;height:${r.height}px;background:#0a0b14"><style>${css}</style>${n.outerHTML}</div>`,svg=`<svg xmlns="http://www.w3.org/2000/svg" width="${r.width}" height="${r.height}"><foreignObject width="100%" height="100%">${html}</foreignObject></svg>`,u=URL.createObjectURL(new Blob([svg],{type:'image/svg+xml'})),im=new Image;await new Promise((ok,no)=>{im.onload=ok;im.onerror=no;im.src=u});const c=document.createElement('canvas');c.width=r.width*2;c.height=r.height*2;c.getContext('2d').drawImage(im,0,0,c.width,c.height);URL.revokeObjectURL(u);return await new Promise(ok=>c.toBlob(ok,'image/png'))};
A.fallbackCardBlob=async()=>{const n=$('.rc-card'),c=document.createElement('canvas'),g=c.getContext('2d'),lines=n.innerText.split('\n').filter(Boolean);c.width=1080;c.height=1080;g.fillStyle='#0a0b14';g.fillRect(0,0,c.width,c.height);g.strokeStyle='#312e45';g.lineWidth=2;g.strokeRect(36,36,1008,1008);g.fillStyle='#f2effa';g.font='52px Georgia';lines.slice(0,2).forEach((s,i)=>g.fillText(s.slice(0,34),76,150+i*72));g.fillStyle='#aaa3b8';g.font='25px monospace';lines.slice(2,14).forEach((s,i)=>g.fillText(s.slice(0,62),76,340+i*48));return await new Promise(ok=>c.toBlob(ok,'image/png'))};
A.safeCardBlob=async()=>{try{return await A.cardBlob()}catch{return await A.fallbackCardBlob()}};
A.copyRecap=async()=>{try{const b=await A.safeCardBlob();await navigator.clipboard.write([new ClipboardItem({'image/png':b})])}catch{}};
A.exportRecap=async()=>{try{const b=await A.safeCardBlob(),name=`obelisk-recap-${S.slide+1}.png`;if(window.showSaveFilePicker){const h=await showSaveFilePicker({suggestedName:name,types:[{description:'PNG image',accept:{'image/png':['.png']}}]}),w=await h.createWritable();await w.write(b);await w.close()}else{const a=document.createElement('a'),u=URL.createObjectURL(b);a.href=u;a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(u),1000)}}catch{}};
A.moreActivity=()=>{S.months++;renderContent()};
A.ledgerNoise=()=>{S.noiseLedger=!S.noiseLedger;renderContent()};
A.day=i=>{S.day=i;renderContent()};
A.chartTip=(e,text)=>{const t=$('#chartTip');if(!t)return;t.textContent=text;t.style.left=e.clientX+12+'px';t.style.top=e.clientY+12+'px';t.classList.add('show')};
A.chartTipOff=()=>$('#chartTip')?.classList.remove('show');
A.generate=()=>{const opts=[['this-week','This week'],['last-week','Last week'],['this-month','This month'],['last-month','Last month']];const commands={'this-week':'/obelisk recap this week','last-week':'/obelisk recap last week','this-month':'/obelisk recap this month','last-month':'/obelisk recap last month'};$('#modalHost').innerHTML=`<div class="generate-backdrop" onclick="if(event.target===this)A.closeGenerate()"><div class="generate-modal"><div class="generate-head"><span class="diamond"></span><span class="generate-title">Generate a new recap</span><button class="generate-close" aria-label="Close" onclick="A.closeGenerate()">×</button></div><div class="generate-body"><p class="generate-copy">Recaps are generated by Claude Code. Run the command below in your terminal — Obelisk will pick it up automatically.</p><div class="generate-options">${opts.map(o=>`<button class="generate-option ${S.generateWindow===o[0]?'active':''}" onclick="A.generateSelect('${o[0]}')"><span class="generate-radio"></span><span>${o[1]}</span></button>`).join('')}</div><div class="generate-command"><span class="prompt">$</span> <span id="generateCmd">${commands[S.generateWindow]}</span><button id="copyCmd" aria-label="Copy command" onclick="A.copyCmd(this)">▣</button></div><div class="generate-hint">Generation takes ~30s. New recaps appear in this list automatically.</div></div></div></div>`};
A.closeGenerate=()=>{$('#modalHost').innerHTML=''};
A.generateSelect=key=>{S.generateWindow=key;A.generate()};
A.copyCmd=el=>{el.classList.add('copied');el.textContent='✓';setTimeout(()=>{if(el.isConnected){el.classList.remove('copied');el.textContent='▣'}},1600)};
A.undoToast=()=>{clearInterval(S.undoTimer);const paint=()=>{if(!S.undo)return;const n=S.undo.entries.length,noun=n===1?'memory':'memories';$('#toast').innerHTML=`<div class="toast" onclick="A.undoAction()">${S.undo.action==='archive'?'Archived':'Restored'} ${n} ${noun}.<button>Undo (${S.undo.countdown}s)</button></div>`};paint();S.undoTimer=setInterval(()=>{if(!S.undo)return clearInterval(S.undoTimer);S.undo.countdown--;if(S.undo.countdown<=0){clearInterval(S.undoTimer);S.undo=null;$('#toast').innerHTML=''}else paint()},1000)};
A.archive=ids=>{const targets=(Array.isArray(ids)?ids:[ids]).map(id=>memories.find(x=>x.id===id)).filter(Boolean);if(!targets.length)return;const action=targets[0].archived?'restore':'archive';S.undo={action,countdown:5,entries:targets.map(x=>({id:x.id,archived:x.archived}))};targets.forEach(x=>x.archived=action==='archive');S.selected.clear();if(targets.some(x=>x.id===S.cursor))S.cursor=null;S.page='memory';S.detail=null;const rows=visibleMemoryRows();if(rows.length)S.cursor=rows[0].id;render();A.undoToast()};
A.undoAction=()=>{if(!S.undo)return;S.undo.entries.forEach(v=>{const x=memories.find(m=>m.id===v.id);if(x)x.archived=v.archived});clearInterval(S.undoTimer);S.undo=null;$('#toast').innerHTML='';render()};
A.auto=()=>{S.auto=!S.auto;renderContent()};
A.rebuild=()=>{const b=$('#rebuild');if(!b||b.disabled)return;b.disabled=true;b.textContent='Rebuilding…';setTimeout(()=>{const next=$('#rebuild');if(next){next.disabled=false;next.textContent='Rebuild index'}},1100)};
A.noise=()=>{S.showNoise=!S.showNoise;render()};
A.quiet=()=>{S.quietShown=!S.quietShown;renderContent()};
A.projectSearchV2=q=>{S.projectQ=q.toLowerCase();const list=document.querySelector('.project-list');if(list)list.innerHTML=projectButtonsV2()+(S.page.startsWith('session')&&!S.showNoise?`<button class="noise-fold" onclick="A.noise()"><span></span><span class="label">${noiseProjects.length} test projects hidden</span><span class="badge">${noiseProjects.length}</span></button>`:'')};
document.addEventListener('click',e=>{const b=e.target.closest('.settings-v2 .btn');if(b?.textContent.trim().startsWith('Browse'))A.browse()});
document.addEventListener('keydown',e=>{if(S.page==='recapDetail'&&!/INPUT|TEXTAREA/.test(e.target.tagName)&&e.key.toLowerCase()==='p'){e.preventDefault();e.stopImmediatePropagation();S.recapPalette=(S.recapPalette+1)%7;renderContent()}});
document.addEventListener('keydown',e=>{const input=/INPUT|TEXTAREA/.test(e.target.tagName)||e.target.isContentEditable,key=e.key.length===1?e.key.toLowerCase():e.key,mod=e.metaKey||e.ctrlKey;if(mod&&['1','2','3'].includes(key)){e.preventDefault();key==='1'?A.nav('sessions'):A.memoryView(key==='2'?'active':'archived');return}if(S.page==='sessionDetail'&&mod){const sizes=[12,13,14,15,16,18],i=sizes.indexOf(S.fontSize);if(['=','+'].includes(key)){e.preventDefault();S.fontSize=sizes[Math.min(sizes.length-1,i+1)];renderContent();return}if(key==='-'){e.preventDefault();S.fontSize=sizes[Math.max(0,i-1)];renderContent();return}if(key==='0'){e.preventDefault();S.fontSize=14;renderContent();return}}if(mod&&key==='z'&&S.undo&&['memory','memoryDetail'].includes(S.page)){e.preventDefault();A.undoAction();return}if(S.page==='recapDetail'&&!input){if(['ArrowLeft','PageUp'].includes(key)){e.preventDefault();A.slide(-1);return}if(['ArrowRight','PageDown',' '].includes(key)){e.preventDefault();A.slide(1);return}if(key==='Home'){e.preventDefault();A.goSlide(0);return}if(key==='End'){e.preventDefault();A.goSlide(4);return}}if(input){if(key==='Escape')e.target.blur();return}if(S.page==='sessions'&&key==='m'){S.debugEmpty=!S.debugEmpty;renderContent();return}if(S.page==='memoryDetail'){if(key==='Escape'){e.preventDefault();A.backMemory();return}if(key==='d'){e.preventDefault();A.archive(S.detail);return}}if(S.page==='memory'){if(key==='j'||key==='ArrowDown'){e.preventDefault();A.moveMemory(1,e.shiftKey);return}if(key==='k'||key==='ArrowUp'){e.preventDefault();A.moveMemory(-1,e.shiftKey);return}if(key==='Enter'&&S.cursor){e.preventDefault();A.openCursor();return}if(key==='x'&&S.cursor){e.preventDefault();A.select(S.cursor);return}if(key==='d'){e.preventDefault();A.archive(S.selected.size?[...S.selected]:(S.cursor?[S.cursor]:[]));return}if(key==='u'&&S.undo){e.preventDefault();A.undoAction();return}}if(key==='/'&&['sessions','memory'].includes(S.page)){e.preventDefault();$('#search')?.focus();return}if(key==='s'&&['sessions','memory'].includes(S.page)){e.preventDefault();A.sort();return}if(key==='Escape'&&['sessions','memory'].includes(S.page)){if(S.selected.size){S.selected.clear();renderContent()}else if(S.q){S.q='';renderToolbar();renderContent()}}});
render();
</script>
</body>
</html>