all: delete the code nothing calls
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 8s
CI & Build / Python tests (push) Successful in 10s
CI & Build / integration (push) Failing after 1m21s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m47s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m48s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m39s
Desktop (Tauri) / Update manifest (push) Successful in 3s
Android / Kotlin + Rust (APK) (push) Successful in 8m37s

From the audit (#5178). Each was unreachable from every client:

- Checklist add-item and delete-item: REST POST /items and DELETE /items/<id>,
  the Tauri commands, the store, rest and local adapters, the core's
  add_item/delete_item, set_item_text and remove_item, and the FFI exports.
  Adding, rewording and removing an item are body edits in every editor. The
  checked toggle stays, and its rewriter is simpler without the drop branch.
- Manual unfurl: POST /unfurl and its adapters. Previews arrive in the
  background after a save (unfurl_queue).
- The /api/config `android_client` key, android_release() and the
  APK_NAME/MANIFEST_NAME aliases. Phones poll /api/client/android.
- users.email_verified and users.avatar_path (migration 0037). Nothing set
  the first or read the second; the SMTP reset never checked verification.
- derive::extract_tags (only tests used it; the shared fixture now runs
  through extract_tag_spans), the unused check and link icons, and the
  unused editor_add_item string.
- The blob scheme is renamed tsblob -> inkblob. URLs are built as notes are
  read, so nothing stored carries the old one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 19:00:44 -04:00
co-authored by Claude Opus 5.5
parent fd1d50662f
commit 7eacd0569c
26 changed files with 159 additions and 491 deletions
+1 -4
View File
@@ -4,7 +4,7 @@
//
// 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, URL unfurl) — those reject with a clear message
// yet (account auth, device linking) — those reject with a clear message
// rather than silently failing; the board, editor, attachments, capture, filters,
// labels, checklists, reminders, import and export all work fully offline.
@@ -55,9 +55,7 @@ export const local: Repo = {
completeReminder: (id) => invoke<Note>("notes_complete_reminder", { id }),
snoozeReminder: (id, minutes) => invoke<Note>("notes_snooze_reminder", { id, minutes }),
setLabels: (id, labelIds) => invoke<Note>("notes_set_labels", { id, labelIds }),
addItem: (id, text) => invoke<Note>("notes_add_item", { id, text }),
updateItem: (id, itemId, changes) => invoke<Note>("notes_update_item", { id, itemId, changes }),
deleteItem: (id, itemId) => invoke<Note>("notes_delete_item", { id, itemId }),
// Kept on this device and uploaded by the next sync once linked (#5168). The bytes
// go as the raw IPC body; the name is percent-encoded because a header carries
// only ASCII.
@@ -70,7 +68,6 @@ export const local: Repo = {
},
}),
deleteAttachment: (id, attId) => invoke<Note>("notes_delete_attachment", { id, attId }),
unfurl: () => Promise.reject<Note>(new Error(NEEDS_SERVER)),
deletePreview: (id, previewId) => invoke<Note>("notes_delete_preview", { id, previewId }),
// The archive crosses as raw bytes, as an attachment does.
import: async (file) => invoke<ImportResult>("notes_import", new Uint8Array(await file.arrayBuffer())),
+2 -5
View File
@@ -40,9 +40,9 @@ export type NoteChanges = Partial<
Pick<Note, "body" | "pinned" | "archived" | "remind_at" | "recurrence">
>;
/** The one item change that isn't typing in the body: ticking its box. */
export interface ChecklistItemChanges {
text?: string;
checked?: boolean;
checked: boolean;
}
@@ -131,12 +131,9 @@ export interface NotesRepo {
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>;
/** Save every note as a zip. Resolves to the path it was saved at, or null when
-3
View File
@@ -84,13 +84,10 @@ export const rest: Repo = {
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)),
// A same-origin GET with the session cookie; the browser saves the attachment
-2
View File
@@ -13,7 +13,6 @@ const paths: Record<string, string> = {
tag: '<path d="M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z"/><circle cx="7.5" cy="7.5" r=".5" fill="currentColor"/>',
pencil: '<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/><path d="m15 5 4 4"/>',
plus: '<path d="M5 12h14"/><path d="M12 5v14"/>',
check: '<path d="M20 6 9 17l-5-5"/>',
checkbox: '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="m9 12 2 2 4-4"/>',
image: '<rect width="18" height="18" x="3" y="3" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>',
bell: '<path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/>',
@@ -27,7 +26,6 @@ const paths: Record<string, string> = {
device: '<rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/>',
sync: '<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M3 21v-5h5"/>',
paperclip: '<path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"/>',
link: '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
filter: '<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/>',
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
copy: '<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>',
-4
View File
@@ -49,10 +49,6 @@ export interface PublicConfig {
// Every client this server holds, keyed by platform id. Absent on a server that
// holds none, and absent on the desktop's own offline config — the Tauri build
// answers `config_get` locally and has no clients to hand out.
//
// `/api/config` also carries `android_client`, which is NOT declared here: it
// exists for phones in the field polling for their own update, not for this app,
// and reading it here would be a second path to the same fact.
clients?: Record<string, ClientRelease>;
}
+1 -16
View File
@@ -184,18 +184,10 @@ export const useNotesStore = defineStore("notes", () => {
reconcile(await repo.notes.setLabels(id, labelIds));
}
async function addItem(id: string, text: string): Promise<void> {
reconcile(await repo.notes.addItem(id, text));
}
async function updateItem(id: string, itemId: string, changes: { text?: string; checked?: boolean }): Promise<void> {
async function updateItem(id: string, itemId: string, changes: { checked: boolean }): Promise<void> {
reconcile(await repo.notes.updateItem(id, itemId, changes));
}
async function deleteItem(id: string, itemId: string): Promise<void> {
reconcile(await repo.notes.deleteItem(id, itemId));
}
async function uploadAttachment(id: string, file: File): Promise<void> {
reconcile(await repo.notes.uploadAttachment(id, file));
}
@@ -204,10 +196,6 @@ export const useNotesStore = defineStore("notes", () => {
reconcile(await repo.notes.deleteAttachment(id, attId));
}
async function unfurl(id: string, url: string): Promise<void> {
reconcile(await repo.notes.unfurl(id, url));
}
async function deletePreview(id: string, previewId: string): Promise<void> {
reconcile(await repo.notes.deletePreview(id, previewId));
}
@@ -299,12 +287,9 @@ export const useNotesStore = defineStore("notes", () => {
snoozeReminder,
saveEdit,
setLabels,
addItem,
updateItem,
deleteItem,
uploadAttachment,
deleteAttachment,
unfurl,
deletePreview,
importNotes,
fetchOne,
-1
View File
@@ -6,7 +6,6 @@ export interface User {
id: string;
email: string;
display_name: string;
email_verified: boolean;
is_admin: boolean;
}