From 759b829b97005c4738cf7d95d22c7f14b8a0d799 Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Mon, 15 Jun 2026 16:39:46 -0700 Subject: [PATCH 1/2] feat(docs): dynamic version badge + command-palette search empty state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Hero version badge pulled at build time (latest echo/v5 via Go module proxy, with fallback) instead of hardcoded v5.2 — refreshes via the daily cron. - Replace the empty Pagefind search modal with a command-palette launchpad (Start here / Popular quick links + shortcut footer); shown only while the query is empty, hidden once results appear. Co-Authored-By: Claude Opus 4.8 (1M context) --- site/astro.config.mjs | 1 + site/src/components/HomeHero.astro | 4 +-- site/src/components/Search.astro | 43 ++++++++++++++++++++++++++++++ site/src/data/github.ts | 19 +++++++++++++ site/src/styles/terminal.css | 28 +++++++++++++++++++ 5 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 site/src/components/Search.astro diff --git a/site/astro.config.mjs b/site/astro.config.mjs index 20512b87..b6a54196 100644 --- a/site/astro.config.mjs +++ b/site/astro.config.mjs @@ -29,6 +29,7 @@ export default defineConfig({ components: { Footer: './src/components/Footer.astro', PageTitle: './src/components/PageTitle.astro', + Search: './src/components/Search.astro', }, head: [ // Google Analytics (carried over from the Docusaurus site, anonymized IP). diff --git a/site/src/components/HomeHero.astro b/site/src/components/HomeHero.astro index 4d5dad99..9d3f683e 100644 --- a/site/src/components/HomeHero.astro +++ b/site/src/components/HomeHero.astro @@ -2,12 +2,12 @@ // Living Terminal hero: code window + a terminal pane that types `curl` // and streams Echo's JSON response. Animation is client-side, with a // static final-state fallback for reduced-motion / no-JS. -import { starsLabel } from '../data/github.ts'; +import { starsLabel, echoVersion } from '../data/github.ts'; ---
- Echo v5.2 — now released + Echo {echoVersion} — now released

Build fast Go APIs.
Without the bloat.

A high-performance, minimalist Go web framework — a zero-allocation diff --git a/site/src/components/Search.astro b/site/src/components/Search.astro new file mode 100644 index 00000000..a8f0fde6 --- /dev/null +++ b/site/src/components/Search.astro @@ -0,0 +1,43 @@ +--- +import Default from '@astrojs/starlight/components/Search.astro'; +--- + + + diff --git a/site/src/data/github.ts b/site/src/data/github.ts index e052c8d4..cd6b2f2d 100644 --- a/site/src/data/github.ts +++ b/site/src/data/github.ts @@ -29,3 +29,22 @@ export const stars = await fetchStars(); /** e.g. 32412 -> "32.4k" */ export const starsLabel = stars >= 1000 ? `${(stars / 1000).toFixed(1)}k` : String(stars); + +const FALLBACK_VERSION = 'v5'; + +// Latest released echo/v5, via the Go module proxy — canonical and v5-only +// (avoids GitHub "latest release" occasionally pointing at a v4 patch). +async function fetchLatestVersion(): Promise { + try { + const res = await fetch('https://proxy.golang.org/github.com/labstack/echo/v5/@latest'); + if (!res.ok) throw new Error(`Go proxy responded ${res.status}`); + const data = await res.json(); + return typeof data.Version === 'string' ? data.Version : FALLBACK_VERSION; + } catch (e) { + console.warn(`[github] version fetch failed; using fallback ${FALLBACK_VERSION}:`, e); + return FALLBACK_VERSION; + } +} + +/** e.g. "v5.2.1" */ +export const echoVersion = await fetchLatestVersion(); diff --git a/site/src/styles/terminal.css b/site/src/styles/terminal.css index 55c8d3dd..b68a6e19 100644 --- a/site/src/styles/terminal.css +++ b/site/src/styles/terminal.css @@ -87,6 +87,34 @@ /* search box (chrome) */ site-search button { border-radius: 8px !important; } +/* Empty search modal: a command-palette launchpad (quick links + shortcut + footer) injected by Search.astro, shown only before a query exists. Pagefind + marks the results drawer .pagefind-ui__hidden when empty. */ +.echo-search-empty { display: none; } +.pagefind-ui:has(.pagefind-ui__drawer.pagefind-ui__hidden) .echo-search-empty { display: block; } +.echo-search-empty { margin-top: 16px; } +.ess-group + .ess-group { margin-top: 14px; } +.ess-label { + font-family: var(--sl-font-mono); font-size: 0.66rem; letter-spacing: 0.12em; + text-transform: uppercase; color: var(--sl-color-gray-4); padding: 0 4px 6px; +} +.ess-link { + display: flex; align-items: center; gap: 11px; padding: 9px 12px; border-radius: 9px; + text-decoration: none; color: var(--sl-color-white); font-size: 0.9rem; line-height: 1; +} +.ess-link i { font-size: 1.05rem; color: var(--echo-cyan-text); width: 1.2em; text-align: center; } +.ess-link .ess-sec { margin-left: auto; font-family: var(--sl-font-mono); font-size: 0.7rem; color: var(--sl-color-gray-4); } +.ess-link:hover, .ess-link:focus-visible { background: var(--echo-soft); outline: none; } +.ess-foot { + display: flex; gap: 18px; justify-content: center; flex-wrap: wrap; + margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--sl-color-hairline); + font-family: var(--sl-font-mono); font-size: 0.72rem; color: var(--sl-color-gray-4); +} +.ess-foot kbd { + font-family: var(--sl-font-mono); font-size: 0.72rem; background: var(--sl-color-bg-inline-code); + border: 1px solid var(--sl-color-hairline); border-radius: 5px; padding: 1px 6px; margin-right: 5px; +} + /* code blocks — let Expressive Code round the whole frame as one unit (title bar: rounded top, code body: rounded bottom). Don't force the inner

 corners or it detaches from the title bar into a second box. */

From 5b5fc343b490b9a4e4bb9db78b557148b33ee643 Mon Sep 17 00:00:00 2001
From: Vishal Rana 
Date: Mon, 15 Jun 2026 16:46:31 -0700
Subject: [PATCH 2/2] =?UTF-8?q?fix(docs):=20address=20PR=20review=20?=
 =?UTF-8?q?=E2=80=94=20robust=20version=20guard=20+=20bounded=20observer?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- github.ts: require a 'vX' tag (reject empty/garbage so the badge can't render
  blank), and add a 5s fetch timeout to both build-time fetches so a hung
  connection falls back instead of stalling the build.
- Search.astro: bound the empty-state MutationObserver with a 10s timeout +
  dev-only warning, so it can't watch the whole body forever if Pagefind never
  mounts (dev) or selectors change upstream. Mark decorative icons aria-hidden.

Co-Authored-By: Claude Opus 4.8 (1M context) 
---
 site/src/components/Search.astro | 21 +++++++++++++++------
 site/src/data/github.ts          | 14 +++++++++++---
 2 files changed, 26 insertions(+), 9 deletions(-)

diff --git a/site/src/components/Search.astro b/site/src/components/Search.astro
index a8f0fde6..05a9b895 100644
--- a/site/src/components/Search.astro
+++ b/site/src/components/Search.astro
@@ -11,15 +11,15 @@ import Default from '@astrojs/starlight/components/Search.astro';
     
opennavigateescclose @@ -39,5 +39,14 @@ import Default from '@astrojs/starlight/components/Search.astro'; if (!inject()) { const obs = new MutationObserver(() => { if (inject()) obs.disconnect(); }); obs.observe(document.body, { childList: true, subtree: true }); + // Bound the observer: if Pagefind never mounts (disabled in dev, or the + // Starlight markup changed on upgrade), stop watching so it can't run for + // the page's lifetime. Losing the launchpad is harmless; search still works. + setTimeout(() => { + obs.disconnect(); + if (import.meta.env.DEV && !document.querySelector('.echo-search-empty')) { + console.warn('[search] empty-state panel not injected — .pagefind-ui selectors may have changed'); + } + }, 10000); } diff --git a/site/src/data/github.ts b/site/src/data/github.ts index cd6b2f2d..ab9e961d 100644 --- a/site/src/data/github.ts +++ b/site/src/data/github.ts @@ -15,7 +15,7 @@ async function fetchStars(): Promise { const token = process.env.GITHUB_TOKEN; if (token) headers.Authorization = `Bearer ${token}`; - const res = await fetch(`https://api.github.com/repos/${REPO}`, { headers }); + const res = await fetch(`https://api.github.com/repos/${REPO}`, { headers, signal: AbortSignal.timeout(5000) }); if (!res.ok) throw new Error(`GitHub API responded ${res.status}`); const data = await res.json(); return typeof data.stargazers_count === 'number' ? data.stargazers_count : FALLBACK_STARS; @@ -36,10 +36,18 @@ const FALLBACK_VERSION = 'v5'; // (avoids GitHub "latest release" occasionally pointing at a v4 patch). async function fetchLatestVersion(): Promise { try { - const res = await fetch('https://proxy.golang.org/github.com/labstack/echo/v5/@latest'); + const res = await fetch('https://proxy.golang.org/github.com/labstack/echo/v5/@latest', { + signal: AbortSignal.timeout(5000), + }); if (!res.ok) throw new Error(`Go proxy responded ${res.status}`); const data = await res.json(); - return typeof data.Version === 'string' ? data.Version : FALLBACK_VERSION; + const v = typeof data.Version === 'string' ? data.Version.trim() : ''; + // Require a real "vX..." tag — an empty/garbage value would silently render a blank badge. + if (!/^v\d/.test(v)) { + console.warn(`[github] unexpected version ${JSON.stringify(data.Version)}; using fallback ${FALLBACK_VERSION}`); + return FALLBACK_VERSION; + } + return v; } catch (e) { console.warn(`[github] version fetch failed; using fallback ${FALLBACK_VERSION}:`, e); return FALLBACK_VERSION;