From 860b2331d4e2628bac66fe6f0fda47c1f0e44897 Mon Sep 17 00:00:00 2001 From: CryptoJym Date: Fri, 25 Sep 2026 18:17:01 -0600 Subject: [PATCH] Rebuild the site around the running system: the ship's map and the Stardate log - The ship's map: an isometric deck of eleven cubes (the memory core, JEV, its five lanes, deep dream, the judge kit and the fleet) with a card per cube, a guided tour of how the parts work together, and an assimilation counter. Every card and step is also plain HTML, readable without JavaScript. - "The collective in two minutes": what the collective is, how memory stays clean, and why every change is reversible, with a scope note separating our running collective from what you install. - The Stardate log at /assets/stardate/: eight lab entries, each with what we did, what we learned, open questions and a lab book log. Markdown sources in site/stardate/entries, rendered by site/stardate/publish.mjs, checked by site/verify-stardate.mjs (format, freshness, links, privacy floor). - Existing sections, functions and ids are unchanged; the old four-layer section's id moves from #system to #layers. RELEASE-INVENTORY.json is resealed from a clean archive. Checked: verify-stardate, verify-links (341), the map and publish --check, the Pages workflow steps, the release guard and its 22 unit tests, the browser suite on Chromium and WebKit (50/50), axe at 320-1920 px, and an independent privacy and truth review. Co-Authored-By: Claude Opus 5.5 --- RELEASE-INVENTORY.json | 150 +++- site/app.js | 287 ++++++ site/assets/generate-system-map.mjs | 253 ++++++ .../stardate/2026-267-recall-budget-jam.html | 66 ++ .../stardate/2026-268-clm-side-test.html | 66 ++ .../stardate/2026-268-deep-dream-day-one.html | 66 ++ .../stardate/2026-268-grok-hooks-ab.html | 66 ++ .../stardate/2026-268-incoming-filter-on.html | 67 ++ .../stardate/2026-268-judge-kit-reviews.html | 65 ++ .../stardate/2026-268-marker-chains.html | 67 ++ .../stardate/2026-268-memory-reach.html | 66 ++ site/assets/stardate/index.html | 84 ++ site/assets/stardate/stardate.css | 401 +++++++++ site/index.html | 343 ++++++- site/scene.js | 8 +- site/stardate/README.md | 91 ++ .../entries/2026-267-recall-budget-jam.md | 48 + .../entries/2026-268-clm-side-test.md | 51 ++ .../entries/2026-268-deep-dream-day-one.md | 49 + .../entries/2026-268-grok-hooks-ab.md | 54 ++ .../entries/2026-268-incoming-filter-on.md | 52 ++ .../entries/2026-268-judge-kit-reviews.md | 50 ++ .../entries/2026-268-marker-chains.md | 53 ++ .../stardate/entries/2026-268-memory-reach.md | 53 ++ site/stardate/publish.mjs | 40 + site/stardate/stardate.mjs | 395 ++++++++ site/styles.css | 844 ++++++++++++++++++ site/verify-links.mjs | 7 +- site/verify-map.mjs | 119 +++ site/verify-stardate.mjs | 179 ++++ site/verify.mjs | 20 +- 31 files changed, 4122 insertions(+), 38 deletions(-) create mode 100644 site/assets/generate-system-map.mjs create mode 100644 site/assets/stardate/2026-267-recall-budget-jam.html create mode 100644 site/assets/stardate/2026-268-clm-side-test.html create mode 100644 site/assets/stardate/2026-268-deep-dream-day-one.html create mode 100644 site/assets/stardate/2026-268-grok-hooks-ab.html create mode 100644 site/assets/stardate/2026-268-incoming-filter-on.html create mode 100644 site/assets/stardate/2026-268-judge-kit-reviews.html create mode 100644 site/assets/stardate/2026-268-marker-chains.html create mode 100644 site/assets/stardate/2026-268-memory-reach.html create mode 100644 site/assets/stardate/index.html create mode 100644 site/assets/stardate/stardate.css create mode 100644 site/stardate/README.md create mode 100644 site/stardate/entries/2026-267-recall-budget-jam.md create mode 100644 site/stardate/entries/2026-268-clm-side-test.md create mode 100644 site/stardate/entries/2026-268-deep-dream-day-one.md create mode 100644 site/stardate/entries/2026-268-grok-hooks-ab.md create mode 100644 site/stardate/entries/2026-268-incoming-filter-on.md create mode 100644 site/stardate/entries/2026-268-judge-kit-reviews.md create mode 100644 site/stardate/entries/2026-268-marker-chains.md create mode 100644 site/stardate/entries/2026-268-memory-reach.md create mode 100644 site/stardate/publish.mjs create mode 100644 site/stardate/stardate.mjs create mode 100644 site/verify-map.mjs create mode 100644 site/verify-stardate.mjs diff --git a/RELEASE-INVENTORY.json b/RELEASE-INVENTORY.json index 55e228a..4dc7cea 100644 --- a/RELEASE-INVENTORY.json +++ b/RELEASE-INVENTORY.json @@ -1,6 +1,6 @@ { "excluded_self": "RELEASE-INVENTORY.json", - "file_count": 372, + "file_count": 396, "files": [ { "bytes": 1914, @@ -1563,9 +1563,9 @@ "sha256": "e2d5eeb2ce1e090d08862be290dfb2d20577272b7262627a8018211e01d6015f" }, { - "bytes": 2718, + "bytes": 14473, "path": "site/app.js", - "sha256": "a2dcd419b71edeed321614e92c394ee71877413703c559b3ca9b1539b87b02c7" + "sha256": "bee0bc996140d34d0e5839bfc6e62d8a606c322644abe4214573b5e816d25499" }, { "bytes": 4396, @@ -1622,6 +1622,11 @@ "path": "site/assets/generate-cube.py", "sha256": "1cab603b29a2e3c9ea36f57ce340920dccef9a6ee16ba1f505e7ee960bbce228" }, + { + "bytes": 14153, + "path": "site/assets/generate-system-map.mjs", + "sha256": "9f0361bba479f227bf098af57d55437b9752ca04e5cb7515df5a1e9341bdc848" + }, { "bytes": 251, "path": "site/assets/mark.svg", @@ -1652,6 +1657,56 @@ "path": "site/assets/providers/openai.svg", "sha256": "b6930d08a383f03735bbf317ab9e410648501804f501cf58f563e8d5274e0880" }, + { + "bytes": 7475, + "path": "site/assets/stardate/2026-267-recall-budget-jam.html", + "sha256": "8b18dd4272c65ea93f804e2d5b5f670ba05df06f5eabc5aa899ec5699040c5b5" + }, + { + "bytes": 8314, + "path": "site/assets/stardate/2026-268-clm-side-test.html", + "sha256": "d01f229fb24e1512d54f961080c0388598a37f734a214539f88e6d93c1b62029" + }, + { + "bytes": 8482, + "path": "site/assets/stardate/2026-268-deep-dream-day-one.html", + "sha256": "f202b778954af39a4349da60f6df2bac6cb6b00ea2ff3ae9eced096f813cccf7" + }, + { + "bytes": 8257, + "path": "site/assets/stardate/2026-268-grok-hooks-ab.html", + "sha256": "815a2875a1e2f60949794567d4939fa7d0c6347c0cb046df58cb37a576911bad" + }, + { + "bytes": 7869, + "path": "site/assets/stardate/2026-268-incoming-filter-on.html", + "sha256": "a38df021e28c871e04bcae2c67effaa54ceb2625d64d48ba8885c7088acdecec" + }, + { + "bytes": 7710, + "path": "site/assets/stardate/2026-268-judge-kit-reviews.html", + "sha256": "b271aabcdbc089898b8f4984dc5f9f44f40e94927bd3c7ee8e73a667749b7e92" + }, + { + "bytes": 7932, + "path": "site/assets/stardate/2026-268-marker-chains.html", + "sha256": "cfd28bcaca87d62731574c113dc27264c3a9e365ea64415584a2f5b5af40ce24" + }, + { + "bytes": 8092, + "path": "site/assets/stardate/2026-268-memory-reach.html", + "sha256": "70e8a9b1b154b5c71441c074f242572e8001ca7c72220fb86d1b18284a91ed90" + }, + { + "bytes": 8374, + "path": "site/assets/stardate/index.html", + "sha256": "51bc251d6a0f23b2d309dac650b4b6a4b91057f6162f82f5ec2138e7d93ca416" + }, + { + "bytes": 7046, + "path": "site/assets/stardate/stardate.css", + "sha256": "31ab6524b09775ab3765c3ed6240397941f5c41c684910dca3355089a261c767" + }, { "bytes": 9836, "path": "site/configure.css", @@ -1698,9 +1753,9 @@ "sha256": "0ef3fd6b85ade71ccb594334f5f3e4a41000f2d9167c380b95b9248d73297d2c" }, { - "bytes": 40555, + "bytes": 91082, "path": "site/index.html", - "sha256": "89371ee52f8ce4cbeb2fd661de401083db31d152d1b013c8f6bd362d5cd3aecd" + "sha256": "59ddda9bdaeed52e7671f3a10eeffe88abfdb37986db1b4147440b6b2ff7f007" }, { "bytes": 2877, @@ -1708,14 +1763,69 @@ "sha256": "bd2cceb71e4443839ddd0e05e112ad3a0a52958e516f1ab7d6f8a43e0ae39c52" }, { - "bytes": 9615, + "bytes": 9960, "path": "site/scene.js", - "sha256": "1a8e3c67a216eaf239e41223700b82e6cf66e4c2a757d76d737379105ac4cecb" + "sha256": "f3fb5de21e62479f6fcc1c9126f3163dbb4eb7a3a287a64b9414a48c754523f7" + }, + { + "bytes": 4006, + "path": "site/stardate/README.md", + "sha256": "7d12c7fcf23a05cc00a1425d0b619b8f422e3f106234e3521cc59a8a5dc78e8b" }, { - "bytes": 32107, + "bytes": 3470, + "path": "site/stardate/entries/2026-267-recall-budget-jam.md", + "sha256": "8afcecb62a32871bc38e549f1e9f785fb44a8c8a679a381d7c54e9aeb51e9c5f" + }, + { + "bytes": 3676, + "path": "site/stardate/entries/2026-268-clm-side-test.md", + "sha256": "f5c6432059172bba4bed215be597394e62fce5b2cc2f35f9a4baa60caaad94e8" + }, + { + "bytes": 3839, + "path": "site/stardate/entries/2026-268-deep-dream-day-one.md", + "sha256": "dab39f0dfc2c07f372aa58232f78817d7753b15e634ce9114f89b932b5ba339a" + }, + { + "bytes": 3522, + "path": "site/stardate/entries/2026-268-grok-hooks-ab.md", + "sha256": "e90d263eaed4218d9471ae533156246943300e103ca16393ce78d9ca2741244a" + }, + { + "bytes": 3565, + "path": "site/stardate/entries/2026-268-incoming-filter-on.md", + "sha256": "f1bc2dfe472e3a8ff6f9cea35fa3b0b84b8bfdac47146f83c180ca3dd17ff208" + }, + { + "bytes": 3183, + "path": "site/stardate/entries/2026-268-judge-kit-reviews.md", + "sha256": "a234371abb14440ec3598b57e43465dfe83cfa7555d0d46c7ce59d786e8ec0d7" + }, + { + "bytes": 3755, + "path": "site/stardate/entries/2026-268-marker-chains.md", + "sha256": "942a8b5763a6d205473b624555949c83da79759d1d5e6f77817ea9829459844f" + }, + { + "bytes": 3469, + "path": "site/stardate/entries/2026-268-memory-reach.md", + "sha256": "37054bd09340768179275144f3b2010428b3bf476fa7810cd1f7b79e0c8f6d2a" + }, + { + "bytes": 1952, + "path": "site/stardate/publish.mjs", + "sha256": "e9b3aee47279968d24eae1b61c9dfa3359e2485cee53b0cc386e230d9fe30fa2" + }, + { + "bytes": 19504, + "path": "site/stardate/stardate.mjs", + "sha256": "80511b3233b79ea29710fcd1140a374819dca979636ca7927ef94c84736cfd29" + }, + { + "bytes": 48300, "path": "site/styles.css", - "sha256": "133511e31c2aae749ce9376b5e7c4757fa11ec34d3fb988f5ec1a4eaa615780c" + "sha256": "04e466033c64bb886b2a2d6da9bee71edd535491da646a9310bc8000f24bb37a" }, { "bytes": 1081, @@ -1783,14 +1893,24 @@ "sha256": "3b1bef292bc037c13b9291bf034421a791d7e011a244e184da68a9c565bd2d09" }, { - "bytes": 2009, + "bytes": 2283, "path": "site/verify-links.mjs", - "sha256": "edc3d3529ea3382663faa3fdbec157352692e0d2e89e5de98ceeca111a79db7c" + "sha256": "999257d4f0159267f0dcc0cb8311d7ea2eef6852c3a2e3a0e4c817c78760f163" + }, + { + "bytes": 8125, + "path": "site/verify-map.mjs", + "sha256": "3d6d9e9be392aa684ef688f9b7b2e3de3d7ebf0595c2db65666a137d5a992e42" + }, + { + "bytes": 10730, + "path": "site/verify-stardate.mjs", + "sha256": "24598b63866dcf3f17f0b1498f704e3e3cdc4d9caf08ca2e35f7344320022cae" }, { - "bytes": 12794, + "bytes": 13966, "path": "site/verify.mjs", - "sha256": "06dbd515ebdc2e2bc61b2b9d729af9e071ddb75109f7aa81ada3c4b1baa83005" + "sha256": "13b988b925ddbde8e37a4d70122c0354fe76fa7c4272490bc6f0c536a17f1155" }, { "bytes": 6275, @@ -1863,7 +1983,7 @@ "sha256": "cd4bdb4529012e0cfcd38e059215f9ea433b8ee1fa636276b36c6515e7949e28" } ], - "inventory_sha256": "787241ae9bf10d37224ad69423f5b37cbc3d8110df4c4e7d9461320adf1afbe6", + "inventory_sha256": "772c13116dd673c24a24aeee691bcf57ee60891a7b75da8310f286d5c7a28a58", "schema": "borg-public-inventory/v1", - "total_bytes": 87734443 + "total_bytes": 87981209 } diff --git a/site/app.js b/site/app.js index d32d34a..0d92e85 100644 --- a/site/app.js +++ b/site/app.js @@ -74,3 +74,290 @@ }); }); })(); + +/* Ship's map: a console around static cards. Every card, step and entry stays in the + HTML; this adds the card panel, assimilation progress, the guided tour and the flow cycle. */ +(() => { + "use strict"; + const section = document.querySelector("#system"); + if (!section) return; + const art = section.querySelector(".map-art"); + const nodes = [...section.querySelectorAll(".map-node[data-part]")]; + const cards = new Map( + [...section.querySelectorAll(".part-card[data-part]")].map((card) => [card.dataset.part, card]), + ); + const panel = section.querySelector("#map-card"); + const count = section.querySelector("#map-count"); + const countLabel = section.querySelector("#map-count-label"); + const ring = section.querySelector(".ring-fill"); + const announce = section.querySelector(".map-announce"); + const tourButton = section.querySelector("#map-tour"); + const resetButton = section.querySelector("#map-reset"); + const hud = section.querySelector(".map-hud-text"); + const steps = [...section.querySelectorAll(".flow-steps > li")]; + const reduced = window.matchMedia("(prefers-reduced-motion: reduce)"); + const total = cards.size; + const key = "borg-map-assimilated-v1"; + const name = (part) => cards.get(part)?.querySelector("h4").textContent.trim() || "Stardate log"; + const readout = (part) => + cards.get(part)?.querySelector(".part-readout").textContent.replace(/\s+/g, " ").trim() || ""; + let seen = new Set(); + try { + seen = new Set(JSON.parse(localStorage.getItem(key) || "[]").filter((part) => cards.has(part))); + } catch { + seen = new Set(); + } + let tour = -1; + let cycle = 0; + let timer = 0; + + const cubes = (part) => (art ? [...art.querySelectorAll(`.cube[data-part="${part}"]`)] : []); + function save() { + try { + localStorage.setItem(key, JSON.stringify([...seen])); + } catch { + /* Progress is optional; private browsing may refuse storage. */ + } + } + function progress() { + const done = seen.size; + count.textContent = `${done} of ${total}`; + countLabel.textContent = done === total ? "parts assimilated. The collective is complete." : "parts assimilated"; + ring.style.strokeDasharray = `${(done / total) * 100} 100`; + section.classList.toggle("is-complete", done === total); + resetButton.hidden = done === 0; + for (const [part, card] of cards) { + const assimilated = seen.has(part); + cubes(part).forEach((cube) => cube.classList.toggle("is-assimilated", assimilated)); + nodes.filter((node) => node.dataset.part === part) + .forEach((node) => node.classList.toggle("is-assimilated", assimilated)); + let badge = card.querySelector(".part-state"); + if (assimilated && !badge) { + badge = document.createElement("p"); + badge.className = "part-state"; + badge.textContent = "Assimilated"; + card.append(badge); + } else if (!assimilated && badge) badge.remove(); + } + } + function assimilate(parts) { + const fresh = parts.filter((part) => cards.has(part) && !seen.has(part)); + fresh.forEach((part) => seen.add(part)); + if (fresh.length) { + save(); + progress(); + } + return fresh; + } + function highlight(part, className) { + art?.querySelectorAll(`.${className}`).forEach((cube) => cube.classList.remove(className)); + if (part) cubes(part).forEach((cube) => cube.classList.add(className)); + } + function button(label, onClick, primary = false) { + const control = document.createElement("button"); + control.type = "button"; + control.className = primary ? "map-button primary" : "map-button"; + control.textContent = label; + control.addEventListener("click", onClick); + return control; + } + function nextUnseen(after) { + const order = [...cards.keys()]; + const start = order.indexOf(after); + for (let i = 1; i <= order.length; i++) { + const part = order[(start + i) % order.length]; + if (!seen.has(part)) return part; + } + return null; + } + function say(message) { + announce.textContent = ""; + requestAnimationFrame(() => { + announce.textContent = message; + }); + } + function reveal() { + const box = panel.getBoundingClientRect(); + if (box.top < 0 || box.top > innerHeight * 0.7) + panel.scrollIntoView({ block: "start", behavior: reduced.matches ? "auto" : "smooth" }); + } + function open(part, { fromMap = true } = {}) { + const card = cards.get(part); + if (!card) return; + exitTour(false); + const copy = card.cloneNode(true); + copy.removeAttribute("id"); + copy.removeAttribute("role"); + copy.querySelectorAll("[id]").forEach((element) => element.removeAttribute("id")); + copy.removeAttribute("aria-labelledby"); + copy.querySelector(".part-state")?.remove(); + const heading = copy.querySelector("h4"); + const title = document.createElement("h3"); + title.id = "map-panel-title"; + title.textContent = heading.textContent; + heading.replaceWith(title); + const fresh = assimilate([part]); + const next = nextUnseen(part); + const actions = document.createElement("div"); + actions.className = "map-card-nav"; + if (next) actions.append(button(`Next: ${name(next)}`, () => open(next, { fromMap: false }), true)); + else { + const log = document.createElement("a"); + log.className = "map-button primary"; + log.href = "./assets/stardate/"; + log.textContent = "Every part seen. Read the Stardate log"; + actions.append(log); + } + actions.append(button("All cards", () => document.querySelector(`#part-${part}`).scrollIntoView({ behavior: reduced.matches ? "auto" : "smooth" }))); + panel.replaceChildren(copy, actions); + nodes.forEach((node) => node.classList.toggle("is-selected", node.dataset.part === part)); + nodes.forEach((node) => (node.dataset.part === part ? node.setAttribute("aria-current", "true") : node.removeAttribute("aria-current"))); + highlight(part, "is-selected"); + const status = fresh.length + ? `${name(part)} assimilated. ${seen.size} of ${total}.${seen.size === total ? " The collective is complete." : ""}` + : `${name(part)}. Already assimilated.`; + say(status); + if (!fromMap) panel.focus({ preventScroll: true }); + reveal(); + } + function showStep(index) { + const step = steps[index]; + const parts = step.dataset.parts.split(" ").filter((part) => cards.has(part)); + section.dataset.flow = String(index + 1); + steps.forEach((item, i) => item.classList.toggle("is-active", i === index)); + art?.querySelectorAll(".cube.is-step").forEach((cube) => cube.classList.remove("is-step")); + parts.forEach((part) => cubes(part).forEach((cube) => cube.classList.add("is-step"))); + return parts; + } + function renderTour() { + const step = steps[tour]; + const parts = showStep(tour); + const fresh = assimilate(parts); + const eyebrow = document.createElement("p"); + eyebrow.className = "eyebrow"; + eyebrow.textContent = `GUIDED TOUR · STOP ${tour + 1} OF ${steps.length}`; + const title = document.createElement("h3"); + title.id = "map-panel-title"; + title.textContent = step.querySelector("h4").textContent; + const text = document.createElement("p"); + text.textContent = step.querySelector("p").textContent; + const list = document.createElement("ul"); + list.className = "tour-parts"; + for (const part of parts) { + const item = document.createElement("li"); + const label = document.createElement("b"); + label.textContent = name(part); + const lede = document.createElement("span"); + lede.textContent = cards.get(part).querySelector(".part-lede").textContent + " "; + const value = document.createElement("span"); + value.className = "readout"; + value.textContent = readout(part); + item.append(label, lede, value); + list.append(item); + } + const actions = document.createElement("div"); + actions.className = "map-card-nav"; + if (tour > 0) actions.append(button("Back", () => { tour--; renderTour(); })); + if (tour < steps.length - 1) actions.append(button("Next stop", () => { tour++; renderTour(); }, true)); + else actions.append(button("Finish the tour", () => exitTour(true), true)); + actions.append(button("Exit", () => exitTour(true))); + panel.replaceChildren(eyebrow, title, text, list, actions); + say(`Stop ${tour + 1} of ${steps.length}: ${title.textContent}${fresh.length ? ` ${fresh.length} new part${fresh.length > 1 ? "s" : ""} assimilated, ${seen.size} of ${total}.` : ""}`); + panel.focus({ preventScroll: true }); + reveal(); + } + function startTour(index = 0) { + stopCycle(); + tour = index; + tourButton.hidden = true; + renderTour(); + } + function exitTour(restore) { + if (tour < 0) return; + tour = -1; + tourButton.hidden = false; + steps.forEach((item) => item.classList.remove("is-active")); + delete section.dataset.flow; + if (restore) { + panel.replaceChildren(...intro.map((node) => node.cloneNode(true))); + say("Tour closed."); + tourButton.focus(); + } + syncCycle(); + } + function stopCycle() { + clearInterval(timer); + timer = 0; + } + function syncCycle() { + const running = section.dataset.motion === "running" && tour < 0; + if (running && !timer) { + showStep(cycle); + timer = setInterval(() => { + cycle = (cycle + 1) % steps.length; + showStep(cycle); + }, 4200); + } else if (!running && timer) { + stopCycle(); + } + if (!running && tour < 0) { + delete section.dataset.flow; + steps.forEach((item) => item.classList.remove("is-active")); + art?.querySelectorAll(".cube.is-step").forEach((cube) => cube.classList.remove("is-step")); + } + } + + const intro = [...panel.childNodes]; + section.classList.add("game-on"); + for (const node of nodes) { + const part = node.dataset.part; + if (part === "log") continue; + node.addEventListener("click", (event) => { + if (event.metaKey || event.ctrlKey || event.shiftKey || event.button !== 0) return; + event.preventDefault(); + open(part); + }); + const scan = () => { + highlight(part, "is-hover"); + if (hud) hud.textContent = `${name(part).toUpperCase()} · ${readout(part)}`; + }; + const clear = () => { + highlight(null, "is-hover"); + if (hud) hud.textContent = "Hover or focus a cube to scan it."; + }; + node.addEventListener("pointerenter", scan); + node.addEventListener("focus", scan); + node.addEventListener("pointerleave", clear); + node.addEventListener("blur", clear); + } + steps.forEach((step, index) => { + const heading = step.querySelector("h4"); + const control = document.createElement("button"); + control.type = "button"; + control.className = "flow-step-button"; + control.textContent = heading.textContent; + control.setAttribute("aria-label", `Tour stop ${index + 1}: ${heading.textContent}`); + control.addEventListener("click", () => startTour(index)); + heading.replaceChildren(control); + }); + tourButton.hidden = false; + tourButton.addEventListener("click", () => startTour(0)); + resetButton.addEventListener("click", () => { + seen.clear(); + save(); + progress(); + highlight(null, "is-selected"); + nodes.forEach((node) => { + node.classList.remove("is-selected"); + node.removeAttribute("aria-current"); + }); + if (tour < 0) panel.replaceChildren(...intro.map((node) => node.cloneNode(true))); + say("Progress reset. Every part is unassimilated again."); + }); + section.addEventListener("keydown", (event) => { + if (event.key === "Escape" && tour >= 0) exitTour(true); + }); + new MutationObserver(syncCycle).observe(section, { attributes: true, attributeFilter: ["data-motion"] }); + progress(); + syncCycle(); +})(); diff --git a/site/assets/generate-system-map.mjs b/site/assets/generate-system-map.mjs new file mode 100644 index 0000000..8f56a99 --- /dev/null +++ b/site/assets/generate-system-map.mjs @@ -0,0 +1,253 @@ +#!/usr/bin/env node +// Generates the isometric deck art for the home page system map (inline SVG in index.html, +// between the system-map:art markers) plus the hotspot positions. Deterministic, no dependencies. +// node site/assets/generate-system-map.mjs rewrite the block in index.html +// node site/assets/generate-system-map.mjs --check exit 1 if the block is out of date +import { readFile, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const W = 1000; +const H = 800; +const C = Math.cos(Math.PI / 6); +const S = 0.5; +const r = value => Math.round(value * 10) / 10; +const pt = ([x, y]) => `${r(x)},${r(y)}`; +let seed = 7331; +const random = () => ((seed = (seed * 16807) % 2147483647) / 2147483647); + +// Parts: footprint centre on the deck (x, y), edge length e in px, hover height z. +export const NODES = [ + { id: 'fleet', x: 176, y: 440, e: 0, z: 0, cluster: true }, + { id: 'incoming', x: 332, y: 338, e: 60, z: 0 }, + { id: 'packets', x: 332, y: 566, e: 60, z: 0 }, + { id: 'jev', x: 500, y: 452, e: 96, z: 0 }, + { id: 'dreaming', x: 668, y: 338, e: 60, z: 0 }, + { id: 'recall', x: 668, y: 566, e: 60, z: 0 }, + { id: 'cache', x: 500, y: 662, e: 52, z: 0 }, + { id: 'memory', x: 812, y: 446, e: 138, z: 0 }, + { id: 'judge', x: 372, y: 214, e: 68, z: 46 }, + { id: 'deepdream', x: 800, y: 196, e: 68, z: 46 }, + { id: 'log', x: 880, y: 690, e: 40, z: 0 }, +]; +const byId = Object.fromEntries(NODES.map(node => [node.id, node])); + +// Conduits follow "How it fits together": [id, from, to, steps, bend] +export const CONDUITS = [ + ['fleet-incoming', 'fleet', 'incoming', '1', 0.10], + ['incoming-memory', 'incoming', 'memory', '1', -0.16], + ['jev-packets', 'jev', 'packets', '2', 0.0], + ['packets-fleet', 'packets', 'fleet', '2', 0.12], + ['memory-recall', 'memory', 'recall', '2', 0.08], + ['recall-fleet', 'recall', 'fleet', '2', 0.22], + ['memory-dreaming', 'memory', 'dreaming', '3', 0.14], + ['deepdream-memory', 'deepdream', 'memory', '3', 0.1], + ['cache-fleet', 'cache', 'fleet', '4', -0.18], + ['judge-fleet', 'judge', 'fleet', '5', 0.14], + ['judge-jev', 'judge', 'jev', '5', -0.1], + ['memory-log', 'memory', 'log', '6', -0.12], +]; +// JEV's own spokes to each lane light up whenever that lane is part of a step. +const SPOKES = [['incoming', '1'], ['packets', '2'], ['recall', '2'], ['dreaming', '3'], ['cache', '4']]; + +function cubeFaces(cx, cy, e, z = 0) { + const u = [e / 2 * C, e / 2 * S]; + const v = [-e / 2 * C, e / 2 * S]; + const at = (a, b, h) => [cx + a * u[0] + b * v[0], cy - z + a * u[1] + b * v[1] - h]; + const h = e; + const T = { back: at(-1, -1, h), right: at(1, -1, h), front: at(1, 1, h), left: at(-1, 1, h) }; + const B = { back: at(-1, -1, 0), right: at(1, -1, 0), front: at(1, 1, 0), left: at(-1, 1, 0) }; + return { + T, B, + top: [T.back, T.right, T.front, T.left], + left: [T.left, T.front, B.front, B.left], + right: [T.front, T.right, B.right, B.front], + footprint: [B.back, B.right, B.front, B.left], + }; +} + +// Affine map of the unit square onto a face (origin, u-axis end, v-axis end) for greeble panels. +function faceMatrix(o, a, b) { + return `matrix(${r(a[0] - o[0])} ${r(a[1] - o[1])} ${r(b[0] - o[0])} ${r(b[1] - o[1])} ${r(o[0])} ${r(o[1])})`; +} + +function greebles(variant, rows = 5, cols = 5) { + seed = 97 + variant * 131; + const cells = []; + const q = value => Math.round(value * 1000) / 1000; + for (let row = 0; row < rows; row++) { + for (let col = 0; col < cols; col++) { + if (random() < 0.22) continue; + const w = random() < 0.3 ? 2 : 1; + if (col + w > cols) continue; + const x = col / cols + 0.018; + const y = row / rows + 0.018; + const shade = ['#2c3925', '#1e2819', '#34432c', '#151c12'][Math.floor(random() * 4)]; + cells.push(``); + if (random() < 0.34) { + const lx = x + 0.02 + random() * (w / cols - 0.12); + cells.push(``); + } + } + } + return cells.join(''); +} + +function cube(node, { mini = false, held = false } = {}) { + const f = cubeFaces(node.x, node.y, node.e, node.z || 0); + const v = Math.round(node.x + node.y) % 3; + const faces = [ + ['top', f.top, faceMatrix(f.T.left, f.T.back, f.T.front), `g-top-${v % 2}`], + ['left', f.left, faceMatrix(f.T.left, f.T.front, f.B.left), `g-side-${v}`], + ['right', f.right, faceMatrix(f.T.front, f.T.right, f.B.front), `g-side-${(v + 1) % 3}`], + ]; + const body = faces.map(([name, poly, matrix, symbol]) => + `` + + (mini ? '' : ``)).join(''); + const { T, B } = f; + const edges = ``; + const shadow = node.z ? `` + + `` : ''; + const mix = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]; + const trim = mini ? `` : ''; + return `${shadow}${body}${edges}${trim}`; +} + +function fleetCluster(node) { + const d = 38; + const u = [d * C, d * S]; + const v = [-d * C, d * S]; + // Six working studios in two rows, and the owner's studio set apart (it takes no agent lanes). + const spots = [[-1, -0.55], [0, -0.55], [1, -0.55], [-1, 0.55], [0, 0.55], [1, 0.55]]; + const cubes = spots.map(([a, b]) => ({ x: node.x + a * u[0] + b * v[0], y: node.y + a * u[1] + b * v[1], e: 30, z: 0 })) + .sort((p, q) => p.y - q.y) + .map(spot => cube(spot, { mini: true })); + const held = { x: node.x + 2.35 * u[0] + 1.35 * v[0], y: node.y + 2.35 * u[1] + 1.35 * v[1], e: 30, z: 0 }; + return cubes.join('') + `${cube(held, { mini: true, held: true })}`; +} + +function anchor(id) { + const node = byId[id]; + return [node.x, node.y - (node.z || 0) * 0.35]; +} + +function conduitPath(from, to, bend) { + const a = anchor(from); + const b = anchor(to); + const mx = (a[0] + b[0]) / 2; + const my = (a[1] + b[1]) / 2; + const dx = b[0] - a[0]; + const dy = b[1] - a[1]; + const cx = mx - dy * bend; + const cy = my + dx * bend; + return `M${pt(a)} Q${pt([cx, cy])} ${pt(b)}`; +} + +function deck() { + const top = [500, 158]; + const right = [982, 436]; + const bottom = [500, 714]; + const left = [18, 436]; + const plate = [top, right, bottom, left]; + const drop = 16; + const lines = []; + for (let i = 1; i < 12; i++) { + const t = i / 12; + const a = [top[0] + (right[0] - top[0]) * t, top[1] + (right[1] - top[1]) * t]; + const b = [left[0] + (bottom[0] - left[0]) * t, left[1] + (bottom[1] - left[1]) * t]; + const c = [top[0] + (left[0] - top[0]) * t, top[1] + (left[1] - top[1]) * t]; + const d = [right[0] + (bottom[0] - right[0]) * t, right[1] + (bottom[1] - right[1]) * t]; + lines.push(`M${pt(a)}L${pt(b)}M${pt(c)}L${pt(d)}`); + } + const rules = ['REVERSIBLE BY DEFAULT', 'RECEIPTS WITHOUT TEXT', 'FAIL OPEN', 'A BUDGET AND A GRANT PER CALL', 'REVIEWED INSTALLS', 'AN EXACT UNDO']; + const angle = Math.atan2(bottom[1] - left[1], bottom[0] - left[0]) * 180 / Math.PI; + const angle2 = Math.atan2(right[1] - bottom[1], right[0] - bottom[0]) * 180 / Math.PI; + const edgeText = (words, from, deg) => `${words}`; + return ` + + + + + +${edgeText(rules.slice(0, 3).join(' · '), [left[0] + 40, left[1] + 40 * Math.tan(angle * Math.PI / 180) + 2], angle)} +${edgeText(rules.slice(3).join(' · '), [bottom[0] + 34, bottom[1] + 34 * Math.tan(angle2 * Math.PI / 180) + 2], angle2)} +`; +} + +export function renderArt() { + const defs = ` + + + + +${greebles(1)} +${greebles(2)} +${greebles(3)} +${greebles(4)} +${greebles(5)} +`; + const conduits = CONDUITS.map(([id, from, to, steps, bend]) => { + const d = conduitPath(from, to, bend); + return ``; + }).join('\n'); + const spokes = SPOKES.map(([id, steps]) => + ``).join('\n'); + const order = [...NODES].sort((a, b) => (a.y - (a.z || 0) * 0.01) - (b.y - (b.z || 0) * 0.01)); + const cubes = order.map(node => { + const inner = node.cluster ? fleetCluster(node) : node.id === 'log' ? logConsole(node) : cube(node); + return `${node.id === 'memory' ? `` : ''}${inner}`; + }).join('\n'); + return ``; +} + +function logConsole(node) { + const f = cubeFaces(node.x, node.y, node.e, 0); + const slab = cubeFaces(node.x, node.y, node.e * 1.5, 0); + const flat = [slab.footprint[0], slab.footprint[1], slab.footprint[2], slab.footprint[3]].map(([x, y]) => [x, y - 10]); + return `` + + `` + + `` + + [0.25, 0.45, 0.65].map(t => ``).join('') + + `` + + (f ? '' : ''); +} + +/** Hotspot centre and size for each part, as percentages of the art box. */ +export function hotspots() { + return Object.fromEntries(NODES.map(node => { + if (node.cluster) return [node.id, { x: node.x / W * 100, y: (node.y - 30) / H * 100, w: 25, h: 16 }]; + const h = node.e * 2; + const cy = node.y - (node.z || 0) - node.e * 0.5; + return [node.id, { x: node.x / W * 100, y: cy / H * 100, w: Math.max(node.e * 2 * C, 70) / W * 100, h: Math.max(h, 60) / H * 100 }]; + }).map(([id, box]) => [id, Object.fromEntries(Object.entries(box).map(([k, value]) => [k, Math.round(value * 10) / 10]))])); +} + +const START = ''; +const END = ''; +if (import.meta.url === `file://${process.argv[1]}`) { + const site = path.dirname(path.dirname(fileURLToPath(import.meta.url))); + const file = path.join(site, 'index.html'); + const html = await readFile(file, 'utf8'); + const start = html.indexOf(START); + const end = html.indexOf(END); + if (start < 0 || end < start) throw new Error('index.html is missing the system-map:art markers'); + const next = html.slice(0, start) + START + '\n' + renderArt() + '\n' + html.slice(end); + if (process.argv.includes('--hotspots')) console.log(JSON.stringify(hotspots(), null, 2)); + else if (process.argv.includes('--check')) { + if (next !== html) { console.error('System map art is out of date. Run: node site/assets/generate-system-map.mjs'); process.exit(1); } + console.log('System map art is current.'); + } else { + await writeFile(file, next); + console.log(`System map art written (${renderArt().length} bytes).`); + } +} diff --git a/site/assets/stardate/2026-267-recall-budget-jam.html b/site/assets/stardate/2026-267-recall-budget-jam.html new file mode 100644 index 0000000..4809e09 --- /dev/null +++ b/site/assets/stardate/2026-267-recall-budget-jam.html @@ -0,0 +1,66 @@ + + + + + + + + +Stardate 2026.267: The recall budget jam and its fix — BORG Collective + + + + + + + +
+BORGCOLLECTIVE + +
+
+ +
+
+

