From 3ec7687a69d10a6191a69cc090f79fafcecba179 Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Wed, 30 Sep 2026 14:33:56 -0700 Subject: [PATCH 1/6] ui: use Starlight primitives and refresh Echo theme --- cookbook/hello-world/server.go | 2 + site/astro.config.mjs | 7 - site/performance-baseline.json | 36 ++-- site/src/components/ConfigReference.astro | 5 +- site/src/components/Footer.astro | 5 +- site/src/components/HomeHero.astro | 57 ++---- site/src/components/Search.astro | 70 +++---- site/src/content/docs/index.mdx | 77 +++++--- site/src/styles/terminal.css | 229 ++++++++++------------ 9 files changed, 233 insertions(+), 255 deletions(-) diff --git a/cookbook/hello-world/server.go b/cookbook/hello-world/server.go index d2a8d493..d8c92861 100644 --- a/cookbook/hello-world/server.go +++ b/cookbook/hello-world/server.go @@ -18,9 +18,11 @@ func main() { e.Use(middleware.Recover()) // Route => handler + // docs:start route e.GET("/", func(c *echo.Context) error { return c.String(http.StatusOK, "Hello, World!\n") }) + // docs:end route // docs:end hero // Start server diff --git a/site/astro.config.mjs b/site/astro.config.mjs index 1961100e..19a695d0 100644 --- a/site/astro.config.mjs +++ b/site/astro.config.mjs @@ -88,13 +88,6 @@ export default defineConfig({ href: 'https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&family=Fragment+Mono&display=swap', }, }, - { - tag: 'link', - attrs: { - rel: 'stylesheet', - href: 'https://unpkg.com/@phosphor-icons/web@2.1.1/src/regular/style.css', - }, - }, // Default social card. Starlight already emits og:title/description/url // and twitter:card=summary_large_image, but no image — add a site-wide // default so shares aren't imageless. Absolute URLs are required by diff --git a/site/performance-baseline.json b/site/performance-baseline.json index 422d1a73..c11cfa67 100644 --- a/site/performance-baseline.json +++ b/site/performance-baseline.json @@ -1,65 +1,61 @@ { "/": { "html": { - "raw": 27523, - "gzip": 8601 + "raw": 48300, + "gzip": 11466 }, "firstLoadAssets": { - "raw": 94854, - "gzip": 19546 + "raw": 116830, + "gzip": 24929 }, "externalDomains": [ "fonts.googleapis.com", "github.com", - "unpkg.com", "widget.kapa.ai", "www.googletagmanager.com" ] }, "/middleware/cors/": { "html": { - "raw": 60522, - "gzip": 11390 + "raw": 69902, + "gzip": 14312 }, "firstLoadAssets": { - "raw": 118355, - "gzip": 26052 + "raw": 119554, + "gzip": 26305 }, "externalDomains": [ "fonts.googleapis.com", - "unpkg.com", "widget.kapa.ai", "www.googletagmanager.com" ] }, "/next/": { "html": { - "raw": 27645, - "gzip": 8615 + "raw": 47422, + "gzip": 11306 }, "firstLoadAssets": { - "raw": 94869, - "gzip": 19549 + "raw": 116845, + "gzip": 24932 }, "externalDomains": [ "fonts.googleapis.com", "github.com", - "unpkg.com", "www.googletagmanager.com" ] }, "/next/middleware/cors/": { "html": { - "raw": 60976, - "gzip": 11405 + "raw": 69740, + "gzip": 13971 }, "firstLoadAssets": { - "raw": 118370, - "gzip": 26055 + "raw": 119569, + "gzip": 26308 }, "externalDomains": [ "fonts.googleapis.com", - "unpkg.com", "www.googletagmanager.com" ] } diff --git a/site/src/components/ConfigReference.astro b/site/src/components/ConfigReference.astro index 89f5649b..07a91c4d 100644 --- a/site/src/components/ConfigReference.astro +++ b/site/src/components/ConfigReference.astro @@ -5,6 +5,7 @@ import prometheusManifest from '../generated/prometheus.json'; import telemetryManifest from '../generated/open-telemetry.json'; import externalSources from '../../external-sources.json'; import { formatRevision } from '../data/source.mjs'; +import { Badge } from '@astrojs/starlight/components'; interface Props { name: string; @@ -43,7 +44,7 @@ const sourceUrl = (file: string, line: number) => `${repository}/blob/${manifest {labels.field}{labels.type}{labels.description}{labels.source} {config.fields.map((field) => - {field.name}{field.deprecated && {labels.deprecated}} + {field.name}{field.deprecated && } {field.type} {field.doc || '—'} L{field.line} @@ -76,5 +77,5 @@ const sourceUrl = (file: string, line: number) => `${repository}/blob/${manifest tbody th { font-weight: 500; } code { overflow-wrap: anywhere; white-space: pre-wrap; } .field-description { min-width: 16rem; white-space: pre-line; overflow-wrap: anywhere; } - .deprecated { display: inline-block; margin-inline-start: .5rem; font-size: .75em; color: var(--sl-color-text-accent); } + .deprecated { margin-inline-start: .5rem; } diff --git a/site/src/components/Footer.astro b/site/src/components/Footer.astro index e220dc8a..b6cf43ec 100644 --- a/site/src/components/Footer.astro +++ b/site/src/components/Footer.astro @@ -1,5 +1,6 @@ --- import Default from '@astrojs/starlight/components/Footer.astro'; +import { Icon } from '@astrojs/starlight/components'; const year = new Date().getFullYear(); --- @@ -8,10 +9,10 @@ const year = new Date().getFullYear(); © {year} LabStack LLC. Released under the MIT License. diff --git a/site/src/components/HomeHero.astro b/site/src/components/HomeHero.astro index ddd6514b..21b4852d 100644 --- a/site/src/components/HomeHero.astro +++ b/site/src/components/HomeHero.astro @@ -4,18 +4,16 @@ // static final-state fallback for reduced motion. import { starsLabel } from '../data/github.ts'; import stable from '../generated/echo-source.json'; -import { Code } from 'astro:components'; +import { Code, Icon, LinkButton } from '@astrojs/starlight/components'; import helloWorldSource from '../../../cookbook/hello-world/server.go?raw'; import { sourceSnippet } from '../../plugins/remark-source-code.mjs'; const next = process.env.DOCS_CHANNEL === 'next'; const helloWorldCode = sourceSnippet(helloWorldSource, 'hero', 'cookbook/hello-world/server.go'); -// The code window is aria-hidden, so Shiki's
 must not be a tab stop.
-const notFocusable = { pre(node: { properties: Record }) { delete node.properties.tabindex; } };
 ---
 
-
+
- {next ? 'Echo next — preview documentation' : `Echo ${stable.release} — release documentation`} + {next ? 'Echo next — preview documentation' : `Echo ${stable.release} — release documentation`}

Build fast Go APIs.
Without the bloat.

A high-performance, minimalist Go web framework — a zero-allocation @@ -23,47 +21,22 @@ const notFocusable = { pre(node: { properties: Record }) { dele production services in minutes.

- Get Started - {starsLabel} on GitHub + Get Started + {starsLabel} on GitHub
- +
-
~/echo $ curl localhost:1323
HTTP/1.1 200 OK
Hello, World!
diff --git a/site/src/components/Search.astro b/site/src/components/Search.astro index ad6c8baa..1dd667ed 100644 --- a/site/src/components/Search.astro +++ b/site/src/components/Search.astro @@ -1,20 +1,12 @@ --- import Default from '@astrojs/starlight/components/Search.astro'; import { Icon } from '@astrojs/starlight/components'; +import { labels } from './search-launchpad'; const isNext = process.env.DOCS_CHANNEL === 'next'; -const base = import.meta.env.BASE_URL.replace(/\/$/, ''); const locale = Astro.locals.starlightRoute.locale ?? 'en'; -const docsBase = `${base}${locale === 'en' ? '' : `/${locale}`}`; -const labels = ({ - en: { start: 'Start here', popular: 'Popular', quickstart: 'Quickstart', routing: 'Routing', binding: 'Binding', guide: 'Guide', middleware: 'Middleware', cookbook: 'Cookbook', open: 'open', navigate: 'navigate', close: 'close' }, - es: { start: 'Empieza aquí', popular: 'Popular', quickstart: 'Inicio rápido', routing: 'Rutas', binding: 'Binding', guide: 'Guía', middleware: 'Middleware', cookbook: 'Recetario', open: 'abrir', navigate: 'navegar', close: 'cerrar' }, - ja: { start: 'はじめに', popular: 'よく見るページ', quickstart: 'クイックスタート', routing: 'ルーティング', binding: 'バインディング', guide: 'ガイド', middleware: 'ミドルウェア', cookbook: 'クックブック', open: '開く', navigate: '移動', close: '閉じる' }, - 'pt-br': { start: 'Comece aqui', popular: 'Populares', quickstart: 'Guia de início', routing: 'Roteamento', binding: 'Binding', guide: 'Guia', middleware: 'Middleware', cookbook: 'Receitas', open: 'abrir', navigate: 'navegar', close: 'fechar' }, - 'zh-cn': { start: '从这里开始', popular: '热门内容', quickstart: '快速入门', routing: '路由', binding: '数据绑定', guide: '指南', middleware: '中间件', cookbook: '示例', open: '打开', navigate: '导航', close: '关闭' }, -} as Record>)[locale]; -if (!labels) throw new Error(`Search.astro: add launchpad labels for locale "${locale}"`); +if (!labels[locale]) throw new Error(`Search.astro: add launchpad labels for locale "${locale}"`); --- -
+
{/* The kapa.ai index covers release docs; next pages use their own Pagefind index. */} {!isNext && }
- - diff --git a/site/src/content/docs/index.mdx b/site/src/content/docs/index.mdx index cd6a7f33..0709d0c9 100644 --- a/site/src/content/docs/index.mdx +++ b/site/src/content/docs/index.mdx @@ -18,7 +18,6 @@ import FeatureCard from '../../components/FeatureCard.astro'; import HomeHero from '../../components/HomeHero.astro'; import { starsLabel } from '../../data/github.ts'; import { CardGrid, Code, LinkButton, LinkCard, Steps } from '@astrojs/starlight/components'; -import { Image } from 'astro:assets'; @@ -62,8 +61,8 @@ import { Image } from 'astro:assets';
  • Run

    -

    Start serving on port 1323.

    - +

    Fetch dependencies and start serving on port 1323.

    +
  • @@ -83,19 +82,19 @@ import { Image } from 'astro:assets';
    Sponsors

    Backed by teams who build on Echo.

    - Blacksmith + Blacksmith BlacksmithFaster, drop-in GitHub Actions runners. - Encore + Encore EncoreThe platform for building Go-based cloud backends.
    Become a sponsor
    diff --git a/site/src/styles/terminal.css b/site/src/styles/terminal.css index fbaa2b04..adde0a28 100644 --- a/site/src/styles/terminal.css +++ b/site/src/styles/terminal.css @@ -195,10 +195,6 @@ site-search button { border-radius: 8px !important; } .echo-stats b { display: block; font-family: var(--sl-font); font-weight: 700; font-size: 1.6rem; color: var(--sl-color-white); letter-spacing: -0.02em; } .echo-stats span { font-size: 0.8rem; color: var(--sl-color-gray-4); } .echo-features { margin-top: 28px; } -.echo-features .card { border-color: var(--sl-color-hairline); background: var(--sl-color-bg-sidebar); border-radius: 13px; padding: 20px; } -.echo-features .card .title { font-family: var(--sl-font); font-weight: 600; letter-spacing: normal; font-size: 1.05rem; gap: .7rem; } -.echo-features .card .icon { color: var(--echo-cyan-text); } -.echo-features .card .body { color: var(--sl-color-gray-3); font-size: .9rem; line-height: 1.62; } .echo-features .card-grid, .echo-eco .card-grid { grid-template-columns: minmax(0, 1fr); gap: 14px; } @media (min-width: 40rem) { .echo-features .card-grid, .echo-eco .card-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (min-width: 64rem) { .echo-features .card-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } .echo-eco .card-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } } @@ -360,8 +356,10 @@ starlight-toc a[aria-current='true'] { color: var(--echo-cyan-text); border-inli An inline script sets data-js during parsing; no-JS and reduced-motion visitors still see the static state. */ @media (prefers-reduced-motion: no-preference) { - .hh-term[data-js]:not([data-typing]) :is([data-cmd], [data-out]) { visibility: hidden; } + /* If the script never starts, reveal the static state after 2s instead of staying blank. */ + .hh-term[data-js]:not([data-typing]) :is([data-cmd], [data-out]) { visibility: hidden; animation: hh-reveal 0s 2s forwards; } } +@keyframes hh-reveal { to { visibility: visible; } } .hh-term .pr { color: var(--echo-cyan-text); } .hh-term .dim { color: var(--sl-color-gray-3); } .hh-term .response { color: var(--echo-cyan-text); } From 56baa684e344fdaaa9cea855a7bf0fb22a60a1fe Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Wed, 30 Sep 2026 15:41:36 -0700 Subject: [PATCH 6/6] ui: light-mode text hierarchy, hero button icons, search observer bound - Light theme: darken gray-3 to #42566a so secondary text again sits between gray-2 and caption gray-4 (7.3:1 vs 5.2:1 on the page background); the two were nearly the same color. - Hero buttons: size LinkButton's 1.5rem icons to the button text. - Search: bound the observer to 10s of visible time, so it still stops when Pagefind never mounts but a background tab gets the launchpad once shown. - Launchpad labels share the English key set as a type; drop a fallback the build already rules out and an ignore attribute Pagefind never sees. --- site/src/components/Search.astro | 20 +++++++++++--------- site/src/components/search-launchpad.ts | 12 +++++++----- site/src/styles/terminal.css | 3 ++- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/site/src/components/Search.astro b/site/src/components/Search.astro index 8fb22426..beeed34a 100644 --- a/site/src/components/Search.astro +++ b/site/src/components/Search.astro @@ -39,14 +39,16 @@ if (!labels[locale]) throw new Error(`Search.astro: add launchpad labels for loc if (search && !inject()) { const obs = new MutationObserver(() => { if (inject()) obs.disconnect(); }); obs.observe(search, { childList: true, subtree: true }); - // No time bound: idle callbacks can wait until a background tab is shown, and - // watching only costs nothing while Pagefind is not mounted. - if (import.meta.env.DEV) { - setTimeout(() => { - if (!document.querySelector('.echo-search-empty')) { - console.warn('[search] empty-state panel not injected — .pagefind-ui selectors may have changed'); - } - }, 10000); - } + // Bound the observer to 10s of visible time: Pagefind mounts on idle, which a + // background tab may not reach until it is shown. If it never mounts (disabled + // in dev, or Starlight markup changed), stop watching; search still works. + const bound = () => 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); + if (document.visibilityState === 'visible') bound(); + else document.addEventListener('visibilitychange', bound, { once: true }); } diff --git a/site/src/components/search-launchpad.ts b/site/src/components/search-launchpad.ts index 36ab1def..bd0413ff 100644 --- a/site/src/components/search-launchpad.ts +++ b/site/src/components/search-launchpad.ts @@ -6,11 +6,13 @@ const icons = { random: '', document: '', padlock: '', - "open-book": '', + 'open-book': '', }; -export const labels: Record> = { - en: { start: 'Start here', popular: 'Popular', quickstart: 'Quickstart', routing: 'Routing', binding: 'Binding', guide: 'Guide', middleware: 'Middleware', cookbook: 'Cookbook', open: 'open', navigate: 'navigate', close: 'close' }, +const en = { start: 'Start here', popular: 'Popular', quickstart: 'Quickstart', routing: 'Routing', binding: 'Binding', guide: 'Guide', middleware: 'Middleware', cookbook: 'Cookbook', open: 'open', navigate: 'navigate', close: 'close' }; + +export const labels: Record = { + en, es: { start: 'Empieza aquí', popular: 'Popular', quickstart: 'Inicio rápido', routing: 'Rutas', binding: 'Binding', guide: 'Guía', middleware: 'Middleware', cookbook: 'Recetario', open: 'abrir', navigate: 'navegar', close: 'cerrar' }, ja: { start: 'はじめに', popular: 'よく見るページ', quickstart: 'クイックスタート', routing: 'ルーティング', binding: 'バインディング', guide: 'ガイド', middleware: 'ミドルウェア', cookbook: 'クックブック', open: '開く', navigate: '移動', close: '閉じる' }, 'pt-br': { start: 'Comece aqui', popular: 'Populares', quickstart: 'Guia de início', routing: 'Roteamento', binding: 'Binding', guide: 'Guia', middleware: 'Middleware', cookbook: 'Receitas', open: 'abrir', navigate: 'navegar', close: 'fechar' }, @@ -18,10 +20,10 @@ export const labels: Record> = { }; export function launchpadHTML(locale: string, docsBase: string) { - const l = labels[locale] ?? labels.en; + const l = labels[locale]; const link = (path: string, icon: keyof typeof icons, text: string, section: string) => ` ${text} ${section}`; - return `
    + return `
    ${l.start}
    ${link('/guide/quickstart/', 'rocket', l.quickstart, l.guide)} diff --git a/site/src/styles/terminal.css b/site/src/styles/terminal.css index adde0a28..56872d58 100644 --- a/site/src/styles/terminal.css +++ b/site/src/styles/terminal.css @@ -42,7 +42,7 @@ --sl-color-white: #142432; --sl-color-gray-1: #1d2f3d; --sl-color-gray-2: #344b5e; - --sl-color-gray-3: #526575; + --sl-color-gray-3: #42566a; --sl-color-gray-4: #5a6d7d; --sl-color-gray-5: #d4e0e8; --sl-color-gray-6: #eaf1f5; @@ -343,6 +343,7 @@ starlight-toc a[aria-current='true'] { color: var(--echo-cyan-text); border-inli .hh-btn.pri:hover { box-shadow: 0 10px 26px -8px var(--echo-cyan); } .hh-btn.sec { border: 1px solid var(--sl-color-hairline-light); color: var(--sl-color-white); background: var(--sl-color-bg-sidebar); } .hh-btn.sec:hover { border-color: var(--echo-cyan); } +.hh-btn svg { font-size: 1.2em; } .hh-install { max-width: 26rem; } .hh-install .expressive-code { --ec-codeFontSize: .85rem; } .hh-eyebrow svg { flex-shrink: 0; }