Skip to content

Surface clip and settings save failures in the UI #169

Description

@cb-jeeves

Problem

In src/renderer/src/providers/clips/storage.ts, a failure to load stored history is handled carefully: the catch at line 95 sets loadError, Clips.tsx:87 renders <LoadFailedBanner>, and saving stays disabled for the session so blank state is never written over the real file. The doc comment on the hook spells this contract out.

A failure to save gets none of that. Both save paths swallow the error into the console and return:

  • storage.ts:154catch (error) { console.error('Failed to save clips to storage:', error); }, the debounced storageSaveClips that persists the clip history and lock state.
  • storage.ts:174 — the same shape for storageSaveSettings({ maxClips }).

Nothing in the window changes. For an app whose whole premise is that it quietly remembers everything you copy, that is the worst failure to hide: if the encrypted store is unwritable (disk full, permissions, a keystore hiccup, the main process wedged), the user keeps copying all day, sees a full, healthy-looking clip list, and discovers on next launch that none of it survived. Locked clips — the ones deliberately marked as worth keeping — are lost the same way. The maxClips case is quieter but equally confusing: change the limit in Settings, it appears to take, and it is back to the old value after a restart.

The pieces to fix it are already in place. useToast is imported and called in this provider's own parent, src/renderer/src/providers/clips/index.tsx:152, and LoadFailedBanner already exists for the persistent case.

Proposal

Surface save failures in the UI, in one reviewable change:

  1. Have useClipsStorage track a save failure alongside loadError and return it — e.g. saveError, set when storageSaveClips or storageSaveSettings rejects, and cleared on the next save that succeeds. Use the existing errorText helper for the message.
  2. Show it. A toast on the first failure ("Clips could not be saved — ") plus a persistent indicator while the condition lasts reads best: a toast alone can be missed, and the debounced save retries on every change, so the state can persist for a long time. Reusing/parameterising LoadFailedBanner for a "not saving" variant keeps this to one banner component.
  3. Do not disable anything or drop clips. Unlike the load path, a failed save is safe to keep retrying — the in-memory list is still the truth. Just make sure repeated failures do not spam a toast per debounce tick (only notify on transitions into the failing state).

Out of scope: retry/backoff policy in the main process, changes to the storage format or encryption, and anything about the load path, which already behaves correctly.

Value

Anyone whose clip store becomes unwritable finds out while they can still act on it — copy the important thing elsewhere, free disk space, restart the app — rather than after a restart has taken the history with it. You would know it worked by making storageSaveClips reject (a mocked IPC failure in a unit test, or a read-only store directory by hand) and seeing the app say so instead of only the devtools console.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    freightFreight: queue this issue for automated deliveryfreight:in-reviewFreight: consist finalized, awaiting human reviewimproverIssue originated from an improvement sweep

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions