soulsync/webui
Broque Thomas 44dd7f980f Discover: unify Decade + Genre tabbed browsers
Both tabbed-browser sections — Time Machine ("Decade") and Browse by
Genre — re-implemented the same lifecycle by hand: fetch tabs list,
render the tab strip, attach click handlers, fetch content per tab,
render track list with sync + download action buttons + sync-status
block, handle empty/error/loading states. ~314 lines of identical
boilerplate split across two browsers.

Lifted into one shared `createTabbedBrowserSection(config)` helper.
Each browser is now a thin wrapper:

```js
const ctrl = createTabbedBrowserSection({
    id: 'decade-browser',
    tabsContainerEl: '#decade-tabs',
    contentContainerEl: '#decade-content',
    fetchTabs: async () => { ... },
    renderTabButton: (tab, isActive) => `<button>...</button>`,
    fetchTabContent: async (tab) => { ... },
    renderTabContent: (tracks, tab) => `...`,
    onTabContentRendered: (tab, contentEl) => { ... },
    emptyMessage / errorMessage,
});
```

Migrated:

- `loadDecadeBrowserTabs` 85 → 3 lines
- `loadDecadeTracks` 67 → 3 lines
- `loadGenreBrowserTabs` 92 → 3 lines
- `loadGenreTracks` 70 → 3 lines

Helper: ~125 lines + ~100 lines of per-browser config blocks +
~25 lines of shared `_renderTabbedTrackList` (the two browsers had
byte-identical track-row markup so it lifted cleanly).

Public function names preserved — the four migrated functions stay
on the same signature so existing callers (`loadDiscoverPage`,
refresh buttons, inline handlers) don't change.

Side effects preserved — `decadeTracksCache[year]`, `activeDecade`,
`genreTracksCache[name]`, `activeGenre`, `availableGenres` still
mutated at the same lifecycle moments. The decade-specific
`startDecadeSync(decade)` and genre-specific `startGenreSync(name)`
sync-button handlers stay where they are; they're click handlers
attached to rendered content, not part of the tab lifecycle.

What didn't fit (intentionally left alone):

- `_renderCompactTrackRow` (the existing shared track-row helper) is
  NOT used by the tabbed browsers — they had their own template
  with a `track_data_json` fallback chain `_renderCompactTrackRow`
  doesn't do. Unifying these two would change behavior for
  non-tabbed sections, so the tabbed-browser variant lives as
  `_renderTabbedTrackList`. Future cleanup could merge them by
  giving `_renderCompactTrackRow` an opt-in fallback flag.
- `switchDecadeTab` / `switchGenreTab` still know about cache shape
  so they can skip refetch on already-loaded tabs. Keeping that
  in the per-browser switch is fine — it's a click handler, not
  lifecycle.

Net: 8546 → 8578 LOC on `discover.js` (+32). Helper boilerplate
offsets the line count, but the win is single-source-of-truth, not
raw line reduction.

`node --check` clean. 2222/2222 full suite green.
2026-05-08 07:15:37 -07:00
..
static Discover: unify Decade + Genre tabbed browsers 2026-05-08 07:15:37 -07:00
index.html Discover section controller foundation + migrate Recent Releases 2026-05-07 18:14:56 -07:00