<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Areev</title>
<style>
:root {
color-scheme: light;
--bg:#F6F7FB; --surface:#FFFFFF; --raise:#F1F3F8; --well:#F7F8FC;
--line:#E3E6ED; --line2:#D5DAE6; --hair:#EDEFF4;
--ink:#0B0D12; --text:#3E4657; --muted:#5A6072; --dim:#6E7686; --faint:#8B92A3; --ghost:#A8AEBC;
--accent:#1F3BE0; --accent-hover:#1730BC; --accent-soft:#E8ECFF; --accent-line:#C2CCEE;
--ok:#0F8A5F; --ok-soft:#E9F6F0; --ok-line:#CBE8DA;
--warn:#C2410C; --warn-soft:#FFF3EC; --warn-line:#FBDCC8;
--danger:#C81E1E; --danger-soft:#FDECEC; --danger-line:#F6CDCD;
--hash:#0E8F82;
--cat-person:#1F3BE0; --cat-org:#0E8F82; --cat-project:#7A3BE0;
--cat-thing:#B45309; --cat-note:#F2542D;
--sel:rgba(31,59,224,.20);
--shadow:0 10px 30px rgba(11,13,18,.10);
--card-shadow:0 1px 2px rgba(11,13,18,.04), 0 2px 6px rgba(11,13,18,.05);
--sans:'Inter',-apple-system,system-ui,'Segoe UI',Roboto,sans-serif;
--mono:ui-monospace,'SF Mono',SFMono-Regular,Menlo,Consolas,monospace;
--col:880px; --wide:1500px;
}
@media (min-width:1500px) { :root { --col:1000px; --wide:1700px; } }
@media (min-width:1800px) { :root { --col:1100px; --wide:1900px; } }
@media (min-width:2200px) { :root { --col:1200px; --wide:2100px; } }
@media (prefers-color-scheme: dark) { :root { color-scheme: dark;
--bg:#0B0E14; --surface:#111621; --raise:#171D2A; --well:#0D121B;
--line:#222A38; --line2:#333C4D; --hair:#1C2431;
--ink:#F2F5FA; --text:#C9D1DE; --muted:#9AA4B7; --dim:#8B95A8; --faint:#79839A; --ghost:#5F6980;
--accent:#7C93F5; --accent-hover:#95A9F8; --accent-soft:#1A2340; --accent-line:#39477A;
--ok:#3DD68C; --ok-soft:#12251C; --ok-line:#255140;
--warn:#F59E5B; --warn-soft:#2A1A11; --warn-line:#5A3A22;
--danger:#F87171; --danger-soft:#2B1517; --danger-line:#5E2B2E;
--hash:#3FCFC0;
--cat-person:#7C93F5; --cat-org:#3FCFC0; --cat-project:#B39BFF;
--cat-thing:#E0A458; --cat-note:#FF8A66;
--sel:rgba(124,147,245,.32);
--shadow:0 10px 30px rgba(0,0,0,.45);
--card-shadow:0 1px 2px rgba(0,0,0,.30);
} }
:root[data-theme="light"] { color-scheme: light;
--bg:#F6F7FB; --surface:#FFFFFF; --raise:#F1F3F8; --well:#F7F8FC;
--line:#E3E6ED; --line2:#D5DAE6; --hair:#EDEFF4;
--ink:#0B0D12; --text:#3E4657; --muted:#5A6072; --dim:#6E7686; --faint:#8B92A3; --ghost:#A8AEBC;
--accent:#1F3BE0; --accent-hover:#1730BC; --accent-soft:#E8ECFF; --accent-line:#C2CCEE;
--ok:#0F8A5F; --ok-soft:#E9F6F0; --ok-line:#CBE8DA;
--warn:#C2410C; --warn-soft:#FFF3EC; --warn-line:#FBDCC8;
--danger:#C81E1E; --danger-soft:#FDECEC; --danger-line:#F6CDCD;
--hash:#0E8F82;
--cat-person:#1F3BE0; --cat-org:#0E8F82; --cat-project:#7A3BE0;
--cat-thing:#B45309; --cat-note:#F2542D;
--sel:rgba(31,59,224,.20);
--shadow:0 10px 30px rgba(11,13,18,.10);
--card-shadow:0 1px 2px rgba(11,13,18,.04), 0 2px 6px rgba(11,13,18,.05);
}
:root[data-theme="dark"] { color-scheme: dark;
--bg:#0B0E14; --surface:#111621; --raise:#171D2A; --well:#0D121B;
--line:#222A38; --line2:#333C4D; --hair:#1C2431;
--ink:#F2F5FA; --text:#C9D1DE; --muted:#9AA4B7; --dim:#8B95A8; --faint:#79839A; --ghost:#5F6980;
--accent:#7C93F5; --accent-hover:#95A9F8; --accent-soft:#1A2340; --accent-line:#39477A;
--ok:#3DD68C; --ok-soft:#12251C; --ok-line:#255140;
--warn:#F59E5B; --warn-soft:#2A1A11; --warn-line:#5A3A22;
--danger:#F87171; --danger-soft:#2B1517; --danger-line:#5E2B2E;
--hash:#3FCFC0;
--cat-person:#7C93F5; --cat-org:#3FCFC0; --cat-project:#B39BFF;
--cat-thing:#E0A458; --cat-note:#FF8A66;
--sel:rgba(124,147,245,.32);
--shadow:0 10px 30px rgba(0,0,0,.45);
--card-shadow:0 1px 2px rgba(0,0,0,.30);
}
* { box-sizing:border-box; margin:0; }
html, body { height:100%; }
body { background:var(--surface); color:var(--text); font:15px/1.55 var(--sans); -webkit-font-smoothing:antialiased; }
a { color:var(--accent); text-decoration:none; }
::-webkit-scrollbar { width:10px; height:10px; }
::-webkit-scrollbar-thumb { background:var(--line2); border-radius:8px; border:3px solid transparent; background-clip:content-box; }
:focus-visible { outline:2px solid var(--accent); outline-offset:2px; border-radius:4px; }
@media (prefers-reduced-motion: reduce) { * { transition:none !important; animation:none !important; } }
.mrow .say, .mrow .prov, .srow .t, .evi-row .t, .ev .say, .ans-text,
.kv .v, #ginspect .meta { min-width:0; overflow-wrap:anywhere; }
.mrow, .srow, .evi-row, .ev, .acts { min-width:0; }
.dev-only { display:none !important; }
html.dev .dev-only { display:block !important; }
html.dev .dev-flex { display:flex !important; }
#shell { display:flex; min-height:100vh; align-items:stretch; }
#rail { width:260px; flex-shrink:0; background:var(--bg); border-right:1px solid var(--line);
padding:26px 18px 20px 22px; display:flex; flex-direction:column; gap:26px; position:sticky; top:0; height:100vh; }
#main { flex:1; min-width:0; display:flex; flex-direction:column; }
.page { padding:44px 56px 56px 48px; display:flex; flex-direction:column; gap:24px; }
.col { width:100%; max-width:var(--col); margin-left:auto; margin-right:auto; }
.wide { width:100%; max-width:var(--wide); margin-left:auto; margin-right:auto; }
.composer { background:var(--surface); border:1px solid var(--line2); border-radius:12px;
box-shadow:var(--card-shadow); }
.qedit { position:relative; }
#qhl, #qtext { font-family:var(--mono); font-size:13px; line-height:21px;
padding:16px 18px; white-space:pre-wrap; overflow-wrap:break-word;
border:0; margin:0; }
#qhl { position:absolute; inset:0; pointer-events:none; overflow:hidden;
color:var(--text); border-radius:12px 12px 0 0; }
#qtext { position:relative; width:100%; display:block; resize:none; overflow-y:auto;
background:none; color:transparent; caret-color:var(--ink);
border-radius:12px 12px 0 0; min-height:88px; max-height:340px; }
#qtext::selection { background:var(--sel); }
#qtext:focus { outline:none; }
#qtext::placeholder { color:var(--faint); }
#qhl .k { color:var(--ink); font-weight:600; }
#qhl .s { color:var(--hash); }
#qhl .n { color:var(--warn); }
#qhl .c { color:var(--faint); }
.composer .cbar { display:flex; align-items:center; gap:9px; flex-wrap:wrap;
padding:11px 14px; border-top:1px solid var(--line);
background:var(--well); border-radius:0 0 12px 12px; }
.composer .kbd { font-size:11.5px; font-weight:500; opacity:.75; }
.onramp { display:flex; flex-direction:column; gap:12px; }
.qsec { display:flex; align-items:center; gap:11px; padding-top:10px; }
.qsec i { flex:1; height:1px; background:var(--line); }
.starters { display:grid; grid-template-columns:repeat(auto-fit, minmax(340px, 1fr)); gap:12px; }
.starter { text-align:left; background:var(--surface); border:1px solid var(--line);
border-radius:11px; padding:15px 16px; display:flex; flex-direction:column; gap:10px;
cursor:pointer; transition:border-color .12s, box-shadow .12s; }
.starter:hover { border-color:var(--accent-line); box-shadow:var(--card-shadow); }
.starter b { font-size:15px; font-weight:600; color:var(--ink); line-height:21px; }
.starter code { font-family:var(--mono); font-size:11.5px; color:var(--muted); background:var(--bg);
border:0; border-radius:6px; padding:8px 10px; display:block;
white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.vocab { display:grid; grid-template-columns:repeat(auto-fit, minmax(185px, 1fr)); gap:10px; }
.vc { text-align:left; background:var(--surface); border:1px solid var(--line); border-radius:10px;
padding:13px 14px; display:flex; flex-direction:column; gap:5px; cursor:pointer;
transition:border-color .12s; }
.vc:hover { border-color:var(--accent-line); }
.vc .top { display:flex; align-items:baseline; gap:8px; }
.vc .k { font-family:var(--mono); font-size:12.5px; color:var(--ink); flex:1; }
.vc .n { font-size:17px; font-weight:600; color:var(--ink); }
.vc small { font-size:12.5px; line-height:18px; color:var(--muted); }
.recents { display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:12px 26px; }
.rrow { display:flex; flex-direction:column; gap:3px; padding:9px 0; text-align:left;
background:none; border:0; border-bottom:1px solid var(--hair); cursor:pointer; width:100%; }
.rrow:hover .rq, .rrow:hover .rn { color:var(--accent); }
.rrow .rq { font-family:var(--mono); font-size:11.5px; color:var(--text);
white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.rrow .rn { font-size:14px; color:var(--ink); font-weight:500;
white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.rrow .rm { font-size:12.5px; color:var(--faint);
white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.rempty { padding:9px 0; font-size:13px; color:var(--faint); }
.qsum { display:flex; flex-direction:column; gap:3px; padding-bottom:13px; }
.qsum .h { font-size:19px; font-weight:600; color:var(--ink); letter-spacing:-.01em; }
.qsum .h em { font-style:normal; font-weight:400; font-size:15px; color:var(--muted); }
#qbar { display:flex; align-items:center; gap:12px; flex-wrap:wrap;
padding:12px 0; border-bottom:1px solid var(--line); }
#qmore { padding:16px 0; }
#qerr { background:var(--danger-soft); border:1px solid var(--danger-line); border-radius:10px;
padding:12px 14px; font-size:13px; }
#qerr .code { font-family:var(--mono); font-size:11px; color:var(--danger);
border:1px solid var(--danger-line); border-radius:5px; padding:1px 6px; margin-right:8px; }
#qerr pre { margin-top:8px; font-family:var(--mono); font-size:12.5px; color:var(--muted); white-space:pre-wrap; }
#qerr .hint { margin-top:7px; color:var(--warn); }
code { font-family:var(--mono); font-size:.92em; background:var(--raise); border:1px solid var(--line);
border-radius:5px; padding:1px 5px; }
.menuwrap { position:relative; display:inline-block; }
.btn.hasmenu::after { content:''; width:6px; height:6px; flex-shrink:0; margin-left:2px;
border-right:1.7px solid currentColor; border-bottom:1.7px solid currentColor;
transform:translateY(-2px) rotate(45deg); }
.jsonv { line-height:19px; }
.jsonv .jk { color:var(--ink); font-weight:600; }
.jsonv .js { color:var(--hash); }
.jsonv .jn { color:var(--warn); }
.jsonv .jb { color:var(--ok); font-weight:500; }
.jsonv .jz { color:var(--faint); }
.menu { position:absolute; z-index:30; top:calc(100% + 6px); right:0; background:var(--surface);
border:1px solid var(--line2); border-radius:11px; min-width:300px; max-width:460px;
max-height:340px; overflow-y:auto; box-shadow:var(--shadow); padding:5px; }
.menu .mi { padding:8px 11px; border-radius:8px; cursor:pointer; font-size:13px; color:var(--text); }
.menu .mi:hover { background:var(--raise); }
.menu .mi small { display:block; font-family:var(--mono); font-size:11px; color:var(--dim);
white-space:nowrap; overflow:hidden; text-overflow:ellipsis; margin-top:2px; }
.menu .sep { padding:8px 11px 4px; font-size:11px; font-weight:600; letter-spacing:.09em; color:var(--dim); }
#qcards { display:grid; grid-template-columns:repeat(auto-fill, minmax(300px, 1fr));
grid-auto-rows:8px; gap:0 16px; }
.qcard { background:var(--surface); border:1px solid var(--line); border-radius:12px;
padding:14px 16px; display:flex; flex-direction:column; gap:8px;
box-shadow:var(--card-shadow); overflow:hidden; }
.qcard .hd { display:flex; align-items:center; gap:8px; }
.qcard .kind { font-size:11px; font-weight:600; letter-spacing:.06em; text-transform:uppercase; }
.qcard .say { font-size:15px; line-height:22px; color:var(--ink); overflow-wrap:anywhere; }
.qcard .kv { display:flex; gap:8px; font-size:12.5px; padding:3px 0; border-top:1px solid var(--hair); }
.qcard .kv b { flex-shrink:0; min-width:84px; color:var(--dim); font-weight:500; }
.qcard .kv span { color:var(--text); overflow-wrap:anywhere; min-width:0; }
#qtable { width:100%; border-collapse:collapse; font-size:13.5px; }
#qtable th { text-align:left; font-size:11px; font-weight:600; letter-spacing:.06em; color:var(--dim);
text-transform:uppercase; padding:8px 12px 8px 0; border-bottom:1px solid var(--line2); white-space:nowrap; }
#qtable td { padding:9px 12px 9px 0; border-bottom:1px solid var(--hair); vertical-align:top;
max-width:340px; overflow-wrap:anywhere; }
.seg.sm button { height:26px; padding:0 11px; font-size:12.5px; }
#graphView { width:100%; max-width:var(--wide); margin-left:auto; margin-right:auto; }
.brand { display:flex; align-items:center; gap:9px; }
.brand b { font-size:17px; font-weight:700; color:var(--ink); letter-spacing:-.02em; }
.nav { display:flex; flex-direction:column; gap:2px; }
.nav-item { display:flex; align-items:center; gap:11px; padding:9px 11px; border-radius:9px;
cursor:pointer; color:var(--text); font-size:15px; font-weight:500; border:0; background:none;
width:100%; text-align:left; font-family:inherit; transition:background .12s; }
.nav-item:hover { background:var(--raise); }
.nav-item.on { background:var(--accent-soft); color:var(--accent); font-weight:600; }
.nav-item svg { flex-shrink:0; stroke:currentColor; }
.nav-item .grow { flex:1; }
.pill { display:inline-flex; align-items:center; justify-content:center; min-width:20px; height:20px;
padding:0 6px; border-radius:10px; background:var(--warn); color:#fff; font-size:11px; font-weight:600; }
.sec-label { font-size:11.5px; font-weight:600; color:var(--dim); letter-spacing:.09em; }
#entities { display:flex; flex-direction:column; gap:1px; overflow-y:auto; min-height:0; }
.ent { display:flex; align-items:center; gap:10px; padding:7px 11px; border-radius:8px; cursor:pointer;
border:0; background:none; width:100%; text-align:left; font-family:inherit; transition:background .12s; }
.ent:hover { background:var(--raise); }
.ent.on { background:var(--accent-soft); }
.ent .av { display:flex; align-items:center; justify-content:center; width:22px; height:22px; flex-shrink:0;
border-radius:11px; background:var(--raise); color:var(--muted); font-size:10.5px; font-weight:600; }
.ent.on .av { background:var(--accent); color:#fff; }
.ent .nm { flex:1; font-size:14.5px; font-weight:500; color:var(--ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ent .ct { width:22px; flex-shrink:0; text-align:right; font-size:13px; color:var(--faint); font-variant-numeric:tabular-nums; }
.rail-foot { margin-top:auto; display:flex; flex-direction:column; gap:14px; padding:0 11px 2px; }
.health { display:flex; align-items:center; gap:8px; font-size:13px; color:var(--muted); cursor:pointer; }
.dot { width:7px; height:7px; border-radius:4px; background:var(--ghost); flex-shrink:0; }
.toggle-row { display:flex; align-items:center; gap:10px; padding-top:14px; border-top:1px solid var(--line); }
.toggle-row span { flex:1; font-size:13.5px; color:var(--dim); }
.sw { width:34px; height:20px; flex-shrink:0; border-radius:10px; background:var(--line2); padding:2px;
display:flex; align-items:center; cursor:pointer; border:0; transition:background .15s; }
.sw i { width:16px; height:16px; border-radius:8px; background:#fff; display:block; transition:transform .15s; }
.sw.on { background:var(--accent); }
.sw.on i { transform:translateX(14px); }
h1 { font:700 31px/38px var(--sans); color:var(--ink); letter-spacing:-.025em; }
h2 { font:700 15px var(--sans); color:var(--ink); letter-spacing:-.01em; }
.sub { font-size:14.5px; line-height:22px; color:var(--muted); }
.meta { font-size:13px; color:var(--dim); }
.tiny { font-size:12.5px; color:var(--faint); }
.mono { font-family:var(--mono); font-size:11.5px; color:var(--hash); }
button { font-family:inherit; cursor:pointer; }
.btn { display:inline-flex; align-items:center; justify-content:center; gap:7px; height:40px; padding:0 20px;
border-radius:10px; background:var(--accent); color:#fff; border:0; font-size:14.5px; font-weight:600;
transition:background .12s; white-space:nowrap; flex-shrink:0; }
.btn:hover { background:var(--accent-hover); }
.btn.sm { height:34px; padding:0 15px; font-size:13px; border-radius:9px; }
.btn.ghost { background:var(--surface); border:1px solid var(--line2); color:var(--text); font-weight:500; }
.btn.ghost:hover { background:var(--raise); }
.btn.danger { background:none; border:0; color:var(--danger); font-weight:600; padding:0; height:auto; }
.btn:disabled { opacity:.5; cursor:default; }
.chip { padding:6px 13px; border-radius:16px; border:1px solid var(--line); background:var(--surface);
font-size:13.5px; color:var(--text); cursor:pointer; transition:border-color .12s, color .12s; }
.chip:hover { border-color:var(--line2); }
.chip.on { background:var(--accent); border-color:var(--accent); color:#fff; font-weight:600; }
.seg { display:inline-flex; padding:3px; border-radius:10px; background:var(--raise); }
.seg button { height:30px; padding:0 15px; border-radius:8px; border:0; background:none; color:var(--muted);
font-size:13.5px; font-weight:500; }
.seg button.on { background:var(--surface); color:var(--accent); font-weight:600; box-shadow:0 1px 2px rgba(11,13,18,.06); }
.tabs { display:flex; align-items:flex-end; gap:26px; border-bottom:1px solid var(--line); }
.tabs button { padding:0 0 11px; border:0; background:none; font-size:14.5px; font-weight:500; color:var(--muted); }
.tabs button.on { color:var(--accent); font-weight:600; box-shadow:inset 0 -2px 0 var(--accent); }
input[type=text], textarea { font-family:inherit; background:var(--surface); border:1px solid var(--line2);
border-radius:10px; color:var(--ink); padding:9px 12px; font-size:14px; width:100%; }
input[type=text]:focus, textarea:focus { outline:none; border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-soft); }
.askbar { display:flex; align-items:center; gap:13px; height:56px; padding:0 14px 0 18px; background:var(--surface);
border:1px solid var(--line2); border-radius:13px; transition:border-color .12s, box-shadow .12s; }
.askbar:focus-within { border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-soft); }
.askbar input { border:0; background:none; padding:0; font-size:16px; flex:1; }
.askbar input:focus { outline:none; box-shadow:none; }
.askbar input::placeholder { color:var(--faint); }
.examples { display:flex; align-items:center; gap:8px; flex-wrap:wrap; padding:0 2px; }
.examples .tiny { flex-shrink:0; }
.card { background:var(--surface); border:1px solid var(--line); border-radius:14px; box-shadow:var(--card-shadow); }
.banner { display:flex; align-items:center; gap:12px; padding:13px 16px; border-radius:11px;
background:var(--warn-soft); border:1px solid var(--warn-line); }
.banner .grow { flex:1; font-size:14.5px; color:var(--text); }
.banner b { color:var(--ink); font-weight:600; }
.banner .go { flex-shrink:0; font-size:14px; font-weight:600; color:var(--accent); cursor:pointer; }
.empty { padding:34px 0; text-align:center; color:var(--dim); font-size:14px; }
.planv { display:flex; flex-direction:column; padding-top:6px; }
.prow { display:flex; gap:14px; padding:10px 0; border-bottom:1px solid var(--hair); font-size:13.5px; }
.prow b { width:104px; flex-shrink:0; color:var(--dim); font-weight:500; }
.prow span { color:var(--text); font-family:var(--mono); font-size:12.5px; }
.pnote { margin-top:14px; padding:12px 14px; background:var(--warn-soft);
border:1px solid var(--warn-line); border-radius:9px; font-size:13px;
line-height:20px; color:var(--text); }
.nores { display:flex; flex-direction:column; align-items:flex-start; gap:9px;
padding:30px 26px; background:var(--surface); border:1px solid var(--line);
border-radius:12px; }
.nores .t { font-size:17px; font-weight:600; color:var(--ink); }
.nores .d { font-size:14px; line-height:21px; color:var(--muted); max-width:62ch; }
.nores .acts { display:flex; gap:9px; flex-wrap:wrap; padding-top:4px; }
.v-hash { color:var(--hash); font-family:var(--mono); font-size:11.5px; }
.v-time { color:var(--dim); }
.v-yes { color:var(--ok); font-weight:500; }
.v-no { color:var(--warn); font-weight:500; }
.v-num { color:var(--ink); font-weight:500; font-variant-numeric:tabular-nums; }
.grp-head { display:flex; align-items:baseline; gap:10px; padding:22px 0 10px; }
.grp-head:first-child { padding-top:6px; }
.mrow { display:flex; align-items:flex-start; gap:18px; padding:14px 8px 14px 0; border-top:1px solid var(--hair);
cursor:pointer; border-radius:8px; transition:background .1s; }
.mrow:hover { background:var(--raise); }
.mrow.sel { background:var(--accent-soft); }
.mrow .body { flex:1; display:flex; flex-direction:column; gap:4px; min-width:0; }
.mrow .say { font-size:16.5px; line-height:25px; color:var(--ink); }
.mrow .prov { font-size:13px; line-height:20px; color:var(--dim); }
.mrow .when { width:96px; flex-shrink:0; text-align:right; font-size:13px; color:var(--dim); padding-top:4px; }
.mrow.gone .say { color:var(--ghost); text-decoration:line-through; }
.mrow.sup .say { color:var(--muted); }
#answer { display:flex; flex-direction:column; padding:28px 30px 0; }
.ans-label { font-size:11.5px; font-weight:600; color:var(--accent); letter-spacing:.09em; }
.ans-text { font-size:20px; line-height:31px; color:var(--ink); letter-spacing:-.01em; }
.srow { display:flex; align-items:center; gap:14px; padding:11px 0; border-bottom:1px solid var(--hair); cursor:pointer; }
.srow:last-child { border-bottom:0; }
.srow .t { flex:1; font-size:15px; color:var(--ink); }
.srow .w { width:96px; flex-shrink:0; text-align:right; font-size:13px; color:var(--dim); }
.disclose { display:flex; align-items:center; gap:10px; padding:15px 0 18px; border-top:1px solid var(--hair);
cursor:pointer; font-size:14px; color:var(--text); }
.disclose .grow { flex:1; }
.disclose .caret { transition:transform .15s; }
.disclose.open .caret { transform:rotate(90deg); }
pre.code { font-family:var(--mono); font-size:13px; line-height:21px; color:var(--ink); background:var(--well);
border:1px solid var(--hair); border-radius:10px; padding:14px 16px; overflow-x:auto; white-space:pre-wrap;
word-break:break-word; }
#gwrap { background:var(--well); border:1px solid var(--line); border-radius:16px; overflow:hidden; }
#gstage, #qstage { position:relative; }
#gcanvas { display:block; width:100%; height:560px; cursor:grab; }
#qcanvas { display:block; width:100%; height:460px; cursor:grab;
background:var(--well); border:1px solid var(--line); border-radius:14px; }
#gcanvas.grabbing, #qcanvas.grabbing { cursor:grabbing; }
.float { position:absolute; background:var(--surface); border:1px solid var(--line); border-radius:12px; box-shadow:var(--shadow); }
#gtools { top:16px; left:16px; display:flex; align-items:center; gap:10px; background:none; border:0; box-shadow:none; }
#glegend { bottom:16px; left:16px; display:flex; align-items:center; gap:14px; height:34px; padding:0 14px; border-radius:17px; }
#glegend span { display:flex; align-items:center; gap:6px; font-size:12.5px; color:var(--text); white-space:nowrap; }
#gzoom { bottom:16px; right:16px; display:flex; align-items:center; height:34px; padding:0 4px; border-radius:17px; }
#qzoom { bottom:14px; right:14px; display:flex; align-items:center; height:34px; padding:0 4px; border-radius:17px; }
#gzoom button, #qzoom button { width:30px; height:28px; border:0; background:none; color:var(--text); font-size:16px; border-radius:14px; }
#gzoom button.lbl, #qzoom button.lbl { width:auto; padding:0 10px; font-size:12px; font-weight:600; color:var(--accent); }
#ginspect { top:16px; right:16px; width:288px; padding:18px; display:flex; flex-direction:column; gap:14px; }
#gscrub { display:flex; flex-direction:column; gap:11px; padding:16px 20px 18px; background:var(--surface); border-top:1px solid var(--line); }
#gscrub input[type=range] { width:100%; accent-color:var(--accent); }
.scrub-ticks { display:flex; justify-content:space-between; font-size:11.5px; color:var(--faint); }
#sugList, #conBody { display:flex; flex-direction:column; gap:16px; }
.stack { display:flex; flex-direction:column; }
.sug { display:flex; flex-direction:column; gap:16px; padding:24px 28px 22px; }
.tag { display:inline-flex; align-items:center; gap:7px; padding:4px 10px 4px 8px; border-radius:14px; font-size:12px; font-weight:600; }
.evi { display:flex; flex-direction:column; background:var(--well); border:1px solid var(--hair); border-radius:11px; padding:2px 16px; }
.evi-row { display:flex; align-items:center; gap:12px; padding:12px 0; border-bottom:1px solid var(--hair); }
.evi-row:last-child { border-bottom:0; }
.evi-row .t { flex:1; font-size:15px; color:var(--ink); }
.evi-row .w { width:140px; flex-shrink:0; text-align:right; font-size:13px; color:var(--dim); }
.acts { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.acts .note { flex:1; text-align:right; font-size:13.5px; color:var(--dim); }
.reason { display:flex; flex-direction:column; gap:9px; padding:14px 16px;
background:var(--well); border:1px solid var(--line); border-radius:11px; }
.reason .line { display:flex; align-items:center; gap:10px; }
.ev { display:flex; align-items:flex-start; gap:16px; padding:14px 0; border-top:1px solid var(--hair); }
.ev .at { width:52px; flex-shrink:0; font-size:13px; color:var(--dim); padding-top:3px; }
.ev .ic { width:18px; flex-shrink:0; display:flex; justify-content:center; padding-top:5px; }
.ev .body { flex:1; display:flex; flex-direction:column; gap:3px; min-width:0; }
.ev .say { font-size:15.5px; color:var(--ink); }
.apps { display:flex; gap:16px; }
.app { flex:1; display:flex; flex-direction:column; gap:8px; background:var(--well); border:1px solid var(--line);
border-radius:12px; padding:14px 16px; }
.app .hd { display:flex; align-items:center; gap:9px; }
.app .hd b { flex:1; font-size:14.5px; font-weight:600; color:var(--ink); }
.conns { display:flex; }
.conns div { flex:1; display:flex; justify-content:center; }
.conns i { width:2px; height:22px; background:var(--line2); display:block; border-radius:1px; }
.hubbar { display:flex; align-items:center; gap:14px; background:var(--accent-soft); border:1.5px solid var(--accent);
border-radius:12px; padding:16px 18px; }
.kv { display:flex; align-items:baseline; gap:12px; padding:9px 0; border-top:1px solid var(--hair); }
.kv .k { width:190px; flex-shrink:0; font-size:13.5px; color:var(--dim); }
.kv .v { flex:1; font-size:14px; color:var(--ink); word-break:break-word; }
#panelWrap { position:fixed; inset:0; z-index:40; }
#panelShade { position:absolute; inset:0; background:rgba(11,13,18,.35); backdrop-filter:blur(2px); }
#panel { position:absolute; top:0; right:0; bottom:0; width:min(430px,96vw); background:var(--surface);
border-left:1px solid var(--line2); box-shadow:var(--shadow); padding:28px 30px 32px; overflow-y:auto;
display:flex; flex-direction:column; gap:24px; }
.pgroup { display:flex; flex-direction:column; gap:14px; padding-top:20px; border-top:1px solid var(--hair); }
.step { display:flex; gap:14px; }
.step .rail { display:flex; flex-direction:column; align-items:center; width:10px; flex-shrink:0; gap:4px; }
.step .bul { width:10px; height:10px; border-radius:5px; background:var(--ok); flex-shrink:0; }
.step .bul.past { background:var(--surface); border:1.5px solid var(--line2); }
.step .ln { width:1px; flex:1; background:var(--line); }
#devbar { width:330px; flex-shrink:0; background:var(--bg); border-left:1px solid var(--line);
padding:26px 22px 28px; display:flex; flex-direction:column; gap:24px; position:sticky; top:0;
height:100vh; overflow-y:auto; }
.oprow { display:flex; align-items:center; gap:10px; padding:7px 0; border-bottom:1px solid var(--hair);
cursor:pointer; font-family:var(--mono); font-size:11.5px; }
.oprow .t { width:30px; flex-shrink:0; color:var(--ghost); }
.oprow .o { width:70px; flex-shrink:0; }
.oprow .h { flex:1; min-width:0; font-family:var(--sans); font-size:12.5px; color:var(--text);
overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.oprow .h.addr { font-family:var(--mono); font-size:11.5px; color:var(--hash); }
.oprow .h.sys { color:var(--faint); }
#toast { position:fixed; bottom:24px; left:50%; transform:translateX(-50%); background:var(--ink); color:var(--bg);
border-radius:10px; padding:10px 20px; font-size:14px; z-index:99; box-shadow:var(--shadow); display:none; }
@media (max-width:1180px) { #devbar { display:none !important; } }
@media (max-width:900px) { #rail { display:none; } .page { padding:28px 22px 44px; } }
[hidden] { display:none !important; }
</style>
<script>
(function () {
var q = new URLSearchParams(location.search);
var t = q.get('theme') || localStorage.getItem('areev.theme');
if (t === 'light' || t === 'dark') document.documentElement.dataset.theme = t;
var d = q.get('dev');
if (d === '1' || (d !== '0' && localStorage.getItem('areev.dev') === '1'))
document.documentElement.classList.add('dev');
})();
</script>
<div id="shell">
<nav id="rail">
<div class="brand">
<svg width="22" height="22" viewBox="0 0 24 24" aria-hidden="true"><rect x="7.5" y="2.5" width="14" height="14" rx="4.5" fill="none" stroke="var(--accent-line)" stroke-width="2"/><rect x="2.5" y="7.5" width="14" height="14" rx="4.5" fill="var(--accent)"/></svg>
<b>Areev</b>
</div>
<div class="nav" id="nav">
<button class="nav-item on" data-page="memory">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M5 4.5A1.5 1.5 0 016.5 3H17a1.5 1.5 0 011.5 1.5V21l-6.5-3.4L5.5 21V4.5z" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="grow">Memory</span>
</button>
<button class="nav-item" data-page="activity">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M3 12.5h4.2l2.6-7 4.2 13 2.4-6h4.6" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="grow">Activity</span>
</button>
<button class="nav-item" data-page="runs">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M5 4.5v15l13-7.5-13-7.5z" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="grow">Runs</span>
<span class="pill" id="runPill" hidden>0</span>
</button>
<button class="nav-item" data-page="suggestions">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M12 3.5l1.9 4.9 4.9 1.9-4.9 1.9L12 17.1l-1.9-4.9L5.2 10.3l4.9-1.9L12 3.5z" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="grow">Suggestions</span>
<span class="pill" id="sugPill" hidden>0</span>
</button>
<button class="nav-item" data-page="connect">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M9.5 14.5l5-5M8 12l-2 2a3.5 3.5 0 005 5l2-2M16 12l2-2a3.5 3.5 0 00-5-5l-2 2" stroke-width="1.7" stroke-linecap="round"/></svg>
<span class="grow">Connect</span>
</button>
<button class="nav-item" data-page="query">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M4.5 6.5h15M4.5 12h9M4.5 17.5h12" stroke-width="1.7" stroke-linecap="round"/><path d="M16.5 15.5l2.2 2.2" stroke-width="1.7" stroke-linecap="round"/><circle cx="15" cy="14" r="2.6" stroke-width="1.7"/></svg>
<span class="grow">Query</span>
</button>
</div>
<div style="display:flex;flex-direction:column;gap:9px;min-height:0">
<span class="sec-label" style="padding:0 11px">PEOPLE & TOPICS</span>
<div id="entities"><span class="tiny" style="padding:0 11px">loading…</span></div>
</div>
<div class="rail-foot">
<div class="health" id="health" title="check every stored memory against its content address">
<span class="dot" id="hdot"></span><span id="htext">checking…</span>
</div>
<div class="toggle-row" id="whorow" style="display:none">
<span id="whoami" title="the principal this session is bound to">…</span>
<button id="signbtn" style="background:none;border:0;color:var(--accent);cursor:pointer;font:inherit;padding:0">Sign in</button>
</div>
<div class="toggle-row">
<span>Developer mode</span>
<button class="sw" id="devsw" aria-label="developer mode"><i></i></button>
</div>
<div class="toggle-row" style="border:0;padding-top:0">
<span>Dark mode</span>
<button class="sw" id="themesw" aria-label="dark mode"><i></i></button>
</div>
<span class="tiny" id="dblabel" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{DB}}</span>
</div>
</nav>
<main id="main">
<section class="page" id="page-memory">
<div style="display:flex;align-items:flex-end;gap:16px" class="col">
<div style="flex:1;display:flex;flex-direction:column;gap:7px">
<h1 id="memTitle">What your agent remembers</h1>
<span class="sub" id="memSub">loading…</span>
</div>
<div class="seg" id="mview">
<button class="on" data-v="list">List</button>
<button data-v="graph">Graph</button>
</div>
</div>
<div id="listView" style="display:flex;flex-direction:column;gap:24px">
<div class="col" style="display:flex;flex-direction:column;gap:11px">
<div class="askbar">
<svg width="19" height="19" viewBox="0 0 24 24" fill="none" style="flex-shrink:0"><circle cx="11" cy="11" r="6.6" stroke="var(--accent)" stroke-width="1.8"/><path d="M16 16l4.2 4.2" stroke="var(--accent)" stroke-width="1.8" stroke-linecap="round"/></svg>
<input type="text" id="ask" placeholder="Search your memories — or ask a question and press Enter" autocomplete="off">
<button class="btn sm" id="askBtn">Ask</button>
</div>
<div class="examples" id="examples"></div>
</div>
<div class="banner col" id="sugBanner" hidden>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" style="flex-shrink:0"><path d="M12 3.5l1.9 4.9 4.9 1.9-4.9 1.9L12 17.1l-1.9-4.9L5.2 10.3l4.9-1.9L12 3.5z" stroke="var(--warn)" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="grow" id="sugBannerText"></span>
<span class="go" id="sugBannerGo">Review →</span>
</div>
<div class="col" id="filterBar" hidden></div>
<div class="card col" id="answer" hidden></div>
<div class="col" id="memList"><div class="empty">loading…</div></div>
</div>
<div id="graphView" hidden>
<div id="gwrap">
<div id="gstage">
<canvas id="gcanvas"></canvas>
<div class="float" id="gtools">
<div style="display:flex;align-items:center;gap:9px;height:36px;width:250px;padding:0 12px;background:var(--surface);border:1px solid var(--line);border-radius:10px">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" style="flex-shrink:0"><circle cx="11" cy="11" r="6.6" stroke="var(--dim)" stroke-width="1.9"/><path d="M16 16l4.2 4.2" stroke="var(--dim)" stroke-width="1.9" stroke-linecap="round"/></svg>
<input type="text" id="gjump" placeholder="Jump to anyone…" style="border:0;background:none;padding:0;font-size:13.5px">
</div>
<div class="seg" id="gdepth" style="background:var(--surface);border:1px solid var(--line)">
<button class="on" data-d="1">Direct</button><button data-d="2">2 steps</button>
</div>
</div>
<div class="float" id="glegend"></div>
<div class="float" id="gzoom">
<button id="gout" title="zoom out">−</button><button id="gin" title="zoom in">+</button><button class="lbl" id="gfit">Fit</button>
</div>
<div class="float" id="ginspect" hidden></div>
</div>
<div id="gscrub">
<div style="display:flex;align-items:center;gap:9px">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" style="flex-shrink:0"><circle cx="12" cy="12" r="8.5" stroke="var(--muted)" stroke-width="1.8"/><path d="M12 7.5V12l3 2" stroke="var(--muted)" stroke-width="1.8" stroke-linecap="round"/></svg>
<b style="font-size:13.5px;color:var(--ink)">Rewind</b>
<span class="meta" style="flex:1">drag to see what your agent knew at any point — nothing is ever deleted from the history</span>
<span class="chip on" id="gnow" style="height:26px;padding:0 12px;display:inline-flex;align-items:center">now</span>
</div>
<input type="range" id="grange" min="0" max="100" value="100">
<div class="scrub-ticks" id="gticks"></div>
</div>
</div>
</div>
</section>
<section class="page" id="page-activity" hidden>
<div class="col" style="display:flex;flex-direction:column;gap:7px">
<h1>Activity</h1>
<span class="sub">Everything your agent learned, changed, or forgot — newest first.</span>
</div>
<div class="examples col" id="actFilters"></div>
<div class="col" id="actList"><div class="empty">loading…</div></div>
</section>
<section class="page" id="page-suggestions" hidden>
<div class="col" style="display:flex;flex-direction:column;gap:7px">
<h1>Suggestions</h1>
<span class="sub">Ways to make this memory more reliable. Nothing is changed until you approve it, and every change can be undone.</span>
</div>
<div class="col" style="display:flex;align-items:center;gap:16px">
<div class="tabs" id="sugTabs" style="flex:1">
<button class="on" data-s="pending">Needs review</button>
<button data-s="applied">Applied</button>
<button data-s="rejected">Dismissed</button>
</div>
<button class="btn sm" id="runLoop">Run analysis</button>
</div>
<div class="col" id="sugList"><div class="empty">loading…</div></div>
</section>
<section class="page" id="page-runs" hidden>
<div class="col" style="display:flex;flex-direction:column;gap:7px">
<h1>Runs</h1>
<span class="sub">Governed workflow runs: journaled, checkpointed, pausable. A run waiting on a person shows its approval below — approving requires your own sign-in (a shared console token is refused: the approver's identity is the audit record).</span>
</div>
<div class="col" id="runList"><div class="empty">loading…</div></div>
</section>
<section class="page" id="page-connect" hidden>
<div class="col" style="display:flex;flex-direction:column;gap:7px">
<h1>Connect</h1>
<span class="sub">Attach agents to this memory, and keep every app your customers reach you through remembering the same things.</span>
</div>
<div class="tabs col" id="conTabs">
<button class="on" data-c="agent">Your agent</button>
<button data-c="sync">Sync across apps</button>
<button data-c="settings">Settings</button>
</div>
<div class="col" id="conBody"></div>
</section>
<section class="page" id="page-query" hidden>
<div class="col" style="display:flex;align-items:center;gap:16px">
<h1>Query</h1>
<div style="flex:1"></div>
<button class="btn ghost sm" id="qreset" hidden>New query</button>
</div>
<span class="col sub" id="qintro">Ask the memory a precise question. Every view in this console
runs the same language underneath — start from an example below and edit it.</span>
<div class="col composer">
<div class="qedit">
<div id="qhl" aria-hidden="true"></div>
<textarea id="qtext" spellcheck="false" rows="3"
placeholder='RECALL facts WHERE subject = "john" LIMIT 20'></textarea>
</div>
<div class="cbar">
<button class="btn sm" id="qrun">Run <span class="kbd">⌘⏎</span></button>
<button class="btn ghost sm" id="qexplain">Explain</button>
<button class="btn ghost sm" id="qsave">Save</button>
<div class="menuwrap"><button class="btn ghost sm hasmenu" id="fmtBtn">Format</button>
<div class="menu" id="fmtMenu" hidden></div></div>
<div style="flex:1"></div>
<span class="tiny" id="qnotice"></span>
</div>
</div>
<div class="col" id="qerr" hidden></div>
<div class="col onramp" id="qonramp">
<div class="qsec"><span class="sec-label">START FROM A QUESTION</span><i></i>
<span class="tiny">Each one writes real CAL into the editor — edit it from there</span></div>
<div class="starters" id="qstarters"></div>
<div class="qsec"><span class="sec-label">WHAT’S IN THIS MEMORY</span><i></i>
<span class="tiny">Swap any of these for <code>facts</code> in a query</span></div>
<div class="vocab" id="qvocab"></div>
<div class="qsec"><span class="sec-label">SAVED & RECENT</span><i></i>
<div class="menuwrap"><button class="btn ghost sm hasmenu" id="savedBtn">All saved</button>
<div class="menu" id="savedMenu" hidden></div></div>
<div class="menuwrap"><button class="btn ghost sm hasmenu" id="histBtn">Full history</button>
<div class="menu" id="histMenu" hidden></div></div></div>
<div class="recents" id="qrecents"></div>
</div>
<div class="col" id="qresult" hidden>
<div id="qsummary"></div>
<div id="qbar"></div>
<div id="qbody"></div>
<div id="qmore" hidden></div>
</div>
</section>
</main>
<aside id="devbar" class="dev-only dev-flex">
<div style="display:flex;flex-direction:column;gap:11px">
<span class="sec-label">STORE</span>
<div id="devStats"></div>
</div>
<div style="display:flex;flex-direction:column;gap:0">
<span class="sec-label" style="padding-bottom:12px">OP LOG</span>
<div id="devLog"></div>
</div>
<div style="display:flex;flex-direction:column;gap:9px;margin-top:auto">
<span class="sec-label">CAL</span>
<span class="tiny">Every view here runs CAL underneath. The full workbench —
history, saved queries, templates and every output format — lives on the Query page.</span>
<button class="btn ghost sm" id="devQuery">Open Query →</button>
</div>
</aside>
</div>
<div id="panelWrap" hidden><div id="panelShade"></div><aside id="panel"></aside></div>
<div id="toast"></div>
<script>
'use strict';
const $ = id => document.getElementById(id);
const esc = s => String(s == null ? '' : s).replace(/&/g,'&').replace(/</g,'<')
.replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');
const el = (tag, cls, txt) => { const n = document.createElement(tag); if (cls) n.className = cls;
if (txt != null) n.textContent = txt; return n; };
let TOKEN = sessionStorage.getItem('areev.token') || '';
async function api(url, opts) {
opts = Object.assign({}, opts);
if (TOKEN) opts.headers = Object.assign({}, opts.headers, { Authorization: 'Bearer ' + TOKEN });
let r;
try { r = await fetch(url, opts); } catch (e) { return { ok: false, error: 'network error' }; }
if (r.status === 401) {
const t = window.prompt('This console is protected. Paste its token:');
if (t) { TOKEN = t; sessionStorage.setItem('areev.token', t); return api(url, opts); }
return { ok: false, error: 'not authorised' };
}
try { return await r.json(); } catch (e) { return { ok: false, error: 'bad response' }; }
}
const cal = q => api('/api/cal', { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: q }) });
async function refreshWhoami() {
const w = await api('/api/whoami');
if (!w || !w.ok || w.mode !== 'credentials') return;
const row = $('whorow'); row.style.display = '';
$('whoami').textContent = w.owner ? 'Owner session' : w.principal;
$('signbtn').textContent = TOKEN ? 'Sign out' : 'Sign in';
}
document.addEventListener('DOMContentLoaded', () => {
const btn = $('signbtn');
if (btn) btn.addEventListener('click', () => {
if (TOKEN) { TOKEN = ''; sessionStorage.removeItem('areev.token'); }
else {
const t = window.prompt('Paste your access token:');
if (!t) return;
TOKEN = t; sessionStorage.setItem('areev.token', t);
}
location.reload();
});
refreshWhoami();
});
function toast(msg) {
const t = $('toast'); t.textContent = msg; t.style.display = 'block';
clearTimeout(toast._h); toast._h = setTimeout(() => { t.style.display = 'none'; }, 2000);
}
function copyText(s, label) {
if (!navigator.clipboard) { toast('copy unavailable'); return; }
navigator.clipboard.writeText(s).then(() => toast((label || 'Copied') + ' ✓'), () => toast('Copy failed'));
}
function download(name, text, mime) {
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([text], { type: mime || 'application/json' }));
a.download = name; a.click(); URL.revokeObjectURL(a.href);
}
const hlcMs = h => Math.floor((h || 0) / 65536);
const shortHash = h => h ? h.slice(0, 12) + '…' : '';
const cap = s => s ? s.charAt(0).toUpperCase() + s.slice(1) : '';
const titleize = s => /^[a-z]+( [a-z]+)*$/.test(String(s || '')) ? cap(s) : String(s || '');
const lowerFirst = s => s ? s.charAt(0).toLowerCase() + s.slice(1) : '';
function relTime(ms) {
if (!ms) return '';
const d = Date.now() - ms;
if (d < 45000) return 'just now';
const m = Math.floor(d / 60000); if (m < 60) return m + (m === 1 ? ' minute ago' : ' minutes ago');
const h = Math.floor(m / 60); if (h < 24) return h + (h === 1 ? ' hour ago' : ' hours ago');
const dd = Math.floor(h / 24); if (dd < 30) return dd + (dd === 1 ? ' day ago' : ' days ago');
const mo = Math.floor(dd / 30); return mo + (mo === 1 ? ' month ago' : ' months ago');
}
const shortWhen = ms => {
if (!ms) return '';
const d = Date.now() - ms;
if (d < 45000) return 'now';
const m = Math.floor(d / 60000); if (m < 60) return m + 'm';
const h = Math.floor(m / 60); if (h < 24) return h + 'h';
const dd = Math.floor(h / 24); if (dd < 30) return dd + 'd';
return Math.floor(dd / 30) + 'mo';
};
const dayKey = ms => new Date(ms).toDateString();
function dayLabel(ms) {
const k = dayKey(ms), today = new Date().toDateString();
const y = new Date(Date.now() - 86400000).toDateString();
if (k === today) return 'TODAY';
if (k === y) return 'YESTERDAY';
return new Date(ms).toLocaleDateString(undefined, { day: 'numeric', month: 'long' }).toUpperCase();
}
const clock = ms => new Date(ms).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
const isoTitle = ms => ms ? new Date(ms).toISOString() : '';
function createdMs(f) {
const c = f && f.created_at;
if (typeof c === 'number') return c;
if (typeof c === 'string') { const p = Date.parse(c); return isNaN(p) ? 0 : p; }
return 0;
}
function ctxText(f) {
const c = f && f.context;
if (!c || typeof c !== 'object') return '';
for (const k of ['content', 'text', 'message', 'summary', 'description']) {
if (typeof c[k] === 'string' && c[k].trim()) return c[k];
}
return '';
}
function preview(f) {
if (!f) return '';
const v = f.object != null ? f.object : f.content != null ? f.content
: f.description != null ? f.description : f.tool_content != null ? f.tool_content
: f.summary != null ? f.summary : f.title != null ? f.title
: ctxText(f) ? ctxText(f)
: f.data != null ? JSON.stringify(f.data) : '';
const s = typeof v === 'string' ? v : JSON.stringify(v);
return s.length > 140 ? s.slice(0, 140) + '…' : s;
}
const cssVar = n => getComputedStyle(document.documentElement).getPropertyValue(n).trim();
const REL_PHRASE = {
prefers: 'Prefers {o}', likes: 'Likes {o}', dislikes: 'Dislikes {o}',
works_at: 'Works at {o}', employed_by: 'Works at {o}', member_of: 'Is a member of {o}',
lives_in: 'Lives in {o}', based_in: 'Is based in {o}', headquartered_in: 'Headquartered in {o}',
owns_project: 'Owns the {o} project', maintains: 'Maintains {o}', works_on: 'Works on {o}',
collaborates_with: 'Collaborates with {o}', knows: 'Knows {o}', reports_to: 'Reports to {o}',
asked_about: 'Asked about {o}', email: 'Email is {o}', phone: 'Phone number is {o}',
role: 'Role is {o}', title: 'Title is {o}', speaks: 'Speaks {o}', located_in: 'Is located in {o}',
};
const MAX_SAY = 180;
const clampText = s => s.length > MAX_SAY ? s.slice(0, MAX_SAY).trimEnd() + '…' : s;
function predicate(g) {
const f = g.fields || {};
if (g.type === 'fact') {
const r = f.relation || '', o = f.object;
const os = clampText(typeof o === 'string' ? o : JSON.stringify(o == null ? '' : o));
const tpl = REL_PHRASE[r];
if (tpl) return tpl.replace('{o}', os);
if (!r) return os;
return cap(r.replace(/_/g, ' ')) + ' ' + os;
}
const fallback = 'A ' + (g.type || 'memory') + ' with no readable text';
if (g.type === 'tool') {
const name = f.tool_name || 'A tool';
const body = f.tool_content || ctxText(f) || '';
return name + (f.is_error ? ' failed' : ' ran') + (body ? ' — ' + clampText(body) : '');
}
if (g.type === 'observation') return f.content || f.object || ctxText(f) || fallback;
if (g.type === 'event') return f.content || f.description || preview(f) || fallback;
if (g.type === 'goal') return f.description || f.content || preview(f) || fallback;
return preview(f) || fallback;
}
function sentence(g) {
const f = g.fields || {}, p = predicate(g);
if (!f.subject) return p;
const s = String(f.subject);
if (p.toLowerCase().startsWith(s.toLowerCase())) return cap(p);
return titleize(s) + ' ' + lowerFirst(p);
}
function provenanceParts(g) {
const f = g.fields || {}, bits = [];
const prevGrain = f.derived_from && BYHASH[f.derived_from];
const was = g._prevValue
|| (prevGrain ? (typeof (prevGrain.fields || {}).object === 'string'
? prevGrain.fields.object : preview(prevGrain.fields)) : null);
if (was) bits.push('Changed from ' + was);
const when = createdMs(f) || hlcMs(g.hlc);
if (!bits.length) bits.push('Learned ' + relTime(when));
if (g.type === 'observation') bits.push('noticed by your agent, not confirmed');
if (f.namespace && f.namespace !== 'shared') bits.push('in ' + f.namespace);
if (typeof f.confidence === 'number' && f.confidence < 0.75) bits.push('low confidence');
return bits;
}
const provenance = g => provenanceParts(g).join(' · ');
let BROWSE = [], BYHASH = {}, STATS = {}, CONFIG = null, LOG = [], RECS = {}, TELEMETRY = null;
let PAGE = 'memory', MVIEW = 'list', SUGTAB = 'pending', CONTAB = 'agent', ACTFILTER = 'all';
let ENTITY = null, ANSWER = null, SEL = null, ACTIVE_ENTS = [];
const isGone = g => !!(g.forgotten || g.missing || g.op === 'forget');
const isCurrent = g => !isGone(g) && !g.superseded_by && !g._stale;
const INTERNAL_NS = new Set(['areev-loop']);
function isInternal(g) {
const f = g.fields || {};
if (INTERNAL_NS.has(f.namespace)) return true;
const s = f.subject;
return typeof s === 'string' && /^__.*__$/.test(s);
}
const currentGrains = () => BROWSE.filter(g => isCurrent(g) && !isInternal(g));
function headHashes(grains) {
const best = new Map();
for (const g of grains) {
if (isGone(g) || (g.type && g.type !== 'fact')) continue;
const f = g.fields || {};
if (!f.subject || !f.relation) continue;
const k = (f.namespace || '') + '