Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions edge-apps/klipfolio-power-metrics-dashboard/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Klipfolio Dashboard</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<iframe id="dashboard" title="Klipfolio Dashboard"></iframe>
<script src="screenly.js?version=1" defer></script>
<script src="script.js"></script>
Comment on lines +7 to +12
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The CSS and JavaScript files should be organized in a static/ folder structure following the codebase convention. All other edge apps in this repository (iframe, clock, weather, etc.) use static/css/, static/js/, and static/images/ subdirectories rather than placing assets in the root directory. This improves organization and maintainability.

Suggested change
<link rel="stylesheet" href="styles.css">
</head>
<body>
<iframe id="dashboard" title="Klipfolio Dashboard"></iframe>
<script src="screenly.js?version=1" defer></script>
<script src="script.js"></script>
<link rel="stylesheet" href="static/css/styles.css">
</head>
<body>
<iframe id="dashboard" title="Klipfolio Dashboard"></iframe>
<script src="static/js/screenly.js?version=1" defer></script>
<script src="static/js/script.js"></script>

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The custom script should be loaded with the defer attribute. Following the codebase convention seen in other edge apps, scripts that depend on screenly.js should use defer to ensure proper loading order and prevent race conditions where the script tries to access the screenly object before it's available.

Suggested change
<script src="script.js"></script>
<script src="script.js" defer></script>

Copilot uses AI. Check for mistakes.
</body>
</html>
4 changes: 4 additions & 0 deletions edge-apps/klipfolio-power-metrics-dashboard/instance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
syntax: instance_v1
id: 01JT3644XKZ7BWNVY1ZQXTW4AG
name: Klipfolio Dashboard Edge App
15 changes: 15 additions & 0 deletions edge-apps/klipfolio-power-metrics-dashboard/screenly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
syntax: manifest_v1
id: 01JSR8A2CW98PG8B0SR3T0MYX5
description: A Klipfolio dashboard edge app for Screenly
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The screenly.yml manifest is missing standard metadata fields that are present in other edge apps. Following the codebase convention, the manifest should include: icon (URL to app icon), author (e.g., "Screenly, Inc."), ready_signal (set to true for proper rendering timing), and a more descriptive description field. These fields provide important information for the app catalog and runtime behavior.

Suggested change
description: A Klipfolio dashboard edge app for Screenly
description: An edge app that displays a Klipfolio PowerMetrics dashboard within Screenly.
icon: https://assets.screenlyapp.com/apps/klipfolio-power-metrics-dashboard/icon.png
author: Screenly, Inc.
ready_signal: true

Copilot uses AI. Check for mistakes.
settings:
dashboard_url:
type: string
title: Dashboard URL
optional: true
help_text: The URL for the dashboard
dashboard_passcode:
type: secret
title: Dashboard Passcode
optional: true
Comment on lines +9 to +14
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The dashboard_url and dashboard_passcode settings are marked as optional, but the code does not handle the case where they are not provided. In script.js lines 4-5, fallback messages are set ('Dashboard URL not set', 'Passcode not set'), but these will be used as actual values rather than triggering an error. Since the app cannot function without these values, they should be marked as 'optional: false' in the manifest.

Suggested change
optional: true
help_text: The URL for the dashboard
dashboard_passcode:
type: secret
title: Dashboard Passcode
optional: true
optional: false
help_text: The URL for the dashboard
dashboard_passcode:
type: secret
title: Dashboard Passcode
optional: false

Copilot uses AI. Check for mistakes.
help_text: The passcode for the dashboard
68 changes: 68 additions & 0 deletions edge-apps/klipfolio-power-metrics-dashboard/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* global screenly, Sentry */
/* eslint-disable-next-line no-unused-vars, no-useless-catch */
Comment on lines +1 to +2
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The eslint-disable comment on line 2 references 'no-unused-vars' and 'no-useless-catch', but there are no try-catch blocks that simply rethrow errors in this file, making 'no-useless-catch' unnecessary. Additionally, the Sentry global is declared but never used in the code. Either remove the unused eslint-disable rules or implement the Sentry logging mentioned in the PR TODO list.

Copilot uses AI. Check for mistakes.

const dashboardUrlRaw = screenly.settings.dashboard_url || 'Dashboard URL not set'
const passcode = screenly.settings.dashboard_passcode || 'Passcode not set'
Comment on lines +4 to +5
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The settings are accessed at the module level (lines 4-5) before the window load event, which may execute before screenly.js has fully initialized. This could result in undefined values or errors. The settings should be accessed inside the event handler after ensuring screenly is available, similar to how they're accessed on line 18 within the try-catch block.

Copilot uses AI. Check for mistakes.

// Initialize when page loads
window.addEventListener('load', function() {
// Wait a short time for screenly.js to load
setTimeout(function() {
// Get the dashboard iframe
const dashboard = document.getElementById('dashboard')

// Construct the URL after screenly.js has loaded
let dashboardUrl
try {
// Try to use Screenly's CORS proxy
dashboardUrl = screenly.cors_proxy_url + encodeURIComponent(dashboardUrlRaw)
} catch (e) {
// Fallback to direct URL if screenly is not available
console.error("Error accessing screenly.cors_proxy_url, using direct URL", e)
dashboardUrl = dashboardUrlRaw;
}

// Set dashboard URL
dashboard.src = dashboardUrl

// Attempt to inject passcode after iframe loads
dashboard.onload = function() {
setTimeout(injectPasscodeIntoIframe, 1000)
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The script does not call screenly.signalReadyForRendering() to indicate when the app is ready for display. When ready_signal is set to true in the manifest, the app should call this method after the iframe loads successfully. This helps Screenly know when to display the content. Reference the iframe edge app implementation which calls this method in the iframe's onload handler.

Suggested change
setTimeout(injectPasscodeIntoIframe, 1000)
setTimeout(function() {
// First, try to inject the passcode
injectPasscodeIntoIframe()
// Then signal to Screenly that the app is ready for rendering
try {
if (typeof screenly !== 'undefined' &&
typeof screenly.signalReadyForRendering === 'function') {
screenly.signalReadyForRendering()
}
} catch (e) {
console.error("Error calling screenly.signalReadyForRendering:", e)
}
}, 1000)

Copilot uses AI. Check for mistakes.
};
}, 500) // 500ms delay for screenly.js to load
Comment on lines +9 to +32
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The 500ms delay for screenly.js to load is arbitrary and may cause race conditions. The screenly.js script is loaded with the defer attribute, which means it will execute after the DOM is parsed but before the load event. Using a setTimeout with a fixed delay is unreliable. Instead, listen for a screenly-specific event or check for the existence of the screenly object, or move the script loading to use proper defer attributes as seen in other edge apps.

Suggested change
// Wait a short time for screenly.js to load
setTimeout(function() {
// Get the dashboard iframe
const dashboard = document.getElementById('dashboard')
// Construct the URL after screenly.js has loaded
let dashboardUrl
try {
// Try to use Screenly's CORS proxy
dashboardUrl = screenly.cors_proxy_url + encodeURIComponent(dashboardUrlRaw)
} catch (e) {
// Fallback to direct URL if screenly is not available
console.error("Error accessing screenly.cors_proxy_url, using direct URL", e)
dashboardUrl = dashboardUrlRaw;
}
// Set dashboard URL
dashboard.src = dashboardUrl
// Attempt to inject passcode after iframe loads
dashboard.onload = function() {
setTimeout(injectPasscodeIntoIframe, 1000)
};
}, 500) // 500ms delay for screenly.js to load
// Get the dashboard iframe
const dashboard = document.getElementById('dashboard')
// Construct the URL after screenly.js has loaded
let dashboardUrl
try {
// Try to use Screenly's CORS proxy
dashboardUrl = screenly.cors_proxy_url + encodeURIComponent(dashboardUrlRaw)
} catch (e) {
// Fallback to direct URL if screenly is not available
console.error("Error accessing screenly.cors_proxy_url, using direct URL", e)
dashboardUrl = dashboardUrlRaw;
}
// Set dashboard URL
dashboard.src = dashboardUrl
// Attempt to inject passcode after iframe loads
dashboard.onload = function() {
setTimeout(injectPasscodeIntoIframe, 1000)
};

Copilot uses AI. Check for mistakes.
});

// Function to inject passcode into iframe
function injectPasscodeIntoIframe() {
try {
const iframe = document.getElementById('dashboard')

// Try to access the iframe document
let iframeDoc;
try {
iframeDoc = iframe.contentDocument || iframe.contentWindow.document
} catch (e) {
console.error("Cannot access iframe content due to cross-origin restrictions", e)
return
}

// Get passcode input
const input = iframeDoc.getElementById('passcode')

// If passcode input exists, inject passcode and submit
if (input) {
console.log("Found passcode field, injecting code");
input.value = passcode
input.dispatchEvent(new Event('input', { bubbles: true }))

// Get the submit button and click it
const button = iframeDoc.getElementById('submit')
button.click()
console.log("Clicked submit button")
Comment on lines +60 to +61
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

Missing null check for the button element before calling click(). If the submit button with id 'submit' doesn't exist in the iframe document (line 59), the code will throw an error when trying to call button.click() on line 60. Add a null check similar to the one used for the input field.

Suggested change
button.click()
console.log("Clicked submit button")
if (button) {
button.click()
console.log("Clicked submit button")
} else {
console.log("Submit button not found");
}

Copilot uses AI. Check for mistakes.
} else {
console.log("Passcode input field not found");
}
} catch (e) {
console.error("Error in injectPasscodeIntoIframe:", e)
}
}
Comment on lines +36 to +68
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The approach of injecting passcode into an iframe will not work due to cross-origin security restrictions. Lines 43-46 attempt to catch cross-origin errors, but even if the iframe loads through the CORS proxy, the Same-Origin Policy prevents accessing or manipulating the iframe's DOM from the parent window. This fundamental security restriction means the passcode injection functionality (lines 36-68) cannot work as designed. The TODO comment in the PR description acknowledges CORS and iframe issues that need to be resolved.

Copilot uses AI. Check for mistakes.
17 changes: 17 additions & 0 deletions edge-apps/klipfolio-power-metrics-dashboard/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* This file is no longer needed as all styles are defined in the HTML */
/* The file is kept for reference in case styles need to be extracted later */
Comment on lines +1 to +2
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The inline comment states this CSS file is no longer needed and kept for reference, but the file is still linked in index.html. Either remove the misleading comment and use this file properly, or remove the file and the link tag. Having a "placeholder" file that claims to not be used creates confusion and technical debt.

Suggested change
/* This file is no longer needed as all styles are defined in the HTML */
/* The file is kept for reference in case styles need to be extracted later */
/* Styles for the Klipfolio Power Metrics dashboard layout */
/* Defines full-screen behavior for the page and embedded dashboard iframe */

Copilot uses AI. Check for mistakes.

body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}

#dashboard {
width: 100%;
height: 100%;
border: none;
display: block;
}