WebUI access was a single shared password. This adds optional
Authenticator TOTP as a second factor, with one-time recovery codes
and SSH as the last-resort disable path.
Login stays two HTTP posts with no pending token: a correct password
and enabled 2FA without a second factor returns { needsTotp: true };
the client then resubmits { password, totp } or { password, recoveryCode }.
The TOTP secret is wrapped with a password-derived AES-256-GCM key.
Eight XXXX-XXXX recovery codes are stored as SHA-256 hashes and each
code is consumed once. Enabling or disabling 2FA invalidates other
sessions; disabling still requires password plus TOTP or a recovery
code. SNOWLUMA_DEV_MODE hides the feature.
The login page uses a dedicated second-factor screen (Interior OTP
cells for the 6-digit code; the same cell UI for recovery codes,
grouped as XXXX-XXXX). Settings expose enroll/confirm/disable and
one-time recovery-code display.
Fixes#351
Move confirmations, editors, and help surfaces onto one consistent interaction model.
Preserve accessible focus handling while making destructive and in-progress actions clearer.
Make line endings deterministic across macOS, Windows, and Linux while preserving the native checkout format required by Windows launchers.
Pin the tested application runtime and Node type baseline, add a shared startup preflight, and keep independently published packages on their existing compatibility ranges.
Align local scripts, pull request validation, complete package builds, dependency metadata, and contributor documentation around the same Node and pnpm contract. The root test command now covers every tested workspace package while a focused Core command remains available for local iteration.
Keep per-widget configuration when persisted overview layouts are migrated against the current widget catalogue. Account shortcuts, notes, links, filters, and other configurable widgets now retain their saved values after a page reload.
Add a WebUI Vitest entry point and regression coverage for restoring an account widget with saved configuration.
Fixes#243.
Replace native range inputs with an accessible Radix-backed control that preserves keyboard and assistive-technology semantics.
Reproduce the elastic handle, track stretch, tick visibility, and reduced-motion behavior while keeping SnowLuma theme tokens. Adopt the component for interface scale and background image controls.
Keep terminal styling confined to interactive console output while storing plain structured log lines for subscribers and file persistence.
Sanitize downloads again at the WebUI boundary so cached legacy entries and fallback-formatted records cannot leak CSI, OSC, DCS, or C1 control sequences. Add regression coverage for colored output, hyperlinks, control strings, malformed sequences, and preserved multiline text.
The logs page rendered every filtered row, so a full 2000-line view mounted
~10k DOM nodes and cost ~66ms of layout per commit. Switch to a virtualized
list (@tanstack/react-virtual) that mounts only the visible window plus a small
overscan — a few dozen nodes regardless of how many lines the view holds.
Rows are dynamically measured (measureElement) so wrap mode's variable-height
lines need no fixed row-height assumption; the size cache is keyed by row id so
the maxLines front-drop doesn't mis-attribute heights across the index shift.
The column header moved just outside the scroll viewport so the virtual list is
the sole scroll content (no sticky-offset math). Auto-scroll now follows the
tail via scrollToIndex with a next-frame re-pin (freshly-appended rows carry the
size estimate until measured, which otherwise left wrap-mode tails short), and
re-runs on filter/level/wrap/maxLines changes, not just new logs. ScrollArea
gained an optional viewportRef so the virtualizer can drive off the real Radix
scroll element. The per-row entrance fade is dropped (rows mount/unmount on
scroll); divide-y became a per-row border.