Skip to content

feat(email): shift+e marks thread not done; undoable archive toasts#5033

Merged
evanhutnik merged 2 commits into
mainfrom
evan/email-mark-not-done
Jul 20, 2026
Merged

feat(email): shift+e marks thread not done; undoable archive toasts#5033
evanhutnik merged 2 commits into
mainfrom
evan/email-mark-not-done

Conversation

@evanhutnik

Copy link
Copy Markdown
Contributor

Summary

Adds a dedicated shift+e "mark not done" hotkey to the email thread view and fixes feedback gaps + a race in the thread archive flow.

Changes

  • shift+e marks a thread as not done in the email thread view — the explicit inverse of e. No-op when the thread is already in the inbox. (New entity.action.markNotDone hotkey token.)
  • Undoable toasts for the direct archive fallback. When a thread has no soup entity to drive the mark-done action (opened from a notification, direct link, search, etc.), archiving previously went through a silent archiveMutation. That path now uses a new useUndoableArchiveThreadMutation, so both directions show the same "Marked as done" / "Marked as not done" toast with an Undo button as the soup mark-done action, and entries land on the mutation undo stack (cmd+z works). Undo/redo replays the opposite flagArchived call with the same optimistic inbox_visible handling, preserving the X-Email-Link-Id header for non-primary inboxes.
  • Undo navigates back. When a soup mark-done navigated the split to the next item, undoing now re-opens the restored entity via the same caller-provided navigation callback (guards included — no forced navigation in contexts that never navigated).
  • Fix: archive mutation didn't await its request. mutationFn was void throwOnErr(...), so the mutation "succeeded" instantly: the settle-time thread refetch raced the in-flight PATCH and, when the GET won, clobbered the optimistic inbox_visible flip — making e mark the same thread as done twice in a row instead of toggling. It also meant onError never fired on real failures. Both the existing useArchiveThreadMutation and the new undoable variant now await the request.

- Add shift+e hotkey in the email thread view to mark a done thread as
  not done (inverse of 'e')
- Route the thread view's direct archive/unarchive fallback through an
  undoable mutation so both directions get a Marked as done / Marked as
  not done toast with an Undo button, matching the soup mark-done action
- On undo of a soup mark-done that navigated to the next item, navigate
  back to the restored entity via the caller's navigation callback
- Fix archive mutationFn not awaiting the request (void throwOnErr):
  the settle-time refetch raced the in-flight PATCH and could clobber
  the optimistic inbox_visible flip, making 'e' mark as done twice in
  a row instead of toggling; onError also never fired

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 12900a59-0a55-4587-acdd-93a75469ffc8

📥 Commits

Reviewing files that changed from the base of the PR and between cb467d9 and ad114d7.

📒 Files selected for processing (3)
  • apps/web/src/features/block-email/component/EmailContext.tsx
  • apps/web/src/lib/queries/email/thread.ts
  • apps/web/src/lib/queries/undo.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/src/lib/queries/email/thread.ts
  • apps/web/src/features/block-email/component/EmailContext.tsx

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added an option to mark completed email threads as not done using Shift+E.
    • Added undo and redo support when marking email threads done or not done.
    • Added navigation back to the relevant thread when undoing the action.
  • Bug Fixes

    • Improved archive and unarchive feedback with context-specific toast messages and error handling.

Walkthrough

Email threads now support undoable archive and unarchive mutations with optimistic cache updates, rollback, query invalidation, and undo/redo toast actions. The email context exposes markNotDone, and the shift+e hotkey invokes it through a new token. Mark-done undo handling also accepts navigation callbacks to return to the relevant entity.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title uses conventional commit format and stays under the 72-character limit while describing the main changes.
Description check ✅ Passed The description clearly matches the code changes, covering the new hotkey, undoable archive flow, and request-await fix.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
apps/web/src/lib/queries/email/thread.ts (1)

