-
Notifications
You must be signed in to change notification settings - Fork 0
The Desktop App
The localhost dashboard is great for one project. The moment you have five, "open a tab, find the port, remember which is which" becomes the friction the tool was supposed to remove. The desktop app (dreamcontext-beta, macOS, Tauri 2) is the answer — and the design decision that shaped everything is that it is not a rewrite. It wraps the exact same React dashboard and Node server the dashboard command already serves.
The Rust shell launches the bundled (or, preferentially, the globally-installed) CLI in --launcher mode — the dashboard server booted with no default vault. The launcher page lists every registered vault; clicking one opens a new native window pointed at the same server with a ?vault=<name> parameter. The SPA reads that, and injects an X-Dreamcontext-Vault header on every API call. The server resolves a per-request context root from that header through a strict, name-only resolver that rejects anything path-shaped or unknown with a 400 — a deliberate confused-deputy guard, so a window can never be tricked into reading a directory outside the registry. The upshot: multi-vault is just multi-window over one shared Node process. No in-window switcher, no cache thrash.
A quiz-style wizard lets you create a brand-new project (native macOS folder picker), initialize an existing folder, or clone one from GitHub — all without ever touching a shell. Vault-agnostic server routes (/api/launcher/scaffold, /detect, /defaults, plus /github/repos and /clone for the clone path) back it. The load-bearing decision: the server spawns the bundled CLI in a child process (execFile, no shell, arg array) with cwd set to the target directory — the long-lived launcher never mutates its own working directory. The clone path is the same posture: after a device-flow/PAT GitHub sign-in you search your repos, and the chosen one is cloned as a cancelable background job — git clone --progress spawned with a hardened argv, stderr streamed as live "Receiving objects: N%" progress, SIGTERM-cancelable (git cleans the partial dest), with a concurrent-dest guard and a dest-conflict pre-check that offers to open the existing project instead of dead-ending. The wizard is deterministic and ships no LLM; quiz answers map 1:1 to init's flags (a cloned repo's description and detected stack prefill the quiz). The rich, code-scanning enrichment is still Claude Code's job — the success screen hands you a prompt to paste.
The dashboard's older Sleepy "Search & Ask" page was replaced by a full Agent surface: a multi-session, split-pane terminal that drives a real Claude Code session inside any vault. Each pane carries its own tab bar, and ⌘D drag-to-split, ⌘T/⌘W, and a minimize-to-corner dock manage the layout. The hard-won detail: each session lives in a detached DOM node re-parented on view changes rather than unmounted, so the PTY/xterm/WebSocket never remount and a long-running claude process survives every navigation. A ⌘K command palette searches the corpus over /api/recall (BM25, with an optional Haiku toggle) and jumps anywhere; dropping an image onto a pane injects it straight into the vault. Because a Finder-launched app may lack the Claude CLI or node-pty, a first-run prerequisite installer reports what's missing (/api/agent/install) and installs it in one click — with a per-theme ANSI ramp tuned to a 4.5 contrast ratio so the terminal stays readable in both light and dark mode. The dock was then made screen-true: status is classified from the visible buffer tail rather than byte-flow — a ❯-numbered option list reads as asking, a lingering "esc to interrupt" holds working — which ends the ready-flap during silent tool calls. Six kinds rank by urgency, and the state that matters most, asking, is the one the UI shouts: the tile shakes, wears a "?" bubble, gets a synthesized two-note chime, and jumps the queue; the anchor mirrors the most urgent row. Status dots were removed entirely — the figure is the status (a per-state Sleepy face on agent tiles, a blinking prompt glyph on shells), with magenta reserved exclusively for "needs you". Guard: a sleep/brain-resolve re-issue never injects a prompt into an asking session.
Sleepy is a global-hotkey quick-capture companion: a transparent, always-on-top notch panel (true transparency via macOSPrivateApi) with an animated mascot whose mood tracks your sleep debt. It captures into any registered vault and offers three modes — Learn (save to memory, then enrich), Ask (one-shot project Q&A, nothing saved), and Sleep (run a full consolidation from the notch). A few hard-won details are worth recording because they are the kind of thing you only learn by shipping:
-
The mascot is an animated WebP, not a
<video>. WKWebView hard-blocks<video>autoplay and Tauri exposes no override; an<img>with an animated WebP autoplays unconditionally. -
The capture write is in-process, not a child CLI call. Because a Finder-launched
.appdoesn't inherit your interactive-shell PATH, a spawneddreamcontext memory remembercould resolve a stale global or none at all. Since the server is dreamcontext, it writes the CHANGELOG entry directly — failure-proof. -
Enrichment spawns
claudethrough an interactive login shell (-ilc). Tools added to PATH in~/.zshrcare invisible to a non-interactive shell, so a Finder-launched app got "command not found: claude" until the interactive-login flag was used.
The app updates continuously without an Apple Developer ID or the Tauri updater, because the entire delivery path is CLI/curl-driven — which never sets the com.apple.quarantine bit, so Gatekeeper's notarization check never fires (ad-hoc signing already satisfies Apple Silicon's must-be-signed rule). Two mechanisms make it work:
-
The thin-shell pivot. The app prefers your globally-installed, auto-upgrading CLI over its bundled copy. Since that global CLI carries the whole server/dashboard/route stack, ~95% of changes ride the normal
dreamcontext upgradewith no app rebuild; the bundled copy is only a first-run fallback. -
dreamcontext app install | update | status. Installs to~/Applications(no admin) via an atomic same-volume swap, tracks the installed version in~/.dreamcontext/app.json, and can fire a detached background update from the same ≤1×/24h hook tick the version nudge uses (opt out withDREAMCONTEXT_APP_AUTO_UPDATE=0). The security catch: a downloaded artifact requires a matching per-asset.sha256or the install refuses — ad-hoc signing proves integrity in transit at best, never origin, so it is not treated as a substitute.
A GitHub Actions workflow (desktop-release.yml) builds, ad-hoc-signs, packages the .app.tar.gz + checksum, and publishes to a GitHub Release on every v* tag — E2E-verified end to end at v0.8.1. A companion ci.yml runs the unit + integration suite (vitest) on every push to main and every PR, so the test suite finally gates the line — the gap that previously let a recall regression slip through a tag-only build.
The desktop app is a working beta: not Apple-signed/notarized (local install only, first launch may need a right-click → Open), macOS only for now, and the Sleepy companion's visual design is shipped but not yet formally user-accepted.
- Why It Exists
- The Problem in Depth
- The Architecture
- The Hook Mechanism
- The Sleep Cycle
- Neuroscience-Inspired Memory
- The Dashboard
- Project task overrides
- Council Debates
- Memory Recall (BM25 over the curated corpus)
- Lab (Insights)
- Automations
- Federation
- Brain Cloud Sync
- Linked Repos
- Obsidian Integration
- CLI Design
- Install & Update
- The Desktop App
- Design Tradeoffs
- What Comes Next