Two things in this commit. Functional download / matched-download behaviour is untouched — same JS handlers, same routes for the download actions, same album-expand interaction. VISUAL REDESIGN - Glass search-bar card with accent radial wash + focus ring + pill primary search button - Source chip row above the search bar (see below) - Always-visible compact filter pill row (Type / Format / Sort) — pills carry both ``bs-filter-pill`` (new visual) and ``filter-btn`` (legacy class for ``resetFilters`` + ``applyFiltersAndSort`` in wishlist-tools.js to keep working) - Accent-tinted status pill matching the dashboard / auto-sync look - Album result cards: glass card with accent left-edge stripe, 52px brand-tinted cover icon, chevron expand indicator, pill action buttons (Download / Matched Album), accent glow on hover - Track result cards: glass row with accent stripe, 44px icon, pill action buttons (Stream / Download / Matched Download) - Multi-disc separators inside expanded album track lists styled with the accent treatment - Responsive: action button columns stack vertically below 900px New CSS lives in a self-contained ``webui/static/basic-search-v2.css`` sheet linked from index.html. Selectors are scoped to ``#basic-search-section`` for any class that already exists in style.css (``.album-result-card``, ``.album-icon``, ``.track-*``, etc.); the new ``bs-*`` prefixed classes for the search bar / filters / source row / status are unscoped because they only exist in the new markup. ``!important`` is used on the card-level rules to defeat the original unscoped ``.album-result-card`` etc. rules in style.css that would otherwise leak heavyweight padding / box-shadow / 56px icon styles into the new design. Also removed ``overflow: hidden`` from the original ``.album-result-card`` and ``.track-result-card`` rules in style.css — those two classes only render in ``downloads.js`` basic search results (verified via grep, two render sites only), so the removal can't impact any other UI. SOURCE PICKER (hybrid mode) - New ``GET /api/search/sources`` endpoint returns the list of active sources from the orchestrator's chain (or the single active source in single-source mode). - Frontend renders a chip row above the search bar. Click a chip to target that source for the next search; the chip's brand accent fills. - In single-source mode the lone chip is rendered as a dashed- border label so the user always knows what they're searching but can't accidentally try to switch to sources that aren't configured. - ``/api/search`` accepts an optional ``source`` body param. When set, ``core/search/basic.py:run_basic_search`` resolves the client directly via ``orchestrator.client(source)`` and calls its ``.search()`` instead of going through the hybrid chain. - Backwards compatible: omitting ``source`` falls through to the original ``orchestrator.search()`` call exactly as before. Unknown source names also fall back to the default — typo protection. TESTS (5 new + 6 pre-existing = 11 total in test_search_basic.py) - source param routes to specific client, NOT orchestrator chain - no source param preserves original orchestrator-default behaviour - unknown source name falls back to orchestrator default - ``run_basic_soulseek_search`` backwards-compat alias preserved - source-targeted path serialises albums + tracks correctly 101 search-suite tests pass. |
||
|---|---|---|
| .. | ||
| docs/migration | ||
| src | ||
| static | ||
| tests | ||
| .gitignore | ||
| .oxfmtrc.json | ||
| .oxlintrc.json | ||
| index.html | ||
| package-lock.json | ||
| package.json | ||
| playwright.config.ts | ||
| README.md | ||
| tsconfig.json | ||
| vite.config.ts | ||
| vitest.config.ts | ||
| vitest.setup.ts | ||
WebUI Hybrid Rendering
SoulSync's web UI is in a transition phase:
- most pages still render through the legacy vanilla JS shell
/issuesis rendered by the new React app- a small shell bridge keeps both runtimes aware of the active page, profile context, and navigation state
How It Fits Together
flowchart LR
Browser["Browser parses /webui/index.html"]
Legacy["Legacy shell scripts\n(core.js -> ... -> init.js)"]
Bridge["shell-bridge.js\nwindow.SoulSyncWebShellBridge"]
React["Vite React app\nsrc/app/main.tsx"]
Router["TanStack Router\nwindow.SoulSyncWebRouter"]
Browser --> Legacy
Browser --> React
Legacy --> Bridge
React --> Router
Router --> Bridge
Bridge --> Legacy
Runtime Roles
-
webui/static/init.js- boots the legacy shell
- selects the active profile
- handles the legacy page loading flow
-
webui/static/shell-bridge.js- owns the browser-side bridge object
- exposes
window.SoulSyncWebShellBridge - owns the shared page chrome and route handoff helpers
-
webui/src/app/main.tsx- mounts the React app
- binds
window.SoulSyncWebRouter
-
webui/src/platform/shell/route-controllers.tsx- listens for bridge readiness
- keeps React pages aligned with the shell
Load Order
The current order in index.html matters:
- legacy shell scripts load first
init.jssets up the shell runtimeshell-bridge.jspublishes the bridge and shared chrome helpers after the shell state exists- the Vite React app is injected through
{{ vite_assets('body') }}and boots as a module after parsing
That order avoids load-time references to missing globals and keeps the React side able to react to bridge readiness events. The React entry can start fetching early, but the shell bridge and legacy globals are already available by the time the React runtime starts acting on them.
Notes
- The bridge is intentionally small and browser-only.
- This is the start of the migration, not a full replacement of the legacy shell.
- When adding another React page, check whether it needs:
- a route entry in
webui/src/platform/shell/route-manifest.ts - bridge typings in
webui/src/platform/shell/globals.d.ts - a legacy fallback path in
webui/static/init.js - bridge glue or handoff logic in
webui/static/shell-bridge.js
- a route entry in
Folder Layout
The React webui uses a small set of predictable folders so route slices stay easy to extend, test, and understand.
webui/src/
app/ React bootstrap, router, query client, shared API client
components/ Shared UI primitives
platform/ Shell bridge and browser/platform integration
routes/ Route-local code and TanStack Router pages
test/ Shared test utilities and setup helpers
Migration planning docs live under webui/docs/migration/.
- keep the high-level route backlog there
- add one route-specific sketch per migration task
- keep migration notes close to the WebUI code rather than the repo root
Route Slices
- Keep route-specific code inside
webui/src/routes/<route>/. - Put the routing entry in
route.tsx. - Put route-local UI in a
-ui/folder. - Prefix non-routing files with
-so TanStack Router ignores them. - Keep the route slice small and cohesive.
- Prefer a few files with clear responsibilities over many tiny files with overlapping names.
Example:
webui/src/routes/issues/
route.tsx
-issues.types.ts
-issues.api.ts
-issues.helpers.ts
-issues.api.test.ts
-issues.helpers.test.ts
-ui/
issues-page.tsx
issue-detail-modal.tsx
issue-domain-host.tsx
The initial issues slice is the model to follow:
-issues.api.tsholds request code and query options-issues.helpers.tsholds pure normalization and formatting-issues.types.tsholds shared types-ui/holds the page, modal, and legacy handoff UI
Shared Code
- Put reusable UI in
webui/src/components/. - Put shell integration in
webui/src/platform/. - Put bootstrap and app-wide wiring in
webui/src/app/. - Move code up a level only when it is genuinely shared.
- Avoid creating new conventions that overlap with existing ones.
Testing Choices
We have a lot of testing tools available, but we do not need all of them for every feature.
- Use plain unit tests for pure functions and small transforms.
- Use React component or route tests when the behavior lives in the UI or router.
- Use MSW-backed tests when request shape, response handling, or error handling matters.
- Use Playwright when the behavior is best proven end-to-end with the server and browser together.
- Prefer the smallest test setup that still proves the thing that can regress.
Development
The repo root now owns the full local-dev instructions. Start there for the portable launcher and backend/frontend setup:
- README.md for the end-to-end dev flow
npm run checkandnpm run fixfor React-side linting and formatting