One registry entry per Site, and one shared Series-page read for the Poll and the Acquisition #94

Closed
opened 2026-08-11 23:59:46 +07:00 by sulthan · 0 comments
Owner

Problem Statement

Adding support for a new Site means finding six unrelated places that compare
the site string, spread across three files in the latest package: the Latest
Chapter parse switch, the Cover parse switch, the browser-backed Site list, the
fetcher choice, the host pin, and the payload read inside the browser. Nothing
ties them together and nothing fails at compile time, so a Site wired into five
of the six works in development and then misbehaves in production — silently
skipped, or fetched over the wrong route, or parsed against the wrong document.

The same friction shows up a second way. The Poll and the Acquisition both need
the same five steps — gate the address, choose the route, fetch the page, find
the Latest Chapter, find the Cover address — and each has its own copy. The two
copies have already drifted once: cover-byte routing had to be deliberately
pulled out into a shared address-shaped rule to stop them diverging again.

Both problems land on the same person: whoever adds the seventh Site, or changes
how any existing Site is read.

Solution

Give each Site exactly one entry in one registry, and make the Poll and the
Acquisition share the single read they both perform.

A registry entry answers a fixed set of questions about its Site: which hostname
its addresses must carry, how to find the Latest Chapter in a page body, how to
find the Cover address in a page body, and — only for a Site behind a JavaScript
challenge — how to read its payload from a cleared browser tab and how to know
the payload arrived. Everything about how a Site behaves lives in its entry, and
nowhere else.

The Poll and the Acquisition keep their own purposes and their own policies.
Only the read they have in common moves into one module, which returns the two
facts it found and persists nothing.

Recorded in ADR-0009. The domain term for the creation-time read is
Acquisition, now defined in CONTEXT.md.

