`npm run knip` now runs a second, --production pass, which asks whether the code that ships uses an export, not just whether anything does. Of the 47 exports only tests reached: - 34 are exported for their test and say so with @internal, naming the test. - 11 were module-private names with a stray `export`. Two of them were not used at all and are deleted. - The unused alreadyReported re-export is gone. Test kits, the e2e suite and the two build-tooling modules are excluded from the production project, each named in knip.jsonc. noUncheckedIndexedAccess is now on for the node and e2e tsconfigs too (0 errors), matching the app project. tsconfig.node.json listed a tailwind.config.js that has not existed since Tailwind 4. That entry is now playwright.config.ts, which the typed lint finds through this project.
E2E characterization (golden-master) net
Playwright tests that lock the app's five core flows — feed (scroll + filter), channel (open → back), player (open/close), settings (dirty-save + guard), and page switching — so the Platform refactor can prove it changed nothing. They assert current, correct behavior; they deliberately do not encode the known Platform bugs (those get their own specs when the sprints that fix them land).
Run
siftlode e2e # against the baked localdev :8080 stack (authoritative)
siftlode e2e-ui # same, Playwright UI mode
siftlode e2e-seed # (re)seed the test account only, no tests
The localdev stack must be up (siftlode dev). global.setup.ts reseeds the account and logs in
once, sharing the session via e2e/.auth/state.json (git-ignored).
Browsers — every flow runs in three projects: chromium (Desktop Chrome), webkit (Desktop
Safari) and ipad (iPad gen 7 landscape: 1080×810, touch, mobile). Install the engines once per
machine with npx playwright install chromium webkit; run one project with
npx playwright test --project=ipad. WebKit catches what every iOS browser inherits from the engine
(JS, CSS/layout, event order). It does not reproduce iPad media refusals: Playwright's WebKit
decodes through the host OS (Media Foundation on Windows, GStreamer on Linux), and measured on
2026-09-14 it played an mp3-in-mp4 and an AV1 mp4 that a real iPad refuses. Check a media format
on a real iPad against the dev stack over the LAN (http://<desktop LAN IP>:8080, e.g. a
login-free /watch/<token> share link).
Target — PW_BASE_URL (default http://127.0.0.1:8080):
-
:8080(baked SPA) is authoritative and same-origin. After a frontend change you mustsiftlode dev(rebuild) for its baked SPA to carry the latest test hooks. -
:5173(Vite dev) serves source live — the fast refactor loop; it proxies/apito:8080, so the stack must still be up. All five flows are proxy-safe (none use the WebSocket):PW_BASE_URL=http://localhost:5173 siftlode e2e
Account & fixture
Tests run as a dedicated, non-demo admin account e2e@siftlode.local (never a real account;
admin so the suite can reach the admin-only pages / render branches, matching the real primary
user), reset to a deterministic baseline every run by
scripts/e2e_seed.py (run
in-container so it reuses the app's models + argon2 hasher): the top-3 organic channels subscribed,
one in-progress + one watched + one saved video, prefs pinned to language: en. Credentials come
from E2E_EMAIL (default e2e@siftlode.local) and E2E_PASSWORD — required, no default: run
the suite through the ops wrapper, which exports it from the secret store. Override the container name with
E2E_API_CONTAINER; skip the in-container seed with E2E_SKIP_SEED=1.
Selectors
The app has no data-testids of its own and all copy is i18n (en/hu), so the specs key on a thin
data-testid layer (added with this sprint) plus the locale pinned to en. Stable ARIA hooks that
already existed are used directly (role="dialog" on the player, aria-current="page" on the
active nav row, aria-pressed on the feed toggles).
Typecheck
npx tsc -p e2e/tsconfig.json --noEmit (the specs are outside the app's src tsconfig).