mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
8 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
048fea07ca
|
ui: the last of Semantic UI, in name and in dead CSS
The library has been uninstalled for some time, but the codebase still read as though it were there: identifiers named after it, rules written against its class names, and about 180 comments explaining a rule in terms of a thing that is no longer installed. Renamed, keeping the vocabulary the app actually uses: semanticUIColorMap -> navColorHexMap, semanticColorNames -> paletteColorNames, semanticNames -> iconNameAliases, semanticSizes -> sizeAliases, and SemanticUIColors -> NavColors in wrolpi/schema.py, which is what validates nav_color on the way in. ColorToSemanticHexColor had no callers at all and is gone. The baseline ratchet has done its job -- the list reached zero, so the JSON is deleted and the guard keeps only the checks with teeth: no package, no import, no `className="ui "`, no toast library. Its Theme.tsx check went with the wrappers it was watching for. It also asserts its own walk found files, so a clean result cannot mean it scanned nothing. Deleted from App.css: every rule requiring `.ui`, `.inverted`, `.segment` or `.modal`, none of which any component has set since the stylesheet stopped loading, plus the form skeletons, ui-alerts and progress-text rules that nothing renders. Two of them were doing harm rather than nothing: the drag tints had a dark-theme variant under `.inverted` that stopped applying, leaving 15% of a fixed blue over a dark row, and now mix a token instead. Two selectors naming markup that no longer exists were live bugs. The file browser's filter had `.ui.input.file-browser-filter`, so it rendered at the browser default width on a bar laid out to give it 34em -- now qualified by its container, because `.wrolpi-action-input` sets `width: 100%` at the same specificity and is loaded later. And SearchBox takes named props rather than a rest spread, so the `id` DestinationForm hands it was dropped in silence and the label it renders pointed at nothing; the download forms' Destination field has had no associated label since the migration. "Semantic roles" survives in four places, where it means what it says. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4337ec2154
|
ui: the map pin buttons were three sizes for three reasons
Reported as "all different sizes", and the pin row's three actions measured
46x30, 30x30 and 54x36. Three causes.
APIButton dropped `size`. `useAPIButton` declared `size = 'medium'` as a
parameter and then never put it into the props it hands to Button, so every
APIButton in the app rendered at Mantine's default regardless of what its call
site asked for -- seventeen call sites pass one, from `xs` on the map pins to
`big` and `huge` on Settings. The delete pin was 36px tall beside two 30px
siblings because of it. Passing the parameter default through would have fixed
the bug by breaking everything else -- every APIButton naming no size would have
grown from `sm` to `md` -- so the default is undefined now and Button's own
default stands. Eight buttons on three other pages do grow as a result, which is
their call sites finally being honoured: VideoPlayer's `large`, one of Channels'
`big`, and Settings' two `big` and two `huge`.
An icon-only Button was not square. A Button is as wide as its content plus its
horizontal padding; an ActionIcon is square by construction; and the pin row
mixes the two, because AddToPlaylistButton renders an IconButton when it has no
label. Their heights already agreed -- ActionIcon's scale was remapped onto
Button's some commits ago -- and this is the other axis of the same alignment,
keyed off Mantine's own `--button-height` so the two stay in step through every
size.
They were also misaligned and touching, which is a third thing: bare inline-block
siblings align on text BASELINES, and a Button's baseline is not an ActionIcon's,
so the middle control sat 4.3px above the other two. A Group, as every other row
of buttons in the app uses.
Written test-first at Roland's request. Seven cypress cases went red on the
sizes -- widths 46 against 30, 54 against 36, 62 against 42, 70 against 50, and
"xs is shorter than lg: expected 36 to be below 32" for the dropped size -- with
both inverses green throughout: a labelled button stays as wide as its label, and
an APIButton naming no size keeps the height it had.
The alignment claim is measured in ui-layout.cy.js, including the premise that
bare siblings really are misaligned, since three controls of identical height not
lining up is not believable otherwise. The call sites are held by a source
guard, because MapPins fetches from the API and builds its rows through
SortableTable.
That guard earned itself twice over. Written for the pin rows, it found the
map-files subscribe cell putting a preview button beside a subscribe button with
the same defect. Then a first attempt at a general "any cell with two controls"
rule had to be abandoned: a cell whose buttons are the arms of a ternary renders
only one and needs no Group, and excluding ternaries to allow for that also
excluded the subscribe cell, whose `role={isSubscribed ? ... : ...}` says nothing
about how many controls it holds. Named cells with a stated scope beat a general
rule that quietly stops covering things.
Two slips of mine in that file, both caught by running it: `expect(value, message)`
is chai and throws in jest, and `onSave(pin.id, ...)` was not unique to the cell
it named -- the edit row's TextInput calls it from onKeyDown in a different cell,
so the check passed against the wrong one.
1132 jest / 64 suites, 243 in ui-layout.cy.js, clean npm run build. Confirmed
live: three 30x30 controls, vertical spread 0, 10px gaps.
|
||
|
|
6ed7baf960
|
review: one inset, and two tests that could not fail
Acting on the review of #625. No bugs; four suggestions and three nits, of which two were already handled and one needed no code. The weld used three separate 8px literals -- the control's padding, its flex gap, and the clear button's negative margins -- which had to stay in lockstep or the button would drift past the border or back inside it. They are one `--searchbox-inset` now. Cancelling the gap on the clear button's LEFT was a real side effect: when a field is both `loading` and `clearable`, which SearchResultsInput allows, the DOM order is input, spinner, clear, and the spinner ended up welded to the divider. Only the right-hand padding is cancelled now; the 8px before the button stays, and a case covers the pair. `min-height` was still coming from `--ai-size`, so the rule only half-owned the height it claimed to set -- true today by 0.15px, the field being 36.15 against a 36 minimum. Unset now. Two nits needed no code and are recorded as findings rather than changes. The clear button's corners are already square: the theme zeroes every `--mantine-radius-*`, so there are no wedges to show against the field, and a case now depends on that explicitly. And a label-less button with BOTH `icon` and `iconAfter` would stay mis-centred, but no call site writes one, so the limit is documented instead of designed around. Blank children now count as no label. `toArray` drops null and booleans but keeps `''` and whitespace, which would have left the glyph off-centre; no call site does it today and the predicate should not depend on that. Two corrections to my own work, both caught by planting: The fragment case. I wrote that `toArray` flattens fragments away to nothing and tested `<></>` as unlabelled. It does not -- it returns the fragment as one child, so `<></>` is indistinguishable from real content. The test now records what actually happens, with `<>Delete</>` as the shape that occurs in practice. The min-height assertion compared `--ai-size` -- which reads back as `calc(2.25rem * var(--mantine-scale))` -- against a computed `36px`. Two strings that can never match, so it passed with the rule removed. It compares resolved lengths now, paired with an ordinary IconButton that DOES carry the minimum, so its absence is attributable to this rule rather than to a Mantine default. An earlier attempt fabricated a shorter field by overriding the input's font size; that was deleted, because a test that invents a layout the app never renders is not evidence about this app. 1128 jest / 63 suites, 231 in ui-layout.cy.js, clean npm run build. Confirmed live: inset 8px, clear button 1px from the field edge, 8px from the input, at the field's full inner height with no minimum of its own. |
||
|
|
1ce8df0bad
|
ui: three button-layout defects, each with a different cause
Reported together as "button groups", but they turned out to be three unrelated
things.
The file browser's footer buttons touched while the Channel Edit page's had
gaps. Channel Edit lays its buttons out with Mantine's `Group`, which is a flex
row with a gap; the footer is hand-rolled and was a plain block, so its
inline-block buttons sat flush -- JSX drops whitespace-only lines, so there was
not even a space character between them. The footer is now the flex row every
other button group already is, wrapping because it carries eight controls.
The Settings config table's Import and Save buttons had off-centre icons while
Restore in the same row looked right. Restore is an IconButton -- an ActionIcon,
which centres a lone glyph by construction. Import and Save are icon-only
Buttons, and an icon on a Button goes into Mantine's `leftSection`, which carries
a `margin-inline-end` to hold it off the label. With no label that margin is
pure offset: measured at 8px left of centre. An icon with no label is now
rendered as the button's content instead, so it centres. `Children.toArray`
decides that, because it drops nulls and the file browser passes
`{label('Delete')}` -- null whenever the bar is too narrow for words, which is
exactly where the offset showed most.
The search field's clear button sat inside the field rather than attached to it:
the control is a bordered box with `padding: 0 8px`, leaving the button 9px short
of the edge and 2px short of the height. Semantic welded it to the field --
measured on the QA Pi at zero gap and full height -- so it now stretches and
negative margins cancel the padding and the flex gap. Negative margins rather
than dropping the control's padding, because that padding is what holds the
search icon and the text off the border; only this one child should escape it.
The `html` prefix on that rule is load-bearing: Mantine sets ActionIcon's height
from `--ai-size` on its own single class, and at equal specificity the winner is
whichever stylesheet loaded last.
Fourteen new cases. One correction worth recording: the height assertion first
compared against the control's border box and failed on correct behaviour -- the
button fills the CONTENT box, and the 1px above and below it is the field's own
border, which it should sit inside rather than paint over. Each fix planted
separately; the icon change reddens three, the footer one, the clear button one.
Every case is paired with an inverse -- a labelled button must keep its icon on
the left, and the field must still be padded around its other children.
1128 jest / 63 suites, 226 in ui-layout.cy.js, clean npm run build. Confirmed
live: footer gaps 8px on one row, config glyphs at 0.0px from centre, clear
button 1px from the field edge at the field's full inner height.
|
||
|
|
47d932d25d
|
ui: an icon button is the same height as the button beside it
The Details/Open pair on every archive card came out 36px beside 28px.
Mantine gives ActionIcon a size scale unrelated to Button's, and defaults
the two differently:
size xs sm md lg xl default
Button 30 36 42 50 60 sm (36)
ActionIcon 18 22 28 34 44 md (28)
So the two never matched, at any size, and every toolbar mixing them was
ragged -- the archive card is just where it shows most.
Mantine already ships the aligned scale as `--ai-size-input-*`, for an
ActionIcon standing beside an Input; those five values are identical to
`--button-height-*`. ui.css points one at the other, which fixes every
size at once including ones no call site uses yet, and IconButton now
defaults to `sm` like Button.
Both halves are needed and both are proven so: dropping just the `sm`
remap reddens the default and `sm` cases while xs/md/lg/xl stay green;
reverting just the default reddens the default case alone, at 42 vs 36 --
the mismatch inverted rather than fixed.
A further test pins the direction. The pair could also be squared up by
cutting the labelled button down to 28px, which would be the wrong fix.
Gallery gains a row of Button/IconButton pairs at all five sizes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
7de1bead98
|
ui: three silent library defects the agents' reports surfaced
Each of these was reported by an agent working around it rather than patched in one file, which is why all three are fixed once, for every call site. Button accepted an `href` and dropped it. Mantine renders an anchor only when it also gets `component='a'`; Semantic spelled that `as='a'`. So a migrated call site that kept just the href produced something that looks like a download link and navigates nowhere -- silently, with no warning. Button and IconButton now infer the anchor from the href. The media filter ran twice on nested media. A `.media` wrapper filters its subtree as one unit -- the audio player needs that, because its control glyphs are SVG and the element-type rule does not reach them -- but the poster image inside also matched that rule. Two passes of a luminance-to-red matrix is not one: the second takes 0.2126 of an already-red pixel, so the poster came out markedly darker than everything around it. Leaf media inside a filtered wrapper is now left alone. Progress had no indeterminate state. Semantic's `indicating` animated a stripe when the size was unknown; without it an upload that has not reported yet sits at 0% and reads as stalled -- and `aria-valuenow="0"` tells a screen reader the same wrong thing. It now animates, reports no value, and falls back to a static fill under prefers-reduced-motion. Also: night and amber now override an inline `--label-color`. A call site may legitimately need many distinguishable colours -- the Flasher's chip badges use a twelve-colour palette no four tokens can express -- but on a night-adapted screen a green or blue chip badge defeats the entire mode. The labels lose their distinctness there, which is the right trade. |
||
|
|
6ea9d3645f
|
ui: Common.js and thirteen more files off Semantic
Phase 2, part 3. Baseline 63 -> 50.
Common.js is the one that mattered: 51 files import it, so its Semantic usage
was propping up the rest of the migration. Everything in it now comes from the
library -- Paginator, the two search inputs, DirectorySearch, TabLinks, the
toggles, the messages, APIButton, Breadcrumbs, SimpleAccordion, IframeViewer,
CardPoster, FileIcon, LoadStatistic, the theme menu, and the hotspot icons.
Also migrated: Placeholder, Apps, ArchivePreview, CbzViewer, AddToPlaylist,
TaggedDeleteConfirmModal, the collection edit/tag forms, and four inventory
components.
New in the library, all in the gallery and tested:
SearchBox Semantic's `<Search category>` with its grouped results shape
kept, because four call sites already build it. A real
combobox rather than a div that listens for clicks.
Pagination Semantic's prop names; onPageChange gets the page number.
TabBar the strip only -- routing stays with the caller.
IconStack Semantic's IconGroup, named once for assistive technology.
Table.HeaderCell sorted/onSort -- a real button carrying aria-sort.
Button/IconButton translate Semantic's size names.
That last one is defensive: unmigrated callers still pass size='tiny', and
Mantine drops a size it does not recognise without a word, so the button
quietly renders at the wrong size. Mapping here keeps the mixed state honest.
Three real bugs found and fixed on the way, none of them cosmetic:
* `fileSuffixIconName` returned fourteen icon names the map did not have, so
opening a folder with a PDF, a Word document or an archive in it logged an
error and drew a fallback glyph. All fourteen mapped, with a test that
walks the mapper's own return values, and another asserting every mapped
name resolves to a Tabler component that exists.
* `Statistic` relied on Semantic's `Statistic.Group` for its spacing, so on
the dashboard the load averages ran together into "1 MIN. LOAD5 MIN.
LOAD15 MIN. LOAD". It carries its own spacing now.
* Semantic's inverted stylesheet colors every descendant of an inverted
segment, so Toggle labels inherited #dddddd -- white pixels in night mode,
on the Settings page, which is exactly where someone adjusts their WROLPi
in the dark. Restated from tokens.
The baseline ratchet gained a third assertion: a migrated file may still lean
on Semantic's CSS classes without importing the library, which the import check
cannot see and which would collapse silently when Phase 4 deletes the
stylesheet. There is one such bridge today and it is now declared.
|
||
|
|
fb4c03d7a0
|
ui: token-driven component library on Mantine
Adds src/components/ui: buttons, messages, labels, progress, status, tables,
form controls, panels, cards, statistics, modals, tooltips, tabs, and icons.
Everything resolves its colors through theme tokens, so no component branches
on the current theme, and Semantic UI keeps rendering the app meanwhile.
Mantine is bridged rather than restyled: its own CSS variables are pointed at
our tokens. Four details were not obvious, and each caused a visible defect
in night mode before being fixed:
- Structural colors must go in the resolver's light/dark blocks. Mantine
emits its values under [data-mantine-color-scheme], which outranks the
:root block that `variables` becomes, so its grays were winning.
- The dark and gray palettes are mapped shade by shade to tokens. Mantine
reads them directly for table borders, zebra stripes and input chrome;
they can be neither flattened nor left alone.
- Mantine writes --button-* as inline styles, which no stylesheet can
outrank, so the dashed danger treatment sets real properties instead.
- Switch colors are written onto inner elements, so they need scoped CSS
rather than a variable on the root. Adds a --knob token for control
handles, because Mantine's default thumb follows theme.white, which is
near-black in the dark-based themes.
An audit of every painted pixel in the /theme-sample gallery now finds no
non-red channel in night mode.
Icons come from @tabler/icons-react with the 102 Semantic names the app uses
mapped to components; they stroke with currentColor, so they follow the theme
without any rule of their own. Toasts keep the react-semantic-toasts-2
signature so those call sites migrate by changing an import.
TypeScript 5 is required: Mantine's declarations use `const` type parameters,
which 4.9 cannot parse (Mantine 7 is the same, so downgrading is no escape).
That needs two npm overrides to keep clean installs working, both verified
against a Docker build: react-scripts declares peerOptional typescript ^3||^4,
and fork-ts-checker 6.5.2 assigns typescript.performance.mark, which TS 5
made getter-only - crashing the dev server's type checker on every rebuild.
semantic-baseline.test.js ratchets the removal: it fails if a file not already
listed imports Semantic UI. An ESLint rule would have printed 75 warnings per
rebuild, and CI unsets CI= for its build step, so it would guard nothing there.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|