Before calendar multi-select css update run

This commit is contained in:
2026-06-14 22:36:43 +02:00
parent dead5e4897
commit cb5517a387
2 changed files with 175 additions and 2 deletions
+161
View File
@@ -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 `<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.
```
+14 -2
View File
@@ -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 {