Skip to content

Show chapter names, and say which chapter Continue opens - #84

Merged
AngeloSha merged 35 commits into
AngeloSha:mainfrom
Squeaks72:pr/chapter-names
Sep 25, 2026
Merged

AngeloSha merged 35 commits into
AngeloSha:mainfrom
Squeaks72:pr/chapter-names

Conversation

@Squeaks72

Copy link
Copy Markdown
Contributor

Summary

Chapters never showed a name, only their number. The cause is in how they are stored. The downloader names every file from its number alone (Chapter 12.cbz), and the scanner takes lib_books.title from the filename, so the title ends up as the number again. The source's own name (out.chapterUsed.title) was available when each chapter landed, but nothing saved it.

This PR fixes that in four places and shows the result:

  • Store it when a chapter lands. setBookMeta now takes the landed copy's title from the add, download and nightly-sweep paths. A new chapterName() filter (bff/src/lib/library.ts) drops titles that only repeat the number (Chapter 12, Ch. 12, Episode 012, 12). Without it, a copy from a source that says "Chapter 12" would replace a real name that an earlier copy supplied.
  • Heal old chapters on every source check. replaceListing() already writes the source's listing for the same chapters, so it now fills any lib_books.title that is still just its number, using the listing title when that title is a real name. This fixes existing libraries without a migration, and a hand-set title never matches the pattern. On one real library this recovered names for about 866 chapter rows. Many sources only ever publish numbers, so plenty of rows stay number-only, and that is expected.
  • Keep names through a rescan. This was a bug I found while porting. persistScan's upsert ran title=EXCLUDED.title on every add, sweep and manual scan, which reset every stored name back to "Chapter N" whenever anything anywhere was fetched. Now the scan keeps the stored title when the filename says nothing but the number and the row holds a real name. A file that carries its own name (Chapter 4 - Title.cbz) is still titled from the file.
  • UI. A chapter row reads Ch. 12 · The Sound of Thunder (web/lib/format.ts chapterName(), the same filter). The series page names the chapter the Continue button opens, with page 7 of 23 when it is part-read.

Type

  • Fix
  • Feature
  • Docs
  • Refactor / chore

Testing

Re-run after rebasing onto v0.45.0 (91b41aa): bff 1861 tests, 1848 pass, 9 fail, 4 skipped — the same 9 embedded-Postgres entrypoint failures main has in this environment (main: 1857 / 1844 / 9 / 4), nothing new; web 432/432; both typechecks OK. The detail below is from the first run, on v0.44.0.

I ran the upstream CI setup in node:24-alpine against a real postgres:16-alpine (TEST_DATABASE_URL set, so the SQL tests run instead of skipping), first on plain upstream/main (a8bc502) to get a baseline and then on this branch.

upstream main this branch
bff tsc --noEmit OK OK
bff tests 1834 tests: 1821 pass, 9 fail, 4 skip 1838 tests: 1825 pass, 9 fail, 4 skip
web tsc --noEmit OK OK
web tests 421 / 421 422 / 422

The same 9 failures appear on both sides. They are the entrypointEmbedded tests, which need an embedded Postgres binary that this sandbox lacks. There are no new failures.

One upstream assertion changed. updater.int.test.ts › "only the chapters that landed are stamped…" pins the exact shape of r.landed. The sweep now also records the chapter's title there, so the expected object gains title: 'Chapter 6'. The test's intent is unchanged: a book that did not land keeps its stamp. The full run had this as its only extra failure. After the fix I re-ran the affected DB files (updater, chapterNames, scan, seriesListing, scanlatorStamp) and all 77 tests passed, which gives the totals in the table.

New tests:

  • bff/test/chapterNames.int.test.ts:
    • the name filter (a pure test that runs everywhere)
    • setBookMeta stores a real name, and a later bare title does not replace it
    • the listing heal fills only bare titles and leaves a hand-set title alone
    • a real persistScan over CBZs on disk keeps a stored name, and a file with its own name still wins. This one fails against the old title=EXCLUDED.title upsert.
  • web/test/format.test.ts: chapterName for the web.

