Files
F-r-ldraledigplannerare/JUNIOR_CALENDAR_MULTISELECT_UPDATE.md
T

162 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `<CalendarWeek selectedDates={selectedDates} />`.
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
<CalendarWeek
...
selectedDates={highlightedDates} // was: selectedDates
...
/>
```
**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.
```