Skip to content

luci-theme-footstrap: add new theme - #8903

Merged
hnyman merged 1 commit into
openwrt:masterfrom
VizzleTF:luci-theme-footstrap
Aug 20, 2026
Merged

luci-theme-footstrap: add new theme#8903
hnyman merged 1 commit into
openwrt:masterfrom
VizzleTF:luci-theme-footstrap

Conversation

@VizzleTF

@VizzleTF VizzleTF commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Pull request details

Description

Add Footstrap, a standalone theme for OpenWrt 24.10 and newer. It depends on luci-base and nothing else and ships no CSS framework; pages are rendered by luci-base's own view JS.

  • Sidebar or top bar, switched by the user and remembered per browser.
  • Light and dark modes, palettes, density and colour axes, applied before the first paint.
  • Search over the menu tree.
  • Client-side navigation that swaps the view instead of reloading the page. It falls back to a full load when a page's stylesheet would otherwise carry over.
  • Settings page built from .cbi-value rows, ui.Select and ui.RangeSlider.

Developed at https://github.com/VizzleTF/luci-theme-footstrap

Navigation is client-side: a click swaps the view rather than reloading the document, so a page opens without the full-load round trip. Measured against bootstrap on a Netcore N60 Pro running 25.12.2, five runs, medians:

bootstrap footstrap
38-page tour 11 306 ms 4933 ms
router CPU time 37.3 s 18.4 s
median page 3.03× faster

Method and per-page results:
https://github.com/VizzleTF/luci-theme-footstrap/blob/main/docs/benchmark.md

Discussed on the forum since July:
https://forum.openwrt.org/t/footstrap-new-theme-for-luci-2x-faster-than-bootstrap/251930

Screenshot or video of changes (if applicable)

Desktop

overview-desktop-dark appearance-desktop-light

Mobile

network-mobile-dark

Maintainer (preferred)

@VizzleTF


Tested on

OpenWrt version: OpenWrt 25.12.4 and OpenWrt 24.10.8 (aarch64_generic containers), plus OpenWrt 25.12.2 (r32802-f505120278) on a Netcore N60 Pro
LuCI version: this branch, built with the x86_64 master and 24.10 SDKs
Web browser(s): Chromium 151, Firefox 145, Safari 26
And a single release is downloaded 200–240 times

@openwrt openwrt Bot added the add package Introduces a new package Makefile build script label Aug 3, 2026
@jow-

jow- commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

I like it. Could be a spiritual successor of the OpenWrt 2020 theme. Ideally I'd like to apply the same improvements to the bootstrap default theme too, while keeping its current style.

@VizzleTF

VizzleTF commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for a feedback @jow- !

'd like to apply the same improvements to the bootstrap

Palettes are templated by design: adding one is a matter of filling in a set of variables, not touching the chrome. So a palette matching bootstrap's current look is a small amount of work.

image

That might cover the "same improvements, current style" case without backporting client-side navigation logic into bootstrap itself.

Also theme can be much faster if this will be merged

@VizzleTF

VizzleTF commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Like this)
image

@skramstad

Copy link
Copy Markdown
Contributor

I love this @VizzleTF . 👍 Just out of curiosity, should/could we minify some files? The ones that are more generic (never changes).

@VizzleTF

VizzleTF commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

I love this @VizzleTF . 👍 Just out of curiosity, should/could we minify some files? The ones that are more generic (never changes).

Thank you for feedback!

I minified it in my repo, its saves around 15% (65kb instead of 75kb)

i can do it here also, if it doesn’t need to be readable 😄

@skramstad

Copy link
Copy Markdown
Contributor

I love this @VizzleTF . 👍 Just out of curiosity, should/could we minify some files? The ones that are more generic (never changes).

Thank you for feedback!

I minified it in my repo, its saves around 15% (65kb instead of 75kb)

i can do it here also, if it doesn’t need to be readable 😄

Ah okey. :) I'll let @jow- make that decision 👍

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 new commit (3985204). The commit message matches the diff, so no commit-check notes.

Two functional findings (login-form parity with the base sysauth.ut, and a layout-default that is stated three times and disagrees once) plus four nits are inline. CI on 39852045 is green across all three FormalityCheck runs, so nothing here is CI-grounded.


Generated by Claude Code

Comment thread themes/luci-theme-footstrap/htdocs/luci-static/resources/fs-prefs.js Outdated
Comment thread themes/luci-theme-footstrap/ucode/template/themes/footstrap/header.ut Outdated
Comment thread themes/luci-theme-footstrap/htdocs/luci-static/resources/fs-prefs.js Outdated
Comment thread themes/luci-theme-footstrap/htdocs/luci-static/resources/menu-footstrap.js Outdated
Comment thread themes/luci-theme-footstrap/Makefile Outdated
@VizzleTF
VizzleTF force-pushed the luci-theme-footstrap branch from 3985204 to 4251198 Compare August 3, 2026 18:02
VizzleTF added a commit to VizzleTF/luci-theme-footstrap that referenced this pull request Aug 3, 2026
…default once

