feat(frontend): the folders are layers — an import only goes down, a module is reached through its index.ts

`local/layers` (eslint.config.js) resolves every import to its file and refuses one that goes up
(lib/ → components/ui/ → features/ → modules/ → shell/) or that reaches into another module's own
files. A module's index.ts is its public face: the stores, mounted at the root and read app-wide,
which features/ may read too; a page is still opened lazily by import(). Its contract test probes
every edge, and three mutants of the rule (off, no face check, a door for everyone) each turn it red.

Six modules got that face; the shared channel UI (about, tags, tag manager) moved to
features/channel, the filter chips and the saved-views context to features/filters, and the prefs
store's controller type moved next to the store instead of importing it from its own page. The
header's per-module controls and the eagerly loaded rails keep eight inline suppressions, each
naming why: they go when the header gets slots and every rail loads lazily.

`chunkGuard` fails the production build when a lazily imported module lands in a boot chunk (the
entry, the shell, what they import) — e.g. a page re-exported from an index.ts, which makes its
import() a silent no-op. Proved on a build with that mutant; today's build has none.
This commit is contained in:
2026-10-22 23:30:27 +02:00
parent 63a0fecc8b
commit d5867bacca
66 changed files with 453 additions and 109 deletions
+3 -3
View File
@@ -7,9 +7,9 @@ import { api, NO_TAG_SENTINEL, type FeedFilters, type Tag, type TagCategory } fr
import { defaultLayout, type PanelLayout } from "../../lib/panelLayout";
import { useDebounced } from "../../lib/useDebounced";
import { useActiveFilters } from "../../lib/useActiveFilters";
import TagManager from "../channels/TagManager";
import TagManager from "../../features/channel/TagManager";
import SavedViewsPopover from "../../features/filters/SavedViewsPopover";
import { useSavedViews } from "./savedViewsContext";
import { useSavedViews } from "../../features/filters/savedViewsContext";
import FilterRail from "../../components/ui/FilterRail";
import { type PanelItem } from "../../components/ui/PanelGroups";
import { type MediaSourceKind } from "../../lib/mediaSource";
@@ -20,7 +20,7 @@ import YearFilter from "./YearFilter";
import Combobox from "../../components/ui/Combobox";
import MatchToggle from "../../components/ui/MatchToggle";
import { useFeedFilters, useFeedFiltersActions } from "./feedFiltersContext";
import { useChannelsActions } from "../channels/channelsContext";
import { useChannelsActions } from "../channels";
import { useTags } from "../../lib/useTags";
import { compareText } from "../../lib/collate";