1.11.0
Latest
October 2, 2026
· build
Added
- Auto-refresh and Reload in Browse Data — The database browser now re-reads the open table whenever you return to its window, on demand with a Reload button (⌘R), or continuously with a new Auto-refresh toggle, and shows when the rows were last read. A refresh never interrupts a cell you're editing or a delete you're confirming, and never re-runs a statement you typed.
Changed
.test routing now runs alongside Laravel Herd and Valet — Vaporware gives its own .test domains a dedicated loopback address (127.0.0.86) and redirects only that address's ports 80/443. Every other .test name still resolves to 127.0.0.1, so Herd or Valet keep serving their own sites at the same time — nothing has to be stopped or "taken over," and the old port-conflict / take-over prompts are gone.- Prompt to update routing from an earlier version — If
.test routing was set up by an older build (which claimed all of ports 80/443 and could hand your domains to another local tool), Vaporware now flags it and offers a one-click Update Routing.
Fixed
.test HTTPS no longer intermittently serves another tool's certificate — On a machine also running Herd, .test sites could hand back Herd's certificate; Vaporware's domains now reach Vaporware on every connection.- The local HTTPS certificate no longer piles up in your login keychain — the proxy certificate was being added to the login keychain on every domain change; it's now kept in memory only.
Security
- The
.test proxy and DNS server now listen on 127.0.0.1 only — they previously bound all interfaces, leaving the proxy and every running project behind it reachable from the local network on ports 7080/7443.
1.10.0
September 21, 2026
· build
Added
- Settings from the menubar — a new Settings… item lets you open preferences straight from the menubar, without first opening the main window.
- External-drive awareness — Vaporware now notices when a project's folder isn't available (typically because the external drive it lives on is unplugged) and shows a notice naming the drive; the project's settings, domains and databases are left untouched, and reconnecting the drive clears it automatically. Missing-folder projects are also skipped at login instead of opening onto a row of failed builds.
- Richer galleries in generated sites — galleries in sites exported or re-synced from Pixla Sites now support click-to-open full-size zoom (a lightbox that still works with JavaScript off), per-image focus point and zoom, choose-your-fit framing (fill-and-crop or fit-whole), portrait and phone aspect ratios, phone and tablet device frames for app mockups, and automatic dark-mode screenshot swapping.
- CDN-hosted site images — images in generated sites can be served from
cdn.pixla.app instead of being inlined into every page.
Changed
- Generated-site responsive breakpoints — exported sites now use the same tablet (1024px) and mobile (720px) breakpoints as Pixla Sites, and can hide individual elements at a given width.
Fixed
- Build progress is accurate again — Vaporware now reads SwiftPM's current build output correctly: a normal rebuild no longer falsely claims "Dependencies changed — resolving and recompiling," build progress advances instead of stalling, and a running server's own log output can no longer knock it back into "Building." In daemon mode these build indicators are also cleared properly between restarts.
- Clearer errors for missing project folders — starting a server whose folder isn't there now explains that the folder is missing or its drive is disconnected, instead of an opaque process failure.
- Removing a project with its drive unplugged — "Remove project ▸ Move folder to Trash" no longer silently removes only the record while leaving the folder behind; the trash step is skipped when the folder can't be reached.
- Opening newer Pixla Sites documents — a
.pxs saved by a newer version now opens instead of failing with "the data couldn't be read," with any unrecognized appearance values rendering plainly.
1.9.0
September 20, 2026
· build
Added
- Favorites — right-click a project in the sidebar and choose Add to Favorites, or switch on Favorite in its Overview. A favorite moves to the top of its own list (the ungrouped projects, or its group, without being taken out of it) and always appears in the menubar panel, however many you star. Its star sits in a fixed column, so stars line up whatever the project names.
- Next run for automatic schedules — Settings ▸ Dependency Updates, Settings ▸ Backups and the Backups screen now count down to the next scheduled run, with a status dot, and say whether the background daemon or the app runs it. Before, they showed only when the last run was, which couldn't tell you whether the next one was minutes or days away, or whether anything would run at all. Due means it starts within five minutes. Last run now reads "20 seconds ago" instead of a bare "20 sec".
- Check All Projects Now and Back Up All show progress — while they run, they name the project or database they're on, how many there are, and a running clock. Checking everything can take minutes, and a greyed-out button with a spinner that long looked the same as one that had hung.
- Long menubar lists scroll — Services and Projects scroll past about ten rows instead of stretching the panel, so Quit and the other actions stay on screen.
Fixed
- The menubar no longer hides the projects you use — its project list was the first five in the order projects were added, which you can't see or change, so the project you actually work on could be missing and a running server past the fifth had no stop control in the panel. It now lists favorites first, then every running project, then the top of your sidebar up to five, with More in Vaporware… when some are left out. (The Services list was never trimmed; every database is still shown.)
- No two projects share a port — new projects created while the background daemon managed your services were all given the scaffold's default port, which only collided the day two of them ran at once. Every project now gets its own free port whichever process creates it, its
.env PORT= is corrected to match, and Project Settings warns (and won't save) if you type a port another project already uses. - Editing a project's port keeps its
.test domain working — changing a port in Project Settings used to leave myapp.test forwarding to the old port, or to whatever project had since taken it. The domain now re-points automatically, and any domain already pointing at the wrong port is repaired at startup. - A stale build banner in daemon mode — with the background daemon running, "Dependencies changed — resolving and recompiling" could stick on the Overview tab for every restart after a single real dependency resolution, and "First build" never showed for a fresh project. Both now report correctly.
1.8.5
September 11, 2026
· build
Changed
- Appearance switcher on published sites is now a menu — the theme control in an exported/published Vapor site's nav is a System / Light / Dark menu that shows the active appearance and ticks the current choice, replacing the single-click Light↔Dark toggle. Visitors can now pick "System" (follow the OS) explicitly, and choosing the appearance that's already showing registers visibly instead of looking like a dead button.
Fixed
- Published sites again follow the visitor's OS appearance — a generated site no longer locks itself to one theme for visitors who never used the switcher; with no explicit choice saved, the page tracks the operating system's light/dark setting live, with no flash on load.
1.8.4
September 9, 2026
· build
Fixed
vw sites resync no longer strips a site's fonts — the exporter's web fonts now travel with the exporter itself (reached through the package bundle) instead of only living in the app. Run from the terminal, resync previously found no fonts, emitted no @font-face, and deleted the fonts already in Public/fonts/ as orphans — so a site re-synced from the command line quietly lost its typography. The app and the CLI now produce the same site.- The app ships its exporter fonts only once — those six fonts had been bundled into both the app and the package that uses them, adding roughly a megabyte of duplicate payload to every build. There's now a single copy.
- Declining a generated middleware now sticks — the choice was recorded and then discarded by the very next re-sync, which rebuilt the project's sync record from scratch. Since a re-sync is exactly what you do right after declining, the "not registered" notice always came back. The decision now survives the re-sync.
- Re-synced sites no longer change in every file for no reason —
@font-face rules were emitted in an unstable order, so regenerating a site produced spurious differences from the previous export. They're now written in a stable, sorted order.
1.8.3
September 6, 2026
· build
Fixed
- Re-syncing a site from the
vw command line no longer strips its fonts — when a site was re-synced from the terminal instead of the app, the exporter couldn't find the fonts it ships, so it wrote no @font-face rules and deleted the fonts already in the site's Public/fonts/ as if they were leftovers — quietly losing the site's typography. The fonts now travel with the exporter, so the terminal and the app produce the same site. - A generated middleware that doesn't belong in your project can now be declined — re-sync warns when a middleware it wrote isn't registered in
configure.swift, but sometimes not registering it is the correct choice (for example, a project with its own canonical-host handling). Previously there was no way to say so, and the notice came back on every re-sync reading like a defect. The re-sync sheet now has a Not for this project button that records the decision so the notice stops returning. - The re-sync sheet's middleware notice reads cleanly again — a gap of stray spaces sat mid-sentence, and the wording stayed plural even when only one middleware was flagged, so a single finding looked like two. The text now matches the count and reads without the gap.
1.8.2
September 6, 2026
· build
Changed
- Help now explains where background work runs — the Dependency Updates, Mail, Backups and Background daemon articles spell out that automatic backups, dependency-update checks and the mail catcher run inside the daemon when it's enabled (so they keep to their schedule with Vaporware quit, and the results are waiting when you next open the window) and inside the app otherwise.
Fixed
- The app shrinks back by about a megabyte — the fix below moved the exporter's six web fonts into the package that uses them, but left the old copies in the app as well, so every build shipped all six twice. Only one copy now.
vw sites resync stripped a site's fonts — the exporter locates the fonts it ships by asking its own bundle, which is the app when the app runs it and the *command's* when the terminal does. From the CLI it therefore found none, wrote no @font-face, and — because the same check decides what a site should contain — treated the fonts already in Public/fonts/ as leftovers and deleted them. A site re-synced from the terminal quietly lost its typography, from a command documented as doing what the app does. The fonts now travel with the exporter, so both routes produce the same site.- Declining a generated middleware now sticks — the decision was recorded, then thrown away by the very next re-sync, which rebuilt the project's sync record from scratch. Since re-syncing is exactly what you do straight after declining, the notice always came back.
- A generated middleware you don't want can now be declined — re-sync tells you when a middleware it wrote isn't registered in
configure.swift, because until it is, it does nothing. But "register it" isn't always right: a project with its own canonical-host handling must *not* register the generated one, which would redirect the very host the project deliberately serves. There was nowhere to record that, so the notice came back on every re-sync in wording that read as a defect — and the only way to quiet it was to do the wrong thing. Not for this project now remembers the decision. - The re-sync sheet's middleware notice had a hole in the middle of a sentence — a run of stray spaces sat where a clause had been removed, and the wording underneath stayed plural while the heading above it said "needs one line", so one finding read as two. Both agree with the count now.
- A local SMTP catcher no longer fails when its port is taken at the wrong moment — it checked whether a port was free and then bound it, and anything on the Mac could claim it in between. Losing that race was reported as an error, which made "binds the next free port if 1025 is taken" untrue exactly when it mattered. It now tries to bind and steps to the next port when that fails.
- Commands no longer starve each other on a busy Mac — reading a command's output blocked a thread from a shared pool for as long as the command ran, so enough at once (a build, a port scan, several databases) and there was nothing left to read the next one's output, which stopped *that* command, nor to enforce the timeout that would have freed things up. Reads and timeouts now get threads of their own.
- Mail said "Stopped" while it was catching perfectly well — with the background daemon running, the window asked once, as it connected, whether the catcher was up. The daemon starts the catcher *last*, after bringing your databases and servers up, so on a Mac with a few projects the answer was always "not yet" — and nothing ever asked again. The catcher now says when it starts, stops or can't bind, so the window shows what is actually true.
- The mail switch and port did nothing — they were documented as applying "on next launch", which quietly stopped being possible once the daemon owned the catcher: the daemon is meant to outlive the app, so relaunching Vaporware changed nothing at all. The switch now takes effect immediately, and the port when you press Return, wherever the catcher is running.
- A mail port saved by an older version was ignored — it had been stored as a number while newer builds read it as text, so a custom port silently fell back to 1025. Both are read now.
- "Update packages in ^[6 project](inflect: true)?" — the confirmation dialog printed its own pluralisation markup instead of "6 projects". Same bug as the backup message fixed in 1.8.0; the counts that reach a dialog, button or label are now pluralised in Swift, where the result can't depend on which overload SwiftUI picks.
1.8.1
September 6, 2026
· build
Fixed
- Automatic backups are actually automatic now — the schedule used to live in the app, so it only ran while Vaporware's window was open, and worse, on any launch that handed the services to the background daemon it was never started at all: nothing was backed up on a schedule and Last run stayed frozen on the day you switched the daemon on. Whichever process owns your services now runs the schedule — the daemon sweeps headlessly with Vaporware quit and nothing on screen, the app sweeps when it's in charge — and because both read the same cadence, retention and last-run stamp, backups never happen twice and Last run reflects every sweep whether or not a window was open for it. The Backups screen now says which one is running the schedule.
- Dependency-update checks run in the background too — same story as backups: the sweep lived in the app, so it only ran while Vaporware was open, and the results you came back to were as old as the last time you happened to have the window up. The daemon now sweeps headlessly when it owns your services, and a window that opens later shows what it found — including a sweep that finishes *while* you're looking, which used to require reopening the app to notice.
- Mail is caught even when Vaporware isn't open — the SMTP catcher lived in the app, so it stopped the moment you quit the window. But the daemon starts your projects' servers at login, and a server that sends a password-reset email to a port nothing is listening on doesn't fail quietly. The daemon now runs the catcher and owns the inbox, so mail sent by anything it started is captured; open Vaporware later and it's all there, with new mail arriving live while you watch. Exactly one of the two ever holds the port and writes the inbox, so nothing is caught twice or half-written.
- "Back Up All" now counts as a run — only the scheduled sweep used to record itself, so backing up by hand left Last run reading days ago beside snapshots you'd just taken — and didn't hold the timer off, which was free to dump every database again minutes later. A backup is a backup now, whoever started it: the time updates the moment it finishes, and a scheduled sweep can no longer start on top of one already in progress.
1.8.0
September 1, 2026
· build
Added
- Backups and Routing, beside Services and Mail — two more app-wide destinations at the bottom of the sidebar. Routing owns the DNS resolver, the proxies, the
.test switch and every hostname being served; a project's tab is now Domain (singular — a project has one) and keeps a single line saying whether it can be reached, with a way through. Two screens called Domains, one telling you to "Open Domains", was one screen too many. Services keeps a one-line network summary for the same reason. Backups shows every backup on this Mac at once: what exists per database, whether the schedule is on and when it last ran, Back Up Now for one or Back Up All for everything, and restore behind a confirmation. Routing lists every .test domain being served with the project behind it and whether it's HTTPS, above the DNS/proxy/.test panel that now lives only there. Both answer questions no per-project tab can. vw sites resync — the re-sync you run from a project's Overview is now available from the terminal too, for scripts and release steps (the sheet is still the way to do it by hand — it previews conflicts and lets you choose): vw sites resync <path> regenerates its Leaf templates, Public/ assets and machine-owned controllers from the .pxs it came from, leaving your own code alone. --dry-run lists what would change and writes nothing; --overwrite-conflicts regenerates design files you'd hand-edited (the default keeps them and names them). It needs nothing but the project folder — the source document is recorded in the project's own manifest — so no daemon and no app window. The exporter moved into VaporwareKit to make this possible; the app runs exactly the same code, so the two can't drift.- Services — a new destination at the bottom of the sidebar, beside Mail, headed the way Mail is (its name, and how many services are up), for everything Vaporware runs that isn't one project. The database engines it downloads (install, change version, remove); *every* database on your Mac with its state, Start All / Stop All, and the same per-database controls a project's Databases tab has; the DNS resolver, the HTTP and HTTPS proxies and the
.test routing switch — including whatever other app is holding a port Vaporware wanted, named with a Stop button; and whether the mail catcher is listening. All of it was reachable before, split between two panes of Settings, a per-project tab and four dots in the status bar — three copies of the same facts, of which the Settings one couldn't show a port conflict at all. Settings keeps what you *choose* (which engine a new project gets, the mail port); this is what you *do*. - Edit your data — the database browser could show you a wrong value and leave you to go and fix it in a terminal. Double-click a cell to change it, Return to save, Escape to abandon; right-click for Set to NULL or Delete Row…. Every write names the row by its primary key and nothing else, so an edit changes the row you clicked even when another row looks identical — and the table is re-read afterwards, so what you end up looking at is what the database stored rather than what you typed. A table with no primary key stays read-only and says why, because there is no expression that names one of its rows; so does a result you got from your own statement, whose columns may be computed or from several tables. The statement box still runs anything.
- Rebuild on Save — Swift is compiled, so until now every edit meant going back to the Overview and pressing Restart. Turn on Rebuild on Save in a project's Overview and Vaporware watches its Swift files: save one and the server rebuilds and relaunches on its own, with the Overview noting what triggered it and the console showing the build as usual. Saving several files at once costs one rebuild rather than one each, a save during a rebuild queues another instead of being lost, and a build that fails leaves the watch running so the next save tries again. Only
.swift files count — Leaf templates and Public/ assets are read from disk per request and never needed a restart, and build output in .build is ignored, which is what stops a rebuild from triggering itself. Off by default and per project, since rebuilding a large project is real work; stopping the server stops the watch. - The license is presented on first launch — Vaporware ships as a signed disk image rather than through the App Store, so nothing in installing it ever showed you the agreement you were accepting. The first launch now shows the EULA with Agree and Continue or Quit, and Settings ▸ Legal records which version you accepted and when. Acceptance is remembered per license version, so a revision asks once more rather than riding on an older yes — and restoring a settings export never accepts on your behalf.
- Routes — a new tab lists every route your app registers: method, path, and the description where one is set, with a filter and a double-click that opens a plain
GET route in your browser. The list comes from the app itself (the same routes command Vapor gives you in a terminal), not from reading your source, so a route added by a controller, a route group or a package you depend on is in it — and it can't disagree with what's actually serving. - Commands & migrations — a project's Overview can now run the commands the project itself registers. Vaporware asks your app what it offers and lists what comes back, so nothing is assumed on your behalf: a command you added this morning is there, and one your project doesn't have never appears. Fluent's migrations get their own Run Migrations and Revert Last Batch… buttons (the revert asks first, and says what a revert actually does to your data), and the last run's output stays put when you switch tabs.
- Choose how the server runs — Project Settings ▸ Run now covers the build configuration (Debug or Release, for when you need to measure real performance rather than a debug build's), Vapor's
--env, and any extra arguments. The hostname and port stay Vaporware's, because .test routing and the rest of the UI are pinned to them. - Browse your data — Browse Data… on a PostgreSQL, MySQL or SQLite database opens its tables: pick one and its rows appear, capped at 200 with the table's true row count beside them so a screenful is never mistaken for the whole table. Type a statement to go further, copy a row or the whole result as tab-separated text, and watch the table list refresh when a statement changes the schema. Reads go through the engine's own client, so what you see is what the engine says. (Redis isn't tabular; it keeps its console.)
- Exported sites set the right content type on every file — a Vapor app generated from a Pixla Sites project now ships a middleware that fills in the
Content-Type for files Vapor would otherwise serve without one — web fonts (.ttf/.otf/.woff2) and .webmanifest — and adds the utf-8 charset to .svg, .js and the JSON or feed files something *reads* from Public/ (a game fetching its news feed, an RSS reader, an appcast), so the other end decodes them as UTF-8 instead of guessing. It only fills in a type that's missing, or completes one whose base it already agrees with, and never overrides a type Vapor set itself. - Re-sync flags generated middleware you haven't wired up — a middleware the exporter started emitting after a project was first generated lands on disk but is never registered in your own
configure.swift, so it looks installed and quietly does nothing. The re-sync preview now lists any such middleware and prints the exact app.middleware.use(…) lines to paste, since re-sync deliberately won't edit configure.swift for you. - Take the request log with you — the Logs tab can copy the log or save it as text or CSV, and exports exactly what your filters are showing. The Console section on Overview gained a copy button that takes *all* the output rather than the last 100 lines on screen — the build failure worth pasting into a bug report is usually the part that scrolled off the top.
- Acknowledgments in the app — Settings ▸ Legal ▸ Third-Party Notices (and *Notices* in the About panel) now opens the notices themselves rather than a web link, and lists the database engines actually downloaded to this Mac with their versions. Vaporware fetches those from their official sources at your direction and never redistributes them, so which ones exist is a property of your machine, not of a document.
- A pinned toolchain that isn't installed now says so — a project pinned to a Swift version you don't have builds with the system default instead. It still does, but the notice bar now tells you, and offers the toolchain picker.
- Update every project at once — Settings ▸ General ▸ Dependency Updates ▸ Update All Projects runs the update for each project that has one waiting, one at a time, naming the project it's on as it goes. It skips any project that's mid-build, and each one is snapshotted before its own update, so any of them can still be reverted individually from its Overview.
Fixed
- "Back Up All" told you more had been backed up than was — it reported the number of databases it *tried*, and the loop skips failures on purpose so one bad database can't stop the rest. A stopped server or a database that was never created counted as a success. It now says what actually worked ("Backed up 1 database; 2 couldn't be dumped"), and the message no longer leaks its own pluralisation markup.
vw sites resync <name> re-synced the wrong folder — a bare project name was tested against the filesystem first, and fileExists resolves relative to your working directory. So running it from a folder that happened to contain a same-named directory re-synced *that* instead of the registered project, and --overwrite-conflicts would have written into it. A reference is now a path only when it looks like one (contains /, or starts with . or ~); anything else is a project name, and a name that isn't registered but matches a nearby folder says so and suggests ./name.- A folder that was never generated from Pixla Sites says that — re-syncing one reported "the linked Pixla Sites document couldn't be found — it may have been moved or deleted", sending you to look for a file that never existed. A folder with no sync manifest and a linked project whose document has gone are different problems and now read differently.
- A failed backup no longer leaves a file that looks like one —
pg_dump and mysqldump write the destination file themselves, and both start writing before they discover a problem. So a dump that failed (a database that isn't there, a server that isn't running) left a partial file behind, listed among your backups and offered to Restore — which would have overwritten a live database with a fragment. A failed dump now takes its file with it, and a dump that "succeeded" while writing nothing is reported as the failure it is. - Quitting stops the engines even when you don't quit from the menu — an unhandled
SIGTERM killed the app outright, so the clean-shutdown path never ran and every database engine was left holding its port and data directory until the next launch reaped it. That is exactly the case that matters: logging out, or killing the app to hand ports back to another local server. A terminating signal now goes through the same quit the ⌘Q does. - Adding a database without linking it to a project actually creates it now — Add Database with *Link to project* switched off wrote a record and stopped: no
CREATE DATABASE, no user. The row then said Running (the engine was up; your database simply wasn't on it) and browsing it reported the engine's own *Unknown database*. It's provisioned like any other now, and stored only once the engine agrees it exists, so a failure leaves nothing behind. The background daemon has always done this correctly — the in-process path, which is what you get unless you turn the daemon on, never got the fix. - A database could be handed a port nothing was listening on — one engine hosts every database of its type, so its port belongs to the engine. Whether that engine was running, though, was answered out of whichever copy of the service you happened to ask, and only the copy that had started it said yes. Any other copy saw the port as busy, concluded another app had it, and permanently moved the cluster to the next port — after which every database provisioned carried a port nothing answered on, straight into your
.env. MySQL and Redis now record a pid file, as PostgreSQL always has, so the answer is the same whoever asks; a port held by one of Vaporware's own servers is never mistaken for a conflict; and starting a database uses the engine's port rather than the record's, so a record that had drifted is corrected the next time it starts. - Quitting stops the database engines again — for MySQL and Redis, quitting only stopped a server the exact copy of the service that was shutting down had itself started, so the engine usually kept running: holding its port and data directory until the next launch reaped it. That mattered most in the case it was meant to serve — quitting Vaporware to hand ports back to another local tool.
- The developer endpoint overrides are out of the shipping app — Settings ▸ Integrations offered two free-text fields that repoint where your Pixla API key is sent and where a Share tunnel (and its token, and your server's traffic) goes. Neither is any use unless you're running a hub or a relay on this Mac, and a field like that in a released app is one "paste this to fix publishing" away from handing your key to someone else. They now appear only in a development build — or in any build where one is already set, so a value can always be seen and cleared — and an override that's live outside a development build says so in the notice bar, naming the address, with Use Pixla to clear it. Whatever the setting, an override can no longer downgrade the connection:
https/wss are honoured, plain http/ws only to this machine, and anything else falls back to Pixla rather than carrying your key in the clear. - Every notice now offers the thing that fixes it — three didn't. "Pretty domains aren't being routed" and "The mail catcher didn't start" told you something was wrong and left you to find the control yourself; both now open the control that fixes them — the new Services destination for the proxy and
.test routing, Settings ▸ Mail for the mail port. And the read-only notice, shown when another Vaporware instance owns your services, said to quit that one *and reopen this one* — it now has Take Over, because quitting the other releases the lock and claiming it is exactly what a launch does. - A SQLite project points at one database file, absolutely — a provisioned SQLite database went into
.env as a *relative* path, which resolves against whatever directory the server happened to be launched from: the project folder under swift run, somewhere else under Xcode. So a project could quietly end up with two database files and your rows in whichever one the last launcher created. .env now carries an absolute path in SQLITEPATH (the form Fluent's .sqlite(.file(…)) takes — its driver has no URL initialiser, unlike Postgres and MySQL) alongside DATABASEURL, and a project whose file already lives in its own folder keeps pointing at that one, so nothing moves under you. - "Read Commands" shows it's working — reading a project's commands builds it, which can take minutes, and the row simply said "Asking the app what it can do…" with the button greyed out: indistinguishable from a hang. It now shows a spinner, a running clock, and says the first read compiles the project.
- A development build no longer writes the installed app's settings — Vaporware isolates a dev build's files (its own
config.json, databases, mail, certificates), and the window says so in a banner. Preferences were not files: they live in the preferences domain, which is keyed to the app's bundle identifier, and a dev build shares that with the installed app. So a dev run quietly rewrote the real app's API key, daemon mode, dismissed notices and update-check bookkeeping while the banner claimed isolation. A build using a scratch state directory now keeps preferences in its own suite, named after that directory — so a second VWSTATEDIR profile gets its own too. Release builds are untouched and keep every setting they have; test runs, which are scratch builds, can no longer write your preferences either. - "Check All Projects Now" now counts as a run — only the scheduled sweep recorded when it last ran, so pressing the button left Last run frozen at whatever the timer last did, however many times you pressed it. A manual check also didn't defer the scheduled one, so the timer could re-resolve every project over the network minutes after you'd just done it by hand. Any completed sweep now records itself, and the row updates the moment it finishes rather than when the window is reopened.
1.7.0
August 29, 2026
· build
Added
- Commit
Package.resolved after an update — an update rewrites a file that's normally tracked in git, and leaving it dirty is how it ends up committed later by accident inside something unrelated. The Versions section now says when Package.resolved has uncommitted changes, shows the message a commit would carry ("Update swift-log to 1.15.0", or a count and a listing when several moved), and commits it on one click. Turn on Settings ▸ General ▸ Dependency Updates ▸ Commit Package.resolved after updating to have it done for you. It commits that one file and nothing else — not even changes you'd already staged — and a revert is recorded as a revert rather than an update.
Fixed
- Update notices read as sentences again — a dependency-update notice could show its raw markup ("^[1 dependency update](inflect: true) for MyApp.") instead of the intended text. It now reads "1 dependency update for MyApp." or "3 dependency updates for MyApp.", with the wording agreeing with the count.
- Long-running commands can no longer hang the app — every command Vaporware shells out to (the dependency check, a revert's re-resolve, git,
lsof port scanning, launchctl, unmounting a downloaded engine, the Swift-version probe) waited for it in a way that could simply never return, wedging the caller for good. They now share one implementation that takes the exit from the process itself and drains output as it arrives, so a command producing more than 64K can't stall either. Every wait is bounded, and on expiry the command is *terminated* rather than left running — an abandoned swift package keeps SwiftPM's build lock and an abandoned git keeps index.lock, and either would break the next command you ran yourself. - A Vaporware commit no longer swallows work you'd already staged — after a Pixla Sites re-sync (or a
.gitignore fixup), Vaporware staged the files it had written and then committed, which committed the *index* — so anything you had git added but not yet committed was silently folded into its commit. It now commits by pathspec, which takes only the named files and ignores the index entirely. It also stops rather than improvising when git refuses a partial commit, such as during a merge.
1.6.0
August 29, 2026
· build
Added
- Gallery elements in exported sites — Pixla Sites galleries now carry through to the generated Vapor project instead of being dropped from the export.
Changed
- Navbar logo matches Pixla Sites — an exported site's navbar now shows its own logo image in place of the generated mark, and stands alone as a logo-only lockup when the site name is cleared.
1.5.0
August 27, 2026
· build
Added
- Versions on a project's Overview — a new section shows what the project actually builds against: the Swift toolchain in use (its own pin, or the system default with its version), the manifest's Swift tools version, and the resolved version of Vapor and every other dependency it declares, with transitive packages tucked behind a count you can expand. Versions come from
Package.resolved, so they're the builds you really get rather than the ranges Package.swift asks for, and they refresh after Update Packages or a build. A project whose dependencies have never been resolved says so instead of showing nothing. - Dependency update checks — the Versions section can ask SwiftPM what a
swift package update would actually change, and shows it inline: a package with a newer version available reads 4.122.1 → 4.123.0. Underneath, "4 updates available" expands to name every one of them — including updates to transitive packages, which would otherwise be counted but hidden inside the collapsed list — and an Update Packages button applies them without leaving the section. The answer comes from swift package update --dry-run, so every update listed is one that button can genuinely take — and, as the section says, it covers only what your Package.swift already allows; a new major version outside that range is a manifest edit, not an update. Checks are skipped while a project is building, since SwiftPM holds a lock on .build throughout. - Commit
Package.resolved after an update — an update rewrites a file that's normally tracked in git, and leaving it dirty is how it ends up committed later by accident inside something unrelated. The Versions section now says when Package.resolved has uncommitted changes, shows the message a commit would carry ("Update swift-log to 1.15.0", or a count and a listing when several moved), and commits it on one click. Turn on Settings ▸ General ▸ Dependency Updates ▸ Commit Package.resolved after updating to have it done for you. It commits that one file and nothing else — not even changes you'd already staged — and a revert is recorded as a revert rather than an update. - Vaporware checks your projects for dependency updates on its own — once a day by default, in the background, and tells you in the notice bar: "1 dependency update for MyApp." with a Review… button that takes you there. Projects with something waiting also carry a badge in the sidebar, so you can see at a glance which ones are behind. Only projects you've already built are checked (an unresolved project would mean resolving its whole graph over the network for something you haven't opened), never one that's mid-build, and a background check that *fails* — offline overnight, say — leaves the last good answer alone rather than replacing it with "couldn't check". Frequency lives in Settings ▸ General ▸ Dependency Updates, along with an off switch and Check All Projects Now. Dismissing a notice is keyed to the versions it was about, so silencing today's update doesn't bury next month's.
- Revert a package update — Vaporware saves the project's
Package.resolved before every update, so Revert Packages… can put the previous versions back and re-resolve against them. The last ten are kept per project, listed by date and package count, and reverting takes its own snapshot first so it too can be undone. It rolls back dependencies only — not source changes made to suit them — and forces a rebuild. Snapshots are removed with the project.
Fixed
- Long-running commands can no longer hang the app — every command Vaporware shells out to (the dependency check, a revert's re-resolve, git,
lsof port scanning, launchctl, unmounting a downloaded engine, the Swift-version probe) waited for it in a way that could simply never return, wedging the caller for good. They now share one implementation that takes the exit from the process itself and drains output as it arrives, so a command producing more than 64K can't stall either. Every wait is bounded, and on expiry the command is *terminated* rather than left running — an abandoned swift package keeps SwiftPM's build lock and an abandoned git keeps index.lock, and either would break the next command you ran yourself. - A Vaporware commit no longer swallows work you'd already staged — after a Pixla Sites re-sync (or a
.gitignore fixup), Vaporware staged the files it had written and then committed, which committed the *index* — so anything you had git added but not yet committed was silently folded into its commit. It now commits by pathspec, which takes only the named files and ignores the index entirely. It also stops rather than improvising when git refuses a partial commit, such as during a merge. - A folder is no longer mistaken for a Vapor project because of its name — Vaporware decided by looking for "vapor" anywhere in
Package.swift, so a plain SwiftPM package called NotAVaporApp, or one whose comments merely mention Vapor, was accepted and added. It now looks for real evidence first — a dependency's URL, path or registry id, a .product reference, a pin in Package.resolved (which also catches Vapor arriving through another package), or an import Vapor in the sources — and only then falls back to a text match, now blind to comments and to the project's own names. Forks, mirrors and local checkouts of Vapor are still recognised; the aim is that no real project is ever refused. - A rejected hand-off from another app now says so — when Pixla Editor's "Open in Vaporware" (or a
.pxs import) pointed at something Vaporware couldn't accept, the app came to the front and did nothing: the reason went to the system log and never reached you. It now appears in the notice bar — "Couldn't open PlainSwiftPackage." with the reason and a Show in Finder button — ahead of any long-running condition, so a reply to what you just did isn't queued behind a chronic one. Each attempt is its own notice, so dismissing one doesn't silence the next try. - Package updates now use the project's own Swift toolchain —
swift package update ran with whatever swift was on the system path, even for a project pinned to a different toolchain, so a project could resolve its dependencies with one compiler and build them with another. Every swift invocation for a project — run, update, update check, revert — now resolves the toolchain the same way.
1.4.1
August 26, 2026
· build
Fixed
- Accurate download sizes in exported Sites pages — when exporting a Pixla Sites project to Vapor, a
{filesize} token now reflects the actual size of a referenced ("keep on disk / always latest") download measured at export time, instead of the possibly-stale size recorded in the .pxs. Matters most for a rebuilt release binary (e.g. a .dmg hosted on the CDN), whose page now quotes the build actually being deployed. If the file is missing or not yet built, the token keeps the last known size rather than blanking.
1.4.0
August 26, 2026
· build
Added
- Notices — when something needs attention, it appears in a bar across the top of the window with a button that fixes it, and ‹ › arrows when more than one is waiting. Problems clear themselves once resolved rather than needing to be dismissed; the ones you *can* dismiss come back next launch if they're still true, and purely informational ones stay gone. Settings ▸ General ▸ Notices ▸ Show Again brings back anything you dismissed.
- Select several projects at once — ⌘-click or ⇧-click in the sidebar, across groups if you like, then move them to a group or remove them together. The removal sheet names every project it's about and keeps the same two opt-ins, so a batch is never a shortcut past the safeguards.
- Project groups and drag-to-arrange in the sidebar — drag a project to reorder it, or below a group's row to file it there; drag it back above every group to take it out, and drag a group to move it with its projects. Projects inside a group are indented under its folder. "New Group…" lives in the sidebar's + menu, and every project's context menu has "Move to Group". Groups collapse and stay collapsed, and deleting one keeps its projects — they return to the top list. The arrangement is stored separately from your project data, so it survives daemon mode instead of competing with
config.json for ownership.
Changed
- Links in captured mail open in your browser — clicking a link in a captured message opens it in your default browser instead of loading inside the reading pane, so following a password-reset or confirmation link takes you to your app while the email stays put. Only
http, https, and mailto links are followed.
Fixed
- A deleted project could come back — when the app couldn't reach the background daemon it started managing services itself *alongside* the still-running daemon, so two processes owned
config.json. Each saved its own copy of the project list over the other's, and a project deleted in one reappeared from the other. A running daemon is now always adopted before the app considers managing anything itself — including when this build has no daemon binary to launch — and the app refuses to write config.json at all unless it actually holds the owner lock. If another instance owns your services, this one says so in a banner and stays read-only instead of silently competing with it. vw runs commands again instead of launching the app — the installer asked the app bundle for an executable named vaporware, and on a case-insensitive disk that matched the app's own Vaporware binary, so the vw command on your PATH pointed at the GUI. It only ever links the real bundled CLI now.vaporware: too many levels of symbolic links — reinstalling could point the vaporware/vaporwared commands at themselves, because the install source was resolved *after* the old link had already been removed. The target is resolved first now, and an install that would point a command at itself is refused rather than written.- The sidebar's empty state fits a narrow sidebar — "No Projects", its explanation, and its two buttons were clipped at both edges in a narrow sidebar, which is the first thing a new user sees. The text wraps and the buttons stack instead.
- The sidebar can't be dragged arbitrarily wide — it now stops at a sensible maximum.
- The Mail inbox fits the window again — at smaller window sizes the message list pushed the reading pane off the right edge, clipping "Clear All" and the message itself, and squeezed the sidebar until the project names and the Mail row went blank. The two panes now share the space they actually have, and the divider between them can be dragged (and is remembered) instead of jumping whenever a message was selected or cleared.
- Searching the inbox no longer leaves a stale message on screen — a message the search filtered out of the list kept showing in the reading pane with nothing highlighted beside it. The selection now clears with the list.
- Switching between Mail and a project — selecting Mail no longer risks the sidebar clearing that selection out from under the inbox.
1.3.3
August 23, 2026
· build
Fixed
- Links in captured mail open in your browser — clicking a link in a captured message used to load the destination inside the message pane, turning the mail reader into a browser with no address bar and no way back (following a password-reset link replaced the email with the page). Links now hand off to your default browser and the message stays put.
Security
- Captured messages can't reach the filesystem or run scripts — a captured message is untrusted input, so only
http, https, and mailto links are handed off; other schemes such as file: and javascript: are refused rather than followed.
1.3.2
August 12, 2026
· build
Fixed
- Share tunnels stay up under heavy page loads — a page that fires many requests at once no longer corrupts and drops the tunnel; proxied responses are now sent strictly one at a time, so bursts of parallel traffic get through reliably.
- Large responses and uploads over Share — big response bodies and POST requests with large bodies now round-trip through a Share tunnel instead of killing the connection. Responses larger than 5 MB return a clear error instead of failing silently.
- Idle Share tunnels no longer drop — the tunnel now relies on the relay's keep-alive instead of a client-side ping that could clash with in-flight traffic, preventing dropped connections on idle or busy tunnels alike.
1.3.1
August 12, 2026
· build
Fixed
- Share tunnels no longer drop when idle — a shared project's temporary public URL stays connected during quiet periods instead of being closed after about a minute of inactivity.
1.3.0
August 11, 2026
· build
Added
- Share — give a running server a temporary public HTTPS URL (
https://<id>.preview.pixla.app) for client demos and webhook testing (Stripe callbacks, Sign in with Apple return URLs — things localhost/.test can't receive). One click on a running project's Overview; reuses your Pixla API key. Ephemeral — the link ends when you stop sharing, stop the server, or after a few hours.
1.2.6
August 11, 2026
· build
Changed
- Shared navbar and footer in exported sites — a legacy per-page navbar/footer is consolidated into a single shared partial at export time, so every page (including custom ones) renders from one source with no drift.
- Exported Turnstile widget matches the site theme — the generated Turnstile widget now tracks the site's own light/dark toggle rather than only the OS, re-rendering when a visitor flips the theme.
Fixed
- Exported forms connect to the hosted database — generated
configure.swift now uses a managed DATABASEURL (with TLS) when present and falls back to discrete DB* variables for local dev, so exported sites with forms reach the production Postgres instead of a hardcoded localhost/no-TLS config that never could. - No stray Turnstile widget on sites without it — the exporter no longer emits the Turnstile script and an empty, broken widget when no sitekey is configured, preserving the no-third-party-script promise for sites that don't use Turnstile.
1.2.5
August 6, 2026
· build
Changed
- Exported Vapor projects share one navbar and footer — the Pixla Sites → Vapor exporter now emits your site's global navbar and footer once as shared
nav.leaf/footer.leaf templates that every page pulls in, so custom pages the sync never regenerates (like a changelog) stay in step with the rest of the site instead of drifting.
Fixed
- Exported canonical-host middleware no longer collides with your own — the generated redirect middleware is now named
PixlaCanonicalHostMiddleware, so re-syncing a project can't overwrite a site's own hand-authored CanonicalHostMiddleware.
1.2.3
August 6, 2026
· build
Fixed
- Menubar no longer crashes the app — per-project server controls are now created when the project list changes rather than during view rendering, fixing an intermittent crash (SIGABRT) triggered from the menubar.
- Exported Vapor sites render pages that contain
# — page content such as anchor links, CSS hex colors, and id selectors was being parsed as Leaf tags and returned a 500 error; the exporter now escapes # in page content so those pages load correctly.
1.2.2
August 6, 2026
· build
Changed
- Exported Vapor apps redirect to your canonical domain — Pixla Sites projects exported to Vapor now include canonical-host middleware that 301-redirects direct hits on the
<app>.fly.dev and grey <sub>.pixla.app hostnames to your verified custom domain, so search engines index a single URL. Only safe (GET/HEAD) navigations are redirected, deep links are preserved, and it stays a no-op until a custom domain is active.
1.2.1
August 5, 2026
· build
Changed
- Direct CDN links for downloads on apex sites — Exporting a Pixla Sites project now emits a direct, isolated
cdn.pixla.app link for a Host-on-CDN download even when the site has no publish subdomain (a first-party apex site), resolving it under its own downloads/<slug>/ folder instead of falling back to a same-site path that relied on a redirect.
1.2.0
August 5, 2026
· build
Added
- Timing-based spam protection in exported forms — forms in projects exported from Pixla Sites now include a hidden timing field that records how long a visitor spent filling out the form. The generated server flags implausibly fast submissions as scripted bots, complementing the existing honeypot and Turnstile defenses. Fully offline-safe with a tiny inline script — no third-party or CDN dependency — and each form on a page times independently.
1.1.0
August 4, 2026
· build
Added
- Spam filtering for exported form submissions — Vapor apps generated from Pixla Sites projects now score public form submissions with a content-based heuristic (links, solicitation phrases, self-promotion, submit timing). Submissions that look like spam are quarantined: still stored and recoverable, but never trigger a notification.
- Cloudflare Turnstile on exported forms — generated forms can opt into bot verification, with the token checked server-side on submit. It activates only when Turnstile keys are configured, so sites without it ship no third-party script, and it fails open during a Cloudflare outage so an upstream problem can't take a form offline.
1.0.8
August 4, 2026
· build
Changed
- Namespaced CDN download links in exported Sites — when exporting a Pixla Sites project to a Vapor app, CDN-hosted download links now resolve to an isolated
cdn.pixla.app/downloads/<namespace>/<file> path (namespaced by the site's publish subdomain) instead of a shared root path, so downloads from different projects can't collide or be guessed. Projects without a publish subdomain fall back to a same-site link.
1.0.7
August 3, 2026
· build
Added
- CDN-hosted downloads in Pixla Sites export — when a download is flagged "Host on CDN" in Pixla Sites, exporting the site to a Vapor app now links buttons and CTAs straight to its
cdn.pixla.app URL and skips bundling the file into the project, so large per-release binaries stay out of the exported app.
1.0.6
August 2, 2026
· build
Fixed
- Pixla Sites import — opening or importing a Pixla Sites (
.pxs) project no longer fails with a date-format error; the project's .pxs file now decodes with the canonical ISO8601 date format.
1.0.5
August 2, 2026
· build
Changed
- Consistent Pixla Sites exports — Pixla Sites projects exported to Vapor now render through the same shared page renderers as Pixla Sites itself, so the generated HTML, styling, and dark-mode chrome stay in sync with the builder (correcting places where the exporter's output had drifted).
1.0.4
August 2, 2026
· build
Fixed
- Environment tab no longer carries "Undo" or import state across projects — Switching to a different project now clears any pending delete-undo and import notice, so an "Undo" can't act on the wrong project (which could re-insert the previous project's variable into the one now shown).
1.0.3
August 2, 2026
· build
Fixed
- Exported site buttons stay legible — Secondary and ghost buttons in Vapor projects exported from Pixla Sites now use accent-deep text with a themed hairline border, so they still read as buttons even when the light brand tint sits close to the page background.
1.0.2
July 31, 2026
· build
Fixed
- Environment variable editor no longer leaves your project dirty — when the env editor updates
.gitignore to keep .env and its credential backups out of git, it now commits that one change for you, so editing environment variables doesn't leave an uncommitted file behind. {filesize} now resolves for referenced downloads in exported sites — when exporting a Pixla Sites project to Vapor, a {filesize} placeholder next to a download link for a referenced file (such as a .dmg) now shows the correct size (e.g. 8.6 MB) instead of coming out empty.
1.0.1
July 31, 2026
· build
Fixed
- The env editor now keeps
.env and its timestamped backups out of git, so credential copies can't be accidentally committed.
1.0.0
July 31, 2026
· build
First release of Vaporware.
Added
- Local Vapor development environment — open or scaffold a Vapor project and run it with one click; no manual
swift run, Docker, or config. Each project gets a live status bar (Starting → Building → Running) with uptime and PID. - Per-project Swift toolchains — build each project with a chosen Swift version, recorded in a standard
.swift-version file. - Managed database engines — PostgreSQL, MySQL, Redis, and SQLite, run and monitored from the app with no Homebrew or Docker; each gets its own port to avoid clashes (e.g. Herd).
- One-step provisioning — create a database while scaffolding a project and have its credentials written straight into
.env, with credential editing, connection-string copying, and "Show Data in Finder". - Pretty
.test domains with automatic HTTPS — reach a project at myapp.test via a built-in DNS resolver and reverse proxy (no /etc/hosts edits), optionally over trusted local HTTPS. Offers to take over ports 80/443 when another tool holds them. - Environment variable editor — add/edit/remove, comment out lines, drag to reorder, flag duplicates, undo deletes, and import/export
.env files. - Real-time log viewer — stream requests and errors with method/path/status/duration, colour-coded and filterable.
- Built-in mail catcher — a local SMTP server (port 1025) that captures outgoing mail into an in-app inbox; view as HTML, text, or raw source, save attachments, search, and persist as
.eml. - Database backups — snapshot and restore PostgreSQL and MySQL on demand or on a schedule (6 hours / daily / weekly) with retention, plus restore from an external
.sql dump. - Quick actions — Open in Browser, Show in Finder, Open in Terminal, Open in Editor (Xcode, VS Code, Cursor, Zed, Sublime Text, Pixla Editor, or default), Update Packages, and Export as
.zip. Preferred-terminal picker (Terminal, iTerm, Warp, Ghostty) and per-database psql/mysql/redis-cli/sqlite3 consoles with credentials pre-wired. - Menubar utility and background operation — run from the menubar, keep services running with the window closed, Start All / Stop All, and optional run-at-login.
vw command-line tool and background daemon — script Vaporware (vw status --watch, doctor, and project/server/db/env/domain/dns/proxy groups) with --json output and shell completions; an optional vaporwared daemon keeps services running headless.- Publish to Pixla Hosting — one-click hand-off: pick a
yourname.pixla.app subdomain, server size, region, machine count, and an optional database, then watch the remote build stream live. Re-deploy and Reset & Retry for existing sites. - Pixla Sites → Vapor export & re-sync — turn a no-code Pixla Sites project into a runnable Vapor 4 app (pages → Leaf, forms → Fluent routes); "Re-sync Design" regenerates the design while leaving your server code untouched.
- Automatic updates and a first-launch tour with an offer to move into Applications.
- In-app help, feedback, and a one-click uninstaller that cleanly removes
.test routing, the local HTTPS certificate, CLI tools, and app data.