Bring the docs in line with the code

- python-backend/README: track analysis is implemented (2e0e6ad); the cache layout,
  the module table (lirc, screen, timers, podcasts, tippen) and the route table
  (track analysis, HA proxy, typing game) were missing pieces.
- docs/REPO_OVERVIEW: Mein Zimmer and the clock are implemented; Home Assistant is
  also reached over HTTP through the room page's proxy, not MQTT only; the IR remote,
  scripts/, the optional config sections and figure kind were not mentioned.
- web/README: the key table was the mockup's (Ctrl transport keys, TAB over 'all');
  ?pi=1 is now a preset with per-browser overrides, not a read-once flag; the room
  page and the typing game exist; eslint is part of the checks.
- Docstrings that described earlier designs: HaConfig (written as browser-direct in
  the same commit that built the proxy, a8ed350), services/__init__, events,
  playback, media, colors, tippen progress and AppHeader, lowPower.ts, and the
  'Hoerbuecher' spelling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 00:28:37 +02:00
parent a0ac48ccbf
commit a737ccf108
12 changed files with 105 additions and 77 deletions

View File

@@ -33,9 +33,11 @@ For the device, `npm run build` writes `dist/`, and `general.web.static_dir` in
```sh
npm run test # vitest over src/lib
npx tsc --noEmit # strict, noUncheckedIndexedAccess
npm run check # tsc (strict, noUncheckedIndexedAccess) and eslint
```
`../scripts/check.sh web` runs both.
Only the pure modules are tested - the search, the keyboard machine and the time
formatting. That is where the behaviour lives; the components are mostly layout.
@@ -61,11 +63,12 @@ are local.
|---|---|
| `src/lib/keyboard.ts` | The key map, as a pure function from a keypress to a list of actions. Testable without a DOM. |
| `src/lib/search.ts` | Filtering and the three-level browse hierarchy. The whole library is in memory, so this is instant - see the note on `buildSearchIndex` below. |
| `src/lib/lowPower.ts` | The `?pi=1` profile: what gets cheaper, and by how much. |
| `src/lib/perfSettings.ts` | The performance knobs: the `FULL` and `PI` presets, and per-browser overrides from the ⚡ dialog. |
| `src/lib/lowPower.ts` | The measurements behind the `?pi=1` preset, and the ambient-canvas quality numbers. |
| `src/lib/covers.ts` | Generated cover art from the album's three colours, and the book-spine treatment. |
| `src/hooks/usePlayerState.ts` | The websocket, with reconnect-and-reseed. |
| `src/hooks/usePlaybackClock.ts` | Interpolates the 2 Hz position onto animation frames. |
| `src/components/` | Layout only. |
| `src/components/` | Layout only. `RoomView` is the "Mein Zimmer" page, `TippenApp` the typing game. |
`src/api/types.ts` mirrors `musicmouse/services/web/schemas.py`; the two are hand-kept in
step.
@@ -73,26 +76,29 @@ step.
## Keyboard
The device is meant to be driven from a keyboard, so every screen is reachable without a
pointer. `F1` shows the same list in-app.
pointer. `F1` shows the full list in-app; `src/components/HelpOverlay.tsx` is that list
and `src/lib/keyboard.ts` is the binding - change both together.
| Key | |
|---|---|
| `SPACE` | play / pause |
| `CTRL+H` / `CTRL+L` | previous / next track |
| `CTRL+J` / `CTRL+K` | quieter / louder |
| `SHIFT+H` / `SHIFT+L` | previous / next track |
| `SHIFT+J` / `SHIFT+K` | quieter / louder |
| `SHIFT+M` | mute |
| `SHIFT+←` / `SHIFT+→` | seek 15 s |
| `A-Z`, `0-9` | search albums as you type |
| `?` | switch to searching individual tracks |
| `TAB` | cycle all / music / audiobooks |
| `← ↑ ↓ →` | move the selection while browsing; transport and volume while playing |
| `ENTER` | play the selection |
| `ESC` | back out one layer at a time |
| `/` | back to browsing |
| `0-9` | what the IR remote's number key does |
| `← ↑ ↓ →`, `CTRL+HJKL` | move the selection |
| `ENTER` / `SHIFT+ENTER` | play the selection / open its track list |
| `TAB` | cycle music / audiobooks / podcasts |
| `A-Z` | search albums as you type |
| `?` | search individual tracks |
| `/` | open search, or switch albums ↔ tracks |
| `A` then `0-9` | put the playing album on that remote key (`SHIFT+A`: just this song) |
| `INSERT` | clock on / off |
| `DELETE` | screen off / on (only on the device itself) |
| `ESC` | close, or clear the search |
| `F1` | this list |
`SHIFT+arrows` is the one addition to the mockup: plain arrows were already taken, and
seeking is the one thing a real player can do that the mockup could not.
## The Raspberry Pi profile
`?pi=1` loads the same app with nothing on screen animating itself. It exists for
@@ -125,15 +131,18 @@ sea behind everything - it just no longer moves or follows the track.
`AMBIENCE_QUALITY` in `src/lib/lowPower.ts` is kept accurate for whoever turns the
canvas back on.
The flag is read once at module load from the URL and nowhere else - no persistence, no
auto-detection. That is what makes "is this the profile or the hardware?" answerable by
editing the address bar. The kiosk gets it from `pi_kiosk_url` in the ansible repo.
`?pi=1` selects the `PI` preset in `src/lib/perfSettings.ts`, and each knob can then be
overridden from the ⚡ dialog, stored in that browser's localStorage. Without `?pi=1`
the stored values are ignored and the full version applies, which keeps "is this the
profile or the hardware?" answerable by editing the address bar. Settings are read once
at load, so a change applies on reload. The kiosk gets the flag from `pi_kiosk_url` in
the ansible repo.
## Parent mode
`?parentMode=1` opens a settings panel - volume limits, the rotary step, button
brightness, and a button to re-read the library. Saving writes the device's
`config.yml`.
`?parentMode=1` adds a way into a settings panel - volume limits, the rotary step, button
brightness, the screen's auto-off, the parents' timer, and a button to re-read the
library. Saving writes the device's `config.yml`.
It is hidden from the child, not protected from them: there is no authentication on the
endpoints behind it. See `../python-backend/README.md`.
@@ -190,7 +199,10 @@ than patching one element at a time:
home-indicator padding while the keyboard is up
([safe-area-inset-bottom does not update for the keyboard](https://webventures.rejh.nl/blog/2025/safe-area-inset-bottom-does-not-update/)).
## Not implemented
## Other tabs
The mockup's second page, "Mein Zimmer" (Hue lights, a roller shutter and scenes), is
not built. Nothing here depends on it.
- **Mein Zimmer** (`RoomView`): the mockup's room page - lights, a roller shutter and
scenes - shown when the backend has an `ha` section. Every Home Assistant call goes
through the backend's `/api/ha/...` proxy, so the browser never sees the token.
- **Tippen** (`TippenApp`): the typing game, when the backend has a `tippen` section.
Passing lessons unlocks tracks in the library.

View File

@@ -1,5 +1,5 @@
/** The header, matching ../../web/src/components/AppHeader.tsx so the two apps open the
* same way. The mascot bobs; `SHOW_DRIFT` doubles as the decorative-motion switch. */
/** The typing tab's header, matching ../AppHeader.tsx so both tabs open the same
* way. The mascot bobs; `SHOW_DRIFT` doubles as the decorative-motion switch. */
import type { ReactNode } from "react";
import { SKIN } from "../../lib/skins";

View File

@@ -41,19 +41,14 @@
* "decorative loops" row above: noise). Turning the canvas back on from the dialog
* wants the blur back on with it - the 118% row is that combination.
*
* Read once at module load, from the URL and nowhere else: no persistence, no
* auto-detection. The kiosk points Firefox at `http://localhost:8080/?pi=1` (see the
* `pi_kiosk_url` host var in the ansible repo) and a laptop opening the same page gets
* the full-fat version, which is what makes "is this the profile or the hardware?"
* answerable by editing the address bar.
* The kiosk points Firefox at `http://localhost:8080/?pi=1` (see the `pi_kiosk_url` host
* var in the ansible repo). That flag is no longer a single switch: it selects the `PI`
* preset in `lib/perfSettings.ts`, which the ⚡ dialog can override per knob, and every
* knob here and in `theme.ts` reads `PERF`. This header is kept for the measurements.
*/
import { PERF, PERF_ENABLED } from "./perfSettings";
// Superseded as a single switch: every knob below and in theme.ts now comes from
// `PERF` (lib/perfSettings.ts), which `?pi=1` seeds and the settings dialog overrides.
// This header is kept for the measurements it records.
export interface AmbienceQuality {
/** Multiplies the canvas backing store relative to its on-screen size, before
* `devicePixelRatio`. At 0.5 the loop fills a quarter of the pixels and the browser