STARDATE 2026.267

+

The recall budget jam and its fix

+
+
Date
America/Denver
+
Status
Finished
+
Format
Did · Learned · Open · Log
+
+

Recall fell back to its old behaviour on 1,539 of 1,685 live attempts because its budget ledger was full of phantom charges, while real spend was about $0.0014. We traced the cause and fixed the accounting.

+
+
+

What we did

+

Recall is the step where JEV, our small judge model, picks which memories go into an agent's context before it answers. Every JEV lane (one job the judge does) has a daily budget. When the budget says no, the lane falls back to its old behaviour. Nothing breaks, but the agent answers without the judge's help.

+

Each lane keeps a ledger: before each call it books an estimated cost, called a reservation.

+

On 2026-09-24 we found recall falling back far too often. Why was a lane that spends almost nothing always out of money? We looked at 1,685 live recall attempts, the lane's ledger, and the timing of each step inside one recall.

+
+
+

What we learned

+

The budget was full of phantom charges. 1,539 of the 1,685 attempts, about 91%, fell back because the budget said no. The ledger showed $0.4989 charged against a $0.50 limit. Real spend was about $0.0014.

+

The chain:

+
  1. A recall step has 2.0 seconds in total.
  2. About 0.4 s went on two Inbox lookups of the lane's grant (its signed permission), and about 0.4 s on fetching its key from the vault.
  3. The lane kept 1.0 s in reserve for a retry, and the call itself got only about 0.3 s.
  4. Large requests take 0.4 to 0.5 s, so they timed out with an unknown outcome.
  5. An unknown outcome was booked at a worst case that grew with request size and question count, then doubled. With about 86 questions in a large recall, each booking was 16 to 750 times the real bill.
  6. These bookings, and those for refused calls (HTTP 403), never aged out of what was meant to be a daily window. They piled up until the ledger was full.
+

What surprised us: the lane was not short of money at all. Its own bookkeeping blocked it. The same pessimistic accounting sits in the shared client used by other JEV lanes, including the incoming filter and nightly dreaming. Any lane with real traffic and occasional timeouts will jam the same way.

+

The fix is a corrected client for the lane. A booking is capped by the request's size in bytes, old bookings age out of a true rolling window, and a refused request (a 4xx error) costs nothing. The lesson we keep: check a lane's ledger before blaming the provider.

+

A later readout, from our system map on 2026-09-25, shows recall applying on 590 of 591 prompts over six hours. That is a later reading, not proof that this fix alone caused it.

+
+
+

Open questions

+
  • Other lanes shared the same accounting when we found this. Should they move to the corrected client before real traffic jams them?
  • The lookups eat into a 2.0 s step. How much of that time can we win back?
  • The later readout covers six hours. Does it hold over days and under heavy load?
+
+
+

Lab book log

+
  1. Found 1,539 of 1,685 live recall attempts falling back on budget.

  2. timing

    Grant lookups took about 0.4 s, the key fetch about 0.4 s; the call got about 0.3 s.

  3. root cause

    Unknown outcomes were booked at 16 to 750 times the real bill and never aged out.

  4. fix

    Corrected client: capped bookings, rolling-window aging, no charge for refused calls.

  5. Later readout from the system map: recall applies on 590 of 591 prompts over six hours.