The theme-local sysauth.ut dropped auth_fields, auth_message, auth_html and
auth_assets, which the dispatcher hands every sysauth template. With
luci-plugin-2fa installed the OTP input was never drawn, so luci_otp was never
posted and a router with 2FA enabled could not be logged into while this theme
was active. The template exists only to pass blank_page: true and now stays a
superset of the generic one; each conditional attribute carries a leading space
because ucode emits none of its own.

_resolvedDefault() still answered 'sidebar' after the built-in layout flipped to
the top bar, so on a fresh install matchesSavedDefault() was false before the
user touched anything and Reset-to-saved landed on the wrong layout.

Three comments described an Appearance page at admin/system/appearance that the
package never shipped, counted fourteen axes against AXIS_KEYS' twenty-one, and
listed a po/ that luci.mk's Build/Prepare does not copy.

Found by the review on openwrt/luci#8903; verified on all four owlab routers.
@VizzleTF
VizzleTF force-pushed the luci-theme-footstrap branch from 4251198 to b7402d3 Compare August 3, 2026 18:42
@unisequence

Copy link
Copy Markdown

this theme is awesome, much much better than others like proton2025/aurora.

@jow-

jow- commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

The three more or less official themes are:

  • Bootstrap (current default)
  • OpenWrt 2020 (attempt for modern theme based on OpenWrt's logo refresh design)
  • OpenWrt (legacy design)

I'm inclined to take this but given that maintaining non-default themes is effort no one is willing to spend, one of the other themes likely has to be deprecated. I'd lean towards deprecating 2020 in favor to this but maybe let's hear the takes from @feckert @systemcrash @hnyman

@VizzleTF

VizzleTF commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

given that maintaining non-default themes is effort no one is willing to spend

I'll be glad to keep maintaining the theme. If something changes in luci-base and it breaks,
no problem — I'll fix it.

I also have a few other pet projects I've kept going for a couple of years now, like
CommitSage (a VS Code extension I've been shipping since August 2024). I still fix issues
there when they come up and answer users, so this isn't something I'd start and walk away from.

@hnyman

hnyman commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

If a new theme is added, I would drop at least the 2020, but maybe also the legacy theme, which is likely not used by many.

(Personally I use bootstrap, which works well)

@skramstad

Copy link
Copy Markdown
Contributor

Been testing this since PR, and I am a fan. Clean, fast, simple and refreshing.
@VizzleTF : This is a personal preference, but is it easy to change the sans font? I really like the Inter font, but understands it add Kb :) . It has some font-features that I prefer.

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the force-push 39852045b7402d3. The commit message is unchanged and still matches the diff, so no commit-check notes.

All six threads from the last round are addressed: sysauth.ut now carries auth_message / auth_fields / auth_html / auth_assets and is a proper superset of the generic template; _resolvedDefault().layout is top; the Makefile, header.ut and fs-prefs.js header comments and the menu-footstrap.js indentation are all corrected.

The new work in this revision is the third palette. The list is consistently extended across all five places that need it — cascade.css (light + dark blocks, same token set as the other palettes), head.ut's _sd_pal whitelist, head.ut's pre-paint, fs-prefs.js's new PALETTES array, and the fs-appearance.js select — and swapping enumAxis for the wallpaper/density validate-against-a-list shape is the right call for a three-valued axis.

One functional finding inline, in the factory that palette just moved out of: enumAxis derives its window.__fsSD field name as key.slice(3), which is wrong for its one remaining instance (fs-pattern-inkpattern-ink, but the field is pattern_ink). Plus three nits. CI on b7402d3 is green across all three FormalityCheck runs, so nothing here is CI-grounded.


Generated by Claude Code

Comment thread themes/luci-theme-footstrap/htdocs/luci-static/resources/fs-prefs.js Outdated
Comment thread themes/luci-theme-footstrap/htdocs/luci-static/resources/fs-prefs.js Outdated
Comment thread themes/luci-theme-footstrap/root/etc/uci-defaults/30_luci-theme-footstrap Outdated
Comment thread themes/luci-theme-footstrap/ucode/template/themes/footstrap/sysauth.ut Outdated
VizzleTF added a commit to VizzleTF/luci-theme-footstrap that referenced this pull request Aug 4, 2026
…rver sends

enumAxis derived its window.__fsSD field name as the localStorage key minus
'fs-', giving 'pattern-ink' where head.ut emits 'pattern_ink'. sd() answered
undefined for good, so the axis reported the built-in 'theme' whatever the
router had saved: the page was pre-painted data-pattern-ink="original" while
the Ink control read Theme, and pressing Save-as-default wrote 'theme' over the
stored value. Reproduced and fixed on 25.12/apk and 24.10/opkg. colorAxis folds
the hyphen too, where every key is one word today and the failure would be as
silent tomorrow.

Also corrected the comments that had stopped describing the code: the
uci-defaults fall-through called the built-in layout "the sidebar default", the
axis-shape inventory named a hueAxis that does not exist, omitted surfaceAxis,
counted five instances against fifteen, and still listed updateCheck and a
wallpaper that persists to the router; four CSS comments and the file tree in
docs/css.md pointed at styles/01-fonts.css, deleted with the webfonts in 0.12.1.
The sysauth whitespace rationale now states what ucode actually does.

Found by the review on openwrt/luci#8903.
@VizzleTF
VizzleTF force-pushed the luci-theme-footstrap branch 2 times, most recently from 7273c92 to 98201ff Compare August 4, 2026 06:26

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the force-push b7402d398201ff. The commit message is unchanged and still matches the diff, so no commit-check notes.

First, my ucode whitespace nit from the last round was wrong and your measurement is right — I checked the source rather than my recollection. lstrip_blocks/trim_blocks are not off by default the way I assumed: both the ucode CLI (main.c:531-532) and, more to the point, uhttpd's ucode plugin (ucode.c:197-201) set both to true, and loadfile() inherits the running VM's config and only overrides the keys it is handed (lib.c:5522) — so LuCI's loadfile(path, { raw_mode: false }) keeps them on. The lexer then strips \t\v\f\r before {% and the newline after %} with plain tags (lexer.c:992, lexer.c:1076), which reproduces your A\nBCD exactly. The leading spaces are load-bearing and the comment now states the rule correctly.

The enumAxis/colorAxis sdKey fold, the uci-defaults layout comment and the keep.d inventory are all correct against the tree — pattern_ink is what head.ut:119`` emits and what header.ut's `FS_AXES` reads back, and both `/etc/footstrap` uploads are listed with matching symlinks in the uci-defaults script.

One functional finding inline, in the sysauth.ut block this revision rewrote: the new unconditional auth_message precedence fixes the second dispatcher branch but breaks the first one, where a wrong password on a 2FA router now shows only the plugin's boilerplate. Plus two nits. CI on 98201ff is green across all three FormalityCheck runs, so nothing here is CI-grounded.


Generated by Claude Code

Comment thread themes/luci-theme-footstrap/htdocs/luci-static/resources/fs-prefs.js Outdated
Comment thread themes/luci-theme-footstrap/root/etc/uci-defaults/30_luci-theme-footstrap Outdated
VizzleTF added a commit to VizzleTF/luci-theme-footstrap that referenced this pull request Aug 4, 2026
`auth_message` is not "the credentials were accepted". The dispatcher's first
branch copies it out of get_challenges() BEFORE the password is checked
(dispatcher.uc:956), and that call answers with a message on every pending
result -- the literal 'Additional verification required' when the plugin has
none of its own (authplugins.uc:312). So on a router running luci-plugin-2fa
both it and `fuser` are set whenever a password is rejected, and the
message-first spelling this theme took from luci-theme-bootstrap then rendered
only the plugin's boilerplate: the user is never told the password was wrong.
The generic template's `auth_message && !fuser` gets that case right and the
other one wrong -- on the OTP step the credentials were correct.

`auth_plugin` separates the two dispatcher branches: it is set only in the
second (dispatcher.uc:1018), i.e. exactly when session_setup() accepted the
credentials. The credentials alert is now gated on !auth_plugin and the
backend's message renders beside it rather than instead of it, so all four
states are distinct. The credentials alert also moves to .alert-message.error,
the danger variant bootstrap uses here, because once both can appear together
an amber box for each reads a rejected password as a routine notice.

Measured on owrt2512 (25.12.4/apk) and owrt2410 (24.10.8/opkg): the template
compiles under `ucode -T -c` on all four dev routers, the four scopes render
through LuCI's own loadfile/render path with the expected alerts and no throw
for the absent auth_plugin, and a real wrong-password POST returns 403 with
.alert-message.error on both package managers. Patching the dispatcher's
sysauth scope reproduces the bug on the shipped spelling: wrong password with
a challenge pending rendered no credentials error at all.

Also in this commit, all found by the same review round on openwrt/luci#8903:

* tools/axes.mjs now holds the window.__fsSD field name every axis reads,
  running the deriving factories' own formula lifted out of fs-prefs.js rather
  than restating it -- a restated copy stays green while the JS says something
  else, which was measured. surfaceAxis joins the key scan too (21 -> 25).
* three stale comments corrected: the "Appearance -> Background image" label,
  propAxis's worked example, and fs-sheets.js's page-ownership rationale, which
  named a LuCI file that exists on none of the four dev routers. Re-measured
  against what actually injects a sheet there.
@VizzleTF
VizzleTF force-pushed the luci-theme-footstrap branch from 98201ff to 717de96 Compare August 5, 2026 03:33
@openwrt openwrt Bot added the not following guidelines Pull request does not follow formatting guidelines label Aug 5, 2026
@VizzleTF
VizzleTF force-pushed the luci-theme-footstrap branch from 717de96 to 247891c Compare August 5, 2026 03:39
@openwrt openwrt Bot removed the not following guidelines Pull request does not follow formatting guidelines label Aug 5, 2026

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the force-push 98201ff247891c.

All three threads from the last round are addressed and correct against the tree. sysauth.ut now renders the two alerts independently with the credentials one gated on !auth_plugin, which covers all four dispatcher states; the propAxis header comment matches the hyphen fold; and the keep.d / uci-defaults wording now uses the "Appearance -> Wallpaper -> File" spelling that fs-appearance.js actually builds.

The new work is a router-side webfont opt-in (font_sans / font_mono / fonts, the /etc/footstrap/fonts symlink and keep.d entry, a conditional fonts/fonts.css link and an unlayered <style>) plus one CSS narrowing. Two things I checked and found sound, since they are the parts most likely to be wrong: /etc/footstrap/fonts works as a directory in keep.d — sysupgrade's list_static_conffiles() passes the keep.d lines to find … \( -type f -o -type l \), which recurses — and the new <style data-fs-shell> cannot be mistaken for a view sheet or disturb the layer order, because VIEW_SHEETS exempts [data-fs-shell] on style as well as link and both new tags sit after the cascade.css anchor reassertLayerOrder() measures against. The .cbi-page-actions .cbi-dropdown:not(.btn):not(.cbi-button) narrowing also lines up with the real markup: luci.js:2197-2207 gives the Save & Apply combo cbi-dropdown btn cbi-button cbi-button-apply, so the reset no longer flattens it in the Apply unchecked state where .cbi-button-apply has dropped off.

One finding and two nits inline. CI on 247891c is green across all eight checks (three FormalityCheck runs, eslint, CodeQL, build / Test x86_64), so nothing here is CI-grounded.

Commit checks

  • 247891c "luci-theme-footstrap: add new theme" — the message's font paragraph is now a description of only half of what the diff does. It says "The theme carries no webfonts: the font stacks name Manrope and JetBrains Mono first and the system stack after, so a machine with either installed uses it and one without falls through silently" — still true of the package contents, but this revision adds a router-side opt-in on top of that fallback: three new UCI options in footstrap.settings (font_sans, font_mono, fonts) read by header.ut/head.ut, a new /www/luci-static/footstrap/fonts symlink created by uci-defaults, a new keep.d path, and a conditional @font-face stylesheet link. A UCI contract and a new sysupgrade-preserved path are the kind of thing worth a sentence in the message, since the message is what a bisect or a git log search lands on. (The .cbi-page-actions .cbi-dropdown narrowing is a detail, not worth a line.)

Generated by Claude Code

Comment thread themes/luci-theme-footstrap/root/etc/uci-defaults/30_luci-theme-footstrap Outdated
Comment thread themes/luci-theme-footstrap/htdocs/luci-static/resources/fs-sheets.js Outdated
Comment thread themes/luci-theme-footstrap/ucode/template/themes/footstrap/partials/head.ut Outdated
@feckert

feckert commented Aug 5, 2026

Copy link
Copy Markdown
Member

If a new theme is added, I would drop at least the 2020, but maybe also the legacy theme, which is likely not used by many.

(Personally I use bootstrap, which works well)

I feel exactly the same way.

If a modern upstream gets merged I will use this instead of my current used theme https://github.com/jerrykuku/luci-theme-argon

@VizzleTF Unless this is just a one-time "fire-and-forget" operation. Then I would say @jow- 👍

VizzleTF added a commit to VizzleTF/luci-theme-footstrap that referenced this pull request Aug 5, 2026
font_sans and font_mono are printed unescaped into one <style>, and the
charset whitelist has to admit both quote characters -- the shipped default
is `"Manrope",system-ui,...`. A quote does not END anything, it EXTENDS:
`font_sans=My"Font` opens a CSS string that runs to the end of the sheet and
swallows `; --fs-font-mono: ...; }` with it, so a router that set only the
mono font correctly lost it because of a typo in the other option.

Measured on owrt2512 with that value in place. Before: the element renders
`:root {--fs-font-sans: My"Font; --fs-font-mono: JetBrains Mono; }` as one
open string and neither property survives. After: the malformed value is
refused and `--fs-font-mono: JetBrains Mono` is printed alone, while
`"Manrope",system-ui` still renders in full. head.ut compiles under
`ucode -T -c` on all four dev routers and the behaviour was checked on both
package managers.

Also in this commit, from the same review round on openwrt/luci#8903:

* the webfont comments say what the package does -- read the three uci
  options, serve /etc/footstrap/fonts, keep it across sysupgrade -- and where
  the one-line installer lives, since it is in this repository and not in the
  package: it names third-party font hosts, which the theme must not carry.
  The manual path is spelled out beside it.
* fs-sheets.js's ownership example is corrected against the in-tree app: only
  HexEditor's injector runs at module eval, the view's own sheet comes from
  render(), and what makes either invasive is the bare selectors, not the
  ones pinned to #file-manager-container.
@jow-

jow- commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

@jow- Have you formed an opinion about the theme universe's future?

Not fully, but my current reasoning is roughly:

  • bootstrap & footstrap are somewhat close design-wise, so eventually it might make sense to supersede the former with the latter, but only after some settling phase and not immediately, given that bootstrap was the year long default
  • classic OpenWrt is antique and probably should be dropped but I suspect it has a larger user base than 2020
  • OpenWrt 2020, while more modern, feels unpolished by todays standards, receives care-only maintenance and its main advantage, the better mobile friendliness, is adequately covered by modern bootstrap and this new footstrap theme, while it's CI (the blue/grey color schema modeled after the OpenWrt logo rework) could be implemented in terms of a color palette choice on top of footstrap

So I guess it's 60% drop 2020, 40% drop OpenWrt classic for me. In the end I only use bootstrap, and bootstrap was the only theme I seriously spent effort in, so I am not emotionally attached to either 2020 or classic.

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the force-push 0b99fa495a3d8730. Same base (1c70b2b), one commit. The message gained a paragraph on scroll anchoring and changed "its app icons" to "one app icon"; both describe hunks that are in the diff, so no commit-check notes. The delta is 282 added / 149 removed across eleven files.

All three threads from the last round are addressed. tokenPad is gone and git grep tokenPad now returns nothing in the package, with contentPad left carrying the fallback role on its own. --fs-content-max is in the model: the token resolves to 1280px, .fs-content is box-sizing: border-box (the * reset in @layer base), so max-width includes the gutter and Math.min(outerW - cut, contentMax) - contentPad is the right arithmetic — the old form answered ~2280 for a 1224px column at 2560px. And the router's contract-break line now names a URL that resolves from inside openwrt/luci.

The cascade.css delta is worth saying explicitly: I diffed it declaration by declaration, and every one of the 144 changed lines is 1px solid var(--fs-border)var(--fs-hairline) or the field transition pair → var(--fs-field-transition). Nothing else moved, including the overflow-anchor: none on .table.fs-dt that the new anchoring code depends on.

The anchoring rewrite holds up on its two load-bearing choices. Asking CSS.supports('overflow-anchor', 'auto') rather than a browser name is the right shape, and the localStorage.fsEngineAnchor switch is what makes the WebKit branch reachable without a Mac. The hit test is a real improvement over the frame walk for the reason given — a poll tick that grows something inside the frame the fold cuts through leaves that frame's own top at drift 0 — and the [data-fs-chrome] step down past the sticky bar is safe on the elements that carry the mark: .fs-pattern is pointer-events: none, .fs-skip:not(:focus) is a 1×1 clip at left: 8px and #fs-nav-progress is pointer-events: none, so none of them can swallow the hit test at x = the centre of #view; in the desktop sidebar layout nav.fs-sidebar is position: static beside .fs-main, so it is never under that x either. _rest cannot leak a drifted reference into a correction: run() writes it before scheduleAnchor() sets _anchorPending, but a second mutation batch in that window is discarded by scheduleAnchor's own if (_anchorPending) return, and the rAF re-takes it after applyAnchor(). The > innerHeight + 200 clamp is the right guard for a subtree replacement.

The icon consolidation checks out: app-icon-512.png is a valid 512×512 PNG (indexed, 4679 bytes, down from 15709), nothing in the Makefile or root/ enumerates the two deleted files, and manifest.json still parses with one any maskable entry — which clears Chromium's installability bar, since that asks for one icon of 144px or larger rather than for a 192/512 pair. Pointing apple-touch-icon at the same 512 raster is fine; iOS scales it and never reads the manifest's array.

One finding and one nit inline, neither a merge blocker. The finding is the one worth a look: moving fs-overview out of the prologue also moved the guarantee that its three template globals exist before a view can render, and three comments in the tree still state that guarantee as a fact.

CI on 5a3d8730 is green across all eight checks, so nothing here is CI-grounded.


Generated by Claude Code

Comment thread themes/luci-theme-footstrap/htdocs/luci-static/resources/fs-chrome.js Outdated
@VizzleTF
VizzleTF force-pushed the luci-theme-footstrap branch from 5a3d873 to 3dd9f2d Compare August 19, 2026 22:39

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the force-push 5a3d87303dd9f2de. Same base (1c70b2b), one commit, message byte-identical and still matching the diff, so no commit-check notes. The delta is 210 added / 71 removed across six files, and it is two themes: the last round's ordering finding, and a second front against the poll tick moving the page under the reader.

The ordering finding is addressed at the cause. The three template globals now live in menu-footstrap-common.js, which the footer requires on every page and which evaluates them at module scope — before wirePageModules() can require fs-overview and before the chrome wires the router, so the guarantee that progressbar/renderBox/renderBadge exist before any view can render is back, and it no longer depends on which of two require() chains wins. The typeof guards still make a full page load a no-op, so the template's own copies keep winning where they exist. fs-menutree.js and fs-router.js were repointed for the move; the file header of fs-overview.js was not (nit inline).

measureShell()'s third key term is right and the reasoning for it is the file's own: .fs-content carries no chrome mark, sheets.scopeToCurrentPage() flips foreign sheets on and off on every client navigation, and neither the width nor the density moves when it does — so a memo keyed on those two alone really would stay pinned at the previous page's gutter. body[data-page] is the one attribute the router always restamps, and it restamps it before the staged render, so the term is stamped by the time anything reads the memo on the incoming page.

Two findings and one nit inline. The one worth a look before merge is the by branch: the clamp receipt is guarded by a page stamp the router writes after it resets the scrollers, and across a promise boundary, so the forward-navigation reset sits inside the window where a clamp and a reset look identical. The other is swapContent(), which measures a height and restores the pin in the same statement sequence — no layout ever runs with the pin applied, so it cannot be doing the work its comment credits it with.

CI on 3dd9f2de: three FormalityCheck runs completed green, the rest not yet reported. Nothing failed, so nothing here is CI-grounded.


Generated by Claude Code

Comment thread themes/luci-theme-footstrap/htdocs/luci-static/resources/fs-overview.js Outdated
Comment thread themes/luci-theme-footstrap/htdocs/luci-static/resources/fs-fit.js
Comment thread themes/luci-theme-footstrap/htdocs/luci-static/resources/fs-overview.js Outdated
@VizzleTF
VizzleTF force-pushed the luci-theme-footstrap branch from 3dd9f2d to 407c9b7 Compare August 20, 2026 06:10
A standalone theme for OpenWrt 24.10 and newer. It ships no CSS framework
and depends on nothing but luci-base: the package is the server chrome
(ucode templates), one stylesheet and a set of theme JS modules, while
every page is still rendered by luci-base's own view JS.

One chrome morphs between a collapsible sidebar and a top bar, chosen by
the user and remembered client-side. Light and dark modes, a palette and a
density axis are applied before the first paint, so a reload never flashes
the previous colours. A menu search covers the rendered tree, and a
client-side router swaps the view without a full document load, refusing
to reuse a document an app's own stylesheet has invaded.

Third-party luci-app-* packages are the constraint it is built around: the
chrome is namespaced (fs-*, --fs-*, [data-fs-chrome]) and is defended by
not matching an app's selectors, while .cbi-* and #view stay shared
surfaces an app is entitled to win on. The theme's own settings page is
drawn with stock .cbi-value rows, ui.Select and ui.RangeSlider.

The stylesheet is generated from a layered source kept in the theme's own
repository and committed here as one file, the way the other themes carry
theirs. The theme carries no webfonts: the font stacks name Manrope and
JetBrains Mono first and the system stack after, so a machine with either
installed uses it and one without falls through silently.

A router may name fonts of its own on top of that fallback, through three
UCI options in footstrap.settings that the templates read back: font_sans
and font_mono are font-family stacks printed into an unlayered <style>,
and `fonts` is the md5 of an @font-face sheet the admin installs under
/etc/footstrap/fonts, which uci-defaults links under /www and keep.d
preserves across sysupgrade. The package installs no faces and fetches
nothing at run time; it only serves what is already there.

A web manifest and one app icon ship with the theme, so the interface can
be installed to a phone's home screen. Both are static files — a theme may
not register a dispatcher node — which fixes start_url at uhttpd's default
/cgi-bin/luci/ and the splash colour at the default palette's; the manifest
is served as .json because uhttpd's compiled-in MIME map has no entry for
.webmanifest.

The client router looks up every luci-base surface it calls before it wires
anything, and on a miss logs which name is gone and stays off, leaving the
plain server-dispatched interface rather than a half-working client one.

Where the browser has no scroll anchoring of its own, the theme keeps the
reader's place itself: a poll tick that changes the height of anything
above the reader moves the page under them on WebKit, which never
implemented it. The capability decides, never a browser name, so where the
engine already anchors, this stays out of the way and nothing is corrected
twice. The two page-specific modules are required on the pages that use
them rather than on every page.

Signed-off-by: Ivan Kvashonkin <vizzlef@gmail.com>
@VizzleTF
VizzleTF force-pushed the luci-theme-footstrap branch from 407c9b7 to f3f0ab7 Compare August 20, 2026 11:10
@hnyman

hnyman commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

@jow-

Based on your comment:

So I guess it's 60% drop 2020, 40% drop OpenWrt classic for me. In the end I only use bootstrap, and bootstrap was the only theme I seriously spent effort in, so I am not emotionally attached to either 2020 or classic.

I suggest that we

  • merge this new theme as it is, just to get the feedback from wider user populace.
  • if there is no major negative feedback (or the needed changes are fixed/implemented quickly), we could at end of September remove the 2020 theme and make this to PROVIDE it.
  • I would also remove the ancient OpenWrt theme at that time, as Bootstrap has been the default now for so many years, that you really need to be old-school to have seen that as natural.

Ps. The AI based tweaking seems to be continued forever, so getting all minor points addressed like a never-ending story. Let's merge it now.

@VizzleTF

VizzleTF commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

The AI based tweaking seems to be continued forever

@hnyman i got reviews by users and fix small SPA bugs on some browsers (safari, firefox) - thats why its looks like that.
But i guess it could be merged like this

@VizzleTF

Copy link
Copy Markdown
Contributor Author

button pointed to the right

@castillofrancodamian did it

@hnyman
hnyman merged commit 6f08de7 into openwrt:master Aug 20, 2026
8 checks passed
@hnyman

hnyman commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

I merged this in.
Let's see, what feedback you get (and if there are bugs not found so far).

@hnyman

hnyman commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

@VizzleTF
One wish for further development, regarding the package infra:

Your current uci-defaults file is quite verbose and makes lots of cleanup actions based on assumption that there might have been old Footstrap versions.

In the next few weeks (by mid-September or so), you should clean up the uci-defaults file in the main LuCI repo, and start from assumption that the users of the officially downloadable package have started from the current now published first version. There may naturally be a few old initial early adopters / test users, but even most of them will have upgraded to the new "official" version by then.
(And in general, OpenWrt pretty much starts from the assumption that users rather sysupgrade the whole firmware image than upgrade individual packages. So cleanup is more limited to config than to removing old left-overs, as they usually disappear in sysupgrade in any case.)

So, please cleanup and shorten the uci-defaults file to be more in line with the other themes, and remove/shorten most of the verbose commentary.

hnyman pushed a commit that referenced this pull request Aug 21, 2026
luci-theme-openwrt-2020 is one colour scheme and no dark mode: the CI cyan
#00B5E2 on the navy #002B49, white content, and #5CB85C / #CC8800 /
#CC1111 for the semantic three. This adds that identity to footstrap as a
palette, which is the form suggested in the discussion on #8903 for
keeping the look if the theme itself is retired.

The pair splits the work along the reason it exists. Dark IS 2020's own
scheme — the navy is the canvas and the cyan is nearly untouched. Light
keeps the hue and darkens it, because #00B5E2 measures 2.09:1 on white,
and every level of this theme's exported colour tier has to pass WCAG AA
as text on all three surfaces: apps read those names as color: about as
often as background:. The semantic three move for the same reason — on
white 2020's green is 2.28:1 and its amber 3.42:1 — while its red at
5.25:1 is kept where it was.

The palette is registered in the four places the theme requires: the
palette axis in fs-prefs.js, the server-default whitelist and the
pre-paint switch in partials/head.ut, and the label in fs-appearance.js.
It is opt-in through Appearance; nothing changes for anyone who does not
pick it.

Contrast was measured across the whole matrix rather than by eye: 56
palette x mode x tint combinations, 4352 checks, every level clear of AA
on background, panel and panel2, and every ink clear on the fill it sits
on. axe-core reports no serious or critical violations on the widget
gallery in either mode.

Signed-off-by: Ivan Kvashonkin <vizzlef@gmail.com>
Tubetrue01 pushed a commit to Tubetrue01/luci that referenced this pull request Aug 21, 2026
luci-theme-openwrt-2020 is one colour scheme and no dark mode: the CI cyan
#00B5E2 on the navy #002B49, white content, and #5CB85C / #CC8800 /
#CC1111 for the semantic three. This adds that identity to footstrap as a
palette, which is the form suggested in the discussion on openwrt#8903 for
keeping the look if the theme itself is retired.

The pair splits the work along the reason it exists. Dark IS 2020's own
scheme — the navy is the canvas and the cyan is nearly untouched. Light
keeps the hue and darkens it, because #00B5E2 measures 2.09:1 on white,
and every level of this theme's exported colour tier has to pass WCAG AA
as text on all three surfaces: apps read those names as color: about as
often as background:. The semantic three move for the same reason — on
white 2020's green is 2.28:1 and its amber 3.42:1 — while its red at
5.25:1 is kept where it was.

The palette is registered in the four places the theme requires: the
palette axis in fs-prefs.js, the server-default whitelist and the
pre-paint switch in partials/head.ut, and the label in fs-appearance.js.
It is opt-in through Appearance; nothing changes for anyone who does not
pick it.

Contrast was measured across the whole matrix rather than by eye: 56
palette x mode x tint combinations, 4352 checks, every level clear of AA
on background, panel and panel2, and every ink clear on the fill it sits
on. axe-core reports no serious or critical violations on the widget
gallery in either mode.

Signed-off-by: Ivan Kvashonkin <vizzlef@gmail.com>
@PalebloodSky

PalebloodSky commented Aug 21, 2026

Copy link
Copy Markdown

@VizzleTF clicked back through all pages on v0.14 and few comments. First some compliments: love the Status -> Overview layout. Love the "Port status" icons just being green lights rather than icons. Love the OpenWrt 2020 colorway option as homage.

Just a few suggestions for improvement (observed using Chrome 151):

  1. Status -> Channel Analysis: "Refresh Channels" button spacing is a bit too close to "Channel Analysis" title.
  2. System -> System -> Footstrap tab: if you change the Palette and click save, the Footstrap tab disappears until you click System again to refresh it.
  3. Services -> irqbalance (requires luci-app-irqbalance): space between /proc/interrupts output is too close to "General settings" compared to Bootstrap. Not sure if it's your theme or the luci app.
  4. Network -> SQM QoS (requires luci-app-sqm): space between the "Delete" button on the right and the tabs below is too close compared to Bootstrap. Again, not sure if it's your theme or the luci app.

Fun idea if you get time: "Forum" palette, which would match the openwrt forum colorway (e.g. blue bar at the top, dark grey bg, white text, etc).

Again, congrats on the merge! Take care.

@VizzleTF

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed list — went through all four on a test router.

1 and 4 were mine and are fixed — both on luci-base classes, so every app gets it.
2 I could not reproduce on 25.12 with Chrome 151 — which release and router are you on?
3 the irqbalance page renders nothing at all on my stand, so I can't measure it.

For anything further: either here, tagging me, or — preferred — an issue at https://github.com/VizzleTF/luci-theme-footstrap/issues

@hnyman

hnyman commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Firefox shows this for Irqbalance:
no space after the irq proc listing, before the next sections starts.
Deviation from all other sections on the page:

image

@VizzleTF

Copy link
Copy Markdown
Contributor Author

Deviation from all other sections on the page

Because it has no section ( bare ui.Textarea)
Bootstrap same 0px there, but a section is a card in footstrap, so it reads as two cards fused.

Give any block 16px gap in .cbi-map - will be delivered in next PR (or 0.14.2 from my repo)

@hnyman

hnyman commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Because it has no section ( bare ui.Textarea)

If you feel that the more correct fix would be to fix the irqbalance app itself to have a section around the textarea, please propose it in the same PR in a separate commit. Not all LuCI apps are perfect...

@VizzleTF

VizzleTF commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Not all LuCI apps are perfect

Thats why i decide to work with that in theme, instead of

fix the irqbalance app itself

PS i also check how its look in section - and its just add "more air" instead of smthn usefull for users.
image
image

@VizzleTF

Copy link
Copy Markdown
Contributor Author

@hnyman if you like it i can send it:
before:
image

and after:
image

@PalebloodSky

PalebloodSky commented Aug 24, 2026

Copy link
Copy Markdown

@VizzleTF I installed v0.14.2, issues 1, 3, and 4 are resolved, nice fixes! Having the router hostname in the browser tab is a great addition too.

Regarding my issue 2 with the button disappearing - it's still happening, even in Incognito (clean browser cache). Strangly it doesn't always disappear, only sometimes.

Router: GL-MT6000
OpenWrt: 25.12.5
Browsers: Chrome 151 on Win11 PC and an iPhone iOS 26 w/Chrome (which uses Webkit on iOS). Both have the issue.

Attempt to reproduce by going to: System -> System -> Footstrap: change the Palette or Density from dropdown then click Save (not Save & Apply, just Save), the page refreshes then the Footstrap tab button is gone, until you click System again to refresh it.

Example below: iPhone, using 2020 palette, I switch to Bootstrap palette from the dropdown, hit Save at the bottom, and then then Footstrap button disappears:

IMG_4303 IMG_4304

@hnyman

hnyman commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Regarding the irqbalance and your comment:

if you like it i can send it:

Please place the commit somewhere available, or just do a separate PR about it, so that I could test your idea. The commit(s) obviously develop the app from the current status, by splitting the IRQs table by IRQ nature, right?

VizzleTF added a commit to VizzleTF/luci that referenced this pull request Aug 25, 2026
Saving on System -> System redraws the map: the old tab group goes, a
new one
is built, and ui.tabs marks it ready by setting an attribute. The theme's
observer watched childList only, so when that attribute landed after the
last
change to the group's children, the mount had already returned with no
group
to attach to and nothing woke it again -- the tab was missing until the
next
navigation.

Reported on 25.12.5 from Chrome and from iOS as "sometimes it disappears
after
Save" (openwrt#8903). Modelled as a deterministic race: remove the
pane,
drop data-initialized, restore it 400ms later with no other mutation. Three
failures out of three before, three passes out of three after, on 25.12 and
24.10. The observer now watches that attribute, and a miss retries on a
widening delay, since the attribute alone would still rely on ui.tabs
setting
it that way.

The sliders on that tab also drop a calcunits option they passed to
ui.RangeSlider: the widget renders that span only when a calculate function
produced a value, and no axis here passes one.

Signed-off-by: Ivan Kvashonkin <vizzlef@gmail.com>
VizzleTF added a commit to VizzleTF/luci that referenced this pull request Aug 25, 2026
Saving on System -> System redraws the map: the old tab group goes, a
new one
is built, and ui.tabs marks it ready by setting an attribute. The theme's
observer watched childList only, so when that attribute landed after the
last
change to the group's children, the mount had already returned with no
group
to attach to and nothing woke it again -- the tab was missing until the
next
navigation.

Reported on 25.12.5 from Chrome and from iOS as "sometimes it disappears
after
Save" (openwrt#8903). Modelled as a deterministic race: remove the
pane,
drop data-initialized, restore it 400ms later with no other mutation. Three
failures out of three before, three passes out of three after, on 25.12 and
24.10. The observer now watches that attribute, and a miss retries on a
widening delay, since the attribute alone would still rely on ui.tabs
setting
it that way.

The sliders on that tab also drop a calcunits option they passed to
ui.RangeSlider: the widget renders that span only when a calculate function
produced a value, and no axis here passes one.

Signed-off-by: Ivan Kvashonkin <vizzlef@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

add package Introduces a new package Makefile build script

Projects

None yet

Development

Successfully merging this pull request may close these issues.