Commit Graph

231 Commits

Author SHA1 Message Date
MechaCat02
0d9505ce9f fix: address skeleton/nav-progress review findings
Correctness:
- MangaGridSkeleton: covers rendered 0px tall because height="0" is a
  definite height that defeats aspect-ratio. Skeleton gains an `aspectRatio`
  prop (height stays auto) so the 2/3 cover box is reserved and the grid no
  longer shifts when real covers load.
- Move `.manga-card` layout into global tokens.css (it was scoped to
  MangaCard, so the skeleton's cards inherited none of it). Both consumers
  now share one definition.
- NavProgress: rebuilt as an idle/loading/done state machine. It now
  completes to full and fades on settle (was snapping away instantly),
  restarts the trickle when one navigation supersedes another (was parking
  frozen near 90%), animates transform/opacity only with will-change gated to
  the active phase, and hides itself in reader fullscreen.
- Skeleton: text-variant height resolved in one place (no inline style
  shadowing a stylesheet rule); shimmer gradient/animation gated behind
  prefers-reduced-motion: no-preference so reduced motion shows a flat block.

A11y:
- Home loading region announces via visually-hidden text instead of an
  aria-label a live region won't reliably read.

Cleanup:
- Drop the fragile :first-child selector and index-keying ceremony in
  MangaGridSkeleton; simplify Skeleton's style construction.

Tests: adds regression guards for the 0px cover collapse, the aspect-ratio
box, text-height defaulting, and the nav-progress complete/restart phases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 20:02:27 +02:00
MechaCat02
7bdbe3ce5b fix: define missing --font-md and --success-soft-bg tokens
Both were referenced by components (RecommendationShelf, the manga detail
page) but never declared, so var() fell through to inherited/fallback
values. Adds --font-md (1rem, on the xs/sm/md/lg scale) and
--success-soft-bg in both the light and dark theme blocks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 19:44:43 +02:00
MechaCat02
3364ea52c9 feat: async decode for manga cover images
Adds decoding="async" to the MangaCard cover so image decode happens off the
main thread, keeping grid scroll/paint smooth as covers stream in. The 2/3
aspect-ratio already reserves space, so no layout shift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 19:41:28 +02:00
MechaCat02
def97d3087 feat: skeleton loading state on the home catalog
Replaces the plain "Loading…" text with a content-shaped MangaGridSkeleton
so the catalog grid's layout is reserved while it loads and swaps in without
a shift. Keeps the data-testid="loading" wrapper (now role="status") for
accessibility and selector stability.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 19:40:28 +02:00
MechaCat02
09f12c8959 feat: global navigation progress bar
Most data routes run their load with ssr = false, so SvelteKit holds the
previous page on screen while the next one's data resolves — with no
feedback. NavProgress, mounted once in the root layout and driven by the
$navigating store, shows a thin top bar that trickles while a navigation is
pending and fades out on completion. Degrades to a static visible bar under
prefers-reduced-motion. New --z-nav-progress token sits above the fixed
header but below modals/toasts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 19:38:14 +02:00
MechaCat02
34c1122e4e feat: add MangaGridSkeleton loading placeholder
Composes Skeleton into a cover-grid placeholder that reuses the shared
.manga-grid / .manga-card layout, so it matches the real catalog grid at
every breakpoint and content swaps in without a layout shift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 19:31:58 +02:00
MechaCat02
200ab1c0a0 feat: add Skeleton loading-placeholder primitive
A reusable, token-driven shimmer block with configurable width/height,
radius, and variant. Decorative (aria-hidden) — loading state is announced
by the container. Freezes to a solid muted block under the global
prefers-reduced-motion override.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 19:28:00 +02:00
MechaCat02
ef8d226ba6 fix(detail): resync reaction toggle across manga navigation
All checks were successful
deploy / test-backend (push) Successful in 36m1s
deploy / test-frontend (push) Successful in 10m41s
deploy / build-and-push (push) Successful in 11m41s
deploy / deploy (push) Successful in 13s
The detail page component is reused across /manga/A -> /manga/B
navigations (clicking a similar or recommendation card), so
ReactionButtons' locally-held `current` state kept the previous manga's
reaction until interacted with — a visibly wrong thumb on the new page.
Re-seed `current` from the loader-supplied prop via an effect keyed on
mangaId. Also fetch the homepage's two independent /me/* shelves
concurrently (Promise.allSettled) instead of in series.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 18:17:18 +02:00
MechaCat02
19db66a845 feat(home): add a "Recommended for you" shelf
Surface the content-based /me/recommendations feed as a horizontal shelf on
the homepage, below the Continue-reading shelf. Fetched after the catalogue
(same non-blocking pattern), shown only when signed in and the feed is
non-empty; reuses MangaCard in a fixed-width scroller. Component unit test +
e2e (signed-in shows cards, anonymous hides).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 17:46:33 +02:00
MechaCat02
d6a109df2d feat(recommendations): content-based "Recommended for you" endpoint
Add GET /me/recommendations: rank mangas by weighted tag overlap with the
user's taste — explicit like +1.0, bookmark +0.5, dislike −1.0 (reaction
overrides bookmark). Per-tag affinities are summed, candidates scored by
their tags' affinity normalized by tag count (anti-tag-stuffing, à la
list_similar), with already-reacted/bookmarked/read mangas excluded and
net-negative candidates dropped (dislike down-ranks, not browse-hides).
Reuses manga_cols/cards_from_rows. Integration tests cover like-driven recs,
dislike down-rank, bookmark half-weight ordering, seen-exclusion, empty
cold-start, and auth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 17:42:05 +02:00
MechaCat02
bb833c7e71 feat(detail): like/dislike buttons on the manga page
Add a tri-state like/dislike toggle beside the bookmark action (signed-in
only): click a reaction to set it, the other to switch, or the active one to
clear — optimistic with rollback, mirroring the bookmark toggle. Seeds from
the new GET /me/reactions/:id in the detail loader (non-critical: any failure
degrades to an unset toggle). Unit tests cover the state machine; e2e drives
set → switch → clear against the real endpoints.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 17:20:24 +02:00
MechaCat02
258a536254 feat(reactions): per-user like/dislike on mangas
Add a private per-user reaction resource: a manga_reactions table (one row
per user+manga, like/dislike, cascades) and endpoints PUT/DELETE
/mangas/:id/reaction plus GET /me/reactions/:manga_id, wired on the existing
per-user seam (mirrors bookmarks/read_progress). Unknown manga → 404 via the
FK-violation mapping; an invalid reaction value → 422. Reactions are never
exposed publicly — this is a taste signal for the upcoming content-based
recommendations. Integration tests cover set/toggle/clear/read, per-user
isolation, and the 404/422/401 paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 16:54:11 +02:00
MechaCat02
9c4a93c058 fix(reader): harden swipe/long-press coexistence and next-chapter prefetch
All checks were successful
deploy / test-backend (push) Successful in 32m21s
deploy / test-frontend (push) Successful in 10m33s
deploy / build-and-push (push) Successful in 11m40s
deploy / deploy (push) Successful in 18s
- A long-press now cancels any pending swipe, so a hold-then-drag gesture
  opens the action sheet without also turning the page (regression-tested).
