Vercel Pulse
Your Vercel deployments and Cloudflare analytics, live in the macOS menu bar.
Download for macOSWhat's new
Added
- Automatic updates, via Sparkle. The app checks daily, and Preferences gains an Updates section with a "Check Now" button and a toggle for automatic checks. Pinned project menus get a "Check for Updates…" item. This is the first version that can update itself — 1.5.0 and earlier have to be replaced by hand once.
LICENSE(proprietary) andREADME.md.
Fixed
- Stale status was shown as if it were current. A project kept its last known deployment while every poll failed, and the error was only ever surfaced for projects that had no deployment at all — so after a revoked token or a dropped network, the popover went on showing a confident green "Live" nobody had confirmed in an hour. Failing rows now carry a warning and a dimmed status, a "can't reach Vercel" banner appears when everything is failing, and pinned menu bar items show the error instead of hiding it behind the last known deployment.
Changed
- Accounts with more than 100 projects now load all of them — the project list follows Vercel's pagination cursor instead of stopping at the first page.
- Cloudflare zones are matched to projects by exact base domain. The old rule used a substring test, so a project called "app" claimed the zone "myapp.com", and whichever project sorted first won.
- The notification permission prompt now appears on the first deployment state change rather than at launch, where it had no context.
- Distribution is now a DMG, not a zip. Unzipping with Finder strips the stapled notarization ticket, which forces Gatekeeper to phone home on first launch. The DMG keeps the ticket and gives the normal drag-to-Applications install. The zip is now only the transport used to submit to Apple.
Internal
- The release pipeline was rebuilt around the nine-stage process from the Spelko handbook: sign inside-out → notarize + staple the app → build the DMG → sign + notarize + staple that too → Sparkle signature strictly after stapling → appcast → deploy the site last → verify with cache-busting polls.
scripts/sign-release.shsigns Sparkle's helpers plain and innermost-first and the app last with entitlements, never--deep, and finishes by running Sparkle'sAutoupdateto assert the kernel didn't kill it (exit 137). That failure mode breaks in-app updates silently and permanently for everyone on the affected version, and nothing else detects it.scripts/make-appcast.shrefuses to run on an unstapled DMG, since stapling changes the file size and alengthmeasured earlier makes Sparkle reject the download for every user.scripts/verify-release.shproves a published release is reachable, polling withCache-Control: no-cacheand checking an older DMG too.- A test suite (30 tests) covering the pure logic that has broken before: timestamp formatting, build duration, domain selection, zone matching and the stale-status rules.
swift test, andrelease.shgates on it. release.shautomates the whole release: checks, version bump, tests, signed and notarized build, appcast regeneration, commit and tag.- Both scripts prefer a stored notarytool keychain profile over environment variables, so the Apple app-specific password is entered once, ever.
- Sparkle is vendored in
Vendor/Sparkle/rather than fetched as a remote package; SwiftPM's artifact download hangs on the build machine. build-app.shembeds and signs the framework inside-out.--deepsigning, which it used before, mis-signs Sparkle's XPC services and embedded Updater.app in a way that only shows up when an update is attempted.- The update feed and DMGs are hosted on Vercel at
vercelpulse.loxo.hr, from the staticsite/directory.make-appcast.shalso rewrites the download page, so the page and the feed can't disagree about the current version. release.sheditsInfo.plistin place instead of withPlistBuddy Set, which rewrote the whole file and dropped every comment in it.