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:
@@ -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.
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user