# AGENTS.md — Polaris Task Force ## What this is Next.js 16 + Payload CMS 3.68.5 app for an Arma 3 unit. PostgreSQL database via `@payloadcms/db-postgres` + Drizzle. Tailwind CSS v4 (no config file — CSS-based). shadcn/ui (new-york style, `lucide` icons). Dark-themed frontend. ## Package manager **Bun** is the primary package manager (`bun.lock`, `bunfig.toml`). `pnpm-lock.yaml` also exists; use Bun for installs and running scripts. ## Essential commands ```bash bun install # install deps bun run dev # dev server (webpack, localhost:3000) bun run devsafe # clears .next cache then dev bun run build # production build (--max-old-space-size=8000, webpack) bun run lint # ESLint (next/core-web-vitals + next/typescript) bun run test # runs test:int then test:e2e sequentially bun run test:int # vitest integration tests only bun run test:e2e # playwright e2e tests only bun run db # drizzle-kit wrapper (e.g. bun run db migrate) bun run generate:types # regenerates payload-types.ts bun run generate:importmap # regenerates payload admin importMap ``` ## Type checking / linting order `bun run lint` runs ESLint. There is no separate `typecheck` script — TypeScript errors surface during `bun run lint` and `bun run build`. ## Test details - **Integration tests**: `tests/int/**/*.int.spec.ts` — Vitest with jsdom. Requires a live PostgreSQL database (connection from `.env`). Uses `dotenv/config` via `vitest.setup.ts`. - **E2E tests**: `tests/e2e/*.e2e.spec.ts` — Playwright (Chromium only). Auto-starts `bun run dev` via `webServer` config. Currently minimal (homepage smoke test). - Run a single integration test: `bun run vitest run tests/int/api.int.spec.ts` - Run a single e2e test: `bun run playwright test tests/e2e/frontend.e2e.spec.ts` ## Generated files — never edit manually - `src/payload-types.ts` — regenerated by `bun run generate:types` - `src/payload-generated-schema.ts` — regenerated by Payload db-schema generation - `src/app/(payload)/admin/importMap.js` — regenerated by `bun run generate:importmap` - `src/app/(payload)/layout.tsx` — auto-generated by Payload ## Path aliases - `@/*` → `./src/*` - `@payload-config` → `./src/payload.config.ts` ## App structure - `src/app/(frontend)/` — public-facing pages (dashboard, logistics, home). Layout has sidebar + auth check. - `src/app/(payload)/` — Payload admin panel and API routes. Auto-generated layout. - `src/app/my-route/` — example custom API route. ## Collections (Payload CMS) Organized by domain under `src/collections/`: - **users/** — Users (auth, username login), Ranks, Profiles, Awards, Qualifications, Assignments, Experience - **intelligence/** — Missions, Campaigns, Factions, Technologies - **logistics/** — Assets, Resources, Vehicles, Structures - **world/** — Maps, NarrativeEvents - **server/** — MissionFiles, ModLists - **game/** — GameRules (global), GameStructures, GameHardResources, GameEventLogs Access control helpers live in `src/utils/access-control/` (isRole, hasRoles). Roles: `guest`, `user`, `admin`, `developer`. ## Event Log System `src/collections/game/GameEventLogs.ts` — `game-event-logs` collection. - **Schema**: `system` (boolean, default `true` — system-generated vs GM/narrative), `timestamp`, `type` (text/indexed), `message` (human-readable), `actor` (→ users), `structure` (→ game-structures), `targetCollection` (select — pick from known collections), `targetId` (number, polymorphic ref), `data` (JSON blob). - **Access**: any logged-in user can read/create. Update restricted to admins/developers. Delete restricted to developers. - **Emit**: `emitGameEvent(payload, { type, message, actor?, structure?, targetCollection?, targetId?, data? })` in `src/utils/event-log/emit.ts`. Silently catches errors (fire-and-forget). Always sets `system: true`. - **Types**: `EventTypes` constants in `src/utils/event-log/eventTypes.ts` — add new event categories there without touching the collection. Using these constants gives TypeScript narrowing on the `type` field. - **Wiring**: Server actions in `actions.ts` emit events after successful mutations. `GameStructures` afterChange hook catches admin-panel storage edits. `Structures` beforeChange hook emits `structure:resize`. - **targetCollection options**: `game-structures`, `structures`, `resources`, `assets`, `vehicles`, `factions`, `missions`, `campaigns`, `users`, `technologies`, `maps`. If a new collection is added that should be a valid target, add an option here. - **Narrative events**: admins/developers can create entries manually in the Payload admin panel with `system: false` for GM-written narrative events. These are visually distinct in the UI (amber accent, book icon). ### UI `EventLedger` component (`src/components/frontend/storage/EventLedger.tsx`): - Queries the REST API (`/api/game-event-logs`) for a given `structureId` and renders a scrollable feed. - Accepts optional `refreshKey` prop — parent passes an incrementing counter to trigger re-fetch after mutations. - Auto-polls every 30s for background updates. - Shows actor name prefix ("You" for current user, username for others). - Filter by event type via multi-select shadcn `DropdownMenuCheckboxItem`. - Narrative events (system: false) styled with amber border + book icon. - On re-fetch, preserves existing entries while loading to avoid flicker. ### Adding event logging for new actions 1. Add event type constant to `src/utils/event-log/eventTypes.ts` if it doesn't exist yet. 2. If the target collection slug isn't in the `TARGET_COLLECTIONS` array in `GameEventLogs.ts`, add it. 3. Call `emitGameEvent(payload, { type: EventTypes.xxx, message: "...", ... })` after the successful mutation in the server action. 4. `EventLedger` will pick it up automatically if it's scoped to the same `structureId`. ### Non-structure events The `EventLedger` currently filters by `structure.id`. For event logs scoped to other entities (e.g., faction finance ledger), a new ledger variant would need to be created that queries by `targetCollection` + `targetId` instead. ## Auth Username-based login (no email login). Users log in via Payload admin with `username` only. ## Database PostgreSQL via `@payloadcms/db-postgres`. Schema defined in `src/payload-generated-schema.ts`. Migrations in `src/migrations/` (timestamp-named .ts + .json pairs, registered in `migrations/index.ts`). Drizzle config reads `DATABASE_URI` from env. In development, `postgresAdapter` uses `push: true` to auto-sync schema. ## Code style - **Prettier**: double quotes, trailing commas (all), 100 char print width, semicolons. - **ESLint**: `next/core-web-vitals` + `next/typescript`. `@typescript-eslint/no-unused-vars` warns (prefix unused with `_`). - **Tailwind v4**: no `tailwind.config` — configured via `@tailwindcss/postcss` in `postcss.config.mjs` and CSS imports. Use `cn()` from `@/lib/utils` for class merging. ## UI components shadcn/ui components live in `src/components/ui/`. Use `bunx shadcn@latest add ` to add new ones. Frontend components in `src/components/frontend/`. Payload admin custom components referenced in `payload.config.ts` under `admin.components`. ## Environment - `.env` — local dev (PostgreSQL connection string + PAYLOAD_SECRET) - `.env.example` — template (still shows MongoDB URI — outdated, the app uses PostgreSQL) - `.env.test` — test/staging database - `test.env` — NODE_OPTIONS for playwright (loaded by playwright config) ## Deploy `bun run deploy` bumps patch version (via `bun pm version patch`), builds, then runs `build/deploy.sh`. The `build/` directory is gitignored so the deploy script is not in the repo. ## Gotchas - `.env.example` shows MongoDB URI but the app uses PostgreSQL — trust `DATABASE_URI` format in `.env.test` as the real reference. - `bun run build` passes `--max-old-space-size=8000` — the build is memory-intensive. - The `devturbo` script uses Turbopack; `dev` and `devsafe` use webpack. These are different bundlers with different behavior. - Playwright tests auto-start the dev server — make sure port 3000 is free before running e2e. - Payload admin layout and importMap are auto-generated — do not edit by hand. - `.npmrc` sets `legacy-peer-deps=true` for dependency resolution compatibility.