Skip to content

Add searchable Settings (Spotlight-style)#165

Merged
StudioNirin merged 9 commits into
StudioNirin:mainfrom
Brandon-Haney:pr/searchable-settings
May 12, 2026
Merged

Add searchable Settings (Spotlight-style)#165
StudioNirin merged 9 commits into
StudioNirin:mainfrom
Brandon-Haney:pr/searchable-settings

Conversation

@Brandon-Haney

@Brandon-Haney Brandon-Haney commented May 12, 2026

Copy link
Copy Markdown
Collaborator

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.

  • 78 indexed settings live in web/settings_search_index.py (each with tab, setting_id, label, hint, section, subsection, icon, tone, keywords so synonyms work). Every indexed setting carries a matching data-setting-id on its form-group.
  • Client-side JS in web/static/js/settings_search.js renders results live as you type, supports keyboard navigation (arrows / Enter / Esc), and on click navigates to /settings/<tab>#setting-<id>.
  • The hash handler runs a two-phase highlight: a 1.1s @keyframes ss-flash-pulse gold outline pulse, then a persistent .ss-flash-pinned outline 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-row so the flash never lands on the hidden popup row that also carries the data-setting-id.

Test plan

  • Open /settings/plex and type a query like "webhook" in the top search bar — confirm results from multiple tabs appear, grouped by tab name.
  • Click a result on a different tab and confirm the page navigates to the right tab and the matching setting outlines in gold for ~1s, then keeps a persistent outline until you click or press a key.
  • Reload /settings/notifications#setting-webhook_url directly and confirm the highlight still applies (i.e. the polling fallback works for HTMX-loaded partials).
  • Use arrow keys + Enter in the results dropdown and confirm keyboard nav matches the visible highlight.
  • Search for synonyms (e.g. "alert" should surface notification settings, "path" should surface mapping/library settings) and confirm keywords matches as expected.
  • Confirm the focus ring inside #settings-search-input is suppressed (no double-ring against the wrapper outline).

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 StudioNirin merged commit 0360874 into StudioNirin:main May 12, 2026
2 checks passed
@Brandon-Haney Brandon-Haney deleted the pr/searchable-settings branch May 13, 2026 14:52
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.

2 participants