PRD F13 said the form asked how often after Kind and had no word on examples; F17 and DESIGN.md had no example row. Adds the spec and plan, and ticks the TODO item. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
112 lines
8.6 KiB
Markdown
112 lines
8.6 KiB
Markdown
# Examples to start from, and a pass over the add forms — 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:** Give *Add an event* and *Add a task* a row of examples that shape the form, and make both
|
|
forms read as a reader thinks.
|
|
|
|
**Architecture:** Examples are pure data in `src/shared/templates.ts`. Each form applies one through a
|
|
pure function on its state, so the suite (static render, no clicks) can prove what a tap does. The
|
|
event form's derived state moves into one exported pure function, `eventFormState`, so validity, the
|
|
draft and the "still needed" line all come from one place. Shared field/pill classes, the example row
|
|
and the "still needed" sentence live in `src/client/components/formParts.tsx`.
|
|
|
|
**Tech Stack:** Bun, React 19 (static render in tests via `react-dom/server`), Tailwind, zod.
|
|
|
|
**Spec:** `docs/superpowers/specs/2026-10-14-form-examples-design.md`
|
|
|
|
## Global Constraints
|
|
|
|
- An example sets a shape, never a fact: never a title (placeholder only), date, time, target or game. Only exception: *Monthly shop buy-out* sets `required: "1"`.
|
|
- Nothing is saved until the reader saves; Save stays disabled until every date and figure the shape needs is typed.
|
|
- No date is guessed — no "today", no "the 1st".
|
|
- "I don't know when it ends" stays first-class; preset windows, measured cycles, delays, `comesRoundEarly`, the stranded notice and `until` survival behave exactly as before.
|
|
- No schema, store, id or export change.
|
|
- The examples row renders only on add (no `initial`), never on edit.
|
|
- Copy: How often → *Once · Every day · Every week · Every month · On its own cycle*; Repeat → *Doesn't repeat · Comes back as it ends · Comes back after a wait*; Kind → *Banner · Story · Rerun · Challenge / endgame · Login rewards · Shop · Maintenance · Other*; the name field is *Name* in both forms.
|
|
- Commits wait for the owner's go-ahead (AGENTS.md); work stays uncommitted until asked.
|
|
|
|
## Review Focus
|
|
|
|
1. A reader types a title and dates, then taps an example — the title and dates must survive (Task 1/2/3 tests on `withEventTemplate` / `withTaskTemplate`).
|
|
2. Switching from *Recurring endgame cycle* to *Banner with a known end* must leave no repeat on the draft (Task 3 test).
|
|
3. *A few days each week* leaves the day count empty: Save disabled and the line says "how many days" (Task 2 test).
|
|
4. Choosing *On its own cycle* from *Doesn't repeat* must land on *Comes back as it ends*; a stored custom-never record must still reopen as never (Task 3 test).
|
|
5. At 360px the example row and the pills wrap without horizontal scroll (Task 4 browser check).
|
|
|
|
---
|
|
|
|
### Task 1: The examples, as data
|
|
|
|
**Files:**
|
|
- Create: `src/shared/templates.ts`
|
|
- Test: `test/templates.test.ts`
|
|
|
|
**Interfaces:**
|
|
- Produces: `EventTemplate`, `TaskTemplate`, `EVENT_TEMPLATES`, `TASK_TEMPLATES` (shapes in the spec).
|
|
|
|
- [ ] **Step 1: Write the failing test** — ids unique per list; `sets` keys limited to the shape keys;
|
|
no `sets` carries a title/date/target; only `monthly-shop` sets `required`, to `"1"` (and
|
|
`few-days` to `""`); placeholders non-empty and none equals a real event name in the list (smoke).
|
|
- [ ] **Step 2:** `bun test test/templates.test.ts` → FAIL (module not found).
|
|
- [ ] **Step 3: Implement** `src/shared/templates.ts` with the two tables from the spec verbatim.
|
|
- [ ] **Step 4:** `bun test test/templates.test.ts` → PASS.
|
|
|
|
### Task 2: Task form — shared parts, examples, still-needed
|
|
|
|
**Files:**
|
|
- Create: `src/client/components/formParts.tsx` — `field`, `pill(on)`, `labelClass`, `stillNeededLine(needs: string[]): string | null`, `ExampleRow`.
|
|
- Modify: `src/client/components/TaskForm.tsx`
|
|
- Test: `test/custom-ui.test.tsx`, `test/templates.test.ts`
|
|
|
|
**Interfaces:**
|
|
- Consumes: `TASK_TEMPLATES`, `TaskTemplate`.
|
|
- Produces:
|
|
- `stillNeededLine(["a name","a start date"]) === "Still needed: a name and a start date."`; three → `"a, b and c"`; `[]` → `null`.
|
|
- `ExampleRow<T extends {id:string;label:string;hint:string}>({ examples: readonly T[]; chosen: string | null; onChoose: (t: T) => void })` — renders "Start from an example", one pill per example with `aria-pressed`, and the chosen one's hint.
|
|
- `withTaskTemplate(f: TaskFields, t: TaskTemplate): TaskFields` — `{...f, unit, kind, required?, amountUnit?}` only.
|
|
- `taskStillNeeded(f: TaskFields): string[]` — in order "a name", "a day of the month", "how many days", "the target".
|
|
|
|
- [ ] **Step 1: Failing tests:**
|
|
- `stillNeededLine` cases above.
|
|
- `withTaskTemplate` keeps `game`, `title`, `target`, `weekday`, `monthDay`; sets `unit`/`kind`; *A few days each week* empties `required`.
|
|
- `taskStillNeeded`: blank title → "a name"; `kind:"days", required:""` → "how many days"; `unit:"week", required:"9"` → "how many days"; amount with `target:""` → "the target"; complete → `[]`.
|
|
- Every task template, applied to `{title:"x", target:"100", required kept or "2"}` fields, gives `taskStillNeeded === []` and a non-null `taskDraftFrom`.
|
|
- Render: add form contains "Start from an example" and each label; edit form (`initial`) does not; label "Name" present.
|
|
- [ ] **Step 2:** `bun test test/custom-ui.test.tsx test/templates.test.ts` → FAIL.
|
|
- [ ] **Step 3: Implement** — formParts.tsx; TaskForm imports `field`/`pill`, labels "Name", inputs `px-3 py-2`, `chosen` state, `ExampleRow` only when `initial === undefined`, title placeholder from the chosen template, Save `disabled={draft === null || needs.length > 0}`, the still-needed line beside the buttons in `text-faint`.
|
|
- [ ] **Step 4:** same command → PASS; `bun run typecheck` clean.
|
|
|
|
### Task 3: Event form — order, words, pills, messages, examples
|
|
|
|
**Files:**
|
|
- Modify: `src/client/components/CustomForms.tsx`
|
|
- Test: `test/custom-ui.test.tsx`, `test/templates.test.ts`
|
|
|
|
**Interfaces:**
|
|
- Consumes: `EVENT_TEMPLATES`, `ExampleRow`, `pill`, `stillNeededLine`.
|
|
- Produces:
|
|
- `EventShape = { type: EventType; cadence: Cadence; repeatMode: RepeatMode; endKnown: boolean; ownCadence: boolean }`, held as one `useState`.
|
|
- `withEventTemplate(shape: EventShape, t: EventTemplate): EventShape` → `{...shape, ...t.sets, ownCadence: false}`.
|
|
- `withCadence(shape: EventShape, cadence: Cadence): EventShape` — choosing `custom` while `repeatMode === "never"` moves to `"forever"`.
|
|
- `eventStillNeeded(s: { title; startDate; datedWindow; endKnown; endDate; delayNeedsEnd; needsCycle }): string[]` — "a name", "a start date", "an end date", "a cycle length".
|
|
- `eventFormState(values: EventFormValues, existingUntil: string | null)` — returns `{ startsAt, endsAt, endMs, preset, datedWindow, measured, measuring, delayNeedsEnd, repeat, backwards, endMissing, earlyReturn, needs, valid, draft }`, the block that is inline today, moved verbatim, plus `needs`. `valid = needs.length === 0 && !backwards && !endMissing && !earlyReturn && !repeatIncomplete`.
|
|
|
|
- [ ] **Step 1: Failing tests:**
|
|
- `withEventTemplate` keeps nothing it shouldn't touch (shape only) and clears `ownCadence`.
|
|
- `withCadence(never-shape, "custom").repeatMode === "forever"`; a stored custom-never event reopens with *Doesn't repeat* pressed.
|
|
- Endgame → Banner: `eventFormState` with the banner shape and dates has `repeat === null`.
|
|
- Each event template, with title "x", start `2026-10-01`, end `2026-10-15`, is `valid` with a draft; a fresh banner with no dates lists "a name", "a start date", "an end date".
|
|
- Copy: update existing assertions (`Cadence` → `How often`; answers list; `value="daily" selected=""` → `/aria-pressed="true"[^>]*>Every day</`; `with a delay` → `Comes back after a wait`).
|
|
- Add form shows the example row; edit form does not.
|
|
- [ ] **Step 2:** `bun test test/custom-ui.test.tsx test/templates.test.ts` → FAIL.
|
|
- [ ] **Step 3: Implement** in the spec's order with messages beside their fields.
|
|
- [ ] **Step 4:** same → PASS; full `bun test` and `bun run typecheck` green.
|
|
|
|
### Task 4: Browser check and docs
|
|
|
|
- [ ] Build (`bun run build:css && bun run build:js && bun run build:static`), serve on a fresh port, puppeteer (scratchpad) at 360px and 1280px: open Settings → Your own → Add an event, tap each example, check no horizontal scroll, fill the banner and save, see the row; same for Add a task.
|
|
- [ ] PRD F13 and F17: a sentence each on examples and their bar. DESIGN.md chips: the example row. TODO.md: tick the item.
|
|
- [ ] Final `bun test`, `bun run typecheck`.
|
|
- [ ] Final whole-change review (fresh reviewer); then report. Changelog entry follows the feature commit when the owner asks to commit.
|