Commit Graph
23 Commits
Author SHA1 Message Date
bvandeusenandClaude Opus 5 fe683595df M10.7e: desktop Sync settings screen (task 2108)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 11s
CI & Build / Build & push image (push) Successful in 33s
Desktop (Tauri) / Tauri desktop (Linux) (push) Failing after 1m32s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m4s
The surface that turns the engine into a feature (rule 27). Desktop-only —
the web build IS a server's UI, so a "connect a server" screen there would be
nonsense; the route redirects to the board and the nav entry is hidden.

UNLINKED IS THE RESTING STATE, not an incomplete setup. The empty case leads
with "Working offline on this device — everything works without a server",
because a screen that framed the default as a problem would push people into
configuring something they may never need. The app is local-first; this is
opt-in.

Probe before credentials. "Check" shows who actually answered — site name,
version, and the M10.6 verdict — before any password or token is typed. An
incompatible server is shown in red and the sign-in fields never appear, so
you cannot hand a credential to something that can't use it. `degraded` names
the missing capabilities rather than staying quiet and letting a feature
mysteriously do nothing.

Both credential paths, matching the Rust side: email+password (a fresh
install has no session to mint a token from) or a pasted device token (for
anyone who'd rather not type a password into a desktop app). Secrets are
cleared from component state the moment they're exchanged.

Disconnect states plainly that the token stays valid server-side and points
at Account -> Linked devices, rather than implying a remote revoke that
didn't happen (issue 2110). Wording avoids "revoke" for exactly that reason.

Push rejections are surfaced verbatim after a sync, never swallowed — a
duplicate label name is the realistic case and only a person can resolve it.

Adds schema v3: last_sync_at. The cursor can't answer "am I up to date?" —
it's a revision watermark, not a time, and it doesn't move at all when a sync
legitimately finds nothing new, so "synced a moment ago, nothing new" would
be indistinguishable from "never synced". Stamped only after BOTH halves of
the cycle succeed; a stamp after a partial cycle would claim currency the
data doesn't have. Cleared on unlink so a new server can't inherit it.

run_cycle now returns the post-cycle status, so the UI updates from one
round-trip instead of chasing every sync with a status call.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SreJkbxB4gx8pPsu8QbLPi
2026-07-26 00:30:37 -04:00
bvandeusenandClaude Opus 4.8 44a5466793 M9 S5 (frontend): shared BaseModal for the standard modals + BaseInput in Account
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 8s
CI & Build / Build & push image (push) Successful in 27s
- BaseModal.vue (new): the backdrop + dialog-panel shell (dimmed fixed overlay,
  bordered rounded panel, role=dialog, close on Escape + backdrop mousedown).
  Caller sizes/pads/shadows the panel via `panelClass`, picks start/center
  `align`, and sets an `ariaLabel` for header-less panels.
- LabelsModal, CommandPalette, and the AppShell keyboard-shortcuts overlay drop
  their hand-rolled backdrop+panel shells and slot their content into BaseModal
  (~12 lines of overlay boilerplate each → gone).
- NoteEditor deliberately keeps its own shell: its backdrop mousedown is
  drag-guarded and its Esc/⌘-Enter handling is bespoke (unsaved-edit safety),
  so folding it in would risk regressing the app's core editing surface (rule 28).
- AccountView's one device-name field now uses the shared BaseInput. SettingsView
  is intentionally NOT converted — its rows are a horizontal label+control pattern
  (checkbox/number/text, direct value mutation), a different shape than BaseInput's
  vertical form field.

Frontend-only; CI vue-tsc is the type/template gate (no local typecheck, rule 10).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-23 21:58:59 -04:00
bvandeusenandClaude Opus 4.8 3c78060051 Header: use the app icon instead of the "TS" text badge (links home)
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 7s
CI & Build / Build & push image (push) Successful in 33s
The header showed a plain "TS" text badge while the real brand icon
(public/icon.svg — the yellow knowledge-graph tile) was only used as the
favicon/PWA icon. Swap the badge for the actual icon, and make the logo +
name a RouterLink to the board (standard "logo goes home").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-23 18:07:26 -04:00
bvandeusenandClaude Opus 4.8 1df70c53bd Capture: Enter-to-start + on-board notice; dismiss discards a new note
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 8s
CI & Build / Build & push image (push) Successful in 33s
Live-pass feedback: make the "waiting for input" state visible and starting
a note more deliberate, and let dismiss cancel an accidental note.

- New notes are now a confirm-to-keep dialog: Esc OR click-away DISCARD a
  brand-new, not-yet-persisted note (so an accidental keystroke / type-to-
  compose never litters); Ctrl/Cmd+Enter, the footer button, or Shift+Enter
  commit it. A compose already persisted by a rich action, and any existing
  note, still close-and-save on dismiss. The compose footer button is now a
  filled "Add note" so the save path is unmistakable.
- Enter (board, nothing focused) starts a new note; a subtle dashed on-board
  notice — "Press Enter or start typing to add a note" — makes capture
  discoverable instead of silent (click it to compose too). Type-to-compose
  stays as the fast path.
- Empty-state copy + shortcuts help updated (Enter/c for a new note).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-23 14:11:11 -04:00
bvandeusenandClaude Opus 4.8 675b3aa248 Capture UX: "+ New" header button + type-to-compose (two-mode board keys)
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 9s
CI & Build / Build & push image (push) Successful in 30s
Live-pass feedback: the wide "Take a note…" bar showed through behind the
compose modal and felt redundant. Per operator choice, remove the bar and
make capture header-button + keyboard driven.

- Removed the board's inline "Take a note…" trigger bar.
- AppShell gains a "+ New" header button (next to search) — navigates to the
  board if needed, then opens the compose modal. The `c` shortcut now does
  the same (unified with newNote()).
- Type-to-compose: on the board with no card focused, any other single
  printable key opens a new note SEEDED with that key (AppShell onKeydown
  fall-through, after the reserved / c ? g shortcuts). Seed travels via
  ui.composeSeed → NoteEditor's new `initialBody` prop; caret placed at end.
- Two-mode board keyboard (BoardView): RESTING = arrows enter browse, letters
  type-to-compose; BROWSING (a card focused) = j/k move, e/x/# act, Enter
  opens, Esc exits to resting. ui.boardCardFocused tells the global handler
  to stand down while browsing so it doesn't swallow card keys.
- Updated the shortcuts help + the empty-state copy ("Hit + New — or just
  start typing").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-23 13:59:23 -04:00
bvandeusenandClaude Opus 4.8 d1bc91a54a M6 1908b: foreground reminder delivery + recurrence/snooze UI (frontend)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Python tests (push) Successful in 9s
CI & Build / TypeScript typecheck (push) Successful in 14s
CI & Build / Build & push image (push) Successful in 29s
Completes 1908 without Web Push. While the app is open, due reminders now
actually surface; recurring reminders + snooze/done are manageable.

- reminders store (singleton): polls /api/notes/reminders every 45s while
  the app is open; each due reminder fires ONCE as a toast (with an "Open"
  action) and, if the user opts in, a page-context OS Notification — no
  service worker, no PWA. Silently primes a stale backlog on first load;
  only announces recently-due ones. AppShell starts/stops it.
- Editor reminder section: a Repeat picker (Does not repeat / Daily /
  Weekly / Monthly / Yearly) + Done (advances a recurring reminder / clears
  a one-off) + Snooze 1h/1d, shown when a reminder is set.
- RemindersView rebuilt as a chronological list: per row a due time +
  recurrence badge + Done / 1h / 1d, click to open; plus an "Enable
  notifications" opt-in and a note that background alerts come with native.
- Note type gains `recurrence`; notes store setRecurrence /
  completeReminder / snoozeReminder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-23 11:58:44 -04:00
bvandeusenandClaude Opus 4.8 5c045aed63 M6 1902b: facet bar + saved views UI (frontend)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 8s
CI & Build / Build & push image (push) Successful in 37s
The dead-simple facet bar over the board + saved views in the sidebar,
completing task 1902. Filter state lives in the URL query, so a filtered
board is a shareable lens and a saved view is just a link ("one space,
many lenses").

- notes/facets.ts: facetsFromQuery / facetsToQuery / facetCount helpers.
- FilterBar.vue (board only): a "Filters (N)" toggle expanding to text
  search + color swatches + label chips + has-reminder / has-attachment /
  Lists / Notes toggles + a created-date range; Clear + "Save view".
  Each control writes the URL query (router.replace).
- notes store: load(view, label, facets) builds the query; NoteFacets type
  + activeFacets; import reload preserves active facets.
- savedFilters store + sidebar "Views" section (each a query-link, delete
  on hover); loaded on mount.
- BoardView derives facets from the query, reloads on facet change (ignores
  ?open=), and shows a "no notes match these filters" empty state.
- Backend: saved-filter param whitelist keys on `label` (matches the
  repeatable ?label= query) so saved views keep their labels. New filter
  icon.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-23 08:45:56 -04:00
bvandeusenandClaude Opus 4.8 3c76b50a9c Sync 2: device-token bearer auth + linked-devices UI (M8)
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 10s
CI & Build / Build & push image (push) Successful in 33s
Native clients (Tauri/Android) authenticate sync with a long-lived device
bearer token, alongside the existing web session cookie.

Backend:
- security.py: generate_token() (secrets.token_urlsafe) + hash_token()
  (SHA-256 — device tokens are already high-entropy, so no slow KDF; keeps
  per-request bearer auth cheap). Only the hash is stored.
- device_tokens table (migration 0016): id, user_id, token_hash (unique),
  name, created_at, last_used_at.
- login_required now accepts `Authorization: Bearer <token>` OR the session
  cookie. Session path stays DB-free (fast); bearer path looks up the token
  hash, sets g.user_id, and stamps last_used_at.
- Endpoints: POST /api/auth/device-login (public; email+password → token,
  the native first-link flow), POST /api/auth/devices (session/bearer →
  token, web "link a device"), GET /api/auth/devices (list), DELETE
  /api/auth/devices/<id> (revoke). All owner-scoped; token shown once.

Frontend:
- Per-user (not admin) /account view "Linked devices": create a token
  (one-time reveal + copy), list devices (name, linked/last-synced), revoke
  with confirm. Top-bar device icon for all users; devices Pinia store.

Tests (DB-free): token hash determinism + uniqueness; device endpoints
auth-guard (401 without auth, before DB); device-login input validation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-22 22:59:52 -04:00
bvandeusenandClaude Opus 4.8 333ab9ce74 Import: ThoughtSync-native round-trip + Google Keep Takeout
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 8s
CI & Build / Build & push image (push) Successful in 33s
Complete the export/import pair (task 1907). POST /api/notes/import takes
an uploaded .zip and appends its notes — never overwriting existing ones.

Two formats, auto-detected:
- ThoughtSync export: recognized by its notes.json (app == thoughtsync);
  round-trips title/body/color/kind/pinned/archived/remind_at/timestamps/
  labels/items and re-attaches image media from the zip.
- Google Keep Takeout: each Keep <note>.json → a note. Maps title,
  textContent/listContent (+ checked), labels, Keep color enum (nearest
  palette match), isPinned/isArchived, isTrashed (→ trash), created/edited
  microsecond timestamps; folds annotation URLs into the body; resolves
  attachment filePaths relative to the note's folder.

Imported notes reuse create_note's derivation + reconciliation:
display-title derive, #tag reconcile, [[wiki-link]] rewrite. Explicit
labels attach as manual (via_tag=false); inline #tags reconcile as tags.
Image attachments copied into media storage; non-image types (e.g. Keep
audio) skipped until any-file attachments land.

Frontend: an Import control in the sidebar (next to Export) — hidden file
input + FormData POST + result toast ("Imported N notes (M skipped)"),
reloading the board + labels. New upload icon; notes-store importNotes().

Tests: import auth-guard + pure-helper coverage (_usec_to_dt, _keep_spec
list/text/color/annotation/attachment mapping, _native_spec round-trip).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-22 21:24:02 -04:00
bvandeusenandClaude Opus 4.8 cc828559ff M6: export — download all your notes as a zip
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 9s
CI & Build / Build & push image (push) Successful in 33s
Data portability / no lock-in (task 1907, export half). GET /api/notes/export streams a zip of the caller's notes: a machine-readable notes.json (notes + labels + items + reminders + attachment refs), a human-readable Markdown file per note (frontmatter + body / checklist), and the attachment media. Sidebar 'Export' link (same-origin GET, session cookie) downloads it. _slugify unit-tested.

Import (Google Keep Takeout) is the follow-up increment of this task.

Pure backend + small frontend; no migration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-22 16:48:49 -04:00
bvandeusenandClaude Opus 4.8 47974f62ee M7: editor keyboard + one-click card toolbars (live-pass fixes)
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 9s
CI & Build / Build & push image (push) Successful in 30s
From the unified-editor live pass (task 1922):

- Esc now returns to the base Notes board from any view (AppShell); an open editor consumes Esc first (its handler stops propagation) so it closes before navigating.

- Ctrl/Cmd+Enter = finish & close in BOTH frames — it already closed the modal editor; now it also commits+collapses the board composer (matches the email/chat 'send' convention). Shift+Enter still saves & starts a new note.

- Fix: the inline composer committed on document mousedown, collapsing and shifting the board before a click landed — so clicking another card's pin/archive/trash took two clicks. Commit on click (bubble phase) instead; the target's action fires first, then the composer closes.

Pure frontend.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-22 14:53:18 -04:00
bvandeusenandClaude Opus 4.8 95b0e30fc7 M6: browse notes by creation date (Timeline lens)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 8s
CI & Build / Build & push image (push) Successful in 1m2s
A temporal recall path — find a note by WHEN it was captured, not just what it contains (task 1903, first of the M6 recall items).

Backend: list_notes gains an optional created_at range (created_after / created_before, half-open interval) + sort=created; also lays groundwork for the richer-search facets (task 1902). New _parse_iso_dt helper with a DB-free unit test.

Frontend: a Timeline view (sidebar nav + 'g t' + command palette) grouping active notes newest-first into local-time buckets (Today / Yesterday / Earlier this week / this month / Month YYYY), plus an optional From/To date filter. Built as a lens on the same NoteCard masonry, consistent with the existing Reminders/Search views.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-22 12:26:35 -04:00
bvandeusenandClaude Opus 4.8 d3bb091f73 m4: global API-error surface (no more silent failures)
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 10s
CI & Build / Build & push image (push) Successful in 31s
The api client now catches network failures and non-JSON bodies robustly,
and routes unexpected errors (offline / 5xx) to a global toast — 4xx stay
with the caller so forms keep their inline messages. Toast actions are now
optional (undo toasts keep their button; error toasts are message-only), and
ToastHost moved from AppShell to the app root so toasts show everywhere,
including the login screen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-21 00:07:04 -04:00
bvandeusenandClaude Opus 4.8 ecbdd12ba9 m4: mobile nav — hamburger + slide-in drawer
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 10s
CI & Build / Build & push image (push) Successful in 29s
Below sm the sidebar had no replacement (nav was inaccessible on phones).
The sidebar aside is now a responsive drawer: fixed + off-canvas with a
hamburger toggle and backdrop on mobile, static in-flow on sm+ (same markup,
no duplication). Closes on nav-click, backdrop, Esc, or route change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-21 00:03:15 -04:00
bvandeusenandClaude Opus 4.8 9baa9217c7 ui: undo toast after trash / archive
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 8s
CI & Build / Build & push image (push) Successful in 29s
A global ToastHost (mounted in the app shell) shows a transient toast with
an Undo action, driven by the ui store (showToast/dismiss/runAction, 5s
auto-dismiss). The notes store fires it centrally: trash → Undo restores;
archive → Undo unarchives — so it works from card, editor, and keyboard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-20 13:15:19 -04:00
bvandeusenandClaude Opus 4.8 f321c2bf70 labels: per-label color
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 8s
CI & Build / Python tests (push) Successful in 10s
CI & Build / Build & push image (push) Successful in 32s
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
2026-07-20 13:11:50 -04:00
bvandeusenandClaude Opus 4.8 4c72dfea0b keyboard: board card navigation (j/k/arrows + actions)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 16s
CI & Build / Build & push image (push) Successful in 34s
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
2026-07-20 11:18:28 -04:00
bvandeusenandClaude Opus 4.8 b24316843e keyboard: command palette (Cmd/Ctrl+K)
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 7s
CI & Build / Build & push image (push) Successful in 30s
Fuzzy quick-open overlay: type to jump to any note by title (titles index)
or run a command — New note, Go to Board/Graph/Reminders/Archive/Trash, and
Open Settings (admins). Arrow keys move the selection, Enter activates, Esc
closes; Cmd/Ctrl+K toggles it (works even while typing).

Selecting a note navigates to the board with ?open=<id>; BoardView watches
that query and opens the editor (fetching the note if it isn't loaded), then
clears the param. Added to the ? cheat-sheet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-20 11:05:50 -04:00
bvandeusenandClaude Opus 4.8 4bd4ce5a1f keyboard: global shortcuts + ? cheat-sheet
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 18s
CI & Build / Build & push image (push) Successful in 27s
App-shell keydown dispatcher (active only inside the authed shell):
- c        new note (jump to board / reopen composer via a ui-store signal)
- /        focus the search box
- g b/g/r  jump to Board / Graph / Reminders (two-key, 800ms window)
- ?        open a keyboard-shortcuts cheat-sheet overlay
- Esc      blur a focused field / close the cheat-sheet

Ignored while typing in an input/textarea/contenteditable (except Esc).
New stores/ui.ts carries the compose signal; QuickAdd exposes open(); the
board reopens the composer on the signal when already on the board.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-20 10:23:44 -04:00
bvandeusenandClaude Opus 4.8 c57982d910 M3 reminders: notes.remind_at + Reminders view
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 7s
CI & Build / Build & push image (push) Successful in 32s
- Migration 0010: notes.remind_at (nullable tz). PATCH accepts remind_at
  (ISO set / null clear); GET /api/notes/reminders (soonest first, non-trashed);
  serialize includes remind_at.
- Frontend: datetime util (local<->ISO, format, overdue); notes store setReminder;
  editor datetime-local picker + clear; card reminder chip (overdue = red);
  sidebar Reminders entry + /reminders view.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-20 08:12:14 -04:00
bvandeusenandClaude Opus 4.8 ad006ccb58 M3 graph view: /api/graph + force-directed SVG
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 8s
CI & Build / Build & push image (push) Successful in 28s
- graph blueprint: GET /api/graph resolves note_links to target notes by
  normalized title (owner-scoped, non-trashed, self-excluded) → {nodes, edges}
  of connected notes.
- GraphView: hand-rolled force simulation (repulsion + edge springs + centering,
  cooling over ~400 frames), SVG nodes/edges, click a node to open it (reuses the
  editor with link navigation). Sidebar Graph entry + route; empty state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-20 08:05:45 -04:00
bvandeusenandClaude Opus 4.8 ffc008bf4d M2 search: Postgres FTS backend + top search bar + results
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 8s
CI & Build / Build & push image (push) Successful in 31s
- Migration 0005: generated tsvector column (title A + body B) + GIN index on
  notes; GET /api/notes/search?q= (websearch_to_tsquery, ts_rank, ACL-scoped,
  excludes trash), labels merged into results.
- Persistent AppShell layout (parent route + <RouterView> children) so the new
  top search box keeps focus across board/search/label navigation.
- SearchView (debounced live search from the shell → /search?q=, results masonry,
  no-match empty state); BoardView/SearchView render inside the shared shell.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-19 22:03:19 -04:00
bvandeusenandClaude Opus 4.8 2046600a95 M2 labels frontend: sidebar shell + label filter, chips, picker, manage
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 13s
CI & Build / Build & push image (push) Successful in 34s
- 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
2026-07-19 21:56:14 -04:00