Problem
Settings > Backup correctly disables Run Backup Now when no destination has been saved, a save is in flight, or the form is dirty (client/src/components/settings/BackupTab.jsx:177 and client/src/components/settings/BackupTab.jsx:182). However, the actual reason is exposed only through the button's title attribute at client/src/components/settings/BackupTab.jsx:183 and client/src/components/settings/BackupTab.jsx:448.
The existing tests encode that hover-only contract at client/src/components/settings/BackupTab.test.jsx:190, client/src/components/settings/BackupTab.test.jsx:200, and client/src/components/settings/BackupTab.test.jsx:232. The adjacent live region at client/src/components/settings/BackupTab.jsx:457 says only Unsaved changes or Saving…; it is blank when no destination is configured and never tells the user that Run uses saved settings.
Trigger
- Open
/settings/backup on a touch device with no saved destination, or edit any backup setting without saving it.
Run Backup Now is greyed out.
- Touch users cannot hover the title, keyboard users receive no reliable disabled-control tooltip, and the no-destination case has no adjacent explanation at all.
The gate is necessary: POST /api/backup/run re-reads the persisted destination and excludes rather than receiving the live form (server/routes/backup.js:23). The missing piece is visible explanation, not removal of the gate.
Impact
Users see an inert primary action without a dependable remedy. In the dirty case they may know something is unsaved but not that saving is specifically required before Run; in the empty-destination case they have to infer the prerequisite from the rest of the form.
Fix
- In
client/src/components/settings/BackupTab.jsx, derive a concise visible disabled reason from the existing savedDestPath / saving / dirty state and render it beside the action bar whenever the prerequisite gate is active.
- Give the hint a stable id and connect the disabled button with
aria-describedby. The title may remain as supplementary desktop affordance, but it must not be the only explanation.
- Keep
Running… as the action's own progress feedback; do not add redundant reason text for the running state.
- Update
client/src/components/settings/BackupTab.test.jsx to assert the visible reason and accessible description for no destination, dirty state, and in-flight save, plus disappearance after a successful save.
Rejected alternative: toast when the disabled button is clicked. Native disabled buttons do not dispatch the click reliably and that still leaves touch, keyboard, and first-glance users without discoverable guidance.
Dispatch rationale: model:light and effort:low fit a localized presentation/test update over state that is already correct and well covered.
Acceptance criteria
Problem
Settings > Backup correctly disables
Run Backup Nowwhen no destination has been saved, a save is in flight, or the form is dirty (client/src/components/settings/BackupTab.jsx:177andclient/src/components/settings/BackupTab.jsx:182). However, the actual reason is exposed only through the button'stitleattribute atclient/src/components/settings/BackupTab.jsx:183andclient/src/components/settings/BackupTab.jsx:448.The existing tests encode that hover-only contract at
client/src/components/settings/BackupTab.test.jsx:190,client/src/components/settings/BackupTab.test.jsx:200, andclient/src/components/settings/BackupTab.test.jsx:232. The adjacent live region atclient/src/components/settings/BackupTab.jsx:457says onlyUnsaved changesorSaving…; it is blank when no destination is configured and never tells the user that Run uses saved settings.Trigger
/settings/backupon a touch device with no saved destination, or edit any backup setting without saving it.Run Backup Nowis greyed out.The gate is necessary:
POST /api/backup/runre-reads the persisted destination and excludes rather than receiving the live form (server/routes/backup.js:23). The missing piece is visible explanation, not removal of the gate.Impact
Users see an inert primary action without a dependable remedy. In the dirty case they may know something is unsaved but not that saving is specifically required before Run; in the empty-destination case they have to infer the prerequisite from the rest of the form.
Fix
client/src/components/settings/BackupTab.jsx, derive a concise visible disabled reason from the existingsavedDestPath/saving/dirtystate and render it beside the action bar whenever the prerequisite gate is active.aria-describedby. The title may remain as supplementary desktop affordance, but it must not be the only explanation.Running…as the action's own progress feedback; do not add redundant reason text for the running state.client/src/components/settings/BackupTab.test.jsxto assert the visible reason and accessible description for no destination, dirty state, and in-flight save, plus disappearance after a successful save.Rejected alternative: toast when the disabled button is clicked. Native disabled buttons do not dispatch the click reliably and that still leaves touch, keyboard, and first-glance users without discoverable guidance.
Dispatch rationale:
model:lightandeffort:lowfit a localized presentation/test update over state that is already correct and well covered.Acceptance criteria
aria-describedby.Running…progress state.title.