desktop M10.3: frontend data-source adapter seam (repo interface + rest.ts)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 9s
CI & Build / Build & push image (push) Successful in 29s

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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
2026-07-24 22:31:00 -04:00
co-authored by Claude Opus 4.8
parent b08cdb92b5
commit 20cf15c99c
14 changed files with 346 additions and 90 deletions
+2 -3
View File
@@ -1,8 +1,7 @@
<script setup lang="ts">
import { computed, watch } from "vue";
import { useRoute } from "vue-router";
import { api } from "../api/client";
import { type Note } from "../stores/notes";
import { repo } from "../adapters";
import { useNoteList } from "../composables/useNoteList";
import { useNoteEditor } from "../composables/useNoteEditor";
import AsyncState from "../components/AsyncState.vue";
@@ -17,7 +16,7 @@ const noMatchSubtitle = computed(() => `Nothing found for "${query.value}".`);
const { items: results, loading, error, load: run } = useNoteList(async () => {
const q = query.value.trim();
if (!q) return [];
return (await api.get<{ notes: Note[] }>(`/api/notes/search?q=${encodeURIComponent(q)}`)).notes;
return repo.notes.search(q);
}, "Search failed.");
const { editing, open: openEditor, close: closeEditor, navigate: onNavigate } = useNoteEditor({
+13 -8
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from "vue";
import { api } from "../api/client";
import { repo } from "../adapters";
import type { NoteListQuery } from "../adapters/repo";
import { type Note } from "../stores/notes";
import { addLocalDays, parseLocalDate } from "../notes/datetime";
import { useNoteList } from "../composables/useNoteList";
@@ -19,18 +20,22 @@ const fromDate = ref("");
const toDate = ref("");
const hasRange = computed(() => !!fromDate.value || !!toDate.value);
function buildQuery(): string {
const params = new URLSearchParams({ filter: "active", sort: "created" });
function buildQuery(): NoteListQuery {
const from = parseLocalDate(fromDate.value);
if (from) params.set("created_after", from.toISOString());
const to = parseLocalDate(toDate.value);
// Half-open upper bound: start of the day AFTER `to`, so the whole `to` day is included.
if (to) params.set("created_before", addLocalDays(to, 1).toISOString());
return params.toString();
return {
view: "active",
sort: "created",
facets: {
created_after: from ? from.toISOString() : undefined,
// Half-open upper bound: start of the day AFTER `to`, so the whole `to` day is included.
created_before: to ? addLocalDays(to, 1).toISOString() : undefined,
},
};
}
const { items, loading, error, load } = useNoteList(
async () => (await api.get<{ notes: Note[] }>(`/api/notes?${buildQuery()}`)).notes,
async () => repo.notes.list(buildQuery()),
"Couldn't load the timeline.",
);