From c7ba23f37187a9dec67523388e69a0894f02d1c4 Mon Sep 17 00:00:00 2001 From: Jage9 Date: Sun, 22 Feb 2026 03:50:52 -0500 Subject: [PATCH] Show max length in item tooltips --- client/public/version.js | 2 +- client/src/items/itemRegistry.ts | 7 +++++++ client/src/main.ts | 4 ++++ client/src/network/protocol.ts | 1 + server/app/items/clock.py | 3 +-- server/app/items/dice.py | 2 +- server/app/items/radio.py | 2 +- server/app/items/wheel.py | 6 ++++-- server/app/items/widget.py | 2 +- 9 files changed, 21 insertions(+), 8 deletions(-) diff --git a/client/public/version.js b/client/public/version.js index d1104da..3f681a6 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.22 R144"; +window.CHGRID_WEB_VERSION = "2026.02.22 R145"; // 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 2f7f1c7..664296a 100644 --- a/client/src/items/itemRegistry.ts +++ b/client/src/items/itemRegistry.ts @@ -68,6 +68,7 @@ export type ItemPropertyValueType = 'boolean' | 'text' | 'number' | 'list' | 'so export type ItemPropertyMetadata = { valueType?: ItemPropertyValueType; tooltip?: string; + maxLength?: number; range?: { min: number; max: number; @@ -140,6 +141,12 @@ function normalizePropertyMetadataRecord(raw: Record | undefine if (typeof valueObj.tooltip === 'string' && valueObj.tooltip.trim().length > 0) { metadata.tooltip = valueObj.tooltip.trim(); } + if (valueObj.maxLength !== undefined) { + const maxLength = Number(valueObj.maxLength); + if (Number.isFinite(maxLength) && maxLength > 0) { + metadata.maxLength = Math.floor(maxLength); + } + } const range = valueObj.range; if (range && typeof range === 'object') { const rangeObj = range as Record; diff --git a/client/src/main.ts b/client/src/main.ts index b95a3ba..9fc2bc5 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -819,6 +819,10 @@ function describeItemPropertyHelp(item: WorldItem, key: string): string { } } + if (metadata?.maxLength !== undefined) { + parts.push(`Max length: ${metadata.maxLength} characters.`); + } + parts.push(EDITABLE_ITEM_PROPERTY_KEYS.has(key) ? 'Editable.' : 'Read only.'); return parts.join(' '); } diff --git a/client/src/network/protocol.ts b/client/src/network/protocol.ts index 4706866..81ddaf0 100644 --- a/client/src/network/protocol.ts +++ b/client/src/network/protocol.ts @@ -50,6 +50,7 @@ export const welcomeMessageSchema = z.object({ z.object({ valueType: z.enum(['boolean', 'text', 'number', 'list', 'sound']).optional(), tooltip: z.string().optional(), + maxLength: z.number().int().positive().optional(), range: z .object({ min: z.number(), diff --git a/server/app/items/clock.py b/server/app/items/clock.py index ae0575f..8f7e4b8 100644 --- a/server/app/items/clock.py +++ b/server/app/items/clock.py @@ -64,7 +64,7 @@ TIME_ZONE_OPTIONS: tuple[str, ...] = ( DEFAULT_PARAMS: dict = {"timeZone": DEFAULT_TIME_ZONE, "use24Hour": False} PROPERTY_METADATA: dict[str, dict[str, object]] = { - "title": {"valueType": "text", "tooltip": "Display name spoken and shown for this item."}, + "title": {"valueType": "text", "tooltip": "Display name spoken and shown for this item.", "maxLength": 80}, "timeZone": {"valueType": "list", "tooltip": "Timezone used when the clock speaks time."}, "use24Hour": {"valueType": "boolean", "tooltip": "Use 24 hour format instead of AM/PM."}, } @@ -92,4 +92,3 @@ def use_item(item: WorldItem, nickname: str, clock_formatter: Callable[[dict], s self_message=f"{item.title} says {display_time}.", others_message=f"{nickname} checks {item.title}. {item.title} says {display_time}.", ) - diff --git a/server/app/items/dice.py b/server/app/items/dice.py index d2be7cd..0ec9df9 100644 --- a/server/app/items/dice.py +++ b/server/app/items/dice.py @@ -21,7 +21,7 @@ DEFAULT_TITLE = "Dice" DEFAULT_PARAMS: dict = {"sides": 6, "number": 2} PROPERTY_METADATA: dict[str, dict[str, object]] = { - "title": {"valueType": "text", "tooltip": "Display name spoken and shown for this item."}, + "title": {"valueType": "text", "tooltip": "Display name spoken and shown for this item.", "maxLength": 80}, "sides": { "valueType": "number", "tooltip": "Number of sides on each die.", diff --git a/server/app/items/radio.py b/server/app/items/radio.py index 8de3b10..39c16e2 100644 --- a/server/app/items/radio.py +++ b/server/app/items/radio.py @@ -43,7 +43,7 @@ CHANNEL_OPTIONS: tuple[str, ...] = ("stereo", "mono", "left", "right") EFFECT_OPTIONS: tuple[str, ...] = ("reverb", "echo", "flanger", "high_pass", "low_pass", "off") PROPERTY_METADATA: dict[str, dict[str, object]] = { - "title": {"valueType": "text", "tooltip": "Display name spoken and shown for this item."}, + "title": {"valueType": "text", "tooltip": "Display name spoken and shown for this item.", "maxLength": 80}, "streamUrl": {"valueType": "text", "tooltip": "Audio stream URL used by this radio."}, "enabled": {"valueType": "boolean", "tooltip": "Turns playback on or off for this radio."}, "mediaVolume": { diff --git a/server/app/items/wheel.py b/server/app/items/wheel.py index 0385ec3..85fdd0b 100644 --- a/server/app/items/wheel.py +++ b/server/app/items/wheel.py @@ -21,10 +21,11 @@ DEFAULT_TITLE = "wheel" DEFAULT_PARAMS: dict = {"spaces": "yes, no"} PROPERTY_METADATA: dict[str, dict[str, object]] = { - "title": {"valueType": "text", "tooltip": "Display name spoken and shown for this item."}, + "title": {"valueType": "text", "tooltip": "Display name spoken and shown for this item.", "maxLength": 80}, "spaces": { "valueType": "text", "tooltip": "Comma-delimited list of wheel spaces. Example: yes, no, maybe.", + "maxLength": 4000, }, } @@ -35,6 +36,8 @@ def validate_update(_item: WorldItem, next_params: dict) -> dict: spaces_raw = next_params.get("spaces", "") if not isinstance(spaces_raw, str): raise ValueError("spaces must be a comma-delimited string.") + if len(spaces_raw) > 4000: + raise ValueError("spaces must be 4000 characters or less.") spaces = [token.strip() for token in spaces_raw.split(",") if token.strip()] if not spaces: raise ValueError("spaces must include at least one value, separated by commas.") @@ -65,4 +68,3 @@ def use_item(item: WorldItem, nickname: str, _clock_formatter: Callable[[dict], delayed_self_message=landed, delayed_others_message=landed, ) - diff --git a/server/app/items/widget.py b/server/app/items/widget.py index 04448fb..981aa26 100644 --- a/server/app/items/widget.py +++ b/server/app/items/widget.py @@ -47,7 +47,7 @@ DEFAULT_PARAMS: dict = { EFFECT_OPTIONS: tuple[str, ...] = ("reverb", "echo", "flanger", "high_pass", "low_pass", "off") PROPERTY_METADATA: dict[str, dict[str, object]] = { - "title": {"valueType": "text", "tooltip": "Display name spoken and shown for this item."}, + "title": {"valueType": "text", "tooltip": "Display name spoken and shown for this item.", "maxLength": 80}, "enabled": {"valueType": "boolean", "tooltip": "Turns this widget on or off."}, "directional": {"valueType": "boolean", "tooltip": "If on, emitted sound favors the facing direction."}, "facing": {