From 646369826cc595e0d42e6fe7058243598f03f0c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Apr 2026 08:08:56 +0000 Subject: [PATCH] [docs](trx-rs): add frontend styling & performance improvement analysis Comprehensive audit of the trx-frontend-http web UI covering CSS performance (backdrop-filter, color-mix, theme duplication), JavaScript patterns (monolithic app.js, innerHTML usage, render path efficiency), HTML structure, responsive design, accessibility, and server-side delivery. Prioritised recommendations from quick wins to longer-term architectural changes. https://claude.ai/code/session_01M4zemxk7J2Uu7CcNkrgERD Signed-off-by: Claude --- doc/frontend_improvements.md | 347 +++++++++++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 doc/frontend_improvements.md diff --git a/doc/frontend_improvements.md b/doc/frontend_improvements.md new file mode 100644 index 0000000..8e0f34d --- /dev/null +++ b/doc/frontend_improvements.md @@ -0,0 +1,347 @@ +# Frontend Styling & Performance Improvements + +*Analysis date: 2026-04-01* + +This document captures observations and improvement recommendations for the +trx-rs web frontend (`trx-frontend-http`). The frontend is a single-page +application served as embedded static assets (gzip-compressed with ETag +caching) from the Actix-Web server. + +## Current asset inventory + +| File | Lines | Size | +|------|------:|-----:| +| `style.css` | 5,318 | 144 KB | +| `app.js` | 11,928 | 428 KB | +| `index.html` | 1,564 | 96 KB | +| `webgl-renderer.js` | 526 | 20 KB | +| `decode-history-worker.js` | 176 | 8 KB | +| `leaflet-ais-tracksymbol.js` | 120 | 8 KB | +| 15 plugin scripts | 7,360 | 304 KB | +| **Total** | **~27,000** | **~1 MB** | + +All assets are pre-compressed with `flate2` (gzip, `Compression::best()`) and +served with `ETag` + `If-None-Match` support for conditional requests. The +Actix `Compress` middleware handles dynamic responses. + +--- + +## 1. CSS observations + +### 1.1 Monolithic stylesheet (P1) + +`style.css` is a single 5,318-line file covering every tab, theme, responsive +breakpoint, map overlay, decoder UI, scheduler, recorder, and settings panel. +Browsers must parse the entire stylesheet before first paint even though most +users only interact with 1-2 tabs at a time. + +**Recommendations:** +- Split into logical partitions: `base.css` (variables, reset, layout), `tabs/*.css` (per-tab styles), `themes/*.css`. The server can concatenate and compress at build time. +- At minimum, move the theme colour blocks (lines 3770-5318, ~1,550 lines / 29% of the file) into a separate `themes.css` loaded asynchronously after initial paint, since the default theme is already in `:root`. +- Consider using `@layer` (CSS Cascade Layers) to manage specificity between base, component, and theme styles, eliminating the need for `!important` (currently 21 occurrences). + +### 1.2 `backdrop-filter` overuse (P1) + +There are 26 `backdrop-filter` declarations (13 pairs with `-webkit-` prefix). +`backdrop-filter: blur()` is one of the most expensive CSS properties -- it +forces the browser to composite, rasterize, and blur everything behind the +element on every frame. + +Affected areas: tab bar, controls tray, frequency overlay, modals, connection +banner, bottom nav, neon-disco theme overlay. + +**Recommendations:** +- Remove `backdrop-filter` from elements that are always opaque or rarely overlap dynamic content (e.g. bottom tab bar over static background). +- For the spectrum/waterfall overlay controls, use a solid semi-transparent `background` instead of blur -- the visual difference is negligible on a dark spectrogram. +- Where blur is desired (modals), use `will-change: backdrop-filter` and keep blur radius low (4-6px instead of 12-18px). Larger radii are proportionally more expensive. +- Gate expensive blur behind a `@media (prefers-reduced-motion: no-preference)` query or a `[data-effects="full"]` attribute so low-end devices can opt out. + +### 1.3 `color-mix()` usage (P2) + +184 occurrences of `color-mix(in srgb, ...)` throughout the stylesheet. While +`color-mix` is well-supported in modern browsers, each call is resolved at +computed-value time. Repeated identical mixes (e.g. button hover states +repeated across themes) add unnecessary style recalculation cost. + +**Recommendations:** +- Pre-compute frequently used mixes as CSS custom properties in the theme blocks (e.g. `--btn-hover-bg`, `--btn-active-bg`). +- This reduces computed-value work and also makes the palette more explicit and maintainable. + +### 1.4 Theme system duplication (P2) + +Each of the 10 colour themes repeats ~28 variable declarations for both dark +and light mode (560 variable declarations total). The theme blocks span lines +3770-5318 (29% of the entire stylesheet). + +**Recommendations:** +- Move themes to a separate file loaded after first paint (the default `:root` theme is always available). +- Consider generating theme CSS from a data source (JSON/TOML) at build time to reduce manual duplication. +- Use `color-scheme` and `light-dark()` (CSS Color Level 5) to collapse the dark/light pairs where values differ only in lightness. + +### 1.5 Transitions on non-essential properties (P3) + +25 `transition` declarations, several targeting `background`, `border-color`, +and `box-shadow` simultaneously. Multi-property transitions on buttons and +inputs cause style recalculation on hover/focus for every such element. + +**Recommendations:** +- Prefer transitioning only `opacity` and `transform` (GPU-composited). +- For colour changes, use `transition: background-color 100ms` rather than the shorthand `background` which also transitions `background-image` and other sub-properties. +- Add `will-change: transform` only to elements that are actively animating (currently only 2 occurrences, which is good). + +### 1.6 Missing `contain` declarations (P2) + +Tab content panels, decode history tables, map containers, and spectrum +canvases do not use CSS `contain` or `content-visibility`. When a large decode +history table updates, the browser recalculates layout for the entire page. + +**Recommendations:** +- Add `contain: content` to inactive tab panels (`[data-tab]:not(.active)`). +- Add `content-visibility: auto` with `contain-intrinsic-size` to off-screen panels (decode history, map, statistics). This lets the browser skip rendering for hidden content entirely. +- Add `contain: strict` to the spectrum/waterfall canvas containers since their size is fixed and they don't affect sibling layout. + +--- + +## 2. JavaScript observations + +### 2.1 Monolithic `app.js` (P1) + +The main application script is 11,928 lines (428 KB uncompressed). It is loaded +synchronously in the HTML `` (via embedded asset), blocking first paint +until fully parsed and executed. The 15 plugin scripts add another 7,360 lines. + +**Recommendations:** +- Mark the script tag `defer` or move it to end of `` so HTML parsing completes before script execution. +- Split `app.js` into logical modules: `core.js` (SSE, auth, render loop), `spectrum.js`, `map.js`, `decoder.js`, `recorder.js`, `settings.js`. Load non-critical modules lazily when the user navigates to the corresponding tab. +- Use ES modules (`type="module"`) for clean dependency management and tree-shaking potential. + +### 2.2 DOM query overhead (P2) + +The codebase contains ~359 `querySelector`/`getElementById` calls, many of +which execute on every SSE event (inside `render()`). DOM lookups are not free, +especially `querySelector` with compound selectors. + +**Recommendations:** +- Cache DOM references at initialization time (many already are, but the render path still re-queries elements like `document.getElementById("tab-main")`). +- Move repeated lookups (e.g. line 3575 `document.getElementById("tab-main")` inside `es.onmessage`) to module-level constants. + +### 2.3 `innerHTML` usage (P2) + +33 `innerHTML` assignments in `app.js` and 72 across plugin scripts. Each +`innerHTML` write forces the browser to: +1. Serialize the old DOM subtree for GC +2. Parse the HTML string +3. Build and insert a new DOM subtree + +This is both a performance concern (layout thrashing) and a security concern +(XSS if any user-controlled data is interpolated without escaping). + +**Recommendations:** +- Replace `innerHTML` with DOM APIs (`createElement`/`appendChild`) or `DocumentFragment` for bulk updates (only 4 `createDocumentFragment` uses currently). +- For large lists (decode history, bookmarks, recorder file lists), use a virtualised list pattern that only renders visible rows. +- Where `innerHTML` is used to clear a container, prefer `replaceChildren()` (clears children without HTML parsing). + +### 2.4 SSE render path efficiency (P2) + +Every SSE state event triggers `render(update)` which is a ~300-line function +touching dozens of DOM elements. The function does not diff -- it +unconditionally sets properties even when values have not changed. + +The string-equality guard (`if (evt.data === lastRendered) return`) is a good +optimisation for identical payloads, but when any field changes (e.g. S-meter +value), the entire render function runs. + +**Recommendations:** +- Implement field-level diffing: compare individual fields against previous values and only update DOM elements whose backing data changed. +- Group updates by tab: if the user is on the "Map" tab, skip render work for "Main" tab elements (meters, frequency display, controls). +- Use `scheduleUiFrameJob()` (already exists at line 3685) more aggressively to batch DOM writes into animation frames. + +### 2.5 Spectrum/waterfall rendering (P2) + +The WebGL renderer (`webgl-renderer.js`) is well-implemented with proper +shader programs and batched draws. However: +- The CSS colour parsing (`parseCssColor`) uses a DOM probe element (appended to + body) and `getComputedStyle` as a fallback, which triggers layout. +- The colour cache is a simple `Map` with no eviction policy. + +**Recommendations:** +- Parse theme colours once when the theme changes, not on every frame. +- Invalidate the `cssColorCache` on theme switch events. + +### 2.6 Plugin script loading (P3) + +All 15 plugin scripts are loaded eagerly in `index.html` regardless of which +decoders are active. Plugins like `ais.js`, `vdes.js`, `sat.js`, +`sat-scheduler.js`, and `hf-aprs.js` are only relevant for specific use cases. + +**Recommendations:** +- Load plugin scripts on demand when the corresponding decoder or feature is activated. +- Use dynamic `import()` if migrated to ES modules, or lazy `