feat(forms): start an event or a task from an example, and say what Save needs

Readers who knew what they wanted could not see which answers in Add an
event or Add a task produced it. Both add forms now open with a row of
examples (templates.ts) that set the form's shape only: kind, how often,
how it repeats, how it is measured. Never a name, date, target or game,
and no date is guessed, so nothing an example set can be saved unread.

Both forms also had a pass: plain words and pills for how often and
repeat, the name asked first and the kind after the dates, each message
beside its field, and a "Still needed" line read from the same check that
disables Save. The event form's derived state is now one pure function,
eventFormState, so the examples are proved to complete into a saveable
event without a click.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5.5 committed 2026-10-14 03:10:15 +02:00
1 parent 0701e5e16e
commit 6c66b812b1
7 files changed
+1151 -365

No files matched your search

+114
View File
@@ -0,0 +1,114 @@
import type { Cadence, RepeatMode } from "./recurrence.ts";
import type { EventType } from "./schema.ts";
/**
* Examples to start *Add an event* and *Add a task* from.
*
* A reader who knows what they want — a banner that ends on the 28th, 1300
* points a week — could not see which answers in the form produce it. One tap
* here gives the form that shape, and the reader fills in the rest.
*
* **An example sets a shape, never a fact.** No title (the example's text is
* only a placeholder), no date, no time, no target, no game: those are claims
* about a game, and a figure the reader did not type would read as one we
* sourced — the bar `routines.ts` holds built-ins to. No date is guessed
* either, not even "today": games reset on different days, and a guessed date
* that looks deliberate is the failure this product exists to avoid. The form
* keeps Save disabled until every date and figure the shape needs is typed, so
* nothing an example set can be saved unread.
*/
export interface EventTemplate {
/** Kebab-case, unique within the list. */
id: string;
/** The chip's text. */
label: string;
/** The name field's placeholder while this example is chosen. */
placeholder: string;
/** One line: what is left for the reader to fill in. */
hint: string;
sets: { type: EventType; cadence: Cadence; repeatMode: RepeatMode; endKnown: boolean };
}
export interface TaskTemplate {
id: string;
label: string;
placeholder: string;
hint: string;
sets: {
unit: "week" | "month";
kind: "days" | "amount";
/** As typed; "" empties the field. Only "once" is ever a shape. */
required?: string;
amountUnit?: string;
};
}
export const EVENT_TEMPLATES: readonly EventTemplate[] = [
{
id: "banner",
label: "Banner with a known end",
placeholder: "Limited banner",
hint: "Fill in when it starts and when it ends.",
sets: { type: "banner", cadence: "one-off", repeatMode: "never", endKnown: true },
},
{
id: "login",
label: "Login event",
placeholder: "Login bonus",
hint: "Fill in when it starts and when it ends, or tick that you don't know.",
sets: { type: "login", cadence: "one-off", repeatMode: "never", endKnown: true },
},
{
id: "monthly-shop",
label: "Monthly shop reset",
placeholder: "Shop reset",
hint: "Fill in a day it resets on. Each one runs until the next.",
sets: { type: "shop", cadence: "monthly", repeatMode: "never", endKnown: true },
},
{
id: "endgame-cycle",
label: "Recurring endgame cycle",
placeholder: "Endgame cycle",
hint: "Fill in one cycle's start and end; how often it comes back is worked out from them.",
sets: { type: "challenge", cadence: "custom", repeatMode: "forever", endKnown: true },
},
{
id: "maintenance",
label: "Maintenance",
placeholder: "Maintenance",
hint: "Fill in when it starts and ends, with times.",
sets: { type: "maintenance", cadence: "one-off", repeatMode: "never", endKnown: true },
},
];
export const TASK_TEMPLATES: readonly TaskTemplate[] = [
{
id: "weekly-points",
label: "Weekly points target",
placeholder: "Weekly points",
hint: "Fill in the target, and the day the week rolls over if it isn't Monday.",
sets: { unit: "week", kind: "amount", amountUnit: "points" },
},
{
id: "few-days",
label: "A few days each week",
placeholder: "Weekly chore",
hint: "Fill in how many days a week it needs.",
sets: { unit: "week", kind: "days", required: "" },
},
{
id: "monthly-shop",
label: "Monthly shop buy-out",
placeholder: "Monthly shop",
hint: "Fill in the day the month rolls over.",
sets: { unit: "month", kind: "days", required: "1" },
},
{
id: "monthly-target",
label: "Monthly target",
placeholder: "Monthly target",
hint: "Fill in the target and what it counts.",
sets: { unit: "month", kind: "amount" },
},
];