Surface save failures in the UI - #170
dantheuber wants to merge 7 commits into
Conversation
📊 Coverage Report
|
🧪 E2E Test Results✅ 38 passed, 0 failed, 0 skipped
|
Issue #169. The debounced clip and settings saves swallowed their errors into the console, so an unwritable store looked healthy until the next launch. The hook now returns saveError alongside loadError: the two save paths report independently and the list shows the first still failing, so a settings write that lands cannot mask a refused clip write. Saving is not disabled and no clips are dropped - the in-memory list is still the truth, and the next successful save clears the report. Files: providers/clips/storage.ts, storage.test.tsx. UI surfacing (toast plus banner) follows in the next commit.
Issue #169. A save that never lands now shows in the window instead of only the devtools console: the provider toasts "Clips could not be saved" with the reason on the transition into the failing state, and the list carries a persistent banner for as long as it lasts, since the debounced save retries on every change and a toast is easy to miss. Key decisions: LoadFailedBanner and the new SaveFailedBanner share one StorageFailedBanner shell (title, lines, reason) rather than a second component; the load banner wins when both are set, because that path pauses saving and its copy says so. A ref gates the toast so repeated failures cannot spam one per debounce tick, and it resets once a save lands so a later failure is announced again. Nothing is disabled and no clips are dropped. Files: components/clips/Clips.tsx (+test), providers/clips/index.tsx (+test), providers/clips/types.ts. Note for a resumed run: the provider test drives repeat saves through the settings-update listener, and several providers register one, so it fans the update out to all of them.
Replace the ternary-and-&& expression in the list render with a small StorageBanner component that spells out the precedence between an unreadable history and a refused save. Build the load banner's lines with a plain conditional instead of a spread, and note the new save failure banner and toast in the README.
cb-jeeves
left a comment
There was a problem hiding this comment.
This adds a save-failure path to useClipsStorage and a SaveFailedBanner plus toast in the clips window, with the banner choice pulled into its own StorageBanner component. The UI work is sound, but the feature does not fire in the shipped app: the main-process save wrappers swallow their errors and resolve false, so the renderer's catch never runs. That is the one blocking item. The other two are worth a look while you are in these files.
All four lenses ran this round.
The submit pass ran on claude-opus-5 after its primary model failed.
The correctness pass ran on claude-opus-5 after its primary model failed; maintainability pass ran on claude-opus-5 after its primary model failed; ux-dx pass ran on claude-opus-5 after its primary model failed.
saveClips and saveSettings in the main process caught every error and resolved false, so the renderer's catch never ran in the shipped app and the save-failed banner could not appear. They now log and rethrow like exportData and importData, so the handler rejects and the storage error's own message reaches the window. Electron wraps a rejected handler as "Error invoking remote method 'channel': Error: reason"; errorText strips that wrapper so banners and toasts show only the reason.
A failed settings save used to render the clip-loss copy even though every clip was on disk. saveError now carries its source, the list shows settings-specific lines for a refused clip limit, and the toast title follows suit. The hook keeps the two outcomes in one state value with a functional update instead of a ref mirrored into state.
|
Responded to review round 1, pushed 5eca56d:
|
cb-jeeves
left a comment
There was a problem hiding this comment.
The refused-save path now rejects through to the renderer and the banner names the file, which closes the blocking item from round 1. No further findings.
The submit pass ran on claude-opus-5 after its primary model failed.
The correctness pass ran on claude-opus-5 after its primary model failed; maintainability pass ran on claude-opus-5 after its primary model failed; ux-dx pass ran on claude-opus-5 after its primary model failed.
This branch assembles the listed issues for review.
Issues in this consist
Closes #169
What & why
Debounced clip and settings saves that failed were only ever logged to the devtools console, so an unwritable store looked healthy until the app was relaunched. This surfaces those failures in the window itself: a toast on the transition into a failing state, plus a persistent banner for as long as the failure lasts.
Changes
useClipsStoragenow reportssaveErroralongside the existingloadError, tracking clip and settings save failures independently so one succeeding can't mask the other failing.Clips.tsxrenders the failure via a newStorageBannercomponent that makes the precedence explicit: an unreadable history banner wins over a refused-save banner, since the load-failure path also pauses saving.LoadFailedBannerandSaveFailedBannershare oneStorageFailedBannershell (title, lines, reason).Review notes
Clips.tsx.