diff --git a/frontend/src/adapters/index.ts b/frontend/src/adapters/index.ts
index f7d07f5..7898d32 100644
--- a/frontend/src/adapters/index.ts
+++ b/frontend/src/adapters/index.ts
@@ -2,9 +2,8 @@
// `api/client` in stores/views.
//
// The desktop shell runs fully offline against the on-device SQLite core
-// (`local.ts`, over Tauri `invoke`); the web build uses REST. A server-connected
-// desktop (opt-in sync) comes in M10.7 — at which point this becomes
-// `isDesktop() && !hasConfiguredServer() ? local : rest`.
+// (`local.ts`, over Tauri `invoke`); the web build uses REST. A desktop linked to a
+// server still reads and writes locally: the core syncs underneath.
import { isDesktop } from "../desktop/bridge";
import { rest } from "./rest";
diff --git a/frontend/src/adapters/local.ts b/frontend/src/adapters/local.ts
index 78eff9d..cc937c0 100644
--- a/frontend/src/adapters/local.ts
+++ b/frontend/src/adapters/local.ts
@@ -84,14 +84,15 @@ export const local: Repo = {
titles: () => invoke("notes_titles"),
},
- // Sharing is between accounts on a server, so these go to the linked one (#5175).
- // Unlinked, each rejects with the core's explanation, which the dialog shows.
+ // The server's admin settings aren't reached from the desktop, linked or not.
settings: {
list: () => Promise.reject(new Error(NEEDS_SERVER)),
save: () => Promise.reject(new Error(NEEDS_SERVER)),
sendTestEmail: () => Promise.reject<{ to: string }>(new Error(NEEDS_SERVER)),
},
+ // Sharing is between accounts on a server, so these go to the linked one (#5175).
+ // Unlinked, each rejects with the core's explanation, which the dialog shows.
shares: {
directory: () => invoke("shares_directory"),
list: (noteId) => invoke("shares_list", { noteId }),
diff --git a/frontend/src/components/AccountList.vue b/frontend/src/components/AccountList.vue
index 25c8a4e..050def3 100644
--- a/frontend/src/components/AccountList.vue
+++ b/frontend/src/components/AccountList.vue
@@ -7,7 +7,8 @@ import OneTimeLink from "./OneTimeLink.vue";
import { useRowAction, useLoad } from "../composables/useAction";
// Admin: the accounts on this instance, and a password reset link for any of them
-// (#5173). There is no mail path, so the admin hands the link over.
+// (#5173). The admin hands the link over; a person can also mail one to themselves
+// when the server can send email (#5266).
interface Account {
id: string;
diff --git a/frontend/src/components/NoteEditor.vue b/frontend/src/components/NoteEditor.vue
index 59f43f6..27cb357 100644
--- a/frontend/src/components/NoteEditor.vue
+++ b/frontend/src/components/NoteEditor.vue
@@ -47,9 +47,6 @@ function setBody(text: string): void {
blocks.value = splitBlocks(text);
}
const labelList = ref(props.note ? [...props.note.labels] : []);
-// Whether this editor is showing the checklist. A note HAS a checklist (M13 step 2)
-// rather than BEING one, so this is a view flag, not a property of the note: it turns
-// on when the note already carries items, and when someone asks for one.
const saving = ref(false);
const root = ref(null);
// One element per block, keyed by the block's id — the only thing about a block that
@@ -489,12 +486,6 @@ async function uploadFile(file: File) {
}
}
-// ---- link previews ----
-//
-// Nothing to trigger any more: the server unfurls a note's URLs in the background
-// after each save (`unfurl_queue.py`) and the preview arrives on a later read. What
-// is left here is removing one you don't want — the editor is the only place with
-// room to offer that, and the card deliberately doesn't.
async function onFileChange(e: Event) {
const input = e.target as HTMLInputElement;
const file = input.files?.[0];
@@ -638,7 +629,10 @@ function revPreview(rev: NoteRevision): string {
device until you remove it.
-
+
-
+
and pass `items` to /cards.
//
-// `fetcher` returns the notes to show (or [] — e.g. an empty search box). A thrown
+// `fetcher` returns the notes to show. A thrown
// ApiError's `.error` becomes the message; otherwise `fallbackError` is shown.
export function useNoteList(
fetcher: () => Promise,
diff --git a/src/inkwell/password_resets.py b/src/inkwell/password_resets.py
index 03c9371..3f46219 100644
--- a/src/inkwell/password_resets.py
+++ b/src/inkwell/password_resets.py
@@ -1,7 +1,6 @@
"""Password resets: an admin makes a one-hour link for an account (#5173).
-The app has no mail path, so a forgotten password used to need a hand on the database
-(#2939 §2). This reuses what invites established: a random token, only its hash
+Before this, a forgotten password needed a hand on the database (#2939 §2). This reuses what invites established: a random token, only its hash
kept, the link shown once to the admin who hands it over.
Using the link sets a new password and signs the account out everywhere. Its web
diff --git a/src/inkwell/serialize.py b/src/inkwell/serialize.py
index 92857dd..622684b 100644
--- a/src/inkwell/serialize.py
+++ b/src/inkwell/serialize.py
@@ -2,7 +2,7 @@
shapes the API returns, so a field name or format is defined once. Each entity has a
BASE serializer with the fields common to every context; callers layer on the
extras their surface needs (usage count in the labels API, sync_revision/purged_at
-in sync deltas). Grows as sections adopt it (labels here; note/user/device later)."""
+in sync deltas). Labels and people so far."""
from __future__ import annotations
from .common import iso