Skip to content

feat(clock): implement clock app redesign with web components#663

Open
nicomiguelino wants to merge 9 commits intomasterfrom
redesign-clock-app
Open

feat(clock): implement clock app redesign with web components#663
nicomiguelino wants to merge 9 commits intomasterfrom
redesign-clock-app

Conversation

@nicomiguelino
Copy link
Contributor

@nicomiguelino nicomiguelino commented Feb 4, 2026

User description

Summary

Implements the clock app redesign by creating a new clock-new app and integrating web components from #616 into the edge-apps-library.

Changes

edge-apps-library:

  • Add web components: auto-scaler, app-header, brand-logo, dev-tools
  • Add Screenly Design System styles (base styles and design tokens)
  • Add core utilities and component registration
  • Add weather icon mapping and screen orientation utilities
  • Update package.json exports to include components, core, and styles paths

clock-new:

  • Implement clock app with <auto-scaler> and <app-header> web components
  • Use library utilities for timezone, locale, time formatting, and weather
  • Add weather integration with OpenWeatherMap API
  • Include Kelly Slab font, background image, and weather icons
  • Import Screenly Design System styles
  • Implement responsive design for portrait and landscape

PR Type

Enhancement, Tests, Documentation


Description

  • Add Edge Apps web components suite

  • Introduce initEdgeApp auto-scaling helper

  • Add screen/weather utilities and tests

  • Implement redesigned clock-new sample app


Diagram Walkthrough

flowchart LR
  lib["@screenly/edge-apps library"]
  comps["Web components (`auto-scaler`, `app-header`, etc.)"]
  core["Core helper: `initEdgeApp`"]
  utils["Utilities (locale, screen, weather)"]
  clock["`edge-apps/clock-new` app"]
  owm["OpenWeatherMap (optional)"]

  lib -- "exports" --> comps
  lib -- "exports" --> core
  lib -- "exports" --> utils
  clock -- "imports" --> lib
  clock -- "fetches weather" --> owm
Loading

File Walkthrough

Relevant files
Enhancement
14 files
main.ts
Implement clock UI updates and weather                                     
+173/-0 
style.css
Add redesigned clock styles and layout                                     
+163/-0 
index.html
Use declarative `` and header                             
+43/-0   
auto-scaler.ts
Add scalable container web component                                         
+349/-0 
app-header.ts
Add header component with time/date                                           
+280/-0 
brand-logo.ts
Add branding logo component with fallback                               
+221/-0 
dev-tools.ts
Add dev overlay reacting to scale                                               
+223/-0 
index.ts
Export components and side-effect registration                     
+11/-0   
register.ts
Centralize component registration side-effects                     
+9/-0     
index.ts
Add `initEdgeApp` to compose components                                   
+112/-0 
weather.ts
Add weather icon mapping helpers                                                 
+100/-0 
screen.ts
Add orientation detection utilities                                           
+34/-0   
locale.ts
Lazy-load geocoding to reduce bundling                                     
+4/-1     
index.ts
Re-export utils, core, components, types                                 
+10/-1   
Configuration changes
4 files
package.json
Add build/dev scripts and workspace dependency                     
+35/-0   
screenly.yml
Add manifest and configurable app settings                             
+45/-0   
package.json
Export `components`, `core`, and `styles`                               
+10/-0   
vite.config.ts
Switch library build output to IIFE                                           
+1/-0     
Documentation
1 files
README.md
Document app usage, settings, development                               
+43/-0   
Tests
1 files
screen.test.ts
Add unit tests for screen utilities                                           
+70/-0   
Additional files
16 files
.ignore +1/-0     
screenly_qc.yml +45/-0   
eslint.config.ts +7/-0     
README.md +178/-0 
vite-env.d.ts +11/-0   
fonts.css +31/-0   
index.css +7/-0     
reset.css +37/-0   
index.css +10/-0   
colors.css +4/-0     
index.css +9/-0     
shadows.css +8/-0     
spacing.css +6/-0     
typography.css +9/-0     
index.ts +50/-0   
index.ts +2/-0     

nicomiguelino and others added 2 commits February 3, 2026 19:56
Create foundational structure for the clock app redesign with the same settings as the existing clock app. The new app uses the edge-apps-library and follows the QR Code app pattern for simplicity.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ponents

Apply necessary changes to enable the clock app redesign based on #616:

edge-apps-library changes:
- Add web components: auto-scaler, app-header, brand-logo, dev-tools
- Add Screenly Design System styles (base, tokens)
- Add core utilities and component registration
- Add weather icon mapping utilities
- Add screen orientation detection utilities
- Update package.json exports to include components, core, and styles
- Update vite.config.ts output format to IIFE

clock-new changes:
- Implement clock app with <auto-scaler> and <app-header> web components
- Use library utilities (getMetadata, getTimeZone, getLocale, formatTime, etc.)
- Add weather integration with OpenWeatherMap API
- Include Kelly Slab font and background image
- Add all weather condition icons
- Import Screenly Design System styles
- Implement responsive design for portrait and landscape

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@github-actions
Copy link

github-actions bot commented Feb 4, 2026

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

616 - Partially compliant

Compliant requirements:

  • Add core Edge Apps web components (e.g., auto-scaler, app-header, brand-logo, dev-tools).
  • Provide sample app (clock-new).
  • Include screen + weather utilities.
  • Update/extend library exports to include components/core/styles paths.
  • Add Screenly Design System base styles and design tokens.

Non-compliant requirements:

  • Integrate CLI generator and app scaffolding scripts.
  • Provide sample app (weather-new) with Vite config.
  • Add unit tests for weather utilities.
  • Redesign the weather app.

Requires further human verification:

  • Redesign the clock app (visual parity with design, layout, typography, spacing).
  • Responsive design behavior in portrait/landscape on real devices and various resolutions.
  • Auto-scaling correctness when running inside Screenly runtime (including safe-area/overscan expectations).
  • OpenWeatherMap integration behavior with/without API key, rate limits, and offline/network failure modes.
⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

Weather parsing and fetch handling are brittle: data.main?.temp is checked via truthiness so 0°C will be ignored; also fetch() responses are not checked for response.ok before JSON parsing/usage, and error payloads could bypass the cod check. Consider validating HTTP status, guarding JSON shape, and using a numeric check for temperature.

// Get weather data (optional - requires OpenWeatherMap API key)
async function getWeatherData(
  lat: number,
  lng: number,
  tz: string,
): Promise<void> {
  try {
    const apiKey = getSetting<string>('openweathermap_api_key')
    if (!apiKey) {
      return
    }

    const response = await fetch(
      `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lng}&units=metric&appid=${apiKey}`,
    )
    const data = await response.json()

    // cod can be number (200) or string ("200") depending on endpoint
    if ((data.cod === 200 || data.cod === '200') && data.main?.temp) {
      currentTemp = Math.round(data.main.temp)
      currentWeatherId = data.weather?.[0]?.id ?? null

      if (temperatureEl) {
        temperatureEl.textContent = `${currentTemp}°`
      }

      // Update weather icon
      if (weatherIconEl && currentWeatherId) {
        const iconKey = getWeatherIconKey(
          currentWeatherId,
          Math.floor(Date.now() / 1000),
          tz,
        )
        weatherIconEl.src = getWeatherIconUrl(iconKey, '/static/images/icons')
        weatherIconEl.alt = data.weather?.[0]?.description || 'Weather icon'
      }
    }
  } catch (error) {
    console.warn('Failed to get weather data:', error)
  }
}
State Sync

Attribute changes update host CSS variables and re-render, but interval lifecycle isn’t re-evaluated. If show-time/show-date are toggled after connection, the timer may keep running unnecessarily, or never start (if initially off). Consider starting/stopping the interval in attributeChangedCallback based on the latest settings and triggering an immediate updateTime() when enabling.

  // Update time every second
  if (this.showTime || this.showDate) {
    this.updateInterval = window.setInterval(() => {
      this.updateTime()
    }, 1000)
  }
}