274-404: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Significant duplication between useArchiveThreadMutation, useUndoableArchiveThreadMutation, and replayThreadArchive.

All three implement the same throwOnErr(emailClient.flagArchived(...)) call, the same rollback-on-error against context.previousData, and the same pair of invalidateQueries calls. Extracting a shared "perform archive + rollback + invalidate" primitive would reduce the risk of the three copies drifting (e.g. the recent await-fix only touched two of the three call sites).

♻️ Sketch of a shared helper
+async function performArchiveRequest(params: ArchiveThreadParams) {
+  await throwOnErr(
+    async () =>
+      await emailClient.flagArchived(
+        { id: params.threadId, value: params.archive },
+        params.linkId
+      )
+  );
+}

Then mutationFn in both mutations and replayThreadArchive's try-block can call performArchiveRequest(params).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/queries/email/thread.ts` around lines 274 - 404, Extract the
shared archive operation used by useArchiveThreadMutation,
useUndoableArchiveThreadMutation, and replayThreadArchive into a single
performArchiveRequest helper. Centralize the emailClient.flagArchived call,
throwOnErr handling, rollback using the prior thread data on failure, and both
query invalidations in that helper, then update all three callers to use it
while preserving their existing callback behavior and error propagation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/features/block-email/component/EmailContext.tsx`:
- Around line 404-406: Update the onError handling in EmailContext to
distinguish archive and unarchive failures, using the mutation parameters
exposed by useUndoableArchiveThreadMutation (or the consolidated signature
change) to select the matching archive or mark-not-done error message. Preserve
the existing direction-specific success behavior.

In `@apps/web/src/lib/queries/email/thread.ts`:
- Around line 361-390: The archive mutation’s onError callback discards the
mutation parameters, preventing callers from distinguishing archive versus
unarchive failures. Update the onError option type and the useUndoableMutation
onError handler around threadArchiveOnMutate to pass the relevant
ArchiveThreadParams to options.onError, and update its callers to use
params.archive when selecting the failure message.
- Around line 355-404: Update useUndoableArchiveThreadMutation so the undoLabel
is determined from params.archive, using the appropriate action label for
archive versus unarchive instead of always returning “Mark Done.” Keep the
existing undo and redo behavior unchanged.

---

Nitpick comments:
In `@apps/web/src/lib/queries/email/thread.ts`:
- Around line 274-404: Extract the shared archive operation used by
useArchiveThreadMutation, useUndoableArchiveThreadMutation, and
replayThreadArchive into a single performArchiveRequest helper. Centralize the
emailClient.flagArchived call, throwOnErr handling, rollback using the prior
thread data on failure, and both query invalidations in that helper, then update
all three callers to use it while preserving their existing callback behavior
and error propagation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cd7f8691-fbb0-45b6-b62e-b3d7fc3ca474

📥 Commits

Reviewing files that changed from the base of the PR and between 8626813 and cb467d9.

📒 Files selected for processing (6)
  • apps/web/src/features/block-email/component/Email.tsx
  • apps/web/src/features/block-email/component/EmailContext.tsx
  • apps/web/src/features/block-email/util/emailHotkeys.ts
  • apps/web/src/features/next-soup/actions/make-mark-done-action.ts
  • apps/web/src/lib/core/hotkey/tokens.ts
  • apps/web/src/lib/queries/email/thread.ts

Comment thread apps/web/src/features/block-email/component/EmailContext.tsx Outdated
Comment thread apps/web/src/lib/queries/email/thread.ts
Comment thread apps/web/src/lib/queries/email/thread.ts Outdated
Direction-specific failure toast and undo label for the archive
mutation: onError now receives the mutation params, and undoLabel
accepts a per-entry function of the variables.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@evanhutnik

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@evanhutnik
evanhutnik merged commit d2ccb1b into main Jul 20, 2026
23 checks passed
@evanhutnik
evanhutnik deleted the evan/email-mark-not-done branch July 20, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant