diff --git a/docs/superpowers/follow-ups/2026-06-02-blog-search-featured-match.md b/docs/superpowers/follow-ups/2026-06-02-blog-search-featured-match.md deleted file mode 100644 index dc375ed..0000000 --- a/docs/superpowers/follow-ups/2026-06-02-blog-search-featured-match.md +++ /dev/null @@ -1,34 +0,0 @@ -# Follow-up: featured article should respect search match - -**Date filed:** 2026-06-02 -**Source:** Manual test of `feat/blog-search` before merge -**Relates to:** `docs/superpowers/specs/2026-06-01-blog-search-design.md` (§ "What should happen to the featured article when a query is active?") - -## What's wrong - -When a query is typed into the blog search input, the featured article currently hides regardless of whether its content matches the query. In manual testing this felt wrong — if the featured article matches the search term, it should remain visible like any other matching row. - -## What the original design said - -During brainstorming, three options were presented for featured-article behavior under filtering. The user chose "Hide while filtering" with the reasoning "simpler mental model: query active = filter mode." After actually using the feature, the user now wants option 2 from that brainstorm: **treat the featured article as a filterable row** — visible iff it matches the query. - -## What needs to change - -In `src/components/molecules/BlogSearch.astro`, the `applyFilter` function has this branch: - -```ts -} else if (isFeatured) { - // Featured hides whenever there's a query, regardless of match. - visible = false; -} -``` - -Replace it so the featured row goes through the same `tokens.every(t => haystack.includes(t))` check as every other row. Drop the `isFeatured` special case entirely — it can fall through to the general match branch. - -Then sanity-check the empty-state count: `visibleCount` currently skips the featured (`if (visible && !isFeatured) visibleCount += 1`). Once featured participates in the filter, it should count toward the visible total — otherwise a query that *only* matches the featured will trigger the "no entries match" message even though featured is showing. Drop the `!isFeatured` guard there too. - -Visually the featured block is much bigger than a row, so when it's the sole match the layout will look unusual but not broken. If that turns out to feel wrong in practice, the second-order fix would be to render the featured-as-result in the same compact PostRow style as the archive. Don't pre-emptively build that — wait until the simple behavior change is in and see if it's actually needed. - -## Update the spec when you fix this - -Update `docs/superpowers/specs/2026-06-01-blog-search-design.md`'s relevant section so the spec reflects the new behavior. The current text claims "Featured hides while filtering" — that line is now wrong. diff --git a/docs/superpowers/plans/2026-06-01-blog-search.md b/docs/superpowers/plans/2026-06-01-blog-search.md deleted file mode 100644 index 1f0cf6e..0000000 --- a/docs/superpowers/plans/2026-06-01-blog-search.md +++ /dev/null @@ -1,601 +0,0 @@ -# Blog Search Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Add a light, in-page keyword/topic filter to `/blog` that narrows the year-grouped archive as the reader types. - -**Architecture:** Tiny client-side filter implemented as `data-search` attributes on each row + featured block. A small inline vanilla-JS script in a new `BlogSearch.astro` component tokenizes the query, toggles `.is-hidden` on rows whose `data-search` doesn't contain every token, hides empty year heads, and toggles a no-results message. No JSON index, no fetch, no React island, no new dependencies. - -**Tech Stack:** Astro 5 content collections, Astro components (`.astro`), scoped CSS, vanilla JS. - -**Source spec:** `docs/superpowers/specs/2026-06-01-blog-search-design.md` - -**Verification note:** This repo has no automated test harness, and the spec deliberately doesn't add one. Each task ends with a manual verification step using the Astro dev server (`npm run dev` → http://localhost:4321/blog) and/or grep/curl against the rendered HTML. Confirm the verification passes before committing. - ---- - -## File map - -| File | Action | Responsibility | -|---|---|---| -| `src/components/molecules/PostRow.astro` | modify | Accept `searchText` prop, emit `data-search` on root anchor | -| `src/components/molecules/YearHead.astro` | modify | Emit `data-year-head` on root | -| `src/pages/blog/index.astro` | modify | Build `searchText` per post; wire to `PostRow` + featured `
`; render `` and the empty-state `

