Backend
- sort=recent stays a back-compat alias for created (parse_sort); invalid
sort/order now return the structured 422 envelope instead of plain-text 400
- per-field default direction on omitted order (dates desc, text asc), matching
the frontend so a bare ?sort=<field> reads the same in UI and API
- migration 0033: index mangas(updated_at DESC, id) backing the default sort
and its id tie-break; NULLS LAST now applied only to the nullable author key
- tests: recent alias, per-field default (title+author), invalid-value 422,
and a tie-break test that pins ordering by ascending id (mutation-verified)
Frontend
- pure sort helpers extracted to $lib/mangaSort with unit tests; coerceSort
honors the recent alias so pasted/legacy URLs resolve to the same field
- SegmentedControl: roving tabindex + arrow/Home/End keyboard nav, anchored on
focus so rapid keypresses don't stick
- UX: visible "Direction" labels (desktop + mobile), mobile sort-sheet section
headings and a "Done" button; README sort/order contract updated
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 1 of the mobile redesign: add the reusable primitives every
later phase plugs into, and switch the layout to a 4-tab bottom nav
+ compact AppBar on phone viewports while leaving the desktop header
untouched above 640px.
- New primitives: BottomNav, Sheet, AppBar, SegmentedControl, all
with vitest unit tests.
- Layout swaps the desktop header for AppBar + BottomNav under the
existing 640px breakpoint. Login / register / reader routes opt
out of the mobile chrome.
- Library tab currently points at /bookmarks; the curated /library
wrapper lands in Phase 5.
- Independent ResizeObservers publish --app-header-h,
--mobile-app-bar-h, --app-bottom-nav-h, with a zero-height skip so
hidden bars don't clobber the visible one's value.
- viewport-fit=cover + env(safe-area-inset-*) tokens for notched
devices and the iOS home indicator.
- Playwright spec covers visibility at 390px / 1280px viewports and
tab navigation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>