- The next-chapter prefetch no longer retries on every page flip when the
  endpoint fails (guard clears only when the next chapter changes), and it
  clears a previous chapter's warmed images on navigation so stale hidden
  <img>s don't linger.
- Tap zones set touch-action: pan-y so vertical panning stays native while
  horizontal swipes reach our handler.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 21:57:36 +02:00
MechaCat02
bfaa166e0a fix(home): keep finished series off the Continue-reading shelf
A "Continue reading" shelf listing series the reader already finished (read
to the last page, nothing new) is odd. Expose the last-read chapter's
page_count on the read-progress list, and filter the shelf to drop entries
that are caught up (on the last page of the latest chapter with no new
chapters). Mid-chapter and has-new-chapters entries stay; the full history
view is unaffected. Finished detection is a pure, unit-tested helper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 21:52:58 +02:00
MechaCat02
3824eaafb2 fix(history): count distinct new chapter numbers, authoritative on detail
new_chapters_count over-counted when scanlations share a chapter number
((manga_id, number) is non-unique, migration 0013): COUNT(*) tallied rows,
so duplicate-numbered chapters inflated the badge. Switch both the list and
per-manga read-progress queries to COUNT(DISTINCT number), and drop the dead
COALESCE (an empty set counts as 0, and the NULL-number case is handled by
the predicate). Fix the misleading comment.

