feat(logic): array local init + sizing enforcement in localstate

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino Ferrari
2026-06-24 11:01:32 +02:00
parent f776de378f
commit 062bb44dba
2 changed files with 22 additions and 1 deletions
+18 -1
View File
@@ -11,9 +11,15 @@
import { writable, type Readable, type Writable } from './store'; import { writable, type Readable, type Writable } from './store';
import type { SignalValue, SignalMeta, StateVar } from './types'; import type { SignalValue, SignalMeta, StateVar } from './types';
import { parseInitialArray, applySizing, type ArrVal } from './arraypolicy';
const valueStores = new Map<string, Writable<SignalValue>>(); const valueStores = new Map<string, Writable<SignalValue>>();
const metaStores = new Map<string, Writable<SignalMeta | null>>(); const metaStores = new Map<string, Writable<SignalMeta | null>>();
const decls = new Map<string, StateVar>();
export function declaredVar(name: string): StateVar | undefined {
return decls.get(name);
}
const DEFAULT_VALUE: SignalValue = { value: null, quality: 'unknown', ts: null }; 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'; return v.initial === 'true' || v.initial === '1';
case 'string': case 'string':
return v.initial; return v.initial;
case 'array':
return parseInitialArray(v);
default: { default: {
const n = parseFloat(v.initial); const n = parseFloat(v.initial);
return isNaN(n) ? 0 : n; return isNaN(n) ? 0 : n;
@@ -53,6 +61,7 @@ function coerce(v: StateVar): any {
// publishes metadata. Call this whenever a panel is loaded. // publishes metadata. Call this whenever a panel is loaded.
export function initLocalState(vars: StateVar[] | undefined): void { export function initLocalState(vars: StateVar[] | undefined): void {
for (const v of vars ?? []) { for (const v of vars ?? []) {
decls.set(v.name, v);
valueW(v.name).set({ valueW(v.name).set({
value: coerce(v), value: coerce(v),
quality: 'good', quality: 'good',
@@ -64,6 +73,9 @@ export function initLocalState(vars: StateVar[] | undefined): void {
displayLow: v.low ?? 0, displayLow: v.low ?? 0,
displayHigh: v.high ?? 100, displayHigh: v.high ?? 100,
writable: true, writable: true,
elem: v.elem,
sizing: v.sizing,
capacity: v.capacity,
}); });
} }
} }
@@ -77,6 +89,11 @@ export function getLocalMetaStore(name: string): Readable<SignalMeta | null> {
} }
// writeLocalState updates a local variable's live value in place. // 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 { 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() });
} }
+4
View File
@@ -28,6 +28,10 @@ export interface SignalMeta {
description?: string; description?: string;
properties?: Record<string, string>; properties?: Record<string, string>;
tags?: string[]; 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 // A single widget in an interface