{"owner":"koala73","repo":"worldmonitor","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md"],"skills":{"AGENTS.md":"# AGENTS.md\n\nAgent entry point for WorldMonitor. Read this first, then follow links for depth.\n\n## What This Project Is\n\nReal-time global intelligence dashboard. TypeScript SPA (Vite + Preact) with 186 top-level TypeScript component files, 80+ Vercel Edge API endpoint entries, a Tauri desktop app with Node.js sidecar, and a Railway relay service. Aggregates geopolitics, military, finance, climate, cyber, maritime, and aviation data across 39 freshness-tracked source groups.\n\n## Repository Map\n\n```\n.\n├── src/                    # Browser SPA (TypeScript, class-based components)\n│   ├── app/                # App orchestration (data-loader, refresh-scheduler, panel-layout)\n│   ├── bootstrap/          # Startup/recovery (chunk reload, deferred Sentry, SW update)\n│   ├── components/         # 186 top-level TypeScript component files\n│   ├── config/             # Variant configs, panel/layer definitions, market symbols\n│   ├── features/           # Self-contained feature surfaces (stock research room)\n│   ├── services/           # Business logic (237 service modules and domain directories)\n│   ├── shared/             # Cross-cutting helpers (premium paths, registries, staleness)\n│   ├── embed/              # Embeddable widget loader\n│   ├── styles/             # Global CSS (layers, themes, panel styles)\n│   ├── shims/              # Runtime shims (child-process for sidecar)\n│   ├── data/               # Static JSON datasets (conservation, renewable, happiness)\n│   ├── e2e/                # Map test harnesses (consumed by Playwright specs)\n│   ├── types/              # TypeScript type definitions\n│   ├── utils/              # Shared utilities (circuit-breaker, theme, URL state, DOM)\n│   ├── workers/            # Web Workers (analysis, ML/ONNX, vector DB)\n│   ├── generated/          # Proto-generated client/server stubs (DO NOT EDIT)\n│   ├── locales/            # i18n translation files\n│   └── App.ts              # Main application entry\n├── api/                    # Vercel Edge Functions (plain JS, self-contained)\n│   ├── _*.js               # Shared helpers (CORS, rate-limit, API key, relay)\n│   ├── health.js           # Health check endpoint\n│   ├── bootstrap.js        # Bulk data hydration endpoint\n│   └── <domain>/           # Domain-specific endpoints (aviation/, climate/, etc.)\n├── server/                 # Server-side shared code (used by Edge Functions)\n│   ├── _shared/            # Redis, rate-limit, LLM, caching, response headers\n│   ├── gateway.ts          # Domain gateway factory (CORS, auth, cache tiers)\n│   ├── router.ts           # Route matching\n│   └── worldmonitor/       # Domain handlers (mirrors proto service structure)\n├── proto/                  # Protobuf definitions (sebuf framework)\n│   ├── buf.yaml            # Buf configuration\n│   └── worldmonitor/       # Service definitions with HTTP annotations\n├── shared/                 # Cross-platform data (JSON configs for markets, RSS domains)\n├── data/                   # Static data (telegram channels, OREF threat translations, gamma irradiators)\n├── public/                 # Static assets served as-is (favicons, textures, .well-known, llms.txt)\n├── scripts/                # Seed scripts, build helpers, data fetchers\n├── src-tauri/              # Tauri desktop shell (Rust + Node.js sidecar)\n│   └── sidecar/            # Node.js sidecar API server\n├── consumer-prices-core/   # Consumer-price scrapers (Playwright, per-country baskets; Railway/Docker)\n├── workers/                # Cloudflare Workers (edge CORS preflight for api.worldmonitor.app)\n├── tests/                  # Unit/integration tests (node:test runner)\n├── e2e/                    # Playwright E2E specs\n├── pro-test/               # Standalone Pro QA app (separate package)\n├── docs/                   # Mintlify documentation site\n│   └── solutions/          # Documented solutions to past problems (bugs, patterns, practices) — YAML frontmatter (module, tags, problem_type)\n├── docker/                 # Docker build for Railway services\n├── deploy/                 # Deployment configs (nginx)\n├── CONCEPTS.md             # Shared domain vocabulary (entities, named processes, status concepts)\n└── blog-site/              # Static blog (built into public/blog/)\n```\n\n## How to Run\n\n```bash\nnpm ci                   # Deterministic install (also runs blog-site postinstall)\nnpm run dev              # Start Vite dev server (full variant)\nnpm run dev:tech         # Start tech-only variant\nnpm run dev:energy       # Start energy-security variant\nnpm run typecheck        # tsc --noEmit (strict mode)\nnpm run typecheck:api    # Typecheck API layer separately\nnpm run test:data        # Run unit/integration tests\nnpm run test:sidecar     # Run sidecar + API handler tests\nnpm run test:e2e         # Run all Playwright E2E tests\nmake generate            # Regenerate proto stubs + per-service & unified OpenAPI specs (requires buf + sebuf v0.11.1 plugins)\nnpm run worktree:bootstrap          # Fresh worktree: link local env files + npm ci with tmp cache\nnpm run worktree:bootstrap:test-only # Fresh docs/test worktree: same, but npm ci --ignore-scripts\nnpm run worktree:env                # Link ignored local env files only\n```\n\n## Fresh Worktree Bootstrap\n\nWorktrees usually start without ignored local state. When creating or entering one:\n\n1. Start from `origin/main` or the requested base, not a dirty local branch.\n2. Run `npm run worktree:bootstrap` before typecheck/tests. The helper links ignored `.env.local` / `.env` from the main worktree when Git can infer it, and installs deps with `npm ci --cache /tmp/worldmonitor-npm-cache`.\n3. If only docs/test tooling is needed and native postinstall work is unnecessary, use `npm run worktree:bootstrap:test-only`.\n4. If live credentials are unavailable, do not fabricate secrets. Run the non-credentialed checks you can and report the credential gate explicitly.\n\nEnv rules:\n\n- Link only `.env.local` and `.env`. Never copy or link `.env.vercel-backup` or `.env.vercel-export`; the pre-push guard blocks those files even as symlinks.\n- Override env source discovery with `WM_ENV_SOURCE=/path/to/worldmonitor npm run worktree:env` when the main worktree cannot be inferred.\n- `.env*` files are ignored local state. Do not add, print, or summarize secret values.\n\nValidation hygiene:\n\n- Prefer `npm ci` over `npm install` in fresh worktrees. Use `npm_config_cache=/tmp/worldmonitor-npm-cache` for `npx` or install commands if cache ownership errors appear.\n- After bootstrap or pre-push, run `git status --short`. If dependency bootstrap changed lockfiles you did not intend to edit, remove those incidental changes before finalizing.\n- After install, prefer local tools such as `./node_modules/.bin/tsx --test ...` for focused TypeScript tests when `npx` is flaky.\n\n## Architecture Rules\n\n### Dependency Direction\n\n```\ntypes -> config -> services -> components -> app -> App.ts\n```\n\n- `types/` has zero internal imports\n- `config/` imports only from `types/`\n- `services/` imports from `types/` and `config/`\n- `components/` imports from all above\n- `app/` orchestrates components and services\n\n### API Layer Constraints\n\n- `api/*.js` are Vercel Edge Functions: **self-contained JS only**\n- They CANNOT import from `../src/` or `../server/` (different runtime)\n- Only same-directory `_*.js` helpers and npm packages\n- Enforced by `tests/edge-functions.test.mjs` and pre-push hook esbuild check\n\n### Server Layer\n\n- `server/` code is bundled INTO Edge Functions at deploy time via gateway\n- `server/_shared/` contains Redis client, rate limiting, LLM helpers\n- `server/worldmonitor/<domain>/` has RPC handlers matching proto services\n- All handlers use `cachedFetchJson()` for Redis caching with stampede protection\n\n### Proto Contract Flow\n\n```\nproto/ definitions -> buf generate -> src/generated/{client,server}/ -> handlers wire up\n```\n\n- GET fields need `(sebuf.http.query)` annotation\n- `repeated string` fields need `parseStringArray()` in handler\n- `int64` maps to `string` in TypeScript\n- CI checks proto freshness via `.github/workflows/proto-check.yml`\n\n## Variant System\n\nThe app ships multiple variants with different panel/layer configurations:\n\n- `full` (default): All features\n- `tech`: Technology-focused subset\n- `finance`: Financial markets focus\n- `commodity`: Commodity markets focus\n- `happy`: Positive news only\n- `energy`: Energy security, chokepoints, oil/gas, and disruption timelines\n\nVariant is set via `VITE_VARIANT` env var. Config lives in `src/config/variants/`.\n\n## Key Patterns\n\n### Adding a New API Endpoint\n\n1. Define proto message in `proto/worldmonitor/<domain>/`\n2. Add RPC with `(sebuf.http.config)` annotation\n3. Run `make generate`\n4. Create handler in `server/worldmonitor/<domain>/`\n5. Wire handler in domain's `handler.ts`\n6. Use `cachedFetchJson()` for caching, include request params in cache key\n\n### Adding a New Panel\n\n1. Create `src/components/MyPanel.ts` extending `Panel`\n2. Register in `src/config/panels.ts`\n3. Add to variant configs in `src/config/variants/`\n4. Wire data loading in `src/app/data-loader.ts`\n\n### Circuit Breakers\n\n- `src/utils/circuit-breaker.ts` for client-side\n- Used in data loaders to prevent cascade failures\n- Separate breaker per data domain\n\n### Caching\n\n- Redis (Upstash) via `server/_shared/redis.ts`\n- `cachedFetchJson()` coalesces concurrent cache misses\n- Cache tiers: fast (5m), medium (10m), slow (30m), static (2h), daily (24h)\n- Cache key MUST include request-varying params\n\n## Testing\n\n- **Unit/Integration**: `tests/*.test.{mjs,mts}` using `node:test` runner\n- **Sidecar tests**: `api/*.test.mjs`, `src-tauri/sidecar/*.test.mjs`\n- **E2E**: `e2e/*.spec.ts` using Playwright\n- **Visual regression**: Golden screenshot comparison per variant\n\n## CI Checks (GitHub Actions)\n\n| Workflow | Trigger | What it checks |\n|---|---|---|\n| `typecheck.yml` | PR + push to main | `tsc --noEmit` for src and API |\n| `lint.yml` | PR (markdown changes) | markdownlint-cli2 |\n| `proto-check.yml` | PR (proto changes) | Generated code freshness |\n| `build-desktop.yml` | `v*` tag, manual | Tauri desktop build |\n| `test-linux-app.yml` | Twice-weekly schedule, manual | Desktop Canary (Linux): release-processed AppImage smoke — crash, sidecar readiness/liveness, rendered content |\n| `test.yml` (`desktop-config`, `desktop-rust` jobs) | PR touching desktop-coupled paths | Desktop version consistency, AppImage post-processing syntax, Tauri config/capability parse, desktop build env parity (#5905, also in `unit`), `cargo test --locked` (#5902) |\n\n## Pre-Push Hook\n\nRuns automatically before `git push`. Two tiers:\n\n**Always (state-dependent, fast — run even on a cache hit):** local Vercel env-dump guard, PR-state check (no pushes to merged/closed PR branches), branch-contamination guard (>20 commits ahead), `scripts/` lockfile sync.\n\n**Tree-dependent (skipped entirely on a green-tree cache hit):** Unicode safety and version sync (always run for uncached trees), plus the diff-scoped checks: TypeScript (frontend tsc on `src/`-surface changes; `typecheck:api` on `api/|server/|scripts/|src/generated/`; Convex tsc on `convex/`), CJS syntax, boundary/safe-html/Sentry-coverage/rate-limit/premium-fetch lints (each also fires when its own guardrail script changes), edge esbuild check (`api/|server/|src/generated/|scripts/check-edge-function-bundles.mjs` — edge entries bundle-import server code, and the shared checker retriggers its own gate), markdown/MDX lint, proto + pro-test bundle freshness, change-scoped tests. `package.json`/`tsconfig` changes — or an unresolvable `origin/main` diff — force everything (an unresolvable diff also bypasses the green-tree cache: a blind run trusts nothing, including prior attestations).\n\n**Green-tree cache:** a tree that passed the full gate is recorded (`$GIT_DIR/wm-prepush-green`); re-pushing the identical tree (remote failure, message-only amend) skips all tree-dependent checks — same tree, same result. Delete that file to force a full re-run.\n\nHeavy checks (`test:data`, typechecks, edge-bundle) must run **sequentially** in worktrees — parallel runs OOM (exit 137).\n\n## Shipping Velocity (Agent Workflow)\n\n- **Before starting work on an issue:** check for parallel/duplicate work first — `gh pr list --search \"<issue#>\"` AND `git worktree list` (background codex/claude sessions ship PRs under the same account).\n- **PR delivery authority:** a user request to implement, fix, or ship a scoped change authorizes creating and updating the ready PRs needed to deliver it, including corrective follow-up PRs discovered by review or CI, plus monitoring and repairing those PRs without additional per-PR confirmation. This authority is limited to the requested change and its delivery branches; review-only or diagnostic requests remain read-only.\n- **Merge authority is explicit and non-delegable:** never merge a PR, enable auto-merge, queue a merge, or run any equivalent GitHub merge action unless the user has explicitly requested that specific action in the current conversation. A request to implement, ship, push, create a PR, or monitor CI does **not** authorize merging. Wait for clear approval and report the ready state instead.\n- **PR push readiness is mandatory:** before every push, re-fetch the live PR head and base, verify the remote head has not advanced, and check GitHub mergeability. Do not push a branch that is behind, `CONFLICTING`, or `DIRTY`; update from the latest PR/base state and resolve conflicts first. A successful `git push` is not delivery completion.\n- **After pushing a PR:** start `gh pr checks <n> --watch` (or an equivalent bounded monitor), wait for all required CI checks to reach green, then re-fetch the PR head and verify GitHub reports no conflict (`mergeable: MERGEABLE` / clean merge state). If checks are pending, failing, or the PR becomes conflicting, keep repairing and re-checking; do not report the PR as ready or complete until both CI and mergeability are green. Never use `--no-verify` to bypass this gate or turn on auto-merge without the explicit approval above.\n- **docs/plans/ is gitignored** — plan documents are local working state and do not travel between worktrees or ship in PRs.\n- **PR-review verification:** never assert a finding is fixed/stale from memory — re-fetch the PR head SHA and diff the cited lines first.\n\n## Deployment\n\n- **Web**: Vercel (auto-deploy on push to main)\n- **Relay/Seeds**: Railway (Docker, cron services)\n- **Desktop**: Tauri builds via GitHub Actions\n- **Docs**: Mintlify (proxied through Vercel at `/docs`)\n\n## Critical Conventions\n\n- `fetch.bind(globalThis)` is BANNED. Use `(...args) => globalThis.fetch(...args)` instead\n- Edge Functions cannot use `node:http`, `node:https`, `node:zlib`\n- Always include `User-Agent` header in server-side fetch calls\n- Yahoo Finance requests must be staggered (150ms delays)\n- New data sources MUST have bootstrap hydration wired in `api/bootstrap.js` — unless nothing in `src/` renders them. A dataset with no dashboard consumer registers in `api/health.js` `STANDALONE_KEYS` instead and stays out of the tiered payload every client downloads; `tests/bootstrap.test.mjs` enforces the converse, that no tier key lacks a `getHydratedData`/`ensureHydrated` consumer. Once a panel does read one, promote it into `BOOTSTRAP_CACHE_KEYS` with a tier — `ON_DEMAND_KEY_NAMES` for an opt-in panel, so the payload is fetched per-key on render rather than riding a tier every visitor downloads (`fxYoy` and `sharedFxRates` went this way for the FX panel, #6199)\n- Redis seed scripts MUST write `seed-meta:<key>` for health monitoring\n- Seed credentials load only via `loadEnvFile()` (inert under test runtimes, resolves `.env.local` at the checkout root, `only:` narrows the keys) — never hand-roll a `.env` reader or resolve one from `$HOME` or an absolute literal. Note `worktree:bootstrap` symlinks the source checkout's `.env.local`, so a bootstrapped worktree shares real credentials when a seeder is actually run\n\n## External References\n\n- [Architecture (system reference)](ARCHITECTURE.md)\n- [Design Philosophy (why decisions were made)](docs/architecture.mdx)\n- [Contributing guide](CONTRIBUTING.md)\n- [Data sources catalog](docs/data-sources.mdx)\n- [Health endpoints](docs/health-endpoints.mdx)\n- [Adding endpoints guide](docs/adding-endpoints.mdx)\n- [API reference (OpenAPI)](docs/api/)\n"},"files":{"AGENTS.md":"# AGENTS.md\n\nAgent entry point for WorldMonitor. Read this first, then follow links for depth.\n\n## What This Project Is\n\nReal-time global intelligence dashboard. TypeScript SPA (Vite + Preact) with 186 top-level TypeScript component files, 80+ Vercel Edge API endpoint entries, a Tauri desktop app with Node.js sidecar, and a Railway relay service. Aggregates geopolitics, military, finance, climate, cyber, maritime, and aviation data across 39 freshness-tracked source groups.\n\n## Repository Map\n\n```\n.\n├── src/                    # Browser SPA (TypeScript, class-based components)\n│   ├── app/                # App orchestration (data-loader, refresh-scheduler, panel-layout)\n│   ├── bootstrap/          # Startup/recovery (chunk reload, deferred Sentry, SW update)\n│   ├── components/         # 186 top-level TypeScript component files\n│   ├── config/             # Variant configs, panel/layer definitions, market symbols\n│   ├── features/           # Self-contained feature surfaces (stock research room)\n│   ├── services/           # Business logic (237 service modules and domain directories)\n│   ├── shared/             # Cross-cutting helpers (premium paths, registries, staleness)\n│   ├── embed/              # Embeddable widget loader\n│   ├── styles/             # Global CSS (layers, themes, panel styles)\n│   ├── shims/              # Runtime shims (child-process for sidecar)\n│   ├── data/               # Static JSON datasets (conservation, renewable, happiness)\n│   ├── e2e/                # Map test harnesses (consumed by Playwright specs)\n│   ├── types/              # TypeScript type definitions\n│   ├── utils/              # Shared utilities (circuit-breaker, theme, URL state, DOM)\n│   ├── workers/            # Web Workers (analysis, ML/ONNX, vector DB)\n│   ├── generated/          # Proto-generated client/server stubs (DO NOT EDIT)\n│   ├── locales/            # i18n translation files\n│   └── App.ts              # Main application entry\n├── api/                    # Vercel Edge Functions (plain JS, self-contained)\n│   ├── _*.js               # Shared helpers (CORS, rate-limit, API key, relay)\n│   ├── health.js           # Health check endpoint\n│   ├── bootstrap.js        # Bulk data hydration endpoint\n│   └── <domain>/           # Domain-specific endpoints (aviation/, climate/, etc.)\n├── server/                 # Server-side shared code (used by Edge Functions)\n│   ├── _shared/            # Redis, rate-limit, LLM, caching, response headers\n│   ├── gateway.ts          # Domain gateway factory (CORS, auth, cache tiers)\n│   ├── router.ts           # Route matching\n│   └── worldmonitor/       # Domain handlers (mirrors proto service structure)\n├── proto/                  # Protobuf definitions (sebuf framework)\n│   ├── buf.yaml            # Buf configuration\n│   └── worldmonitor/       # Service definitions with HTTP annotations\n├── shared/                 # Cross-platform data (JSON configs for markets, RSS domains)\n├── data/                   # Static data (telegram channels, OREF threat translations, gamma irradiators)\n├── public/                 # Static assets served as-is (favicons, textures, .well-known, llms.txt)\n├── scripts/                # Seed scripts, build helpers, data fetchers\n├── src-tauri/              # Tauri desktop shell (Rust + Node.js sidecar)\n│   └── sidecar/            # Node.js sidecar API server\n├── consumer-prices-core/   # Consumer-price scrapers (Playwright, per-country baskets; Railway/Docker)\n├── workers/                # Cloudflare Workers (edge CORS preflight for api.worldmonitor.app)\n├── tests/                  # Unit/integration tests (node:test runner)\n├── e2e/                    # Playwright E2E specs\n├── pro-test/               # Standalone Pro QA app (separate package)\n├── docs/                   # Mintlify documentation site\n│   └── solutions/          # Documented solutions to past problems (bugs, patterns, practices) — YAML frontmatter (module, tags, problem_type)\n├── docker/                 # Docker build for Railway services\n├── deploy/                 # Deployment configs (nginx)\n├── CONCEPTS.md             # Shared domain vocabulary (entities, named processes, status concepts)\n└── blog-site/              # Static blog (built into public/blog/)\n```\n\n## How to Run\n\n```bash\nnpm ci                   # Deterministic install (also runs blog-site postinstall)\nnpm run dev              # Start Vite dev server (full variant)\nnpm run dev:tech         # Start tech-only variant\nnpm run dev:energy       # Start energy-security variant\nnpm run typecheck        # tsc --noEmit (strict mode)\nnpm run typecheck:api    # Typecheck API layer separately\nnpm run test:data        # Run unit/integration tests\nnpm run test:sidecar     # Run sidecar + API handler tests\nnpm run test:e2e         # Run all Playwright E2E tests\nmake generate            # Regenerate proto stubs + per-service & unified OpenAPI specs (requires buf + sebuf v0.11.1 plugins)\nnpm run worktree:bootstrap          # Fresh worktree: link local env files + npm ci with tmp cache\nnpm run worktree:bootstrap:test-only # Fresh docs/test worktree: same, but npm ci --ignore-scripts\nnpm run worktree:env                # Link ignored local env files only\n```\n\n## Fresh Worktree Bootstrap\n\nWorktrees usually start without ignored local state. When creating or entering one:\n\n1. Start from `origin/main` or the requested base, not a dirty local branch.\n2. Run `npm run worktree:bootstrap` before typecheck/tests. The helper links ignored `.env.local` / `.env` from the main worktree when Git can infer it, and installs deps with `npm ci --cache /tmp/worldmonitor-npm-cache`.\n3. If only docs/test tooling is needed and native postinstall work is unnecessary, use `npm run worktree:bootstrap:test-only`.\n4. If live credentials are unavailable, do not fabricate secrets. Run the non-credentialed checks you can and report the credential gate explicitly.\n\nEnv rules:\n\n- Link only `.env.local` and `.env`. Never copy or link `.env.vercel-backup` or `.env.vercel-export`; the pre-push guard blocks those files even as symlinks.\n- Override env source discovery with `WM_ENV_SOURCE=/path/to/worldmonitor npm run worktree:env` when the main worktree cannot be inferred.\n- `.env*` files are ignored local state. Do not add, print, or summarize secret values.\n\nValidation hygiene:\n\n- Prefer `npm ci` over `npm install` in fresh worktrees. Use `npm_config_cache=/tmp/worldmonitor-npm-cache` for `npx` or install commands if cache ownership errors appear.\n- After bootstrap or pre-push, run `git status --short`. If dependency bootstrap changed lockfiles you did not intend to edit, remove those incidental changes before finalizing.\n- After install, prefer local tools such as `./node_modules/.bin/tsx --test ...` for focused TypeScript tests when `npx` is flaky.\n\n## Architecture Rules\n\n### Dependency Direction\n\n```\ntypes -> config -> services -> components -> app -> App.ts\n```\n\n- `types/` has zero internal imports\n- `config/` imports only from `types/`\n- `services/` imports from `types/` and `config/`\n- `components/` imports from all above\n- `app/` orchestrates components and services\n\n### API Layer Constraints\n\n- `api/*.js` are Vercel Edge Functions: **self-contained JS only**\n- They CANNOT import from `../src/` or `../server/` (different runtime)\n- Only same-directory `_*.js` helpers and npm packages\n- Enforced by `tests/edge-functions.test.mjs` and pre-push hook esbuild check\n\n### Server Layer\n\n- `server/` code is bundled INTO Edge Functions at deploy time via gateway\n- `server/_shared/` contains Redis client, rate limiting, LLM helpers\n- `server/worldmonitor/<domain>/` has RPC handlers matching proto services\n- All handlers use `cachedFetchJson()` for Redis caching with stampede protection\n\n### Proto Contract Flow\n\n```\nproto/ definitions -> buf generate -> src/generated/{client,server}/ -> handlers wire up\n```\n\n- GET fields need `(sebuf.http.query)` annotation\n- `repeated string` fields need `parseStringArray()` in handler\n- `int64` maps to `string` in TypeScript\n- CI checks proto freshness via `.github/workflows/proto-check.yml`\n\n## Variant System\n\nThe app ships multiple variants with different panel/layer configurations:\n\n- `full` (default): All features\n- `tech`: Technology-focused subset\n- `finance`: Financial markets focus\n- `commodity`: Commodity markets focus\n- `happy`: Positive news only\n- `energy`: Energy security, chokepoints, oil/gas, and disruption timelines\n\nVariant is set via `VITE_VARIANT` env var. Config lives in `src/config/variants/`.\n\n## Key Patterns\n\n### Adding a New API Endpoint\n\n1. Define proto message in `proto/worldmonitor/<domain>/`\n2. Add RPC with `(sebuf.http.config)` annotation\n3. Run `make generate`\n4. Create handler in `server/worldmonitor/<domain>/`\n5. Wire handler in domain's `handler.ts`\n6. Use `cachedFetchJson()` for caching, include request params in cache key\n\n### Adding a New Panel\n\n1. Create `src/components/MyPanel.ts` extending `Panel`\n2. Register in `src/config/panels.ts`\n3. Add to variant configs in `src/config/variants/`\n4. Wire data loading in `src/app/data-loader.ts`\n\n### Circuit Breakers\n\n- `src/utils/circuit-breaker.ts` for client-side\n- Used in data loaders to prevent cascade failures\n- Separate breaker per data domain\n\n### Caching\n\n- Redis (Upstash) via `server/_shared/redis.ts`\n- `cachedFetchJson()` coalesces concurrent cache misses\n- Cache tiers: fast (5m), medium (10m), slow (30m), static (2h), daily (24h)\n- Cache key MUST include request-varying params\n\n## Testing\n\n- **Unit/Integration**: `tests/*.test.{mjs,mts}` using `node:test` runner\n- **Sidecar tests**: `api/*.test.mjs`, `src-tauri/sidecar/*.test.mjs`\n- **E2E**: `e2e/*.spec.ts` using Playwright\n- **Visual regression**: Golden screenshot comparison per variant\n\n## CI Checks (GitHub Actions)\n\n| Workflow | Trigger | What it checks |\n|---|---|---|\n| `typecheck.yml` | PR + push to main | `tsc --noEmit` for src and API |\n| `lint.yml` | PR (markdown changes) | markdownlint-cli2 |\n| `proto-check.yml` | PR (proto changes) | Generated code freshness |\n| `build-desktop.yml` | `v*` tag, manual | Tauri desktop build |\n| `test-linux-app.yml` | Twice-weekly schedule, manual | Desktop Canary (Linux): release-processed AppImage smoke — crash, sidecar readiness/liveness, rendered content |\n| `test.yml` (`desktop-config`, `desktop-rust` jobs) | PR touching desktop-coupled paths | Desktop version consistency, AppImage post-processing syntax, Tauri config/capability parse, desktop build env parity (#5905, also in `unit`), `cargo test --locked` (#5902) |\n\n## Pre-Push Hook\n\nRuns automatically before `git push`. Two tiers:\n\n**Always (state-dependent, fast — run even on a cache hit):** local Vercel env-dump guard, PR-state check (no pushes to merged/closed PR branches), branch-contamination guard (>20 commits ahead), `scripts/` lockfile sync.\n\n**Tree-dependent (skipped entirely on a green-tree cache hit):** Unicode safety and version sync (always run for uncached trees), plus the diff-scoped checks: TypeScript (frontend tsc on `src/`-surface changes; `typecheck:api` on `api/|server/|scripts/|src/generated/`; Convex tsc on `convex/`), CJS syntax, boundary/safe-html/Sentry-coverage/rate-limit/premium-fetch lints (each also fires when its own guardrail script changes), edge esbuild check (`api/|server/|src/generated/|scripts/check-edge-function-bundles.mjs` — edge entries bundle-import server code, and the shared checker retriggers its own gate), markdown/MDX lint, proto + pro-test bundle freshness, change-scoped tests. `package.json`/`tsconfig` changes — or an unresolvable `origin/main` diff — force everything (an unresolvable diff also bypasses the green-tree cache: a blind run trusts nothing, including prior attestations).\n\n**Green-tree cache:** a tree that passed the full gate is recorded (`$GIT_DIR/wm-prepush-green`); re-pushing the identical tree (remote failure, message-only amend) skips all tree-dependent checks — same tree, same result. Delete that file to force a full re-run.\n\nHeavy checks (`test:data`, typechecks, edge-bundle) must run **sequentially** in worktrees — parallel runs OOM (exit 137).\n\n## Shipping Velocity (Agent Workflow)\n\n- **Before starting work on an issue:** check for parallel/duplicate work first — `gh pr list --search \"<issue#>\"` AND `git worktree list` (background codex/claude sessions ship PRs under the same account).\n- **PR delivery authority:** a user request to implement, fix, or ship a scoped change authorizes creating and updating the ready PRs needed to deliver it, including corrective follow-up PRs discovered by review or CI, plus monitoring and repairing those PRs without additional per-PR confirmation. This authority is limited to the requested change and its delivery branches; review-only or diagnostic requests remain read-only.\n- **Merge authority is explicit and non-delegable:** never merge a PR, enable auto-merge, queue a merge, or run any equivalent GitHub merge action unless the user has explicitly requested that specific action in the current conversation. A request to implement, ship, push, create a PR, or monitor CI does **not** authorize merging. Wait for clear approval and report the ready state instead.\n- **PR push readiness is mandatory:** before every push, re-fetch the live PR head and base, verify the remote head has not advanced, and check GitHub mergeability. Do not push a branch that is behind, `CONFLICTING`, or `DIRTY`; update from the latest PR/base state and resolve conflicts first. A successful `git push` is not delivery completion.\n- **After pushing a PR:** start `gh pr checks <n> --watch` (or an equivalent bounded monitor), wait for all required CI checks to reach green, then re-fetch the PR head and verify GitHub reports no conflict (`mergeable: MERGEABLE` / clean merge state). If checks are pending, failing, or the PR becomes conflicting, keep repairing and re-checking; do not report the PR as ready or complete until both CI and mergeability are green. Never use `--no-verify` to bypass this gate or turn on auto-merge without the explicit approval above.\n- **docs/plans/ is gitignored** — plan documents are local working state and do not travel between worktrees or ship in PRs.\n- **PR-review verification:** never assert a finding is fixed/stale from memory — re-fetch the PR head SHA and diff the cited lines first.\n\n## Deployment\n\n- **Web**: Vercel (auto-deploy on push to main)\n- **Relay/Seeds**: Railway (Docker, cron services)\n- **Desktop**: Tauri builds via GitHub Actions\n- **Docs**: Mintlify (proxied through Vercel at `/docs`)\n\n## Critical Conventions\n\n- `fetch.bind(globalThis)` is BANNED. Use `(...args) => globalThis.fetch(...args)` instead\n- Edge Functions cannot use `node:http`, `node:https`, `node:zlib`\n- Always include `User-Agent` header in server-side fetch calls\n- Yahoo Finance requests must be staggered (150ms delays)\n- New data sources MUST have bootstrap hydration wired in `api/bootstrap.js` — unless nothing in `src/` renders them. A dataset with no dashboard consumer registers in `api/health.js` `STANDALONE_KEYS` instead and stays out of the tiered payload every client downloads; `tests/bootstrap.test.mjs` enforces the converse, that no tier key lacks a `getHydratedData`/`ensureHydrated` consumer. Once a panel does read one, promote it into `BOOTSTRAP_CACHE_KEYS` with a tier — `ON_DEMAND_KEY_NAMES` for an opt-in panel, so the payload is fetched per-key on render rather than riding a tier every visitor downloads (`fxYoy` and `sharedFxRates` went this way for the FX panel, #6199)\n- Redis seed scripts MUST write `seed-meta:<key>` for health monitoring\n- Seed credentials load only via `loadEnvFile()` (inert under test runtimes, resolves `.env.local` at the checkout root, `only:` narrows the keys) — never hand-roll a `.env` reader or resolve one from `$HOME` or an absolute literal. Note `worktree:bootstrap` symlinks the source checkout's `.env.local`, so a bootstrapped worktree shares real credentials when a seeder is actually run\n\n## External References\n\n- [Architecture (system reference)](ARCHITECTURE.md)\n- [Design Philosophy (why decisions were made)](docs/architecture.mdx)\n- [Contributing guide](CONTRIBUTING.md)\n- [Data sources catalog](docs/data-sources.mdx)\n- [Health endpoints](docs/health-endpoints.mdx)\n- [Adding endpoints guide](docs/adding-endpoints.mdx)\n- [API reference (OpenAPI)](docs/api/)\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# AGENTS.md\n\nAgent entry point for WorldMonitor. Read this first, then follow links for depth.\n\n## What This Project Is\n\nReal-time global intelligence dashboard. TypeScript SPA (Vite + Preact) with 186 top-level TypeScript component files, 80+ Vercel Edge API endpoint entries, a Tauri desktop app with Node.js sidecar, and a Railway relay service. Aggregates geopolitics, military, finance, climate, cyber, maritime, and aviation data across 39 freshness-tracked source groups.\n\n## Repository Map\n\n```\n.\n├── src/                    # Browser SPA (TypeScript, class-based components)\n│   ├── app/                # App orchestration (data-loader, refresh-scheduler, panel-layout)\n│   ├── bootstrap/          # Startup/recovery (chunk reload, deferred Sentry, SW update)\n│   ├── components/         # 186 top-level TypeScript component files\n│   ├── config/             # Variant configs, panel/layer definitions, market symbols\n│   ├── features/           # Self-contained feature surfaces (stock research room)\n│   ├── services/           # Business logic (237 service modules and domain directories)\n│   ├── shared/             # Cross-cutting helpers (premium paths, registries, staleness)\n│   ├── embed/              # Embeddable widget loader\n│   ├── styles/             # Global CSS (layers, themes, panel styles)\n│   ├── shims/              # Runtime shims (child-process for sidecar)\n│   ├── data/               # Static JSON datasets (conservation, renewable, happiness)\n│   ├── e2e/                # Map test harnesses (consumed by Playwright specs)\n│   ├── types/              # TypeScript type definitions\n│   ├── utils/              # Shared utilities (circuit-breaker, theme, URL state, DOM)\n│   ├── workers/            # Web Workers (analysis, ML/ONNX, vector DB)\n│   ├── generated/          # Proto-generated client/server stubs (DO NOT EDIT)\n│   ├── locales/            # i18n translation files\n│   └── App.ts              # Main application entry\n├── api/                    # Vercel Edge Functions (plain JS, self-contained)\n│   ├── _*.js               # Shared helpers (CORS, rate-limit, API key, relay)\n│   ├── health.js           # Health check endpoint\n│   ├── bootstrap.js        # Bulk data hydration endpoint\n│   └── <domain>/           # Domain-specific endpoints (aviation/, climate/, etc.)\n├── server/                 # Server-side shared code (used by Edge Functions)\n│   ├── _shared/            # Redis, rate-limit, LLM, caching, response headers\n│   ├── gateway.ts          # Domain gateway factory (CORS, auth, cache tiers)\n│   ├── router.ts           # Route matching\n│   └── worldmonitor/       # Domain handlers (mirrors proto service structure)\n├── proto/                  # Protobuf definitions (sebuf framework)\n│   ├── buf.yaml            # Buf configuration\n│   └── worldmonitor/       # Service definitions with HTTP annotations\n├── shared/                 # Cross-platform data (JSON configs for markets, RSS domains)\n├── data/                   # Static data (telegram channels, OREF threat translations, gamma irradiators)\n├── public/                 # Static assets served as-is (favicons, textures, .well-known, llms.txt)\n├── scripts/                # Seed scripts, build helpers, data fetchers\n├── src-tauri/              # Tauri desktop shell (Rust + Node.js sidecar)\n│   └── sidecar/            # Node.js sidecar API server\n├── consumer-prices-core/   # Consumer-price scrapers (Playwright, per-country baskets; Railway/Docker)\n├── workers/                # Cloudflare Workers (edge CORS preflight for api.worldmonitor.app)\n├── tests/                  # Unit/integration tests (node:test runner)\n├── e2e/                    # Playwright E2E specs\n├── pro-test/               # Standalone Pro QA app (separate package)\n├── docs/                   # Mintlify documentation site\n│   └── solutions/          # Documented solutions to past problems (bugs, patterns, practices) — YAML frontmatter (module, tags, problem_type)\n├── docker/                 # Docker build for Railway services\n├── deploy/                 # Deployment configs (nginx)\n├── CONCEPTS.md             # Shared domain vocabulary (entities, named processes, status concepts)\n└── blog-site/              # Static blog (built into public/blog/)\n```\n\n## How to Run\n\n```bash\nnpm ci                   # Deterministic install (also runs blog-site postinstall)\nnpm run dev              # Start Vite dev server (full variant)\nnpm run dev:tech         # Start tech-only variant\nnpm run dev:energy       # Start energy-security variant\nnpm run typecheck        # tsc --noEmit (strict mode)\nnpm run typecheck:api    # Typecheck API layer separately\nnpm run test:data        # Run unit/integration tests\nnpm run test:sidecar     # Run sidecar + API handler tests\nnpm run test:e2e         # Run all Playwright E2E tests\nmake generate            # Regenerate proto stubs + per-service & unified OpenAPI specs (requires buf + sebuf v0.11.1 plugins)\nnpm run worktree:bootstrap          # Fresh worktree: link local env files + npm ci with tmp cache\nnpm run worktree:bootstrap:test-only # Fresh docs/test worktree: same, but npm ci --ignore-scripts\nnpm run worktree:env                # Link ignored local env files only\n```\n\n## Fresh Worktree Bootstrap\n\nWorktrees usually start without ignored local state. When creating or entering one:\n\n1. Start from `origin/main` or the requested base, not a dirty local branch.\n2. Run `npm run worktree:bootstrap` before typecheck/tests. The helper links ignored `.env.local` / `.env` from the main worktree when Git can infer it, and installs deps with `npm ci --cache /tmp/worldmonitor-npm-cache`.\n3. If only docs/test tooling is needed and native postinstall work is unnecessary, use `npm run worktree:bootstrap:test-only`.\n4. If live credentials are unavailable, do not fabricate secrets. Run the non-credentialed checks you can and report the credential gate explicitly.\n\nEnv rules:\n\n- Link only `.env.local` and `.env`. Never copy or link `.env.vercel-backup` or `.env.vercel-export`; the pre-push guard blocks those files even as symlinks.\n- Override env source discovery with `WM_ENV_SOURCE=/path/to/worldmonitor npm run worktree:env` when the main worktree cannot be inferred.\n- `.env*` files are ignored local state. Do not add, print, or summarize secret values.\n\nValidation hygiene:\n\n- Prefer `npm ci` over `npm install` in fresh worktrees. Use `npm_config_cache=/tmp/worldmonitor-npm-cache` for `npx` or install commands if cache ownership errors appear.\n- After bootstrap or pre-push, run `git status --short`. If dependency bootstrap changed lockfiles you did not intend to edit, remove those incidental changes before finalizing.\n- After install, prefer local tools such as `./node_modules/.bin/tsx --test ...` for focused TypeScript tests when `npx` is flaky.\n\n## Architecture Rules\n\n### Dependency Direction\n\n```\ntypes -> config -> services -> components -> app -> App.ts\n```\n\n- `types/` has zero internal imports\n- `config/` imports only from `types/`\n- `services/` imports from `types/` and `config/`\n- `components/` imports from all above\n- `app/` orchestrates components and services\n\n### API Layer Constraints\n\n- `api/*.js` are Vercel Edge Functions: **self-contained JS only**\n- They CANNOT import from `../src/` or `../server/` (different runtime)\n- Only same-directory `_*.js` helpers and npm packages\n- Enforced by `tests/edge-functions.test.mjs` and pre-push hook esbuild check\n\n### Server Layer\n\n- `server/` code is bundled INTO Edge Functions at deploy time via gateway\n- `server/_shared/` contains Redis client, rate limiting, LLM helpers\n- `server/worldmonitor/<domain>/` has RPC handlers matching proto services\n- All handlers use `cachedFetchJson()` for Redis caching with stampede protection\n\n### Proto Contract Flow\n\n```\nproto/ definitions -> buf generate -> src/generated/{client,server}/ -> handlers wire up\n```\n\n- GET fields need `(sebuf.http.query)` annotation\n- `repeated string` fields need `parseStringArray()` in handler\n- `int64` maps to `string` in TypeScript\n- CI checks proto freshness via `.github/workflows/proto-check.yml`\n\n## Variant System\n\nThe app ships multiple variants with different panel/layer configurations:\n\n- `full` (default): All features\n- `tech`: Technology-focused subset\n- `finance`: Financial markets focus\n- `commodity`: Commodity markets focus\n- `happy`: Positive news only\n- `energy`: Energy security, chokepoints, oil/gas, and disruption timelines\n\nVariant is set via `VITE_VARIANT` env var. Config lives in `src/config/variants/`.\n\n## Key Patterns\n\n### Adding a New API Endpoint\n\n1. Define proto message in `proto/worldmonitor/<domain>/`\n2. Add RPC with `(sebuf.http.config)` annotation\n3. Run `make generate`\n4. Create handler in `server/worldmonitor/<domain>/`\n5. Wire handler in domain's `handler.ts`\n6. Use `cachedFetchJson()` for caching, include request params in cache key\n\n### Adding a New Panel\n\n1. Create `src/components/MyPanel.ts` extending `Panel`\n2. Register in `src/config/panels.ts`\n3. Add to variant configs in `src/config/variants/`\n4. Wire data loading in `src/app/data-loader.ts`\n\n### Circuit Breakers\n\n- `src/utils/circuit-breaker.ts` for client-side\n- Used in data loaders to prevent cascade failures\n- Separate breaker per data domain\n\n### Caching\n\n- Redis (Upstash) via `server/_shared/redis.ts`\n- `cachedFetchJson()` coalesces concurrent cache misses\n- Cache tiers: fast (5m), medium (10m), slow (30m), static (2h), daily (24h)\n- Cache key MUST include request-varying params\n\n## Testing\n\n- **Unit/Integration**: `tests/*.test.{mjs,mts}` using `node:test` runner\n- **Sidecar tests**: `api/*.test.mjs`, `src-tauri/sidecar/*.test.mjs`\n- **E2E**: `e2e/*.spec.ts` using Playwright\n- **Visual regression**: Golden screenshot comparison per variant\n\n## CI Checks (GitHub Actions)\n\n| Workflow | Trigger | What it checks |\n|---|---|---|\n| `typecheck.yml` | PR + push to main | `tsc --noEmit` for src and API |\n| `lint.yml` | PR (markdown changes) | markdownlint-cli2 |\n| `proto-check.yml` | PR (proto changes) | Generated code freshness |\n| `build-desktop.yml` | `v*` tag, manual | Tauri desktop build |\n| `test-linux-app.yml` | Twice-weekly schedule, manual | Desktop Canary (Linux): release-processed AppImage smoke — crash, sidecar readiness/liveness, rendered content |\n| `test.yml` (`desktop-config`, `desktop-rust` jobs) | PR touching desktop-coupled paths | Desktop version consistency, AppImage post-processing syntax, Tauri config/capability parse, desktop build env parity (#5905, also in `unit`), `cargo test --locked` (#5902) |\n\n## Pre-Push Hook\n\nRuns automatically before `git push`. Two tiers:\n\n**Always (state-dependent, fast — run even on a cache hit):** local Vercel env-dump guard, PR-state check (no pushes to merged/closed PR branches), branch-contamination guard (>20 commits ahead), `scripts/` lockfile sync.\n\n**Tree-dependent (skipped entirely on a green-tree cache hit):** Unicode safety and version sync (always run for uncached trees), plus the diff-scoped checks: TypeScript (frontend tsc on `src/`-surface changes; `typecheck:api` on `api/|server/|scripts/|src/generated/`; Convex tsc on `convex/`), CJS syntax, boundary/safe-html/Sentry-coverage/rate-limit/premium-fetch lints (each also fires when its own guardrail script changes), edge esbuild check (`api/|server/|src/generated/|scripts/check-edge-function-bundles.mjs` — edge entries bundle-import server code, and the shared checker retriggers its own gate), markdown/MDX lint, proto + pro-test bundle freshness, change-scoped tests. `package.json`/`tsconfig` changes — or an unresolvable `origin/main` diff — force everything (an unresolvable diff also bypasses the green-tree cache: a blind run trusts nothing, including prior attestations).\n\n**Green-tree cache:** a tree that passed the full gate is recorded (`$GIT_DIR/wm-prepush-green`); re-pushing the identical tree (remote failure, message-only amend) skips all tree-dependent checks — same tree, same result. Delete that file to force a full re-run.\n\nHeavy checks (`test:data`, typechecks, edge-bundle) must run **sequentially** in worktrees — parallel runs OOM (exit 137).\n\n## Shipping Velocity (Agent Workflow)\n\n- **Before starting work on an issue:** check for parallel/duplicate work first — `gh pr list --search \"<issue#>\"` AND `git worktree list` (background codex/claude sessions ship PRs under the same account).\n- **PR delivery authority:** a user request to implement, fix, or ship a scoped change authorizes creating and updating the ready PRs needed to deliver it, including corrective follow-up PRs discovered by review or CI, plus monitoring and repairing those PRs without additional per-PR confirmation. This authority is limited to the requested change and its delivery branches; review-only or diagnostic requests remain read-only.\n- **Merge authority is explicit and non-delegable:** never merge a PR, enable auto-merge, queue a merge, or run any equivalent GitHub merge action unless the user has explicitly requested that specific action in the current conversation. A request to implement, ship, push, create a PR, or monitor CI does **not** authorize merging. Wait for clear approval and report the ready state instead.\n- **PR push readiness is mandatory:** before every push, re-fetch the live PR head and base, verify the remote head has not advanced, and check GitHub mergeability. Do not push a branch that is behind, `CONFLICTING`, or `DIRTY`; update from the latest PR/base state and resolve conflicts first. A successful `git push` is not delivery completion.\n- **After pushing a PR:** start `gh pr checks <n> --watch` (or an equivalent bounded monitor), wait for all required CI checks to reach green, then re-fetch the PR head and verify GitHub reports no conflict (`mergeable: MERGEABLE` / clean merge state). If checks are pending, failing, or the PR becomes conflicting, keep repairing and re-checking; do not report the PR as ready or complete until both CI and mergeability are green. Never use `--no-verify` to bypass this gate or turn on auto-merge without the explicit approval above.\n- **docs/plans/ is gitignored** — plan documents are local working state and do not travel between worktrees or ship in PRs.\n- **PR-review verification:** never assert a finding is fixed/stale from memory — re-fetch the PR head SHA and diff the cited lines first.\n\n## Deployment\n\n- **Web**: Vercel (auto-deploy on push to main)\n- **Relay/Seeds**: Railway (Docker, cron services)\n- **Desktop**: Tauri builds via GitHub Actions\n- **Docs**: Mintlify (proxied through Vercel at `/docs`)\n\n## Critical Conventions\n\n- `fetch.bind(globalThis)` is BANNED. Use `(...args) => globalThis.fetch(...args)` instead\n- Edge Functions cannot use `node:http`, `node:https`, `node:zlib`\n- Always include `User-Agent` header in server-side fetch calls\n- Yahoo Finance requests must be staggered (150ms delays)\n- New data sources MUST have bootstrap hydration wired in `api/bootstrap.js` — unless nothing in `src/` renders them. A dataset with no dashboard consumer registers in `api/health.js` `STANDALONE_KEYS` instead and stays out of the tiered payload every client downloads; `tests/bootstrap.test.mjs` enforces the converse, that no tier key lacks a `getHydratedData`/`ensureHydrated` consumer. Once a panel does read one, promote it into `BOOTSTRAP_CACHE_KEYS` with a tier — `ON_DEMAND_KEY_NAMES` for an opt-in panel, so the payload is fetched per-key on render rather than riding a tier every visitor downloads (`fxYoy` and `sharedFxRates` went this way for the FX panel, #6199)\n- Redis seed scripts MUST write `seed-meta:<key>` for health monitoring\n- Seed credentials load only via `loadEnvFile()` (inert under test runtimes, resolves `.env.local` at the checkout root, `only:` narrows the keys) — never hand-roll a `.env` reader or resolve one from `$HOME` or an absolute literal. Note `worktree:bootstrap` symlinks the source checkout's `.env.local`, so a bootstrapped worktree shares real credentials when a seeder is actually run\n\n## External References\n\n- [Architecture (system reference)](ARCHITECTURE.md)\n- [Design Philosophy (why decisions were made)](docs/architecture.mdx)\n- [Contributing guide](CONTRIBUTING.md)\n- [Data sources catalog](docs/data-sources.mdx)\n- [Health endpoints](docs/health-endpoints.mdx)\n- [Adding endpoints guide](docs/adding-endpoints.mdx)\n- [API reference (OpenAPI)](docs/api/)\n","category":"root","tokens":4100}]}