Add .dark class on html element with direct element styling as
fallback for when CSS custom properties don't work.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The server-side theme cookie sets data-theme attribute, but CSS was
only using @media (prefers-color-scheme: dark). Need both selectors
so theme works via cookie AND via system preference.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add theme POST handler that sets HttpOnly cookie
- Update preferences page to use <select> dropdown instead of JS buttons
- Theme cookie set on POST /preferences with theme parameter
- Theme read from cookie on all page renders
- No JavaScript required for theme selection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove inline JS that sets data-theme from localStorage
- Use @media (prefers-color-scheme: dark) in CSS for automatic dark mode
- Remove JS-dependent theme toggle from preferences
- Theme now follows system preference automatically
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
data-domain was set to the full result URL (https://en.wikipedia.org/...).
This caused /favicon/ to receive malformed domain strings.
Now extracts u.Hostname() in FromResponse and passes it as Domain
to result_item.html.
Favicons are now cached in Valkey/Redis instead of an in-memory map:
- TTL: 24 hours (up from 1 hour in-memory)
- ETag derived from body SHA256 (no extra storage needed)
- Falls back to in-memory on cache miss when Valkey is unavailable
- GetBytes/SetBytes added to cache package for raw byte storage
In-memory faviconCache map, sync.RWMutex, and time-based expiry
logic removed from handlers.go.
Adds a Kafka-hosted favicon proxy at /favicon/<domain>:
- Fetches favicon.ico from the target domain
- In-memory cache with 1-hour TTL and ETag support (304 Not Modified)
- Max 64KB per favicon to prevent memory abuse
- Privacy: user browser talks to Kafka, not Google/DuckDuckGo
New "Self (Kafka)" option in the favicon service selector.
Defaults to None. No third-party requests when self is chosen.
Before: every HTMX response returned the full results_inner template
(~400 lines of corrections, meta, pagination, back-to-top) even though
only the result list (#urls) changed between searches.
After:
- Corrections and results-meta use hx-swap-oob="true" — they update
in-place in the DOM without duplication, no extra payload
- #urls div carries hx-select="#urls" hx-target="#urls" hx-swap="innerHTML"
— only the result rows are extracted from the response and swapped
- Pagination forms replaced with paginate-btn buttons — JS calls
htmx.ajax() directly with select:#urls so only result rows reload
- Header search form gains hx-get/hx-target/hx-select for partial updates
on subsequent searches
Payload reduction per HTMX swap: ~60-70% (no more nav, meta, pagination,
back-to-top, htmx-indicator in the swap response body)
Add internal/httpclient package as a singleton RoundTripper used by
all outbound engine requests (search, engines, autocomplete, upstream).
Key Transport settings:
- MaxIdleConnsPerHost = 20 (up from Go default of 2)
- MaxIdleConns = 100
- IdleConnTimeout = 90s
- DialContext timeout = 5s
Previously, the default transport limited each host to 2 idle connections,
forcing a new TCP+TLS handshake on every search for each engine. With
12 engines hitting the same upstream hosts in parallel, connections
were constantly recycled. Now warm connections are reused across all
goroutines and requests.
- Add favicon service preference: None (default), Google, DuckDuckGo
- result_item.html: remove hardcoded Google favicon src, defer to JS
- applyFavicon() reads data-domain attr and sets src or display:none
- Privacy-by-default: users must explicitly opt in to any favicon service
- Add favicon selector to both the settings panel and preferences page
Covers the full lifecycle: interface, SearchRequest/SearchResponse,
result building, graceful degradation, factory wiring, planner
registration, testing, and RSS parsing example.
Each engine now has a distinctive color accent applied to its result
card (left border) and engine badge (colored left strip + text).
16 engines mapped to brand-appropriate colors:
Google blue, Bing teal, DDG orange-red, Brave red, Qwant blue,
Wikipedia dark, GitHub purple, Reddit orange-red, YouTube red,
Stack Overflow amber, arXiv crimson, Crossref navy blue.
Pure CSS via data-engine attribute — no JavaScript.
Uses the Stack Exchange API v3 (/search/advanced) to find questions
sorted by relevance. No API key required (300 req/day); optionally
configure via STACKOVERFLOW_KEY env var or [engines.stackoverflow].
Results include score, answer count, view count, and tags in the
snippet. Assigned to the 'it' category, triggered by the IT category
tab or explicit engine selection.
6 tests covering parsing, edge cases, and helpers.
The project uses pure Go HTML templates + CSS. The React frontend
(frontend/), SPA handler (internal/spa/), and prebuilt binary (kafka)
were dead weight.
Also removes the frontend replacement plan/spec docs.
- Match .page-current padding to button padding (0 0.75rem)
- Add box-sizing: border-box to buttons
- Add margin/padding reset to pagination forms
- Simplify page-current flex layout
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Match padding and sizing for active page number and next button
to match inactive page buttons.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Simple preferences page with engine selection
- Light/dark theme toggle with localStorage persistence
- Clean form layout without complex JS dependencies
- Add dark theme CSS variables
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wikipedia returns HTML entities like <span> which were being
double-escaped by Go templates. Now using html.UnescapeString and
template.HTML to render properly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace React SPA with simple Go templates using search-zen-50
visual style. No JavaScript required - pure HTML/CSS with clean
teal accent color scheme, monospace logo, and minimal design.
- Simplified base.html without HTMX or autocomplete JS
- Clean homepage with centered search box
- Results page with sticky header and category tabs
- Simplified CSS matching search-zen-50 aesthetics
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add internal/spa package for embedding React build
- Wire SPA handler in main.go for non-API routes
- Add gitignore entry for internal/spa/dist
- Add implementation plan
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace template-based handlers (h.Index, h.Preferences) with the new spa
handler. API routes (healthz, search, autocompleter, opensearch.xml) are
registered first as exact matches, followed by the SPA catchall handler
for all other routes. Remove unused views and io/fs imports.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Creates internal/spa package that:
- Embeds React build output from cmd/kafka/dist/
- Provides HTTP handler for static file serving
- Falls back to index.html for SPA client-side routing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reverted CSS to the known-working state at 4b0cde9, then re-applied
only the image grid styles. The duplicate .results-layout block is
intentional — it was present in the working version too.
The old 3-column layout block (referencing .left-sidebar/.right-sidebar
classes that don't exist in the HTML) was overriding the correct layout
defined earlier. Removed the stale duplicate.
Three new image search engines:
- bing_images: Bing Images via RSS endpoint
- ddg_images: DuckDuckGo Images via VQD API
- qwant_images: Qwant Images via v3 search API
Frontend:
- Image grid layout with responsive columns
- image_item template with thumbnail, title, and source metadata
- Hover animations and lazy loading
- Grid activates automatically when category=images
Backend:
- category=images routes to image engines via planner
- Image engines registered in factory and engine allowlist
- extractImgSrc helper for parsing thumbnail URLs from HTML
- IsImageSearch flag on PageData for template layout switching