From 456d637a24e9ee92abbf918f0c33a8422efc013b Mon Sep 17 00:00:00 2001 From: Hermes Date: Sat, 1 Aug 2026 13:16:05 +0000 Subject: [PATCH] fix: isolate note editor in memoized pane to prevent focus stealing on parent re-render --- fix_pane.py | 354 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 354 insertions(+) create mode 100644 fix_pane.py diff --git a/fix_pane.py b/fix_pane.py new file mode 100644 index 0000000..998206f --- /dev/null +++ b/fix_pane.py @@ -0,0 +1,354 @@ +import sys + +with open(sys.argv[1], 'r') as f: + content = f.read() + +# Replace the entire NotesPage function with a version that uses a separate memoized right pane +old = '''function NotesPage() { + const queryClient = useQueryClient(); + const [search, setSearch] = useState(""); + const [selectedNoteId, setSelectedNoteId] = useState(null); + const [showBacklinks, setShowBacklinks] = useState(false); + const [showVersions, setShowVersions] = useState(false); + const selectedNoteRef = useRef(null); + + useRealtime({ enabled: true }); + + const { data: notesData, isLoading } = useApiQuery>( + ["notes", search], + "/notes?limit=200" + (search ? "&search=" + encodeURIComponent(search) : "") + ); + + const notes = notesData?.items || []; + + const createMutation = useMutation({ + mutationFn: () => api.post("/notes", { title: "Untitled", content: "" }), + onSuccess: (note) => { + queryClient.invalidateQueries({ queryKey: ["notes"] }); + selectedNoteRef.current = note; + setSelectedNoteId(note.id); + }, + }); + + const updateMutation = useMutation({ + mutationFn: ({ id, data }: { id: string; data: any }) => api.patch("/notes/" + id, data), + onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["notes"] }); }, + }); + + const deleteMutation = useMutation({ + mutationFn: (id: string) => api.delete("/notes/" + id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["notes"] }); + selectedNoteRef.current = null; + setSelectedNoteId(null); + }, + }); + + const selectNote = async (note: Note) => { + try { + const detail = await api.get("/notes/" + note.id); + selectedNoteRef.current = detail; + } catch { + selectedNoteRef.current = note; + } + setSelectedNoteId(note.id); + setShowBacklinks(false); + setShowVersions(false); + }; + + const selectedNote = selectedNoteRef.current; + + return ( +
+ {/* Left pane - note list */} +
+
+
+ + setSearch(e.target.value)} className="pl-8" /> +
+
+
+ +
+ + {isLoading ? ( +
Loading...
+ ) : notes.length === 0 ? ( +
No notes yet.
+ ) : ( +
+ {notes.map((note) => ( + + ))} +
+ )} +
+
+ + {/* Right pane - editor */} +
+ {selectedNote ? ( + <> +
+ +
+ + + + + + + + + Delete Note + Are you sure you want to delete "{selectedNote.title}"? + + + Cancel + deleteMutation.mutate(selectedNote.id)} className="bg-destructive text-destructive-foreground">Delete + + + +
+
+
+ { if (selectedNoteRef.current) { updateMutation.mutate({ id: selectedNoteRef.current.id, data: { content: html } }); } }} /> +
+ {/* Backlinks section */} + {showBacklinks && selectedNote.backlinks && selectedNote.backlinks.length > 0 && ( +
+

Linked from

+
+ {selectedNote.backlinks.map((bl) => ( +
+ {bl.noteTitle} +
+ ))} +
+
+ )} + {/* Version history */} + {showVersions && ( +
+

Version History

+

Version history available via API.

+
+ )} + + ) : ( +
+
+ +

Select a note or create a new one

+
+
+ )} +
+
+ ); +}''' + +new = '''// Memoized right pane - only re-renders when note changes, not on parent re-renders +const NoteEditorPane = memo(function NoteEditorPane({ note, onDelete }: { note: Note; onDelete: (id: string) => void }) { + const [showBacklinks, setShowBacklinks] = useState(false); + const [showVersions, setShowVersions] = useState(false); + const queryClient = useQueryClient(); + + const updateMutation = useMutation({ + mutationFn: ({ id, data }: { id: string; data: any }) => api.patch("/notes/" + id, data), + onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["notes"] }); }, + }); + + return ( + <> +
+ +
+ + + + + + + + + Delete Note + Are you sure you want to delete "{note.title}"? + + + Cancel + onDelete(note.id)} className="bg-destructive text-destructive-foreground">Delete + + + +
+
+
+ { updateMutation.mutate({ id: note.id, data: { content: html } }); }} /> +
+ {/* Backlinks section */} + {showBacklinks && note.backlinks && note.backlinks.length > 0 && ( +
+

Linked from

+
+ {note.backlinks.map((bl) => ( +
+ {bl.noteTitle} +
+ ))} +
+
+ )} + {/* Version history */} + {showVersions && ( +
+

Version History

+

Version history available via API.

+
+ )} + + ); +}); + +function NotesPage() { + const queryClient = useQueryClient(); + const [search, setSearch] = useState(""); + const [selectedNoteId, setSelectedNoteId] = useState(null); + const selectedNoteRef = useRef(null); + + useRealtime({ enabled: true }); + + const { data: notesData, isLoading } = useApiQuery>( + ["notes", search], + "/notes?limit=200" + (search ? "&search=" + encodeURIComponent(search) : "") + ); + + const notes = notesData?.items || []; + + const createMutation = useMutation({ + mutationFn: () => api.post("/notes", { title: "Untitled", content: "" }), + onSuccess: (note) => { + queryClient.invalidateQueries({ queryKey: ["notes"] }); + selectedNoteRef.current = note; + setSelectedNoteId(note.id); + }, + }); + + const deleteMutation = useMutation({ + mutationFn: (id: string) => api.delete("/notes/" + id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["notes"] }); + selectedNoteRef.current = null; + setSelectedNoteId(null); + }, + }); + + const selectNote = async (note: Note) => { + try { + const detail = await api.get("/notes/" + note.id); + selectedNoteRef.current = detail; + } catch { + selectedNoteRef.current = note; + } + setSelectedNoteId(note.id); + }; + + const selectedNote = selectedNoteRef.current; + + return ( +
+ {/* Left pane - note list */} +
+
+
+ + setSearch(e.target.value)} className="pl-8" /> +
+
+
+ +
+ + {isLoading ? ( +
Loading...
+ ) : notes.length === 0 ? ( +
No notes yet.
+ ) : ( +
+ {notes.map((note) => ( + + ))} +
+ )} +
+
+ + {/* Right pane - editor (memoized, won't re-render on parent state changes) */} +
+ {selectedNote ? ( + deleteMutation.mutate(id)} /> + ) : ( +
+
+ +

Select a note or create a new one

+
+
+ )} +
+
+ ); +}''' + +content = content.replace(old, new, 1) + +with open(sys.argv[1], 'w') as f: + f.write(content) +print('Done')