mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-31 10:28:43 -04:00
* refactor(ui): add layout/text primitives and per-page CSS blocks
The React UI already shipped a design system (tokens, form grids, data
tables, stat cards, callouts) that the pages largely bypassed: ~2,000
`style={{ ... }}` literals across src/pages and src/components. Each one is
a spacing or colour decision made locally, so no two pages share a rhythm,
which is the main reason the app reads as unfinished rather than as one
product.
Two additions, both to App.css:
- A small semantic primitive layer: .stack / .hstack for vertical and
horizontal rhythm, .text-note / .text-sub / .text-meta / .text-mono for
the text roles the pages kept re-deriving, .tone-* + .icon-chip for
semantically tinted icons, plus scale-locked spacing and size steps.
Deliberately short and semantic, not a utility framework: the size and
spacing classes exist mainly so that an OFF-scale value stays an inline
style and therefore stays visible.
- Named blocks for the shapes fifteen pages actually have (.p2p-diagram,
.usage-tile, .tr-code, .mw-badge, .set-rail, ...), so those shapes are
defined once instead of per call site.
Two findings worth recording. The type scale is xs 0.6875 / sm 0.8125 /
base 0.875, and 0.75rem was in use roughly 100 times without being on it
(along with 0.7, 0.85, 1.1 and 0.625rem); all now snap to the nearest step.
And there were thirteen distinct table column widths across the app where
three or four would do; they are pulled into .col-w-* so the ladder is
visible in one place, ready to normalise separately.
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude Code:claude-opus-5 [Bash] [Edit] [Write]
* refactor(ui): move repeated inline styles onto shared classes
Six passes over src/pages and src/components, each matching a whole
`style={{ ... }}` attribute exactly so the swap is provably equivalent:
- the identical "waiting for the first response" wrapper, repeated
verbatim in 21 files
- text roles (size + colour combinations) onto .text-note / .text-meta /
.text-sub / .text-mono
- semantic colours, type-scale steps, .panel-title, .list-row
- spacing and weight steps, .stack / .hstack rows
- table column widths, small pills, chart legend swatches
- the remaining shapes appearing three or more times
One class of bug is worth calling out, because it is what a careless
style-to-class conversion produces and it is invisible to every check we
run. Adding `className="x"` to an element that already had a className
leaves TWO className attributes; JSX keeps the last and silently drops the
first, so `<i className={icon} className="text-xs" />` loses its icon while
passing eslint, `vite build` and the full Playwright suite. 112 of these
were introduced and repaired here. The gate added in a later commit fails
on them.
No visual change is intended beyond snapping off-scale font sizes onto the
type scale. Verified after every pass: eslint 0 errors, vite build passes,
Playwright page-render-smoke + navigation 22/22.
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude Code:claude-opus-5 [Bash] [Edit] [Write]
* refactor(ui): convert fifteen pages onto named classes, add inline-style gate
Full per-page conversions, each one reading the page, naming the shapes it
actually has, and leaving inline only what is computed at runtime:
P2P 205->0 Traces 50->0 ConfigFieldRenderer 23->0 NodeDetail 29->1
ModelEditor 25->1 NodeInstallPicker 29->1 FineTune 175->2
Nodes 33->2 ImportModel 35->3 Talk 43->4 AgentJobDetails 27->4
Usage 97->6 Settings 24->6 Middleware 54->8 Backends 87->45
Every remainder is genuinely dynamic: a data-driven badge colour, a
`width: ${pct}%`, a tooltip's coordinates.
Naming the shapes made reuse fall out on its own. Nodes reuses the P2P
setup shapes (both present the same "no workers yet, here is how to add
one" flow) and Model Editor reuses the Settings section rail, which was
byte-identical. Two shared *style objects* also turned out to be classes
wearing a costume and were deleted: `monoCell` in Usage and `hintStyle` in
ImportModel.
Some fixes fell out of the conversion. The evaluation toggle on the
fine-tuning page was a hand-rolled div that rendered as a clipped circle;
it is the existing Toggle component now. That page's empty state pointed at
a "New Job" button that was scrolled off the top of the page, and now
carries its own call to action. And `.input--file` is added at the system
level rather than as a local hack, because ImageGen and VideoGen truncate
their file inputs the same way today.
scripts/inline-style-gate.mjs is the ratchet that keeps this from
regressing. It does not forbid inline styles; it fails when the total goes
UP (same discipline as the coverage baseline) and when an element carries
two className attributes. eslint would catch the latter via
react/jsx-props-no-duplicate-props, but that needs eslint-plugin-react,
which this project does not depend on, so the check lives next to the tool
that causes the problem.
npm run lint:inline-styles # check against baseline
npm run lint:inline-styles:report # per-file counts, worst first
npm run lint:inline-styles:write # refresh after converting
Net: 2,061 -> 611 inline styles. eslint 0 errors, vite build passes,
Playwright page-render-smoke + navigation 22/22, gate green on both checks.
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude Code:claude-opus-5 [Bash] [Edit] [Write]
---------
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Co-authored-by: Ettore Di Giacinto <mudler@localai.io>