Light, audit-first perf pass (task 1870):
- lazy-load + async-decode the card thumbnail and editor attachment
images so an image-heavy board / large note doesn't eagerly fetch
and decode every off-screen image up front
Audit found the rest already in good shape and left as-is: all routes
are code-split via dynamic import(); the titles index (palette,
[[-autocomplete, wiki-link resolution) is cached in its store and only
fetched once; board refetches are scoped to real view/label changes with
no double-load on mount.
(Server-side thumbnail generation — cards load full-res uploads — is a
larger, separate feature, noted for later, out of scope for a light pass.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
Refine the masonry drag interaction (task 1869) so reordering reads as
intentional instead of an accidental, feedbackless jump:
- explicit grip handle (top-left, hover-revealed, board views only) gates
dragging — a plain click or text-select no longer starts a drag
- source card dims while dragging; the card under the pointer shows a
brand ring + slight lift, so the drop position is clear before release
- proper move cursor via dataTransfer effectAllowed/dropEffect
- dragleave uses a relatedTarget guard so the target ring doesn't flicker
over child elements
- clear drag state on dragend (BoardView clears the tracked source even
when a drag is cancelled off-target)
- add a "grip" icon to the shared Icon set
Native HTML5 DnD stays library-free; it remains a desktop/mouse
affordance (touch reorder is the M5 Android client's domain).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
QuickAdd body textarea auto-grows to fit content (min ~3 rows, capped with
scroll) instead of a fixed 3 rows. NoteCard's hover toolbar gains a color
dot that opens a swatch popover to recolor the note in place (outside-click
closes it; listener attached only while open).
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
Focused-card selection on the board: j/k or arrows move it, Enter opens,
e archives, # pins, x trashes the focused card (actions gated off the Trash
view). The selection is a brand ring on the card; it scrolls into view and
clamps as the list/view changes. A window keydown listener on BoardView,
ignored while typing, on a button/link, or while the editor modal is open.
Added the card keys to the ? cheat-sheet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
Move the overlay toolbar from bottom-right to top-right and make it a
rounded-full pill (window-control style, per operator). Top placement also
avoids ever overlapping the reminder/label chips at the card bottom.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
- NoteCard: overlay the hover toolbar (absolute bottom-right, translucent
backdrop, pointer-events gated) instead of reserving a permanent action row.
At rest cards are now content-sized with even padding, so text no longer
looks pinned to the top above an empty strip. Affects every card surface
(board, search, graph, reminders).
- NoteEditor: Cmd/Ctrl+Enter saves & closes (mirrors Esc-closes).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
- Migration 0009: note_links (source_id, target_norm). Parse [[...]] from body on
create/update and rewrite the source's links. GET /api/notes/titles (owner
{id,title} index for client-side resolution); GET /api/notes/<id>/backlinks.
- Frontend: titles store; LinkedText renders [[Title]] styled on cards; editor
shows Links (outgoing, resolve/create-on-click) + Linked-from (backlinks),
clicking navigates the editor to the target note (board + search).
- notes store: fetchOne, createTitled. DB-free link-parser tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
- Migration 0008: notes.position (int). Board orders pinned -> position ->
updated_at; new notes created at top (max position + 1). POST /api/notes/reorder
assigns positions from the given order (owner-scoped).
- notes store: position on Note, position-aware sort, optimistic reorder().
- NoteCard reorderable (native HTML5 draggable + dragstart/drop); BoardView moves
the dragged note before the drop target and persists.
Note: drag on a CSS-columns masonry has imperfect during-drag visuals (columns
reflow); order persists correctly. Candidate for a polish pass / layout tweak.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
- Migration 0007: note_attachments (path/mime/size). Upload POST
/api/notes/<id>/attachments (multipart, png/jpeg/gif/webp, 12MB cap via
MAX_CONTENT_LENGTH) stored under Config.media_root() (first use of DATA_DIR);
owner/ACL-scoped GET serves the file (nosniff); DELETE removes row + file.
Note responses include attachments[].
- Frontend: notes store uploadAttachment (FormData)/deleteAttachment; editor
image button + paste-to-upload + thumbnail grid with remove; card shows the
first image as a cover.
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