Files
inkwell/frontend/src/adapters/rest.ts
T
bvandeusen bc22f8e249
CI & Build / Build now, or wait for Android? (push) Successful in 2s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Failing after 6s
CI & Build / Build & push image (push) Skipped
CI & Build / Python tests (push) Successful in 8s
Desktop (Tauri) / Tauri desktop (Linux) (push) Failing after 31s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Failing after 37s
Desktop (Tauri) / Update manifest (push) Skipped
Android / Kotlin + Rust (APK) (push) Failing after 1m56s
Remove [[wiki-links]], backlinks and the graph
Operator, 2026-08-22 (note 2897): ThoughtSync is an intermediary surface. You
write here because it's easy — a notebook in your pocket — and later you recall
the thing and go finish it somewhere else. Recall is the product; organization
is secondary. A linking system is organization, and it isn't what this is for.

So: `[[wiki-links]]`, backlinks, the `[[` autocomplete, the note_links table,
`/api/notes/link-search`, `/api/notes/<id>/backlinks`, the whole graph blueprint
and GraphView. Rust core loses `extract_links`, `backlinks`, `link_search` and
`create_titled`; the desktop loses the three Tauri commands that exposed them.

This subsumes 982d24c rather than reverting it. That commit bound links to a
note id so a rename would stop rewriting other notes' bodies — real infra, but
infra for a feature that is now gone, and nothing it added survives. Alembic
0023 stays in the chain anyway: it shipped in an image and may already be
applied, and deleting an applied revision strands a database's version pointer.
0024 drops the table and takes the column with it. The history stays honest
about the fact that it existed for a day.

Two things deliberately kept, because they were serving recall and only
incidentally serving links:

- `/api/notes/titles` and the titles store. The command palette lists them so
  you can jump to a note by name. `resolve()` — the name→note lookup that only
  linking needed — is gone.
- `display_title`. Every note still has a name for search results and export
  filenames. What that name is FOR changed; that it exists did not.

`notes/links.py` is now `notes/tags.py`, holding the #tag→label reconciliation
it always also owned. A file called links.py with no links in it would have been
exactly the drift this removal is meant to end.

Also swept out on the way: `_escape_like`, whose only caller was link-search,
and the `graph` icon. Nothing lost that a person typed — note_links was always
derived, and the `[[text]]` is still sitting in every body it was written in.
2026-08-22 12:00:57 -04:00

113 lines
5.7 KiB
TypeScript

