Finish the calendar in the UI language v2 #257

Merged
Grandiras merged 2 commits from ui-v2-calendar into main 2026-10-04 17:28:26 +00:00
Owner

Finishes the calendar in the UI language v2, to the Figma frames Desktop · Calendar · Month (41:2646 / 73:5429), Timeline (67:4583 / 73:5596), Phone · Calendar (57:1370 / 58:1524) and Calendar & search parts (34:180), light and dark.

What changed

  • Head: the month is the page's large title over its line (All boards · 22 due this month), then Month/Timeline, Today (only while it would move), ‹ › and a ⋯ holding Export .ics and Subscribe (they were two buttons). On a board's calendar the head is the board's title bar (BoardTitleBar gains TitleContent, MenuContent, CssClass, Active and CloseMenu()): the month takes the title, the line names the board (Summer fair · 7 due this month), the views stay, and the exports join Share and Board settings in the board's ⋯. Narrow, the controls wrap under the title.
  • Month grid: white days in a recessed tray; the day number top-left, today a filled pill, the 1st reads Oct 1, other months' days faded in the tray with their cards (CalendarMonth.ByDay now covers the whole grid). Entries are a dot (board colour on all boards, the first tag's on one board, as before) and one line; overdue is red text. Three chips, then +N more (a button, so the day sheet is reachable by keyboard); a shorter day shows as many whole chips as fit and its total where that leaves any out — container queries on the day, since every chip stays in the markup.
  • Done: a card in its board's deck (last column) is done on the calendar too, as on the board: quiet, never overdue, not counted as overdue (ColumnService.GetDeckIdsAsync, one query).
  • Meta row: the range (timeline), the legend (the filter; left-out chips recede, picked ones carry a tick) and 1 overdue.
  • Timeline: weekday letters over numbers, past days grey, today a pill; weekend bands and a today line as two overlays on the column grid; lanes with the board's mark (a tag's dot on one board), name and count; bars tinted in the lane's colour with a solid edge (red when overdue, faded when done); a white card that ends with its lanes.
  • Phone: large title with the year in its line, a compact month (its own markup: a number and dots; a tap picks the day — filled circle, today tinted — and a day of another month brings its month), and the agenda grouped by day (Monday, October 5 · tomorrow) of rows with a colour bar, title, Board · Column · Person +N and a chevron, opening at the picked day or today (scrollAgenda). The filter moved into ⋯; the old flyout and anchorFlyout are gone.
  • Dark mode for all of it; en + de strings (cal.count.month, .quarter, cal.overdue, cal.more, cal.date.weekday).

Judgement calls

  • A board's calendar puts the month in the board's title bar rather than a second large title under the board's name — the phone settings pages' precedent (the board in the pill, the page in the title), and the brief's "the board's version" of the line. The bar also shows on a phone here, without its views.
  • The month view keeps the legend row although the draft's month frame has none: it is the filter (a phone has it in ⋯).
  • Timeline packing keeps the documented rule (docs/calendar.md: a bar is shortened at the next card; only cards sharing a column add a row). The draft stacks bars instead; on a busy board that trades lane height for titles, and changing it is a product call.
  • A board's own calendar still lanes and colours by tag; the draft only shows the general calendar.
  • The phone gets controls its frame leaves out: ‹ › and Today level with the title, Month/Timeline and ⋯ on a row below. Paging, the views, the filter and the exports need a place.
  • The day sheet and the subscribe dialog are kept as they were (not in the draft); CardModal is untouched.

