# The package: source tree, Makefile, install scripts Reference for what is where in `luci-theme-footstrap/`, what the Makefile does that a template theme would not, and what runs on the router at install and removal time. How the packages are actually built and published: [ci.md](ci.md). ## Source tree ``` luci-theme-footstrap/ ├── Makefile ├── build-css.sh styles/ → cascade.css (cat + awk, no node) ├── mangle-tokens.sh shorten the private --fs-* names in a BUILT sheet ├── strip-templates.sh drop the comments from .ut (template and whole-line code) ├── strip-shell.sh drop whole-line # from root/**.sh ├── build-apk.sh SDK build, kept so the theme stays buildable without owfeed ├── dev-sync.sh deploy to a HARDWARE router over ssh (containers use owlab) ├── update-po.sh regenerate/verify the translation catalogue ├── luci-upstream.pin pinned openwrt/luci commit + sha256 of the borrowed tools ├── styles/ CSS SOURCE. Not shipped — luci.mk does not copy it ├── po/ translation catalogue; luci.mk turns it into luci-i18n-* packages │ ├── templates/footstrap.pot │ ├── ru/footstrap.po │ └── es/footstrap.po ├── htdocs/luci-static/ → /www/luci-static/ │ ├── footstrap/ cascade.css (GENERATED, gitignored), logo.svg, │ │ manifest.json + app-icon-192.png (ONE raster: every browser that │ │ installs a page picks the largest icon and downscales, and iOS reads │ │ the apple-touch-icon LINK, which points at the same file) │ │ (the raster is COMMITTED and made by tools/build-icons.mjs — the │ │ buildbot has no browser; `npm run icons` proves it matches logo.svg) │ │ (pattern.svg and fonts/ are NOT here: they are symlinks uci-defaults │ │ makes to /etc/footstrap/, which the admin uploads or installs) │ └── resources/ menu-footstrap.js, menu-footstrap-common.js, fs-*.js ├── root/ → / │ ├── etc/uci-defaults/30_luci-theme-footstrap │ ├── etc/config/footstrap empty stub, written at runtime │ ├── lib/upgrade/keep.d/luci-theme-footstrap what sysupgrade carries across a flash │ └── usr/share/rpcd/acl.d/luci-theme-footstrap.json └── ucode/template/themes/footstrap/ → /usr/share/ucode/luci/template/… ├── header.ut footer.ut sysauth.ut └── partials/{head,brand,logout,notices,notice,search,icon,footer}.ut ``` `luci.mk` installs by directory presence — no install recipes needed: | Source | Installed to | |---|---| | `ucode/*` | `/usr/share/ucode/luci/` | | `htdocs/*` | `/www/` | | `root/*` | `/` | | `root/etc/uci-defaults/*` | picked up as `LUCI_DEFAULTS` | Only `src/ luasrc/ htdocs/ root/ ucode/ po/` are copied verbatim. `styles/` is not in that list, which is why `cascade.css` is generated in `Build/Prepare` straight into the build tree and is absent from git. **Never edit `cascade.css`.** Colours go in `styles/03-palettes.css`, scales and tokens in `styles/02-tokens.css`. See [css.md](css.md). **The web manifest is a static file, and that is a constraint, not a shortcut.** A theme may not register a dispatcher node — it would outlive the theme that registered it — so there is nothing that could render the manifest per request. Two values are fixed by that: `start_url`/`scope` are `/cgi-bin/luci/`, uhttpd's own default, and `background_color`/`theme_color` are the DEFAULT palette's page colour, since the manifest is read once at install time and cannot follow a live Appearance change (it paints the splash and the installed window's chrome, never the page). Chrome's install prompt needs a secure context, so over plain HTTP what this buys is iOS's Add to Home Screen — which reads the `apple-touch-icon` link rather than the manifest — plus the icon itself. Regenerate it with `node tools/build-icons.mjs` whenever `logo.svg` changes; `npm run icons` fails if you forget. The committed file is **quantised to a 32-colour palette**, which is 4.6 KB where the browser's own RGBA screenshot is 15.7 KB: the picture is a flat background, one ink colour and the ramp between them, so a palette is the right encoding and the worst channel moves by 18 of 255 on 0.2% of the pixels. That step is the one thing in this repo that wants **ImageMagick**, and only when regenerating — `npm run icons` compares PIXELS in the browser it already runs, so a different ImageMagick version is not a failure and a redrawn logo still is. Nothing in luci-base could stand in for the icon, which is worth stating: it ships functional glyphs (interfaces, signal bars, ports) and no logo or raster of any kind, and every theme carries its own. ## Makefile: what differs from a template theme ```makefile PKG_NAME:=luci-theme-footstrap LUCI_NAME:=luci-theme-footstrap # pin: luci.mk keys the Build/Prepare hook name on LUCI_NAME, # which defaults to the checkout directory — the CSS build # would silently not run in a renamed checkout FOOTSTRAP_VERSION?= # CI injects it from the tag; locally the version is git-derived LUCI_TITLE:=Footstrap Theme LUCI_DEPENDS:=+luci-base # the WHOLE dependency list LUCI_PKGARCH:=all # noarch: one build for every target LUCI_MAINTAINER / LUCI_URL # otherwise the package claims "OpenWrt LuCI community" LUCI_MINIFY_CSS:=0 # see below PKG_LICENSE:=Apache-2.0 # the theme, and nothing else: it carries no webfonts include $(TOPDIR)/feeds/luci/luci.mk # ABSOLUTE, not ../../luci.mk: CI rsyncs the package into # package/, not into the feed ``` **Do not set `PKG_VERSION`.** `luci.mk` derives it from git; CI injects `FOOTSTRAP_VERSION` from the tag, because an SDK build has no `.git` to derive from. ### Minification: CSS off, JS on two paths Two different tools; confusing them is expensive. - **`LUCI_MINIFY_CSS:=0` is mandatory.** luci.mk's CSS minifier is **csstidy**, old enough to mangle `:has()`, `color-mix()` and nested `calc()`: the package installs and the layout falls apart. `build-css.sh` minifies instead — a string-aware awk pass of its own. - **`LUCI_MINIFY_JS` has two paths.** A release CI build pre-minifies with **terser** (`tools/minify-js.mjs`, which can mangle identifiers — jsmin cannot) and sets `FOOTSTRAP_PREMIN=1`, which turns `LUCI_MINIFY_JS` to `0`; jsmin on top of terser output would reopen the `return /re/` trap on forms terser legitimately emits. A build **without** node (SDK user, buildbot) keeps the default `1`, and jsmin minifies the untouched source. Both paths matter: comments are ~60% of the JS source, and uhttpd serves `/www` **uncompressed**, so those are bytes on the wire and in flash. The source therefore has to stay jsmin-safe — see the regex rule in [conventions.md](conventions.md). ### `Build/Prepare` — six steps, in this order The hook (its name keys on `LUCI_NAME`) runs right after luci.mk copies the sources into `PKG_BUILD_DIR`, and edits the **copy**: 1. **Copy `LICENSE`** into `PKG_BUILD_DIR` — `PKG_LICENSE_FILES` resolves against *that*, and luci.mk does not copy the package root. 2. **`build-css.sh`** → `cascade.css` in the build tree. `cat`/`awk` only, so it runs on the OpenWrt buildbot with no host toolchain. 3. **`mangle-tokens.sh`** — shorten the private `--fs-*` names, 16% of the sheet. **Before** step 4 on purpose: the reserved set is derived by reading the JS and the templates, so it must see them whole — and step 4 is what strips the template comments. It reads them from the **source** tree, never from `PKG_BUILD_DIR` — in CI the build tree's JS has already been through terser, its comments are gone, and five names that only appear in a comment would stop being reserved. That made the shipped sheet depend on *who* built it. 4. **`strip-templates.sh`** — the comments out of the `.ut` files: `{# … #}` template comments, and `/* … */` code comments **that own their lines**, wherever they sit (ucode inside `{% … %}`, JavaScript inside an inline `