disconnectedCallback() {
  if (this.updateInterval) {
    clearInterval(this.updateInterval)
  }
}

attributeChangedCallback() {
  this.loadAttributes()
  this.render()
}
Logic Bug

center-content default is inconsistent: _centerContent is initialized true but loadAttributes() overwrites it with hasAttribute('center-content'), making the effective default false. Also, if reference-width/reference-height attributes are removed/invalid, prior values may remain (not reset), potentially causing stale scaling behavior. Consider aligning defaults with docs/intent and resetting internal fields when attributes are missing/invalid.

private loadAttributes() {
  const refWidthAttr = this.getAttribute('reference-width')
  const refHeightAttr = this.getAttribute('reference-height')

  if (refWidthAttr && refHeightAttr) {
    const referenceWidth = Number(refWidthAttr)
    const referenceHeight = Number(refHeightAttr)

    if (Number.isFinite(referenceWidth) && Number.isFinite(referenceHeight)) {
      this._referenceWidth = referenceWidth
      this._referenceHeight = referenceHeight
    }
  }

  const orientationAttr = this.getAttribute('orientation')
  if (
    orientationAttr === 'landscape' ||
    orientationAttr === 'portrait' ||
    orientationAttr === 'auto'
  ) {
    this._orientation = orientationAttr
  } else {
    this._orientation = 'auto'
  }

  this._centerContent = this.hasAttribute('center-content')

  const paddingAttr = this.getAttribute('padding')
  if (paddingAttr && Number.isFinite(Number(paddingAttr))) {
    this._padding = String(Number(paddingAttr))
  } else {
    this._padding = '20'
  }

  const debounceMsAttr = this.getAttribute('debounce-ms')
  if (debounceMsAttr && Number.isFinite(Number(debounceMsAttr))) {
    this._debounceMs = Number(debounceMsAttr)
  } else {
    this._debounceMs = 100
  }
}

@github-actions
Copy link

github-actions bot commented Feb 4, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Avoid locale-dependent time parsing

Avoid parsing timeData.formatted because it can vary by locale (different
separators/order) and can break time rendering. Use the structured fields returned
by formatTime() (hour/minute) to build the display consistently.

edge-apps/clock-new/src/main.ts [111-116]

 if (timeEl) {
-  // Format as "HH:MM" or "H:MM" depending on locale
-  const timeStr = timeData.formatted.split(' ')[0] // Remove AM/PM if present
-  const [hour, minute] = timeStr.split(':')
-  timeEl.textContent = `${hour}:${minute}`
+  // Use structured parts to avoid locale-dependent parsing
+  timeEl.textContent = `${timeData.hour}:${timeData.minute}`
 }
Suggestion importance[1-10]: 7

__

Why: Parsing timeData.formatted via split(' ')/split(':') is locale-fragile and can break in locales with different ordering/separators; using formatTime()'s structured parts (timeData.hour/timeData.minute) is more robust and consistent.

Medium
Manage timer on attribute changes

Ensure the update interval starts/stops when show-time/show-date changes after
initial render; currently toggling these attributes can leave the clock frozen or
keep an unnecessary interval running. Update the callback to call updateTime() and
manage this.updateInterval based on the latest attribute state.

