11"use client" ;
22
3- import { useCallback , useEffect , useState } from "react" ;
3+ import { useCallback , useEffect , useRef , useState } from "react" ;
44import { X , Github , FileText , Shield , Coffee , Smartphone , Sparkles , LogOut , ExternalLink , Upload , Download , BarChart3 } from "lucide-react" ;
55import { signOut } from "next-auth/react" ;
66import { Capacitor } from "@capacitor/core" ;
@@ -96,6 +96,9 @@ export default function MobileDashboard() {
9696 const [ wscaSyncing , setWscaSyncing ] = useState ( false ) ;
9797 const [ wscaSyncStatus , setWscaSyncStatus ] = useState < string | null > ( null ) ;
9898 const importInputId = "moltly-import-input" ;
99+ const noteSaveTimers = useRef < Record < string , number > > ( { } ) ;
100+ const pendingNoteUpdates = useRef < Record < string , ResearchNote [ ] > > ( { } ) ;
101+ const noteSaveLatestRequest = useRef < Record < string , number > > ( { } ) ;
99102
100103 // Cross-platform export helper: uses Capacitor on native platforms, falls back to web download
101104 const exportJsonText = useCallback ( async ( jsonText : string ) => {
@@ -189,6 +192,12 @@ export default function MobileDashboard() {
189192 return ( ) => window . removeEventListener ( "scroll" , onScroll ) ;
190193 } , [ ] ) ;
191194
195+ useEffect ( ( ) => {
196+ return ( ) => {
197+ Object . values ( noteSaveTimers . current ) . forEach ( ( timer ) => window . clearTimeout ( timer ) ) ;
198+ } ;
199+ } , [ ] ) ;
200+
192201 const openNewEntry = ( ) => {
193202 setEditingId ( null ) ;
194203 setFormState ( defaultForm ( ) ) ;
@@ -638,7 +647,17 @@ export default function MobileDashboard() {
638647 setStacks ( ( prev ) => prev . filter ( ( s ) => s . id !== id ) ) ;
639648 } ;
640649
650+ const cancelPendingNoteSave = ( stackId : string ) => {
651+ if ( noteSaveTimers . current [ stackId ] ) {
652+ window . clearTimeout ( noteSaveTimers . current [ stackId ] ) ;
653+ delete noteSaveTimers . current [ stackId ] ;
654+ }
655+ delete pendingNoteUpdates . current [ stackId ] ;
656+ noteSaveLatestRequest . current [ stackId ] = Date . now ( ) ;
657+ } ;
658+
641659 const onCreateNote = async ( stackId : string , note : Partial < ResearchNote > ) => {
660+ cancelPendingNoteSave ( stackId ) ;
642661 const base : ResearchNote = {
643662 id : crypto . randomUUID ( ) ,
644663 title : ( note . title ?? "New Note" ) . trim ( ) || "New Note" ,
@@ -654,21 +673,59 @@ export default function MobileDashboard() {
654673 await onUpdateStack ( stackId , { notes : updated . notes , name : stack . name } ) ;
655674 } ;
656675
657- const onUpdateNote = async ( stackId : string , noteId : string , updates : Partial < ResearchNote > ) => {
658- const stack = stacks . find ( ( s ) => s . id === stackId ) ;
659- if ( ! stack ) return ;
660- const notes = stack . notes . map ( ( n ) => ( n . id === noteId ? { ...n , ...updates , updatedAt : new Date ( ) . toISOString ( ) } : n ) ) ;
661- await onUpdateStack ( stackId , { notes, name : stack . name } ) ;
676+ const onUpdateNote = ( stackId : string , noteId : string , updates : Partial < ResearchNote > ) => {
677+ if ( ! mode ) return ;
678+
679+ const now = new Date ( ) . toISOString ( ) ;
680+ let nextNotes : ResearchNote [ ] | null = null ;
681+
682+ setStacks ( ( prev ) =>
683+ prev . map ( ( s ) => {
684+ if ( s . id !== stackId ) return s ;
685+ nextNotes = s . notes . map ( ( n ) => ( n . id === noteId ? { ...n , ...updates , updatedAt : now } : n ) ) ;
686+ return { ...s , notes : nextNotes , updatedAt : now } ;
687+ } )
688+ ) ;
689+
690+ if ( ! nextNotes ) return ;
691+ pendingNoteUpdates . current [ stackId ] = nextNotes ;
692+
693+ if ( ! isSync ) return ;
694+
695+ if ( noteSaveTimers . current [ stackId ] ) {
696+ window . clearTimeout ( noteSaveTimers . current [ stackId ] ) ;
697+ }
698+
699+ noteSaveTimers . current [ stackId ] = window . setTimeout ( async ( ) => {
700+ const requestId = Date . now ( ) ;
701+ noteSaveLatestRequest . current [ stackId ] = requestId ;
702+ const payload = pendingNoteUpdates . current [ stackId ] ?? nextNotes ;
703+ try {
704+ const res = await fetch ( `/api/research/${ stackId } ` , {
705+ method : "PATCH" ,
706+ headers : { "Content-Type" : "application/json" } ,
707+ body : JSON . stringify ( { notes : payload } ) ,
708+ } ) ;
709+ if ( ! res . ok ) return ;
710+ const saved = ( await res . json ( ) ) as ResearchStack ;
711+ if ( noteSaveLatestRequest . current [ stackId ] !== requestId ) return ;
712+ setStacks ( ( prev ) => prev . map ( ( s ) => ( s . id === stackId ? saved : s ) ) ) ;
713+ } catch ( err ) {
714+ console . error ( err ) ;
715+ }
716+ } , 400 ) ;
662717 } ;
663718
664719 const onDeleteNote = async ( stackId : string , noteId : string ) => {
720+ cancelPendingNoteSave ( stackId ) ;
665721 const stack = stacks . find ( ( s ) => s . id === stackId ) ;
666722 if ( ! stack ) return ;
667723 const notes = stack . notes . filter ( ( n ) => n . id !== noteId ) ;
668724 await onUpdateStack ( stackId , { notes, name : stack . name } ) ;
669725 } ;
670726
671727 const onDuplicateNote = async ( stackId : string , noteId : string ) => {
728+ cancelPendingNoteSave ( stackId ) ;
672729 const stack = stacks . find ( ( s ) => s . id === stackId ) ;
673730 if ( ! stack ) return ;
674731 const src = stack . notes . find ( ( n ) => n . id === noteId ) ;
0 commit comments