Expose new_chapters_count on GET /me/read-progress/:manga_id and drive the
detail page's "N new" badge from it, instead of recomputing over the
paginated chapter list — which under-counted series past 50 chapters and
disagreed with the homepage shelf. Read markers stay client-side over the
loaded chapters and are documented as by-number (all we persist).

Adds duplicate-scanlation tests at both endpoints.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 21:48:36 +02:00
MechaCat02
f1349100d7 feat(reader): prefetch the next chapter's first pages near the end
When the reader reaches within two pages of the end of a chapter (tracked in
both modes off the furthest page reached), warm the next chapter's first few
page images via hidden imgs so flipping over renders instantly. Fetched once
per next-chapter id, guarded against mid-flight chapter changes, and retried
on failure. e2e asserts the prefetch is absent at the chapter start and
present (with the next chapter's URLs) near the end.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 21:28:06 +02:00
MechaCat02
3f1a5e9c41 feat(reader): swipe left/right to turn pages in single mode
Wire horizontal swipe into the mobile tap-zone overlay (single mode only,
so continuous mode keeps native vertical scroll). A leftward swipe past the
threshold turns to the next page, rightward to the previous; a mostly-
vertical drag is ignored so panning a tall page never turns it. The gesture
suppresses the synthesized zone tap so it doesn't double-fire. Swipe
classification is a pure, unit-tested helper; e2e drives real touch pointer
events for horizontal (both directions) and vertical cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 21:23:34 +02:00
MechaCat02
c212deb7b0 feat(reader): add a keyboard-shortcut help overlay
Press `?` in the reader to open a Sheet documenting the existing bindings
(arrows/J/K paging, Home/End, chapter nav, Esc); Esc or the close button
dismisses it. While open, the reader swallows navigation keys so paging
can't happen behind the overlay. e2e covers open-on-? and close-on-Esc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 21:18:47 +02:00
MechaCat02
f5692ea109 feat(ui): show a tooltip with the full text on truncated titles
Add an `overflowTooltip` Svelte action that sets a native `title` only when
an element is actually clipped (ellipsis or line-clamp), re-checking on
resize. Native title is keyboard/touch reachable and zero-dep, unlike a
hover-only popover. Applied to the clipped titles on MangaCard, BookmarkList,
HistoryList, and the Continue-reading shelf.

Unit tests cover the overflow decision and the set/clear/update behaviour;
an e2e verifies a real-browser truncated card title gets the tooltip while a
short one does not.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 21:08:28 +02:00
MechaCat02
ad1689b818 feat(detail): link genre and tag chips to the filtered catalogue
Genre chips now deep-link to /?genres=<id> and user-tag chips to
/?tags=<id>, reusing the homepage catalogue's existing filter params —
turning the detail page's metadata into one-tap discovery. Tag chips keep
their remove affordance (the Chip's href branch preventDefaults the remove
button). e2e asserts both chip hrefs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 21:04:38 +02:00
MechaCat02
01d18e7ba2 feat(detail): mark read chapters and count new ones on the manga page
On the manga detail page, dim chapters at or before the reader's last-read
chapter (with an accent check + screen-reader "Read." label) and show a
"N new since last read" badge counting chapters that have landed since.
All client-side from the already-loaded chapter list and per-manga read
progress — no extra requests. The count reflects the loaded (paginated)
chapter list. Logic lives in a pure, unit-tested chapterProgress helper;
e2e covers the mid-series and never-opened cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 21:01:58 +02:00
MechaCat02
2267a83f6c feat(home): add a Continue-reading shelf with new-chapter badges
Surface a horizontal "Continue reading" shelf at the top of the homepage,
fed by the user's read-progress history. Each card jumps back to the exact
page they left off (deep-linking `?page=N` past the first) and flags how
many chapters have landed since — the personal new_chapters_count from the
read-progress list. The shelf is fetched after the catalogue load so the
public browse path stays unauthenticated; guests get an empty list (401
swallowed) and the shelf stays hidden.

Add new_chapters_count to the frontend ReadProgressSummary type to match
the backend. Component unit tests cover href/deep-link, badge visibility,
and the no-chapter fallback; e2e covers signed-in (shelf + badge) and
anonymous (hidden).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 20:57:17 +02:00
MechaCat02
f318c3bf51 feat(history): report personal new-chapter count on read-progress list
Add `new_chapters_count` to each `GET /me/read-progress` item: how many
chapters sit past the reader's last-read chapter, computed by a correlated
subquery on chapter number. It is 0 when the reader is caught up, and 0
when the last-read chapter is unknown (manga-level progress or a deleted
chapter) so we never over-claim. (manga_id, number) is non-unique, so this
counts positions past the reader rather than distinct files.

Backs the personal "new since last read" indicator on the upcoming
Continue-reading shelf. Tests cover the mid-series, caught-up, and
no-read-chapter cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 20:50:49 +02:00
MechaCat02
cf8971faae fix(reader): render pages at a consistent width instead of thin stripes
Reader page sizing was height-driven — `max-height: 90vh` in single mode
and unbounded natural width in continuous mode. A long vertical page hit
the height cap first and, preserving aspect ratio, collapsed into a thin
vertical stripe; narrow and wide scans rendered at different widths.

Switch both modes to width-driven sizing via a capped `--reader-page-width`
reading column (min(100%, 700px)): every page renders at the same width
regardless of intrinsic dimensions, and tall pages extend downward and
scroll instead of shrinking. Top-align the single-mode grid and make the
prev/next chevrons sticky so they stay reachable on a long page.

Add a Playwright spec asserting equal rendered width across differently-
proportioned pages and that a tall page exceeds the viewport height. The
page-context-menu fixture served a 1x1 image that now renders as a 700x700
square taller than the viewport, making Playwright scroll it into view
before right-clicking — a synthetic scroll that trips the menu's by-design
close-on-scroll. Give that fixture a realistic landscape aspect so the page
fits the viewport, matching real user behaviour (no scroll on right-click).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 20:45:59 +02:00
MechaCat02
8acc0e6cc2 feat(frontend): use the Mangalord monogram as the browser tab icon
Some checks failed
deploy / test-frontend (push) Has been cancelled
deploy / build-and-push (push) Has been cancelled
deploy / deploy (push) Has been cancelled
deploy / test-backend (push) Has been cancelled
The favicon link pointed at a non-existent favicon.ico. Ship the
monogram SVGs from static/ and wire two theme-aware <link rel="icon">
tags so the tab icon follows the browser color scheme: the dark-M
"light" monogram on light chrome, the white-M "dark" monogram on dark.

Bump 0.93.10 -> 0.94.0 (feat).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 19:34:45 +02:00
MechaCat02
4e154434a1 fix(upload): stream chapter pages to storage instead of buffering the whole chapter
The chapter upload handler read every `page` part fully into a Vec before
writing any, so peak memory was the whole chapter (bounded only by the
200 MiB body limit and amplified by concurrent uploads). It also accepted
an unbounded number of pages.

Stream each page part to a `staging/{upload_id}/…` key as it arrives — at
most one page's bytes are held at a time — then, once the chapter row (and
its id) exists, promote each staged blob to its final key via a new
`Storage::rename` (LocalStorage: fs rename; default impl: stream+delete for
future backends). Finalization is all-or-nothing: on any failure the DB
rolls back and both staged and already-finalized blobs are cleaned up.

