1
0
Fork 0

docs: document documentation category, guided tours, and e2e bootstrap

This commit is contained in:
Jason Fraley 2026-09-30 18:43:33 -04:00
parent d16e721ccc
commit d1ae4c3bb7

View file

@ -46,7 +46,7 @@ Known pre-existing errors (not yours, don't widen scope to fix them): `src/colle
- **Dedicated test database**: all tests run against `<db>_test` (derived from `DATABASE_URI`, or set `TEST_DATABASE_URI`), never the dev database. `tests/test-db.ts` bootstraps it idempotently: ensure database exists, push the schema (drizzle `push`, because the migration chain assumes a schema that push created), truncate every table (migration journals and PostGIS internals survive), seed baselines (`seedRoles` + a Game Rules global with a main currency resource, `tests/seed-baseline.ts`), and repair id sequences (migrations backfill explicit-id rows, which leaves sequences behind and causes `ValidationError: field is invalid: id` on creates). Truncate runs at both global setup (clears crashed-run leftovers) and teardown, so every run starts and ends empty. `TEST_DB_HARD_RESET=1` drops and recreates the test database from scratch instead of truncating. Serialized test files (`fileParallelism: false`) because parallel workers share the one database. - **Dedicated test database**: all tests run against `<db>_test` (derived from `DATABASE_URI`, or set `TEST_DATABASE_URI`), never the dev database. `tests/test-db.ts` bootstraps it idempotently: ensure database exists, push the schema (drizzle `push`, because the migration chain assumes a schema that push created), truncate every table (migration journals and PostGIS internals survive), seed baselines (`seedRoles` + a Game Rules global with a main currency resource, `tests/seed-baseline.ts`), and repair id sequences (migrations backfill explicit-id rows, which leaves sequences behind and causes `ValidationError: field is invalid: id` on creates). Truncate runs at both global setup (clears crashed-run leftovers) and teardown, so every run starts and ends empty. `TEST_DB_HARD_RESET=1` drops and recreates the test database from scratch instead of truncating. Serialized test files (`fileParallelism: false`) because parallel workers share the one database.
- **Integration tests**: `tests/int/**/*.int.spec.ts` — Vitest with jsdom. Requires the live PostgreSQL test database (bootstrapped automatically via `vitest-global.ts`; `vitest.setup.ts` points the process at it). Uses `dotenv/config` via `vitest.setup.ts`. - **Integration tests**: `tests/int/**/*.int.spec.ts` — Vitest with jsdom. Requires the live PostgreSQL test database (bootstrapped automatically via `vitest-global.ts`; `vitest.setup.ts` points the process at it). Uses `dotenv/config` via `vitest.setup.ts`.
- **E2E tests**: `tests/e2e/*.e2e.spec.ts` — Playwright with a project named "vivaldi" that launches the **Vivaldi binary** (`/usr/bin/vivaldi`, headless args in `playwright.config.ts`) — not plain Chromium. The `webServer` config auto-starts `pnpm dev` with `DATABASE_URI` pointed at the test DB (bootstrapped by `tests/e2e/global-setup.ts`, with `reuseExistingServer: true`). Currently minimal (homepage smoke test). Caveat: when your own dev server is already on port 3000, `reuseExistingServer` silently reuses it and its DEV database, bypassing the test-DB wiring; free the port first for a true test-DB run. - **E2E tests**: `tests/e2e/*.e2e.spec.ts` — Playwright with a project named "vivaldi" that launches the **Vivaldi binary** (`/usr/bin/vivaldi`, headless args in `playwright.config.ts`) — not plain Chromium. Override the binary with `E2E_BROWSER_PATH` (e.g. Playwright's bundled chromium) when the installed Vivaldi crashes under automation; the default stays Vivaldi. The `webServer` config auto-starts `pnpm dev` with `DATABASE_URI` pointed at the test DB (bootstrapped by `tests/e2e/global-setup.ts`, with `reuseExistingServer: true`). `global-setup` also seeds the shared e2e login: `dev` / `Test123` (developer legacy role + a superuser Roles doc for permission-gated UI + the "J. Fraley" rank so the dashboard heading matches). E2E runs serially (`workers: 1`, one webpack dev server) with generous `timeout`/`expect` timeouts because first navigation to a route triggers a cold webpack compile. Currently: tours smoke (auto-start, dismissal, replay), frontend, operations, reservations, supply boxes, OPORD recall, session banner. Caveat: when your own dev server is already on port 3000, `reuseExistingServer` silently reuses it and its DEV database, bypassing the test-DB wiring; free the port first for a true test-DB run.
- Run a single integration test: `bun run vitest run tests/int/api.int.spec.ts` - 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` - Run a single e2e test: `bun run playwright test tests/e2e/frontend.e2e.spec.ts`
@ -307,7 +307,8 @@ Leaflet world map at `/map` (top-level sidebar entry, whole unit views it). Full
`src/collections/wiki/`: `wiki-pages`, `wiki-revisions`, `wiki-templates`. A user-maintained field guide for campaigns, characters, places, and the stories around them. Admin group: **Wiki**. `src/collections/wiki/`: `wiki-pages`, `wiki-revisions`, `wiki-templates`. A user-maintained field guide for campaigns, characters, places, and the stories around them. Admin group: **Wiki**.
- **WikiPages**: `title` (text, required), `slug` (text, required, unique + indexed, generated by the service layer from the title), `category` (select, required: Campaign/World/Lore/Characters/Plot/Media/Guides/Meta), `tags` (text hasMany), `body` (textarea, required, markdown source), `lockdown` (checkbox, default false), `lockedBy`/`lockedAt` (→ users / date), `lastEditor` (→ users). Access: read/create/update any logged-in user; delete requires intelligence qualification (`hasIntelligenceQualification`). - **WikiPages**: `title` (text, required), `slug` (text, required, unique + indexed, generated by the service layer from the title), `category` (select, required: Campaign/World/Lore/Characters/Plot/Media/Guides/Meta/Documentation), `tags` (text hasMany), `body` (textarea, required, markdown source), `lockdown` (checkbox, default false), `lockedBy`/`lockedAt` (→ users / date), `lastEditor` (→ users). Access: read/create/update any logged-in user EXCEPT the **Documentation** category, where create/update is restricted to admin/developer (enforced twice: collection access functions for the admin-panel/REST path AND the wiki server actions, because the service layer runs with `overrideAccess`); delete requires intelligence qualification (`hasIntelligenceQualification`). A plain user cannot escalate a page into Documentation via update either (the update access check consults the incoming category AND the stored doc's category).
- **Documentation category**: in-depth, non-developer guides for the app's core systems (logistics and storage rules, banking, market and negotiation, shipments, base management, the map). Content lives as markdown files in `src/tools/seed/documentation/` (first H1 becomes the page title); `bun run src/tools/seed/seedDocumentation.ts` upserts them into `wiki-pages` (idempotent by slug, safe to re-run after edits). Migration `20260930_162200_add_documentation_wiki_category` adds the enum value. The wiki editor only offers Documentation in the category picker to admin/developer users; non-privileged users opening a Documentation page's edit URL get a "maintained by admins and developers" notice instead of the editor. Seeded pages have no `lastEditor`; the wiki index and detail header display **System** as the author for editor-less Documentation pages (other categories keep the "Unknown editor" fallback).
- **WikiRevisions**: `page` (→ wiki-pages), `revisionNumber` (number, min 1), `titleSnapshot`/`contentSnapshot`, `editor` (→ users), `summary`, `type` (`create`/`edit`/`restore`), `restoredFromRevision`. Access: read logged-in; create/update/delete `false` (written internally via `overrideAccess`). - **WikiRevisions**: `page` (→ wiki-pages), `revisionNumber` (number, min 1), `titleSnapshot`/`contentSnapshot`, `editor` (→ users), `summary`, `type` (`create`/`edit`/`restore`), `restoredFromRevision`. Access: read logged-in; create/update/delete `false` (written internally via `overrideAccess`).
- **WikiTemplates**: `name` (unique), `body` (textarea, snippet with `{{param}}` placeholders), `description`. Read logged-in; write admin/developer. - **WikiTemplates**: `name` (unique), `body` (textarea, snippet with `{{param}}` placeholders), `description`. Read logged-in; write admin/developer.
- **Service lib**: `src/lib/wiki/`. - **Service lib**: `src/lib/wiki/`.
@ -362,6 +363,20 @@ A Discord bot living in `src/bot/`, run as a standalone long-running process via
- **Notifications / announcements (feature 3)**: `notificationBridge` polls `user-notifications` (cursor = last seen id; cap 5 DMs/tick) and DMs opted-in users (`preferences.discord.enabled`, not in `mutedTypes`). `/announce` (staff only) posts an announcement embed. New notify sites partially done: banking emits `finance:deposit`; shipments has no notify site yet. - **Notifications / announcements (feature 3)**: `notificationBridge` polls `user-notifications` (cursor = last seen id; cap 5 DMs/tick) and DMs opted-in users (`preferences.discord.enabled`, not in `mutedTypes`). `/announce` (staff only) posts an announcement embed. New notify sites partially done: banking emits `finance:deposit`; shipments has no notify site yet.
- **Remaining polish**: the web preferences UI does not yet expose the `preferences.discord` toggles — they exist on the Users collection and are read by the bridge, but are currently only settable in the Payload admin panel. - **Remaining polish**: the web preferences UI does not yet expose the `preferences.discord` toggles — they exist on the Users collection and are read by the bridge, but are currently only settable in the Payload admin panel.
## Guided Tours
Permission-aware, per-page onboarding tours. Tours are defined in code per page; no DB collections involved.
- **Registry**: `src/lib/tours/registry.ts` — `TOURS` array of `TourDefinition` (`id`, exact `path`, `steps`). Add a page's tour there; the auto-start + header replay control pick it up automatically. Pure module (client-safe).
- **Steps**: brief, keyed to stable selectors (`data-tour="..."` attributes placed in the page markup; pages use a shared `data-tour="page-header"` anchor for their header block). A step may carry `showWhen: (gates) => boolean` to hide itself from users who cannot access the feature it describes. `visibleTourSteps` (`src/lib/tours/types.ts`) filters per visitor.
- **Gates**: `TourGateContext` mirrors the sidebar's visibility inputs (`hasIntelligence`, `hasLogistics`, `hasAdminPanel`, `statisticsEnabled`, `helpdeskEnabled`); the `(frontend)` layout resolves them server-side and passes them to `TourHost`.
- **Engine**: `TourHost` (`src/components/frontend/tours/`) is mounted in the `(frontend)` layout for logged-in users only. It auto-starts the current route's tour the FIRST time a given browser visits the page; `GuidedTour` renders a spotlight + card (missing selectors fall back to a centered card; late-mounting targets are re-anchored via MutationObserver). Navigating away kills the running tour.
- **Dismissals**: per-browser localStorage (`ptf:tour:dismissals`, tour id → dismissedAt, permanent) via `src/lib/tours/dismissals.ts` (mirrors announcement dismissals). Dismissal happens on Skip/Close/Done. A dismissed tour never auto-starts again.
- **Skip all (opt-out)**: the tour card's "Skip all tours on this device" sets a per-browser flag (`ptf:tours:opted-out`) that suppresses auto-start entirely; tours become opt-in via the header replay control. The Account page has a "Guided Tours" toggle (`ToursPreference`) to re-enable. Replay always works regardless of the flag.
- **Replay**: `TourReplayButton` (help icon in `SiteHeader`, rendered only on routes that have a tour) dispatches `ptf:tour:replay`; `TourHost` re-runs the tour regardless of dismissal or opt-out. Any user can replay.
- **Coverage**: dashboard, roster, Friday Ops, event log, world map, ops overview + ledger, structures, shipments, banking, market, wiki, awards, locker. Remaining pages join by adding registry entries + anchors (reservations and supply boxes deliberately have no tour yet: their e2e specs interact with the page, and the tour overlay blocks clicks).
- **Tests**: `tests/int/tours.int.spec.ts` (step filtering by gates, registry invariants incl. dash-clean copy, dismissal storage) and `tests/e2e/tours.e2e.spec.ts` (auto-start, dismissal persistence across reload, replay).
## Auth ## Auth
Username-based login (no email login). Users log in via Payload admin with `username` only. Username-based login (no email login). Users log in via Payload admin with `username` only.