Back to sh0
sh0

The UI That Looked Complete

The backup dashboard had modals, CronBuilder, 3-step wizards, and a schedule table. Everything looked finished. Nothing worked. Here is what we fixed.

Thales (Juste Gnimavo) | March 27, 2026 6 min sh0
uxdashboardsveltebackupmodalsdesignfalse-confidenceintegration-testing

I opened the backups page. It had tabs for History and Schedules. A "Trigger Backup" button with a blue gradient. A "Create Schedule" button. A CronBuilder component with 17 presets and a 5-column field editor. Storage provider configuration with 13 supported backends. Three-step wizards for both triggering and scheduling. An empty state with a nice illustration. Toast notifications. Pagination.

I clicked "Trigger Backup." I selected my PostgreSQL instance. I clicked "Backup Now." The toast said "Backup triggered." A card appeared in the history list: yellow badge, "pending."

I waited. I refreshed. Still pending. I checked the logs. Nothing. The backup engine had not been called. The button was connected to an API handler that created a database row and returned 202. That was it.

The UI was perfect. The backend was a dead end.

Everything the User Could See

Here is what the backup page looked like before the fix session:

  • A professional layout with tabs (History | Schedules)
  • A schedule info banner showing "2 active schedules -- Next run: tomorrow at 02:00"
  • A CronBuilder with presets (every hour, daily at 2 AM, weekdays at 9 AM)
  • Backup cards with coloured status headers (green for completed, yellow for pending)
  • Source type icons (database or volume)
  • File size display, destination labels, encryption badges
  • Restore button, delete button, download button (disabled, "coming soon")
  • Storage provider cards with test connection functionality

From a product demo perspective, this is compelling. From a production perspective, nothing behind the buttons worked:

FeatureUI StatusBackend Status
Trigger backupButton works, toast showsHandler creates record, never executes
Schedule backupForm submits, schedule savedScheduler never spawned
Run NowIcon clickableCalls same broken trigger endpoint
DownloadButton disabledNo endpoint existed
DeleteUses browser confirm()Works but unprofessional UX
Edit scheduleNo button existsAPI endpoint works
Database listShows "0 databases"Only queries databases table, misses apps

Seven features. Two worked (delete and toggle schedule). Five were broken or missing.

What We Shipped in One Session

1. Delete Confirmation Modal

The old code used confirm() -- a browser native dialog. It looks different on every OS, cannot be styled, and provides no context about what is being deleted. We replaced it with a proper modal:

A red danger banner with the source name, Cancel and Delete buttons, a loading state while the API call completes. Professional UI for a destructive action.

2. Schedule Cards Instead of a Table

The schedule list was a 7-column table: Status, Source, Schedule, Destination, Retention, Last Run, Actions. On a standard laptop screen, the columns were cramped and the cron expression was barely readable.

We replaced it with card-based layout matching the backup history cards. Each card shows: status badge and cron expression in the header, source name with type icon, a 3-column grid for destination/retention/last run, and next run time. Action buttons (Run Now, Edit, Pause, Delete) are in the card header.

3. Schedule Button in History Tab

The "Create Schedule" button only appeared when the user switched to the Schedules tab. But the most natural moment to create a schedule is right after triggering a manual backup -- "this worked, now I want it to run automatically." We added a Schedule button with a calendar icon next to the Trigger Backup button in the History tab.

4. Backup Download

The download button was disabled with a "coming soon" tooltip. We added:

Backend: GET /api/v1/backups/:id/download -- reads the backup file from storage, sets Content-Disposition: attachment with a descriptive filename (backup-database-2a6705b2.gz), and streams the bytes.

Frontend: backupsApi.download(id) -- fetches the file as a blob, creates a temporary <a> element with download attribute, clicks it programmatically, and cleans up. The download starts immediately with the correct filename.

5. Run Now Confirmation

The Play button on schedules triggered an immediate backup with no confirmation. One misclick and a large database dump starts. We added a confirmation modal showing the source name, type, destination, and cron expression before executing.

6. Edit Schedule Modal

Schedules could be created but not edited. The API endpoint (PATCH /api/v1/backup-schedules/:id) already supported updates, and schedulesApi.update() existed in the frontend API client. We added a Pencil icon button and a modal with the CronBuilder, destination select, and retention input pre-filled with the current values.

7. CronBuilder Cleanup

The CronBuilder showed both a text input and a select dropdown for each of the 5 cron fields, making 10 UI elements in a row. For most users, the select dropdowns cover every use case. We removed the text inputs (keeping only selects), hid the field editor when a preset is selected (since the preset already sets all fields), and tightened the spacing.

The Confidence Gap

The dangerous thing about a complete-looking UI is that it creates confidence. When I saw the backup page with its modals and CronBuilder and 3-step wizards, I assumed it worked. The UI was the evidence. Code review confirmed the assumption -- the handler accepted a request and returned a response, the scheduler had a tick method, the engine had a create_backup function.

The gap was in the wiring. The handler did not call the engine. The scheduler was not spawned. The volume backup used the wrong API. The database name was wrong. Each of these is a single missing line or a wrong variable -- invisible in code review, obvious in testing.

The fix is not more code review. It is integration testing. A test that:

  1. Triggers a backup via the API
  2. Waits for the status to change from "pending" to "completed"
  3. Downloads the backup file
  4. Verifies the file is a valid gzip archive
  5. For database backups: verifies the SQL content contains the expected tables

This test would have caught every bug in this article. We did not have it. We do now.

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles