Skip to content

Conversation

@iamplugged
Copy link
Contributor

@iamplugged iamplugged commented Jan 20, 2026

User description

@screenly/edge-apps is a TypeScript library and tooling set for building Screenly Edge Apps. It provides:

  • Runtime helpers for working with Edge Apps metadata, settings, themes, locales, time formatting, and screen information.
  • UI components (header, brand logo, dev tools, auto-scaler) to quickly scaffold consistent, production-ready apps.
  • A template app and CLI scripts to generate new Edge Apps with a batteries‑included setup.

Use this package when you want to build modern, Vite-based Edge Apps that follow Screenly’s best practices out of the box.

How is it structured?

At a high level:

  • src/: Source for the library itself.
    • src/components/: Reusable UI components (app-header, brand-logo, dev-tools, auto-scaler, and registry utilities).
    • src/core/: Core entry points for Edge Apps runtime helpers. This is there for backward compatibility.
    • src/styles/: Base styles and design tokens (fonts, colors, spacing, typography, etc.).
    • src/utils/: Utility functions for locale, time/date formatting, metadata, screen data, settings, theme, UTM, and weather.
    • src/types/: Shared TypeScript types.
    • src/test/: Test helpers and JSDOM setup for unit tests.
  • template/: A minimal Edge App template (Vite + TypeScript + Tailwind CSS) used as the starting point when creating new apps.
  • scripts/: CLI helpers (cli.ts, create.ts) used to generate apps from the template.
  • bin/edge-apps-scripts.ts: The published CLI entry point (wired via the edge-apps-scripts binary in package.json).

Associated PRs

Most of these pull requests are stacked PRs (i.e., PRs that are branched off this PR).


PR Type

Enhancement, Tests


Description

  • Add core web components (auto-scaler, header, dev-tools, logo)

  • Integrate CLI generator and app scaffolding scripts

  • Provide sample apps (clock-new, weather-new) with Vite configs

  • Include screen & weather utilities with unit tests


File Walkthrough

Relevant files
Enhancement
13 files
auto-scaler.ts
Implement `` web component for scaling           
+346/-0 
app-header.ts
Create `` component with time/date display     
+266/-0 
dev-tools.ts
Add `` overlay for development               
+213/-0 
brand-logo.ts
Introduce `` component with fallback                 
+215/-0 
index.ts
Export and register all web components                                     
+12/-0   
index.ts
Add `initEdgeApp` helper for declarative setup                     
+108/-0 
index.ts
Define orientation and auto-scaler option types                   
+50/-0   
screen.ts
Add screen orientation detection utilities                             
+35/-0   
weather.ts
Provide weather icon mapping utilities                                     
+97/-0   
cli.ts
Extend CLI with `create` command                                                 
+15/-0   
main.ts
Add Weather App main logic and rendering                                 
+303/-0 
main.ts
Add Clock App main logic and UI updates                                   
+161/-0 
index.ts
Consolidate exports for utils, core, components, types     
+10/-1   
Configuration changes
4 files
create.ts
Implement app generator CLI script                                             
+198/-0 
vite.config.ts
Configure Vite for library with custom plugins                     
+211/-0 
vite.config.ts
Setup Vite config for weather-new app                                       
+211/-0 
vite.config.ts
Setup Vite config for clock-new app                                           
+207/-0 
Tests
1 files
screen.test.ts
Add unit tests for screen utilities                                           
+139/-0 
Additional files
55 files
package.json +4/-4     
.prettierrc.json +6/-0     
README.md +62/-0   
eslint.config.ts +15/-0   
index.html +34/-0   
package.json +32/-0   
postcss.config.js +7/-0     
screenly.yml +16/-0   
styles.css +156/-0 
vite-env.d.ts +12/-0   
tailwind.config.js +12/-0   
tsconfig.json +34/-0   
README.md +387/-78
tailwind.config.base.js +36/-0   
index.html [link]   
package.json +20/-13 
README.md +166/-0 
register.ts +10/-0   
vite-env.d.ts +12/-0   
fonts.css +31/-0   
index.css +9/-0     
reset.css +39/-0   
index.css +11/-0   
colors.css +4/-0     
index.css +11/-0   
shadows.css +8/-0     
spacing.css +6/-0     
typography.css +9/-0     
index.ts +2/-0     
locale.ts +4/-1     
.prettierrc.json +6/-0     
README.md +62/-0   
eslint.config.ts +15/-0   
index.html +81/-0   
package.json +32/-0   
postcss.config.js +7/-0     
screenly.yml +14/-0   
screenly_qc.yml +17/-0   
main.ts +63/-0   
styles.css +20/-0   
vite-env.d.ts +12/-0   
tailwind.config.js +36/-0   
tsconfig.json +34/-0   
.prettierrc.json +6/-0     
README.md +62/-0   
eslint.config.ts +15/-0   
index.html +49/-0   
package.json +32/-0   
postcss.config.js +7/-0     
screenly.yml +50/-0   
styles.css +232/-0 
vite-env.d.ts +16/-0   
tailwind.config.js +36/-0   
tsconfig.json +34/-0   
package.json +9/-0     

