Skip to content

feat(calendar): redesign app using plain HTML/CSS/TS#717

Draft
nicomiguelino wants to merge 19 commits intomasterfrom
feat/overhaul-calendar-app
Draft

feat(calendar): redesign app using plain HTML/CSS/TS#717
nicomiguelino wants to merge 19 commits intomasterfrom
feat/overhaul-calendar-app

Conversation

@nicomiguelino
Copy link
Contributor

Summary

  • Migrate calendar app from Vue 3 + Pinia to framework-free Web Components
  • Add reusable <weekly-calendar-view> custom element to edge-apps-library
  • Port column layout algorithm and 12-hour window logic from the blueprint
  • Implement partial event clipping and column-scoped current-time indicator

- Migrate from Vue 3 + Pinia to framework-free Web Components
- Add reusable weekly-calendar-view custom element to edge-apps-library

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nicomiguelino nicomiguelino self-assigned this Mar 2, 2026
@github-actions
Copy link

github-actions bot commented Mar 2, 2026

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 Security concerns

SSRF / untrusted URL fetching:
fetchCalendarEventsFromICal constructs icalUrlWithProxy from screenly.settings.ical_url (a secret) and optionally prepends screenly.cors_proxy_url via string concatenation. If app settings can be influenced, this enables fetching arbitrary URLs (directly or via proxy), which is a common SSRF risk and may expose internal endpoints through the runtime/proxy. Mitigations to consider: validate ical_url scheme/host (allowlist or at least restrict to http/https), URL-encode the target when passing through the proxy, and ensure the proxy endpoint cannot be abused to reach internal networks.

⚡ Recommended focus areas for review

Date Filtering

The iCal fetcher filters events using only the event start timestamp within the computed view range. This can drop events that start before the range but overlap into it (e.g., multi-hour events crossing midnight or spanning into the day/week). Consider filtering by overlap (eventEnd > rangeStart && eventStart < rangeEnd) and validating behavior for all-day and multi-day events.

const mappedViewMode: ViewMode =
  viewMode === 'monthly' ? VIEW_MODE.SCHEDULE : (viewMode as ViewMode)
const { startDate, endDate } = getDateRangeForViewMode(
  mappedViewMode,
  timezone,
)

const startTimestamp = startDate.getTime()
const endTimestamp = endDate.getTime()

const chunkSize = 100
const events: CalendarEvent[] = []