User Stories

  1. As a Reader, I want a newly bookmarked Series to show its Cover and Latest Chapter immediately, so that my Library is useful the moment I add to it.
  2. As a Reader, I want a Series on any supported Site to be polled on the same schedule, so that no part of my Library goes stale because of which Site it came from.
  3. As a Reader, I want a Series whose page cannot be fetched to be skipped quietly and retried later, so that one broken Series never stalls the rest of my Library.
  4. As a Reader, I want the ember accent to keep meaning a New Chapter and nothing else, so that the signal I actually act on stays trustworthy.
  5. As a Reader, I want my Progress, favourites and lifecycle bucket to be untouched by this change, so that nothing I curated is disturbed.
  6. As a Reader on a Site behind a challenge, I want the backend to keep reading that Site through the browser, so that kagane and novelfull Series keep tracking their Latest Chapter.
  7. As a Reader whose Series lives on a Site reachable over plain TLS, I want that Series polled even when no browser is configured, so that four of the six Sites never depend on a machine I might have switched off.
  8. As a Reader, I want a Series that already has a Cover to keep it, so that artwork does not churn between polls.
  9. As a Reader, I want a Series with a blank Cover to acquire one at the next Poll, so that rows created before Cover support heal without my intervention.
  10. As a maintainer adding a seventh Site, I want one place to describe it, so that I cannot forget a step that has no compile-time check.
  11. As a maintainer adding a seventh Site, I want the questions a Site must answer to be visible in one type, so that I know what I am required to supply before I start.
  12. As a maintainer adding a Site that needs no browser, I want to supply nothing browser-related, so that the common case stays small.
  13. As a maintainer adding a Site behind a challenge, I want to supply only how to read its payload and how to know it arrived, so that I do not re-implement the challenge-clearing loop.
  14. As a maintainer adding a Site whose page needs interaction before the payload exists, I want to express clicks and waits in the same place as the read, so that ordinary in-tab sequences need no new machinery.
  15. As a maintainer adding a Site that cannot work within the shared tab lifecycle at all, I want a documented escape hatch, so that one unusual Site does not force a change on the other six.
  16. As a maintainer, I want that escape hatch to stay unbuilt until a Site needs it, so that the registry carries no field with zero users.
  17. As a maintainer, I want three Sites that find their Cover the same way to share one implementation, so that identical behaviour is written once.
  18. As a maintainer, I want an unknown site string to fail exactly as it does today, so that the existing skip-and-log behaviour is preserved.
  19. As a maintainer, I want each Site's address host pinned, so that a stored address on a dead domain fails cleanly instead of fetching the wrong document.
  20. As a maintainer, I want the second host check inside a browser Site's read to remain, so that the SSRF defence in depth around a client-supplied address is not weakened.
  21. As a maintainer, I want Cover bytes to stay routed by address shape rather than Site name, so that the Poll and the Acquisition cannot drift apart on that rule.
  22. As a maintainer, I want the challenge-clearing loop to stay in one place, so that its measured behaviour is not duplicated per Site.
  23. As a maintainer, I want the Poll to keep stamping its check time before the fetch, so that a broken Series still consumes its cooldown instead of being retried every tick.
  24. As a maintainer, I want the Acquisition to keep stamping its check time after a successful fetch, so that a Reader's brand-new Bookmark gets a fast retry while they are watching it.
  25. As a maintainer, I want that asymmetry stated in a comment, so that the next reader does not "fix" it by making the two orders match.
  26. As a maintainer, I want the shared read to return facts rather than persist them, so that the Poll and the Acquisition keep their own persistence policies.
  27. As a maintainer, I want the Poll to keep skipping a write when the chapter number is unchanged, so that unchanged Series do not churn rows.
  28. As a maintainer, I want the Acquisition to keep its own timeout and concurrency limit, so that a slow Site cannot hold a Reader's write open.
  29. As a maintainer, I want the Poll to keep its panic recovery, so that one malformed page cannot take down the background poller.
  30. As a maintainer, I want the Poll to keep its batch, cadence and due-queue ordering, so that scheduling behaviour is untouched by a refactor of the read.
  31. As a maintainer, I want the existing per-Site parse tables to pass unmodified, so that I have direct evidence the registry preserved behaviour.
  32. As a maintainer, I want the existing address-gate table to pass unmodified, so that the pinning change is visible as a deliberate diff rather than a silent one.
  33. As a maintainer, I want no new test seam introduced for the registry, so that the number of places tests reach into the package does not grow.
  34. As a maintainer, I want the registry verified through the Poll and the Acquisition, so that the tests exercise the same path production uses.
  35. As a reviewer, I want the security-critical address gate to be recognisably the same rule after the change, so that I can confirm no fetch path was widened.
  36. As a reviewer, I want to see which Sites gained a host pin, so that I can judge the risk to stored addresses separately from the refactor.
  37. As an implementing agent, I want the settled decisions recorded in an ADR, so that I do not re-derive them or contradict them.
  38. As an implementing agent, I want the domain term for the creation-time read defined, so that I name the module after a concept the project already has.
  39. As an implementing agent, I want the two phases ordered, so that I do not write the shared read against the switches it is meant to replace.
  40. As an implementing agent, I want the asuracomic.net removal kept out of this change, so that a CORS or deploy mistake cannot be confused with a registry bug.
  41. As an operator, I want no configuration change required by this work, so that deploying it is a plain image swap.
  42. As an operator, I want the behaviour with no browser configured to be unchanged, so that the API stack keeps running when the home machine is asleep.

Implementation Decisions

Phase one — the Site registry.

  • One registry in the latest package, keyed by the stored site string, holding one entry per Site. Six entries: asura, demonic, comix, kagane, novelfull, lightnovelworld.
  • An entry is a record of values and functions, not a Go interface with a method set. Most Sites differ in one or two answers and three share a single Cover implementation, so a method set would produce near-empty types. A missing answer is a nil value, handled where an unknown Site is already handled.
  • An entry answers: the exact hostname its addresses must carry; how to find the Latest Chapter given the address and the body; how to find the Cover address given the address and the body; and, optionally, how to read its payload in a browser.
  • The browser part of an entry supplies two things: an action that reads the payload from a cleared tab, and a predicate that reports whether the payload has arrived. It does not supply navigation, timeouts, cadence, tab lifecycle or challenge handling — those stay in the browser fetcher, shared by every browser-backed Site.
  • The browser read may refuse an address. That refusal is the second host pin, kept per-Site, and it is deliberate duplication of the shared address gate: a headless browser is a strong SSRF primitive and the address arrives in a client-supplied request body.
  • Host matching is exact — no www. or subdomain tolerance — matching the existing pins.
  • All six Sites get a host pin. Three have one today; three do not.
  • The existing dispatch functions for Latest Chapter, Cover and address-gating remain as functions, reduced to registry lookups. Their signatures do not change, which is what keeps the existing tables valid.
  • The browser-backed Site list and the fetcher choice become derived from the registry rather than separate literals.
  • Cover byte retrieval is not moved. It is routed by address shape, deliberately, so that the Poll and the Acquisition share one rule.
  • No escape-hatch field is added in this phase. Under the chosen browser split, no current Site needs one. ADR-0009 records that an unusual Site takes an override rather than widening the shared question set, and that the field arrives with the Site that needs it.
  • No new file. The registry lives beside the parse functions it absorbs.

Phase two — the shared Series-page read.

  • One module performs the read that the Poll and the Acquisition have in common: gate the address, choose the route, fetch the page, parse the Latest Chapter, parse the Cover address.
  • It returns the facts it found and an error. It persists nothing, stamps nothing and decides nothing about scheduling.
  • The Poll and the Acquisition are not merged. Each keeps its own trigger, stamp order, write policy, Cover policy, concurrency limit and panic handling.
  • The Poll keeps stamping the check time before the fetch; the Acquisition keeps stamping it after success. The asymmetry is intentional — a Reader is present for an Acquisition and deserves a fast retry — and gets a comment saying so.
  • No schema change. No wire-format change. No new configuration.

Ordering. Phase one lands before phase two, so that the shared read is written against the registry rather than against the switches it replaces.

Testing Decisions

A good test here asserts observable behaviour: what the Poll or the Acquisition
writes to a Series given a page body, which Sites are considered fetchable, and
what happens when a fetch fails. It does not assert the shape of the registry,
the presence of a field, or that a particular function was called.

  • Seams: no new ones. Two existing seams cover this work, and both already exist. Behaviour is verified through the Poll's single-tick entry point and the Acquisition's entry point, each with a fake fetcher injected and a real Postgres behind it. Per-Site parse behaviour is verified through the existing in-package tables that drive the parse functions by site string.
  • The regression proof is that existing tests pass unmodified. The per-Site Latest Chapter table, the per-Site Cover table, the challenge-body table and the address-gate table all drive by site string through function signatures this work does not change. If they need editing to pass, behaviour moved and the diff needs justifying.
  • One deliberate exception. The address-gate table gains cases for the three Sites that did not previously pin a host. That edit is expected and is the visible record of the pinning decision.
  • No registry-level test suite. A test that reads entries out of the registry and asserts their contents would test the data, not the behaviour, and would be the new seam this work is trying to avoid.
  • Prior art. The existing poller and acquire tests are the pattern to follow: a real Postgres from the package's test-container helper, a fake fetcher returning a fixed body and status, then assertions on the stored Series. The per-Site fixtures trimmed from live pages, with their capture dates in comments, are the pattern for any new parse case.
  • Live checks stay environment-gated. The existing smoke tests that need a real browser or a real Site remain opt-in via their environment variables, matching current convention.
  • The full suite needs Docker, because each test package starts a throwaway Postgres.

Out of Scope

  • Dropping asuracomic.net. Agreed as a separate change, after this one. It reaches beyond the backend — the CORS allowlist in compose and the example environment file, the userscript's match list and host test, the CORS fixtures in the API tests, the notes in README and AGENTS — and it needs a deploy-time change to the live environment. Keeping it separate means a CORS or deploy mistake cannot be mistaken for a registry bug.
  • Rewriting stored addresses on the dead asura domain. A one-off statement, run once, belonging with the removal above rather than here.
  • Merging the Poll and the Acquisition. Explicitly rejected. They have different triggers and different policies.
  • Moving Cover byte retrieval into the registry. Explicitly rejected; it is routed by address shape on purpose.
  • Exporting the latest package's parse layer. Noted as a real observation about the test surface, but not required by this work and not attempted here.
  • Any change to the userscripts, the web UI, the schema, the wire format or configuration.
  • Adding a seventh Site. This work makes that cheaper; it does not do it.

Further Notes

ADR-0009 records the principle behind the registry's shape and names the failure
mode a future review is likely to hit: an override used by exactly one Site
looks like an inconsistency to collapse, and collapsing it means either widening
the question set for every Site or scattering the shared tab lifecycle. Both
were rejected on evidence.

Three facts constrain the browser side and are easy to lose in a refactor. The
tab is held open across re-reads because a challenge needs several seconds of
live page to solve itself and write clearance into the shared cookie jar —
reading once and closing the tab clears nothing. The browser is on-demand and on
a separate machine, so an unreachable one must degrade exactly as no browser at
all. And the two Sites behind the browser have different payloads: one exposes
its chapter list only through an API that must be called from inside the page,
the other renders its chapters into the served HTML.

The pinning of the three previously unpinned Sites is the only behavioural
change in this work. For one of them the pin is a strict improvement rather than
a restriction: its old domain redirects deep links to the site root, discarding
the path, so a stored address on that domain currently fetches the wrong
document and parses it. After pinning it fails cleanly instead.

## Problem Statement Adding support for a new Site means finding six unrelated places that compare the site string, spread across three files in the `latest` package: the Latest Chapter parse switch, the Cover parse switch, the browser-backed Site list, the fetcher choice, the host pin, and the payload read inside the browser. Nothing ties them together and nothing fails at compile time, so a Site wired into five of the six works in development and then misbehaves in production — silently skipped, or fetched over the wrong route, or parsed against the wrong document. The same friction shows up a second way. The Poll and the Acquisition both need the same five steps — gate the address, choose the route, fetch the page, find the Latest Chapter, find the Cover address — and each has its own copy. The two copies have already drifted once: cover-byte routing had to be deliberately pulled out into a shared address-shaped rule to stop them diverging again. Both problems land on the same person: whoever adds the seventh Site, or changes how any existing Site is read. ## Solution Give each Site exactly one entry in one registry, and make the Poll and the Acquisition share the single read they both perform. A registry entry answers a fixed set of questions about its Site: which hostname its addresses must carry, how to find the Latest Chapter in a page body, how to find the Cover address in a page body, and — only for a Site behind a JavaScript challenge — how to read its payload from a cleared browser tab and how to know the payload arrived. Everything about how a Site behaves lives in its entry, and nowhere else. The Poll and the Acquisition keep their own purposes and their own policies. Only the read they have in common moves into one module, which returns the two facts it found and persists nothing. Recorded in ADR-0009. The domain term for the creation-time read is **Acquisition**, now defined in `CONTEXT.md`. ## User Stories 1. As a Reader, I want a newly bookmarked Series to show its Cover and Latest Chapter immediately, so that my Library is useful the moment I add to it. 2. As a Reader, I want a Series on any supported Site to be polled on the same schedule, so that no part of my Library goes stale because of which Site it came from. 3. As a Reader, I want a Series whose page cannot be fetched to be skipped quietly and retried later, so that one broken Series never stalls the rest of my Library. 4. As a Reader, I want the ember accent to keep meaning a New Chapter and nothing else, so that the signal I actually act on stays trustworthy. 5. As a Reader, I want my Progress, favourites and lifecycle bucket to be untouched by this change, so that nothing I curated is disturbed. 6. As a Reader on a Site behind a challenge, I want the backend to keep reading that Site through the browser, so that kagane and novelfull Series keep tracking their Latest Chapter. 7. As a Reader whose Series lives on a Site reachable over plain TLS, I want that Series polled even when no browser is configured, so that four of the six Sites never depend on a machine I might have switched off. 8. As a Reader, I want a Series that already has a Cover to keep it, so that artwork does not churn between polls. 9. As a Reader, I want a Series with a blank Cover to acquire one at the next Poll, so that rows created before Cover support heal without my intervention. 10. As a maintainer adding a seventh Site, I want one place to describe it, so that I cannot forget a step that has no compile-time check. 11. As a maintainer adding a seventh Site, I want the questions a Site must answer to be visible in one type, so that I know what I am required to supply before I start. 12. As a maintainer adding a Site that needs no browser, I want to supply nothing browser-related, so that the common case stays small. 13. As a maintainer adding a Site behind a challenge, I want to supply only how to read its payload and how to know it arrived, so that I do not re-implement the challenge-clearing loop. 14. As a maintainer adding a Site whose page needs interaction before the payload exists, I want to express clicks and waits in the same place as the read, so that ordinary in-tab sequences need no new machinery. 15. As a maintainer adding a Site that cannot work within the shared tab lifecycle at all, I want a documented escape hatch, so that one unusual Site does not force a change on the other six. 16. As a maintainer, I want that escape hatch to stay unbuilt until a Site needs it, so that the registry carries no field with zero users. 17. As a maintainer, I want three Sites that find their Cover the same way to share one implementation, so that identical behaviour is written once. 18. As a maintainer, I want an unknown site string to fail exactly as it does today, so that the existing skip-and-log behaviour is preserved. 19. As a maintainer, I want each Site's address host pinned, so that a stored address on a dead domain fails cleanly instead of fetching the wrong document. 20. As a maintainer, I want the second host check inside a browser Site's read to remain, so that the SSRF defence in depth around a client-supplied address is not weakened. 21. As a maintainer, I want Cover bytes to stay routed by address shape rather than Site name, so that the Poll and the Acquisition cannot drift apart on that rule. 22. As a maintainer, I want the challenge-clearing loop to stay in one place, so that its measured behaviour is not duplicated per Site. 23. As a maintainer, I want the Poll to keep stamping its check time before the fetch, so that a broken Series still consumes its cooldown instead of being retried every tick. 24. As a maintainer, I want the Acquisition to keep stamping its check time after a successful fetch, so that a Reader's brand-new Bookmark gets a fast retry while they are watching it. 25. As a maintainer, I want that asymmetry stated in a comment, so that the next reader does not "fix" it by making the two orders match. 26. As a maintainer, I want the shared read to return facts rather than persist them, so that the Poll and the Acquisition keep their own persistence policies. 27. As a maintainer, I want the Poll to keep skipping a write when the chapter number is unchanged, so that unchanged Series do not churn rows. 28. As a maintainer, I want the Acquisition to keep its own timeout and concurrency limit, so that a slow Site cannot hold a Reader's write open. 29. As a maintainer, I want the Poll to keep its panic recovery, so that one malformed page cannot take down the background poller. 30. As a maintainer, I want the Poll to keep its batch, cadence and due-queue ordering, so that scheduling behaviour is untouched by a refactor of the read. 31. As a maintainer, I want the existing per-Site parse tables to pass unmodified, so that I have direct evidence the registry preserved behaviour. 32. As a maintainer, I want the existing address-gate table to pass unmodified, so that the pinning change is visible as a deliberate diff rather than a silent one. 33. As a maintainer, I want no new test seam introduced for the registry, so that the number of places tests reach into the package does not grow. 34. As a maintainer, I want the registry verified through the Poll and the Acquisition, so that the tests exercise the same path production uses. 35. As a reviewer, I want the security-critical address gate to be recognisably the same rule after the change, so that I can confirm no fetch path was widened. 36. As a reviewer, I want to see which Sites gained a host pin, so that I can judge the risk to stored addresses separately from the refactor. 37. As an implementing agent, I want the settled decisions recorded in an ADR, so that I do not re-derive them or contradict them. 38. As an implementing agent, I want the domain term for the creation-time read defined, so that I name the module after a concept the project already has. 39. As an implementing agent, I want the two phases ordered, so that I do not write the shared read against the switches it is meant to replace. 40. As an implementing agent, I want the asuracomic.net removal kept out of this change, so that a CORS or deploy mistake cannot be confused with a registry bug. 41. As an operator, I want no configuration change required by this work, so that deploying it is a plain image swap. 42. As an operator, I want the behaviour with no browser configured to be unchanged, so that the API stack keeps running when the home machine is asleep. ## Implementation Decisions **Phase one — the Site registry.** - One registry in the `latest` package, keyed by the stored site string, holding one entry per Site. Six entries: asura, demonic, comix, kagane, novelfull, lightnovelworld. - An entry is a record of values and functions, not a Go `interface` with a method set. Most Sites differ in one or two answers and three share a single Cover implementation, so a method set would produce near-empty types. A missing answer is a nil value, handled where an unknown Site is already handled. - An entry answers: the exact hostname its addresses must carry; how to find the Latest Chapter given the address and the body; how to find the Cover address given the address and the body; and, optionally, how to read its payload in a browser. - The browser part of an entry supplies two things: an action that reads the payload from a cleared tab, and a predicate that reports whether the payload has arrived. It does not supply navigation, timeouts, cadence, tab lifecycle or challenge handling — those stay in the browser fetcher, shared by every browser-backed Site. - The browser read may refuse an address. That refusal is the second host pin, kept per-Site, and it is deliberate duplication of the shared address gate: a headless browser is a strong SSRF primitive and the address arrives in a client-supplied request body. - Host matching is exact — no `www.` or subdomain tolerance — matching the existing pins. - All six Sites get a host pin. Three have one today; three do not. - The existing dispatch functions for Latest Chapter, Cover and address-gating remain as functions, reduced to registry lookups. Their signatures do not change, which is what keeps the existing tables valid. - The browser-backed Site list and the fetcher choice become derived from the registry rather than separate literals. - Cover byte retrieval is not moved. It is routed by address shape, deliberately, so that the Poll and the Acquisition share one rule. - No escape-hatch field is added in this phase. Under the chosen browser split, no current Site needs one. ADR-0009 records that an unusual Site takes an override rather than widening the shared question set, and that the field arrives with the Site that needs it. - No new file. The registry lives beside the parse functions it absorbs. **Phase two — the shared Series-page read.** - One module performs the read that the Poll and the Acquisition have in common: gate the address, choose the route, fetch the page, parse the Latest Chapter, parse the Cover address. - It returns the facts it found and an error. It persists nothing, stamps nothing and decides nothing about scheduling. - The Poll and the Acquisition are not merged. Each keeps its own trigger, stamp order, write policy, Cover policy, concurrency limit and panic handling. - The Poll keeps stamping the check time before the fetch; the Acquisition keeps stamping it after success. The asymmetry is intentional — a Reader is present for an Acquisition and deserves a fast retry — and gets a comment saying so. - No schema change. No wire-format change. No new configuration. **Ordering.** Phase one lands before phase two, so that the shared read is written against the registry rather than against the switches it replaces. ## Testing Decisions A good test here asserts observable behaviour: what the Poll or the Acquisition writes to a Series given a page body, which Sites are considered fetchable, and what happens when a fetch fails. It does not assert the shape of the registry, the presence of a field, or that a particular function was called. - **Seams: no new ones.** Two existing seams cover this work, and both already exist. Behaviour is verified through the Poll's single-tick entry point and the Acquisition's entry point, each with a fake fetcher injected and a real Postgres behind it. Per-Site parse behaviour is verified through the existing in-package tables that drive the parse functions by site string. - **The regression proof is that existing tests pass unmodified.** The per-Site Latest Chapter table, the per-Site Cover table, the challenge-body table and the address-gate table all drive by site string through function signatures this work does not change. If they need editing to pass, behaviour moved and the diff needs justifying. - **One deliberate exception.** The address-gate table gains cases for the three Sites that did not previously pin a host. That edit is expected and is the visible record of the pinning decision. - **No registry-level test suite.** A test that reads entries out of the registry and asserts their contents would test the data, not the behaviour, and would be the new seam this work is trying to avoid. - **Prior art.** The existing poller and acquire tests are the pattern to follow: a real Postgres from the package's test-container helper, a fake fetcher returning a fixed body and status, then assertions on the stored Series. The per-Site fixtures trimmed from live pages, with their capture dates in comments, are the pattern for any new parse case. - **Live checks stay environment-gated.** The existing smoke tests that need a real browser or a real Site remain opt-in via their environment variables, matching current convention. - The full suite needs Docker, because each test package starts a throwaway Postgres. ## Out of Scope - **Dropping asuracomic.net.** Agreed as a separate change, after this one. It reaches beyond the backend — the CORS allowlist in compose and the example environment file, the userscript's match list and host test, the CORS fixtures in the API tests, the notes in README and AGENTS — and it needs a deploy-time change to the live environment. Keeping it separate means a CORS or deploy mistake cannot be mistaken for a registry bug. - **Rewriting stored addresses on the dead asura domain.** A one-off statement, run once, belonging with the removal above rather than here. - **Merging the Poll and the Acquisition.** Explicitly rejected. They have different triggers and different policies. - **Moving Cover byte retrieval into the registry.** Explicitly rejected; it is routed by address shape on purpose. - **Exporting the `latest` package's parse layer.** Noted as a real observation about the test surface, but not required by this work and not attempted here. - **Any change to the userscripts, the web UI, the schema, the wire format or configuration.** - **Adding a seventh Site.** This work makes that cheaper; it does not do it. ## Further Notes ADR-0009 records the principle behind the registry's shape and names the failure mode a future review is likely to hit: an override used by exactly one Site looks like an inconsistency to collapse, and collapsing it means either widening the question set for every Site or scattering the shared tab lifecycle. Both were rejected on evidence. Three facts constrain the browser side and are easy to lose in a refactor. The tab is held open across re-reads because a challenge needs several seconds of live page to solve itself and write clearance into the shared cookie jar — reading once and closing the tab clears nothing. The browser is on-demand and on a separate machine, so an unreachable one must degrade exactly as no browser at all. And the two Sites behind the browser have different payloads: one exposes its chapter list only through an API that must be called from inside the page, the other renders its chapters into the served HTML. The pinning of the three previously unpinned Sites is the only behavioural change in this work. For one of them the pin is a strict improvement rather than a restriction: its old domain redirects deep links to the site root, discarding the path, so a stored address on that domain currently fetches the wrong document and parses it. After pinning it fails cleanly instead.
sulthan added the ready-for-agent label 2026-08-11 23:59:46 +07:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sulthan/mangaBookmark#94