The new UI string ({chapter} · page {page} of {pages}) is translated in all eight locale files. npm run test:i18n is a Puppeteer walk against a running server, so it could not run in this sandbox. The locale-parity unit test passes.

Checklist

  • Focused scope (one topic)
  • Matches the existing TypeScript style
  • Docs updated if user-facing (docs/USAGE.md)
  • No new default sources hardcoded to specific sites (the engine system reaches sites by URL)

🤖 Generated with Claude Code

Squeaks72 and others added 14 commits September 24, 2026 10:27
A downloaded chapter's file is named from its number alone, so the scanner's
title for it was the number said twice. Sources do give real names, and the
downloader already wrote them into each CBZ's ComicInfo -- nothing read them
back. setBookMeta now stores the landed copy's name, filtered so a source that
also just says "Chapter 12" does not replace one restatement with another,
and rows show it beside the number.

The series page also names the chapter the Continue button opens, with the
page position when one is part-read.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A chapter downloaded before the downloader learned to record the source's name
is stuck titled after its own file, which is named from the number alone. The
listing written on each check is the same data for the same chapters and is
already in hand, so replaceListing now fills in any book title that is still a
bare restatement of its number.

Only from a listing title that is not itself one: plenty of sources genuinely
call every chapter "Chapter 12", and swapping one restatement for another is
churn. A title set by hand never matches the pattern, so an override is safe.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The sweep is how most chapters arrive, and its Landed record carried the
group and the source but not the chapter's name, so a chapter fetched
overnight stayed "Chapter 12" until the next check's listing heal. It now
passes the name to setBookMeta like the add and download paths do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
persistScan's upsert re-derived every book title from its filename, and it
runs on every add, every sweep and every manual scan -- so each time anything
anywhere was fetched, every name in the library went back to "Chapter 12"
until that series' next check healed it. The scan now keeps the row's title
when the filename says nothing but the number and the row holds a real name;
a file that carries a name of its own still wins.

Tests: the name filter, the landed stamp, the listing heal and the rescan.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ptions

Library ratings could not express "keep ecchi off the shelf" without moving
series between libraries to get a display outcome. Two admin settings say it
directly -- genres to treat as adult, sources to treat as adult beyond what
their extension declares -- and a per-series adult_exempt keeps one wrongly
tagged classic visible without turning the whole filter off.

