Skip to content
Contact

Case studies / Tigg Accounting · Feb – Mar 2026

Why a backup can't be a button

A company backup is requested, prepared, delivered and eventually expires. I designed and built the lifecycle so people can leave the page and still get their data.

LifecycleFrontendTagged release · Mar 2026

Role

UI/UX design, all of the backup frontend

Timeline

Feb – Mar 2026

Team

Senior backend engineer, a frontend engineer (review and merge), QA

Platform

Accounting web; download page works on phones

Not mine

The backup job, the email and the retention rule (backend)

Measured outcome

None measured

the key decision

The backup outlives the page: progress is resumable and the link also arrives by email.

how deep do you want to go?

Lifecycle
Diagram from the case study. Product screenshots are being added.
That's the brief. Switch to Story for the full argument, or Deep for states, iterations and implementation.
Chapter 01

A button that would lie

"Can I get my data out?" is a question every owner of an accounting product asks sooner or later. Tigg's answer is a full company backup: products, contacts, the chart of accounts, ledger transactions and stock movements, packaged by the server as a downloadable file.

The obvious design is a "Backup Now" button with a spinner, and it would lie in ordinary situations. A full backup is a server job that runs in several parts and can take minutes. During that time the admin opens an invoice, reloads the page, switches to another organisation, or loses the connection for a moment. A button-and-spinner page forgets the job at each of those moments, even though the server keeps working.

Server jobruns for minutes Admindoes other work Button-only UIwhat it would know Shipped designwhat it does parts 1–7 complete one by one, then a final step 1234567final step done Backup Nowopens Invoicesreloadsswitches to org-bnetwork dropscomes back spinner ××××× job forgottenjob forgottenwhich org's job?"failed"nothing to show store job id+ organization keep pointeron unmount "Reconnectingto backup status…" resume only iforganisation matches tolerate 4 missedpolls; fail on 5th history row+ email link
DiagramWhy a backup can't be a button. The server job keeps running whatever the admin does. A button-only page loses track of it at every ordinary interruption; the bottom lane shows what the shipped design does instead.

So I designed the backup as a lifecycle: requested, preparing, ready, downloaded, deleted or expired, and sometimes failed. The page is only one view of that lifecycle, and it doesn't own the job.

Chapter 02

Designing around leaving, not staying

I designed the backup page and built all of its frontend, plus the public page that an emailed link opens, between February and March 2026. The backend team built the backup job itself, the email and the retention rule.

Three decisions follow from "the backup outlives the page":

  • The job is remembered. When a backup starts, the browser saves a small pointer: which job, for which organisation. Coming back to the page, or reloading it, shows "Reconnecting to backup status…" and picks up where it left off. The pointer is kept only if the organisation matches. Each organisation has its own address, and an accountant who works for several clients shouldn't see one client's backup on another client's page.
  • The link arrives by email too. The backend emails a download link when the backup is ready, so nobody has to wait on the page. Small backups finish while the admin watches; large ones don't hold them there.
  • One dropped check isn't a failure. The page checks the job's status in the background. A single failed check is ignored; four in a row are tolerated; only the fifth ends the wait.
Figma designThe backup page: one primary action, a progress panel that exists only while a job runs, the retention rule, and the history.
Chapter 03

From a live stream to polling

My first version, on 11 February, listened to a live status stream from the server, and each step of the job updated the message on screen. Within the hour I replaced the inline delete confirmation with a proper dialog and added the saved pointer, so a reload no longer lost the job. Two days later I added the email-link download page.

Then the backend side ran into trouble. The senior backend engineer flagged problems with the live stream, so on 27 February I switched the page to asking for the status at intervals. On release day, 17 March, I tuned how often it asks: the checks settled at 3 seconds, then 10, then every 30. A small job reports back within seconds, and a long one costs only two requests a minute.

Loading historyon mount Idleempty · with history Starting"Preparing backup…" Start failedtoast, pointer cleared History errortoast, empty table Resuming"Reconnecting…" Pollingnext in 3 · 10 · 30 s Transient failuren = 1…4, invisible Job not foundrare ending Completed100%, toast, refetch Failedfailed, or any part failed Gave upjob may still run ok error Backup Now error job id, +1 s 3 s mount + same org error success, n=0 n = 5 failed completed "job not found" Every terminal state clears the stored pointer and returns to Idle. Unmount clears all six timers but keeps the pointer, so a later visit resumes. If a request cannot be made, or the start returns no job, the page goes back to Idle. dashed = rarer endings; next refinement is a clearer message
DiagramThe backup page's states as shipped. Transient failures are hidden on purpose. Dashed states are the rarer endings, where a clearer message is the next refinement.
Chapter 04

An honest word about the progress bar

Polling made progress coarse. The server reports how many of its parts are done, usually seven. Between checks, the bar would sit still for up to 30 seconds and then jump, which looks broken.

So the bar is partly estimated. It shows the larger of two numbers: the real share of parts completed, and a simulated value that creeps up in small random steps. The simulated value stops at a cap, and the real number is held at 99% until the server says the backup is complete, because the final step comes after the last part. At first the cap was 91%. It came down to 61% on release day, a change that also grew out of the backend problems the senior backend engineer raised. Looking back, the lower cap also stops the estimate promising "nearly done" while the job is still early.

