Astra is an AI-powered language learning browser extension with three main development surfaces:
- Browser Extension (
src/) — WXT-based Manifest V3 extension (Chrome/Firefox/Safari) - Astra Relay Server (
src/server/) — Node.js backend for auth, translation relay, sync - Web App (
src/web/) — React+Vite standalone web companion
Before broad repo exploration, read the AI context index in docs/ai-context/:
docs/ai-context/source-ui.md— UI/runtime design-token paths that are safe to hand to AI.docs/ai-context/source-code.md— source-code bundles by task area.docs/ai-context/generated-runtime-cache.md— runtime packages, caches, build outputs, result folders, and reference artifacts to avoid by default.
For the canonical source-priority/default-read vs generated/runtime classification boundary, see docs/investigations/ai-readable-classification-boundary.md.
| Task | Command |
|---|---|
| Install deps | pnpm install (runs wxt prepare via postinstall) |
| Extension dev (Chrome) | pnpm dev |
| Web app dev | pnpm dev:web (port 4173). Imports src/assets/astra-style1-tokens.css; default UI theme is light (data-astra-theme="light"). Use --accent-primary in new CSS (legacy alias: --accent-blue). |
| Relay server | pnpm relay:start (port 8787) or pnpm relay:dev (watch) |
| Repo knowledge guardrail | pnpm check:repo-knowledge |
| Type check | pnpm type-check |
| Release lint | pnpm lint:ci |
| Unit tests | pnpm test |
| Deterministic bench | pnpm bench |
| Build extension | pnpm build |
| Live bench (Playwright, extension-loaded) | pnpm build then npx playwright install chromium (or npx playwright install --with-deps chromium like CI). Required release live gate is pnpm bench:live:lane:release-proof, which runs source-core, extension-core, learning-loop, document-proof, youtube-proof, and youtube-holdout. On Linux without a real display, prefix with xvfb-run -a (matches .github/workflows/ci.yml live-browser job). |
-
Node 22 + pnpm 10 are required (matches CI in
.github/workflows/ci.yml). -
Extension-loaded live scenarios (
bench-live/site-automation-autostart, onboarding, vocabulary smoke, etc.) launch Chromium with--load-extension. They resolve the browser viascript/bench-live/driver.ts, preferring Playwright’s Chromium (chromium.executablePath()) when installed. If that binary is missing, the driver falls back to system Google Chrome, which often returnsnet::ERR_BLOCKED_BY_CLIENTonchrome-extension://…URLs used to seedchrome.storage— not an extension logic bug. Fix: runnpx playwright install chromiumonce per machine/CI image. InCI=true, the driver also avoids Playwright’schannel: "chrome"for the same reason. -
Safari extension CI is a sync gate, not an independent source tree. Any PR that changes extension assets, entrypoints, public locales, CSS, or bundled UI can make
build-extension (safari)fail with[astra-ios] Safari build output is out of sync with committed extension resources. Do not hand-editios/AstraShell Extension/Resources. If the Safari check fails for sync only, run:Run
pnpm check:safari-sync-neededbefore pushing extension-affecting changes. This guard is part ofpnpm lint:ci; it fails early when a PR changes Safari build inputs without also including the synced iOS resource snapshot.
pnpm build:safari
pnpm ios:sync-extension
bash ios/scripts/verify-safari-build-sync.sh
git add "ios/AstraShell Extension/Resources"
git commit -m "chore(ios): sync Safari resources for <change>"Only include that generated resources commit when Safari resources are truly changed by the build output; otherwise clean unrelated generated files before committing.
pnpm installmay warn about ignored build scripts (esbuild, core-js, etc.). These do not block development — esbuild ships a pre-built WASM fallback.- The relay server does not auto-load
src/server/.env. It readsprocess.envonly (seesrc/server/config.ts). Copysrc/server/.env.example→src/server/.envfor documentation, but to actually use keys you must either export them in the shell beforepnpm relay:startor inject them via your host/CI secret store. - Managed translation keys: When
GOOGLE_TRANSLATE_API_KEY/GOOGLE_CLOUD_TRANSLATE_API_KEY,OPENAI_API_KEY,GOOGLE_GENERATIVE_AI_API_KEY, and/orOPENROUTER_API_KEYare provided (e.g. Cursor Cloud user secrets), restart the relay after adding them so the Node process inherits the variables. A long-running relay started without keys will keep returning provider-key configuration errors until restarted. - Hello world (translate) check (terminal, relay on
127.0.0.1:8787):
TOKEN=$(curl -s -X POST http://127.0.0.1:8787/v1/auth/session \
-H 'Content-Type: application/json' \
-d '{"email":"demo@astra.local","password":"astra-demo-pass","deviceId":"dev-check"}' \
| node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.parse(d).sessionToken))")
curl -s -X POST http://127.0.0.1:8787/v1/translate \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"texts":["Hello, world."],"targetLang":"zh-CN","task":"translate","serviceMode":"fast"}'Expect {"translations":["…"]} when keys are loaded.
- The default dev credentials are
demo@astra.local/astra-demo-pass(port 8787). - The web app at port 4173 communicates with the relay at
http://127.0.0.1:8787/v1. In a headless Cloud Agent VM, CORS blocks browser-initiated requests from localhost:4173 to 127.0.0.1:8787. This is expected; the web app is primarily designed to work alongside the browser extension. For terminal-based API testing, usecurldirectly against the relay. - Release lint uses
pnpm lint:ci. It covers the release-critical TypeScript, provider, storage, Cloudflare, live-bench, maintenance, and web entrypoint files and is expected to pass. Full-repo strict lint may still include legacy cleanup outside the release gate. pnpm testis expected to pass. As of the 2026-05-18 release-gate run, the full suite is 161 files / 1416 tests green.- Repo structure is guarded by
pnpm check:repo-knowledge; tracked files should not be reintroduced under legacy top-levelserver/,web/,platform/,bench/,bench-live/,bench-opt/,agent-config/,scripts/, orplans/. - TypeScript type-check (
pnpm type-check) passes cleanly. - The lockfile may require
pnpm install(without--frozen-lockfile) ifpackage.jsonhas been updated butpnpm-lock.yamlhasn't been regenerated.