Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Please add a PR description 🙂Respect the reviewers — a description helps others understand the changes and review them faster. Keep it short, clear, and to the point. It also serves as documentation for future reference. The PR was moved to Draft until a description is added. |
|
Thanks for adding a description — the PR is now marked as Ready for Review. |
|
Thanks for adding a description — the PR is now marked as Ready for Review. |
There was a problem hiding this comment.
🟡 Changes recommended
Several remaining races can remount stale editors, redirect after stale draft saves, or update incorrect and missing covers.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Improves note-switching safety by associating saves and editor state with the originating note.
Changes:
- Captures note IDs when saves begin.
- Recreates editors when note IDs change.
- Discards stale note-load results.
File summaries
| File | Description |
|---|---|
src/presentation/pages/Note.vue |
Captures save-time note IDs and guards cover updates. |
src/presentation/pages/HistoryVersion.vue |
Passes note IDs through history restoration. |
src/application/services/useNoteEditor.ts |
Resets editor readiness when notes change. |
src/application/services/useNote.ts |
Targets saves explicitly and guards stale loads. |
Review details
Suppressed comments (2)
src/presentation/pages/HistoryVersion.vue:108
- Although the content save is now bound to the original id, the cover update still reads
props.noteIdafter the await. If navigation changes this component's note while the save or screenshot is pending, the old version's screenshot is written as the new note's cover. Capture the id once and verify it is still current before updating.
await save(historyContent.value, undefined, props.noteId);
src/application/services/useNote.ts:284
- A captured
nullid does not fully bind a draft save to its originating page. If draft creation is in flight and the user opens an existing note, this branch still unconditionally callsrouter.replacewhen creation finishes and pulls the user away from the note they selected; switching between two draft routes is also indistinguishable because both ids arenull. Scope the post-create navigation to the originating editor/route, ideally by returning the created id and letting the still-active caller navigate.
if (currentNoteId === null) {
- Files reviewed: 4/4 changed files
- Comments generated: 4
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| return; | ||
| } | ||
|
|
||
| isEditorReady.value = false; |
| if (loadId !== currentLoadId) { | ||
| return; | ||
| } |
| const canEdit = ref(false); | ||
|
|
||
| const { isEditorReady, editorConfig } = useNoteEditor({ | ||
| noteId, |
| if (updatedNoteCover !== null && noteIdAtCallTime !== null && noteIdAtCallTime === props.id) { | ||
| await updateCover(noteIdAtCallTime as NoteId, updatedNoteCover); | ||
| } |
There was a problem hiding this comment.
🟡 Changes recommended
Several unresolved races can restore stale editor state, misroute load failures, or skip valid note loads.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (4)
src/application/services/useNoteEditor.ts:118
- This watcher can leave editor readiness in the wrong state. On an existing-note → new-note transition,
resetNote()does not changenoteTools, so nothing turnsisEditorReadyback on; conversely, an already-running tools load is not invalidated and can turn it back on with the previous note's configuration. Drive the tool-load token/readiness transition fromnoteIdas well asnoteAndUserToolsso every note change starts or supersedes one initialization.
watch(
() => toValue(options.noteId),
(newId, oldId) => {
/**
* Same note just got an id after save — keep the editor as-is
*/
if (oldId === null && newId !== null && options.getLastCreatedNoteId !== undefined && newId === options.getLastCreatedNoteId()) {
return;
}
isEditorReady.value = false;
},
{ immediate: true }
src/application/services/useNoteEditor.ts:186
- Keep the editor hidden while a changed tool set is loading. Without resetting
isEditorReadyhere, initial note data can mount the editor with the preliminary empty config, and changinghistoryId(which changes content/tools but notnoteId) never recreates the editor to consume the new data and tools.
const loadId = ++currentLoadId;
toolsUserConfigLoaded.value = false;
src/application/services/useNote.ts:225
- The load token guards only successful responses. If an older request rejects after a newer note has been requested, this
catchstill removes the current navbar page and redirects the user to an error route. Ignore errors from superseded loads before applying these side effects.
/**
* If a newer load request has superseded this one — discard stale results
* to prevent mismatched content/tools state when switching notes quickly
*/
if (loadId !== currentLoadId) {
return;
src/presentation/pages/Note.vue:181
- This condition makes cover generation impossible for a newly created note:
noteIdAtCallTimeis necessarilynull, even thoughsave()has replaced the route with the created id by this point. Havesave()return the id it actually saved, then compare that id with the current route and update that note's cover.
if (updatedNoteCover !== null && noteIdAtCallTime !== null && noteIdAtCallTime === props.id) {
await updateCover(noteIdAtCallTime as NoteId, updatedNoteCover);
- Files reviewed: 4/4 changed files
- Comments generated: 4
- Review effort level: Balanced
| } | ||
|
|
||
| watch(currentId, (newId, prevId) => { | ||
| watch(currentId, (newId, _prevId) => { |
| if (historyContent.value !== undefined) { | ||
| await save(historyContent.value, undefined); | ||
| await save(historyContent.value, undefined, props.noteId); |
| /** | ||
| * If a newer load request has superseded this one — discard stale results | ||
| * to prevent mismatched content/tools state when switching notes quickly | ||
| */ | ||
| if (loadId !== currentLoadId) { | ||
| return; |
| /** | ||
| * Case for newly created note, | ||
| * we don't need to re-load it | ||
| * If the note was just created via save() and is still a draft (no id yet), | ||
| * skip the reload to avoid recreating the editor with the same content. | ||
| */ | ||
| if (isDraftSaving) { | ||
| if (newId === lastCreatedNoteId && note.value !== null && !('id' in note.value)) { | ||
| return; |
Problem
When switching between notes, the previously opened note's content could be applied to the newly opened note. Two root causes:
save()used the id of the currently open note at completion time, so it wrote the old content into whatever note was open then.Solution
1-st Fix: destroy the old editor on note change.
useNoteEditornow watchesnoteIdand resetsisEditorReadyon every change. Since the<Editor>is rendered withv-if="isEditorReady", the previous editor is unmounted/destroyed and a fresh one is mounted only after the new note's data and tools are ready. A live old editor can no longer save into the new note.2-nd Fix: bind each save to the note it was started for.
noteChanged()captures the note id at save start and passes it tosave(), so an in-flight save always targets the correct note. Cached content (lastUpdateContent) and the note cover are only updated when the current note still matches the saved note, preventing stale data from leaking between notes.Key changes
useNote.ts—save()now takes the captured note id and uses it for the update; cached content is only stored when the current note hasn't changed. Removed the now-unneededisNoteSavingflag and the related draft-save skip logic.useNoteEditor.ts— added anoteIdoption and reset the editor state when the note changes.Note.vue— captures the note id at save time and passes it through; the cover is only updated when the captured id still matches the current note.