// REST implementation of the repository seam: maps each semantic operation to the
// existing HTTP endpoint via `api/client`. This is the ONLY place that knows about
// URL paths, query strings, and multipart bodies. Behaviour here must stay a
// verbatim mirror of the calls the stores/views made before the seam existed — the
// web app is unchanged; the offline `local.ts` source (M10.5) is the alternative.
import { api } from "../api/client";
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 {
DeviceToken,
ImportResult,
NoteChanges,
NoteCreateInput,
NoteListQuery,
ChecklistItemChanges,
Repo,
} from "./repo";
// Render a board query to the GET /api/notes query string. Mirrors the param
// building that used to live in notes.store.load()/TimelineView (order-preserving;
// query-param order is irrelevant to the server, but kept close for review).
function notesQuery(q: NoteListQuery): string {
const params = new URLSearchParams();
params.set("filter", q.view);
if (q.labelId) params.append("label", q.labelId);
for (const id of q.facets?.label ?? []) if (id) params.append("label", id);
if (q.facets?.q) params.set("q", q.facets.q);
if (q.facets?.color) params.set("color", q.facets.color);
if (q.facets?.kind) params.set("kind", q.facets.kind);
if (q.facets?.has_reminder) params.set("has_reminder", "true");
if (q.facets?.has_attachment) params.set("has_attachment", "true");
if (q.facets?.created_after) params.set("created_after", q.facets.created_after);
if (q.facets?.created_before) params.set("created_before", q.facets.created_before);
if (q.sort) params.set("sort", q.sort);
return params.toString();
}
function fileForm(file: File): FormData {
const form = new FormData();
form.append("file", file);
return form;
}
export const rest: Repo = {
config: {
get: () => api.get<PublicConfig>("/api/config"),
},
auth: {
me: () => api.get<User>("/api/auth/me"),
login: (email, password) => api.post<User>("/api/auth/login", { email, password }),
register: (email, password, displayName) =>
api.post<User>("/api/auth/register", { email, password, display_name: displayName }),
logout: () => api.post<void>("/api/auth/logout"),
},
devices: {
list: async () => (await api.get<{ devices: Device[] }>("/api/auth/devices")).devices,
create: (name) => api.post<DeviceToken>("/api/auth/devices", { name }),
remove: (id) => api.del<void>(`/api/auth/devices/${id}`),
},
labels: {
list: async () => (await api.get<{ labels: Label[] }>("/api/labels")).labels,
create: (name) => api.post<Label>("/api/labels", { name }),
rename: (id, name) => api.patch<Label>(`/api/labels/${id}`, { name }),
setColor: (id, color) => api.patch<Label>(`/api/labels/${id}`, { color }),
remove: (id) => api.del<void>(`/api/labels/${id}`),
merge: (sourceId, into) => api.post<Label>(`/api/labels/${sourceId}/merge`, { into }),
},
notes: {
list: async (query) => (await api.get<{ notes: Note[] }>(`/api/notes?${notesQuery(query)}`)).notes,
get: (id) => api.get<Note>(`/api/notes/${id}`),
create: (input: NoteCreateInput) => api.post<Note>("/api/notes", input),
update: (id, changes: NoteChanges) => api.patch<Note>(`/api/notes/${id}`, changes),
completeReminder: (id) => api.post<Note>(`/api/notes/${id}/reminder/complete`),
snoozeReminder: (id, minutes) => api.post<Note>(`/api/notes/${id}/reminder/snooze`, { minutes }),
setLabels: (id, labelIds) => api.put<Note>(`/api/notes/${id}/labels`, { label_ids: labelIds }),
addItem: (id, text) => api.post<Note>(`/api/notes/${id}/items`, { text }),
updateItem: (id, itemId, changes: ChecklistItemChanges) =>
api.patch<Note>(`/api/notes/${id}/items/${itemId}`, changes),
deleteItem: (id, itemId) => api.del<Note>(`/api/notes/${id}/items/${itemId}`),
uploadAttachment: (id, file) => api.postForm<Note>(`/api/notes/${id}/attachments`, fileForm(file)),
deleteAttachment: (id, attId) => api.del<Note>(`/api/notes/${id}/attachments/${attId}`),
unfurl: (id, url) => api.post<Note>(`/api/notes/${id}/unfurl`, { url }),
deletePreview: (id, previewId) => api.del<Note>(`/api/notes/${id}/previews/${previewId}`),
import: (file) => api.postForm<ImportResult>("/api/notes/import", fileForm(file)),
reorder: (orderedIds) => api.post<void>("/api/notes/reorder", { ids: orderedIds }),
trash: (id) => api.post<Note>(`/api/notes/${id}/trash`),
restore: (id) => api.post<Note>(`/api/notes/${id}/restore`),
deleteForever: (id) => api.del<void>(`/api/notes/${id}`),
revisions: async (id) => (await api.get<{ revisions: NoteRevision[] }>(`/api/notes/${id}/revisions`)).revisions,
restoreRevision: (id, revId) => api.post<Note>(`/api/notes/${id}/revisions/${revId}/restore`),
reminders: async () => (await api.get<{ notes: Note[] }>("/api/notes/reminders")).notes,
titles: async () => (await api.get<{ titles: TitleEntry[] }>("/api/notes/titles")).titles,
search: async (q) => (await api.get<{ notes: Note[] }>(`/api/notes/search?q=${encodeURIComponent(q)}`)).notes,
},
savedFilters: {
list: async () => (await api.get<{ filters: SavedFilter[] }>("/api/saved-filters")).filters,
create: (name, params) => api.post<SavedFilter>("/api/saved-filters", { name, params }),
remove: (id) => api.del<void>(`/api/saved-filters/${id}`),
rename: (id, name) => api.patch<SavedFilter>(`/api/saved-filters/${id}`, { name }),
},
};