Desktop Bottom-Dock Redesign — Unified Meters Dashboard¶
Date: 2026-04-18
Status: Design proposal (no code changes)
Author: UX/UI design pass
Scope: desktop-v2 skin only (amber-lcd and mobile skins untouched)
1. Context¶
The desktop bottom dock (frontend/src/components-v2/layout/RadioLayout.svelte
lines 254–298) currently renders three cards: an RX1 summary
(receiver-summary-card-main), an RX2 summary or an audio FFT scope
(audio-scope-dock-card → AudioSpectrumPanel), and a DockMeterPanel
showing S/SWR/Po with a radio-button toggle. The VFO header already shows
frequency, mode, filter, and a full-width S-meter for both receivers in
~156 px at the top of the layout, so the two summary cards are pure
duplication and the meter panel is undersized and incomplete (no ALC, no
Id/Vd, no TEMP, no peak-hold, no capability gating). The IC-7610 exposes
S, Po, SWR, ALC, COMP, Vd, Id via 0x15-family commands
(src/icom_lan/commands/meters.py), all already surfaced in runtime state
(frontend/src/lib/types/state.ts lines 103–108: compMeter, vdMeter,
idMeter, powerMeter, swrMeter, alcMeter). The dock must become a
proper TX-telemetry and station-health dashboard — TX metering is
life-safety adjacent (SWR spikes, ALC overdrive, final-PA protection) and
must be legible at desk distance without squinting.
Correction to the brief (from user). The widget in the current dock
(AudioSpectrumPanel) is a combined instrument, not just an AF FFT.
It renders, overlaid on a single canvas:
1. AF FFT spectrum (receive audio frequency content).
2. RX filter passband shape (bandwidth + shoulders).
3. PBT (passband tuning) shift indicator.
4. For radios that support them (e.g. FTX-1): notch and contour filter
indicators.
This composite widget is a deliberate design, and it must be preserved as-is. No decomposition into separate panels. The decision below is about where the combined widget lives, not what it shows.
2. Meter Inventory¶
Source field names are from RadioState in
frontend/src/lib/types/state.ts. Availability rows reflect IC-7610
(primary target) and a generic "other radio" column that describes the
capability-gated fallback behavior.
| Meter | Unit | Source | State field | IC-7610 | Other radios | Idle (RX) | Active (TX) |
|---|---|---|---|---|---|---|---|
| S | S-units + dB over S9 | RX | sValue (main), subSValue |
yes | yes (universal) | live, primary | dimmed (not relevant) |
| Po | W (0–100 %) | TX | powerMeter |
yes | usually yes | zero / dim | live, primary |
| SWR | ratio (1.0–∞) | TX | swrMeter |
yes | usually yes | zero / dim | live, alarm when > 2.0 |
| ALC | % of limit | TX | alcMeter |
yes | usually yes | zero / dim | live, warn when in red zone |
| COMP | dB compression | TX (SSB) | compMeter |
yes (SSB only) | varies | zero / dim | live only when compressor on (compressorOn) |
| Id | A drain current | TX | idMeter |
yes | mid/high-end only | zero / dim | live, informational |
| Vd | V drain voltage | TX | vdMeter |
yes | mid/high-end only | always live (shows ≈13.8 V on RX too) | live |
| TEMP | relative 0–100 | shared | (not in state yet — see open question Q4) | available over 0x15 on many rigs, not exposed by IC-7610 CI-V | varies | — | — |
Normalization. Raw values 0–255 are already normalized via
meter-utils.ts (normalize, formatSMeter, formatSwr,
formatPowerWatts, formatAlc). COMP/Id/Vd need formatters added
(formatCompDb, formatAmps, formatVolts) — belongs in that same file.
Capability gating. A meter tile is shown only when its state field
is defined (!== undefined) AND, for COMP, when compressorOn === true.
Radios that don't report Id/Vd simply hide those tiles; the grid
re-flows. No "N/A" placeholders — absent meters disappear.
3. Layout Proposal¶
Strategy¶
Replace the three-card dock with one full-width MetersDockPanel
that always shows all capability-available meters simultaneously as a
horizontal grid of tiles. No more source-selector toggle — this is a
dashboard, not a single-reading panel. The audio FFT scope moves (see
§4).
Dimensions¶
- Dock height: 144 px total (up from 112 px).
- +32 px buys us two-line tiles: big value on top, proper label + bar below. Current 112 px is cramped even for the truncated S/Po/SWR triad.
- This is ≈9 % of a 1440 × 900 laptop screen — still modest relative to the spectrum plot which owns the middle.
- Horizontal padding: 12 px (matches
.bottom-dockexistinggap: 12px). - Tile grid:
display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); gap: 8px;— tiles self-size down to 132 px min, typically 6 tiles fit at 1440 px width at ≈220 px each. - Tile internal padding: 10 px top/bottom, 12 px left/right.
Priority / ordering¶
Tiles render in fixed priority order so muscle memory survives capability changes. Left → right:
- Po (TX primary)
- SWR (TX safety)
- ALC (TX correctness)
- COMP (SSB only, compressor on)
- Id (PA health)
- Vd (PSU health)
- S (optional duplicate — see §8)
- TEMP (if ever exposed)
Dual-RX vs single-RX¶
The meters are station-level, not per-receiver — SWR/Po/ALC/Id/Vd apply to the single PA regardless of dual watch. So the dock is identical for single- and dual-RX radios. (The top VFO header already handles dual-RX.) No second row of tiles for RX2.
ASCII wireframe — IC-7610, TX active¶
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ STATION METERS TX ● │ ← 20px title row
├────────────┬────────────┬────────────┬────────────┬────────────┬────────────┬──────────┤
│ Po │ SWR │ ALC │ COMP │ Id │ Vd │ S │
│ 78 W │ 1.3 │ 42 % │ 6 dB │ 15.2 A │ 13.8 V │ — │
│ ▓▓▓▓▓▓▓░░ │ ▓▓▓░░░░░░ │ ▓▓▓▓▓░░░ │ ▓▓▓░░░░░░ │ ▓▓▓▓░░░░ │ ▓▓▓▓▓▓▓▓ │ │
│ peak 82W │ peak 1.5 │ peak 55% │ │ │ │ │
└────────────┴────────────┴────────────┴────────────┴────────────┴────────────┴──────────┘
← 144 px tall, full-width, ~220 px per tile at 1440 px wide →
ASCII wireframe — IC-7610, RX idle (no TX)¶
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ STATION METERS RX │
├────────────┬────────────┬────────────┬────────────┬────────────┬────────────┬──────────┤
│ Po │ SWR │ ALC │ COMP │ Id │ Vd │ S │
│ — │ — │ — │ — │ — │ 13.8 V │ S7 │
│ (dimmed) │ (dimmed) │ (dimmed) │ (dimmed) │ (dimmed) │ ▓▓▓▓▓▓▓▓ │ ▓▓▓▓░░░░ │
│ │ │ │ │ │ │ │
└────────────┴────────────┴────────────┴────────────┴────────────┴────────────┴──────────┘
Tile anatomy¶
┌─ tile ─────────────┐
│ Po peak 82W │ ← label (11px) + peak readout (9px, right)
│ 78 W │ ← value (22px, Roboto Mono, weight 700)
│ ▓▓▓▓▓▓▓░░░░░░░░░ │ ← horizontal bar, 6px tall, gradient from --v2-meter-*-fill
└────────────────────┘
4. Audio-scope / filter-curve integration¶
Decision: move AudioSpectrumPanel (the combined AF-FFT + passband +
PBT + notch/contour widget — see §1 correction) out of the dock into the
right sidebar as a collapsible panel below the existing DSP controls.
The widget is preserved intact. Argument:
- The dock's new job (meters dashboard) demands full width and visual unity. Splitting it with a scope breaks the "one glance, one truth" read of station telemetry.
- The combined widget is diagnostic/tuning-assist, not ever-present; operators open it when chasing a signal or setting up a filter, then often collapse it. Sidebar-collapsible matches that usage pattern.
- Right sidebar already hosts
DspPanel/TxPanel/RxAudioPanel— a widget that shows AF content + filter shape + PBT + notch/contour belongs with DSP/audio, not wedged between RX1 and the meters. - Only the mount point changes — no modification to
AudioSpectrumPanel.svelteitself. Notch/contour overlays for FTX-1 continue to function unchanged.
The separate RX filter-bandwidth summary in FilterPanel on the left
sidebar (the small trapezoid + "Filter: 3200 Hz" readout) stays put —
this is a different widget from AudioSpectrumPanel and was never in
the dock.
Rejected alternatives:
- Keep the combined widget in the dock alongside meters — kills the dashboard read, forces meters into a narrow strip.
- Move to VFO header — header is already 156 px tall and dense; adding a canvas widget here steals vertical space from the spectrum.
5. States¶
| State | Trigger | Visual |
|---|---|---|
| Idle (RX) | txActive === false |
TX-only tiles dim to opacity: 0.35, bars show 0, values show —. S-meter and Vd stay fully lit. TX indicator shows RX in muted color. |
| Pre-TX (PTT armed) | pttArmed && !txActive (50–200 ms window) |
Brief border: 1px solid var(--v2-accent-yellow) pulse on Po/SWR/ALC tiles. Signals "about to transmit." Optional — may be cut if runtime doesn't expose pttArmed. |
| TX active | txActive === true |
TX-only tiles fully lit (opacity: 1). Peak-hold overlays appear. S-meter dims to 0.35. Top-right status chip shows TX ● in --v2-accent-red with pulsing dot. |
| TX fault | swrMeter > 2.0 OR alcMeter > 0.9 |
The offending tile's border becomes --v2-accent-red, value text turns --v2-accent-red-alt, tile gains subtle box-shadow: 0 0 6px var(--v2-accent-red). No modal, no blocking — hams are adults. |
| Capability missing | state.<meter> === undefined at mount |
Tile not rendered. Grid re-flows. |
| Compressor off | compressorOn !== true |
COMP tile not rendered. |
| Power-off | runtime.radioPowerOn === false |
Whole dock hidden by the existing power-off-overlay (no change). |
6. Visual language¶
Typography¶
| Element | Size | Weight | Font | Color token |
|---|---|---|---|---|
| Tile value | 22 px | 700 | Roboto Mono |
--v2-text-white (active) / --v2-text-muted (idle) |
| Tile unit suffix (W, dB, A) | 12 px | 500 | Roboto Mono |
--v2-text-secondary |
| Tile label (Po, SWR, …) | 11 px | 700, letter-spacing 0.12 em | Roboto Mono |
--v2-text-light |
| Peak-hold readout | 9 px | 600 | Roboto Mono |
--v2-text-dim |
| Panel title "STATION METERS" | 11 px | 700, letter-spacing 0.18 em | Roboto Mono |
--v2-text-light |
Current dock uses 8–11 px labels; we standardize on 11 px for labels and 22 px for values. No label below 9 px anywhere.
Colors¶
Reuse existing meter gradients:
- Po →
--v2-meter-power-fill/-track - SWR →
--v2-meter-swr-fill/-track(built-in red zone past 2.0) - ALC →
--v2-meter-alc-fill/-track - S →
--v2-meter-s-fill/-track - COMP → new token
--v2-meter-comp-fill— propose same green ramp as SWR's safe region,linear-gradient(90deg, #1f7a4a 0%, #4ed37b 100%) - Id → new token
--v2-meter-id-fill— amber,linear-gradient(90deg, #8a6a14 0%, #f2cf4a 100%) - Vd → new token
--v2-meter-vd-fill— blue-cyan,linear-gradient(90deg, #1a5a6d 0%, #7cfce5 100%)
Tile background: var(--v2-bg-card) with 1px solid var(--v2-border).
On fault: border swaps to --v2-accent-red.
Peak-hold indicators¶
SWR, Po, ALC get a 2-second peak-hold:
- Small inverted triangle marker on the bar at peak position, color
--v2-accent-yellow. - Peak numeric value printed top-right of tile in 9 px
(
peak 82 W). - Peak decays linearly to current value over 2 s after last update ≥ current.
- Double-click tile → reset peak immediately.
Id, Vd, COMP don't need peak-hold (steady-state readings).
7. Interaction¶
| Gesture | Action |
|---|---|
| Click tile | No-op by default (meters are read-only). Avoids accidental modal spawning during TX. |
| Double-click tile | Reset peak-hold (for Po/SWR/ALC tiles only). |
| Long-press (500 ms) / right-click tile | Open small popover with: peak value, time since peak, option to toggle between "instantaneous" and "1-s averaged" display. Popover is deferred — can ship without it (Issue 4). |
| Keyboard | Dock is not focus-reachable by default. Consider Tab stop on the panel title only, with Enter to reset all peaks. Deferred. |
| Hover | Tile border lifts from --v2-border to --v2-border-soft; no other effect. |
No source-selector radio group (S/SWR/Po buttons in current
DockMeterPanel) — deliberately removed. All meters always visible.
8. Integration with top VFO header¶
Removed from dock¶
receiver-summary-card-main— RX1 freq/mode/filter/S-meter. Remove entirely. DuplicatesVfoPanelinVfoHeader.receiver-summary-card-sub— RX2 equivalent. Remove entirely.DockMeterPanel(S/SWR/Po toggle variant). Replace with newMetersDockPanel.audio-scope-dock-card(theAudioSpectrumPanelslot). Move to right sidebar (see §4).
S-meter in the new dock — yes, keep it¶
Even though VfoHeader has a full-width LinearSMeter, include a small S tile at the far right of the meters dock for two reasons:
- During TX the VfoHeader S-meter is frozen / not relevant. The dock S tile fills in the "current RX signal context" role at a glance.
- Symmetry: operators expect to see S alongside Po/SWR. Physical rigs put them on the same multimeter.
The S tile in the dock is secondary (smaller visual weight via
opacity: 0.85 when TX inactive, the VfoHeader one is primary).
Net change per RadioLayout.svelte¶
- Lines 254–298 (bottom-dock section) shrink from 3 card types + meter
panel to one
<MetersDockPanel />. - Audio-scope mount migrates to
RightSidebar.svelte.
9. Atomic implementation issues¶
Break into four GitHub issues, each ≤3 files and ≤200 LOC.
Issue A — Add MetersDockPanel component with core tiles (Po, SWR, ALC, S)¶
Title: feat(ui): add MetersDockPanel with Po/SWR/ALC/S tiles
Files (3):
- frontend/src/components-v2/panels/MetersDockPanel.svelte (new)
- frontend/src/components-v2/panels/meter-utils.ts (edit — add formatAmps, formatVolts, formatCompDb, peak-hold helper)
- frontend/src/components-v2/panels/__tests__/MetersDockPanel.test.ts (new)
Scope: render tiles, capability-gate by !== undefined, basic bars. No peak-hold yet. Uses existing tokens only.
LOC budget: ~180.
Issue B — Replace bottom-dock in RadioLayout + remove duplicates¶
Title: refactor(ui): replace bottom-dock summary cards with MetersDockPanel
Files (2):
- frontend/src/components-v2/layout/RadioLayout.svelte (edit — lines 254–298 and associated CSS)
- frontend/src/components-v2/layout/RightSidebar.svelte (edit — mount AudioSpectrumPanel as collapsible)
Scope: wire the new panel, delete receiver-summary-card markup and styles, migrate audio scope to right sidebar. LOC budget: ~140 (mostly deletions).
Issue C — Add Id/Vd/COMP tiles + capability gating¶
Title: feat(ui): add Id/Vd/COMP tiles with capability gating to MetersDockPanel
Files (2):
- frontend/src/components-v2/panels/MetersDockPanel.svelte (edit)
- frontend/src/components-v2/theme/tokens.css (edit — add --v2-meter-id-fill, --v2-meter-vd-fill, --v2-meter-comp-fill)
Scope: extend the tile set; COMP gated on compressorOn.
LOC budget: ~120.
Issue D — Add peak-hold + fault highlighting¶
Title: feat(ui): add peak-hold and SWR/ALC fault highlighting to MetersDockPanel
Files (2):
- frontend/src/components-v2/panels/MetersDockPanel.svelte (edit)
- frontend/src/components-v2/panels/meter-utils.ts (edit — peak-hold decay logic + tests)
Scope: 2-s peak decay for Po/SWR/ALC, SWR > 2.0 red border, ALC > 0.9 red border, double-click reset. LOC budget: ~150.
Ordering: A → B → C → D. After B the visible UI is already improved (no duplication, cleaner dock); C and D add polish without blocking.
10. Open questions¶
- Q1 — S tile in dock: keep or cut? §8 argues keep. If the user feels it's still duplication given the VfoHeader meter, we drop it and the 6-tile grid becomes cleaner at narrow widths.
- Q2 — Pre-TX yellow pulse. Requires
pttArmedor similar in runtime state. Does the WS protocol actually emit an event between PTT down and TX confirmed? If not, drop the state entirely (no polling hack worth it). - Q3 — Peak-hold reset gesture. Double-click per tile vs. single global "reset peaks" button in the panel header? Pro-reset-button: one click clears all. Pro-double-click: per-meter control. Lean toward double-click to match physical-rig muscle memory (tap-the-needle-back-to-zero).
- Q4 — TEMP meter exposure. Is IC-7610 PA temp reachable via any CI-V subcommand we're not using, or is it truly controller-only? If we can add it, the 7-tile grid lights up a useful warning for contest/DX operating. Needs backend investigation, out of scope for this frontend plan.
- Q5 — Audio-scope panel collapsed-by-default? Proposal: yes, collapsed on first render; user opens it when debugging audio. Saves right-sidebar scroll.
- Q6 — Tile minimum width on very narrow displays. At <1100 px the grid will wrap to two rows of 3–4 tiles. Is that acceptable for desktop-v2 (which isn't supposed to be responsive to mobile sizes anyway), or do we force a single-row scroll?
- Q7 — Does removing
receiver-summary-card-*break any test selectors or Playwright flows? Grep shows no external refs, but confirm in Issue B.
Appendix — Token additions summary¶
To be added to frontend/src/components-v2/theme/tokens.css:
--v2-meter-comp-fill: linear-gradient(90deg, #1f7a4a 0%, #4ed37b 100%);
--v2-meter-comp-track: linear-gradient(90deg, rgba(31,122,74,0.3) 0%, rgba(78,211,123,0.2) 100%);
--v2-meter-id-fill: linear-gradient(90deg, #8a6a14 0%, #f2cf4a 100%);
--v2-meter-id-track: linear-gradient(90deg, rgba(138,106,20,0.3) 0%, rgba(242,207,74,0.2) 100%);
--v2-meter-vd-fill: linear-gradient(90deg, #1a5a6d 0%, #7cfce5 100%);
--v2-meter-vd-track: linear-gradient(90deg, rgba(26,90,109,0.3) 0%, rgba(124,252,229,0.2) 100%);
--v2-meter-fault-border: var(--v2-accent-red);
--v2-meter-fault-glow: 0 0 6px rgba(255, 32, 32, 0.45);