Add MAX_PAGES_PER_CHAPTER (UploadConfig, default 2000, 0 = disabled),
rejecting an over-cap upload with 413 before any DB write. Also document
the crawler-side CRAWLER_MAX_IMAGES_PER_CHAPTER (added earlier) in
.env.example + docker-compose so the env-coverage test passes.

Tests: LocalStorage rename unit tests; a 413 over-cap upload test; existing
rollback + happy-path upload tests still green (the fault-injecting storage
counts put/put_stream, so mid-upload failure still rolls back).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 21:33:47 +02:00
MechaCat02
141cd52f7e fix(frontend): percent-encode fileUrl key segments
fileUrl interpolated the raw storage key into the path, so a key segment
containing a reserved character (`?`, `#`, `%`, space) could be
reinterpreted as a query/fragment delimiter. Encode each `/`-separated
segment with encodeURIComponent, keeping slashes as literal path
separators. Keys are backend-generated today, so this is defence-in-depth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 21:18:34 +02:00
MechaCat02
83a9ab40cd fix(crawler): reap dead jobs alongside done ones
The retention reaper deleted only `state = 'done'` rows, so terminal
`dead` jobs (retries exhausted) accumulated in crawler_jobs forever —
slow unbounded table growth. Both states are end-of-life and never
leased again.

