refactor(sort): source-agnostic SortControl + SortOption model (R13 S1 foundation)

Introduce the one sort control every content surface will share:
- columnSort.ts gains the SortOption vocabulary + pure pickSort/healSort/
  hydrateSort helpers (direction lives in SortState, never in the key).
- SortControl.tsx: the generic dropdown + asc/desc toggle, with an optional
  "no sort" choice (tables) and a trailing adornment slot (the feed's reshuffle).
- ColumnSortControl is now a thin wrapper over SortControl (API unchanged, so
  Channels + ChannelDiscovery are untouched); optionsFromColumns moved to
  lib/sortOptions.ts to keep the component file fast-refresh clean.
- useSortState.ts: the per-account persist/hydrate/heal plumbing the channel
  manager and discovery each hand-rolled, ready to replace both.

Pure logic is fully unit-tested (columnSort.test.ts 10 -> 23). Gate green:
tsc/eslint/prettier clean, vitest 93 pass. Feed/Plex/channels migration follows.
This commit is contained in:
2026-08-13 23:24:59 +02:00
parent f3b95ed543
commit 7d9da80795
6 changed files with 260 additions and 44 deletions
+10
View File
@@ -0,0 +1,10 @@
import { type Column } from "../components/tableHeader";
import { type SortOption } from "./columnSort";
/** The `SortOption[]` a table exposes for sorting — its sortable columns, labelled by header. Bridges
* the `Column<T>` contract to the source-agnostic sort vocabulary the shared `SortControl` consumes. */
export function optionsFromColumns<T>(columns: Column<T>[]): SortOption[] {
return columns
.filter((c) => c.sortable && c.sortValue)
.map((c) => ({ key: c.key, label: c.header }));
}