v7.8 — Portfolio Truth and a brain that never sleeps
For a year, "Swing Deck" was a tab in your browser. Type localhost:8001. Hope the Python server you started in Terminal was still alive. Don't quit Terminal or it dies. v7.8 ships next month and reframes that completely: Swing Deck is now a standalone Mac app with a brain that runs 24/7. Plus real Portfolio Truth — a broker_state daemon polls E*TRADE every 30 seconds and surfaces broker-vs-dashboard drift in real time. Plus six silent broker bugs caught and pinned. Release window: June 1, 2026, when the GitHub Actions quota refreshes. 16 commits stacked locally; this post is what's in them.
The framing shift
The product up to v7.7 had a structural problem nobody talked about: the "dashboard" and the "engine" were the same process. If you closed the browser tab, you closed your watchlist's view of itself — but if you closed Terminal too (or rebooted, or your laptop went to sleep), you also killed the audit cycle, the alert engine, the scanner, the broker reconciliation. The whole discipline machine.
For a tool whose whole pitch is "discipline that runs while you don't," that gap mattered. Pre-market scanner fires at 7am ET. Earnings drops at 4:30pm. VIX spikes at 9:32am. Macro print at 8:30am. If your "trading app" is a browser tab you closed last night, the alerts that would tell you about any of those never fire. The engine that you bought specifically because you wanted it watching while you weren't — it wasn't watching either.
v7.8 separates the two: the brain runs 24/7 as a system service; the window is something you open when you want to look.
1. Standalone Mac app (PWA install)
The dashboard now ships a proper PWA manifest. Safari menu → File → Add to Dock creates a real standalone app: own Dock icon, no URL bar, no tabs, native macOS traffic lights, system notifications attributed to "Swing Deck" instead of "Safari." Chrome's "Install" works the same way (menu → Cast, Save, Share → Install Swing Deck). Both use the same WKWebView / Blink engines the browser always uses — there's no native binary, just a manifest the browser respects to drop the chrome.
Why this matters more than it sounds: the cognitive friction of "is this a browser tab or an app?" was higher than I realized. Cmd+W behaves like an app now (closes the window, no "this will close N tabs" prompt). Cmd+T does nothing (there are no tabs). Spotlight finds it. Cmd+Tab treats it as its own app. The Dock badge can show pending-confirmation counts. None of that is a feature you'd put on a marketing page, but together they remove a kind of background noise — the constant low-grade reminder that this is "a website I happen to use a lot" rather than "an app I work with."
What we tried first: Tauri. Shipped a full scaffold attauri-app/, then spent four patch releases chasing browser-API divergences —window.openblocked by WKWebView,<a>navigation routed differently,window.confirm()silently returningfalse. The fifth divergence (confirm()) would have meant refactoring 28 dashboard buttons that gate on it. We pivoted to Safari Add-to-Dock. The Tauri scaffold stays in the repo as a future option for customer distribution once the dashboard is wrapper-agnostic. Honest scope — what we tried, what worked, what we'll come back to. A separate post is queued on that pivot.
2. 24/7 brain (macOS LaunchAgent)
The real shift. A single command:
./packaging/launchd/install.sh
...writes a user-scope macOS LaunchAgent that runs control_server.py (which auto-spawns the audit watcher and index fetcher as managed subprocesses) 24/7 starting at every login, with auto-respawn on crash. The installer detects your Python (framework 3.14 preferred, then Homebrew, then system), substitutes paths into a checked-in plist template via sed, validates with plutil -lint, then launchctl loads it and smoke-checks that port :8001 is bound within 10 seconds.
The one environment variable that matters: AUTO_SHUTDOWN_ON_CLOSE=false. The dashboard has always had a watchdog that exits the server when no dashboard heartbeat is detected for 5 minutes — that watchdog made sense when "running Swing Deck" meant "I am at my desk looking at it." It actively breaks the 24/7 use case. The LaunchAgent disables it.
The result: alerts fire pre-market at 4am AZ regardless of whether I have a window open. The audit cycle runs every ~2 minutes through overnight. The framework's state is current when I sit down in the morning, not "let me wait while it cold-starts." The viewing surface — the dashboard tab — became optional. The discipline engine is always-on.
Uninstall is the symmetric one-liner: ./packaging/launchd/uninstall.sh. Logs in ~/Library/Logs/SwingDeck/ stay in place as operator trail. The plist sets KeepAlive.Crashed=true but SuccessfulExit=false — respawn on crash, but SIGTERM still stops cleanly so you can launchctl unload when you want to.
3. Portfolio Truth (4 phases)
The companion shift. The dashboard's understanding of broker state used to rely on whatever was in audit_output.json, which itself was a snapshot of what the framework recommended — not necessarily what the user actually held at the broker. v7.8 restructures that completely:
- Phase 1 — broker_state daemon. A new
broker_state_fetcher.pypolls E*TRADE every 30 seconds and writesbroker_snapshot.jsonas the single source of broker truth. Account-picker heuristic prefers your INDIVIDUAL account over IRA by checking symbol overlap withpositions.json— catches the bug where the daemon was polling the wrong account and silently surfacing the wrong NAV. The Reconcile pill in the hero strip shows broker-vs-dashboard drift live: positions, cash, open orders. Severity-color-coded (green / amber / red). Click it for the diff breakdown plus accept/reject per row. - Phase 2 — transaction auto-routing. A new
transactions_router.pyclassifies BUY/SELL/DIV/INT/FEE into the right ledger automatically. DEPOSIT/WITHDRAW/SPLIT queue for confirmation and auto-adjust the drawdown baseline viaportfolio_rebalancer.adjust_drawdown_baseline(). Pre-fix, the drawdown daemon would capture inflated peaks during the user's "shares-typed-but-cash-not-yet-deducted" edit window — phantom 7%-drop alerts on a portfolio that didn't exist. Fixed with a three-layer architecture: atomic input on the dashboard side, mid-transition jump guard on the daemon side, manual override endpoint as the escape hatch. - Phase 3 — Open Orders strip. Built, then removed in v7.8.38 as redundant with the existing PENDING @ ETRADE widget. Worth mentioning because it's how a small dev shop should work: ship the experiment, look at it for a day, remove what's redundant. No vestigial UI for the sake of "but we built it."
- Phase 4 — paranoid-mode order verify. A new
order_verify_tracker.pyschedules T+45s / T+90s / T+120s verify checks after everyplace_okevent. Catches ghosted orders — the scenario where the broker accepts a place but the order doesn't actually appear in their open-orders book a few seconds later — inside two minutes instead of the next time you happen to look at the dashboard.
Local-first stays local-first. The broker_state daemon is your machine talking to your broker, never through us. We don't proxy. We don't see your positions. We don't see your fills. The dashboard you run is the same dashboard we ship; the broker integration runs in your Python process on your laptop.
4. Six silent broker bugs caught and pinned
The most uncomfortable kind of bug is the one that fails quietly enough that nobody notices. v7.8 caught six of them — not because they were urgent, but because the user reported a symptom and we traced. A full post on the deepest two of these is queued; here's the summary:
- v7.8.42 — FEED DEGRADED false alarm. The data-feed health chip was tripping into amber/red on calls that weren't actually failing. Macro symbols (^VIX, CL=F) were being sent to equity-tier providers (Tradier, Polygon Stock) that structurally can't quote indices or futures. Every macro fetch counted as a failed call. Fix: gate equity-tier providers behind
_is_index_futures_forex(). - v7.8.43 — compute_daily history guard. A degraded-feed morning let a 0-1 row history frame reach
compute_daily, which crashed the entire audit cycle withIndexError. Now self-guards via_MIN_DAILY_BARS=30. Skips that ticker, completes the cycle. - v7.8.44 — audit cadence floor.
AUTO_REFRESH_MIN=0meant cycles ran back-to-back hammering the data tiers — ~556 cycles/day. Bumped to a 1-minute floor (~2.75-min effective cadence, ~⅓ the load). - v7.8.45 — portfolio-loop per-ticker try/except. The candidates loop already isolated per-ticker failures; the portfolio loop didn't. One bad ticker could abort the whole cycle. Now mirrors candidates loop with full traceback logging.
- v7.8.73 — E*TRADE route deprecation. Server log had
Broker reconcile failed: E*Trade GET /v1/accounts/list → 404every 5 minutes for weeks. We'd seen it dozens of times and assumed "transient API issue." Live probe showed the 404 was actually a Tomcat HTML response — the route is just gone from prod. The.jsonsuffix variant works fine. - v7.8.75 — OAuth stale-pool 404. User reported Connect E*Trade button stopped working. The same code worked from a fresh
python3REPL. Long-running processes drift into a connection-pool state where the gateway rejects them — urllib3 reuses sockets, keep-alive does weird things over many hours, and E*TRADE's WAF starts returning 404 (not 401, not 429 — 404). Fix:Connection: closeon the OAuth1Session.
All six now have pin tests + bug-class lessons in the project memory so the next dev (which is also me, three weeks from now) sees the pattern. The pattern in all six is the same: error logs that look like noise until you trace them. The work isn't catching the bug when it screams. It's noticing when the system stops screaming about something it should be screaming about.
What didn't make it
A few things in flight that didn't ship in v7.8 and that the roadmap is honest about:
- Tauri-based customer distribution. Scaffold built. Then four browser-API divergences in succession (window.open, navigation, confirm dialogs, scope-ACL permission system). Pivoted to Safari Add-to-Dock for the personal-use path. Tauri stays committed for a future customer-distribution revisit once the dashboard is wrapper-agnostic.
- E*TRADE live order placement (orders:place scope). Preview / edit / cancel verified end-to-end. Live place still gated by Morgan Stanley's separate scope decision. Application submitted; typical turnaround 2-4 weeks.
- Light mode for the dashboard. Token-based color system already factored; the work is auditing every hardcoded color. Tracked in /roadmap as in-active-build.
- Customer-facing v7.x DMG. The existing
packaging/SwingDeck.appbash-launcher bundle is at v4.3.0 — ~80 versions stale. The plan is to modernize it to wrap v7.8 + the new LaunchAgent, but kept separate from the v7.8 release so the customer-distribution flow can be tested independently.
How to try it
Until the June 1 release window: clone from source.
git clone https://github.com/pinoy81/swing-audit
cd swing-audit
python3 control_server.py
Then, in Safari:
- Open
http://localhost:8001/ - File → Add to Dock
- Click the new Dock icon — standalone window opens, no URL bar, no tabs
For 24/7 daemons (optional but recommended if you trade pre-market): ./packaging/launchd/install.sh. Uninstall any time via ./packaging/launchd/uninstall.sh.
On June 1, the same is available via the normal .dmg install at swing-deck.com/download.
The pitch
Discipline-as-a-product has a structural problem: you have to be using the product for it to work. v7.8's whole point is to remove the "have to be using" dependency — the engine runs whether you're watching or not. The window is optional. The brain isn't.
Combined with the broker reconciliation work, the picture for May 2026 is: a local-first dashboard whose broker truth is real-time and whose discipline engine is always-on. Same trader, same framework, same local-only data — just no longer dependent on you remembering to start the server every morning.
Claim a Founding Slot — $14.50/mo
Questions, feature requests, or just want to see how the LaunchAgent flow works on your setup before you commit? Reply to contact@swing-deck.com. Real human, usually under 24h.
Disclosure: Swing Deck is built and operated by one person. The product is local-first; positions, broker tokens, and journal entries never leave your machine. AI features use your own API keys (BYOK). We don't proxy your data through our servers. Pricing as of 2026-05-16. Past performance is not indicative of future results. Nothing in this post is investment advice; it's a description of what software does. Source code for the framework + dashboard is available at github.com/pinoy81/swing-audit.