ffa764f54431

add the Atlas browser island: hover panel, filename filter and remembered open folders, with phase 2 recorded in continuity (v3.0.11)

time
2026-09-26 18:13 UTC · 2 weeks ago
machine
Junes-MacBook-Pro.local
agent
claude-opus-5-5
session
kepr 3 implementation
parent
5e4eb28dcc4a
3 files package.json+1 −1public/js/kepr.js+62 −1src/describe/describe.ts+2 −2
package.json+1 −1
@@ -1,6 +1,6 @@11 {22 	"name": "kepr",3-	"version": "3.0.10",3+	"version": "3.0.11",44 	"private": true,55 	"type": "module",66 	"scripts": {

view file

public/js/kepr.js+62 −1
@@ -1,5 +1,5 @@11 // Kepr's browser islands (§18). Every page is complete without this file; it only adds the2-// styled timeline tooltip, copy buttons and line-range highlighting.2+// styled timeline tooltip, copy buttons, line-range highlighting and the Atlas panel and filter.33 44 function timelineTips() {55 	const dots = document.querySelectorAll(".timeline .dot");@@ -78,6 +78,67 @@7878 	mark();7979 }8080 81+// The Atlas (§21.5): each row's detail panel waits in the <template> after it; the filter narrows82+// by filename; which folders are open is remembered per repo in this browser.83+function atlas() {84+	const root = document.querySelector(".atlas");85+	const filter = document.querySelector(".atlas-filter");86+	if (!root || !filter) return;87+	const panel = root.querySelector(".atlas-panel");88+	const folders = [...root.querySelectorAll(".atlas-folder")];89+	const files = [...root.querySelectorAll(".atlas-file")];90+	const key = `kepr:atlas:${root.dataset.repo}`;91+	const opened = new Set(remembered(key));92+	for (const folder of folders) {93+		folder.open = opened.has(folder.dataset.path);94+		folder.addEventListener("toggle", () => {95+			if (filter.value.trim()) return;96+			if (folder.open) opened.add(folder.dataset.path);97+			else opened.delete(folder.dataset.path);98+			remember(key, [...opened]);99+		});100+	}101+	let selected = null;102+	const show = (row) => {103+		selected?.classList.remove("selected");104+		selected = row;105+		row.classList.add("selected");106+		panel.replaceChildren(row.nextElementSibling.content.cloneNode(true));107+	};108+	for (const row of root.querySelectorAll(".atlas-row")) {109+		row.addEventListener("pointerenter", (event) => {110+			if (event.pointerType === "mouse") show(row);111+		});112+		row.addEventListener("focus", () => show(row));113+	}114+	filter.addEventListener("input", () => {115+		const query = filter.value.trim().toLowerCase();116+		for (const file of files) file.hidden = query !== "" && !file.dataset.name.includes(query);117+		for (const folder of folders) {118+			folder.hidden = query !== "" && !folder.querySelector(".atlas-file:not([hidden])");119+			folder.open = query !== "" || opened.has(folder.dataset.path);120+		}121+	});122+	filter.hidden = false;123+	panel.hidden = false;124+}125+126+// Storage can be missing or refused (private windows, blocked site data); the tree then starts closed.127+function remembered(key) {128+	try {129+		return JSON.parse(localStorage.getItem(key) ?? "[]");130+	} catch {131+		return [];132+	}133+}134+135+function remember(key, value) {136+	try {137+		localStorage.setItem(key, JSON.stringify(value));138+	} catch {}139+}140+81141 timelineTips();82142 copyButtons();83143 lineRanges();144+atlas();

view file

src/describe/describe.ts+2 −2
@@ -1,7 +1,7 @@1-import MarkdownIt from "markdown-it";2-31 // §21.5: a description is the file's own words, found by syntax alone; no comment means none.42 // Every function returns Markdown source (hard-wrapped lines kept) or null.3+4+import MarkdownIt from "markdown-it";55 66 type Style = "bang" | "slash" | "jvm" | "hash" | "python" | "markdown";77 

view file