Skip to content

Conversation

@nicomiguelino
Copy link
Contributor

@nicomiguelino nicomiguelino commented Jan 25, 2026

User description

Implements Google Calendar-style overlapping event layout using a column-based algorithm with BFS for event clustering.

Changes:

  • Events now visually overlap with proper width distribution (85% for left column, 50% for right)
  • Z-index stacking matches Google Calendar behavior (right events appear on top)
  • Fixes duplicate event rendering across multiple time slots

Applies to:

  • Daily calendar view
  • Weekly calendar view (to be tested)

Screenshots

image image image image

PR Type

Enhancement


Description

  • Add event clustering and layout utilities

  • Integrate column-based overlap layout in views

  • Replace event style functions with wrapper/item styles

  • Update SCSS for wrapper-based event styling


Diagram Walkthrough

flowchart LR
  A["All events"] -- "findEventClusters()" --> B["Event clusters"]
  B -- "calculateClusterLayouts()" --> C["Layouts map"]
  C -- "getWrapperStyle() & getItemStyle()" --> D["Calendar views"]
Loading

File Walkthrough

Relevant files
Enhancement
event-layout-utils.ts
Implement column-based event layout utilities                       

edge-apps/blueprint/ts/utils/event-layout-utils.ts

  • Add eventsOverlap for time overlap detection
  • Implement findEventClusters with BFS adjacency
  • Add calculateClusterLayouts for column spans
  • Provide getEventKey for stable event mapping
+186/-0 
DailyCalendarView.vue
Integrate overlap layout in daily view                                     

edge-apps/blueprint/ts/components/calendar/DailyCalendarView.vue

  • Import layout utils and dayjs plugins
  • Compute eventLayouts with clusters and layouts
  • Replace getEventStyle with getWrapperStyle and getItemStyle
  • Add getEventLayout and getAllEventsForToday helpers
+110/-27
WeeklyCalendarView.vue
Integrate overlap layout in weekly view                                   

edge-apps/blueprint/ts/components/calendar/WeeklyCalendarView.vue

  • Group events by day and compute eventLayouts
  • Import layout utils and update dayjs plugins
  • Replace style functions with wrapper/item variants
  • Add per-day clustering and layout computation
+140/-24
Formatting
daily-calendar-view.scss
Adjust daily view event wrapper styles                                     

edge-apps/blueprint/ts/assets/calendar/daily-calendar-view.scss

  • Add .calendar-event-wrapper border and radius
  • Move positioning from .calendar-event-item to wrapper
  • Update .calendar-event-item for left border style
+14/-7   
weekly-calendar-view.scss
Adjust weekly view event wrapper styles                                   

edge-apps/blueprint/ts/assets/calendar/weekly-calendar-view.scss

  • Define event-wrapper-styles mixin for wrappers
  • Update event-base-styles mixin parameters
  • Apply wrapper mixin to .calendar-event-wrapper
+16/-11 

…t layout

- Add column-based layout algorithm using BFS for connected event clusters
- Implement visual overlap with 70% overlap ratio for earlier column events
- Add z-index stacking where higher columns appear on top
- Only render events in their starting time slot to avoid duplicates
- Remove hardcoded right positioning from SCSS to support dynamic widths
@github-actions
Copy link

github-actions bot commented Jan 25, 2026

PR Reviewer Guide 🔍

(Review updated until commit caf62cf)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Missing Timezone Plugin

The utility calls dayjs(...).tz(...) without importing or extending the timezone plugin in this module, which can lead to runtime errors if the plugin isn't applied globally.

  const aStart = dayjs(a.startTime).tz(timezone)
  const aEnd = dayjs(a.endTime).tz(timezone)
  const bStart = dayjs(b.startTime).tz(timezone)
  const bEnd = dayjs(b.endTime).tz(timezone)
  return aStart.isBefore(bEnd) && bStart.isBefore(aEnd)
}
Cache Key Collision

The cache key in getWrapperStyle omits the layout.span value, so events with the same start/end times and column but different spans may incorrectly reuse cached styles.

// Create cache key that includes layout information to prevent collisions
// for events with identical start/end/backgroundColor but different positions
const cacheKey = `wrapper-${event.startTime}-${event.endTime}-${layout.index}-${layout.total}`

