From 20cf15c99c51e791591e6f746a01c765b1e916ca Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 24 Jul 2026 22:31:00 -0400 Subject: [PATCH] desktop M10.3: frontend data-source adapter seam (repo interface + rest.ts) Extract a typed repository interface (adapters/repo.ts) from the scattered store/view -> api.* calls, backed by adapters/rest.ts (verbatim HTTP mapping) and selected through adapters/index.ts. Every store and the notes-facing views now depend on `repo`, never the HTTP client directly -- the seam the offline local source (M10.5, over Tauri invoke) plugs into next. Behavior-preserving for web: rest.ts maps each semantic method to the exact endpoint the code called before; query-string and multipart building moved out of the stores/views into rest.ts (the one place that knows the URL shape). Client-side logic (reconcile/sort/optimistic reorder/toasts) stays in the stores. GraphView + admin SettingsView keep direct api calls -- out of the offline-core scope (M10.5 is board/editor/capture/search/filter/labels/ checklists/reminders). Task 1992. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm --- frontend/src/adapters/index.ts | 19 ++++ frontend/src/adapters/repo.ts | 140 +++++++++++++++++++++++++ frontend/src/adapters/rest.ts | 117 +++++++++++++++++++++ frontend/src/components/NoteEditor.vue | 9 +- frontend/src/stores/config.ts | 6 +- frontend/src/stores/devices.ts | 8 +- frontend/src/stores/labels.ts | 15 ++- frontend/src/stores/notes.ts | 63 ++++------- frontend/src/stores/reminders.ts | 4 +- frontend/src/stores/savedFilters.ts | 10 +- frontend/src/stores/session.ts | 14 +-- frontend/src/stores/titles.ts | 5 +- frontend/src/views/SearchView.vue | 5 +- frontend/src/views/TimelineView.vue | 21 ++-- 14 files changed, 346 insertions(+), 90 deletions(-) create mode 100644 frontend/src/adapters/index.ts create mode 100644 frontend/src/adapters/repo.ts create mode 100644 frontend/src/adapters/rest.ts diff --git a/frontend/src/adapters/index.ts b/frontend/src/adapters/index.ts new file mode 100644 index 0000000..45a6e0f --- /dev/null +++ b/frontend/src/adapters/index.ts @@ -0,0 +1,19 @@ +// 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. + +import { rest } from "./rest"; +import type { Repo } from "./repo"; + +export const repo: Repo = rest; + +export type { Repo } from "./repo"; diff --git a/frontend/src/adapters/repo.ts b/frontend/src/adapters/repo.ts new file mode 100644 index 0000000..9ea20ee --- /dev/null +++ b/frontend/src/adapters/repo.ts @@ -0,0 +1,140 @@ +// The data-source seam. Stores/views talk to this typed repository interface +// instead of reaching for the HTTP client directly, so the SAME UI can run +// against the REST backend (web + a server-connected desktop) or a fully-local +// on-device source (offline desktop). `rest.ts` implements it over `api/client`; +// `local.ts` (M10.5) implements it over Tauri `invoke`; `index.ts` picks one. +// +// Keep this interface a thin, semantic mirror of the current calls: every method +// maps 1:1 to a backend operation and returns the same shape the stores already +// consume. Client-side logic (list reconciliation, optimistic updates, toasts) +// stays in the stores — the repo is data access only. + +import type { NoteColor } from "../notes/colors"; +import type { Note, NoteFacets, NoteView, NoteKind, 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"; + +// ---- notes payload shapes ---------------------------------------------------- + +// The full board query the GET /api/notes endpoint accepts. `rest.ts` renders it +// to a query string (the one place that knows the URL shape); a local source +// reads the same fields structurally. +export interface NoteListQuery { + view: NoteView; + labelId?: string | null; + facets?: NoteFacets; + // The timeline view sorts by creation instead of the board's pinned/position order. + sort?: "created"; +} + +export interface NoteCreateInput { + title: string; + body: string; + color: NoteColor; + kind?: NoteKind; + items?: string[]; +} + +// The mutable subset of a note (PATCH /api/notes/:id). +export type NoteChanges = Partial< + Pick +>; + +export interface ChecklistItemChanges { + text?: string; + checked?: boolean; +} + +export interface Backlink { + id: string; + title: string; +} + +export interface ImportResult { + source: string; + imported: number; + skipped: number; +} + +export interface DeviceToken { + token: string; + device: Device; +} + +// ---- per-domain repositories ------------------------------------------------- + +export interface ConfigRepo { + get(): Promise; +} + +export interface AuthRepo { + me(): Promise; + login(email: string, password: string): Promise; + register(email: string, password: string, displayName: string): Promise; + logout(): Promise; +} + +export interface DevicesRepo { + list(): Promise; + create(name: string): Promise; + remove(id: string): Promise; +} + +export interface LabelsRepo { + list(): Promise; + create(name: string): Promise