Commit Graph

4 Commits

Author SHA1 Message Date
MechaCat02
51ea254dde fix: surface streamed load failures inline
All checks were successful
deploy / test-backend (push) Successful in 30m44s
deploy / test-frontend (push) Successful in 10m46s
deploy / build-and-push (push) Successful in 10m59s
deploy / deploy (push) Successful in 13s
The streamed loaders on bookmarks, collections, library, search, and the
collection detail page swallowed only ApiError into their in-band `error`
field and re-threw anything else. A raw network failure (a non-ApiError
TypeError from fetch) therefore escaped the `{#await}` to SvelteKit's
generic error page. On the collection detail page a content-load failure
showed a misleading "this collection is empty", and on search a failed
results query showed a false "no matches" because the streamed `error`
was never rendered.

Fold non-ApiError failures into the same in-band `error` so every
transient blip renders as an inline message, add a `contentError` branch
to the collection detail grid, and render the search results `error` in
both result views.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 19:11:37 +02:00
MechaCat02
16cdec051a feat: loading skeletons on the bookmarks and collections pages
Both loaders now stream the whole result (the single fetch is also the auth
gate) so the page shows a skeleton while it loads instead of the global nav
bar only: a ListRowSkeleton on bookmarks and a MangaGridSkeleton on the
collections grid, resolving to the sign-in / error / empty / list branches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 07:18:43 +02:00
MechaCat02
49f6d4d213 bugfix: third-pass audit follow-ups (F1-F4 + dockerignore)
- F1: backend/Dockerfile now copies Cargo.lock alongside Cargo.toml
  and builds with --locked, so the production image runs against the
  exact crate versions CI tested. Without this, cargo silently
  resolved fresh on each image build and "we tested it" stopped being
  true for the binary you ship.
- F2: POST /api/v1/mangas/{id}/chapters rejects chapter `number < 1`
  with 422 validation_failed. Mirrors the bookmark page>=1 rule from
  0.9.4 — chapter numbers are 1-indexed everywhere (URLs, upload
  form, reader) and 0/negative numbers had no legitimate use. Three
  cases (0, -1, -100) in api_uploads.rs.
- F3: bookmarks/+page.ts no longer re-throws non-401 ApiErrors as
  SvelteKit's generic 500 page. Surfaces the error message inline via
  a new `data.error` field; the page renders an alert when present.
  Same UX shape as the home page's existing error handling.
- F4: dropped Space from the reader keyboard binding. On portrait
  phones and narrow desktop windows the page image overflows the
  viewport and the user expects Space to scroll — preventDefaulting
  it skipped past unread content. ArrowRight + j remain.
- New backend/.dockerignore and frontend/.dockerignore so the local
  target/ and node_modules/ don't get shipped into the build context
  on every `docker compose build`.

Lockstep version bump to 0.10.2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 00:13:14 +02:00
MechaCat02
e92c581c7b feat: bookmarks (CRUD + per-user listing + frontend toggle)
Backend:
- Migration 0004_bookmarks_unique.sql adds a partial unique index on
  (user_id, manga_id) WHERE chapter_id IS NULL. The 0001 UNIQUE
  constraint over (user_id, manga_id, chapter_id) doesn't block dupes
  when chapter_id is NULL under Postgres's default NULLS DISTINCT, so a
  user could otherwise bookmark the same manga twice at the manga
  level. Chapter-level dupes are still caught by the 0001 constraint.
- repo::bookmark with create / list_for_user / find_owner / delete.
  create catches the 23505 unique violation and surfaces it as
  AppError::Conflict so handlers return a clean 409.
- POST /api/v1/bookmarks { manga_id, chapter_id?, page? } — CurrentUser
  required. Pre-validates the manga exists (404 if not) and, when
  chapter_id is supplied, that the chapter belongs to that manga (also
  404), so FK violations can't bubble up as 500s.
- DELETE /api/v1/bookmarks/{id} — owner-only. 404 if unknown, 403 if it
  exists for another user, 204 on success. Idempotent: deleting an
  already-deleted bookmark is 404, not 500.
- GET /api/v1/me/bookmarks — paged envelope, sorted by created_at DESC,
  scoped to the current user so the URL itself can't be used to peek at
  someone else's bookmarks.

Integration coverage in tests/api_bookmarks.rs (9 cases): create+list
returns only own; duplicate manga-level bookmark → 409; unknown manga
→ 404; unauthenticated POST → 401; user A cannot delete user B's
bookmark (403); unknown delete → 404; double-delete → 404, not 500;
/me/bookmarks requires auth; paged envelope shape on empty list.

Frontend:
- lib/api/bookmarks.ts with createBookmark / deleteBookmark /
  listMyBookmarks. listMyBookmarksOrEmpty wraps the 401 case so pages
  can render anonymously without try/catch boilerplate.
- /manga/[id] overview: pre-loads the user's bookmark list in its load
  function and renders either:
  - "★ Bookmarked" / "☆ Bookmark" toggle with aria-pressed when authed;
    click POSTs or DELETEs and mutates a local working copy of the
    bookmark list (optimistic UI without re-fetching);
  - or a "Sign in to bookmark" link for anonymous users.
- /bookmarks page lists the current user's bookmarks (chapter-level
  bookmarks link into the reader, manga-level back to the overview).
  Anonymous users see a sign-in prompt instead of a 401 page.

E2E in e2e/bookmarks.spec.ts (3 cases): authed toggle round-trip
(bookmark, see in /bookmarks list, unbookmark); anonymous user gets the
sign-in CTA on the overview; anonymous /bookmarks shows the sign-in
prompt. Existing reader.spec.ts updated for the new
bookmark-signin/toggle test IDs.

Lockstep version bump to 0.7.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:40:27 +02:00