Skip to content

fix(english/novelbuddy): restore search results and clamp query length - #2681

Open
RibatTRW wants to merge 4 commits into
lnreader:masterfrom
RibatTRW:fm/lnreader-novelbuddy-search
Open

RibatTRW wants to merge 4 commits into
lnreader:masterfrom
RibatTRW:fm/lnreader-novelbuddy-search

Conversation

@RibatTRW

@RibatTRW RibatTRW commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Intent

Fix the broken novelbuddy source so a reader can search it again, and include screenshots of the working fix in the PR.

plugins/english/novelbuddy.ts works for browsing and reading, but search does not: the plugin's own live check fails searchNovels with Cannot read properties of undefined (reading 'items'), while the site's search API (api.novelbuddy.me/titles/search) answers 200 with about 64 KB of data. So the site is up and returning search results - the plugin is reading an envelope that no longer exists. This is one of the plugins this fleet's plugin health sweep confirmed as genuinely broken today (not a site-side block).

Make sure the fix's pull request shows a photo of the plugin working correctly inside the plugin playground, and run that screenshot process headless if possible.

What Changed

  • Search requests now go through a shared searchTitles helper that checks the HTTP status and throws a clear NovelBuddy search failed with HTTP <status> error when the response is not OK or has no data.items, instead of failing with an undefined-property error. Both the filtered popular-novels search and the search-by-term path use it.
  • Search queries are clamped to 50 characters (clampQuery) before being sent, avoiding HTTP 400 responses from the search API for longer queries. The cut never leaves a lone surrogate half.
  • Plugin version bumped from 2.1.3 to 2.1.4.

Risk Assessment

🚨 High: The change does not touch the response parsing that the reported failure points to, so the stated search breakage is likely still present, and the query-trim rationale is unverified in the repo.

Testing