Still purely a surfacing filter: nothing here changes who may open a series.
browsable() cannot bind parameters, so the configured names are held to a
strict character shape (letters, digits, space, ' - + / & ( ) . ! :) on the
way in, again at the interpolation, and quoted with ' doubled on the way out.
GET /api/series/:id returns overrides.adultExempt so an editor can seed it,
and the settings PATCH and series meta PUT are described in openapi.yaml.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Admin -> Settings gets an "18+ filter" section after Notifications: one chip
per genre the library holds and one per source, each saving on its own. Both
lists are fetched with the reveal on, because with "Show 18+" off a ticked
genre or source is exactly what the listings stop returning -- a picker built
from them would lose the chip the moment it was lit. The series "Edit details"
dialog gets the matching "Always show" checkbox. Strings in all eight locales,
and a note in USAGE.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The keydown handler had no ArrowLeft/ArrowRight cases at all, so in paged
mode arrows fell through to the browser's native scrolling of the snap-x
track: the first press nudged it a few pixels and the snap pulled it back,
so it took two presses to land on the next page. Bind them (plus PageUp/
PageDown/Space) to the same scrollBy the tap zones use, with preventDefault
so the native scroll stops competing.

A focused form control (the page slider, the settings sheet) keeps its own
arrow keys and Space in both modes, so it can still be nudged by keyboard.
USAGE.md lists the reader's keys.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With a keyboard, pressing a letter or digit while no text field or dialog
has focus opens the command palette with that character already typed, so
searching the library is just "start typing". Ctrl/Cmd+K and "/" still
open it empty. Modifier chords, punctuation, IME composition, claimed
keys and open aria-modal dialogs are left alone; the reader is excluded
as before. The palette input now focuses in a layout effect so the
second keystroke isn't lost to <body>. The palette's action list,
including its Desktop filtering (DESKTOP_HIDDEN.paletteKeys), is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AdultToggle renders on its own only when the account holds an 18+ library, so
an install whose only adult content was a genre or source on the 18+ filter
hid those series with no Show 18+ button on Library or Home. GET
/api/adult-filter answers { configured } -- only the flag, never the lists,
and false for an account capped below 18 -- and Library and Home pass it to
the existing toggle as alsoWhen, the way Discover passes its hidden-source
count. A settings save invalidates it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A term submitted with a source chosen now asks only that source (&source= on
search-all) instead of fanning out to every one and answering with everything,
and the picker stays on screen so the filter is visible and clearable rather
than silently narrowing the results.

`source` only narrows the route's existing surfaceable() set, so an adult
source without adult=1, one above the account's age limit, or an unknown id
asks nobody and answers the empty shape. The search query's key now includes
the chosen source; discoverSearch.test.ts and discoverAdult.test.ts pin the
new key and the picker being mounted in both modes, and searchAll.int.test.ts
covers the narrowed fan-out. Documented in openapi.yaml, docs/api.md and
USAGE.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Upstream's reader already knows a series' readingDirection (the `rtl`
state, from the series or the downloaded record) but used it only to flip
the order of a double spread; the paged track always ran left to right.

New reader pref `pagedDirection` (series | ltr | rtl, default series).
With `series`, a RIGHT_TO_LEFT series lays paged mode's track out
right-to-left: the next page is to the left (swipe right, tap the left
edge, ←), and spreads put their first page on the right. Every other
series reads exactly as before -- including every series on the built-in
library, which reports WEBTOON. `ltr`/`rtl` override it for all series.
Toggle in the reader sheet and Profile → Reading.

The track now always states its `dir`. Under the Arabic UI it used to
inherit dir="rtl" from <html>, and the positive-scrollLeft maths left the
page counter on 1 and clamped a jump or resume to the first page.

On a right-to-left track the footer row takes dir="rtl": the page slider
fills and drags from the right, matching the page track, and
previous/next chapter swap sides with their chevrons swapped to keep
pointing outwards. The page counter stays LTR.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A source is the closest thing the app has to a format: a webtoon source wants
continuous vertical scroll, a manga source wants paged right-to-left. One
global default was wrong for half the library and the alternative was fixing
every series by hand.

Reader prefs now resolve global < source < series, and the settings sheet can
pin the current look to the chapter's source (or forget it again). Stored and
synced beside the per-series memory, under a key prefix that cannot be picked
up by the per-series collector.

Tests pin the prefix, the forget path and the pull; the three new strings are
translated in all eight locales, and USAGE.md describes the button.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t Continue

Chapter list
- The series page and the reader asked the books route for one page of
  1000, so One Piece's chapters 1001+ never appeared in the list or in the
  reader's prev/next/jump list. lib/seriesBooks.ts fetchAllBooks() walks
  every page (tested). The smart-offline planner had the same cap
  server-side.
- The list now renders 100 rows a page (lib/chapterPages.ts, tested) with
  a pager above and below (first/prev/range picker/next/last). It opens on
  the page holding the "Continue" chapter; a tap on the pager pins a page,
  and a new series, sort, or filter follows Continue again. A chapter chip
  in Sources & translations turns to the page holding that row before it
  scrolls to it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AngeloSha and others added 15 commits September 25, 2026 10:18
…per book

On top of @Squeaks72's paging, which fixes a real bug on main -- Continue opened chapter 1 for anyone past
chapter 1000:

- The picker named pages by row position. Ghost, run and "show all" rows are rows but not chapters, and
  newest first labelled the page holding 1193-1094 "1-100". Pages are now named by the first and last chapter
  numbers they show, in the list's own order, and the count beside it is chapters.
- The page followed Continue live, so expanding an older-chapters run, "Show all" or "Mark all read" flipped
  the list away from what the reader was doing. It is decided once per series, sort and filter, once the
  chapters and the sources' listing (whose ghost rows would shift it) have both arrived.
- A scan between the two requests could hand the same book back at the 1000 boundary: two rows, one React key.
  Kept once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ns keep their direction

On top of @Squeaks72's right-to-left paging, which also fixes a bug on main (under the Arabic interface the
paged track inherited RTL and the page counter stuck on page 1):

