diff --git a/timeline/index.html b/timeline/index.html
index 3c3fb8d..9b832f0 100644
--- a/timeline/index.html
+++ b/timeline/index.html
@@ -987,11 +987,28 @@
How to read it
// began while a role ran still did. The selection is state, not a place: remembered in
// the browser like the card's width, never written into the address — and a link to a
// row of a kind that is off turns that kind back on, because a link must land.
+ //
+ // A link can still set it. ?kinds= names the kinds to show, by name and in any case,
+ // comma-separated: those are on and the rest off, and the page reads it the way it reads
+ // ?lang= — once, on arrival, then strips it from the address and remembers the selection
+ // as if the menu had been used, so the link hands the visitor a view and nothing they
+ // copy afterwards carries it. A name the model does not know is dropped, and a list naming
+ // no kind at all is ignored whole rather than emptying the ledger.
var KINDS = data.entities.filter(function(e){ return e.type === "experience-kind"; })
.sort(function(a, b){ return a.name.localeCompare(b.name, "en"); });
var KIND_KEY = "timeline-kinds", on = {};
KINDS.forEach(function(k){ on[k.name] = true; });
try { var saved = JSON.parse(localStorage.getItem(KIND_KEY) || "null"); if (saved) KINDS.forEach(function(k){ if (k.name in saved) on[k.name] = !!saved[k.name]; }); } catch (e) {}
+ var linked = /[?&]kinds=([^]*)(&|$)/.exec(location.search), linkedKinds = null;
+ if (linked) {
+ var named = decodeURIComponent(linked[1]).toLowerCase().split(",");
+ var known = KINDS.filter(function(k){ return named.indexOf(k.name.toLowerCase()) >= 0; });
+ if (known.length) { linkedKinds = {}; KINDS.forEach(function(k){ linkedKinds[k.name] = known.indexOf(k) >= 0; }); }
+ try {
+ var q = location.search.replace(/([?&])kinds=[^]*(&|$)/, "$1").replace(/[?&]$/, "");
+ history.replaceState(null, "", location.pathname + q + location.hash);
+ } catch (e) {}
+ }
var kinds = document.getElementById("kinds"), kindsLabel = document.getElementById("kindslabel"), kindsMenu = document.getElementById("kindsmenu");
var none = h("li", null, "none"); none.hidden = true; ol.appendChild(none);
var boxes = [];
@@ -1087,7 +1104,7 @@ How to read it
window.addEventListener("hashchange", arrive);
new MutationObserver(render).observe(document.documentElement, { attributes: true, attributeFilter: ["lang"] });
- applyKinds();
+ if (linkedKinds) { on = linkedKinds; applyKinds(true); } else applyKinds();
arrive();
}
diff --git a/timeline/og.sha b/timeline/og.sha
index b6aa91b..e8f01e1 100644
--- a/timeline/og.sha
+++ b/timeline/og.sha
@@ -1 +1 @@
-4e61cc3847555fee53e2bee1397ed745291be6cddfabbc66b882562466fbc59a
+c82190396ef09ea5f16cd25f466022be5b674e4828d9e1e9172acee71c1c1f10
diff --git a/verify/check.mjs b/verify/check.mjs
index f0fbcbf..f715552 100644
--- a/verify/check.mjs
+++ b/verify/check.mjs
@@ -311,7 +311,27 @@ const CHECKS = {
await page.goto(BASE + spec.path + "#" + target);
const opened = await page.evaluate((id) => document.getElementById(id).open, target);
if (!opened) return `arriving at #${target} did not open that row`;
- // Leave the page as it was found — at rest, no row open — for whatever check runs next.
+ // Arriving with ?kinds= sets the selection from the address: the kinds named are on and
+ // the rest off, the parameter leaves the address the way ?lang= does, and the selection
+ // is remembered as if the menu had been used. A name the model does not know is ignored,
+ // and a list naming no kind at all is ignored whole.
+ await page.goto(BASE + spec.path + "?kinds=" + kindOff.toLowerCase() + ",nosuchkind");
+ const linked = await page.evaluate((kind) => ({
+ hidden: [...document.querySelectorAll("#ledger li.k-" + kind.toLowerCase())].filter(li => li.hidden).length,
+ others: [...document.querySelectorAll("#ledger li:has(details)")].filter(li => !li.classList.contains("k-" + kind.toLowerCase()) && !li.hidden).length,
+ label: document.getElementById("kindslabel").textContent, search: location.search,
+ stored: (() => { try { return JSON.parse(localStorage.getItem("timeline-kinds")); } catch (e) { return null; } })() }), kindOff);
+ if (linked.hidden) return `?kinds=${kindOff} hid ${linked.hidden} rows of that kind`;
+ if (linked.others) return `?kinds=${kindOff} left ${linked.others} rows of other kinds showing`;
+ if (linked.label !== kindOff) return `with ?kinds=${kindOff} the filter reads ${JSON.stringify(linked.label)}`;
+ if (linked.search) return `the address still carries ${linked.search}`;
+ if (!linked.stored || linked.stored[kindOff] !== true || Object.keys(linked.stored).some(k => k !== kindOff && linked.stored[k])) return `?kinds=${kindOff} was not remembered: ${JSON.stringify(linked.stored)}`;
+ await page.goto(BASE + spec.path + "?kinds=nosuchkind");
+ const ignored = await page.evaluate(() => ({ hidden: [...document.querySelectorAll("#ledger li:has(details)")].filter(li => li.hidden).length, label: document.getElementById("kindslabel").textContent }));
+ if (ignored.label !== kindOff) return `?kinds naming no kind changed the selection to ${JSON.stringify(ignored.label)}`;
+ // Leave the page as it was found — at rest, every kind on, no row open — for whatever
+ // check runs next.
+ await page.evaluate(() => { try { localStorage.removeItem("timeline-kinds"); } catch (e) {} });
await page.goto(BASE + spec.path);
return null;
},