7.9 KiB
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):
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):
// 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:
<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:
.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):
.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
CalendarWeekprops (other than the one value passed toselectedDates). - The modal, week-number gutter, or legend.
selectedDatesstate semantics — it stays the committed selection used for saving.
How to verify (manual)
- Turn on week numbers in Settings (optional) and open the calendar.
- 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.
- Release the mouse: the "Plan N days" dialog opens and the same days stay highlighted (unchanged from today).
- Cancel/Save: highlight clears (unchanged from today).
- A single click still opens the single-day modal (unchanged).
Tests (must stay green + add coverage)
CI gates from frontend/ — all must pass:
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
pointerDownon one current-month day, thenpointerEnteron a later day without firingpointerup. - Assert that the cells in that range now have the
calendar-day--selectedclass before anypointerup— 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.