if (eventStyleCache.has(cacheKey)) {
  return eventStyleCache.get(cacheKey)!
Cache Not Cleared

The style cache in WeeklyCalendarView isn't cleared when the events list changes, which can cause stale positioning to persist after updates.

// Get wrapper style for an event (positioning only) - with caching for better performance
const getWrapperStyle = (event: CalendarEvent): Record<string, string> => {
  // Get layout for this event using column-based algorithm (Google Calendar style)
  const layout = getEventLayout(event)

  // Create cache key that includes layout information to prevent collisions
  // for events with identical start/end/backgroundColor but different positions
  const cacheKey = `wrapper-${event.startTime}-${event.endTime}-${layout.index}-${layout.total}`

@github-actions
Copy link

github-actions bot commented Jan 25, 2026

PR Code Suggestions ✨

Latest suggestions up to caf62cf
Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Initialize dayjs timezone plugin

The utility uses .tz() without importing or extending the timezone plugin, causing
runtime errors. Add the UTC and timezone plugins and extend dayjs before using
.tz().

edge-apps/blueprint/ts/utils/event-layout-utils.ts [1-2]

 import dayjs from 'dayjs'
+import utc from 'dayjs/plugin/utc'
+import timezone from 'dayjs/plugin/timezone'
+dayjs.extend(utc)
+dayjs.extend(timezone)
 import type { CalendarEvent } from '../constants/calendar'
Suggestion importance[1-10]: 8

__

Why: Without extending the UTC and timezone plugins, calls to .tz() will fail at runtime in event-layout-utils.ts, so this change is critical to avoid errors.

Medium
General
Clear style cache on layout change

The wrapper style cache isn’t cleared when layouts change, which may serve stale
positioning after events update. Watch eventLayouts and clear eventStyleCache on
change.

edge-apps/blueprint/ts/components/calendar/DailyCalendarView.vue [240]

 const eventStyleCache = new Map<string, Record<string, string>>()
 
+watch(eventLayouts, () => {
+  eventStyleCache.clear()
+})
+
Suggestion importance[1-10]: 5

__

Why: Caching wrapper styles without clearing on layout updates can lead to stale positioning; watching eventLayouts ensures the cache stays in sync when layouts change.

Low
Include unique id in event key

Generating keys from start/end times and title can collide for identical events;
include a unique id (if available) to ensure distinct keys.

edge-apps/blueprint/ts/utils/event-layout-utils.ts [184-186]

 export const getEventKey = (event: CalendarEvent): string => {
-  return `${event.startTime}|${event.endTime}|${event.title || ''}`
+  return event.id
+    ? `${event.id}|${event.startTime}|${event.endTime}|${event.title || ''}`
+    : `${event.startTime}|${event.endTime}|${event.title || ''}`
 }
Suggestion importance[1-10]: 4

__

Why: Adding an id field to the event key helps prevent collisions when multiple events share the same start/end times and title, though it assumes CalendarEvent includes an id.

Low

Previous suggestions

Suggestions up to commit 5e059bd
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix access to now prop

The now prop is defined as a Date, not a reactive ref, so now.value will be
undefined. Use props.now directly (or destructure it via toRefs) to correctly access
the current date.

edge-apps/blueprint/ts/components/calendar/DailyCalendarView.vue [345-350]

 const getAllEventsForToday = computed(() => {
-  const today = dayjs(now.value).tz(props.timezone)
-  return events.value.filter((event) => {
-    return dayjs(event.startTime).tz(props.timezone).isSame(today, 'day')
-  })
+  const today = dayjs(props.now).tz(props.timezone)
+  return events.value.filter((event) =>
+    dayjs(event.startTime).tz(props.timezone).isSame(today, 'day')
+  )
 })
Suggestion importance[1-10]: 9

__

Why: Referencing now.value will be undefined since now isn’t a ref, so switching to props.now fixes a critical bug in event filtering based on the current date.

High
Use columnSpan in width

The current width calculation ignores multi‐column spans and will misrender events
that can expand over several columns. Incorporate layout.span into the width and
overlap calculation so that events spanning multiple columns render at the correct
width. This ensures your columnSpan logic is actually applied in the style.

edge-apps/blueprint/ts/components/calendar/DailyCalendarView.vue [393-406]

-// Get layout for this event using column-based algorithm (Google Calendar style)
+// Get layout for this event using column-based algorithm
 const layout = getEventLayout(event)
-
-// Calculate width and left position based on column layout
-// Google Calendar style: events in earlier columns visually overlap into later columns
-const baseWidth = 100 / layout.total
+const span = layout.span
+const total = layout.total
+const baseWidth = 100 / total
 const left = layout.index * baseWidth
 
-// Events overlap into the next column's space (except the last column)
-// overlapRatio controls how much of the next column's space to overlap into
+// Overlap into adjacent columns
 const overlapRatio = 0.7
-const isLastColumn = layout.index === layout.total - 1
-const overlapBonus = isLastColumn ? 0 : baseWidth * overlapRatio
-const width = baseWidth + overlapBonus
+const overlapBonus = baseWidth * overlapRatio * (span - 1)
+const width = baseWidth * span + overlapBonus
Suggestion importance[1-10]: 8

__

Why: The change correctly applies the multi‐column span logic to avoid misrendered event widths, improving functional accuracy when events span multiple columns.

Medium

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements a Google Calendar-style overlapping event layout for the daily calendar by computing overlap clusters, assigning events to columns, and applying width/left/z-index positioning to event blocks.

Changes:

  • Added a BFS-based clustering + column assignment algorithm to compute overlapping event layouts.
  • Updated event rendering to avoid duplicate rendering across multiple time slots by rendering only at the start hour.
  • Updated event styling to apply column-based width/left positioning and z-index stacking; removed conflicting right styling in SCSS.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
edge-apps/blueprint/ts/components/calendar/DailyCalendarView.vue Adds overlap clustering + column layout calculations and applies width/left/z-index styling; changes per-slot event selection to “start-only”.
edge-apps/blueprint/ts/assets/calendar/daily-calendar-view.scss Removes right positioning to allow inline left/width layout to control event placement.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

nicomiguelino and others added 8 commits January 24, 2026 19:33
- Add optional chaining to safely access column element before calling isSameOrBefore
- Prevents 'Object is possibly undefined' TypeScript error
Replace queue.shift() with index-based queue traversal to reduce time complexity of BFS from O(n²) to O(n)
…idth and left offset

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Use layout.span instead of layout.columnSpan to match the return type of getEventLayout function
- Add layout index and total columns to cache key to prevent collisions
- Events with identical start/end/backgroundColor now cache separately based on layout position
- Add colored left border to calendar event items matching weekly view style
- Use theme color for visual consistency with Google Calendar appearance
- Fix border syntax and add base style for proper CSS specificity
- Reduce event height by 4% to create visual separation between adjacent events
- Ensure minimum height of 90% of original to maintain readability
- Matches Google Calendar appearance with minimal spacing between events
…n weekly calendar

- Add event clustering with BFS for transitive overlap detection
- Implement column-based layout algorithm for consistent event positioning
- Add visual overlapping effect with 70% overlap ratio
- Include z-index stacking for proper event layering
- Add 4% gap between adjacent events for visual separation
- Update cache key to include layout properties preventing collisions
- Maintain event style caching for performance
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 10 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Change isAfter to isBefore comparison for accurate week range check
- Ensures events at the start of the week are properly included
…View

- Change layoutMap key from 'startTime-endTime' string to CalendarEvent object
- Prevents cache collisions when multiple distinct events share identical start/end times
- Aligns with DailyCalendarView implementation
- Add getEventKey helper function generating stable key from startTime, endTime, and title
- Replace object reference keying with composite string key in layoutMap
- Ensures consistent layout lookup regardless of event object identity
- Prevents layout misapplication for duplicate start/end time events
- Pre-sort events by start time for efficient overlap detection with early termination
- Build adjacency list once per cluster to eliminate O(n²) repeated comparisons in BFS
- Pre-compute events per column to reduce span calculation from O(n³) to O(n²)
- Add isSameOrAfter dayjs plugin for proper time comparisons
- Reduces complexity in dense overlap scenarios from O(n³) to O(n² log n)
- Create event-layout-utils.ts with shared functions for overlapping event layout
- Move EventLayout interface, findEventClusters, calculateClusterLayouts, getEventKey
- Update DailyCalendarView and WeeklyCalendarView to use shared utilities
- Reduce code duplication (~280 lines removed)
…lity

- Wrap calendar events with a new wrapper container for border styling
- Add thin white border (0.07rem) around events in both daily and weekly views
- Split getEventStyle into getWrapperStyle (positioning) and getItemStyle (background-color)
- Preserve existing red left border and event color-coding
- Add back default grey (#e6e7e7) background to calendar-event-item
- Fixes transparent events in calendar apps that don't provide backgroundColor
@nicomiguelino nicomiguelino marked this pull request as ready for review January 26, 2026 04:05
@nicomiguelino nicomiguelino changed the base branch from master to dependabot/bun/edge-apps/asset-metadata/bun-4fb95960b5 January 26, 2026 04:05
@nicomiguelino nicomiguelino merged commit b2f1e7e into dependabot/bun/edge-apps/asset-metadata/bun-4fb95960b5 Jan 26, 2026
20 checks passed
@github-actions
Copy link

Persistent review updated to latest commit caf62cf

@nicomiguelino nicomiguelino deleted the calendar-apps/fix-layout-of-overlapping-events branch January 26, 2026 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant