Files
thoughtsync/frontend/src/adapters/repo.ts
T
bvandeusen 7033995975
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 11s
CI & Build / Python tests (push) Successful in 16s
CI & Build / integration (push) Successful in 18s
CI & Build / Build & push image (push) Successful in 37s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m3s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m13s
Desktop (Tauri) / Update manifest (push) Successful in 5s
search is a facet on the board, not a place you go
Operator (note 2930): tags exist so you can *"filter during a search"*. The
server has always been able to do that — `GET /api/notes` composes `?q=` with
`?label=` and the rest into one AND-ed query. The frontend never reached it.

The header search box navigated to `/search`, and that view called a DIFFERENT
endpoint — `GET /api/notes/search?q=`, full text only, no facets at all. So the
one screen you landed on when you searched was the one screen where you could not
narrow by tag. Tag filtering lived on the board's FilterBar, which is where you
weren't searching. Two search boxes, two endpoints, and only the hidden one did
what tags are for.

Now the header box writes `?q=` into the board's URL beside whatever labels are
already there, and stays on the lens you're in — searching while looking at Trash
searches Trash. The box READS from the URL rather than holding its own copy, so
it stays in step with the Filters panel's Clear and with a saved view opened from
the sidebar.

Deleted: `SearchView.vue`, its route, `GET /api/notes/search`, `repo.notes.search`
and both adapter implementations, and the `notes_search` Tauri command whose only
caller was the adapter entry. FilterBar loses its own "Search text…" input — it
was the same facet, hidden behind a collapsed panel, duplicating a box that is
always on screen. Filters now does what its name says: narrowing. The header does
searching.

`core::store::search` STAYS. Android calls it through the FFI (`search_notes`) and
has its own search surface — which has the same no-tag-filter gap the web just
lost, and deserves the same fix on its own terms rather than as a rider here.
2026-08-23 10:58:23 -04:00

131 lines
4.5 KiB
TypeScript

// 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, 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 {
body: string;
color: NoteColor;
items?: string[];
}
// The mutable subset of a note (PATCH /api/notes/:id).
export type NoteChanges = Partial<
Pick<Note, "body" | "color" | "pinned" | "archived" | "remind_at" | "recurrence">
>;
export interface ChecklistItemChanges {
text?: string;
checked?: boolean;
}
export interface ImportResult {
source: string;
imported: number;
skipped: number;
}
export interface DeviceToken {
token: string;
device: Device;
}
// ---- per-domain repositories -------------------------------------------------
export interface ConfigRepo {
get(): Promise<PublicConfig>;
}
export interface AuthRepo {
me(): Promise<User>;
login(email: string, password: string): Promise<User>;
register(email: string, password: string, displayName: string): Promise<User>;
logout(): Promise<void>;
}
export interface DevicesRepo {
list(): Promise<Device[]>;
create(name: string): Promise<DeviceToken>;
remove(id: string): Promise<void>;
}
export interface LabelsRepo {
list(): Promise<Label[]>;
create(name: string): Promise<Label>;
rename(id: string, name: string): Promise<Label>;
setColor(id: string, color: string): Promise<Label>;
remove(id: string): Promise<void>;
merge(sourceId: string, into: string): Promise<Label>;
}
export interface NotesRepo {
list(query: NoteListQuery): Promise<Note[]>;
get(id: string): Promise<Note>;
create(input: NoteCreateInput): Promise<Note>;
update(id: string, changes: NoteChanges): Promise<Note>;
completeReminder(id: string): Promise<Note>;
snoozeReminder(id: string, minutes: number): Promise<Note>;
setLabels(id: string, labelIds: string[]): Promise<Note>;
addItem(id: string, text: string): Promise<Note>;
updateItem(id: string, itemId: string, changes: ChecklistItemChanges): Promise<Note>;
deleteItem(id: string, itemId: string): Promise<Note>;
uploadAttachment(id: string, file: File): Promise<Note>;
deleteAttachment(id: string, attId: string): Promise<Note>;
unfurl(id: string, url: string): Promise<Note>;
deletePreview(id: string, previewId: string): Promise<Note>;
import(file: File): Promise<ImportResult>;
reorder(orderedIds: string[]): Promise<void>;
trash(id: string): Promise<Note>;
restore(id: string): Promise<Note>;
deleteForever(id: string): Promise<void>;
revisions(id: string): Promise<NoteRevision[]>;
restoreRevision(id: string, revId: string): Promise<Note>;
reminders(): Promise<Note[]>;
titles(): Promise<TitleEntry[]>;
}
export interface SavedFiltersRepo {
list(): Promise<SavedFilter[]>;
create(name: string, params: NoteFacets): Promise<SavedFilter>;
remove(id: string): Promise<void>;
rename(id: string, name: string): Promise<SavedFilter>;
}
export interface Repo {
config: ConfigRepo;
auth: AuthRepo;
devices: DevicesRepo;
labels: LabelsRepo;
notes: NotesRepo;
savedFilters: SavedFiltersRepo;
}