Repository navigation
build(web): generate the UI's Tailwind CSS with the pinned standalone CLI - #685
Conversation
… CLI The web UI is written in Tailwind classes but had no Tailwind build: app.css hand-maintained ~500 utility rules. Replace them with a purged build from the standalone Tailwind CLI (v3.4.19, no Node), committed so the Pi never builds anything. - scripts/build_css.py downloads the CLI for the current OS/CPU, checks its pinned SHA-256, caches it outside the repo and builds static/v3/tailwind.css (+ plugin-frame.css). --check fails when the committed CSS is stale; a new CI job runs it. - Content globs cover templates/v3, static/v3 JS and blueprints; a safelist covers the runtime-built colour classes in tools.html and the utilities app.css defined that core no longer uses (plugin widgets may). - darkMode matches <html data-theme="dark">. The values app.css had customised (gray text, emerald/amber fills, font line-heights, token shadows, transition, ring offset) move into the theme; keyboard-only focus rules stay in app.css with Tailwind's versions blocklisted. - app.css keeps tokens, base, components and dark overrides and loads after tailwind.css, so its rules still win where they did. - Border utilities now get preflight's width/style reset, so border-b, border-t and divide-y draw. 2xl: classes work (the hand-written selectors were invalid CSS). The scroll-hint rule no longer wipes the background of elements with their own bg-*. - Plugin web_ui iframes link a local plugin-frame.css instead of the Tailwind v2 CDN, which failed in AP mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 7 minutes. View limit detailsLimit details: You’ve used the included review currently available. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (23)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 240 |
| Duplication | 0 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
… in the plugin-frame safelist - build_css.py refuses a non-https download URL before urlopen (which also follows file:// and custom schemes), with a test. The subprocess import and the CLI run are annotated: list-form argv, no shell, the CLI path is computed locally and the binary is SHA-256-verified. - plugin-frame.config.js builds its safelist from regex literals instead of new RegExp() over in-file constants (ESLint security-node non-literal-reg-expr). The pattern sources are unchanged, and build_css.py --check still reports the committed CSS up to date. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
app.css no longer holds the utility classes (they moved to the generated tailwind.css), and login.html (#683) links only app.css, so the login page rendered almost unstyled once both PRs were on main. Link tailwind.css before app.css, as base.html does, and extend the load-order test to every template that links app.css. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: - CHANGELOG.md: kept both; "Update channels" first, then main's sections. - docs/ARCHITECTURE.md: the verifier paragraph now says both things -- the heartbeat check from #687 and the return to old_ref from this branch. - web_interface/blueprints/pages_v3.py: the General partial gets both update_channel_status (this branch) and web_login (#683). Follow-ups for main's changes: - #688: the channel API test fixture stubs api_v3.plugin_catalog, not the retired plugin_manager. - #683: test_web_auth pins that GET/POST /system/update-channel need login from the LAN when login is on (the default rule; mutation-checked by exempting the endpoints). - #685: scripts/build_css.py regenerates identical CSS; --check passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#684) Adds auto_update.channel: stable follows the newest vX.Y.Z release tag (detached HEAD; pre-releases and other tags ignored), beta follows main as before. Nothing ever moves a device backwards: a checkout newer than the newest release keeps following main (or stays put when detached) until a release contains its commit. Legacy configs migrate to stable when they reach a release. Update Code, the weekly updater's preflight, and the verifier's rollback (back to old_ref: branch or detached release) all honour the channel. General tab Update Channel select, GET/POST /api/v3/system/update-channel, release-aware Overview banner and Tools git panel. New installs default to stable. Rig fix (ledpi): /system/check-update reports update_available: false when the channel's action is none (a detached HEAD newer than the newest release), matching Update Code; the Tools panel no longer calls every detached HEAD "a release". Merged with main through #687 (heartbeat verifier, #683 login, #688 plugin_catalog, #685 Tailwind build). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Replaces the hand-written Tailwind subset in
static/v3/app.csswith a real, purged Tailwind build, per the owner decision on the Sept 2026 web UI audit (P0, "undefined utility layer"): standalone Tailwind CLI, no Node, generated CSS committed, the Pi never builds anything.scripts/build_css.pydownloads the pinned Tailwind v3.4.19 standalone CLI for the current OS/CPU (Linux x64/arm64/armv7, macOS, Windows), checks it against a pinned SHA-256, caches it outside the repo, and builds two files.--checkbuilds to a temp dir and fails if the committed files differ.static/v3/tailwind.css(generated, committed) holds the utilities. Content globs:templates/v3/**/*.html,static/v3/**/*.js(excluding vendored*.min.js) andblueprints/**/*.py. A safelist covers the one runtime-assembled family (bg-${color}-50etc. intools.html).darkModeis['selector', '[data-theme="dark"]'], matching how the app switches themes.app.cssloses ~515 duplicated utility rules and keeps what is actually custom: tokens, base styles, components, dark-theme overrides and the end-of-file guards. It loads aftertailwind.css, so components and[data-theme="dark"]overrides still win where they did. The values app.css had customised live in the Tailwind theme (web_interface/tailwind/tailwind.config.js), so responsive and state variants use them too: darker light-mode gray text, emerald/amberbg-green-*/bg-yellow-*, font-size line-heights, token shadows, the narrowtransition, and ring-offset colour.focus:outline-noneandpeer-focus:*are blocklisted so app.css's keyboard-only (:focus-visible) versions stay the only ones.web_ui/iframes (/v3/plugin-ui/...) no longer load Tailwind v2.2.19 from cdnjs, which fails in AP mode. They link a localstatic/v3/plugin-frame.css: preflight plus a safelist of the common utility families, using v2's palette (green/yellow/purple = emerald/amber/violet). The officialof-the-dayfragment renders pixel-identical to the CDN version apart from its spinner.css-buildjob ("Tailwind CSS is up to date") intest.ymlrunspython scripts/build_css.py --check.bg-opacity-*andflex-shrink-*(both used here), switches the palette to oklch (every colour would shift), and needs Safari 16.4+/Chrome 111+.State of main vs. the audit
The audit's "333 classes with no rule /
.hiddenmissing" predates #568, which hand-filled most of the gaps. So the visual change here is smaller than the audit implies. What this PR fixes on top of main:border-b,border-t,divide-y(and.borderonly by per-rule patching) set a width with noborder-style, so tab-row underlines, section dividers and table/list dividers never drew. The build adds preflight's*{border-width:0;border-style:solid}reset (plushrand native text fields keeping a 1px border).2xl:class was dead. app.css wrote.2xl\:grid-cols-4, which is invalid CSS because of the unescaped leading digit. Tailwind escapes it, so at 1536px and wider the plugin grids go to 5 columns and the gutters widen, as the markup says..overflow-x-auto { background: … }(shorthand) resetbackground-color, so the live preview stagebg-gray-900 … overflow-x-autorendered white, and the Tools command-output<pre class="bg-gray-900 text-gray-100 overflow-x-auto">was light text on no background. The hint now skips elements with their ownbg-*.web_ui_actionscard (white text on no background), thetext-indigo-600disk icon on Tools, toggle-switch knob offsets (translate-x-4/7), slider accents, password-strength colours,-mb-pxon the tab rows, andresize-*on the textarea widget.duration-*/ease-*now overridetransition-*andpr-20overridespx-3(plugin search field), as written.space-y-*on<p>children now zeroes the UA bottom margin (Tailwind's rule), so the plugin-card metadata rows use the 6px gap the markup asks for instead of ~14px.Plugin compatibility (a PRODUCT.md hard constraint): third-party widget JS renders into the page and can't be scanned, so the config safelists the ~17 utilities main defined that core no longer uses. The generated set is a superset of what worked before. I checked the one official plugin widget (
soccer-scoreboard/widgets/custom-leagues.js); every utility it uses is present.Visual check (before = origin/main
c3a7a110, after = this branch)Headless Chrome, full-page, 8 pages × {1280px, 375px} × {light, dark} = 32 pairs each side. They're saved locally (not committed) in the agent's scratchpad
shots/{before,after2,compare2};compare2/holds before | after | diff-mask composites.bg-gray-900) as the markup specifies, instead of whitespace-y-1.5now as specified); page ~57px shorter; store unchanged/api/v3/hardware/status, not CSS)--color-borderand read subtlyEnvironment noise excluded: live CPU/memory numbers, git branch/dirty state, SSE preview timing, and an "update available" banner in the first after-run (that server was one commit behind; I re-shot after rebasing).
Not fixed here (pre-existing, same on main): the "Logs tab" link in the Display hardware banner has no colour class, so it's browser-default blue (poor on the dark amber box); the Starlark Apps card title is overlapped by its toggle.
Payload (gzip level 6, Flask-Compress's default)
app.cssapp.csstailwind.cssplugin-frame.css(iframes only; replaces the full v2 CDN build)Tests
test/test_build_css_script.py(pins are 64-hex SHA-256; per-OS/CPU asset mapping; unsupported CPU errors clearly; a corrupt cached CLI is replaced and a tampered download is rejected).test_web_static_audit.pynow readstailwind.css+app.css(CSS-unescaping\32xl\:), and asserts the runtime-built colour classes exist and thattailwind.cssloads beforeapp.css.test_pages_v3_partials.pyasserts the plugin iframe links the local file and no CDN. Smoke/gzip tests cover the new assets.build_css.py --checkmutation-checked: a new class in a template makes it exit 1. Five repeat builds are byte-identical, and reordering the content globs doesn't change the output.--checkfailed because a Windows checkout (core.autocrlf) has a CRLFapp.input.css, and Tailwind's minifier merges rules differently for it (same rules, different bytes). The content files' line endings turned out not to matter.build_css.pynow feeds the CLI an LF copy of the input (with a test for it), and the committed Windows-built CSS passes--checkon CI's Linux runner.pytest -m "not hardware" test/ --ignore=test/plugins) on Windows, diffed against an origin/main baseline worktree: identical FAILED/ERROR sets (59 failed + 6 errors on both, the known Windows-only set); branch 6116 passed vs baseline 6098 (the new tests).REQUIRE_DOM=1 node test/js/run_all.jsagainst a live server): all suites pass on both baseline and branch (499 ok each).Contributor impact
Change a template or static JS → run
python3 scripts/build_css.pyand commitstatic/v3/tailwind.css; CI tells you if you forgot. Documented inweb_interface/README.md(new "Styling (Tailwind CSS)" section),CONTRIBUTING.md, the widgets README, anddocs/WEB_INTERFACE_GUIDE.md.🤖 Generated with Claude Code