Skip to content

Fix archive (and every other mutation) not persisting to IndexedDB - #5

Merged
shashankbl merged 2 commits into
mainfrom
shashankbl/fix-archive-persistence
May 20, 2026
Merged

Fix archive (and every other mutation) not persisting to IndexedDB#5
shashankbl merged 2 commits into
mainfrom
shashankbl/fix-archive-persistence

Conversation

@shashankbl

Copy link
Copy Markdown
Contributor

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:

  • `updateProject` / `updateMember` / `updateCFP`
  • `assignMemberToProject` / `unassignMemberFromProject`
  • `assignProjectToCFP` / `updateCFPAssignment` / `unassignProjectFromCFP`

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

  • Create a project, archive it from the detail page, navigate to /projects — gone
  • Visit /archives — shows the archived project. Reload the page — still shows it (this is the regression test; before this fix it would re-appear in /projects)
  • Restore from /archives, reload — back in /projects, gone from /archives
  • Permanent Delete from /archives, reload — fully gone
  • Same flow for a member and a CFP
  • Edit a project's plan / setup, reload — edit persists
  • Assign a member to a project, reload — assignment persists
  • Assign a CFP to a project, set status to `submitted`, reload — both persist

🤖 Generated with Claude Code

shashankbl and others added 2 commits May 19, 2026 17:57
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>
@vercel

vercel Bot commented May 20, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
pi-hub Ready Ready Preview, Comment May 20, 2026 1:06am

@shashankbl
shashankbl merged commit c7dd512 into main May 20, 2026
3 checks passed
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