Add searchable Settings (Spotlight-style)#165
Merged
StudioNirin merged 9 commits intoMay 12, 2026
Conversation
Persistent search bar at the top of /settings/* with live filtering, keyboard navigation (Ctrl/Cmd+K, arrows, Enter, Esc), and a jump-and-flash interaction that scrolls the target form-group into view and pulses an orange ring around it on result click. Indexes 78 settings across all 10 tabs with curated synonyms so intent matches non-obvious labels (e.g. "discord" -> Webhook URL, "cron" -> Schedule, "arr" -> Sonarr/Radarr). The index lives in Python (web/services/settings_search_index.py) and is exposed to templates as JSON via a template global, then consumed client-side by settings_search.js. Every indexed setting has a matching data-setting-id attribute in its template; a cross-check script confirms coverage. CSS uses --plex-* design tokens throughout; light theme overrides the highlight mark for legibility.
web/config.py imported from web.services.settings_search_index, which triggers web/services/__init__.py to load cache_service, which re-imports web.config while it's still mid-load. The result was "cannot import name 'get_time_format' from partially initialized module 'web.config'" at container startup. Move the index module from web/services/ to web/ so the import doesn't trigger the services package init. The index is pure data with no project imports, so it's a better fit outside the services tree anyway.
plex-theme.css applies a 3px box-shadow ring to every input:focus globally. For the settings-search input that sits inside a styled wrapper, this rendered as an inner ring competing with the wrapper's :focus-within outline — a confusing double-outline effect. Override the global rule for this input only.
flashTarget() now polls for the target element for up to 3 seconds instead of relying on a single 200ms timeout, so cross-tab navigation and HTMX-loaded sub-partials (user list, path mapping rows) no longer drop the scroll/highlight when they finish rendering late. Fixes Popular Settings clicks that bounce through a full page nav and were the most exposed to the old race. Replaces the brief one-shot ring flash with a two-phase highlight: a 1000ms gold pulse, then a persistent left-edge accent + soft ring that stays until the user clicks anywhere or presses any key. Lets users actually locate the setting they searched for after looking away.
Replaces the subtle inset-shadow ring with a 2-3px gold outline plus outline-offset (6px) so the marker sits a few pixels outside the form element, giving it room to breathe. Bumps the pulse animation colors and adds a flat persistent state that's clearly readable on dark and light themes. Adds a console.info log in flashTarget so DevTools shows whether the jump fires and finds the target (helps diagnose Popular Settings clicks that report as "not working" on specific tabs).
Adds inline outline/box-shadow styles in applyFlash() so the persistent
marker shows up even when a stale stylesheet is cached. The CSS class
still drives the pulse animation, but the inline styles ensure the
2px gold outline at 6px offset is never blocked by cache mismatches.
Also logs the resolved target + card in applyFlash so DevTools can
confirm the element was found and the .closest('.card') lookup
returned the expected ancestor.
The flashTarget selector [data-setting-id=...] matched the search popup's result row (also carrying data-setting-id for click delegation) before the actual form-group on the page, so the highlight was being applied to an invisible element inside the closed results panel. Excluding .ss-result-row from the lookup points the flash at the real target element.
The hard gold bar on the inner-left of the pinned setting was visually loud and clashed with the soft outer outline + faded fill. Removing the inset box-shadow leaves a clean ring + subtle background tint, which reads more like a search-result highlight than an active state.
The console.info logs were diagnostic scaffolding for chasing the wrong-target selector bug; with the fix landed they just add noise. The PIN_STYLES inline fallback was hedging against stale-CSS caching, which turned out not to be the underlying issue — the CSS class alone is sufficient now that flashTarget points at the real element.
StudioNirin
approved these changes
May 12, 2026
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.
Summary
Adds a persistent search bar at the top of every
/settings/*tab that filters across all 10 tabs at once. Type a query, see matching settings grouped by tab, click one to jump to the right tab and have the setting flash-highlighted in place.web/settings_search_index.py(each withtab,setting_id,label,hint,section,subsection,icon,tone,keywordsso synonyms work). Every indexed setting carries a matchingdata-setting-idon its form-group.web/static/js/settings_search.jsrenders results live as you type, supports keyboard navigation (arrows / Enter / Esc), and on click navigates to/settings/<tab>#setting-<id>.@keyframes ss-flash-pulsegold outline pulse, then a persistent.ss-flash-pinnedoutline that dismisses on the next click or keypress.flashTarget()polls for the target up to 3s so HTMX-loaded partial tabs have time to render. The target selector excludes.ss-result-rowso the flash never lands on the hidden popup row that also carries thedata-setting-id.Test plan
/settings/plexand type a query like "webhook" in the top search bar — confirm results from multiple tabs appear, grouped by tab name./settings/notifications#setting-webhook_urldirectly and confirm the highlight still applies (i.e. the polling fallback works for HTMX-loaded partials).keywordsmatches as expected.#settings-search-inputis suppressed (no double-ring against the wrapper outline).