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.
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
- 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` is supported and item property rows recompute live after updates.
- 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`
### 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/*/module.py`) and plugins point directly to those modules.
- **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):** ✅ Mostly complete. Client consumes `welcome.uiDefinitions` and disables item menus when schema is missing.
- **Phase 4 (metadata-driven editor + visibility dependencies):** ⚠️ Partially complete. Generic metadata-driven editing is in place, but `visibleWhen` is not included in the client protocol schema, so dependency visibility rules from server metadata are dropped during message validation.
- **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.
- Carry `visibleWhen` from server metadata through client protocol validation into `itemRegistry`.
Problem today:
- Server emits `visibleWhen`, but client protocol schema drops unknown metadata fields, so dependency visibility can silently fail.
Implementation:
- Update `client/src/network/protocol.ts``welcome.uiDefinitions.itemTypes[].propertyMetadata` schema to allow `visibleWhen` object values of `string | number | boolean`.
- Add a client test/fixture (or lightweight runtime assertion) that confirms `visibleWhen` survives parse.
Acceptance criteria:
- For a property like `facing` with `visibleWhen: { directional: true }`, toggling `directional` updates property visibility without hardcoded client logic.
- One canonical place (`propertyMetadata`) defines type, range, tooltip, options, and visibility for each property.
- New list property requires server-only metadata changes for options.
5.**Eliminate manual property label mapping in client (medium):** either include labels in server metadata or auto-humanize keys so new properties are readable without code changes.
6.**Add a script/check for “new item completeness” (low):** one CI check that verifies plugin discovery, protocol acceptance, docs presence, and test coverage for each discovered type.