From 41cf4b3598e6caa46006d83a05856eae50ec4c6f Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 24 Jul 2026 23:05:04 -0400 Subject: [PATCH] desktop M10.5: wire the offline local source; boot to board with no server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit adapters/local.ts implements the repository seam over the M10.4 Tauri commands via invoke (camelCase args -> the commands' snake_case params); adapters/index.ts now selects local when running in the desktop shell, rest on web. bridge.ts exports invoke for it. This is the commit that resolves the black screen: a fresh desktop launch reads config + session + notes from the on-device SQLite core, so the router's auth gate passes with a synthetic local user and the board renders with zero server and zero account. Account auth, device linking, attachment upload, URL unfurl and file import reject with a "connect a server" message (no offline meaning yet); everything else — board, editor, capture, search, filters, labels, checklists, reminders — works fully offline. desktop.yml also now rebuilds the app on frontend adapter/bridge changes, since the desktop bundle embeds the frontend. Task 1994. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm --- .forgejo/workflows/desktop.yml | 4 ++ frontend/src/adapters/index.ts | 17 +++---- frontend/src/adapters/local.ts | 86 ++++++++++++++++++++++++++++++++++ frontend/src/desktop/bridge.ts | 2 +- 4 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 frontend/src/adapters/local.ts diff --git a/.forgejo/workflows/desktop.yml b/.forgejo/workflows/desktop.yml index 41beadc..0d3e5d5 100644 --- a/.forgejo/workflows/desktop.yml +++ b/.forgejo/workflows/desktop.yml @@ -13,6 +13,10 @@ on: tags: ["v*"] paths: - "desktop/**" + # The desktop app embeds the frontend, and the data seam / Tauri bridge are + # what the offline core rides on — rebuild the app when those change too. + - "frontend/src/adapters/**" + - "frontend/src/desktop/**" - ".forgejo/workflows/desktop.yml" workflow_dispatch: diff --git a/frontend/src/adapters/index.ts b/frontend/src/adapters/index.ts index 45a6e0f..f7d07f5 100644 --- a/frontend/src/adapters/index.ts +++ b/frontend/src/adapters/index.ts @@ -1,19 +1,16 @@ // The data source the whole app uses. Import `repo` from here — never reach for // `api/client` in stores/views. // -// Today it always resolves to the REST source. M10.5 adds the offline desktop -// source (`local.ts`, over Tauri `invoke`) and flips this to a runtime choice: -// -// import { isDesktop } from "../desktop/bridge"; -// import { local } from "./local"; -// export const repo: Repo = isDesktop() && !hasConfiguredServer() ? local : rest; -// -// so a fresh desktop launch runs fully offline, and a server-connected one keeps -// using REST. Web is unaffected. +// The desktop shell runs fully offline against the on-device SQLite core +// (`local.ts`, over Tauri `invoke`); the web build uses REST. A server-connected +// desktop (opt-in sync) comes in M10.7 — at which point this becomes +// `isDesktop() && !hasConfiguredServer() ? local : rest`. +import { isDesktop } from "../desktop/bridge"; import { rest } from "./rest"; +import { local } from "./local"; import type { Repo } from "./repo"; -export const repo: Repo = rest; +export const repo: Repo = isDesktop() ? local : rest; export type { Repo } from "./repo"; diff --git a/frontend/src/adapters/local.ts b/frontend/src/adapters/local.ts new file mode 100644 index 0000000..4c20230 --- /dev/null +++ b/frontend/src/adapters/local.ts @@ -0,0 +1,86 @@ +// Offline desktop implementation of the repository seam: maps each operation to a +// Tauri command backed by the on-device SQLite store (desktop/src-tauri/src/local). +// Selected by index.ts when running inside the desktop shell with no server. +// +// Argument keys are camelCase; Tauri converts them to the Rust commands' snake_case +// parameters (e.g. labelIds -> label_ids). A few operations have no offline meaning +// yet (account auth, device linking, attachment upload, URL unfurl, file import) — +// those reject with a clear message rather than silently failing; the board, editor, +// capture, search, filters, labels, checklists and reminders all work fully offline. + +import { invoke } from "../desktop/bridge"; +import type { Note, NoteRevision } from "../stores/notes"; +import type { Label } from "../stores/labels"; +import type { SavedFilter } from "../stores/savedFilters"; +import type { Device } from "../stores/devices"; +import type { TitleEntry } from "../stores/titles"; +import type { User } from "../stores/session"; +import type { PublicConfig } from "../stores/config"; +import type { Backlink, DeviceToken, ImportResult, Repo } from "./repo"; + +const NEEDS_SERVER = "That's not available offline — connect a server to use it."; + +export const local: Repo = { + config: { + get: () => invoke("config_get"), + }, + + auth: { + me: () => invoke("auth_me"), + login: () => Promise.reject(new Error("You're offline — there's no account to sign in to.")), + register: () => Promise.reject(new Error("You're offline — accounts are created on a server.")), + logout: () => Promise.resolve(), + }, + + devices: { + list: () => Promise.resolve([]), + create: () => Promise.reject(new Error(NEEDS_SERVER)), + remove: () => Promise.resolve(), + }, + + labels: { + list: () => invoke("labels_list"), + create: (name) => invoke