From 062bb44dbacfb2d9d3f4ad55f195585b1dfcf92d Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Wed, 24 Jun 2026 11:01:32 +0200 Subject: [PATCH] feat(logic): array local init + sizing enforcement in localstate Co-Authored-By: Claude Opus 4.6 --- web/src/lib/localstate.ts | 19 ++++++++++++++++++- web/src/lib/types.ts | 4 ++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/web/src/lib/localstate.ts b/web/src/lib/localstate.ts index 4c70707..b9710ba 100644 --- a/web/src/lib/localstate.ts +++ b/web/src/lib/localstate.ts @@ -11,9 +11,15 @@ import { writable, type Readable, type Writable } from './store'; import type { SignalValue, SignalMeta, StateVar } from './types'; +import { parseInitialArray, applySizing, type ArrVal } from './arraypolicy'; const valueStores = new Map>(); const metaStores = new Map>(); +const decls = new Map(); + +export function declaredVar(name: string): StateVar | undefined { + return decls.get(name); +} const DEFAULT_VALUE: SignalValue = { value: null, quality: 'unknown', ts: null }; @@ -42,6 +48,8 @@ function coerce(v: StateVar): any { return v.initial === 'true' || v.initial === '1'; case 'string': return v.initial; + case 'array': + return parseInitialArray(v); default: { const n = parseFloat(v.initial); return isNaN(n) ? 0 : n; @@ -53,6 +61,7 @@ function coerce(v: StateVar): any { // publishes metadata. Call this whenever a panel is loaded. export function initLocalState(vars: StateVar[] | undefined): void { for (const v of vars ?? []) { + decls.set(v.name, v); valueW(v.name).set({ value: coerce(v), quality: 'good', @@ -64,6 +73,9 @@ export function initLocalState(vars: StateVar[] | undefined): void { displayLow: v.low ?? 0, displayHigh: v.high ?? 100, writable: true, + elem: v.elem, + sizing: v.sizing, + capacity: v.capacity, }); } } @@ -77,6 +89,11 @@ export function getLocalMetaStore(name: string): Readable { } // writeLocalState updates a local variable's live value in place. +// When the declared variable is an array type, the written value is passed +// through applySizing to enforce the declared sizing policy. export function writeLocalState(name: string, value: any): void { - valueW(name).set({ value, quality: 'good', ts: new Date().toISOString() }); + const sv = decls.get(name); + let v = value; + if (sv?.type === 'array' && Array.isArray(value)) v = applySizing(value as ArrVal[], sv); + valueW(name).set({ value: v, quality: 'good', ts: new Date().toISOString() }); } diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index 96a9f5b..71362dd 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -28,6 +28,10 @@ export interface SignalMeta { description?: string; properties?: Record; tags?: string[]; + // array-local extra fields (present when type === 'array'): + elem?: 'number' | 'bool' | 'array'; + sizing?: 'dynamic' | 'capped' | 'fixed'; + capacity?: number; } // A single widget in an interface