feat(structure): compact archive + tag pages + hero/featured, and fix site search
Deploy / build (push) Failing after 13m52s

Found a real bug while verifying the nav search: /pagefind/pagefind.js is an ES
module that defines no window.Pagefind, but Search.astro loaded it with a classic
<script src> and read window.Pagefind — so every query answered "Search is
unavailable." (confirmed live before the fix). Now a dynamic import; the specifier
must be a variable or Rollup resolves it at build time and the build fails.

Measured before/after in a real browser:

- archive /posts/ + /zh/posts/: 18,220px -> 3,788px (71 rows at 44px, grouped by
  year) with a client-side filter (title/tags/description text, category chips,
  14 tag chips, zero requests). Tested: "nginx" -> 6 rows, chip devops -> 28.
- tag pages for tags with >= 3 posts. Measured 235 distinct tags (an earlier brief
  claimed 74), 156 used exactly once — pages only where a topic exists: 40 tags ->
  80 pages, build 165 -> 245 pages. Pills link only when a page exists.
- homepage + ZH homepage: hero (288px) with the real scale claim, three CTAs, a
  fixed Featured 4 (scale -> automation -> observability -> a delivered result),
  Latest 10 -> 6. ZH hero had linked "关于我" to the English /about/.
- long code blocks collapse above 420px with an Expand button + gradient, copy
  button still copies the whole block (verified on the 1010px block).
- search is visible ("⌕ Search /" pill, "/" plus the existing Ctrl/Cmd+K) and the
  zero-result screen offers topic chips instead of a dead end.

Docs: project-state B3 entry, ops-runbook Pagefind gotcha.
This commit is contained in:
hoelee committed 2026-10-16 21:18:25 +08:00
1 parent 8619e48271
commit f4008d9e8b
15 files changed
+669 -60

No files matched your search

+11 -1
View File
@@ -1,5 +1,8 @@
---
import type { CollectionEntry } from 'astro:content';
import { getCollection } from 'astro:content';
import { postLang } from '../lib/lang';
import { tagCounts, tagLink } from '../lib/tags';
interface Props {
posts: CollectionEntry<'posts'>[];
@@ -24,6 +27,10 @@ const catHref = (cat: string) => `/${isZh ? 'zh/categories' : 'categories'}/${ca
const og = (post: CollectionEntry<'posts'>) => post.data.ogImage || '/og-default.png';
// Tag pills only link when the tag has its own page (>= TAG_MIN_POSTS in this language).
const counts = tagCounts((await getCollection('posts', ({ data }) => !data.draft))
.filter((p) => postLang(p.slug) === lang));
// The row thumbnail is displayed at ~398 CSS px wide, so the list used to pull a
// 1200x630 PNG (~45 KB) per row for a 398px slot. Serve 400w/800w WebP derived from
// the same art instead (5.8 KB / 16.1 KB — see scripts/optimize-images.py).
@@ -64,7 +71,10 @@ const thumb = (post: CollectionEntry<'posts'>, w: 400 | 800) =>
)}
<span class="sep">·</span>
<a class="tag" href={catHref(post.data.category)}>{post.data.category}</a>
{post.data.tags.map((t) => <span class="tag">{t}</span>)}
{post.data.tags.map((t) => {
const href = tagLink(t, counts, lang);
return href ? <a class="tag" href={href}>{t}</a> : <span class="tag">{t}</span>;
})}
</div>
<p>{post.data.description}</p>
</div>