@@ -57,6 +57,7 @@ export default function NoteWidget({ widgetId }: Props) {
5757 const [ selectedId , setSelectedId ] = useState < string > ( "" ) ;
5858 const [ draft , setDraft ] = useState < string > ( "" ) ;
5959 const [ lastSavedAt , setLastSavedAt ] = useState < Date | null > ( null ) ;
60+ const [ saveState , setSaveState ] = useState < "idle" | "saving" | "error" > ( "idle" ) ;
6061 const [ showRecovered , setShowRecovered ] = useState ( false ) ;
6162 const [ autoBlur , setAutoBlur ] = useState ( ( ) => {
6263 try {
@@ -160,8 +161,9 @@ export default function NoteWidget({ widgetId }: Props) {
160161 client . setState ( BACKUP_KEY , serializeNotes ( nextNotes ) ) ,
161162 ] ) ;
162163 setLastSavedAt ( new Date ( ) ) ;
164+ return true ;
163165 } catch {
164- // Keep local state even if gateway write fails.
166+ return false ;
165167 }
166168 } , [ client ] ) ;
167169
@@ -250,12 +252,14 @@ export default function NoteWidget({ widgetId }: Props) {
250252
251253 const saveCurrent = async ( ) => {
252254 if ( ! selectedNote ) return ;
255+ setSaveState ( "saving" ) ;
253256 const next = notes . map ( ( n ) =>
254257 n . id === selectedNote . id
255258 ? { ...n , content : draft , updatedAt : new Date ( ) . toISOString ( ) }
256259 : n
257260 ) ;
258- await persist ( next ) ;
261+ const saved = await persist ( next ) ;
262+ setSaveState ( saved ? "idle" : "error" ) ;
259263 } ;
260264
261265 const deleteCurrent = async ( ) => {
@@ -387,22 +391,32 @@ export default function NoteWidget({ widgetId }: Props) {
387391 < >
388392 < textarea
389393 value = { draft }
390- onChange = { ( e ) => setDraft ( e . target . value ) }
394+ onChange = { ( e ) => {
395+ setDraft ( e . target . value ) ;
396+ setLastSavedAt ( null ) ;
397+ setSaveState ( "idle" ) ;
398+ } }
391399 placeholder = { t ( "note.placeholder" ) }
392400 className = "ui-field flex-1 min-h-0 resize-none leading-relaxed"
393401 />
394402 < div className = "flex justify-between items-center" >
395- < span className = { clsx ( "text-[11px] transition-colors" , lastSavedAt ? "text-accent-green" : "text-text-muted" ) } >
396- { lastSavedAt
403+ < span className = { clsx (
404+ "text-[11px] transition-colors" ,
405+ saveState === "error" ? "text-accent-red" : lastSavedAt ? "text-accent-green" : "text-text-muted"
406+ ) } >
407+ { saveState === "error"
408+ ? t ( "note.saveFailed" )
409+ : lastSavedAt
397410 ? `${ t ( "note.savedAt" ) } ${ lastSavedAt . toLocaleTimeString ( ) } `
398411 : t ( "note.unsaved" ) }
399412 </ span >
400413 < button
401414 onClick = { ( ) => void saveCurrent ( ) }
415+ disabled = { saveState === "saving" }
402416 className = "inline-flex items-center gap-1.5 text-xs px-2.5 py-1 rounded-lg border border-accent-blue/50 text-accent-blue hover:bg-accent-blue/10 transition-colors"
403417 >
404418 < Save size = { 12 } />
405- { t ( "note.save" ) }
419+ { saveState === "saving" ? t ( "note.saving" ) : t ( "note.save" ) }
406420 </ button >
407421 </ div >
408422 </ >
0 commit comments