diff --git a/.gitignore b/.gitignore index 5185e40..a25082e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ client/dist/ .DS_Store *.log *.bak + +# Local planning scratch +plans/ diff --git a/plans/audio-architecture-update-plan.md b/plans/audio-architecture-update-plan.md deleted file mode 100644 index d59a39e..0000000 --- a/plans/audio-architecture-update-plan.md +++ /dev/null @@ -1,126 +0,0 @@ -# Audio Architecture Update Proposal - -Date: 2026-02-25 - -## Goals - -1. Fix correctness issues first (sound origin for carried items). -2. Improve runtime stability without increasing server/upstream load. -3. Reduce duplicate work in audio runtime (shared streams, cached effects). -4. Keep server-first boundaries clear and avoid client/server drift. - -## Proposed Implementation Sequence - -### Phase 1: Correctness + low-risk fixes - -1. Carried-item `useSound` source position (server) -- Problem: `item_use_sound` currently uses `item.x/y`, which can be stale while carried. -- Change: resolve source position via carrier when `carrierId` is set, same pattern as piano. -- Files: - - `server/app/server.py` -- Acceptance: - - Using a carried item emits sound from carrier’s current square for all listeners. - -2. Stream retry policy hardening (client) -- Status: partially done (throttled retry + cap + cooldown). -- Follow-up: - - Add small inline debug counters in runtime (non-user-facing unless debug enabled). - - Ensure cooldown reset after successful play and cleanup path reset are covered by tests. -- Files: - - `client/src/audio/radioStationRuntime.ts` - - `client/src/audio/itemEmitRuntime.ts` -- Acceptance: - - No retry spam under repeated failures. - - Retry state recovers automatically when playback succeeds. - -### Phase 2: Performance and scaling improvements - -3. Emit source strategy -removed - -4. Reverb impulse cache (client) -- Problem: effect chain rebuilds can recreate impulse buffers frequently. -- Change: cache impulse responses by `(sampleRate, effectValueBucket)` in `effects.ts`. -- Files: - - `client/src/audio/effects.ts` -- Acceptance: - - Effect toggling no longer repeatedly regenerates same impulse buffers. - - No audible regressions in reverb behavior. - -### Phase 3: Consistency and maintainability - -5. Centralize sound URL normalization policy -- Problem: normalization logic exists in multiple places (server validator + client resolver + proxy behavior). -- Change: - - Define one policy doc and align implementation points: - - server validation/normalization - - client runtime resolution - - proxy Dropbox/http normalization behavior - - Move server normalization logic to shared item-sound helper(s), not tied to a specific item type module. -- Files: - - `server/app/items/...` shared validator/normalizer helper module - - per-item validators (`widget`, `radio_station`, and future sound-accepting items) call shared helper - - `client/src/main.ts` (`resolveIncomingSoundUrl`) - - `deploy/php/media_proxy.php` - - `docs/protocol-notes.md` or new dedicated audio policy section -- Acceptance: - - Same input URL/path yields predictable behavior across use/emit/radio. - - Fewer edge mismatches (`none/off`, `sounds/`, full URLs, Dropbox links). - -### Phase 4: Output routing + observability (defer) - -6. Output-device routing behavior -- Problem: `setSinkId` on muted element may not map to all WebAudio-rendered domains. -- Change options: - - A: Explicitly document browser limitation + current behavior. - - B: Investigate alternate routing architecture and apply if robust in target browsers. -- Recommendation: - - Ship A first (fast, clear UX), then evaluate B separately. -- Files: - - `docs/controls.md` and/or `docs/runtime-flow.md` - - optional runtime status text in settings UI -- Acceptance: - - Users get accurate expectation of output-device behavior. - -7. Audio runtime debug observability -- Change: - - Add optional debug object/report for: - - active radio shared sources - - active emit outputs/shared sources - - retry failures and cooldown state - - Keep disabled by default. -- Files: - - `client/src/audio/radioStationRuntime.ts` - - `client/src/audio/itemEmitRuntime.ts` - - optional small hook in `main.ts` for debug dump command -- Acceptance: - - Runtime state can be inspected quickly during field troubleshooting. - -## Risks and Mitigation - -1. Shared emit pooling could accidentally couple per-item controls. -- Mitigation: maintain per-item gain/effect nodes after shared source split. - -2. Output routing changes can be browser-fragile. -- Mitigation: document-first rollout, then narrow-scope prototype for alternate routing. - -3. Normalization centralization can break legacy links. -- Mitigation: add targeted tests for representative URL/path cases before refactor. - -## Suggested PR/Commit Breakdown - -1. Carried-item sound origin fix (server). -2. Emit shared source pooling. -3. Reverb impulse cache. -4. Sound normalization alignment (server/client/proxy + docs). -5. Output routing docs/UX clarification. -6. Optional debug observability layer. - -## Definition of Done - -1. Carried item sounds always originate from current carrier position. -2. No unbounded retry loops for stream failures. -3. Emit runtime reuses identical stream URLs. -4. Reverb buffer creation is cached and stable under effect churn. -5. Sound URL/path behavior is documented and consistent across server/client/proxy. -6. Audio runtime state is inspectable when debugging is enabled. diff --git a/plans/item-architecture-refactor-plan.md b/plans/item-architecture-refactor-plan.md deleted file mode 100644 index 27bc1bd..0000000 --- a/plans/item-architecture-refactor-plan.md +++ /dev/null @@ -1,331 +0,0 @@ -# Server-First Item Architecture Refactor Plan - -## Goal -Make the **server the only source of truth** for item definitions, schema, defaults, options, validation rules, and editable behavior. The client should consume server definitions and provide UX/rendering/audio only. - -This plan removes client fallback definitions and introduces a repeatable, consistent item authoring structure so adding new item types is low-risk and uniform. - -## Target End State - -### 1) Source of truth -- Server owns, for each item type: - - Type id, label, tooltip - - Full property schema (value type, required/optional, min/max/step, maxLength, enum options) - - Defaults (global + per-item initial params) - - Editability and read-only behavior - - Validation, normalization, migration policy (if any) - - Runtime actions (`use`, optional custom actions) - - Capability list -- Server sends this as canonical `uiDefinitions` + schema metadata on `welcome` (or equivalent bootstrap). - -### 2) Client model -- Client has no static fallback item definitions. -- If schema payload is missing/invalid, item features are unavailable (explicit error/status), not silently guessed. -- Client property editor and item menus are metadata-driven. -- Client runtime behavior modules remain for UX/audio only (e.g., piano local mode), keyed by server type ids. - -### 3) Repeatable item authoring -Adding an item type uses one standard server folder/template and a short checklist, with **auto-discovery** on server boot (no manual registry edits). - ---- - -## Proposed Architecture - -## A) Server: Item Type Package Contract (Auto-Discovered) -Create/standardize per-item server packages under something like: -- `server/app/items/types//` - -Each item type package exports the same contract: -- `definition.py` - - `type_id`, `label`, `tooltip` - - `schema` (properties + metadata) - - `defaults` - - `editable_properties` - - `capabilities` -- `validator.py` - - `validate_create(params) -> normalized_params` - - `validate_update(existing, patch) -> normalized_params` - - Must drop unknown keys by default. -- `actions.py` - - `use(context, item, client, payload?) -> result` - - optional `custom_actions` handlers -- `ui.py` (optional if definition is enough) - - transforms schema -> `uiDefinitions` payload fragments - -A central loader in server scans `server/app/items/types/*` at boot and imports one plugin entrypoint per folder (for example `plugin.py` with `ITEM_TYPE_PLUGIN` export). - -The discovered plugins are then assembled into an in-memory registry object exposing: -- validation hooks -- defaults -- ui definitions -- capabilities -- action dispatch - -This means: -- adding a new item folder + plugin file is sufficient for server registration -- no hand-edited master list is required - -## B) Server: Strict Params Hygiene -In update flow: -- Build next params by applying patch into current params. -- Run through type validator that: - - strips unknown keys - - normalizes known keys - - enforces types/ranges/options -- Persist only validated output. - -No raw client params should persist. - -## C) Server: Save Strategy -Replace synchronous `save_state()` every mutation with coalesced writes: -- mark dirty on mutation -- debounce write (e.g., 100-300ms) -- cap max delay (e.g., 1-2s) -- flush on shutdown/signal - -This preserves durability while reducing event-loop blocking. - -## D) Client: Schema-Driven UI Runtime -Refactor client item registry/editor to consume server schema only. - -Client keeps: -- Presentation helpers -- Generic item behavior path driven only by schema/metadata -- Optional per-item UX runtime modules only where needed (example: piano key mode) - -Client removes: -- static defaults/options/editability lists as authority -- fallback-driven assumptions -- requirement for per-item client modules when behavior is generic - -Property editor logic becomes generic: -- `valueType: boolean` -> toggle -- `valueType: list` + `options` -> list select -- `valueType: number` + `range` -> numeric editor/stepper -- `valueType: text/sound` + `maxLength` -> text editor -- `readonly` -> blocked edit with status - -Special-case handlers only for UX extras (e.g., live preview for certain fields). - -### D.1) Dependent Property Rules -Add dependency metadata to server schema so client can hide dependent fields generically. - -Recommended metadata fields per property: -- `visibleWhen`: simple predicate (for example `{ directional: true }`) - -Example: -- `facing` has `visibleWhen: { directional: true }` -- when `directional` is `false`, `facing` is hidden -- when a controlling property changes, the property menu is recomputed immediately so visibility updates live - -## E) Protocol Shape (Recommended) -Ensure `welcome.uiDefinitions` includes enough to be complete: -- `itemTypes[]` - - `type`, `label`, `tooltip` - - `editableProperties[]` - - `propertyMetadata{ key -> { valueType, tooltip, range, maxLength, options?, readonly?, visibleWhen? } }` - - `globalProperties` - - `capabilities` -- `itemTypeOrder[]` - -Optional future: -- `schemaVersion` for compatibility checks. - ---- - -## Phased Implementation Plan - -## Phase 0: Preconditions and guardrails -1. Document canonical schema contract in `docs/item-schema.md`. -2. Add tests asserting unknown keys are rejected/stripped per type. -3. Add tests asserting `uiDefinitions` completeness for all registered types. - -Deliverable: -- Locked schema contract and tests before heavy refactor. - -## Phase 1: Server type package standardization + auto-discovery -1. Standardize all existing item types to same package contract. -2. Move any remaining type-specific logic out of generic server paths into per-type packages. -3. Add auto-discovery loader APIs: -- `get_type_definition(type_id)` -- `validate_update(type_id, existing, patch)` -- `build_ui_definitions()` -4. Loader scans item folders at startup and registers plugins automatically. - -Deliverable: -- Uniform server-side item modules for all current item types. - -## Phase 2: Strict validation and unknown-key stripping -1. Enforce strict allowed-key filtering in per-type validators. -2. Fail/strip behavior decision: -- recommended: strip unknown keys on load/update, optionally log at debug level. -3. Backfill tests for each type. - -Deliverable: -- No unsupported params can persist. - -## Phase 3: Client removes authority/fallback definitions -1. Remove client hardcoded item defaults/options as authoritative data. -2. Keep only bootstrap guards: -- if schema missing/invalid, fail item UX with explicit status (no fallback behavior). -3. Refactor `itemRegistry` to be a runtime cache of server definitions. - -Deliverable: -- Client item UI driven entirely by server payload. - -## Phase 4: Metadata-driven property editor + visibility dependencies -1. Replace key-specific submit/toggle/list branches with generic metadata-based handlers. -2. Keep a small optional hook map: -- `onPropertyPreviewChange(type,key,value)` for UX preview. -3. Implement `visibleWhen` semantics with live menu recompute when controlling values change. -4. Verify all current item properties work with generic editor. - -Deliverable: -- Adding a new field usually requires server changes only. - -## Phase 5: Behavior registry completion (optional modules) -1. Keep one generic behavior path that works for items with no special runtime. -2. Keep per-item behavior modules only when UX/audio runtime is truly custom. -3. Ensure all runtime hooks are accessed via registry interfaces. -4. Remove any remaining type checks in `main.ts` and shared handlers. - -Deliverable: -- `main.ts` stays orchestration-only. - -## Phase 6: Coalesced persistence -1. Implement debounced save queue in server item service. -2. Add durability tests (flush on shutdown). -3. Add config knobs (debounce ms, max delay). - -Deliverable: -- lower save overhead under bursty updates. - ---- - -## Repeatable New Item Template - -When adding a new item type: - -1. Server -- Create `server/app/items/types//` -- Implement: - - `definition.py` - - `validator.py` - - `actions.py` - - `plugin.py` (entrypoint export for auto-discovery) -- Add tests: - - create defaults - - update validation (valid + invalid + unknown keys) - - `use` behavior - - `uiDefinitions` fields present - -2. Client -- Add `client/src/items/types//behavior.ts` only if custom UX runtime exists. -- Prefer zero client type-specific code for generic items. -- If behavior module is needed, register via behavior loader pattern. -- No hardcoded property logic in editor. - -3. Docs -- Update `docs/item-types.md` -- Update `docs/item-schema.md` -- Update controls docs if keybindings changed. - ---- - -## Risks and Mitigations - -1. Risk: temporary client breakage when fallback removed. -- Mitigation: explicit schema-required startup check and clear status error. - -2. Risk: inconsistent schema during deploy rollover. -- Mitigation: include `schemaVersion` and reject incompatible client/server combinations with clear reconnect message. - -3. Risk: over-generalized editor misses edge-case UX. -- Mitigation: keep small per-item preview hooks while generic editor handles core commit logic. - -4. Risk: debounced persistence data loss on crash. -- Mitigation: short debounce + max-delay + flush on shutdown. - ---- - -## Suggested Execution Order for Your Repo (Practical) -1. Implement strict unknown-key stripping on server (highest impact, lowest UX risk). -2. Implement server plugin auto-discovery for item type folders. -3. Convert client item registry to require server schema payload (remove fallback authority). -4. Make item property editor fully metadata-driven with dependency rules. -5. Finalize optional client behavior modules (only for custom UX items like piano). -6. Add coalesced persistence. - ---- - -## Definition of Done -- Server item validators fully define accepted params and drop unknowns. -- Server item types are boot-loaded from folder plugins (no manual master registry edits). -- `uiDefinitions` is complete and authoritative for all item UI config. -- Client contains no authoritative item defaults/options/editability outside server payload. -- Client has no fallback schema path. -- New item addition follows one template with predictable files/tests. -- `main.ts` has no item-type-specific runtime branches. - ---- - -## Implementation Update (2026-02-24) - -### Completed -- Phase 0: - - Added server-side contract coverage for `uiDefinitions` completeness. - - Added/kept tests for unknown-key stripping and validation behavior. -- Phase 1: - - Server item plugins are auto-discovered from `server/app/items/types/*/plugin.py`. - - Registry now builds type order/modules from discovered plugins. -- Phase 2: - - Unknown params are stripped by validators and use-path updates are revalidated before persist. -- Phase 3: - - Client item registry now requires server `uiDefinitions`; no fallback item-definition authority. - - Missing/invalid schema now disables item menus with explicit status. -- Phase 4: - - Property editor behavior is metadata-driven by `valueType/range/options/maxLength`. - - `visibleWhen` now survives protocol parsing and item property rows recompute live after updates. - - List options are now carried in `propertyMetadata[key].options` (no separate `propertyOptions` map). -- Phase 5: - - Client runtime behavior remains modular per item via behavior registry; `main.ts` orchestration no longer carries item-specific business branches. -- Phase 6: - - Coalesced/debounced state saving implemented. - - Flush-on-shutdown implemented. - - Save timing now configurable via: - - `storage.state_save_debounce_ms` - - `storage.state_save_max_delay_ms` - - Additional: - - Added plugin contract tests to validate required item package files/exports. - - Added unknown-item-type rejection for `item_add`. - - Added `capabilities` in `welcome.uiDefinitions.itemTypes`. - - Removed hardcoded client/server item-type enum restrictions (string-based ids + runtime known-type checks). - -### Notes -- Client item-specific runtime is now reduced to only `piano`; simple items (`dice`, `wheel`, `clock`, `radio_station`, `widget`) run through generic client flows with no custom behavior module. -- Server item implementations now live inside per-type folders (`server/app/items/types/*/definition.py`, `validator.py`, `actions.py`). -- Plugins now compose module surfaces via a shared helper (`server/app/items/types/plugin_helpers.py`), so per-type `module.py` is no longer required. -- Added docs sample folder at `docs/examples/item-type-sample/` and updated template docs to reflect the package layout. -## Completion Review (2026-02-24) - -### Status against phases -- **Phase 0 (docs + guardrails):** ✅ Complete. `docs/item-schema.md` exists and server tests cover UI schema completeness + unknown-key stripping behavior. -- **Phase 1 (server package standardization + auto-discovery):** ✅ Complete. Item types are standardized under `server/app/items/types/*` with `plugin.py` entrypoints and discovered by `server/app/items/registry.py`. -- **Phase 2 (strict validation + unknown-key stripping):** ✅ Complete. Type validators enforce allowed keys and normalize values; tests verify unknown params are stripped. -- **Phase 3 (client removes fallback authority):** ✅ Complete. Client consumes `welcome.uiDefinitions` and disables item menus when schema is missing. -- **Phase 4 (metadata-driven editor + visibility dependencies):** ✅ Complete. Generic metadata-driven editing is in place and `visibleWhen` survives client protocol parsing. -- **Phase 5 (behavior registry completion):** ✅ Complete. Runtime behavior is registry-driven with generic path + optional piano module. -- **Phase 6 (coalesced persistence):** ✅ Complete. Debounced/max-delay state save and flush-on-shutdown are implemented in server lifecycle. - -### Recommendations / Cleanup (remaining) -1. **Eliminate manual property label mapping in client (medium):** either include labels for all properties in server metadata or auto-humanize keys so new properties are readable without code changes. -2. **Strengthen new-item completeness checks (low):** extend the plugin contract tests to optionally assert docs coverage and richer protocol examples per discovered type. - -### Item 1-6 Status (explicit) -1. **visibleWhen end-to-end:** ✅ Done. -2. **Remove hardcoded item-type literals/enums:** ✅ Done (string-based ids + runtime known-type checks). -3. **Include capabilities in `uiDefinitions.itemTypes`:** ✅ Done. -4. **Move list options into property metadata:** ✅ Done (`propertyMetadata[key].options` only). -5. **Remove manual client property-label mapping:** ❌ Not fully done yet. Metadata labels are supported, but legacy hardcoded label map still exists as fallback. -6. **New-item completeness validation check:** ✅ Baseline done (plugin contract tests added for required files/exports). -Potential enhancement: add docs/protocol example assertions for each discovered type.