Redraw the calendar as an almanac: gridless cells, typed bands

Day cells lose their borders and fills; red days, klämdagar and
Saturdays are told apart by the numeral's ink with a 12px R/K mark,
today is a filled circle, overrides and exclusions mark the numeral.
Bands carry their funding type by fill, ink and hatching and their
owner by a left cap. The preset rail becomes a chip row, the legend a
help popover, the period popover's three delete buttons fold behind
one "Ta bort…", and solo users pick a type directly in the day modal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XBRLomdLK9jF7wU7a8AA6H
This commit is contained in:
2026-09-19 19:02:02 +02:00
parent d14f86478a
commit dbef997da4
28 changed files with 1486 additions and 1109 deletions
+18 -11
View File
@@ -10,9 +10,10 @@
* default it now creates a fixed, one-off `LeaveWish` and lets the planner
* fund it (a `calendar-band-wish:` band, not `adhoc:`) — see
* `Calendar.tsx`'s `handleSaveWish`. The old "write exact ad-hoc
* `planned_leave_day` rows" behaviour still exists, behind the modal's
* "Advanced" disclosure (`handleSavePinnedDay`) — the first test below
* opens it explicitly to keep exercising that path.
* `planned_leave_day` rows" behaviour still exists (`handleSavePinnedDay`):
* a solo household reaches it by picking a concrete type in the modal's
* type select (a couple through the "Advanced" disclosure) — the first
* test below does so explicitly to keep exercising that path.
*
* The drag deliberately crosses a month boundary (the last two days of one
* month into the first day of the next) — a regression guard for the
@@ -57,7 +58,7 @@ function targetRange(): { month: Date; dates: [string, string, string] } {
return { month, dates }
}
test('dragging a range and pinning it through Advanced plans every day exactly, and deleting it removes the days for good', async ({
test('dragging a range and pinning an exact type plans every day exactly, and deleting it removes the days for good', async ({
page,
}) => {
await registerAndReachOnboarding(page, uniqueEmail('drag-delete'), 'Robin Testsson')
@@ -79,10 +80,12 @@ test('dragging a range and pinning it through Advanced plans every day exactly,
// A multi-day drag opens the day popover pre-selected on every dragged
// date — it does not create a period/wish by itself.
await expect(page.getByTestId('modal-title')).toHaveText('Planera 3 dagar')
// The default save path would create a fixed wish instead (see the next
// test) — opening "Advanced" keeps this test on the exact-pinned-days
// path todo.md #10/#11 kept as an escape hatch.
await page.getByTestId('advanced-toggle').click()
// The default save path ("let the planner choose") would create a fixed
// wish instead (see the next test) — picking a concrete type keeps this
// test on the exact-pinned-days path todo.md #10/#11 kept as an escape
// hatch. A solo account has no "Advanced" toggle: the select is right
// there.
await page.getByTestId('day-type-select').selectOption('vacation')
await page.getByTestId('save-button').click()
await expect(page.getByTestId('planned-day-modal__content')).not.toBeVisible()
@@ -112,9 +115,11 @@ test('dragging a range and pinning it through Advanced plans every day exactly,
await expect(page.getByTestId('period-adhoc-notice')).toBeVisible()
await expect(page.getByTestId('period-popover__content')).toContainText(`${firstDate} – ${lastDate}`)
// todo.md #9 — an ad-hoc band has no recurring plan, so "every year" is
// disabled; "this day"/"this period" are the only meaningful choices, and
// "this period" removes the whole ad-hoc range (today's only behaviour).
// todo.md #9 — the three delete scopes sit behind "Ta bort…". An ad-hoc
// band has no recurring plan, so "every year" is disabled; "this day"/
// "this period" are the only meaningful choices, and "this period"
// removes the whole ad-hoc range (today's only behaviour).
await page.getByTestId('period-delete-menu-button').click()
await expect(page.getByTestId('period-delete-all-years-button')).toBeDisabled()
await page.getByTestId('period-delete-year-button').click()
await expect(page.getByTestId('period-delete-confirm')).toBeVisible()
@@ -183,6 +188,7 @@ test('a drag saved without opening Advanced creates a planner-funded wish, and t
// thing, same as "every year".
await wishBand.first().click()
await expect(page.getByTestId('period-popover__content')).toBeVisible()
await page.getByTestId('period-delete-menu-button').click()
await expect(page.getByTestId('period-delete-day-button')).toBeEnabled()
await page.getByTestId('period-delete-year-button').click()
@@ -224,6 +230,7 @@ test('removing a single day out of a planner-placed period keeps it removed acro
// `clickedDate`). Either way the popover's "this day" removes exactly one.
await wishBand.first().click()
await expect(page.getByTestId('period-popover__content')).toBeVisible()
await page.getByTestId('period-delete-menu-button').click()
await page.getByTestId('period-delete-day-button').click()
await expect(page.getByTestId('period-delete-confirm')).toBeVisible()
await page.getByTestId('period-delete-confirm-button').click()