Always fetch and merge the latest
masterbefore creating a new worktree or branch.git fetch origin && git merge origin/masterSkipping this causes the new branch to diverge immediately and makes later rebases / merges unnecessarily painful.
dist/ directories and generated files are in .gitignore and must never
be committed to master or any feature branch.
| Artifact | Why ignored |
|---|---|
packages/web/dist/ |
Vite build output — regenerated on every build |
packages/*/dist/ |
tsc / Vite outputs for rs-module, extension, thunderbird |
packages/web/src/lib/plugin-downloads.generated.ts |
Generated by scripts/gen-plugin-metadata.mjs during predev/prebuild |
packages/web/public/downloads/ |
Extension ZIPs/XPIs staged before being copied into dist/ |
Deployment only: the release workflow and scripts/deploy.sh use
git add -f on a throwaway deploy/<version> branch to force-add
packages/web/dist/ for the 5apps subtree-push. That deploy commit is
never pushed to origin/master.
This is a client-side only application. There is no server-side logic — Node.js is used only for build tooling. Do not introduce any server-side code, API routes, or backend logic.
@inbox-rs/web— Svelte 5 web app, built with Vite. Dev server on port 5173.@inbox-rs/extension— Browser extension (Chrome Manifest V3 + Firefox). Built with Vite + Svelte.@inbox-rs/rs-module— Shared TypeScript library for data types, schemas, and CRUD operations. Compiled withtsc.
All data flows directly from the browser to a remoteStorage server — no intermediary API.
- Web app and extension both use
remotestoragejsto read/write data. - For local development, an Armadietto remoteStorage server runs in Docker (
docker-compose up, port 8000).
npm install
npm run build --workspace=packages/rs-module # required once before dev/build
npm run dev # Starts web app dev server (auto-generates plugin metadata)
npm run build # Full web app build (also builds extensions + generates metadata)
npm run build:extension # Build browser extension only
npm run devrunsscripts/gen-plugin-metadata.mjsas apredevhook — this generatesplugin-downloads.generated.tswithout building the extensions, so cold-start dev is fast.
npm run buildrunsscripts/package-plugins.mjsasprebuildwhich builds all extensions, packages them as ZIPs/XPIs, and then generates the same metadata file.
- No server-side code. Everything runs in the browser.
- No Node.js-only dependencies in runtime code. Packages like
onnxruntime-nodemust be optional — the app runs in-browser only. - Target both Chrome and Firefox for the extension.
Always run
npm run checkandnpm run testbefore committing. Both must pass.npm run check # biome check — lint + format + organize-imports npm run test # full vitest suite across all workspacesUse
npm run check, notnpm run lint.biome lintonly runs the linter and skips the formatter and import-sort passes — CI runsnpx biome ci(≈biome check), so a passingnpm run lintdoesn't mean CI will pass. Ifnpm run checkflags formatting or import-order issues,npx biome check --writewill auto-fix the safe ones; re-runnpm run checkafterwards to confirm.If either step fails, fix the underlying issue and re-run. Don't
--no-verifypast a failing hook and don't commit "I'll fix it in the next push" — the next push is harder to land cleanly. CI will catch this anyway, so failing locally just costs you a round-trip.
CSS rules targeting <input>, <textarea>, or <select> must never set
font-size below 1rem (16px). iOS Safari auto-zooms the viewport when
a focused form control's computed font-size is below 16px, and that zoom
doesn't reset on blur — it persists and breaks the layout.
The rule applies to:
- Element selectors:
input,textarea,select, and any compound (e.g.input:focus,input::placeholder,input[type='text']). - Class selectors that are applied to a form control in the markup, e.g.
.search,.abbrev-input,.code-input,.quick-add input. :global(...)wrappers used inside scoped Svelte styles.
Checkboxes, radios, and file inputs don't trigger the iOS zoom, but the floor applies repo-wide for consistency.
If a form control omits font-size entirely it inherits from the root,
which packages/web/src/styles/global.css sets to 16px (17px on narrow
viewports) — so inheritance is safe and is the preferred default. Add an
explicit font-size only when you need to override the inherited value;
when you do, keep it >= 1rem.
Enforced by packages/web/src/lib/input-font-size.test.ts. The scanner walks
every .svelte and .css file under packages/{web,extension,thunderbird}/src
and fails on any rule whose selector targets a form control AND explicitly
declares font-size below 1rem (or below the equivalent in px/em/%).
It does not flag:
- Rules that omit
font-size— safe via inheritance from the 16px root. - Rules that use the
fontshorthand — we don't use it on form controls anywhere. - Values that are CSS-wide keywords (
inherit,initial,unset, etc.). var(--…)custom-property values — we don't statically resolve the cascade, so the detector treats them as compliant. The codebase has zero uses offont-size: var(…)on form controls today; don't introduce one unless you can guarantee the variable never resolves below 1rem.
When you add a class that's applied to a form control but doesn't include
input/textarea/select as a token in its name (e.g. .search), add it
to the NAMED_INPUT_CLASSES list in that test.