Files
thoughtsync/desktop
bvandeusenandClaude Opus 5 c40263967d
Desktop (Tauri) / Tauri desktop (Linux) (push) Failing after 1m31s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 1m47s
desktop: render synced attachments instead of broken images (task 2114)
A synced note carried the SERVER's relative attachment path
(/api/notes/<id>/attachments/<aid>). In the webview that resolves against the
app origin and 404s, so every synced image rendered broken even though the
bytes were already on disk from M10.7d. The absolute server URL wouldn't have
worked either: that route wants a bearer token the webview never sends, and it
would put an offline app on the network to show a file it already has.

The bytes now come off disk over a custom URI scheme, served straight from the
content-addressed blob store. The webview caches and range-requests them like
any other resource — which a data: URI would have thrown away — and the URL is
immutable-cacheable because a content address can never describe different
bytes.

Two things worth knowing about the shape of this:

The URL is rewritten in `load_attachments`, the single place the desktop
builds an attachment for the UI. NoteCard and NoteEditor are untouched, so
there's no second render site to drift.

The scheme's URL form is NOT the same on every platform: `scheme://localhost/`
on Linux and macOS, `http://scheme.localhost/` on Windows and Android. Getting
it wrong breaks exactly one channel, silently, and a headless CI runner can
never tell you.

The mime rides in the URL, and this scheme is an origin of its own, so an
attachment claiming to be text/html would run as a document there. Only media
families are echoed back; everything else is served as an opaque download,
which is the right treatment for an arbitrary file anyway. Path safety is
inherited rather than re-implemented — the handler reads through BlobStore,
which already refuses anything that isn't a bare sha256.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SreJkbxB4gx8pPsu8QbLPi
2026-07-26 17:16:44 -04:00
..

ThoughtSync desktop (Tauri v2)

Local-first desktop client. The window loads the shared Vue 3 frontend from ../frontend; the Rust core (src-tauri) owns the on-device store and the opt-in sync engine (built out across the M10 milestone). Works fully offline; optionally syncs to a self-hosted ThoughtSync server.

Layout

desktop/
  src-tauri/
    Cargo.toml
    build.rs
    tauri.conf.json          # frontendDist -> ../../frontend/dist, devUrl :5173
    capabilities/default.json
    src/
      main.rs                # thin shim -> lib::run()
      lib.rs                 # tauri::Builder entry point

The Vue frontend is the sibling ../frontend package, shared with the web build. On desktop it is backed by a local data source via frontend/src/adapters/ (M10.3) instead of the server REST API. frontend and src-tauri are siblings, not nested, so the beforeDev/beforeBuild commands cd "$(git rev-parse --show-toplevel)/frontend" to resolve regardless of the CLI's working directory.

Prerequisites

The toolchain (Rust + Node + WebKitGTK 4.1 + tauri-cli) is provided by the ci-tauri CI image. For local dev: install Rust + Node, cargo install tauri-cli, and the Tauri v2 Linux system deps — see CI-tauri/Dockerfile in the CI-runner repo for the exact apt list (libwebkit2gtk-4.1-dev, libgtk-3-dev, librsvg2-dev, libayatana-appindicator3-dev, libxdo-dev, patchelf, ...).

Dev

cd desktop/src-tauri && cargo tauri dev

beforeDevCommand starts the Vite dev server (port 5173) in ../frontend.

Build (Linux)

cargo tauri icon "$(git rev-parse --show-toplevel)/frontend/public/icon.svg"
cd desktop/src-tauri && cargo tauri build   # -> .deb + .AppImage

App icons are generated from the frontend's icon.svg via cargo tauri icon (not committed; CI does this before cargo tauri build). Bundle targets: deb, appimage (Linux-first; Windows/macOS later, no code changes expected).

Status

Scaffold (M10.2): boots the shared Vue UI in a native window. Until the local data adapter lands (M10.3 + M10.5) the app has no server configured, so it shows the login screen without a working backend — full offline functionality arrives with the local SQLite store (M10.4) + adapters/local.ts (M10.5).