+
+
+ +
+ + + diff --git a/site/assets/stardate/2026-268-clm-side-test.html b/site/assets/stardate/2026-268-clm-side-test.html new file mode 100644 index 0000000..7517c4c --- /dev/null +++ b/site/assets/stardate/2026-268-clm-side-test.html @@ -0,0 +1,66 @@ + + + + + + + + +Stardate 2026.268: The CLM side test, and why we stay on JEV — BORG Collective + + + + + + + +
+BORGCOLLECTIVE + +
+
+ +
+
+

STARDATE 2026.268

+

The CLM side test, and why we stay on JEV

+
+
Date
America/Denver
+
Status
Finished
+
Format
Did · Learned · Open · Log
+
+

We tested whether CLM, an 8B model run on one of our own studios, could pick recall context instead of JEV. It could not; it found the needed rows far less often and was fast enough only with a precomputed cache.

+
+
+

What we did

+

Recall picks which memory rows go into an agent's context before it answers, and today JEV, our small judge model, does that job. Could CLM, an 8B model run locally on an Apple Silicon studio, do it instead, at least as well and fast enough?

+

We used a frozen holdout of 600 episodes. An episode is one question with a pool of candidate rows; a holdout is a test set kept apart from all tuning. In 300 episodes the needed evidence was present. CLM's input format and a score cutoff were chosen on a separate practice set, and the rule for switching was written before any scoring. CLM ran zero-shot, as it comes, with no training on our data. Its authors say this kind of use needs fine-tuned heads, so a zero-shot result is not the model's ceiling.