Reproduced the reported failure with npm run check:plugin against the base commit (searchNovels FAIL with the exact TypeError), then confirmed the target commit passes all four live steps (popularNovels, searchNovels, parseNovel, parseChapter). Stood up the real playground (Vite on a loopback address so the playground's hardcoded localhost:3000 proxy matches, because ports 3000-3004 were held by other sessions), drove NovelBuddy's Search tab in headless Chromium, and captured screenshots of a 52-character title search returning results. Also drove a 150+ character query (the network request was confirmed as the 50-character clamp), a short query, and a 49-ASCII-character plus emoji query that stays error-free. Probed api.novelbuddy.me directly to confirm the 50-character boundary (50 returns 200, 51 returns 400). Cleaned up the dev server, headless browser, generated multisrc output, and temp files; the worktree is clean.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
Search for a 52-character title in the playground returns results (the query the live check fails on) ✅ pass live ~/.no-mistakes/evidence/01M4ZP064CS74MD9J4PPS1G1Y6/novelbuddy-search-long-query-playground.png
Search for a 150+ character query in the playground returns results without an error; the request is clamped to 50 characters ✅ pass live ~/.no-mistakes/evidence/01M4ZP064CS74MD9J4PPS1G1Y6/novelbuddy-search-overlong-query-playground.png (network request https://api.novelbuddy.me/titles/search?q=Reincarnation+Of+The+Strongest…
Search for a short query ('solo leveling') in the playground returns results ✅ pass live playground accessibility snapshot showing 'Solo Leveling: Digispirit.', 'MTL-Solo Leveling', 'Solo Leveling- Ragnarok' result headings (no screenshot saved for this scenario)
Adversarial: a query whose 50th code unit starts an emoji does not show an error in the playground ✅ pass live playground snapshot shows 'No results found' rather than 'Failed to fetch'; direct API probe of 49 'a' characters returns 200 with empty items. The exact request URL for this query was not captured, s…
Live check searchNovels on the base commit reproduces the reported TypeError ✅ pass live npm run check:plugin output for base commit 49b4727 (temporary copy, removed afterwards): searchNovels FAIL 'Cannot read properties of undefined (reading 'items')'
Live check of the fixed plugin passes all four steps (popular, search, parseNovel, parseChapter) ✅ pass live npm run check:plugin -- plugins/english/novelbuddy.ts output: all four PASS
  • Evidence: Playground: NovelBuddy search for 52-char title returns results (PR screenshot) (local file: ~/.no-mistakes/evidence/01M4ZP064CS74MD9J4PPS1G1Y6/novelbuddy-search-long-query-playground.png)
  • Evidence: Playground: NovelBuddy search for 150+ char query returns results (query clamped to 50 chars) (local file: ~/.no-mistakes/evidence/01M4ZP064CS74MD9J4PPS1G1Y6/novelbuddy-search-overlong-query-playground.png)
Evidence: Live check, base commit: searchNovels FAIL
| `searchNovels` | ❌ FAIL | Cannot read properties of undefined (reading 'items') |
Evidence: Live check, target commit: all four steps PASS
| `popularNovels` | ✅ PASS | 24 novels |
| `searchNovels` | ✅ PASS | 2 results for "Reincarnation Of The Strongest Sword God (Web Novel)" |
| `parseNovel` | ✅ PASS | 3956 chapters |
| `parseChapter` | ✅ PASS | 17954 chars |

Screenshots

These come from this repo's plugin playground (npm run dev:start, NovelBuddy selected), with this source's live results on screen. They do not come from the LNReader app: the Android toolchain is not installed on the machine that made this change, so the fix has not been run in the app. The screenshots were captured on commit e5c7dd9, before the later review fixes (the word-boundary trim was removed in a later commit), so the search results they show are from that earlier head.

  1. Popular listing in the playground:

Plugin playground: NovelBuddy popular listing

  1. Search for the full 52-character title Reincarnation Of The Strongest Sword God (Web Novel), which used to fail. Results are on screen:

Plugin playground: NovelBuddy search results for the 52-character title

  1. Novel detail, with metadata and summary:

Plugin playground: NovelBuddy novel detail

  1. Chapter list:

Plugin playground: NovelBuddy chapter list

  1. Chapter text:

Plugin playground: NovelBuddy chapter text

Reproduction (live, 2026-10-15)

The search API's status depends on the query length. The requests below were sent directly to https://api.novelbuddy.me/titles/search?q=<query>&limit=24&page=1:

Query Length HTTP status Body
50 × a 50 200 data.items present, empty list (218 bytes)
51 × a 51 400 success: false, "Search query must be between 1 and 200 characters" (292 bytes)
Reincarnation Of The Strongest Sword God (Web Novel) 52 400 same error body (293 bytes)
Reincarnation Of The Strongest Sword God 40 200 data.items with 24 results

A 400 has no data, so body.data.items throws Cannot read properties of undefined (reading 'items'). That is the error the live check reported on the base commit. The 50-character cap in clampQuery keeps queries within the limit the API accepts. The API's error message says 200 characters, but the measured limit is 50. The shared searchTitles helper now throws a clear HTTP <status> error instead of the undefined-property error when a response has no data.items.

AI disclosure

This PR was written with AI assistance (Claude). The change, its validation runs and the write-up were AI-driven. No human has reviewed it yet.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 8 issues (1 error, 4 warnings, 3 infos)
  • 🚨 plugins/english/novelbuddy.ts:30 - Intent conformance: the required behavior is that search works again. The reported failure is Cannot read properties of undefined (reading &#39;items&#39;), which means the API response no longer has data.items. This diff does not change how the response is parsed. parseNovels still reads body.data.items at plugins/english/novelbuddy.ts:30, and searchNovels still passes the raw body to it at lines 205-208. The only search change is query trimming (line 204). If the envelope change is the root cause, the same TypeError stays reachable on every search. The commit message and version bump claim a fix for a 400 on long queries, which is a different failure from the one the intent describes. Remedy needs your decision: confirm the actual response shape from api.novelbuddy.me/titles/search and update parseNovels/searchNovels to read it, or justify why the 50-char trim addresses the items error.
  • ⚠️ plugins/english/novelbuddy.ts:13 - The 50-character limit is asserted in a code comment and commit message with no supporting evidence in the repo, and it is not the failure the intent reports. clampQuery (lines 13-20) and both call sites (line 61 in popularNovels, line 204 in searchNovels) are new behavior whose necessity depends on that unverified claim. If the claim is wrong, this is an extra behavior change to the query.
  • ℹ️ plugins/english/novelbuddy.ts:16 - clampQuery can split a UTF-16 surrogate pair at index 50. query.slice(0, 50) keeps a lone high surrogate when the 50th code unit starts an emoji or other astral character, and that lone surrogate is then URL-encoded. The word-boundary fallback only helps when there is a space before it.

🔧 Fix applied.
6 issues (1 error, 3 warnings, 2 infos) still open:

  • 🚨 plugins/english/novelbuddy.ts:30 - Intent conformance: the required behavior is that search works again. The reported failure is Cannot read properties of undefined (reading &#39;items&#39;), which means the API response no longer has data.items. This diff does not change how the response is parsed. parseNovels still reads body.data.items at plugins/english/novelbuddy.ts:30, and searchNovels still passes the raw body to it at lines 205-208. The only search change is query trimming (line 204). If the envelope change is the root cause, the same TypeError stays reachable on every search. The commit message and version bump claim a fix for a 400 on long queries, which is a different failure from the one the intent describes. Remedy needs your decision: confirm the actual response shape from api.novelbuddy.me/titles/search and update parseNovels/searchNovels to read it, or justify why the 50-char trim addresses the items error.
  • ⚠️ plugins/english/novelbuddy.ts:13 - The 50-character limit is asserted in a code comment and commit message with no supporting evidence in the repo, and it is not the failure the intent reports. clampQuery (lines 13-20) and both call sites (line 61 in popularNovels, line 204 in searchNovels) are new behavior whose necessity depends on that unverified claim. If the claim is wrong, this is an extra behavior change to the query.
  • ℹ️ plugins/english/novelbuddy.ts:16 - clampQuery can split a UTF-16 surrogate pair at index 50. query.slice(0, 50) keeps a lone high surrogate when the 50th code unit starts an emoji or other astral character, and that lone surrogate is then URL-encoded. The word-boundary fallback only helps when there is a space before it.
  • ⚠️ plugins/english/novelbuddy.ts:78 - searchTitles parses the body with result.json() before checking the response, so a non-JSON error response (e.g. an HTML 403/503 from a block page) throws a SyntaxError and never reaches the new HTTP ${result.status} error. Check result.ok/result.status first, or read the body as text and parse it afterwards. Sibling: the same unchecked json() pattern in parseChapter's chapters/chapter fetches is pre-existing and out of this change's scope.
  • ⚠️ plugins/english/novelbuddy.ts:19 - Simplification: the word-boundary fallback in clampQuery (lastIndexOf(&#39; &#39;) and the .trim()) is not required by the user's stated remedy (keep clampQuery, avoid splitting a surrogate pair, no other behavior changes). It changes what is searched, e.g. a 60-character query is cut back to its last whole word rather than to 50 characters. Remedy: remove the word-boundary branch and keep only the length cap plus the surrogate guard (cut = query.slice(0, 50), drop a trailing high surrogate). This is a product-behavior choice, so it needs the author's decision.
  • ℹ️ plugins/english/novelbuddy.ts:71 - The shared searchTitles helper also changes popularNovels (line 71): browse requests with a keyword now go through clampQuery and the HTTP-status error instead of a TypeError on the same malformed response. The user's remedy was scoped to searchNovels ('no other behavior changes'). The change is confined to the error message and the keyword clamp that the user already accepted, so it is noted, not blocking.

🔧 Fix applied.
8 issues (1 error, 4 warnings, 3 infos) still open:

  • 🚨 plugins/english/novelbuddy.ts:30 - Intent conformance: the required behavior is that search works again. The reported failure is Cannot read properties of undefined (reading &#39;items&#39;), which means the API response no longer has data.items. This diff does not change how the response is parsed. parseNovels still reads body.data.items at plugins/english/novelbuddy.ts:30, and searchNovels still passes the raw body to it at lines 205-208. The only search change is query trimming (line 204). If the envelope change is the root cause, the same TypeError stays reachable on every search. The commit message and version bump claim a fix for a 400 on long queries, which is a different failure from the one the intent describes. Remedy needs your decision: confirm the actual response shape from api.novelbuddy.me/titles/search and update parseNovels/searchNovels to read it, or justify why the 50-char trim addresses the items error.
  • ⚠️ plugins/english/novelbuddy.ts:13 - The 50-character limit is asserted in a code comment and commit message with no supporting evidence in the repo, and it is not the failure the intent reports. clampQuery (lines 13-20) and both call sites (line 61 in popularNovels, line 204 in searchNovels) are new behavior whose necessity depends on that unverified claim. If the claim is wrong, this is an extra behavior change to the query.
  • ℹ️ plugins/english/novelbuddy.ts:16 - clampQuery can split a UTF-16 surrogate pair at index 50. query.slice(0, 50) keeps a lone high surrogate when the 50th code unit starts an emoji or other astral character, and that lone surrogate is then URL-encoded. The word-boundary fallback only helps when there is a space before it.
  • ⚠️ plugins/english/novelbuddy.ts:78 - searchTitles parses the body with result.json() before checking the response, so a non-JSON error response (e.g. an HTML 403/503 from a block page) throws a SyntaxError and never reaches the new HTTP ${result.status} error. Check result.ok/result.status first, or read the body as text and parse it afterwards. Sibling: the same unchecked json() pattern in parseChapter's chapters/chapter fetches is pre-existing and out of this change's scope.
  • ⚠️ plugins/english/novelbuddy.ts:19 - Simplification: the word-boundary fallback in clampQuery (lastIndexOf(&#39; &#39;) and the .trim()) is not required by the user's stated remedy (keep clampQuery, avoid splitting a surrogate pair, no other behavior changes). It changes what is searched, e.g. a 60-character query is cut back to its last whole word rather than to 50 characters. Remedy: remove the word-boundary branch and keep only the length cap plus the surrogate guard (cut = query.slice(0, 50), drop a trailing high surrogate). This is a product-behavior choice, so it needs the author's decision.
  • ℹ️ plugins/english/novelbuddy.ts:71 - The shared searchTitles helper also changes popularNovels (line 71): browse requests with a keyword now go through clampQuery and the HTTP-status error instead of a TypeError on the same malformed response. The user's remedy was scoped to searchNovels ('no other behavior changes'). The change is confined to the error message and the keyword clamp that the user already accepted, so it is noted, not blocking.
  • ⚠️ plugins/english/novelbuddy.ts:13 - Stale commit message: e5c7dd9 ("trim search queries to the 50-character API limit") says queries are trimmed "to a word boundary", but 64ab656 removed the word-boundary branch, so clampQuery (lines 13-20) now only caps at 50 code units and drops a trailing lone high surrogate. The user required the commit message to record the reproduced 400-vs-200 evidence; the message currently describes behavior the code no longer has and omits the 200 side of that evidence. Remedy is rewording the commit message (amend or squash on the branch), not a code change.
  • ℹ️ plugins/english/novelbuddy.ts:77 - Error message is misleading when the response is 2xx but has no data.items (e.g. a 200 with data: null): searchTitles line 77 reports HTTP 200 as the failure, which reads like a status error when the status was fine. The 2xx-without-items path is not the 400 the user asked to surface. Rephrase the message to say the response had no data.items, and keep the status in it. Not a regression: before this change the same response threw a TypeError.
✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
Search for a 52-character title in the playground returns results (the query the live check fails on) ✅ pass live ~/.no-mistakes/evidence/01M4ZP064CS74MD9J4PPS1G1Y6/novelbuddy-search-long-query-playground.png
Search for a 150+ character query in the playground returns results without an error; the request is clamped to 50 characters ✅ pass live ~/.no-mistakes/evidence/01M4ZP064CS74MD9J4PPS1G1Y6/novelbuddy-search-overlong-query-playground.png (network request https://api.novelbuddy.me/titles/search?q=Reincarnation+Of+The+Strongest…
Search for a short query ('solo leveling') in the playground returns results ✅ pass live playground accessibility snapshot showing 'Solo Leveling: Digispirit.', 'MTL-Solo Leveling', 'Solo Leveling- Ragnarok' result headings (no screenshot saved for this scenario)
Adversarial: a query whose 50th code unit starts an emoji does not show an error in the playground ✅ pass live playground snapshot shows 'No results found' rather than 'Failed to fetch'; direct API probe of 49 'a' characters returns 200 with empty items. The exact request URL for this query was not captured, s…
Live check searchNovels on the base commit reproduces the reported TypeError ✅ pass live npm run check:plugin output for base commit 49b4727 (temporary copy, removed afterwards): searchNovels FAIL 'Cannot read properties of undefined (reading 'items')'
Live check of the fixed plugin passes all four steps (popular, search, parseNovel, parseChapter) ✅ pass live npm run check:plugin -- plugins/english/novelbuddy.ts output: all four PASS
  • npm run check:plugin -- plugins/english/novelbuddyBaseTmp.ts (temporary copy of base commit 49b4727): searchNovels FAIL with Cannot read properties of undefined (reading &#39;items&#39;), others PASS
  • npm run check:plugin -- plugins/english/novelbuddy.ts (target 64ab656): popularNovels, searchNovels, parseNovel, parseChapter all PASS
  • curl probes of api.novelbuddy.me/titles/search: 50-char q → 200, 51-char q → 400, 52-char title → 400, 'solo leveling' → 200 with data.items, 49 'a's → 200 empty
  • Playground at http://127.0.0.1-mapped localhost:3000 (npx vite --host 127.0.0.2 --port 3000 --strictPort, Chromium with --host-resolver-rules MAP localhost 127.0.0.2): selected NovelBuddy, Search tab, searched 'Reincarnation Of The Strongest Sword God (Web Novel)' → results rendered; screenshot saved
  • Playground search for 'solo leveling' → results rendered (Solo Leveling: Digispirit., MTL-Solo Leveling, etc.)
  • Playground search for a ~150-char query → results rendered; captured network request q=Reincarnation Of The Strongest Sword God (Web Nove (50-char clamp)
  • Playground search for 49 'a's + emoji + extra → 'No results found', no 'Failed to fetch' error
  • npm run clean:multisrc and git status check to remove transient generated output
⚠️ **Document** - 1 warning
  • ⚠️ plugins/english/novelbuddy.ts:10 - The clampQuery comment states the 400-vs-200 behavior as 'Reproduced against api.novelbuddy.me/titles/search', but no repro, request log, or test is recorded in the repo. The diff also does not change how data.items is parsed, so the reported 'reading items' TypeError is addressed only if the 400 on long queries is the root cause. Needs author confirmation: either attach the repro evidence, or soften the comment to match what was verified.

🔧 Fix applied.
1 warning still open:

  • ⚠️ plugins/english/novelbuddy.ts:10 - Commit messages on this branch (e5c7dd9, aa20e12) still say the 400-vs-200 behavior was 'reproduced' and describe a word-boundary trim that 64ab656 removed. The code comment is now corrected, but git history was not rewritten. Amending or squashing these commits is a branch-history decision, so it is left for the author.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…limit

The search API answers HTTP 400 to any query over 50 characters, and full
novel titles such as "Reincarnation Of The Strongest Sword God (Web Novel)"
exceed that. The 400 body carries no `data`, so searchNovels threw
"Cannot read properties of undefined (reading 'items')". Trim queries to a
word boundary before sending them, for search and the keyword filter.

Bump version to 2.1.4.
@greptile-apps

greptile-apps Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable issues were found.

Summary

Updates NovelBuddy search and bumps the plugin version to 2.1.4.

  • NovelBuddy searches cap long queries and check their replies.

Acknowledgments in RibatTRW's supplied PR description: the misleading error for a successful response without data.items is explicitly noted as non-blocking; broader keyword clamping is described as accepted. Earlier word-boundary, surrogate, and response-check concerns are addressed by the final diff. Stale commit-message concerns are explicitly left for the author.

Reviews (1) · Last reviewed commit: "no-mistakes(document): soften clampQuery..." · Reviewed by Greptile

This branch has not been deployed

No deployments
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.

1 participant