Typing lessons like duolingo & musicmouse cleanup

This commit is contained in:
2026-09-12 18:58:02 +02:00
parent 498243af46
commit a5210fead2
50 changed files with 3074 additions and 710 deletions

View File

@@ -29,6 +29,11 @@ export function useHomeAssistant(config: HaConfig | null): HomeAssistant {
// when the patch landed resolves with pre-click data - without this, that stale
// response clobbers the optimistic "on" back to "off" until the *next* poll catches
// up, which is what made toggling feel laggy despite the optimistic update existing.
//
// `usePlayerState`'s `optimistic()` solves the same "local guess vs. eventual truth"
// problem far more simply, by just patching the whole object - it can get away with
// that because the backend pushes over a websocket rather than being polled, so
// there's no in-flight request that can resolve late and stomp on a newer patch.
const optimisticAt = useRef<Record<string, number>>({});
const entityIds = useMemo(

View File

@@ -15,7 +15,12 @@ const RECONNECT_DELAY_MS = 1500;
export interface Connection {
state: PlayerState | null;
online: boolean;
/** Apply a change locally so a keypress feels instant; the next frame reconciles. */
/** Apply a change locally so a keypress feels instant; the next frame reconciles.
*
* A plain whole-object patch is enough here because state only ever arrives pushed
* over the websocket - there's no in-flight poll that could resolve late and stomp
* on it. `useHomeAssistant`'s `optimistic()` solves the same problem for a *polled*
* source, where that race is real, with a per-entity timestamp guard instead. */
optimistic: (patch: Partial<PlayerState>) => void;
}
@@ -34,13 +39,21 @@ export function usePlayerState(onLibraryChanged: () => void): Connection {
const connect = () => {
const protocol = location.protocol === "https:" ? "wss:" : "ws:";
socket = new WebSocket(`${protocol}//${location.host}/api/ws`);
const ws = new WebSocket(`${protocol}//${location.host}/api/ws`);
socket = ws;
socket.onopen = () => {
setOnline(true);
// The server sends a snapshot on connect, but a reconnect may have missed
// changes in between, so ask for the truth as well.
void api.state().then(setState).catch(() => undefined);
// changes in between, so ask for the truth as well. Guarded against a slow
// response landing after a *later* reconnect already replaced this socket -
// every other fetch-on-mount hook in this codebase guards the same way.
void api
.state()
.then((fetched) => {
if (!closed && socket === ws) setState(fetched);
})
.catch(() => undefined);
};
socket.onmessage = (event) => {