From b22ae82897c51c7e4407d65f4c33e68b68fcb2f6 Mon Sep 17 00:00:00 2001 From: Sulthan Zaki Date: Mon, 10 Aug 2026 18:12:03 +0700 Subject: [PATCH] Restore the novel script's lost module-scope constants (#74) (#75) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > *This was generated by AI during triage.* Fixes #74. `novel-bookmark.user.js` was split out of `manga-bookmark.user.js` and lost four module-scope constants. Every use of them is behind a `try/catch` or a fire-and-forget promise, so the `ReferenceError`s were swallowed rather than reported. | constant | used at | effect while missing | | --- | --- | --- | | `LATEST_CHECK_THROTTLE_MS` | `:722` | `backgroundRefreshLatest()` throws before computing `due` — no background latest-check ever runs for novels (the symptom in #74) | | `LATEST_CHECK_BATCH` | `:724` | same throw | | `CACHE_KEY` | `:215`, `:224` | `loadCache()` always returns `[]`, `saveCache()` silently no-ops — the local cache never persists | | `LASTCHECKED_KEY` | `:232`, `:241` | last-checked map never persists, so the throttle would not hold even once the first two are defined | #74 named only the two throttle constants. The two cache keys are the same lost lines with the same root cause, so they are restored here too — fixing only the pair the issue named would leave `backgroundRefreshLatest()` re-fetching every series on every navigation, because `saveLastChecked()` would still be a no-op. Values and comments copied verbatim from `manga-bookmark.user.js:37-43`; throttle 4h, batch 1. ## Verification - `node --check userscript/novel-bookmark.user.js` — clean. - `node --test userscript/test/logic.test.js userscript/test/novel-logic.test.js` — 47/47 pass. - New test `every SCREAMING_CASE constant the script uses is declared in it` scans both scripts (comments and string literals stripped first, so prose and SVG path data do not trip it). Confirmed it fails — 1 failing test — when `LATEST_CHECK_BATCH` is deleted again, and passes when restored. A behavioural test cannot reach this: the storage helpers and the background refresh are exactly the layers the harness does not cover (see the `testing-the-userscript` skill), and the errors are swallowed anyway. A static guard is the only instrument that sees this bug class. On-device confirmation that the ember now lights for novels is still outstanding — that needs Violentmonkey against a live novelfull/lightnovelworld page. Reviewed-on: https://gitea.violetcrown.my.id/sulthan/mangaBookmark/pulls/75 Co-authored-by: Sulthan Zaki Co-committed-by: Sulthan Zaki --- userscript/novel-bookmark.user.js | 7 +++++++ userscript/test/novel-logic.test.js | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/userscript/novel-bookmark.user.js b/userscript/novel-bookmark.user.js index 7a89952..cca85f8 100644 --- a/userscript/novel-bookmark.user.js +++ b/userscript/novel-bookmark.user.js @@ -25,6 +25,13 @@ // This script owns the novel library; the manga script is a separate install // with its own prefix, so the two never share a cache, a queue or a panel. const STORE_PREFIX = "bmgr:novel:"; + const CACHE_KEY = STORE_PREFIX + "cache"; + + // Per-device record of when each series was last checked for new chapters. + // Deliberately not synced: each device does its own checking. + const LASTCHECKED_KEY = STORE_PREFIX + "lastchecked"; + const LATEST_CHECK_THROTTLE_MS = 4 * 60 * 60 * 1000; + const LATEST_CHECK_BATCH = 1; // series fetched per navigation // series fetched per navigation // Which library this script's rows belong to. The manga script is a separate // install that declares "manga"; the backend keeps whichever it is told. diff --git a/userscript/test/novel-logic.test.js b/userscript/test/novel-logic.test.js index 62e689e..926e8c7 100644 --- a/userscript/test/novel-logic.test.js +++ b/userscript/test/novel-logic.test.js @@ -165,3 +165,28 @@ test("kindOf defaults a missing kind to manga", () => { test("kindOf passes through novel", () => { assert.equal(kindOf({ kind: "novel" }), "novel"); }); + +// ============================================================ +// Source guard +// +// Issue #74: the novel script was split off the manga one and lost four +// module-scope constants. The reads sit inside try/catch or a fire-and-forget +// promise, so the ReferenceError never surfaced — nothing but a static check +// catches this class. +// ============================================================ + +test("every SCREAMING_CASE constant the script uses is declared in it", () => { + const fs = require("node:fs"); + for (const f of ["novel-bookmark.user.js", "manga-bookmark.user.js"]) { + const src = fs.readFileSync(require.resolve("../" + f), "utf8") + // comments and strings carry prose and SVG path data in the same shape + .replace(/\/\/[^\n]*|\/\*[\s\S]*?\*\/|"[^"\n]*"|'[^'\n]*'|`[\s\S]*?`/g, " "); + const declared = new Set( + [...src.matchAll(/\b(?:const|let|var|function)\s+([A-Z][A-Z0-9_]{2,})\b/g)].map((m) => m[1]), + ); + for (const name of new Set(src.match(/\b[A-Z][A-Z0-9_]{2,}\b/g) || [])) { + if (name.startsWith("GM_") || name in globalThis) continue; + assert.ok(declared.has(name), `${f} uses ${name} but never declares it`); + } + } +});