1
0
Fork 0

chore: update AGENTS.md with information about shipments, game-tick SSE notifications, storage rules enforcement

This commit is contained in:
Jason Fraley 2026-08-02 03:10:57 -04:00
parent d583fe6de6
commit 1bf7c945cf

View file

@ -2,7 +2,7 @@
## 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.
Next.js 16 + Payload CMS 3.79.1 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
@ -15,7 +15,7 @@ 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 lint # BROKEN under Next 16 — see typecheck below
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
@ -24,9 +24,15 @@ bun run generate:types # regenerates payload-types.ts
bun run generate:importmap # regenerates payload admin importMap
```
## Type checking / linting order
## Type checking / linting
`bun run lint` runs ESLint. There is no separate `typecheck` script — TypeScript errors surface during `bun run lint` and `bun run build`.
`bun run lint` is **broken** under Next 16 — `next lint` was removed and errors out with `Invalid project directory provided, no such directory: .../lint`. Don't rely on it. The reliable typecheck is:
```bash
npx tsc --noEmit 2>&1 | grep -E "error TS" | grep -v "\.next/"
```
Known pre-existing errors, don't chase them: `src/utils/access-control/hasLogisticsQualification.ts` (lines 12, 19 — `'user' is possibly 'null'`) and `src/payload-generated-schema.ts:1848` (generated file). Anything else is yours.
## Test details
@ -49,7 +55,8 @@ bun run generate:importmap # regenerates payload admin importMap
## App structure
- `src/app/(frontend)/` — public-facing pages (dashboard, logistics, home). Layout has sidebar + auth check.
- `src/app/(frontend)/` — public-facing pages (dashboard, logistics, home). Layout has sidebar + auth check (guests get `LandingPage`, no shell).
- `src/app/login/` — standalone login route, intentionally OUTSIDE the gated `(frontend)` group. Own dark `<html>` layout that reuses `(frontend)/styles.css`.
- `src/app/(payload)/` — Payload admin panel and API routes. Auto-generated layout.
- `src/app/my-route/` — example custom API route.
@ -59,10 +66,10 @@ 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
- **logistics/** — Assets, Resources, Vehicles, Structures, Shipments
- **world/** — Maps, NarrativeEvents
- **server/** — MissionFiles, ModLists
- **game/** — GameRules (global), GameStructures, GameHardResources, GameEventLogs
- **game/** — GameRules (global), GameStructures, GameHardResources, GameEventLogs, GameVehicles
Access control helpers live in `src/utils/access-control/` (isRole, hasRoles). Roles: `guest`, `user`, `admin`, `developer`.
@ -75,7 +82,7 @@ Access control helpers live in `src/utils/access-control/` (isRole, hasRoles). R
- **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.
- **targetCollection options**: `game-structures`, `structures`, `resources`, `assets`, `vehicles`, `game-vehicles`, `factions`, `missions`, `campaigns`, `users`, `technologies`, `maps`, `shipments`. 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
@ -98,6 +105,31 @@ Access control helpers live in `src/utils/access-control/` (isRole, hasRoles). R
### 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.
## Shipments & game tick
Shipping simulation (`src/collections/logistics/Shipments.ts`, `src/scripts/`, `src/lib/shipping.ts`, `src/lib/distance.ts`):
- **Shipment fields**: `origin`/`destination` → `game-structures`, `transportVehicle` → `game-vehicles`, `cargo[]` (relationship to resources/assets/vehicles + amount), `distance`, `fuelCost`/`fuelConsumed`, `status` (`pending`/`dispatched`/`in_transit`/`arrived`/`completed`/`cancelled`/`failed`/`stranded`), `autoReturn` checkbox, `failureReason`.
- **Game tick**: `bun run payload game-tick` — a `bin` registered on `payload.config.ts`, NOT an npm script. It processes active shipments + fuel consumption, then `process.exit(0)`.
- **Arrival handling** (`src/scripts/processShipmentTick.ts`): destination storage rules are re-checked on arrival; rejected cargo bounces back to origin, shipment goes `failed`, `ShipmentFail` event logged.
- **GameRules tuning**: `proximityThreshold` and `gameTickIntervalMinutes` live on the global `game-rules` doc.
- **UI**: `src/app/(frontend)/logistics/shipments/` (list + `[id]` detail with `ShipmentActions` controls), `src/app/(frontend)/logistics/game-vehicles/` (deployed vehicle views).
### Realtime SSE pipeline (in-memory, single-instance only)
gameTick → POST `/api/game-tick/notify` (guarded by `x-game-tick-secret` header = `GAME_TICK_NOTIFY_SECRET` env) → in-process bus (`src/lib/realtime/bus.ts`, module-level subscriber Set) → SSE `GET /api/realtime` → `GameTickRealtime` (`src/components/frontend/realtime/`) calls `router.refresh()` and dispatches `ptf:game-tick` → consumers via `src/hooks/useGameTick.ts` (`ShipmentToasts`, `EventLedger`). **Will not work across multiple server instances.**
- `GameTickRealtime` mounts only for logged-in users; `ShipmentToasts` only for logistics-qualified users.
- `hasLogisticsQualification(payload, user)` (`src/utils/access-control/hasLogisticsQualification.ts`) queries Profiles `progression.qualifications` for "logistics" (case-insensitive); admin/developer always pass. Used to gate logistics-only UI.
## Storage rules (logistics)
`Structure` collection has `allowedStorage` (per-item caps), `prohibitedStorage`, and `restrictToAllowed` (whitelist gate) under `storage`. Shared logic in `src/lib/storageRules.ts` (`checkStorageDeposit`, `isResourceProhibited`, `isResourceWhitelisted`, `getResourceStorageCap`, `storageViolationMessage`).
- Enforcement order: **prohibited → whitelist → per-item cap**, counting grid + void storage.
- Enforced in: `structures/actions.ts` (`addResource`, `transferResource`, `placeResourceOnGrid`), `shipments/actions.ts` (`createShipment` destination check), and `processShipmentTick.ts` on arrival.
- UI: `ManageStorageDialog` caps deposits to `min(mass, allowance)` and filters non-whitelisted items; structure page shows an amber "whitelist mode" banner when `restrictToAllowed` is set.
## Narrative Events System
`src/collections/world/NarrativeEvents.ts` — `narrative-events` collection. A flowchart-based narrative event editor using `@xyflow/react` (React Flow) in the Payload admin panel.
@ -119,6 +151,11 @@ The `EventLedger` currently filters by `structure.id`. For event logs scoped to
Username-based login (no email login). Users log in via Payload admin with `username` only.
- `src/app/(frontend)/layout.tsx` is the gate: guests render `LandingPage` (no app shell); authed users get sidebar + `GameTickRealtime`.
- `/login` (`src/app/login/page.tsx` + `src/components/frontend/auth/LoginForm.tsx`) POSTs `{ username, password }` to `/api/users/login`, then `router.push("/")` + `router.refresh()`. The page redirects already-authed users to `/`.
- Logout lives in `NavUser` (sidebar) → `/api/users/logout`.
- Tip: a corrupted `payload-token` cookie causes an infinite login loop (`Unexpected end of JSON input` on `/api/users/me`) — clear cookies/use incognito. Dev user `dev` / `Test123`.
## 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.
@ -140,7 +177,7 @@ shadcn/ui components live in `src/components/ui/`. Use `bunx shadcn@latest add <
## Environment
- `.env` — local dev (PostgreSQL connection string + PAYLOAD_SECRET)
- `.env.example` — template (still shows MongoDB URI — outdated, the app uses PostgreSQL)
- `.env.example` — template. `DATABASE_URI` line still shows MongoDB (outdated — trust `.env.test` for the real Postgres format), but `APP_URL` + `GAME_TICK_NOTIFY_SECRET` are current and required by the game tick.
- `.env.test` — test/staging database
- `test.env` — NODE_OPTIONS for playwright (loaded by playwright config)