Escape does not close either of the calendar's two dialogs #173

Closed
opened 2026-08-26 23:18:15 +00:00 by claude-bot · 2 comments
Collaborator

Calendar.razor has two overlays that declare themselves dialogs — the day sheet (src/Ponente.Kanban/Components/Pages/Calendar.razor:468, .calendar-overlay / .calendar-day-sheet) and the subscribe panel (:506, .calendar-overlay / .calendar-subscribe-panel). Both carry role="dialog" aria-modal="true", and neither has any keyboard handling: no @onkeydown, no tabindex, nothing focused when it opens. Escape does nothing on either — they close only by clicking the overlay or the ✕.

Every other dialog in the app closes on Escape (CardModal, FeedbackModal, ChangelogModal, and the column tag flyout in ColumnView), so this reads as an oversight rather than a decision.

Noticed while fixing #165, and deliberately left out of scope there: the fix for that one is wwwroot/js/modal-escape.js, which redirects an Escape pressed with nothing focused onto the topmost .modal-backdrop. These two overlays use their own class and so are not covered by it. Either give them the .modal-backdrop shape (a tabindex="-1" element with an @onkeydown, which is what the other three do and what would make the existing JS carry them for free), or widen the selector in modal-escape.js — the first is probably the smaller change, but the class carries the calendar's own layering (docs/frontend.md, and the z-index note beside .calendar-overlay in app.css), so it needs looking at rather than renaming.

tests/e2e/calendar.spec.js opens the day cell already; the assertion belongs in that fixture.


Filed by Claude while working on #165 (run).

`Calendar.razor` has two overlays that declare themselves dialogs — the day sheet (`src/Ponente.Kanban/Components/Pages/Calendar.razor:468`, `.calendar-overlay` / `.calendar-day-sheet`) and the subscribe panel (`:506`, `.calendar-overlay` / `.calendar-subscribe-panel`). Both carry `role="dialog" aria-modal="true"`, and neither has any keyboard handling: no `@onkeydown`, no `tabindex`, nothing focused when it opens. Escape does nothing on either — they close only by clicking the overlay or the ✕. Every other dialog in the app closes on Escape (`CardModal`, `FeedbackModal`, `ChangelogModal`, and the column tag flyout in `ColumnView`), so this reads as an oversight rather than a decision. Noticed while fixing #165, and deliberately left out of scope there: the fix for that one is `wwwroot/js/modal-escape.js`, which redirects an Escape pressed with nothing focused onto the topmost `.modal-backdrop`. These two overlays use their own class and so are not covered by it. Either give them the `.modal-backdrop` shape (a `tabindex="-1"` element with an `@onkeydown`, which is what the other three do and what would make the existing JS carry them for free), or widen the selector in `modal-escape.js` — the first is probably the smaller change, but the class carries the calendar's own layering (`docs/frontend.md`, and the z-index note beside `.calendar-overlay` in `app.css`), so it needs looking at rather than renaming. `tests/e2e/calendar.spec.js` opens the day cell already; the assertion belongs in that fixture. --- _Filed by Claude while working on #165 ([run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/398))._
Author
Collaborator

🤖 On it — follow along in the action run; the reply will link the full session transcript.

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

🤖 On it — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/403); the reply will link the full session transcript. <!-- claude-run:comment --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/issue-173/run-403.md) — updating every ~5 minutes while the task runs.
Author
Collaborator

Escape now closes both calendar dialogs. Branch claude/issue-173.

What changed

Both overlays got the shape the other three dialogs use, rather than the class: tabindex="-1" plus @onkeydown on .calendar-overlay, and the page focuses that overlay from OnAfterRenderAsync on the render that opened it. Focus goes to the overlay itself, not to a control inside it — for ChangelogModal's reason: neither dialog is here to be typed into (the sheet is a list to read; the subscribe panel's one field is readonly and often still empty while the token is minted).

