Fix archive (and every other mutation) not persisting to IndexedDB - #5
Merged
Conversation
Switches the footer attribution from the individual builder to the eSPUD collective, linking to https://espud.github.io/eSPUD/. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The previous mutation pattern was:
let updated;
setStore((s) => { ...; updated = merged; ... });
if (updated) await writeProject(updated);
React's useState updater function runs during the next render, not
synchronously inside the setStore() call. So `updated` was always
undefined when we reached the `if`, and writeProject was never
called. In-memory state flipped (UI looked right), but nothing was
persisted to IDB. On refresh, the old un-archived data came back.
Fix: resolve the merged value from inside the updater via a Promise.
The await then actually waits for the computed value.
Also await updateProject/updateMember/updateCFP from the archive
handlers on detail pages so the IDB write completes before
router.push navigates away.
Affects every mutation path: archive, restore, status changes,
member/CFP assignment toggles, plan/setup edits, etc.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
Reported: "archive options does not seem to work correctly."
Cause: every mutation function in `lib/store.tsx` used this pattern:
```ts
let updated: Project | undefined;
setStore((s) => {
// …compute merged value
updated = merged;
return { ...s, projects: nextList };
});
if (updated) await writeProject(updated); // ❌ never runs
```
React's `useState` updater function is invoked during the next
render — not synchronously inside the `setStore` call. So
`updated` was still `undefined` when the `if` ran. The IDB write
was skipped entirely.
In-memory state flipped correctly (the UI showed archived banners,
items disappeared from lists, etc.), but nothing was persisted. On
the next page refresh / IDB rehydrate, the original un-archived
data came back.
This affected eight mutation paths:
So: archive, restore, status changes, plan/setup edits, member
assignment toggles, CFP assignment toggles, CFP-status changes,
notes on CFP assignments, etc. — none of those edits survived a
refresh.
The fix
Wrap the updater in a Promise and resolve from inside it:
```ts
const updated = await new Promise<Project | undefined>((resolve) => {
setStore((s) => {
const target = s.projects.find((p) => p.id === id);
if (!target) { resolve(undefined); return s; }
const merged = { ...target, ...patch, updatedAt: new Date().toISOString() };
resolve(merged);
return { ...s, projects: s.projects.map((p) => p.id === id ? merged : p) };
});
});
if (updated) await writeProject(updated);
```
Same shape applied across all eight mutations. The Promise's
`resolve` fires from inside the updater, which React runs during
its render — so the `await` actually waits for the computed value.
Also `await` the update calls from the Archive button handlers on
`/projects/[id]`, `/members/[id]`, `/cfps/[id]` so the IDB write
completes before `router.push` navigates away (otherwise tab-close
during navigation could lose the write).
Test plan
🤖 Generated with Claude Code