How verified

  • tools/verify.sh green (600 unit tests; CalendarMonthTests updated for the grid-wide ByDay).
  • Full Playwright suite on an isolated stack: 162 passed, no flakes (calendar specs extended: done vs overdue, three chips and +N more vs the total on a short day, the phone's pick and agenda, the filter in ⋯, the head's line, a lane's mark and count; export links are read from ⋯ via expectExportHref).
  • Side-by-side screenshots against the frames at 1440×900 and 390×844, light and dark, plus 820px, 1024px, 1280×720 and German.

🤖 Generated with Claude Code

Finishes the calendar in the UI language v2, to the Figma frames *Desktop · Calendar · Month* (41:2646 / 73:5429), *Timeline* (67:4583 / 73:5596), *Phone · Calendar* (57:1370 / 58:1524) and *Calendar & search parts* (34:180), light and dark. ## What changed - **Head**: the month is the page's large title over its line (*All boards · 22 due this month*), then Month/Timeline, Today (only while it would move), ‹ › and a ⋯ holding **Export .ics** and **Subscribe** (they were two buttons). On a board's calendar the head *is* the board's title bar (`BoardTitleBar` gains `TitleContent`, `MenuContent`, `CssClass`, `Active` and `CloseMenu()`): the month takes the title, the line names the board (*Summer fair · 7 due this month*), the views stay, and the exports join Share and Board settings in the board's ⋯. Narrow, the controls wrap under the title. - **Month grid**: white days in a recessed tray; the day number top-left, today a filled pill, the 1st reads *Oct 1*, other months' days faded in the tray **with their cards** (`CalendarMonth.ByDay` now covers the whole grid). Entries are a dot (board colour on all boards, the first tag's on one board, as before) and one line; overdue is red text. Three chips, then **+N more** (a button, so the day sheet is reachable by keyboard); a shorter day shows as many whole chips as fit and its total where that leaves any out — container queries on the day, since every chip stays in the markup. - **Done**: a card in its board's deck (last column) is done on the calendar too, as on the board: quiet, never overdue, not counted as overdue (`ColumnService.GetDeckIdsAsync`, one query). - **Meta row**: the range (timeline), the legend (the filter; left-out chips recede, picked ones carry a tick) and *1 overdue*. - **Timeline**: weekday letters over numbers, past days grey, today a pill; weekend bands and a today line as two overlays on the column grid; lanes with the board's mark (a tag's dot on one board), name and count; bars tinted in the lane's colour with a solid edge (red when overdue, faded when done); a white card that ends with its lanes. - **Phone**: large title with the year in its line, a compact month (its own markup: a number and dots; a tap **picks** the day — filled circle, today tinted — and a day of another month brings its month), and the agenda grouped by day (*Monday, October 5 · tomorrow*) of rows with a colour bar, title, *Board · Column · Person +N* and a chevron, opening at the picked day or today (`scrollAgenda`). The filter moved into ⋯; the old flyout and `anchorFlyout` are gone. - Dark mode for all of it; en + de strings (`cal.count.month`, `.quarter`, `cal.overdue`, `cal.more`, `cal.date.weekday`). ## Judgement calls - **A board's calendar puts the month in the board's title bar** rather than a second large title under the board's name — the phone settings pages' precedent (the board in the pill, the page in the title), and the brief's "the board's version" of the line. The bar also shows on a phone here, without its views. - **The month view keeps the legend row** although the draft's month frame has none: it is the filter (a phone has it in ⋯). - **Timeline packing keeps the documented rule** (docs/calendar.md: a bar is shortened at the next card; only cards sharing a column add a row). The draft stacks bars instead; on a busy board that trades lane height for titles, and changing it is a product call. - **A board's own calendar still lanes and colours by tag**; the draft only shows the general calendar. - **The phone gets controls its frame leaves out**: ‹ › and Today level with the title, Month/Timeline and ⋯ on a row below. Paging, the views, the filter and the exports need a place. - The day sheet and the subscribe dialog are kept as they were (not in the draft); CardModal is untouched. ## How verified - `tools/verify.sh` green (600 unit tests; `CalendarMonthTests` updated for the grid-wide `ByDay`). - Full Playwright suite on an isolated stack: 162 passed, no flakes (calendar specs extended: done vs overdue, three chips and +N more vs the total on a short day, the phone's pick and agenda, the filter in ⋯, the head's line, a lane's mark and count; export links are read from ⋯ via `expectExportHref`). - Side-by-side screenshots against the frames at 1440×900 and 390×844, light and dark, plus 820px, 1024px, 1280×720 and German. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Finish the calendar in the UI language v2
All checks were successful
CI / build (pull_request) Successful in 43s
57a18cfedb
The month is the page's large title with its line (a board's calendar puts it
in the board's title bar and names the board in the line), then the view,
Today, the arrows and a ⋯ that holds Export .ics and Subscribe. The month is
white days in a recessed tray: a dot and a line per card, three and then
"+N more", fewer whole ones and the total on a short day, other months' days
faded with their cards. The timeline gets weekday letters, weekend bands, a
today line, lanes with the board's mark and a count, and bars with a coloured
edge. On a phone: a large title with the year in its line, a compact month
with dots whose days pick where the agenda opens, and the agenda grouped by
day; the filter moves into ⋯. A card in its board's deck is done on the
calendar too: quiet, and never overdue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-257/run-2026-10-04T16-24-12-625Z.md) — updating every ~5 minutes while the review runs.
Collaborator

🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/706); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-257/run-706.md) — updating every ~5 minutes while the task runs.
claude-reviewer approved these changes 2026-10-04 16:24:42 +00:00
Dismissed
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

I found no blocking problems. I read the diff and checked GetDatedCardsAsync; I did not build or run anything.

  • Includes: GetDatedCardsAsync already loads BoardColumn, Tags and Assignees.Member, which the new agenda meta line (AgendaMeta) needs, so it adds no lazy-load or null risk.
  • Docs and tests: docs/calendar.md, docs/frontend.md and docs/ui.md are updated with their reasons. The CalendarMonth.ByDay change is covered by the updated unit tests.

Minor notes, none blocking:

  • ScrollAgendaAsync records its key even when the agenda is hidden (desktop width). Resizing to phone width afterwards won't scroll the agenda to its lead day until the next month, pick or filter change.
  • The phone month cells (.calendar-mini-day) and the agenda sections both carry data-date. Any e2e selector on [data-date] alone will match both, so scope it to a class.
  • The day sheet now uses Filtered.Where(c => c.DueDate == detailDay), which relies on Filtered keeping the date-then-id order that GetDatedCardsAsync returns. It holds today, but ByDay no longer guarantees it there.

🧠 claude-sonnet-5-5 · ⏱ 0m 16s · 3 turns · tokens: 72.3k in / 1.7k out (+94.8k cache-read)
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. I found no blocking problems. I read the diff and checked `GetDatedCardsAsync`; I did not build or run anything. - **Includes:** `GetDatedCardsAsync` already loads `BoardColumn`, `Tags` and `Assignees.Member`, which the new agenda meta line (`AgendaMeta`) needs, so it adds no lazy-load or null risk. - **Docs and tests:** `docs/calendar.md`, `docs/frontend.md` and `docs/ui.md` are updated with their reasons. The `CalendarMonth.ByDay` change is covered by the updated unit tests. Minor notes, none blocking: - `ScrollAgendaAsync` records its key even when the agenda is hidden (desktop width). Resizing to phone width afterwards won't scroll the agenda to its lead day until the next month, pick or filter change. - The phone month cells (`.calendar-mini-day`) and the agenda sections both carry `data-date`. Any e2e selector on `[data-date]` alone will match both, so scope it to a class. - The day sheet now uses `Filtered.Where(c => c.DueDate == detailDay)`, which relies on `Filtered` keeping the date-then-id order that `GetDatedCardsAsync` returns. It holds today, but `ByDay` no longer guarantees it there. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 16s · 3 turns · tokens: 72.3k in / 1.7k out (+94.8k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-257/run-706.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:57a18cfedb436635357e82cd607a7376ea159199 -->
Collaborator

🤖 Resolving the merge conflicts — the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the fix runs.

🤖 Resolving the merge conflicts — the reply will link the full session transcript. <!-- opencode-run:conflict --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-257/run-2026-10-04T16-40-50-607Z.md) — updating every ~5 minutes while the fix runs.
Collaborator

