Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2143b09
chore(skills): add create-an-edge-app skill with Edge App setup guide…
nicomiguelino Jan 27, 2026
180917b
chore: ignore `.claude/` when running Super-Linter
nicomiguelino Jan 27, 2026
fbd6a31
chore: update .claude/skills/create-an-edge-app/SKILL.md
nicomiguelino Jan 27, 2026
070c405
chore(skills): improve HTML organization guidance in create-an-edge-a…
nicomiguelino Jan 27, 2026
a397ecc
feat: add birthday-greeting edge app with responsive design
nicomiguelino Jan 28, 2026
56b1616
chore(birthday-greeting): add a language identifier for fenced code b…
nicomiguelino Jan 28, 2026
7159fdd
docs(skill): remove code blocks and reference live examples in create…
nicomiguelino Jan 28, 2026
7adc7dd
chore(skills): resolve Markdown linting errors
nicomiguelino Jan 28, 2026
085d082
chore(skills): fix grammatical errors in create-an-edge-app skill
nicomiguelino Jan 28, 2026
b3c732a
added app icon
salmanfarisvp Jan 28, 2026
10447f0
Merge branch 'master' into create-claude-skills
nicomiguelino Jan 28, 2026
bc2054d
docs: add Figma design consultation to Edge App creation workflow
nicomiguelino Jan 28, 2026
120533b
chore(skills): replace bare URLS with links
nicomiguelino Jan 28, 2026
edc897b
Merge branch 'create-claude-skills' into new-edge-app/birthday-app
nicomiguelino Jan 28, 2026
faba107
Merge branch 'master' into new-edge-app/birthday-app
nicomiguelino Jan 28, 2026
36f4ad1
fix(birthday-greeting): prevent race condition in image loading
nicomiguelino Jan 28, 2026
6326ebe
chore(birthday-greeting): add accessibility attributes to the logo SVG
nicomiguelino Jan 28, 2026
8653405
chore(birthday-greeting): assign value to the empty `alt` attribute i…
nicomiguelino Jan 28, 2026
a1f8f78
fix(birthday-greeting): update regex checks for Base64 values so that…
nicomiguelino Jan 28, 2026
abcc2d1
chore(birthday-greeting): address formatting errors
nicomiguelino Jan 28, 2026
4c81b67
test(birthday-greeting): add comprehensive tests for image validation
nicomiguelino Jan 28, 2026
2ba3bc5
refactor(birthday-greeting): make image required and prevent duplicat…
nicomiguelino Jan 28, 2026
f142d0c
refactor(birthday-greeting): extract SVG logo to separate TypeScript …
nicomiguelino Jan 29, 2026
02872ab
style(birthday-greeting): improve responsive design with rem units an…
nicomiguelino Jan 29, 2026
6df6769
style(birthday-greeting): lay groundwork for portrait display support
nicomiguelino Jan 29, 2026
f3c2077
Merge branch 'master' into new-edge-app/birthday-app
nicomiguelino Jan 29, 2026
69be24f
Merge branch 'master' into new-edge-app/birthday-app
nicomiguelino Jan 30, 2026
381ff69
Merge branch 'master' into new-edge-app/birthday-app
nicomiguelino Jan 31, 2026
02f5fc9
Merge branch 'master' into new-edge-app/birthday-app
nicomiguelino Feb 4, 2026
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
189 changes: 189 additions & 0 deletions .claude/skills/create-an-edge-app/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
---
name: create-an-edge-app
description: The recommended way to create an Edge App
---

# Creating an Edge App

## When Creating an Edge App

- Create a new directory for a new Edge App inside the `edge-apps/` directory.
- The directory name should follow the `kebab-case` naming convention.

## Directory Structure

The new Edge Apps directory structure should closely resemble that of the following Edge Apps:

- QR Code (`edge-apps/qr-code/`)
- Menu Board (`edge-apps/menu-board/`)
- Grafana (`edge-apps/grafana/`)
- CAP Alerting (`edge-apps/cap-alerting/`)

```plaintext
edge-apps/[new-edge-app]
├── bun.lock
├── index.html
├── package.json
├── README.md
├── screenly_qc.yml
├── screenly.yml
├── src
│   ├── css
│   │   └── style.css
│   ├── main.test.ts
│   └── main.ts
└── static
└── img
└── icon.svg
```

The aforementioned Edge Apps heavily rely on the Edge Apps library, which lives inside the `edge-apps/edge-apps-library/` directory.

- Most of the scripts inside the `package.json` of each of these apps execute the `edge-apps-scripts` command.
- All of these apps depend on the `@screenly/edge-apps` library, which maps to `workspace:../edge-apps-library`.
- `edge-apps/[new-edge-app]/src/main.ts` is a required file.
- Running `bun run build` inside `edge-apps/[new-edge-app]` will run `edge-apps-scripts build`, which is very opinionated.

### Creating the Manifest Files

The new app should have the following manifest files:
- `screenly.yml`
- `screenly_qc.yml`

Here's a basic example of what `screenly.yml` could look like:

