From 82551d7e5c02328271846da5aa05beddf5d37b56 Mon Sep 17 00:00:00 2001 From: dmarzzz <22778355+dmarzzz@users.noreply.github.com> Date: Tue, 25 Aug 2026 20:58:51 -0400 Subject: [PATCH] fix(site): support direct-file Grove preview --- docs/post/grove/file-preview.js | 38 +++++++++++++++++++++++++++++++++ docs/post/grove/grove.css | 4 ++-- docs/post/grove/index.html | 19 +++++++++-------- docs/post/site.css | 2 +- test/site.selftest.mjs | 5 ++++- 5 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 docs/post/grove/file-preview.js diff --git a/docs/post/grove/file-preview.js b/docs/post/grove/file-preview.js new file mode 100644 index 0000000..6cece87 --- /dev/null +++ b/docs/post/grove/file-preview.js @@ -0,0 +1,38 @@ +/* global document, window */ + +if (window.location.protocol === "file:") { + const setText = (selector, value) => { + const element = document.querySelector(selector); + if (element) element.textContent = value; + }; + + const showLocalPreview = () => { + document.body.classList.remove("is-checking"); + document.body.classList.add("is-local-preview"); + + setText("[data-view-state]", "Local preview · open the hosted Grove for live stats"); + setText("[data-snapshot-state]", "Local preview"); + setText("[data-network]", "Sepolia"); + setText("[data-view-time]", "Not connected"); + setText("[data-snapshot-cadence]", "15 min"); + setText("[data-node-count]", "—"); + setText("[data-view-age]", "Not connected"); + setText("[data-node-hours]", "—"); + setText("[data-history-low]", "—"); + setText("[data-history-high]", "—"); + setText("[data-history-samples]", "0"); + setText("[data-history-coverage]", "0%"); + + const chart = document.querySelector("[data-history-chart]"); + chart?.setAttribute( + "aria-label", + "Local file preview. Open the hosted Grove for live signed count history.", + ); + }; + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", showLocalPreview, { once: true }); + } else { + showLocalPreview(); + } +} diff --git a/docs/post/grove/grove.css b/docs/post/grove/grove.css index 0345acc..256aff4 100644 --- a/docs/post/grove/grove.css +++ b/docs/post/grove/grove.css @@ -6,7 +6,7 @@ background: radial-gradient(circle at 50% 16%, rgba(101, 125, 95, 0.14), transparent 31rem), linear-gradient(180deg, rgba(7, 12, 9, 0.86), rgba(7, 12, 9, 0.98)), - url("/fig/shade-tree-banner.webp") center / cover no-repeat; + url("../fig/shade-tree-banner.webp") center / cover no-repeat; } .network-hero { @@ -761,7 +761,7 @@ } @media (max-width: 360px) { - .network-page .nav-links a[href="/research/"] { + .network-page .nav-links a[href="../research/index.html"] { display: none; } diff --git a/docs/post/grove/index.html b/docs/post/grove/index.html index c311da5..4af0230 100644 --- a/docs/post/grove/index.html +++ b/docs/post/grove/index.html @@ -13,10 +13,11 @@ - - - - + + + + + Shade Tree Grove Network Stats @@ -24,13 +25,13 @@
@@ -134,8 +135,8 @@

24 hours of shade

diff --git a/docs/post/site.css b/docs/post/site.css index 8aec1a5..92c3263 100644 --- a/docs/post/site.css +++ b/docs/post/site.css @@ -51,7 +51,7 @@ body:not(.home-page)::before { content: ""; background: linear-gradient(180deg, rgba(7, 16, 12, 0.8), rgba(7, 16, 12, 0.97)), - url("/fig/shade-tree-banner.webp") center / cover no-repeat; + url("./fig/shade-tree-banner.webp") center / cover no-repeat; filter: saturate(0.72) contrast(1.05); transform: scale(1.02); } diff --git a/test/site.selftest.mjs b/test/site.selftest.mjs index 4bf8796..b3dac94 100644 --- a/test/site.selftest.mjs +++ b/test/site.selftest.mjs @@ -65,6 +65,7 @@ const labCss = read("lab/lab.css"); const labScript = read("lab/lab.js"); const groveCss = read("grove/grove.css"); const groveLoader = read("grove/network.js"); +const groveFilePreview = read("grove/file-preview.js"); const groveScene = read("grove/scene.js"); const groveFreshness = read("grove/freshness.js"); const groveHistory = read("grove/history.js"); @@ -97,6 +98,7 @@ check("Protocol Lab keeps sensitive fields out of its public card", /IP, target, check("Protocol Lab route is responsive and reduced-motion safe", /@media \(max-width: 650px\)[\s\S]*?\.route-stages\s*\{[\s\S]*?grid-template-columns:\s*1fr/.test(labCss) && /@media \(prefers-reduced-motion: reduce\)/.test(labCss) && /matchMedia\("\(prefers-reduced-motion: reduce\)"\)/.test(labScript)); check("Protocol Lab has replay, local-run, and post-success share actions", /Walk this route/.test(labPage) && /data-share>Share this route/.test(labPage) && /data-copy-link>Copy route link/.test(labPage) && /href="\.\.\/agent\/index\.html">Run locally/.test(labPage) && /result\.hidden = true/.test(labScript) && /result\.hidden = false/.test(labScript)); check("Protocol Lab opens as a styled app from direct-file and clean hosted URLs", /href="\.\/lab\/index\.html">Lab/.test(landing) && /href="\.\.\/site\.css"/.test(labPage) && /href="\.\.\/lab\/lab\.css"/.test(labPage) && /src="\.\.\/lab\/lab\.js"/.test(labPage) && /href="\.\.\/index\.html"/.test(labPage) && !/(?:href|src)="\/(?:site|favicon|lab|agent|grove|research)/.test(labPage) && /new URL\(canonicalLabUrl\)/.test(labScript) && /history\.replaceState\(null, "", `\?scenario=/.test(labScript)); +check("Grove opens as a styled, honest preview from direct-file and a live app when hosted", /href="\.\.\/site\.css"/.test(grovePage) && /href="\.\/grove\.css"/.test(grovePage) && /src="\.\/network\.js"/.test(grovePage) && /src="\.\/file-preview\.js"/.test(grovePage) && /href="\.\.\/index\.html"/.test(grovePage) && /href="\.\.\/research\/index\.html"/.test(grovePage) && !/(?:href|src)="\/(?:site|favicon|fig|grove|research)/.test(grovePage) && /window\.location\.protocol === "file:"/.test(groveFilePreview) && /Local preview · open the hosted Grove for live stats/.test(groveFilePreview) && /data-node-count\]", "—"/.test(groveFilePreview)); check("Protocol Lab has a dedicated static social image", /og:image" content="https:\/\/shade-tree-node\.vercel\.app\/fig\/shade-tree-lab-og\.png"/.test(labPage)); check("landing has one H1 and one decorative canvas", (landing.match(/]+aria-hidden="true"/g) || []).length === 1); check("Grove has one H1, one focused detail section, and one live status", (grovePage.match(/