- Resume and `?page=` links opened a right-to-left series on page 1. A streamed series learns its direction in
  the same tick it becomes ready, so the new flip effect ran right after the resume scroll with `current` still
  0 in its closure. It now acts only on a real flip, tracked by the direction the track was last laid out for.
- In paged mode the bottom bar follows the track both ways. On a left-to-right read under the Arabic interface
  it inherited RTL and ran opposite to its own pages.
- Text inside the track (the missing-page caption, the Up Next and failure cards) took the track's direction:
  Arabic in an LTR paragraph, or English in RTL on a right-to-left read. It keeps the interface's, via useRtl().
- A double-tap zoom in paged mode centred scrollLeft, which there IS the page: it jumped to mid-chapter (page 1
  on a right-to-left track). Centring is for the webtoon column only.
- A right-to-left series forced to read left to right still swaps its spreads, on purpose: a double-page spread
  is one drawing and only reassembles with its first page on the right. Said so in the code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…keys leave sheets and controls alone

On top of @Squeaks72's paged-mode keys:

- Steps are computed from the slide on screen, not "one width from wherever the scroll is": scrollBy stacked on
  a smooth scroll still in flight, so two quick presses or a held key landed one or two pages on.
- A key with Alt, Ctrl or Meta is never the reader's. Alt+Left (browser Back) turned a page, and Ctrl+F opened
  the browser's Find AND toggled fullscreen -- the second one was already on main.
- With a sheet open (settings, page grid, chapter list) keys no longer act underneath it, and Escape closes the
  sheet instead of leaving the reader; none of the three listens for Escape itself.
- A focused button or link keeps Space and Enter: Space on a chapter row turned the page and swallowed the click.
- Shift+Space goes back a page, in both modes.
- On a right-to-left read (with the paged-direction change beneath this) Left and Right stay physical, like the
  tap zones, while Space, PageDown and Down mean "next" whichever way the pages run.
- The page slider lets go of focus after a drag, so in the webtoon column Space scrolls again straight away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	web/public/locales/ar.json
#	web/public/locales/de.json
#	web/public/locales/es.json
#	web/public/locales/fr.json
#	web/public/locales/ja.json
#	web/public/locales/pt-BR.json
#	web/public/locales/ru.json
#	web/public/locales/zh.json
…aks into the global one

On top of @Squeaks72's per-source reader settings:

- Choosing a look in the reader also saved it as the GLOBAL default: setPref saved the reader's live prefs
  wholesale, and those are the default with the source's and the series' settings laid over it. So "switch to
  paged, pin this source" left every other source opening paged too, and even a brightness change saved the
  source's look as everyone's. A change made with a title open now writes its look to that title only, and
  only what it touched to the global default, which is set under Profile -> Settings. globalPrefsChange() is
  the rule, and it has a test.
- The default never applied to a downloaded chapter, online or off: the reader loads the offline record first
  and it names no source, so `chapters[0].sourceId` was empty. A series mixing copies from several sources
  flipped its look mid-read. It is keyed by the series' primary source now, learned when the series loads
  and remembered on the device for opening it offline.
- The row could show a nineteen-digit extension id for a name. With no name there is no row.
- The reading direction from the paged-direction change beneath this is part of the remembered look, per
  series and per source ("this manga site: paged, right to left" can now be pinned).
- The settings sheet is capped at 85dvh and scrolls: with the two new rows it pushed its top off a short phone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…itched off, and loses no keys

On top of @Squeaks72's type-to-search:

- Under a Japanese or Chinese interface the palette opened holding the raw Latin letter and the IME composed
  after it ("w" + "あんぴーす"): with nothing focused the browser does not route keys through an input method.
  There it now opens empty and focused, and the IME composes the title from the next keystroke. Safari's
  spelling of an IME keystroke (keyCode 229, key "Process") also counts as composing.
- Single-key shortcuts must be possible to switch off (WCAG 2.1.4). Profile -> Settings -> "Type anywhere to
  search" does, on this device, and covers "/" as well. Ctrl/Cmd+K stays.
- A letter typed while the palette was open but its box had lost focus was swallowed. The palette is now a
  dialog (role, aria-modal), so the global handler stands down for it, and the open palette puts stray letters
  into its box.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aches every surface it promises

