diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-09-07 07:07:32 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-09-07 07:07:32 +0000 |
| commit | 86f1486b77cb345f3cfdacecbc676692cd0d89f7 (patch) | |
| tree | d202ec51599a0b7c7a0827a9b7c4867688c888a7 /web/static/js | |
| parent | 97ca0d218f8c9a983b6e6d02e01325f266baac32 (diff) | |
Split timeline width across lanes instead of stacking overlapping events
The CSS gives every .calendar-event `left: 8px; right: 8px`, and the
positioning code only ever overrode `left` (by 16px per column). So two
events occupying the same slot both stayed full-width to the right edge
and the later one simply covered the earlier one, leaving a 16px sliver.
The column assignment was computing a correct answer that the layout then
threw away.
Reported as two yard-work chores rendering on top of each other. They sit
at byte-identical timestamps because a bucket cycle stamps every item it
activates with the same due_date, which makes this the maximally-bad case
(identical top and height, so the overlap is total).
Events now split the available width across the number of lanes in their
overlap cluster, and a lone event keeps the full-width behaviour it had.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
Diffstat (limited to 'web/static/js')
| -rw-r--r-- | web/static/js/app.js | 27 |
1 files changed, 25 insertions, 2 deletions
diff --git a/web/static/js/app.js b/web/static/js/app.js index 6071ce3..0a74b46 100644 --- a/web/static/js/app.js +++ b/web/static/js/app.js @@ -54,7 +54,21 @@ function initTimelineCalendar(calendarId, nowLineId, untimedSectionId) { ev.column = col; } - // Position events with column-based indentation + // How many lanes each event has to share its slot with. The CSS gives every + // event `left: 8px; right: 8px` (full width); previously only `left` was + // overridden, by 16px per column, so two events at the SAME time stayed + // full-width and the later one simply covered the earlier one except for a + // 16px sliver -- reported as two bucket chores "rendered on top of each + // other". They land on identical timestamps because a bucket cycle stamps + // every item it activates with the same due_date. + eventData.forEach(function(ev) { + const overlapping = eventData.filter(function(other) { + return other.endMin > ev.startMin && other.startMin < ev.endMin; + }); + ev.lanes = Math.max.apply(null, overlapping.map(function(o) { return o.column; })) + 1; + }); + + // Position events, splitting the available width across lanes eventData.forEach(function(ev) { const el = ev.el; const hour = parseInt(el.dataset.hour); @@ -65,7 +79,16 @@ function initTimelineCalendar(calendarId, nowLineId, untimedSectionId) { el.style.top = top + 'px'; el.style.height = height + 'px'; - el.style.left = (8 + ev.column * 16) + 'px'; + if (ev.lanes > 1) { + const lane = '((100% - 16px) / ' + ev.lanes + ')'; + el.style.left = 'calc(8px + ' + ev.column + ' * ' + lane + ')'; + el.style.width = 'calc(' + lane + ' - 4px)'; + el.style.right = 'auto'; + } else { + el.style.left = '8px'; + el.style.right = '8px'; + el.style.width = ''; + } el.style.display = 'block'; // Debounced hover effect (100ms delay) |
