Better Intra — Agent Guide
Quick start
npm install
npm run build:firefox # single production build for Firefox
npm run build:chrome # single production build for Chrome
npm run dev:firefox # watch + web-ext hot-reload
Project structure
src/main.ts— content script entrypoint. Feature init viafeatureInitializersmap. Runs onhttps://*.intra.42.fr/*atdocument_start.src/popup/popup.ts— popup entrypoint.src/features/— self-contained features:logtime/,clusters/,profile/,shortcuts/,account/,friends/,hub/.src/config.ts— single source of truth for all chrome.storage keys; typedBetterIntraConfiginterface + defaults.manifests/manifest.{chrome,firefox}.json— per-browser manifests (merged with version from package.json at build time via Vite plugin).better-intra-worker/— separate Cloudflare Worker (wrangler) for cloud settings sync. Has its ownpackage.json.
Build quirks
Always set both TARGET and BUILD_OUT_DIR env vars (cross-env handles this). The build pipeline is: tsc → vite build (content script) → vite build --config vite.popup.config.ts (popup) → vite build --config vite.background.config.ts (background service worker).
cross-env TARGET=firefox BUILD_OUT_DIR=dist-firefox tsc && cross-env TARGET=firefox BUILD_OUT_DIR=dist-firefox vite build && cross-env TARGET=firefox BUILD_OUT_DIR=dist-firefox vite build --config vite.popup.config.ts && cross-env TARGET=firefox BUILD_OUT_DIR=dist-firefox vite build --config vite.background.config.ts
tsctype-checks only (noEmit: truein tsconfig.json).- Output is
dist-firefox/ordist-chrome/(gitignored). content.jsis a tiny IIFE boot (public/content.js) injected atdocument_start: it injectshook.jssynchronously, then dynamically imports the real appcontent-app.js.content-app.jsis built as ESM with code-splitting (chunks/*.js).content-app.js+chunks/*.jsare exposed viaweb_accessible_resources. Feature modules (profile/logtime/clusters/…) are lazy-loaded per host insrc/main.ts.popup.jsis bundled separately.background.jsis bundled as IIFE.- Add any new content-script ESM chunk directories to
web_accessible_resourcesin both manifests. manifest.jsonis generated on the fly from per-browser manifest templates.- Icons are copied from
public/icons/on build. To regenerate:node scripts/generate-icons.js(requiressharp).
dev scripts
npm run dev:firefox— watch mode + web-ext auto-reload with Firefox.npm run dev:chrome— watch mode + web-ext auto-reload with Chrome.npm run dev:brave— like Chrome but runs Brave viascripts/run-brave.js, which resolves the Brave binary per OS (override withBRAVE_BINARY).
Framework & toolchain
- Vite 8 +
@tailwindcss/viteplugin (notailwind.config.js— Tailwind v4 CSS-driven config). - daisyUI 5 — loaded via
@plugin "daisyui"insrc/assets/style.css; only a subset of components included. - TypeScript 6 —
strict: true,moduleResolution: bundler,types: ["chrome"]. lit-html— used for DOM templating in the settings UI (hub) and popup.web-ext— for running and signing the extension.- Icons: Font Awesome SVG icons in
src/assets/svg/.
DaisyUI note
daisyUI is scoped to shadow DOM roots (see style.css root: config). Only these components are included: button, toggle, input, select, label, card, tab, modal, divider, swap, status, stat, badge, countdown, avatar, indicator, list, loading, skeleton, progress, steps, join, dropdown, menu, alert. Tailwind content detection is scoped to src/ via source("../") so docs/GIFs don’t generate CSS.
Testing
Tests use Vitest with jsdom environment and global API. Run npm test (single pass) or npm run test:watch (watch mode). Test files: tests/config.test.ts, tests/marks.test.ts, tests/visuals.test.ts, tests/friends.test.ts. Setup: tests/setup.ts. Config: vitest.config.ts.
PWA versioning
Bump the PWA version at commit time, following semver (keep better-intra-pwa/package.json and package-lock.json in sync, then rebuild). Do not bump for uncommitted edits — a batch of changes since the last commit shares the current version and gets a single bump when committed.
- major — breaking changes
- minor — new features
- patch — fixes
The version is injected at build time as __APP_VERSION__ (see better-intra-pwa/vite.config.ts) and shown in Settings → About & Debug. The extension package version is separate — do not bump it for PWA changes.
Do not bump the better-intra-pwa submodule pointer in the parent repo’s commits.
Extension mechanics
- Runs as a content script injected at
document_startonhttps://*.intra.42.fr/*. - Hooks
window.fetchinpublic/hook.js(web accessible resource) to intercept/locations_statsfor logtime data. - Settings stored in
chrome.storage.localvia typedgetConfig()helper. - Cloud sync (optional) talks to
better-intra-workerCloudflare Worker via OAuth2 with 42 API. - Evaluations feature has a background service worker (
src/background.ts) that polls the worker for pending notifications and shows Chrome notifications. Built separately viavite.background.config.ts. - Discord DM notifications are sent by the worker’s 5-min cron — doesn’t require browser to be open.
Worker (better-intra-worker/)
Two KV namespaces
BETTER_INTRA_KV— user data (session tokens, settings, encrypted 42 token, discordId), app token cache, project map, friend IDs cache, online cache.EVAL_KV— eval state keys (EVAL_{hash}_{id}_role), pending notifications (PENDING_{hash}), enabled users list (EVAL_ENABLED_HASHES), Discord-linked users list (DISCORD_HASHES).
Commands
cd better-intra-worker
npm install
npm run dev # wrangler dev
npm run deploy # wrangler deploy
npx wrangler kv:namespace create EVAL_KV # first time, paste id into wrangler.json
npx wrangler secret put DISCORD_BOT_TOKEN # set Discord bot token
Unified evaluations architecture
Worker cron (*/5 * * * *) fetches 42 API /v2/me/scale_teams for each user, detects state changes (null → booked → revealed), sends Discord DMs, and stores pending notifications in EVAL_KV. Extension background service worker polls /evaluations?action=pending every 5 min and shows Chrome notifications from whatever pending list the cron prepared. No 42 API call from the extension side.
CI
- Release drafter — on push/PR to
main; auto-categorizes commits into draft release. - Publish — triggers on GitHub Release publish; builds Firefox (
.xpi) and Chrome (.zip); signs Firefox via AMO for full releases.
DOs and DON’Ts
- NEVER use
innerHTML— lit-html is always available. Userender(unsafeHTML(...), container)fromlit-htmlandlit-html/directives/unsafe-html.jsinstead. - NEVER commit without explicit user approval — do not run
git commit, amend, or push unless the user explicitly asks you to. - NEVER add commit descriptions — commit messages must be a single subject line with no body/description.
NEVER deploy the worker without explicit user consent
NEVER delete from remote D1
Never run destructive commands against the remote D1 database (DELETE, DROP, TRUNCATE, wrangler d1 execute --remote with destructive SQL, etc.). Remote data is not reproducible. To change stored state, rely on the application’s own INSERT OR REPLACE writes or non-destructive updates; ask the user before any statement that removes rows or schema.
No linter; Prettier for formatting
Prettier is used for formatting (editor-level; no committed config). No ESLint or similar configured.