@github-actions
Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

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

ResizeObserver scope

The component uses a ResizeObserver on document.body (fallback to window.resize) which may not catch all layout changes or could lead to unnecessary observations; consider observing the host element or always relying on window resize for consistency and tearing down observers properly.

private setupResizeHandling(): void {
  this.teardownResizeHandling()

  // Use ResizeObserver if available, fallback to window resize
  if (typeof ResizeObserver !== 'undefined') {
    this.resizeObserver = new ResizeObserver(() => {
      this.debouncedResize()
    })
    this.resizeObserver.observe(document.body)
  } else {
Path replacements

The template copy logic uses replaceAll on placeholder keys and constructs relative paths which could break on edge cases (e.g., Windows backslashes or nested templates); verify replacement correctness and cross-platform path handling.

  let content = fs.readFileSync(srcPath, 'utf8')

  // Replace placeholders
  for (const [key, value] of Object.entries(replacements)) {
    content = content.replaceAll(key, value)
  }

  fs.writeFileSync(destPath, content)
}
Sleet mapping

The getWeatherIconKey logic groups all IDs 600–699 under 'snow', making the 'sleet' night override unreachable; ensure sleet conditions (e.g., ID 611/612) map correctly or adjust the range check.

if (id >= 200 && id <= 299) {
  icon = 'thunderstorm'
} else if (id >= 300 && id <= 399) {
  icon = 'drizzle'
} else if (id >= 500 && id <= 599) {
  icon = 'rain'
} else if (id >= 600 && id <= 699) {
  icon = 'snow'
} else if (id >= 700 && id <= 799) {
  icon = 'haze'
} else if (id === 800) {
  icon = 'clear'
} else if (id === 801) {
  icon = 'partially-cloudy'
} else if (id >= 802 && id <= 804) {
  icon = 'mostly-cloudy'
}

@github-actions
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Point to compiled distribution

The package.json points main/module/types to TypeScript source files which won’t be
consumable by Node or bundlers. Point these fields to the compiled JavaScript output
(in dist) and corresponding declaration files to ensure correct resolution.

edge-apps/edge-apps-library/package.json [6-8]

-"main": "./src/index.ts",
-"module": "./src/index.ts",
-"types": "./src/index.ts",
+"main": "./dist/index.js",
+"module": "./dist/index.js",
+"types": "./dist/index.d.ts",
Suggestion importance[1-10]: 9

__

Why: The package.json should reference compiled JavaScript and declaration files (./dist/index.js, ./dist/index.d.ts) so Node and bundlers can properly consume the package instead of raw TypeScript.

High
General
Use Tailwind directives

Replace Tailwind CSS @import rules with the official @tailwind directives to ensure
proper layer processing by Tailwind’s PostCSS plugin.

edge-apps/edge-apps-library/template/src/styles.css [6-8]

-@import 'tailwindcss/base';
-@import 'tailwindcss/components';
-@import 'tailwindcss/utilities';
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
Suggestion importance[1-10]: 7

__

Why: Replacing @import 'tailwindcss/*' with @tailwind directives ensures Tailwind’s PostCSS plugin processes the base, components, and utilities layers correctly rather than importing raw CSS files.

Medium
Show correct temperature unit

The temperature unit label is hard-coded to a degree symbol only. Use the computed
scale (C or F) to display the correct unit next to the degree symbol.

edge-apps/weather-new/src/main.ts [254-256]

 const tempScale = getTempScale(locationData.country)
 setText(cityEl, cityName)
-setText(tempUnitEl, '°')
+setText(tempUnitEl, `°${tempScale}`)
Suggestion importance[1-10]: 6

__

Why: The code always displays only °, omitting the actual unit. Appending tempScale ensures the user sees °C or °F, improving clarity.

Low
Observe documentElement for resize

Observing document.body may not catch viewport‐size changes reliably. Instead,
observe document.documentElement or fallback to window resize events alongside the
observer for robust resizing detection.

edge-apps/edge-apps-library/src/components/auto-scaler/auto-scaler.ts [264-271]

 if (typeof ResizeObserver !== 'undefined') {
   this.resizeObserver = new ResizeObserver(() => {
     this.debouncedResize()
   })
-  this.resizeObserver.observe(document.body)
+  // Observe the root element for viewport changes
+  this.resizeObserver.observe(document.documentElement)
+  // Also listen to window resize as a fallback
+  window.addEventListener('resize', this.boundDebouncedResize)
 } else {
   window.addEventListener('resize', this.boundDebouncedResize)
 }
Suggestion importance[1-10]: 5

__

Why: Observing document.body may miss viewport size changes in some cases. Switching to document.documentElement and also listening to window.resize makes resize detection more robust.

Low
Simplify input validation loop

The prompt loop logic and indentation are confusing and may skip feedback. Refactor
to a do...while loop to ensure the user is prompted until a non-empty name is
entered.

edge-apps/edge-apps-library/scripts/create.ts [82-87]

 let appName = ''
-while (!appName || appName.trim() === '') {
+do {
   appName = await question(rl, 'App name (e.g., my-dashboard): ')
-if (!appName || appName.trim() === '') {
+  if (!appName.trim()) {
     console.log('❌ App name is required. Please try again.\n')
   }
-}
+} while (!appName.trim())
Suggestion importance[1-10]: 4

__

Why: Refactoring the while loop into a do…while makes the prompt flow clearer and avoids the awkward indentation, improving maintainability.

Low

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 introduces a comprehensive Edge Apps library with web components, utilities, CLI tooling, and sample applications to streamline Screenly Edge App development.

Changes:

  • Added reusable web components (auto-scaler, app-header, dev-tools, brand-logo) with encapsulated TypeScript, HTML, and CSS
  • Integrated CLI generator for scaffolding new Edge Apps from a template
  • Provided sample applications (clock-new, weather-new) demonstrating library usage with Vite configurations

Reviewed changes

Copilot reviewed 71 out of 138 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
edge-apps/edge-apps-library/src/components/* Web component implementations for UI elements
edge-apps/edge-apps-library/src/utils/* Utility functions for screen, weather, locale, and time
edge-apps/edge-apps-library/scripts/* CLI tools for app generation
edge-apps/edge-apps-library/template/* Template files for new Edge Apps
edge-apps/weather-new/* Sample weather app implementation
edge-apps/clock-new/* Sample clock app implementation
edge-apps/edge-apps-library/src/styles/* Design system styles and tokens
edge-apps/edge-apps-library/configs/* Shared Tailwind configuration

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

Comment on lines +215 to +219
// Run the CLI
run().catch((error) => {
console.error('Error:', error.message)
process.exit(1)
})
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

The CLI execution code (lines 214-219) appears at the end of the file but there's already a main execution flow expected through the module. This could cause the CLI to run twice if the file is imported elsewhere or result in unintended execution. This code should likely be wrapped in a check like if (import.meta.url === \file://${process.argv[1]}`) { ... }` to only run when executed directly.

Suggested change
// Run the CLI
run().catch((error) => {
console.error('Error:', error.message)
process.exit(1)
})
// Run the CLI only when executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
run().catch((error) => {
console.error('Error:', error.message)
process.exit(1)
})
}

Copilot uses AI. Check for mistakes.
const screenlyGlobal = (window as any).screenly
const sentryDsn = screenlyGlobal?.settings?.sentry_dsn as string | undefined
if (sentryDsn) {
Sentry.init({ dsn: sentryDsn })
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

The code references Sentry but there's no import statement for the Sentry SDK. This will cause a ReferenceError at runtime when Sentry DSN is configured.

Copilot uses AI. Check for mistakes.
Comment on lines +120 to +122
// Lazy-load offline-geocode-city so apps that never call getLocale
// don't need to bundle it (and won't hit its lz-string dependency).
const { getNearestCity } = await import('offline-geocode-city')
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

The comment mentions avoiding the lz-string dependency, but this optimization only works if the module is tree-shakeable and the bundler is configured correctly. Consider documenting this requirement in the function's JSDoc or README to ensure developers configure their build tools appropriately.

Copilot uses AI. Check for mistakes.
@renatgalimov renatgalimov marked this pull request as ready for review February 2, 2026 15:17
"scripts": {
"generate-mock-data": "screenly edge-app run --generate-mock-data",
"predev": "bun run generate-mock-data",
"dev": "vite",
Copy link
Contributor

Choose a reason for hiding this comment

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

Please make use of the Screenly Edge App server (i.e., screenly edge-app run) instead of vite. Please refer to the package.json of existing new Edge Apps like the qr-code, menu-board, grafana, and cap-alerting apps.

"scripts": {
"generate-mock-data": "screenly edge-app run --generate-mock-data",
"predev": "bun run generate-mock-data",
"dev": "vite",
Copy link
Contributor

Choose a reason for hiding this comment

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

Please make use of the Screenly Edge App server (i.e., screenly edge-app run) instead of vite. Please refer to the package.json of existing new Edge Apps like the qr-code, menu-board, grafana, and cap-alerting apps.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please refer to the package.json of existing new Edge Apps like the qr-code, menu-board, grafana, and cap-alerting apps. This file should look similar to package.json of the Edge Apps mentioned.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please refer to the package.json of existing new Edge Apps like the qr-code, menu-board, grafana, and cap-alerting apps. This file should look similar to package.json of the Edge Apps mentioned.

Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be possible to move this inside the Edge Apps library instead?

@nicomiguelino
Copy link
Contributor

@iamplugged, here are my general comments:

  • Please apply the changes from edge-apps/clock-new and edge-apps/weather-new into the existing directories, edge-apps/clock, edge-apps/weather, respectively. Once done, please remove the 'clock-new' and weather-new directories.

Copy link
Contributor

Choose a reason for hiding this comment

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

This was created before the changes that centralizes Tailwind into the Edge Apps library were merged. Please remove this file if not needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

We already moved such files into the Edge Apps library to prevent duplicate files. We can now remove this.

Copy link
Contributor

Choose a reason for hiding this comment

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

We already moved such files into the Edge Apps library to prevent duplicate files. We can now remove this.

Copy link
Contributor

Choose a reason for hiding this comment

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

We already moved such files into the Edge Apps library to prevent duplicate files. We can now remove this.

Copy link
Contributor

Choose a reason for hiding this comment

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

We already moved such files into the Edge Apps library to prevent duplicate files. We can now remove this.

Copy link
Contributor

Choose a reason for hiding this comment

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

We already moved such files into the Edge Apps library to prevent duplicate files. We can now remove this.

Copy link
Contributor

Choose a reason for hiding this comment

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

We already moved such files into the Edge Apps library to prevent duplicate files. We can now remove this.

@nicomiguelino
Copy link
Contributor

XSS Security Review

I've identified CRITICAL XSS vulnerabilities in this PR that should be addressed before merging:

🚨 CRITICAL: XSS in weather-new app

Location: edge-apps/weather-new/src/main.ts - renderForecast() function (lines 225-229)

Problem: Using innerHTML with template literal interpolation is a textbook XSS vulnerability pattern. While current data sources (numeric temp, formatted time) appear safe, this creates a critical security risk if:

  • Settings are compromised
  • API responses are intercepted/modified
  • Data sources change in the future

Attack Vector Example: If a malicious actor could control timeValue or locale settings, they could inject HTML/JavaScript that executes when innerHTML parses the string.

Solution: Replace innerHTML with safe DOM methods using textContent instead of template literal interpolation.

⚠️ HIGH: Pattern Risk in dev-tools.ts

Location: edge-apps/edge-apps-library/src/components/dev-tools/dev-tools.ts (lines 167-174)

Uses innerHTML for numeric viewport data. While the data sources (window dimensions) are safe, this violates the principle of never using innerHTML with dynamic values.

✅ Positive Security Practices Found

The library components demonstrate excellent security:

  • app-header.ts: Uses textContent for formatted time/date
  • brand-logo.ts: Uses img.src for URLs and textContent for names
  • clock-new/src/main.ts: Consistently uses textContent for all dynamic data

Recommend the weather-new app follow these same patterns.

Recommendation

Please fix the weather-new innerHTML usage before merging. This is a critical security vulnerability that violates OWASP guidelines even if current data sources appear safe.

nicomiguelino and others added 7 commits February 2, 2026 16:28
…o the existing `clock` and `weather` directories (#657)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Remove all comments from tsconfig.json files in clock, weather, and template
directories to fix JSON parsing errors in super-linter. Also add root-level
package.json to .prettierignore.
@nicomiguelino nicomiguelino changed the title [Draft]: Edge app library updates feat(clock, weather, edge-apps-library): redesign the clock and weather apps Feb 3, 2026
nicomiguelino added a commit that referenced this pull request Feb 4, 2026
…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>
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