Call it a cycle, not "every N days"
"Every 26 days" is ordinary English for an interval, but it sits directly beneath a start and an end — a duration — and a reader who has just been thinking in durations reads it as another one. Naming the shape of the repetition is what separates them, and "cycle" is the word this genre already uses. A cycle of one unit is named rather than numbered, because nobody says "a 1-week cycle"; a longer one takes the singular unit, since a hyphenated "26-day" is an adjective and not a count. The article follows how the number sounds — an 8-day cycle, an 11-day, an 84-day — which is the kind of wrong that reads as sloppiness rather than as a bug. The manual control becomes "Cycle length" for the same reason: it is asking for the length of the cycle, not for a duration the reader already gave. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1f1d608e95
commit
4c3aa4c7e3
@@ -69,11 +69,39 @@ export function strandedNotice(count: number): string | null {
|
|||||||
} you've already recorded.`;
|
} you've already recorded.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** How often a rule comes round, in the words the form offered. */
|
/** Whether a number opens on a vowel sound when read aloud. */
|
||||||
|
function takesAn(n: number): boolean {
|
||||||
|
// Eight, eleven and eighteen, plus every number in the eighties — which is
|
||||||
|
// still inside the 365 ceiling `interval` is capped at, so nothing larger
|
||||||
|
// needs considering.
|
||||||
|
return n === 8 || n === 11 || n === 18 || (n >= 80 && n <= 89);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How often a rule comes round, in the words the form offered.
|
||||||
|
*
|
||||||
|
* "Cycle" rather than "every N days", because this sits directly beneath a
|
||||||
|
* start and an end — a duration — and a reader who has just been thinking in
|
||||||
|
* durations reads "every 26 days" as another one. Naming the shape of the
|
||||||
|
* repetition is what separates the two, and "cycle" is the word this genre
|
||||||
|
* already uses for it.
|
||||||
|
*
|
||||||
|
* A cycle of one unit is named rather than numbered: nobody says "a 1-week
|
||||||
|
* cycle". A longer one takes the singular unit, because a hyphenated
|
||||||
|
* "26-day" is an adjective, not a count.
|
||||||
|
*/
|
||||||
export function cadenceLabel(repeat: Repeat | null): string | null {
|
export function cadenceLabel(repeat: Repeat | null): string | null {
|
||||||
if (repeat === null) return null;
|
if (repeat === null) return null;
|
||||||
if (repeat.interval === 1) return `every ${repeat.unit.replace(/s$/, "")}`;
|
if (repeat.interval === 1) {
|
||||||
return `every ${repeat.interval} ${repeat.unit}`;
|
const named: Record<RepeatUnit, string> = {
|
||||||
|
days: "daily",
|
||||||
|
weeks: "weekly",
|
||||||
|
months: "monthly",
|
||||||
|
};
|
||||||
|
return `on a ${named[repeat.unit]} cycle`;
|
||||||
|
}
|
||||||
|
const article = takesAn(repeat.interval) ? "an" : "a";
|
||||||
|
return `on ${article} ${repeat.interval}-${repeat.unit.replace(/s$/, "")} cycle`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -617,7 +645,7 @@ export function EventForm({
|
|||||||
{repeatMode === "forever" && !measuring && (
|
{repeatMode === "forever" && !measuring && (
|
||||||
<div className="mt-2 grid grid-cols-2 gap-2">
|
<div className="mt-2 grid grid-cols-2 gap-2">
|
||||||
<label className={labelClass()}>
|
<label className={labelClass()}>
|
||||||
Every
|
Cycle length
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={1}
|
||||||
@@ -678,7 +706,7 @@ export function EventForm({
|
|||||||
seeing that spelled out is how they catch a wrong number. */}
|
seeing that spelled out is how they catch a wrong number. */}
|
||||||
<p className="mt-1.5 text-xs leading-relaxed text-faint">
|
<p className="mt-1.5 text-xs leading-relaxed text-faint">
|
||||||
after it ends
|
after it ends
|
||||||
{repeat !== null ? ` · ${cadenceLabel(repeat)} in all` : ""}
|
{repeat !== null ? ` · ${cadenceLabel(repeat)}` : ""}
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+28
-7
@@ -399,7 +399,7 @@ describe("stating a repeat", () => {
|
|||||||
onCancel={() => {}}
|
onCancel={() => {}}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(html).toContain("every 8 days");
|
expect(html).toContain("on an 8-day cycle");
|
||||||
expect(html).toContain("from your dates");
|
expect(html).toContain("from your dates");
|
||||||
expect(html).not.toContain("Wait");
|
expect(html).not.toContain("Wait");
|
||||||
});
|
});
|
||||||
@@ -433,7 +433,7 @@ describe("stating a repeat", () => {
|
|||||||
expect(html).toContain("after it ends");
|
expect(html).toContain("after it ends");
|
||||||
// A week's gap after a week's window is a fortnightly rule; both readings
|
// A week's gap after a week's window is a fortnightly rule; both readings
|
||||||
// are shown so the reader can check the one against the other.
|
// are shown so the reader can check the one against the other.
|
||||||
expect(html).toContain("every 2 weeks");
|
expect(html).toContain("on a 2-week cycle");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("with no end date there is nothing to measure, so it asks", () => {
|
test("with no end date there is nothing to measure, so it asks", () => {
|
||||||
@@ -446,7 +446,7 @@ describe("stating a repeat", () => {
|
|||||||
onCancel={() => {}}
|
onCancel={() => {}}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(html).toContain("Every");
|
expect(html).toContain("Cycle length");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("a delay needs an end date to be measured from", () => {
|
test("a delay needs an end date to be measured from", () => {
|
||||||
@@ -564,12 +564,33 @@ describe("the sheet says how often", () => {
|
|||||||
at: AT,
|
at: AT,
|
||||||
updatedAt: AT,
|
updatedAt: AT,
|
||||||
});
|
});
|
||||||
expect(cadenceLabel(rule.repeat)).toBe("every 2 weeks");
|
expect(cadenceLabel(rule.repeat)).toBe("on a 2-week cycle");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("an interval of one drops the number and the plural", () => {
|
test("a cycle of one unit is named, not numbered", () => {
|
||||||
expect(cadenceLabel({ unit: "weeks", interval: 1, until: null })).toBe("every week");
|
// "on a 1-week cycle" is what the general form would produce and nobody
|
||||||
expect(cadenceLabel({ unit: "months", interval: 1, until: null })).toBe("every month");
|
// says it. The adverb is the natural reading and costs one lookup.
|
||||||
|
expect(cadenceLabel({ unit: "days", interval: 1, until: null })).toBe("on a daily cycle");
|
||||||
|
expect(cadenceLabel({ unit: "weeks", interval: 1, until: null })).toBe("on a weekly cycle");
|
||||||
|
expect(cadenceLabel({ unit: "months", interval: 1, until: null })).toBe("on a monthly cycle");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a longer cycle takes the singular unit as an adjective", () => {
|
||||||
|
// "26-day", not "26-days" — the hyphenated form is adjectival.
|
||||||
|
expect(cadenceLabel({ unit: "days", interval: 26, until: null })).toBe("on a 26-day cycle");
|
||||||
|
expect(cadenceLabel({ unit: "months", interval: 3, until: null })).toBe("on a 3-month cycle");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the article follows how the number sounds, not how it is spelled", () => {
|
||||||
|
// "a 8-day cycle" is the kind of wrong that reads as sloppiness rather
|
||||||
|
// than as a bug. Eight, eleven and eighteen open on a vowel; so does
|
||||||
|
// everything in the eighties, which is still inside the 365 ceiling.
|
||||||
|
expect(cadenceLabel({ unit: "days", interval: 8, until: null })).toBe("on an 8-day cycle");
|
||||||
|
expect(cadenceLabel({ unit: "days", interval: 11, until: null })).toBe("on an 11-day cycle");
|
||||||
|
expect(cadenceLabel({ unit: "days", interval: 18, until: null })).toBe("on an 18-day cycle");
|
||||||
|
expect(cadenceLabel({ unit: "days", interval: 84, until: null })).toBe("on an 84-day cycle");
|
||||||
|
expect(cadenceLabel({ unit: "days", interval: 7, until: null })).toBe("on a 7-day cycle");
|
||||||
|
expect(cadenceLabel({ unit: "days", interval: 80, until: null })).toBe("on an 80-day cycle");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("a non-repeating event has no cadence to show", () => {
|
test("a non-repeating event has no cadence to show", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user