Facts pinned: on this branch the calendar passes TitleContent, so .board-titlebar-name does not exist on a calendar page (that h1 only renders when TitleContent is null) — but the board's name is in the title-bar line (SubLine(wide) = ScopeName = _board?.Name). So keeping generic naming with .board-titlebar-name for the calendar entry would fail on this branch. Correct resolution: per-entry locator — .board-titlebar for the calendar (title-bar contains the board via its line), .page-crumb for settings (main's truthful addition):

The run reported an error after replying: Upstream request failed: [invalid_request_error] native reasoning control reasoning_effort is not allowed

⚠️ Files still unresolved: docs/frontend.md, docs/ui.md, src/Ponente.Kanban/Services/ColumnService.cs, tests/e2e/boards.spec.js — nothing was pushed.


⏱ 10m 25s · 35 steps · $0.0657 · tokens: 65.3k in / 8.2k out (+1579.1k cache-read) · Go: 5h 8% · wk 15% · mo 90%

📜 Full session transcript

Facts pinned: on this branch the calendar passes `TitleContent`, so `.board-titlebar-name` does **not** exist on a calendar page (that h1 only renders when `TitleContent` is null) — but the board's name *is* in the title-bar line (`SubLine(wide) = ScopeName = _board?.Name`). So keeping generic `naming` with `.board-titlebar-name` for the calendar entry would fail on this branch. Correct resolution: per-entry locator — `.board-titlebar` for the calendar (title-bar contains the board via its line), `.page-crumb` for settings (main's truthful addition): _The run reported an error after replying: Upstream request failed: [invalid_request_error] native reasoning control reasoning_effort is not allowed_ ⚠️ Files still unresolved: `docs/frontend.md`, `docs/ui.md`, `src/Ponente.Kanban/Services/ColumnService.cs`, `tests/e2e/boards.spec.js` — nothing was pushed. --- ⏱ 10m 25s · 35 steps · $0.0657 · tokens: 65.3k in / 8.2k out (+1579.1k cache-read) · Go: 5h 8% · wk 15% · mo 90% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-257/run-2026-10-04T16-40-50-607Z.md) <!-- opencode-session:ses_ef835d6dcffeA0Xqr26rjr2FOp -->
opencode-reviewer left a comment

💬 Verdict: comment — the reviewer did not submit a structured review; the raw output follows.

Let me check how the phone's floating tab bar is accounted for on the calendar page now that the old height: calc(100vh - var(--phone-nav-h)) rule was removed.


⏱ 30m 3s · 48 steps · $0.1440 · tokens: 116.1k in / 2.3k out (+2794.5k cache-read) · Go: 5h 8% · wk 15% · mo 90%
📜 Full session transcript

💬 **Verdict: comment** — the reviewer did not submit a structured review; the raw output follows. Let me check how the phone's floating tab bar is accounted for on the calendar page now that the old `height: calc(100vh - var(--phone-nav-h))` rule was removed. --- ⏱ 30m 3s · 48 steps · $0.1440 · tokens: 116.1k in / 2.3k out (+2794.5k cache-read) · Go: 5h 8% · wk 15% · mo 90% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-257/run-2026-10-04T16-24-12-625Z.md)
Grandiras force-pushed ui-v2-calendar from 57a18cfedb
All checks were successful
CI / build (pull_request) Successful in 43s
to 20b3eff646
All checks were successful
CI / build (pull_request) Successful in 47s
2026-10-04 17:05:40 +00:00
Compare
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-257/run-2026-10-04T17-06-41-303Z.md) — updating every ~5 minutes while the review runs.
Collaborator

🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/710); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-257/run-710.md) — updating every ~5 minutes while the task runs.
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

I found no blocking problems, and the points from my last pass are the same non-blocking notes. I read the diff only; I did not build or run anything, and part of the diff was truncated.

  • ScrollAgendaAsync: it records its key even while the agenda is hidden at desktop width. After a resize to phone width, the agenda won't scroll to its lead day until the next month, pick or filter change.
  • data-date: .calendar-mini-day and .calendar-agenda-day both carry it, so a selector on [data-date] alone matches both. Scope selectors to a class.
  • Day sheet order: it uses Filtered.Where(c => c.DueDate == detailDay), which relies on Filtered keeping the date-then-id order from GetDatedCardsAsync. That holds today, but ByDay no longer enforces it there.