+
+
+

What we learned

+

Quality: no. Routing picks 8 rows from the pool. How often did those 8 hold every row the answer needs, over the 300 evidence-present episodes?

+
RouterFull coverVersus the JEV arm
JEV arm's routing (cheap word matching)230 (76.7%)baseline
Installed current workflow192 (64.0%)−12.7 points
CLM, row text as is81 (27.0%)−49.7 points
CLM, row prefixed with its file name146 (48.7%)−28.0 points
+

Both CLM gaps have uncertainty ranges entirely below zero, the pre-registered signal to stay on JEV.

+

End to end, CLM's own arm declined every question: on the practice set, no score cutoff beat declining everything. Its 30 most confident answers were 26 critical errors and 4 fully supported.

+

What surprised us: one CLM variant beat a baseline by 6.3 points of fully supported answers, yet a control using 8 random rows matched it in all three seeds. The gain came from the framework's word-matching repair pass, which fired in 440 of 600 episodes behind CLM against 197 behind the word-matching router.

+

Speed: only with a cache. On a shared GPU that was 92 to 99% busy, encoding the query and every row took 3.0 to 4.0 s at the median, against a recall budget of about 1.6 s. With each row's embedding (numbers that stand for its meaning) cached ahead of time, it took 123 to 125 ms. Cold start was 16 s to ready, and peak memory was 20.7 GB.

+

Not a port defect. Our local build matched the maintainers' reference outputs, with the largest gap under 0.001 in cosine terms.

+
+
+

Open questions

+
  • The framework has no recording of JEV's own reranking. A shadow run comparing JEV's top picks with a candidate's on live pools would fill that gap.
  • The questions name topic terms from the answering row, which favours word matching. Would real, more semantic queries narrow a 28-point gap?
  • A fine-tuned head of about 19 million parameters might help, in a separate project that never trains on this holdout.
+
+
+

Lab book log

+
  1. The source report records no run dates; this entry was filed on this day.

  2. pre registration

    Switch rule written before any calibration or holdout scoring.

  3. parity

    Local build matched the maintainers' outputs within 0.001 cosine.

  4. dev

    Format and cutoff chosen; no cutoff beat declining everything.

  5. holdout

    600 episodes scored; CLM routing 49.7 and 28.0 points behind.

  6. latency

    60 real pools timed on a shared GPU; 3.0 to 4.0 s median uncached.

  7. control

    Random rows matched the best CLM variant.

  8. decision

    Stay on JEV.

+
+
+ +
+ + + diff --git a/site/assets/stardate/2026-268-deep-dream-day-one.html b/site/assets/stardate/2026-268-deep-dream-day-one.html new file mode 100644 index 0000000..9c6c91c --- /dev/null +++ b/site/assets/stardate/2026-268-deep-dream-day-one.html @@ -0,0 +1,66 @@ + + + + + + + + +Stardate 2026.268: Deep dream, day one — BORG Collective + + + + + + + +
+BORGCOLLECTIVE + +
+
+ +
+
+

STARDATE 2026.268

+

Deep dream, day one

+
+
Date
America/Denver
+
Status
Running
+
Format
Did · Learned · Open · Log
+
+

Our first full clean-up pass over the main studio's memory retired 3,133 of 117,506 live memories by 09:36, all reversibly. The near-duplicate wave paused at the day's JEV budget, and we resumed it the same afternoon.

+
+
+

What we did

+

Deep dream is the on-demand, full-pass version of our nightly memory clean-up. It looks for duplicates and useless notes, such as tool echoes ("Wall time 1.2 seconds") and old snapshots of a machine's state. Our question: how much can one pass retire, with every change undoable?

+

Nothing is deleted. A note is retired with a retirement marker, a flag that hides it from recall without deleting it. One command restores a whole wave.

+

Three independent code reviews came first, and every required fix was applied. A rehearsal on a copy of the memory applied and undid every stage, byte for byte. The live plan, made at 06:22, covered 117,506 live memories in four waves. In every wave except exact duplicates, JEV, our small judge model, must clear each retirement.

+
+
+

What we learned

+

By 09:36, 3,133 memories were retired, 2.7% of the plan.

+
WaveCandidatesRetiredWhy the rest stayed
Machine-state snapshots1393 kept by JEV; 1 not yet 7 days old
Tool echoes49837190 kept by JEV; 3 held back for privacy; 34 pointed at by another marker
Exact duplicates535 extra copies114 protected decisions; the rest refused by the planner's compatibility rules
Near-duplicates9,642 pairs2,7426,010 pairs judged so far; 142 contradictions logged, not merged; 103 held by the chain rule
+

For near-duplicates, JEV must say "same fact" or "supersedes" with at least 90% confidence; then the shorter or older copy is retired. The chain rule keeps any note that another marker points to.

+

The checks held. Recall, sampled 40 times after each wave, never returned a retired copy, and found the kept fact about 90% of the time. Every changed memory changed only in its marker and labels. A live undo test on the snapshot wave restored 9, and all 13 notes matched the before-snapshot byte for byte. Spot checks of merges down to a similarity of 0.92 found only true rewordings.

+

It was cheap. JEV calls since 07:05 cost $0.176, for 6,173 calls. Deep dream was set to stop at $0.23 of a rolling 24-hour budget of $0.25 that it shares with the nightly. The near-duplicate wave stopped at the day's JEV budget. The remaining 3,632 pairs were first set for the next morning; instead we raised the rolling budget to $1.00, with an undo, and resumed them at 16:09.

+

What surprised us: exact duplicates, which sound easy, retired only 11 of 535 extra copies. And on most near-duplicate pairs JEV was at least 90% sure the notes were redundant, but split its answer between "same fact" and "newer replaces older". Neither answer reached 90%, so both copies stayed.

+
+
+

Open questions

+
  • Which copy should we keep when JEV splits its answer? One proposal: the newer copy when it holds everything the older says, otherwise the longer.
  • 385 exact-duplicate groups hold the same text saved as different kinds of note. Nothing merges them yet.
  • The lane re-checks its permission with about 6 Inbox hub calls per pair, which caused 9 safe stops when the hub was slow. Can it check less often, safely?
+
+
+

Lab book log

+
  1. review

    Three independent code reviews, then a rehearsal on a copy; every stage undone byte for byte.

  2. Live plan made over 117,506 live memories.

  3. Cost window opens; $0.176 over 6,173 JEV calls from here.

  4. 3,133 retired, 2.7% of the plan; near-duplicates paused at the day's JEV budget.

  5. Rolling budget raised to $1.00, with an undo; the remaining near-duplicate pairs resumed.

+
+
+ +
+ + + diff --git a/site/assets/stardate/2026-268-grok-hooks-ab.html b/site/assets/stardate/2026-268-grok-hooks-ab.html new file mode 100644 index 0000000..ba7df2e --- /dev/null +++ b/site/assets/stardate/2026-268-grok-hooks-ab.html @@ -0,0 +1,66 @@ + + + + + + + + +Stardate 2026.268: Grok memory hooks, an A/B test — BORG Collective + + + + + + + +
+BORGCOLLECTIVE + +
+
+ +
+
+

STARDATE 2026.268

+

Grok memory hooks, an A/B test