On top of @Squeaks72's configurable 18+ filter, whose permission model holds (max_age_rating stays the only
ceiling; hidden sources are still never asked; admin-only):

- The genre list was pasted into SQL as literals behind a character whitelist -- the one place in the codebase
  admin-entered data met a query string. browsable() now names the column and reads the list with
  jsonb_array_elements_text, so no value can escape anything. That also fixed three things at once:
  - notification digests ignored the genres (notify builds its context without loading the lists);
  - case was folded in JavaScript on the way in and by Postgres on the way out, so a genre the two lowercase
    differently never matched while its chip showed ticked -- both sides fold in SQL now;
  - the whitelist was dropping real genres ("Boys’ Love", Thai, Devanagari). Genres are stored as typed.
  With no genre configured an uncorrelated check short-circuits the clause, so listings pay nothing.
- "Find missing chapters" and Fetch answered 404 for a series the switch hid: POST /api/sources/fill/scan and
  /fetch looked the series up through browsable(). They act on a series someone opened, so they answer to
  visible() -- the permission. This was already true of 18+ libraries on main.
- Source ids went through the genre whitelist, which dropped `_` and anything past 60 characters: the chip lit,
  then went dark. They get their own shape (sanitiseSourceIds).
- The source hunt read only an extension's own flag, so a source the admin named adult could be followed onto a
  clean series by the failure hunt or the nightly repair. sweepAllowedFor now honours the list.
- docs/api.md describes the named genres and sources, and the two by-id routes; the token checkboxes say
  "Include 18+ content", since they now cover more than libraries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…never shown as one

@Squeaks72's change showed chapter names and which chapter Continue opens -- both good -- by writing names into
lib_books.title. But title is the FILENAME's: bookDto fills `name` and `metadata.title` from it, and on a
library built by hand that is `One Piece v02 c012 [Digital]`, which became the name on most rows. And every
screen that prints the title alone (reader header, History, offline Downloads, OPDS) would have lost the
chapter number once a name filled it.

- The source's name goes to its own nullable column, lib_books.chapter_name (a v0.46.0 block at the end of the
  DDL), and to `chapterName` on the book DTO. `title` is never touched again, so everything printing it reads
  exactly as before, and "Vol." is still decided from the filename.
- The scan's upsert is back to main's `title=EXCLUDED.title`: nothing to protect any more, and no per-row
  regex over a 40k-chapter library.
- chapterName() takes the volume, the chapter word and the number off the FRONT, in the ways sources write them
  ("Vol.3 Chapter 12", "Capítulo 12", "第12話", "12화"), and keeps what follows: "Vol.3 Chapter 12: The Return"
  is named "The Return". 12 does not match the front of 120 or 12.5. The listing heal uses the same rule in
  JavaScript, with an equality join, and fills only chapters that have no name yet.
- The web shows `chapterName` and nothing else -- no fallback to the filename.
- Continue no longer reads "page 0 of 23" after Mark unread.

Each fix was proved by undoing it alone: writing the name into `title` again, the whole-string rule, the column
dropped from booksSrc, and the web falling back to the filename each fail their own test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It was inserted into the middle of the v0.40.0 block (after auto_follow_on_failure, and beside the age-rating
columns), the same spot two other open pull requests had also chosen -- three-way conflicts on a line nobody
meant to share. Each release's DDL is appended at the end under its own version comment, as v0.43.0 did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tings sheet speaks every language

Two bugs found while reviewing the reader pull requests, both already on main:

- In paged mode with the default "Collapse", a repeated page got a slide of its own that never rendered: the
  render window skips collapsed pages, and a paged track has no thin band to draw one as. The reader saw a
  blank screen with a faint page number. There, Collapse now shows the page like any other -- one swipe.
  Not removed, although the sheet's comment said so: removing would give the two modes different page orders,
  and nothing re-anchors the current page when the flow is rebuilt, so switching mode mid-chapter would land on
  another page. The comment now says what happens.