for (let i = 0; i < vevents.length; i += chunkSize) {
  const chunk = vevents.slice(i, i + chunkSize)

  chunk.forEach((vevent) => {
    const event = new ical.Event(vevent)
    const eventStart = event.startDate.toJSDate()
    const eventTimestamp = eventStart.getTime()

    if (eventTimestamp >= endTimestamp || eventTimestamp < startTimestamp) {
      return
    }

    const eventEnd = event.endDate.toJSDate()

    events.push({
      title: event.summary || 'Busy',
      startTime: eventStart.toISOString(),
      endTime: eventEnd.toISOString(),
      isAllDay: event.startDate.isDate,
    })
  })
Robust Parsing

bypass_cors is parsed via JSON.parse on a settings value that may be undefined or not valid JSON, which can throw and fail the whole fetch. Also, concatenating the proxy URL and the iCal URL without encoding can produce malformed URLs (double slashes, missing scheme, etc.). Add safer parsing/defaulting for the setting and build the proxied URL defensively.

const screenlySettings = screenly.settings
const { ical_url: icalUrl } = screenlySettings
const corsProxy = screenly.cors_proxy_url
const bypassCors = Boolean(
  JSON.parse(screenlySettings.bypass_cors as string),
)
const viewMode = screenlySettings.calendar_mode as string

const icalUrlWithProxy = bypassCors
  ? `${corsProxy}/${icalUrl as string}`
  : (icalUrl as string)

const response = await fetch(icalUrlWithProxy)
Performance

The component rerenders the entire shadow DOM on every now update (set every second) and on any events/timezone/locale change by rebuilding all DOM nodes. This can be expensive on low-powered devices and may cause layout/paint churn. Consider minimizing updates (e.g., update only the current-time indicator position), caching computed layouts/timeSlots per window, or using a more incremental render strategy.

get now(): Date {
  return this._now
}

set now(value: Date) {
  this._now = value
  if (this._initialized) this._render()
}

private _getWeekStart(): Date {
  const tz = this._timezone
  const nowInTz = dayjs(this._now).tz(tz)
  const weekStart = nowInTz.startOf('week')
  return weekStart.toDate()
}

private _getEventLayouts(): Map<string, EventLayout> {
  const tz = this._timezone
  const weekStartDate = dayjs(this._getWeekStart()).tz(tz)
  const layoutMap = new Map<string, EventLayout>()
  const eventsByDay = new Map<number, CalendarEvent[]>()

  const weekEvents = this._events.filter((event) => {
    const eventStart = dayjs(event.startTime).tz(tz)
    return (
      !eventStart.isBefore(weekStartDate) &&
      eventStart.isBefore(weekStartDate.add(7, 'day'))
    )
  })

  weekEvents.forEach((event) => {
    const eventStart = dayjs(event.startTime).tz(tz)
    const dayDiff = eventStart.diff(weekStartDate, 'day')
    const dayIndex = ((dayDiff % 7) + 7) % 7

    if (!eventsByDay.has(dayIndex)) {
      eventsByDay.set(dayIndex, [])
    }
    eventsByDay.get(dayIndex)!.push(event)
  })

  for (let dayIndex = 0; dayIndex < 7; dayIndex++) {
    const dayEvents = eventsByDay.get(dayIndex) || []
    if (dayEvents.length === 0) continue

    const clusters = findEventClusters(dayEvents, tz)
    for (const cluster of clusters) {
      const clusterLayouts = calculateClusterLayouts(cluster, tz)
      for (const [event, layout] of clusterLayouts) {
        layoutMap.set(getEventKey(event), layout)
      }
    }
  }

  return layoutMap
}

private _render() {
  const shadow = this.shadowRoot!
  const tz = this._timezone
  const locale = this._locale
  const now = this._now

  const currentHour = parseInt(
    now.toLocaleString('en-US', {
      hour: 'numeric',
      hour12: false,
      timeZone: tz,
    }),
    10,
  )
  const windowStartHour = getWindowStartHour(currentHour)
  const timeSlots = generateTimeSlots(windowStartHour, now, locale)

  const weekStart = this._getWeekStart()
  const eventLayouts = this._getEventLayouts()

  const todayStr = dayjs(now).tz(tz).format('YYYY-MM-DD')

  const currentMinute = parseInt(
    now.toLocaleString('en-US', {
      minute: 'numeric',
      timeZone: tz,
    }),
    10,
  )
  const currentSlotIndex = timeSlots.findIndex(
    (slot) => slot.hour === currentHour,
  )
  const minuteFraction = currentMinute / 60
  const timeIndicatorPct =
    currentSlotIndex >= 0
      ? ((currentSlotIndex + minuteFraction) / 12) * 100
      : -1

  shadow.innerHTML = `<style>${componentCss}</style>`

  const container = document.createElement('div')
  container.className = 'weekly-calendar-container'

  const title = document.createElement('p')
  title.className = 'this-week-title'
  title.textContent = 'This week'
  container.appendChild(title)

  const weekGrid = document.createElement('div')
  weekGrid.className = 'week-grid'

  const timeGutter = document.createElement('div')
  timeGutter.className = 'time-gutter'
  for (const slot of timeSlots) {
    const label = document.createElement('div')
    label.className = 'time-label'
    label.textContent = slot.time
    timeGutter.appendChild(label)
  }
  weekGrid.appendChild(timeGutter)

  const daysGrid = document.createElement('div')
  daysGrid.className = 'days-grid'

  for (let dayIdx = 0; dayIdx < 7; dayIdx++) {
    const dayDate = new Date(weekStart)
    dayDate.setDate(dayDate.getDate() + dayIdx)
    const dayDateStr = dayjs(dayDate).tz(tz).format('YYYY-MM-DD')
    const isToday = dayDateStr === todayStr

    const dayCol = document.createElement('div')
    dayCol.className = 'day-column'
    setAttribute(dayCol, 'data-day-index', String(dayIdx))

    const dayHeader = document.createElement('div')
    dayHeader.className = isToday ? 'day-header today' : 'day-header'

    const dayName = document.createElement('span')
    dayName.className = 'day-name'
    dayName.textContent = DAYS_OF_WEEK[dayIdx] || ''
    dayHeader.appendChild(dayName)

    const dayDateNum = document.createElement('span')
    dayDateNum.className = 'day-date'
    dayDateNum.textContent = String(dayDate.getDate())
    dayHeader.appendChild(dayDateNum)

    dayCol.appendChild(dayHeader)

    const dayBody = document.createElement('div')
    dayBody.className = 'day-body'

    for (let rowIdx = 0; rowIdx < 12; rowIdx++) {
      const hourRow = document.createElement('div')
      hourRow.className = 'hour-row'
      dayBody.appendChild(hourRow)
    }

    const windowEndHour = (windowStartHour + 12) % 24

    const dayEvents = this._events.filter((event) => {
      if (event.isAllDay) return false
      const eventStart = dayjs(event.startTime).tz(tz)
      const eventDate = eventStart.format('YYYY-MM-DD')
      if (eventDate !== dayDateStr) return false

      const startH = eventStart.hour() + eventStart.minute() / 60
      const endDt = dayjs(event.endTime).tz(tz)
      const endH = endDt.hour() + endDt.minute() / 60

      const normalizedWindowEnd =
        windowEndHour <= windowStartHour
          ? windowEndHour + 24
          : windowEndHour
      const normalizedStart =
        startH < windowStartHour ? startH + 24 : startH
      const normalizedEnd =
        endH <= windowStartHour ? endH + 24 : endH

      return normalizedStart < normalizedWindowEnd && normalizedEnd > windowStartHour
    })

    for (const event of dayEvents) {
      const key = getEventKey(event)
      const layout = eventLayouts.get(key) ?? {
        event,
        column: 0,
        columnSpan: 1,
        totalColumns: 1,
      }

      const style = getEventStyle(event, windowStartHour, layout, tz)

      const wrapper = document.createElement('div')
      wrapper.className = 'event-wrapper'
      wrapper.style.setProperty('top', `${style.topPct}%`)
      wrapper.style.setProperty('height', `${style.heightPct}%`)
      wrapper.style.setProperty('width', `${style.widthPct}%`)
      wrapper.style.setProperty('left', `${style.leftPct}%`)
      wrapper.style.setProperty('z-index', String(style.zIndex))

      const item = document.createElement('div')
      const itemClasses = ['event-item']
      if (style.clippedTop) itemClasses.push('clipped-top')
      if (style.clippedBottom) itemClasses.push('clipped-bottom')
      item.className = itemClasses.join(' ')

      if (event.backgroundColor) {
        item.style.setProperty('background-color', event.backgroundColor)
      }

      const titleEl = document.createElement('div')
      titleEl.className = 'event-title'
      titleEl.textContent = event.title

      const timeEl = document.createElement('div')
      timeEl.className = 'event-time'
      timeEl.textContent = formatEventTime(
        event.startTime,
        event.endTime,
        locale,
        tz,
      )

      item.appendChild(titleEl)
      item.appendChild(timeEl)
      wrapper.appendChild(item)
      dayBody.appendChild(wrapper)
    }

    if (isToday && timeIndicatorPct >= 0 && timeIndicatorPct <= 100) {
      const indicator = document.createElement('div')
      indicator.className = 'current-time-indicator'
      indicator.style.setProperty('top', `${timeIndicatorPct}%`)
      dayBody.appendChild(indicator)
    }

    dayCol.appendChild(dayBody)
    daysGrid.appendChild(dayCol)
  }

  weekGrid.appendChild(daysGrid)
  container.appendChild(weekGrid)
  shadow.appendChild(container)
}

@github-actions
Copy link

github-actions bot commented Mar 2, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Include overlapping events in range

The current range filter only includes events whose start is inside the range,
which drops events that start before the window but overlap into it (common for long
meetings). Filter by range overlap (start < endDate && end > startDate) so ongoing
events are included.

edge-apps/calendar-new/src/events.ts [83-99]

 const event = new ical.Event(vevent)
 const eventStart = event.startDate.toJSDate()
-const eventTimestamp = eventStart.getTime()
+const eventEnd = event.endDate?.toJSDate?.() ?? eventStart
 
-if (eventTimestamp >= endTimestamp || eventTimestamp < startTimestamp) {
+const eventStartTs = eventStart.getTime()
+const eventEndTs = eventEnd.getTime()
+
+// Include events that overlap the requested window
+if (eventStartTs >= endTimestamp || eventEndTs <= startTimestamp) {
   return
 }
-
-const eventEnd = event.endDate.toJSDate()
 
 events.push({
   title: event.summary || 'Busy',
   startTime: eventStart.toISOString(),
   endTime: eventEnd.toISOString(),
   isAllDay: event.startDate.isDate,
 })
Suggestion importance[1-10]: 8

__

Why: The current filter only checks whether an event’s start time falls within [startDate, endDate), which incorrectly drops events that begin before the window but overlap into it. The proposed overlap check fixes a real correctness issue in fetchCalendarEventsFromICal() and the improved_code reflects the intended logic.

Medium
Avoid layout key collisions

Using getEventKey() can collide when multiple events share the same start/end/title,
causing incorrect layout lookups (overlaps rendered wrong). Store layouts keyed by
the event object reference instead (e.g., WeakMap<CalendarEvent, EventLayout>),
since the same objects are used during the render pass.

edge-apps/edge-apps-library/src/components/weekly-calendar-view/weekly-calendar-view.ts [5-355]

 import {
   type CalendarEvent,
   type EventLayout,
   findEventClusters,
   calculateClusterLayouts,
-  getEventKey,
 } from './event-layout.js'
 ...
-private _getEventLayouts(): Map<string, EventLayout> {
+private _getEventLayouts(): WeakMap<CalendarEvent, EventLayout> {
   const tz = this._timezone
   const weekStartDate = dayjs(this._getWeekStart()).tz(tz)
-  const layoutMap = new Map<string, EventLayout>()
+  const layoutMap = new WeakMap<CalendarEvent, EventLayout>()
   ...
-        layoutMap.set(getEventKey(event), layout)
+        layoutMap.set(event, layout)
   ...
   return layoutMap
 }
 ...
-    const key = getEventKey(event)
-    const layout = eventLayouts.get(key) ?? {
+    const layout = eventLayouts.get(event) ?? {
       event,
       column: 0,
       columnSpan: 1,
       totalColumns: 1,
     }
Suggestion importance[1-10]: 7

__

Why: Keying layouts by getEventKey() can collide when multiple events share the same startTime/endTime/title, causing eventLayouts.get(key) to return the wrong EventLayout. Switching to a WeakMap<CalendarEvent, EventLayout> keyed by the event object reference avoids collisions and fits the current flow where the same event objects are passed through clustering and rendering.

Medium
Reduce costly rerenders frequency

Updating now every second forces a full calendar re-render each second (the
component rebuilds the entire DOM), which can cause unnecessary CPU usage and jank.
Update on minute boundaries instead, which is sufficient for the time indicator and
dramatically reduces work.

edge-apps/calendar-new/src/main.ts [27-32]

 const tick = () => {
   calendarEl.now = new Date()
 }
 tick()
-setInterval(tick, 1000)
 
+const scheduleNextMinuteTick = () => {
+  const now = new Date()
+  const msUntilNextMinute =
+    (60 - now.getSeconds()) * 1000 - now.getMilliseconds()
+
+  setTimeout(() => {
+    tick()
+    setInterval(tick, 60_000)
+  }, msUntilNextMinute)
+}
+
+scheduleNextMinuteTick()
+
Suggestion importance[1-10]: 6

__

Why: Updating calendarEl.now every second triggers WeeklyCalendarView’s full _render() each second, which is unnecessarily expensive since the UI only needs minute-level updates for the time indicator. The suggested minute-boundary scheduling is a reasonable performance improvement, though not strictly required for correctness.

Low

nicomiguelino and others added 18 commits March 10, 2026 16:16
- Fix missing error cause in events.ts
- Extract CSS and utility functions into separate files to satisfy max-lines rules
- Add e2e screenshots

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… file

- Update `.this-week-title` to Inter Regular (weight 400), normal style, centered, with letter-spacing
- Remove unused `weekly-calendar-view.css` (CSS lives in `weekly-calendar-view-styles.ts`)
- Increase day name font size to 0.98rem with line-height 1.333
- Increase day date font size to 1.469rem with line-height 1.556
- Add opacity 0.8 to today's day name
- Increase day header height and time gutter padding-top to 5.225rem
- Increase time gutter width to 6.5rem and padding-right to 1rem
- Match time label font size to day name (0.98rem)
- Set day-body overflow to visible so the time indicator dot is not clipped
- Adjust current time indicator dot left offset to -0.27rem
- Remove horizontal margin from .header to fix app header alignment
- Force landscape orientation with letterbox bars and vertical centering
- Fix app header margins and compact event display for short events
- Fix timezone-aware day header date and locale-aware day names
- Match Figma design tokens for event card and title styles
- Replace --calendar-accent-color with --theme-color-primary
- Style app-header background and text color from theme
- Make weekly-calendar-view background transparent
- Add `daily-calendar-view` Web Component with same styling as weekly view
- Add `calendar-view-utils.ts` with shared `buildTimeGutter` and `buildEventElement`
- Extract `filterEventsForWindow` into shared utils to eliminate duplication
- Support `calendar_mode` setting to switch between weekly and daily views

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `schedule-calendar-view` Web Component showing today/tomorrow events
- Move `calendar-view-utils.ts` to components root (shared by all views)
- Fix `generateTimeSlots` to use configured timezone for time labels
- Support `schedule` as default `calendar_mode` in calendar app

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add per-mode screenshot loops (schedule, weekly, daily)
- Rename files from `WxH.png` to `{mode}-WxH.png`
- Enrich ICS mock data with more events for schedule view
- Set screenly_color_accent to #2E8B57 in mock settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move icon.svg from static/ to static/img/
- Update icon URL in manifest files to match new path
- Remove unused bg.webp from static/images/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename calendar-new to calendar, replacing the old Vue 3/Pinia app
- Add schedule, weekly, and daily calendar views as Web Components
- Remove Vue, Vite, and blueprint dependencies
- Update build tooling to use edge-apps-scripts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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

This PR migrates the Calendar edge app from a Vue 3 + Pinia implementation to framework-free Web Components, and introduces reusable calendar view components in edge-apps-library (daily/weekly/schedule) with shared event layout + rendering utilities.

Changes:

  • Add <weekly-calendar-view>, <daily-calendar-view>, and <schedule-calendar-view> Web Components (plus shared event layout + DOM builder utilities) to edge-apps-library.
  • Rewrite the Calendar app to render those components from plain HTML/CSS/TS, including new screenshot-test coverage and updated assets.
  • Remove Vue/Vite/Vitest/Pinia scaffolding from the Calendar app and update manifests/docs accordingly.

Reviewed changes

Copilot reviewed 46 out of 87 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
edge-apps/edge-apps-library/src/components/weekly-calendar-view/weekly-calendar-view.ts New weekly view Web Component rendering a 7-day grid with event layout + current-time indicator.
edge-apps/edge-apps-library/src/components/weekly-calendar-view/weekly-calendar-view-utils.ts Shared helpers for windowing, slot generation, event styling/clipping, and safe attribute setting.
edge-apps/edge-apps-library/src/components/weekly-calendar-view/weekly-calendar-view-styles.ts Shadow-DOM CSS for the weekly view.
edge-apps/edge-apps-library/src/components/weekly-calendar-view/index.ts Public exports for weekly view component + event type.
edge-apps/edge-apps-library/src/components/weekly-calendar-view/event-layout.ts Event overlap clustering + column layout algorithm for time-grid views.
edge-apps/edge-apps-library/src/components/vite-env.d.ts Add *.css?inline module typing for component CSS imports.
edge-apps/edge-apps-library/src/components/schedule-calendar-view/schedule-calendar-view.ts New schedule list view Web Component (today + tomorrow, capped).
edge-apps/edge-apps-library/src/components/schedule-calendar-view/schedule-calendar-view-styles.ts Shadow-DOM CSS for the schedule view.
edge-apps/edge-apps-library/src/components/schedule-calendar-view/index.ts Public export for schedule view component.
edge-apps/edge-apps-library/src/components/daily-calendar-view/daily-calendar-view.ts New daily view Web Component (12-hour window, event layout, current-time indicator).
edge-apps/edge-apps-library/src/components/daily-calendar-view/daily-calendar-view-styles.ts Shadow-DOM CSS for the daily view.
edge-apps/edge-apps-library/src/components/daily-calendar-view/index.ts Public export for daily view component.
edge-apps/edge-apps-library/src/components/calendar-view-utils.ts Shared DOM builders for time gutter + event cards used by daily/weekly views.
edge-apps/edge-apps-library/src/components/register.ts Register new calendar custom elements alongside existing components.
edge-apps/edge-apps-library/src/components/index.ts Export new calendar view components (and CalendarEvent type) from the library entrypoint.
edge-apps/edge-apps-library/package.json Add dayjs dependency needed by the new components/utilities.
edge-apps/edge-apps-library/bun.lock Lockfile update for added dependency.
edge-apps/calendar/src/types.ts New local types (CalendarEvent, ViewMode, VIEW_MODE) for the rewritten app.
edge-apps/calendar/src/main.ts Rewrite app bootstrap to use Web Components, theme/error setup, and periodic ticking/refresh.
edge-apps/calendar/src/events.ts Update iCal fetching/parsing to be framework-free and accept explicit timezone input.
edge-apps/calendar/src/css/style.css New global app styles (layout + view switching via .active).
edge-apps/calendar/index.html Replace Vue mount point with static layout using auto-scaler, app-header, and calendar view elements.
edge-apps/calendar/package.json Switch scripts/tooling to edge-apps-scripts workflow; remove Vue/Pinia/Vitest plumbing.
edge-apps/calendar/README.md Update deployment/dev/testing/screenshot instructions for the new architecture.
edge-apps/calendar/e2e/screenshots.spec.ts Add Playwright-based screenshot generation test across modes and resolutions.
edge-apps/calendar/screenly.yml Update manifest icon URL path.
edge-apps/calendar/screenly_qc.yml Update QC manifest icon URL path.
edge-apps/calendar/.ignore Add ignore file for deployment packaging (node_modules).
edge-apps/calendar/.gitignore Add gitignore for build artifacts/logs/etc.
edge-apps/calendar/vitest.config.ts Remove Vitest config (no longer using Vitest).
edge-apps/calendar/vite.config.ts Remove Vite config (now handled by edge-apps-scripts).
edge-apps/calendar/tsconfig.vitest.json Remove Vitest TS config.
edge-apps/calendar/tsconfig.node.json Remove node/tooling TS config.
edge-apps/calendar/tsconfig.json Remove TS project references for the prior Vue setup.
edge-apps/calendar/tsconfig.app.json Remove Vue app TS config.
edge-apps/calendar/src/utils.ts Remove re-export utilities tied to the old blueprint/Vue setup.
edge-apps/calendar/src/test-setup.ts Remove prior Vitest setup.
edge-apps/calendar/src/stores/settings.ts Remove Pinia settings store.
edge-apps/calendar/src/stores/calendar.ts Remove Pinia calendar store.
edge-apps/calendar/src/constants.ts Remove old constants re-export.
edge-apps/calendar/src/components/tests/App.spec.ts Remove Vue unit test.
edge-apps/calendar/src/assets/main.scss Remove old SCSS styling tied to Vue blueprint layout.
edge-apps/calendar/src/App.vue Remove Vue root component.
edge-apps/calendar/playwright.config.ts Remove old Playwright config (now using shared screenshot helpers).
edge-apps/calendar/eslint.config.ts Remove Vue/Vitest/Playwright-specific ESLint config (now via edge-apps-scripts).
edge-apps/calendar/e2e/vue.spec.ts Remove old Vue-focused Playwright test.
edge-apps/calendar/e2e/tsconfig.json Remove old e2e TS config.
edge-apps/calendar/.vscode/extensions.json Remove Vue/Vitest editor recommendations.
edge-apps/calendar/screenshots/weekly-800x480.webp Add updated weekly-mode screenshot artifact.
edge-apps/calendar/screenshots/weekly-720x1280.webp Add updated weekly-mode screenshot artifact.
edge-apps/calendar/screenshots/weekly-480x800.webp Add updated weekly-mode screenshot artifact.
edge-apps/calendar/screenshots/schedule-800x480.webp Add updated schedule-mode screenshot artifact.
edge-apps/calendar/screenshots/schedule-720x1280.webp Add updated schedule-mode screenshot artifact.
edge-apps/calendar/screenshots/schedule-480x800.webp Add updated schedule-mode screenshot artifact.
edge-apps/calendar/screenshots/daily-800x480.webp Add updated daily-mode screenshot artifact.
edge-apps/calendar/screenshots/daily-720x1280.webp Add updated daily-mode screenshot artifact.
edge-apps/calendar/screenshots/daily-480x800.webp Add updated daily-mode screenshot artifact.
edge-apps/calendar/screenshots/daily-1280x720.webp Add updated daily-mode screenshot artifact.
edge-apps/calendar/screenshots/daily-1080x1920.webp Add updated daily-mode screenshot artifact.
edge-apps/calendar/static/images/enable-google-calendar-api.png Include static documentation image asset for setup instructions.
edge-apps/calendar/static/images/authorization-code.png Include static documentation image asset for setup instructions.
edge-apps/calendar/src/assets/font/Aeonik-Regular.woff2 Include font asset (legacy/compat).
edge-apps/calendar/src/assets/font/Aeonik-Regular.woff Include font asset (legacy/compat).
edge-apps/calendar/public/fonts/Aeonik-Regular.woff2 Include font asset served from public/.
edge-apps/calendar/public/fonts/Aeonik-Regular.woff Include font asset served from public/.
edge-apps/calendar/public/favicon.ico Include app favicon asset.

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

Comment on lines +57 to +62
const tick = () => {
activeEl.now = new Date()
}
tick()
setInterval(tick, 1000)

Comment on lines +10 to +16
import './weekly-calendar-view/weekly-calendar-view.js'
import './daily-calendar-view/daily-calendar-view.js'
import './schedule-calendar-view/schedule-calendar-view.js'

import { WeeklyCalendarView } from './weekly-calendar-view/weekly-calendar-view.js'
import { DailyCalendarView } from './daily-calendar-view/daily-calendar-view.js'
import { ScheduleCalendarView } from './schedule-calendar-view/schedule-calendar-view.js'
Comment on lines +198 to +217
// 'en-US' is intentional — we need locale-independent numeric output
// for parseInt() to work correctly regardless of the configured locale.
const currentHour = parseInt(
now.toLocaleString('en-US', {
hour: 'numeric',
hour12: false,
timeZone: tz,
}),
10,
)
const windowStartHour = getWindowStartHour(currentHour)
const timeSlots = generateTimeSlots(windowStartHour, now, locale, tz)
const weekStart = this._getWeekStart()
const eventLayouts = this._getEventLayouts()
const todayStr = dayjs(now).tz(tz).format('YYYY-MM-DD')

const currentMinute = parseInt(
now.toLocaleString('en-US', { minute: 'numeric', timeZone: tz }),
10,
)
Comment on lines +62 to +69
get now(): Date {
return this._now
}

set now(value: Date) {
this._now = value
if (this._initialized) this._render()
}
Comment on lines +173 to +186
export function setAttribute(
el: HTMLElement,
name: string,
value: string,
): void {
const allowed = new Set([
'class',
'style',
'data-day-index',
'data-hour',
'title',
'aria-label',
])
if (allowed.has(name) || name.startsWith('data-')) {
Comment on lines +138 to +162
// 'en-US' is intentional — we need locale-independent numeric output
// for parseInt() to work correctly regardless of the configured locale.
const currentHour = parseInt(
now.toLocaleString('en-US', {
hour: 'numeric',
hour12: false,
timeZone: tz,
}),
10,
)
const windowStartHour = getWindowStartHour(currentHour)
const timeSlots = generateTimeSlots(windowStartHour, now, locale, tz)
const todayStr = dayjs(now).tz(tz).format('YYYY-MM-DD')
const todayEvents = filterEventsForWindow(
this._events,
todayStr,
windowStartHour,
tz,
)
const eventLayouts = this._getEventLayouts(todayEvents)

const currentMinute = parseInt(
now.toLocaleString('en-US', { minute: 'numeric', timeZone: tz }),
10,
)
Comment on lines +42 to 48
const { timezone } = settings
const screenlySettings = screenly.settings
const { ical_url: icalUrl } = screenlySettings
const corsProxy = screenly.cors_proxy_url
const bypassCors = Boolean(
JSON.parse(screenly.settings.bypass_cors as string),
JSON.parse(screenlySettings.bypass_cors as string),
)
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.

2 participants