From 60c0ced7b7bcd6effd9a772dee87de49add827b9 Mon Sep 17 00:00:00 2001 From: Jage9 Date: Tue, 24 Feb 2026 19:06:08 -0500 Subject: [PATCH] Server-first label metadata and architecture guidance --- AGENTS.md | 3 +++ client/public/version.js | 2 +- client/src/items/itemRegistry.ts | 43 +++++++++++++------------------- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2b7ea5a..ed4ab34 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,9 +23,12 @@ ## Coding Style & Naming Conventions - TypeScript: strict typing, `camelCase`, small focused modules. - Python: PEP 8, 4 spaces, `snake_case`, typed Pydantic models. +- Architecture: server-first by default. Keep grid/world rules, authoritative validation, and canonical definitions on server whenever practical. +- Client scope: UI/UX, rendering, input, and audio presentation. Avoid client-owned gameplay/business rules when server can own them. - Backward compatibility: not required during current development phase; prefer simpler clean-cut changes over compatibility shims/migrations unless the user asks otherwise. - Python docstrings: for `server/app` changes, include module docstring, class docstring, and docstrings for public functions/methods where behavior/contracts matter. - Shared logic first: when behavior is reused across modes/features, implement it in shared helpers/modules rather than duplicating branch-specific logic. +- Keep `main.ts` as orchestration glue. Move reusable feature logic to focused modules; ask before large/structural refactors. - Keep protocol changes synced in `client/src/network/protocol.ts` and `server/app/models.py`. ## Documentation Maintenance diff --git a/client/public/version.js b/client/public/version.js index 390a48c..ec8a531 100644 --- a/client/public/version.js +++ b/client/public/version.js @@ -1,5 +1,5 @@ // Maintainer-controlled web client version. // Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) -window.CHGRID_WEB_VERSION = "2026.02.24 R228"; +window.CHGRID_WEB_VERSION = "2026.02.25 R229"; // Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid. window.CHGRID_TIME_ZONE = "America/Detroit"; diff --git a/client/src/items/itemRegistry.ts b/client/src/items/itemRegistry.ts index 5b054e2..57a8575 100644 --- a/client/src/items/itemRegistry.ts +++ b/client/src/items/itemRegistry.ts @@ -35,6 +35,7 @@ let itemTypeEditableProperties: Partial> = {}; let itemTypeCapabilities: Partial> = {}; let itemTypeGlobalProperties: Partial>> = {}; let itemTypePropertyMetadata: Partial>> = {}; +let propertyLabelByKey: Record = {}; export let EDITABLE_ITEM_PROPERTY_KEYS = new Set( Object.values(itemTypeEditableProperties).flatMap((keys) => keys ?? []), @@ -151,31 +152,14 @@ export function getItemTypeCapabilities(itemType: ItemType): string[] { /** Returns human-facing label for a property key. */ export function itemPropertyLabel(key: string): string { - const metadataLabel = Object.values(itemTypePropertyMetadata) - .map((entry) => entry?.[key]?.label) - .find((label) => typeof label === 'string' && label.trim().length > 0); + const metadataLabel = propertyLabelByKey[key]; if (metadataLabel) return metadataLabel; - if (key === 'use24Hour') return 'use 24 hour format'; - if (key === 'emitRange') return 'emit range'; - if (key === 'mediaVolume') return 'media volume'; - if (key === 'emitVolume') return 'emit volume'; - if (key === 'emitSoundSpeed') return 'emit sound speed'; - if (key === 'emitSoundTempo') return 'emit sound tempo'; - if (key === 'mediaChannel') return 'media channel'; - if (key === 'mediaEffect') return 'media effect'; - if (key === 'mediaEffectValue') return 'media effect value'; - if (key === 'emitEffect') return 'emit effect'; - if (key === 'emitEffectValue') return 'emit effect value'; - if (key === 'instrument') return 'instrument'; - if (key === 'voiceMode') return 'voice mode'; - if (key === 'octave') return 'octave'; - if (key === 'attack') return 'attack'; - if (key === 'decay') return 'decay'; - if (key === 'release') return 'release'; - if (key === 'brightness') return 'brightness'; - if (key === 'useSound') return 'use sound'; - if (key === 'emitSound') return 'emit sound'; - return key; + const words = key + .replace(/([a-z0-9])([A-Z])/g, '$1 $2') + .replace(/[_-]+/g, ' ') + .trim() + .toLowerCase(); + return words || key; } /** Returns editable properties for one item instance/type. */ @@ -241,6 +225,7 @@ export function applyServerItemUiDefinitions(uiDefinitions: UiDefinitionsPayload itemTypeCapabilities = {}; itemTypeGlobalProperties = {}; itemTypePropertyMetadata = {}; + propertyLabelByKey = {}; rebuildEditablePropertyKeySet(); return false; } @@ -256,6 +241,7 @@ export function applyServerItemUiDefinitions(uiDefinitions: UiDefinitionsPayload const nextCapabilities: Partial> = {}; const nextGlobals: Partial>> = {}; const nextPropertyMetadata: Partial>> = {}; + const nextPropertyLabels: Record = {}; for (const definition of uiDefinitions.itemTypes) { if (!definition || typeof definition.type !== 'string') continue; @@ -273,7 +259,13 @@ export function applyServerItemUiDefinitions(uiDefinitions: UiDefinitionsPayload nextCapabilities[itemType] = definition.capabilities.filter((entry) => typeof entry === 'string'); } if (definition.propertyMetadata && typeof definition.propertyMetadata === 'object') { - nextPropertyMetadata[itemType] = normalizePropertyMetadataRecord(definition.propertyMetadata); + const normalizedMetadata = normalizePropertyMetadataRecord(definition.propertyMetadata); + nextPropertyMetadata[itemType] = normalizedMetadata; + for (const [propertyKey, propertyMetadata] of Object.entries(normalizedMetadata)) { + if (typeof propertyMetadata.label === 'string' && propertyMetadata.label.trim().length > 0) { + nextPropertyLabels[propertyKey] = propertyMetadata.label.trim(); + } + } } if (definition.globalProperties && typeof definition.globalProperties === 'object') { const normalized: Record = {}; @@ -298,6 +290,7 @@ export function applyServerItemUiDefinitions(uiDefinitions: UiDefinitionsPayload itemTypeCapabilities = nextCapabilities; itemTypeGlobalProperties = nextGlobals; itemTypePropertyMetadata = nextPropertyMetadata; + propertyLabelByKey = nextPropertyLabels; itemTypeSequence = explicitOrder ?? discoveredOrder; rebuildEditablePropertyKeySet(); return itemTypeSequence.length > 0;