* Repos table (CollectionTable), New Repo and Edit pages (frequency,
mode, branch, description, tag, delete with an optional "also delete
its files").
* A GitHub-like repo page: upstream, branch, latest commit, a status
message when not yet downloaded or the last update failed, the file
tree (directories through ?path=, files in the file preview), and each
directory's README.
* Global search: a Repos section under Other with escaped README
headlines, Repos in the suggestions, and the search text sent to the
Other estimate.
* Settings: the Repos Directory field.
* CollectionTable columns may `render` their cell.
* Theme gallery: the repo summary, tree, and status icons.
Co-Authored-By: Claude Opus 5.5 <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>
Greptile's three findings on #590, all confirmed real before fixing, and each one
verified red against the original code afterwards.
P1, the directory fields had no accessible names. All five rendered their
visible label as a bare <label> with no htmlFor while passing no label prop, so
their inputs had a description ("/media/wrolpi/") and no name at all -- five
fields in one grid, holding similar relative paths, indistinguishable to a
screen reader. The labels now belong to the inputs, InfoPopup content and all.
This predates PathInput; the old TextInput call sites had the same gap, and
moving to a component that supports labels is what made it fixable.
P1, description and error were rendered but never referenced. aria-describedby
pointed only at the prefix, so the help text listing the variables a path
accepts, and the reason a path was rejected, were both invisible to assistive
technology. Both are now in the description, and an errored field reports
aria-invalid.
P2 was worse than reported: at `flex: none` a long prefix took the entire row
and left the input eighteen pixels wide -- not "consumes the editable area" but
a field nobody can type in. The media directory is whatever the API reports, so
its length was never ours to assume. The prefix now shrinks and ellipsises at
half the control, with the full path on `title` and in the accessible
description.
The naming fault could not have been caught where the others were. jsdom cannot
see layout, and the Settings page needs enough mocking that it had no test at
all -- which is why these five fields were uncovered when they broke. The new
e2e spec intercepts the API and lets the real page render, so it measures the
names and the geometry a user actually gets. Reverting one field to a bare
label fails it; a long prefix fails the browser spec at 18px.
962 jest, 15 browser component, 46 e2e, clean cache-busted build, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>