fix(ui): a sticky table header turns the scroller's top fade off itself; DataTable renders one layout

- "A page with a sticky ColumnHead must declare fadeTop: false" lived in
  prose: a registry flag on three pages plus an App special case for the
  Channels card layout, and the next table page would have re-acquired the
  fade-on-the-header defect with every lane green. ColumnHead now registers
  itself with the enclosing PageScroller (ui/stickyTop) while mounted; the
  registry flag, the App branch and the prop plumbing are gone. Measured on
  localdev: the top mask is 0px under Channels' and Audit's headers, 20px on
  Home and the Feed.
- ui/DataTable mounted its table AND its card list at every width, one
  hidden by CSS: double DOM and render cost, duplicate ids, two keyboard
  copies of every row. It now mounts the one the `md` media query calls for
  (lib/useMediaQuery); the keyboard test asserts one layout at each width.
- The Plex module's content width went px → rem with the rest of the page.
This commit is contained in:
2026-09-19 07:51:33 +02:00
parent 8018cd4685
commit 6fda49369f
10 changed files with 225 additions and 110 deletions
+1 -10
View File
@@ -20,7 +20,6 @@ import { usePref } from "./lib/usePref";
import { useVerifyEntry } from "./lib/useVerifyEntry";
import { useNavigation, useNavigationActions } from "./components/navigationContext";
import { useFeedFilters, useFeedFiltersActions } from "./components/feedFiltersContext";
import { useChannelLayout } from "./components/channelsContext";
import { useFeedView, useFeedViewActions } from "./components/feedViewContext";
import { moduleDef, moduleOrder } from "./lib/modules";
import { PAGE_CONTENT, PAGE_RAIL, preloadPages } from "./components/moduleRegistry";
@@ -71,11 +70,6 @@ export default function App() {
// handlers that mutate the feed view (onShowInFeed, tag-click, the feed scrollKey).
const filters = useFeedFilters();
const { setFilters } = useFeedFiltersActions();
// The channel manager turns its top fade OFF for the table (its sticky header sits at the scroller
// top) but wants it ON for the card layout (no sticky header) — so the fade depends on the layout.
// Deliberately the NARROW layout context: subscribing to the manager's whole state here would
// re-render the app root on every keystroke in its search box.
const channelLayout = useChannelLayout();
// 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();
@@ -325,7 +319,7 @@ export default function App() {
const me = meQuery.data!;
const pageGate = moduleDef(page).gate;
const activePage: Page = !pageGate || pageGate(me) ? page : "feed";
const { Component: PageContent, fadeTop, maxWidth } = PAGE_CONTENT[activePage];
const { Component: PageContent, maxWidth } = PAGE_CONTENT[activePage];
const railDef = channelView ? undefined : PAGE_RAIL[activePage];
return (
@@ -392,9 +386,6 @@ export default function App() {
<PageShell
scrollKey={normalScrollKey}
maxWidth={maxWidth}
// Channels: the registry disables the top fade for its sticky-header table, but the card
// layout has no sticky header, so re-enable it there.
fadeTop={activePage === "channels" ? channelLayout === "cards" : (fadeTop ?? true)}
header={<Header me={meQuery.data!} onYtSearch={enterYtSearch} />}
banners={
<>