# Build and Tooling This page documents the platform-side build pipeline, local dev server plugins, generated artifacts, and validation commands. MobileGym is a single Vite project, not a monorepo. Runtime code lives under `os/`, `apps/`, and `system/`; benchmark tooling lives under `bench_env/`. ## Package scripts | Command | Current behavior | |---|---| | `npm run dev` | Starts Vite on port `3000`, host `0.0.0.0`. | | `npm run build` | Runs `vite build`. | | `npm run preview` | Runs `vite preview`. | | `npm run test` | Runs `vitest run`. | | `npm run test:watch` | Runs `vitest`. | | `npm run lint` | Runs ESLint on `os/` and `apps/`, then `scripts/lint_store_getters.mjs`. | | `npm run build:web-css` | Builds `web/tailwind.css` with the Tailwind CLI. | | `npm run watch:web-css` | Watches the standalone web CSS entry. | ## TypeScript `tsconfig.json` uses: | Setting | Value | |---|---| | target | `ES2022` | | module | `ESNext` | | module resolution | `bundler` | | JSX | `react-jsx` | | strict | `true` | | path alias | `@/*` maps to the repository root | The main TS project includes `index.tsx`, `os/**/*.ts(x)`, `apps/**/*.ts(x)`, and `system/**/*.ts(x)`. It excludes generated/output-heavy directories such as `dist`, `runs`, `bench_env`, `ui_dumps`, and extracted asset folders. For large runtime changes, run: ```bash npx tsc --noEmit ``` Small doc/style/data changes do not require a full type check. ## ESLint `eslint.config.js` applies to runtime code in: - `os/**/*.{ts,tsx}` - `apps/**/*.{ts,tsx}` - `system/**/*.{ts,tsx}` Rules currently enforced: | Rule | Why | |---|---| | No bare `Date.now()` | Runtime code must use `TimeService.now()` or `TimeService.realNow()` depending on simulated vs wall-clock time. | | No `new Date(...)` | Runtime code must use `TimeService.getDate()`, `fromTimestamp()`, `fromLocalParts()`, or parsing helpers. | | React Hooks rules | Standard hook correctness. | | React Hooks exhaustive deps | Warning-level dependency checks. | `os/TimeService.ts` is exempt from the Date restrictions because it implements the abstraction. `scripts/lint_store_getters.mjs` catches Zustand patterns that do not re-render correctly, especially store actions used as query getters and subscriptions to stable function references. ## Vite config The project uses `vite.config.ts` with React and several project-specific plugins. ### Server settings | Setting | Value | |---|---| | port | `3000` | | host | `0.0.0.0` | | allowedHosts | `true` | | ignored watch path | `**/runs/**` | ### Resolve settings | Setting | Value | |---|---| | alias | `@` → repository root | | dedupe | `react`, `react-dom`, `lucide-react` | | optimizeDeps exclude | `@sqlite.org/sqlite-wasm` | `@sqlite.org/sqlite-wasm` is excluded so its own `new URL("sqlite3.wasm", import.meta.url)` path resolves beside the package assets instead of Vite's optimized dependency cache. ## Vite plugins | Plugin | Dev | Build/preview | Purpose | |---|---|---|---| | `tailwindCliPlugin` | Builds `index.css`, then starts a Tailwind CLI watcher. | Builds `index.css` during `buildStart`. | Uses Tailwind CSS v4 CLI instead of the JS plugin. | | `react()` | yes | yes | React transform and Fast Refresh. | | `accessLogPlugin` | yes | preview | Logs method, URL, status, and timing. | | `serveAppAssetsPlugin` | yes | build | Serves or emits app-owned assets under `/@app-assets//...`. | | `serveCdnPlugin` | yes | preview | Maps `/cdn/` to repo-local `mobilegym-data/`. | | `listPublicFilesPlugin` | yes | no | Provides `/api/list-public-files` for local viewers. | | `fileSystemPlugin` | yes | build | Provides `/api/sdcard` in dev and emits `sdcard/manifest.json` in builds. | | `runsExplorerPlugin` | yes | no | Provides `/api/runs` for local run exploration. | | `apiGatewayPlugin` | yes | preview | Provides `/api/gw//...` same-origin gateway endpoints. | ## App assets App-owned binary assets should live under: - `apps//assets/` - `system//assets/` They are available through stable URLs: ```text /@app-assets// ``` WMR bundles owned by an app may live under: ```text apps//wmr/ system//wmr/ ``` and are served as: ```text /@app-assets//wmr/ ``` In dev, the plugin serves files from disk. In production builds, it emits them into `dist/@app-assets/`. Prefer Vite imports for ordinary component assets when possible. Use the stable URL path when a subsystem needs a URL string, such as WMR XML/image resources. ## CDN data `serveCdnPlugin` maps: ```text /cdn/ ``` to: ```text mobilegym-data/ ``` The helper in `os/utils/cdn.ts` should be used when runtime code needs URLs for this local/production-compatible data root. ## Virtual SD card `fileSystemPlugin` scans `public/sdcard`. | Endpoint/artifact | Behavior | |---|---| | `GET /api/sdcard` | Dev-only JSON listing of files and directories. Cached until watched files change. | | `dist/sdcard/manifest.json` | Build artifact emitted from the same scan logic. | The scanner intentionally keeps system-style dot directories such as `.data` and `.cache`, but skips known noise files like `.DS_Store` and `.manifest.json`. ## API gateway `apiGatewayPlugin` is the same-origin gateway used by `NetworkService`. | Service | Endpoint | Notes | |---|---|---| | `fetch` | `POST /api/gw/fetch` | JSON payload with `url`, `method`, `headers`, and string `body`. | | `proxy` | `/api/gw/proxy?url=https://...` | Streams requests/responses for assets and non-string bodies. | `VITE_GW_ALLOW_HOSTS` can be set to a comma-separated host allowlist. If it is empty, dev mode allows all hosts. The gateway keeps a server-side cookie jar per `x-gw-session` and contains defensive handling for upstream compression, aborts, retries, and repeated 404 image/video fetches. ## Navigation artifacts Run after modifying navigation declarations: ```bash node scripts/build_nav_artifacts.mjs ``` Useful variants: ```bash node scripts/build_nav_artifacts.mjs --data data/index.ts node scripts/build_nav_artifacts.mjs --skip-tasks node scripts/check_navigation_declaration_consistency.mjs --actions ``` Generated files are written under `public/`: | Artifact | Meaning | |---|---| | `_nav_graph.json` | Full schema graph. | | `_nav_graph_simplified.json` | Route-level graph with uiStates collapsed. | | `_data_graph.json` | Concrete data-mode graph when `--data` is provided. | | `_action_tasks.json` / `.jsonl` | Generated action trajectories from the schema graph. | | `_action_tasks_data.json` | Generated action trajectories from the data graph when `--data` is provided. | See [`../navigation/declaration.md`](../navigation/declaration.md) and [`../navigation/data-sources.md`](../navigation/data-sources.md) for declaration semantics. ## WMR tooling WMR is the Widget Markup Runtime used by launcher widgets and app-owned canvas widgets. Current implementation lives under: | Area | Files | |---|---| | Widget discovery | `os/wmr/WmrWidgetService.ts` | | Bundle loading/cache | `os/wmr/WmrBundleCache.ts` | | XML parser | `os/wmr/engine/parser.ts` | | Expression/variable runtime | `os/wmr/engine/expression.ts`, `os/wmr/engine/variables.ts` | | Canvas renderer | `os/wmr/engine/renderer.ts`, `os/wmr/WmrRenderer.tsx` | | Content providers | `os/wmr/engine/contentProviders.ts` | | Perf metrics | `os/wmr/WmrPerf.ts` via `window.__WMR_PERF__` | Theme widgets are listed from `cdn('themes')/manifest.json`. App-owned WMR bundles can use inline sources and `/@app-assets//wmr/...` URLs. ## i18n tooling OS-level i18n lives in: | File | Role | |---|---| | `os/locale.ts` | Locale state, backed by `OsStateStore.settings.global.language`. | | `os/i18n/index.ts` | `useOsT()`, `osT()`, and app-name localization helpers. | | `os/i18n/en.ts` | English dictionary and `patchAppNames()`. | | `os/PackageManagerService.ts` | Auto-discovers manifests and patches `displayNameEn` into the English dictionary. | Manifest `displayNameEn` values are injected automatically; new apps should not edit `os/i18n/en.ts` just to add their app name. ## Validation checklist | Change type | Suggested check | |---|---| | Navigation declaration or new page route | `node scripts/build_nav_artifacts.mjs ` | | Runtime TypeScript behavior across several files | `npx tsc --noEmit` | | OS/app runtime code | `npm run lint` | | System app runtime code | `npx eslint system/` | | Store action/query selector changes | `node scripts/lint_store_getters.mjs [AppName...]` | | Bench tasks | Follow `bench_env/docs/task/` guides before editing or running tasks. | ## New app checklist Use this as the compact version of the app onboarding checklist: | Area | Check | |---|---| | Manifest | `manifest.ts` exports `manifest`, has a unique `id`, `displayName`, `displayNameEn`, icon, theme, and any `intentFilters`. | | Entry component | `App.tsx` matches `*App.tsx`, has a default export, wraps routes in `MemoryRouter`, and calls `useAppNavigationHandler` inside the router. | | Navigation | Every route, discrete UI state, transition, action, and scroll container is declared in `navigation.declaration.ts`. | | Business navigation | Pages use the app's `go()` / `back()` helper, never direct `useNavigate()` imports. | | DOM tagging | Interactive controls use gesture hooks so they emit `data-trigger*` or `data-action*`; back/close controls bind `system.back`. | | Data | Structural constants live in `constants.ts`; replaceable runtime defaults live in `data/defaults.json`; `data/index.ts` exports `_CONFIG`. | | Large data | Big public datasets stay out of `defaults.json`; add `loader.ts` and decide hook vs store-action vs service consumption. | | Store | `state.ts` uses `createAppStore*` with the same appId as `manifest.id`; `_temp` is reserved for transient UI state. | | Resources | App icons are `Ic*` aliases in `res/icons.tsx`; data-driven icon names also use `Ic*`. | | UI chrome | Page roots reserve status bar space and declare status/navigation bar foreground when needed. | | Keyboard | Bottom input bars use flex layout plus `data-keep-keyboard`; tab bars that should disappear use `data-hide-on-keyboard`. | | Validation | Run `node scripts/build_nav_artifacts.mjs ` after navigation changes; run type/lint checks when the runtime change is broad. |