Files
SubMiner/vendor/hachidori/extension/README.md
T
sudacode d9fdc7ef6d feat(dictionary): add Hachidori backend support
- Add backend selection, setup gating, Anki integration, and external host support
- Add launcher flags, documentation, packaging, and focused tests
- Open on-demand overlay modals on the first attempt
2026-09-22 00:21:19 -07:00

12 KiB
Raw Blame History

The Hachidori extension

This folder is the shared extension source and the Manifest V3 package exactly as Chrome 128 or newer loads it, with no build step. The JavaScript is plain ES modules and classic scripts, the dictionary engine is committed WebAssembly under vendor/, and everything runs inside the browser. To run it in Chrome from a checkout, open chrome://extensions, turn on Developer mode, choose Load unpacked and select this folder. scripts/package-store.py zips this same folder, with the licence files, for the Chrome Web Store.

manifest.firefox.json is the reviewed Firefox MV2 manifest. The same packager writes the Firefox XPI from these sources minus the Chrome-only files listed in scripts/firefox-package.json, with that manifest in place of manifest.json; scripts/prepare-firefox.mjs stages the same layout in an ignored directory for lint and the Firefox smoke test. See the Firefox guide to build and temporarily install the unsigned XPI.

The architecture guide explains how the pieces work together and lists every runtime message and stored key. This page says where things are.

Entry points

Drag a lookup popup's bottom-right corner to resize it. The size is shared by subsequent and nested lookups in that page, including after closing and reopening the popup. Reloading or navigating the page (or restarting the browser) starts a new reading session with the saved Design dimensions. Dragging does not change those saved settings or other tabs.

The popup action row is one non-wrapping keyboard and visual group: a nested Close or Back control first, then Anki, pronunciation, personal-dictionary edit, and custom buttons in saved order. A custom button opens a URL template or mines with a chosen Anki Template. Actions share a 36-pixel height and a 5-pixel gap. At narrow popup widths the whole action row scrolls horizontally instead of wrapping, clipping, or overlapping controls. Browser mode opens link buttons in a Chrome tab; overlay mode asks its embedding host to open the same validated URL in the system browser.

manifest.json names them.

File Runs as Role
background.js the service worker Routes every runtime message and owns everything in chrome.storage.local: dictionary metadata, options, the personal dictionary, update schedules, lookup counts, automatic-backup metadata, first-run and sharing state. It also owns the alarms, the Anki gateway and the sharing host and client. It holds no engine state, so Chrome may stop it whenever it is idle.
firefox-background.html, firefox-background.js Firefoxs persistent MV2 background page Loads the shared background module and hosts offscreen.html in one authenticated hidden iframe so the engine remains warm.
content.js, with the classic scripts listed under content_scripts every web page Scans the Japanese text near the pointer, renders the popup in a closed shadow root through render/popup.js and render/glossary.js, and adds the popup's Anki and pronunciation controls (anki-content.js, audio-content.js). Chrome also injects capture-content.js; Firefox does not. content.css is the only style the page itself receives: the source highlight.
offscreen.html, offscreen.js Chromes offscreen document or Firefoxs hidden background iframe Owns the dictionary engine. engine-worker.js runs the pthread build with direct OPFS once opfs-capability-worker.js has proved the browser can, engine-worker-idbfs.js runs the pthread build on IDBFS when the browser has shared memory but no OPFS access handles (Electron), both through engine-worker-runtime.js; engine-service.js is also the single-thread IDBFS fallback. Pronunciation, Anki and the first-run installer load here on demand. Chrome also hosts media capture here.
settings.html, settings.js the options page Dictionaries, groups, updates, the personal dictionary, Reading, Design, pronunciation, Anki, keybinds, backup and sharing, with media capture where supported and global search. The larger sections have their own *-settings.js controller; design-preview.html is the live preview inside Design.
startup.html, startup.js a tab opened once after install First-run setup: recommended dictionaries, Anki detection, a practice lookup, and the offer to use a Hachidori that another browser on this computer already shares. Overlay mode skips it.
toolbar.html, toolbar.js the toolbar button's popup Turns lookups on and off, shows the sharing state and opens Settings. Chrome also exposes the recording action here.
capture.html, capture.js a Chrome-only tab opened from the toolbar or Settings Controls media capture. The recorder itself, capture-host.js, runs in the offscreen document and keeps going when this tab closes. Firefox does not expose this entry point.

overlay-mode.js, its browser-api.js dependency, render/reader.css and icons.css are the only files web pages may fetch (web_accessible_resources). The popup and its Anki controls load the two stylesheets; overlay hosts use the shared mode contract.

Modules by feature

