Skip to content

Software stack

In the product

  • TypeScript and WXT. One set of source files makes a manifest for each browser.
  • React 19 with @wxt-dev/module-react.
  • dexie with dexie-react-hooks for the cache.
  • hls.js for video from Patreon.
  • @tanstack/react-virtual for the list of posts.
  • Tailwind CSS 4 and shadcn/ui on Base UI for the panel (ADR-017, the panel). shadcn/ui is not a dependency at run time: the command copies a component into components/ui/, and that file then belongs to Gopher. The components and the palette come from the Aqua registry (@aqua, ADR-018), which components.json names under registries. @phosphor-icons/react gives the icons, one import for each icon. components.json says "iconLibrary": "phosphor", and Aqua writes lucide-react imports in Select. That file carries Phosphor imports, and the comment in the file records the change. A Phosphor icon gives itself no aria-hidden, thus each decorative icon must carry that attribute.

Dexie holds entity data only. Media bytes go to the Cache API. The state of the service worker goes to browser.storage.session. The build loads hls.js only when it is necessary. The file is 574 kB, and most sessions do not play a video.

The panel costs 183 kB of JavaScript and 11 kB of CSS, after compression. Base UI and the icons made it 73 kB larger than the panel that we wrote ourselves. Phosphor holds six weights for each icon, and 9 kB of that number is the difference from lucide-react. The panel loads no code over the network, thus this is a cost in memory and not in time. Measure it again if the panel gets more components.

For development: Playwright and tsx.

Software that Gopher does not use, and why

  • @tanstack/react-table. The table of attachments sorts three columns. That is approximately 40 lines in components/media.tsx. The library solves a much larger problem.
  • A library for localization (@wxt-dev/i18n, i18next, react-intl). The layer in lib/i18n/ is approximately 100 lines and has no dependency (ADR-016, localization). It gives the panel what the panel needs: a catalog for each language, placeholders, plural forms from Intl.PluralRules, and a setting that the user controls. A library adds bytes, a second set of names, and a message format that nothing here uses. Examine this decision again if Gopher gets a language with rules that Intl cannot express, or many more languages.
  • webextension-polyfill. WXT 0.21 does not include it. wxt/browser gives globalThis.browser on Firefox and globalThis.chrome on Chromium. Both give a promise for each API that Gopher calls. There is one difference: a listener for runtime.onMessage must call sendResponse and then return true. If the listener returns a promise, only Firefox sends the reply.