0% 20% 40% 60% 80% 100% 0 20 40 60 80 100 120 140 seconds since Backup Now old cap 91 (until Mar 17) cap 61 reached ≈ 23 s displayed number is simulated real polls Displayed = max(real, simulated) Simulated (expected value) Real, as seen at each poll Assumed job: 7 parts,one every 15 s, status"completed" at 115 s. Poll checks: 4, 14, 44,74, 104, 134 s. Real % per part: 0, 14, 29,43, 57, 71, 86, 99, 100.
ChartDisplayed, simulated and real progress for one assumed job. Until the real value passes the cap, the number on screen is simulated: in this example, for about the first 100 seconds. This is an illustration from the page's own timing rules, not a measurement.

I'm not fully comfortable with it. For most of a typical wait, the number is invented. The status messages cycle through a fixed set rather than following the real stage, so "Compressing data…" can appear before "Exporting products…". My first version tied each message to the part just completed, and I'd go back to that: show real stage names, keep the bar indeterminate until the server reports finer progress, and add a line saying it's safe to leave because the link will arrive by email.

Chapter 05

Ready, downloaded, deleted, expired

When the job completes, the bar reaches 100%, a toast confirms it, and the new backup appears in the history table with its time, the person who started it, its status, and a download link. The download asks the server for a short-lived link at the moment of the click and then opens it.

The emailed link opens a public page that works on a phone, because that's where emails often get read. It has four outcomes: the link is invalid, the download is being prepared, the link has expired, or "Download started". If the person isn't signed in, they're sent to sign in and then brought back to the same link.

Email linksigned details paramspresent? no Invalid backup linkGo to Login yes Preparing yourdownload... verify Unauthorized → Loginreturn to this page ExpiredGo to Tigg · Contact Support Invalid backup link ✓ Download startedGo to Tigg · Close this tab after login, back to the same link Guards: the return address must point inside Tigg, and one link is verified only once at a time. If the link's parameters land on the settings page instead, it redirects to this public page.
DiagramThe emailed-link flow. The page works without signing in until it needs to, then sends the person through sign-in and back to the same link.

Deleting a backup opens a confirmation that repeats which backup is going ("Backup from 12-03-2026 10:14:05"), because the rows look alike, and says it can't be undone. Failed backups have no delete action. On release day I added the retention rule right above the table: "Backups expire after 5 days and will be removed from this list." Rows that disappear without warning look like lost data.

Product screenshot, test company dataThe page an emailed link opens.
Chapter 06

Results and what I learned

The backup shipped in a beta release on 16 March 2026 and in a tagged release the next day, and it's in production. Measured outcome: none. I have no figures on how long backups take, how often people leave and come back, how often the page gives up while the server finishes anyway, or how many links expire unused. Those four numbers are what I'd instrument first.

Separate activity from measurement. The simulated fill made the wait feel alive, but for most of it the number was invented. Showing that work is happening doesn't require claiming how much.

Design the long wait around leaving. Resuming and the emailed link matter more than the bar, because they're what lets someone stop watching.

Name every ending. A long job can end in more ways than done or failed: the page can lose contact, or the server can forget the job. Each ending deserves its own sentence for the admin, for example "We lost contact. Your backup may still finish; check the history."

Deep dive 07

Iterations, edge cases and one neighbouring piece

Five weeks, dated

  1. 11 Feb 2026First versionPage, history table, live status stream, messages tied to each step. Within the hour: a delete dialog in place of the inline confirmation, the saved pointer, and resume on reload.
  2. 13 Feb 2026Email-link download pageThe public page, with sign-in and return to the same link.
  3. 27 Feb 2026Stream replaced by pollingPrompted by problems the senior backend engineer flagged. Simulated progress with a 91% cap and cycling messages arrived at the same time.
  4. 9 Mar 2026Tolerance for dropped checksUp to four failed checks in a row are ignored; the fifth ends the wait. Merged for testing the same day.
  5. 17 Mar 2026Release dayCap lowered to 61%; checks at 3, 10, then every 30 seconds; the 5-day retention note. I rewrote the page in a newer component style and reverted it seven minutes later; looking back, staying consistent with the codebase on release day was the safer choice.

Edge cases and how the page handles them

SituationWhat happens
Admin leaves or reloads mid-backupThe job continues; returning shows "Reconnecting to backup status…"
Admin switches to another organisationNo resume there; the saved pointer is left for its own organisation
One to four status checks failNothing visible; checking continues
Fifth check in a row failsThe wait ends with an error, though the job may still finish on the server (a gap)
The server reports any part failed"Backup failed" (or the server's message); no partial file is offered
The server no longer knows the jobThe page stops quietly (a gap)
Repeated clicks on Backup NowThe button is disabled while a job runs
No history yet"No backup history available. Click "Backup Now" to create your first backup."
Emailed link opened while signed outSign in, then back to the same link
Emailed link too old"This backup link has expired." with a way back to Tigg or to support

Nearby: who can see what, where

Later, in June 2026, I also designed a new permission editor. It shows organisation-wide and branch-specific access as two labelled regions, each with a count. I also wrote copy that separates a switched-off feature from a missing permission: "This is an organization setting, not a permission on the user you are editing." A frontend teammate integrated it into the branch-wise accounting release.

Notes on sources

No ticket or written rationale survives for the backup. The dates come from the version history; the reasons for the stream and cap changes come from my own account of the senior backend engineer's concerns. Other reasons given here are my reading in hindsight. Tigg's public changelog lists the backup feature on 9 February 2026, two days before my frontend work began; I take that as the announcement date. The diagrams are redrawn from the shipped page with fictional data.

Deep chapters (states, iterations, implementation) are hidden in Story mode. Switch to Deep at the top to read them.