Skip to content

Feat/tauri desktop v2 - #245

Merged
itsmeakhil merged 21 commits into
mainfrom
feat/tauri-desktop-v2
Jul 11, 2026
Merged

Feat/tauri desktop v2#245
itsmeakhil merged 21 commits into
mainfrom
feat/tauri-desktop-v2

Conversation

@itsmeakhil

Copy link
Copy Markdown
Collaborator

No description provided.

itsmeakhil and others added 21 commits July 9, 2026 22:51
Old effort archived at tag archive/tauri-desktop-2026-07.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Exclusion build script moves API routes, middleware, and dynamic
marketing segments aside, builds with TAURI_BUILD=1, restores
failure-safe. Gates cookies()/headers() in app layout and i18n
request config behind TAURI_BUILD. Produces apps/web/out/ with
all 57 tool pages. Web build path unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
apps/desktop: Tauri v2 scaffold with single local_api(method,path,body)
command dispatching to a Rust router that mirrors FastAPI /api/v1/*
contracts. SQLCipher DB keyed by a Keychain device key; generic
encrypted-envelope entries schema with sync metadata (dirty flag,
tombstones, last_synced_at). Local handlers: master-vault, backup
codes, synthetic personal workspace; loud 501 stubs for unmapped
routes. 5 Rust router tests.

apps/web: desktop seam in lib/desktop/ (isDesktop guard, invoke
bridge, apiFetch) with branches at the backend-auth choke points
(proxyJsonAuthed, backendFetch, session helpers) and a synthetic
local user in useAuth. All branches compile out of web bundles via
NEXT_PUBLIC_TAURI env guard.

Verified: tauri dev boots, webview loads, local_api round-trips
(user-preferences 501 logged as expected — Phase 3 route).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rust handlers mirroring FastAPI contracts for: password-manager,
api-keys, environment-manager (envelope entries), sql/redis/nosql
connection vaults, code-snippets, bookmarks + folders (snapshot,
move, import, clear-all), notes (ISO timestamps, recursive delete),
tasks + projects (wrapped list, filters, stats, export/import),
api-client (collections with items:apply-delta tree ops,
environments, history, workspaces, public-mocks), user-preferences
(singleton with backend-synced defaults, tool-usage, nosql query
history). Remote-only prefixes (url-shortener, s3-drive, game-scores,
dns-lookup) rejected with 503 until the Phase 4 remote bridge.

Query strings now flow through the bridge to handlers. 15 router
tests pass; tauri dev boots with zero unmapped-route hits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gating

Rust remote_api command (reqwest + persistent cookie jar serialized
into SQLCipher kv) calls FastAPI directly: no CORS, HttpOnly JWT
cookie flow unchanged, session survives restarts. clear_remote_session
wipes the jar on sign-out.

Desktop data routing (lib/desktop/router.ts): remote-only tools
(url-shortener, s3-drive, game-scores, dns-lookup) always remote with
a sign-in gate; workspaces API merges the local personal workspace
with remote orgs/workspaces when signed in; a non-local active
workspace routes all data calls remote (shared workspaces stay
cloud-only, DEK crypto unchanged in the webview); everything else
stays local.

Cloud sign-in via system browser (OAuth popups don't work in
WKWebView): web login page with ?desktop=1 mints a Firebase custom
token (new POST /api/v1/auth/desktop-token, same pattern as passkey)
and hands it back through the mydevtools:// deep link; the app signs
in with signInWithCustomToken and runs the normal session exchange
through the Rust jar.

DesktopOnlineGate wraps s3-drive, url-shortener, dns-lookup,
email-validator; notes image upload requires cloud sign-in; useAuth
prefers a real Firebase user over the synthetic local user.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Internal /desktop/sync/* Rust endpoints expose raw row state (dirty
rows, tombstones) and resolution actions: mark-synced (with server-id
re-key; synced tombstones hard-deleted), apply-remote (clean upsert),
remove-local. Toggling sync off clears last_synced_at + sync_state so
re-enabling does a fresh additive merge instead of inferring remote
deletions.

TS engine (lib/desktop/sync-engine.ts) reconciles the local personal
workspace against the user's remote personal workspace: push dirty
first (tombstone→DELETE, new→POST+re-key, else PATCH; 404-on-update
recreates, 409-on-create updates), then pull with LWW on updatedAt —
dirty local rows never overwritten; clean+synced rows absent remotely
are deleted locally. 13 tool adapters (envelope vaults, DB
connections, snippets, bookmarks+folders, notes, tasks+projects,
api-client collections/environments/history).

Runs on session events, network regain, 5-min interval, and debounced
5s after local writes; rounds skip while a shared workspace is active
(workspace cookie would misroute). Settings page gets a desktop-only
Cloud Sync card (sign-in, toggle, sync-now). 16 Rust tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
/api/sql-client/* (tokio-postgres text protocol + mysql_async — SQL
splitter ported from lib/sql-split.ts with quote/comment/dollar-quote
awareness), /api/nosql/* (official mongodb driver, clients cached per
connection string, node-compatible JSON serialization: ObjectId→hex,
Date→ISO, recursive _id conversion, aggregation stage blocklist), and
/api/redis-commander/* (redis-rs, per-request connections like the web
routes; execute/keys/key CRUD/copy/rename/bulk-delete/flush/export/
import/info/clients/slowlog/scanner/json/search/streams/timeseries/
pubsub-publish; dangerous-command blocklist kept). Localhost hosts
allowed — local databases are the point of the desktop app. MONITOR
and pub/sub subscribe (SSE on web) return 501, deferred to v1.1.

Frontend: 46 raw fetch() call sites across 20 sql-client /
nosql-explorer / redis-commander files swept to apiFetch (pass-through
fetch on web); live-DB paths always route to the local Rust drivers
regardless of active workspace.

Verified: 18 unit tests + mongo/redis integration roundtrips against
local mongod:27017 and redis:6379 (cargo test -- --ignored).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rust http_request mirrors the /api/proxy envelope exactly (status,
statusText, headers, setCookies, redirectChain, body, isBase64, time,
size): manual redirect follower with RFC 7231 301/302/303→GET
semantics, multipart form-data rebuild per hop, textual-vs-base64
body detection. Desktop allows localhost/private targets (user's own
machine); cloud metadata endpoints stay blocked. http_request_stream
relays SSE chunks over a tauri Channel with cancel support; sse-client
gets a desktop branch feeding the same event parser.

apiFetch routes /api/proxy → http_request and returns clear 501s for
proxy-grpc/ntlm/spnego/mock (v1.1). 9 more proxy call sites swept
(api-client, recorder, fuzz, graphql-introspect, oauth2, scim, runner,
perf runner, grpc-web).

gitignore-generator works fully offline: all 569 toptal templates
bundled into public/gitignore-templates.json (459KB), desktop branch
reads the bundle instead of the proxy route.

Verified: httpbin parity test (GET headers/args, POST, 302-flips-POST-
to-GET with redirectChain, binary→base64, metadata 403).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add tauri-plugin-window-state (window size/position persist). Fix
beforeBuildCommand path (runs from apps/desktop). build:desktop now
bundles the .app then wraps it with scripts/make-dmg.sh (hdiutil) —
Tauri's bundle_dmg.sh drives Finder via AppleScript and flakes in
restricted sessions; hdiutil is deterministic and needs no Finder.
Adds apps/desktop/README.md (architecture, dev workflow, Gatekeeper
right-click-open, regression matrix, v1.1 deferrals).

Verified: full release build → 25MB unsigned MyDevTools.dmg; the
built .app launches, creates the SQLCipher DB (Keychain device key),
and the DB is unreadable by plain sqlite3 (encryption confirmed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Desktop now boots to the login screen instead of the synthetic-local
dashboard. Root / gate: on desktop, probe the cloud session (remote_api
→ backend) and route to /dashboard if authed, else /login — the
marketing landing never renders in the app.

New DesktopLogin panel (rendered by LoginForm when isDesktop, since
OAuth popups don't work in WKWebView): 'Sign in with your browser'
opens the system browser at <webview-origin>/login?desktop=1 (dev) or
the configured site (packaged); 'Continue without signing in' enters
with the local vault. desktopWebBase() reuses the webview origin in
dev so the minted token validates against the same backend.

On the browser handoff, the deep-link handler establishes the session
and fires mydevtools:desktop-authed → DesktopInit routes to /dashboard
via Next (static-export paths resolve). LoginRedirectIfAuthed ignores
the synthetic desktop-local user so the sign-in screen isn't bypassed.

Verified via instrumented boot: desktop webview redirects to /login,
remote_api probes backend :8006 session/check (401→refresh→stay on
login), local_api serves user-preferences — desktop path confirmed
active.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Return leg was dead in dev: macOS routes mydevtools:// only to a
bundled .app (Info.plist), never the bare 'tauri dev' binary, so the
browser callback never reached the running app.

Switch to the native-app loopback pattern: await_browser_auth binds an
ephemeral 127.0.0.1 port, reports it to the frontend over a Channel,
and the browser redirects the minted token to
http://127.0.0.1:<port>/callback?token=... The Rust server returns a
'Signed in' page and resolves with the token — works identically in
dev and packaged, no URL-scheme registration. startCloudSignIn now
awaits the full round-trip, then establishes the session and routes to
/dashboard. mydevtools:// deep link kept as a secondary path.

Also hide the login page's 'Back to home' link on desktop (no
marketing site to return to) via HideOnDesktop.

Tests: loopback_callback_roundtrip (favicon probe ignored, real
callback returns token + Signed-in page) + token parsing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign-in is now the only path on the desktop login screen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The browser callback never fired when the system browser already had a
logged-in session: LoginRedirectIfAuthed bounced the
/login?desktop=1&cb=<port> handoff page straight to /dashboard, so the
token was never minted and the loopback server waited forever.

Extract the handoff into lib/desktop-handoff.ts and run it for BOTH the
just-authed (login-form) and already-authed (LoginRedirectIfAuthed)
paths — an existing session now mints the token and returns to the app
instead of redirecting to the dashboard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The already-authed handoff 401'd: the browser's Firebase client session
outlives the backend JWT cookie, and /auth/desktop-token requires the
cookie. Call ensureBackendSession(user) before handoffDesktopToken so a
stale cookie is re-exchanged first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A stalled handoff no longer locks the button for the 5-min timeout —
clicking again starts a fresh browser handoff.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Desktop sends long_lived=true on /auth/session; backend issues a
60-day refresh cookie (LONG_LIVED_REFRESH_TOKEN_EXPIRE_DAYS, default
60) instead of the web REFRESH_TOKEN_EXPIRE_DAYS, and persists a
refresh_long_lived flag on the user so /refresh preserves the 60-day
TTL on every rotation. Decoupled from the web session length.

The desktop cookie jar (persisted in SQLCipher) keeps the refresh
cookie across relaunches, so a returning user stays signed in for up
to 60 days of inactivity — sliding, since each refresh resets the TTL.

Verified: attach_auth_cookies(refresh_days=60) emits Max-Age=5184000.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… sync

Phase A — offline-first entry: desktop launch now always routes to
/dashboard (the local master-password gate is the real security
boundary); the cloud session probe is fire-and-forget and never routes.
Fixes the /login dead-end offline and stops kicking out signed-in users
who lose internet. /login stays reachable for the optional browser
sign-in.

Phase B — stop third-party leaks that bypassed the local store; route
through the Rust proxy (http_request), online-only, no data upload:
- proxyGet() helper + useFaviconSrc hook + FaviconImg component;
  bookmark + password-entry favicons fetch via proxy → data URL, letter
  avatar fallback offline (no more Google/DuckDuckGo hits from webview).
- HIBP breach check routes through proxy (k-anonymity prefix only);
  button disabled offline.
- Notes image upload blocked unless Cloud Sync is on + signed in.
- OpenAPI-by-URL import routes through proxy, skipped offline.

Phase C — preferences sync: syncPreferences() in the sync round
(container-level LWW on updatedAt) syncs theme/enabledTools/favorites/
pinned/toolStats to cloud when sync is on. Unconfigured local prefs use
updatedAt=0 so the cloud copy wins until the user changes one.

All desktop-only via isDesktop(); web bundle unaffected. 22 Rust tests
pass; export build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mydevtools-tech Ready Ready Preview, Comment Jul 11, 2026 1:45pm

@itsmeakhil
itsmeakhil merged commit 3b24875 into main Jul 11, 2026
2 checks passed
@itsmeakhil
itsmeakhil deleted the feat/tauri-desktop-v2 branch August 12, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant