Where the Work Happens: Architecture, Privacy, and Resource Trade-offs
FluentRead’s service broker can hide provider-specific protocols, but it cannot make browser runtimes, storage, networks, and local models behave as if they were one place. This chapter locates the work across those boundaries and then compares cloud and local execution. The practical result is a way to decide whether a workflow fits a browser, privacy requirement, and resource budget before treating “local” or “supported” as a blanket promise.
A browser extension is several cooperating runtimes
A webpage translator appears to live inside the page, but a dependable extension cannot put every responsibility there. The page or extension UI needs to capture user intent and display a result. Background coordination needs to route messages, validate requests, manage provider calls, and survive the possibility that a browser page or service worker disappears. Pure rules—such as language handling, candidate logic, or request identity—are easier to test when they do not depend directly on the DOM or a particular browser API. Provider adapters need to speak external protocols without knowing how a Vue settings page is laid out. Storage and offscreen computation need their own lifecycle because they outlive or outscope a single visible page.
FluentRead’s architecture expresses these as boundaries rather than as a single large entrypoint. Entrypoints assemble application behavior; features own user-facing capabilities; core code holds rules that should remain independent; services coordinate cross-feature use cases; providers implement vendor protocols; platform code contains browser, storage, network, and offscreen details. The point is not to make the repository look tidy. It is to decide who owns state and who is allowed to make a transition.
For example, a content feature can request a translation, but it should not construct a DeepL or AI-specific HTTP payload. The broker can select and schedule a provider, but it should not reach into a Vue component to decide where a result belongs. A provider can report a response or error, but it should not manipulate page DOM. A core rule can decide whether a request identity is equivalent to another one, but it should not read browser storage to discover credentials. Each boundary narrows the number of reasons a component can change and makes failures easier to attribute.
Static registration is part of this browser-specific design. WXT discovers fixed entrypoints at build time, and FluentRead uses static registries for the different runtime contexts instead of scanning directories or importing unknown features dynamically. That keeps builds and tree-shaking predictable and avoids placing every context’s code into one universal bundle. It also makes the set of handlers explicit enough for tests to check. The trade-off is less runtime flexibility: adding a feature means changing a known composition point rather than dropping a module into a directory and expecting the extension to discover it. That explicitness matters when a feature crosses contexts. A selection action may begin in page content, ask the background for capability, call a provider, and return a result to a card. If one step were silently loaded into every runtime, the extension would have less control over permissions, bundle size, and failure ownership. Keeping the registration static makes the path more visible even when it makes the architecture less magical.
The MV3 background context adds a second constraint.