Skip to content

Fix: overwrite note content on switching - #362

Open
7eliassen wants to merge 12 commits into
mainfrom
fix/notes-switching-content-overwrite
Open

7eliassen wants to merge 12 commits into
mainfrom
fix/notes-switching-content-overwrite

Conversation

@7eliassen

@7eliassen 7eliassen commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Problem

When switching between notes, the previously opened note's content could be applied to the newly opened note. Two root causes:

  1. The previous note's editor stayed mounted while the new note loaded, so its handlers remained alive and could write the old content into the new note.
  2. An in-flight 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. useNoteEditor now watches noteId and resets isEditorReady on every change. Since the <Editor> is rendered with v-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 to save(), 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.tssave() 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-unneeded isNoteSaving flag and the related draft-save skip logic.
  • useNoteEditor.ts — added a noteId option 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.

@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
codex-ui Ready Ready Preview Aug 19, 2026 4:15pm

@codex-assistant

Copy link
Copy Markdown

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.

@7eliassen 7eliassen linked an issue Aug 11, 2026 that may be closed by this pull request
@codex-assistant
codex-assistant Bot marked this pull request as ready for review August 11, 2026 12:22
@codex-assistant

Copy link
Copy Markdown

Thanks for adding a description — the PR is now marked as Ready for Review.

@codex-assistant
codex-assistant Bot marked this pull request as ready for review August 19, 2026 16:15
@codex-assistant

Copy link
Copy Markdown

Thanks for adding a description — the PR is now marked as Ready for Review.

@7eliassen
7eliassen changed the base branch from fix/editor-block-displaying to main August 19, 2026 16:16
@neSpecc
neSpecc requested a balanced review from Copilot September 16, 2026 18:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.noteId after 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 null id 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 calls router.replace when creation finishes and pulls the user away from the note they selected; switching between two draft routes is also indistinguishable because both ids are null. 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;
Comment on lines +224 to +226
if (loadId !== currentLoadId) {
return;
}
const canEdit = ref(false);

const { isEditorReady, editorConfig } = useNoteEditor({
noteId,
Comment on lines +180 to 182
if (updatedNoteCover !== null && noteIdAtCallTime !== null && noteIdAtCallTime === props.id) {
await updateCover(noteIdAtCallTime as NoteId, updatedNoteCover);
}

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 change noteTools, so nothing turns isEditorReady back 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 from noteId as well as noteAndUserTools so 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 isEditorReady here, initial note data can mount the editor with the preliminary empty config, and changing historyId (which changes content/tools but not noteId) 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 catch still 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: noteIdAtCallTime is necessarily null, even though save() has replaced the route with the created id by this point. Have save() 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) => {
Comment on lines 107 to +108
if (historyContent.value !== undefined) {
await save(historyContent.value, undefined);
await save(historyContent.value, undefined, props.noteId);
Comment on lines +220 to +225
/**
* 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;
Comment on lines 418 to 423
/**
* 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;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Note content may be overwritten

2 participants