diff --git a/luci-theme-aurora/.claude/skills/aurora-performance/SKILL.md b/luci-theme-aurora/.claude/skills/aurora-performance/SKILL.md new file mode 100644 index 00000000..92069ca7 --- /dev/null +++ b/luci-theme-aurora/.claude/skills/aurora-performance/SKILL.md @@ -0,0 +1,61 @@ +--- +name: aurora-performance +description: Use when developing an OpenWrt LuCI theme — editing ucode (.ut) templates, adding or loading CSS/JS/font/image assets, writing CSS animations or menu/drawer interactions, reviewing a theme PR for performance, or measuring TTFB, transfer size, memory, or dropped frames on router-served pages. +--- + +# Aurora Theme Performance + +## Overview + +Two machines are involved, and they have almost nothing in common. The +**server** is the router: 1–2 slow cores, 64–512 MB RAM, squashfs flash, a +single-process `uhttpd` with no dynamic compression — every cycle you spend +there repeats on every page view. The **client** is the browser, usually a +phone on the router's own WiFi, doing the actual painting and animating. Both +planes have budgets, and "it feels fine on my laptop" proves nothing on +either of them. Every optimization claim needs a measurement, not a +rationalization — "consistent with the existing pattern" is not a cost +justification. + +## When to use + +- Editing a `.ut` template, or anything that adds a `ubus`/`uci`/`fs` call +- Adding or changing what loads in `
` — CSS, JS, fonts, images +- Writing a CSS animation, transition, or JS-driven interaction +- Reviewing a theme PR for performance impact +- Measuring TTFB, transfer size, memory, or dropped frames + +Not for: pure content/copy changes with no runtime surface (rewording a +label, fixing a typo, adjusting a translation string). + +## The three planes + +| Touching | Read | +|---|---| +| `.ut` templates, ubus/uci/fs calls, shipped file sizes | references/server.md | +| `` links/scripts, assets, caching, compression | references/loading.md | +| CSS animations, JS interactions, transitions | references/runtime.md | +| Verifying/quantifying any of the above | references/measuring.md | + +## Non-negotiables (quick reference) + +- **S1** — Compute at build time, never at request time. → references/server.md +- **S2** — Per-request ubus/uci/fs calls are a budget. → references/server.md +- **S3** — Every byte sent is router CPU. → references/server.md +- **S4** — Memory has a ceiling. → references/server.md +- **L1** — Kill render-blocking requests. → references/loading.md +- **L2** — Download once, cache forever. → references/loading.md +- **L3** — Ship compressed. → references/loading.md +- **R1** — Animate compositor properties only. → references/runtime.md +- **R2** — JS must not force synchronous layout. → references/runtime.md +- **R3** — Accessibility is functionality. → references/runtime.md + +## Budgets & ledger + +The reference files above are generic to any LuCI theme. The concrete numbers +for THIS theme — budget table, optimization ledger, accepted exceptions — live +in `references/aurora-budgets.md`. Consult it before declaring a budget met or +re-proposing a previously rejected optimization. The measured baselines backing +those numbers sit in `baselines/` (git-ignored: they record device model and +LAN address, so they stay local). Porting this skill to another theme means +swapping `references/aurora-budgets.md` and `baselines/`. diff --git a/luci-theme-aurora/.claude/skills/aurora-performance/references/aurora-budgets.md b/luci-theme-aurora/.claude/skills/aurora-performance/references/aurora-budgets.md new file mode 100644 index 00000000..ac325352 --- /dev/null +++ b/luci-theme-aurora/.claude/skills/aurora-performance/references/aurora-budgets.md @@ -0,0 +1,77 @@ +# Performance — aurora budgets & ledger + +Methodology lives in this skill's reference files (`server.md`, `loading.md`, +`runtime.md`, `measuring.md`, alongside this file). This file holds what is +specific to THIS theme: budgets, the optimization ledger, and accepted +exceptions. The measured baselines backing the numbers live in the skill's +`baselines/` directory (git-ignored — they record device model and LAN +address, so they stay local). + +## Budgets + +| Metric | Budget | Track | Source | +|---|---|---|---| +| main.css (gzip-transferred) | ≤ 30 KB | size | measured 2026-07 (28 KB) | +| Per-page cold transfer (all theme assets, gzip) | ≤ 60 KB | size | sum of current gzip sizes + headroom | +| Blocking requests before first paint | ≤ 4 | count | current waterfall | +| Repeat-visit asset requests | ≈ 0 | count | target state; package-built CSS/JS URLs are versioned, but long-lived cache headers still need live verification | +| TTFB, login page (device) | proposed: ≤ 130 ms | latency | local device baseline, 2026-07 | +| LCP @ 4× CPU + Slow 4G | TBD — fill from baseline | latency | local baseline archive | +| INP @ 4× CPU | TBD — fill from baseline | latency | local baseline archive | +| uhttpd VmRSS during page load | proposed: ≤ 2050 kB | memory | local device baseline, 2026-07 | + +Budget revisions require a new baseline entry under `../baselines/`. + +## Optimization ledger + +### Landed +(compositor animation rework; mega-menu idle pre-measurement; on-demand +patches; `font-display: swap`) + +### Pending +| Item | Principle | Estimated gain | +|---|---|---| +| Precompressed `.gz` assets | S1+L3 | ~260 KB → ~35 KB cold | +| Terser `compress`+`mangle` in `vite.config.ts` | L3 | ~20 KB → ~10 KB | +| Inline `@font-face` + preload woff2 | L1 | −1 blocking RTT | +| SVGO `logo.svg` | L3 | 45 KB → est. < 20 KB | +| Long-lived cache headers for versioned CSS/JS | L2 | after LuCI build-time `?v=$(PKG_VERSION)`, kills per-click 304s if headers permit disk/memory cache reuse | +| `defer` head scripts | L1 | needs on-device timing verification | + +### Notes + +- **LuCI build-time asset versioning** — Source templates may show + `{{ media }}/main.css`, `{{ media }}/login.css`, or + `{{ resource }}/menu-aurora.js` without a query string. When packaged + through LuCI's `luci.mk`, quoted `{{ media }}/... .css` and + `{{ resource }}/... .js` links are rewritten to append + `?v=$(PKG_VERSION)`; for aurora 1.0.7 this yields + `/luci-static/aurora/main.css?v=1.0.7` and + `/luci-static/aurora/login.css?v=1.0.7`. Do not re-propose manual + cache-versioning for these links unless inspecting the installed package + or live HTML proves the rewrite did not happen. + +### Accepted exceptions + +- **`.cbi-progressbar` width transition** — the inner bar's `width` is set via + inline style by LuCI core's `Progressbar` widget, so a `transform: scaleX()` + swap would need a JS observer to mirror that value into a custom property + (plus RTL-aware `transform-origin`). Given the bar updates infrequently + (firmware/package install progress, not a 60fps animation), the single + explicit `transition-[width]` is left as-is rather than adding that + infrastructure. +- **Per-request `lsdir()`** — `header.ut` calls `fs.lsdir()` at render time to + discover installed patches (see the on-demand third-party patches design). + Accepted per S1 because it's a single directory read on an already-dynamic + template render, not a hot loop, and it's what makes patches a drop-in + extension point without a build-time registry. +- **`backdrop-blur` paint flashing** — elements with `backdrop-blur` (mega-menu + panel, modal scrim) **will** show some green flashing while animating — + that's the inherent cost of a blur layer, not a regression. Judge the + **reflow-class** animations (height / shadow) on whether they still flash, + *not* whether blur reaches zero flash. + +## Baselines + +Local baseline reports live in `../baselines/` when present. That directory is +git-ignored because reports include device model and LAN address. diff --git a/luci-theme-aurora/.claude/skills/aurora-performance/references/loading.md b/luci-theme-aurora/.claude/skills/aurora-performance/references/loading.md new file mode 100644 index 00000000..209796e1 --- /dev/null +++ b/luci-theme-aurora/.claude/skills/aurora-performance/references/loading.md @@ -0,0 +1,83 @@ +# The Wire + +Between the router and the browser is a link that is usually the router's +own WiFi radio to a phone — slower and less reliable than a wired desktop +connection. Every request that has to happen before first paint is an RTT +the user watches. The conventions in a typical LuCI theme (a single import +manifest, no bundler) already steer you toward the right structural choice; +the gap is almost always in *quantifying* what you added and *verifying* it +with a waterfall, not in the structural decision itself. + +## L1 — Kill render-blocking requests + +**Why.** Each `` or synchronous `