The changes I looked at hold up:

  • GetDeckIdsAsync: one query, and it applies the same "a board of one column has no deck" rule as the board.
  • ByDay: it now covers the whole grid, and the unit tests were updated for that.
  • Docs and changelog: docs/calendar.md, docs/frontend.md and docs/ui.md are updated with their reasons, and the changelog entry is in both locales.

🧠 claude-sonnet-5-5 · ⏱ 0m 4s · 2 turns · tokens: 71.0k in / 542 out (+12.2k cache-read)
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. I found no blocking problems, and the points from my last pass are the same non-blocking notes. I read the diff only; I did not build or run anything, and part of the diff was truncated. - **`ScrollAgendaAsync`:** it records its key even while the agenda is hidden at desktop width. After a resize to phone width, the agenda won't scroll to its lead day until the next month, pick or filter change. - **`data-date`:** `.calendar-mini-day` and `.calendar-agenda-day` both carry it, so a selector on `[data-date]` alone matches both. Scope selectors to a class. - **Day sheet order:** it uses `Filtered.Where(c => c.DueDate == detailDay)`, which relies on `Filtered` keeping the date-then-id order from `GetDatedCardsAsync`. That holds today, but `ByDay` no longer enforces it there. The changes I looked at hold up: - **`GetDeckIdsAsync`:** one query, and it applies the same "a board of one column has no deck" rule as the board. - **`ByDay`:** it now covers the whole grid, and the unit tests were updated for that. - **Docs and changelog:** `docs/calendar.md`, `docs/frontend.md` and `docs/ui.md` are updated with their reasons, and the changelog entry is in both locales. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 4s · 2 turns · tokens: 71.0k in / 542 out (+12.2k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-257/run-710.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:20b3eff646fa2e11b52943486863c13319982dc7 -->
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

The calendar v2 read is solid: the head swap into BoardTitleBar (or its own header.calendar-head on the general calendar) is correctly rendered exactly once per scope with no duplicate ids; the month-tray layout with the container-query chip cap is internally consistent; the timeline's weekend/today overlays share the exact column grid so they align; and ColumnService.GetDeckIdsAsync cleanly answers "done" for the loaded boards in one query. The phone-height accounting works through the existing body:has(.board-header) { padding-bottom: var(--phone-nav-h) } + .calendar-page { flex: 1 } derivation — no --phone-nav-h subtraction is needed from the page itself, which resolves the question I left open on the last pass.


⏱ 20m 58s · 42 steps · $0.1241 · tokens: 318.2k in / 2.5k out (+2053.4k cache-read) · Go: 5h 9% · wk 16% · mo 90%
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. The calendar v2 read is solid: the head swap into `BoardTitleBar` (or its own `header.calendar-head` on the general calendar) is correctly rendered exactly once per scope with no duplicate ids; the month-tray layout with the container-query chip cap is internally consistent; the timeline's weekend/today overlays share the exact column grid so they align; and `ColumnService.GetDeckIdsAsync` cleanly answers "done" for the loaded boards in one query. The phone-height accounting works through the existing `body:has(.board-header) { padding-bottom: var(--phone-nav-h) }` + `.calendar-page { flex: 1 }` derivation — no `--phone-nav-h` subtraction is needed from the page itself, which resolves the question I left open on the last pass. --- ⏱ 20m 58s · 42 steps · $0.1241 · tokens: 318.2k in / 2.5k out (+2053.4k cache-read) · Go: 5h 9% · wk 16% · mo 90% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-257/run-2026-10-04T17-06-41-303Z.md) <!-- opencode-reviewed-head:20b3eff646fa2e11b52943486863c13319982dc7 --> <!-- opencode-verdict:approve -->
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
5 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/Ponente!257
No description provided.