refactor(feed-view): move the feed view mode into FeedViewProvider

Lift the feed's view-mode state (server-synced: persists on pick, adopted from
prefs on login) out of App into FeedViewProvider. App reads it via useFeedView()
to hand to Feed/ChannelPage; the view adopt lines leave App's big prefs-adopt
effect. Removes another App-owned cross-cutting state ahead of the render flip.
(Platform S6b.3)
This commit is contained in:
2026-07-19 01:00:05 +02:00
parent dd921e3209
commit 36e4419c98
3 changed files with 82 additions and 17 deletions
+7 -14
View File
@@ -26,7 +26,6 @@ import {
} from "./lib/panelLayout";
import { configureNotifications, getNotifSettings, notify, removeByMetaKind, type NotifSettings } from "./lib/notifications";
import { hintsEnabled, setHintsEnabled } from "./lib/hints";
import { parseFeedView, type FeedView } from "./lib/feedView";
import {
getAccountRaw,
LS,
@@ -37,6 +36,7 @@ import {
import type { PrefsController } from "./components/SettingsPanel";
import { useNavigation, useNavigationActions } from "./components/NavigationProvider";
import { useFeedFilters, useFeedFiltersActions } from "./components/FeedFiltersProvider";
import { useFeedView, useFeedViewActions } from "./components/FeedViewProvider";
// Persistent shell — always mounted, so eagerly imported.
import Welcome from "./components/Welcome";
import SetupWizard from "./components/SetupWizard";
@@ -98,9 +98,10 @@ export default function App() {
// handlers that mutate the feed view (onShowInFeed, tag-click, the feed scrollKey).
const filters = useFeedFilters();
const { setFilters } = useFeedFiltersActions();
// The feed's view mode is picked from the feed's own toolbar; like every Settings pref it
// persists the moment you choose it.
const [view, setViewState] = useState<FeedView>(() => parseFeedView(readAccount(LS.feedView, null)));
// The feed's view mode (card/row/tile density) lives in FeedViewProvider — server-synced, shared
// by the feed page and the channel page. App reads it only to hand to <Feed>/<ChannelPage>.
const view = useFeedView();
const { setView } = useFeedViewActions();
// Settings-page prefs: applied live by the effects below, and auto-saved (debounced) on change
// — no draft, no Save button (see the auto-save effect).
const [perf, setPerf] = useState(() => getAccountRaw(PERF_KEY) === "1");
@@ -160,11 +161,6 @@ export default function App() {
writeAccount(LS.navCollapsed, next);
api.savePrefs({ navCollapsed: next }).catch(() => {});
}
function setView(next: FeedView) {
setViewState(next);
writeAccount(LS.feedView, next);
api.savePrefs({ view: next }).catch(() => {});
}
function setFilterCollapsed(next: boolean) {
setFilterCollapsedState(next);
writeAccount(LS.filterCollapsed, next);
@@ -357,12 +353,9 @@ export default function App() {
setHints(adopted.hints);
setNotif(adopted.notifications);
baselineRef.current = adopted;
// Out-of-scope prefs stay instant (edited outside the Settings page).
// Out-of-scope prefs stay instant (edited outside the Settings page). The feed view is adopted
// by FeedViewProvider (its own passive ["me"] observer), not here.
const prefsRec = prefs as Record<string, unknown>;
// parseFeedView also maps the pre-0.45 "grid" / "list" values forward.
const adoptedView = parseFeedView(prefs.view);
setViewState(adoptedView);
writeAccount(LS.feedView, adoptedView);
(["feed", "plex", "playlists"] as PanelId[]).forEach((p) => {
const raw = prefsRec[PREF_KEY[p]];
if (raw) {