HTML5 native drag-and-drop doesn't fire on touchscreens — mobile
users couldn't reorder the list at all. Swapped the manual DnD
wiring (dragstart/dragover/drop) for svelte-dnd-action, which uses
Pointer Events and handles mouse, touch, AND keyboard reorder
uniformly. Linear-quality reorder UX for ~11 KB gzipped.
Replacement details:
- bun add svelte-dnd-action (0.9.69)
- Home.svelte: ~70 lines of manual handler code deleted, replaced
with ~30 lines wiring up `use:dndzone` + `onconsider` +
`onfinalize`. The midpoint-position math for sort_position is
unchanged (finalize gives us the new neighbour list directly).
- ActivityRow.svelte: the drag handle's `onpointerdown` flips a
parent-owned dragDisabled flag to false — the library then
takes over. Standard "handle-only drag" recipe; clicks on the
title/buttons inside the card don't initiate drag because
dragDisabled stays true everywhere else.
- dndItems is a buffer copy of `filtered` that the library
mutates during a drag. An $effect re-syncs it from `filtered`
between drags (so new activities still float to the top, etc).
- Shadow item (the library's placeholder while dragging) is
rendered at 30% opacity so the drop target is visible without
flashing.
Accessibility wins for free:
- Keyboard reorder: focus an item, press Space to "pick up",
arrow keys to move, Space to drop. Screen readers get
polite-live announcements of each move from the library.
- Touch reorder works on iOS Safari and Android Chrome.
96 tests still pass; typecheck clean; build ok.
Bundle: 122 KB → 154 KB (gzipped 42 → 53 KB, ~+11 KB).
Foundation for an E2E-encrypted activity list per
winter-list-claude-code-prompt.md.
Server (Bun + Hono):
- bun:sqlite with WAL and the spec's schema (idempotent migration)
- opaque server-stored sessions, httpOnly cookie
- signup / challenge / login / logout / me / password / recovery-challenge /
recovery-complete
- activity CRUD with strict visibility rules: private uses ciphertext+nonce,
semi never serializes owner_id, public attributes the owner
- tag store with normalisation + autocomplete (semi/public only)
Frontend (Svelte 5 + Vite):
- libsodium-wrappers-sumo for client crypto (Argon2id + XChaCha20-Poly1305).
SUMO is required because the standard build omits crypto_pwhash.
- IndexedDB-backed private tag index (never leaves the browser)
- in-memory DEK (no localStorage); page reload re-prompts for password
- signup shows the recovery code once; tag input merges server + private
sources with clear labelling
- Bokmål UI
Crypto module (shared/crypto.ts):
- pure, runs in both Bun and the browser via a runtime-conditional loader
that papers over libsodium-wrappers-sumo's broken ESM entry (createRequire
on server, Vite alias in the browser)
- DEK wrap/unwrap, AEAD payload encryption, recovery code generation with
a visually-unambiguous alphabet
Verification:
- 22 crypto round-trip tests (wrap/unwrap, AEAD tamper rejection, password
change preserves ciphertexts, recovery still works after rotation)
- typecheck passes for server and frontend
- Vite production build succeeds; libsodium SUMO chunk is ~315 KB gzipped
Single-image Containerfile for podman: builds frontend in a builder stage,
runs Bun in a slim runtime; one volume for the SQLite file; BUILD_DATE /
GIT_REVISION baked into OCI labels and /etc/build-info.
Known limitation deferred for this commit: the recovery endpoint has no
server-side proof of the recovery code (anyone who knows an email can lock
out the legitimate user, though they can't read any data). Closed in the
next commit.