edge-apps/edge-apps-library/src/components/app-header/app-header.ts [110-146]

 async connectedCallback() {
   this.upgradeProperty('showLogo')
   this.upgradeProperty('showTime')
   this.upgradeProperty('showDate')
   this.upgradeProperty('timeFormat')
 
   this.loadAttributes()
 
   // Initialize timezone and locale
   try {
     this.timezone = await getTimeZone()
     this.locale = await getLocale()
   } catch (error) {
     console.warn('Failed to get timezone/locale:', error)
   }
 
   this.render()
   this.updateTime()
 
-  // Update time every second
-  if (this.showTime || this.showDate) {
-    this.updateInterval = window.setInterval(() => {
-      this.updateTime()
-    }, 1000)
+  // Update time every second (only when needed)
+  if ((this._showTime || this._showDate) && this.updateInterval === undefined) {
+    this.updateInterval = window.setInterval(() => this.updateTime(), 1000)
   }
 }
 
 attributeChangedCallback() {
   this.loadAttributes()
   this.render()
+  this.updateTime()
+
+  const shouldRun = this._showTime || this._showDate
+  if (shouldRun && this.updateInterval === undefined) {
+    this.updateInterval = window.setInterval(() => this.updateTime(), 1000)
+  } else if (!shouldRun && this.updateInterval !== undefined) {
+    clearInterval(this.updateInterval)
+    this.updateInterval = undefined
+  }
 }
Suggestion importance[1-10]: 7

__

Why: As written, toggling show-time/show-date after connection can leave updateInterval running unnecessarily or never started; managing this.updateInterval in attributeChangedCallback() and calling this.updateTime() keeps the component behavior correct and efficient.

Medium

nicomiguelino and others added 5 commits February 5, 2026 08:53
…CSS to separate file

- Remove --sourcemap flag from edge-apps-scripts build command (keep it in build:dev)
- Add cssCodeSplit: false to vite.config.ts to extract CSS into dist/css/style.css
- This fixes deployment issues where index.html references dist/css/style.css but it wasn't being generated
- CSS was being inlined into JS, causing asset loading issues on Anywhere screens

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Change description from "Screenly Clock New Edge App" to "Screenly Clock Edge App"
- Update icon path from clock-new to clock to match the deployed icon location

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add weather icon imports and temperature display handling. Hide weather section when API key is unavailable or API calls fail.
Add production and QC app IDs matching the clock app structure.
Remove old Vue-based clock app and replace with redesigned implementation using edge-apps-library web components. The new implementation uses edge-apps-scripts build system and includes weather integration with locale-aware temperature units.
@nicomiguelino
Copy link
Contributor Author

nicomiguelino commented Feb 5, 2026

XSS Security Review for PR #663

Summary

No XSS vulnerabilities found. The code follows secure patterns throughout.


Safe Patterns Used

1. textContent for dynamic text (XSS-safe)

File Line Usage
edge-apps/clock/src/main.ts 115 temperatureEl.textContent = \${currentTemp}°``
edge-apps/clock/src/main.ts 145 timeEl.textContent = \${hour}:${minute}``
edge-apps/clock/src/main.ts 149 periodEl.textContent = timeData.dayPeriod
edge-apps/clock/src/main.ts 156 dateEl.textContent = formatLocalizedDate(...)
edge-apps/clock/src/main.ts 178 locationEl.textContent = locationName
edge-apps/edge-apps-library/src/components/app-header/app-header.ts 196, 208 Time/date display
edge-apps/edge-apps-library/src/components/brand-logo/brand-logo.ts 152 nameEl.textContent = screenName

2. Build-time SVG imports

Weather icons in edge-apps/clock/src/weather-icons.ts are imported at build time, not fetched from untrusted sources.

3. Shadow DOM isolation

Web components (auto-scaler, app-header, brand-logo) use Shadow DOM, limiting scope of any potential issues.


Areas Reviewed

img.src assignments

File Line Source Risk
edge-apps/clock/src/main.ts 128 WEATHER_ICONS[iconKey] (build-time import) None
edge-apps/edge-apps-library/src/components/brand-logo/brand-logo.ts 130 setupBrandingLogo() from Screenly settings Low

innerHTML usage

File Line Source Risk
edge-apps/edge-apps-library/src/components/app-header/app-header.ts 222 Hardcoded template literal None
edge-apps/edge-apps-library/src/components/brand-logo/brand-logo.ts 165 Hardcoded template literal None

CSS property injection

File Lines Source Risk
edge-apps/edge-apps-library/src/utils/theme.ts 73-89 Screenly settings (color values) Very Low

Optional Hardening Recommendations

  1. Validate that screenly.settings.screenly_logo_* URLs match HTTP/HTTPS patterns before use
  2. Validate color values match hex color patterns (e.g., /^#[0-9A-Fa-f]{6}$/)

Verdict

The PR is safe from XSS attacks. All dynamic content is handled using secure APIs (textContent, build-time imports, Shadow DOM isolation).

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 implements a clock app redesign by creating a new clock-new app and introducing a comprehensive web components library for Edge Apps. The redesign moves away from Vue/Pinia architecture to vanilla TypeScript with custom web components, improving maintainability and reusability across Edge Apps.

Changes:

  • Created web components library with auto-scaling, headers, and dev tools
  • Implemented redesigned clock app with weather integration
  • Added Screenly Design System with CSS tokens and base styles

Reviewed changes

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

Show a summary per file
File Description
edge-apps/edge-apps-library/src/components/auto-scaler/auto-scaler.ts Auto-scaling web component for responsive layouts
edge-apps/edge-apps-library/src/components/app-header/app-header.ts Header component with time/date display
edge-apps/edge-apps-library/src/components/brand-logo/brand-logo.ts Logo component with fallback support
edge-apps/edge-apps-library/src/components/dev-tools/dev-tools.ts Development overlay for debugging
edge-apps/edge-apps-library/src/core/index.ts Helper function to initialize edge apps
edge-apps/edge-apps-library/src/utils/weather.ts Weather icon mapping utilities
edge-apps/edge-apps-library/src/utils/screen.ts Screen orientation detection
edge-apps/edge-apps-library/src/styles/* Design system tokens and base styles
edge-apps/clock/src/main.ts Redesigned clock app implementation
edge-apps/clock/src/css/style.css Clock app styles with responsive design
edge-apps/clock/index.html Declarative web component usage
edge-apps/clock/package.json Updated dependencies and build scripts
edge-apps/clock/screenly.yml Added OpenWeatherMap API configuration
Comments suppressed due to low confidence (5)

edge-apps/edge-apps-library/src/utils/weather.ts:1

  • The hardcoded hour values (5 and 20) for night determination should be extracted as named constants to improve code readability and maintainability. Consider defining NIGHT_END_HOUR = 5 and NIGHT_START_HOUR = 20 at the module level.
    edge-apps/edge-apps-library/src/components/auto-scaler/auto-scaler.ts:1
  • Consider applying multiple style properties in a single operation using Object.assign(this.style, {...}) or this.style.cssText for better performance and readability.
    edge-apps/edge-apps-library/src/components/dev-tools/dev-tools.ts:1
  • The localhost hostname checks could be extracted to a constant array for easier maintenance and potential extension. Consider defining LOCALHOST_HOSTNAMES = ['localhost', '127.0.0.1'] and using LOCALHOST_HOSTNAMES.includes(window.location.hostname).
    edge-apps/edge-apps-library/src/core/index.ts:1
  • The safeZone parameter is marked as deprecated but there's no @deprecated JSDoc tag. Add proper JSDoc deprecation notice to help developers understand this parameter should not be used.
    edge-apps/edge-apps-library/src/components/app-header/app-header.ts:1
  • The expression (this._timeFormat !== '24h' && undefined) is confusing. If the intent is to pass undefined when format is not '24h', this could be simplified to this._timeFormat === '12h' ? true : this._timeFormat === '24h' ? false : undefined for better clarity.

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

@nicomiguelino nicomiguelino changed the title feat(clock-new): implement clock app redesign with web components feat(clock): implement clock app redesign with web components Feb 5, 2026
nicomiguelino and others added 2 commits February 5, 2026 14:11
Extract inline validation logic to isValidWeatherResponse() with proper typing and documentation explaining why both number and string types are checked for the cod field.
…mponent

Add z-index design tokens to centralize layering values across Edge Apps.
Enhance brand-logo component to display brand name alongside logo with
proper vertical alignment and spacing.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@nicomiguelino nicomiguelino changed the base branch from master to overhaul-edge-apps-library February 6, 2026 00:21
@nicomiguelino nicomiguelino force-pushed the overhaul-edge-apps-library branch from e248be9 to 9c99c17 Compare February 6, 2026 00:27
@nicomiguelino nicomiguelino changed the base branch from overhaul-edge-apps-library to master February 6, 2026 00:29
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