From cb5517a3874cd48b0266ff5c876661dcba5f2519 Mon Sep 17 00:00:00 2001 From: Jacob Hagstedt Date: Sun, 14 Jun 2026 22:36:43 +0200 Subject: [PATCH] Before calendar multi-select css update run --- JUNIOR_CALENDAR_MULTISELECT_UPDATE.md | 161 ++++++++++++++++++++++++++ frontend/src/pages/calendar.css | 16 ++- 2 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 JUNIOR_CALENDAR_MULTISELECT_UPDATE.md diff --git a/JUNIOR_CALENDAR_MULTISELECT_UPDATE.md b/JUNIOR_CALENDAR_MULTISELECT_UPDATE.md new file mode 100644 index 0000000..17e1ba8 --- /dev/null +++ b/JUNIOR_CALENDAR_MULTISELECT_UPDATE.md @@ -0,0 +1,161 @@ +# Update — Calendar multi-select drag feedback (bug fixes) + +**Audience:** the junior developer who implemented the calendar multi-select feature. +**Area:** `frontend/` only. No backend changes. This is a small, focused follow-up — the feature works, these are two visual/interaction bugs in the *drag-to-select* behavior. + +> Great work on the feature — bars, week numbers, bulk apply and the legend all look good. There are exactly **two** things to fix, described below. Please change **only** what's needed for these two; don't touch the save/bulk-apply logic, the bar rendering, or the modal behavior. + +--- + +## The two bugs (what the user sees vs. what they want) + +### Bug 1 — Days are NOT highlighted *while* you drag-select + +**What happens now:** When you press and drag across days, the day cells stay white during the drag. The blue "selected" highlight (light blue background + blue border) only appears **after** you release the mouse and the "Plan N days" dialog opens. + +**What we want:** As soon as you start dragging, every day in the current selection range should be highlighted **immediately and live**, updating as you drag over more days — and it should look just like the **hover** state of a single day. The highlight must be visible *during* the drag, before the dialog appears. + +### Bug 2 — The day numbers get text-selected (blue "selecting text" boxes) + +**What happens now:** While dragging, the browser selects the day-number text, so the numbers (1, 2, 3, …) get the blue "highlighted text" look — exactly like dragging to select text on a web page. You can see this on the numbers in both screenshots. + +**What we want:** Dragging across the calendar must **never** select text. No day number (or any other calendar text) should get the text-selection highlight while dragging. + +--- + +## Root cause (so the fix is obvious) + +### Bug 1 root cause — in `src/pages/Calendar.tsx` + +The `.calendar-day--selected` highlight is driven by the `selectedDates` state, which is passed down to ``. + +But `selectedDates` is **only written in the `pointerup` handler** (the `useEffect` around lines 29–49): + +```ts +const handlePointerUp = () => { + if (isDragging && dragAnchor && dragCurrent) { + const dates = datesInRange(dragAnchor, dragCurrent) + setSelectedDates(new Set(dates)) // <-- only happens on RELEASE + ... + setIsModalOpen(true) + } + setIsDragging(false) +} +``` + +During the drag itself, only `dragAnchor` and `dragCurrent` change (via `handlePointerEnter`). Because `selectedDates` is still empty until release, nothing is highlighted while dragging. That's the whole bug — it's a *timing* problem, not a styling problem. + +### Bug 2 root cause — in `src/pages/calendar.css` + +`.calendar-day` (and its `.calendar-day__number` child) have **no `user-select` rule**, so the browser's default text selection kicks in during a drag. Note the week-number cells (`.calendar__week-number`) already set `user-select: none` — the day cells just need the same. + +--- + +## Exactly what to change + +### Fix 1 — show the selection live during the drag (`src/pages/Calendar.tsx`) + +Render the highlight from a **derived "preview" set** instead of only the committed `selectedDates`. While dragging, the preview is the range between the anchor and the current day; otherwise it's the committed selection. + +Add this just before the `return` (near where `firstSelectedDate` is computed, ~line 184): + +```ts +// Dates to highlight in the grid. While dragging, show a live preview of the +// range under the cursor; otherwise show the committed selection. +const highlightedDates = + isDragging && dragAnchor && dragCurrent + ? new Set(datesInRange(dragAnchor, dragCurrent)) + : selectedDates +``` + +Then pass `highlightedDates` (not `selectedDates`) to the week: + +```tsx + +``` + +**Important — do NOT change `selectedDates` itself or how it's used elsewhere.** The committed `selectedDates` is what `handleSavePlannedDay` / `handleDeletePlannedDay` read on save. We are only changing **what gets rendered as highlighted**, not what gets saved. Leave the `pointerup` handler and the save logic exactly as they are. + +**Make the highlight match hover.** The look is already close, but make sure `.calendar-day--selected` matches `.calendar-day:hover` so "selecting" and "hovering" look identical. In `src/pages/calendar.css` the hover rule is: + +```css +.calendar-day:hover { + border-color: #0066cc; + box-shadow: 0 2px 8px rgba(0, 102, 204, 0.1); + background-color: #f9fbff; +} +``` + +The existing `.calendar-day--selected` (lines ~117–121) already uses the same three values, so this should need no change — just verify they stay in sync. Do **not** invent a new selected color; the requirement is "same as hover." + +### Fix 2 — stop text selection while dragging (`src/pages/calendar.css`) + +Add `user-select: none` to the day cell so dragging can never select the numbers. Put it on `.calendar-day` (it will also cover the `.calendar-day__number` child): + +```css +.calendar-day { + /* ...existing rules... */ + user-select: none; + -webkit-user-select: none; /* Safari */ +} +``` + +Keep both lines — Safari still needs the `-webkit-` prefix, and ESLint/CI won't complain about it. (If you prefer, you can instead add it once to the grid container `.calendar__grid` so it covers the whole calendar; either is acceptable, but `.calendar-day` is the minimal, targeted change.) + +--- + +## What NOT to touch + +- The bulk-apply / save loop in `handleSavePlannedDay`, and the delete logic. +- The spanning-bar rendering, lane layout, or `CalendarWeek` props (other than the one value passed to `selectedDates`). +- The modal, week-number gutter, or legend. +- `selectedDates` state semantics — it stays the *committed* selection used for saving. + +--- + +## How to verify (manual) + +1. Turn on week numbers in Settings (optional) and open the calendar. +2. Press on a day and drag across several days **without releasing**: + - ✅ Every day under the drag is highlighted live (same look as hovering one day), updating as you move. + - ✅ No day number gets the blue text-selection box; nothing on the calendar looks like selected text. +3. Release the mouse: the "Plan N days" dialog opens and the same days stay highlighted (unchanged from today). +4. Cancel/Save: highlight clears (unchanged from today). +5. A single click still opens the single-day modal (unchanged). + +--- + +## Tests (must stay green + add coverage) + +CI gates from `frontend/` — all must pass: + +```bash +npx tsc --noEmit +npm run lint # zero warnings +npm run coverage # 90% gate +``` + +Add a test in `src/pages/Calendar.test.tsx` (or the existing multi-select test) proving the **live** highlight: + +- Fire `pointerDown` on one current-month day, then `pointerEnter` on a later day **without** firing `pointerup`. +- Assert that the cells in that range now have the `calendar-day--selected` class **before** any `pointerup` — this is exactly the behavior that was missing. + +(There is no clean unit assertion for "text isn't selectable" — that's enforced by the CSS rule; cover it in the manual check above. If you want a guard, a small test asserting `.calendar-day` has the expected class is enough; the `user-select` value itself isn't worth asserting in jsdom.) + +Don't delete or weaken any existing calendar test to make things pass (see `AGENTS.md`). After the change, run `graphify update .`. + +--- + +## Definition of done + +- [ ] Dragging highlights the day range **live**, matching the hover look, before the dialog opens. +- [ ] No text (day numbers or otherwise) gets selected while dragging. +- [ ] Committed selection, save/bulk-apply, delete, modal, bars, week numbers and legend all behave exactly as before. +- [ ] A test covers the live (pre-`pointerup`) highlight. +- [ ] `tsc --noEmit`, `npm run lint` (0 warnings), `npm run coverage` (≥90%) pass; existing tests still green. +- [ ] `graphify update .` run. +``` diff --git a/frontend/src/pages/calendar.css b/frontend/src/pages/calendar.css index d7ac1ec..2ea9b3f 100644 --- a/frontend/src/pages/calendar.css +++ b/frontend/src/pages/calendar.css @@ -115,8 +115,13 @@ } .calendar-day--selected { - background-color: #e3f2fd; + background-color: #f9fbff; border-color: #0066cc; + box-shadow: 0 2px 8px rgba(0, 102, 204, 0.1); +} + +.calendar-day--selected:hover { + background-color: #f0f8ff; } .calendar-day__events { @@ -257,6 +262,10 @@ font-weight: 600; font-size: 12px; color: #999; + background-color: #fafafa; + border-right: 1px solid #d0d0d0; + pointer-events: none; + user-select: none; } .calendar__week-number { @@ -267,8 +276,11 @@ font-weight: 600; font-size: 11px; color: #999; - border-right: 1px solid #e0e0e0; + border-right: 1px solid #d0d0d0; background-color: #fafafa; + pointer-events: none; + user-select: none; + min-height: 84px; } .calendar__week-number-text {