@@ -140,6 +140,43 @@ interface Domain {
140140 } ) ) ;
141141 }
142142
143+ /**
144+ * Edits persist in localStorage so a revisit finds them immediately, with
145+ * no sign-in and nothing leaving the browser. The saved copy wins over
146+ * trips.js on load; Reset discards it and returns to the file.
147+ */
148+ const DATA_KEY = "ilr.data.v1" ;
149+
150+ function saveData ( ) : void {
151+ const payload = JSON . stringify ( { visaValidFrom, firstArrivalOn, trips } ) ;
152+ try { localStorage . setItem ( DATA_KEY , payload ) ; } catch { /* private mode */ }
153+ }
154+
155+ function clearSavedData ( ) : void {
156+ try { localStorage . removeItem ( DATA_KEY ) ; } catch { /* private mode */ }
157+ }
158+
159+ /** True when saved data existed and was loaded. Malformed data is ignored. */
160+ function restoreData ( ) : boolean {
161+ let raw : string | null = null ;
162+ try { raw = localStorage . getItem ( DATA_KEY ) ; } catch { /* private mode */ }
163+ if ( ! raw ) return false ;
164+ try {
165+ const d = JSON . parse ( raw ) ;
166+ if ( ! d || ! Array . isArray ( d . trips ) ) return false ;
167+ const str = ( v : unknown ) => ( typeof v === "string" ? v : "" ) ;
168+ visaValidFrom = str ( d . visaValidFrom ) ;
169+ firstArrivalOn = str ( d . firstArrivalOn ) ;
170+ trips = d . trips . map ( ( t : unknown ) => {
171+ const o = ( t || { } ) as Record < string , unknown > ;
172+ return { depart : str ( o . depart ) , ret : str ( o . ret ) , place : str ( o . place ) , reason : str ( o . reason ) } ;
173+ } ) ;
174+ return true ;
175+ } catch {
176+ return false ;
177+ }
178+ }
179+
143180 /**
144181 * The initial stay as a pseudo-trip in front of the real ones, so overlap
145182 * and picker-bound checks can treat all occupied date ranges uniformly.
@@ -653,6 +690,7 @@ interface Domain {
653690 if ( ! had && overlapsRow ( 0 ) ) {
654691 if ( f === "visa" ) visaValidFrom = prev ; else firstArrivalOn = prev ;
655692 }
693+ saveData ( ) ;
656694 render ( ) ;
657695 return ;
658696 }
@@ -668,13 +706,15 @@ interface Domain {
668706 } else {
669707 trip [ f as keyof Trip ] = t . value ;
670708 }
709+ saveData ( ) ;
671710 render ( ) ;
672711 } ) ;
673712
674713 el ( "triptable" ) . addEventListener ( "click" , ( e ) => {
675714 const t = e . target as HTMLElement ;
676715 if ( t . dataset . del === undefined ) return ;
677716 trips . splice ( Number ( t . dataset . del ) , 1 ) ;
717+ saveData ( ) ;
678718 render ( ) ;
679719 } ) ;
680720
@@ -687,10 +727,12 @@ interface Domain {
687727 if ( t . depart && t . ret ) last = Math . max ( last , toDay ( t . ret ) + 30 ) ;
688728 }
689729 trips . push ( { depart : toISO ( last ) , ret : toISO ( last + 7 ) , place : "" , reason : "" } ) ;
730+ saveData ( ) ;
690731 render ( ) ;
691732 } ) ;
692733
693734 el ( "reset" ) . addEventListener ( "click" , ( ) => {
735+ clearSavedData ( ) ;
694736 loadFromFile ( ) ;
695737 render ( ) ;
696738 flash ( "reset to trips.js" ) ;
@@ -806,6 +848,7 @@ interface Domain {
806848 restoreSplit ( ) ;
807849 initSplitter ( ) ;
808850 loadFromFile ( ) ;
851+ restoreData ( ) ;
809852 render ( ) ;
810853 watchPlotHeight ( ) ;
811854} ) ( ) ;
0 commit comments