Correct the PRD's claim of an until control, and stop the form erasing one

The PRD said a reader's rule could optionally stop on a date; the until
control was deliberately descoped from the form during planning, and the
PRD was written as though it shipped. Corrected the PRD to describe what
actually ships, and noted that the field still exists in the schema —
reachable today only by importing a file that carries one.

That gap had a second-order bug behind it: because the edit form rebuilt
`repeat` from scratch on every save, hard-coding `until: null`, a rule that
already carried a non-null `until` had it silently reset to eternal on any
edit at all, including a pure title fix. Extracted the `repeat`-building
logic into `repeatFrom`, which now carries `initial?.repeat?.until` forward,
and is exported so the behaviour is provable without a submit nothing in
this suite's renderToStaticMarkup tests can click.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Lucas Winther
2026-08-28 05:04:39 +02:00
co-authored by Claude Opus 5
parent d433eeee50
commit e3c4fae1b9
3 changed files with 71 additions and 7 deletions
+36
View File
@@ -3,6 +3,7 @@ import { renderToStaticMarkup } from "react-dom/server";
import {
cadenceLabel,
EventForm,
repeatFrom,
strandedNotice,
} from "../src/client/components/CustomForms.tsx";
import { YourOwn } from "../src/client/components/YourOwn.tsx";
@@ -422,6 +423,41 @@ describe("stating a repeat", () => {
});
});
describe("repeatFrom", () => {
test("a fresh rule has no `until` to carry — there is no control for one", () => {
expect(repeatFrom("weeks", 2, null)).toEqual({ unit: "weeks", interval: 2, until: null });
});
test("an edit preserves the `until` already on the record", () => {
// The form has no field for this, so the only way it can end up on the
// save is by surviving from what was already there — an import-sourced
// rule, since that is the only door `until` has today.
const existing = "2027-01-01T00:00:00.000Z";
expect(repeatFrom("weeks", 2, existing)).toEqual({
unit: "weeks",
interval: 2,
until: existing,
});
// It survives a schedule change too — changing the unit or interval is a
// different edit from changing when the series stops, and the reader
// never touched the latter.
expect(repeatFrom("months", 1, existing)).toEqual({
unit: "months",
interval: 1,
until: existing,
});
});
test("turning the repeat off drops it, existing `until` included", () => {
expect(repeatFrom("never", 2, "2027-01-01T00:00:00.000Z")).toBeNull();
});
test("an invalid interval is refused the same way regardless of `until`", () => {
expect(repeatFrom("weeks", 0, "2027-01-01T00:00:00.000Z")).toBeNull();
expect(repeatFrom("weeks", 400, null)).toBeNull();
});
});
describe("what a reschedule costs", () => {
test("says nothing when nothing would be stranded", () => {
expect(strandedNotice(0)).toBe(null);