The class stays .calendar-overlay, and modal-escape.js names it beside .modal-backdrop instead. Renaming would have handed these two the modal's looks along with its keyboard — the class carries the calendar's own layering (z-index 900, under the card dialog's 1000, and no blur). The JS's "last in document order is topmost" rule still holds across the two classes: the only pair that can be on screen together is an overlay and the CardModal opened from it, which Calendar.razor renders after both, and at 900 the overlay covers the header the other dialogs are opened from.

One thing worth recording, because it decides where each test presses Escape: the day cell is a <div> and takes no focus, so the day sheet lands in exactly the "nothing focused" window modal-escape.js covers — the widened selector is what carries it. The Subscribe control is a <button> and keeps focus, so the JS deliberately does not step in there and the overlay taking focus itself is the whole mechanism.

Tests

tests/e2e/calendar.spec.js, in the fixtures that already open each one:

  • the busy-day test presses Escape immediately after the sheet opens (the JS path), then reopens and carries on;
  • the subscription test presses Escape with Regenerate focused (the bubbling path), and separately polls that the overlay took focus when it opened.

Both fail on the unmodified code — verified by stashing the Calendar.razor and modal-escape.js changes and re-running: the sheet stays open after Escape, and the focus poll times out.

Verified

dotnet build clean, dotnet test 448/448. E2E run against a host-run stack (no container runtime in this job, so app on :8090 with local Postgres, ASPNETCORE_ENVIRONMENT=Development): calendar.spec.js + calendar-all.spec.js + changelog.spec.js → 30 passed, and board.spec.js → 23 passed.

One pre-existing failure, not from this change: calendar.spec.js:515 "a six-week month fits a short window without a clipped last row" fails here on unmodified main too (I checked out the stash and re-ran it to confirm), with a grid overflow of 14px. It's a pixel-metric assertion and this container isn't the compose image it was written against — it passed on my first run of the file and failed on later ones, so I'd treat it as environmental rather than a real regression, but it's worth knowing it's sensitive.

Docs and changelog

docs/calendar.md gains the paragraph explaining the shape and why the class was kept and the selector widened; docs/frontend.md and docs/ui.md no longer say "the three modals". The existing unreleased changelog line was widened from "a dialog" to "every dialog" rather than given a second bullet saying the same thing — that line shipped as a promise this change makes true.

No image: the change is keyboard behaviour, and the "after" frame is a calendar with nothing on it.


⏱ 20m 25s · 84 turns · tokens: 103.4k in / 33.3k out (+6863.2k cache-read)
📜 Full session transcript
→ Opened PR: #175

Escape now closes both calendar dialogs. Branch `claude/issue-173`. ## What changed Both overlays got **the shape the other three dialogs use**, rather than the class: `tabindex="-1"` plus `@onkeydown` on `.calendar-overlay`, and the page focuses that overlay from `OnAfterRenderAsync` on the render that opened it. Focus goes to the overlay itself, not to a control inside it — for `ChangelogModal`'s reason: neither dialog is here to be typed into (the sheet is a list to read; the subscribe panel's one field is `readonly` and often still empty while the token is minted). The class stays `.calendar-overlay`, and **`modal-escape.js` names it beside `.modal-backdrop`** instead. Renaming would have handed these two the modal's looks along with its keyboard — the class carries the calendar's own layering (z-index 900, under the card dialog's 1000, and no blur). The JS's "last in document order is topmost" rule still holds across the two classes: the only pair that can be on screen together is an overlay and the `CardModal` opened from it, which `Calendar.razor` renders after both, and at 900 the overlay covers the header the other dialogs are opened from. One thing worth recording, because it decides where each test presses Escape: the **day cell is a `<div>`** and takes no focus, so the day sheet lands in exactly the "nothing focused" window `modal-escape.js` covers — the widened selector is what carries it. The **Subscribe control is a `<button>`** and keeps focus, so the JS deliberately does *not* step in there and the overlay taking focus itself is the whole mechanism. ## Tests `tests/e2e/calendar.spec.js`, in the fixtures that already open each one: - the busy-day test presses Escape **immediately** after the sheet opens (the JS path), then reopens and carries on; - the subscription test presses Escape with Regenerate focused (the bubbling path), and separately polls that the overlay took focus when it opened. Both fail on the unmodified code — verified by stashing the `Calendar.razor` and `modal-escape.js` changes and re-running: the sheet stays open after Escape, and the focus poll times out. ## Verified `dotnet build` clean, `dotnet test` 448/448. E2E run against a host-run stack (no container runtime in this job, so app on `:8090` with local Postgres, `ASPNETCORE_ENVIRONMENT=Development`): `calendar.spec.js` + `calendar-all.spec.js` + `changelog.spec.js` → 30 passed, and `board.spec.js` → 23 passed. One pre-existing failure, **not from this change**: `calendar.spec.js:515` *"a six-week month fits a short window without a clipped last row"* fails here on unmodified `main` too (I checked out the stash and re-ran it to confirm), with a grid overflow of 14px. It's a pixel-metric assertion and this container isn't the compose image it was written against — it passed on my first run of the file and failed on later ones, so I'd treat it as environmental rather than a real regression, but it's worth knowing it's sensitive. ## Docs and changelog `docs/calendar.md` gains the paragraph explaining the shape and why the class was kept and the selector widened; `docs/frontend.md` and `docs/ui.md` no longer say "the three modals". The existing unreleased changelog line was widened from "a dialog" to "every dialog" rather than given a second bullet saying the same thing — that line shipped as a promise this change makes true. No image: the change is keyboard behaviour, and the "after" frame is a calendar with nothing on it. --- ⏱ 20m 25s · 84 turns · tokens: 103.4k in / 33.3k out (+6863.2k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/issue-173/run-403.md) → Opened PR: https://git.grandiras.net/Grandiras/Ponente/pulls/175
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
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#173
No description provided.