Files share a prefix with the feature they belong to. A rule that more than one context needs lives in a module with no Chrome dependency, so Settings, the service worker and both engine runtimes run the same code.

  • Dictionaries and stored state. reader-options.js is the one stored options view every context reads. dictionary-group-state.js and dictionary-groups.js hold the group rules and their Settings controls, dictionary-name-drafts.js the autosaved names, dictionary-progress.js the import progress. managed-dictionary-source.js and recommended-dictionaries.js define the trusted update sources and the starter set; custom-dictionary.js the personal dictionary's source format and archive; setup-state.js the first-run stages and initial selections; setup-installer.js the offscreen recommended installer, observed from startup and Settings by recommended-install-client.js. json-value.js and response-limits.js are the comparison and size rules the transaction boundaries share.
  • Lookup statistics. lookup-stats-identity.js, a classic script so the content script can use it, and lookup-stats.js.
  • Anki. anki.js is the AnkiConnect gateway and anki-setup.js recognises an existing mining setup. anki-templates.js, anki-values.js, anki-glossary.js, anki-pitch.js, anki-resources.js and anki-audio.js build the note fields and media. Stored Anki Templates group each destination, note type, field mapping and duplicate policy; the first powers the built-in action and custom Anki buttons select the others by stable ID. Settings edits every field mapping through an accessible marker combobox while retaining the mapping string exactly. anki-duplicates.js and anki-enrichment.js handle a note that already exists; anki-digest.js hashes media. anki-client-media.js validates final screenshot, capture and browser-speech media crossing a linked-browser boundary. anki-mining.js and anki-worker.js are the mining service in the service worker. anki-index.js and anki-index-cache.js provide the shared scoped duplicate and maturity index, including cache-only View readiness and click-time live ID repair. anki-offscreen.js launches anki-index-worker.js for complete refreshes without moving note fields through the service worker.
  • Pronunciation. audio-sources.js, audio-repository.js, audio-cache.js and audio-player.js fetch, keep and play audio in the offscreen document (audio-offscreen.js); speech.js wraps the browser's text-to-speech.
  • Media capture. capture-host.js is the offscreen recorder. capture-session.js, capture-buffer.js, capture-timeline.js and capture-speech.js are its bounded buffers, occurrence timeline and speech detection. capture-audio-worklet.js, capture-frame-client.js with capture-frame-worker.js, and capture-encoder-client.js with capture-encoder-worker.js move audio sampling, frame grabbing and animated AVIF encoding (avif-sequence.js) off the main thread. texthooker-protocol.js parses the text a texthooker sends.
  • Backup. backup-archive.js is the manual ZIP format, backup-state.js the shared snapshot rules, backup-automatic.js the two-record daily retention, cadence and age rules, backup-downloads.js the pending downloads, and backup-settings.js the manual and automatic restore controls.
  • Sharing. sharing-protocol.js is the wire contract both sides import; sharing-host.js and sharing-client.js are the two roles in the service worker; sharing-settings.js is the Settings section. anki-addon.js pins and downloads the compatible .ankiaddon release from hachidori-anki, which owns the Python relay, its tests, and packaging.
  • Pages. settings-search.js and settings-dom.js serve Settings; experimental-settings.js renders the Advanced → Experimental features switches from the registry in reader-options.js; keybind-settings.js, custom-button-settings.js and external-links.js the keybinds and custom buttons in the popup; local-file-access.js the notice about Chrome's Allow access to file URLs permission; startup-practice.js the practice step. visual-novel.js and visual-novel.css draw the background scenes behind the startup page and the Design preview from the images in assets/ (see assets/ATTRIBUTION.md); design-preview.js renders the preview from sample-meal.svg and local sample data.
  • Renderer. render/ is the popup renderer ported from GameSentenceMiner, which adapts Hoshi Reader and Yomitan; render/ATTRIBUTION.md records what came from where.
  • Overlay mode. overlay-mode.js is the one switch a host such as the GameSentenceMiner overlay flips in its copy. It also defines the shared host-capability policy used by Settings, the toolbar, the reader and the service worker; see overlay mode.
  • Vendored code. vendor/hoshidicts-threaded.{mjs,wasm}, vendor/hoshidicts-threaded-idbfs.{mjs,wasm} and vendor/hoshidicts.{mjs,wasm} are the three builds of the hoshidicts engine from wasm/build.sh, vendor/avif-encoder.{mjs,wasm} the AVIF encoder from wasm/avif/, and vendor/zip.js the pinned zip.js runtime. They are committed build output: update them with their source change and otherwise leave them alone.
  • icons/ holds the extension's icons.

Conventions

  • Every script, stylesheet and page starts with an SPDX-License-Identifier: GPL-3.0-or-later line; the files under render/ also keep their upstream copyright lines.
  • A rule the content script needs as well as the module contexts lives in a classic script that publishes one globalThis.HD… object (HDReaderOptions, HDLookupStats, HDDictionaryGroups, …); modules import such a file for its side effect.
  • Runtime messages are objects with a target and an hd_* type, and they carry explicit ids, revisions or generations so a stale reply fails closed. Stored values are revisioned and written only by the service worker; a page edits them by compare-and-set. The offscreen document never touches chrome.storage itself.
  • Nothing here is generated except vendor/. There is no bundler, transpiler or minifier: what is committed is what ships.

Checking a change

node test/make-fixture.mjs      # writes the dictionary fixtures once
node test/extension-smoke.mjs   # this folder's JavaScript against the real engine, in Node
node test/chrome-e2e.mjs        # this folder loaded unpacked into a real Chrome

The test guide says what each suite proves and how to install the browser and jsdom they need; the validation list in AGENTS.md says which checks each kind of change requires. Sharing changes have their own suites, listed in sharing.

More