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