`; add `.is-hidden` rule | -| `src/components/molecules/BlogSearch.astro` | create | Input, styles, and the filter script (including keyboard shortcuts) | - ---- - -## Task 1: `PostRow` accepts and emits `data-search` - -**Files:** -- Modify: `src/components/molecules/PostRow.astro` - -- [ ] **Step 1: Add the `searchText` prop and emit it on the root anchor** - -Edit `src/components/molecules/PostRow.astro` — change the frontmatter and the `` tag: - -```astro ---- -interface Props { - date: string; - title: string; - href: string; - deck?: string; - tag?: string; - searchText: string; -} - -const { date, title, href, deck, tag, searchText } = Astro.props; ---- - - - {date} - - {title} - {deck && {deck}} - - {tag && {tag}} - -``` - -Leave the ` -``` - -- [ ] **Step 2: Mount the component and add the empty-state element + `.is-hidden` rule in the blog index** - -In `src/pages/blog/index.astro`: - -(a) Inside the `` body, place `` between the closing `` of `.masthead` and the `{featured && (...)}` block. The resulting structure should look like: - -```astro -

- {/* ...unchanged... */} -
- - - - { - featured && ( -
- {/* ...unchanged... */} -
- ) - } -``` - -(b) After the closing `` of `.archive`, add the empty-state paragraph: - -```astro - -``` - -(c) Inside the existing ``): - -```css - .is-hidden { display: none !important; } - - .empty { - font-family: var(--font-mono); - font-size: 11px; - letter-spacing: 0.08em; - color: var(--fg-on-dark-3); - margin: clamp(28px, 5vh, 52px) 0 0; - font-feature-settings: "onum" 1; - } - :global(body:not(.dark)) .empty { color: var(--fg-3); } -``` - -(The `!important` on `.is-hidden` defends against any future row-level `display` rule overriding it.) - -- [ ] **Step 3: Verify the build and visual placement** - -Run: `npm run build 2>&1 | tail -10` -Expected: Build succeeds. - -Run: `grep -c 'class="bsearch"' dist/blog/index.html` -Expected: `1`. - -Run: `grep -c 'data-empty' dist/blog/index.html` -Expected: `2` (the `

` and the inner ``). - -Run: `npm run dev` (background), open http://localhost:4321/blog, confirm the search input renders between the masthead and the featured article and looks at home with the rest of the page. Typing has no effect yet — that's expected. Then stop the dev server. - -- [ ] **Step 4: Commit** - -```bash -git add src/components/molecules/BlogSearch.astro src/pages/blog/index.astro -git commit -m "feat(blog): render search input, empty-state, hidden utility" -``` - ---- - -## Task 5: Add the filter script to `BlogSearch` - -**Files:** -- Modify: `src/components/molecules/BlogSearch.astro` - -- [ ] **Step 1: Append the ` -``` - -A few notes for the engineer: - -- The script runs once at module-evaluation time after the DOM is parsed (Astro hoists ` diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro index 290243e..14329fc 100644 --- a/src/pages/blog/index.astro +++ b/src/pages/blog/index.astro @@ -43,7 +43,7 @@ function buildSearchText(post: (typeof posts)[number]): string { return parts .join(" ") .toLowerCase() - .replace(/[<>#*_`\[\]()]/g, " ") + .replace(/[#*_>`\[\]()]/g, " ") .replace(/\s+/g, " ") .trim(); } @@ -129,7 +129,7 @@ for (const post of posts) {

@@ -265,9 +265,7 @@ for (const post of posts) { .feature .img { order: -1; aspect-ratio: 16 / 9; } } - /* :global so the runtime-toggled class reaches PostRow and YearHead roots, - which carry their own component scope hashes — not this page's. */ - :global(.is-hidden) { display: none !important; } + .is-hidden { display: none !important; } .empty { font-family: var(--font-mono);