The trash model itself was already right and needed no change: notes soft-
delete (`trashed` locally, `deleted_at` server-side), Trash is a real view,
restore works, permanent deletion is a separate second step only offered on
an already-trashed note, `trash()` shows an Undo toast, and nothing auto-
purges — trash persists until someone acts. Sync carries all of it: a trashed
note syncs WITH its content, and only `purged_at` deletes a client's copy.
What was missing is the guard on the irreversible step. "Delete forever" and
label deletion were one click, silent, with no confirmation — and M10.7 has
changed what that costs. Before, a mis-click lost a note on one machine.
Now it pushes a tombstone that deletes it from every linked device, and the
local tombstone survives to make sure it gets there.
Both guards live in the STORE, not the call sites: NoteCard and NoteEditor
both offer delete-forever, and duplicating the copy is how two prompts drift
until one of them stops matching what actually happens.
The copy names the real consequence — "deleted from every device you sync
with" — because that's the part a user cannot infer from a button in a Trash
view. The label prompt also says the notes themselves are kept, since that's
what people actually worry about when deleting a label.
Labels deliberately get a confirmation but NOT a trash of their own. A label
is organization, not content; the reversible middle step notes get would be
ceremony around something that costs nothing to recreate.
Saved-filter deletion already confirmed (AppShell), so these two were the
outliers, not a new convention.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SreJkbxB4gx8pPsu8QbLPi
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
Extends the existing label manager (create/rename/color/delete) with the two missing maintenance tools (task 1904), so the label list stays clean — which matters more now that #tags mint labels automatically.
Backend: GET /api/labels returns a per-label note count (one grouped query); new POST /api/labels/<id>/merge moves the source label's notes onto a target and deletes the source (repoint via delete+reinsert to avoid mutating the composite PK; preserves via_tag; dedupes notes already on the target). Body #tags are NOT rewritten, so a tag-sourced label re-mints on next edit if its #tag text remains — a documented nuance.
Frontend: LabelsModal shows each label's note count and a 'merge into…' picker; also restores the previously-missing close (x) and merge icons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
Give labels a color (migration 0011 adds labels.color, server_default
'default' so existing labels keep the neutral chip). The PATCH endpoint now
updates name and/or color; note serialization carries each label's color.
Frontend: a swatch picker per label in the Edit-labels modal, colored chips
on cards + in the editor (LABEL_CHIP_CLASSES), and a color dot on each
sidebar label. Reuses the note color vocabulary. (Graph node coloring rides
this in the graph-liveliness task.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
- AppShell: persistent left sidebar (Notes · labels · Archive · Trash) + top bar
(site name, admin Settings, sign out); BoardView now renders inside it.
- labels store (list/create/rename/delete); Note gains labels[]; notes store
gains setLabels + label-aware reconcile + /api/notes?label= loading.
- /label/:id route → label-filtered board.
- LabelPicker (tag a note, create-on-the-fly) in the editor; label chips shown
on cards and in the editor; LabelsModal to create/rename/delete labels.
- api client PUT; new icons (note/tag/pencil/plus/check); nav-link styles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm