Skip to content

fix(indexer): match elided titles when the release separates the apostrophe (#2762) - #2763

Merged
vavallee merged 2 commits into
vavallee:mainfrom
misterk72:fix/2762-elided-titles-second-pass
Sep 27, 2026
Merged

vavallee merged 2 commits into
vavallee:mainfrom
misterk72:fix/2762-elided-titles-second-pass

Conversation

@misterk72

@misterk72 misterk72 commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #2762.

FoldForTitleMatch deletes both apostrophe forms so a possessive collapses to one token
(Ender's -> enders), which is deliberate and pinned by two tests. A release name keeps the
character as a separator (Stephen.King.L.Outsider.2018.FRENCH), so a title that elides with an
apostrophe (L'Outsider, l'isola) folded to a token no release name contains: the search
returned zero results for a release the indexer had answered with, and reported nothing.

The fix adds a second reading of the same alphabet, tried only after the strict one fails, at
every point that judges a release:

  • newznab.titleHasRelevantResult -- the indexer response gate, which drops the whole response
    before the cascade advances;
  • filterRelevantDebug -- the interactive search, and auto-grab, since SearchBookWithOutcomes
    projects it;
  • filterRelevant -- the searcher own relevance filter, the only one that also applies the
    identity gate.

Nothing that matches today changes behaviour: the strict reading is attempted first, the second
pass is skipped when the two readings are identical, and it returns nothing for a title with no
apostrophe. No fold changes and no stored key changes, so there is no Rev bump, no backfill and
no new row in the shared fixture corpus. docs/search-design.md gains a section stating the
divergence and why the strict fold stays primary.

Checklist

  • Commits signed off with git commit -s -- see Sign your work
  • Tests added or updated
  • docs/DEPLOYMENT.md updated if env vars, config, or upgrade path changed -- not applicable: no env var, config or upgrade path changed
  • Added a changelog fragment under changelog.d/ (not an edit to CHANGELOG.md) -- see changelog.d/README.md
  • Wiki pages updated if user-facing behaviour changed -- nothing on the wiki describes the fold or the elision case, so there is nothing to correct there

Test plan

  • go test ./cmd/... ./internal/... -- 57 packages, exit 0, run with Go 1.26.6, the version CI uses
  • gofmt -l . and go vet ./... -- clean across the repository
  • golangci-lint run v2.11.4 on the changed packages -- 0 issues
  • Coverage of the changed packages: internal/indexer 94.2%, internal/indexer/newznab 87.1%, internal/textutil 86.0%
  • cd web && npm run build -- not run: no file under web/ is touched by this change
  • Measured on a live instance before and after, against the same indexer and the same book: L'Outsider 0 -> 2 releases, the typographic apostrophe 0 -> 2, the space spelling 2 -> 2, possessive matching unchanged

Each added test fails without its part of the change: one per gate, plus one asserting the two
filter paths agree, which is the test kind the project already carries from #713.

…trophe (vavallee#2762)

FoldForTitleMatch deletes both apostrophe forms so a possessive collapses to one
token ("Ender's" -> "enders"), which is deliberate and pinned by its own tests.
A release name keeps the character as a separator instead
("Stephen.King.L.Outsider.2018.FRENCH"), so a title that elides with an
apostrophe ("L'Outsider", "l'isola") folded to a token no release name contains.
The search answered zero results for a release the indexer had returned, and
reported nothing.

Try a second reading of the same alphabet, only after the strict one fails, at
every point that judges a release: the indexer response gate
(newznab.titleHasRelevantResult), the interactive and auto-grab filter
(filterRelevantDebug), and the searcher's own relevance filter (filterRelevant).
The strict reading is always attempted first, the second pass is skipped when the
two readings are identical, and it returns nothing for a title with no apostrophe.

No fold changes and no stored key changes, so there is no Rev bump and no backfill.

Signed-off-by: Kassabji, Christophe <4427381+misterk72@users.noreply.github.com>
@github-actions github-actions Bot added the bindery-notified Discord notification already sent for this PR label Sep 24, 2026
@codecov

codecov Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…ilter

Codecov reported one uncovered line in the previous commit: the subtitle
branch of filterRelevantDebug, where primaryTitle ("Dune: Messiah" -> "Dune")
makes the searcher consider the title's primary half as well. A release
routinely names the book without its subtitle, so that half needs the same
elision reading.

Reverting the four changed files to their pre-fix state fails this test,
together with the four elision cases and the test asserting the two filter
paths agree. The first attempt at the red proof was inconclusive because
removing the fallback from that single line left the variable unused and the
package failed to build rather than failing a test.

Signed-off-by: Kassabji, Christophe <4427381+misterk72@users.noreply.github.com>

@vavallee vavallee left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, and thanks for measuring it. Strict match first with the elided reading only as a fallback keeps possessives exactly as they were. I did look at the identity gate: the elided reading can shrink it to one word (Sac d'os gives [sac]), but tryMatch still needs the author tokens, so any widening stays within one author. Tests fail on main for the stated reason, and it merges and passes on current main.

@misterk72

Copy link
Copy Markdown
Contributor Author

Thanks for the review, and for checking the identity gate — that was the part I was least sure about.

I verified your reading: in internal/indexer/searcher.go, tryMatch passes the author token sets into titleMatchesResult, so even when the elided reading collapses the primary title to a single token, the author tokens are still required and the widened comparison cannot cross into another author. Good to have it pinned at that gate rather than holding by accident.

The branch is 11 commits behind main without conflicts. You said it merges and passes on current main, so it is ready as it stands; happy to rebase onto main and re-run CI first if you prefer a linear history before merging.

Happy to add a test covering the case you raised, where the elided reading reduces the primary title to one token (Sac d'os), if you want that behaviour pinned in the suite — say the word and I will push it as a separate commit.

For what it is worth, the patch is running locally on a French library where the affected titles returned nothing at all before; those now match.

@vavallee

Copy link
Copy Markdown
Owner

Heads up, nothing for you to do yet. I found today that the relevance filter's two copies had drifted: #2502's guards only ever reached filterRelevant, and every real search runs filterRelevantDebug. #2812 merges them into one filterRelevantDetailed. Your elision fallback is in both copies, so whichever of these lands second has to fold it into the one function. If #2812 goes first I'll do that rebase for you rather than bounce it back.

@vavallee
vavallee merged commit 648d3f1 into vavallee:main Sep 27, 2026
41 checks passed
@misterk72
misterk72 deleted the fix/2762-elided-titles-second-pass branch September 27, 2026 04:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bindery-notified Discord notification already sent for this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Elided titles never match releases that separate the apostrophe (French L'Outsider): measured 0 results at three separate gates

2 participants