Skip to content

fix(frontend): keep unsaved memory edits when a memory is added - #261

Merged
dovvnloading merged 1 commit into
mainfrom
fix/memory-edits-survive-an-add
Sep 7, 2026
Merged

fix(frontend): keep unsaved memory edits when a memory is added#261
dovvnloading merged 1 commit into
mainfrom
fix/memory-edits-survive-an-add

Conversation

@dovvnloading

Copy link
Copy Markdown
Owner

The problem

Edit a memory row, then add a new memory, and the edit reverts — silently, with nothing to undo it.

Memory 2: "Second"  ->  user types "Edited"  ->  adds "Third"  ->  Memory 2 is "Second" again

Root cause

The draft list is re-seeded from the server whenever the server's answer changes:

if (!changed) return;
lastServerMemos.current = memos;
setDraft(memos.map((value, id) => ({ id, value })));   // wholesale replace

That effect exists for a good reason — #241 added it so a row the server normalized away (trimmed to nothing, a case-insensitive duplicate) stops sitting on screen looking saved. The mistake is that it replaces rather than reconciles.

Adding a memory also changes the server's answer. So the add succeeds, memos changes, the effect fires, and every other row the user had edited and not yet saved is overwritten with the server's copy.

The comment above it claimed the opposite:

Re-seed whenever the server's answer actually changes, which leaves in-progress edits alone between saves.

That only held while nothing else changed the list — which an add always does.

The fix

Each row now remembers the server value it was seeded from, and a changed server list carries every surviving row's in-progress value across by matching on that origin:

const existing = byOrigin.get(value)?.shift();
return existing ? { ...existing, origin: value } : { id: nextId++, value, origin: value };

Matching on origin rather than current value is the whole point: an edited row no longer equals its server value, and that is precisely the row being preserved. shift() so duplicate server values claim distinct rows rather than aliasing onto one.

Both behaviours are pinned, including together

The risk with a fix like this is satisfying it by simply preferring the draft — which would put #241's bug straight back. So one of the new tests exercises both directions in the same update: a normalized-away row is dropped at the same moment an edit to a surviving row is kept.

Test Before After
unsaved edit survives an add
normalized-away row dropped and edit to surviving row kept
drops rows the server normalized away (existing, #241)

Both new tests fail against the unfixed component; the existing #241 test passes throughout.

Check Result
npm test -- --run 270 passed, 31 files
npm run typecheck clean
npm run lint clean
python -m pytest -q 907 passed

Compatibility and rollback

One component's local state. No API contract, stored data, or migration. A list that has not changed still skips the effect entirely, exactly as before. Reverting the commit restores the previous behaviour exactly.

Limits

A row the user deleted locally but has not saved is still restored when the server list changes, since the server still has it. That is pre-existing and arguably correct — the deletion is not committed until "Save changes" — but it is the same class of question as the edit case, and worth revisiting if it bites.

Reconciliation matches on value, so if the server reorders two memories the rows follow the server's order rather than the screen's. With a set-like list whose order comes from the server, that is the intended outcome.

🤖 Generated with Claude Code

Editing a memory row and then adding a new memory reverted the edit, with no
warning and nothing to undo it.

The draft list is re-seeded from the server whenever the server's answer
changes. That effect exists for a reason -- #241 added it so a row the server
normalized away (trimmed to nothing, a case-insensitive duplicate) stops
sitting on screen looking saved -- but it re-seeded wholesale. Adding a memory
also changes the server's answer, so every other row the user had edited and
not yet saved was silently overwritten with the server's copy.

Its own comment claimed the opposite ("leaves in-progress edits alone between
saves"); that only held while nothing else changed the list.

The effect now reconciles instead of replacing. Each row remembers the server
value it was seeded from, and a server list that has changed carries every
surviving row's in-progress value across by matching on that origin, building
a fresh row only for genuinely new entries. Matching on the origin rather than
the current value is the point: an edited row no longer equals its server
value, which is exactly the case being preserved.

Both behaviours are now pinned, including one test that exercises them
together -- a normalized-away row is dropped in the same update that an edit
to a surviving row is preserved -- so the fix cannot be satisfied by simply
preferring the draft over the server or the reverse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dovvnloading
dovvnloading merged commit dccd810 into main Sep 7, 2026
7 checks passed
@dovvnloading
dovvnloading deleted the fix/memory-edits-survive-an-add branch September 7, 2026 15:24
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.

1 participant