238 lines
11 KiB
Markdown
238 lines
11 KiB
Markdown
# Housekeeping Content Events Vertical Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** Deliver a complete ASE event and event-type operator workflow backed by real database data and existing audited mutations.
|
||
|
||
**Architecture:** Keep the generic Content query/command foundation, add strict route-specific event payloads, and render them through a focused `ContentEventWorkflow`. Use the existing event tables and mutation runtime; introduce dedicated delete command IDs only to enforce reason confirmation.
|
||
|
||
**Tech Stack:** Next.js 16, React 19, TypeScript, Drizzle/MySQL, Zod, Vitest, React server actions
|
||
|
||
**Spec:** `docs/superpowers/specs/2026-09-01-housekeeping-content-events-vertical-design.md`
|
||
|
||
## Global Constraints
|
||
|
||
- Work directly on `codex/housekeeping-rebuild-stepwise`; do not create a worktree.
|
||
- Preserve `/admin/events` and all unrelated local/untracked files.
|
||
- Do not add dependencies or change the database schema.
|
||
- Keep Polls and Prefixes behavior unchanged.
|
||
- Write and run a failing test before each production behavior change.
|
||
- Keep every destructive event or event-type action reason-protected and auditable.
|
||
|
||
---
|
||
|
||
### Task 1: Typed event query payloads and production loading
|
||
|
||
**Files:**
|
||
- Modify: `src/features/housekeeping/domains/content/queries/content-queries.ts`
|
||
- Modify: `src/features/housekeeping/domains/content/queries/content-queries-production.ts`
|
||
- Test: `src/features/housekeeping/domains/content/queries/content-queries.test.ts`
|
||
|
||
**Interfaces:**
|
||
- Produces: `ContentEventTypePayload`, `ContentEventSummaryPayload`, `ContentEventDetailPayload`, and their public type guards.
|
||
- Produces: validated private payloads for list, create, detail, and type routes.
|
||
- Consumes: `ContentQueryItem.privatePayload`, normalized `params.id`, `list.search`, `list.status`, `list.pageSize`, and `list.offset`.
|
||
|
||
- [x] **Step 1: Write failing contract tests**
|
||
|
||
Add literal fixtures proving that malformed event payloads fail closed, event list input normalizes a bounded status, and a detail adapter returning more than one item is rejected.
|
||
|
||
- [x] **Step 2: Run the query test and confirm RED**
|
||
|
||
Run: `yarn.cmd vitest run src/features/housekeeping/domains/content/queries/content-queries.test.ts`
|
||
|
||
Expected: FAIL because event payload guards and status normalization do not exist.
|
||
|
||
- [x] **Step 3: Implement the minimal event contracts**
|
||
|
||
Add route-specific interfaces with JSON-safe primitive fields and type guards. Extend list input with `status`, normalized to lowercase and at most 32 characters. Extend `isValidData` so each event route accepts only its corresponding payload and detail accepts at most one selected item.
|
||
|
||
- [x] **Step 4: Run the query test and confirm GREEN**
|
||
|
||
Run the command from Step 2. Expected: PASS.
|
||
|
||
- [x] **Step 5: Write failing production-loader tests**
|
||
|
||
Add tests proving:
|
||
|
||
```ts
|
||
expect(list.items[0]?.privatePayload).toMatchObject({
|
||
typeName: "Tournament",
|
||
registrationCount: 12,
|
||
});
|
||
expect(detail.items).toHaveLength(1);
|
||
expect(detail.items[0]?.privatePayload).toMatchObject({
|
||
description: "Complete event",
|
||
eventTypes: [{ id: "2", name: "Tournament" }],
|
||
prizes: [{ id: "4", prizeType: "badge" }],
|
||
winners: [{ userId: "9", username: "Alice" }],
|
||
registrations: [{ userId: "10", username: "Bob" }],
|
||
});
|
||
```
|
||
|
||
Also assert that the serialized detail SQL binds the requested ID and that status filtering is bound, not interpolated.
|
||
|
||
- [x] **Step 6: Run the loader tests and confirm RED**
|
||
|
||
Run the command from Step 2. Expected: FAIL because production definitions only expose generic summaries.
|
||
|
||
- [x] **Step 7: Implement production event loaders**
|
||
|
||
Update event list and type definitions to project full safe summaries. Load event-create options from active event types. Add a dedicated detail loader that performs bounded, parameterized reads for the selected event, event types, prizes, winners with usernames, and registrations with usernames. Return not-found as an empty successful result.
|
||
|
||
- [x] **Step 8: Run the loader tests and confirm GREEN**
|
||
|
||
Run the command from Step 2. Expected: PASS.
|
||
|
||
### Task 2: Event command safety and form field semantics
|
||
|
||
**Files:**
|
||
- Modify: `src/features/housekeeping/domains/content/commands/content-commands.ts`
|
||
- Modify: `src/features/housekeeping/domains/content/pages/content-command-form.tsx`
|
||
- Test: `src/features/housekeeping/domains/content/commands/content-commands.test.ts`
|
||
- Test: `src/features/housekeeping/domains/content/pages/content-pages.test.tsx`
|
||
|
||
**Interfaces:**
|
||
- Produces: `content.engagement.event.delete` and `content.engagement.event-type.delete`, both mapped to existing mutations with `requiresReason: true`.
|
||
- Produces: `ContentCommandField.type === "datetime-local"`, submitted as the normalized browser value.
|
||
|
||
- [x] **Step 1: Write failing command-policy tests**
|
||
|
||
Assert the two delete command IDs exist, use `event.change` and `event-type.change`, retain `PERMS.EVENTS_EDIT`, and require reasons while non-destructive change commands do not.
|
||
|
||
- [x] **Step 2: Run command tests and confirm RED**
|
||
|
||
Run: `yarn.cmd vitest run src/features/housekeeping/domains/content/commands/content-commands.test.ts`
|
||
|
||
Expected: FAIL because the dedicated delete commands are absent.
|
||
|
||
- [x] **Step 3: Implement command metadata**
|
||
|
||
Extend the command definition tuple with an optional `requiresReason` flag and register both dedicated delete command IDs without adding mutation operations.
|
||
|
||
- [x] **Step 4: Run command tests and confirm GREEN**
|
||
|
||
Run the command from Step 2. Expected: PASS.
|
||
|
||
- [x] **Step 5: Write a failing date/time form test**
|
||
|
||
Render a field with `type: "datetime-local"` and assert the real input type and default value. Submit it and assert the existing server action receives the exact normalized date/time string.
|
||
|
||
- [x] **Step 6: Run page tests and confirm RED**
|
||
|
||
Run: `yarn.cmd vitest run src/features/housekeeping/domains/content/pages/content-pages.test.tsx`
|
||
|
||
Expected: FAIL because `datetime-local` is not supported.
|
||
|
||
- [x] **Step 7: Implement the minimal field support**
|
||
|
||
Add `datetime-local` to the field union and map it to `<input type="datetime-local">`; keep the existing bounded string parser.
|
||
|
||
- [x] **Step 8: Run page tests and confirm GREEN**
|
||
|
||
Run the command from Step 6. Expected: PASS.
|
||
|
||
### Task 3: Complete Event workflow UI
|
||
|
||
**Files:**
|
||
- Create: `src/features/housekeeping/domains/content/pages/event-workflow.tsx`
|
||
- Modify: `src/features/housekeeping/domains/content/pages/engagement.tsx`
|
||
- Create: `src/features/housekeeping/domains/content/pages/event-workflow.test.tsx`
|
||
- Modify: `src/features/housekeeping/domains/content/pages/content-pages.test.tsx`
|
||
|
||
**Interfaces:**
|
||
- Produces: `ContentEventWorkflow(props)` for the four event routes.
|
||
- Consumes: typed payload guards from Task 1 and command IDs/field semantics from Task 2.
|
||
|
||
- [x] **Step 1: Write failing list and state tests**
|
||
|
||
Render real `HousekeepingResult` fixtures and assert loading, forbidden, dependency-error, empty, and ready states. The ready list must expose search, status filtering, result count, type, schedule, capacity, registrations, create/type links, and bounded previous/next links.
|
||
|
||
- [x] **Step 2: Run workflow tests and confirm RED**
|
||
|
||
Run: `yarn.cmd vitest run src/features/housekeeping/domains/content/pages/event-workflow.test.tsx`
|
||
|
||
Expected: FAIL because the component does not exist.
|
||
|
||
- [x] **Step 3: Implement the list/state slice**
|
||
|
||
Create the focused workflow component and route the four event route IDs to it from `ContentEngagementPage`, leaving Polls and Prefixes on the existing generic implementation.
|
||
|
||
- [x] **Step 4: Run workflow tests and confirm GREEN**
|
||
|
||
Run the command from Step 2. Expected: PASS for list/state tests.
|
||
|
||
- [x] **Step 5: Write failing create/detail tests**
|
||
|
||
Assert create uses real type options and date/time inputs. Assert detail prepopulates title, description, selected type, schedule, capacity, room, status, recurrence, and image; automatically binds event IDs for prizes/winners; renders usernames for registrations; and exposes a reason-required delete form using `content.engagement.event.delete`.
|
||
|
||
- [x] **Step 6: Run workflow tests and confirm RED**
|
||
|
||
Run the command from Step 2. Expected: FAIL because create/detail workflow sections are incomplete.
|
||
|
||
- [x] **Step 7: Implement create/detail**
|
||
|
||
Build field factories from the validated payload. Render not-found separately from dependency failure. Keep related forms and lists inside the selected event detail; never expose manual event-ID inputs.
|
||
|
||
- [x] **Step 8: Run workflow tests and confirm GREEN**
|
||
|
||
Run the command from Step 2. Expected: PASS for create/detail tests.
|
||
|
||
- [x] **Step 9: Write failing event-type tests**
|
||
|
||
Assert a create form and one prefilled update form per type, plus a reason-required delete form using `content.engagement.event-type.delete`; no operator-entered type ID field is allowed.
|
||
|
||
- [x] **Step 10: Run workflow tests and confirm RED**
|
||
|
||
Run the command from Step 2. Expected: FAIL until type management is implemented.
|
||
|
||
- [x] **Step 11: Implement event-type management and refactor**
|
||
|
||
Add create/update/delete sections using typed payloads. Extract small field and formatting helpers while all tests stay green.
|
||
|
||
- [x] **Step 12: Run focused Content tests and confirm GREEN**
|
||
|
||
Run:
|
||
|
||
`yarn.cmd vitest run src/features/housekeeping/domains/content/queries/content-queries.test.ts src/features/housekeeping/domains/content/commands/content-commands.test.ts src/features/housekeeping/domains/content/pages/content-pages.test.tsx src/features/housekeeping/domains/content/pages/event-workflow.test.tsx`
|
||
|
||
Expected: all focused tests PASS.
|
||
|
||
### Task 4: Full verification and draft PR update
|
||
|
||
**Files:**
|
||
- Modify: `docs/superpowers/plans/2026-09-01-housekeeping-content-events-vertical.md`
|
||
- Modify: draft PR 53 body in English and Dutch
|
||
|
||
**Interfaces:**
|
||
- Consumes: all deliverables from Tasks 1–3.
|
||
- Produces: verified commit(s), pushed branch, and current bilingual PR evidence.
|
||
|
||
- [x] **Step 1: Run static and targeted checks**
|
||
|
||
Run the repository TypeScript, Biome, Knip, focused test, and Housekeeping matrix commands from `package.json` and the existing Housekeeping evidence workflow. Fix only failures caused by this vertical.
|
||
|
||
- [x] **Step 2: Run the full test suite with coverage**
|
||
|
||
Run: `yarn.cmd test`
|
||
|
||
Expected: zero failing test files and zero failing tests.
|
||
|
||
- [x] **Step 3: Run the production build**
|
||
|
||
Run: `yarn.cmd build`
|
||
|
||
Expected: exit code 0 with canonical `/ase-next` routes generated.
|
||
|
||
- [x] **Step 4: Review the exact diff**
|
||
|
||
Run: `git diff --check`, `git status --short`, and `git diff --stat origin/main...HEAD` after committing. Confirm `.remember/` and `.superpowers/brainstorm/` remain untouched and untracked.
|
||
|
||
- [x] **Step 5: Commit and push exact paths**
|
||
|
||
Commit query/command/UI/test/plan files with a scoped message, then push `codex/housekeeping-rebuild-stepwise`.
|
||
|
||
- [x] **Step 6: Update and verify the draft PR**
|
||
|
||
Add the Events vertical and fresh verification counts to PR 53 in English and Dutch. Confirm the remote head matches local HEAD and inspect CI status without claiming deployment.
|