+
+
Date
America/Denver
+
Status
Finished
+
Format
Did · Learned · Open · Log
+
+

In 96 headless Grok runs, a memory-search tool got the fewest knowledge questions wrong and answered about 3× cheaper and 4× faster. Injecting recall into the prompt made Grok worse.

+
+
+

What we did

+

How should Grok reach our shared memory? We compared four setups, called arms. (A hook is a small program that runs at set points in an agent's loop and can add text to its prompt.)

+
  • A: no memory hooks.
  • R: recall injected into the prompt, as Claude gets it on the main studio.
  • M: a read-only memory-search tool.
  • C: recall plus advice hooks from JEV, our small judge model.
+

The 12 tasks were six fleet-knowledge questions whose answers live in memory, and six small coding tasks with tests. Each ran twice per arm: 96 headless runs (scripted, nobody at the keyboard), same settings throughout. Each run had a fresh private home folder, and grading happened elsewhere, so no agent saw the answer key.

+
+
+

What we learned

+

All 48 coding runs passed in every arm; memory neither helped nor hurt. Knowledge questions told a different story (12 runs per arm, 95% ranges in parentheses):

+
ArmWrongMean costMedian time
M, search tool25% (9 to 53%)$0.11924 s
A, no hooks50% (25 to 75%)$0.386106 s
C, recall and advice50% (25 to 75%)$0.390276 s
R, recall injected75% (47 to 91%)$0.373113 s
+
  • The search tool won on cost and speed: about 3× cheaper and about 4× faster, clear against every arm (p ≤ 0.04). On accuracy, only its gap to injected recall is clear (p = 0.04); against no hooks it is suggestive, not proven (p = 0.40).
  • Injected recall made Grok worse. 8 of its 12 knowledge runs hit the 20-turn limit without answering. The recall block sent Grok hunting through the host to check what it said: 216 tool calls on host paths, against 7 for the search tool.
  • Advice hooks gave no clear gain. Turns took about 14 s instead of about 6 s, causing 4 of the 5 timeouts, perhaps partly because each test run used a fresh state folder.
  • Stale memories mislead. Both of M's misses on which machines take no new work came from old machine-state notes treated as current. The search tool returns facts without their dates.
+

What surprised us most: recall meant to help sent Grok checking instead of answering.

+

Caveats. With 12 runs per arm, treat the accuracy order as a strong hint; cost and speed are solid. "No memory" is not knowledge-free: in 10 of 12 no-hook knowledge runs, Grok read the host's own notes.

+

Two lessons for anyone running agents: an agent without a sandbox can read anything its user account can, and a killed run can leave its tool processes running.

+
+
+

Open questions

+
  • Would the search tool, recommended but not yet built, help lane-host agents in production?
  • Would returning each memory with its date and type stop the stale-note misses?
  • Can JEV advice calls get fast enough for Grok lanes? Until then we would keep them off.
+
+
+

Lab book log

+
  1. setup

    Harness built and validated for $3.96: 12 tasks, 4 arms, 2 repeats.

  2. runs

    96 headless Grok runs on one lane host; recorded cost $16.30.

  3. cleanup

    Stopped ten leftover scans that ran up to 55 minutes after killed runs.

  4. grading

    Graded elsewhere; one task regraded by hand, same rule for every arm.

  5. Report written: $20.26 recorded, about $22 to 25 counting five timed-out runs.

+
+
+ +
+ + + diff --git a/site/assets/stardate/2026-268-incoming-filter-on.html b/site/assets/stardate/2026-268-incoming-filter-on.html new file mode 100644 index 0000000..12758b1 --- /dev/null +++ b/site/assets/stardate/2026-268-incoming-filter-on.html @@ -0,0 +1,67 @@ + + + + + + + + +Stardate 2026.268: The incoming filter, from shadow to on — BORG Collective + + + + + + + +
+BORGCOLLECTIVE + +
+
+ +
+
+

STARDATE 2026.268

+

The incoming filter, from shadow to on

+
+
Date
America/Denver
+
Status
Finished
+
Format
Did · Learned · Open · Log
+
+

After 24 hours in shadow mode we read all 116 would-be drops by hand and found no durable facts. We then switched the filter on, and its first real drop was written to an owner-only recovery copy before being dropped.

+
+
+

What we did

+

Agents write facts to our shared memory as they work. Most new rows, 80 to 98%, arrive through one capture path, and some of it is noise: echoes of tool output and scraps of telemetry. We wanted JEV, our small judge model, to stop that noise before it is stored, without losing a real fact.

+

So the filter started in shadow mode: it judges every fact but drops nothing. After an independent review we set the rules for leaving shadow:

+
  • at least 24 hours of shadow after a credential fix, with credential errors near zero;
  • at least 100 judged candidates;
  • every would-be drop read by hand, with zero durable facts among them;
  • drops counted under the live filter's caps;
  • latency measured over at least 20 captures.
+

Once on, the filter may drop at most half of any one capture, and at most 8 facts an hour across the whole service. It may only drop two kinds of noise: telemetry fragments and echoes of commands that ran.

+
+
+

What we learned

+

The shadow day. In 24 hours the filter saw 418 captures and 2,498 candidate facts, and made 1,894 JEV calls. Credential errors were 0.4%.

+

The would-be drops. With both caps applied, it would have dropped 83 facts; the caps spared 33 more. We read all 116 by hand. Every one was an echo of test, CI (automatic build-and-test runs), build or script output, plus one fragment of system time. None was a durable fact.

+

Latency. 95% of captures finished within 45.0 s, against 41.3 s before: 3.7 s more.

+

What surprised us was how uniform the list was: nothing but output echoes and one scrap of system time.

+

Going on. At 13:00 on 2026-09-25 we switched the filter on, and the service restarted in about 4 seconds. At 14:07 came the first real drop, a script error echo. It was written first to an owner-only recovery copy. We verified that the fact was absent from what was stored, and that the receipt links to the copy by hash.

+

Safety nets. If a recovery copy cannot be written and read back, nothing is dropped. Copies are kept for 30 days and purged only by hand, after review. A dropped fact can be restored by storing it again, word for word, in the same scope. The fast rollback is a switch in the service settings and a restart; the full one restores the exact previous files.

+
+
+

Open questions

+
  • Facts that match the capture hook's own drop patterns are kept unjudged, by design, so some echoes still get stored until the deep dream catches them. Should the filter judge those too?
  • The caps spared 33 would-be drops in shadow. Are they tighter than they need to be?
  • Will zero durable facts among the drops hold as the work changes?
+
+
+

Lab book log

+
  1. Credential fix in place; the 24-hour shadow clock counts from here.

  2. Capture path starts in shadow mode: JEV judges, nothing is dropped.

  3. Revised build installed after a third review; recovery copies forced to disk.

  4. shadow

    24 hours: 418 captures, 2,498 candidate facts, 1,894 JEV calls.

  5. review

    All 116 would-be drops read by hand; zero durable facts.

  6. Switched to on; the service restarted in about 4 seconds.

  7. First real drop, a script error echo, saved first to a recovery copy and verified.

+
+
+ +
+ + + diff --git a/site/assets/stardate/2026-268-judge-kit-reviews.html b/site/assets/stardate/2026-268-judge-kit-reviews.html new file mode 100644 index 0000000..67a3b5f --- /dev/null +++ b/site/assets/stardate/2026-268-judge-kit-reviews.html @@ -0,0 +1,65 @@ + + + + + + + + +Stardate 2026.268: The judge kit and its two independent reviews — BORG Collective + + + + + + + +
+BORGCOLLECTIVE + +
+
+ +
+
+

STARDATE 2026.268

+

The judge kit and its two independent reviews

+
+
Date
America/Denver
+
Status
Finished
+
Format
Did · Learned · Open · Log
+
+

We added seven quality items around our agents. Two independent code reviews found real defects, and every high and medium finding is now fixed.

+
+
+

What we did

+

The judge kit is our set of quality checks around the agents. We added seven items, each with a canary (a small automatic check that runs all the time) in our regression harness.

+
ItemWhat it doesFirst result
Outcome gradingHourly, asks whether each recalled memory was used0.8% of 4,451 clearly used
Claim checksHourly, asks whether a "done" claim has evidenceabout 5% of claim turns flagged
Pre-send guardChecks recipients and holds before a client message; only orchestration-tier models may sendtests 20 of 20
Brief checksBlocks a task brief with no acceptance check, artifact or stop condition45% of real briefs lacked acceptance checks
Hook triageMeasures the text our hooks add to promptsabout 18.7 KB per prompt
Drift alarmsEvery 15 minutes, compares each lane with its last 24 hoursin a backtest, caught the 09-24 incidents within the hour
Routing scoreboardHourly, observed success per model, account and machineone model, 90% on one studio and 65% on another
+

Six items had passed their own tests, canaries and live samples, but no second reader had seen the code. Our question: would independent reviewers find what our own tests missed?

+
+
+

What we learned

+

The first review found real defects. A reviewer on another studio read all the code, ran every suite and wrote 12 probe scripts.

+
  • Five high-severity and five medium defects: three in the pre-send guard, two in the drift alarms, and partial grading in the hourly checks.
  • The kind of thing it caught: a lane failing every call never raised an alarm, and turns still in progress were graded on a partial reply and never graded again.
+

All 5 high and 5 medium findings were fixed, and 15 of the 16 low. We also made subagents (helpers started by another agent) draft-only for client messages.

+

The second review, of the fixes, found three more: one high and two medium, in the pre-send guard and the brief check. All are fixed, along with 11 low findings.

+

What surprised us: code that had passed its own tests, canaries and live samples still hid high-severity gaps.

+
+
+

Open questions

+
  • Only 0.8% of injected memories are clearly used. Is recall picking poorly, or is the grader strict?
  • A tested patch for the Inbox check-in saved 4.7 MB a day (38%) in a replay, and it is now live on the main studio. Will it hold up across the fleet?
+
+
+

Lab book log

+
  1. build

    Seven items built, each with a canary.

  2. review one

    First review of six items: 5 high, 5 medium, 16 low.

  3. fixes

    All high and medium fixed, 15 of 16 low; probes rerun clean.

  4. review two

    Second review, of the fixes: one high, two medium, 11 low.

  5. refix

    Second-review findings fixed; three low items accepted as they are; 100 tests pass; four superseded grants revoked.

  6. Report written: all seven items live, regression 12 of 12.

+
+
+ +
+ + + diff --git a/site/assets/stardate/2026-268-marker-chains.html b/site/assets/stardate/2026-268-marker-chains.html new file mode 100644 index 0000000..8c55b30 --- /dev/null +++ b/site/assets/stardate/2026-268-marker-chains.html @@ -0,0 +1,67 @@ + + + + + + + + +Stardate 2026.268: Marker chains, when the kept copy gets retired — BORG Collective + + + + + + + +
+BORGCOLLECTIVE + +
+
+ +
+
+

STARDATE 2026.268

+

Marker chains, when the kept copy gets retired

+
+
Date
America/Denver
+
Status
Running
+
Format
Did · Learned · Open · Log
+
+

We found 527 duplicate markers that had quietly switched off because the copy they pointed to was later retired. The fix is installed, the repair has run, and the first protected nightly run is next.

+
+
+

What we did

+

When our memory holds two copies of the same fact, the nightly dream keeps one and retires the other with a retirement marker, a flag that hides a note from recall without deleting it. Each marker points to its kept copy, the one that stays visible.

+

On 2026-09-25 we asked a narrow question: do those markers keep working over time? A marker hides its duplicate only while its kept copy is live and unmarked. So we ran a census, a read-only count of every marker and the state of its kept copy, on the main studio.

+
+
+

What we learned

+

Markers were quietly switching off. At 13:44 the census found 7,222 marked notes. 527 exact-duplicate markers were inactive, because their kept copy had itself been retired later. Nothing was lost, but each of those duplicates was visible to recall again.

+
  • 503 were exact-on-exact chains, built up since 09-06 at about 26 a night: a later exact-duplicate pass retired an earlier marker's kept copy.
  • 24 came from 4 kept copies that the nightly's near-duplicate step retired.
  • None came from deep dream. Its chain rule, which keeps any note another marker points to, held.
+

The cause. The nightly's exact-duplicate step picked the winner of each group of identical copies by source quality, then by id. A new identical copy with a smaller id won, and the old kept copy was retired. The retention planner, which proposes what to retire, also skips any note that carries a marker, even an inactive one. So no later run ever fixed a chain.

+

What surprised us: nothing reported it. A marker written by any tool can be undone, silently, by a later run that retires its kept copy.

+

The fix. Three changes:

+
  • the planner takes a protected list, so a kept copy is never proposed for retirement and always wins its group;
  • the nightly scans every marker's kept copy before it plans, which took 0.8 s live, and guards its near-duplicate proposals too;
  • restore can target chosen markers only.
+

A repair restores only the chained markers in the nightly's journal, and the next protected nightly marks them again.

+

Where it stands. An independent review returned accept, with 0 blocking findings. The fix is installed with a one-command undo. The repair restored the 527 markers with 0 other changes to the stored notes, and the census then showed 0 inactive markers.

+
+
+

Open questions

+
  • The first protected nightly run, set for 09-26 03:30, will re-mark up to 500 of the restored duplicates. Will it do so cleanly?
  • Any new tool that writes retention markers must protect the kept copies of all existing markers. How do we make sure every future writer does?
  • Dream health should be measured by the count of inactive markers, not just markers applied. What count should raise an alarm?
+
+
+

Lab book log

+
  1. Start of the count: 503 exact-on-exact chains built up from here, about 26 a night.

  2. Census on the main studio: 7,222 marked notes, 527 inactive duplicate markers.

  3. cause

    Traced to the nightly's exact step letting a newer identical copy win its group.

  4. fix

    Planner protects every kept copy; the nightly scans them before each plan.

  5. review

    Independent review: accept, with 0 blocking findings.

  6. install

    Fix installed with a one-command undo.

  7. repair

    527 markers restored with 0 other changes; the census then showed 0 inactive.

  8. State recorded; the first protected nightly is set for 09-26 03:30.

+
+
+ +
+ + + diff --git a/site/assets/stardate/2026-268-memory-reach.html b/site/assets/stardate/2026-268-memory-reach.html new file mode 100644 index 0000000..d10f9d3 --- /dev/null +++ b/site/assets/stardate/2026-268-memory-reach.html @@ -0,0 +1,66 @@ + + + + + + + + +Stardate 2026.268: Memory rarely reaches the agents that need it — BORG Collective + + + + + + + +
+BORGCOLLECTIVE + +
+
+ +
+
+

STARDATE 2026.268

+

Memory rarely reaches the agents that need it

+
+
Date
America/Denver
+
Status
Finished
+
Format
Did · Learned · Open · Log
+
+

On lane hosts, recall delivered something on only 5 to 10% of prompts, and almost nothing for Grok. The facts were in memory, but queries built from the raw prompt missed them.

+
+
+

What we did

+

Our agents share one memory. Recall is the step that hands an agent the useful memories before it answers. Before judging whether memory helps, we asked a plainer question: does the right memory reach the agent at all?

+

At about 01:00 on 2026-09-25 we measured three things:

+
  • Lane hosts, the studios that run our agents' delegated jobs. From the recall hook's log since 09-18 (a hook is a small program that runs at set points in an agent's loop), we counted how many prompts got any memory.
  • The main studio, where Claude gets recall on almost every prompt, reranked by JEV, our small judge model.
  • Retrieval. For six fleet-knowledge questions, did recall carry the answering fact, and could a focused search find it?
+
+
+

What we learned

+

On lane hosts, recall rarely delivered anything. Across all runtimes it delivered something on about 5 to 10% of prompts.

+
Lane hostClaude prompts with recallCodex prompts with recall
one studio63 of 1,20563 of 641
another32 of 401not listed
a third9 of 9020 of 85
a fourth0 of 23not listed
a fifth0 of 15not listed
+

Grok got almost nothing: its recall step before tool calls logged 730 skips on one lane host. The cause is a word-matching and project gate. It keeps a memory only if the prompt shares its words, and drops memories filed under a different project, which is just the name of the agent's working folder.

+

On the main studio, recall arrives but is rarely used. It injects 3 to 5 memories on almost every prompt. Our outcome grader found only about 0.8% clearly used.

+

The facts are there; the queries miss them. Recall on the main studio carried the answering fact for 1 of the 6 knowledge questions, or 2 of 6 with file content added to the query. A focused memory search found the same facts at the top. The fact that one studio takes no new agent work, for example, scored 0.83 to 0.86.

+

What surprised us is why. Prompts often name the key thing only inside a file, and recall at prompt time never sees it. So when memory looks unhelpful, the problem is mostly delivery and query building, not missing knowledge. Our rule now: before judging memory's value in any test, check that recall delivered the relevant fact.

+
+
+

Open questions

+
  • Would a search tool that agents call themselves fix delivery on lane hosts? Our Grok A/B test points that way.
  • Would running recall again after the agent reads its files close the gap?
  • Six questions is a small test. How often does recall carry the answer on a larger set?
+
+
+

Lab book log

+
  1. Start of the lane-host recall log used for this count.

  2. Counted delivery per lane host and runtime: about 5 to 10% of prompts got anything.

  3. grok check

    Grok's recall before tool calls logged 730 skips on one lane host.

  4. main recall

    The main studio's recall injects 3 to 5 memories on almost every prompt; about 0.8% clearly used.

  5. retrieval test

    Recall carried the answer for 1 of 6 questions, or 2 with file content added.

  6. search test

    A focused search found the same facts at the top, scoring 0.83 to 0.86.

+
+
+ +
+ + + diff --git a/site/assets/stardate/index.html b/site/assets/stardate/index.html new file mode 100644 index 0000000..d452b5b --- /dev/null +++ b/site/assets/stardate/index.html @@ -0,0 +1,84 @@ + + + + + + + + +Stardate log — the lab book of the BORG Collective + + + + + + + +
+BORGCOLLECTIVE + +
+
+
+

LAB BOOK / THE COLLECTIVE

+

STARDATE
LOG.

+

Every experiment the collective runs is written up here, the same way every time: what we did, what we learned, what we still don’t know, and the lab book log.

+
+
Reading a stardate
2026.268 is the year, then the day of the year in America/Denver. Day 268 of 2026 is 25 September 2026.
+
Status
Running means the work is still going. Finished means it reached a result. Superseded means a later entry replaced it.
+
Entries
8 so far, newest first. Numbers come from the collective’s own reports; names, machines and accounts are left out.
+
+
+
+

Entries, newest first

+
    +
  1. +

    Stardate 2026.268 25 September 2026

    +
    1. +

      STARDATE 2026.268 Running

      +

      Marker chains, when the kept copy gets retired

      +

      We found 527 duplicate markers that had quietly switched off because the copy they pointed to was later retired. The fix is installed, the repair has run, and the first protected nightly run is next.

      +
    2. +

      STARDATE 2026.268 Finished

      +

      The incoming filter, from shadow to on

      +

      After 24 hours in shadow mode we read all 116 would-be drops by hand and found no durable facts. We then switched the filter on, and its first real drop was written to an owner-only recovery copy before being dropped.

      +
    3. +

      STARDATE 2026.268 Running

      +

      Deep dream, day one

      +

      Our first full clean-up pass over the main studio's memory retired 3,133 of 117,506 live memories by 09:36, all reversibly. The near-duplicate wave paused at the day's JEV budget, and we resumed it the same afternoon.

      +
    4. +

      STARDATE 2026.268 Finished

      +

      Memory rarely reaches the agents that need it

      +

      On lane hosts, recall delivered something on only 5 to 10% of prompts, and almost nothing for Grok. The facts were in memory, but queries built from the raw prompt missed them.

      +
    5. +

      STARDATE 2026.268 Finished

      +

      The CLM side test, and why we stay on JEV

      +

      We tested whether CLM, an 8B model run on one of our own studios, could pick recall context instead of JEV. It could not; it found the needed rows far less often and was fast enough only with a precomputed cache.

      +
    6. +

      STARDATE 2026.268 Finished

      +

      Grok memory hooks, an A/B test

      +

      In 96 headless Grok runs, a memory-search tool got the fewest knowledge questions wrong and answered about 3× cheaper and 4× faster. Injecting recall into the prompt made Grok worse.

      +
    7. +

      STARDATE 2026.268 Finished

      +

      The judge kit and its two independent reviews

      +

      We added seven quality items around our agents. Two independent code reviews found real defects, and every high and medium finding is now fixed.

      +
    +
  2. +
  3. +

    Stardate 2026.267 24 September 2026

    +
    1. +

      STARDATE 2026.267 Finished

      +

      The recall budget jam and its fix

      +

      Recall fell back to its old behaviour on 1,539 of 1,685 live attempts because its budget ledger was full of phantom charges, while real spend was about $0.0014. We traced the cause and fixed the accounting.

      +
    +
  4. +
+
+
+ + + diff --git a/site/assets/stardate/stardate.css b/site/assets/stardate/stardate.css new file mode 100644 index 0000000..308e61e --- /dev/null +++ b/site/assets/stardate/stardate.css @@ -0,0 +1,401 @@ +/* Stardate log pages. Base type, colours, header and footer come from styles.css. */ +.sd-page { + background: + radial-gradient(ellipse 70% 40% at 80% 0%, #13221355, transparent 70%), + var(--bg); +} +.sd-nav { + flex-wrap: wrap; + justify-content: flex-end; + row-gap: 0; +} +.sd-nav [aria-current="page"] { + color: var(--acid); +} +.sd-main { + padding: 56px 0 96px; + outline: none; +} +.sd-hero { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + gap: 24px 80px; + align-items: end; + padding-bottom: 56px; + border-bottom: 1px solid var(--line); +} +.sd-hero .eyebrow { + grid-column: 1 / -1; +} +.sd-hero h1 { + font-size: clamp(44px, 6vw, 84px); + line-height: 1; + letter-spacing: -0.05em; +} +.sd-hero h1 span { + color: var(--acid); +} +.sd-lede { + color: #c1c9ba; + font-size: 17px; + line-height: 1.75; + max-width: 520px; +} +.sd-key { + grid-column: 1 / -1; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 14px; + margin: 12px 0 0; +} +.sd-key div { + border: 1px solid var(--line); + background: #0c110bb3; + padding: 18px 20px; +} +.sd-key dt { + font: 10px var(--mono); + letter-spacing: 0.08em; + color: var(--acid); + text-transform: uppercase; +} +.sd-key dd { + margin: 8px 0 0; + color: var(--muted); + font-size: 14px; +} +.sd-key b { + color: var(--fg); +} +.sd-list { + padding-top: 48px; +} +.sd-list > h2 { + font-size: 28px; + letter-spacing: -0.02em; +} +.sd-days, +.sd-day-entries { + list-style: none; + padding: 0; + margin: 0; +} +.sd-day { + display: grid; + grid-template-columns: 220px minmax(0, 1fr); + gap: 24px; + padding: 30px 0; + border-bottom: 1px solid var(--line); +} +.sd-day-head { + font: 700 13px/1.5 var(--mono); + color: var(--muted); + letter-spacing: 0.04em; +} +.sd-day-head span { + display: block; + font: 700 22px/1.2 var(--font); + color: var(--acid); + letter-spacing: 0; + margin-bottom: 4px; +} +.sd-day-entries { + display: grid; + gap: 12px; +} +.sd-crumbs ol { + list-style: none; + display: flex; + flex-wrap: wrap; + gap: 8px; + padding: 0; + margin: 0; + font: 11px var(--mono); + color: var(--muted); +} +.sd-crumbs li + li::before { + content: "/"; + margin-right: 8px; + color: #56684a; +} +.sd-crumbs a { + text-decoration: underline; + text-underline-offset: 4px; + color: var(--fg); +} +.sd-entry { + max-width: 860px; +} +.sd-entry-head { + padding: 34px 0 34px; + border-bottom: 1px solid var(--line); +} +.sd-stardate { + font: 12px var(--mono); + letter-spacing: 0.12em; + color: var(--muted); +} +.sd-stardate b { + color: var(--acid); + font-size: 15px; +} +.sd-entry h1 { + font-size: clamp(34px, 4.6vw, 58px); + line-height: 1.08; + letter-spacing: -0.04em; + margin-top: 16px; +} +.sd-meta { + display: flex; + flex-wrap: wrap; + gap: 12px 36px; + margin: 26px 0 0; +} +.sd-meta dt { + font: 10px var(--mono); + letter-spacing: 0.08em; + color: var(--muted); + text-transform: uppercase; +} +.sd-meta dd { + margin: 6px 0 0; + font-size: 15px; +} +.sd-tz { + font: 11px var(--mono); + color: var(--muted); + margin-left: 6px; +} +.sd-summary { + margin-top: 24px; + font-size: 19px; + line-height: 1.6; + color: #d6ddcf; +} +.sd-section { + padding: 38px 0 6px; +} +.sd-section h2 { + display: flex; + align-items: baseline; + gap: 14px; + font-size: 27px; + letter-spacing: -0.02em; + margin-bottom: 18px; +} +.sd-num { + font: 11px var(--mono); + color: var(--acid); + letter-spacing: 0.06em; +} +.sd-section p, +.sd-section li { + color: #c3cabb; + font-size: 16px; + line-height: 1.75; +} +.sd-section p + p, +.sd-section p + ul, +.sd-section p + ol, +.sd-section ul + p, +.sd-section ol + p, +.sd-section .sd-table + p, +.sd-section p + .sd-table, +.sd-section ul + .sd-table, +.sd-section .sd-table + ul { + margin-top: 16px; +} +.sd-section ul, +.sd-section ol { + padding-left: 22px; + margin: 0; +} +.sd-section li + li { + margin-top: 8px; +} +.sd-section li ul { + margin-top: 8px; +} +.sd-section strong { + color: var(--fg); +} +.sd-section code { + font: 0.9em var(--mono); + color: #d1e9ba; +} +.sd-table { + overflow-x: auto; + border: 1px solid var(--line); + background: #0b0f0acc; +} +.sd-table table { + width: 100%; + border-collapse: collapse; + font-size: 14px; + min-width: 520px; +} +.sd-table th, +.sd-table td { + text-align: left; + padding: 11px 14px; + border-bottom: 1px solid #1f291c; + vertical-align: top; + color: #c3cabb; + font-weight: 400; +} +.sd-table thead th { + font: 10px var(--mono); + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--acid); +} +.sd-table tbody th { + color: var(--fg); + font-weight: 700; +} +.sd-table td.num { + font-family: var(--mono); + font-size: 13px; + white-space: nowrap; +} +.sd-table tr:last-child > * { + border-bottom: 0; +} +.sd-log { + list-style: none; + padding: 0 !important; + margin: 0; + border-left: 1px solid #3a4a30; +} +.sd-log li { + display: grid; + grid-template-columns: 150px minmax(0, 1fr); + gap: 18px; + padding: 10px 0 10px 20px; + margin: 0 !important; + position: relative; +} +.sd-log li::before { + content: ""; + position: absolute; + left: -4px; + top: 19px; + width: 7px; + height: 7px; + background: var(--acid); +} +.sd-log time, +.sd-phase { + font: 12px/1.9 var(--mono); + color: var(--acid); + white-space: nowrap; +} +.sd-phase { + color: #b3c3a6; + text-transform: uppercase; + letter-spacing: 0.06em; + font-size: 11px; +} +.sd-log p { + font-size: 15px; +} +.sd-pager { + display: grid; + grid-template-columns: 1fr auto 1fr; + gap: 14px; + align-items: stretch; + margin-top: 56px; + padding-top: 28px; + border-top: 1px solid var(--line); + max-width: 860px; +} +.sd-pager a { + border: 1px solid var(--line); + padding: 16px 18px; + font-size: 15px; + font-weight: 700; + line-height: 1.35; + min-height: 44px; +} +.sd-pager a:hover { + border-color: var(--acid); +} +.sd-pager span { + display: block; + font: 400 10px var(--mono); + color: var(--muted); + letter-spacing: 0.08em; + text-transform: uppercase; + margin-bottom: 6px; +} +.sd-pager-all { + grid-column: 2; + align-self: center; + text-align: center; + font-family: var(--mono) !important; + font-size: 12px !important; + font-weight: 400 !important; +} +.sd-pager-older { + grid-column: 1; + grid-row: 1; +} +.sd-pager-newer { + grid-column: 3; + grid-row: 1; + text-align: right; +} +@media (max-width: 1100px) { + .sd-hero { + grid-template-columns: 1fr; + } + .sd-key { + grid-template-columns: 1fr; + } + .sd-day { + grid-template-columns: 1fr; + gap: 16px; + } +} +@media (max-width: 760px) { + .sd-nav a:not(.sd-keep):not(.nav-source) { + display: none; + } + .sd-nav .nav-source { + display: block; + } + .sd-main { + padding: 36px 0 64px; + } + .sd-hero { + padding-bottom: 36px; + } + .sd-lede { + font-size: 15px; + } + .sd-summary { + font-size: 17px; + } + .sd-section h2 { + font-size: 23px; + } + .sd-section p, + .sd-section li { + font-size: 15px; + } + .sd-log li { + grid-template-columns: 1fr; + gap: 2px; + } + .sd-pager { + grid-template-columns: 1fr; + } + .sd-pager a, + .sd-pager-all, + .sd-pager-older, + .sd-pager-newer { + grid-column: 1; + grid-row: auto; + text-align: left; + } +} diff --git a/site/index.html b/site/index.html index f81edb3..eed9db0 100644 --- a/site/index.html +++ b/site/index.html @@ -6,7 +6,7 @@