Commit Graph
4 Commits
Author SHA1 Message Date
bvandeusenandClaude Opus 5 be0eb94225 frontend: reorder cards with Pointer Events so touch can do it at all (task 2697)
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 9s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Build & push image (push) Successful in 36s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m17s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m12s
Desktop (Tauri) / Update manifest (push) Successful in 5s
Native HTML5 drag-and-drop never fires from touch — the API predates it and was
never wired to it — so on a phone reordering did nothing whatsoever, and the grip
that starts it was hover-gated on top of that. Pointer Events cover mouse, touch
and stylus on one code path instead of two.

The awkward part is hit-testing. Native DnD routed dragover/drop to whatever was
under the cursor, so each card learned on its own that it was the target. A
captured pointer sends every move to the element that captured it, so the dragged
card has to hit-test for itself and publish the result where the other cards can
see it — hence the shared refs in useCardDrag. It reads the DOM via
elementFromPoint rather than tracking geometry because the board is a CSS masonry:
visual order isn't derivable from model order, and cards reflow as the column
count changes. Asking the browser what is actually under the finger is the only
answer that stays true.

Capture is what makes the gesture survive crossing a card boundary; touch-action:
none claims it from the browser's scrolling; a 6px threshold keeps a tap from
becoming a drag; and pointercancel is handled so a system interruption leaves no
half-set state.

The parent contract is unchanged apart from `drop` now carrying the target's ID
rather than its note — the dragged card finds its target in the DOM, so an id is
all it can know without a second lookup. BoardView keeps its own tracking of what
was picked up; that it now duplicates the composable's draggingId is real, and
noted for the DRY pass rather than expanded into here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 16:39:37 -04:00
bvandeusenandClaude Opus 5 3a1496e5fa frontend: honor prefers-reduced-motion, and let frontend work reach the desktop
CI & Build / Python lint (push) Successful in 3s
CI & Build / Python tests (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Build & push image (push) Successful in 31s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m38s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m33s
Two halves of the same gap. The app had no reduced-motion handling at all — the
setting appeared nowhere in the frontend — and the desktop build didn't rebuild
on frontend changes, so shared UI work shipped to the web and silently never
reached the desktop app.

The CSS guard is global and blunt so it catches every Tailwind `transition`
already scattered through the components, and catches M7's motion work without
each new component having to remember. Near-zero durations rather than `none`,
so transitionend/animationend still fire and nothing waiting on them hangs.
useReducedMotion covers what CSS can't reach: JS-driven motion, where the honest
response to the preference is no animation at all rather than a faster one. It's
reactive because the setting can change while the app is open.

The path filter was narrowed to the adapter/bridge directories against a
"~20-40 min" build cost recorded in the header. Measured runs are 4-5 minutes,
so that cost isn't there, and the frontend is compiled into the binary by
generate_context! — any part of it changing means the shipped desktop app is
stale. Desktop, web and Android are peer surfaces on one quality bar, so shared
frontend work has to reach all of them by construction rather than by whichever
directory it happened to touch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 11:51:07 -04:00
bvandeusenandClaude Opus 4.8 590d3ff2f6 M9 S3 (frontend): recall views adopt useNoteList + AsyncState/EmptyState
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 10s
CI & Build / Build & push image (push) Successful in 34s
The Search / Timeline / Reminders views each hand-rolled the same
items+loading+error scaffold, retry button, and editor-host glue.

- useNoteList(fetcher, fallbackError) (new): the load-a-note-list scaffold
  (items/loading/error + a load() that never leaves a half-state). Views
  supply just the fetcher; the refs drive <AsyncState>.
- SearchView / TimelineView / RemindersView: adopt useNoteList + useNoteEditor
  + <AsyncState>/<EmptyState>; drop the local list/loading/error refs, the
  duplicated retry blocks, and the notes.items-shadowing navigate glue.
- GraphView: editor host now via useNoteEditor (openNode/closeEditor/onNavigate
  collapse to navigate); loading/error via <AsyncState>. Its two empty states
  keep inline markup/buttons, so they stay custom (not forced into EmptyState).
- reminders store: fetchReminders() is the single owner of /api/notes/reminders;
  both the background poll (check) and RemindersView read through it.

Frontend-only; CI vue-tsc is the type 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:36:24 -04:00
bvandeusenandClaude Opus 4.8 18ca4d4db4 S1: frontend infra — AsyncState/EmptyState + useNoteEditor, adopted in BoardView
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 8s
CI & Build / Python tests (push) Successful in 11s
CI & Build / Build & push image (push) Successful in 30s
M9 section S1, commit 4 (frontend). Introduce the shared UI primitives the 7 data
views + 5 editor hosts were hand-rolling:

- components/AsyncState.vue: the loading / error(+Retry) wrapper (emits `retry`).
- components/EmptyState.vue: the centered title/subtitle "nothing here" block.
- composables/useNoteEditor.ts: the editing/open/close/navigate glue every editor
  host duplicated, as one controller (onClose hook + local-list resolution for
  [[wiki-link]] navigation).

BoardView adopts all three: its three hand-rolled loading/error/empty blocks
collapse into <AsyncState> + <EmptyState>, and its editor glue into useNoteEditor.
The other views + editor hosts adopt these in the Organize (S3) and Auth (S5)
sections. Behavior-preserving.

Frontend has no local typecheck (rule 10); CI's vue-tsc is the gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
2026-07-23 19:40:37 -04:00