- The reader settings sheet hard-coded English: Mode, Theme, Fit, Pages per view, Brightness, Page gap,
  Auto-scroll, the mode buttons and the theme names -- every one of them already translated in all eight
  locale files, just never asked for. The CSS `capitalize` that dressed up the raw theme ids is gone with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_meta.strings is the number of keys, and the Continue line's key went in mid-file without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Include 18+ libraries" became "Include 18+ content" when the admin's 18+ filter
started hiding more than libraries. USAGE.md still sent readers to
Profile -> Connections -> External readers -> Include 18+ libraries in this
reader, a place the app no longer has, and consoleNamesInDocs caught it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ranch

The PRs merge in order, so each branch carries the ones before it and merges without conflicts.
The PRs merge in order, so each branch carries the ones before it and merges without conflicts.

# Conflicts:
#	web/public/locales/ar.json
#	web/public/locales/de.json
#	web/public/locales/es.json
#	web/public/locales/fr.json
#	web/public/locales/ja.json
#	web/public/locales/pt-BR.json
#	web/public/locales/ru.json
#	web/public/locales/zh.json
The PRs merge in order, so each branch carries the ones before it and merges without conflicts.
The PRs merge in order, so each branch carries the ones before it and merges without conflicts.

# Conflicts:
#	web/public/locales/ar.json
#	web/public/locales/de.json
#	web/public/locales/es.json
#	web/public/locales/fr.json
#	web/public/locales/ja.json
#	web/public/locales/pt-BR.json
#	web/public/locales/ru.json
#	web/public/locales/zh.json
The PRs merge in order, so each branch carries the ones before it and merges without conflicts.

# Conflicts:
#	web/public/locales/ar.json
#	web/public/locales/de.json
#	web/public/locales/es.json
#	web/public/locales/fr.json
#	web/public/locales/ja.json
#	web/public/locales/pt-BR.json
#	web/public/locales/ru.json
#	web/public/locales/zh.json
The PRs merge in order, so each branch carries the ones before it and merges without conflicts.

# Conflicts:
#	web/public/locales/ar.json
#	web/public/locales/de.json
#	web/public/locales/es.json
#	web/public/locales/fr.json
#	web/public/locales/ja.json
#	web/public/locales/pt-BR.json
#	web/public/locales/ru.json
#	web/public/locales/zh.json
The PRs merge in order, so each branch carries the ones before it and merges without conflicts.

# Conflicts:
#	bff/src/lib/migrate.ts
#	web/public/locales/ar.json
#	web/public/locales/de.json
#	web/public/locales/es.json
#	web/public/locales/fr.json
#	web/public/locales/ja.json
#	web/public/locales/pt-BR.json
#	web/public/locales/ru.json
#	web/public/locales/zh.json
@AngeloSha
AngeloSha merged commit a717aeb into AngeloSha:main Sep 25, 2026
12 checks passed
AngeloSha added a commit that referenced this pull request Sep 25, 2026
… extension engine

Right-to-left paging (#90), one key per page (#92), reader settings pinned to
a source (#95), type-to-search (#94), chapter names (#84), a chapter list that
pages past chapter 1000 (#89), Discover keeping its source through a search
(#87) and an 18+ filter an admin can widen (#86) -- seven merged with fixes on
top, one as it was. The extension engine (Suwayomi-Server, headless) gets the
desktop app's memory cap in every compose file (#72).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AngeloSha

Copy link
Copy Markdown
Owner

Thanks for this. Chapter names are something I've wanted for a while, and saying which chapter Continue opens is a really nice touch.

I had to change how the name is stored before merging. lib_books.title is the filename's, and on a library someone built by hand that's things like One Piece v02 c012 [Digital], so it would have shown up as the chapter's name on most rows. And the reader header, History, Downloads and OPDS all print the title on its own, so they'd have lost the chapter number as soon as a name filled it in.

So the name now has its own column, chapter_name, and the title is never touched. The bare-title check also strips volume prefixes and chapter words in other languages ("Vol.3 Chapter 12", "Capítulo 12", "第12話") and keeps whatever comes after the number, so "Vol.3 Chapter 12: The Return" gets named "The Return". And Continue no longer says "page 0 of 23" after Mark unread.

Each change is its own commit on your branch with the reasoning. Merged, and it shipped in v0.46.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants