feat(app): the module registry drives the render (S6b DoD)

Replace App's page-render ternary and the three side-rail page conditionals with
a data-driven lookup: components/moduleRegistry maps each page to a prop-free
content component (+ optional side rail + shell fadeTop), and App renders
PAGE_CONTENT[page]/PAGE_RAIL[page] gated by moduleDef(page).gate (an unreachable
gated page falls back to the feed, as the old ternary did). Content modules and
the two rails are now prop-free: they read `me` via the new useMe() hook and
their module-local state from the scoped providers; FeedPage/MessagesPage are the
only wrappers (they wire the global feed scope / meId). App.tsx 796->431 lines,
zero `page===X` chrome conditionals — closes the Platform epic DoD. (Platform S6b.3)
This commit is contained in:
2026-07-19 01:25:32 +02:00
parent fd1742e783
commit a42e0a9e3e
11 changed files with 193 additions and 106 deletions
+4 -2
View File
@@ -31,7 +31,7 @@ import {
X,
Youtube,
} from "lucide-react";
import { api, type Me, type Video } from "../lib/api";
import { api, type Video } from "../lib/api";
import { formatDuration } from "../lib/format";
import { notify } from "../lib/notifications";
import { notifyYouTubeActionError } from "../lib/youtubeErrors";
@@ -41,6 +41,7 @@ import UndoToolbar from "./UndoToolbar";
import { PageToolbar } from "./PageShell";
import { useConfirm } from "./ConfirmProvider";
import { usePlaylists, usePlaylistsActions } from "./PlaylistsProvider";
import { useMe } from "../lib/useMe";
type SortKey = "manual" | "title" | "duration" | "channel";
type SortDir = "asc" | "desc";
@@ -179,7 +180,8 @@ function Row({
);
}
export default function Playlists({ me }: { me: Me }) {
export default function Playlists() {
const me = useMe();
const canWrite = me.can_write;
// The selected playlist lives in PlaylistsProvider, shared with the PlaylistsRail (which owns the
// list + its rail controls). This detail pane reacts to the selection.