10 KiB
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:
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/<AppName>/.... |
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/<service>/... same-origin gateway endpoints. |
App assets
App-owned binary assets should live under:
apps/<AppName>/assets/system/<AppName>/assets/
They are available through stable URLs:
/@app-assets/<AppName>/<path>
WMR bundles owned by an app may live under:
apps/<AppName>/wmr/
system/<AppName>/wmr/
and are served as:
/@app-assets/<AppName>/wmr/<path>
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:
/cdn/<path>
to:
mobilegym-data/<path>
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:
node scripts/build_nav_artifacts.mjs <AppName>
Useful variants:
node scripts/build_nav_artifacts.mjs <AppName> --data data/index.ts
node scripts/build_nav_artifacts.mjs <AppName> --skip-tasks
node scripts/check_navigation_declaration_consistency.mjs <AppName> --actions
Generated files are written under public/:
| Artifact | Meaning |
|---|---|
<app>_nav_graph.json |
Full schema graph. |
<app>_nav_graph_simplified.json |
Route-level graph with uiStates collapsed. |
<app>_data_graph.json |
Concrete data-mode graph when --data is provided. |
<app>_action_tasks.json / .jsonl |
Generated action trajectories from the schema graph. |
<app>_action_tasks_data.json |
Generated action trajectories from the data graph when --data is provided. |
See ../navigation/declaration.md and ../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/<AppName>/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 <AppName> |
| 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 | <Name>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 <APPNAME>_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 <AppName> after navigation changes; run type/lint checks when the runtime change is broad. |