diff --git a/admin/src/definitions/DefinitionsTab.test.tsx b/admin/src/definitions/DefinitionsTab.test.tsx
index 038429b..b54475f 100644
--- a/admin/src/definitions/DefinitionsTab.test.tsx
+++ b/admin/src/definitions/DefinitionsTab.test.tsx
@@ -362,6 +362,39 @@ describe("DefinitionsTab", () => {
expect(screen.getByText("4 definitions ยท 3 children")).toBeTruthy();
});
+ it("the weekday picker lists Sunday first and Sunday still stores 6", async () => {
+ vi.useFakeTimers({ toFake: ["Date"] });
+ vi.setSystemTime(new Date(2026, 8, 23, 9, 0)); // Wed 23 Sep 2026, local
+ await renderReady();
+
+ fireEvent.click(screen.getByRole("button", { name: "New" }));
+ const s = sheet();
+ fireEvent.change(within(s).getByRole("textbox", { name: "Title" }), {
+ target: { value: "Feed the cat" },
+ });
+ fireEvent.click(within(s).getByRole("button", { name: /Assigned children/ }));
+ fireEvent.click(within(s).getByRole("checkbox", { name: "Declan" }));
+ fireEvent.change(within(s).getByRole("combobox", { name: "Repeats" }), {
+ target: { value: "weekly" },
+ });
+
+ const days = within(s).getByRole("group", { name: "Days" });
+ expect(
+ within(days)
+ .getAllByRole("button")
+ .map((b) => b.getAttribute("aria-label")),
+ ).toEqual(["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]);
+ fireEvent.click(within(days).getByRole("button", { name: "Sunday" }));
+ fireEvent.click(within(s).getByRole("button", { name: "Evening" }));
+
+ await act(async () => {
+ fireEvent.click(within(s).getByRole("button", { name: "Save changes" }));
+ });
+ const body = api.writes()[0].body as { rule: DefinitionRule };
+ expect(body.rule.weekday_set).toEqual([2, 6]);
+ expect(meta(await screen.findByTestId("definition-99"))).toContain("on Sun, Wed");
+ });
+
it("tapping a row opens the sheet in editing state and saving PATCHes", async () => {
await renderReady();
const row = screen.getByTestId("definition-11");
diff --git a/admin/src/definitions/DefinitionsTab.tsx b/admin/src/definitions/DefinitionsTab.tsx
index b4803b5..5db5f16 100644
--- a/admin/src/definitions/DefinitionsTab.tsx
+++ b/admin/src/definitions/DefinitionsTab.tsx
@@ -27,6 +27,7 @@ import {
} from "../api/definitions";
import {
MONTH_SHORT,
+ WEEKDAY_DISPLAY_ORDER,
WEEKDAY_SHORT,
WINDOW_LABELS,
WINDOW_ORDER,
@@ -680,9 +681,9 @@ function EditSheet({
{draft.repeats === "weekly" || draft.repeats === "custom" ? (
- {WEEKDAY_SHORT.map((label, day) => (
+ {WEEKDAY_DISPLAY_ORDER.map((day) => (
))}
diff --git a/admin/src/definitions/format.ts b/admin/src/definitions/format.ts
index a2246d4..2741859 100644
--- a/admin/src/definitions/format.ts
+++ b/admin/src/definitions/format.ts
@@ -21,6 +21,9 @@ export const WINDOW_LABELS: Record = {
/** Index 0 = Monday .. 6 = Sunday, the core's weekday numbering. */
export const WEEKDAY_SHORT = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
+/** Display order for a week: Sunday first. The stored indices stay Monday=0. */
+export const WEEKDAY_DISPLAY_ORDER = [6, 0, 1, 2, 3, 4, 5];
+
export const MONTH_SHORT = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
@@ -51,7 +54,7 @@ export function assigneeSummary(
function weekdayList(days: number[] | null): string {
return [...(days ?? [])]
- .sort((a, b) => a - b)
+ .sort((a, b) => WEEKDAY_DISPLAY_ORDER.indexOf(a) - WEEKDAY_DISPLAY_ORDER.indexOf(b))
.map((day) => WEEKDAY_SHORT[day])
.join(", ");
}
diff --git a/admin/src/schedule/ScheduleTab.test.tsx b/admin/src/schedule/ScheduleTab.test.tsx
index feae62c..24af4d0 100644
--- a/admin/src/schedule/ScheduleTab.test.tsx
+++ b/admin/src/schedule/ScheduleTab.test.tsx
@@ -131,10 +131,20 @@ describe("ScheduleTab", () => {
expect(today.className).toContain("schedule-day--today");
expect(today.dataset.state).toBe("home");
expect(today.getAttribute("aria-label")).toBe("Sep 23 (today): Home");
- // September 2026 starts on a Tuesday: one leading pad after the initials.
+ // Sunday-first header; September 2026 starts on a Tuesday: two leading pads.
const grid = screen.getByRole("group", { name: "Month grid" });
+ expect(Array.from(grid.children, (c) => c.textContent).slice(0, 7)).toEqual([
+ "S",
+ "M",
+ "T",
+ "W",
+ "T",
+ "F",
+ "S",
+ ]);
expect(grid.children[7].tagName).toBe("SPAN");
- expect(grid.children[8].getAttribute("data-date")).toBe("2026-09-01");
+ expect(grid.children[8].tagName).toBe("SPAN");
+ expect(grid.children[9].getAttribute("data-date")).toBe("2026-09-01");
const legend = screen.getByRole("list", { name: "Legend" });
expect(within(legend).getAllByRole("listitem").map((li) => li.textContent)).toEqual([
diff --git a/admin/src/schedule/ScheduleTab.tsx b/admin/src/schedule/ScheduleTab.tsx
index 6bd5eb4..3ab4384 100644
--- a/admin/src/schedule/ScheduleTab.tsx
+++ b/admin/src/schedule/ScheduleTab.tsx
@@ -46,7 +46,8 @@ export const SCROLL_BOTTOM_PADDING = "92px";
export const DEFAULT_CYCLE_LENGTH_WEEKS = 2;
const CYCLE_LENGTH_CHOICES = [1, 2, 3, 4];
-const WEEKDAY_INITIALS = ["M", "T", "W", "T", "F", "S", "S"];
+/** Sunday-first header; `weekdayOf` stays Monday=0. */
+const WEEKDAY_INITIALS = ["S", "M", "T", "W", "T", "F", "S"];
export const STATE_LABELS: Record = {
home: "Home",
@@ -130,7 +131,7 @@ interface MonthGridProps {
function MonthGrid({ month, today, schedule, effective, overrides, onMonth, onToggle }: MonthGridProps) {
const dates = monthDates(month);
- const leading = weekdayOf(month);
+ const leading = (weekdayOf(month) + 1) % 7;
return (
<>
diff --git a/admin/src/schedule/cycle.test.ts b/admin/src/schedule/cycle.test.ts
index 26217f5..6473c6a 100644
--- a/admin/src/schedule/cycle.test.ts
+++ b/admin/src/schedule/cycle.test.ts
@@ -58,6 +58,24 @@ describe("calendar helpers", () => {
expect(weekdayOf("2026-09-06")).toBe(6);
});
+ it("weekdayOf keeps the stored Monday=0 .. Sunday=6 numbering (display order is separate)", () => {
+ // A full known week plus dates across month and year ends.
+ expect(
+ [
+ "2026-09-07",
+ "2026-09-08",
+ "2026-09-09",
+ "2026-09-10",
+ "2026-09-11",
+ "2026-09-12",
+ "2026-09-13",
+ ].map(weekdayOf),
+ ).toEqual([0, 1, 2, 3, 4, 5, 6]);
+ expect(weekdayOf("2026-11-01")).toBe(6); // Sunday
+ expect(weekdayOf("2027-01-01")).toBe(4); // Friday
+ expect(weekdayOf("2024-02-29")).toBe(3); // Thursday
+ });
+
it("monthStart shifts across year ends", () => {
expect(monthStart("2026-12-15", 1)).toBe("2027-01-01");
expect(monthStart("2026-01-31", -1)).toBe("2025-12-01");