Server-first label metadata and architecture guidance

This commit is contained in:
Jage9
2026-02-24 19:06:08 -05:00
parent 59db0a0dda
commit 60c0ced7b7
3 changed files with 22 additions and 26 deletions

View File

@@ -23,9 +23,12 @@
## Coding Style & Naming Conventions ## Coding Style & Naming Conventions
- TypeScript: strict typing, `camelCase`, small focused modules. - TypeScript: strict typing, `camelCase`, small focused modules.
- Python: PEP 8, 4 spaces, `snake_case`, typed Pydantic models. - 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. - 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. - 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. - 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`. - Keep protocol changes synced in `client/src/network/protocol.ts` and `server/app/models.py`.
## Documentation Maintenance ## Documentation Maintenance

View File

@@ -1,5 +1,5 @@
// Maintainer-controlled web client version. // Maintainer-controlled web client version.
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) // 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. // Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
window.CHGRID_TIME_ZONE = "America/Detroit"; window.CHGRID_TIME_ZONE = "America/Detroit";

View File

@@ -35,6 +35,7 @@ let itemTypeEditableProperties: Partial<Record<ItemType, string[]>> = {};
let itemTypeCapabilities: Partial<Record<ItemType, string[]>> = {}; let itemTypeCapabilities: Partial<Record<ItemType, string[]>> = {};
let itemTypeGlobalProperties: Partial<Record<ItemType, Record<string, string | number | boolean>>> = {}; let itemTypeGlobalProperties: Partial<Record<ItemType, Record<string, string | number | boolean>>> = {};
let itemTypePropertyMetadata: Partial<Record<ItemType, Record<string, ItemPropertyMetadata>>> = {}; let itemTypePropertyMetadata: Partial<Record<ItemType, Record<string, ItemPropertyMetadata>>> = {};
let propertyLabelByKey: Record<string, string> = {};
export let EDITABLE_ITEM_PROPERTY_KEYS = new Set<string>( export let EDITABLE_ITEM_PROPERTY_KEYS = new Set<string>(
Object.values(itemTypeEditableProperties).flatMap((keys) => keys ?? []), Object.values(itemTypeEditableProperties).flatMap((keys) => keys ?? []),
@@ -151,31 +152,14 @@ export function getItemTypeCapabilities(itemType: ItemType): string[] {
/** Returns human-facing label for a property key. */ /** Returns human-facing label for a property key. */
export function itemPropertyLabel(key: string): string { export function itemPropertyLabel(key: string): string {
const metadataLabel = Object.values(itemTypePropertyMetadata) const metadataLabel = propertyLabelByKey[key];
.map((entry) => entry?.[key]?.label)
.find((label) => typeof label === 'string' && label.trim().length > 0);
if (metadataLabel) return metadataLabel; if (metadataLabel) return metadataLabel;
if (key === 'use24Hour') return 'use 24 hour format'; const words = key
if (key === 'emitRange') return 'emit range'; .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
if (key === 'mediaVolume') return 'media volume'; .replace(/[_-]+/g, ' ')
if (key === 'emitVolume') return 'emit volume'; .trim()
if (key === 'emitSoundSpeed') return 'emit sound speed'; .toLowerCase();
if (key === 'emitSoundTempo') return 'emit sound tempo'; return words || key;
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;
} }
/** Returns editable properties for one item instance/type. */ /** Returns editable properties for one item instance/type. */
@@ -241,6 +225,7 @@ export function applyServerItemUiDefinitions(uiDefinitions: UiDefinitionsPayload
itemTypeCapabilities = {}; itemTypeCapabilities = {};
itemTypeGlobalProperties = {}; itemTypeGlobalProperties = {};
itemTypePropertyMetadata = {}; itemTypePropertyMetadata = {};
propertyLabelByKey = {};
rebuildEditablePropertyKeySet(); rebuildEditablePropertyKeySet();
return false; return false;
} }
@@ -256,6 +241,7 @@ export function applyServerItemUiDefinitions(uiDefinitions: UiDefinitionsPayload
const nextCapabilities: Partial<Record<ItemType, string[]>> = {}; const nextCapabilities: Partial<Record<ItemType, string[]>> = {};
const nextGlobals: Partial<Record<ItemType, Record<string, string | number | boolean>>> = {}; const nextGlobals: Partial<Record<ItemType, Record<string, string | number | boolean>>> = {};
const nextPropertyMetadata: Partial<Record<ItemType, Record<string, ItemPropertyMetadata>>> = {}; const nextPropertyMetadata: Partial<Record<ItemType, Record<string, ItemPropertyMetadata>>> = {};
const nextPropertyLabels: Record<string, string> = {};
for (const definition of uiDefinitions.itemTypes) { for (const definition of uiDefinitions.itemTypes) {
if (!definition || typeof definition.type !== 'string') continue; 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'); nextCapabilities[itemType] = definition.capabilities.filter((entry) => typeof entry === 'string');
} }
if (definition.propertyMetadata && typeof definition.propertyMetadata === 'object') { 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') { if (definition.globalProperties && typeof definition.globalProperties === 'object') {
const normalized: Record<string, string | number | boolean> = {}; const normalized: Record<string, string | number | boolean> = {};
@@ -298,6 +290,7 @@ export function applyServerItemUiDefinitions(uiDefinitions: UiDefinitionsPayload
itemTypeCapabilities = nextCapabilities; itemTypeCapabilities = nextCapabilities;
itemTypeGlobalProperties = nextGlobals; itemTypeGlobalProperties = nextGlobals;
itemTypePropertyMetadata = nextPropertyMetadata; itemTypePropertyMetadata = nextPropertyMetadata;
propertyLabelByKey = nextPropertyLabels;
itemTypeSequence = explicitOrder ?? discoveredOrder; itemTypeSequence = explicitOrder ?? discoveredOrder;
rebuildEditablePropertyKeySet(); rebuildEditablePropertyKeySet();
return itemTypeSequence.length > 0; return itemTypeSequence.length > 0;