July 16, 2026
Building Career OS: a local-first desktop app for your career
Career OS is a local-first desktop app for running your work life from one place: a task board that pulls in the JIRA issues assigned to you, a GitHub activity and velocity view, and all the career stuff that usually rots in scattered docs, like a brag sheet, a promotion timeline, development goals, a vision board, a personal timeline, and 1:1 / feedback notes. It looks like Notion, but everything lives on your own machine.
This post is a walk through how it's actually built: why it's a desktop app instead of a web app, the one integration that was trickier than it looks (talking to SQLite from React), how the JIRA and GitHub connections stay read-only and secret-safe, and how it ships signed auto-updates. It's also open source now, so there's a section at the end on running it and contributing.
Why a desktop app, and why local-first
The data this app holds is exactly the data you don't want sitting in someone else's cloud: candid notes toward your promotion case, feedback from your manager, half-formed goals. So one constraint drove every other decision: your data never leaves your machine.
- Storage is local SQLite. Every task, win, and note lives in a single database file in your OS app-data directory. No account, no server, works fully offline.
- Secrets live in the OS keychain. JIRA and GitHub tokens are stored in the macOS Keychain from the Rust side, never in the database, and never in a config file you might accidentally commit.
That single choice, local-first, is why it's a Tauri desktop app and not a web app.
The stack, and why
Career OS is one Tauri application: a Rust core with a React front-end rendered in the system webview.
| Layer | Choice | Why |
|---|---|---|
| Shell | Tauri v2 (Rust) | Tiny native binaries with direct access to the keychain, filesystem, and notifications, driving a webview UI. |
| UI | React + Vite + TypeScript | Fast dev loop and one type vocabulary shared from the database models to the component props. |
| Components | Tailwind + shadcn/ui | A clean, Notion-ish surface without hand-rolling a design system, plus a ⌘K command palette. |
| Data | SQLite via tauri-plugin-sql, typed with Drizzle ORM | A real local database with a typed query builder on top. |
| Server state | TanStack Query | Caching and invalidation, even though the "server" is a file on disk. |
| Charts | Recharts | The GitHub contribution graph and the delivery-velocity charts. |
The trickiest piece: talking to SQLite from React
A Tauri app has no Node server at runtime, so the database can't be reached the usual way. SQLite lives in the Rust layer (through tauri-plugin-sql) and the React side talks to it across Tauri's JS↔Rust bridge.
I still wanted Drizzle's typed query builder on top of that. The bridge is Drizzle's sqlite-proxy driver, which lets you hand it your own async "just run this SQL" function. Wiring it to the plugin took one non-obvious adapter: the plugin hands back rows as objects, but the proxy driver expects positional arrays. Since SQLite returns columns in SELECT order and JavaScript preserves object key order, Object.values(row) reconstructs precisely the array Drizzle is looking for. One small function, and suddenly the whole app has a fully typed local database.
Schema changes flow the same direction: I define tables in Drizzle, run drizzle-kit generate to emit SQL, and embed those migrations in the Rust side so they apply automatically the next time the app launches.
Integrations that stay read-only and secret-safe
Two connections, both read-only, both built so a token can't leak:
- JIRA surfaces the issues assigned to you; GitHub surfaces your pull requests (authored, reviewed, and commented on), recent commits, and a contribution/velocity graph.
- Every API call is made from Rust, not the browser. The token is read straight from the keychain in the native layer and never touches the front-end, which also sidesteps CORS entirely.
- Results are cached locally, so the app is fully usable offline; a background sync refreshes them on launch and every fifteen minutes.
The best war story here was GitHub. My work account is an enterprise-managed user behind SAML SSO, and the very first sync returned zero PRs, with no error at all. It turns out GitHub returns a perfectly normal 200 OK with empty results when your token isn't authorized for the org, and quietly sets an X-GitHub-SSO header explaining why. Once the app surfaced that header, and I authorized the token and added the repo scope, everything showed up.
Keeping it snappy
A handful of things make it feel instant:
- Route-level code splitting, so the initial load doesn't ship the charting library until you open the Activity page, plus prefetch on hover over the sidebar links.
- Optimistic updates. Drag a task to Done and it moves immediately, instead of waiting on a database round-trip.
- Aggressive query caching. Because every write invalidates its own cache, local reads never need to re-run just because you navigated away and back.
- Indexes on the columns everything sorts by, so it stays fast as the data grows.
Shipping updates
Because it's a real installed app, it can update itself. A GitHub Actions workflow builds and cryptographically signs a release whenever I push a version tag; on launch the app checks GitHub Releases and offers a one-click "update & restart." The private signing key never leaves my machine; only the public key ships inside the app.
A note on building with AI
I'll be transparent, same as with my other projects: a large majority of this was built with an AI coding agent. The interesting work wasn't generating code, it was the architecture calls and the guardrails. Local-first from day one, tokens in the keychain, one typed path to the database, strictly read-only integrations, migrations that can't silently drift. Those are exactly the decisions that keep an AI-accelerated build from collapsing into a pile of disconnected snippets.
It's open source, come build
Career OS is public on GitHub, and contributions are genuinely welcome. It's a clean, modern Tauri + React codebase that's easy to get running:
- Clone it,
npm install, thennpm run tauri dev(you'll need Node and the Rust toolchain). - Good first PRs: JIRA sprint-velocity charts, more integrations (Linear, GitLab), a Windows/Linux build in CI, or extra themes.
On my own roadmap:
- Two-way JIRA: transition tickets and log work from inside the app, not just read them.
- A richer promo-packet export: a polished PDF that assembles your brag sheet, milestones, and goals for review season.
- Cross-platform releases: the build is macOS-first today; Windows and Linux are mostly a CI matrix away.
If you want to try it or contribute, it's at github.com/jadenbanze/career-os.