44 * @todo : actually link to patchConnection
55 */
66
7- import React , { createContext , useState , useContext } from "react" ;
7+ import React , { createContext , useState , useContext , useEffect , useRef } from "react" ;
88import { InstrumentKey , instrumentKeys } from "./params" ;
9-
9+ import { getPatchConnection } from "./common/patchConnection" ;
1010export interface StoredState {
1111 selectedInstrument : InstrumentKey ;
1212}
@@ -37,9 +37,61 @@ export const StoredStateStoreProvider: React.FC<{
3737 children : React . ReactNode ;
3838} > = ( { children } ) => {
3939 const [ state , setState ] = useState < StoredState > ( initialState ) ;
40+ // Keep a ref of last state so we can compare inside effects without stale closures
41+ const stateRef = useRef ( state ) ;
42+ useEffect ( ( ) => {
43+ stateRef . current = state ;
44+ } , [ state ] ) ;
45+
46+ // Push a delta to the patch connection for each updated key
47+ const sendStoredStateDelta = ( delta : Partial < StoredState > ) => {
48+ const patchConnection = getPatchConnection ( ) ;
49+ if ( ! patchConnection ) return ;
50+ for ( const k in delta ) {
51+ const key = k as keyof StoredState ;
52+ const value = delta [ key ] ;
53+ // Validate value before sending
54+ if ( key === 'selectedInstrument' && value && ! instrumentKeys . includes ( value as InstrumentKey ) ) {
55+ console . warn ( `Invalid selectedInstrument value: ${ value } , skipping` ) ;
56+ continue ;
57+ }
58+ // Future-proof: allow null/undefined to clear
59+ patchConnection . sendStoredStateValue ?.( key , value as any ) ;
60+ }
61+ } ;
4062
4163 const setStoredState = ( value : Partial < StoredState > ) => {
42- setState ( ( prevState ) => ( { ...prevState , ...value } ) ) ;
64+ if ( ! value || Object . keys ( value ) . length === 0 ) return ;
65+
66+ // Validate incoming values
67+ const validatedValue : Partial < StoredState > = { } ;
68+ for ( const k in value ) {
69+ const key = k as keyof StoredState ;
70+ const val = value [ key ] ;
71+ if ( key === 'selectedInstrument' ) {
72+ if ( val && instrumentKeys . includes ( val as InstrumentKey ) ) {
73+ ( validatedValue as any ) [ key ] = val ;
74+ } else {
75+ console . warn ( `Invalid selectedInstrument: ${ val } , keeping current value` ) ;
76+ }
77+ } else {
78+ ( validatedValue as any ) [ key ] = val ;
79+ }
80+ }
81+
82+ setState ( ( prevState ) => {
83+ const next = { ...prevState , ...validatedValue } ;
84+ // Send only changed keys
85+ const changed : Partial < StoredState > = { } ;
86+ for ( const k in validatedValue ) {
87+ const key = k as keyof StoredState ;
88+ if ( prevState [ key ] !== next [ key ] ) ( changed as any ) [ key ] = next [ key ] ;
89+ }
90+ if ( Object . keys ( changed ) . length ) {
91+ sendStoredStateDelta ( changed ) ;
92+ }
93+ return next ;
94+ } ) ;
4395 } ;
4496
4597 const updateStoredStateItem : StoredStateUpdater = ( key ) => ( value ) =>
@@ -66,6 +118,82 @@ export const StoredStateStoreProvider: React.FC<{
66118 } ) ;
67119 } ;
68120
121+ // Initial sync + listener registration
122+ useEffect ( ( ) => {
123+ let patchConnection : any ;
124+ try {
125+ patchConnection = getPatchConnection ( ) ;
126+ } catch {
127+ return ;
128+ }
129+ if ( ! patchConnection ) return ;
130+
131+ // Listener for individual key updates from patch
132+ const storedStateListener = ( { key, value } : { key : string ; value : any } ) => {
133+ if ( ! ( key in stateRef . current ) ) return ; // ignore keys we don't know yet
134+ const typedKey = key as keyof StoredState ;
135+
136+ // Validate incoming value
137+ if ( typedKey === 'selectedInstrument' ) {
138+ if ( ! value || ! instrumentKeys . includes ( value as InstrumentKey ) ) {
139+ return ;
140+ }
141+ }
142+
143+ if ( stateRef . current [ typedKey ] !== value ) {
144+ setState ( ( prev ) => ( { ...prev , [ typedKey ] : value } ) ) ;
145+ }
146+ } ;
147+
148+ try {
149+ patchConnection . addStoredStateValueListener ?.( storedStateListener ) ;
150+ } catch { }
151+
152+ // Request full state so we can merge existing values (if any) stored by host
153+ try {
154+ patchConnection . requestFullStoredState ?.( ( full : any ) => {
155+ try {
156+ const values = full ?. values || { } ;
157+ const incoming : Partial < StoredState > = { } ;
158+ for ( const k in values ) {
159+ if ( k in stateRef . current ) {
160+ const key = k as keyof StoredState ;
161+ const value = values [ k ] ;
162+
163+ // Validate values from patch
164+ if ( key === 'selectedInstrument' ) {
165+ if ( value && instrumentKeys . includes ( value as InstrumentKey ) ) {
166+ ( incoming as any ) [ key ] = value ;
167+ }
168+ } else {
169+ ( incoming as any ) [ key ] = value ;
170+ }
171+ }
172+ }
173+ if ( Object . keys ( incoming ) . length ) {
174+ setState ( ( prev ) => ( { ...prev , ...incoming } ) ) ;
175+ } else {
176+ // If host has nothing, push our initial state so it becomes persisted
177+ sendStoredStateDelta ( stateRef . current ) ;
178+ }
179+ } catch { }
180+ } ) ;
181+ } catch { }
182+
183+ // Also request each individual key to trigger callbacks (mirrors ParamStore pattern)
184+ try {
185+ for ( const key in stateRef . current ) {
186+ patchConnection . requestStoredStateValue ?.( key ) ;
187+ }
188+ } catch { }
189+
190+ return ( ) => {
191+ try {
192+ patchConnection . removeStoredStateValueListener ?.( storedStateListener ) ;
193+ } catch { }
194+ } ;
195+ } , [ ] ) ;
196+
69197 return (
70198 < StoredStateStoreContext . Provider
71199 value = { {
0 commit comments