feat(browse): highlight search matches and disable infinite scroll while searching#111
Merged
Conversation
…ile searching (Closes #24) Wire the FTS5 snippet from /api/search into the browse feed so matched terms render with <mark> highlighting, and suspend the scroll-triggered auto-load while a search query is active. - Preserve the FTS5 snippet through the API client (drop only rank) and add it to BrowseItem - Render highlighted snippets in ContentCard, falling back to the plain preview when there is no snippet - Consolidate the XSS-safe snippet parser into src/lib/snippet.ts, shared by the browse card and the command palette; segments render as React text children so no dangerouslySetInnerHTML ever touches untrusted content (single source of truth for the safety property) - Add an infiniteScroll prop to ContentFeed; browse-page passes !filters.q so scrolling no longer auto-loads mid-search (a manual Load more still paginates) - Style <mark> with the --primary-muted design token - Bump 0.10.0 -> 0.11.0 + README badge Closes #24
…s ring Review feedback on #24: - Suppress the native ::-webkit-search-cancel-button on search inputs so only the toolbar's custom clear button shows (was rendering two X icons in Chrome/Safari). - Switch --color-ring from --primary (blue) to --foreground (cream) so focus indicators are neutral and on-brand, matching the ::selection treatment. Blue stays reserved for primary actions/links/selected-nav. design-tokens.md updated to match.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #24 — Real-time search integration
Most of issue #24's plumbing already existed (debounced 300 ms input,
?q=URL sync,/api/searchrouting, loading/empty states, clear button). This PR closes the two genuinely-unmet acceptance criteria.What changed
1. Matching text highlighted in results
/api/searchalready returns an FTS5snippetwith<mark>…</mark>match markers, but the client was deliberately stripping it.stripSearchOnlynow preservessnippet(still dropsrank), andBrowseItemcarries it.ContentCardrenders the snippet with<mark>highlighting, falling back to the plain content preview when there is none.2. When searching, infinite scroll disabled
infiniteScrollprop onContentFeed;browse-pagepasses!filters.q. TheIntersectionObserversentinel is suspended during an active search (per the spec, "results appear in feed, replacing infinite scroll"), while a manual "Load more" still paginates so results are never cut off. Clearingqre-enables infinite scroll.3. Consolidated the XSS-safe snippet parser (
src/lib/snippet.ts)renderSnippetwere two parallel implementations of the same security-critical transform. Extracted one sharedparseSnippet(token-walk, no regex) used by both — a single source of truth for the safety property.dangerouslySetInnerHTMLanywhere in the path (FTS5'ssnippet()does not HTML-escape its input, so this matters).4. Housekeeping
<mark>styled with the--primary-muteddesign token.0.10.0→0.11.0(user-facing feature) + README badge.Verification
pnpm verifygreen: lint, typecheck, build, 767 tests, knip.snippetpreserved,rankdropped), card snippet render + XSS-safety (a<script>in content renders as inert text), sharedparseSnippetunit tests, and the feed'sinfiniteScrollon→offdisconnect()transition.@oracle review
Two-pass
@oraclereview (triggered by the >200-line size and the XSS security boundary). Verdict: XSS handling sound, infinite-scroll gating correct, no must-fix or should-fix findings — clear to open PR. Both should-fix items from pass 1 (a stalesafeSnippetHtmldoc reference, and the duplicated parser) were resolved and confirmed on pass 2.