Skip to content

feat(search): add server-side search with navbar ui across themes#101

Merged
x3ek merged 4 commits into
mainfrom
feat/11-search
Jun 12, 2026
Merged

feat(search): add server-side search with navbar ui across themes#101
x3ek merged 4 commits into
mainfrom
feat/11-search

Conversation

@x3ek

@x3ek x3ek commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Closes #11

What

Keyword search for posts, accessible from the navbar on every page in all three bundled themes.

Backend

  • services/search.py — pure, unit-tested scorer: tokenize (lowercase [a-z0-9]+), exact + prefix match tiers, presence-based weights (title 12/8, tags 10/6, description 5/3, body 2/1), multi-token AND, score → date-desc tie-break.
  • GET /search?q= — top 8 results as JSON {query, results: [{title, url, date, tags, excerpt, draft}]}. Queries under 2 chars return empty 200 (client fires per debounce tick). Registered before the pages catch-all.
  • Draft awareness without leakage — anonymous visitors search published posts; admins also match drafts (flagged). The tokenized index is cached under two audience-separated keys (search:index:published / search:index:all, admin key only read behind is_admin); /search responses are never cached and carry Cache-Control: no-store. The webhook cache-warm pre-builds both variants.

Frontend

  • Shared _search.html partial + search.js (vanilla IIFE, no dependencies) reaching all themes via the default-theme template/static fallbacks; per-theme or per-site overridable.
  • Two modes per the issue spec: button (icon opens dropdown w/ input — default, terminal) and input (inline navbar input — blue-tech). Shared behavior: 200ms debounce, results-as-you-type, Cmd/Ctrl+K, Escape, click-outside, arrow-key + Enter navigation with combobox/listbox ARIA, draft badge.
  • XSS-safe rendering: results are built exclusively with createElement/textContent (frontmatter is author-supplied; autoescape doesn't cover JSON→DOM). Out-of-order responses guarded by AbortController + current-input check.
  • Fixes terminal's .nav-container overflow: hidden, which clipped the dropdown (the sticky-nav risk called out in planning, confirmed and fixed during verification).

Docs

  • theme-creator skill: search component contract (search_mode + include, class hooks, override paths) and the search.js static-fallback note.

Tests

28 new: 20 unit (tests/test_search.py — weighting, prefix vs substring, AND semantics, tie-breaks, limits, draft passthrough, result shape) + 8 integration (tests/test_routes_search.py — JSON shape/route reachability, draft hidden anon vs flagged admin, short/no-match queries, and cache-separation in both request orders). Full suite 323 passed; run-checks.py 4/4.

Verified (dev server + Playwright, user-inspected in browser)

  • All three themes: search flow, results rendering, navigation; terminal restyled per review (blue borders, slight rounding).
  • Default: Cmd+K, light + dark dropdown, click-outside; fixed a .nav-links a style collision on result links.
  • Blue-tech: inline input + frosted dropdown over the sticky nav, Escape close.
  • Terminal: 375px mobile fit; pixel-art logo unaffected by the overflow fix.
  • Drafts live-verified both ways (anonymous excluded; admin via DEV_SKIP_AUTH sees flagged result).

Follow-up

Fuzzy matching (typo tolerance) deliberately out of scope — issue to be filed after merge (stdlib difflib tier, rapidfuzz upgrade path).

🤖 Generated with Claude Code

x3ek and others added 3 commits June 11, 2026 21:31
Pure tokenize/index/score service (exact + prefix tiers, weighted title > tags > description > body, multi-token AND, date tie-break). GET /search?q= returns top 8 results as JSON with Cache-Control: no-store; drafts only match for admins. The built index is cached under two audience-separated keys and pre-warmed by the webhook; responses are never cached to prevent draft leakage. Registered before the pages catch-all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Shared _search.html partial (button and input modes via search_mode set before include) and shared search.js (vanilla IIFE: 200ms debounce, AbortController, textContent-only rendering, Cmd/Ctrl+K, Escape, click-outside, arrow-key navigation), both reaching every theme through the default-theme fallback. Button mode in default and terminal, inline input mode in blue-tech, styled per theme. Removes terminal's .nav-container overflow:hidden which clipped the dropdown.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a server-side post search endpoint to SquishMark and wires a shared navbar search UI into all bundled themes (default, blue-tech, terminal), with tests and theme-creator documentation to support overrides/fallbacks.

Changes:

  • Introduces GET /search?q= backed by a cached, audience-separated (published vs drafts) in-memory search index and a deterministic scorer.
  • Adds a shared _search.html navbar partial plus search.js behavior (debounced fetching, keyboard navigation, dropdown UI) and theme-specific CSS for button/input modes.
  • Adds unit + integration tests validating scoring, JSON shape, draft gating, and cache separation; updates theme-creator docs accordingly.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
themes/terminal/static/css/style.css Removes navbar overflow clipping and adds terminal-theme dropdown search styling.
themes/terminal/base.html Includes shared search partial (button mode) and loads search.js via static fallback path.
themes/default/static/style.css Adds default-theme search component styles (dropdown, results list, states).
themes/default/static/search.js Implements shared client-side behavior for navbar search (debounce, fetch, keyboard nav, safe DOM rendering).
themes/default/base.html Adds search partial in navbar and loads search.js.
themes/default/_search.html Adds shared search partial supporting button vs input mode markup.
themes/blue-tech/static/style.css Adds blue-tech styling for inline (input mode) navbar search and dropdown.
themes/blue-tech/base.html Includes shared search partial (input mode) and loads search.js.
tests/test_search.py Unit tests for tokenization, scoring/weighting, AND semantics, tie-breaks, and result shape.
tests/test_routes_search.py Integration tests for /search JSON behavior, draft gating, and cache separation across request order.
tests/conftest.py Registers the new integration test module for environment pinning/reset behavior.
src/squishmark/services/search.py Adds search indexing + scoring logic and cached index builder (published vs drafts).
src/squishmark/routers/webhooks.py Warms search indexes after webhook-driven cache refresh.
src/squishmark/routers/search.py Adds /search route with admin-aware draft inclusion and Cache-Control: no-store.
src/squishmark/main.py Registers the new search router before the pages catch-all.
.claude/skills/theme-creator/references/template-variables.md Documents search component contract (mode, hooks, override paths, script include).
.claude/skills/theme-creator/references/static-files.md Documents search.js static fallback behavior and override mechanism.

Comment thread src/squishmark/routers/search.py Outdated
Comment thread themes/default/static/search.js
Comment thread themes/default/static/search.js
Addresses Copilot review: the JSON response now echoes the stripped query it actually executed, and result options initialize aria-selected=false with the active option toggled true for screen-reader feedback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@x3ek x3ek merged commit bd39bab into main Jun 12, 2026
5 checks passed
@x3ek x3ek deleted the feat/11-search branch June 12, 2026 02:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Search functionality

2 participants