3470f48c8b57

prevent dense timeline dots flashing before canvas loads (v3.0.208)

time
2026-10-03 14:53 UTC · 1 week ago
machine
Junes-MacBook-Pro.local
agent
gpt-6.1-sol
session
tidy operator navigation
parent
bd7e99daf81b
6 files package.json+1 −1public/css/kepr.css+3 −0public/js/kepr.js+3 −1public/js/nav.js+9 −0src/pages/timeline.ts+3 −3tests/navbar.test.ts+2 −0
package.json+1 −1
@@ -1,6 +1,6 @@11 {22 	"name": "kepr",3-	"version": "3.0.207",3+	"version": "3.0.208",44 	"private": true,55 	"type": "module",66 	"scripts": {

view file

public/css/kepr.css+3 −0
@@ -510,6 +510,9 @@510510 .timeline .dot {511511 	fill: var(--accent);512512 }513+.timeline-pending .timeline .dot {514+	visibility: hidden;515+}513516 .timeline .d0 {514517 	fill: var(--accent-dim);515518 	fill-opacity: 0.45;

view file

public/js/kepr.js+3 −1
@@ -990,7 +990,9 @@990990 991991 // Widths are only final once Martian Mono has loaded.992992 document.fonts.ready.then(tickerPace);993-timeline().catch((error) => console.error("kepr: the timeline could not load", error));993+timeline()994+	.catch((error) => console.error("kepr: the timeline could not load", error))995+	.finally(() => document.documentElement.classList.remove("timeline-pending"));994996 copyButtons(document);995997 panels();996998 saveLists();

view file

public/js/nav.js+9 −0
@@ -2,6 +2,15 @@22 // nav, before the rest of the page, so the signed-out links never show first. Pages are the same33 // for everyone; the readable kepr_editor cookie, set beside the session, carries the form token44 // that log out posts back. Only the private notification preview needs a request.5+// Suppress the dense static dots before the timeline markup can paint.6+document.documentElement.classList.add("timeline-pending");7+// A failed deferred script never reaches the timeline's finally block.8+window.addEventListener("load", () => {9+	if (typeof window.timeline !== "function") {10+		document.documentElement.classList.remove("timeline-pending");11+	}12+});13+514 {615 	const token = /(?:^|;\s*)kepr_editor=([0-9a-f]{64})(?:;|$)/.exec(document.cookie)?.[1];716 	const link = document.querySelector('.nav-links a[href^="/sign-in"]');

view file

src/pages/timeline.ts+3 −3
@@ -25,12 +25,12 @@2525 		const x = span === 0 ? 50 : 1 + (98 * (t - first)) / span;2626 		const near = countBetween(sorted, t - reach, t + reach);2727 		const level = near >= 8 ? 3 : near >= 4 ? 2 : near >= 2 ? 1 : 0;28-		return html`<circle class="dot d${level}" cx="${x.toFixed(3)}%" cy="28" r="${2.5 + level * 0.5}"/>`;28+		return html`<circle class="dot d${level}" cx="${x.toFixed(3)}%" cy="32" r="${2.5 + level * 0.5}"/>`;2929 	});30-	return html`<svg class="timeline" width="100%" height="56" role="img" aria-label="${30+	return html`<svg class="timeline" width="100%" height="64" role="img" aria-label="${3131 		saves.length3232 	} saves over time" data-src="/${repo}/timeline">33-<line class="timeline-line" x1="0" x2="100%" y1="28" y2="28"/>33+<line class="timeline-line" x1="0" x2="100%" y1="32" y2="32"/>3434 ${dots}3535 </svg>`;3636 }

view file

tests/navbar.test.ts+2 −0
@@ -14,6 +14,7 @@1414 	let replacement: unknown[] = [];1515 	const document = {1616 		cookie,17+		documentElement: { classList: { add: () => {} } },1718 		addEventListener: () => {},1819 		querySelector: () => ({1920 			parentElement: { after: () => {} },@@ -39,6 +40,7 @@3940 	};4041 	runInNewContext(script, {4142 		document,43+		window: { addEventListener: () => {} },4244 		fetch: async () => ({ ok: true, json: async () => ({ unread: 0, notifications: [] }) }),4345 	});4446 	return { nodes, replacement };

view file