```yaml
---
syntax: manifest_v1
description: The app's description goes here.
icon: The app's icon path (HTTPS URL) goes here.
author: Screenly, Inc.
ready_signal: true
settings:
setting_1:
type: string
default_value: ''
title: Setting 1
optional: true
help_text:
properties:
advanced: true
help_text: The help text for setting 1 goes here.
type: string # This can be one of the following: datetime, number, select, boolean, or string
schema_version: 1
setting_2:
type: secret
default_value: ''
title: Setting 2
optional: true
help_text:
properties:
advanced: true
help_text: The help text for setting 2 goes here.
type: boolean
schema_version: 1
```

More information about the manifest files can be found in the [Edge Apps documentation in the `Screenly/cli` repository](https://raw.githubusercontent.com/Screenly/cli/refs/heads/master/docs/EdgeApps.md).

### Creating `src/main.ts`

The new app's `main.ts` should look like the following:

```typescript
import './css/style.css'
import {
setupTheme,
getSettingWithDefault,
setupErrorHandling,
signalReady,
} from '@screenly/edge-apps'

async function startApp(): Promise<void> {
// The main logic of your Edge App goes here.

// Signal the digital signage player that the app is ready to be displayed.
signalReady()
}

window.onload = function () {
const setting1 = getSettingWithDefault<string>('setting_1', 'default_value_1')
const setting2 = getSettingWithDefault<boolean>('setting_2', false) // The `false` here serves as a fallback when the value for `setting_2` is not set or when `setting_2` is not defined.

// Setup error handling with panic-overlay
setupErrorHandling()

// Setup branding colors using the library
setupTheme()

startApp()
}
```

### Creating `main.test.ts`

```typescript
import { describe, test, expect, beforeEach, afterEach } from 'bun:test'
import { setupScreenlyMock, resetScreenlyMock } from '@screenly/edge-apps/test'
import { getSettingWithDefault } from '@screenly/edge-apps'

// eslint-disable-next-line max-lines-per-function
describe('Edge App Settings', () => {
beforeEach(() => {
setupScreenlyMock(
{
location: 'Test Location',
hostname: 'display-01',
},
{
setting_1: 'test_value',
setting_2: 'true',
},
)
})

afterEach(() => {
resetScreenlyMock()
})

// eslint-disable-next-line max-lines-per-function
test('should retrieve settings with correct values', () => {
const setting1 = getSettingWithDefault<string>('setting_1', 'default_value_1')
const setting2 = getSettingWithDefault<boolean>('setting_2', false)

expect(setting1).toBe('test_value')
expect(setting2).toBe(true)
})
})
```

### Creating `index.html`

```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Your Edge App Title</title>
<script src="screenly.js?version=1"></script>
<link rel="stylesheet" href="dist/css/style.css" />
</head>
<body>
<!-- The main HTML structure of your Edge App goes here. -->
<script src="dist/js/main.js"></script>
</body>
</html>
```

It is a best practice to organize HTML code into templates and Web Components as the app grows in complexity.
Consider using content templates first. If the app requires more complex UI components, consider using Web Components.

### Creating `style.css`

```css
@import 'tailwindcss';

/* Add your custom styles here */
/* You can create other CSS files inside `src/css/` and import them here if needed. */
```
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ edge-apps/powerbi/**
edge-apps/qr-code/
edge-apps/menu-board/
.github/workflows/
.claude/
67 changes: 67 additions & 0 deletions edge-apps/birthday-greeting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Birthday Greeting

A vibrant and responsive birthday greeting app for digital signage displays. This app displays personalized birthday messages with the person's name, role, and optional photo.

## Features

- Personalized birthday greeting with name and role
- Optional photo support with Base64 image encoding
- Fallback placeholder when no photo is provided

## Deployment

Create and deploy the Edge App:

```bash
bun install
screenly edge-app create --name birthday-greeting --in-place
bun run deploy
```

## Configuration

The app accepts the following settings via `screenly.yml`:

- `name` (required) - The name of the person having a birthday (e.g., "Amy Smith")
- `role` (required) - The role or position of the person (e.g., "Sales Manager")
- `image` (optional) - A Base64-encoded image of the person. If not provided or if the image fails to load, a placeholder will be displayed

### Example Configuration

```yaml
name: 'Amy Smith'
role: 'Sales Manager'
image: 'data:image/jpeg;base64,/9j/4AAQSkZJRg...'
```

## Image Format

The `image` setting accepts Base64-encoded images in the following formats:

1. **Full data URI** (recommended):

```text
data:image/jpeg;base64,/9j/4AAQSkZJRg...
```

2. **Pure Base64 string** (will be automatically formatted):
```text
/9j/4AAQSkZJRg...
```

## Development

```bash
bun install # Install dependencies
bun run build # Build the app
bun run dev # Start development server
bun run lint # Lint and fix code
```

## Testing

The app includes comprehensive tests for settings retrieval and image handling.

```bash
bun run test # Run all tests
```
Loading
Loading