283 occurrences across 23 files, all of them mine and all of them in the
theme/UI work: comments, test names, and a handful of local identifiers
(`colours`, `roleColours`, `awaitColoured`) plus one test fixture tag named
'Uncoloured'.
Nothing was required to keep the other spelling. Checked before replacing: no
CSS custom property or class name contains it, no library prop or API key does,
and every string holding it is a test name or our own copy. The renamed
identifiers were checked for collisions with an existing `color`/`colors` in the
same file first -- there were none.
Verified as a pure rename: 262 lines changed, and normalising the spelling on
every removed line reproduces the added line exactly, for all 262. So the diff
contains no other edit hiding in it.
1134 jest / 64 suites, 271 cypress component tests with the same 12 pre-existing
Semantic-era failures in Tags/Channels/Download/Inventory, clean npm run build.
Both of Greptile's findings on #591 were real, and the first was a plain miss:
mockModule shipped with no caller, so its documented usage had never once been
executed and could not have worked. It called jest.requireActual on a path
passed in by the spec, which resolves relative to test-utils.js rather than to
the caller -- for any spec in a subdirectory the documented
'../hooks/customHooks' pointed outside src altogether. It now takes the module
the spec has already resolved, and throws on a string rather than quietly
resolving to the wrong one, which was the original failure.
It also has a caller now. FileBrowser.test.js listed six hooks in a hand-written
factory and returned only those, so every other export of customHooks was
undefined for anything it rendered -- the exact landmine the helper exists to
remove.
The second: `inverted` drove Mantine's colour scheme on its own, so asking for
night or amber through the new theme option produced a tree that reported a dark
theme while Mantine rendered its light one. Production cannot reach that
combination, and a harness that invents states the app never has is worse than
one that is merely incomplete -- what a test proves about such a tree is true of
nothing. The scheme is now derived from the theme name via isDarkTheme, and
data-theme is stamped on <html>, without which a component test cannot exercise
any token table or the night-mode treatment at all. isDark remains overridable
so a spec can still make the two disagree deliberately.
test-utils.test.js covers the harness itself, which had none. Eight of its
twelve assertions were red against the code above.
986 jest, 46 e2e, clean cache-busted build, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Specs had no shared vocabulary for the app's data or its contexts, so each one
rebuilt both. A settings object was written out by hand wherever one was needed,
twenty-odd keys at a time, and nothing held those copies to each other or to the
API. Contexts were worse: to render a consumer a spec mocked the whole context
module, which replaces every export including the providers, so each spec
reinvented the pieces it still needed. One supplies a `Media` that always
reports desktop -- quietly leaving that component's mobile branch untested -- and
one hand-writes a context as a bare `_currentValue` with no provider, working only
because useContext reads that React internal.
test-fixtures.js is now the single definition of both, shared by jest, the
Cypress component specs, and e2e intercept bodies, so the three cannot disagree
about what the API returns. Every fixture is a function taking overrides; a
shared mutable object lets one test change what the next one sees.
The drift this prevents was already real. The theme fixture supplied `i`, `s`
and `t` holding hardcoded Semantic greys for months after ThemeProvider stopped
supplying them, and ThemeContext still declared all four. So the fixture agreed
with the tests, and with nothing else: FileBrowser was reading `inverted` off the
context and pasting the resulting `undefined` into a className, which is why the
file browser's footer -- near-white #f9fafb, with a `.inverted` dark variant that
nothing could select any more -- stayed light in dark, night and amber. A white
bar pinned to the bottom of the screen in night mode costs the user exactly the
dark adaptation the theme exists to protect. It takes tokens now.
test-fixtures.test.js holds each fixture to its context's declared shape in both
directions, so a property added to one and forgotten in the other fails a test.
Two failed attempts are recorded in test-utils.js because both were instructive.
Importing Tags.js and FileWorkerStatusContext.js so the harness could supply
those providers broke eight suites: it put both into the module graph of every
spec ahead of that spec's own mocks, and components bound to a second instance of
a mocked module -- a different jest.fn() than the test had configured, so a hook
mocked to return a value returned undefined, in a file no test mentions.
Requiring them lazily fixed that and not its mirror image: useContext read a
different TagsContext than the harness had required, so the provider was ignored
and the context default won. A harness must not change what a spec imports, so
it owns only contexts/contexts.js and a spec passes any other context itself.
DomainTagging is converted off the `_currentValue` fake as proof, and now asserts
the tag name a user sees rather than a test id its own mock invented. The five
remaining specs are listed in a ratchet so the count can only fall.
974 jest, 15 browser component, 46 e2e, clean cache-busted build, tsc clean.
Fixtures are confirmed absent from the shipped bundle.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Filtering media was keyed on `data-theme="night"`, which made it a property of
the theme that no user could turn off. Some people want their media tinted to
match the interface; some want to see the actual image.
A theme now declares a filter and whether it starts on, and the user decides:
night night-red on by default. An unfiltered thumbnail undoes dark
adaptation, which is the whole point of the mode.
amber amber-mono off by default. Amber is a look, not a night-vision
aid, so media keeps its real colors until asked for.
light - no filter offered, so no toggle is shown.
dark -
The choice is stored per theme, so wanting amber's tint says nothing about
wanting night's. The toggle lives in the theme picker, which puts it on the
Settings page and in the gallery at once.
Mechanically the rules now key on a `data-media-filter` attribute rather than
`data-theme`: a rule keyed on the theme could not be turned off. index.html
stamps that attribute before first paint alongside data-theme -- without it,
night mode paints a screen of unfiltered thumbnails while the bundle arrives
over a hotspot, costing the user the adaptation the theme exists to protect.
Tests cover both the attribute rule and the fact that the inline script's
duplicated ids and defaults still agree with names.ts.
The amber matrix is the same luminance collapse tinted to #ff9500 (red full,
green 0.584, blue zero) with a gentler gamma than night's: amber carries far
more luminance and clips to a flat wash otherwise.
The gallery gains a Media filtering section showing the WROLPi logo filtered,
beside an unfiltered copy that opts out with `night-unfiltered`. That copy is
behind a click on purpose -- an unfiltered image is a bright patch, and night
mode should not spring one on someone reading in the dark.
ui-design.md gains the media filtering contract it never got in Phase 0,
including the note that a Phase 3 custom theme names an existing filter id and
cannot ship its own matrix, since that would be code rather than data.
Phase 2, part 1. Three files leave Semantic UI (baseline 75 -> 72), and the
components they needed join the library and the gallery:
Header Semantic's shape (as/icon/subheader/dividing). 47 files use
<Header>, and the level now picks the size, so the type scale
stays a scale instead of each call site choosing a font size.
Message gains an icon and a dismiss button.
ActionInput input and button sharing one outline, for <Input action={...}/>.
Loading centered loader with a caption, replacing the
<Loader active inline='centered'> + wrapper div in 19 places.
Modal becomes compound - Modal.Header/.Content/.Actions, plus
Semantic's open, closeIcon, and size names - because 34 files
are written against that shape.
test-utils mounts MantineProvider with ThemeProvider's exact configuration:
migrated components render Mantine internals and every one of their test files
would otherwise need its own provider.
One Semantic bleed found and fixed: semantic.min.css sets
`h1,h2,h3,h4,h5 { font-family: Lato }` and loads after Mantine's element rule,
so headings ignored the theme's body face. Only visible in amber, where the
body is mono and the headings were not. A class-level restatement outranks it;
the rule comes out when Semantic does.
Verified: 925 jest tests, clean production build, and a night-mode audit of
every painted pixel in the gallery (red-only, one known false positive). The
Configs backup and restore flow was exercised against the real API.
Every color now resolves through CSS custom properties on
html[data-theme=...], so a theme is a table of values rather than code.
Four themes ship: light, dark, night (red hues only, to preserve dark
adaptation off-grid) and amber (monochrome amber terminal).
Fonts are exposed as two roles, --font-body and --font-mono, which a theme
assigns a bundled face to; amber uses the mono face. Faces must be
self-hosted, never fetched from a CDN, so the app keeps working offline.
Night mode also filters media that cannot consume tokens - video, images,
PDFs, embedded pages, canvases - through an SVG color matrix that maps
luminance onto the red channel. Tag such an element with `.media`; a filter
on an app-level ancestor would create a containing block and break fixed
positioning.
ThemeProvider persists the choice and index.html stamps data-theme from
localStorage before the bundle loads, so the first paint already matches the
saved theme on a slow connection. The OS preference is followed only while
"system" is selected; night and amber are explicit choices. The nav toggle
becomes a five-item picker.
Semantic components still render themselves, so only the shell is
token-driven for now; the inverted props stay in place until each component
is migrated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Catch useForm fetcher errors and surface them so DomainEditPage can show a not-found message instead of hanging on the loader (and so the webpack overlay no longer flakes Cypress).
* Batch reorganization is supported for both Domain and Channel Collections.
* Batch reorganization happens when any Collection does not match the respective file format. For example, a Channel Collection file format that does not match the `yt_dlp_options.file_name_format` will show `needs_reorganization`.
* Individual Collections can always be reorganized. The reorganization is a little more aggressive. Useful for when some files may have been missed in the batch reorganization.
Replacing Channels and Domains with Collection.
Moving downloading relationships from Channel to Collection.
Created domains.yaml config to manage tagged domain collections.
Adding turn-key upgrade solution in the UI.