diff --git a/src/apps/feed/index.tsx b/src/apps/feed/index.tsx
index d3d61a9..13d5f39 100644
--- a/src/apps/feed/index.tsx
+++ b/src/apps/feed/index.tsx
@@ -91,7 +91,7 @@ export default function FeedApp({ setTitle }: AppProps) {
variant="ghost"
size="sm"
className="h-7 gap-1.5 px-2 text-xs"
- onClick={() => openApp('notes', {})}
+ onClick={() => openApp('notes')}
>
New note
diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts
index 73c2959..01966dc 100644
--- a/src/hooks/useLocalStorage.ts
+++ b/src/hooks/useLocalStorage.ts
@@ -1,4 +1,19 @@
-import { useState, useEffect } from 'react';
+import { useCallback, useEffect, useState } from 'react';
+
+/**
+ * Fired on `window` whenever `useLocalStorage` writes a key, so every
+ * component sharing that key *within this document* stays in sync — the
+ * native `storage` event only fires in *other* tabs/documents, never the
+ * one that made the write. Without this, e.g. two "New Note" draft windows
+ * open at once would silently diverge: each holds its own React state, both
+ * write to the same localStorage entry, and neither sees the other's edits.
+ */
+const LOCAL_STORAGE_EVENT = 'app:local-storage';
+
+interface LocalStorageEventDetail {
+ key: string;
+ value: string;
+}
/**
* Generic hook for managing localStorage state
@@ -24,17 +39,30 @@ export function useLocalStorage(
}
});
- const setValue = (value: T | ((prev: T) => T)) => {
- try {
- const valueToStore = value instanceof Function ? value(state) : value;
- setState(valueToStore);
- localStorage.setItem(key, serialize(valueToStore));
- } catch (error) {
- console.warn(`Failed to save ${key} to localStorage:`, error);
- }
- };
+ const setValue = useCallback(
+ (value: T | ((prev: T) => T)) => {
+ setState((prev) => {
+ try {
+ const valueToStore = value instanceof Function ? value(prev) : value;
+ const serialized = serialize(valueToStore);
+ localStorage.setItem(key, serialized);
+ window.dispatchEvent(
+ new CustomEvent(LOCAL_STORAGE_EVENT, {
+ detail: { key, value: serialized },
+ }),
+ );
+ return valueToStore;
+ } catch (error) {
+ console.warn(`Failed to save ${key} to localStorage:`, error);
+ return prev;
+ }
+ });
+ },
+ [key, serialize],
+ );
- // Sync with localStorage changes from other tabs
+ // Sync with localStorage changes from other tabs, and from other
+ // components sharing this key in this tab (see LOCAL_STORAGE_EVENT above).
useEffect(() => {
const handleStorageChange = (e: StorageEvent) => {
if (e.key === key && e.newValue !== null) {
@@ -45,10 +73,23 @@ export function useLocalStorage(
}
}
};
+ const handleLocalChange = (e: Event) => {
+ const detail = (e as CustomEvent).detail;
+ if (detail?.key !== key) return;
+ try {
+ setState(deserialize(detail.value));
+ } catch (error) {
+ console.warn(`Failed to sync ${key} from localStorage:`, error);
+ }
+ };
window.addEventListener('storage', handleStorageChange);
- return () => window.removeEventListener('storage', handleStorageChange);
+ window.addEventListener(LOCAL_STORAGE_EVENT, handleLocalChange);
+ return () => {
+ window.removeEventListener('storage', handleStorageChange);
+ window.removeEventListener(LOCAL_STORAGE_EVENT, handleLocalChange);
+ };
}, [key, deserialize]);
return [state, setValue] as const;
-}
\ No newline at end of file
+}