v1.37.0
LatestSeptember 15, 2026Restores are several times faster. Restoring a virtual disk, a SQL backup or a system image used to fetch and check one 64 KB piece at a time, opening and closing the backup file for every piece; a 261 GB virtual machine took 3 h 37 m from a local disk. The agent now reads whole runs of consecutive pieces in single 8 MB requests, keeps several requests in flight while the previous one is being written, decrypts and verifies on worker cores, and writes the output sequentially into a preallocated file. Every piece is still checked against its recorded hash before it is written. Measured on the customer host from local packs: 115 MB/s with the old loop, 197 MB/s with the new one, with per-piece logging and screen updates that also slowed the shipped version removed. Cloud restores keep eight downloads in flight so a link that caps each connection is no longer the ceiling.
Cloud backups upload several pieces at once. Many business links cap a single connection at 1–3 MB/s while allowing many connections; the agent uploaded one 128 MB piece at a time over a fresh connection each time. Pieces are now staged on disk as they are sealed and uploaded in parallel (8 streams in Performance mode, 3 in Background, adjustable in Settings → Backup), each stream signed and sent without buffering in memory, over pooled keep-alive connections, and a failed upload is retried three times before the backup is failed. Memory use does not grow with the number of streams.
Hyper-V incremental backups read only what changed. A running VM is now backed up through a Hyper-V recovery checkpoint that becomes a change-tracking reference point; the next incremental asks Hyper-V which blocks changed since that point and reads only those, so a daily incremental of a 300 GB VM reads megabytes instead of the whole disk. Each report states whether change tracking was used, and a VM whose tracking was reset or unavailable is read in full and re-anchored rather than trusting a partial answer. Where a recovery checkpoint cannot be taken the previous production-checkpoint method is used unchanged. Older agents refuse to restore these new manifests, which is correct: they cannot apply changed ranges.
Restoring a chain of Hyper-V incremental backups made since v1.24 no longer merges complete disk images as if they were differencing disks. Those incrementals contain the whole disk (deduplicated), so the newest one is now restored on its own; only chains from before v1.24 still go through Merge-VHD.
Weekly restore verification now covers Hyper-V backups: a random sample of blocks from the newest run of each VM set is fetched, decrypted and compared with the hash recorded at backup time, and whole changed ranges of the newest incremental are checked end to end. Virtual disks never fit the 25 MB file sample, so those sets had always reported as skipped.
VMs that must never be frozen still get change tracking. A backup set that opts a VM out of guest quiescing now takes a crash-consistent recovery checkpoint (no freeze) instead of reading through a volume shadow, so its incrementals read only the changed blocks like every other VM.
A VM that runs on a checkpoint chain left by another product can now be backed up as it is. The base disk and every checkpoint file are backed up (the unchanged parent files are not re-read on later runs) and restored as the same chain, re-linked and checked with Hyper-V, so the VM comes back exactly as it runs today. The old checkpoint is not a revert point in the restored copy.
Merging another product's checkpoint no longer holds up the backup wizard. The merge runs in the background after the set is saved; the Server Backup page shows its progress, elapsed time and size with a Retry, backups skip that VM and say why until the merge completes, and a merge interrupted by an agent restart is resumed.
Scripted (silent) installs now finish on their own: the staged activation code is redeemed, the device is registered and the agent starts in the tray without anyone opening the app on the machine.
Very large objects are uploaded in parts. Anything over the 5 GB single-upload limit of S3-compatible storage now goes up as a multipart upload instead of failing at the end; an attempt to send more than 5 GB in one request is refused up front with a clear error.
v1.36.6
September 15, 2026The background service task now always runs the installed agent. On one host the Windows scheduled task created by a very old build still pointed at that old program in a Downloads folder and ran it as SYSTEM at every boot, because no later installer ever re-pointed the task. The installer now repairs the task on every install and upgrade, the app checks it at start-up, and the agent refuses to register a task for any program outside its install folder.
Release candidates shared for verification are now stored under a separate rc address with a short cache lifetime, so a re-uploaded candidate can never be served stale by the download network. Released versions are unchanged.
v1.36.5
September 13, 2026Backing up one virtual machine no longer pauses every other VM on the same disk. Until now the snapshot used for a running-VM backup asked Hyper-V to briefly freeze and then merge every VM on that volume, not just the one being backed up, on every run. The agent now takes an application-consistent production checkpoint of the target VM only, reads the resulting static disk directly, and removes the checkpoint as soon as the read finishes; no other VM is touched. Checkpoints are named with the run and process that own them, removed on completion, failure or cancel, and swept on the next start if the agent was killed mid-run; a checkpoint that could not be removed is reported rather than left silently.
Each Hyper-V report now states, per VM, whether the backup was application-consistent or crash-consistent and why, and how it was read. A guest that cannot be quiesced (no integration services, or a failed quiesce) falls back to a crash-consistent backup and is flagged in the report, the portal and the emailed summary rather than being discovered at restore time. Sets can also opt a VM out of quiescing entirely for workloads where even a brief freeze is unacceptable.
VMs that already run on a checkpoint chain left by another product are flagged when you pick them, with an option to merge that chain first or skip the VM, instead of silently backing up one layer of an incomplete disk.
Compression is now level 4 (was 6), roughly 1.5 times faster per core for a few percent more storage on typical virtual-disk data. Existing backups restore unchanged.
v1.36.4
September 12, 2026Automatic updates now install. Since v1.26 the update step wrote its helper script in a text encoding that Windows PowerShell misread on standard English systems, so the app would download the new version, say Update ready, close, and then nothing would happen. The script is now written in a form PowerShell reads correctly everywhere. One-time note: because the faulty step belongs to the OLD version, this release cannot install itself automatically on machines running v1.36.3 or earlier. Install it once from the download page; every update after that applies on its own.
v1.36.3
September 11, 2026Large backups no longer wait until the end to upload. Backup data is now sent to your destination in 128 MB pieces while the backup is still running, instead of being held in memory and uploaded once at the finish. A 300 GB virtual machine used to keep the agent at well over 16 GB of memory for the whole run and would not complete at all on a machine with 32 GB; memory now stays flat at a few hundred megabytes regardless of size, and if a piece cannot be uploaded the backup stops immediately instead of discovering the failure hours later.
Backups run up to 6 times faster on multi-core machines. Virtual disks are now processed in parallel by up to 8 workers in Performance mode (2 in Background mode), and file hashing uses the hardware-accelerated Windows crypto library instead of a software routine, which is about 19 times faster on its own. Measured on a 16-core server: 30 MB/s before, 189 MB/s after, with identical backup contents either way.
The compression-level setting is now actually applied. Since v1.0 every backup had used the default level regardless of configuration. The default is unchanged, so existing backups and storage use are unaffected.
v1.36.2
September 10, 2026Running Hyper-V virtual machines back up end to end. v1.36.1 fixed the snapshot request, but the agent then misread the reply from the snapshot tool, threw the fresh snapshot away, and reported the VM as failed — after spending several minutes creating it. The reply is now read as the tool actually writes it, and the snapshot metadata file the tool produces is kept in the temp folder and cleaned up instead of being dropped into the install folder on every run.
Plans with no storage limit now say "Unlimited" on the dashboard, in Settings and in the command line, instead of "0 B limit". The desktop also reports its version on every check-in, so the portal and admin console show which version each computer is actually running rather than the one it first enrolled with.
v1.36.1
September 8, 2026Backing up a running Hyper-V virtual machine works again. Since v1.24 the agent asked Windows for a kind of volume snapshot that only storage-array hardware can provide, so on an ordinary Hyper-V host every attempt to back up a running or paused VM stopped with "VSS shadow copy failed", no matter how healthy the host was. Virtual machines that were shut down or in a saved state were never affected, because they are read directly and never use a snapshot. Running VMs are now captured through the Microsoft Hyper-V VSS Writer as intended, so the guest is quiesced and the backup is application-consistent.
v1.36.0
September 5, 2026Notification email addresses no longer disappear. The desktop agent treated its own list as the only truth, so an address added in the Customer Portal was deleted again the next time anyone saved Settings on a PC. The two lists are now reconciled in both directions — addresses added in the portal appear on the desktop, addresses removed on the desktop are removed everywhere, and nothing is lost when a machine is offline.
v1.35.0
August 19, 2026Verified restorable — on a schedule (weekly by default), every device restores a small random sample of files from its newest backup to a scratch folder, checks each file against the hash recorded at backup time, and reports the result. The portal, the admin console, and the emailed consolidated report now show whether each device is verified restorable, so a backup that cannot be restored is found by the schedule rather than on the day you need it.
Honest daily reports — the consolidated report no longer calls a day with zero backups "healthy". The subject line flags [Attention] when backups failed, when no backups ran at all, or when any device has gone 7 days without one, with a banner explaining what needs looking at. Device counts also stop including machines you have archived, moved to Trash, or revoked.
Two-factor authentication you can actually set up — Settings → Security in the portal now enrols an authenticator app (QR code or manual key), verifies it, and lets you remove it. Change password works from the same place. Accounts without two-factor see a reminder on every page.
Cancel plan from the portal — account owners can cancel at the end of the current paid term from Billing, and reverse it any time before that date. Backups keep running until the term ends, and cancelling never deletes data.
Immutable storage — the backup bucket now keeps every deleted or overwritten object for 14 days under an object-lock rule, and the platform refuses to reclaim storage sooner than that. A stolen password or a mistaken delete cannot erase a backup before you notice.
Crash and error reporting in the desktop agent and the web apps, a live status page backed by real health checks, French and Spanish in the customer portal and admin console, and a ransomware early-warning alert when a backup run reports an abnormal number of changed or deleted files.
v1.34.0
July 27, 2026Silent install for managed customers — administrators generating an enrollment code now also get a copy-paste PowerShell command that downloads Backup Engine, installs it silently, and enrolls the device automatically the first time the app starts. Nothing needs to be typed on the customer's PC; the device signs in and lands in the right site on its own.
Managed computers are locked to their organization — once a device is enrolled to a managed account, sign-ins from unrelated Backup Engine accounts are refused with a clear message. Team members of the organization sign in normally, and the managing administrator can sign in as the customer (audited) without walking through the customer picker.
Account activation that actually completes — clicking an invitation email now lands on the set-password page reliably (previously some invites dead-ended on the home page), ends on a "Thank you — your account is activated" confirmation pointing at the Customer Portal, and the account immediately shows Active to your administrator. Admins can also activate an account directly, optionally setting the password at the same time.
Enrollment confirmation on the desktop — after activating with a code, the success screen shows "Enrolled into {organization} — {site}" so technicians can verify the device landed in the right place at a glance.
v1.33.0
July 19, 2026Incremental detection on encrypted manifests — every file was being re-classified as "New" on each run, so an unchanged backup set reported thousands of new files and the counts on your Reports and job history were inflated. Nothing was ever backed up twice (deduplication meant the unchanged data was never re-uploaded), but the numbers you were reading did not reflect what actually changed. Runs from v1.33.0 onward correctly report new, modified, and unchanged files.
Back up individual files, not just folders — a backup set (and a site backup policy rule) can now point at a single file instead of a whole directory. This is what makes the browser bookmark presets work: a policy can protect just the bookmarks file on each machine without pulling in the rest of the browser profile.
Android app v0.1.0
MobileJuly 19, 2026First Android build — the Backup Engine mobile app is now available as a direct APK download (about 58 MB) from the Download page. This is Phase 1: it backs up photos, contacts, calendars, and documents from your phone to the same account and storage region as your computers, and you sign in with your existing Backup Engine credentials.
Sideload install, early access. The APK is not on Google Play yet, so you install it directly: download it on the phone, open the file, and allow your browser to "Install unknown apps" when prompted (required for anything installed outside the Play Store). Step-by-step instructions are on the Download page.
Known limitations of this build: it is signed with test keys rather than a Play Store release key, and it does NOT auto-update — to move to a newer build you download the current APK from the Download page and install it over the top. Automatic updates arrive with the Google Play release. The iOS app is not released yet.
The mobile app ships on its own version track, so the Android version number (v0.1.0) and the desktop agent version (v1.33.0) advance independently and are not meant to match.
v1.32.0
July 1, 2026Run Backup Now from anywhere — trigger an immediate backup of any device's backup set from the Customer Portal or the admin Control Center. The device picks the request up within seconds (when online) and runs the backup silently in the background, exactly as if Backup Now had been clicked on the machine itself. Requests expire safely if the device stays offline, and a run that's already queued or running is never doubled up.
Backup screen: "This device / Other devices" view — backup sets that belong to your account's other computers are no longer mixed into one list. A new selector splits the Backup screen into your own sets and a read-only view of every other device's sets, grouped by computer, so multi-device accounts can see their whole protection picture at a glance.
Auto-update reliability — fixed the case where the updater said "the app will reopen automatically" but nothing came back. If the Windows permission prompt is declined or the install fails, the app now restarts itself on the previous version and shows a clear "the last update did not complete" notice on the Dashboard (with a pointer to the update log) instead of failing silently. The post-install relaunch also gained multiple fallback layers so the app reliably reopens after a successful update.
Billing groundwork — the Customer Portal's Billing page now shows your payment method (card, pre-authorized debit, or cheque — set by our billing team) and your real invoice history as invoicing goes live.
v1.31.0
June 24, 2026Backups paused banner — when a device has been archived, moved to Trash, or revoked from the portal, the desktop app now shows a clear "backups are paused" banner explaining why and how to resume, and the scheduler stops attempting runs on that device.
Storage usage accuracy — the agent now reports the exact encrypted size of every uploaded chunk, so the storage meter on your dashboard and portal reflects what is actually stored in the cloud for all new backups.
v1.30.0
June 23, 2026Manage your backup sets from the portal — view and edit a device's backup sets (folders to include/exclude, schedule, VSS/CDP, email options) from the Customer Portal or the admin Customer-360 device view, and the changes now apply to the device automatically on its next sync. This release makes the desktop agent honor edits made on the server: a backup set changed in the portal wins even if the device had a pending local tweak, and removing a set from the portal removes it on the device too.
Device management across the platform — archive a device (parks it and stops backups, reversible), delete it to a Trash/recycle bin (restorable), or permanently delete it. Permanently deleting a device reclaims its storage. Available from both the Customer Portal and the admin Control Center, including drill-down to a full device detail view.
Duplicate-device detection — when the same computer appears more than once in an account (e.g. after a reinstall or signing in with a different login), it's now flagged as a possible duplicate so you can archive or remove the redundant copy. Reinstalling a previously archived/trashed machine now registers cleanly as a fresh, active device.
Revoked, archived, and trashed devices are now clearly labelled everywhere they appear, and are correctly excluded from device counts, the dashboard, and plan device-slot usage.
v1.29.1
June 21, 2026Windows installer now bundles the Microsoft Visual C++ 2015–2022 runtime (app-local). On a clean Windows 10 / Windows Server PC without the VC++ Redistributable installed, the agent failed to start after install with "The code execution cannot proceed because MSVCP140.dll (or VCRUNTIME140_1.dll) was not found." The installer now ships these runtime DLLs alongside the app, so Backup Engine runs on a fresh Windows install with no prerequisites. If a machine already updated to v1.29.0 and won't launch, install v1.29.1 once from this page to fix it.
v1.29.0
June 21, 2026Activation codes & QR enrollment — set up Backup Engine on a new computer from a one-day code. From the portal (or the admin Customer-360 → Sites tab), generate an activation code for a specific site or the default site, optionally emailed to anyone, with a scannable QR. On the desktop login, choose "Have an activation code?", enter it, and the device signs in and registers to the correct site automatically. Codes expire in a day (configurable up to 7), can be single-device or multi-device, and are revocable at any time.
Install on behalf of a customer (Tech Bench staff) — an administrator signing into the desktop app can set this computer up as a device for a managed customer: pick the customer and the site, and the agent registers under that customer's account. Available to admin staff with the appropriate rights; every install-on-behalf is audited.
Site backup policies — define which folders must be backed up (e.g. Desktop and My Documents) once, and enforce them across one site or every site. Rules use cross-platform templates ({HOME}/Documents, …) that the agent resolves per operating system, and can be marked required (enforced) or optional. Each device automatically keeps a managed "Backup policy" set in sync with the rules on every login.
Multi-site organization — name your sites/locations, see each site's devices and team, assign team members per site with per-site roles, and view a consolidated report grouped by site (emailable + subscribable). A "Get Started" wizard walks new organizations through setup. Admins can create sites and generate enrollment codes on a customer's behalf from the Customer-360 view.
Admin-provisioned accounts are no longer asked to re-pick a plan on first desktop login. When an administrator creates a customer with a plan already assigned, the desktop now skips the plan/region picker and goes straight to device registration.
v1.27.3
June 8, 2026Auto-update on the EXE installer track was silently failing: customer reported v1.27.0/v1.27.1 → v1.27.2 went through the download + "Update ready, will restart" message, the old app closed, and then nothing happened — clicking the desktop icon brought back the OLD version. Root cause: the apply-update PowerShell script called Start-Process on setup.exe without -Verb RunAs. setup.exe has PrivilegesRequired=admin in its Inno manifest, so Start-Process fell back to CreateProcess + requireAdministrator. In a detached PowerShell child (which our updater is — we Process.start it with ProcessStartMode.detached and immediately exit the GUI), CreateProcess can silently refuse to elevate without ever surfacing UAC. Net effect: app killed by our process-stop step, setup.exe never ran, then the script "relaunched" the original EXE at the unchanged install path → user sees the old version restored exactly as before. Fix: both Start-Process calls in _buildExeApplyScript (the silent /VERYSILENT path and the fallback visible-installer path) now pass -Verb RunAs, which uses ShellExecute("runas") to guarantee the UAC dialog renders on the user's interactive desktop. NOTE: this fix only affects auto-update FROM v1.27.3 onwards because the apply script is generated by whichever version is currently installed. Customers stuck on v1.27.0–v1.27.2 must install v1.27.3 manually from the download page once.
scripts/upload-installer.mjs now sets explicit Cache-Control headers on every R2 upload. Versioned keys (v1.27.3/backup-engine-windows-1.27.3.exe) get `public, max-age=31536000, immutable` since the bytes never change once published — CDN edges serve them without an origin round-trip. The latest-alias key (latest/windows/backup-engine-windows.exe) gets `public, max-age=300` so new releases propagate to the CDN edge within 5 minutes instead of however long the upstream default TTL happened to be. Before this fix, the customer who tested v1.27.2 saw the website still serving the v1.27.0 setup.exe through the /latest/ link even after the v1.27.2 upload had landed — they had to grab the versioned URL directly to get the right binary.
v1.27.2
June 8, 2026Continue with Google on the desktop opened a browser but only ever launched a TRUNCATED OAuth URL — Supabase fell back to implicit flow + the site URL as redirect, so the browser landed on www.backupengine.com/en#access_token=… instead of the desktop's loopback listener, and the desktop app showed a "Could not open browser… 'redirect_to' is not recognized as an internal or external command" error. Root cause: Windows cmd.exe interprets `&` as a command separator, so `cmd /c start "" "https://…/authorize?provider=google&redirect_to=…&code_challenge=…"` saw four commands instead of one URL — only the first segment up to the first `&` actually got handed to ShellExecute. Fix: switch the Windows browser-launch shim from `cmd /c start` to `rundll32 url.dll,FileProtocolHandler <url>`, which routes through ShellExecute directly with no shell interpretation. URL is now passed verbatim regardless of `&`/`?`/`#` content. Same fix applied to UpdaterService's url opener so the App Installer URI (ms-appinstaller:?source=…) is also safe.
v1.27.1
June 8, 2026Complete your account dialog on the desktop for brand-new Google sign-ups. Picks plan (Free / Professional / Business / Server Edition) + data region (US / Canada / EU) right after OAuth and before device registration, so device-limit checks operate on the user's actual chosen plan rather than the default 'free'. Saved selections are written to profiles.plan_id, profiles.storage_region, and profiles.onboarded_at via a direct PATCH to PostgREST.
Latent signup bug fixed: the handle_new_user() Postgres trigger never read the `selected_plan` + `region` fields the desktop and web signup forms have been sending via auth.signUp's options.data since v1.0. Every signup fell through to the column defaults (plan_id='free', storage_region='ca'). The 20260608120000_profile_onboarding migration rewrites the trigger to honour those metadata fields when present (password signup path) and to leave onboarded_at NULL when absent (OAuth signup path) — giving the desktop a clean signal to show the picker. Existing rows get onboarded_at=created_at backfilled so they don't see the new dialog.
OAuth signup flow on desktop now mirrors the web signup contract end-to-end: pick plan → pick region → land on device registration → set encryption passphrase. The plan + region can still be changed from Settings later. Internally this uses a single _showCompleteAccountDialog() reused from both the LoginScreen Google handler (_proceedAfterAuth) and the SignUpScreen Google handler (which bounces through LoginScreen._resumeIfAuthed).
v1.27.0
June 8, 2026Continue with Google — sign-in and sign-up via Google OAuth now work across the entire platform: portal, admin, and desktop. Existing email/password accounts auto-link with Google for the same verified email (Supabase's default behaviour, matching what GitHub, Slack, Asana do), so a user can sign in either way once they've used Google once. Email/password is preserved as an alternative — no method is removed.
Desktop app implements OAuth via RFC 7636 PKCE with a loopback HTTP listener. The flow: app launches the system browser to Supabase's /authorize endpoint, the user signs in to Google, Supabase redirects back to http://127.0.0.1:<random-port>/auth/callback which the desktop's temporary HTTP server captures, then exchanges the code for tokens via /auth/v1/token?grant_type=pkce. No custom Windows URL scheme registration; no admin rights needed; bound to loopback IPv4 only so external machines cannot intercept the callback. The browser shows a branded "You're signed in — you can close this window" page after the round-trip.
Admin Control Center: Google sign-in goes through the same OAuth handshake but the /auth/callback enforces an admin role check. After the code exchange, the callback looks up profile.role for the OAuth-authenticated user — non-admins get signOut() + redirect back to /auth/login?error=not_admin with a hard-stop banner. Means a stray Google account can't sneak into the admin app even if Supabase OAuth auto-creates a user record.
Portal hosts its own /auth/callback route instead of routing OAuth through the marketing site's callback. v1.26.2 and earlier relied on www.backupengine.com / its local-dev port 3000 being up for portal OAuth to complete — broken if you ran only the portal locally, broken if a different project was using port 3000. The portal is now self-contained.
OAuth callback URL on the portal previously read NEXT_PUBLIC_WEB_URL with a default of http://localhost:3000, then appended /auth/callback. That defaulted path collided with whatever else the developer might have running on 3000 (a known issue when working on multiple Next.js projects in parallel). The new resolveCallbackUrl reads window.location.origin and routes back to the same portal that initiated the flow.
v1.26.2
June 7, 2026Forgot password link on the desktop login screen was a dead button — empty onPressed callback shipping since v1.0. Now opens a dialog that takes the email (pre-filled from the login form if already entered), calls Supabase GoTrue's /auth/v1/recover endpoint, and flips to a "check your email" confirmation. The confirmation uses GoTrue's account-enumeration-safe wording ("if an account exists…") since the recover endpoint deliberately returns 200 for missing emails.
Portal pages /auth/forgot-password and /auth/reset-password added. The portal's login screen has linked to /auth/forgot-password since v1.0 but the page didn't exist — both the portal's own link and the email magic-link from the desktop request hit a 404. New forgot-password page mirrors the desktop dialog (email → "check your email" confirmation); new reset-password page handles the GoTrue recovery callback (detects #access_token=…&type=recovery&… in the URL hash, lets the user set a new password ≥10 chars, redirects back to login on success).
Reset-password page rejects expired / already-consumed magic links explicitly. Supabase recovery links are single-use and expire after 1 hour. If a user lands on /auth/reset-password without a valid recovery session, the page surfaces "this link is invalid or has expired" with a button to request a new one, instead of silently rendering a form that would fail on submit.
v1.26.1
June 7, 2026Close (X) button did nothing on the login screen. Root cause: main.dart sets windowManager.setPreventClose(true) at startup so the main shell can intercept the red-X (minimize to tray / confirm-on-active-backup). The intercept lives on _MainShell — when the user logged out, MaterialApp swapped _MainShell for LoginScreen and the WindowListener vanished, but the prevent-close flag stayed on, so the X became a no-op. Fix: LoginScreen.initState now drops the flag and _MainShell.initState re-arms it after a successful login. Logged-out X cleanly closes the app; logged-in X still drives the existing tray / confirm flows.
Agent Updates card moved from Settings to the bottom of the About page. The card duplicated build/version info that already lived on About, and Settings was getting cluttered. Same Check-for-Updates button, same UpdateService.checkNow() under the hood, just in the page where the rest of the install + version info lives. Settings is now scoped to behavior toggles only.
v1.26.0
June 5, 2026IMPORTANT — one-time uninstall + reinstall required if you are on v1.25.x. Backup Engine is no longer distributed as an .msix package. v1.26.0 ships as a signed Inno Setup .exe installer (same SSL.com EV Code Signing cert, same publisher: Tech Bench Services Ltd.). The cutover is per-format, not per-cert, so Windows treats the new install as a fresh app even though the publisher chain is unchanged. To install v1.26.0: (1) open Settings > Apps, find "Backup Engine", click Uninstall. (2) Download the new installer from this page and double-click to run it. Your account login + backup sets resync from the cloud on first launch. Backup data on iDrive e2 / Local PC destinations is untouched.
No more "Select an app to open this .msix file" picker dialog. v1.25.0's MSIX could trigger Windows's file-association picker the first time a user double-clicked the installer (the "Always" button fixed it permanently, but the friction at first-install was a real annoyance). The Inno Setup .exe is a self-contained installer — double-click, UAC prompt once for admin elevation, click Install, done.
Windows Server 2016 and 2019 installs now work out of the box. v1.25.x MSIX needed the App Installer package, which is NOT pre-installed on Server 2016/2019 — admins had to manually run Add-AppxPackage from PowerShell before they could install Backup Engine. The new .exe installer has zero such dependency: it runs on Server 2016 through 2025, Windows 10 1809+, and Windows 11.
Standard install path: C:\Program Files\Backup Engine\. Per-machine install means scheduled tasks running as SYSTEM and the optional BackupEngineService daemon can reach the agent binaries cleanly — which matters for unattended server backups. v1.25.x MSIX installed per-user under WindowsApps with a path that changed every version (net.techbench.backupengine_<version>_x64_…), making cross-version log paths and scheduled-task definitions awkward.
Auto-update now downloads a signed setup.exe and runs it with /VERYSILENT /SUPPRESSMSGBOXES /NORESTART. The progress dialog walks the same four phases — download, kill running processes, install, relaunch — but the install step now triggers a single UAC prompt (admin elevation required because the EXE installs to Program Files). A new PowerShell apply script orchestrates the kill-install-relaunch flow with the same verbose logging to ProgramData that the v1.24.4 MSIX flow used, so support can diagnose any failure path from one log location.
Removed the msix dev dependency from pubspec.yaml + the msix_config block (no longer used). Removed scripts/build-windows-signed.cmd's msix:create + post-process pipeline; the same script now runs ISCC.exe against installers/innosetup/backup-engine.iss after signing the Flutter EXE + DLLs, then signs the resulting setup.exe. Net effect: fewer moving parts, no more "msix:create silently rejects OID-formatted Publisher" workaround.
Detection of how the running agent was installed (InstallType.windowsExe vs windowsMsix vs windowsZip) now checks for unins000.exe sitting next to backup_engine_desktop.exe — Inno Setup's frozen uninstaller filename. windowsMsix is kept in the enum + checked second (via \WindowsApps\ in the EXE path) so an MSIX-installed v1.25.x agent can still detect itself, but new installs all resolve to windowsExe.
v1.25.0
June 5, 2026IMPORTANT — one-time uninstall + reinstall required for existing customers. Backup Engine is now signed by a publicly-trusted SSL.com EV Code Signing certificate (Tech Bench Services Ltd.). The v1.24.5 and earlier MSIX builds used a self-signed development certificate. Windows treats packages signed by different certificate chains as different package identities, even when the package name is unchanged — so Add-AppPackage will refuse to upgrade in-place. To install v1.25.0: (1) open Settings > Apps, find "Backup Engine", click Uninstall. (2) Download the new MSIX from this page and double-click to install. Your account login + backup sets will resync from the cloud after first launch (cross-device sync added in v1.13.0). Backup data on iDrive e2 / Local PC destinations is untouched.
Windows SmartScreen no longer shows "Windows protected your PC" on first install. EV code signing certificates carry instant Microsoft reputation, so the installer dialog now shows "Tech Bench Services Ltd." as the verified publisher with no scary warnings, no "Run anyway" button, no SmartScreen prompt. This applies to both the .msix installer and direct execution of the .exe inside the portable .zip.
Auto-update via Add-AppPackage is now reliable. The v1.24.1 → v1.24.4 silent-no-op failures we documented were partly caused by the self-signed cert chain — Windows could not silently install a package whose publisher cert was not in the local Trusted People store. With the publicly-trusted EV cert, the upgrade path Just Works: the future v1.25.1+ release will install automatically via the in-app banner with no intervention required.
Build pipeline now uses signtool + SSL.com eSigner CKA (cloud signing) instead of msix:create's self-signed test certificate. A new scripts/build-windows-signed.cmd orchestrates the full build → sign exe + DLLs → repack MSIX with EV-Cert-matching Publisher → sign MSIX → upload to R2 flow. Per-signing 2FA is enforced by SSL.com's eSigner per-signing OTP, and the signing log is captured for compliance.
msix:create silently rejected the EV cert's OID-formatted Publisher value and substituted its default placeholder ("CN=Msix Testing, O=Msix Testing Corporation, S=Some-State, C=US"), which broke signing with a 0x8007000b BAD_FORMAT error. The build pipeline now post-processes the manifest after msix:create runs, replaces the Publisher with the cert's exact Subject string (using OID notation for EV-specific attributes — jurisdictionC, jurisdictionST, businessCategory), then repacks with makeappx.exe before signing.
v1.24.5
May 18, 2026About screen now shows the running EXE path, install folder, update download location, and apply log location — so when an upgrade silently fails you can find the relevant paths in 5 seconds instead of digging through %APPDATA% / %ProgramData%. New action buttons: "Open install folder", "Open update folder" (where the staged MSIX/ZIP sits), and "Show last apply log" (renders the apply-update.log inline in a scrollable dialog with selectable text for copy-paste to support).
MSIX relaunch now has 4 fallback layers, not 2. v1.24.4 tried shell:AppsFolder via Start-Process explorer.exe and Invoke-Item; both can silently fail on detached PowerShell sessions when explorer.exe isn't readily available to dispatch URIs. v1.24.5 adds: (3) direct EXE launch from `$pkgAfter.InstallLocation\backup_engine_desktop.exe` — Windows resolves the MSIX activation context from the EXE path inside WindowsApps, same as a double-click; (4) Task Scheduler one-shot — registers a task that fires in 5s under the user's desktop session and auto-cleans up. The Task Scheduler path works even from a detached PowerShell with no interactive desktop, because the task runs under the logged-on user.
Improved logging on the relaunch step: each attempt logs its name + outcome (`[attempt 1] shell:AppsFolder via Start-Process: ok`, `[attempt 3] direct EXE launch threw: $_`, etc.). Combined with v1.24.4's ProgramData-stable log, support can pinpoint exactly which fallback rung succeeded or failed.
v1.24.4
May 15, 2026Customer report from v1.24.1: clicked Update Now, downloaded the new MSIX, saw the success message, app shut down — but never reopened and the install folder still had v1.24.1 binaries. The v1.24.1 MSIX apply script was running Add-AppPackage with no verification step: when Add-AppPackage silently no-op'd (which can happen if `-ForceApplicationShutdown` couldn't close us cleanly, or under several concurrent-install edge cases), the script then tried `Get-AppxPackage` which returned the OLD installed version, treated that as success, and exited without relaunching the app. Three v1.24.4 fixes below.
Stable log location. The v1.10–v1.24.3 apply scripts wrote `apply-update.log` to `$PSScriptRoot` — which sits inside the per-package APPDATA (MSIX redirects %APPDATA% to `Local\Packages\<PFN>\LocalCache\Roaming\...`). When the old version of the MSIX is uninstalled, Windows GCs its package folder and the log goes with it — so by the time the user reports "the update didn't work", the diagnostic is already gone. v1.24.4 logs to `C:\ProgramData\BackupEngine\updates\apply-update.log` instead, which persists across MSIX install / uninstall and is readable without elevation. Same path used by both ZIP and MSIX flows.
Explicit version verification. After Add-AppPackage, the script now calls `Get-AppxPackage -Name net.techbench.backupengine` and compares the installed `Version` against the expected upgrade target (passed in from Dart as `ZZ_EXPECTED_ZZ`). If the installed version is LESS than expected, the script logs `WARN: Add-AppPackage no-op` and falls through to the fallback path. Earlier the script trusted Add-AppPackage's silent success without checking the actual outcome.
Two-layer fallback when Add-AppPackage fails or no-ops. Layer 1: launch `ms-appinstaller:?source=<file path>` which opens Windows App Installer — the OS-native dialog the user can click "Install" on. Works in nearly every scenario including signed-cert-not-trusted-yet. Layer 2 (if ms-appinstaller can't be launched): `Start-Process explorer.exe /select,"<msix path>"` so the user can double-click the MSIX themselves. The script always exits 0 on a fallback path so the calling app doesn't treat it as a crash, and the status JSON records exactly which path was taken.
New CLI command: `backupengine update diagnose` (alias: `update log`). Reads the latest `apply-update.log` + `apply-update-status.json` from ProgramData (with fallback to legacy APPDATA path) and prints them. Lets a user whose upgrade silently failed produce a clean forensic snapshot for support without hunting through filesystems. `--json` returns the parsed status + raw log for tooling.
Verbose Add-AppPackage logging. The cmdlet's stdout + error streams are now captured via `*>&1 | ForEach-Object { Log " Add-AppPackage: $_" }`, so the apply log records exactly what the cmdlet emitted (success message, error code, native package-manager output). Earlier the script just logged "Add-AppPackage threw: $_" which only fired on terminating errors; non-terminating warnings vanished.
Final-message text in the GUI progress dialog updated to "Update ready. Backup Engine will close and reopen in a few seconds. If it does not reopen automatically, Windows App Installer will appear so you can finish manually." Sets the user's expectation that the fallback exists and isn't a sign of failure.
v1.24.3
May 15, 2026`backupengine update check` and `backupengine update install` CLI commands now actually work — they were stubs that always printed "You are running the latest version" regardless of state. The new `check` calls `UpdateChecker.checkForUpdate()` (same code path the GUI banner uses) and prints the available version, install type, download URL, and size; `--json` returns the same as a parseable object. `update install` runs the full apply pipeline (download → extract → swap → restart) and streams `UpdateProgress` events to stdout, so headless / cron-driven update flows can monitor progress just like the GUI dialog does.
End-to-end upgrade test passed: a v1.24.0 install at C:\tmp\test-upgrade\target\ was successfully upgraded to v1.24.3 by running the v1.24.3 apply script against it. robocopy /MIR mirrored 40 files / 41 MB into the install dir, post-swap the binary reports v1.24.3, and Start-Process -PassThru cleanly launched the new EXE. Apply log + status JSON captured the full phase sequence.
v1.24.2
May 15, 2026Hyper-V (and SQL / System State / BMR) backups that completed successfully were not appearing on the Reports tab even though Job History showed them as Success. Root cause was an inconsistent error-handling pattern across backup-type executors: the file-backup path caught report-submission failures and persisted the error to job.errorMessage, but the four server-class paths just called debugPrint() and dropped the failed payload on the floor. Any transient EF hiccup (network blip, JWT expiry, server overload) at the moment of submission permanently lost the report row. Customer report: "I did a new Hyper-V backup, can see it under Job History, but it doesn't appear under Reports."
New `_submitBackupReportSafely` helper consolidates the five backup executors' submission paths through a single queued-retry pipeline. On any exception the payload is re-built (same shape `_submitBackupReport` would have sent) and persisted to a new `pending_backup_reports` table (LocalDb v18 schema). Errors are also surfaced on the job's `errorMessage` field so Job History detail shows "Report upload failed (queued for retry)". No more silent drops — every backup that completes locally either lands its report on the server immediately or stays in the local queue until it does.
Pending-report retry loop runs at app login + every 10 minutes. Walks `pending_backup_reports`, re-submits each payload via `_api.sendBackupReport`, deletes the row on 200 OK, increments retry_count + records `last_error` on failure. A previously-queued report from a prior session catches up on the next agent launch — no user action required.
New CLI: `backupengine reports retry-pending` (alias: `reports retry`). Manually triggers the same retry loop and prints a per-row summary — useful when you know the EF is back up and don't want to wait for the 10-minute timer. Returns exit code 0 when the queue drains, 1 when items still fail.
LocalDb v18 migration adds `pending_backup_reports(id, job_id, backup_set_id, payload_json, created_at, last_retry_at, retry_count, last_error)`. New schema is forward-compatible: a v1.24.1 agent reading this table doesn't exist (the migration only fires on upgrade), and a v1.24.2 agent reading a pre-v18 DB runs the migration on first open. Both directions are safe across the upgrade boundary.
v1.24.1
May 13, 2026Auto-update closed the app but didn't reopen the new version. Customer reported: "I see the visible progress dialog, then it tried to restart the program, but after closing the program, it did not restart." Two distinct root causes — one per install type. ZIP install: the BackupEngineService daemon (when installed) held DLL handles open in the install folder, so robocopy partially failed in a way the old script silently swallowed, and even when files copied, `Start-Process -FilePath $newExe` could fail silently with no retry. MSIX install: `Add-AppPackage -ForceApplicationShutdown` correctly closed the running app and installed the new package — but Add-AppPackage by itself does NOT relaunch the app. There was no explicit re-launch step.
New apply scripts now match the user's exact suggestion: (1) stop the BackupEngineService if it's installed + running, (2) kill every backup_engine_desktop.exe process via Get-Process -Name (catches GUI + tray + worker isolates + the headless --service daemon — whatever is holding file locks), (3) wait 2 s for Windows to fully release handles, (4) copy the files (robocopy /MIR for ZIP installs; Add-AppPackage for MSIX), (5) restart the service if it was running, (6) explicitly launch the new app — `Start-Process -PassThru` for ZIP installs, `explorer.exe shell:AppsFolder\<PFN>!App` for MSIX (the documented Windows way to launch a sideloaded packaged app from a script), (7) write a JSON status file `apply-update-status.json` next to the apply log so a future banner can surface success/failure on the next launch.
ZIP-install apply script verifies the new EXE exists + is bigger than 1 KB after copy (catches a partial-write scenario where robocopy moved file headers but couldn't finish the body because the EXE was still locked) before attempting to relaunch. If verification fails or `Start-Process` returns null, the status file records the failure with a clear remediation message ("Verify the file at … opens cleanly when double-clicked, or reinstall from https://www.backupengine.com/download").
Parent-PID wait shortened from 30 s → 15 s. The process-kill step that runs immediately after covers the long-tail case where the GUI is wedged. Net effect: the apply phase reaches the actual file copy ~15 s faster on a healthy install, and the wait-for-quiet semantics still hold for the wedged case via the unconditional Stop-Process fallback.
v1.24.0
May 13, 2026Hyper-V backups no longer create checkpoints on the VM. The v1.10–v1.23 backup runner called `Checkpoint-VM` before every run to anchor its incremental chain — the resulting `BackupEngine-Reference-*` snapshots stacked up in Hyper-V Manager (six levels deep on one customer report) and each backup ended up capturing only the latest .avhdx delta rather than the full VHDX, producing 5 MB backups of multi-GB VMs. v1.24.0 matches what Veeam / Acronis / Altaro / MSP360 / Ahsay do: read the VHDX through a VSS shadow copy of the host volume (online VMs) or directly from disk (offline VMs). The VM tree stays untouched.
Offline VMs back up via direct VHDX read. When `Get-VM | Select State` returns `Off` or `Saved`, the runner opens the VHDX file in place — the disk isn't locked because the VM isn't running, and the bytes are stable at the moment of read. Zero footprint on the VM, zero quiescing overhead. New `DirectVhdxStrategy` in `hyperv_read_strategy.dart`.
Online VMs back up via VSS Hyper-V Writer + diskshadow.exe. A short diskshadow script (`set context persistent / begin backup / add volume … / create / expose %VolAlias% Z: / end backup`) takes a VSS shadow of the host volume holding the VHDX and mounts it at the next free drive letter. The Hyper-V VSS Writer + the VM's Integration Services quiesce the running VM during the snapshot so the VHDX inside the shadow is application-consistent (not just crash-consistent). At end-of-backup the shadow set + exposed drive are released. New `VssShadowStrategy` + `HypervVssShadow` helper.
Incremental Hyper-V backups now use FastCDC content-addressed dedup rather than checkpoint-based diff exports. The runner reads the full VHDX (same code path as full); chunks that already exist in the cross-set catalog don't re-upload, so on-wire incremental cost matches what the old diff-export delivered without polluting the VM with reference snapshots. Pack-level dedup is what enables this — same mechanism that already keeps file-backup incrementals cheap. The legacy `Export-VMSnapshot` + `_DiffExportResult` code paths are deleted from the runner entirely.
New CLI command: `backupengine hyperv cleanup-checkpoints [vm] [--list]`. Finds every `BackupEngine-Reference-*` checkpoint on a VM (or every VM if no name given), computes a parent-first topological order, and calls `Remove-VMSnapshot` on each one — which merges the differencing data back into the parent VHDX and deletes the .avhdx file. Waits up to 5 minutes for Hyper-V to quiesce the merge before returning. Use `--list` to preview without changing anything. This is the upgrade path for installs that already have the v1.10–v1.23 chain pollution: one command and the VM tree is clean.
New `HypervReadStrategy` abstraction picks `DirectVhdxStrategy` vs `VssShadowStrategy` based on VM power state. Both implement the same `prepare()` / `pathFor(disk)` / `dispose()` contract so the streaming pipeline (FastCDC + AES-GCM + pack writer) doesn't need to know which path produced the bytes. Multi-volume VMs (disks across C: and D:) get one VSS shadow per source volume — VSS works at the volume level so a single shadow can't span volumes anyway. Strategies clean themselves up in `dispose()` regardless of success/failure — no host-side artefacts left behind even when a run fails mid-disk.
Synthetic full (the v1.7.x chain-promotion path that gets triggered when an incremental chain hits its length cap) no longer creates an anchor checkpoint. Combined with the above, this means there are NO code paths in v1.24+ that can create a `BackupEngine-Reference-*` checkpoint. Every manifest is a self-contained restore point; chunk dedup keeps the on-wire shape friendly.
Customer report: 6-level-deep `BackupEngine-Reference-*` chain stacked under a single Hyper-V VM, producing a 5 MB backup of what should have been multiple GB. Root cause was the original architectural decision to use `Checkpoint-VM` as the incremental anchor + a fragile cleanup loop that broke down across runs. Fix is the full v1.24 rewrite above. Run `backupengine hyperv cleanup-checkpoints` on affected VMs to merge the stacked chain back into the parent VHDX.
v1.22.0
May 12, 2026Auto-update flow is now fully visible. Customers reported "I click Update now and nothing happens" — the v1.10–v1.21 implementation shelled out to either Windows App Installer (MSIX install) or a hidden PowerShell script (portable ZIP) and could fail silently with no in-app feedback. Now clicking Update now opens a progress dialog that walks through four labelled phases with a live progress bar: (1) "Preparing update…", (2) "Downloading update… 23.4 MB of 17.5 MB" with a bar that ticks every 100 ms as bytes arrive, (3) "Extracting files… 142 of 412" as the Dart `archive` package unpacks the ZIP into a staging dir, (4) "Applying update — Backup Engine will restart shortly." On error the dialog flips to a red error card with the failure message and a Close button. On success the helper script copies the staged files over `Platform.resolvedExecutable`'s parent directory, kills the running process, and relaunches the new EXE — so the app updates in-place at whatever folder it was launched from.
MSIX installs now use `Add-AppPackage -Path <msix> -ForceApplicationShutdown` via PowerShell instead of the `ms-appinstaller:` URI handler. Add-AppPackage works headlessly with the publisher cert the user already trusted at first install (no second consent prompt) and respects the same visible-progress dialog because we control the download. The old `ms-appinstaller:` path could silently fail on Windows Server SKUs where the App Installer package isn't enabled — that was the original "dead click" report.
ZIP installs use robocopy /MIR for the in-place file swap with a per-file Copy-Item fallback if robocopy returns an error (some restricted environments don't have robocopy.exe on PATH). The PowerShell helper writes a breadcrumb log next to the staging dir at `%APPDATA%\BackupEngine\updates\apply-update.log` for post-mortem debugging if a swap ever fails — `[2026-05-12T...] mirroring … -> …`, exit codes, etc. Cleans up the staging zip + extract dir on success.
New `UpdateProgress` model + `Stream<UpdateProgress>`-based API on `UpdaterService.launchUpdateWithProgress(info)`. Yields `preparing` / `downloading` / `extracting` / `applying` / `done` / `error` phases with `processed` + `total` byte counters. The legacy `launchUpdate(info)` future-based method is preserved for headless callers (CLI, service) that don't need progress. Dialog is `barrierDismissible: false` while work is in flight so the user can't accidentally close it mid-download; on error the user can dismiss + retry from the banner.
v1.21.2
May 12, 2026Every backup in v1.21.0/v1.21.1 was failing with "Bad state: Stream has already been listened to" — the v1.21.1 patch fixed a different stream bug but missed the actual root cause. Stack trace from the diagnostic log pinpointed PackWriterIsolate.spawn() as the real culprit: spawn() registered a temporary listener on the `fromWorker` ReceivePort to wait for the worker's "ready" handshake, cancelled that subscription, then constructed PackWriterIsolate which tried to `.listen()` again for ongoing message dispatch. ReceivePort is single-subscription — once you've called `.listen()` on it, cancelling the subscription does NOT let you re-listen, even if the source object is the same. Fix: install ONE persistent listener up-front that (a) completes the readyCompleter on the first 'ready' message and (b) forwards every subsequent message to the live instance's _onMessage handler via a deferred dispatch (any messages that arrive in the gap between worker ready and instance construction get queued for replay). Same bug pattern existed in RestoreDecryptWorker.spawn() — fixed there too with the same approach. Round-trip verified via CLI: local backup of 3 test files (524 KB random.bin + 2 small txt) completes in 0.1s, restore produces byte-identical files (matching MD5s).
CLI tested with `backupengine backup start "Local Cloud"` (567 files, 332 MB → BackupEngine Cloud, completed in 193s) and `backupengine backup start "Local-Test-1.21.2"` (3 files, 524 KB → Local PC, completed in 0.1s with full restore round-trip and MD5 verification).
v1.21.1
May 11, 2026(Superseded by v1.21.2 — the actual root cause was in PackWriterIsolate.spawn(), not streamPack(). This release's streamPack() rewrite is retained because it's defensively correct, but it was not what unblocked the failure mode.)
v1.21.0
May 11, 2026Backups and restores no longer freeze the desktop GUI. Customer ran into intermittent unresponsive periods during a multi-GB backup with CPU sitting at ~50% — one core pinned on the main isolate while other cores idled. This release moves four pieces of CPU-heavy work into dedicated worker isolates: (1) the end-of-run manifest JSON-encode + AES-GCM body-encrypt (was hundreds of ms of synchronous work right after the file phase hit 100%), (2) the pack-writer that holds every encrypted chunk in memory and runs appendChunk per chunk (was the dominant remaining main-isolate work on big Hyper-V / SQL / BMR backups — 1.6M appendChunk calls + 50 GB of bodies on a 100 GB VHDX), (3) AES-GCM decryption on restore (16-way Future.wait parallelism over downloads was great for I/O but the per-chunk decrypt still ran on main, stuttering the GUI on multi-GB restores), and (4) the recursive folder scan + per-file stat() loop that put 29 k microtasks on the main event loop before a typical file backup even started uploading.
New PackWriterIsolate owns one PackWriter for the whole run. Backup workers send each encrypted chunk fire-and-forget to its uploadPort; the isolate processes messages in arrival order and the end-of-run getStats() doubles as a drain barrier (returns only once every prior append has been applied). End-of-run streamPack() pulls bodies out one at a time so the main isolate never holds the 50-GB pack body in heap. appendErrors counter on the stats reply lets the runner refuse to write a manifest pointing at an incomplete pack. Wired into all five backup runners — file, Hyper-V, SQL, BMR, system-state.
New RestoreDecryptWorkerPool (default 4 workers) handles AES-GCM decryption for restores. Each worker owns its own AesGcmEncryptor reconstructed from the 32-byte DEK inside the isolate (no FFI handle crosses an isolate boundary; the key bytes are the only shared state). For chain restores (Hyper-V / SQL across DEK rotation windows) the pool exposes keyMatches(other) so the runner can fall back to inline decrypt for chain entries whose DEK doesn't match the pool's — matters for ≤1% of cases but kept correct rather than silently producing garbage. Wired into file restore (16-way file workers dispatch to the pool), BMR restore, system-state restore, and Hyper-V chain restore. SQL chain restore left on inline decrypt for now (chain-DEK matching logic deferred to v1.22).
New ManifestEncryptIsolate is a one-shot worker that does JSON-encode + AES-GCM body-encrypt at the end of every backup. For a 29 k-file set the inner manifest is ~7 MB of JSON; the encode + UTF-8 + AES-GCM + base64 chain was ~200–500ms of synchronous main-isolate work — visible as the "Writing manifest…" stall at 100%. Now happens off-main. Side benefit: dropped the JsonEncoder.withIndent(' ') pretty-print on the server-class manifests since they're encrypted on the wire anyway, shaves ~30% off manifest size and roughly halves encode time.
New FolderScanIsolate streams recursive Directory.list() + entity.stat() + exclude-pattern matching off the main isolate. Batches 500 file records at a time so the main side's onBatch progress callback fires at a reasonable rate without holding all results in memory. Replaces the inline _scanFolder + _isExcluded helpers. For 29 k files this used to put ~3–5 s of cumulative microtask work on the main event loop before the upload phase even started.
Refactored processStreamIsolated (the existing chunking pipeline for Hyper-V / SQL / BMR / system-state) to accept an optional packWriterUploadPort. When wired, the worker isolate sends each chunk's bytes DIRECTLY to the pack-writer isolate, bypassing the main isolate entirely for the upload step. The legacy main-isolate-as-pack-builder path stays as a fallback. Per-chunk diagnostic logging (logger.chunk + logger.backend on every append) is dropped in the new path — end-of-run summary keeps total chunks and pack size for support.
Removed JsonEncoder.withIndent pretty-print from Hyper-V, SQL, BMR, and system-state manifest writes (was a v1.6.x-era choice that no longer matters since the body is encrypted on the wire). Combined with the off-main encode this cuts the end-of-run finalize phase wall-clock by 30–50% on large server backups.
PackWriter (the in-memory builder) is no longer allocated on the main isolate — it lives inside PackWriterIsolate. The pre-1.21.0 architecture held every encrypted chunk in a main-isolate List<Uint8List> until streamPack() at end-of-run; for a 100 GB VHDX with 50% dedup that's ~50 GB in main-isolate heap, which produced visible GC pauses on top of the per-chunk appendChunk work. Now the heap pressure lives in the worker isolate and the main isolate stays small.
v1.20.6
May 11, 2026Device detail screen → Recent Backup Jobs now shows the backup-set name ("Cloud-Test") instead of the type-of-backup ("file") as each row's primary label. Type + status moved to compact coloured pills next to the title so a single glance still tells you "Cloud-Test [file] [complete]". Timestamp gets a short "May 11 21:08" format instead of the raw ISO-8601 string. Customers with multiple sets per device finally have useful context — pre-1.20.6 every row read "file · complete" and the only way to tell sets apart was by size.
Recent Backup Jobs history raised from 10 rows → 50. Older fleet visibility without yet needing a paginated view; if customers need more than 50 we'll add a "Load more" button in v1.21+.
Server `backup_jobs` table gained `backup_set_id` + `backup_set_name` columns (migration 20260511000000_backup_jobs_set_name.sql). Name is denormalised on purpose — captured at backup time so the row survives later set rename / delete with the label as it was when the run happened. `backup-job-track` Edge Function startJob accepts both fields; client `backupJobTrackStart` + AppState._trackJobStart thread them through. Legacy rows (every job before this release) carry null in both columns; the GUI falls back to `job_type` for them.
v1.20.5
May 11, 2026Device detail screen "Recent Backup Jobs" section was throwing "type 'List<dynamic>' is not a subtype of type 'Map<String, dynamic>?' in type cast" on every load. Root cause: the /devices Edge Function's path parser used `pathParts.length > 2` to detect a per-device GET, but Supabase's gateway delivers the function at /devices/<id> (length 2 after split + filter) rather than /functions/v1/devices/<id> (length 4). So the GET-with-id request fell through to the list branch and returned an array where the client expected the per-device object. Fixed by matching on the last path segment instead: deviceId = last part when it exists AND isn't the literal "devices". Handles both delivery shapes without depending on the exact prefix.
Device rows + detail screen header were showing the agent version as "vv1.20.2" (double v). The agent had been sending the v-prefixed kAgentVersion string ("v1.20.4") to register-device, the server stored it verbatim, and the GUI then prepended ANOTHER "v" on display. Two-part fix: (1) strip the leading v before sending so new registrations land clean in the database, (2) new RegisteredDevice.displayAgentVersion getter strips a leading "v"/"V" defensively so legacy rows from older agents (which DID send the v) also render correctly. Affects the Dashboard "Your Devices" rows + Device Detail header + Identity → Agent Version row.
v1.20.4
May 10, 2026Device aliases. Every device on the account can now have a friendly user-supplied label (e.g. "Office PC", "Reception Workstation", "Server Room Rack 1") that the desktop GUI shows in place of the raw hostname. Set or clear via the new pencil icon on the device detail screen header. Stored in a new `alias` column on the `devices` table; the existing `name` column stays as the hostname so register-device idempotency keeps working unchanged. PATCH /devices/:id endpoint added with audit logging on `device.rename`.
Per-device detail screen. Tap any row in the Dashboard's "Your Devices" section to drill down: shows the alias-editable header with a "This device" pill on the current machine, lists every backup set owned by that device (pulled from the local cross-device sync cache so it works offline), and surfaces the device's last 10 backup jobs from the /devices/:id GET endpoint with type, status, started-at, and total-size columns. Bottom Identity section dumps the full row (hostname, alias, platform, OS, agent version, device UUID, status, registered-at) for support-grade copy-paste.
Dashboard device rows now show the alias when set (falls back to hostname when not), gained a trailing chevron to signal the row is tappable, and route to the new detail screen on click. Revoked devices stay tappable too — historical data is still useful after a machine retires.
v1.20.3
May 9, 2026New-device registration is now visible UX. Pre-1.20.3 a fresh agent install silently called register-device on first sign-in and the user got no confirmation that anything happened. Now register-device returns device_count + device_limit (computed from the user's plan + admin overrides), and the desktop login flow shows a one-time "Device Registered" dialog with the new device's name + a "Devices on this account: 3 of 5" capacity pill. The dialog colours warning-orange when the user has just hit their plan cap so they immediately see they need to revoke an old device or upgrade before adding another machine.
New "Your Devices" section on the Dashboard. Lists every device on the account with platform icon, agent version, last-seen relative timestamp ("5m ago" / "2h ago" / "3d ago" / "never"), last-backup timestamp, and a "This device" pill on the row matching the current machine. Revoked devices stay listed (greyed out at 65% opacity) so customers can confirm "yes, my old laptop is properly retired." Refresh icon re-pulls the list on demand; otherwise it lazy-loads on Dashboard mount and reuses the cached copy when tabbing back.
register-device Edge Function extended with a /devices roundtrip-saving response field set: { device_count, device_limit }. -1 on device_limit means unlimited (Enterprise / custom plans). Reads from profiles.device_limit_override first, then falls back to plans.device_limit — same precedence the existing check_device_limit SQL function uses, so the dialog shows what the server will actually enforce.
v1.20.2
May 5, 2026Settings → Change Plan section is now tier-aware. Pre-1.20.2 every plan card showed an "Upgrade" button regardless of where the user was — a Server-Edition customer saw "Upgrade" on the $11.99 Professional card, which is misleading. Now the CTA reads "Upgrade" / "Downgrade" / "Current Plan" based on tier comparison (Free → Professional → Server → Enterprise). Downgrade buttons are coloured warning-orange to communicate "step backward" without being alarming red.
Downgrade flow surfaces a capacity-impact dialog BEFORE confirmation: storage-cap before/after with an over-quota warning when the user is currently above the new cap, device-cap before/after, and a "Features removed" line listing CDP / CLI / SQL / Hyper-V / BMR / sysstate / SLA / SSO / white-label items the target plan no longer includes. Customers see exactly what they're losing instead of finding out post-billing.
Settings screen → removed the duplicate "Cloud Storage" section. The endpoint + bucket + connectivity probe lives on the dedicated About page (sidebar → About) and was redundant in Settings.
Settings screen → removed the duplicate "About" section at the bottom. The dedicated About page already covers Application / Version / Website / Support; the Settings tail-section was a leftover from before the About page existed.
v1.20.1
May 5, 2026Settings → "Check for Updates" button actually checks for updates. Pre-1.20.1 it was a stub that always showed a hardcoded "You are running the latest version" snackbar regardless of the real update manifest. Now it calls UpdateService.checkNow() which fetches latest-version.json from R2, compares against the bundled kAgentVersion, and surfaces the actual UpdateInfo via the same broadcast stream the dashboard banner listens on. Result is mirrored in a Settings snackbar so the immediate flow gives feedback. Customers on v1.11.13 / v1.12.x / v1.13.x must manually download v1.20.1 from the downloads page once — the bug means their existing in-app check button can't self-heal.
Settings → About row "Version" was hardcoded to 1.0.3 instead of using the bundled kAgentVersion. Now bumps automatically with every release like the row above it.
Settings → Cloud Storage + About → Cloud Storage rows now show "S3-BackupEngine-Canada-East" / "BackupEngine Cloud" instead of the raw storage-provider endpoint URL + bucket name. Brand consistency — customers see "BackupEngine" in the UI, not the underlying storage vendor. Internal SigV4 signing still uses the real endpoint; this is cosmetic only.
v1.20.0
May 5, 2026Cross-device backup-set sync (v1.13.0 carry-over) is now production-grade — backup sets created on one device appear on every device signed into the same account, with foreign-owned sets greyed out and labelled "Other device." Cloud-destination sets are restorable cross-device because chunk storage moved from per-device prefix (users/<deviceId>/...) to per-account prefix (users/<accountUserId>/...) in v1.13.0.
Backup-set sync timer slowed from 5 min → 30 min. Local mutations still trigger immediate push, so PC1→PC2 propagation stays in seconds when both devices are online; the timer is the offline-recovery backstop. Net Edge Function load drops ~6× per device — well under Pro plan quota at 700+ customer scale.
Per-set + global upload bandwidth shaping. New backupengine sets bandwidth <name> <kbps|off> CLI command and AppSettings.bandwidthLimitMbps slider both apply via a token-bucket RateLimiter on the upload path. Per-set kbps overrides global Mbps; off clears the per-set cap. Restore downloads are NOT throttled — disaster recovery should be as fast as the network allows.
Restore throughput overhauled: 16-way parallel file workers, batched parent-dir creation (29k mkdir calls → ~few hundred uniques), and a density-driven whole-pack download cache that flips to a single GET when ≥25% of a pack's chunks are needed instead of N range-GETs. Expected 5-10× speedup on full-restore workloads vs. v1.13.0; real benchmarks land once customers run them.
Storage-backend choice. Two new --dest options: s3-compat and azure-blob. Wasabi, Backblaze B2 (S3-compat API), MinIO, Ceph RGW, DigitalOcean Spaces, Linode/Vultr Object Storage, and Microsoft Azure Blob Storage all targetable per-set with customer-supplied credentials. Bring your own bucket — your data residency, your billing.
SQL Server point-in-time restore via CLI. backupengine restore run <sql-set> --output none --pitr <iso-timestamp> [--replace] walks the chain (full → diffs → logs at-or-before the target) and emits STOPAT on the final log restore. New recovery-model gate refuses BACKUP LOG against SIMPLE-recovery databases up-front instead of letting sqlcmd surface the cryptic 4214 error mid-run.
Hyper-V Resilient Change Tracking metadata captured in the per-run diagnostic log. Get-VHD's VhdType / Size / FileSize / ParentPath / NumberOfSnapshots / FragmentationPercentage land as a phase=rct_metadata line so customers can verify "yes my incrementals are tiny because this is a differencing disk" without parsing wbadmin output.
VSS writer state captured in System State backup logs. vssadmin list writers runs BEFORE wbadmin starts, parses each writer's state (Stable / Wait_for_completion / Failed), and emits a phase=vss_writers line listing every writer + state at backup time. Catches "Exchange writer was Failed when this backup ran" cases without per-app capture flows (those land in v1.21+ as standalone features).
CLI grew end-to-end multi-storage + PITR support: sets create file with --s3-* / --azure-* flag families, sets bandwidth, restore run --pitr / --replace. Every shipped feature has a CLI surface — no GUI-only feature in the v1.20.0 set.
v1.13.0
May 4, 2026Account-scoped backup_sets table on Supabase + backup-sets-sync Edge Function. Set definitions round-trip between every device on the account; a brand-new sign-in on a fresh PC sees every set the user has, with the ones owned by other devices marked read-only.
Storage prefix migrated from per-device deviceId to per-account Supabase user UUID. Cloud-destination sets are now reachable from any device on the account; local-destination sets still pin to the physical disk.
Backup screen greys out foreign-owned sets, shows an "Other device" pill, disables Run / Edit / Delete / Pause. Restore screen shows the same pill on across-account manifests; cloud-destination foreign sets are restorable, local-destination foreign sets show "Restore unavailable — chunks live on [other device]" with a tooltip.
Chunk-GC sweep now decrypts body-encrypted manifests before walking chunk references. Pre-1.13.0 GC saw an empty top-level files[] array on every encrypted manifest, computed a 0-hash reference set, and deleted every pack as orphaned — silent data loss after every successful backup. Refusing to sweep on any unreadable manifest is the load-bearing fail-safe.
v1.12.4
May 3, 2026Chunk-GC encrypted-manifest unwrap (see v1.13.0 entry — the 1.12.4 hotfix shipped this fix on its own before the 1.13.0 mega-release wrapped it in the broader cross-device feature set).
CLI gained real backup + restore commands: sets create file, sets delete, backup start <name>, restore list <name>, restore run <name> --output <dir> [--version <ts-ms>]. Pre-1.12.4 these were print-only stubs.
v1.12.1
May 3, 2026Streaming pack-file uploads. Pre-1.12.1 the runner materialised a 430 MB pack body as one Uint8List then handed it to file.writeAsBytes — two contiguous-allocation + memcpy passes on the main isolate, freezing the UI for 10+ minutes on 29k-file backups. PackWriter now exposes streamPack() yielding header + each chunk body, BackupBackend.putStream() with LocalBackend's IOSink override pumps the stream straight to disk, no giant buffer in memory.
v1.11.13
May 2, 2026Active Job card on the Dashboard now shows what a backup is doing during the post-100% finalize phases instead of sitting at 100% looking frozen. A small italic line + 12 px spinner cycles through "Writing manifest…" → "Cleaning up old versions…" → "Reclaiming unused storage…" before the card disappears. Multi-destination sets include the destination label in parens so it's clear which copy is being cleaned ("Cleaning up old versions (BackupEngine Cloud)…"). Same wall-clock as before — the chunk-GC sweep is still O(N×M) and that's where most of the 10-20s wait lives — but it no longer looks like the run is stuck.
New BackupJob.currentPhase: String? field — null during the upload phase (card shows file/byte counters as before), populated only during finalize. real_backup_runner emits one extra onProgress with phase='finalizing' before the manifest backend.put. AppState's _setActiveJobPhase / _clearActiveJobPhase helpers cover the prune + GC sequencing and clear at the start of each per-destination iteration so dest1's leftover label doesn't bleed into dest2's upload phase. Applied to all five backup paths (file, hyperv, sql, sysstate, bmr).
currentPhase is intentionally NOT persisted to JSON — it's a live indicator only. Once a job lands in history (status='completed') the field is unset, so the Recent Jobs list doesn't carry stale finalize labels into archived entries.
v1.11.12
May 1, 2026File backups are now parallelized — multiple files read, hashed, encrypted, and uploaded concurrently. Pre-1.11.12 the runner was a sequential for-loop on the GUI thread; on a 32-thread workstation a 56 MB local-PC backup felt slow because one core was bottlenecked on SHA-256 + AES-GCM while the upload pipeline starved between files. Now N self-recursive worker futures share the file queue with up to N files in flight at once. Each worker independently does read → SHA-256 → encrypt → upload; the await points (file read, backend.put) yield to the event loop so the GUI keeps painting.
Settings → Performance gains a "Parallel files during backup" slider. Auto picks max(3, cpuCount/2) — 16 on a 32-thread workstation, 3 on a small laptop. Toggle Auto off to override; the slider clamps to [1, cpuCount × 2]. Internally NULL = Auto, so a future CPU upgrade reflows the default automatically. Same throttle is honoured by GUI-triggered runs and the headless service-mode scheduler.
Files >5 MB stream via openRead instead of readAsBytes so the heap doesn't pin a 200 MB allocation when 16 workers are each holding a full file payload. Smaller files keep the simpler readAsBytes path because the streaming overhead isn't worth it for a 50 KB document. Cancellation observed both inside the worker (between read/hash/encrypt/upload) and at every queue pull — Cancel still stops at next file boundary (~1s).
Dashboard "Active Job" section now shows one card per active backup AND one per active restore (mirrors the v1.11.9 backup card; tinted green for visual distinction with a download-icon, a percent in the green brand color, items done / items total, current item, and bytes downloaded). Section title flips to "Active Jobs" plural when more than one is running.
Caveat: this is concurrency on the main isolate's event loop, not true CPU parallelism via spawned isolates. SHA-256 + AES-GCM encrypt are still synchronous bursts. The wins are I/O overlap (multiple disk reads + uploads in flight), more frequent GUI yield points, and bounded heap pressure from streaming large files. If further isolation is needed, a worker-isolate pool is the next step.
v1.11.10
April 30, 2026Restore screen file-tree checkbox cascade now visibly checks every descendant folder when you click a parent. Pre-1.11.10 the cascade only added leaf files to the selection set, so intermediate folders looked unchecked even after you ticked their parent — the count was right but the visible tree was misleading. New FileTreeNode.allDescendants() walks the full subtree (leaves AND intermediate folders); _toggleFileSelection uses it on both check and uncheck. The "N files selected" counter is unchanged because it filters with where((f) => !f.isFolder).
v1.11.9
April 30, 2026Live restore status across all five restore types (file / Hyper-V / SQL / BMR / System State). Each restore screen now shows a Cancel button on the in-progress panel — cooperative cancel stops at the next chunk or file boundary (typically <1s). The runner throws BackupCancelledException, the screen's finally calls AppState.endRestore, and the screen surfaces a per-type snackbar explaining what happened to partial output (file restores keep what was written; SQL cleans temp .bak; Hyper-V leaves the in-progress VHDX in place; BMR/sysstate leave incomplete trees the user is told are not bootable).
Tray icon now pulses through the same 8-frame animation during a restore as during a backup. Tooltip distinguishes "Backup Engine — N backup(s) running" vs "N restore(s) running" vs "N backup(s) + M restore(s)" so a user hovering the tray sees what's actually in progress.
Dashboard banner shows "Restore in progress — <set name> — N%" when a restore is running and no backup is. Drill into the Restore screen for full counters; the banner just affirms cross-screen activity.
Duplicate-restore guard: starting a restore for a backup set that already has a restore running pops a "Restore already in progress" dialog and refuses to launch a second one. Concurrent restores against the same set would race each other on the destination folder.
AppState gains parallel maps for restore jobs: _activeRestores / _activeRestoreProgress / _restoreCancellationTokens. New methods beginRestore / updateRestoreProgress / endRestore / cancelRestore / isRestoreRunning / activeRestoreForSet — the restore screens register their run via these so the tray, banner, and cancel are all driven from one place. New RestoreJobProgress model carries cross-type fields (bytesDownloaded, itemsDone, currentItem, transferRateBps) so the Dashboard banner doesn't branch on type.
All five restore runners (real_restore_runner, hyperv_restore_runner, sql_restore_runner, bmr_restore_runner, system_state_restore_runner) now accept an optional CancellationToken and call cancelToken?.throwIfCancelled() between chunks/files. The Hyper-V chain reassembler threads the same token down through reassembleSingle for chunk-level checks.
Pause is intentionally absent — chunks are non-resumable single-pull fetches with no resume cursor; pause-and-resume would require resumable byte-range fetches against iDrive e2 plus on-disk job state. Cancel-then-rerun is the supported pattern; chunks already at the destination dedup automatically so re-running is cheap.
v1.11.8
April 29, 2026Cleanup-on-delete dialog. When you delete a backup set, three checkboxes let you decide what other artefacts to wipe alongside it: uploaded data on every destination (CANNOT BE UNDONE — restores from this set become impossible; default OFF), per-run diagnostic logs on this machine (cosmetic; default ON), and report history server-side (every backup_reports row for this set; default OFF to preserve audit history). Defaults are deliberately conservative — checking "delete uploaded backup data" turns the action button red and adds an explicit warning panel.
"Show login screen on startup" toggle in Settings → Application. When ON, the access + refresh tokens are cleared on every app launch so the user is forced to sign in fresh. Off by default to preserve the persistent-login UX existing users expect. Useful on shared machines or when the user wants an explicit auth checkpoint at every session start.
Hyper-V chain defenses. Three independent fixes that all flow from a real broken-chain incident on 2026-04-29: (1) pre-flight disk-existence check before checkpoint creation — if the VM's metadata references a deleted .avhdx the run aborts BEFORE taking a new BackupEngine-Reference checkpoint and surfaces a clean "VHD source file not found — repair via Hyper-V Manager" hint; (2) startup orphan-checkpoint sweep now EXCLUDES BackupEngine-Reference-* (was BackupEngine-* which swept the long-lived incremental anchors and triggered the merge-then-fail cascade above); (3) first per-VM error is propagated to RunnerResult.errorMessage so the GUI Reports view shows the actual reason instead of a bare Fail badge.
Stale-VM-ID badge on Hyper-V backup-set cards. Backup sets store the Hyper-V VM's UUID. If a VM is removed and recreated (e.g. via Remove-VM + New-VM during chain repair) the new instance gets a fresh UUID. The set still references the old one, so every subsequent backup fails with "No matching VMs found." v1.11.8 surfaces this as a yellow warning chip on the Hyper-V card listing the missing VM IDs alongside their saved names. Recreate the set to fix.
Server side: the supabase/functions/reports DELETE endpoint now accepts ?backup_set_id= for bulk-removal of every report tied to a deleted set; powers the cleanup-on-delete dialog's "delete report history" checkbox.
v1.11.7
April 28, 2026Cancel button on Server Backup tab cards. v1.11.3 wired cooperative cancel into Hyper-V backups but only updated the Files-tab card — the Server Backup tab's _ServerSetCard still rendered a disabled "Running…" button while a backup was in flight. v1.11.7 ships the missing UI: when a server-class set is running, swap that button for a real Cancel button that pops the same confirm dialog as the file-backup card. Same cooperative-cancel pipe behind it (CancellationToken → worker isolate → clean shutdown after current chunk).
v1.11.6
April 28, 2026File backups can now be cancelled mid-run. Closes the v1.11.3 → v1.11.4 sequence — Hyper-V (1.11.3) and SQL/BMR/sysstate (1.11.4) had cancel; file backups were the holdout. The Cancel button on a Files-tab card stops the run at the next file boundary; chunks already uploaded remain at the destination and dedup automatically on the next run; no manifest is written for the cancelled run, so it does not appear in the Restore picker.
Optional parallel-disk Hyper-V backups. Full Hyper-V backups can stream multiple disks concurrently. The default is serial (preserves bit-for-bit pre-1.11.6 behaviour) — opt in by constructing the runner with maxConcurrentDisksPerVm > 1. Incrementals stay serial because Export-VMSnapshot isn't safe to invoke concurrently against the same VM.
v1.11.5
April 28, 2026Live scan progress. The first phase of every backup walks the include paths to count files + bytes. On a fast SSD enumerating 100k files this can take 30-60s with no UI feedback — pre-1.11.5 the dashboard showed "0 files / 0 B" frozen for that whole window. Now an onProgress emit with phase='scanning' fires up to 4× per second (250 ms throttle) reporting the running file/byte counts and the most recently enumerated path. The user sees the scan working instead of a stalled run.
Auto-unlock encryption between sessions. When the user has saved their passphrase to the OS keystore (Windows DPAPI / macOS Keychain / Linux libsecret), the agent now auto-unlocks the AES-GCM encryptor at startup so the first scheduled backup of the day doesn't fail with "encryption configured but locked" because the user hasn't opened the app yet. Opt-in per device via Settings → Security → "Remember passphrase on this machine" (off by default — preserves zero-knowledge for users who explicitly never want their passphrase persisted, even DPAPI-protected).
v1.11.4
April 28, 2026Cooperative cancel for SQL Server / Bare-Metal Recovery / System State backups. Mirrors the v1.11.3 Hyper-V work: each runner now accepts a CancellationToken, checks it between chunks, and runs per-type cleanup on cancel. SQL drops the temp .bak being assembled; BMR removes its WindowsImageBackup staging tree; System State removes its staging tree (cancellable only before wbadmin starts replaying — once wbadmin runs, the cancel signal can't reach it).
v1.11.3
April 28, 2026Cooperative cancel for Hyper-V backups + GUI Cancel button. Pre-1.11.3 the agent had cancelBackup(setId) but it only flipped a row in backup_jobs to 'cancelled'; the streaming pipeline kept chunking + uploading until the disk finished naturally. Users reasonably expected the Cancel button to actually stop the run. New BackupCancellation module: AppState owns one CancellationToken per in-flight job; pressing Cancel signals through the runner into processStreamIsolated, which forwards it to the worker isolate over a SendPort; the worker checks between chunks and on cancel stops issuing new uploads, drains in-flight ones (so we don't orphan TCP connections), and returns a partial result. Runner cleans up the in-progress BackupEngine-Reference checkpoint; no manifest is written.
v1.11.2
April 28, 2026Backups no longer freeze the UI or system. The streaming pipeline (FastCDC chunking + AES-GCM encrypt + upload) used to run on the GUI isolate; for a multi-GB Hyper-V backup the cursor would stutter and other apps would feel laggy because the BackupEngine GUI thread was pinned at 100% on hash + encrypt for minutes at a time. v1.11.2 moves that work onto a spawned worker isolate via processStreamIsolated; the GUI isolate forwards file streams in and gets per-chunk progress back. On Windows the agent additionally calls SetPriorityClass(PROCESS_MODE_BACKGROUND_BEGIN) for the duration of a backup, which lowers BOTH CPU priority (BELOW_NORMAL) and disk I/O priority (LOW) at the OS level. Linux/macOS get setpriority(PRIO_PROCESS, 0, 10) (nice +10).
New "Performance mode" toggle in Settings → Performance. OFF (default, recommended): backups run at lowered process and disk-I/O priority and upload one chunk at a time, so the rest of the machine stays snappy. ON: backups run at normal priority and upload up to four chunks in parallel for maximum speed — best for after-hours runs or dedicated backup servers.
v1.11.1
April 27, 2026Version bump only. No user-visible changes — published to keep the auto-update channel populated while the v1.11.2 background-priority work was in flight. The hardware-accelerated AES-GCM from v1.11.0 is unchanged.
v1.11.0
April 24, 2026AES-256-GCM chunk encryption is now hardware-accelerated via dart:ffi into the OS crypto library. Pointycastle (pure Dart) was the only cipher backend since v1.4.2 at ~80 MB/s per core in production AOT builds — the main bottleneck for encrypted backups on large VMs. v1.11.0 replaces that with platform-native code: bcrypt.dll CNG on Windows, libcrypto.so.{3,1.1} on Linux, and CommonCrypto (CCCryptorCreateWithMode + kCCModeGCM) on macOS. Benchmark on Windows Server 2025: 255 MB/s in the Dart VM test harness — a 391× improvement over VM-mode pure Dart, ≥3× over production AOT pure Dart. Expected real-world benefit: on a 200 GB encrypted backup, crypto overhead drops from ~40 minutes to <5 minutes.
Zero caller changes. The existing `AesGcmEncryptor(key)` constructor is now a factory that silently picks the fastest available backend: native if loadable, pure-Dart if the native library is missing / fails to load / crashes at init. The "native unavailable" decision caches per-isolate so a missing DLL doesn't get re-probed 1000 times per backup. An explicit `AesGcmEncryptor.pureDart(key)` constructor and a `debugForcePureDartCrypto` flag let tests exercise either path on demand.
On-wire format is unchanged: every chunk still emits `IV(12) || ciphertext || tag(16)`. Every backup ever written by v1.4.2 → v1.10.0 continues to decrypt identically whether the restore agent runs the native or pure-Dart path — verified by new cross-implementation round-trip tests (native encrypt → pure-Dart decrypt and vice versa).
Context pooling inside the native backends: on Windows, the BCRYPT_ALG_HANDLE (opened + switched to GCM mode) is a process-wide singleton; each encryptor owns its own BCRYPT_KEY_HANDLE for its lifetime. On Linux, each encryptor owns one EVP_CIPHER_CTX* that's reset+re-init'd per op (drops per-op cost from ~2µs ctx-new to ~50ns ctx-reset). On macOS, CommonCrypto doesn't support reset so it's a per-op cryptor with release; best we can do on that platform.
Graceful fallback: any native load failure, FFI error, or unsupported platform (macOS < 10.13, hypothetically BSD) drops to the pure-Dart path silently. Log goes to debugPrint only, never propagates to the backup runner. The factory is guaranteed to return a working encryptor on every platform Flutter desktop supports.
18 backup-core tests pass (5 original round-trip + 13 new covering cross-impl compatibility, forced-pure-Dart path, payload size matrix from 1 byte to 1 MB, native-status exposure). 1 tagged benchmark test skipped by default (run via `flutter test --tags benchmark` for on-demand profiling). Desktop suite: 370 tests pass. Only pre-existing widget_test.dart MyApp failure unchanged.
Windows is the only platform runtime-tested in this release. Linux + macOS bindings are code-complete with matching cross-impl tests but untested on those platforms — same situation as v1.5.1 (Mac/Linux scheduler) and v1.6.0 (Hyper-V CBT). The native-path fallback to pure-Dart guarantees no-regression behavior on Linux/macOS even if the native loader has bugs. A Linux/macOS smoke pass is the recommended follow-up before tagging v1.11.0 GA.
Follow-up optimization deferred: the native throughput of 255 MB/s is well below the theoretical 2-3 GB/s AES-NI ceiling, capped by per-op plaintext/ciphertext buffer churn (6-8 calloc/free pairs per encrypt). Pooling scratch buffers sized to FastCDC max chunk (64 KB) on the encryptor instance would push throughput another 5-10×. Not urgent at 255 MB/s — backups are now network/disk-bound, not crypto-bound.
v1.10.0
April 24, 2026Desktop auto-update checker. The agent now checks downloads.backupengine.com/latest-version.json on launch (10s after login to avoid competing with registration/heartbeat) and every 6 hours while the dashboard is open. A new amber banner at the top of every screen surfaces the update with four actions: "What's new" (opens the changelog in your browser), "Update now" (platform-aware install flow), "Remind tomorrow" (24h snooze), and "Skip this version" (never nag for this version again).
Install-type-aware upgrade paths. Detection uses Platform.resolvedExecutable — paths containing \WindowsApps\ or matching net.techbench.backupengine_ are MSIX; anything else on Windows is ZIP. (1) MSIX: "Update now" opens the ms-appinstaller: URI so the Windows App Installer handles the upgrade. (2) Windows ZIP: downloads the new ZIP to %APPDATA%\Backup Engine\updates\, writes a PowerShell updater script, launches it hidden, and the app exits — the script waits for the PID to clear, unzips over the install dir, and launches the updated EXE. (3) Linux tar.gz and (4) macOS zip: downloads to ~/Downloads/ with an "extract + run to finish" toast; in-place replacement would require knowing the user's packaging (tarball vs .deb vs .dmg vs brew cask) so manual extraction is the safe default.
Don't-check safety guards. The checker never fires while a backup is actively running (via AppState.hasActiveJobs), never runs in CLI or service mode (GUI-only), and never auto-triggers the updater — every install is user-initiated. Clicking "Update now" during an active backup surfaces a snackbar "Cannot update during backup. Will retry later." instead of silently doing nothing.
Skip/snooze persistence via SQLite (LocalDb v13 → v14 — new update_skip and update_snooze tables). Skipping v1.10.1 means you won't see the banner until v1.10.2 releases. Snoozing mutes for exactly 24 hours. A min_supported_version field in the manifest bypasses both — EOL builds always surface as a red (danger) banner with only "What's new" and "Update now" actions.
Backend: latest-version.json manifest on R2 root. scripts/upload-installer.mjs fetches the existing manifest, merges the new platform's entry (preserves other platforms' URLs + sizes), and re-uploads. The GHA cross-platform workflow does the same for Linux + macOS via a new refresh-version-manifest job. Same-version re-uploads preserve released_at so the UI doesn't flap.
37 new tests: 13 UpdateChecker (semver compare, install-type detection, malformed manifest rejection, network failure fallback), 8 UpdateService (skip/snooze persistence, stream emission, guard predicate), 8 UpdaterService (platform dispatch, PowerShell script content verification, Linux/macOS download-only path), 5 UpdateBanner widget (renders, buttons wire to correct callbacks, min_supported_version renders red). Suite: 370/371 pass (only pre-existing widget_test.dart MyApp failure unchanged).
Architectural choice: no new package dependencies. Reused existing http for fetch, path_provider for download paths, and the Process.run pattern from about_screen.dart for opening URLs (matches how other screens already handle the ms-appinstaller: and web links — avoids pulling url_launcher just for this feature).
v1.9.0
April 23, 2026Admin Portal: "Add Customer" end-to-end. Admins can provision a customer account directly from the /users page without waiting for the customer to self-register. Modal form: email + plan + region + encryption mode + delivery method (invite email by default via Supabase magic link, or set temporary password as an advanced option) + billing decision (comp by default, charge immediately as opt-in) + optional organization name + admin-only notes. New Edge Function `admin-create-customer` handles auth user creation via the Auth Admin API, profile patch, and audit log. Rollback on partial-failure keeps the auth.users + profiles tables consistent.
Admin Portal: wire Impersonate / Suspend / Unsuspend / Reset MFA on /users/[id]. Impersonate generates a 10-minute magic link via auth.admin.generateLink and opens the customer portal in a new tab (every session is audit-logged with a required reason). Suspend sets profiles.status='suspended', flips the red banner on the detail page, and force-logs-out all existing sessions via auth.admin.signOut global. Unsuspend reverses. Reset MFA deletes all TOTP factors so the customer can re-enroll on next login.
Admin Portal: inline admin-notes editor (dirty-tracking, Save button) + customer-scoped Audit Log section at the bottom of /users/[id]. Shows the last 200 admin_customer_edits rows for that customer: timestamp, action, admin who performed it, reason, billing decision chip. Client-side CSV export via Blob — no backend call, safe for compliance exports.
Database schema: profiles.status enum (active | pending_invite | suspended), profiles.notes, profiles.created_by_admin_id, suspended_at/by/reason. RLS policies rewritten so suspended customers cannot self-read or self-update their profile even if their JWT is still cached client-side; service-role admin calls bypass RLS so unsuspend still works. admin_users.can_impersonate BOOLEAN (defaults FALSE for everyone, including super_admin — deliberate stricter default).
Desktop agent: signup screen lets customers who downloaded the agent from the website create their account directly in the app, no web-form round-trip. Fields mirror the web signup: email + password (≥12 chars, strength meter) + confirm + region + plan dropdown + optional organization name + ToS checkbox + Turnstile captcha (on Windows). "Sign up" link on the login screen; "Back to Login" on the signup screen. Auto-confirmed accounts flow straight into the main shell; email-verification-required accounts see a confirmation screen with a Resend button.
Desktop on Mac/Linux: captcha is skipped (the v1.8.1 stub renders an explainer; signup submits without a captcha token). If the Supabase project has captcha ENFORCED at the server side, non-Windows signups fail with a clear error — documented with a TODO for a future cross-platform captcha story. TurnstileCaptcha stub already ships with the Linux/macOS builds via the GHA workflow.
9 new desktop widget tests for the signup screen (form validation, strength meter, ToS gating, Supabase signUp mock with success/error paths). Desktop suite: 333/334 pass with --concurrency=1 (only pre-existing widget_test.dart MyApp failure remains). Admin portal tsc + next build both clean.
Backend schema reality check: admin_customer_edits uses customer_id (not target_user_id) and stores reason in billing_note/new_value JSONB rather than a dedicated reason column — the new wrappers handle this so the UI stays clean. encryption_mode enum at the DB level is zero_knowledge | managed (not managed_recovery); the Edge Function accepts managed_recovery in the API shape and maps on write.
v1.8.1
April 22, 2026Encryption audit log: SQLite-backed timeline of every encryption mutation (set passphrase, unlock attempts including wrong-passphrase attempts, lock, change passphrase, factory reset, import-from-manifest, DEK rotation begin/progress/complete/abort). Compliance + incident response now have a chronological "who/when did what" record. New table encryption_audit_log (LocalDb v12 → v13) with timestamp + event_type + outcome + detail + structured metadata_json. NEVER logs key material — passphrases, KEKs, DEKs, salts are explicitly excluded.
Audit log viewer at Settings → Security → Backup Encryption → "View Audit Log" — chip-coded rows by category (green=success, amber=neutral, red=failure), expandable metadata, CSV export to a user-picked path, triple-confirmed Clear Log (Dangerous) action that re-appends a "log_cleared" marker so the post-clear log isn't silent.
Audit-write never breaks the underlying op. Every record() call is wrapped in try/catch + debugPrint on failure; the original encryption mutation always succeeds even if the audit DB is unreachable. Verified by a synthetic _ThrowingLocalDb test.
Windows toast on DEK rotation completion + abort. The already-running tray icon fires a notification when the rotation stream completes ("Encryption rotation complete — All N manifests rotated under the new key.") or halts ("Encryption rotation halted — Stopped after manifest X. Click Resume in Settings → Security to continue."). The DekRotationService also emits a rotationProgress audit event every 10 manifests for forensic review.
New `backupengine encryption status` CLI subcommand for ops monitoring. Plain-text output shows configured/unlocked/verifier-version/manifest-body-encryption/KDF params/active DEK fingerprint/active rotation. Add --json for structured ops-script consumption. Active DEK fingerprint = SHA-256(dek_bytes)[:8] hex — gives ops a "did the DEK actually change after rotation?" check WITHOUT exposing the key (pre-image-resistant).
Selftest hyperv backup-set wiring (completes the v1.8.0 SKIP-marks). `backupengine selftest hyperv --vm <name> --confirm` now runs a real full backup → incremental → chain-promotion (forced via chainLengthCap=2) → restore → sampled byte-compare against the source VHDX (head 64 KiB + tail 64 KiB + 3 deterministic 1 MiB samples). Local-filesystem backend; no cloud cost. LIFO cleanup deletes the test storage dir + every BackupEngine-Reference-<setId>-* checkpoint regardless of partial failure.
25 new tests for v1.8.1 (11 EncryptionAuditService + 5 LocalDb audit table + 5 EncryptionKeyService instrumentation [including the _ThrowingLocalDb resilience test] + 2 DekRotationService rotation success/failure audit + toast + 2 selftest auditLog step) plus 2 new tests for v1.8.2 (selftest hyperv wiring SKIP path on non-Windows).
Test infrastructure note: audit-log tests use SQLite directly so the suite must run with `flutter test --concurrency=1` to avoid "database is locked" contention. Production is single-process so this only affects test ordering. Suite: 324/325 pass with --concurrency=1; only the pre-existing widget_test.dart MyApp failure remains.
Three new TODO(hyperv-validation) markers added by the v1.8.2 wiring for in-VM marker seeding via WinRM/shared-folder + VSS-quiesced source comparison + the existing reference-checkpoint failure mode now exercised by the chain-promotion step.
v1.8.0
April 22, 2026New `backupengine selftest` CLI command for end-to-end smoke validation. v1.5.1 (Mac/Linux scheduler) and v1.6.0/v1.7.1 (Hyper-V CBT chains + chain promotion) shipped with TODO(mac-validation) / TODO(linux-validation) / TODO(hyperv-validation) markers because the dev environment was Windows-only without Hyper-V infra. Selftest gives a one-command tool to confirm those assumptions hold when you reach the right hardware.
`backupengine selftest encryption` (safe, no flags needed): exercises full Argon2id KDF + verifier round-trip, lock/unlock cycles, wrong-passphrase rejection, master-key indirection (encrypt under v1, change passphrase, decrypt under v2 — same DEK), and v1.7.0 DEK rotation through the lifecycle. ~30s on a temp dir, no real backend or system state touched.
`backupengine selftest service --confirm` (DESTRUCTIVE): install/probe/start/stop/uninstall round-trip on the platform service. Real launchd LaunchAgent on Mac, real systemd --user unit on Linux, real Scheduled Task on Windows. Requires --confirm. Fail-fast if the service is already installed (use --force to overwrite).
`backupengine selftest hyperv --vm <name> --confirm` (DESTRUCTIVE): scaffold for the Hyper-V backup → incremental → chain promotion → restore → byte-compare round-trip. v1.8.0 ships with the orchestrator + checkpoint + cleanup harness (real); the actual backup-set construction steps are SKIP-marked pending stable backend client wiring. The TODO(hyperv-validation) markers in the runners stay relevant.
`backupengine selftest --all`: chains all three suites with appropriate skip semantics (Hyper-V skipped if not detected on the host). Useful for a comprehensive validation pass after deployment.
Output formats: plain text (default) shows [PASS]/[FAIL]/[SKIP] per step with timing; --json emits structured output for CI consumption. Exit codes: 0=all pass, 1=any fail, 2=any skip without fail.
Cleanup is LIFO via accumulated cleanup tasks: every side-effect (e.g. checkpoint creation) pushes a teardown closure, and a finally block drains them in reverse order regardless of partial failure. Even if a later step throws, prior cleanup runs.
18 new unit tests covering encryption end-to-end, dispatch routing, arg parsing, exit-code semantics. Stub-based; real Argon2id derivation included so the test suite gets ~22s slower but exercises real crypto. Suite: 299/300 pass (only pre-existing widget_test.dart MyApp failure remains).
v1.7.1
April 22, 2026Hyper-V chain promotion (synthetic full): when an incremental chain hits its length cap, instead of re-uploading the entire live VHDX as a fresh full, BackupEngine now reassembles the existing chain locally (full + N diffs) and re-streams it as a new "synthetic full" manifest. Every chunk that's already in cloud storage gets matched by hash and deduped — typically zero new bytes uploaded. For a 100 GB VM with weekly chain rolls, this saves ~5 TB/year of bandwidth.
Wizard control: a new "Synthetic full when chain reaches cap (recommended)" toggle in the Hyper-V wizard's Schedule step. Default ON for new sets. Off reverts to v1.6.0 behavior (live-VHDX full at chain cap).
Refactor: chain reassembly extracted from the restore runner into a shared HypervChainReassembler. Both restore (single VHDX out to user-picked folder) and chain promotion (single VHDX out to temp dir, then re-streamed) call the same well-tested code. Restore behavior is byte-identical for legacy single-full chains.
Reference checkpoint after promotion: a fresh BackupEngine-Reference-<setId>-synthetic-<ts> checkpoint is created on the host so the next incremental can chain off the synthetic full. If the VM was deleted between reassembly and checkpoint creation, the manifest is still written but flagged "no incrementals possible until next full".
7 new tests (4 reassembler unit + 3 backup-runner integration covering synthetic_full=true zero-byte re-upload via dedup, synthetic_full=false fallback to live-VHDX, and chain-walk failure → live-VHDX downgrade). Suite: 281/281 application tests pass + only the pre-existing widget_test.dart MyApp issue remains.
Three new TODO(hyperv-validation) markers (Merge-VHD ParentPath header preservation across chunked streams, VM-deleted-mid-promotion failure path for Checkpoint-VM, FastCDC-determinism assumption for the dedup-zero-bytes claim). Recommend smoke pass on real Hyper-V before tagging GA.
v1.7.0
April 22, 2026DEK rotation: every chunk + manifest body in every backup set can now be re-encrypted under a fresh Data Encryption Key. Previously the DEK was generated once at setPassphrase and stayed constant forever — a process-memory leak or side-channel compromise of the unlocked DEK exposed every chunk ever encrypted. v1.7.0 lets you periodically rotate the DEK to limit blast radius. After rotation, an attacker who captured the OLD DEK can no longer decrypt anything new.
Triple-confirmed UX (Settings → Security → "Rotate Encryption Key (Advanced)"): warning + checkbox → interruption-acknowledgement + checkbox → type ROTATE to confirm. Then a full-page progress screen shows current manifest / total manifests + abort button. Rotating a large backup set can take hours and consumes significant bandwidth — this is a deliberate maintenance action, not a casual one.
Resumable: a new SQLite table (dek_rotation_progress) durably records every manifest's rotation state. If the rotation is interrupted (crash, network drop, intentional abort), reopening the screen offers Resume which picks up where it left off — no manifest is re-rotated unnecessarily. Plan() seeds rows pessimistically; rotate() walks them in deterministic storage-key order.
Atomic per-manifest swap with dual-DEK transition state. While rotation is in flight, every manifest carries BOTH the OLD wrapped DEK (in a new previous_master_key_envelope field) AND the NEW one. The restore-side resolveActiveDek probe tries the current DEK first and falls back to the previous, so a mid-rotation crash leaves all manifests restorable in either state. After cleanup the previous_master_key_envelope is dropped.
Schema bumps: LocalDb v11→v12 (dek_rotation_progress table), manifest field previous_master_key_envelope (optional, present only mid-rotation). Backward-compat for any manifest without the field is byte-identical to v1.6.x behavior.
Constant-time DEK comparison in EncryptionKeyService to prevent timing-side-channel leaks during rotation state checks. abort() reverts the active key but does NOT rewrite already-migrated chunks — the dual-envelope makes restore work in either direction so storage-level rollback is unnecessary. Rotation is purely additive at the storage layer.
13 new tests across resolveActiveDek probe, manifest field round-trip, EncryptionKeyService rotation lifecycle, DekRotationService rotateOnce + resumability + abort. Suite: 274 pass / 1 pre-existing widget_test.dart MyApp failure unchanged.
v1.6.2
April 22, 2026Inline cross-machine encryption import in the Restore flow. v1.6.0 shipped a standalone Settings → Security → Import path; v1.6.2 catches the same case at the Restore button click. When you select a manifest from another machine and the local agent isn't keyed for it, a passphrase dialog pops immediately — no round-trip through Settings, no "you need to set up encryption first" snackbar. After successful import the restore continues with the freshly-imported key cached for the session (and persisted to disk for future restores under the same passphrase).
Wired into all 5 restore screens: file restore, SQL restore, Hyper-V restore, System State restore, BMR restore. Single shared helper (cross_machine_import_helper.dart) gates each runner so the UX is identical across workloads.
Aborted-vs-failed semantics: cancelling the dialog returns silently (no snackbar — the user backed out on purpose); a wrong passphrase or import error surfaces a clear snackbar with retry guidance. The local config stays untouched on failure (no half-imported state).
Helper accepts nullable manifestRaw — pre-v1.4.2 manifests with no rawJson at all are treated as legacy plaintext = proceed, which preserves back-compat for sets created before encryption shipped.
v1.6.1
April 22, 2026Manifest body encryption: closes the metadata leak that v1.4.2 → v1.6.0 left open. Today the chunk content is encrypted but the manifest itself is plaintext JSON, so anyone with backend access can see file count, file paths, VM/database names, sizes, and chunk-hash fingerprints. v1.6.1 wraps the manifest body in AES-256-GCM under the same DEK that encrypts chunks, so the server now sees only an opaque envelope: {encryption_version, kdf_params, master_key_envelope, manifest_body_encryption: "v1", encrypted_body: "..."}.
Three-way back-compat preserved: legacy plaintext manifests (pre-v1.4.2), v1.4.2 → v1.6.0 manifests (encrypted chunks, plaintext body), and v1.6.1+ manifests (encrypted chunks AND encrypted body) all restore correctly. The manifest-fetch helper detects the wire format from the outer JSON and unwraps when needed.
No new key, no new KDF round, no new envelope: body encryption uses the SAME DEK that encrypts chunks. The manifest_body_encryption marker is a wire-format concern only — once the agent has the key (via local config or v1.6.0 cross-machine import), it has everything it needs to decrypt both body and chunks.
Forward-compat refusal: unknown manifest_body_encryption values (e.g., a future "v2" written by a newer agent) throw rather than silently treating the wrapper as plaintext. Older agents fail loudly instead of corrupting a restore.
22 new tests: 20 unit (round-trip wrap/unwrap, state-read for all three wire formats, malformed envelope rejection, wrong-key rejection, forward-compat refusal) + 2 integration (real_backup + sql_backup end-to-end produce body-encrypted manifests with only the 5 outer keys visible). Suite: 261/262 pass (only pre-existing widget_test.dart MyApp failure remains).
v1.6.0
April 22, 2026Hyper-V differential backup + chain restore (CODE-COMPLETE; runtime validation pending). Today the agent walks through full → diff1 → diff2 → … → leaf manifests via parent_manifest_id pointers. The wizard's new "Backup type" step under Schedule lets you pick between full-only and incremental chain (default 1 full + 6 daily incrementals before a new chain starts). Each incremental captures only the regions of the VHDX that changed since the previous reference checkpoint. Restore validates the chain up front (same source set, same VMs/disks, contiguous reference checkpoints, encryption gate per entry) before reassembling the base VHDX from the full + applying each diff via PowerShell Merge-VHD in order.
Master-key indirection (DEK wrapped under a passphrase-derived KEK). Today's passphrase rotation no longer orphans existing chunks: changing your passphrase derives a new KEK that re-wraps the SAME DEK, so chunks encrypted before the rotation continue to decrypt with the new passphrase. Manifest schema bumped to encryption_version=2 with a master_key_envelope block; v1.4.2 manifests (encryption_version=1, direct passphrase derivation) keep restoring under the legacy path for full back-compat.
Cross-machine encryption import. Settings → Security → "Import Encryption from Manifest" lets a fresh agent import another machine's encryption setup by pointing at one of its v2 manifests + entering the source-machine passphrase. The agent unwraps the master key from the manifest envelope and persists the local config — subsequent backups + restores under the same passphrase work without ever touching the source machine.
Hyper-V manifest schema v3 (chain-aware): backup_kind ("full" | "incremental"), parent_manifest_id pointer, reference_checkpoint name, chain_index (0 for full, 1+ for each diff). Older v2 manifests without backup_kind are treated as full for back-compat. Hyper-V chain restore inherits the v1.4.2 per-entry encryption gate, so a chain mixing pre-v1.4.2 plaintext with v1.4.2 / v1.6.0 encrypted manifests restores correctly.
Passphrase rotation no longer requires multi-paragraph "your data will be unrecoverable" warnings — under master-key indirection the warning becomes a one-liner because the DEK is preserved across rotation. The Change Passphrase flow drops the confirm-checkbox.
Backup-runner integration: every encrypted backup written by v1.6.0+ embeds master_key_envelope + kdf_params in its manifest. The agent that wrote the backup AND any agent that imports the encryption setup can decrypt it. Today's scheme rotates KEKs without rotating DEKs; a future v1.x release adds DEK rotation (re-encrypt all chunks under a fresh DEK) for periodic key hygiene.
35 new tests: 14 Hyper-V (7 backup + 7 restore covering full/incremental fallbacks, single-entry/legacy/chain-7/VM-mismatch/contiguity-break/in-order/mixed-encryption), 21 encryption (11 master-key + 10 manifest-schema) — all stub PowerShell + file IO so they run on Windows without Hyper-V infra. Suite: 239/240 pass (only the pre-existing widget_test.dart MyApp issue remains).
UNVERIFIED on Hyper-V infra: three TODO(hyperv-validation) markers in source flag specific assumptions — long-lived BackupEngine-Reference-* checkpoint vs. unbounded VHDX delta growth on running VMs (hyperv_backup_runner.dart:354), Export-VMSnapshot vs. alternative diff-export verbs (hyperv_backup_runner.dart:683), Merge-VHD acceptance of our diff VHDX layout (hyperv_restore_runner.dart:643). Recommended: smoke pass on a real Hyper-V Server before tagging GA.
v1.5.1
April 22, 2026Mac and Linux background scheduler parity (CODE-COMPLETE; runtime validation pending). ServiceManager refactored from Windows-only schtasks into a platform-dispatching facade with three impls: WindowsServiceManager (existing), MacServiceManager (launchd LaunchAgent at ~/Library/LaunchAgents/net.techbench.backupengine.plist with RunAtLoad + KeepAlive, modern bootstrap/bootout with legacy load/unload fallback), LinuxServiceManager (systemd --user unit at ~/.config/systemd/user/backup-engine.service with Restart=always). Per-user on both platforms — install does NOT require sudo.
Public ServiceManager API is byte-identical to v1.5.0 — every consumer (Schedule screen banner, Settings install button, AppState lifecycle, CLI service subcommand) keeps working unchanged. The platform dispatch lives entirely inside the facade.
Stubbable shell-out runners on every platform (mirrors the v1.5.0 AutostartService pattern). 27 new tests cover install/probe/start/stop/uninstall round-trips on both Mac and Linux — all run on Windows because every shell-out is mocked.
Hardcoded full paths for platform binaries (/bin/launchctl on Mac, /usr/bin/systemctl with /bin/systemctl fallback on Linux). Prevents PATH-injection attacks the same way Windows runners use System32 absolute paths.
Server-class workloads (Hyper-V, SQL, BMR, System State) remain Windows-only by Windows API constraint — those wizards stay hidden in the UI on Mac/Linux. Mac/Linux ship file-backup parity only.
Honesty disclaimer: this release was authored on a Windows-only dev environment. Code analyzes clean, all stubbed unit tests pass, and the spec follows the launchd / systemd documentation. Six specific assumptions (launchctl print exit codes, bootstrap-vs-load fallback chain, systemctl is-active semantics, StandardOutput=append: requires systemd ≥ 240, loginctl enable-linger surfacing, kickstart -k availability on minimum-supported macOS) are flagged with TODO(mac-validation)/TODO(linux-validation) markers in source. A follow-up smoke pass on real Mac and Linux hosts is recommended before tagging v1.5.1 GA.
v1.5.0
April 22, 2026Closes the closed-GUI silent-toast gap. Previously, if the GUI was fully quit (not minimized to tray), scheduled backups fired by the background service ran silently — no Windows toast, no animated tray icon, no visible cue. Now, "Start in tray at user login" auto-launches Backup Engine in tray-only mode at every Windows login (per-user HKCU\Run registry entry, no admin prompt). The tray icon picks up service-initiated backups via the existing cross-process job poll, so toasts and animation work whether the user has the app open or not.
New `--tray-only` CLI flag: same as GUI mode but starts hidden — only the system tray icon appears. Right-click the tray icon to open the window. The "Start in tray at user login" toggle in Settings → Application enables it. Lifts the previous "Start on system boot" preference from a dead toggle into a real registry-backed setting.
AutostartService: shells out to reg.exe (no new dep) to manage the HKCU\Software\Microsoft\Windows\CurrentVersion\Run entry. probe() returns notRegistered / registered / registeredStale (so the UI can detect when an old install path is referenced after the user reinstalls). Idempotent — re-toggling on never accumulates duplicate entries.
Per-user-only registration. Other "start at login" implementations sometimes use HKLM (admin-required, system-wide) — we deliberately use HKCU so a malicious or accidental click can't enable autostart for other users on shared workstations.
8 unit tests cover AutostartService probe, enable, disable, and stale-path detection. The "value not found" branch on disable is treated as success (already-absent = idempotent). Tests stub reg.exe so the real registry is never touched.
v1.4.2
April 22, 2026Zero-knowledge backup encryption goes live: every chunk uploaded to every destination is now encrypted with AES-256-GCM under a key derived from your passphrase via Argon2id (OWASP 2024 baseline — m=64 MiB, t=3, p=1, 32-byte key). The server stores only ciphertext + a manifest fragment containing the KDF salt, so even with full backend access, your data cannot be read without your passphrase. Wires into ALL five backup runners (file, Hyper-V, SQL, System State, BMR) and ALL five restore runners.
Settings → Security → Backup Encryption: status banner (Unlocked / Locked / Not configured), Set Passphrase / Unlock / Change Passphrase / Lock Now actions, plus a triple-confirmed Reset Encryption (Dangerous) action that requires typing the word "RESET" before completing. Lock Now also fires automatically on app quit so the in-memory key never survives the GUI process.
First-backup encryption prompt: when you create your first backup set without encryption configured, a one-time modal explains the zero-knowledge trade-off (only you can read your data; forgetting the passphrase = data loss) and offers Configure Encryption / Skip (insecure). The "Skip" choice is recorded so you don't get nagged on every subsequent set; the "Configure" choice routes you to the Security panel and aborts the current backup so you can re-trigger it under encryption.
Pre-flight check on the Backup Now button: if encryption is configured but locked, the runner refuses to start and shows a "Go to Settings" dialog. Backup runners refuse on the back end too — fail-fast policy means a locked encryption state can never silently fall back to plaintext upload.
Manifest schema v1.4.2: every encrypted manifest carries `encryption_version: 1` plus a `kdf_params` block with the salt + Argon2id tunables. Cross-machine restore reads these to know what KDF produced the key (today's MVP requires the local agent to be unlocked with the same passphrase; a future release lets you import the encryption setup from a manifest).
Backward compatibility: backups captured before v1.4.2 (no encryption fields on the manifest) still restore as plaintext. New backups are encrypted whenever a passphrase is set; the two formats coexist within the same backup set without re-encrypting historical chunks.
SQL chain restore inherits the per-entry gate: a chain mixing pre-v1.4.2 manifests with v1.4.2 encrypted manifests restores correctly — each entry is classified independently and the right decryptor is used per RESTORE statement.
Passphrase rotation re-derives the key, which means existing chunks encrypted under the old passphrase become unrecoverable. The Change Passphrase flow shows a multi-line warning + confirm-checkbox before completing. (A "wrap the master key under the passphrase" design that lets passphrase rotation NOT break old chunks is tracked as a v1.4.x follow-up.)
37 new tests across 4 phases lock down the contract: 13 EncryptionKeyService tests (KDF, verifier round-trip, lock semantics, rotation), 4 backup-runner fail-fast tests, 12 restore-runner tests (legacy back-compat + locked-service refusals + round-trips + malformed-manifest rejection), 8 PassphraseDialog widget tests. 169/170 desktop tests pass (only the pre-existing widget_test.dart MyApp issue is unchanged).
v1.4.1
April 22, 2026AesGcmEncryptor round-trip bug in backup-core: the manual processBytes+doFinal pattern over-allocated to a block boundary and then incorrectly sliced 16 bytes off the result, so any encrypt-then-decrypt of a non-block-aligned payload returned 16 bytes short (or, for sub-block payloads, failed GCM auth verification entirely). Replaced with cipher.process() which handles tag append/verify internally and returns a buffer sized to the exact output. Fix matches the workaround already in place in the v1.4.0 SQL credential vault.
backup-core ships its first test suite: 5 round-trip tests against AesGcmEncryptor (1 byte, 16 bytes, 1 KB, 64 KB, and tampered-ciphertext rejection) — all green. Catches the regression class above and locks down GCM auth-tag behavior for future changes.
No production cloud data is at risk. Today's chunk pipeline currently writes chunks gzip-only (encryption is opt-in via StreamingBackupPipeline's encryptor parameter, not yet wired into the backup wizards). The encryptor was used in the AI-asset and admin-directive restore paths — both correctly rejected truncated chunks via SHA-256 hash verification on round-trip, so no silently corrupted data shipped. Wiring chunk encryption into the main backup wizards is tracked as a separate roadmap item.
v1.4.0
April 22, 2026SQL credential vault: SQL Server Authentication is now first-class. Wizard "Authentication" step lets you pick Windows or SQL auth, enter username + password (obscured + show/hide toggle), and "Test connection" runs SELECT 1 before save. Credentials encrypt with AES-256-GCM (per-device salt + hostname-bound key) and live at %APPDATA%\net.techbench.backupengine\Backup Engine\credentials\vault.enc. Backup sets store only the credential ID — never the password. Settings → Security → Manage SQL Credentials lets you list, edit, and delete saved credentials.
SQL chain restore: pick a "restore point" in the SQL Restore screen and the runner walks the full backup chain — full → diff → log — issuing RESTORE … WITH NORECOVERY for every entry except the last, then RECOVERY on the tail. Optional STOPAT picker for point-in-time log restore. Validates up front that every entry in the chain shares the same source instance and the same database set, so a typoed selection fails before the first sqlcmd call.
SQL WITH MOVE for cross-machine restore: "Advanced: file moves" panel queries RESTORE FILELISTONLY on the source .bak, lets you remap each logical data/log file to a new physical path on the target machine, and emits MOVE clauses in the generated T-SQL. Defensive validation rejects parent-directory traversal, non-data-file extensions (.exe, .dll, etc.), and logical names that don't appear in FILELISTONLY output (typo guard).
sqlcmd argument logger now redacts -P <password> to -P *** in diagnostic logs and console traces — passwords from the new SQL auth flow never hit disk in plaintext anywhere.
WITH MOVE physical-path validator enforces drive-letter prefix + .mdf/.ldf/.ndf extension + no NUL/wildcard chars. Logical-name mismatches between user input and FILELISTONLY are silently dropped (with a warning in the log) rather than emitted as MOVE clauses that would fail at restore time.
v1.3.3
April 22, 2026System State backup + restore runners. Wizard saves were inert since v1.0.9; the backup runner now invokes wbadmin start systemstatebackup, captures the WindowsImageBackup tree (boot files, registry, AD, IIS, COM+, certificates), streams it through FastCDC + chunked upload. Restore reassembles the tree and invokes wbadmin start systemstaterecovery (DSRM warning + reboot prompt for AD-bearing manifests).
Bare-Metal Recovery backup + restore runners. Backup runner invokes wbadmin start backup -allCritical -vssFull, streams the produced .vhdx images. Restore is a "create recovery image" workflow — reassembles to a user-picked external drive + drops RESTORE_INSTRUCTIONS.txt with the exact wbadmin sysrecovery commands the user runs from Windows Recovery Environment.
Server Backup tab now executes ALL four workloads (Hyper-V, SQL, System State, BMR) end-to-end. Wizards no longer save sets that just sit there; every backup type runs, and three of four (Hyper-V, SQL, System State) restore in-place. BMR uses the offline-restore pattern by Windows constraint.
CLI backup start no longer prints "runner not yet available" for ANY backup type — every wizard's output is now a real, executable backup set.
v1.3.2
April 22, 2026Chunk garbage collection: v1.3.0 prunes manifests; v1.3.2 sweeps the orphan chunks those pruned manifests left behind. After every successful backup, ChunkGarbageCollector reads every remaining manifest, computes the union of referenced chunks across file/Hyper-V/SQL types, and deletes any chunk no longer referenced. Defensive: an unreadable manifest pauses GC for that round (better to over-keep than over-delete).
Storage cost from prior backup cycles now actually goes down over time. Combined with v1.3.0's manifest pruning, retention behavior is finally complete: keep N manifests, garbage-collect everything else.
v1.3.1
April 22, 2026AI Assets wizard wiring: the AI Assets screen has been informational since v1.0.5. Toggles now create a real backup set — pick which AI assets to protect (Claude Code transcripts, Cursor caches, HuggingFace models, Ollama, LM Studio, MLflow, W&B, etc.), choose destinations + schedule + review, and the existing file backup pipeline takes over with multi-destination fan-out, retention pruning, and diagnostic logs.
AI Assets screen status banner: shows green "AI Backup configured · runs {schedule} to {destinations}" with Edit / Backup Now / Delete actions when set up; amber "Configure AI Backup" CTA when not.
v1.3.0
April 22, 2026SQL Server restore engine: backup ships in v1.2.0, restore lands now. Pick any SQL manifest in the Restore tab → database checklist → optional target instance + Replace Existing toggle → reassembles each .bak from chunks (decompress + SHA-256 verify per chunk) → calls native RESTORE DATABASE/LOG via sqlcmd. Per-database fault isolation; one DB failing doesn't skip the others.
Configurable retention pruning: wizards have collected a "retain N versions" value since v1.0.9 — orchestration now actually prunes. After every successful run, manifests beyond the keep limit are deleted from each destination. Stale chunks are intentionally NOT deleted yet (chunks are content-addressed and may be referenced by multiple manifests; safe deletion needs a reference-count pass shipping in a follow-up).
ManifestInfo.totalBytes / fileCount now handle SQL manifests too (sum databases[].size_bytes / databases[].chunk_count). Restore source-card line shows "N databases · M GB" for SQL manifests instead of the file-tree default.
v1.2.0
April 22, 2026SQL Server runner: backup wizards have been able to save SQL sets since v1.0.9, but the runner finally executes them. Uses sqlcmd + native BACKUP DATABASE/LOG with COMPRESSION + CHECKSUM. Streams the .bak through the existing FastCDC + dedup pipeline. Per-database fault isolation — one DB failing doesn't skip the others.
Multi-destination reporting end-to-end: backup reports now carry a destinations array (one entry per destination with per-entry status, bytes uploaded, and error message). Admin and customer portals render the array as status-colored chips. Backfill is automatic — older single-destination reports show as 1-element arrays.
Service-aware toast notifications: when the GUI is alive (visible or in tray) and the background service starts/completes a scheduled backup, you get a Windows toast — "Scheduled backup started: My Set" / "completed" / "failed". Closes the visibility gap from v1.1.3 where the tray icon animated but no toast told you why.
Supabase schema v1.2.0: backup_reports.destinations JSONB column with GIN index on the destinations array — supports admin queries like "show me every report that wrote to OneDrive" without table scans.
Edge Function send-backup-report accepts both shapes: new clients send the destinations array; older app builds (pre-v1.2.0) keep working via destination_label fallback that auto-synthesizes a 1-element array on the server side.
v1.1.4
April 22, 2026CLI: new `connectivity test` command runs an end-to-end iDrive PUT/GET/verify/DELETE round-trip and prints SUCCESS/FAIL + latency_ms + timestamp. Returns non-zero exit code on failure for cron-driven monitoring.
CLI: new `logs list [setId]` and `logs show <path>` commands surface per-run diagnostic logs from the command line. Pipes cleanly into jq via `--json` so support workflows can grep, sort, and forward without opening the GUI.
`sets list` now renders all destinations on a multi-destination backup set instead of silently showing only the first. Catches up the human-readable output to match the JSON output, which already included the full destinations array.
Hyper-V backup runner's checkpoint create/remove PowerShell calls now use absolute System32 paths and explicit UTF-8 encoding — same hardening as the detection service. Eliminates a class of false-failure modes when Windows Server PATH is non-standard.
New KB articles: Multi-Destination Backup, Background Service, Diagnostic Logs, Restore Hyper-V VMs. FAQ expanded with 8 entries covering the v1.0.9 → v1.1.3 features.
v1.1.3
April 22, 2026Tray icon now animates when the background service runs a scheduled backup, not just when the GUI launches one. Drives off a 5-second cross-process poll of the backup_jobs table — when the GUI is in the tray and the service starts a backup, the icon picks it up within a few seconds.
Tray tooltip distinguishes service-initiated work: "Service is running 1 backup" / "3 backups running (2 in service)" / "1 backup running" depending on which process is doing the work.
AppState.hasActiveJobs now reflects ANY in-flight backup (GUI or service), so the dashboard "backup running" indicators and the close-guard dialog react correctly when the service is mid-backup.
v1.1.2
April 22, 2026Schedule screen now shows a clear status banner: green when the background service is installed and running (schedules will fire when the app is closed), red when installed-but-stopped (with one-click Start), or amber when not installed (with one-click Install — UAC prompt).
Closing the window with scheduled backups but no background service installed now prompts: Install Service, Don't Ask Again, or Stay Open. Removes the silent failure mode where users assumed schedules would keep running after closing the GUI.
AppState now caches and exposes the live ServiceStatus so any surface (Settings, Schedule, close-guard) reads the same source — refreshes after install/start/stop so the UI reflects current state without a relaunch.
v1.1.1
April 22, 2026Closing the window during a backup now prompts for an action: Minimize to tray (backup keeps running), Keep open, or Quit anyway. With no backup running the window hides to the tray instead of killing the process — click the tray icon to bring it back.
System tray icon plays an animated logo whenever a backup is running, idle icon when nothing is in flight. Hover tooltip shows "Backup Engine — N backups running" so you can check status from the corner of your screen.
Re-enabled the close interception that was disabled in v1.0.7 — the existing 3-choice dialog and tray-animation code were already wired but inert because the close event was never being intercepted.
v1.1.0
April 22, 2026Multi-destination backup sets: a backup can now fan out to BackupEngine Cloud + Local PC + Network PC + Google Drive + OneDrive simultaneously. Each destination produces its own independent restore point — classic 3-2-1 strategy out of the box.
Wizards (file, server: Hyper-V/SQL/BMR/System State) all share a multi-select destination step with per-entry path field, Browse button, auto-detect of cloud sync folders, and a fan-out summary line so you see exactly where your data is going before clicking Save.
Restore screen now lists each (backup set, destination) pair as its own card. Pick which destination to restore FROM — local for speed, cloud for offsite, etc.
Fault-isolated execution: if cloud is down, the local copy still completes. Job result rolls up across destinations: success only when every destination succeeds; failures list which destination(s) failed and why.
Database schema migration v10 → v11 adds a destinations column with full back-compat. Existing single-destination sets keep working unchanged; they appear as 1-element lists in the new model.
v1.0.9
April 22, 2026Hyper-V VM restore: Restore screen now branches on backup type — Hyper-V manifests open a dedicated VM-image restore wizard that reassembles each VHD from chunks and writes a RESTORE_INFO.txt with the Add-VM/Add-VMHardDiskDrive snippets to re-attach the VM
iDrive cloud connectivity test: new Cloud Storage section in Settings + Cloud Storage row in About show last test result with latency, status, and timestamp + a refresh icon for on-demand re-testing
About screen: dedicated page with build info, OS info, cloud connectivity, support links, and legal section
Per-run diagnostic logs: every backup run writes a timestamped log to %APPDATA%\Backup Engine\diagnostic-runs\ capturing PowerShell commands, chunk hashes, backend put/get latency, and errors. "View diagnostic log" button on each backup set card opens it in Notepad
Hyper-V backup runner now writes manifests at the same .../sets/{id}/manifests/{ts}.json path file backups use, so the Restore screen can actually find them (was previously writing to .../hyperv/manifests/ which Restore never scanned)
Hyper-V manifest v2 schema: each disk entry now carries the full ordered chunk_hashes list + storage_prefix so VM restore can fully reassemble the VHD without consulting the live host
Hyper-V disk detection now uses Get-VM | Select -ExpandProperty HardDrives + Get-Item for sizes — works for users with Get-VM access who lack Hyper-V Administrators (Get-VHD elevation). Eliminates "0 disks · 0 B" false negatives
Manifest write failure now propagates to the runner result — previously a failed manifest upload silently reported success against zero restorable chunks
Path traversal hardening on VM restore: malicious manifest disk file_name / storage_prefix values can no longer escape the restore folder or inject backend keys
Per-chunk SHA-256 verification on Hyper-V restore catches backend bit-rot, MITM tampering, and cross-tenant key collisions
Destination wizard now offers a Browse button for Local PC, Network PC, Google Drive (auto-detects sync folder), and OneDrive (reads %OneDrive% env var)
Server backup wizards show an animated sonar-pulse scanning placeholder during the initial probe (Hyper-V VM enumeration, SQL instance discovery, BMR volume scan, System State role detection)
Detection services for Hyper-V / SQL / BMR / System State now use absolute System32 paths and explicit UTF-8 stdout encoding — fixes false negatives on configurations where PATH was unusual or PowerShell defaulted to UTF-16
Version footer at the bottom of the login screen so support can ask "what version are you on?" at a glance
v1.0.8
April 21, 2026Missing-runtime warnings now show the download link as selectable monospace text with a copy-to-clipboard button — applies to WebView2 today and any future bundled-dependency prompts
v1.0.7
April 21, 2026Window close (X) button now actually closes the app — the previous build intercepted the event without consuming it, so X did nothing
Sign-in security check shows a clear "install Microsoft Edge WebView2" message on Windows Server 2019 and older Win10 builds where the runtime is missing (was previously a cryptic environment_creation_failed error)
Portable ZIP bundles the Visual C++ Redistributable runtime so the agent launches on Windows Server 2019 without a separate redist install
v1.0.6
April 21, 2026Server Backup hub: clickable cards for Hyper-V, SQL Server, Bare-Metal Recovery, and System State, each opening a multi-step wizard that creates a scheduled backup set
Hyper-V wizard: VM checklist, application-aware VSS toggle, retention, destination, schedule, review
SQL Server wizard: instance auto-discovery (Windows authentication in v1), database selection (all user DBs or named list), Full / Differential / Transaction-log backup type
System State wizard: components surface (AD, registry, boot, COM+, IIS, certificates), atomic capture via wbadmin
Bare-Metal Recovery wizard: volume picker (system+boot pre-selected, data volumes opt-in), dissimilar-hardware restore notes
Embedded Cloudflare Turnstile in the desktop login (WebView2 over /embed/captcha) — restores login under Supabase CAPTCHA enforcement
OAuth (Google / Microsoft / Apple) on website + portal login
Password recovery via /reset-password — verifies recovery session, signs out after update so the recovery session can't be reused
Profiles now hydrate full_name + avatar_url from OAuth metadata; portal + admin sidebars show user card
CLI: sets list shows Type column with per-type config; backup start dispatches by backupType
Plan label: login pulls profiles.plan_id joined with plans(name) so upgraded accounts see "Server Edition" instead of "Free"
v1.0.5
April 20, 2026AI Infra Backup tier ($395/month, 10 TB, 20 devices) — back up vector databases, AI models, prompt libraries, notebooks, and agent transcripts
Restore Assistant: natural-language restore via Anthropic Claude with read-only / dry-run / write tools and a directive processor
Configurable heartbeat interval (default 60 minutes) with manual "Sync now" refresh
Cloudflare Turnstile bot protection on signup, login, and forgot-password
AI Assets tab moved into Backup section alongside Files and Server Backup
Concurrent backup jobs with per-set progress cards
Backup and restore honor selected destination (cloud vs local)
v1.0.3
March 28, 2026Added team member management with role-based permissions (Owner, Admin, Operator, Viewer)
Added notification email management — configure multiple recipients for backup reports
Added billing & subscription management in desktop agent (upgrade, add-ons, team seats)
CLI: added users, billing, and notifications command groups
Customer Portal: Team Members page with invite, role assignment, and custom permissions
Admin Portal: 19 pages including Revenue, Invoices, Payments, A/R, A/P, Reports, Forecasting, Affiliates, Partners, Staff Management
Fixed SQLite FFI initialization on Windows desktop agent
Fixed navigation rail alignment — menu and content now top-aligned
v1.0.2
March 15, 2026Customer Portal with 10 pages: Dashboard, Devices, File Browser, Restore, Activity, Billing, Settings, Download
Admin Control Center with Dashboard, Users, Devices, Backup Jobs, Storage, Billing, Audit Logs, Settings
Desktop agent: 8 screens with full backup/restore workflow
RBAC system with account-level roles and 20 granular permissions
Fixed Tailwind CSS not loading in Customer Portal (missing PostCSS config)
Fixed locale routing — added root redirect to /en for Portal and Admin
v1.0.1
March 1, 2026Company website redesigned with professional UI, animations, and full content
Pricing page with desktop/mobile plan tabs, add-ons, and FAQ
Features page with core technology, personal, server, and management sections
Download page with platform-specific agents and quick start guide
Multi-language support: English, French, Spanish
v1.0.0
February 15, 2026Initial release — BackupEngine MVP
FastCDC content-defined chunking engine (2-64 KB variable chunks)
AES-256-GCM client-side encryption with Argon2id key derivation
Gzip compression with adaptive skip
Backup and restore pipelines with deduplication
Supabase backend: 14 Edge Functions, 4 database migrations
Desktop agent scaffold: CLI + GUI dual-mode
Row-level security policies on all database tables