"use strict";
function el(tag, props, ...kids) {
const node = document.createElement(tag);
for (const [key, value] of Object.entries(props || {})) {
if (value === null || value === undefined || value === false) continue;
if (key === "class") node.className = value;
else if (key === "text") node.textContent = value;
else if (key.startsWith("on")) node.addEventListener(key.slice(2), value);
else node.setAttribute(key, value === true ? "" : String(value));
}
for (const kid of kids.flat()) {
if (kid === null || kid === undefined || kid === false) continue;
node.append(kid.nodeType ? kid : document.createTextNode(String(kid)));
}
return node;
}
function appended(node, ...kids) {
for (const kid of kids.flat()) if (kid) node.append(kid);
return node;
}
const LOADER_HOT = new Set([1, 6, 7, 9, 14]);
function loadingView(what) {
const cells = [];
for (let i = 0; i < 16; i++) {
cells.push(el("span", { class: LOADER_HOT.has(i) ? "cell hot" : "cell", "data-i": String(i) }));
}
return el(
"div",
{ class: "view loading", role: "status", "aria-live": "polite" },
el("div", { class: "mark", "aria-hidden": "true" }, cells),
el("span", { class: "what", text: what ? `Reading ${what.toLowerCase()}…` : "Reading…" }),
el("span", { class: "track" }, el("span", { class: "run" })),
);
}
function skeleton(...widths) {
return el(
"div",
{ class: "skel", "aria-hidden": "true" },
widths.map((width) => {
const line = el("span", { class: "line" });
line.style.width = `${width}%`;
return line;
}),
);
}
function skeletonRows(rows) {
const widths = [];
for (let i = 0; i < rows; i++) widths.push([92, 78, 85, 64][i % 4]);
return skeleton(...widths);
}
function fill(node, ...kids) {
node.replaceChildren();
for (const kid of kids.flat()) {
if (kid === null || kid === undefined || kid === false) continue;
node.append(kid.nodeType ? kid : document.createTextNode(String(kid)));
}
return node;
}
function setText(node, value) {
const text = value === null || value === undefined ? "" : String(value);
if (node.nodeType === Node.TEXT_NODE) {
if (node.data !== text) node.data = text;
return node;
}
const only = node.firstChild;
if (only && only === node.lastChild && only.nodeType === Node.TEXT_NODE) {
if (only.data !== text) only.data = text;
} else if (node.textContent !== text) {
node.textContent = text;
}
return node;
}
function arrange(parent, want) {
const order = [...new Set(want.filter(Boolean))];
const rank = new Map(order.map((node, i) => [node, i]));
const present = [...parent.children].filter((child) => rank.has(child));
const focused = present.findIndex((child) => child.contains(document.activeElement));
const items = present.map((child, at) => ({ at, value: rank.get(child) }));
const pivot = focused >= 0 ? items[focused].value : -1;
const staying =
focused < 0
? increasing(items)
: [
...increasing(items.filter((item) => item.at < focused && item.value < pivot)),
focused,
...increasing(items.filter((item) => item.at > focused && item.value > pivot)),
];
const stays = new Set(staying.map((at) => present[at]));
let next = null;
for (let i = order.length - 1; i >= 0; i--) {
const node = order[i];
const placed =
node.parentNode === parent && (next ? node.nextElementSibling === next : !node.nextElementSibling);
if (!stays.has(node) && !placed) parent.insertBefore(node, next);
next = node;
}
}
function increasing(items) {
const tails = [];
const before = [];
items.forEach((item, k) => {
let lo = 0;
let hi = tails.length;
while (lo < hi) {
const mid = (lo + hi) >> 1;
if (items[tails[mid]].value < item.value) lo = mid + 1;
else hi = mid;
}
before[k] = lo > 0 ? tails[lo - 1] : -1;
tails[lo] = k;
});
const out = [];
for (let k = tails.length ? tails[tails.length - 1] : -1; k >= 0; k = before[k]) out.unshift(items[k].at);
return out;
}
const TOKEN_HEADER = "Semlith-Token";
const session = (() => {
const KEY = "semlith.token";
let token = "";
try {
const fromUrl = new URLSearchParams(location.search).get("token");
if (fromUrl) {
token = fromUrl;
sessionStorage.setItem(KEY, token);
const clean = location.pathname + location.hash;
history.replaceState(null, "", clean || "/");
} else {
token = sessionStorage.getItem(KEY) || "";
}
} catch (_) {
}
return {
get: () => token,
set(fresh) {
token = fresh;
try {
sessionStorage.setItem(KEY, fresh);
} catch (_) {
}
},
};
})();
function authed(extra) {
return { ...(extra || {}), [TOKEN_HEADER]: session.get() };
}
function imagePreview(path) {
const img = el("img", { class: "preview", alt: "" });
const failed = () =>
img.replaceWith(el("pre", { class: "muted", text: `${path} could not be read` }));
fetch(`/api/image?path=${encodeURIComponent(path)}`, {
credentials: "omit",
headers: authed(),
})
.then((response) => (response.ok ? response.blob() : Promise.reject(response.statusText)))
.then((blob) => {
const reader = new FileReader();
reader.onload = () => {
img.src = String(reader.result);
};
reader.onerror = failed;
reader.readAsDataURL(blob);
})
.catch(failed);
return img;
}
async function api(path, options) {
const options_ = options || {};
const response = await fetch(path, {
credentials: "omit",
...options_,
headers: authed(options_.headers),
});
if (!response.ok) {
let detail = response.statusText;
try {
const body = await response.json();
if (body && body.error) detail = body.error;
} catch (_) {
}
throw new Error(detail || "request failed");
}
return response.json();
}
function post(path, body) {
return api(path, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
}
const NUM = new Intl.NumberFormat();
const n = (value) => NUM.format(value || 0);
function share(part, whole) {
if (!whole) return "0%";
const pct = (part / whole) * 100;
return `${pct < 10 ? pct.toFixed(1) : Math.round(pct)}%`;
}
function bytes(value) {
const units = ["B", "KB", "MB", "GB", "TB"];
let size = Number(value) || 0;
let unit = 0;
while (size >= 1024 && unit < units.length - 1) {
size /= 1024;
unit += 1;
}
return `${unit === 0 ? size : size.toFixed(1)} ${units[unit]}`;
}
function zone() {
const minutes = -new Date().getTimezoneOffset();
const sign = minutes < 0 ? "-" : "+";
const off = Math.abs(minutes);
return `${sign}${String(Math.floor(off / 60)).padStart(2, "0")}:${String(off % 60).padStart(2, "0")}`;
}
function when(unix) {
if (!unix) return "never";
const seconds = Math.max(0, Math.floor(Date.now() / 1000) - unix);
if (seconds < 60) return `${seconds}s ago`;
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
return `${Math.floor(seconds / 86400)}d ago`;
}
function clock(unix) {
return `${new Date(unix * 1000).toTimeString().slice(0, 8)} ${zone()}`;
}
const SVG_NS = "http://www.w3.org/2000/svg";
function icon(d, size, solid) {
const svg = document.createElementNS(SVG_NS, "svg");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("width", String(size || 16));
svg.setAttribute("height", String(size || 16));
svg.setAttribute("fill", solid ? "currentColor" : "none");
svg.setAttribute("stroke", solid ? "none" : "currentColor");
svg.setAttribute("stroke-width", "1.7");
svg.setAttribute("stroke-linecap", "round");
svg.setAttribute("stroke-linejoin", "round");
svg.setAttribute("aria-hidden", "true");
for (const segment of String(d).split("|")) {
const path = document.createElementNS(SVG_NS, "path");
path.setAttribute("d", segment);
svg.append(path);
}
return svg;
}
const ICONS = {
menu: "M4 7h16|M4 12h16|M4 17h16",
search: "M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14z|M20 20l-3.5-3.5",
plus: "M12 5v14|M5 12h14",
sun: "M12 3v2|M12 19v2|M3 12h2|M19 12h2|M5.6 5.6 7 7|M17 17l1.4 1.4|M18.4 5.6 17 7|M7 17l-1.4 1.4|M12 7.8a4.2 4.2 0 1 0 0 8.4 4.2 4.2 0 0 0 0-8.4z",
folder: "M3 7.5A1.5 1.5 0 0 1 4.5 6h4l2 2.5h7A1.5 1.5 0 0 1 19 10v7a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 3 17z",
file: "M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z|M14 3v5h5",
up: "M5 12h14|M11 6l-6 6 6 6",
arrowRight: "M5 12h14|M13 6l6 6-6 6",
alert: "M12 9v4|M12 17h.01|M12 4 3 19h18z",
monitor: "M4 5h16v10H4z|M9 19h6|M12 15v4",
moon: "M20 14.5A8.5 8.5 0 0 1 9.5 4a8.5 8.5 0 1 0 10.5 10.5z",
copy: "M9 9h9a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1z|M6 15H5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1v1",
more:
"M12 4.1a2 2 0 1 0 0 4 2 2 0 0 0 0-4z|M12 10a2 2 0 1 0 0 4 2 2 0 0 0 0-4z|"
+ "M12 15.9a2 2 0 1 0 0 4 2 2 0 0 0 0-4z",
sliders: "M4 7h10|M18 7h2|M4 12h4|M12 12h8|M4 17h11|M19 17h1|M15 5v4|M9 10v4|M16 15v4",
chevron: "M9 6l6 6-6 6",
folderOpen: "M3 7.5A1.5 1.5 0 0 1 4.5 6h4l2 2.5h7A1.5 1.5 0 0 1 19 10v1H7.2a1.5 1.5 0 0 0-1.4 1L3.6 18.5|M3.6 18.5 6 12h15l-2.4 6.5z",
};
const NAV_ICONS = {
stores: "M12 4l8 4-8 4-8-4 8-4|M4 12l8 4 8-4|M4 16.5l8 4 8-4",
files: "M6 3h7l5 5v13H6z|M13 3v5h5",
index: "M4 6h16|M4 12h10|M4 18h13",
corpus: "M5 4h6v16H5z|M13 4h6v16h-6",
search: "M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14z|M16.2 16.2 20 20",
graph: "M5 6h4v4H5z|M15 14h4v4h-4z|M9 8h4v8h2",
ledger: "M5 4h14v16H5z|M8 9h8|M8 13h8|M8 17h5",
agents: "M9 3h6v5H9z|M12 8v3|M5 11h14v9H5z|M9 15h.01|M15 15h.01",
privacy: "M12 3l7 3v6c0 4.3-3 7.3-7 9-4-1.7-7-4.7-7-9V6z",
doctor: "M3 12h3l2-5 3 10 2.5-7 1.5 2h6",
about: "M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16z|M12 11v5|M12 8h.01",
impact: "M4 18l5-6 4 3 7-9|M20 6h-4|M20 6v4",
reports: "M6 3h8l4 4v14H6z|M14 3v4h4|M9 17v-3|M12 17v-6|M15 17v-4",
cloud: "M7.5 18a4 4 0 0 1 .3-8A5 5 0 0 1 17 9.6 3.6 3.6 0 0 1 16.5 18z",
};
function copyButton(getText, label, word) {
const was = label || "Copy";
const face = () => (word ? was : icon(ICONS.copy, 14));
const button = el("button", {
class: "button secondary small copy",
type: "button",
"aria-label": was,
title: was,
onclick: async () => {
const text = typeof getText === "function" ? getText() : getText;
try {
await navigator.clipboard.writeText(text);
} catch (_) {
const area = el("textarea", { class: "offscreen" });
area.value = text;
document.body.append(area);
area.select();
try {
document.execCommand("copy");
} catch (__) {
}
area.remove();
}
button.classList.add("done");
fill(button, "Copied");
setTimeout(() => {
button.classList.remove("done");
fill(button, face());
}, 1400);
},
});
fill(button, face());
return button;
}
function copyField(command, word) {
return el(
"div",
{ class: word ? "copyfield worded" : "copyfield" },
el("code", { class: "text", text: command }),
copyButton(command, "Copy", word),
);
}
function codeBlock(text, label) {
return el(
"div",
{ class: "code-block" },
el("pre", { class: "code", text }),
copyButton(text, label || "Copy"),
);
}
function mono(text) {
return el("code", { class: "mono", text });
}
function says(...parts) {
return el("p", { class: "subtitle" }, parts);
}
function error(message) {
return el(
"div",
{ class: "error", role: "alert" },
icon(ICONS.alert, 17),
el("span", { class: "what", text: message }),
);
}
function empty(message) {
return el("div", { class: "empty" }, message);
}
const tip = {
node: null,
owner: null,
ensure() {
if (!this.node) {
this.node = el("div", { class: "tip", role: "tooltip", "aria-hidden": "true" });
document.body.append(this.node);
}
return this.node;
},
open(content, owner) {
const node = this.ensure();
this.owner = owner || null;
fill(node, content);
node.setAttribute("aria-hidden", "false");
node.dataset.open = "true";
return node;
},
show(content, rect, owner) {
const node = this.open(content, owner);
const width = node.offsetWidth;
const height = node.offsetHeight;
const margin = 8;
let left = rect.left;
if (left + width > window.innerWidth - margin) {
left = Math.max(margin, window.innerWidth - margin - width);
}
let top = rect.top - height - 6;
if (top < margin) top = rect.bottom + 6;
node.style.left = `${Math.max(margin, Math.round(left))}px`;
node.style.top = `${Math.round(top)}px`;
},
follow(x, y) {
const node = this.node;
if (!node) return;
const width = node.offsetWidth;
const height = node.offsetHeight;
const margin = 10;
let left = x + 14;
let top = y - height - 14;
if (left + width > window.innerWidth - margin) left = Math.max(margin, x - width - 14);
if (top < margin) top = y + 20;
node.style.left = `${Math.round(left)}px`;
node.style.top = `${Math.round(top)}px`;
},
contentOf(target) {
return target.hasAttribute("data-tip-title") ? tipCardOf(target) : target.getAttribute("data-tip");
},
at(target) {
this.show(this.contentOf(target), target.getBoundingClientRect(), target);
},
atPoint(x, y, content, owner) {
this.open(content, owner);
this.follow(x, y);
},
hide(owner) {
if (!this.node) return;
if (owner && this.owner !== owner) return;
this.node.dataset.open = "false";
this.node.setAttribute("aria-hidden", "true");
this.owner = null;
},
};
function wireTips() {
const find = (node) => (node && node.closest ? node.closest("[data-tip], [data-tip-title]") : null);
document.addEventListener("pointerover", (e) => {
const target = find(e.target);
if (target) {
if (tip.owner !== target) tip.atPoint(e.clientX, e.clientY, tip.contentOf(target), target);
} else if (tip.owner && tip.owner.nodeType) tip.hide();
});
document.addEventListener("pointermove", (e) => {
if (tip.owner && tip.owner.nodeType && tip.owner.contains(e.target)) tip.follow(e.clientX, e.clientY);
});
document.addEventListener("pointerout", (e) => {
const target = find(e.target);
if (target && !(e.relatedTarget && target.contains(e.relatedTarget))) tip.hide(target);
});
document.addEventListener("focusin", (e) => {
const target = find(e.target);
if (target) tip.at(target);
});
document.addEventListener("focusout", (e) => {
const target = find(e.target);
if (target) tip.hide(target);
});
window.addEventListener("scroll", () => tip.hide(), true);
window.addEventListener("resize", () => tip.hide());
}
const PER_PAGE = [5, 10, 25, 50];
const TABLE_VIEWS = new Map();
function dataTable(spec) {
const columns = spec.columns;
const view = TABLE_VIEWS.get(spec.remember) || {
page: 1,
perPage: spec.perPage || 5,
sort: spec.sort || null,
dir: spec.dir || "asc",
};
if (spec.remember) TABLE_VIEWS.set(spec.remember, view);
let rows = spec.rows || [];
let total = spec.total === undefined ? rows.length : spec.total;
const headRow = el("tr", {});
const body = el("tbody", {});
const foot = el("div", { class: "table-foot" });
const table = el(
"table",
{ class: spec.className || null },
spec.caption ? el("caption", { class: "sr-only", text: spec.caption }) : null,
el("thead", {}, headRow),
body,
);
const node = el(
"div",
{ class: spec.grow ? "card table-card grow" : "card table-card" },
el("div", { class: "table-wrap" }, table),
foot,
);
function pages() {
return Math.max(1, Math.ceil(total / view.perPage));
}
function sorted() {
if (!view.sort) return rows;
const column = columns.find((c) => c.key === view.sort);
if (!column || !column.value) return rows;
const sign = view.dir === "desc" ? -1 : 1;
return [...rows].sort((a, b) => {
if (column.empty) {
const blank = Number(column.empty(a)) - Number(column.empty(b));
if (blank) return blank;
}
const x = column.value(a);
const y = column.value(b);
if (typeof x === "number" && typeof y === "number") return (x - y) * sign;
return String(x).localeCompare(String(y), undefined, { numeric: true }) * sign;
});
}
function shown() {
if (spec.server) return rows;
const from = (view.page - 1) * view.perPage;
return sorted().slice(from, from + view.perPage);
}
function changed() {
if (spec.server && spec.onChange) spec.onChange({ ...view });
else paint();
}
function sortBy(key) {
if (view.sort === key) view.dir = view.dir === "asc" ? "desc" : "asc";
else {
view.sort = key;
view.dir = "asc";
}
view.page = 1;
changed();
}
function paintHead() {
fill(
headRow,
columns.map((column) => {
const on = view.sort === column.key;
if (column.head) {
return el("th", { class: column.className || null }, column.head());
}
if (column.sortable === false) {
return el("th", { class: column.className || null, text: column.label });
}
return el(
"th",
{
class: column.className || null,
"aria-sort": on ? (view.dir === "asc" ? "ascending" : "descending") : "none",
},
el(
"button",
{ class: "sort", type: "button", onclick: () => sortBy(column.key) },
column.label,
el("span", { class: "arrow", text: on ? (view.dir === "asc" ? "↑" : "↓") : "" }),
),
);
}),
);
}
function paintBody() {
const page = shown();
fill(
body,
page.map((row, i) =>
el(
"tr",
{},
columns.map((column) =>
el(
"td",
{ class: column.className || null },
column.render ? column.render(row, i) : String(column.value ? column.value(row) : ""),
),
),
),
),
);
}
function paintFoot() {
const from = total === 0 ? 0 : (view.page - 1) * view.perPage + 1;
const to = Math.min(total, view.page * view.perPage);
fill(
foot,
el("span", { class: "meta", text: `${n(from)}–${n(to)} of ${n(total)}` }),
el("span", { class: "divider-v" }),
el("span", { class: "eyebrow sm", text: "per page" }),
el(
"span",
{ class: "chips" },
PER_PAGE.map((size) =>
el("button", {
class: "chip",
type: "button",
"aria-pressed": String(size === view.perPage),
text: String(size),
onclick: () => {
view.perPage = size;
view.page = 1;
changed();
},
}),
),
),
el("span", { class: "spacer" }),
el("button", {
class: "button secondary small",
type: "button",
text: "Prev",
disabled: view.page <= 1,
onclick: () => {
view.page -= 1;
changed();
},
}),
el("span", { class: "meta", text: `Page ${n(view.page)} / ${n(pages())}` }),
el("button", {
class: "button secondary small",
type: "button",
text: "Next",
disabled: view.page >= pages(),
onclick: () => {
view.page += 1;
changed();
},
}),
);
}
function paint() {
view.page = Math.min(view.page, pages());
paintHead();
paintBody();
paintFoot();
}
paint();
return {
node,
update(next, count) {
rows = next || [];
total = count === undefined ? rows.length : count;
if (view.page > pages()) view.page = pages();
paint();
},
query: () => ({ ...view }),
};
}
function folderPicker(options) {
const { onChoose, choose, onError, multiple } = options || {};
const card = el("div", { class: "card picker", hidden: true });
const ticked = new Set();
function done() {
card.hidden = true;
if (onChoose) onChoose(multiple ? [...ticked] : undefined);
ticked.clear();
}
async function open(path) {
let data;
try {
data = await api(`/api/dirs?path=${encodeURIComponent(path || "")}`);
} catch (e) {
if (onError) onError(e.message);
return false;
}
card.hidden = false;
fill(
card,
el(
"div",
{ class: "crumbs" },
el(
"button",
{
class: "button secondary small",
type: "button",
disabled: !data.parent,
onclick: () => open(data.parent),
},
icon(ICONS.up, 15),
"Up",
),
el("span", { class: "where", text: data.path }),
el("span", { class: "spacer" }),
multiple
? el("button", {
class: "button small",
type: "button",
text: ticked.size
? `Use ${ticked.size} folder${ticked.size === 1 ? "" : "s"}`
: "Use this folder",
onclick: () => {
if (!ticked.size) ticked.add(data.path);
done();
},
})
: el("button", {
class: "button small",
type: "button",
text: choose || "Use this folder",
onclick: () => {
card.hidden = true;
if (onChoose) onChoose(data.path);
},
}),
el("button", {
class: "button secondary small",
type: "button",
text: "Close",
onclick: () => {
card.hidden = true;
},
}),
),
el(
"div",
{ class: "entries" },
data.entries.length
? data.entries.map((entry) => {
const inert = multiple && !entry.dir;
const row = el(
"button",
{
class: inert ? "entry inert" : "entry",
type: "button",
disabled: inert,
title: entry.path,
onclick: () => {
if (entry.dir) open(entry.path);
else {
card.hidden = true;
if (onChoose) onChoose(multiple ? [entry.path] : entry.path);
}
},
},
icon(entry.dir ? ICONS.folder : ICONS.file),
el("span", { class: "name", text: entry.name }),
entry.adoptable ? pill("a store", "good") : null,
);
if (!multiple || !entry.dir) return row;
const box = el("input", {
type: "checkbox",
class: "tick",
"aria-label": `Index ${entry.name}`,
checked: ticked.has(entry.path),
onchange: () => {
if (box.checked) ticked.add(entry.path);
else ticked.delete(entry.path);
open(data.path);
},
});
return el("div", { class: "entry-row" }, box, row);
})
: el("div", { class: "card pad" }, empty("Nothing here that Semlith can index.")),
),
);
return true;
}
return {
node: card,
open,
close: () => {
card.hidden = true;
},
isOpen: () => !card.hidden,
};
}
function pathCell(value, className) {
return el(
"span",
{
class: className ? `one-line tail ${className}` : "one-line tail",
"data-tip": value,
title: value,
},
el("bdi", { text: value }),
);
}
function lineCell(value, className) {
return el("span", {
class: className ? `one-line ${className}` : "one-line",
"data-tip": value,
title: value,
text: value,
});
}
function labelled(id, text, input) {
input.id = id;
return [el("label", { class: "sr-only", for: id, text }), input];
}
function pageHead(title, subtitle, extra) {
const { pill, actions } = extra || {};
return el(
"div",
{ class: "page-head" },
el(
"div",
{ class: "line" },
el("h1", { text: title }),
pill || null,
actions ? el("span", { class: "spacer" }) : null,
actions ? el("div", { class: "actions" }, actions) : null,
),
subtitle ? el("p", { class: "subtitle", text: subtitle }) : null,
);
}
const menu = {
node: null,
owner: null,
ensure() {
if (this.node) return this.node;
this.node = el("div", { class: "menu", role: "menu", hidden: true });
document.body.append(this.node);
document.addEventListener("pointerdown", (e) => {
if (this.node.hidden) return;
if (this.node.contains(e.target) || (this.owner && this.owner.contains(e.target))) return;
this.close();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") this.close();
});
window.addEventListener("resize", () => this.close());
window.addEventListener("scroll", () => this.close(), true);
return this.node;
},
open(at, items) {
const node = this.ensure();
if (this.owner === at && !node.hidden) return this.close();
this.owner = at;
at.setAttribute("aria-expanded", "true");
fill(
node,
items.map((item) =>
el("button", {
class: item.tone ? `menu-item ${item.tone}` : "menu-item",
type: "button",
role: "menuitem",
text: item.label,
onclick: () => {
this.close();
item.onclick();
},
}),
),
);
node.hidden = false;
const box = node.getBoundingClientRect();
const rect = at.getBoundingClientRect();
const margin = 8;
let left = rect.right - box.width;
left = Math.min(Math.max(margin, left), window.innerWidth - margin - box.width);
let top = rect.bottom + 6;
if (top + box.height > window.innerHeight - margin) top = rect.top - box.height - 6;
node.style.left = `${Math.round(left)}px`;
node.style.top = `${Math.round(Math.max(margin, top))}px`;
node.querySelector(".menu-item")?.focus();
},
close() {
if (!this.node) return;
this.node.hidden = true;
if (this.owner) this.owner.setAttribute("aria-expanded", "false");
this.owner = null;
},
};
function rowMenu(items) {
const button = el("button", {
class: "button secondary small icon",
type: "button",
"aria-haspopup": "menu",
"aria-expanded": "false",
"aria-label": "Actions",
title: "Actions",
onclick: () => menu.open(button, items()),
});
fill(button, icon(ICONS.more, 18, true));
return button;
}
function pill(text, tone, props) {
return el(
"span",
{ class: tone ? `pill ${tone}` : "pill", ...(props || {}) },
tone ? el("i", {}) : null,
text,
);
}
const NARROW = "(max-width: 899px)";
const state = {
stores: [],
theme: "light",
navOpen: !window.matchMedia(NARROW).matches,
pendingPath: "",
pendingSymbol: "",
impactSymbol: "",
impactStore: "",
pendingQuery: "",
search: { query: "", stores: [] },
agents: null,
ledger: null,
runs: null,
onRuns: null,
onStores: null,
};
const VIEWS = [
{ group: "Workspace", id: "stores", label: "Stores", title: "Stores" },
{ group: "Workspace", id: "files", label: "Files", title: "Files" },
{ group: "Workspace", id: "index", label: "Index", title: "Index" },
{ group: "Workspace", id: "corpus", label: "Inside the index", title: "Inside the index" },
{ group: "Explore", id: "search", label: "Search", title: "Search" },
{ group: "Explore", id: "graph", label: "Graph", title: "Graph" },
{ group: "Explore", id: "impact", label: "Impact", title: "Impact" },
{ group: "Operate", id: "ledger", label: "Retrieval ledger", title: "Retrieval ledger" },
{ group: "Operate", id: "reports", label: "Reports", title: "Reports" },
{ group: "Operate", id: "agents", label: "Agents", title: "Agents" },
{ group: "Operate", id: "cloud", label: "Cloud", title: "Cloud" },
{ group: "Operate", id: "privacy", label: "Privacy", title: "Privacy" },
{ group: "Machine", id: "doctor", label: "Doctor", title: "Doctor" },
{ group: "Machine", id: "about", label: "About", title: "About" },
];
function paintStoreCount() {
const node = document.getElementById("daemon-stores");
if (!node) return;
const many = state.stores.length;
const parts = [`${many} store${many === 1 ? "" : "s"}`];
if (state.agents !== null) {
parts.push(`${state.agents} agent${state.agents === 1 ? "" : "s"}`);
}
if (state.ledger !== null) parts.push(state.ledger ? "ledger on" : "ledger off");
node.textContent = parts.join(" · ");
}
function noteAgents(data) {
state.agents = (data.connections || []).length;
paintStoreCount();
}
const live = {
seen: {},
watchers: [],
timer: null,
};
function watchLive(domains, run) {
live.watchers.push({ domains, run });
}
function resetLive() {
live.watchers = [];
}
async function pollChanges() {
if (document.visibilityState === "hidden") return;
let counters;
try {
counters = await api("/api/changes");
} catch (_) {
return;
}
const moved = [];
for (const [domain, value] of Object.entries(counters)) {
if (live.seen[domain] === undefined) {
live.seen[domain] = value;
continue;
}
if (live.seen[domain] !== value) {
live.seen[domain] = value;
moved.push(domain);
}
}
if (!moved.length) return;
if (moved.includes("stores")) {
refreshStores()
.then(() => state.onStores && state.onStores())
.catch(() => {});
}
for (const watcher of live.watchers) {
if (watcher.domains.some((domain) => moved.includes(domain))) {
try {
watcher.run(moved);
} catch (_) {
}
}
}
}
function startLive() {
if (live.timer) return;
live.timer = setInterval(pollChanges, 1000);
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") pollChanges();
});
}
let runsAsked = 0;
let runsPainted = 0;
function supersedeRuns() {
runsPainted = ++runsAsked;
}
async function refreshRuns() {
const mine = ++runsAsked;
let data;
try {
data = await api("/api/index/runs");
} catch (_) {
return state.runs;
}
if (mine < runsPainted) return state.runs;
runsPainted = mine;
state.runs = data;
paintRunCount();
if (state.onRuns) state.onRuns(data);
return data;
}
function paintRunCount() {
const on = (state.runs?.runs || []).filter((run) => TICKING.has(run.status) && run.status !== "review").length;
const waiting = (state.runs?.queue || []).length;
const review = (state.runs?.runs || []).filter((run) => run.status === "review").length;
for (const node of document.querySelectorAll(".run-count")) {
const parts = [];
if (on) parts.push(`${on} indexing`);
if (waiting) parts.push(`${waiting} queued`);
if (review) parts.push(`${review} waiting for review`);
node.textContent = parts.join(" · ");
node.hidden = !parts.length;
}
}
const TICKING = new Set(["queued", "review", "running", "pausing", "paused", "held", "stopping"]);
async function repaintView() {
if (!shell.main) return;
const at = scrollHolder()?.scrollTop ?? 0;
await render();
const holder = scrollHolder();
if (holder) holder.scrollTop = at;
}
function scrollHolder() {
if (!shell.main) return null;
return shell.main.querySelector(".view") || shell.main.querySelector(".scroller");
}
function graphInk() {
const style = getComputedStyle(document.documentElement);
const read = (name, fallback) => (style.getPropertyValue(name) || fallback).trim();
return {
edge: read("--line", "#dce3e8"),
hot: read("--blue", "#4c7088"),
node: read("--panel", "#ffffff"),
nodeLine: read("--line", "#dce3e8"),
text: read("--ink-2", "#3a4d5e"),
nearFill: read("--blue-soft", "#e3ecf2"),
nearLine: read("--blue", "#4c7088"),
nearText: read("--ink", "#1e2a35"),
warn: read("--amber-ink", "#7a4e0a"),
sel: read("--accent", "#f0a43c"),
selLine: read("--accent-edge", "#c97f14"),
selText: read("--accent-ink", "#1e2a35"),
muted: read("--muted", "#64778a"),
};
}
function unreadableNotice(failed) {
if (!failed || !failed.length) return null;
const many = failed.length > 1;
return el(
"div",
{ class: "notice bad" },
el("div", {
class: "what",
text: `${failed.length} store${many ? "s" : ""} could not be read, so ${
many ? "they are" : "it is"
} not in these results.`,
}),
el(
"div",
{ class: "rows tight" },
failed.map((store) =>
el(
"details",
{ class: "unreadable" },
el(
"summary",
{},
el("span", { class: "name", text: store.store }),
el("span", { class: "meta one-line", "data-tip": store.path, text: store.path }),
),
el("p", { class: "subtitle", text: store.remedy }),
el("pre", { class: "code", text: store.error }),
),
),
),
);
}
function shortPath(path) {
const parts = String(path).split("/").filter(Boolean);
return parts.slice(-2).join("/") || path;
}
function stillness() {
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}
function graphCanvas(options) {
const { onPick, onHover } = options || {};
const canvas = el("canvas", { class: "graph-canvas" });
const wrap = el("div", { class: "graph-stage" }, canvas);
let nodes = [];
let edges = [];
let total = 0;
let drawn = [];
let selected = null;
let near = new Set();
let hovered = null;
let dragging = null;
let down = null;
let frame = null;
let running = true;
let settled = false;
let attached = false;
let alpha = 1;
let load = [];
function size() {
const ratio = window.devicePixelRatio || 1;
const box = wrap.getBoundingClientRect();
const w = Math.max(box.width, 240);
const h = Math.max(box.height, 240);
canvas.width = Math.round(w * ratio);
canvas.height = Math.round(h * ratio);
const ctx = canvas.getContext("2d");
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
return { w, h, ctx };
}
const MAX_STEP = 0.012;
const ALPHA_FLOOR = 0;
const DRIFT = 0.00003;
function pen(node, w, h) {
return {
mx: Math.min(0.45, ((node.w || 80) / 2 + 4) / w),
my: Math.min(0.45, ((node.h || 28) / 2 + 4) / h),
};
}
function step(w, h) {
const clock = performance.now() * 0.00028;
for (let i = 0; i < nodes.length; i++) {
const a = nodes[i];
for (let j = i + 1; j < nodes.length; j++) {
const b = nodes[j];
let dx = (b.x - a.x) * w;
let dy = (b.y - a.y) * h;
let d2 = dx * dx + dy * dy;
if (d2 < 1) {
d2 = 1;
dx = 0.6;
dy = 0.4;
}
const d = Math.sqrt(d2);
const force = (5200 / d2) * alpha;
a.vx -= ((dx / d) * force) / w;
a.vy -= ((dy / d) * force) / h;
b.vx += ((dx / d) * force) / w;
b.vy += ((dy / d) * force) / h;
}
}
for (const edge of drawn) {
const a = nodes[edge.from];
const b = nodes[edge.to];
if (!a || !b) continue;
const dx = (b.x - a.x) * w;
const dy = (b.y - a.y) * h;
const d = Math.max(1, Math.sqrt(dx * dx + dy * dy));
const pull = (d - 138) * 0.0025 * alpha;
const ax = Math.sqrt(load[edge.from] || 1);
const bx = Math.sqrt(load[edge.to] || 1);
a.vx += ((((dx / d) * pull * d) / w) * 0.02) / ax;
a.vy += ((((dy / d) * pull * d) / h) * 0.02) / ax;
b.vx -= ((((dx / d) * pull * d) / w) * 0.02) / bx;
b.vy -= ((((dy / d) * pull * d) / h) * 0.02) / bx;
}
let moved = 0;
for (const node of nodes) {
node.vx += (0.5 - node.x) * 0.006 * alpha;
node.vy += (0.5 - node.y) * 0.008 * alpha;
node.vx += Math.cos(clock + node.phase) * DRIFT;
node.vy += Math.sin(clock * 0.9 + node.phase * 1.7) * DRIFT;
if (node === dragging) {
node.vx = 0;
node.vy = 0;
continue;
}
node.vx *= 0.86;
node.vy *= 0.86;
node.vx = Math.max(-MAX_STEP, Math.min(MAX_STEP, node.vx));
node.vy = Math.max(-MAX_STEP, Math.min(MAX_STEP, node.vy));
moved += Math.abs(node.vx) + Math.abs(node.vy);
const bound = pen(node, w, h);
node.x = Math.min(1 - bound.mx, Math.max(bound.mx, node.x + node.vx));
node.y = Math.min(1 - bound.my, Math.max(bound.my, node.y + node.vy));
}
alpha = Math.max(ALPHA_FLOOR, alpha * 0.985);
return moved;
}
function reheat(to) {
alpha = Math.max(alpha, to === undefined ? 0.35 : to);
}
function separate() {
const box = wrap.getBoundingClientRect();
const w = Math.max(box.width, 240);
const h = Math.max(box.height, 240);
for (let pass = 0; pass < 4; pass++) {
for (let i = 0; i < nodes.length; i++) {
for (let j = i + 1; j < nodes.length; j++) {
const a = nodes[i];
const b = nodes[j];
if (!a.w || !b.w) return;
const dx = (b.x - a.x) * w;
const dy = (b.y - a.y) * h;
const wantX = (a.w + b.w) / 2 + 8;
const wantY = (a.h + b.h) / 2 + 6;
const overX = wantX - Math.abs(dx);
const overY = wantY - Math.abs(dy);
if (overX <= 0 || overY <= 0) continue;
const pa = pen(a, w, h);
const pb = pen(b, w, h);
if (overX / w < overY / h) {
const push = ((dx >= 0 ? 1 : -1) * overX) / w / 2;
a.x = Math.min(1 - pa.mx, Math.max(pa.mx, a.x - push));
b.x = Math.min(1 - pb.mx, Math.max(pb.mx, b.x + push));
} else {
const push = ((dy >= 0 ? 1 : -1) * overY) / h / 2;
a.y = Math.min(1 - pa.my, Math.max(pa.my, a.y - push));
b.y = Math.min(1 - pb.my, Math.max(pb.my, b.y + push));
}
}
}
}
}
function box(ctx, x, y, w, h, r) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
}
function paint() {
const { w, h, ctx } = size();
const ink = graphInk();
ctx.clearRect(0, 0, w, h);
ctx.font = '500 11px "IBM Plex Mono", ui-monospace, monospace';
for (const node of nodes) {
const ease = stillness() ? (node === hovered ? 1 : 0) : node.hover || 0;
node.hover = ease;
const text = ctx.measureText(node.label).width;
node.w = (text + 22) * (1 + 0.05 * ease);
node.h = 28 * (1 + 0.05 * ease);
}
for (const edge of drawn) {
const a = nodes[edge.from];
const b = nodes[edge.to];
if (!a || !b) continue;
const ax = a.x * w;
const ay = a.y * h;
const bx = b.x * w;
const by = b.y * h;
const hot = edge.from === selected || edge.to === selected;
const angle = Math.atan2(by - ay, bx - ax);
const inset = Math.min(b.w / 2 + 6, Math.abs(b.h / 2 / Math.sin(angle) || b.w));
const ex = bx - Math.cos(angle) * Math.min(inset, b.w / 2 + 6);
const ey = by - Math.sin(angle) * (b.h / 2 + 3);
ctx.save();
ctx.strokeStyle = hot ? ink.hot : ink.edge;
ctx.fillStyle = hot ? ink.hot : ink.edge;
ctx.lineWidth = hot ? 1.7 : 1.1;
const settled = edge.confidence === "extracted" || edge.confidence === "resolved";
if (edge.confidence === "ambiguous") {
ctx.strokeStyle = hot ? ink.hot : ink.warn;
ctx.fillStyle = hot ? ink.hot : ink.warn;
ctx.setLineDash([6, 4]);
} else {
ctx.setLineDash(settled ? [] : [2, 3]);
}
ctx.beginPath();
ctx.moveTo(ax, ay);
ctx.lineTo(ex, ey);
ctx.stroke();
ctx.setLineDash([]);
ctx.beginPath();
ctx.moveTo(ex, ey);
ctx.lineTo(ex - Math.cos(angle - 0.4) * 7, ey - Math.sin(angle - 0.4) * 7);
ctx.lineTo(ex - Math.cos(angle + 0.4) * 7, ey - Math.sin(angle + 0.4) * 7);
ctx.closePath();
ctx.fill();
ctx.restore();
}
nodes.forEach((node, i) => {
const on = i === selected;
const beside = near.has(i);
const x = node.x * w - node.w / 2;
const y = node.y * h - node.h / 2;
box(ctx, x, y, node.w, node.h, 8);
ctx.fillStyle = on ? ink.sel : beside ? ink.nearFill : ink.node;
ctx.save();
if (node.hover) {
ctx.shadowColor = ink.nearLine;
ctx.shadowBlur = 14 * node.hover;
}
ctx.fill();
ctx.restore();
ctx.lineWidth = (on ? 1.6 : 1) + 0.8 * node.hover;
ctx.strokeStyle = on ? ink.selLine : beside ? ink.nearLine : ink.nodeLine;
ctx.stroke();
ctx.fillStyle = on ? ink.selText : beside ? ink.nearText : ink.text;
ctx.fillText(node.label, x + 11, y + node.h / 2 + 4);
});
}
function tick() {
if (wrap.isConnected) attached = true;
else if (attached) {
frame = null;
running = false;
observer.disconnect();
return;
}
const box = wrap.getBoundingClientRect();
if (running || dragging) step(Math.max(box.width, 240), Math.max(box.height, 240));
for (const node of nodes) {
node.hover = (node.hover || 0) + ((node === hovered ? 1 : 0) - (node.hover || 0)) * 0.16;
if (node.hover < 0.002) node.hover = 0;
}
paint();
frame = requestAnimationFrame(tick);
}
function at(event) {
const box = canvas.getBoundingClientRect();
return { x: event.clientX - box.left, y: event.clientY - box.top, w: box.width, h: box.height };
}
function hit(point) {
for (let i = nodes.length - 1; i >= 0; i--) {
const node = nodes[i];
const cx = node.x * point.w;
const cy = node.y * point.h;
if (
Math.abs(point.x - cx) < (node.w || 80) / 2 &&
Math.abs(point.y - cy) < (node.h || 28) / 2
) {
return i;
}
}
return null;
}
function selectAt(index) {
selected = index;
near = new Set();
for (const edge of drawn) {
if (edge.from === index) near.add(edge.to);
if (edge.to === index) near.add(edge.from);
}
}
canvas.addEventListener("pointerdown", (event) => {
const index = hit(at(event));
if (index === null) return;
canvas.setPointerCapture(event.pointerId);
dragging = nodes[index];
reheat();
down = { x: event.clientX, y: event.clientY, index };
canvas.classList.add("dragging");
event.preventDefault();
});
canvas.addEventListener("pointermove", (event) => {
const point = at(event);
if (dragging) {
const bound = pen(dragging, point.w, point.h);
dragging.x = Math.min(1 - bound.mx, Math.max(bound.mx, point.x / point.w));
dragging.y = Math.min(1 - bound.my, Math.max(bound.my, point.y / point.h));
if (!frame) paint();
return;
}
const index = hit(point);
const node = index === null ? null : nodes[index];
canvas.classList.toggle("over-node", Boolean(node));
if (node !== hovered) {
hovered = node;
if (!frame) paint();
}
if (onHover) onHover(node, event.clientX, event.clientY);
});
const release = (event) => {
if (down) {
const moved = Math.abs(event.clientX - down.x) + Math.abs(event.clientY - down.y);
if (moved < 4) {
selectAt(down.index);
if (onPick) onPick(nodes[down.index]);
}
}
dragging = null;
down = null;
canvas.classList.remove("dragging");
if (!frame) paint();
};
canvas.addEventListener("pointerup", release);
canvas.addEventListener("pointercancel", release);
canvas.addEventListener("pointerleave", () => {
hovered = null;
dragging = null;
down = null;
canvas.classList.remove("dragging", "over-node");
if (onHover) onHover(null);
if (!frame) paint();
});
const observer = new ResizeObserver(() => {
if (!frame) paint();
});
observer.observe(wrap);
return {
node: wrap,
draw(data, kinds) {
nodes = (data.nodes || []).map((row, i) => {
const angle = (i / Math.max((data.nodes || []).length, 1)) * Math.PI * 2;
return {
...row,
label: row.name.length > 26 ? `${row.name.slice(0, 25)}…` : row.name,
x: 0.5 + Math.cos(angle) * 0.28,
y: 0.5 + Math.sin(angle) * 0.28,
vx: 0,
vy: 0,
phase: Math.random() * Math.PI * 2,
hover: 0,
callers: 0,
callees: 0,
};
});
edges = data.edges || [];
total = data.total || 0;
selected = null;
near = new Set();
hovered = null;
this.filter(kinds);
alpha = 1;
const box = wrap.getBoundingClientRect();
for (let i = 0; i < 220; i++) {
step(Math.max(box.width, 240), Math.max(box.height, 240));
}
settled = true;
paint();
separate();
paint();
},
filter(kinds) {
drawn = kinds ? edges.filter((e) => kinds.has(e.kind)) : edges;
load = nodes.map(() => 1);
for (const node of nodes) {
node.callers = 0;
node.callees = 0;
}
for (const edge of drawn) {
if (nodes[edge.from]) nodes[edge.from].callees += 1;
if (nodes[edge.to]) nodes[edge.to].callers += 1;
if (load[edge.from] !== undefined) load[edge.from] += 1;
if (load[edge.to] !== undefined) load[edge.to] += 1;
}
if (selected !== null) selectAt(selected);
reheat(0.25);
if (settled) paint();
return drawn.length;
},
pick(name) {
const index = nodes.findIndex((node) => node.name === name);
if (index < 0) return false;
selectAt(index);
paint();
if (onPick) onPick(nodes[index]);
return true;
},
counts: () => ({ nodes: nodes.length, edges: drawn.length, total }),
best() {
const score = new Map();
for (const edge of drawn) {
if (edge.confidence !== "extracted" && edge.confidence !== "resolved") continue;
for (const end of [edge.from, edge.to]) {
score.set(end, (score.get(end) || 0) + 1);
}
}
let pick = null;
let most = 0;
for (const [index, count] of score) {
if (count <= most || !nodes[index]) continue;
most = count;
pick = nodes[index].name;
}
return pick;
},
fit() {
if (!nodes.length) return;
const xs = nodes.map((node) => node.x);
const ys = nodes.map((node) => node.y);
const spread = (values, low, high) => {
const min = Math.min(...values);
const max = Math.max(...values);
const span = max - min;
if (span < 0.001) return () => (low + high) / 2;
return (value) => low + ((value - min) / span) * (high - low);
};
const toX = spread(xs, 0.08, 0.92);
const toY = spread(ys, 0.1, 0.9);
for (const node of nodes) {
node.x = toX(node.x);
node.y = toY(node.y);
node.vx = 0;
node.vy = 0;
}
separate();
paint();
},
running: () => running,
toggle() {
running = !running;
if (running) reheat(0.2);
return running;
},
start() {
if (stillness()) {
running = false;
paint();
return;
}
if (!frame) tick();
},
stop() {
running = false;
if (frame) cancelAnimationFrame(frame);
frame = null;
observer.disconnect();
},
repaint: paint,
};
}
const EDGE_KINDS = ["defines", "calls", "imports", "references", "contains", "aliases"];
async function graphView() {
await refreshStores();
const kinds = new Set(EDGE_KINDS);
const chosen = new Set();
const meta = el("span", { class: "graph-count" });
const rail = el("div", { class: "graph-rail" });
const actions = el("div", { class: "rail-actions", hidden: true });
const unreadable = el("div", { class: "unreadable-slot" });
const canvas = graphCanvas({
onPick: (node) => select(node),
onHover: (node, x, y) => {
if (!node) return tip.hide("graph");
tip.atPoint(
x,
y,
tipCard(node.name, "blue", [
["kind", node.kind],
["file", `${shortPath(node.path)}:${node.start_line}-${node.end_line}`],
["store", node.store || (state.stores[0] && state.stores[0].name) || "—"],
["calls", `${node.callers} in · ${node.callees} out`],
]),
"graph",
);
},
});
const pause = el("button", {
class: "button secondary small",
type: "button",
text: "Pause",
onclick: (e) => {
const on = canvas.toggle();
e.currentTarget.textContent = on ? "Pause" : "Resume";
},
});
const fit = el("button", {
class: "button secondary small",
type: "button",
text: "Fit",
onclick: () => canvas.fit(),
});
const reset = el("button", {
class: "button secondary small",
type: "button",
text: "Reset",
onclick: () => load({}),
});
function paintPause() {
pause.textContent = canvas.running() ? "Pause" : "Settled";
}
function counts() {
const { nodes, edges, total } = canvas.counts();
const shown = total && total > nodes ? `${n(nodes)} of ${n(total)} symbols` : `${n(nodes)} symbols`;
meta.textContent = `${shown} · ${n(edges)} edges`;
}
function blank(message) {
actions.hidden = true;
return fill(
rail,
el("div", { class: "graph-selected" }, el("div", { class: "rail-hint", text: message })),
);
}
function ends(title, list, absent, extra) {
return el(
"div",
{ class: "rail-group" },
el("h3", { text: title }),
list.length
? el(
"ul",
{ class: "rail-list" },
list.map((end) => endRow(end)),
)
: el("div", { class: "rail-hint", text: absent }),
extra || null,
);
}
function endRow(end) {
if (end.confidence === "ambiguous") {
const li = el("li", { class: "ambiguous-row" });
let open = false;
const toggle = el("button", {
class: "link-button",
type: "button",
text: `${end.name} · ${end.definitions} definitions`,
"aria-expanded": "false",
onclick: async () => {
open = !open;
toggle.setAttribute("aria-expanded", String(open));
if (!open) return fill(nested);
fill(nested, skeletonRows(2));
let all;
try {
all = await api(
scoped(`/api/neighbors?name=${encodeURIComponent(state.graphSelected)}&all=1`),
);
} catch (e) {
return fill(nested, error(e.message));
}
const each = all.callees.filter((c) => c.name === end.name);
fill(
nested,
el(
"ul",
{ class: "rail-list nested" },
each.map((one) =>
el(
"li",
{},
el("a", {
href: `#graph?name=${encodeURIComponent(one.name)}`,
text: `${shortPath(one.path)}:${one.start_line}`,
onclick: (e) => {
e.preventDefault();
focus(one.name);
},
}),
),
),
),
);
},
});
const nested = el("div", { class: "nested-wrap" });
return fill(li, toggle, confidenceBadge(end.confidence), el("span", { class: "via", text: end.kind }), nested);
}
return el(
"li",
{},
el("a", {
href: `#graph?name=${encodeURIComponent(end.name)}`,
text: end.name,
title: end.name,
onclick: (e) => {
e.preventDefault();
focus(end.name);
},
}),
confidenceBadge(end.confidence),
el("span", { class: "via", text: end.kind }),
);
}
function confidenceLegend() {
return el(
"div",
{ class: "conf-legend" },
Object.entries(CONFIDENCE).map(([value, [label, why]]) =>
el(
"span",
{ class: "legend-chip" },
el("span", { class: `conf ${value}`, text: label }),
el("span", { class: "why", text: why }),
),
),
);
}
async function select(node) {
actions.hidden = true;
fill(rail, skeletonRows(6));
let data;
try {
data = await api(scoped(`/api/neighbors?name=${encodeURIComponent(node.name)}`));
} catch (e) {
return fill(rail, error(e.message));
}
state.graphSelected = node.name;
const edge = (e) => ({
name: e.name,
kind: e.kind,
confidence: e.confidence,
definitions: e.definitions,
path: e.path,
start_line: e.start_line,
});
fill(
rail,
el(
"div",
{ class: "graph-selected" },
el("span", { class: "eyebrow", text: "Selected symbol" }),
el("h2", { class: "sym", text: node.name, title: node.name }),
el("div", {
class: "loc",
"data-tip": node.path,
text: `${shortPath(node.path)}:${node.start_line}-${node.end_line}`,
}),
el(
"div",
{ class: "chips" },
el("span", { class: "chip static blue", text: node.kind }),
node.store ? el("span", { class: "chip static", text: node.store }) : null,
el("span", {
class: "chip static",
text: `${Math.max(node.end_line - node.start_line + 1, 1)} lines`,
}),
),
),
ends("Incoming", data.callers.map(edge), "Nothing in the graph points at this."),
ends(
"Outgoing",
data.callees.map(edge),
"A leaf, as far as the extracted edges go.",
data.hidden
? el("button", {
class: "chip sm",
type: "button",
text: `Show ${data.hidden} unresolved`,
onclick: async (e) => {
const button = e.currentTarget;
button.textContent = "Loading…";
let all;
try {
all = await api(scoped(`/api/neighbors?name=${encodeURIComponent(node.name)}&all=1`));
} catch (err) {
button.replaceWith(error(err.message));
return;
}
button.replaceWith(
el(
"ul",
{ class: "rail-list nested" },
all.unresolved.map((one) =>
el(
"li",
{},
el("span", { text: one.name }),
el("span", { class: "via", text: one.kind }),
),
),
),
);
},
})
: null,
),
confidenceLegend(),
);
fill(
actions,
el("button", {
class: "button secondary small",
type: "button",
text: "Chunks it lives in",
onclick: () => {
state.pendingQuery = node.name;
go("search");
},
}),
el("button", {
class: "button small",
type: "button",
text: "Blast radius",
onclick: () => {
state.impactSymbol = node.name;
state.impactStore = node.store || (chosen.size === 1 ? [...chosen][0] : "");
go("impact");
},
}),
);
actions.hidden = false;
}
function scoped(path) {
const query = new URLSearchParams();
for (const store of chosen) query.append("store", store);
const tail = query.toString();
return tail ? `${path}${path.includes("?") ? "&" : "?"}${tail}` : path;
}
async function load(params) {
meta.textContent = "loading…";
const query = new URLSearchParams(params || {});
for (const store of chosen) query.append("store", store);
let data;
try {
data = await api(`/api/graph?${query}`);
} catch (e) {
meta.textContent = "";
actions.hidden = true;
return fill(rail, error(e.message));
}
fill(unreadable, unreadableNotice(data.failed));
if (!data.nodes.length) {
meta.textContent = "0 symbols";
canvas.draw({ nodes: [], edges: [] }, kinds);
return blank(
data.total === 0
? "This store has no symbols yet. The graph is built as files are indexed — run Index once, or leave the daemon watching."
: "Nothing in this scope. Clear the filters, or pick a store.",
);
}
canvas.draw(data, kinds);
counts();
if (!data.edges.length) {
return blank(
"Nothing in view is connected. Either this scope holds both ends of no " +
"edge — widen it, or clear it — or the language here is one semlith " +
"parses for definitions but not yet for calls. The Index page's " +
"per-language table says which.",
);
}
const centre = (params && params.name) || canvas.best();
if (centre && canvas.pick(centre)) return;
blank("Pick a node to see what calls it and what it calls.");
}
function focus(name) {
load({ name, limit: "45" });
}
const defsCard = el("div", { class: "card table-card graph-defs", hidden: "" });
async function showDefinitions(names) {
const params = new URLSearchParams({ names: names.join(",") });
for (const store of chosen) params.append("store", store);
let data;
try {
data = await api(`/api/symbol?${params}`);
} catch (_) {
return;
}
const rows = data.table || [];
defsCard.hidden = false;
fill(
defsCard,
el(
"div",
{ class: "table-head" },
el("h2", { text: "Definitions" }),
el("span", { class: "muted", text: `${rows.length} for ${names.length} name${names.length === 1 ? "" : "s"}` }),
),
el(
"div",
{ class: "table-wrap" },
el(
"table",
{},
el("caption", { class: "sr-only", text: "Every definition of the names asked for" }),
el(
"thead",
{},
el(
"tr",
{},
el("th", { text: "Definition" }),
el("th", { text: "Kind" }),
el("th", { text: "Where" }),
el("th", { text: "First line" }),
),
),
el(
"tbody",
{},
rows.length
? rows.map((row) =>
el(
"tr",
{ class: "defs-row" },
el("td", { class: "sym" }, el("code", { text: row.qualified })),
el("td", { text: row.kind }),
el("td", {
class: "where path",
title: `${row.path}:${row.start_line}-${row.end_line}`,
text: `${shortPath(row.path)}:${row.start_line}`,
}),
el("td", {}, el("code", { class: "one-line", text: row.signature })),
),
)
: el("tr", {}, el("td", { colspan: "4", class: "muted", text: "No definition of any of these names." })),
),
),
),
);
}
function applyScope() {
const value = scopeInput.value.trim();
defsCard.hidden = true;
if (value.includes(",")) {
const names = value.split(",").map((n) => n.trim()).filter(Boolean).slice(0, 20);
showDefinitions(names);
return names.length ? load({ name: names[0] }) : load({});
}
if (!value) return load({});
load(value.includes("/") || value.includes(".") ? { path: value } : { name: value });
}
const scopeInput = el("input", {
type: "search",
placeholder: "Scope to a path, or find symbols: a, b, c",
"aria-describedby": "graph-scope-hint",
onkeydown: (e) => {
if (e.key !== "Enter") return;
applyScope();
},
});
const scopeButton = el("button", {
class: "button secondary small in-field",
type: "button",
text: "Scope",
onclick: () => applyScope(),
});
const kindChips = EDGE_KINDS.map((kind) =>
el("button", {
class: "chip",
type: "button",
"aria-pressed": "true",
text: kind,
onclick: (e) => {
const on = e.currentTarget.getAttribute("aria-pressed") !== "true";
e.currentTarget.setAttribute("aria-pressed", String(on));
if (on) kinds.add(kind);
else kinds.delete(kind);
canvas.filter(kinds);
canvas.fit();
counts();
},
}),
);
const storeChips = liveStores().map((store) =>
el("button", {
class: "chip",
type: "button",
"aria-pressed": "false",
text: store.name,
onclick: (e) => {
const on = e.currentTarget.getAttribute("aria-pressed") !== "true";
e.currentTarget.setAttribute("aria-pressed", String(on));
if (on) chosen.add(store.name);
else chosen.delete(store.name);
load({});
},
}),
);
const page = el(
"div",
{ class: "graph-page" },
unreadable,
el(
"div",
{ class: "graph-band" },
pageHead(
"Graph",
"Edges are re-extracted on the same pass that re-embeds a file. Never a stale build artifact.",
{ actions: [el("div", { class: "filters" }, kindChips), fit, reset, pause] },
),
el(
"div",
{ class: "graph-controls" },
el(
"div",
{ class: "graph-scope" },
icon(ICONS.search, 16),
labelled("graph-scope", "Scope the graph", scopeInput),
el("span", {
class: "sr-only",
id: "graph-scope-hint",
text: "Enter applies it",
}),
scopeButton,
),
storeChips.length > 1 ? el("div", { class: "filters" }, storeChips) : null,
),
defsCard,
),
el(
"div",
{ class: "graph-body" },
el(
"div",
{ class: "graph-frame" },
canvas.node,
el(
"div",
{ class: "graph-legend" },
el("span", { class: "key extracted" }, el("i", {}), "extracted"),
el("span", { class: "key resolved" }, el("i", {}), "resolved"),
el("span", { class: "key inferred" }, el("i", {}), "inferred"),
el("span", { class: "key ambiguous" }, el("i", {}), "ambiguous"),
el("span", { class: "key selected" }, el("i", {}), "selected"),
),
meta,
),
el("div", { class: "graph-side" }, el("div", { class: "graph-scroll" }, rail, mapPanel()), actions),
),
);
blank("Pick a node to see what calls it and what it calls.");
setTimeout(async () => {
canvas.start();
paintPause();
const pending = state.pendingSymbol;
state.pendingSymbol = "";
if (pending) return load({ name: pending, limit: "45" });
try {
const overview = await api("/api/graph?limit=1");
const busiest = (overview.nodes || [])[0];
if (busiest) return focus(busiest.name);
} catch (_) {
}
load({});
}, 0);
return page;
}
function ringed(node) {
node.classList.add("ringed");
return node;
}
function stat(label, value, note) {
return el(
"div",
{ class: "stat" },
el("span", { class: "eyebrow", text: label }),
el("span", { class: "value", text: value }),
note ? el("span", { class: "sub", text: note }) : null,
);
}
async function ledgerView() {
let data;
try {
data = await api("/api/ledger");
} catch (e) {
return el("div", { class: "view" }, pageHead("Retrieval ledger"), error(e.message));
}
const on = data.recording;
watchLive(["ledger"], repaintView);
return el(
"div",
{ class: "view" },
pageHead(
"Retrieval ledger",
"Every query an agent ran, recorded locally. The honest token number, a debugging trail, and an audit record that never left the machine.",
{
pill: on
? pill("recording", "on", {
title:
"On by default. Rows live in the store beside the chunks and never leave this machine.",
})
: pill("not recording", null),
actions: [
el("button", {
class: "button secondary",
type: "button",
text: "Build a report",
onclick: () => go("reports"),
}),
],
},
),
on
? null
: el(
"div",
{ class: "notice" },
el("div", { class: "what", text: "Recording is off" }),
el(
"div",
{},
"The daemon was started with ",
mono("--no-ledger"),
". Start it without that flag to record what your agents retrieve. Nothing is sent anywhere; the rows live in the store beside the chunks.",
),
),
el(
"div",
{ class: "strip" },
stat(
"Queries recorded",
n(data.queries),
`${n(data.clients)} client${data.clients === 1 ? "" : "s"}`,
),
stat("Excerpt tokens", n(data.excerpt_tokens), "what agents were actually sent"),
stat(
"Whole-file tokens",
n(data.whole_file_tokens),
"what reading those files whole would have cost, counted with the store's tokenizer",
),
ringed(
stat(
"Measured ratio",
data.ratio ? `${data.ratio.toFixed(1)}×` : "—",
data.ratio ? `coverage ${data.coverage}% · ${data.tier}` : "needs a recorded query",
),
),
stat(
"Coverage",
`${data.coverage || 0}%`,
`${n(data.credited || 0)} of ${n(data.queries)} retrievals credited`,
),
stat(
"Net tokens",
n(data.net_tokens || 0),
"whole-file less excerpt, over rows that found something",
),
),
el(
"div",
{ class: "strip" },
stat(
"Zero-hit",
`${share(data.zero_hit || 0, data.queries || 0)}`,
`${n(data.zero_hit || 0)} of ${n(data.queries)} queries the corpus could not answer — recorded, and credited nothing`,
),
stat(
"Refunds",
`${n(data.refunds || 0)}${data.refunds_measured ? "" : "+"}`,
data.refunds_measured
? "files read whole after all, seen by the steering hook — measured"
: "a floor: no steering hook reports here, so reads semlith never served are uncounted",
),
stat("Tier", data.tier || "modelled", "modelled · measured — measured when the store's own tokenizer counted it"),
),
clientBreakdown(data.by_client),
el(
"div",
{ class: "grid three" },
el(
"div",
{ class: "card pad dense" },
copyField("semlith ledger --last 20"),
el("p", {
class: "subtitle",
text: "Prints the ledger on the command line. The same rows this page shows.",
}),
),
el(
"div",
{ class: "card pad dense" },
copyField("semlith ledger --verify"),
el("p", {
class: "subtitle",
text: "Re-walks the hash chain and names the first row that does not verify.",
}),
el("p", {
class: "subtitle",
text: data.intact
? "The chain is intact."
: "The chain does not verify. Some rows have been edited or removed.",
}),
),
el(
"div",
{ class: "card pad dense" },
copyField("semlith start --no-ledger"),
el("p", {
class: "subtitle",
text: "Run the daemon without recording, for this session only. SEMLITH_LEDGER=0 does the same for a machine.",
}),
),
),
ledgerSessions(data),
ledgerTabs(data),
says(
"Stored in ",
mono("~/.semlith/stores/<name>/store.db"),
", table ",
mono("retrievals"),
". On by default; ",
mono("--no-ledger"),
" or ",
mono("SEMLITH_LEDGER=0"),
" turns it off, and deleting the rows is one ",
mono("DELETE"),
".",
),
);
}
function sessionReplayToggle() {
const state_ = { enabled: false, from: "", client: "" };
const knob = el("span", { class: "knob", "aria-hidden": "true" });
const status = el("span", { class: "replay-state" });
const row = el(
"button",
{ class: "replay-switch", type: "button", role: "switch", "aria-checked": "false", disabled: true },
knob,
el(
"span",
{ class: "replay-switch-text" },
status,
el("span", {
class: "replay-switch-note",
text: "Session replay reads this machine's agent session logs to confirm what an agent did after a semlith answer. Off by default. Local only. Nothing is uploaded.",
}),
),
);
const failure = el("p", { class: "note" });
function paint() {
row.disabled = false;
row.classList.toggle("on", state_.enabled);
row.setAttribute("aria-checked", String(state_.enabled));
status.textContent = state_.enabled
? `On · reading local agent logs${state_.from ? ` under ${state_.from}` : ""}`
: "Off · no agent log is opened";
}
row.addEventListener("click", async () => {
row.disabled = true;
failure.textContent = "";
try {
const answer = await api("/api/ledger/replay", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ on: !state_.enabled }),
});
state_.enabled = !!answer.enabled;
} catch (e) {
failure.textContent = e.message;
row.disabled = false;
return;
}
paint();
});
(async () => {
try {
const data = await api("/api/ledger/replay");
state_.enabled = !!data.enabled;
state_.from = data.from || "";
state_.client = data.client || "";
} catch (_) {
}
paint();
})();
return el(
"div",
{ class: "card pad" },
el("div", { class: "card-head" }, el("h2", { text: "Session replay" })),
row,
failure,
);
}
function ledgerTabs(data) {
const panel = el("div", { class: "tab-panel" });
const views = [
["Retrievals", () => ledgerRows(data)],
["Session replay", () => sessionReplay(data)],
];
const buttons = views.map(([label], i) =>
el("button", {
class: "tab",
type: "button",
text: label,
"aria-pressed": String(i === 0),
onclick: () => show(i),
}),
);
function show(index) {
buttons.forEach((b, i) => b.setAttribute("aria-pressed", String(i === index)));
fill(panel, views[index][1]());
}
show(0);
return el("div", { class: "rows tight" }, el("div", { class: "tabs" }, buttons), panel);
}
function sessionReplay(ledger) {
const body = el("div", { class: "replay-body" });
const meta = el("span", { class: "replay-meta", text: "reading…" });
const rows = (ledger && ledger.rows) || [];
function costOf(query) {
if (!query) return "";
const row = rows.find((r) => (r.query || "") === query);
if (!row) return "";
const hits = row.hits === undefined ? null : row.hits;
const tokens = row.net_tokens === undefined ? row.tokens : row.net_tokens;
const parts = [];
if (hits !== null) parts.push(`${hits} hit${hits === 1 ? "" : "s"}`);
if (tokens !== undefined && tokens !== null) parts.push(`${n(tokens)} tokens`);
return parts.join(" · ");
}
function clockOf(at) {
if (!at) return "—";
const when = new Date(at);
if (Number.isNaN(when.getTime())) return "—";
return when.toLocaleTimeString(undefined, { hour12: false });
}
function answerRow(answer) {
const cost = costOf(answer.query);
return el(
"div",
{ class: "replay-answer" },
el("span", { class: "at", text: clockOf(answer.at) }),
el(
"div",
{ class: "replay-what" },
el(
"div",
{ class: "line one" },
el("span", { class: "q", text: answer.query || answer.tool }),
el("span", { class: "muted", text: answer.query ? cost || answer.tool : cost }),
),
el("span", { class: `replay-badge ${answer.outcome}`, text: answer.word }),
),
);
}
function counts(session) {
const shown = [
["read the whole file", session.refund, "refund"],
["grepped anyway", session.miss, "miss"],
["edited", session.sufficed, "sufficed"],
["nothing recorded", session.unknown, "unknown"],
];
return el(
"span",
{ class: "replay-counts" },
shown.map(([label, count, kind]) =>
count ? el("span", { class: `replay-count ${kind}`, text: `${count} ${label}` }) : null,
),
);
}
function offPanel() {
return el(
"div",
{ class: "replay-off" },
el("p", {
text: "Session replay is off. Nothing is read from your agent logs until you turn it on.",
}),
el("button", {
class: "button secondary small",
type: "button",
text: "Open Privacy",
onclick: () => go("privacy"),
}),
);
}
async function load() {
let data;
try {
data = await api("/api/ledger/replay");
} catch (e) {
meta.textContent = "";
fill(body, error(e.message));
return;
}
const client = data.client === "claude-code" ? "Claude Code" : data.client || "an agent";
if (!data.enabled) {
meta.textContent = `off · ${client}`;
fill(body, offPanel());
return;
}
if (!data.sessions.length) {
meta.textContent = `on · ${client} · no transcript yet`;
fill(
body,
el("p", {
class: "subtitle",
text: `On, and no transcript under ${data.from || "this machine"} holds a semlith call yet.`,
}),
);
return;
}
const answers = data.sessions.reduce((sum, session) => sum + session.answers, 0);
const newest = data.sessions[0];
meta.textContent = `${newest.id.slice(0, 12)} · ${client} · ${n(answers)} quer${answers === 1 ? "y" : "ies"}`;
fill(
body,
el("p", {
class: "note",
text: `Read from ${client} transcripts under ${data.from}${data.skipped ? `, ${data.skipped} older transcript${data.skipped === 1 ? "" : "s"} not read` : ""}.`,
}),
data.sessions.map((session) =>
el(
"section",
{ class: "replay-row" },
el(
"span",
{ class: "line one" },
el("span", { class: "id", text: session.id.slice(0, 12) }),
el("span", { class: "muted", text: session.project }),
el("span", { class: "spacer" }),
el("span", {
class: "muted",
text: `${session.answers} answer${session.answers === 1 ? "" : "s"}`,
}),
),
counts(session),
(session.recent || []).map(answerRow),
session.answers > (session.recent || []).length
? el("p", {
class: "rail-hint",
text: `The last ${(session.recent || []).length} of ${session.answers}.`,
})
: null,
),
),
);
}
load();
return el(
"div",
{ class: "card pad" },
el(
"div",
{ class: "card-head" },
el("h2", { text: "Session replay" }),
meta,
),
el("p", {
class: "subtitle",
text: "Read from this machine's agent session logs, never sent anywhere. Turn on under Privacy.",
}),
body,
);
}
const MODEL_PRICES = [
["Sonnet 5", 3, "sonnet_5"],
["Opus 5", 15, "opus_5"],
["Haiku 4.5", 1, "haiku_4_5"],
];
function offerDownload(name, text, type) {
const blob = new Blob([text], { type: `${type};charset=utf-8` });
const url = URL.createObjectURL(blob);
const link = el("a", { href: url, download: name });
document.body.append(link);
link.click();
link.remove();
setTimeout(() => URL.revokeObjectURL(url), 0);
}
function exportRows(columns, rows, format, name) {
const cells = (row) => columns.map(([, read]) => String(read(row) ?? ""));
const heads = columns.map(([head]) => head);
if (format === "json") {
const out = rows.map((row) => {
const one = {};
columns.forEach(([head, read]) => {
one[head] = read(row);
});
return one;
});
offerDownload(`${name}.json`, JSON.stringify(out, null, 2), "application/json");
return;
}
if (format === "csv") {
const quote = (v) =>
v.includes('"') || v.includes(",") || v.includes("\n")
? `"${v.split('"').join('""')}"`
: v;
const lines = [heads.map(quote).join(","), ...rows.map((row) => cells(row).map(quote).join(","))];
offerDownload(`${name}.csv`, `${lines.join("\n")}\n`, "text/csv");
return;
}
const lines = [
`| ${heads.join(" | ")} |`,
`| ${heads.map(() => "---").join(" | ")} |`,
...rows.map((row) => `| ${cells(row).join(" | ")} |`),
];
offerDownload(`${name}.md`, `${lines.join("\n")}\n`, "text/markdown");
}
function ledgerSessions(data) {
const all = data.sessions || [];
let client = "";
let tier = "";
let price = MODEL_PRICES[0];
const table = dataTable({
className: "w-sessions",
sort: "last",
dir: "desc",
rows: [],
caption: "Every agent session this machine recorded, newest first.",
columns: [
{
key: "last",
label: "Last seen",
className: "meta",
value: (r) => r.last,
render: (r) => el("span", { class: "one-line", title: r.when, text: r.when }),
},
{
key: "session",
label: "Session",
className: "meta",
render: (r) =>
el("span", {
class: "one-line",
title: r.session || "recorded before sessions were",
text: r.session ? r.session.slice(0, 12) : "—",
}),
},
{ key: "client", label: "Agent", className: "meta", render: (r) => r.client },
{ key: "store", label: "Store", className: "meta narrow-drop", render: (r) => r.store },
{ key: "retrievals", label: "Reads", className: "num", value: (r) => r.retrievals, render: (r) => n(r.retrievals) },
{
key: "net_tokens",
label: "Net tokens",
className: "num",
value: (r) => r.net_tokens,
render: (r) => n(r.net_tokens),
},
{
key: "cost",
label: "Cost",
className: "num",
value: (r) => r.net_tokens,
render: (r) => `$${((r.net_tokens / 1e6) * price[1]).toFixed(2)}`,
},
{ key: "tier", label: "Tier", className: "meta", render: (r) => r.tier },
],
});
function shown() {
return all.filter(
(row) => (!client || row.client === client) && (!tier || row.tier === tier),
);
}
function repaint() {
const rows = shown();
table.update(rows, rows.length);
count.textContent = `${rows.length} of ${all.length} session${all.length === 1 ? "" : "s"}`;
}
const count = el("span", { class: "muted" });
const clients = [...new Set(all.map((row) => row.client))].sort();
const clientPick = el(
"select",
{
class: "chip",
"aria-label": "Filter by client",
onchange: (e) => {
client = e.currentTarget.value;
repaint();
},
},
el("option", { value: "", text: "every client" }),
clients.map((one) => el("option", { value: one, text: one })),
);
const tierPick = el(
"select",
{
class: "chip",
"aria-label": "Filter by tier",
onchange: (e) => {
tier = e.currentTarget.value;
repaint();
},
},
el("option", { value: "", text: "every tier" }),
el("option", { value: "measured", text: "measured" }),
el("option", { value: "modelled", text: "modelled" }),
);
const modelPick = el(
"select",
{
class: "chip",
"aria-label": "Cost at",
onchange: (e) => {
price = MODEL_PRICES[Number(e.currentTarget.value)] || MODEL_PRICES[0];
repaint();
},
},
MODEL_PRICES.map(([name], i) =>
el("option", { value: String(i), text: `cost at ${name}` }),
),
);
const columns = () => [
["when", (r) => r.when],
["session", (r) => r.session],
["agent", (r) => r.client],
["store", (r) => r.store],
["reads", (r) => r.retrievals],
["zero_hit", (r) => r.zero_hit],
["net_tokens", (r) => r.net_tokens],
[`cost_usd_at_${price[2]}`, (r) => ((r.net_tokens / 1e6) * price[1]).toFixed(2)],
["tier", (r) => r.tier],
];
const exports = ["Markdown", "CSV", "JSON"].map((label) =>
el("button", {
class: "button secondary small",
type: "button",
text: label,
onclick: () =>
exportRows(
columns(),
shown(),
label === "Markdown" ? "md" : label.toLowerCase(),
"semlith-sessions",
),
}),
);
repaint();
return el(
"div",
{ class: "card pad" },
el("span", { class: "card-title", text: "Sessions" }),
el("div", { class: "filters" }, clientPick, tierPick, modelPick, count, el("span", { class: "spacer" }), exports),
all.length
? table.node
: empty("No session has recorded a retrieval yet."),
el("p", {
class: "subtitle",
text: "Net tokens are whole-file less excerpt, over the reads that found something. A session counted by the four-character fallback is modelled, not measured, and says so in its own row.",
}),
);
}
function ledgerRows(data) {
const rows = data.rows || [];
const table = dataTable({
className: "w-ledger",
sort: "at",
dir: "desc",
rows,
caption: "Every retrieval recorded on this machine, newest first.",
columns: [
{
key: "at",
label: "When",
className: "meta",
value: (r) => r.at,
render: (r) => el("span", { class: "one-line", title: r.when, text: r.when }),
},
{ key: "client", label: "Client", className: "meta", render: (r) => r.client },
{ key: "store", label: "Store", className: "meta narrow-drop", render: (r) => r.store },
{
key: "query",
label: "Query",
className: "path",
render: (r) => el("span", { class: "one-line", title: r.query, text: r.query }),
},
{ key: "hits", label: "Hits", className: "num", render: (r) => n(r.hits) },
{
key: "ms",
label: "ms",
className: "num narrow-drop",
render: (r) => n(r.ms),
},
],
});
table.update(rows, rows.length);
return el(
"div",
{ class: "rows tight" },
rows.length
? table.node
: empty("Nothing recorded yet. A search from any client writes a row here."),
data.legacy_rows
? el("p", {
class: "subtitle",
text: "Some rows here are older than the query id, and were written once per open store — figures that include them may count one search several times. They are left as they are: the chain is never rewritten.",
})
: null,
);
}
function clientBreakdown(byClient) {
const entries = Object.entries(byClient || {}).sort((a, b) => b[1] - a[1]);
if (!entries.length) return null;
return el(
"div",
{ class: "client-breakdown" },
el("span", { class: "eyebrow sm", text: "by client" }),
entries.map(([client, count], i) =>
el(
"span",
{ class: "client" },
i ? el("span", { class: "sep", text: "·" }) : null,
el("span", { class: "who", text: client }),
el("span", { class: "count", text: n(count) }),
),
),
el("span", {
class: "client-note",
text: "— MCP, HTTP and CLI, under the client's own name",
}),
);
}
function liveStores() {
return state.stores.filter((store) => {
if (store.missing || store.unopened) return false;
const roots = store.roots || [];
return !roots.length || roots.some((root) => root.present);
});
}
let storesAsked = 0;
let storesRead = 0;
async function refreshStores() {
const mine = ++storesAsked;
try {
const data = await api("/api/stores");
if (mine < storesRead) return state.stores;
storesRead = mine;
state.stores = data.stores || [];
state.failed = data.failed || [];
paintStoreCount();
} catch (_) {
}
return state.stores;
}
function agentsCard() {
const card = el("div", { class: "card pad dense" });
const body = el("div", { class: "rows" });
fill(
card,
el("span", { class: "card-title", text: "Agents connected" }),
body,
el("span", { class: "spacer" }),
el("button", {
class: "link-button",
type: "button",
text: "Copy config for another client",
onclick: () => go("agents"),
}),
);
fill(body, skeletonRows(3));
api("/api/agents")
.then((data) => {
noteAgents(data);
watchLive(["clients"], repaintView);
const live = data.connections || [];
if (!live.length) {
fill(
body,
el("div", {
class: "rail-hint",
text: "No client is talking to this daemon right now.",
}),
);
return;
}
fill(
body,
live.map((client) =>
el(
"div",
{ class: "kv" },
el("span", { text: client.name }),
el("span", { class: "spacer" }),
el("span", {
class: "meta",
text: `${client.transport} · ${n(client.queries)} quer${
client.queries === 1 ? "y" : "ies"
}`,
}),
),
),
);
})
.catch((e) => fill(body, error(e.message)));
return card;
}
function ask({ title, body, extra, lead, tail, confirm, tone, run, wide }) {
const dialog = el("dialog", { class: wide ? "modal wide" : "modal" });
const problem = el("div", { class: "note" });
const go = el("button", {
class: tone === "bad" ? "button danger" : "button",
type: "button",
text: confirm,
onclick: async () => {
go.disabled = true;
const was = go.textContent;
go.textContent = "Working…";
problem.className = "note";
problem.textContent = "";
try {
await run();
dialog.close();
} catch (e) {
problem.className = "note bad";
problem.textContent = e.message;
go.disabled = false;
go.textContent = was;
}
},
});
fill(
dialog,
el("div", { class: "modal-head" }, el("h2", { class: "card-title", text: title }), el("p", { class: "subtitle", text: body }), lead || null),
extra ? el("div", { class: "modal-body" }, extra) : null,
el("div", { class: "modal-foot" }, tail || null, problem),
el(
"div",
{ class: "actions" },
el("span", { class: "spacer" }),
el("button", {
class: "button secondary",
type: "button",
text: "Cancel",
onclick: () => dialog.close(),
}),
go,
),
);
dialog.addEventListener("close", () => dialog.remove());
dialog.addEventListener("click", (e) => {
if (e.target === dialog) dialog.close();
});
document.body.append(dialog);
dialog.showModal();
return dialog;
}
function confirmDelete(name) {
ask({
title: `Delete ${name}?`,
body: "Its vectors, chunks, graph and ledger are deleted, and the registry stops listing it. The files it indexed are untouched.",
confirm: `Delete ${name}`,
tone: "bad",
run: async () => {
const done = await post("/api/store/delete", { store: name });
await refreshStores();
await render();
note(done.message);
},
});
}
function note(text, bad) {
const holder = document.querySelector(".view > .note.page-note");
if (!holder) return;
holder.className = bad ? "note page-note bad" : "note page-note";
holder.textContent = text;
}
const storesPicked = new Set();
async function storesView() {
const stores = await refreshStores();
for (const name of storesPicked) {
if (!stores.some((s) => s.name === name)) storesPicked.delete(name);
}
watchLive(["stores", "events"], repaintView);
const rootNote = el("div", { class: "note" });
const rootPicker = folderPicker({
choose: "Point the store here",
onError: (message) => {
rootNote.className = "note bad";
rootNote.textContent = message;
},
onChoose: async (path) => {
rootNote.className = "note";
rootNote.textContent = "Re-pointing…";
try {
const done = await post("/api/root", { store: rootPicker.store, root: path });
rootNote.textContent = done.message;
await refreshStores();
} catch (e) {
rootNote.className = "note bad";
rootNote.textContent = e.message;
}
},
});
function repoint(store, missing) {
rootPicker.store = store;
rootNote.className = "note";
rootNote.textContent = `Choose where ${missing.split("/").pop() || missing} lives now.`;
rootPicker.open("");
}
const adoptNote = el("div", { class: "note" });
const picker = folderPicker({
choose: "Adopt this directory",
onError: (message) => {
adoptNote.className = "note bad";
adoptNote.textContent = message;
},
onChoose: async (path) => {
adoptNote.className = "note";
adoptNote.textContent = "Adopting…";
try {
const done = await post("/api/adopt", { path });
adoptNote.textContent =
done.message || `${path} is registered. It joins on the next daemon start.`;
await refreshStores();
} catch (e) {
adoptNote.className = "note bad";
adoptNote.textContent = e.message;
picker.open(path);
}
},
});
const head = pageHead("Stores", "Everything indexed on this machine. Nothing leaves it.", {
actions: [
el(
"button",
{ class: "button secondary", type: "button", onclick: () => picker.open("") },
icon(ICONS.folder),
"Adopt existing .semlith",
),
el(
"button",
{ class: "button", type: "button", onclick: () => go("index") },
icon(ICONS.plus),
"Index a folder",
),
],
});
if (!stores.length) {
return el(
"div",
{ class: "view" },
head,
picker.node,
adoptNote,
empty("No store is open. Index a folder and it appears here."),
);
}
const totals = stores.reduce(
(sum, s) => ({
files: sum.files + s.files,
chunks: sum.chunks + s.chunks,
bytes: sum.bytes + s.bytes,
lines: sum.lines + (s.lines || 0),
watching: sum.watching + (s.watching ? 1 : 0),
formats: Math.max(sum.formats, s.formats || 0),
readers: Math.max(sum.readers, s.readers || 0),
}),
{ files: 0, chunks: 0, bytes: 0, lines: 0, watching: 0, formats: 0, readers: 0 },
);
const dim = stores.find((s) => s.dim)?.dim;
const insideLink = el(
"button",
{ class: "table-follow", type: "button", onclick: () => go("corpus") },
el("span", { text: "See what is actually inside the index" }),
icon(ICONS.arrowRight, 15),
);
const bulkBar = el("div", { class: "bulk", hidden: true });
function paintBulk() {
bulkBar.hidden = storesPicked.size === 0;
if (!storesPicked.size) return;
const names = [...storesPicked].sort();
const many = `${n(names.length)} store${names.length === 1 ? "" : "s"}`;
fill(
bulkBar,
el("span", { class: "meta", text: `${many} selected` }),
el("span", { class: "spacer" }),
el("button", {
class: "button secondary small",
type: "button",
text: "Clear",
onclick: () => {
storesPicked.clear();
for (const box of table.node.querySelectorAll("input.pick")) box.checked = false;
paintBulk();
},
}),
el("button", {
class: "button danger small",
type: "button",
text: `Delete ${many}`,
onclick: () =>
ask({
title: `Delete ${many}?`,
body: `${names.join(", ")}: their vectors, chunks, graph and ledger are deleted, and the registry stops listing them. The files they indexed are untouched.`,
confirm: `Delete ${many}`,
tone: "bad",
run: async () => {
const done = await post("/api/store/delete", { stores: names });
for (const name of done.deleted || []) storesPicked.delete(name);
await refreshStores();
await render();
note(done.message, (done.failed || []).length > 0);
},
}),
}),
);
}
const table = dataTable({
className: "w-stores",
remember: "stores",
caption: "Every store on this machine: where it is, what it holds, and when it was last written to.",
sort: "name",
rows: stores,
columns: [
{
key: "pick",
label: "",
className: "pick",
sortable: false,
head: () => {
const all = el("input", {
type: "checkbox",
class: "pick all",
"aria-label": "Select every store on this page",
onchange: () => {
for (const box of table.node.querySelectorAll("tbody input.pick")) {
if (box.checked !== all.checked) box.click();
}
},
});
return all;
},
render: (s) => {
const box = el("input", {
type: "checkbox",
class: "pick",
"aria-label": `Select ${s.name}`,
onchange: () => {
if (box.checked) storesPicked.add(s.name);
else storesPicked.delete(s.name);
paintBulk();
},
});
box.checked = storesPicked.has(s.name);
return box;
},
},
{
key: "name",
label: "Store",
value: (s) => s.name,
render: (s) =>
el(
"div",
{ class: "rows tight" },
lineCell(s.name, "name"),
pathCell(s.dir, "meta"),
s.missing
? el(
"div",
{ class: "chips" },
pill("missing", "bad", { title: `nothing at ${s.dir}` }),
el("span", {
class: "meta",
text: `nothing at ${s.dir} — delete the entry, or put the store back`,
}),
)
: null,
!s.missing && (s.roots || []).length && !(s.roots || []).some((r) => r.present)
? el(
"div",
{ class: "chips" },
pill("root missing", "bad", {
title: (s.roots || []).map((r) => r.path).join(", "),
}),
el("span", {
class: "meta",
text: `nothing at ${(s.roots || []).map((r) => r.path).join(", ")} — re-point it, or delete the store`,
}),
)
: null,
s.pruned
? el(
"div",
{ class: "chips" },
pill(`${n(s.pruned)} reconciled`, "warn"),
el("span", {
class: "meta",
text: "files it held from outside its roots, dropped when this daemon opened it. The files on disk are untouched.",
}),
)
: null,
pathCell((s.roots || []).map((r) => r.path).join(", ") || "no roots", "meta only-narrow"),
lineCell(`${s.model} · ${s.dim} dims`, "meta"),
s.trusted === false
? el(
"div",
{ class: "chips" },
pill("not trusted", "warn"),
el("button", {
class: "button secondary small",
type: "button",
text: "Trust this store",
onclick: async (e) => {
const button = e.currentTarget;
button.disabled = true;
adoptNote.className = "note";
adoptNote.textContent = "Trusting…";
try {
await post("/api/trust", { path: s.dir });
adoptNote.textContent = `${s.dir} is trusted. semlith opens it from its own directory now, without --store.`;
await refreshStores();
} catch (err) {
adoptNote.className = "note bad";
adoptNote.textContent = err.message;
button.disabled = false;
}
},
}),
)
: null,
s.loose_mode
? el(
"div",
{ class: "chips" },
pill(`mode ${s.loose_mode}`, "warn"),
el("span", {
class: "meta",
text: "other users on this machine can read what this store indexed; the next open narrows it to 700",
}),
)
: null,
),
},
{
key: "roots",
label: "Roots",
className: "meta narrow-drop",
sortable: false,
render: (s) =>
(s.roots || []).length
? s.roots.map((r) =>
r.present
? pathCell(r.path)
: el(
"div",
{ class: "gone-row" },
pathCell(r.path, "gone"),
el("button", {
class: "button danger small",
type: "button",
text: "Re-point",
onclick: () => repoint(s.name, r.path),
}),
),
)
: "—",
},
{ key: "files", label: "Files", className: "num", value: (s) => s.files, render: (s) => n(s.files) },
{
key: "chunks",
label: "Chunks",
className: "num narrow-drop",
value: (s) => s.chunks,
render: (s) => n(s.chunks),
},
{
key: "saved",
label: "Saved",
className: "num narrow-drop",
value: (s) => (s.savings ? s.savings.net_tokens : -1),
render: (s) => {
if (!s.savings || !s.savings.total) {
return el("span", { class: "meta", text: "—" });
}
return el(
"span",
{
class: "meta",
title: `${n(s.savings.net_tokens)} tokens saved over ${n(s.savings.credited)} of ${n(s.savings.total)} retrievals, counted ${s.savings.tier}`,
},
`${n(s.savings.net_tokens)} · ${s.savings.coverage}% · ${s.savings.tier}`,
);
},
},
{
key: "last_write",
label: "Last write",
value: (s) => s.last_write || 0,
render: (s) => {
return s.last_write
? pill(when(s.last_write), "good")
: el("span", { class: "meta", text: "—" });
},
},
{
key: "open",
label: "",
className: "row-end",
sortable: false,
render: (s) =>
rowMenu(() => [
{
label: "Open in Files",
onclick: () => {
state.pendingStore = s.name;
go("files");
},
},
{
label: "Delete store…",
tone: "bad",
onclick: () => confirmDelete(s.name),
},
]),
},
],
});
const feed = stores
.flatMap((s) => (s.events || []).map((e) => ({ at: e.at, text: e.text })))
.sort((a, b) => b.at - a.at)
.slice(0, 40);
paintBulk();
return el(
"div",
{ class: "view" },
head,
el(
"div",
{ class: "strip" },
stat("Stores", n(stores.length), `${totals.watching} being watched`),
stat("Files", n(totals.files), `${n(totals.formats)} formats`),
stat("Chunks", n(totals.chunks), dim ? `${dim}-dimension vectors` : "embedded and searchable"),
stat(
"Lines",
n(totals.lines),
`across ${n(totals.files)} file${totals.files === 1 ? "" : "s"}, read by ${n(
totals.readers,
)} reader${totals.readers === 1 ? "" : "s"}`,
),
stat("On disk", bytes(totals.bytes), "int8 quantised"),
),
bulkBar,
el("div", { class: "note page-note", role: "status", "aria-live": "polite" }),
el(
"div",
{ class: "scroller" },
appended(table.node, insideLink),
el(
"div",
{ class: "grid fill" },
el(
"div",
{ class: "card pad dense" },
el(
"div",
{ class: "head" },
el("span", { class: "card-title", text: "Watcher" }),
el("span", { class: "meta", text: "live" }),
),
feed.length
? el(
"div",
{ class: "feed" },
feed.map((e) =>
el(
"div",
{ class: "row" },
el("span", {
class: "at",
text: clock(e.at),
title: clock(e.at),
}),
lineCell(e.text, "what"),
),
),
)
: empty("Nothing has changed on disk since the daemon started."),
),
agentsCard(),
),
),
picker.node,
adoptNote,
rootPicker.node,
rootNote,
);
}
const FILES_PER_REQUEST = 500;
const FILES_SELECTABLE = 10_000;
async function filesView() {
const chosenExt = new Set();
const summary = el("span", { class: "pill" });
const holder = el("div", {});
const unreadable = el("div", { class: "unreadable-slot" });
const announcer = el("div", { class: "sr-only", role: "status", "aria-live": "polite" });
let justForgot = false;
const storeFilter = storePicker(state.pendingStore || "", () => load(true));
state.pendingStore = "";
const footnote = el("p", {
class: "subtitle",
text: "Forget drops the file's chunks and vectors. The file on disk is untouched.",
});
const pathInput = el("input", {
type: "text",
placeholder: "path glob, e.g. src/**",
oninput: () => {
clearTimeout(load.timer);
load.timer = setTimeout(() => load(true), 200);
},
});
let generation = 0;
async function forget(path, row, store) {
try {
await post("/api/forget", { path, store });
row.remove();
picked.delete(path);
paintBulk();
justForgot = true;
announcer.textContent = `${path} forgotten. Its chunks and vectors are gone from ${store}; the file on disk is untouched.`;
load();
} catch (e) {
bulkNote.className = "note bad";
bulkNote.textContent = e.message;
throw e;
}
}
const picked = new Map();
const bulkNote = el("div", { class: "note", role: "status", "aria-live": "polite" });
const bulkBar = el("div", { class: "bulk", hidden: true });
function paintBulk() {
bulkBar.hidden = picked.size === 0;
if (!picked.size) return;
const many = picked.size;
fill(
bulkBar,
el("span", {
class: "meta",
text: `${n(many)} file${many === 1 ? "" : "s"} selected`,
}),
matches > many
? matches <= FILES_SELECTABLE
? el("button", {
class: "button secondary small",
type: "button",
text: `Select all ${n(matches)} matches`,
onclick: () => selectEveryMatch(),
})
: el("span", {
class: "meta",
text: `${n(matches)} files match — narrow the filter to ${n(
FILES_SELECTABLE,
)} or fewer to select them all`,
})
: null,
el("span", { class: "spacer" }),
el("button", {
class: "button secondary small",
type: "button",
text: "Clear",
onclick: () => {
picked.clear();
for (const box of table.node.querySelectorAll("input.pick")) box.checked = false;
paintBulk();
},
}),
el("button", {
class: "button danger small",
type: "button",
text: `Forget ${n(many)} file${many === 1 ? "" : "s"}`,
onclick: (e) => {
const button = e.currentTarget;
const stores = [...new Set(picked.values())];
ask({
title: `Forget ${n(many)} file${many === 1 ? "" : "s"}?`,
body: `Their chunks and vectors are dropped from ${stores.join(
" and ",
)}. The files on disk are untouched, and indexing the folder again brings them back.`,
confirm: `Forget ${n(many)} file${many === 1 ? "" : "s"}`,
tone: "bad",
run: () => forgetPicked(button),
});
},
}),
);
}
async function forgetPicked(button) {
button.disabled = true;
button.textContent = "Forgetting…";
bulkNote.className = "note";
bulkNote.textContent = "";
const byStore = new Map();
for (const [path, store] of picked) {
if (!byStore.has(store)) byStore.set(store, []);
byStore.get(store).push(path);
}
try {
let files = 0;
let forgot = 0;
let absent = 0;
for (const [store, paths] of byStore) {
const done = await post("/api/forget", { paths, store });
files += done.files === undefined ? 1 : done.files;
forgot += done.forgot || 0;
absent += (done.not_indexed || []).length;
}
picked.clear();
justForgot = true;
paintBulk();
bulkNote.textContent = absent
? `${n(files)} file${files === 1 ? "" : "s"} forgotten, ${n(forgot)} chunk${
forgot === 1 ? "" : "s"
} removed. ${n(absent)} were not indexed and were left alone.`
: `${n(files)} file${files === 1 ? "" : "s"} forgotten, ${n(forgot)} chunk${
forgot === 1 ? "" : "s"
} removed.`;
load();
} catch (e) {
bulkNote.className = "note bad";
bulkNote.textContent = e.message;
button.disabled = false;
paintBulk();
throw e;
}
}
const table = dataTable({
className: "w-files",
caption: "Every indexed file, the reader that parsed it, and what it contributed to the store.",
server: true,
sort: "path",
columns: [
{
key: "pick",
label: "",
className: "pick",
sortable: false,
head: () => {
const all = el("input", {
type: "checkbox",
class: "pick all",
"aria-label": "Select every file on this page",
onchange: () => {
for (const box of table.node.querySelectorAll("tbody input.pick")) {
if (box.checked !== all.checked) box.click();
}
},
});
return all;
},
render: (f) => {
const box = el("input", {
type: "checkbox",
class: "pick",
"aria-label": `Select ${f.path}`,
onchange: () => {
if (box.checked) picked.set(f.path, f.store);
else picked.delete(f.path);
paintBulk();
},
});
box.checked = picked.has(f.path);
return box;
},
},
{
key: "path",
label: "Path",
className: "path",
render: (f) => {
const cell = pathCell(rootRel(f.path, f.store));
cell.title = f.path;
cell.dataset.tip = f.path;
return cell;
},
},
{
key: "store",
label: "Store",
className: "meta narrow-drop",
render: (f) => f.store,
},
{
key: "reader",
label: "Read as",
className: "narrow-drop",
render: (f) => el("span", { class: "tag", text: f.reader }),
},
{
key: "lang",
label: "Language",
className: "meta narrow-drop",
render: (f) => f.lang || "—",
},
{
key: "lines",
label: "Lines",
className: "num",
render: (f) => (f.reader === "image" ? "—" : n(f.lines)),
},
{ key: "chunks", label: "Chunks", className: "num narrow-drop", render: (f) => n(f.chunks) },
{
key: "indexed",
label: "Indexed",
className: "meta",
render: (f) => when(f.indexed_at),
},
{
key: "forget",
label: "",
sortable: false,
render: (f) => {
const button = el("button", {
class: "forget",
type: "button",
text: "Forget",
onclick: () => {
const row = button.closest("tr");
ask({
title: "Forget this file?",
body: `${f.path} — its chunks and vectors are dropped from ${f.store}. The file on disk is untouched, and indexing the folder again brings it back.`,
confirm: "Forget it",
tone: "bad",
run: async () => {
button.disabled = true;
button.textContent = "Forgetting…";
await forget(f.path, row, f.store);
},
});
},
});
return button;
},
},
],
rows: [],
total: 0,
onChange: () => load(),
});
async function load(reset) {
const mine = ++generation;
const view = table.query();
if (reset) view.page = 1;
const params = new URLSearchParams();
if (pathInput.value.trim()) params.set("path", pathInput.value.trim());
for (const ext of chosenExt) params.append("ext", ext);
for (const store of storeFilter.stores()) params.append("store", store);
params.set("sort", view.sort || "path");
params.set("dir", view.dir);
params.set("limit", String(view.perPage));
params.set("offset", String((view.page - 1) * view.perPage));
let data;
try {
data = await api(`/api/files?${params}`);
} catch (e) {
if (mine !== generation) return;
fill(holder, el("div", { class: "card pad" }, error(e.message)));
summary.textContent = "";
return;
}
if (mine !== generation) return;
fill(unreadable, unreadableNotice(data.failed));
summary.textContent = `${n(data.total)} files · ${n(data.formats)} format${
data.formats === 1 ? "" : "s"
} · ${n(data.stores)} store${data.stores === 1 ? "" : "s"}`;
if (!data.total) {
const because = justForgot
? "Nothing left under this filter — the file that matched it has been forgotten."
: "Nothing indexed matches that. The filter, not the corpus — clear it and look again.";
fill(holder, el("div", { class: "card pad" }, empty(because)));
footnote.hidden = true;
justForgot = false;
return;
}
footnote.hidden = false;
justForgot = false;
table.update(data.files, data.total);
if (!holder.contains(table.node)) fill(holder, table.node);
matches = data.total;
}
let matches = 0;
async function selectEveryMatch() {
bulkNote.className = "note";
bulkNote.textContent = `Selecting ${n(matches)} files…`;
for (let offset = 0; offset < matches && offset < FILES_SELECTABLE; offset += FILES_PER_REQUEST) {
const params = new URLSearchParams();
if (pathInput.value.trim()) params.set("path", pathInput.value.trim());
for (const ext of chosenExt) params.append("ext", ext);
for (const store of storeFilter.stores()) params.append("store", store);
params.set("limit", String(FILES_PER_REQUEST));
params.set("offset", String(offset));
let data;
try {
data = await api(`/api/files?${params}`);
} catch (e) {
bulkNote.className = "note bad";
bulkNote.textContent = e.message;
return;
}
for (const file of data.files || []) picked.set(file.path, file.store);
if (!(data.files || []).length) break;
}
bulkNote.textContent = "";
for (const box of table.node.querySelectorAll("tbody input.pick")) box.checked = true;
paintBulk();
}
const EXTENSIONS = ["rs", "md", "py", "ts", "js", "go", "pdf", "docx", "epub", "png", "jpg"];
const extChips = EXTENSIONS.map((ext) =>
el("button", {
class: "chip",
type: "button",
"aria-pressed": "false",
text: `.${ext}`,
onclick: (e) => {
const on = e.currentTarget.getAttribute("aria-pressed") !== "true";
e.currentTarget.setAttribute("aria-pressed", String(on));
if (on) chosenExt.add(ext);
else chosenExt.delete(ext);
clearTimeout(load.timer);
load(true);
},
}),
);
load();
const indexedPane = el(
"div",
{ class: "rows files-pane" },
el(
"div",
{ class: "files-filters" },
el(
"div",
{ class: "filters" },
el(
"div",
{ class: "field wide" },
icon(ICONS.search, 15),
labelled("files-filter", "Filter files by path glob", pathInput),
),
),
el("div", { class: "filters" }, el("span", { class: "filter-label", text: "Type" }), extChips),
el("div", { class: "filters" }, el("span", { class: "filter-label", text: "Store" }), storeFilter.node),
),
bulkBar,
bulkNote,
holder,
footnote,
);
const treePane = filesTree(storeFilter);
const decisionsTab = decisionsPane();
const panes = [
["Indexed", indexedPane, null],
["Tree", treePane.node, treePane.load],
["Decisions", decisionsTab.node, decisionsTab.load],
];
const panel = el("div", { class: "tab-panel" });
const tabButtons = panes.map(([label], i) =>
el("button", {
class: "tab",
type: "button",
text: label,
"data-tab": label,
"aria-pressed": String(i === 0),
onclick: () => showPane(i),
}),
);
function showPane(index) {
tabButtons.forEach((b, i) => b.setAttribute("aria-pressed", String(i === index)));
fill(panel, panes[index][1]);
if (panes[index][2]) panes[index][2]();
}
showPane(state.filesTab === "decisions" ? 2 : state.filesTab === "tree" ? 1 : 0);
state.filesTab = "";
return el(
"div",
{ class: "view" },
unreadable,
pageHead(
"Files",
"What is indexed, and which reader parsed it — so “not indexed” and “not discussed” stop looking the same.",
{ pill: summary },
),
el("div", { class: "tabs" }, tabButtons),
panel,
announcer,
);
}
const FILE_TYPES = [
[/\.rs$/, "ft-rust"],
[/\.(m?js|cjs|jsx)$/, "ft-js"],
[/\.tsx?$/, "ft-ts"],
[/\.py$/, "ft-py"],
[/\.(md|markdown|txt|rst)$/, "ft-md"],
[/\.(json|ya?ml|toml|lock)$/, "ft-data"],
[/\.(html?|css|scss|svg)$/, "ft-web"],
[/\.(png|jpe?g|gif|webp|ico)$/, "ft-image"],
];
const fileType = (name) => (FILE_TYPES.find(([re]) => re.test(name.toLowerCase())) || [null, ""])[1];
function filesTree(storeFilter) {
const sort = "name";
const list = el("ul", { role: "tree", "aria-label": "Indexed files, by folder" });
const box = el("div", { class: "card ftree" }, list);
const url = (store, dir) => {
const params = new URLSearchParams({ tree: "1", format: "json", sort });
if (dir) params.set("dir", dir);
if (store) params.append("store", store);
else for (const name of storeFilter.stores()) params.append("store", name);
return `/api/files?${params}`;
};
function row(level, kind, name, meta, extra) {
const guides = [el("span", { class: "indent lead" })];
for (let i = 1; i < level; i++) guides.push(el("span", { class: "indent" }));
const dir = kind === "dir" || kind === "root";
return el(
"div",
{
class: `ftree-row ${kind === "root" ? "dir root" : kind}${extra?.cls ? ` ${extra.cls}` : ""}`,
role: "treeitem",
"aria-level": String(level),
"aria-expanded": dir ? "false" : null,
tabindex: "-1",
title: extra?.title || null,
},
guides,
el("span", { class: "chev" }, dir ? icon(ICONS.chevron, 12) : null),
el(
"span",
{ class: `ficon${kind === "file" ? ` ${fileType(name)}` : ""}` },
icon(dir ? ICONS.folder : ICONS.file, 15),
),
el("span", { class: "fname", text: name }),
meta ? el("span", { class: "fmeta", text: meta }) : null,
);
}
function folder(where, dirPath, name, meta, level, kind, preload) {
const head = row(level, kind, name, meta, { title: `${where.root}/${dirPath}`.replace(/\/$/, "") });
const kids = el("ul", { role: "group", hidden: "" });
const item = el("li", {}, head, kids);
let loaded = false;
const glyph = head.querySelector(".ficon");
async function open(want) {
const on = want === undefined ? kids.hidden : want;
head.setAttribute("aria-expanded", String(on));
kids.hidden = !on;
fill(glyph, icon(on ? ICONS.folderOpen : ICONS.folder, 15));
if (!on || loaded) return;
loaded = true;
fill(kids, el("li", { class: "ftree-more", text: "Reading…" }));
try {
const data = await api(url(where.store, dirPath));
const mine = (data.roots || []).find((r) => r.store === where.store && r.root === where.root);
children(kids, where, dirPath, mine, level + 1);
} catch (e) {
loaded = false;
fill(kids, el("li", { class: "ftree-more", text: e.message }));
}
}
head.addEventListener("click", () => open());
head.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
open();
} else if (e.key === "ArrowRight") open(true);
else if (e.key === "ArrowLeft") open(false);
});
if (preload) {
loaded = true;
children(kids, where, dirPath, preload, level + 1);
head.setAttribute("aria-expanded", "true");
kids.hidden = false;
fill(glyph, icon(ICONS.folderOpen, 15));
}
return item;
}
function children(ul, where, dirPath, entry, level) {
const items = [];
const under = (name) => (dirPath ? `${dirPath}/${name}` : name);
for (const d of entry?.dirs || []) {
items.push(folder(where, under(d.name), d.name, `${n(d.files)} file${d.files === 1 ? "" : "s"}`, level, "dir"));
}
for (const f of entry?.files || []) {
const meta = f.lines ? `${n(f.lines)} lines · ${n(f.symbols)} symbol${f.symbols === 1 ? "" : "s"}` : `${n(f.chunks)} chunks`;
items.push(el("li", {}, row(level, "file", f.name, meta, { cls: f.stale ? "stale" : "", title: `${where.root}/${under(f.name)}` })));
}
for (const o of entry?.not_indexed || []) {
items.push(el("li", {}, row(level, "file", o.name, `not indexed — ${o.why}`, { cls: "off", title: `${where.root}/${under(o.name)}` })));
}
if (entry?.more) items.push(el("li", { class: "ftree-more", text: `+ ${n(entry.more)} more in this folder` }));
if (!items.length) items.push(el("li", { class: "ftree-more", text: "Empty." }));
fill(ul, items);
}
async function load() {
fill(list, el("li", { class: "ftree-more", text: "Reading…" }));
let data;
try {
data = await api(url(null, ""));
} catch (e) {
fill(list, el("li", { class: "ftree-more", text: e.message }));
return;
}
const roots = data.roots || [];
if (!roots.length) {
fill(list, el("li", { class: "ftree-more", text: "Nothing indexed yet." }));
return;
}
fill(
list,
roots.map((r) => {
const where = { store: r.store, root: r.root };
const leaf = r.root.split("/").filter(Boolean).pop() || r.root;
const label = leaf === r.store ? r.store : `${r.store} · ${leaf}`;
return folder(where, "", label, null, 1, "root", r);
}),
);
}
const node = box;
return { node, load };
}
const CLASS_LABELS = {
content: "secret-shaped value",
credential: "credential file",
policy: "policy limit",
unindexable: "not indexable",
excluded: "your exclusions",
dummy: "test dummies",
};
function decisionsPane() {
const node = el("div", { class: "rows files-pane decisions" });
function refuseDummy(store, row) {
const reviewed = el("input", { type: "checkbox", id: "reviewed-file" });
ask({
title: "Refuse this file?",
body: "Every match in it is a declared test dummy, so it was indexed. Refusing takes it out of this store until you revoke the decision.",
lead: dialogPath(row.path, store),
extra: row.matches && row.matches.length ? findingsList(row.matches) : null,
tail: el("label", { class: "reviewed", for: "reviewed-file" }, reviewed, el("span", { text: "I have reviewed this file" })),
confirm: "Refuse this file",
tone: "bad",
wide: true,
run: async () => {
if (!reviewed.checked) throw new Error("Tick “I have reviewed this file” first.");
await post("/api/refused/accept", { store, path: row.path, mode: "refused", reviewed: true });
await load();
},
});
}
function revoke(store, row) {
ask({
title: "Revoke this decision?",
body:
row.accepted === "refused"
? "The file is indexed again at the next run, as a test-dummy file."
: "The file leaves the store and is refused again with today's reasons; the next scan offers it for a decision.",
lead: dialogPath(row.path, store),
confirm: "Revoke",
tone: "bad",
wide: true,
run: async () => {
await post("/api/refused/revoke", { store, path: row.path });
await load();
},
});
}
const decisionLabel = (row) =>
row.accepted === "refused"
? "refused by you"
: row.accepted === "redacted"
? "accepted, redacted"
: row.accepted
? "accepted as-is"
: "let through";
function table(store, rows) {
return dataTable({
className: "w-decisions",
caption: `Decisions about files in ${store}`,
rows,
columns: [
{
key: "path",
label: "File",
value: (r) => rootRel(r.path, store),
render: (r) => pathCell(rootRel(r.path, store), "path"),
},
{
key: "decision",
label: "Decision",
value: decisionLabel,
render: (r) => el("span", { class: r.accepted && r.accepted !== "refused" ? "pill good" : "pill", text: decisionLabel(r) }),
},
{ key: "rule", label: "Why it was held back", sortable: false, className: "narrow-drop", render: (r) => lineCell(r.class === "dummy" ? "every match is a declared test dummy" : r.rule) },
{
key: "confidence",
label: "Likely real",
className: "num",
value: (r) => (r.confidence == null ? -1 : r.confidence),
render: (r) => (r.confidence == null ? "—" : `${r.confidence} %`),
},
{
key: "action",
label: "",
sortable: false,
className: "decide-col",
render: (r) =>
r.accepted
? el("button", { class: "button secondary small", type: "button", text: "Revoke", onclick: () => revoke(store, r) })
: el("button", { class: "button secondary small", type: "button", text: "Refuse instead", onclick: () => refuseDummy(store, r) }),
},
],
}).node;
}
async function load() {
fill(node, el("p", { class: "subtitle", text: "Reading…" }));
let data;
try {
data = await api("/api/refused");
} catch (e) {
fill(node, error(e.message));
return;
}
const blocks = [
el("p", {
class: "subtitle",
text: "Files you accepted or refused, and files let through because every match is a test dummy. Undo any one of them here; new decisions are made on the Index page after a scan.",
}),
];
for (const store of data.stores || []) {
const rows = (store.rows || []).filter((r) => r.accepted || r.class === "dummy");
if (!rows.length) continue;
blocks.push(el("h2", { class: "section-title", text: store.store }));
blocks.push(table(store.store, rows));
}
if (blocks.length === 1) {
blocks.push(el("div", { class: "card pad" }, empty("No decisions yet. When a scan holds a file back, the Index page asks about it.")));
}
fill(node, ...blocks);
}
return { node, load };
}
function findingsList(matches) {
const signals = (m) =>
(m.signals || []).map((s) => `${s.name} ${s.effect === "up" ? "↑" : "↓"} ${s.detail}`).join(" · ");
return el(
"ul",
{ class: "findings" },
(matches || []).map((m) =>
el(
"li",
{},
el(
"div",
{ class: "finding-head" },
el("code", { class: "finding-value", text: m.masked }),
el("span", { class: "finding-kind", text: `${m.kind} · line ${m.line}` }),
el("span", { class: "spacer" }),
el("strong", { class: "finding-conf", text: `${m.confidence} % likely real` }),
),
signals(m) ? el("div", { class: "finding-signals", text: signals(m) }) : null,
),
),
);
}
function dialogPath(path, store) {
return el("code", { class: "dialog-path", title: path, text: rootRel(path, store) });
}
function reviewOne(store, item, mode, done) {
const reviewed = el("input", { type: "checkbox", id: "reviewed-inline" });
ask({
title: mode === "redacted" ? "Accept with redaction?" : "Accept as-is?",
body:
mode === "redacted"
? "Each detected value is replaced by [REDACTED:…] before anything is stored. Redaction covers only what the scanner detected."
: item.class === "content"
? "The file's full text is indexed, values included."
: `${item.rule}. Accepting indexes it anyway.`,
lead: dialogPath(item.path, store),
extra: item.matches && item.matches.length ? findingsList(item.matches) : null,
tail: el(
"label",
{ class: "reviewed", for: "reviewed-inline" },
reviewed,
el("span", { text: "I have reviewed this file" }),
),
confirm: "Accept this file",
wide: true,
run: async () => {
if (!reviewed.checked) throw new Error("Tick “I have reviewed this file” first.");
await post("/api/refused/accept", { store, path: item.path, mode, reviewed: true });
done();
},
});
}
function storePicker(initial, onChange) {
let picked = initial || "";
const row = el("div", { class: "store-chips" });
const paint = () => {
for (const chip of row.children) {
chip.setAttribute("aria-pressed", String(chip.getAttribute("data-store") === picked));
}
};
const add = (name, label) =>
row.append(
el("button", {
class: "chip",
type: "button",
"data-store": name,
text: label,
onclick: () => {
picked = name;
paint();
onChange();
},
}),
);
add("", "All stores");
for (const store of liveStores()) add(store.name, store.name);
paint();
return { node: row, stores: () => (picked ? [picked] : []) };
}
function freshMark(fresh) {
const ok = fresh !== false;
return el(
"span",
{
class: "fresh",
title: ok ? "indexed from the bytes on disk" : "file changed since it was indexed",
},
el("span", { class: `fresh-dot ${ok ? "is-fresh" : "is-stale"}` }),
el("span", { class: "word", text: ok ? "fresh" : "stale" }),
);
}
function codeGutter(text, startLine) {
const from = Number(startLine) || 1;
return el(
"div",
{ class: "gutter-code" },
String(text == null ? "" : text)
.split("\n")
.map((line, i) =>
el(
"div",
{ class: "code-line" },
el("span", { class: "ln", text: String(from + i) }),
el("span", { class: "lt", text: line }),
),
),
);
}
function spanCard(span, badges) {
const named = span.symbol ? `${span.symbol_kind || ""} ${span.symbol}`.trim() : "";
return el(
"div",
{ class: "span-card" },
el(
"div",
{ class: "span-head" },
el("span", { class: "path", text: span.path }),
el("span", { class: "lines", text: `${span.start_line}-${span.end_line}` }),
span.store ? el("span", { class: "from", text: span.store }) : null,
el("span", { class: "spacer" }),
badges || null,
freshMark(span.fresh),
),
named ? el("div", { class: "sig", text: named }) : null,
codeGutter(span.text, span.start_line),
);
}
const LIST_LABELS = {
definition: "definition — this chunk defines the name that was typed",
vector: "vector — the embedding matched",
keyword: "full text — the terms matched",
graph: "graph — reached from a neighbouring symbol",
image: "image — the picture matched the words",
};
function fusionBadges(lists) {
if (!lists || !lists.length) return null;
return el(
"span",
{ class: "badges" },
lists.map((list) =>
el("span", { class: `badge ${list}`, title: LIST_LABELS[list] || list, text: list }),
),
);
}
const CONFIDENCE = {
extracted: ["extracted", "the import names the target"],
resolved: ["resolved", "name and module hint agree on one definition"],
inferred: ["inferred", "matched by bare name"],
ambiguous: ["ambiguous", "several definitions, none chosen"],
};
function confidenceBadge(value) {
if (!value) return null;
const [label, why] = CONFIDENCE[value] || [value, ""];
return el("span", { class: `conf ${value}`, title: why, text: label });
}
const PROVENANCE_NOTE = {
extracted: "Found through the graph: the source names the target, so nothing had to be ranked.",
resolved: "Found through the graph: the name and its module hint agree on one definition.",
inferred: "Found through the graph by bare name. Corroborate before you rely on it.",
ambiguous:
"Found through the graph, but several definitions carry this name and none was chosen.",
};
function bestLine(text, query) {
const terms = (query || "")
.split(/[^A-Za-z0-9_]+/)
.filter((t) => t.length > 2)
.map((t) => t.toLowerCase());
let best = null;
let bestScore = -1;
for (const raw of (text || "").split("\n")) {
const line = raw.trim();
if (!line) continue;
const lowered = line.toLowerCase();
const score = terms.filter((t) => lowered.includes(t)).length;
if (score > bestScore) {
bestScore = score;
best = line;
}
}
if (!best) return "";
return best.length > 120 ? `${best.slice(0, 120)}…` : best;
}
function rowCost(hit, query) {
const named = hit.symbol ? `${hit.symbol_kind || ""} ${hit.symbol}`.trim() : "";
const marks = (hit.lists || []).join("+") + (hit.fresh === false ? " stale" : "");
const row = ` ${hit.start_line}-${hit.end_line} ${named} ${marks}\n ${bestLine(
hit.text,
query,
)}\n`;
return Math.ceil(row.length / 4);
}
function symbolIn(hit) {
const match = hit.text.match(
/(?:fn|function|def|func|class|struct|interface|type)\s+([A-Za-z_][A-Za-z0-9_]*)/,
);
return match ? match[1] : "";
}
async function searchView() {
await refreshStores();
const results = el("div", { class: "results locate-list" });
const bodyCard = el("div", { class: "span-card" });
const aroundName = el("span", { class: "sym" });
const footer = el("div", { class: "locate-footer" });
const meta = el("span", { class: "search-meta" });
const shapeHint = el("div", { class: "shape-hint", hidden: true });
const ego = graphCanvas({
onHover: (node, x, y) => {
if (!node) return tip.hide("ego");
tip.atPoint(
x,
y,
tipCard(
node.name,
"blue",
[
node.kind ? ["kind", node.kind] : null,
node.path ? ["file", `${shortPath(node.path)}:${node.start_line}-${node.end_line}`] : null,
].filter(Boolean),
),
"ego",
);
},
onPick: (node) => {
aroundName.textContent = node ? node.name : "—";
},
});
const stage = el(
"div",
{ class: "ego-panel", hidden: true },
ego.node,
el("span", { class: "ego-hint", text: "drag a node · click to select" }),
);
const aroundHead = el(
"div",
{ class: "around-head", hidden: true },
el("span", { class: "title" }, "Around ", aroundName),
el("span", { class: "meta", text: "graph expansion" }),
);
const showRing = (on) => {
stage.hidden = !on;
aroundHead.hidden = !on;
};
let k = 8;
const kValue = el("span", { class: "dial-value", text: "k = 8" });
const bump = (delta) => {
const next = Math.min(50, Math.max(1, k + delta));
if (next === k) return;
k = next;
kValue.textContent = `k = ${k}`;
run();
};
const kDial = el(
"div",
{ class: "dial" },
kValue,
el("button", { class: "step", type: "button", "aria-label": "fewer hits", title: "fewer hits", text: "–", onclick: () => bump(-1) }),
el("button", { class: "step", type: "button", "aria-label": "more hits", title: "more hits", text: "+", onclick: () => bump(1) }),
);
let prefer = "any";
const preferPills = ["code", "docs", "any"].map((name) =>
el("button", {
class: "seg",
type: "button",
"aria-pressed": String(name === prefer),
text: name,
onclick: () => {
if (prefer === name) return;
prefer = name;
for (const pill of preferPills) {
pill.setAttribute("aria-pressed", String(pill.textContent === prefer));
}
run();
},
}),
);
const preferDial = el(
"div",
{ class: "dial" },
el("span", { class: "dial-label", text: "Prefer" }),
el("span", { class: "segs" }, preferPills),
);
let view = "results";
const viewPills = ["results", "brief", "exact"].map((name) =>
el("button", {
class: "seg",
type: "button",
"aria-pressed": String(name === view),
text: name,
onclick: () => {
if (view === name) return;
view = name;
for (const pill of viewPills) {
pill.setAttribute("aria-pressed", String(pill.textContent === view));
}
run();
},
}),
);
const viewDial = el(
"div",
{ class: "dial" },
el("span", { class: "dial-label", text: "View" }),
el("span", { class: "segs" }, viewPills),
);
let filterTimer = 0;
const rerun = () => {
clearTimeout(filterTimer);
filterTimer = setTimeout(() => run(), 250);
};
const langField = el("input", {
class: "bare",
size: "8",
placeholder: "any",
oninput: rerun,
onchange: () => run(),
});
const pathField = el("input", {
class: "bare",
size: "8",
placeholder: "any",
oninput: rerun,
onchange: () => run(),
});
const langDial = el(
"div",
{ class: "dial" },
el("span", { class: "dial-prefix", text: "lang:" }),
labelled("search-lang", "Only this language", langField),
);
const pathDial = el(
"div",
{ class: "dial" },
el("span", { class: "dial-prefix", text: "path:" }),
labelled("search-path", "Only paths matching this glob", pathField),
);
const budgetField = el("input", {
class: "bare",
size: "5",
value: "1500",
inputmode: "numeric",
oninput: rerun,
onchange: () => run(),
});
const budgetDial = el(
"div",
{ class: "dial" },
el("span", { class: "dial-label", text: "Budget" }),
labelled("search-budget", "Budget in tokens", budgetField),
el("span", { class: "unit", text: "tok" }),
);
const budget = () => Math.max(200, Number(String(budgetField.value).replace(/[^0-9]/g, "")) || 1500);
const picker = storePicker(state.search.stores[0] || "", () => run());
let generation = 0;
const input = el("input", {
type: "search",
placeholder: "Ask the index a question",
oninput: () => {
if (!input.value.trim()) shapeHint.hidden = true;
},
onkeydown: (e) => {
if (e.key === "Enter") run();
},
});
const bodyCol = el("div", { class: "body-col" }, bodyCard, aroundHead, stage);
const twoStage = el(
"div",
{ class: "two-stage" },
el("div", { class: "locate-col" }, results, footer),
bodyCol,
);
const nothing = () =>
empty("Type a phrase or an identifier. Both halves of the search run either way.");
async function run() {
const query = input.value.trim();
state.search = { query, stores: picker.stores() };
if (!query) {
shapeHint.hidden = true;
fill(results, nothing());
showBody(null, "");
fill(footer);
meta.textContent = "";
return;
}
twoStage.classList.remove("one-stage");
if (view === "brief") {
twoStage.classList.add("one-stage");
return runBrief(query);
}
if (view === "exact") return runExact(query);
const mine = ++generation;
const params = new URLSearchParams({ query, k: String(k), prefer });
for (const store of picker.stores()) params.append("store", store);
const lang = langField.value.trim();
const path = pathField.value.trim();
if (lang) params.append("lang", lang);
if (path) params.append("path", path);
meta.textContent = "searching…";
const slow = setTimeout(() => {
if (mine === generation) {
meta.textContent = "loading the embedding model — the first search after the daemon starts pays for it once";
}
}, 1200);
let data;
try {
data = await api(`/api/search?${params}`);
} catch (e) {
clearTimeout(slow);
if (mine !== generation) return;
fill(results, error(e.message));
showBody(null, "");
fill(footer);
meta.textContent = "";
return;
}
clearTimeout(slow);
if (mine !== generation) return;
if (data.shape_label) {
fill(
shapeHint,
el("span", { class: "dot" }),
el("span", { text: `${data.shape_label} · ${data.weighting}` }),
);
shapeHint.hidden = false;
}
meta.textContent = data.hits.length
? `${data.hits.length} hit${data.hits.length === 1 ? "" : "s"} · ${(data.micros / 1000).toFixed(1)} ms`
: "";
if (!data.hits.length) {
showBody(null, "");
fill(footer);
fill(
results,
empty(
"No chunk in the selected stores matches that. If a store chip is on it is the filter, not the corpus — clear it and ask again.",
),
);
return;
}
const groups = [];
for (const hit of data.hits) {
const found = groups.find((g) => g.path === hit.path && g.store === hit.store);
if (found) found.hits.push(hit);
else groups.push({ path: hit.path, store: hit.store, hits: [hit] });
}
const cap = budget();
let spent = 0;
const shown = [];
for (const group of groups) {
const cost = group.hits.reduce((total, hit) => total + rowCost(hit, query), 4);
if (shown.length && spent + cost > cap) break;
spent += cost;
shown.push(group);
}
const kept = shown.reduce((total, group) => total + group.hits.length, 0);
fill(
results,
shown.map((group) => {
const spans = group.hits.map((hit) => `${hit.start_line}-${hit.end_line}`);
return el(
"div",
{ class: "locate-group" },
el(
"div",
{ class: "locate-file" },
el("span", { class: "file", "data-tip": group.path, text: shortPath(group.path) }),
group.store ? el("span", { class: "from", text: group.store }) : null,
el("span", { class: "spacer" }),
el("span", {
class: "span-summary",
text: spans.length > 1 ? `${spans.length} spans · ${spans.join(", ")}` : spans[0],
}),
),
group.hits.map((hit) => locateRow(hit, query)),
);
}),
);
fill(
footer,
el("span", {
text: `${kept} of ${data.hits.length} shown · ${n(spent)} tokens · budget ${n(cap)}`,
}),
kept < data.hits.length
? el("span", {
class: "truncated",
text: `truncated: ${kept} of ${data.hits.length}`,
})
: null,
);
const first = results.querySelector(".locate-row");
if (first) first.setAttribute("aria-pressed", "true");
showBody(shown[0].hits[0], query);
}
function locateRow(hit, query) {
const line = bestLine(hit.text, query);
const row = el(
"button",
{
class: "locate-row",
type: "button",
"aria-pressed": "false",
onclick: () => {
for (const other of results.querySelectorAll(".locate-row")) {
other.setAttribute("aria-pressed", "false");
}
row.setAttribute("aria-pressed", "true");
showBody(hit, query);
},
},
el(
"span",
{ class: "row-top" },
el("span", {
class: "lines",
text: hit.image
? `${hit.image.width}×${hit.image.height} px`
: `${hit.start_line}-${hit.end_line}`,
}),
hit.image
? null
: el("span", {
class: "sym",
text: hit.symbol ? `${hit.symbol_kind || ""} ${hit.symbol}`.trim() : "prose",
}),
fusionBadges(hit.lists),
el("span", { class: "spacer" }),
freshMark(hit.fresh),
confidenceBadge(hit.provenance),
),
line ? el("span", { class: "locate-excerpt", text: line }) : null,
);
return row;
}
let openHit = null;
let openQuery = "";
let wholeSpan = null;
let signature = "";
let whole = false;
let definitions = 0;
let bodyGeneration = 0;
async function runExact(query) {
const mine = ++generation;
const params = new URLSearchParams({ query, exact: "1" });
for (const store of picker.stores()) params.append("store", store);
const lang = langField.value.trim();
const path = pathField.value.trim();
if (lang) params.append("lang", lang);
if (path) params.append("path", path);
shapeHint.hidden = true;
meta.textContent = "searching…";
let data;
try {
data = await api(`/api/search?${params}`);
} catch (e) {
if (mine !== generation) return;
fill(results, error(e.message));
showBody(null, "");
fill(footer);
meta.textContent = "";
return;
}
if (mine !== generation) return;
const matches = data.matches || [];
meta.textContent = `${matches.length} line${matches.length === 1 ? "" : "s"} · ${n(data.files || 0)} files searched`;
if (!matches.length) {
showBody(null, "");
fill(footer);
fill(results, empty("No indexed line matches that. A query that is not a valid regular expression is searched as literal text."));
return;
}
const groups = [];
for (const m of matches) {
const found = groups.find((g) => g.path === m.path && g.store === m.store);
if (found) found.rows.push(m);
else groups.push({ path: m.path, store: m.store, rows: [m] });
}
fill(
results,
groups.map((group) =>
el(
"div",
{ class: "locate-group" },
el(
"div",
{ class: "locate-file" },
el("span", { class: "file", "data-tip": group.path, text: shortPath(group.path) }),
group.store ? el("span", { class: "from", text: group.store }) : null,
el("span", { class: "spacer" }),
el("span", { class: "span-summary", text: `${group.rows.length} line${group.rows.length === 1 ? "" : "s"}` }),
),
group.rows.map((m) => {
const hit = {
path: m.path,
store: m.store,
start_line: m.start_line,
end_line: m.end_line,
text: m.text,
symbol: m.capture || null,
};
const row = el(
"button",
{
class: "locate-row",
type: "button",
"aria-pressed": "false",
onclick: () => {
for (const other of results.querySelectorAll(".locate-row")) {
other.setAttribute("aria-pressed", "false");
}
row.setAttribute("aria-pressed", "true");
showBody(hit, query);
},
},
el(
"span",
{ class: "row-top" },
el("span", { class: "lines", text: String(m.start_line) }),
el("span", { class: "sym", text: m.capture || "top level" }),
),
el("span", { class: "locate-excerpt", text: m.text }),
);
return row;
}),
),
),
);
fill(
footer,
el("span", { text: `${matches.length} lines · ${groups.length} files` }),
data.truncated ? el("span", { class: "truncated", text: `truncated at ${matches.length}` }) : null,
);
const first = results.querySelector(".locate-row");
if (first) first.setAttribute("aria-pressed", "true");
showBody(groups[0].rows.length ? { ...groups[0].rows[0], symbol: groups[0].rows[0].capture || null } : null, query);
}
async function runBrief(question) {
const mine = ++generation;
const params = new URLSearchParams({ question, budget: String(budget()), prefer });
for (const store of picker.stores()) params.append("store", store);
const lang = langField.value.trim();
const path = pathField.value.trim();
if (lang) params.append("lang", lang);
if (path) params.append("path", path);
showBody(null, "");
shapeHint.hidden = true;
meta.textContent = "assembling…";
const slow = setTimeout(() => {
if (mine === generation) {
meta.textContent =
"loading the embedding model — the first search after the daemon starts pays for it once";
}
}, 1200);
let data;
try {
data = await api(`/api/brief?${params}`);
} catch (e) {
clearTimeout(slow);
if (mine !== generation) return;
fill(results, error(e.message));
fill(footer);
meta.textContent = "";
return;
}
clearTimeout(slow);
if (mine !== generation) return;
const brief = data.brief || {};
const spans = brief.spans || [];
meta.textContent = spans.length ? `one call · ${(data.micros / 1000).toFixed(1)} ms` : "";
if (!spans.length) {
fill(footer);
fill(
results,
empty(
"No chunk in the selected stores matches that. If a store chip is on it is the filter, not the corpus — clear it and ask again.",
),
);
return;
}
const rows = [];
for (const span of spans) {
rows.push(
span.text
? spanCard(span, fusionBadges(span.lists))
: el(
"div",
{ class: "brief-head brief-row" },
el("span", { class: "path", text: `${shortPath(span.path)}:${span.start_line}-${span.end_line}` }),
span.symbol ? el("span", { class: "sym", text: span.symbol }) : null,
el("span", {
class: "brief-dropped",
text: span.over_budget ? "text left out for the budget" : "text: top span only",
}),
el("span", { class: "brief-lists" }, fusionBadges(span.lists)),
),
);
}
for (const symbol of brief.symbols || []) {
const edges = [];
for (const end of symbol.callers || []) {
edges.push(
el(
"div",
{ class: "brief-edge" },
el("span", { class: "k", text: "called by" }),
el("span", { class: "v", text: `${end.name} · ${shortPath(end.path)}:${end.start_line}` }),
),
);
}
for (const end of symbol.callees || []) {
edges.push(
el(
"div",
{ class: "brief-edge" },
el("span", { class: "k", text: "calls" }),
el("span", { class: "v", text: `${end.name} · ${shortPath(end.path)}:${end.start_line}` }),
),
);
}
if (symbol.hidden) {
edges.push(el("div", { class: "brief-dropped", text: `and ${symbol.hidden} more edges` }));
}
rows.push(
el(
"div",
{ class: "brief-symbol" },
el(
"div",
{ class: "brief-head" },
el("span", { class: "sym", text: symbol.name }),
el("span", { class: "brief-lists" }, el("span", { class: "tag", text: symbol.found_by })),
),
...edges,
),
);
}
fill(results, el("div", { class: "brief-view" }, ...rows));
const cut = brief.cut || {};
const dropped = [];
if (cut.spans) dropped.push(`${cut.spans} spans not located`);
if (cut.span_text) dropped.push(`${cut.span_text} left without text`);
if (cut.symbols) dropped.push(`${cut.symbols} symbols' edges`);
const fact = (label, value) =>
el("span", { class: "brief-fact" }, el("span", { class: "k", text: label }), el("span", { class: "v", text: value }));
fill(
footer,
el(
"div",
{ class: "brief-summary" },
fact("tokens", `${n(brief.tokens)} of ${n(brief.budget)}`),
fact("spans", n(spans.length)),
fact("counted with", brief.counted_with || "—"),
fact("dropped", dropped.length ? dropped.join(", ") : "nothing"),
),
);
}
function showBody(hit, query) {
openHit = hit;
openQuery = query;
wholeSpan = null;
signature = "";
whole = false;
definitions = 0;
const mine = ++bodyGeneration;
paintBody();
if (!hit) {
ego.draw({ nodes: [], edges: [] }, null);
aroundName.textContent = "—";
return;
}
const target = hit.symbol || `${hit.path}:${hit.start_line}`;
const params = new URLSearchParams({ target });
if (hit.store) params.append("store", hit.store);
api(`/api/read?${params}`)
.then((data) => {
if (mine !== bodyGeneration) return;
if (!data.span) {
definitions = (data.definitions || []).length;
if (definitions) paintBody();
return;
}
wholeSpan = data.span;
if (hit.symbol) {
signature = (data.span.text || "")
.split("\n")
.map((l) => l.trim())
.find((l) => l.length > 0) || "";
}
paintBody();
})
.catch(() => {
});
drawAround(hit, mine);
}
function paintBody() {
const hit = openHit;
if (!hit) {
fill(
bodyCard,
empty("Pick a row. The span opens here, with what it is called and how it was found."),
);
showRing(false);
return;
}
const showing = whole && wholeSpan ? wholeSpan : hit;
const lines = `${showing.start_line}-${showing.end_line}`;
const wider =
wholeSpan &&
(wholeSpan.start_line < hit.start_line || wholeSpan.end_line > hit.end_line);
fill(
bodyCard,
el(
"div",
{ class: "span-head" },
el("span", { class: "path", "data-tip": hit.path, text: shortPath(hit.path) }),
el("span", { class: "lines", text: lines }),
el("span", { class: "spacer" }),
freshMark(hit.fresh),
confidenceBadge(hit.provenance),
),
hit.provenance && PROVENANCE_NOTE[hit.provenance]
? el("p", { class: "prov-note", text: PROVENANCE_NOTE[hit.provenance] })
: null,
hit.fresh === false
? el("p", {
class: "note",
text: "This file has changed since it was indexed. The lines below are what was read then.",
})
: null,
signature ? el("div", { class: "sig", text: signature }) : null,
hit.image ? imagePreview(hit.path) : codeGutter(showing.text, showing.start_line),
el(
"div",
{ class: "span-foot" },
el("button", {
class: "button secondary small",
type: "button",
disabled: !wider,
title: wider
? null
: definitions
? `${definitions} definitions carry this name, and which one this is was not settled. The Read page lists them.`
: "this span is already the whole of it",
text: whole
? "Collapse to the matched span"
: hit.symbol
? "Read whole symbol"
: "Read the whole section",
onclick: () => {
whole = !whole;
paintBody();
},
}),
el("span", {
class: "body-meta",
"data-tip": hit.path,
text: `semlith_read · ${shortPath(hit.path)}:${lines} · second stage`,
}),
el("span", { class: "spacer" }),
el("a", {
href: "#graph",
class: "quiet",
text: "Open in graph",
onclick: (e) => {
e.preventDefault();
state.pendingSymbol = hit.symbol || symbolIn(hit);
go("graph");
},
}),
),
);
}
async function drawAround(hit, mine) {
const name = hit.symbol || symbolIn(hit);
aroundName.textContent = name || "—";
if (!name) {
showRing(false);
return ego.draw({ nodes: [], edges: [] }, null);
}
showRing(true);
const params = new URLSearchParams({ name });
if (hit.store) params.append("store", hit.store);
let data;
try {
data = await api(`/api/symbol?${params}`);
} catch (_) {
return;
}
if (mine !== bodyGeneration) return;
ego.draw(egoGraph(name, data), null);
ego.pick(name);
}
fill(results, nothing());
paintBody();
setTimeout(() => {
ego.start();
if (state.pendingQuery) {
input.value = state.pendingQuery;
state.pendingQuery = "";
run();
} else if (state.search.query) {
input.value = state.search.query;
run();
}
input.focus();
}, 0);
return el(
"div",
{ class: "search-page" },
el(
"div",
{ class: "search-band" },
el("h1", { class: "sr-only", text: "Search" }),
el(
"div",
{ class: "search-field" },
icon(ICONS.search, 18),
labelled("search-query", "Search the index", input),
),
meta,
shapeHint,
el(
"div",
{ class: "filters" },
picker.node,
el("span", { class: "rule" }),
kDial,
preferDial,
viewDial,
langDial,
pathDial,
budgetDial,
el("span", { class: "dial-note", text: "what one answer may cost an agent" }),
),
),
twoStage,
);
}
function egoGraph(name, data) {
const centre = (data.symbols || [])[0] || {};
const nodes = [
{
name,
kind: centre.kind || "symbol",
path: centre.path,
start_line: centre.start_line,
end_line: centre.end_line,
},
];
const index = new Map([[name, 0]]);
const edges = [];
const add = (label, kind, where) => {
if (!index.has(label)) {
index.set(label, nodes.length);
nodes.push({
name: label,
kind,
path: where && where.path,
start_line: where && where.start_line,
end_line: where && where.end_line,
});
}
return index.get(label);
};
const link = (from, to, kind, confidence) => {
if (from !== to) edges.push({ from, to, kind, confidence });
};
for (const end of data.callers || []) {
link(add(end.name, end.kind, end), 0, end.kind, end.confidence);
}
for (const end of data.callees || []) {
link(0, add(end.name, end.kind, end), end.kind, end.confidence);
}
for (const hop of data.ego || []) {
const via = index.get(hop.via);
if (via === undefined) continue;
const it = add(hop.name, hop.kind);
if (hop.direction === "caller") link(it, via, hop.kind, hop.confidence);
else link(via, it, hop.kind, hop.confidence);
}
return { nodes, edges };
}
function sized(property, share, attrs) {
const node = el("span", attrs || {});
node.style[property] = `${Math.max(0, Math.min(100, share * 100))}%`;
return node;
}
function healthPanel(options) {
const withLanguages = !options || options.languages !== false;
const node = el(
"div",
{ class: "health" },
withLanguages ? el("section", { class: "health-card" }, skeletonRows(5)) : null,
el("section", { class: "health-card" }, skeletonRows(4)),
el("section", { class: "health-card" }, skeletonRows(6)),
);
let rows = [];
async function refresh() {
try {
const data = await api("/api/stores?coverage=1");
rows = data.stores || [];
} catch {
rows = [];
}
paint();
}
function bar(counts) {
const total = counts.reduce((sum, [, n]) => sum + n, 0);
if (!total) return el("div", { class: "rail-hint", text: "No call edges yet." });
return el(
"div",
{ class: "support-bar", role: "img", "aria-label": counts.map(([k, n]) => `${k} ${n}`).join(", ") },
counts.map(([kind, count]) =>
count
? sized("width", count / total, {
class: `seg ${kind}`,
tabindex: "0",
"data-tip": `${kind} · ${n(count)} edge${count === 1 ? "" : "s"} · ${share(count, total)} of ${n(total)}`,
})
: null,
),
);
}
function paint() {
const counts = { extracted: 0, resolved: 0, ambiguous: 0, unresolved: 0 };
const languages = [];
let unresolvedNames = 0;
let several = 0;
const top = [];
const months = new Map();
for (const store of rows) {
for (const row of store.coverage || []) {
if (!row.definitions && !row.files) continue;
counts.extracted += row.extracted || 0;
counts.resolved += row.resolved || 0;
counts.ambiguous += row.ambiguous || 0;
counts.unresolved += row.unresolved || 0;
languages.push({ store: store.name, ...row });
}
const health = store.health;
if (!health) continue;
unresolvedNames += health.unresolved_names || 0;
several += health.several_definitions || 0;
for (const one of health.unresolved_top || []) top.push(one);
for (const one of health.months || []) {
months.set(one.month, (months.get(one.month) || 0) + one.chunks);
}
}
const merge = (into, key, row, fields) => {
const at = into.get(key) || { ...row, stores: new Set() };
if (into.has(key)) for (const field of fields) at[field] = (at[field] || 0) + (row[field] || 0);
at.stores.add(row.store);
into.set(key, at);
return at;
};
const perLanguage = new Map();
for (const row of languages) {
merge(perLanguage, row.language, row, [
"files",
"definitions",
"extracted",
"resolved",
"ambiguous",
"unresolved",
"unparsed",
]);
}
const merged = [...perLanguage.values()];
const perName = new Map();
for (const one of top) merge(perName, one.name, { ...one, store: one.store || "" }, ["edges"]);
const topNames = [...perName.values()].sort((a, b) => b.edges - a.edges);
const carrying = merged.filter((row) => row.extracted || row.resolved || row.ambiguous);
const byFiles = [...merged].sort((a, b) => b.files - a.files).slice(0, 8);
const filesTotal = merged.reduce((sum, row) => sum + row.files, 0) || 1;
const monthRows = [...months.entries()].sort((a, b) => a[0].localeCompare(b[0])).slice(-12);
const peak = Math.max(1, ...monthRows.map(([, count]) => count));
const monthTotal = monthRows.reduce((sum, [, count]) => sum + count, 0);
fill(
node,
!withLanguages ? null : el(
"section",
{ class: "health-card" },
el("span", { class: "eyebrow", text: "Language mix" }),
byFiles.length
? el(
"div",
{ class: "mix" },
byFiles.map((row) =>
el(
"div",
{
class: "mix-row",
tabindex: "0",
"data-tip": `${row.language} · ${n(row.files)} file${row.files === 1 ? "" : "s"} · ${share(
row.files,
filesTotal,
)} of ${n(filesTotal)} indexed · ${[...row.stores].sort().join(", ")}`,
},
el("span", { class: "k", text: row.language }),
el("span", { class: "meter" }, sized("width", row.files / filesTotal)),
el("span", { class: "v", text: `${n(row.files)} file${row.files === 1 ? "" : "s"}` }),
),
),
)
: el("div", { class: "rail-hint", text: "Nothing indexed yet." }),
),
el(
"section",
{ class: "health-card" },
el("span", { class: "eyebrow", text: "Chunks by month indexed" }),
monthRows.length
? el(
"div",
{ class: "months" },
monthRows.map(([month, count]) =>
el(
"div",
{
class: "month",
tabindex: "0",
"data-tip": `${month} · ${n(count)} chunk${count === 1 ? "" : "s"} · ${share(
count,
monthTotal,
)} of the ${n(monthTotal)} in this window · ${share(count, peak)} of the busiest month`,
},
el("span", { class: "col" }, sized("height", count / peak)),
el("span", { class: "m", text: month.slice(2) }),
el("span", { class: "c", text: n(count) }),
),
),
)
: el("div", { class: "rail-hint", text: "No files indexed yet." }),
el("p", { class: "note", text: "When semlith read the file, not when it was written." }),
),
el(
"section",
{ class: "health-card" },
el("span", { class: "eyebrow", text: "Graph health" }),
bar([
["extracted", counts.extracted],
["resolved", counts.resolved],
["ambiguous", counts.ambiguous],
["unresolved", counts.unresolved],
]),
el(
"div",
{ class: "health-facts" },
el("div", { class: "fact" }, el("span", { class: "v", text: n(unresolvedNames) }), el("span", { class: "k", text: "call targets with no definition here" })),
el("div", { class: "fact" }, el("span", { class: "v", text: n(several) }), el("span", { class: "k", text: "names with several definitions" })),
el("div", { class: "fact" }, el("span", { class: "v", text: `${carrying.length} of ${languages.length}` }), el("span", { class: "k", text: "languages carrying edges" })),
),
topNames.length
? el(
"div",
{ class: "mix" },
topNames.slice(0, 5).map((one) =>
el(
"div",
{
class: "mix-row",
tabindex: "0",
"data-tip": `${one.name} · ${n(one.edges)} call${
one.edges === 1 ? "" : "s"
} reach a name no store here holds a definition for · ${[...one.stores]
.filter(Boolean)
.sort()
.join(", ") || "this store"}`,
},
el("span", { class: "k", text: one.name }),
el("span", { class: "v", text: `${n(one.edges)} edge${one.edges === 1 ? "" : "s"}` }),
),
),
)
: null,
),
);
}
refresh();
return { node, paint: refresh };
}
function coveragePanel() {
const node = el("div", { class: "rows" });
let rows = [];
async function refresh() {
try {
const data = await api("/api/stores?coverage=1");
rows = data.stores || [];
} catch {
rows = [];
}
paint();
}
function paint() {
const coverage = [];
for (const store of rows) {
for (const row of store.coverage || []) {
if (!row.definitions && !row.files) continue;
coverage.push({ store: store.name, ...row });
}
}
fill(
node,
coverage.length
? dataTable({
className: "w-coverage",
caption:
"What the graph covers, per language: files indexed, files the parser gave up on, definitions, and call edges by how firmly each one landed.",
sort: "files",
dir: "desc",
rows: coverage,
columns: [
{ key: "store", label: "Store", className: "meta narrow-drop", value: (r) => r.store, render: (r) => r.store },
{ key: "language", label: "Language", value: (r) => r.language, render: (r) => r.language },
{ key: "files", label: "Files", className: "num", value: (r) => r.files, render: (r) => String(r.files) },
{
key: "parser_failed",
label: "Unparsed",
className: "num",
value: (r) => r.parser_failed,
render: (r) =>
el("span", {
class: r.parser_failed ? "bad" : "meta",
title: "Files whose parse expired, so the store holds their text and none of their structure.",
text: String(r.parser_failed),
}),
},
{ key: "definitions", label: "Definitions", className: "num", value: (r) => r.definitions, render: (r) => String(r.definitions) },
{ key: "extracted", label: "Extracted", className: "num", value: (r) => r.extracted, render: (r) => String(r.extracted) },
{ key: "resolved", label: "Resolved", className: "num", value: (r) => r.resolved, render: (r) => String(r.resolved) },
{ key: "ambiguous", label: "Ambiguous", className: "num", value: (r) => r.ambiguous, render: (r) => String(r.ambiguous) },
{
key: "unresolved",
label: "Unresolved",
className: "num",
value: (r) => r.unresolved,
title: "Call targets no definition in this store satisfies.",
render: (r) => String(r.unresolved),
},
{
key: "settled",
label: "Settled",
className: "num",
value: (r) => r.settled,
render: (r) => `${r.settled} %`,
},
],
}).node
: empty("No language holds a definition yet. Index a folder of code and the graph fills in."),
);
}
paint();
refresh();
return { node, paint: refresh };
}
function spellTook(ms) {
if (ms < 100) return "under 0.1s";
if (ms < 1000) return `${(ms / 1000).toFixed(1)}s`;
const all = Math.round(ms / 1000);
if (all < 60) return `${all}s`;
if (all < 3600) return `${Math.floor(all / 60)}m ${String(all % 60).padStart(2, "0")}s`;
return `${Math.floor(all / 3600)}h ${String(Math.floor(all / 60) % 60).padStart(2, "0")}m`;
}
function spellLeft(ms) {
const seconds = Math.round(ms / 1000);
if (seconds < 10) return "almost done";
if (seconds < 60) return `about ${Math.round(seconds / 5) * 5} s left`;
const minutes = Math.round(seconds / 60);
if (minutes < 90) return `about ${minutes} min left`;
return `about ${Math.floor(minutes / 60)} h ${String(minutes % 60).padStart(2, "0")} min left`;
}
function perSecond(value) {
return value >= 10 ? n(Math.round(value)) : Number(value).toFixed(1);
}
function spell(ms) {
const all = Math.max(0, Math.round(ms / 1000));
const seconds = String(all % 60).padStart(2, "0");
const hours = Math.floor(all / 3600);
const minutes = String(Math.floor(all / 60) % 60).padStart(2, "0");
return hours ? `${hours}:${minutes}:${seconds}` : `${minutes}:${seconds}`;
}
const RUN_TONE = {
queued: "warn",
review: "warn",
running: "good",
pausing: "warn",
paused: "warn",
held: "warn",
stopping: "warn",
done: "good",
stopped: "bad",
failed: "bad",
};
const RUN_WORD = {
queued: "queued",
review: "waiting for review",
running: "indexing",
pausing: "pausing",
paused: "paused",
held: "held — waiting for a slot, keeps its progress",
stopping: "stopping",
done: "done",
stopped: "stopped",
failed: "failed",
};
function runCard(run, controls) {
const bar = el("span", {});
const track = el("div", { class: "bar", tabindex: "0" }, bar);
const pct = el("span", { class: "pct" });
const counts = document.createTextNode("");
const rate = el("span", {}, "");
const lanes = el("span", {}, "");
const threads = el("span", {}, "");
const status = el("span", { class: "meta" }, counts, rate, lanes, threads);
const elapsed = el("span", { class: "meta" });
const log = el("div", { class: "log", "aria-live": "polite" });
const problem = el("div", { class: "note bad" }, "");
const outcome = el("div", { class: "note" }, "");
const planLine = el("div", { class: "meta run-plan", hidden: "" }, "");
function paintPlan(next) {
const plan = next.plan;
planLine.hidden = !plan;
if (!plan) return;
const not = Object.values(plan.not_indexed || {}).reduce((a, b) => a + b, 0);
const review = (plan.review || []).length;
setText(
planLine,
next.status === "review"
? `scanned: ${n(plan.embed)} to embed (${bytes(plan.embed_bytes)}) · ${n(plan.unchanged)} unchanged · waiting for Start indexing`
: `plan: ${n(plan.embed)} to embed (${bytes(plan.embed_bytes)}) · ${n(plan.unchanged)} unchanged · ${n(not)} not indexed · ${n(review)} needed review`,
);
}
let last = run;
const badge = pill("", "good");
const badgeWord = badge.lastChild;
const where = el("span", { class: "meta" });
const pause = el("button", { class: "button secondary small", type: "button" });
const stop = el("button", { class: "button secondary small", type: "button" });
const remove = el("button", {
class: "button secondary small",
type: "button",
text: "Remove",
title: "Dismiss this card. The store is not touched.",
});
const expand = el("button", {
class: "button secondary small",
type: "button",
"aria-expanded": "false",
});
let open = true;
let shown = 0;
let readAt = 0;
let ticking = false;
function paintClock() {
const since = Date.now() - readAt;
const next = last;
let text = "";
if (next.status === "queued") {
text = `waiting ${spellTook(ticking ? shown + since : shown)}`;
} else if (next.status === "running") {
text = next.eta_ms === null || next.eta_ms === undefined ? "estimating…" : spellLeft(next.eta_ms - since);
} else if (next.finished_at && next.started_at) {
const queued = next.queued_ms || 0;
text = `took ${spellTook(Math.max(0, shown - queued))} · finished ${new Date(next.finished_at * 1000)
.toTimeString()
.slice(0, 5)}${queued >= 1000 ? ` · queued ${spellTook(queued)}` : ""}`;
}
setText(elapsed, text);
elapsed.hidden = !text;
}
function absorb(next) {
last = next;
paintPlan(next);
shown = next.elapsed_ms || 0;
readAt = Date.now();
ticking = !!next.ticking;
paintClock();
const scanned = next.total ? Math.min(100, (next.scanned / next.total) * 100) : 0;
const finished = next.status === "done";
const width = finished ? 100 : next.status === "stopped" ? 0 : scanned;
bar.style.width = `${width.toFixed(1)}%`;
setText(pct, `${Math.round(width)}%`);
track.setAttribute(
"data-tip",
next.status === "stopped"
? "stopped · everything this run embedded was undone"
: `${n(next.scanned || 0)} of ${n(next.total || 0)} file${next.total === 1 ? "" : "s"} scanned · ${n(
next.chunks || 0,
)} chunk${next.chunks === 1 ? "" : "s"} written · ${Math.round(width)}%`,
);
setText(
badgeWord,
next.status === "queued" && next.position
? `queued · ${next.position} in line`
: next.kind === "catch-up" && next.status === "running"
? "catching up"
: RUN_WORD[next.status] || next.status,
);
const tone = `pill ${RUN_TONE[next.status] || "warn"}`;
if (badge.className !== tone) badge.className = tone;
const live = TICKING.has(next.status);
setText(
counts,
next.phase
? `${n(next.scanned)}/${n(next.total)} files · ${next.phase} · `
: next.total
? `${n(next.scanned)}/${n(next.total)} files · ${n(next.chunks)} chunks · `
: `${RUN_WORD[next.status] || next.status} · `,
);
const reading = live ? next.rate : next.rate_average;
rate.hidden = next.status === "queued" || (!live && (reading === null || reading === undefined));
setText(rate, `${reading === null || reading === undefined ? "—" : perSecond(reading)} chunks/s · `);
const average = next.rate_average;
const tip = average === null || average === undefined ? "" : `${perSecond(average)} chunks/s on average since the first batch`;
if (rate.title !== tip) rate.title = tip;
const split = Object.entries(next.lane_rates || {}).sort((a, b) => b[1] - a[1]);
lanes.hidden = split.length < 2;
setText(lanes, split.length < 2 ? "" : `${split.map(([lane, r]) => `${lane.toUpperCase()} ${n(Math.round(r))}/s`).join(" · ")} · `);
threads.hidden = !next.threads;
setText(threads, next.threads ? `${n(next.threads)} thread${next.threads === 1 ? "" : "s"} · ` : "");
setText(where, (next.paths || []).join(", "));
const deleted = next.delete || "";
setText(outcome, deleted);
const outcomeClass = deleted.startsWith("the store was not deleted") ? "note bad" : "note";
if (outcome.className !== outcomeClass) outcome.className = outcomeClass;
const held = next.status === "paused" || next.status === "pausing";
setText(pause, held ? "Resume" : "Pause");
pause.hidden = !live || next.status === "queued" || next.status === "held" || next.status === "review";
setText(stop, next.status === "queued" ? "Take out of the queue" : "Stop");
stop.hidden = !live;
stop.disabled = next.status === "stopping";
remove.hidden = live;
if (touched === false) {
open = live;
touched = null;
}
paintFold();
}
function paintFold() {
log.hidden = !open;
where.hidden = !open;
setText(expand, open ? "Hide the log" : "Show the log");
expand.setAttribute("aria-expanded", String(open));
}
let touched = false;
pause.addEventListener("click", () => controls.hold(pause.textContent === "Pause"));
stop.addEventListener("click", () => controls.stop(stop.textContent !== "Stop"));
remove.addEventListener("click", () => controls.remove());
expand.addEventListener("click", () => {
open = !open;
touched = null;
paintFold();
if (open && controls.reveal) controls.reveal();
});
const node = el(
"div",
{ class: "card pad run-card" },
el(
"div",
{ class: "head" },
el("span", { class: "card-title", text: run.store }),
badge,
el("span", { class: "spacer" }),
pct,
),
planLine,
track,
el(
"div",
{ class: "filters" },
status,
elapsed,
el("span", { class: "spacer" }),
expand,
pause,
stop,
remove,
),
problem,
outcome,
where,
log,
);
absorb(run);
const clock = setInterval(() => {
if (!node.isConnected) return clearInterval(clock);
if (ticking) paintClock();
}, 1000);
return { node, absorb, log, problem, last: () => last, isOpen: () => open };
}
function logLine(log, event) {
const outcome = event.outcome || null;
const key =
event.event === "file" ? `${event.scanned}/${event.total}` : event.event || "";
const text =
event.event === "file"
? event.why
? `${event.path} — ${event.why}`
: event.path
: logText(event);
log.append(
el(
"div",
{ class: outcome ? `line ${outcome}` : "line" },
key ? el("span", { class: "key", text: key }) : null,
outcome ? el("span", { class: "outcome", text: outcome }) : null,
el("span", { class: "what", text }),
),
);
const atEnd = log.scrollHeight - log.scrollTop - log.clientHeight < 40;
if (atEnd) log.scrollTop = log.scrollHeight;
while (log.childElementCount > 500) log.firstElementChild.remove();
}
function logText(event) {
switch (event.event) {
case "submitted":
return event.ahead
? `waiting — ${n(event.ahead)} run${event.ahead === 1 ? "" : "s"} ahead of this one`
: "submitted";
case "queued":
return event.ahead
? `waiting for ${event.store}'s writer — ${n(event.ahead)} job${
event.ahead === 1 ? "" : "s"
} ahead of this one`
: `waiting for ${event.store}'s writer; it finishes what the watcher is doing first`;
case "started":
return "walking the tree and hashing what it finds";
case "slice":
return `${n(event.remaining)} paths left; the writer is giving the watcher a turn and will carry on`;
case "paused":
return "held between files — the writer is still this run's";
case "resumed":
return "carrying on";
case "error":
return event.error || "failed";
case "done":
if (event.dequeued) return "removed from the queue before it started; nothing was indexed";
if (event.stopped) {
return "stopped — everything this run embedded was undone, so the store is as it was before it started";
}
return `${n(event.indexed)} indexed, ${n(event.unchanged)} unchanged, ${n(
event.skipped,
)} skipped, ${n(event.removed)} removed, ${n(event.chunks)} chunks · ${spell(
event.elapsed_ms || 0,
)}`;
default:
return event.event || "";
}
}
function capped(limit) {
return limit.ceiling === limit.derived
? "It is already what this machine derives."
: "Past it the machine would be promising work it cannot carry.";
}
function steady(reason) {
return String(reason || "")
.replace(/^[\d.]+ [GM]iB free (?=minus|is under)/, "free memory ")
.replace(/, and (?:none|[\d.]+ [GM]iB) is free beyond the reserve out of the [\d.]+ [GM]iB free now$/, "");
}
function settingField(key, label, limit, onSave) {
let current = limit;
let dirty = false;
const input = el("input", {
type: "number",
min: "1",
"aria-label": label,
});
const why = el("div", { class: "note" });
function explain() {
const limit = current;
const asked = Number(input.value);
const held = limit.source === "saved" && asked === limit.value ? `${limit.value} saved. ` : "";
if (limit.source === "environment") {
setText(why, `Set in this daemon's environment, so the page leaves it alone. This machine would derive ${limit.derived} — ${limit.reason}`);
return;
}
if (asked >= limit.ceiling) {
setText(why, `${held}${limit.ceiling} is as high as this machine goes. ${capped(limit)}`);
return;
}
const room = key === "index_memory_mb" ? "what is free now, less the reserve," : `the ${limit.ceiling}`;
if (asked > limit.derived) {
setText(why, `${held}Above the ${limit.derived} this machine would pick on its own, and under ${room} it will allow — ${limit.reason} Yours to set.`);
return;
}
const from =
limit.source === "saved"
? `${limit.value} saved, and this machine derives ${limit.derived}`
: `${limit.derived}, derived`;
setText(
why,
`${from} — ${limit.reason} Room up to ${key === "index_memory_mb" ? "what is free now, less the reserve" : limit.ceiling}.`,
);
}
function update(next) {
current = { ...next, reason: steady(next.reason) };
const max = String(current.ceiling);
if (input.max !== max) input.max = max;
const fixed = current.source === "environment";
if (input.disabled !== fixed) input.disabled = fixed;
const value = String(current.value);
if (!dirty && document.activeElement !== input && input.value !== value) input.value = value;
explain();
}
input.addEventListener("input", () => {
dirty = true;
explain();
});
input.addEventListener("change", () => {
const asked = Math.min(current.ceiling, Math.max(1, Number(input.value) || 1));
input.value = String(asked);
dirty = false;
explain();
onSave(key, asked);
});
update(limit);
return {
update,
node: el(
"div",
{ class: "setting" },
el("div", { class: "field" }, el("span", { class: "prefix", text: label }), input),
why,
),
};
}
const LANE_NAMES = { cpu: "CPU", gpu: "GPU", cuda: "CUDA", worker: "Worker" };
function binarySize(value) {
const mib = (Number(value) || 0) / 1048576;
return mib >= 1024 ? `${(mib / 1024).toFixed(1)} GiB` : `${mib.toFixed(1)} MiB`;
}
function laneState(status) {
const state = (status && status.state) || "idle";
if (state === "downloading") return `downloading ${status.percent ?? 0}%`;
if (status && status.reason) return `${state} — ${status.reason}`;
return state;
}
function accelSection() {
const rows = el("div", { class: "rows accel-lanes" });
const fallback = el("p", { class: "note" });
const problem = el("div", { class: "note" });
const drawn = new Map();
let data = null;
let asked = 0;
let painted = 0;
function say(text, bad) {
problem.className = bad ? "note bad" : "note";
setText(problem, text);
}
async function change(lane, action) {
try {
const answer = await post("/api/accel", { lane, action });
say(answer.said || "");
} catch (e) {
say(e.message, true);
}
await refresh();
}
function row(lane) {
const knob = el("span", { class: "knob", "aria-hidden": "true" });
const title = el("span", { class: "replay-state" });
const where = el("span", { class: "replay-switch-note" });
const share = el("span", { class: "meta" });
const toggle = el(
"button",
{ class: "replay-switch", type: "button", role: "switch", "aria-checked": "false" },
knob,
el("span", { class: "replay-switch-text" }, title, where),
el("span", { class: "spacer" }),
share,
);
const remove = el("button", { class: "button secondary small", type: "button" });
const removeRow = el("div", { class: "filters accel-remove" }, remove);
const drawnRow = { title, where, share, toggle, remove, removeRow, enabled: false, node: null };
toggle.addEventListener("click", () => {
const on = !drawnRow.enabled;
if (on && lane === "cuda") {
ask({
title: "Turn CUDA on?",
body: `It downloads the CUDA pack first, ${binarySize(data?.bytes?.cuda_download || 0)}, once, into this machine's model cache. Runs use it from their next batch.`,
confirm: "Download and turn on",
run: () => change(lane, "on"),
});
return;
}
change(lane, on ? "on" : "off");
});
remove.addEventListener("click", () => change(lane, "remove"));
drawnRow.node = el("div", { class: "accel-lane" }, toggle, removeRow);
return drawnRow;
}
function paint(next) {
data = next;
const seen = new Set();
for (const lane of next.lanes || []) {
seen.add(lane.lane);
let r = drawn.get(lane.lane);
if (!r) {
r = row(lane.lane);
drawn.set(lane.lane, r);
}
r.enabled = !!lane.enabled;
r.toggle.classList.toggle("on", r.enabled);
const checked = String(r.enabled);
if (r.toggle.getAttribute("aria-checked") !== checked) r.toggle.setAttribute("aria-checked", checked);
const name = LANE_NAMES[lane.lane] || lane.lane;
setText(r.title, `${name} · ${lane.device || "no device found"}${lane.variant ? ` · ${lane.variant}` : ""}`);
setText(r.where, `${r.enabled ? "" : "off · "}${laneState(lane.status)}`);
setText(r.share, `${Math.round(lane.share || 0)} %`);
const held = (next.bytes || {})[lane.lane] || 0;
r.removeRow.hidden = !(held > 0 && (lane.lane === "gpu" || lane.lane === "cuda"));
setText(r.remove, `Remove downloaded files (${binarySize(held)})`);
}
for (const [lane, r] of drawn) {
if (seen.has(lane)) continue;
r.node.remove();
drawn.delete(lane);
}
arrange(rows, (next.lanes || []).map((lane) => drawn.get(lane.lane).node));
setText(fallback, next.cpu_fallback ? "The CPU is carrying the work: no GPU lane can." : "");
}
async function refresh() {
const mine = ++asked;
let next;
try {
next = await api("/api/accel");
} catch (_) {
return;
}
if (mine < painted) return;
painted = mine;
paint(next);
}
return {
refresh,
node: el(
"div",
{ class: "rows" },
el("span", { class: "eyebrow", text: "Accelerators" }),
fallback,
rows,
problem,
),
};
}
function rootRel(path, storeName) {
const plainPath = (p) => String(p).replace(/\\/g, "/").replace(/^\/\/\?\//, "");
const key = (p) => (/^[A-Za-z]:\//.test(p) ? p.toLowerCase() : p);
const whole = plainPath(path);
const store = (state.stores || []).find((s) => s.name === storeName);
const roots = (store?.roots || [])
.map((root) => root.path && plainPath(root.path))
.filter(Boolean)
.sort((a, b) => b.length - a.length);
for (const root of roots) {
const prefix = root.endsWith("/") ? root : `${root}/`;
if (key(whole).startsWith(key(prefix))) return whole.slice(prefix.length);
}
return whole;
}
const NO_ACTION = new Set(["unindexable", "excluded", "credential"]);
function spellScan(seconds) {
return seconds < 1 ? `${Math.max(1, Math.round(seconds * 1000))} ms` : `${seconds.toFixed(1)} s`;
}
function scanPanel() {
const node = el("div", { class: "card pad scan-panel", hidden: "" });
let drawnFor = "";
const decided = new Map();
async function paint(runs) {
const held = (runs || []).filter((run) => run.status === "review" && run.plan);
const key = held.map((run) => run.id).join(",");
node.hidden = !held.length;
if (!held.length) {
drawnFor = "";
return;
}
if (key === drawnFor) return;
drawnFor = key;
let refused = { stores: [] };
try {
refused = await api("/api/refused");
} catch {
}
if (key !== drawnFor) return;
const rowsOf = (store) => (refused.stores || []).find((s) => s.store === store)?.rows || [];
draw(held, rowsOf);
}
function draw(held, rowsOf) {
const total = (key) => held.reduce((sum, run) => sum + (run.plan[key] || 0), 0);
const items = held.flatMap((run) =>
(run.plan.review || []).map((item) => ({ ...item, store: run.store })),
);
const noAction = {};
for (const run of held) {
for (const [cls, count] of Object.entries(run.plan.not_indexed || {})) {
if (NO_ACTION.has(cls)) noAction[cls] = (noAction[cls] || 0) + count;
}
}
const eta = held.every((run) => run.plan.eta_ms != null)
? held.reduce((sum, run) => sum + run.plan.eta_ms, 0)
: null;
const stat = (label, value, hint) =>
el(
"div",
{ class: "scan-stat" },
el("span", { class: "eyebrow", text: label }),
el("strong", { text: value }),
hint ? el("span", { class: "meta", text: hint }) : null,
);
const summary = el(
"div",
{ class: "scan-stats" },
stat("To embed", n(total("embed")), bytes(held.reduce((sum, run) => sum + (run.plan.embed_bytes || 0), 0))),
stat("Unchanged", n(total("unchanged")), "already indexed"),
stat("Need a decision", n(items.length), items.length ? "below" : "nothing to decide"),
stat("Not indexed", n(Object.values(noAction).reduce((a, b) => a + b, 0)), "no action needed"),
stat("Estimate", eta == null ? "—" : spellTook(eta), eta == null ? "measured once it starts" : "to embed"),
);
const perStore = held.length > 1
? dataTable({
className: "w-scan-stores",
caption: "What the scan found in each folder",
rows: held,
columns: [
{ key: "store", label: "Store", value: (run) => run.store, render: (run) => run.store },
{ key: "path", label: "Folder", sortable: false, render: (run) => pathCell((run.paths || [])[0] || "") },
{ key: "embed", label: "To embed", className: "num", value: (run) => run.plan.embed, render: (run) => n(run.plan.embed) },
{ key: "unchanged", label: "Unchanged", className: "num", value: (run) => run.plan.unchanged, render: (run) => n(run.plan.unchanged) },
{ key: "review", label: "Decide", className: "num", value: (run) => (run.plan.review || []).length, render: (run) => n((run.plan.review || []).length) },
],
}).node
: null;
const decisionOf = (item) => {
if (decided.has(`${item.store}\n${item.path}`)) return decided.get(`${item.store}\n${item.path}`);
const row = rowsOf(item.store).find((r) => r.path === item.path);
return row && row.accepted ? row.accepted : null;
};
const decisionCell = (item) => {
const cell = el("div", { class: "decide" });
const paintCell = () => {
const now = decisionOf(item);
if (now) {
fill(
cell,
el("span", {
class: now === "kept" ? "pill" : "pill good",
text: now === "kept" ? "stays refused" : now === "redacted" ? "accepted, redacted" : "accepted as-is",
}),
now === "kept"
? el("button", {
class: "button secondary small",
type: "button",
text: "Change",
onclick: () => {
decided.delete(`${item.store}\n${item.path}`);
paintCell();
},
})
: null,
);
return;
}
const accept = (mode) => () =>
reviewOne(item.store, item, mode, () => {
decided.set(`${item.store}\n${item.path}`, mode);
paintCell();
});
fill(
cell,
item.class === "content"
? el("button", { class: "button small", type: "button", text: "Accept redacted", onclick: accept("redacted") })
: null,
el("button", {
class: item.class === "content" ? "button secondary small" : "button small",
type: "button",
text: item.class === "content" ? "Accept as-is" : "Accept",
onclick: accept("as-is"),
}),
el("button", {
class: "button secondary small",
type: "button",
text: "Keep refused",
onclick: () => {
decided.set(`${item.store}\n${item.path}`, "kept");
paintCell();
},
}),
);
};
paintCell();
return cell;
};
const decisions = items.length
? dataTable({
className: "w-scan-review",
caption: "Files the scan held back, each waiting for a decision",
rows: items,
columns: [
{
key: "path",
label: "File",
value: (item) => rootRel(item.path, item.store),
render: (item) => pathCell(rootRel(item.path, item.store), "path"),
},
held.length > 1 ? { key: "store", label: "Store", className: "meta", value: (item) => item.store, render: (item) => item.store } : null,
{ key: "rule", label: "Why", sortable: false, className: "narrow-drop", render: (item) => lineCell(item.rule) },
{
key: "confidence",
label: "Likely real",
className: "num",
value: (item) => (item.confidence == null ? -1 : item.confidence),
render: (item) => (item.confidence == null ? "—" : `${item.confidence} %`),
},
{ key: "decide", label: "Decision", sortable: false, className: "decide-col", render: decisionCell },
].filter(Boolean),
}).node
: el("p", { class: "muted", text: "Nothing here needs a decision." });
const counts = Object.entries(noAction).filter(([, count]) => count);
const listed = el("div", { class: "rows tight", hidden: "" });
const toggle = el("button", {
class: "button secondary small",
type: "button",
text: "Show files",
"aria-expanded": "false",
onclick: () => {
const open = listed.hidden;
listed.hidden = !open;
toggle.setAttribute("aria-expanded", String(open));
setText(toggle, open ? "Hide files" : "Show files");
if (open && !listed.firstChild) {
const rows = held.flatMap((run) =>
rowsOf(run.store)
.filter((row) => NO_ACTION.has(row.class) && !row.accepted)
.map((row) => ({ ...row, store: run.store })),
);
fill(
listed,
rows.length
? dataTable({
className: "w-scan-skipped",
caption: "Files not indexed that need no decision",
rows,
columns: [
{ key: "path", label: "File", value: (row) => rootRel(row.path, row.store), render: (row) => pathCell(rootRel(row.path, row.store)) },
{ key: "class", label: "Kind", value: (row) => row.class, render: (row) => el("span", { class: `pill class-${row.class}`, text: CLASS_LABELS[row.class] || row.class }) },
{ key: "rule", label: "Why", sortable: false, render: (row) => lineCell(row.rule) },
],
}).node
: el("p", { class: "muted", text: "The list is written when the run ends; the counts above are the scan's." }),
);
}
},
});
const skipped = counts.length
? el(
"div",
{ class: "scan-skipped" },
el("span", {
class: "meta",
text: `${counts.map(([cls, count]) => `${n(count)} ${CLASS_LABELS[cls] || cls}`).join(" · ")} — not indexed, no action needed`,
}),
toggle,
)
: null;
fill(
node,
el(
"div",
{ class: "card-head" },
el("h2", { text: held.length === 1 ? `Scanned ${held[0].store}` : `Scanned ${n(held.length)} folders` }),
el("span", { class: "spacer" }),
el("span", {
class: "mono-chip",
text: `scanned in ${spellScan(held.reduce((sum, run) => sum + (run.plan.seconds || 0), 0))} · ready to index`,
}),
),
summary,
perStore,
el("h3", { class: "eyebrow", text: items.length ? `Needs your decision · ${n(items.length)}` : "Needs your decision" }),
decisions,
skipped,
listed,
el("p", {
class: "note",
text: "Start indexing embeds the plan. A file left undecided stays refused, and any decision can be undone later on Files ▸ Decisions. Credential files are never offered.",
}),
);
}
return { node, paint, held: () => (state.runs?.runs || []).filter((run) => run.status === "review") };
}
async function indexView() {
await refreshStores();
const first = await refreshRuns();
const note = el("span", { class: "note index-note" });
const cards = el("div", { class: "cards" });
const queueList = el("div", { class: "queue" });
const queued = new Map();
const queueCard = el(
"div",
{ class: "card pad", hidden: true },
el("span", { class: "eyebrow", text: "Waiting" }),
el("p", {
class: "subtitle",
text: "In the order they will start. Each one begins by itself the moment a run finishes, whether or not this page is open.",
}),
queueList,
);
const settingsCard = el("div", { class: "card pad", hidden: true });
const urlCard = el("div", { class: "card pad", hidden: true });
const urlNote = el("div", { class: "note" });
const urlField = el("input", { type: "text", placeholder: "link to a page, a PDF or a file" });
const urlTarget = el("select", { class: "select" });
const clearDone = el("button", {
class: "button secondary small",
type: "button",
text: "Remove all finished",
hidden: true,
});
let transient = false;
const drawn = new Map();
const cursors = new Map();
function complain(message, focus) {
note.className = "note bad";
note.textContent = message;
if (focus) focus.focus();
}
function say(message) {
note.className = "note";
note.textContent = message;
}
async function control(store, action, run, extra) {
const card = drawn.get(run);
const was = card && card.last().status;
try {
const answer = await post("/api/index/control", { store, action, run, ...(extra || {}) });
if (card) {
setText(card.problem, "");
if (answer && answer.state && card.last().status === was && TICKING.has(was)) {
supersedeRuns();
card.absorb({ ...card.last(), status: answer.state });
}
}
} catch (e) {
if (card) setText(card.problem, e.message);
else complain(e.message);
}
await refreshRuns();
}
async function catchUpLog(run, card) {
const after = cursors.get(run.id);
const from = after === undefined ? run.log_from : after;
let data;
try {
data = await api(
`/api/index/log?store=${encodeURIComponent(run.store)}&run=${run.id}${
from === undefined || from === null ? "" : `&after=${from}`
}`,
);
} catch (_) {
return;
}
for (const line of data.lines || []) logLine(card.log, line);
if (data.cursor !== null && data.cursor !== undefined) cursors.set(run.id, data.cursor);
}
function paint(data) {
const runs = data?.runs || [];
paintStart(runs);
const seen = new Set();
for (const run of runs) {
seen.add(run.id);
let card = drawn.get(run.id);
if (!card) {
card = runCard(run, {
hold: (wantPause) => control(run.store, wantPause ? "pause" : "resume", run.id),
stop: (queued) => {
if (queued) return control(run.store, "dequeue", run.id);
const before = (drawn.get(run.id)?.last() || run).files_before;
const box = el("input", {
type: "checkbox",
class: "pick",
checked: !before,
});
const consequence = el("p", { class: "note" });
const files = `${n(before)} file${before === 1 ? "" : "s"}`;
const explain = () =>
setText(
consequence,
box.checked
? `${before ? `This store holds ${files}. ` : ""}The store is deleted once the run is undone. The files on disk are untouched.`
: before
? `This store holds ${files}; they stay.`
: "The store stays, empty.",
);
box.addEventListener("change", explain);
explain();
ask({
title: `Stop ${run.store}'s index run?`,
body: "Everything it has embedded so far is undone, so the store is left exactly as it was before the run started. The other runs are untouched.",
extra: [
el("label", { class: "filters" }, box, el("span", { class: "subtitle", text: "Also delete the store" })),
consequence,
],
confirm: "Stop and undo",
tone: "bad",
run: () => control(run.store, "stop", run.id, { delete: box.checked }),
});
},
remove: () => control(run.store, "remove", run.id),
reveal: () => {
const card = drawn.get(run.id);
if (card) catchUpLog(run, card);
},
});
drawn.set(run.id, card);
} else {
card.absorb(run);
}
if (card.isOpen()) catchUpLog(run, card);
}
for (const [id, card] of drawn) {
if (seen.has(id)) continue;
card.node.remove();
drawn.delete(id);
cursors.delete(id);
}
const order = runs
.slice()
.sort((a, b) => {
const live = Number(TICKING.has(b.status)) - Number(TICKING.has(a.status));
return live || (b.id || 0) - (a.id || 0);
})
.map((run) => drawn.get(run.id)?.node)
.filter(Boolean);
arrange(cards, order);
if (transient && !runs.some((run) => TICKING.has(run.status)) && !(data?.queue || []).length) {
transient = false;
say("");
}
clearDone.hidden = !runs.some((run) => !TICKING.has(run.status));
idle.hidden = runs.length > 0 || (data?.queue || []).length > 0;
const queue = data?.queue || [];
queueCard.hidden = !queue.length;
const waiting = new Set();
for (const row of queue) {
const key = String(row.run ?? row.store);
waiting.add(key);
let drawnRow = queued.get(key);
if (!drawnRow) {
const position = el("span", { class: "key" });
drawnRow = {
position,
node: el(
"div",
{ class: "queue-row" },
position,
el("span", { class: "name", text: row.store }),
pathCell((row.paths || []).join(", ")),
el("span", { class: "spacer" }),
el("button", {
class: "button secondary small",
type: "button",
text: "Take out of the queue",
onclick: () => control(row.store, "dequeue", row.run),
}),
),
};
queued.set(key, drawnRow);
}
setText(drawnRow.position, `${row.position}`);
}
for (const [key, row] of queued) {
if (waiting.has(key)) continue;
row.node.remove();
queued.delete(key);
}
arrange(
queueList,
queue.map((row) => queued.get(String(row.run ?? row.store)).node),
);
paintSettings(data?.limits);
}
async function saveSetting(key, value) {
try {
const answer = await post("/api/index/settings", { [key]: value });
settingsNote.className = "note";
setText(settingsNote, answer.applied ? `Saved — ${answer.applied}.` : "Saved.");
paintSettings(answer.limits);
} catch (e) {
settingsNote.className = "note bad";
setText(settingsNote, e.message);
}
}
const settingsNote = el("div", { class: "note" });
const accel = accelSection();
let limitsCard = null;
function paintSettings(limits) {
if (!limits) return;
const machine = limits.machine || {};
if (!limitsCard) {
const cores = document.createTextNode("");
const total = document.createTextNode("");
const free = document.createTextNode("");
limitsCard = {
cores,
total,
free,
fields: [
settingField("runs_at_once", "runs at once", limits.runs_at_once, saveSetting),
settingField("embed_threads", "threads each", limits.embed_threads, saveSetting),
settingField("index_memory_mb", "MiB per store", limits.index_memory_mb, saveSetting),
],
};
fill(
settingsCard,
el("span", { class: "eyebrow", text: "How hard this machine may work" }),
el(
"p",
{ class: "subtitle" },
cores,
" logical core(s), ",
total,
" MiB of memory, ",
free,
" MiB free now. Each value below starts from that and is yours to change — they answer each other, so raising one moves what the others suggest. Each stops where this machine does.",
),
el(
"div",
{ class: "settings" },
limitsCard.fields.map((field) => field.node),
),
settingsNote,
accel.node,
);
}
setText(limitsCard.cores, `${machine.logical_cores}`);
setText(limitsCard.total, n(machine.total_memory_mb));
setText(limitsCard.free, n(machine.available_memory_mb));
const [runsAtOnce, threads, memory] = limitsCard.fields;
runsAtOnce.update(limits.runs_at_once);
threads.update(limits.embed_threads);
memory.update(limits.index_memory_mb);
}
const field = el("textarea", {
rows: "2",
placeholder: "~/work/api\n~/work/cli",
value: state.pendingPath || "",
});
state.pendingPath = "";
const target = el(
"select",
{ class: "select", "aria-label": "Where to index into" },
el("option", { value: "each", text: "each folder becomes its own store" }),
liveStores().map((store) =>
el("option", { value: store.name, text: `add to ${store.name}` }),
),
);
function paintTargets() {
const paths = field.value
.split("\n")
.map((path) => path.trim())
.filter(Boolean);
const covers = (store) =>
!paths.length ||
paths.every((path) =>
(store.roots || []).some((root) => root.present && path.startsWith(root.path)),
);
const chosen = target.value;
fill(
target,
el("option", { value: "each", text: "each folder becomes its own store" }),
liveStores().map((store) =>
el("option", {
value: store.name,
text: covers(store)
? `add to ${store.name}`
: `add to ${store.name} — outside its roots`,
disabled: !covers(store),
}),
),
);
target.value = [...target.options].some((o) => o.value === chosen && !o.disabled)
? chosen
: "each";
}
field.addEventListener("input", paintTargets);
paintTargets();
fill(
urlTarget,
liveStores().map((store) => el("option", { value: store.name, text: store.name })),
);
const only = liveStores();
urlTarget.value = only.length === 1 ? only[0].name : "";
if (only.length !== 1) {
urlTarget.prepend(el("option", { value: "", text: "choose a store…" }));
urlTarget.value = "";
}
const picker = folderPicker({
multiple: true,
onChoose: (paths) => {
if (!paths || !paths.length) return;
const already = field.value.split("\n").map((p) => p.trim()).filter(Boolean);
field.value = [...new Set([...already, ...paths])].join("\n");
field.rows = Math.min(8, Math.max(2, field.value.split("\n").length));
},
onError: (message) => complain(message),
});
const projects = projectsChecklist({
onChoose: (paths) => {
field.value = paths.join("\n");
field.rows = Math.min(8, Math.max(2, paths.length));
target.value = "each";
},
onError: (message) => complain(message),
});
const folderButton = el(
"button",
{
class: "button secondary",
type: "button",
"aria-pressed": "false",
onclick: () => reveal("picker"),
},
icon(ICONS.folder),
"Choose folders…",
);
const projectsButton = el(
"button",
{
class: "button secondary",
type: "button",
"aria-pressed": "false",
onclick: () => reveal("projects"),
},
icon(ICONS.folder),
"Projects under a folder…",
);
const urlButton = el(
"button",
{
class: "button secondary",
type: "button",
"aria-pressed": "false",
onclick: () => reveal("url"),
},
icon(ICONS.file),
"Add from a URL",
);
const settingsButton = el(
"button",
{
class: "button secondary",
type: "button",
"aria-pressed": "false",
onclick: () => reveal("settings"),
},
icon(ICONS.sliders),
"Machine limits",
);
function reveal(which) {
const wantPicker = which === "picker" && !picker.isOpen();
const wantProjects = which === "projects" && !projects.isOpen();
const wantUrl = which === "url" && urlCard.hidden;
const wantSettings = which === "settings" && settingsCard.hidden;
if (!wantPicker) picker.close();
if (!wantProjects) projects.close();
urlCard.hidden = !wantUrl;
settingsCard.hidden = !wantSettings;
if (wantPicker) picker.open("");
if (wantProjects) projects.open("");
if (wantUrl) urlField.focus();
if (wantProjects) target.value = "each";
target.disabled = wantProjects;
target.title = wantProjects
? "Each project under the folder becomes its own store."
: "";
folderButton.setAttribute("aria-pressed", String(wantPicker));
projectsButton.setAttribute("aria-pressed", String(wantProjects));
urlButton.setAttribute("aria-pressed", String(wantUrl));
settingsButton.setAttribute("aria-pressed", String(wantSettings));
}
const idle = el(
"div",
{ class: "card pad run-idle" },
el(
"div",
{ class: "card-head" },
el("h2", { text: "Nothing is being read right now" }),
el("span", { class: "spacer" }),
el("span", { class: "mono-chip", text: "queued · writer idle" }),
),
el("p", {
class: "subtitle",
text: "Name a folder above and press Scan: the plan comes first, then Start indexing. The run lives in the daemon, so you can close this tab and come back to it.",
}),
el("p", {
class: "note",
text: "Checkpointed every 30 seconds: vectors are written before the files they cover are marked indexed, so a run that is killed resumes rather than starting again.",
}),
);
const start = el("button", { class: "button", type: "button", text: "Scan" });
const discard = el("button", {
class: "button secondary",
type: "button",
text: "Discard scan",
hidden: "",
});
const scan = scanPanel();
let scanning = false;
function paintStart(runs) {
const held = (runs || []).filter((run) => run.status === "review");
setText(start, scanning ? "Scanning…" : held.length ? "Start indexing" : "Scan");
start.disabled = scanning;
discard.hidden = !held.length || scanning;
scan.paint(runs);
}
const addButton = el("button", {
class: "button",
type: "button",
text: "Fetch and index",
});
async function begin(route, body) {
start.disabled = true;
try {
const answer = await post(route, body);
const started = (answer.runs || []).filter((run) => run.run !== undefined);
const refused = (answer.runs || []).filter((run) => run.error);
transient = true;
const many = started.length === 1 ? "" : "s";
const done =
body.review === "always"
? `Scanned ${started.length} folder${many}: the plan is below. Press Start indexing when it looks right`
: `${started.length} run${many} queued`;
say(`${done}${refused.length ? `; ${refused.length} refused` : ""}.`);
if (refused.length) {
complain(refused.map((run) => `${run.path}: ${run.error}`).join("; "));
}
await refreshStores();
await refreshRuns();
return null;
} catch (e) {
complain(e.message);
return e.message;
} finally {
start.disabled = false;
}
}
start.addEventListener("click", async () => {
const held = scan.held();
if (held.length) {
start.disabled = true;
try {
for (const run of held) {
await post("/api/index/control", { store: run.store, run: run.id, action: "start" });
}
transient = true;
say(`${held.length} run${held.length === 1 ? "" : "s"} queued.`);
} catch (e) {
complain(e.message);
}
await refreshRuns();
paintStart(state.runs?.runs);
return;
}
const paths = field.value
.split("\n")
.map((path) => path.trim())
.filter(Boolean);
if (!paths.length) {
complain("Give a path to scan, or choose a folder.", field);
return;
}
scanning = true;
paintStart(state.runs?.runs);
await begin("/api/index", { path: paths, store: target.value, review: "always" });
scanning = false;
paintStart(state.runs?.runs);
});
discard.addEventListener("click", async () => {
for (const run of scan.held()) {
await control(run.store, "stop", run.id, run.files_before === 0 ? { delete: true } : {});
await control(run.store, "remove", run.id);
}
say("Scan discarded.");
await refreshStores();
await refreshRuns();
paintStart(state.runs?.runs);
});
addButton.addEventListener("click", async () => {
const url = urlField.value.trim();
if (!url) {
urlNote.className = "note bad";
urlNote.textContent = "Give an https URL to fetch.";
urlField.focus();
return;
}
urlNote.className = "note";
urlNote.textContent = "";
if (!urlTarget.value) {
urlNote.className = "note bad";
urlNote.textContent =
"Pick the store to fetch into, in the selector beside this field.";
urlTarget.focus();
return;
}
const failed = await begin("/api/add", { url, store: urlTarget.value });
if (failed) {
urlNote.className = "note bad";
urlNote.textContent = failed;
urlCard.hidden = false;
}
});
clearDone.addEventListener("click", async () => {
const finished = (state.runs?.runs || []).filter((run) => !TICKING.has(run.status));
const gone = (run) => (run.delete || "").startsWith("the store was deleted");
for (const run of finished.filter(gone)) await control(run.store, "remove", run.id);
const stores = new Set(finished.filter((run) => !gone(run)).map((run) => run.store));
for (const store of stores) await control(store, "clear");
});
paint(first);
state.onRuns = (data) => paint(data);
accel.refresh();
watchLive(["runs", "stores"], () => {
refreshRuns();
accel.refresh();
});
state.onStores = () => {
if (document.activeElement !== target) paintTargets();
};
return el(
"div",
{ class: "view" },
pageHead("Index", "What to read, and the run that reads it."),
says(
"Each folder becomes its own store, indexed by its own writer. A run lives in the daemon, not in this page — leaving, refreshing or closing the tab changes nothing, and a run ends only on its Stop or when ",
mono("semlith start"),
" does.",
),
el(
"div",
{ class: "field tall full area" },
el("span", { class: "prefix", text: "paths" }),
labelled("index-path", "Paths to index, one per line", field),
),
el(
"div",
{ class: "index-bar" },
el("div", { class: "index-bar-group" }, folderButton, projectsButton, urlButton, settingsButton),
el("div", { class: "index-bar-group end" }, target, discard, start),
),
note,
el(
"div",
{ class: "scroller" },
picker.node,
projects.node,
fill(
urlCard,
el("span", { class: "eyebrow", text: "Add from a URL" }),
el("p", {
class: "subtitle",
text: "One https request, for exactly this URL — a page, a PDF, or a file on GitHub. Nothing is crawled and no credential is ever sent. The file is saved inside this store's downloads folder, never in your working tree.",
}),
el(
"div",
{ class: "field tall full" },
el("span", { class: "prefix", text: "url" }),
labelled("index-url", "URL to fetch and index", urlField),
),
el(
"div",
{ class: "filters" },
labelled("index-url-store", "Fetch into", urlTarget),
el("span", { class: "spacer" }),
addButton,
),
urlNote,
),
scan.node,
queueCard,
settingsCard,
cards,
idle,
el("div", { class: "filters" }, el("span", { class: "spacer" }), clearDone),
),
);
}
const WORDS_PER_PAGE = 500;
const WORDS_PER_MINUTE = 250;
function spellDuration(minutes) {
if (minutes < 90) return `${Math.max(1, Math.round(minutes))} minutes`;
const hours = minutes / 60;
if (hours < 48) return `${Math.round(hours)} hours`;
return `${Math.round(hours / 24)} days`;
}
function spellSpan(from, to) {
if (!from || !to || to <= from) return "—";
const months = Math.max(0, Math.round((to - from) / (86400 * 30.44)));
const years = Math.floor(months / 12);
const rest = months % 12;
if (!years) return `${months} month${months === 1 ? "" : "s"}`;
return `${years} year${years === 1 ? "" : "s"}${rest ? `, ${rest} month${rest === 1 ? "" : "s"}` : ""}`;
}
function factRow(label, value) {
return el(
"div",
{ class: "kv-row" },
el("span", { class: "k", text: label }),
el("span", { class: "v", text: value }),
);
}
function factPanel(title, rows) {
return el(
"section",
{ class: "card pad kv-panel" },
el("h2", { text: title }),
el("div", { class: "kv-list" }, rows.filter(Boolean)),
);
}
function tipCard(title, tone, rows) {
return el(
"div",
{ class: "tip-card" },
el(
"div",
{ class: "tip-head" },
el("span", { class: `legend-dot ${tone || "blue"}` }),
el("span", { class: "name", text: title }),
),
rows.length
? el(
"div",
{ class: "tip-rows" },
rows.map(([label, value]) =>
el("div", { class: "tip-row" }, el("span", { class: "k", text: label }), el("span", { class: "v", text: String(value) })),
),
)
: null,
);
}
function tipCardOf(target) {
let rows = [];
try {
rows = JSON.parse(target.getAttribute("data-tip-rows") || "[]");
} catch (_) {
rows = [];
}
return tipCard(target.getAttribute("data-tip-title") || "", target.getAttribute("data-tip-tone"), rows);
}
function twelveMonths(months) {
const known = [...months.entries()].sort((a, b) => a[0].localeCompare(b[0]));
if (!known.length) return [];
const [lastYear, lastMonth] = known[known.length - 1][0].split("-").map(Number);
const out = [];
for (let back = 11; back >= 0; back -= 1) {
const at = new Date(Date.UTC(lastYear, lastMonth - 1 - back, 1));
const key = `${at.getUTCFullYear()}-${String(at.getUTCMonth() + 1).padStart(2, "0")}`;
out.push([key, months.get(key) || 0]);
}
return out;
}
const MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
function monthLabel(key) {
const [year, month] = key.split("-").map(Number);
return `${MONTH_NAMES[month - 1]} ${year}`;
}
function monthsCard(months, span) {
const bars = twelveMonths(months);
const peak = Math.max(1, ...bars.map(([, count]) => count));
const total = bars.reduce((sum, [, count]) => sum + count, 0) || 1;
return el(
"section",
{ class: "card pad months-card" },
el(
"div",
{ class: "card-head" },
el("h2", { text: "Chunks added per month" }),
el("span", { class: "spacer" }),
el("span", { class: "mono-chip", text: span }),
),
bars.length
? el(
"div",
{ class: "month-chart" },
bars.map(([key, count], i) =>
el(
"div",
{
class: `month-col${i === bars.length - 1 ? " now" : ""}${count ? "" : " none"}`,
tabindex: "0",
"data-tip-title": monthLabel(key),
"data-tip-tone": i === bars.length - 1 ? "accent" : "blue",
"data-tip-rows": JSON.stringify([
["chunks added", n(count)],
["share of year", share(count, total)],
["vs. peak", share(count, peak)],
]),
},
sized("height", count ? Math.max(0.06, count / peak) : 0.05, { class: "col" }),
),
),
)
: el("div", { class: "rail-hint", text: "No files indexed yet." }),
bars.length
? el(
"div",
{ class: "month-axis" },
bars.map(([key], i) =>
el("span", {
text: i % 3 === 0 || i === bars.length - 1 ? MONTH_NAMES[Number(key.split("-")[1]) - 1] : "",
}),
),
)
: null,
el("p", { class: "note", text: "When semlith read the file, not when it was written." }),
);
}
const EDGE_TIERS = ["extracted", "resolved", "ambiguous", "unresolved"];
function graphHealthCard(corpus) {
const tiers = new Map(corpus.tiers);
const total = EDGE_TIERS.reduce((sum, tier) => sum + (tiers.get(tier) || 0), 0);
const settled = (tiers.get("extracted") || 0) + (tiers.get("resolved") || 0);
const answerable = settled + (tiers.get("ambiguous") || 0);
return el(
"section",
{ class: "card pad health-card-full" },
el(
"div",
{ class: "card-head" },
el("h2", { text: "Graph health" }),
el("span", { class: "mono-chip", text: "counted from the edge table, every open store" }),
),
el("span", { class: "eyebrow", text: "Call edges by tier" }),
total
? el(
"div",
{ class: "tier-bar", role: "img", "aria-label": EDGE_TIERS.map((t) => `${t} ${tiers.get(t) || 0}`).join(", ") },
EDGE_TIERS.map((tier) =>
sized("width", Math.max(0.01, (tiers.get(tier) || 0) / total), {
class: `seg ${tier}`,
tabindex: "0",
"data-tip-title": tier,
"data-tip-tone": tier,
"data-tip-rows": JSON.stringify([
["edges", n(tiers.get(tier) || 0)],
["share", share(tiers.get(tier) || 0, total)],
["of total", n(total)],
]),
}),
),
)
: el("div", { class: "rail-hint", text: "No call edges yet." }),
el(
"div",
{ class: "tier-legend" },
EDGE_TIERS.map((tier) =>
el(
"span",
{ class: "tier-key" },
el("span", { class: `legend-dot ${tier}` }),
el("span", { text: `${tier} ${n(tiers.get(tier) || 0)}` }),
),
),
),
el("p", {
class: "note",
text: total
? `${share(settled, answerable || 1)} of the answerable edges settled on one definition. Unresolved ones name code no open store holds — the standard library, a dependency nobody indexed, a typo.`
: "Index something with a language that carries edges and this fills in.",
}),
el(
"div",
{ class: "health-columns" },
el(
"div",
{ class: "health-col" },
el("span", { class: "eyebrow", text: "Unresolved targets" }),
el("span", {
class: "health-figure",
text: total ? `${n(corpus.unresolved)} · ${share(corpus.unresolved, total)}` : "—",
}),
corpus.unresolvedNames.length
? el(
"div",
{ class: "chips" },
corpus.unresolvedNames.slice(0, 7).map(([name, count]) =>
el("span", {
class: "chip-flat",
tabindex: "0",
"data-tip-title": name,
"data-tip-rows": JSON.stringify([["calls", n(count)]]),
text: name,
}),
),
)
: null,
el("p", { class: "note", text: "Calls into code the store does not hold; hidden from views by default." }),
),
el(
"div",
{ class: "health-col" },
el("span", { class: "eyebrow", text: "Names with several definitions" }),
el("span", { class: "health-figure", text: n(corpus.ambiguousNames) }),
el(
"div",
{ class: "kv-list" },
corpus.ambiguousWorst.length
? corpus.ambiguousWorst.slice(0, 5).map(([name, count]) => factRow(name, n(count)))
: factRow("None", "—"),
),
),
el(
"div",
{ class: "health-col" },
el("span", { class: "eyebrow", text: "Languages carrying edges" }),
el("span", {
class: "health-figure",
text: `${n(corpus.languagesWithEdges)} of ${n(corpus.languageCount)}`,
}),
el("button", {
class: "link-button",
type: "button",
text: "The language table",
onclick: () => go("about"),
}),
),
),
);
}
async function corpusView() {
await refreshStores();
let data;
try {
data = await api("/api/corpus");
} catch (e) {
return el("div", { class: "view" }, pageHead("Inside the index"), error(e.message));
}
let ledger = null;
try {
ledger = await api("/api/ledger");
} catch (_) {
}
const stores = (data.stores || []).filter((row) => !row.error);
const broken = (data.stores || []).filter((row) => row.error);
const sum = (field) => stores.reduce((total, row) => total + (row[field] || 0), 0);
const files = sum("files");
const chunks = sum("chunks");
const lines = sum("lines");
const words = sum("words");
const characters = sum("characters");
const blank = sum("blank_lines");
const comments = sum("comment_lines");
const symbols = sum("symbols");
const dim = stores.length ? stores[0].vector_dim || 384 : 384;
const numbers = chunks * dim;
const pile = (field, key) => {
const into = new Map();
for (const store of stores) {
for (const row of store[field] || []) {
into.set(row[key], (into.get(row[key]) || 0) + (row.count || row.lines || 0));
}
}
return [...into.entries()].sort((a, b) => b[1] - a[1]);
};
const languages = pile("languages", "language");
const kinds = pile("kinds", "name");
const ambiguousWorst = pile("ambiguous_worst", "name");
const languageTotal = languages.reduce((total, [, count]) => total + count, 0) || 1;
const longest = stores
.map((row) => row.longest_file)
.filter(Boolean)
.sort((a, b) => b.lines - a.lines)[0];
const deepest = Math.max(0, ...stores.map((row) => row.deepest_path || 0));
const first = Math.min(...stores.map((row) => row.first_indexed || 0).filter(Boolean));
const last = Math.max(0, ...stores.map((row) => row.last_indexed || 0));
const ms = stores.map((row) => row.median_query_ms || 0).filter(Boolean);
const median = ms.length ? Math.round(ms.reduce((a, b) => a + b, 0) / ms.length) : 0;
const months = new Map();
for (const store of stores) {
for (const row of store.months || []) {
months.set(row.month, (months.get(row.month) || 0) + row.chunks);
}
}
const busiest = [...months.entries()].sort((a, b) => b[1] - a[1])[0];
const empty = !files;
const pages = Math.round(words / WORDS_PER_PAGE);
const mixCard = el(
"section",
{ class: "card pad mix-card" },
el(
"div",
{ class: "card-head" },
el("h2", { text: "Language mix, by line" }),
el("span", { class: "spacer" }),
el("span", {
class: "mono-chip",
text: `${n(lines)} lines · ${languages.length} language${languages.length === 1 ? "" : "s"}`,
}),
),
languages.length
? el(
"div",
{ class: "mix-bar", role: "img", "aria-label": languages.map(([k, v]) => `${k} ${v} lines`).join(", ") },
languages.map(([language, count], i) =>
sized("width", count / languageTotal, {
class: `seg tone-${i % 6}`,
tabindex: "0",
"data-tip-title": language,
"data-tip-tone": `tone-${i % 6}`,
"data-tip-rows": JSON.stringify([
["lines", n(count)],
["share", share(count, languageTotal)],
["of total", n(languageTotal)],
]),
}),
),
)
: null,
languages.length
? el(
"div",
{ class: "mix" },
languages.slice(0, 8).map(([language, count], i) =>
el(
"div",
{
class: "mix-row",
tabindex: "0",
"data-tip-title": language,
"data-tip-tone": `tone-${i % 6}`,
"data-tip-rows": JSON.stringify([
["lines", n(count)],
["share", share(count, languageTotal)],
["of total", n(languageTotal)],
]),
},
el("span", { class: `swatch tone-${i % 6}` }),
el("span", { class: "k", text: language }),
el("span", { class: "meter" }, sized("width", count / languageTotal, { class: `tone-${i % 6}` })),
el("span", { class: "v", text: n(count) }),
el("span", { class: "pct", text: share(count, languageTotal) }),
),
),
)
: el("div", { class: "rail-hint", text: "Nothing indexed yet." }),
el("p", { class: "note", text: "Every one of these carries graph edges as well as search." }),
);
const coverage = coveragePanel();
const unresolved = stores.reduce((sum, row) => sum + (row.unresolved || 0), 0);
const ambiguousNames = stores.reduce((sum, row) => sum + (row.ambiguous_names || 0), 0);
const languagesWithEdges = stores.reduce((sum, row) => sum + (row.languages_with_edges || 0), 0);
const tiers = pile("tiers", "name");
const spanLabel = first && last
? `corpus spans ${new Date(first * 1000).toLocaleDateString(undefined, { month: "short", year: "numeric" })} → ${when(last)}`
: "nothing indexed yet";
const ratio = ledger && ledger.ratio ? ledger.ratio : 0;
return el(
"div",
{ class: "view" },
pageHead(
"Inside the index",
"Measured from the store itself, not estimated. Nobody else can show you this, because nobody else keeps the whole corpus on your machine.",
{
pill: el("span", {
class: "mono-chip",
text: `${stores.length} store${stores.length === 1 ? "" : "s"} · recomputed on every read`,
}),
},
),
unreadableNotice(data.failed),
broken.length
? el(
"div",
{ class: "notice bad" },
el("div", {
class: "what",
text: `${broken.length} store${broken.length === 1 ? "" : "s"} could not be measured, so ${
broken.length === 1 ? "it is" : "they are"
} not in these figures.`,
}),
el(
"div",
{ class: "rows tight" },
broken.map((row) =>
el(
"details",
{ class: "unreadable" },
el("summary", {}, el("span", { class: "name", text: row.store })),
el("pre", { class: "code", text: row.error }),
),
),
),
)
: null,
empty
? el("p", { class: "subtitle", text: "Nothing is indexed yet, so there is nothing to measure." })
: null,
el(
"div",
{ class: "stat-cards" },
stat("Lines of code", n(lines), "counted, not estimated — comments and blanks separated out"),
stat("Words indexed", n(words), "code, prose, slides, spreadsheets and notebooks together"),
stat(
"If it were printed",
`${n(pages)} pp`,
`a stack of A4 you can search in ${median ? `${median} ms` : "milliseconds"}`,
),
stat(
"Reading time",
spellDuration(words / WORDS_PER_MINUTE),
"non-stop at 250 words a minute, no sleep",
),
),
el(
"div",
{ class: "corpus-top" },
mixCard,
el(
"div",
{ class: "corpus-panels" },
factPanel(
"What is in the prose",
kinds.length
? kinds.map(([kind, count]) => factRow(kind, `${n(count)} file${count === 1 ? "" : "s"}`))
: [factRow("Nothing indexed", "—")],
),
factPanel("Shape of the code", [
factRow("Average line", lines ? `${Math.round(characters / lines)} chars` : "—"),
factRow("Comment lines", lines ? share(comments, lines) : "—"),
factRow("Blank lines", lines ? share(blank, lines) : "—"),
factRow("Longest file", longest ? `${shortPath(longest.path)} · ${n(longest.lines)}` : "—"),
factRow("Deepest path", `${deepest} folder${deepest === 1 ? "" : "s"}`),
]),
factPanel("Time in the corpus", [
factRow("First read", Number.isFinite(first) && first ? new Date(first * 1000).toLocaleDateString() : "—"),
factRow("Newest write", last ? when(last) : "—"),
factRow("Span", spellSpan(first, last)),
factRow("Busiest month", busiest ? `${busiest[0]} · ${n(busiest[1])} chunks` : "—"),
]),
factPanel("The vectors themselves", [
factRow("Vectors", n(chunks)),
factRow("Numbers stored", n(numbers)),
factRow("As float32 it would be", bytes(numbers * 4)),
factRow("Quantised to int8", bytes(numbers)),
factRow("Query at this size", median ? `${median} ms` : "not measured yet"),
]),
),
),
monthsCard(months, spanLabel),
graphHealthCard({
tiers,
unresolved,
unresolvedNames: pile("unresolved_names", "name"),
ambiguousNames,
ambiguousWorst,
languagesWithEdges,
languageCount: languages.length,
}),
el(
"div",
{ class: "fact-cards" },
el(
"section",
{ class: "fact-card" },
el("h2", { text: `${n(characters)} characters` }),
el("p", { text: "Every keystroke in the corpus, kept on this machine and nowhere else." }),
),
el(
"section",
{ class: "fact-card" },
el("h2", { text: median ? `${median} ms` : "milliseconds" }),
el("p", {
text: median
? "The middle of every retrieval this machine has recorded — meaning, not just words."
: "Ask this index something and the ledger will start recording how long it took.",
}),
),
el(
"section",
{ class: "fact-card" },
el("h2", { text: ratio ? `${ratio.toFixed(1)}× fewer tokens` : `${n(symbols)} definitions` }),
el("p", {
text: ratio
? `What your agents read versus reading those files whole · coverage ${ledger.coverage}% · ${ledger.tier}`
: "Every definition the graph extracted from this corpus.",
}),
),
),
el("span", { class: "eyebrow", text: "What the graph covers" }),
coverage.node,
);
}
function projectsChecklist(options) {
const { onChoose, onError } = options || {};
const card = el("div", { class: "card picker", hidden: true });
const ticked = new Set();
async function open(path) {
let data;
try {
data = await api(`/api/projects?path=${encodeURIComponent(path || "")}`);
} catch (e) {
if (onError) onError(e.message);
return false;
}
card.hidden = false;
const rows = data.projects || [];
for (const row of rows) {
if (data.repositories && !row.indexed && !ticked.size) ticked.add(row.path);
}
const paint = () => open(data.path);
fill(
card,
el(
"div",
{ class: "crumbs" },
el("button", {
class: "button secondary small",
type: "button",
text: "Up",
disabled: !data.parent,
onclick: () => open(data.parent),
}),
el("span", { class: "where", text: data.path }),
el("span", { class: "spacer" }),
el("button", {
class: "button secondary small",
type: "button",
text: "All",
onclick: () => {
for (const row of rows) ticked.add(row.path);
paint();
},
}),
el("button", {
class: "button secondary small",
type: "button",
text: "None",
onclick: () => {
ticked.clear();
paint();
},
}),
el("button", {
class: "button small",
type: "button",
disabled: !ticked.size,
text: ticked.size ? `Use ${ticked.size}` : "Use",
onclick: () => {
card.hidden = true;
if (onChoose) onChoose([...ticked]);
ticked.clear();
},
}),
el("button", {
class: "button secondary small",
type: "button",
text: "Close",
onclick: () => {
card.hidden = true;
},
}),
),
el("p", {
class: "subtitle",
text: data.repositories
? "The repositories directly under this folder. One level only: a monorepo is one store, and its nested repositories are its own business."
: "Nothing under this folder is a repository, so these are its plain subfolders.",
}),
el(
"div",
{ class: "entries" },
rows.length
? rows.map((row) => {
const box = el("input", {
type: "checkbox",
class: "tick",
"aria-label": `Index ${row.name}`,
checked: ticked.has(row.path),
onchange: () => {
if (box.checked) ticked.add(row.path);
else ticked.delete(row.path);
paint();
},
});
return el(
"div",
{ class: "entry-row" },
box,
el(
"button",
{
class: "entry",
type: "button",
title: `Open ${row.path}`,
onclick: () => open(row.path),
},
icon(ICONS.folder),
el("span", { class: "name", text: row.name }),
row.indexed ? pill(`in ${row.indexed}`, "warn") : null,
),
);
})
: empty("Nothing here that Semlith can index."),
),
(data.folders || []).filter((f) => !rows.some((row) => row.path === f.path)).length
? el(
"div",
{ class: "entries" },
el("p", { class: "subtitle", text: "Or open one of these:" }),
(data.folders || [])
.filter((f) => !rows.some((row) => row.path === f.path))
.map((folder) =>
el(
"div",
{ class: "entry-row" },
el(
"button",
{
class: "entry",
type: "button",
title: `Open ${folder.path}`,
onclick: () => open(folder.path),
},
icon(ICONS.folder),
el("span", { class: "name", text: folder.name }),
),
),
),
)
: null,
);
return true;
}
return {
node: card,
open,
close: () => {
card.hidden = true;
},
isOpen: () => !card.hidden,
};
}
function installPanel() {
const card = el("div", { class: "card pad install-panel" });
const title = () => el("span", { class: "card-title", text: "This machine" });
fill(card, title(), el("p", { class: "subtitle", text: "Checking…" }));
const stateWord = {
done: "done",
"already-done": "already done",
skipped: "not done",
failed: "failed",
};
const tone = (state) =>
state === "done" || state === "already-done" ? "good" : state === "failed" ? "bad" : "warn";
function paint(setup) {
const note = el("div", { class: "note" });
const fixPath = el("button", {
class: "button small",
type: "button",
text: "Add it to PATH",
onclick: async () => {
fixPath.disabled = true;
note.className = "note";
note.textContent = "Editing your shell profile…";
try {
const done = await post("/api/setup", { step: "path" });
note.textContent = `${done.step.detail} — open a new terminal for it to take effect.`;
paint(done.status);
} catch (e) {
note.className = "note bad";
note.textContent = e.message;
fixPath.disabled = false;
}
},
});
const install = el("button", {
class: "button small",
type: "button",
text: "Install it",
disabled: true,
onclick: async () => {
install.disabled = true;
check.disabled = true;
note.className = "note";
note.textContent = "Downloading and verifying…";
try {
const done = await post("/api/upgrade", { action: "apply" });
note.textContent = done.restart;
} catch (e) {
note.className = "note bad";
note.textContent = e.message;
} finally {
check.disabled = false;
}
},
});
const check = el("button", {
class: "button secondary small",
type: "button",
text: "Check for updates",
onclick: async () => {
check.disabled = true;
note.className = "note";
note.textContent = "Asking GitHub…";
try {
const found = await post("/api/upgrade", { action: "check" });
note.textContent = found.available
? `${found.latest} is available; you are on ${found.installed}.`
: `Already on the newest build, ${found.installed}.`;
if (found.blocked) note.textContent += ` ${found.blocked}`;
install.disabled = !found.available || Boolean(found.blocked);
} catch (e) {
note.className = "note bad";
note.textContent = e.message;
} finally {
check.disabled = false;
}
},
});
fill(
card,
title(),
el(
"div",
{ class: "rows" },
(setup.steps || []).map((step) =>
el(
"div",
{ class: "kv step" },
pill(stateWord[step.state] || step.state, tone(step.state)),
el("span", { class: "card-title", text: step.name }),
lineCell(step.detail, "meta"),
step.name === "path" && step.state !== "already-done" && step.state !== "done"
? el("span", { class: "spacer" })
: null,
step.name === "path" && step.state !== "already-done" && step.state !== "done"
? fixPath
: null,
),
),
),
note,
el("hr", { class: "rule" }),
el("span", { class: "eyebrow", text: `Installed version ${setup.version}` }),
el("div", { class: "actions" }, check, install),
);
}
api("/api/setup")
.then(paint)
.catch((e) => fill(card, title(), error(e.message)));
return card;
}
async function doctorView() {
let data;
try {
data = await api("/api/doctor");
} catch (e) {
return el("div", { class: "view" }, pageHead("Doctor"), error(e.message));
}
const state = (c) => {
if (c.note) return { text: "cannot register", kind: null };
if (c.registered) return { text: `registered (${c.scope || "user"})`, kind: "good" };
if (!c.command) return { text: "not registered", kind: "bad" };
if (!c.present) return { text: "not installed", kind: null };
if (c.scope === "project")
return { text: "one project only", kind: "bad" };
return { text: "installed, not registered", kind: "bad" };
};
const steering = (r) => {
const parts = [];
if (r.skill && r.skill !== "paste") parts.push(`skill ${r.skill === "present" ? "linked" : r.skill}`);
if (r.hook && r.hook !== "paste") parts.push(`hook ${r.hook}${r.hook_mode ? ` (${r.hook_mode})` : ""}`);
if (r.always_load !== undefined) parts.push(r.always_load ? "always loaded" : "alwaysLoad missing");
if (r.explorer !== undefined) parts.push(r.explorer ? "research agent" : "no research agent");
if (r.rules && r.rules !== "paste") parts.push(`rule ${r.rules}`);
return parts;
};
const clients = dataTable({
caption:
"Every documented client, whether it is on this machine, whether it is registered, whether the skill and the steering hook are installed, and what would fix it.",
rows: data.clients || [],
columns: [
{ key: "name", label: "Client", sortable: true, value: (r) => r.name },
{
key: "state",
label: "semlith",
sortable: true,
value: (r) => state(r).text,
render: (r) => {
const s = state(r);
return pill(s.text, s.kind);
},
},
{
key: "steering",
label: "Skill & hook",
sortable: true,
value: (r) => steering(r).join(", "),
render: (r) => {
const parts = steering(r);
if (!parts.length) return el("span", { class: "sub", text: "—" });
return el(
"span",
{ class: "pills" },
...parts.map((part) =>
pill(part, part.endsWith("present") || part.endsWith("linked") ? "good" : "warn"),
),
);
},
},
{
key: "repair",
label: "To fix",
render: (r) =>
r.repair
? copyField(r.repair)
: el("span", { class: "sub", text: r.note ? "nothing to run" : "—" }),
},
],
});
const rulesBox = el("div", { class: "strip" });
const note = el("div", { class: "note" });
const paintRules = (rules) => {
rulesBox.textContent = "";
for (const rule of rules) {
const row = el(
"div",
{ class: "stat fact" },
el("span", { class: "eyebrow", text: rule.id }),
el("span", {
class:
rule.applicable === false
? "fact-value"
: rule.ok
? "fact-value"
: "fact-value bad",
text: rule.applicable === false ? "not applicable" : rule.ok ? "holds" : "fails",
}),
el("span", { class: "sub", text: rule.check }),
);
if (!rule.ok && rule.manual) row.appendChild(copyField(rule.manual));
if (!rule.ok && rule.repair) {
const fix = el("button", {
class: "button secondary small",
type: "button",
text: "Fix",
onclick: async () => {
fix.disabled = true;
note.className = "note";
note.textContent = "Applying…";
try {
const out = await post("/api/privacy/fix", { rule: rule.id });
const applied = (out.applied || [])[0];
note.textContent = applied
? `${applied.path}: ${applied.now}, was ${applied.was}` +
(applied.rechecked_ok ? "" : " — the rule still does not hold")
: "Nothing to apply.";
paintRules(out.rules || []);
} catch (e) {
note.className = "note bad";
note.textContent = e.message;
fix.disabled = false;
}
},
});
row.appendChild(fix);
}
rulesBox.appendChild(row);
}
};
paintRules(data.rules || []);
const gpuNote = el("div", { class: "note" });
const gpuTable = dataTable({
caption:
"Each accelerator lane's check: whether its vectors match the CPU's, on which device and variant, the lowest cosine over the known answers, and its rate.",
rows: [],
columns: [
{ key: "lane", label: "Lane", value: (r) => LANE_NAMES[r.lane] || r.lane },
{
key: "result",
label: "Result",
value: (r) => (r.reason ? "n/a" : r.passed ? "ok" : "FAIL"),
render: (r) =>
r.reason ? pill("n/a", null) : r.passed ? pill("ok", "good") : pill("FAIL", "bad"),
},
{ key: "device", label: "Device", value: (r) => r.device || r.reason || "—" },
{ key: "variant", label: "Variant", value: (r) => r.variant || "—" },
{
key: "cosine",
label: "Cosine",
value: (r) => (typeof r.cosine === "number" ? r.cosine.toFixed(4) : "—"),
},
{
key: "rate",
label: "Chunks/s",
value: (r) => (typeof r.chunks_per_s === "number" ? perSecond(r.chunks_per_s) : "—"),
},
],
});
gpuTable.node.hidden = true;
const gpuButton = el("button", {
class: "button secondary small",
type: "button",
text: "Run the GPU check",
onclick: async () => {
gpuButton.disabled = true;
gpuButton.setAttribute("aria-busy", "true");
setText(gpuButton, "Checking…");
gpuNote.className = "note";
setText(gpuNote, "Checking each lane. The first check of a lane downloads what it needs, so it can take a minute.");
try {
const answer = await post("/api/doctor/gpu", {});
gpuTable.update(answer.checks || []);
gpuTable.node.hidden = false;
setText(gpuNote, "");
} catch (e) {
gpuNote.className = "note bad";
setText(gpuNote, e.message);
} finally {
gpuButton.disabled = false;
gpuButton.removeAttribute("aria-busy");
setText(gpuButton, "Run the GPU check");
}
},
});
const rows = data.clients || [];
const registered = rows.filter((c) => c.registered).length;
const fixable = rows.filter((c) => !c.registered && c.repair).length;
return el(
"div",
{ class: "view" },
pageHead(
"Doctor",
"Whether each agent client on this machine can reach semlith, and what to run for the ones that cannot.",
),
el(
"div",
{ class: "head" },
el("span", { class: "card-title", text: "Rules" }),
el("span", {
class: "meta",
text: "what the daemon found when it looked, not what the documentation says",
}),
),
rulesBox,
el(
"div",
{ class: "card pad" },
el(
"div",
{ class: "head" },
el("span", { class: "card-title", text: "GPU check" }),
el("span", { class: "spacer" }),
gpuButton,
),
el("p", {
class: "subtitle",
text: "Whether each accelerator lane's vectors agree with the CPU's, and how fast it runs. The same check as semlith doctor --gpu.",
}),
gpuNote,
gpuTable.node,
),
el(
"div",
{ class: "card pad" },
el(
"div",
{ class: "head" },
el("span", { class: "card-title", text: "Clients" }),
el("span", { class: "spacer" }),
el("span", {
class: "meta",
text: `${n(registered)} registered · ${n(fixable)} to fix · ${n(rows.length)} known`,
}),
),
clients.node,
),
note,
);
}
async function agentsView() {
let data;
try {
data = await api("/api/agents");
} catch (e) {
return el("div", { class: "view" }, pageHead("Agents"), error(e.message));
}
noteAgents(data);
const clients = data.clients || [];
const tools = data.tools || [];
const live = data.connections || [];
const endpoint = data.endpoint || { url: "", open: false };
const address = el("code", { class: "text", text: endpoint.url });
const endpointNote = el("div", { class: "note" });
const toggle = el("button", {
class: "button secondary small",
type: "button",
text: endpoint.open ? "Stop" : "Start",
onclick: () => {
if (!endpoint.open) return apply();
ask({
title: "Close the MCP endpoint?",
body: "Every connected agent loses semlith until it is started again. The daemon, the watcher and the portal are unaffected, and Start puts it back.",
confirm: "Close the endpoint",
tone: "bad",
run: apply,
});
},
});
async function apply() {
toggle.disabled = true;
endpointNote.className = "note";
endpointNote.textContent = endpoint.open ? "Closing…" : "Opening…";
try {
const done = await post("/api/endpoint", { open: !endpoint.open });
endpoint.open = done.open;
toggle.textContent = done.open ? "Stop" : "Start";
endpointNote.textContent = done.open
? "The endpoint is answering. A configured client reconnects on its next call."
: "The endpoint is closed. The daemon, the watcher and the portal are unaffected.";
fill(statePill, el("i", {}), done.open ? "answering" : "closed");
statePill.className = done.open ? "pill good" : "pill warn";
} catch (e) {
endpointNote.className = "note bad";
endpointNote.textContent = e.message;
} finally {
toggle.disabled = false;
}
}
const statePill = pill(endpoint.open ? "answering" : "closed", endpoint.open ? "good" : "warn");
const keyNote = el("div", { class: "note" });
const rotate = el("button", {
class: "button secondary small",
type: "button",
text: "Rotate key",
onclick: async () => {
rotate.disabled = true;
keyNote.className = "note";
keyNote.textContent = "Rotating…";
try {
const done = await post("/api/key", {});
if (stanzas.revealed) stanzas.key = done.key;
showClient(chosen);
const carried = done.updated || [];
const rewrote = carried.length
? `Carried the new key into ${carried.length} configuration file${
carried.length === 1 ? "" : "s"
}: ${carried.join(", ")}.`
: "No configuration file on this machine carried the old key.";
keyNote.textContent = done.previous_valid
? `New key. ${rewrote} The previous one keeps working for fifteen minutes, so a session already open finishes — any client configured elsewhere needs the new stanza before then.`
: `New key. ${rewrote} The previous one is refused now; any client configured elsewhere needs the new stanza.`;
} catch (e) {
keyNote.className = "note bad";
keyNote.textContent = e.message;
} finally {
rotate.disabled = false;
}
},
});
const MASKED = data.key_set ? "sml_" + "•".repeat(24) : "no key yet";
const keyBox = el("code", { class: "text", text: MASKED });
const reveal = el("button", {
class: "button secondary small",
type: "button",
text: "Reveal",
onclick: async () => {
if (stanzas.revealed) {
stanzas.revealed = false;
stanzas.key = "${" + KEY_ENV + "}";
keyBox.textContent = MASKED;
reveal.textContent = "Reveal";
showClient(chosen);
return;
}
reveal.disabled = true;
try {
const shown = await post("/api/agents/reveal", {});
stanzas.revealed = true;
stanzas.key = String(shown.key);
keyBox.textContent = stanzas.key;
reveal.textContent = "Hide";
showClient(chosen);
} catch (e) {
keyNote.className = "note bad";
keyNote.textContent = e.message;
} finally {
reveal.disabled = false;
}
},
});
const connected = dataTable({
className: "w-agents",
caption: "Every documented client, whether it is on this machine, and whether it is registered.",
sort: "name",
grow: false,
rows: live,
columns: [
{
key: "name",
label: "Connected",
value: (c) => c.name,
render: (c) => el("div", { class: "who" }, el("span", { class: "dot good" }), c.name),
},
{ key: "transport", label: "Transport", className: "meta", value: (c) => c.transport },
{ key: "revision", label: "Revision", className: "meta narrow-drop", value: (c) => c.revision },
{
key: "queries",
label: "Queries",
className: "num",
value: (c) => c.queries,
render: (c) => n(c.queries),
},
],
});
const GROUPS = [
["terminal", "Terminal"],
["editor", "Editors"],
["desktop", "Desktop"],
];
const KEY_ENV = data.key_env || "SEMLITH_AGENT_KEY";
const stanzas = { key: "${" + KEY_ENV + "}", revealed: false };
let group = GROUPS.find(([id]) => clients.some((c) => c.group === id))[0];
let chosen = clients.findIndex((c) => c.group === group);
const tabs = el("div", { class: "tabs" });
const chips = el("div", { class: "filters" });
const body = el("div", { class: "stanza" });
const KEY_SLOT = "${SEMLITH_AGENT_KEY}";
const overHttp = (text) => text.includes("/mcp") || text.includes("--transport http");
function httpStanza() {
return `{\n "mcpServers": {\n "semlith": {\n "url": "${endpoint.url}",\n "headers": { "Authorization": "Bearer ${stanzas.key}" }\n }\n }\n}`;
}
function showGroup(id) {
group = id;
const first = clients.findIndex((c) => c.group === id);
showClient(first < 0 ? chosen : first);
}
function showClient(index) {
chosen = index;
const client = clients[index];
fill(
tabs,
GROUPS.map(([id, label]) => {
const count = clients.filter((c) => c.group === id).length;
return el(
"button",
{
class: "tab",
type: "button",
"aria-pressed": String(id === group),
onclick: () => showGroup(id),
},
label,
el("span", { class: "count", text: String(count) }),
);
}),
);
fill(
chips,
clients.map((c, i) =>
c.group === group
? el("button", {
class: "chip",
type: "button",
"aria-pressed": String(i === chosen),
text: c.name,
onclick: () => showClient(i),
})
: null,
),
);
if (!client) {
fill(body, empty("No client stanza is compiled into this build."));
return;
}
const own = (client.stanzas || []).map((s) => ({
format: s.format,
text: s.text.trimEnd().replaceAll(KEY_SLOT, stanzas.key),
}));
const shells = own.filter((s) => s.format === "sh");
const wired = shells.filter((s) => overHttp(s.text));
const spawned = shells.filter((s) => !overHttp(s.text));
const blocks = own.filter((s) => s.format !== "sh");
const http = blocks.filter((s) => overHttp(s.text));
const stdio = blocks.filter((s) => !overHttp(s.text));
const config = http.length ? http : stdio;
fill(
body,
el(
"div",
{ class: "head" },
el("span", { class: "card-title", text: client.name }),
el("span", { class: "meta", text: client.note }),
),
el("span", {
class: "eyebrow",
text: config.length && config === http
? "Over HTTP — one endpoint, one key, no subprocess"
: "As a subprocess — no key needed",
}),
(config.length ? config.map((s) => s.text) : [httpStanza()]).map((text) =>
codeBlock(text, "Copy stanza"),
),
wired.length ? el("span", { class: "eyebrow", text: "Or from a terminal" }) : null,
wired.map((s) => copyField(s.text, true)),
spawned.length
? el("span", { class: "eyebrow", text: "Or add the subprocess from a terminal" })
: null,
spawned.map((s) => copyField(s.text, true)),
);
}
showClient(chosen);
const planBox = el("div", { class: "rules" });
const planNote = el("div", { class: "note" });
let planned = null;
const write = el("button", {
class: "button secondary small",
type: "button",
text: "Write these files",
hidden: true,
onclick: async () => {
write.disabled = true;
planNote.className = "note";
planNote.textContent = "Writing…";
try {
const out = await post("/api/agents/register", { confirm: true });
const written = out.written || [];
planNote.textContent = written.length
? `Wrote ${written.length} file${written.length === 1 ? "" : "s"}. Each has a .semlith-backup beside it.`
: "Nothing needed writing.";
planned = out.plan || [];
paintPlan();
} catch (e) {
planNote.className = "note bad";
planNote.textContent = e.message;
write.disabled = false;
}
},
});
const paintPlan = () => {
planBox.textContent = "";
for (const item of planned || []) {
const what =
item.action === "create"
? "will be created"
: item.action === "merge"
? "will be merged into"
: item.action === "already-done"
? "already has semlith"
: `will be left alone — ${item.reason}`;
planBox.appendChild(
el(
"div",
{ class: "rule-row" },
el(
"div",
{ class: "rule-head" },
el("span", { class: "rule-id", text: item.client }),
),
el("code", { class: "rule-check", text: item.path }),
el("span", { class: "sub", text: what }),
),
);
}
const pending = (planned || []).some(
(p) => p.action === "create" || p.action === "merge",
);
write.hidden = !pending;
write.disabled = !pending;
};
const preview = el("button", {
class: "button small",
type: "button",
text: "Show what would be written",
onclick: async () => {
preview.disabled = true;
planNote.className = "note";
planNote.textContent = "Reading…";
try {
const out = await post("/api/agents/register", {});
planned = out.plan || [];
planNote.textContent = planned.length
? "Nothing has been written yet."
: "There is no client on this machine whose file semlith would write.";
paintPlan();
} catch (e) {
planNote.className = "note bad";
planNote.textContent = e.message;
} finally {
preview.disabled = false;
}
},
});
const registerAll = el(
"div",
{ class: "card pad" },
el("span", { class: "card-title", text: "Register the clients that have no command" }),
says(
"Ten of the twenty-seven cannot be asked to register themselves, so semlith would write their configuration file. Every path is listed before anything is written, each file is backed up beside itself, and one that does not parse is left alone. This is the terminal's ",
mono("semlith setup --register-all"),
".",
),
el("div", { class: "head" }, preview, write),
planBox,
planNote,
);
const MECHANISMS = {
launchd: "a launchd login agent",
systemd: "a systemd user service",
schtasks: "a Windows logon task",
};
const labelled = (label, value) =>
el(
"div",
{ class: "rows tight" },
el("span", { class: "eyebrow", text: label }),
copyField(value),
);
const serviceCard = () => {
if (!data.service) return null;
const status = data.service.status || {};
const mechanism = MECHANISMS[status.mechanism] || "a login service";
const started = data.service.last_started
? ` Last started ${when(data.service.last_started)}.`
: "";
const bits = [];
if (status.installed) {
bits.push(
says(
`The daemon is installed as ${mechanism}, so the endpoint is answering again after a reboot without anybody opening a terminal for it.${started}`,
),
);
} else {
bits.push(
says(
`The daemon is not installed as a login service, so the endpoint lives exactly as long as whatever started it: close that terminal, or reboot, and every client configured below loses semlith until somebody starts it again.${started}`,
),
labelled("To install", "semlith start --service"),
);
}
if (status.definition) bits.push(labelled("Definition", status.definition));
if (status.log) bits.push(labelled("Log", status.log));
if (status.installed && status.restarts === false) {
bits.push(
el("div", {
class: "note",
text: "A logon task is restarted only when it failed. One that exited cleanly stays stopped until the next logon, so a daemon stopped on purpose is a daemon started again on purpose.",
}),
);
}
return el(
"div",
{ class: "card pad dense" },
el(
"div",
{ class: "head" },
el("span", { class: "card-title", text: "Login service" }),
el("span", { class: "spacer" }),
pill(
status.installed ? "installed" : "not installed",
status.installed ? "good" : "warn",
),
),
bits,
);
};
const inUse = (data.doctor || []).filter((c) => c && c.in_use);
const clientState = (c) => {
if (c.note) return { text: "cannot register", kind: null };
if (c.registered) return { text: `registered (${c.scope || "user"})`, kind: "good" };
if (!c.command) return { text: "not registered", kind: "bad" };
if (!c.present) return { text: "not installed", kind: null };
if (c.scope === "project") return { text: "one project only", kind: "bad" };
return { text: "installed, not registered", kind: "bad" };
};
const clientWhy = (c) => {
const lines = [];
if (c.note) lines.push(el("span", { class: "sub", text: c.note }));
if (c.explain && !/this directory/i.test(c.explain))
lines.push(el("span", { class: "sub", text: c.explain }));
const off = (c.disabled_in || []).filter(Boolean);
if (off.length)
lines.push(el("span", { class: "meta", text: `switched off for: ${off.join(", ")}` }));
if (c.repair) lines.push(copyField(c.repair));
if (!lines.length && !c.present && c.fault)
lines.push(
el("span", {
class: "sub",
text: c.command
? `Nothing named ${c.command} is on this machine.`
: "None of this client's configuration files are on this machine.",
}),
);
return lines.length
? el("div", { class: "rows tight" }, lines)
: el("span", { class: "sub", text: "—" });
};
const inUseTable = inUse.length
? dataTable({
caption:
"Every agent client installed on this machine, whether it is registered with semlith, and what to run for the ones that are not.",
sort: "name",
grow: false,
rows: inUse,
columns: [
{ key: "name", label: "Client", sortable: true, value: (c) => c.name },
{
key: "state",
label: "semlith",
sortable: true,
value: (c) => clientState(c).text,
render: (c) => {
const s = clientState(c);
return pill(s.text, c.fault ? "bad" : s.kind);
},
},
{ key: "why", label: "Why", className: "why", render: clientWhy },
],
})
: null;
const inUseCard = data.doctor
? el(
"div",
{ class: "card pad" },
el(
"div",
{ class: "head" },
el("span", { class: "card-title", text: "Clients in use" }),
el("span", { class: "spacer" }),
el("span", {
class: "meta",
text: `${n(inUse.length)} of ${n((data.doctor || []).length)} known`,
}),
),
inUseTable
? inUseTable.node
: empty("No documented client is installed on this machine."),
says("All twenty-seven, including the ones nobody here has, are on the Doctor page."),
)
: null;
return el(
"div",
{ class: "view" },
pageHead("Agents", "One endpoint, every client, no per-client process.", {
pill: statePill,
actions: [
el("div", { class: "copyfield worded" }, address, copyButton(endpoint.url, "Copy", true)),
toggle,
rotate,
],
}),
endpointNote,
el(
"div",
{ class: "grid scroller agent-row" },
el(
"div",
{ class: "rows" },
live.length
? connected.node
: el(
"div",
{ class: "card pad dense" },
el("span", { class: "card-title", text: "Connected" }),
empty("No client is talking to this daemon right now."),
),
el(
"div",
{ class: "card pad dense tools" },
el(
"div",
{ class: "head" },
el("span", { class: "card-title", text: "Tools exposed" }),
el("span", {
class: "meta",
text: `${tools.length} tool${tools.length === 1 ? "" : "s"}`,
}),
),
el(
"div",
{ class: "cost" },
el("span", { class: "card-title", text: "What the tool list costs" }),
el("span", {
class: "cost-line",
text: `${tools.length} tools · ${n(data.tool_list_bytes || 0)} bytes · ${n(
data.tool_list_tokens || Math.ceil((data.tool_list_bytes || 0) / 4),
)} tokens per session`,
}),
el("span", {
class: "cost-note",
text: `read once, before the agent asks anything — counted ${
data.tool_list_tier || "chars4"
}`,
}),
),
el(
"div",
{ class: "tool-grid" },
tools.flatMap((tool) => [
el("span", { class: "tool-name", text: tool.name }),
el("span", { class: "tool-about", text: tool.about }),
]),
),
),
installPanel(),
),
el("div", { class: "card pad stanzas" }, tabs, chips, body),
),
el(
"div",
{ class: "grid agent-row agent-extras" },
serviceCard(),
el(
"div",
{ class: "card pad dense" },
el("span", { class: "card-title", text: "Agent key" }),
el("div", { class: "copyfield" }, keyBox, el("div", { class: "actions" }, reveal)),
says(
"Shown truncated, and fetched in full only when you press Reveal — the page does not receive it just for being open. No registration semlith writes carries it: a registered client launches ",
mono("semlith mcp"),
", which reads the key from ",
mono(data.key_path || "~/.semlith/agent.key"),
" itself, so a rotation reconfigures nothing. The key is for the HTTP stanzas below, which a daemon on another machine still needs — paste one of those and you export ",
mono("${" + KEY_ENV + "}"),
" yourself.",
),
),
),
keyNote,
registerAll,
inUseCard,
);
}
async function privacyView() {
let data;
try {
data = await api("/api/privacy");
} catch (e) {
return el("div", { class: "view" }, pageHead("Privacy"), error(e.message));
}
watchLive(["privacy"], repaintView);
const tokenBox = el("code", { class: "text", text: data.token_preview || "—" });
const rotateNote = el("div", { class: "note" });
const rotate = el("button", {
class: "button secondary small",
type: "button",
text: "Rotate",
onclick: async () => {
rotate.disabled = true;
rotateNote.className = "note";
rotateNote.textContent = "Rotating…";
try {
const fresh = await post("/api/rotate", {});
session.set(String(fresh.token));
tokenBox.textContent = `${String(fresh.token).slice(0, 16)}…`;
rotateNote.textContent =
"Rotated. The old token stopped working immediately. This is the portal's own session token, not the agent key: no client stanza carries it, so nothing needs reconfiguring. The agent key is rotated on the Agents page.";
} catch (e) {
rotateNote.className = "note bad";
rotateNote.textContent = e.message;
} finally {
rotate.disabled = false;
}
},
});
const fact = (key, value, why) =>
el(
"div",
{ class: "stat fact" },
el("span", { class: "eyebrow", text: key }),
el("span", { class: "fact-value", text: value }),
el("span", { class: "sub", text: why }),
);
const rulesBox = el("div", { class: "rules" });
const fixNote = el("div", { class: "note" });
const paintRules = (rules) => {
rulesBox.textContent = "";
for (const rule of rules) {
const unmeasured = rule.applicable === false;
const row = el(
"div",
{ class: rule.ok || unmeasured ? "rule-row" : "rule-row bad" },
el(
"div",
{ class: "rule-head" },
el("span", { class: "dot " + (unmeasured ? "" : rule.ok ? "good" : "warn") }),
el("span", { class: "rule-id", text: rule.id }),
),
el("p", { class: "rule-text", text: rule.rule }),
el(
"div",
{ class: "rule-found" },
el("span", { class: "eyebrow", text: "found" }),
el("code", { class: "rule-check", text: rule.check }),
),
);
if (!rule.ok && rule.manual) {
row.appendChild(
el(
"div",
{ class: "rule-found" },
el("span", { class: "eyebrow", text: "to fix" }),
copyField(rule.manual),
),
);
}
if (!rule.ok && rule.repair) {
const fix = el("button", {
class: "button secondary small",
type: "button",
text: "Fix",
onclick: async () => {
fix.disabled = true;
fixNote.className = "note";
fixNote.textContent = "Applying…";
try {
const out = await post("/api/privacy/fix", { rule: rule.id });
const applied = (out.applied || [])[0];
fixNote.textContent = applied
? `${applied.path}: ${applied.now}, was ${applied.was}` +
(applied.rechecked_ok ? "" : " — the rule still does not hold")
: "Nothing to apply.";
paintRules(out.rules || []);
} catch (e) {
fixNote.className = "note bad";
fixNote.textContent = e.message;
fix.disabled = false;
}
},
});
row.appendChild(fix);
}
rulesBox.appendChild(row);
}
};
paintRules(data.rules || []);
const scanNote = el("div", { class: "note" });
const scanBox = el("div", { class: "rows tight" });
const scanTable = dataTable({
caption: "Every file a store is still holding that semlith would refuse to index today.",
sort: "path",
columns: [
{ key: "store", label: "Store", className: "meta", sortable: true, value: (f) => f.store },
{
key: "path",
label: "Path",
sortable: true,
value: (f) => f.path,
render: (f) => pathCell(f.path),
},
{
key: "why",
label: "Why it would be refused",
sortable: false,
render: (f) => lineCell(f.why),
},
{
key: "forget",
label: "",
sortable: false,
render: (f) =>
el("button", {
class: "forget",
type: "button",
text: "Forget",
onclick: () =>
ask({
title: "Forget this file?",
body: `${f.path} — its chunks and vectors are dropped from ${f.store}. The file on disk is untouched.`,
confirm: "Forget it",
tone: "bad",
run: () => forgetFound([f]),
}),
}),
},
],
rows: [],
});
async function forgetFound(findings) {
scanNote.className = "note";
scanNote.textContent = "";
const byStore = new Map();
for (const f of findings) {
if (!byStore.has(f.store)) byStore.set(f.store, []);
byStore.get(f.store).push(f.key || f.path);
}
try {
for (const [store, paths] of byStore) await post("/api/forget", { paths, store });
} catch (e) {
scanNote.className = "note bad";
scanNote.textContent = e.message;
throw e;
}
await runScan();
}
const scanButton = el("button", {
class: "button secondary small",
type: "button",
text: "Scan",
onclick: () => runScan(),
});
async function runScan() {
scanButton.disabled = true;
scanButton.textContent = "Scanning…";
scanNote.className = "note";
scanNote.textContent = "";
try {
const found = (await api("/api/privacy/scan")).findings || [];
scanTable.update(found);
const stores = [...new Set(found.map((f) => f.store))];
fill(
scanBox,
found.length
? [
el(
"div",
{ class: "bulk" },
el("span", {
class: "meta",
text: `${n(found.length)} file${found.length === 1 ? "" : "s"} would be refused today`,
}),
el("span", { class: "spacer" }),
el("button", {
class: "button danger small",
type: "button",
text: "Forget all",
onclick: () =>
ask({
title: `Forget ${n(found.length)} file${found.length === 1 ? "" : "s"}?`,
body: `Their chunks and vectors are dropped from ${stores.join(
" and ",
)}. The files on disk are untouched.`,
confirm: `Forget ${n(found.length)} file${found.length === 1 ? "" : "s"}`,
tone: "bad",
run: () => forgetFound(found),
}),
}),
),
scanTable.node,
]
: empty("Nothing this store holds would be refused today."),
);
} catch (e) {
scanNote.className = "note bad";
scanNote.textContent = e.message;
} finally {
scanButton.disabled = false;
scanButton.textContent = "Scan again";
}
}
const step = (number, what, command) =>
el(
"div",
{ class: "step-row" },
el("span", { class: "n", text: number }),
el(
"div",
{ class: "what" },
el("div", { class: "say", text: what }),
copyField(command),
),
);
return el(
"div",
{ class: "view" },
pageHead(
"Privacy",
"The claim is “nothing leaves this machine”. This page is how you check it yourself, in about a minute.",
{ pill: pill(data.airgap ? "airgap mode armed" : "airgap off", data.airgap ? "good" : null) },
),
el(
"div",
{ class: "strip" },
fact("Bind address", data.bind, "loopback only, with no flag to change it"),
fact("CORS", "none", "no origin may read this server's responses"),
fact("Host check", "localhost only", "a foreign Host header gets 400 before anything runs"),
fact("Assets", "include_bytes!", "the page you are reading is inside the binary"),
fact("Telemetry", "none", "no analytics, and no update check Semlith makes on its own"),
fact(
"Retrieval ledger",
"records locally",
"the daemon prints this on every start; --no-ledger stops it for a session, SEMLITH_LEDGER=0 for a machine, and the rows never leave the store",
),
fact("Model cache", data.model_cached ? "cached" : "not downloaded", data.model_cache),
fact(
"Cloud",
"not connected",
"this binary has no cloud command and opens no connection to any host; the Cloud page describes an optional service and contacts nothing",
),
),
el(
"div",
{ class: "card pad" },
el(
"div",
{ class: "head" },
el("span", { class: "card-title", text: "Downloads" }),
el("span", { class: "spacer" }),
el("span", {
class: "meta",
text: "everything semlith ever fetches, and when",
}),
),
dataTable({
caption:
"Every file semlith can download: what it is, where it comes from, its size, when the download happens, and whether it is on this machine already.",
rows: data.downloads || [],
columns: [
{ key: "what", label: "What", value: (r) => r.what },
{ key: "source", label: "From", value: (r) => r.source },
{ key: "bytes", label: "Size", value: (r) => r.bytes, render: (r) => bytes(r.bytes) },
{ key: "when", label: "When", value: (r) => r.when },
{
key: "cached",
label: "Here",
value: (r) => (r.cached ? "here" : "not downloaded"),
render: (r) => pill(r.cached ? "here" : "not downloaded", r.cached ? "good" : null),
},
],
}).node,
),
el(
"div",
{ class: "grid scroller" },
el(
"div",
{ class: "rows" },
el(
"div",
{ class: "card pad" },
el("span", { class: "card-title", text: "Verify it yourself" }),
el(
"div",
{ class: "steps-list" },
step(
"1",
"Ask the operating system what this process has open. Only loopback should appear.",
"lsof -nP -p $(pgrep -f 'semlith start') -i",
),
step(
"2",
"Watch every interface but loopback while you search. Nothing should appear.",
"sudo tcpdump -i any -n 'not host 127.0.0.1 and not host ::1'",
),
step(
"3",
"Arm the refusal. Anything that would reach the network exits instead, naming what it refused.",
"semlith start --airgap",
),
step("4", "Or pull the cable: the portal loads and searches with no network at all.", "ifconfig en0 down"),
step(
"5",
"See the ledger for what it is: one local table, written by this machine and nothing else.",
`sqlite3 ${data.store_home || "~/.semlith"}/stores/<name>/store.db 'select count(*) from retrievals'`,
),
),
),
el(
"div",
{ class: "card pad" },
el(
"div",
{ class: "head" },
el("span", { class: "card-title", text: "What is already stored" }),
el("span", { class: "spacer" }),
scanButton,
),
el("p", {
class: "subtitle",
text: "The rules below decide what semlith will take in from now on. This checks what the stores are already holding: every file that semlith would refuse today — indexed before a rule widened, or before the credential content scan existed.",
}),
scanBox,
scanNote,
),
),
el(
"div",
{ class: "rows" },
el(
"div",
{ class: "card pad" },
el("span", { class: "card-title", text: "The one outbound connection that exists" }),
says(
"The embedding model is downloaded once, on first index, and cached. ",
mono("semlith upgrade"),
" and ",
mono("semlith add"),
" reach the network only in the second you ask them to. ",
mono("--airgap"),
" refuses all three and exits naming what it refused.",
),
copyField("SEMLITH_MODEL_CACHE=/media/usb/models semlith index ."),
el(
"div",
{ class: "chips" },
pill(data.model_cached ? "model cached" : "model not downloaded", data.model_cached ? "good" : "warn"),
el("span", {
class: "meta",
text: "granite-embedding-small-english-r2 · int8 · Apache-2.0",
}),
),
),
sessionReplayToggle(),
el(
"div",
{ class: "card pad" },
el("span", { class: "card-title", text: "Session token" }),
el("div", { class: "copyfield" }, tokenBox, el("div", { class: "actions" }, rotate)),
rotateNote,
says(
"Generated at start, handed to this page once by the printed URL, and sent back as a ",
mono(data.token_header),
" header on every ",
mono("/api"),
" route. It is in no cookie: every port on localhost is the same site, so a cookie would travel to a page served by anything else on this machine, and a header will not. Shown truncated — no response carries it in full except the one that rotates it.",
),
el("hr", { class: "rule" }),
el("span", { class: "card-title", text: "Content-Security-Policy" }),
codeBlock(data.csp),
el("p", {
class: "subtitle",
text: `Host headers answered: ${(data.host_allowed || []).join(", ")}. Everything else gets 400.`,
}),
),
),
),
el(
"div",
{ class: "card pad rules-card" },
el(
"div",
{ class: "head" },
el("span", { class: "card-title", text: "Rules" }),
el("span", { class: "spacer" }),
pill(
(data.rules || []).every((r) => r.ok)
? "holding for new writes"
: "check the rows",
(data.rules || []).every((r) => r.ok) ? "good" : "warn",
),
),
el("p", {
class: "subtitle",
text: "What semlith refuses, and what this daemon found when it checked. Every row is a rule the binary enforces and a test that fails if it stops.",
}),
rulesBox,
fixNote,
),
);
}
function langCard(languages, withEdges) {
const edges = new Set(withEdges);
return el(
"div",
{ class: "card pad lang-card" },
el(
"div",
{ class: "rows tight" },
el("span", { class: "card-title", text: `${languages.length} languages` }),
el("span", {
class: "subtitle",
text:
edges.size === languages.length
? `Search filters and the code graph read the same table, so the two cannot disagree. Every one carries graph edges.`
: `Search filters and the code graph read the same table, so the two cannot disagree. ${edges.size} of ${languages.length} carry graph edges.`,
}),
),
el(
"div",
{ class: "lang-grid" },
languages.map((lang) => {
const has = edges.has(lang.name);
const names = (lang.extensions || [])
.map((e) => `.${e}`)
.concat(lang.filenames || []);
return el(
"div",
{ class: "lang-row" },
el("span", {
class: "tick on",
title: "indexed, and --lang selects it",
text: "✓",
}),
el("span", { class: "name", text: lang.name }),
has ? el("span", { class: "graph-mark", text: "graph" }) : null,
el("span", { class: "spacer" }),
el("span", { class: "exts", text: names.join(" ") }),
);
}),
),
says(
`All ${languages.length} are indexed and selectable with `,
mono("--lang"),
edges.size === languages.length
? ", and every one marked "
: `. The ${edges.size} marked `,
mono("graph"),
edges.size === languages.length
? " carries symbols and edges as well, from a tree-sitter grammar. "
: " carry symbols and edges as well, from a tree-sitter grammar; the rest are searched as text. ",
mono("semlith languages"),
" prints the same table. Extension and filename decide the language — file contents are never read to guess it, because a store is searched far more often than it is built. Those grammars are most of what the binary weighs, and no language was dropped to hit a size.",
),
);
}
async function aboutView() {
let about;
let languages;
try {
[about, languages] = await Promise.all([api("/api/about"), api("/api/languages")]);
} catch (e) {
return el("div", { class: "view" }, pageHead("About"), error(e.message));
}
const row = (key, value) =>
el(
"div",
{ class: "kv pair" },
el("span", { class: "k", text: key }),
el("span", { class: "v", text: value }),
);
return el(
"div",
{ class: "view" },
pageHead("About", "One Rust binary. The portal you are reading is compiled into it."),
el(
"div",
{ class: "grid two about-grid" },
el(
"div",
{ class: "rows" },
el(
"div",
{ class: "card pad" },
row("Version", `${about.version} · store format ${about.format_version}`),
row("Binary", `${about.binary} · ${bytes(about.binary_bytes)} · ${about.target}`),
row("Bound to", about.bind),
row("Store home", about.store_home),
row("Model cache", about.model_cache),
row("Source", `${about.license} · free and complete`),
row("Uptime", `${Math.floor(about.uptime / 60)}m · pid ${about.pid}`),
),
),
langCard(languages.languages || [], about.graph_languages || []),
),
el("p", {
class: "subtitle",
text: "The model is fixed when a store is created — vectors from two models are not comparable. To switch, delete the store and index again.",
}),
);
}
function welcomeView() {
const note = el("div", { class: "note" });
const field = el("input", { type: "text", placeholder: "~/Documents/work" });
const version = el("span", { class: "ver" });
(async () => {
try {
const about = await api("/api/about");
version.textContent = `v${about.version}`;
} catch (_) {
}
})();
const step = (num, title, what) =>
el(
"div",
{ class: "step" },
el("span", { class: "eyebrow", text: num }),
el("span", { class: "card-title", text: title }),
el("span", { class: "what", text: what }),
);
return el(
"div",
{ class: "welcome" },
el("div", { class: "lockup" }, logoImage(38), el("span", { class: "name", text: "Semlith" }), version),
el(
"div",
{ class: "hero" },
el(
"div",
{ class: "titles" },
el("h1", { class: "hero-title", text: "No stores yet" }),
el("p", {
class: "subtitle",
text: "The daemon is running and holds nothing. Point it at a folder and it indexes everything a person could read in there — code, Markdown, PDFs, Office files, notebooks, books and mail.",
}),
),
el(
"div",
{ class: "field tall" },
el("span", { class: "prefix", text: "path" }),
labelled("welcome-path", "Folder to index", field),
),
note,
el(
"div",
{ class: "actions" },
el("button", {
class: "button",
type: "button",
text: "Index this folder",
onclick: () => {
const path = field.value.trim();
if (!path) {
note.className = "note bad";
note.textContent = "Give a path first, or open the picker instead.";
field.focus();
return;
}
state.pendingPath = path;
go("index");
},
}),
el("button", {
class: "button secondary",
type: "button",
text: "Open the folder picker",
onclick: () => go("index"),
}),
el("button", {
class: "button secondary",
type: "button",
text: "Adopt an existing .semlith",
onclick: () => go("stores"),
}),
el("button", {
class: "button secondary",
type: "button",
text: "Skip for now",
onclick: () => go("stores"),
}),
),
el("hr", { class: "rule" }),
el("span", { class: "eyebrow", text: "Or from a terminal" }),
copyField("semlith index ~/Documents/work"),
says(
"No ",
mono("--store"),
" flag. It lands in ",
mono("~/.semlith/stores/work"),
" and is registered against that root.",
),
says(
"Queries are recorded to a local file in that store and never leave this machine. Start with ",
mono("semlith start --no-ledger"),
" to skip recording.",
),
),
el(
"div",
{ class: "steps" },
step("01", "It reads the folder", "Code, Markdown, PDF, Office, notebooks, HTML — chunked and embedded locally."),
step("02", "It stays current", "The watcher re-embeds and re-extracts edges on every save."),
step("03", "Your agents connect", "One HTTP endpoint for Claude Code, Codex, Cursor, Zed and the rest."),
step("04", "It keeps a local record", "Records what agents retrieve, locally; --no-ledger to skip."),
),
el("div", { class: "foot", text: `${location.host} · loopback only · no external asset` }),
);
}
function mapPanel() {
const body = el("div", { class: "map-body" }, skeletonRows(4));
const shown = el("div", { class: "map-shown" });
(async () => {
let data;
try {
data = await api("/api/map");
} catch (e) {
fill(body, error(e.message));
return;
}
const list = data.communities || [];
if (!list.length) {
fill(body, el("div", { class: "rail-hint", text: "No call or import edges to group yet." }));
return;
}
fill(
body,
list.map((community) =>
el(
"button",
{
class: "map-row",
type: "button",
onclick: () => {
state.pendingQuery = community.label;
go("search");
},
},
el(
"span",
{ class: "line one" },
el("span", { class: "label", text: community.label }),
el("span", { class: "size", text: `${community.size} symbol${community.size === 1 ? "" : "s"}` }),
),
el("span", {
class: "line hubs",
text: `hubs: ${community.hubs
.map((hub) => (hub.path ? `${hub.name} · ${shortPath(hub.path)}:${hub.line}` : hub.name))
.join(" · ")}`,
}),
el("span", {
class: "line cross",
text: community.cross
? `${community.label} → ${community.cross[0]} · ${community.cross[1]} settled edge${community.cross[1] === 1 ? "" : "s"}`
: "nothing leaves this group",
}),
),
),
);
fill(
shown,
el("span", {
text: `Shown ${data.shown} of ${data.total} · communities over settled calls and imports edges`,
}),
);
})();
return el(
"section",
{ class: "map-panel" },
el("div", { class: "card-head" }, el("h2", { text: "Map" }), el("span", { class: "mono-chip", text: "subsystems" })),
body,
shown,
);
}
function capLabel(text) {
return el("span", { class: "eyebrow", text });
}
function statCell(label, value) {
return el("div", { class: "impact-stat" }, capLabel(label), el("span", { class: "n", text: String(value) }));
}
const IMPACT_NODE_CAP = 34;
function impactCanvas() {
const graph = graphCanvas({
onHover: (node, x, y) => {
if (!node) return tip.hide("impact");
tip.atPoint(
x,
y,
node.hop
? tipCard(node.name, "blue", [
["hops", node.hop],
["file", node.path ? `${shortPath(node.path)}:${node.line}` : "—"],
["reaches", `${node.via} · ${node.edge}`],
["confidence", node.confidence || "—"],
])
: tipCard(node.name, "accent", [["hops", "0 · the subject"]]),
"impact",
);
},
});
const caption = el("span", { class: "canvas-caption", text: "reverse reachability" });
const wrap = el("div", { class: "card impact-canvas-card" }, graph.node, caption);
function shape(subject, rows) {
const index = new Map([[subject, 0]]);
const nodes = [{ name: subject, kind: "symbol" }];
const edges = [];
const add = (name) => {
if (index.has(name)) return index.get(name);
if (nodes.length >= IMPACT_NODE_CAP) return null;
index.set(name, nodes.length);
nodes.push({ name, kind: "symbol" });
return nodes.length - 1;
};
for (const row of [...rows].sort((a, b) => a.hop - b.hop)) {
const from = add(row.name);
if (from !== null && from !== 0 && !nodes[from].hop) {
Object.assign(nodes[from], {
hop: row.hop,
path: row.path,
line: row.line,
via: row.via,
edge: row.kind || "calls",
confidence: row.confidence,
});
}
const to = add(row.via);
if (from === null || to === null) continue;
edges.push({ from, to, kind: row.kind || "calls", confidence: row.confidence });
}
return { nodes, edges, total: rows.length + 1 };
}
return {
node: wrap,
show(name, reached) {
if (!name) return this.clear();
const data = shape(name, reached || []);
graph.draw(data);
graph.pick(name);
const hidden = data.total - data.nodes.length;
caption.textContent = hidden > 0
? `reverse reachability · ${hidden} beyond the ${IMPACT_NODE_CAP} drawn`
: "reverse reachability";
graph.start();
},
clear() {
graph.draw({ nodes: [], edges: [], total: 0 });
caption.textContent = "reverse reachability";
},
};
}
async function impactView() {
await refreshStores();
const results = el("div", { class: "impact-results" });
const subject = el("span", { class: "impact-subject", text: "—" });
const depthPill = el("span", { class: "pill warn", text: "depth 3 · reverse" });
const rings = impactCanvas();
let store = state.impactStore || "";
const scope = () => store;
const pathCard = pathFinderCard(scope);
const traceLane = traceCard(scope);
const reached = statCell("Reached", 0);
const files = statCell("Files", 0);
const inferredCell = statCell("Inferred", 0);
const stats = el("div", { class: "impact-stats" }, reached, files, inferredCell);
const setStats = (a, b, c) => {
reached.querySelector(".n").textContent = String(a);
files.querySelector(".n").textContent = String(b);
inferredCell.querySelector(".n").textContent = String(c);
};
const nameInput = el("input", {
type: "search",
placeholder: "A symbol's name, matched exactly",
"aria-label": "Symbol",
onkeydown: (e) => {
if (e.key === "Enter") run();
},
});
if (state.impactSymbol) nameInput.value = state.impactSymbol;
const depthInput = el("input", {
type: "number",
min: "1",
max: "10",
value: "3",
"aria-label": "Hops",
class: "hops",
});
let allEdges = false;
const verified = chipToggle("Prefer verified edges", true, (on) => {
allEdges = !on;
if (nameInput.value.trim()) run();
});
const reach = el("button", { class: "button small", type: "button", text: "Reach", onclick: () => run() });
const storeChip = el("span", { class: "chips" });
function paintStore() {
fill(
storeChip,
store
? el("button", {
class: "chip sm",
type: "button",
"aria-pressed": "true",
title: "Answering about this store only. Press to ask every open store.",
text: `in ${store} ×`,
onclick: () => {
store = "";
state.impactStore = "";
paintStore();
if (nameInput.value.trim()) run();
},
})
: null,
);
}
paintStore();
function emptyImpact() {
return el(
"div",
{ class: "rows" },
el("p", {
class: "subtitle",
text: "Type a symbol's exact name above and press Reach, or find it on the Graph page, pick it, and press Blast radius.",
}),
el("div", {}, el("button", { class: "button secondary small", type: "button", text: "Open Graph", onclick: () => go("graph") })),
);
}
function reachedRow(row) {
return el(
"tr",
{ class: "impact-row" },
el("td", { class: "sym" }, el("code", { title: row.name, text: row.name })),
row.at
? el("td", {
class: "where path",
title: `${row.path}:${row.at} · call in ${row.name}, defined at line ${row.line}`,
text: `${shortPath(row.path)}:${row.at}`,
})
: el("td", {
class: "where path",
title: `${row.path}:${row.line} · no call-site line in this store; a re-index adds it`,
text: `${shortPath(row.path)}:${row.line} (definition)`,
}),
el(
"td",
{ class: "via" },
el("span", { class: "one-line", text: `${row.via} · ${row.kind}` }),
confidenceBadge(row.confidence),
),
el("td", { class: "hops-n num", text: String(row.hop) }),
);
}
function hopGroup(hop, rows) {
return el(
"tbody",
{ class: "impact-block" },
el(
"tr",
{ class: "impact-hop-row" },
el(
"th",
{ colspan: "4", scope: "rowgroup" },
el(
"div",
{ class: "impact-hop" },
el("h2", { text: `${hop} hop${hop === 1 ? "" : "s"}` }),
el("span", { class: "muted", text: `${rows.length} definition${rows.length === 1 ? "" : "s"}` }),
),
),
),
rows.map(reachedRow),
);
}
async function run() {
const name = nameInput.value.trim();
state.impactSymbol = name;
if (!name) {
subject.textContent = "—";
setStats(0, 0, 0);
rings.clear();
fill(results, emptyImpact());
return;
}
const depth = Math.min(10, Math.max(1, Number(depthInput.value) || 3));
depthInput.value = String(depth);
subject.textContent = name;
depthPill.textContent = `depth ${depth} · reverse`;
fill(results, el("p", { class: "subtitle", text: "Reading…" }));
let data;
try {
const query = new URLSearchParams({ name, depth: String(depth) });
if (allEdges) query.set("all_edges", "1");
if (store) query.set("store", store);
data = await api(`/api/impact?${query}`);
} catch (e) {
rings.clear();
setStats(0, 0, 0);
fill(results, error(e.message));
return;
}
const impact = data.impact;
if (!impact || !impact.reached.length) {
rings.clear();
setStats(0, impact ? impact.files.length : 0, 0);
fill(
results,
el("p", { class: "headline", text: data.headline || "nothing reaches that name" }),
impact && impact.definitions.length
? el("p", { class: "note", text: "The symbol is indexed; nothing in an open store calls it." })
: el("p", {
class: "note",
text: "No definition of that name is in an open store. Check the spelling, or index the repository that holds it.",
}),
);
return;
}
const inferred = impact.reached.filter((r) => r.confidence === "inferred" || r.confidence === "ambiguous").length;
const byHop = new Map();
for (const row of impact.reached) {
if (!byHop.has(row.hop)) byHop.set(row.hop, []);
byHop.get(row.hop).push(row);
}
setStats(impact.reached.length, impact.files.length, inferred);
rings.show(name, impact.reached);
const furthest = impact.reached[impact.reached.length - 1];
if (furthest) {
pathCard.seed(furthest.name, name);
traceLane.seed(furthest.name, name);
}
fill(
results,
el("p", { class: "headline", text: data.headline }),
impact.unqualified
? el("p", {
class: "note",
text: `Nothing named ${name} matched that owner, so this is every definition of the bare name.`,
})
: null,
allEdges
? el("p", {
class: "note hypothesis",
text: "Walked names with several definitions. Rows badged ambiguous are a hypothesis, not a finding.",
})
: null,
el(
"div",
{ class: "card table-card impact-table" },
el(
"div",
{ class: "table-wrap" },
el(
"table",
{},
el("caption", { class: "sr-only", text: `Everything that reaches ${name}, nearest hop first` }),
el(
"thead",
{},
el(
"tr",
{},
el("th", { text: "Reached symbol" }),
el("th", { text: "Where" }),
el("th", { text: "Via" }),
el("th", { class: "num", text: "Hops" }),
),
),
[...byHop.entries()].map(([hop, rows]) => hopGroup(hop, rows)),
),
),
),
el(
"div",
{ class: "card table-card impact-table" },
el("div", { class: "table-head" }, el("h2", { text: "Files" })),
el(
"div",
{ class: "table-wrap" },
el(
"table",
{},
el("caption", { class: "sr-only", text: "The files those definitions are in" }),
el(
"thead",
{},
el(
"tr",
{},
el("th", { text: "File" }),
el("th", { class: "num", text: "Definitions" }),
el("th", { class: "num", text: "Nearest" }),
),
),
el(
"tbody",
{ class: "impact-block impact-files" },
impact.files.map((file) =>
el(
"tr",
{ class: "impact-row" },
el("td", { class: "sym path", text: shortPath(file.path) }),
el("td", { class: "num", text: String(file.symbols) }),
el("td", { class: "num", text: `${file.nearest} hop${file.nearest === 1 ? "" : "s"}` }),
),
),
),
),
),
),
impact.hidden
? el("p", { class: "note", text: `${impact.hidden} more left out at the limit of ${data.limit}.` })
: null,
);
}
fill(results, emptyImpact());
if (state.impactSymbol) run();
return el(
"div",
{ class: "view" },
pageHead("Impact", "Reverse reachability. What breaks if this changes — before the edit, not after the test run."),
el(
"div",
{ class: "impact-columns" },
el(
"div",
{ class: "impact-col" },
el(
"div",
{ class: "card pad impact-subject-card" },
el(
"div",
{ class: "impact-band" },
nameInput,
el("label", { class: "hops-label" }, el("span", { text: "hops" }), depthInput),
verified,
reach,
),
el(
"div",
{ class: "impact-subject-row" },
capLabel("Changing"),
subject,
storeChip,
el("span", { class: "spacer" }),
depthPill,
),
stats,
el(
"ul",
{ class: "impact-guide" },
el("li", {}, el("b", { text: "Reached" }), " — every definition that calls, references or imports this one, directly or through others."),
el("li", {}, el("b", { text: "Inferred" }), " — linked by a bare name match only. Corroborate before relying on it."),
el("li", {}, el("b", { text: "Hops" }), " — how far back to read: 1 is direct callers only. Prefer verified edges leaves out names with several definitions."),
),
),
results,
),
el("div", { class: "impact-col" }, rings.node, pathCard, traceLane),
),
);
}
const PATH_EMPTY = "Two names, and this walks the edges between them.";
const TRACE_EMPTY = "Two names, and this writes the chain out with the lines that support it.";
function pathFinderCard(scope) {
const from = el("input", { type: "text", placeholder: "from", "aria-label": "Path from" });
const to = el("input", { type: "text", placeholder: "to", "aria-label": "Path to" });
const body = el("div", { class: "path-body" });
const query = el("span", { class: "mono-chip", text: "max 6 hops" });
let preferVerified = true;
let strict = false;
const verifiedChip = chipToggle("Prefer verified edges", true, (on) => {
preferVerified = on;
if (!on) strict = false;
strictChip.setAttribute("aria-pressed", String(strict));
run();
});
const strictChip = chipToggle("Strict", false, (on) => {
strict = on;
if (on) {
preferVerified = true;
verifiedChip.setAttribute("aria-pressed", "true");
}
run();
});
async function run() {
const a = from.value.trim();
const b = to.value.trim();
if (!a || !b) {
fill(body, el("p", { class: "subtitle", text: PATH_EMPTY }));
return;
}
const allEdges = !preferVerified && !strict;
query.textContent = `${a} → ${b} · max 6 hops`;
fill(body, el("p", { class: "subtitle", text: "Walking…" }));
let data;
try {
const q = new URLSearchParams({ from: a, to: b, depth: "6" });
if (allEdges) q.set("all_edges", "1");
if (scope && scope()) q.set("store", scope());
data = await api(`/api/path?${q}`);
} catch (e) {
fill(body, error(e.message));
return;
}
if (!data.path) {
fill(
body,
el(
"div",
{ class: "refusal" },
el("p", {
class: "mono",
text: allEdges
? "Not connected within 6 hops by any edge in the store"
: "Not connected within 6 hops by resolved edges",
}),
allEdges
? null
: el("button", {
class: "link",
type: "button",
text: "Show inferred chain",
onclick: () => {
preferVerified = false;
strict = false;
verifiedChip.setAttribute("aria-pressed", "false");
strictChip.setAttribute("aria-pressed", "false");
run();
},
}),
),
);
return;
}
fill(body, chainBlock(data.path, data.summary));
}
fill(body, el("p", { class: "subtitle", text: PATH_EMPTY }));
const node = el(
"section",
{ class: "card pad path-card" },
el("div", { class: "card-head" }, el("h2", { text: "Path finder" }), query),
el("div", { class: "path-band" }, from, to, verifiedChip, strictChip, el("button", { class: "button small", type: "button", text: "Walk", onclick: () => run() })),
body,
);
node.seed = (a, b) => {
if (from.value.trim() || to.value.trim()) return;
if (!a || !b || a === b) return;
from.value = a;
to.value = b;
run();
};
return node;
}
function chipToggle(label, on, onchange) {
const node = el("button", {
class: "chip sm",
type: "button",
text: label,
"aria-pressed": String(on),
onclick: () => {
const next = node.getAttribute("aria-pressed") !== "true";
node.setAttribute("aria-pressed", String(next));
onchange(next);
},
});
return node;
}
function chainBlock(steps, summary) {
const rows = [];
steps.forEach((step, i) => {
rows.push(
el(
"div",
{ class: "hop" },
el("span", { class: "n", text: String(i + 1) }),
el("span", { class: "end", text: `${step.from} @ ${shortPath(step.from_path)}:${step.from_line}` }),
el("span", { class: "arrow", "aria-hidden": "true", text: "→" }),
el("span", { class: "end", text: `${step.to} @ ${shortPath(step.to_path)}:${step.to_line}` }),
el("span", { class: "kind", text: step.kind }),
confidenceBadge(step.confidence),
),
);
const next = steps[i + 1];
if (next && (next.from_path !== step.to_path || next.from_line !== step.to_line)) {
rows.push(
el("div", { class: "seam" }, `seam · ${step.to}: ${Math.max(2, step.definitions)} definitions · continues from ${next.from} @ ${shortPath(next.from_path)}:${next.from_line}`),
);
}
});
const s = summary || {};
let trailer = `${s.hops} hop${s.hops === 1 ? "" : "s"} · ${s.extracted} extracted · ${s.resolved} resolved · ${s.inferred} inferred · ${s.ambiguous} ambiguous`;
if (s.seams) {
const names = (s.ambiguous_names || []).map(([name, count]) => `${name}: ${count} definitions`).join(", ");
trailer += ` · ${s.seams} through ambiguous names (${names})`;
}
return [
...rows,
el("p", { class: "trailer mono", text: trailer }),
s.hypothesis
? el("p", {
class: "note hypothesis",
text: "A hypothesis, not a finding. Read the seam before you rely on it.",
})
: null,
];
}
function traceCard(scope) {
const from = el("input", { type: "text", placeholder: "from", "aria-label": "Trace from" });
const to = el("input", { type: "text", placeholder: "to", "aria-label": "Trace to" });
const body = el("div", { class: "trace-body" });
let evidenceText = "";
const copy = copyButton(() => evidenceText, "Copy as evidence", true);
async function run() {
const a = from.value.trim();
const b = to.value.trim();
if (!a || !b) {
fill(body, el("p", { class: "subtitle", text: TRACE_EMPTY }));
return;
}
fill(body, el("p", { class: "subtitle", text: "Reading…" }));
let data;
try {
const q = new URLSearchParams({ from: a, to: b, depth: "6" });
if (scope && scope()) q.set("store", scope());
data = await api(`/api/trace?${q}`);
} catch (e) {
fill(body, error(e.message));
return;
}
const trace = data.trace;
evidenceText = data.evidence || "";
const parts = [capLabel("Answer"), el("p", { class: "answer", text: trace.answer })];
if (trace.chain && trace.chain.steps.length) {
parts.push(capLabel("Chain"));
parts.push(...chainBlock(trace.chain.steps, trace.chain.summary));
parts.push(capLabel("Supporting lines"));
for (const line of trace.lines) {
parts.push(
el(
"div",
{ class: "support" },
el("code", { text: line.at ? `${line.at} ${line.code}` : "no call line recorded" }),
el(
"div",
{ class: "support-foot" },
el("span", { class: "hop-of", text: line.hop }),
el("span", {
class: `mark ${line.mark.startsWith("candidate") ? "candidate" : "fact"}`,
text: line.mark,
}),
),
),
);
}
}
fill(body, ...parts);
}
fill(body, el("p", { class: "subtitle", text: TRACE_EMPTY }));
const node = el(
"section",
{ class: "card pad trace-card" },
el(
"div",
{ class: "card-head" },
el("h2", { text: "Trace" }),
el("span", { class: "mono-chip", text: "evidence view" }),
el("span", { class: "spacer" }),
copy,
),
el("div", { class: "path-band" }, from, to, el("button", { class: "button small", type: "button", text: "Trace", onclick: () => run() })),
body,
);
node.seed = (a, b) => {
if (from.value.trim() || to.value.trim()) return;
if (!a || !b || a === b) return;
from.value = a;
to.value = b;
run();
};
return node;
}
const REPORTS = [
[
"savings",
"Retrieval savings",
"Tokens the agents did not read, counted from the ledger instead of claimed.",
"for whoever approves the spend",
"What retrieval actually saved, per client, with the arithmetic shown.",
],
[
"access",
"AI access audit",
"Which agent read which file, when, in a hash-chained record nothing can quietly edit.",
"for security review and AI-use policy",
"Exactly what the assistants were shown, session by session.",
],
[
"change",
"Change brief",
"Blast radius for what this machine re-read, written as a note you paste into the pull request.",
"for the reviewer, before the merge",
"What changed, what reaches it, and which callers the tests cover.",
],
[
"health",
"Index health",
"Stale files, roots never indexed, formats skipped, and how much of the tree the graph covers.",
"for the person who owns the store",
"Whether the answers your agents get are current, and where the index has holes.",
],
[
"gaps",
"Knowledge gaps",
"Questions the agents asked that your corpus could not answer well — a to-do list for documentation.",
"for whoever writes the docs",
"Asked and not answered, called and not found, and the names that mislead.",
],
];
const REPORT_FORMATS = [
["markdown", "Markdown", "md", "text/markdown"],
["csv", "CSV", "csv", "text/csv"],
["json", "JSON", "json", "application/json"],
["html", "HTML", "html", "text/html"],
["pdf", "PDF", "pdf", "application/pdf"],
];
const REPORT_WINDOWS = [
["day", "24 hours"],
["week", "7 days"],
["month", "30 days"],
["quarter", "This quarter"],
];
const WINDOWED_KINDS = ["access", "change", "savings", "gaps"];
const REPORT_CADENCES = [
["Daily", 86400],
["Weekly", 604800],
["Monthly", 2592000],
];
function tailPath(path) {
const parts = String(path).split("/").filter(Boolean);
return parts.length > 2 ? `…/${parts.slice(-2).join("/")}/` : `${path}/`;
}
function everyWords(seconds) {
const hit = REPORT_CADENCES.find(([, every]) => every === seconds);
if (hit) return hit[0].toLowerCase();
const plural = (count, unit) => `every ${count} ${unit}${count === 1 ? "" : "s"}`;
if (seconds % 86400 === 0) return plural(seconds / 86400, "day");
if (seconds % 3600 === 0) return plural(seconds / 3600, "hour");
return plural(Math.max(1, Math.round(seconds / 60)), "minute");
}
function until(unix) {
if (!unix) return "not scheduled";
const seconds = unix - Math.floor(Date.now() / 1000);
if (seconds <= 0) return "due now";
if (seconds < 60) return `in ${seconds}s`;
if (seconds < 3600) return `in ${Math.round(seconds / 60)}m`;
if (seconds < 86400) return `in ${Math.round(seconds / 3600)}h`;
return `in ${Math.round(seconds / 86400)}d`;
}
async function reportsView() {
await refreshStores();
let home = "";
try {
home = (await api("/api/about")).store_home || "";
} catch (_) {
}
const reportDir = home ? `${home}/reports/` : "the store home's reports/ directory";
const schedulesFile = home ? `${home}/schedules.json` : "schedules.json in the store home";
let kind = REPORTS[0][0];
let format = REPORT_FORMATS[0][0];
let model = MODEL_PRICES[0];
let span = "month";
let scope = [];
let redact = false;
let excerpts = false;
let body = "";
let file = null;
let report = null;
let written = [];
const def = () => REPORTS.find(([id]) => id === kind);
const fmt = () => REPORT_FORMATS.find(([id]) => id === format);
const ext = () => fmt()[2];
const windowed = () => WINDOWED_KINDS.includes(kind);
const spanName = () => (REPORT_WINDOWS.find(([id]) => id === span) || REPORT_WINDOWS[2])[1];
const scopeWords = () => (scope.length ? scope.join(", ") : "all stores");
const stamp = () => (report && report.generated ? report.generated.slice(0, 10) : "");
const baseName = () => `${kind}${stamp() ? `-${stamp()}` : ""}.${ext()}`;
const picker = el("div", { class: "report-types" });
const builder = el("div", { class: "card pad report-builder" });
const builderProblem = el("div", { class: "note bad" });
const previewName = el("span", { class: "name plain" });
const previewBody = el("pre", { class: "report-text" });
const previewMeta = el("span", { class: "meta" });
const savingsCard = el("section", { class: "card pad savings-card" });
const gapsCard = el("section", { class: "card pad gaps-card" });
const writtenCard = el("section", { class: "card written-card" });
const scheduleCard = el("section", { class: "card pad schedules-card" });
const cliCard = el("section", { class: "card pad report-cli-card" });
function chipGroup(label, chips, options) {
const { inline, note, group } = options || {};
return el(
"div",
{
class: inline ? "chip-group inline" : "chip-group",
"data-group": group || label.toLowerCase(),
},
el("span", { class: inline ? "eyebrow sm" : "eyebrow", text: label }),
el("div", { class: "filters" }, chips),
note ? el("p", { class: "subtitle", text: note }) : null,
);
}
function chip(label, on, onclick, why) {
return el("button", {
class: "chip",
type: "button",
text: label,
"aria-pressed": String(on),
disabled: !onclick,
title: why || null,
onclick: onclick || null,
});
}
async function generate() {
previewName.textContent = `reports/${baseName()}`;
previewMeta.textContent = "Generating…";
fill(previewBody, "");
body = "";
file = null;
report = null;
const query = new URLSearchParams({ kind, format, model: model[0] });
if (windowed()) query.set("window", span);
for (const name of scope) query.append("scope", name);
if (excerpts) query.set("excerpts", "1");
if (redact) query.set("redact", "1");
try {
if (format === "pdf") {
const response = await fetch(`/api/report?${query}`, {
credentials: "omit",
headers: authed(),
});
if (!response.ok) throw new Error(response.statusText || "request failed");
file = await response.blob();
} else {
const data = await api(`/api/report?${query}`);
body = data.text;
report = data.report;
file = new Blob([body], { type: `${fmt()[3]};charset=utf-8` });
}
} catch (e) {
previewMeta.textContent = "";
previewBody.textContent = "";
fill(builderProblem, error(e.message));
paintAll();
return;
}
fill(builderProblem);
previewName.textContent = `reports/${baseName()}`;
if (format === "pdf") {
previewBody.textContent =
`${bytes(file.size)} of PDF. A page cannot show it as text; ` +
`Export and Save to disk write these exact bytes.`;
} else {
previewBody.textContent = body;
}
paintMeta();
paintAll();
}
function paintMeta() {
const rows = ((report && report.blocks) || [])
.filter((block) => block.block === "table")
.reduce((total, block) => total + block.rows.length, 0);
const size = file ? bytes(file.size) : "—";
const parts = [
format === "pdf" ? "rows not counted in a PDF" : `${rows} rows shown`,
`full file ${size}`,
format,
"written to disk, never uploaded",
];
previewMeta.textContent = parts.join(" · ");
}
const copy = copyButton(() => body, "Copy", true);
const exportButton = el("button", {
class: "button tiny ink",
type: "button",
text: "Export",
onclick: () => {
if (!file) return;
offerDownload(baseName(), file, fmt()[3]);
},
});
const saveButton = el("button", {
class: "button",
type: "button",
text: "Save to disk",
title: "Saves through the browser and lists it below",
onclick: () => {
if (!file) return;
offerDownload(baseName(), file, fmt()[3]);
record(baseName(), file.size);
},
});
function record(name, size) {
written.unshift({
name,
title: def()[1],
span: report ? report.window : "",
size,
at: Math.floor(Date.now() / 1000),
});
written = written.slice(0, 6);
paintWritten();
}
function paintPicker() {
fill(
picker,
REPORTS.map(([id, title, blurb, who]) =>
el(
"button",
{
class: `report-type${id === kind ? " on" : ""}`,
type: "button",
"aria-pressed": String(id === kind),
onclick: () => {
kind = id;
paintPicker();
generate();
},
},
el("span", { class: "name", text: title }),
el("span", { class: "what", text: blurb }),
el("span", { class: "for-whom", text: who }),
),
),
);
}
function paintBuilder() {
const [, title, , , answers] = def();
const spanLine = report ? report.window : answers;
fill(
builder,
el(
"div",
{ class: "titles" },
el("span", { class: "card-title", text: title }),
el("p", { class: "subtitle", text: answers }),
el("p", { class: "subtitle span-note", text: spanLine }),
),
chipGroup(
"Window",
REPORT_WINDOWS.map(([id, label]) =>
chip(
label,
windowed() && id === span,
windowed()
? () => {
span = id;
generate();
}
: null,
windowed() ? null : `${title} has no date to narrow by`,
),
),
),
chipGroup("Scope", [
chip("all stores", scope.length === 0, () => {
scope = [];
generate();
}),
...state.stores.map((store) =>
chip(store.name, scope.includes(store.name), () => {
scope = scope.includes(store.name)
? scope.filter((name) => name !== store.name)
: scope.concat(store.name);
generate();
}),
),
]),
chipGroup(
"Format",
REPORT_FORMATS.map(([id, label]) =>
chip(label, id === format, () => {
format = id;
generate();
}),
),
),
el(
"div",
{ class: "report-toggles" },
[
[
"Hash the query text",
"Keeps the who, when and which-file; drops what was asked.",
() => redact,
(on) => {
redact = on;
},
],
[
"Attach retrieved excerpts",
"The exact lines each agent was shown. Larger file.",
() => excerpts,
(on) => {
excerpts = on;
},
],
].map(([label, note, get, set]) => {
const node = el(
"button",
{ class: "report-toggle", type: "button", "aria-pressed": String(get()) },
el("span", { class: "knob" }),
el(
"span",
{ class: "stack" },
el("span", { class: "label", text: label }),
el("span", { class: "why", text: note }),
),
);
node.onclick = () => {
const next = node.getAttribute("aria-pressed") !== "true";
node.setAttribute("aria-pressed", String(next));
set(next);
generate();
};
return node;
}),
),
builderProblem,
);
}
function fact(label) {
for (const block of (report && report.blocks) || []) {
if (block.block !== "facts") continue;
const hit = block.facts.find(([name]) => name === label);
if (hit) return hit;
}
return null;
}
function factValue(label, fallback) {
const hit = fact(label);
return hit ? hit[1] : fallback || "—";
}
function tableStarting(prefix) {
return ((report && report.blocks) || []).find(
(block) => block.block === "table" && block.title.startsWith(prefix),
);
}
function trustBadge(text, tone) {
return el("span", { class: `trust ${tone}`, text });
}
function paintSavings() {
const table = tableStarting("What was not read");
if (!table) {
fill(savingsCard, el("span", { class: "card-title", text: "Retrieval savings" }), empty("Nothing generated yet."));
return;
}
const chain = fact("Ledger chain");
fill(
savingsCard,
el(
"div",
{ class: "savings-head" },
el("span", { class: "card-title", text: "Retrieval savings" }),
el("span", { class: "spacer" }),
chipGroup(
"Model",
MODEL_PRICES.map((price) =>
chip(price[0], price[0] === model[0], () => {
model = price;
generate();
}),
),
{ inline: true },
),
el("span", { class: "mono-chip", text: `$${model[1].toFixed(2)} per Mtok, input` }),
el("span", { class: "rule" }),
chipGroup(
"Cycle",
REPORT_WINDOWS.slice(1).map(([, label]) => chip(label, false, null, "This report has no date to narrow by")),
{ inline: true },
),
),
el(
"div",
{ class: "savings-lines" },
table.rows.map(([name, tokens, cost, rule]) =>
el(
"div",
{ class: "savings-line" },
el(
"div",
{ class: "row" },
el("span", { class: "name", text: name }),
el("span", { class: "spacer" }),
el("span", { class: "tok", text: tokens }),
el("span", { class: "amount", text: cost || "—" }),
),
el("p", { class: "subtitle", text: rule }),
),
),
),
el(
"div",
{ class: "savings-net" },
el("span", { class: "name", text: "Net" }),
el("span", { class: "spacer" }),
el("span", { class: "tok", text: factValue("Net") }),
el("span", { class: "amount", text: factValue("Net cost avoided") }),
),
el(
"div",
{ class: "trust-strip" },
trustBadge(`coverage ${factValue("Coverage")}`, "blue"),
trustBadge(`zero-hit ${factValue("Zero-hit")}`, "plain"),
trustBadge(`refunds ${factValue("Refund rate")}`, "plain"),
trustBadge(`p50 ${factValue("p50")} · p95 ${factValue("p95")}`, "plain"),
trustBadge(`tier ${factValue("Tier")}`, "blue"),
trustBadge(
`ledger ${chain ? chain[2] : "no rows yet"} · ${factValue("Ledger chain", "unknown")}`,
chain && chain[1] === "verifies" ? "green" : "amber",
),
),
el("p", {
class: "subtitle",
text:
"Priced at input rates, because a retrieval is something an agent reads. " +
"Token counts are this store's own tokenizer; a model's counter will differ, " +
"and nothing here reconciles the two.",
}),
);
}
function paintGaps() {
const several = fact("Names that mislead");
fill(
gapsCard,
el(
"div",
{ class: "card-head" },
el("h2", { text: "Names that mislead" }),
el("span", { class: "mono-chip", text: several ? `${several[1]} names` : "—" }),
),
several ? el("p", { class: "subtitle", text: several[2] }) : null,
empty(
"Which names, and how many chains each sits on, is not in the gaps report — " +
"the store counts them without listing them.",
),
);
}
function paintWritten() {
const head = (text, right) =>
el("th", { class: right ? "right" : null, scope: "col", text });
fill(
writtenCard,
el(
"div",
{ class: "written-head" },
el("span", { class: "card-title", text: "Written reports" }),
el("span", { class: "mono-chip", text: reportDir }),
),
el(
"div",
{ class: "scroll-x" },
el(
"table",
{ class: "written-table" },
el(
"thead",
null,
el("tr", null, head("Written"), head("Report"), head("Window"), head("Size", true), head("When", true)),
),
el(
"tbody",
null,
written.map((row) =>
el(
"tr",
null,
el("td", { class: "file", text: row.name }),
el("td", { class: "type", text: row.title }),
el("td", { class: "window", text: row.span }),
el("td", { class: "size right", text: bytes(row.size) }),
el("td", { class: "when right", text: when(row.at) }),
),
),
),
),
),
written.length
? null
: empty(
`Nothing written from this page yet. What the daemon has already put in ${reportDir} ` +
"is not listed here: no route reads that directory.",
),
);
}
let schedules = [];
let scheduleProblem = "";
let scheduleRefused = "";
let cadence = REPORT_CADENCES[2][1];
const destinations = [[tailPath(`${home}/reports`), home ? `${home}/reports` : ""]].concat(
state.stores.map((store) => [`${store.name} root`, store.dir]),
);
let destination = destinations[0][1];
async function loadSchedules() {
try {
const data = await api("/api/schedules");
schedules = Object.entries(data.schedules || {}).map(([id, one]) => ({ id, ...one }));
scheduleProblem = "";
} catch (e) {
schedules = [];
scheduleProblem = e.message;
}
paintSchedules();
}
async function writeSchedule(sent) {
scheduleRefused = "";
try {
await post("/api/schedules", sent);
} catch (e) {
scheduleRefused = e.message;
}
loadSchedules();
}
function scheduleRow(one) {
const title = (REPORTS.find(([id]) => id === one.kind) || [, one.kind])[1];
const where = String(one.dir || "");
const spec = [
everyWords(one.every_seconds),
one.format,
one.stores && one.stores.length ? one.stores.join(", ") : "all stores",
].join(" · ");
return el(
"div",
{ class: "schedule-row" },
el("button", {
class: `schedule-knob${one.enabled ? " on" : ""}`,
type: "button",
disabled: Boolean(scheduleProblem),
title: one.enabled ? "Pause this schedule" : "Resume this schedule",
"aria-pressed": String(Boolean(one.enabled)),
onclick: () => writeSchedule({ action: "set", id: one.id, enabled: !one.enabled }),
}),
el(
"div",
{ class: "stack" },
el("span", { class: "name", text: `${title} → ${where.split("/").filter(Boolean).pop() || where}` }),
el("span", { class: "spec", text: `${spec} → ${where}` }),
el("span", {
class: "next",
text: one.enabled ? `next run ${until(one.next_run)}` : "paused",
}),
one.last_error ? el("span", { class: "failed", text: `failed: ${one.last_error}` }) : null,
one.last_path ? el("span", { class: "wrote", text: `last wrote ${one.last_path}` }) : null,
),
el("span", { class: "spacer" }),
el("span", { class: `state-pill ${one.enabled ? "on" : "paused"}`, text: one.enabled ? "on" : "paused" }),
el("button", {
class: "schedule-remove",
type: "button",
text: "×",
disabled: Boolean(scheduleProblem),
title: "Remove schedule",
onclick: () => writeSchedule({ action: "remove", id: one.id }),
}),
);
}
function paintSchedules() {
const [, title] = def();
const describes =
`Takes the report set up above — ${title.toLowerCase()}, ` +
(windowed() ? `last ${spanName().toLowerCase()}, ` : "") +
`${scopeWords()}, as ${fmt()[1]}. Change those and add again for a second schedule.`;
fill(
scheduleCard,
el(
"div",
{ class: "card-head" },
el("h2", { text: "Schedules" }),
el("span", { class: "mono-chip", text: `run by the daemon · ${schedulesFile}` }),
),
scheduleProblem
? el("p", {
class: "subtitle",
text:
`This daemon serves no schedules route (${scheduleProblem}), so the card is ` +
"read-only until it does. The records themselves are real: `semlith schedule " +
"list` reads the same file the daemon runs from.",
})
: null,
scheduleRefused ? el("div", { class: "note bad" }, scheduleRefused) : null,
schedules.length
? el("div", { class: "schedule-list" }, schedules.map(scheduleRow))
: el("div", { class: "empty centred" }, "No schedules. Set up a report above, then add it here — the daemon writes the file while it is running."),
el(
"div",
{ class: "new-schedule" },
el("span", { class: "eyebrow", text: "New schedule" }),
el("p", { class: "subtitle strong", text: describes }),
el(
"div",
{ class: "filters" },
REPORT_CADENCES.map(([label, every]) =>
chip(label, every === cadence, () => {
cadence = every;
paintSchedules();
}),
),
),
el(
"div",
{ class: "filters" },
el("span", { class: "write-to", text: "write to" }),
destinations.map(([label, path]) =>
chip(
label,
path === destination,
() => {
destination = path;
paintSchedules();
},
path,
),
),
),
el(
"div",
{ class: "filters" },
el("button", {
class: "button add-schedule",
type: "button",
text: `Add schedule · ${everyWords(cadence)}`,
disabled: Boolean(scheduleProblem) || !destination,
onclick: () =>
writeSchedule({
action: "add",
kind,
format,
model: model[0],
every_seconds: cadence,
dir: destination,
window: windowed() ? span : null,
stores: scope,
}),
}),
el("span", {
class: "mono-chip",
text: `${everyWords(cadence)} → ${destination || "?"}/${kind}-<date>.${ext()}`,
}),
),
),
el("p", {
class: "subtitle",
text:
"The daemon runs these while it is up and writes the file in place, so a report can be " +
`a tracked file in the repo with a readable diff each month. The list is ${schedulesFile}; ` +
"× removes one.",
}),
);
}
function paintCli() {
const flags = [`--format ${format}`];
if (windowed()) flags.push(`--window ${span}`);
for (const name of scope) flags.push(`--scope ${name}`);
if (kind === "savings") flags.push(`--model "${model[0]}"`);
const out = home ? `${home}/reports/` : ".";
const one = `semlith report ${kind} ${flags.join(" ")} --out ${out}`;
const wrapped = [`semlith report ${kind} \\`, ...flags.map((f) => ` ${f} \\`), ` --out ${out}`];
const block = [
...wrapped,
"",
"# the schedule list is a file — add, edit or delete by hand:",
`semlith schedule add ${kind} \\`,
` --every ${cadence} --to ${destination || reportDir} \\`,
` --format ${format}`,
"semlith schedule list · semlith schedule remove 2",
].join("\n");
fill(
cliCard,
el(
"div",
{ class: "card-head" },
el("h2", { text: "Same thing without the browser" }),
el("span", { class: "spacer" }),
copyButton(one, "Copy", true),
),
el("pre", { class: "code report-cli", text: block }),
el(
"p",
{ class: "subtitle" },
"The agent can ask for one too: ",
mono("semlith_report"),
" returns the same document over MCP, so “write a change brief for this PR” needs no copy-paste.",
),
);
}
function paintAll() {
paintBuilder();
paintMeta();
savingsCard.hidden = kind !== "savings";
gapsCard.hidden = kind !== "gaps";
if (kind === "savings") paintSavings();
if (kind === "gaps") paintGaps();
paintSchedules();
paintCli();
}
paintPicker();
paintWritten();
paintAll();
generate();
loadSchedules();
return el(
"div",
{ class: "view" },
pageHead(
"Reports",
"Turn the ledger, the index and the graph into a file someone else can read — a saving number for finance, an access record for audit, a blast radius for a pull request. Generated locally and exported as a file you own. Nothing leaves the machine.",
),
picker,
el(
"div",
{ class: "grid two report-row" },
builder,
el(
"section",
{ class: "card report-preview-card" },
el(
"div",
{ class: "report-bar" },
previewName,
el("span", { class: "spacer" }),
copy,
exportButton,
),
previewBody,
el("div", { class: "foot" }, previewMeta, el("span", { class: "spacer" }), saveButton),
),
),
savingsCard,
gapsCard,
writtenCard,
el("div", { class: "grid two report-row" }, scheduleCard, cliCard),
el(
"section",
{ class: "card pad report-extras" },
el("span", { class: "card-title", text: "About this build" }),
el("p", {
class: "subtitle",
text:
"PDF is written by the binary itself — the same blocks as the other four formats, " +
"typeset on US Letter. Export and Save to disk hand you those bytes unchanged.",
}),
el("p", {
class: "subtitle",
text:
"Savings, index health and knowledge gaps count over a store's whole history and " +
"have no date to narrow by, so the Window chips are inert while one of them is chosen.",
}),
),
);
}
const CLOUD_ROWS = [
[
"One URL for cloud agents",
"Claude Code cloud sessions, routines and CI cannot reach a laptop; they can reach an org store.",
],
[
"The whole organisation in one index",
"Cross-repository paths, and documents beside code.",
],
[
"A pull-request check that states what the graph proves",
"With no model and no guess.",
],
];
async function cloudView() {
return el(
"div",
{ class: "view cloud-page" },
pageHead("Cloud", null, { pill: pill("not connected", null) }),
el("p", {
class: "subtitle",
text: "Semlith Cloud is one hosted store for a whole organisation: every connected repository a root of it, indexed on push, served over MCP to any agent, with a pull-request impact check and a team ledger. This binary works without it. Nothing here contacts a server.",
}),
el(
"div",
{ class: "card pad" },
el(
"div",
{ class: "cloud-rows" },
CLOUD_ROWS.map(([lead, rest]) =>
el(
"div",
{ class: "cloud-row" },
el("span", { class: "lead", text: lead }),
el("span", { class: "what", text: rest }),
),
),
),
el("hr", { class: "rule" }),
el("div", { class: "cloud-block" }, copyField("semlith cloud login"), el("p", {
class: "subtitle",
text: "Not in this release. When it arrives it will store an org token under ~/.semlith/ and send it to that host and no other.",
})),
el("div", { class: "cloud-block" }, copyField("semlith cloud connect acme"), el("p", {
class: "subtitle",
text: "Will add the org's store to this machine's registry as a remote store, listed beside the local ones with a remote badge.",
})),
el("p", {
class: "note",
text: "This build has no cloud command and opens no connection to any host. The Privacy page's own reading is where to check that rather than take it from here.",
}),
el(
"div",
{ class: "cloud-foot" },
el("span", { text: "What the cloud stores and deletes is written up at " }),
mono("semlith.com/data"),
el("span", { text: "." }),
),
),
);
}
const RENDER = {
stores: storesView,
files: filesView,
index: indexView,
corpus: corpusView,
search: searchView,
agents: agentsView,
privacy: privacyView,
doctor: doctorView,
about: aboutView,
graph: graphView,
ledger: ledgerView,
impact: impactView,
reports: reportsView,
cloud: cloudView,
};
function go(id) {
if (shell.main && window.matchMedia(NARROW).matches && state.navOpen) {
setNav(false);
}
if ((location.hash || "").slice(1) === id) render();
else location.hash = `#${id}`;
}
function isDark() {
const chosen = document.documentElement.getAttribute("data-theme");
if (chosen) return chosen === "dark";
return window.matchMedia("(prefers-color-scheme: dark)").matches;
}
function logoImage(size) {
return el("img", {
class: "logo",
src: isDark() ? "logo-dark.svg" : "logo.svg",
alt: "",
width: size,
height: size,
});
}
const THEMES = ["system", "light", "dark"];
function theme(next) {
if (next === "system") {
document.documentElement.removeAttribute("data-theme");
} else {
document.documentElement.setAttribute("data-theme", next);
}
state.theme = next;
try {
if (next === "system") localStorage.removeItem("semlith-theme");
else localStorage.setItem("semlith-theme", next);
} catch (_) {
}
for (const img of document.querySelectorAll("img.logo")) {
img.src = isDark() ? "logo-dark.svg" : "logo.svg";
}
paintThemeButton();
}
function paintThemeButton() {
const button = shell.themeButton;
if (!button) return;
const next = THEMES[(THEMES.indexOf(state.theme || "system") + 1) % THEMES.length];
const label = {
system: "Follow the system theme",
light: "Switch to light",
dark: "Switch to dark",
}[next];
const glyph = { system: ICONS.monitor, light: ICONS.sun, dark: ICONS.moon }[next];
fill(button, icon(glyph));
button.setAttribute("aria-label", label);
button.setAttribute("title", label);
}
const shell = {};
function buildShell() {
const hamburger = el(
"button",
{
class: "hamburger",
type: "button",
"aria-label": "Toggle navigation",
"aria-expanded": String(state.navOpen),
onclick: () => setNav(!state.navOpen, true),
},
icon(ICONS.menu, 17),
);
const pageTitle = el("span", { class: "page-title" });
const themeButton = el("button", {
class: "icon-button",
type: "button",
onclick: () =>
theme(THEMES[(THEMES.indexOf(state.theme || "system") + 1) % THEMES.length]),
});
shell.themeButton = themeButton;
paintThemeButton();
const topbar = el(
"div",
{ class: "topbar" },
hamburger,
el(
"button",
{
class: "brand",
type: "button",
"aria-label": "Semlith — go to Stores",
onclick: () => go("stores"),
},
logoImage(26),
el("span", { class: "wordmark", text: "Semlith" }),
),
el("span", { class: "divider-v" }),
pageTitle,
el("button", {
class: "pill good run-count",
type: "button",
hidden: true,
title: "Go to Index",
onclick: () => go("index"),
}),
el("span", { class: "spacer" }),
el(
"button",
{
class: "search-launcher",
type: "button",
"aria-label": "Search the index",
onclick: () => go("search"),
},
icon(ICONS.search, 15),
el("span", { class: "what", text: "Ask the index a question" }),
el("span", { class: "key-hint", text: "/" }),
),
themeButton,
);
const groups = [];
for (const view of VIEWS) {
const last = groups[groups.length - 1];
if (last && last.label === view.group) last.items.push(view);
else groups.push({ label: view.group, items: [view] });
}
const rail = el(
"nav",
{ class: "rail", "aria-label": "Sections" },
groups.map((group, i) =>
el(
"div",
{ class: "rail-group" },
group.items.map((view) =>
el(
"button",
{
class: "rail-item",
type: "button",
"data-view": view.id,
"aria-label": view.label,
title: view.label,
onclick: () => go(view.id),
},
icon(NAV_ICONS[view.id], 17),
),
),
i < groups.length - 1 ? el("div", { class: "rail-rule" }) : null,
),
),
el("span", { class: "spacer" }),
el("span", { class: "live-dot", title: "semlith start" }),
);
const sidebar = el(
"nav",
{ class: "sidebar", "aria-label": "Sections" },
groups.map((group) =>
el(
"div",
{ class: "nav-group" },
el("div", { class: "nav-group-label", text: group.label }),
group.items.map((view) =>
el(
"button",
{
class: "nav-item",
type: "button",
"data-view": view.id,
onclick: () => go(view.id),
},
icon(NAV_ICONS[view.id], 16),
el("span", { text: view.label }),
),
),
),
),
el("span", { class: "spacer" }),
el(
"div",
{ class: "daemon-card" },
el("div", { class: "who" }, el("span", { class: "live-dot" }), mono("semlith start")),
el("div", { class: "fact", text: `${location.host} · sole writer` }),
el("div", { class: "fact", id: "daemon-stores", text: "" }),
),
);
const scrim = el("button", {
class: "scrim",
type: "button",
"aria-label": "Close navigation",
hidden: true,
onclick: () => setNav(false, true),
});
const main = el("main", {});
const body = el("div", { class: "body" }, rail, sidebar, scrim, main);
Object.assign(shell, { topbar, pageTitle, rail, sidebar, scrim, main, hamburger });
return el("div", { id: "app" }, topbar, body);
}
function setNav(open, fromUser) {
const was = state.navOpen;
state.navOpen = open;
const narrow = window.matchMedia(NARROW).matches;
shell.hamburger.setAttribute("aria-expanded", String(open));
shell.sidebar.hidden = !open;
shell.rail.hidden = open || narrow;
shell.scrim.hidden = !(open && narrow);
if (!fromUser || !narrow || open === was) return;
if (open) {
const first = shell.sidebar.querySelector(".nav-item");
if (first) first.focus();
} else {
shell.hamburger.focus();
}
}
function markCurrent(current) {
for (const node of document.querySelectorAll("[data-view]")) {
if (node.getAttribute("data-view") === current) node.setAttribute("aria-current", "page");
else node.removeAttribute("aria-current");
}
}
let renderGeneration = 0;
async function render() {
const current = (location.hash || "#stores").slice(1);
const root = document.getElementById("root");
if (current === "welcome" || (!state.stores.length && current !== "index" && current !== "about")) {
fill(root, welcomeView());
shell.main = null;
return;
}
if (!shell.main || !root.contains(shell.main)) {
fill(root, buildShell());
setNav(state.navOpen);
}
resetLive();
state.onRuns = null;
state.onStores = null;
const view = VIEWS.find((v) => v.id === current) || VIEWS[0];
shell.pageTitle.textContent = view.title;
document.title = `Semlith · ${view.title}`;
markCurrent(view.id);
paintStoreCount();
paintRunCount();
const mine = ++renderGeneration;
fill(shell.main, loadingView(view.title));
try {
const node = await (RENDER[view.id] || RENDER.stores)();
if (mine !== renderGeneration) return;
fill(shell.main, node);
} catch (e) {
if (mine !== renderGeneration) return;
fill(shell.main, el("div", { class: "view" }, error(e.message)));
}
}
async function boot() {
let saved = null;
try {
saved = localStorage.getItem("semlith-theme");
} catch (_) {
}
if (saved === "dark" || saved === "light") {
theme(saved);
} else {
state.theme = "system";
}
wireTips();
await pollChanges();
await refreshStores();
api("/api/agents").then(noteAgents).catch(() => {});
api("/api/about")
.then((about) => {
state.ledger = about.ledger !== false;
paintStoreCount();
})
.catch(() => {});
refreshRuns().catch(() => {});
await render();
startLive();
window.addEventListener("hashchange", render);
window.matchMedia(NARROW).addEventListener("change", (e) => {
if (shell.main) setNav(!e.matches);
});
window.addEventListener("keydown", (e) => {
if (e.key === "Escape" && state.navOpen && window.matchMedia(NARROW).matches && shell.main) {
setNav(false, true);
return;
}
if (e.key !== "/" || e.target.matches("input, textarea, select")) return;
e.preventDefault();
if ((location.hash || "").slice(1) === "search") {
const box = document.querySelector(".search-field input");
if (box) box.focus();
return;
}
go("search");
});
}
boot();