Rename `reap_done` → `reap_terminal` and broaden the filter to
`state IN ('done','dead')`; `pending`/`running` stay untouched. Test
extended to assert old done + old dead both reap while fresh terminal and
old-but-active rows survive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 21:16:56 +02:00
MechaCat02
a141d65db1 fix(analysis): cap decoded pixels on the vision backend too
The vision prep pass decoded pages with bare `image::load_from_memory`,
which applies no allocation bound. `max_pixels` only downscales after a
full decode, so a tiny WebP/JPEG header declaring huge dimensions could
OOM the blocking worker — the same decompression-bomb the OCR backend
already guards against. Manga pages are commonly WebP/JPEG, where the
format's own self-limits are weaker than PNG's.

Route the decode through `decode_within`, applying an `image::Limits`
alloc cap sized from the shared `ocr_max_decode_pixels`
(ANALYSIS_OCR_MAX_DECODE_PIXELS). Add real-WebP bomb coverage asserting
both the helper and the end-to-end Undecodable fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 21:13:27 +02:00
MechaCat02
3a9e7ca2da fix(analysis): hold the OCR permit for the full blocking inference
The OCR dispatcher acquired a borrowed semaphore permit, then ran the
CPU-bound inference in `spawn_blocking`. On cancellation (graceful
shutdown) the future dropped the permit while the detached blocking task
kept running, briefly oversubscribing the blocking pool past
ANALYSIS_WORKERS.

Extract `run_ocr_blocking`, which acquires an *owned* permit and moves it
into the blocking task so the concurrency slot's lifetime matches the
actual work. Covered by a cancellation-invariant test asserting the
permit stays held after the caller future is aborted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 21:08:50 +02:00
MechaCat02
5dc93bfb84 fix(crawler): cap the number of page images per chapter
The per-image byte cap bounds each download but not the count, so a
hostile or compromised reader page listing thousands of <img> tags could
drive an unbounded disk fill. Add `CRAWLER_MAX_IMAGES_PER_CHAPTER`
(CrawlerConfig::max_images_per_chapter, default 2000, 0 = disabled) and
reject an over-cap chapter with a failed ack (exponential backoff) rather
than downloading it.

Threaded through sync_chapter_content and its three call sites (daemon
dispatcher, admin resync, CLI); enforced via `image_count_over_cap` right
after parse. The cap is an env-only safety knob, preserved across settings
reloads.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 21:05:28 +02:00
MechaCat02
592747f1e0 fix(crawler): budget image tail against actual sniff-prefix length
The per-image download cap subtracted the constant SNIFF_PREFIX_BYTES (64)
from the budget instead of the bytes actually drained into the prefix.
Because the prefix loop appends whole chunks, a single ~16 KiB first chunk
fills the 64-byte sniff window in one drain, so the tail could then add
another near-full cap — storing up to ~2× max_image_bytes per page.

Capture prefix.len() before the prefix is moved into the stream and budget
the tail as `max_image_bytes - prefix_len` via a small `remaining_after_prefix`
helper (unit-tested for the overshoot and saturation cases).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:57:29 +02:00
MechaCat02
a0d63ac9fd fix(crawler): guard headless nav against internal SSRF targets on list/detail path
`navigate()` (list/detail/pagination) and the session probe called
`Browser::new_page()` without the SSRF check that already guards the
chapter-content path, so a hostile or compromised scraped source could
serve `<a href="http://169.254.169.254/…">` / `http://postgres:5432/` in a
listing and use the in-container Chromium as a read oracle.

Add `guard_navigate_url` (reusing `ensure_public_target`) at the top of
`navigate`, before rate-limiting or opening a page, and apply the same
check to `fetch_probe_html`. Covers base URL, pagination, and detail links.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:54:45 +02:00
MechaCat02
35c02066fe refactor(clippy): fix the never-loop error and clear all lint warnings
All checks were successful
deploy / test-backend (push) Successful in 30m17s
deploy / test-frontend (push) Successful in 10m33s
deploy / build-and-push (push) Successful in 11m11s
deploy / deploy (push) Successful in 13s
`cargo clippy --all-targets` was failing on a deny-by-default
`never_loop` in the analysis SSE handler (the `loop` always returned on
the first iteration — the stream `unfold` already re-enters per event),
plus ~28 warnings. All resolved with no behaviour change:

