Skip to content

feat(asset-metadata): migrate and redesign asset metadata app#776

Draft
nicomiguelino wants to merge 6 commits intomasterfrom
feat/migrate-asset-metadata-app
Draft

feat(asset-metadata): migrate and redesign asset metadata app#776
nicomiguelino wants to merge 6 commits intomasterfrom
feat/migrate-asset-metadata-app

Conversation

@nicomiguelino
Copy link
Copy Markdown
Contributor

@nicomiguelino nicomiguelino commented Apr 8, 2026

User description

Migrates the Asset Metadata Edge App away from Vue/TypeScript and redesigns it as a plain HTML/CSS/JavaScript app.


PR Type

Enhancement, Tests, Documentation


Description

  • Preserve legacy Vue asset metadata app

  • Render Screenly metadata in responsive cards

  • Configure Vite, TypeScript, manifests, dependencies

  • Add tests, docs, and editor tooling


Diagram Walkthrough

flowchart LR
  cfg["Tooling and manifests"]
  app["Vue metadata dashboard"]
  ui["Responsive card layout"]
  tests["Vitest and Playwright tests"]
  docs["README and editor setup"]
  cfg -- "supports" --> app
  app -- "uses" --> ui
  app -- "verified by" --> tests
  cfg -- "documented in" --> docs
Loading

File Walkthrough

Relevant files
Tests
3 files
vue.spec.ts
Add Playwright smoke test for metadata layout                       
[link]   
App.spec.ts
Add App rendering test with store mocks                                   
[link]   
test-setup.ts
Provide shared `screenly` mock for unit tests                       
[link]   
Configuration changes
12 files
eslint.config.ts
Configure ESLint for Vue, Vitest, Playwright                         
[link]   
playwright.config.ts
Reuse shared Playwright blueprint test configuration         
[link]   
vite.config.ts
Configure Vite plugins, aliases, and asset copying             
[link]   
vitest.config.ts
Set Vitest environment from merged Vite config                     
[link]   
extensions.json
Recommend workspace extensions for Vue development             
[link]   
tsconfig.json
Add TypeScript settings for Playwright tests                         
[link]   
screenly.yml
Add Screenly manifest with analytics settings                       
[link]   
screenly_qc.yml
Add QC manifest with mirrored app settings                             
[link]   
tsconfig.app.json
Configure app TypeScript paths and source inputs                 
[link]   
tsconfig.json
Reference node, app, and Vitest projects                                 
[link]   
tsconfig.node.json
Set tooling TypeScript options for Node files                       
[link]   
tsconfig.vitest.json
Configure TypeScript support for Vitest environment           
[link]   
Enhancement
4 files
main.ts
Bootstrap Vue app with Pinia and styles                                   
[link]   
index.html
Add HTML shell and Screenly script loading                             
[link]   
main.scss
Create responsive grid styling for metadata cards               
[link]   
App.vue
Render metadata cards from blueprint-backed stores             
[link]   
Documentation
1 files
README.md
Document app setup, testing, and deployment                           
[link]   
Dependencies
1 files
package.json
Define app scripts, tools, and dependencies                           
[link]   

…a-old

- Preserve existing Vue/TypeScript implementation as reference
- Free up asset-metadata directory for redesigned app
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 8, 2026

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

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

Ready signal

The app signals screenly.signalReadyForRendering() on mounted, but the branding setup is started in an async onBeforeMount hook that Vue does not wait for. If setupBrandingLogo() performs a fetch or other async work, Screenly can mark the app as ready before the final UI is rendered, resulting in incomplete screenshots or visible late updates.

onBeforeMount(async () => {
  baseSettingsStore.setupTheme()
  await baseSettingsStore.setupBrandingLogo()
})

onMounted(() => {
  screenly.signalReadyForRendering()
})

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 8, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Mock the correct modules

These mocks do not affect App.vue, because the component imports metadataStoreSetup
and baseSettingsStoreSetup from blueprint/stores/... instead of @/stores/.... Mock
the actual imported modules with the same exported setup functions, otherwise the
test can execute real store logic and fail unpredictably.

edge-apps/asset-metadata-old/src/components/tests/App.spec.ts [32-48]

-vi.mock('@/stores/metadata-store', () => ({
-  useScreenlyMetadataStore: () => ({
+vi.mock('blueprint/stores/metadata-store', () => ({
+  metadataStoreSetup: () => ({
     hostname: ref('test-host'),
     screenName: ref('test-screen'),
     hardware: ref('test-hardware'),
     screenlyVersion: ref('test-version'),
     formattedCoordinates: ref('40.7128° N, 74.0060° W'),
     tags: ref(['tag1', 'tag2', 'tag3']),
   }),
 }))
-vi.mock('@/stores/base-settings-store', () => ({
-  useBaseSettingsStore: () => ({
+vi.mock('blueprint/stores/base-settings-store', () => ({
+  baseSettingsStoreSetup: () => ({
     setupTheme: vi.fn(),
     setupBrandingLogo: vi.fn(),
+    brandLogoUrl: ref(''),
     primaryThemeColor: ref(mockScreenly.settings.screenly_color_accent),
   }),
 }))
Suggestion importance[1-10]: 8

__

Why: App.vue imports metadataStoreSetup and baseSettingsStoreSetup from blueprint/stores/..., so the current mocks for @/stores/... do not affect the component. This can make the test run real store logic and break determinism, so the suggestion materially improves test correctness.

Medium
Delay the ready signal

screenly.signalReadyForRendering() currently fires as soon as the component mounts,
even if setupBrandingLogo() is still running. That can mark the asset as ready
before the final UI is rendered, which is especially risky with ready_signal: true.

edge-apps/asset-metadata-old/src/App.vue [2-55]

-import { onBeforeMount, onMounted, type Ref } from 'vue'
+import { nextTick, onMounted, type Ref } from 'vue'
 ...
-onBeforeMount(async () => {
+onMounted(async () => {
   baseSettingsStore.setupTheme()
   await baseSettingsStore.setupBrandingLogo()
-})
-
-onMounted(() => {
+  await nextTick()
   screenly.signalReadyForRendering()
 })
Suggestion importance[1-10]: 8

__

Why: The async work in onBeforeMount() is not guaranteed to finish before onMounted() runs, so screenly.signalReadyForRendering() can fire before the UI is fully ready. That is a meaningful runtime issue given ready_signal: true, and the proposed change addresses it directly.

Medium
Fix route URL matching

The current page.route() pattern does not match the full browser URL, so the
screenly.js request can bypass this mock and make the test flaky or fail. Match any
origin explicitly so the intercepted script is always the one your app executes.

edge-apps/asset-metadata-old/e2e/vue.spec.ts [8-48]

-await page.route('/screenly.js?version=1', async (route) => {
+await page.route('**/screenly.js?version=1', async (route) => {
   const mockScreenlyData = {
     signalReadyForRendering: () => {},
     metadata: {
       coordinates: [40.7128, -74.006],
       hostname: 'test-host',
       screen_name: 'test-screen',
       hardware: 'test-hardware',
       location: 'test-location',
       screenly_version: 'test-version',
       tags: ['tag1', 'tag2', 'tag3'],
     },
     settings: {
       theme: 'light',
       screenly_color_accent: '#972EFF',
       screenly_color_light: '#ADAFBE',
       screenly_color_dark: '#454BD2',
       enable_analytics: 'true',
       tag_manager_id: '',
       override_timezone: 'America/New_York',
       override_locale: 'en',
     },
     cors_proxy_url: 'http://127.0.0.1:8080',
   }
 
   const screenlyJsContent = `
     // Generated screenly.js for test mode
     window.screenly = {
       signalReadyForRendering: () => {},
       metadata: ${JSON.stringify(mockScreenlyData.metadata, null, 2)},
       settings: ${JSON.stringify(mockScreenlyData.settings, null, 2)},
       cors_proxy_url: ${JSON.stringify(mockScreenlyData.cors_proxy_url)}
     }
   `
 
   await route.fulfill({
     status: 200,
     contentType: 'application/javascript',
     body: screenlyJsContent,
   })
 })
Suggestion importance[1-10]: 7

__

Why: page.route() with '/screenly.js?version=1' may fail to match the full browser URL, causing the mock to be skipped and the test to hit the real script. Using a wildcard pattern is a targeted reliability fix for vue.spec.ts.

Medium

nicomiguelino and others added 3 commits April 8, 2026 15:26
- Add new asset-metadata Edge App using edge-app-template
- Implement glassmorphic card layout for screen metadata display
- Support landscape and portrait orientations via CSS Grid
- Use @screenly/edge-apps utilities for theme, branding, and metadata
- Copy screenly.yml and screenly_qc.yml from asset-metadata-old
- Bump @screenly/edge-apps to 0.0.17

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `<app-header>` to index.html above the grid
- Set `flex-direction: column` on `#app` so header stacks on top
- Replace `height: 100%` with `flex: 1` on `.grid-container`

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add background image with dark overlay via `body::before`
- Replace opaque card background with glassmorphic gradient and blur
- Set white text colors on card values, labels, and chips
- Remove "Powered by Screenly" text from logo card
- Set `app-header` text color to white

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
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 Asset Metadata edge app to a vanilla HTML/CSS/TypeScript implementation with a responsive card-based layout, while preserving the legacy Vue version in a separate asset-metadata-old/ folder.

Changes:

  • Replaced the Vue/Pinia UI with a static HTML shell + DOM-driven rendering for Screenly metadata.
  • Added new responsive styling and Playwright-based screenshot generation.
  • Archived the previous Vue implementation (including its tooling/config/tests) under edge-apps/asset-metadata-old/.

Reviewed changes

Copilot reviewed 16 out of 48 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
edge-apps/asset-metadata/tsconfig.json Aligns TS config with shared edge-apps defaults for the new app.
edge-apps/asset-metadata/static/images/bg.webp Background asset for the redesigned UI.
edge-apps/asset-metadata/src/main.ts New DOM-based bootstrap: branding/theme setup + metadata rendering.
edge-apps/asset-metadata/src/css/style.css New responsive card/grid styling for the redesigned UI.
edge-apps/asset-metadata/screenshots/800x480.webp Updated/added marketing screenshot for this resolution.
edge-apps/asset-metadata/screenshots/720x1280.webp Updated/added marketing screenshot for this resolution.
edge-apps/asset-metadata/screenshots/480x800.webp Updated/added marketing screenshot for this resolution.
edge-apps/asset-metadata/screenshots/4096x2160.webp Updated/added marketing screenshot for this resolution.
edge-apps/asset-metadata/screenshots/3840x2160.webp Updated/added marketing screenshot for this resolution.
edge-apps/asset-metadata/screenshots/2160x4096.webp Updated/added marketing screenshot for this resolution.
edge-apps/asset-metadata/screenshots/2160x3840.webp Updated/added marketing screenshot for this resolution.
edge-apps/asset-metadata/screenshots/1920x1080.webp Updated/added marketing screenshot for this resolution.
edge-apps/asset-metadata/screenshots/1280x720.webp Updated/added marketing screenshot for this resolution.
edge-apps/asset-metadata/screenshots/1080x1920.webp Updated/added marketing screenshot for this resolution.
edge-apps/asset-metadata/README.md Updates documentation to match the new build/dev/test flows.
edge-apps/asset-metadata/package.json Switches scripts/deps to the edge-apps-scripts workflow and new app versioning.
edge-apps/asset-metadata/index.html New static HTML layout (auto-scaler + metadata cards).
edge-apps/asset-metadata/e2e/screenshots.spec.ts Adds Playwright screenshot generation coverage.
edge-apps/asset-metadata/bun.lock Locks updated dependencies for the redesigned app.
edge-apps/asset-metadata/.ignore Adds ignore rules for Screenly/app tooling (node_modules).
edge-apps/asset-metadata/.gitignore Adds standard ignores plus generated screenshot PNGs.
edge-apps/asset-metadata-old/vitest.config.ts Preserved legacy Vitest config for the Vue app.
edge-apps/asset-metadata-old/vite.config.ts Preserved legacy Vite config for the Vue app.
edge-apps/asset-metadata-old/tsconfig.vitest.json Preserved legacy TS config for Vitest.
edge-apps/asset-metadata-old/tsconfig.node.json Preserved legacy TS config for Node/tooling.
edge-apps/asset-metadata-old/tsconfig.json Preserved legacy TS project references.
edge-apps/asset-metadata-old/tsconfig.app.json Preserved legacy Vue app TS settings.
edge-apps/asset-metadata-old/static/images/icon.svg Preserved legacy app icon asset.
edge-apps/asset-metadata-old/static/images/asset-metadata-app-preview.jpg Preserved legacy README preview asset.
edge-apps/asset-metadata-old/src/test-setup.ts Preserved legacy test setup with screenly global mock.
edge-apps/asset-metadata-old/src/main.ts Preserved legacy Vue bootstrap entrypoint.
edge-apps/asset-metadata-old/src/components/tests/App.spec.ts Preserved legacy Vue unit test coverage.
edge-apps/asset-metadata-old/src/assets/main.scss Preserved legacy SCSS styling for the Vue UI.
edge-apps/asset-metadata-old/src/assets/font/Aeonik-Regular.woff2 Preserved legacy font asset.
edge-apps/asset-metadata-old/src/assets/font/Aeonik-Regular.woff Preserved legacy font asset.
edge-apps/asset-metadata-old/src/App.vue Preserved legacy Vue component implementation.
edge-apps/asset-metadata-old/screenly.yml Preserved legacy manifest for the Vue app.
edge-apps/asset-metadata-old/screenly_qc.yml Preserved legacy QC manifest for the Vue app.
edge-apps/asset-metadata-old/README.md Preserved legacy README.
edge-apps/asset-metadata-old/public/favicon.ico Preserved legacy favicon asset.
edge-apps/asset-metadata-old/playwright.config.ts Preserved legacy Playwright config.
edge-apps/asset-metadata-old/package.json Preserved legacy dependencies/scripts for Vue app.
edge-apps/asset-metadata-old/index.html Preserved legacy HTML shell for Vue app.
edge-apps/asset-metadata-old/eslint.config.ts Preserved legacy ESLint config for Vue.
edge-apps/asset-metadata-old/e2e/vue.spec.ts Preserved legacy Playwright smoke test.
edge-apps/asset-metadata-old/e2e/tsconfig.json Preserved legacy e2e TS config.
edge-apps/asset-metadata-old/bun.lock Preserved legacy lockfile.
edge-apps/asset-metadata-old/.vscode/extensions.json Preserved legacy editor recommendations.

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

Comment on lines +27 to +31
document.addEventListener('DOMContentLoaded', async () => {
setupErrorHandling()

app.mount('#app')
const { colors, logoUrl } = await setupBranding()

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

If setupBranding() (or any other awaited step) throws, signalReady() is never called, which can leave the player waiting indefinitely for the ready signal. Consider wrapping the async initialization in try/finally so signalReady() is always invoked (and optionally log/report the failure in the catch).

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +58
document.documentElement.style.setProperty(
'--app-bg',
theme === 'dark' ? '#0D1117' : '#E8ECF1',
)
document.documentElement.style.setProperty(
'--card-bg',
theme === 'dark'
? 'rgba(255, 255, 255, 0.07)'
: 'rgba(255, 255, 255, 0.88)',
)
document.documentElement.style.setProperty(
'--card-border',
theme === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.06)',
)
document.documentElement.style.setProperty(
'--text-primary',
theme === 'dark' ? '#F2F2F3' : '#0D0E1A',
)
document.documentElement.style.setProperty(
'--text-secondary',
theme === 'dark' ? '#9CA3AF' : '#6B7280',
)
document.documentElement.style.setProperty('--chip-bg', colors.primary + '22')
document.documentElement.style.setProperty('--chip-color', colors.primary)
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

Several CSS variables are being set here (--app-bg, --card-bg, --text-primary, --text-secondary, --chip-bg, --chip-color) but the app’s CSS doesn’t reference most of them (e.g., .card-value/.card-label and .label-chip use hard-coded colors). This makes the theme/branding logic ineffective and harder to maintain. Either update style.css to use these variables (e.g., use var(--text-primary) / var(--chip-bg)), or remove the unused setProperty calls.

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +63
const logoImg = document.getElementById('brand-logo') as HTMLImageElement
if (logoImg && logoUrl) {
logoImg.src = logoUrl
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

document.getElementById('brand-logo') as HTMLImageElement is an unsafe cast: getElementById can return null (or a non-HTMLImageElement). Consider using querySelector<HTMLImageElement>('#brand-logo') or casting to HTMLImageElement | null so the subsequent null-check is type-safe and future refactors don’t accidentally dereference a null value.

Copilot uses AI. Check for mistakes.
nicomiguelino and others added 2 commits April 8, 2026 21:17
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove empty `src` attribute to avoid spurious document URL request
- Add `hidden` attribute so no broken-image icon appears before JS loads
- Unhide the image only when a valid `logoUrl` is available

Co-Authored-By: Claude Sonnet 4.6 <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