- admin/analysis SSE: drop the dead `loop` wrapper.
- app: match port literals directly instead of `if p == 80` guards.
- repo/user: separate doc list from the following paragraphs.
- repo/upload_history: `sort_by_key(Reverse(..))` over `sort_by`.
- crawler/nav test: construct the error directly (no `unwrap_err` on a
  literal `Err`).
- test helpers: build configs via struct-update syntax instead of
  `Default::default()` + field reassignment; add a type alias for a
  complex audit-row tuple.
- plus the mechanical `deref`/etc. fixes from `cargo clippy --fix`.

Note: not running `cargo fmt` — the backend uses a consistent
hand-formatted style that differs from rustfmt-default across ~110
files, so a blanket reformat would be pure churn.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:45:37 +02:00
MechaCat02
0865659ab3 fix(auth): keep session on a wrong-current-password 401
The change-password endpoint returns 401 for a wrong *current* password,
the same status the global on401 hook uses to detect an expired session.
The hook fired first and cleared session.user, so the account form's
"still signed in?" guard always saw null and bounced the user to /login
instead of surfacing the error inline.

Add a per-call `suppressOn401` option to `request`; changePassword opts
in, so a 401 there stays inline and the session is left intact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 20:11:07 +02:00
MechaCat02
5596e1920d fix(compose): wire ANALYSIS_OCR_MAX_DECODE_PIXELS into the backend service
The OCR decompression-bomb guard added the env var to .env.example and
config.rs but not docker-compose.yml, so the container never received it
and the compose-env-coverage test failed once the branches were integrated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 07:25:59 +02:00
MechaCat02
3cba9ecf95 feat(auth): support optional expiry for bot API tokens
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 07:22:57 +02:00
MechaCat02
ed18d95bb0 feat(crawler): guard Chromium chapter navigation against internal targets
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 07:22:10 +02:00
MechaCat02
2ed42f7b9e feat(crawler): re-validate redirect hops to close SSRF via 3xx
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 07:21:15 +02:00
MechaCat02
a3e53f303b test(chapters): lock open-contribution upload contract + document intent
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 07:19:04 +02:00
MechaCat02
2af421893f fix(crawler): don't hold browser mutex across Chromium close()
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 07:18:49 +02:00
MechaCat02
48f0439273 fix(crawler): evict idle hosts from the per-host rate-limiter map
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 07:18:35 +02:00
MechaCat02
de7aefef69 fix(auth): guard the login + change-password verify paths against oversized passwords
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 07:18:19 +02:00
MechaCat02
3ba30f4ba9 fix(login): close ?next= control-char open-redirect + cover register
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 07:18:01 +02:00
MechaCat02
4306d1c96a fix(frontend): strip hop-by-hop headers from proxied responses too
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 07:17:32 +02:00
MechaCat02
886caaecfa fix(settings): bound manga_limit + the timeout knobs the finding named
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 07:17:14 +02:00
MechaCat02
de510cc19a fix(admin): make force-reanalyze audit transactional; document storage exception
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 07:16:08 +02:00
MechaCat02
5b76e0cc37 fix(analysis): bound concurrent OCR inferences with a shared semaphore
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 07:14:19 +02:00
MechaCat02
66ae4b221b fix(analysis): cap OCR decoded pixels to stop decompression-bomb OOM
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 07:12:40 +02:00
MechaCat02
83c2899373 feat(analysis): add ocrs OCR backend and OCR-driven page text search
All checks were successful
deploy / test-backend (push) Successful in 28m40s
deploy / test-frontend (push) Successful in 10m36s
deploy / build-and-push (push) Successful in 11m8s
deploy / deploy (push) Successful in 12s
Adds an in-process ocrs OCR backend as the active analysis engine
(vision left dormant), enables OCR text search on the page-tag
aggregation endpoints, and reshapes the admin analysis/settings UI to
present the OCR-only surface. Bumps version 0.89.0 -> 0.90.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 19:52:43 +02:00