Skip to content

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

Open
nicomiguelino wants to merge 21 commits intomasterfrom
feat/migrate-asset-metadata-app
Open

feat(asset-metadata): migrate and redesign asset metadata app#776
nicomiguelino wants to merge 21 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

  • Replace Vue app with vanilla TypeScript

  • Add glassmorphic responsive metadata dashboard

  • Adopt @screenly/edge-apps tooling and settings

  • Add multi-resolution screenshot testing


Diagram Walkthrough

flowchart LR
  a["screenly.js metadata"] 
  b["src/main.ts DOM renderer"]
  c["index.html metadata cards"]
  d["src/css/style.css glassmorphic layout"]
  e["package.json shared edge-app scripts"]
  f["e2e/screenshots.spec.ts visual coverage"]

  a -- "feeds" --> b
  b -- "populates" --> c
  d -- "styles" --> c
  e -- "powers build and test" --> b
  c -- "captured by" --> f
Loading

File Walkthrough

Relevant files
Tests
4 files
screenshots.spec.ts
Add screenshot coverage across supported resolutions         
+42/-0   
vue.spec.ts
Remove obsolete Vue end-to-end tests                                         
+0/-70   
App.spec.ts
Remove Vue component unit test suite                                         
+0/-87   
test-setup.ts
Remove global Vitest Screenly setup                                           
+0/-17   
Configuration changes
13 files
eslint.config.ts
Remove Vue-specific ESLint configuration                                 
+0/-34   
playwright.config.ts
Drop custom Playwright config in migration                             
+0/-14   
vite.config.ts
Remove custom Vite and Vue pipeline                                           
+0/-50   
vitest.config.ts
Remove Vitest configuration after migration                           
+0/-15   
.ignore
Ignore installed dependencies directory                                   
+1/-0     
extensions.json
Remove Vue-focused editor recommendations                               
+0/-9     
tsconfig.json
Remove dedicated E2E TypeScript config                                     
+0/-4     
screenly.yml
Add `display_errors` and remove theme setting                       
+11/-7   
screenly_qc.yml
Mirror manifest settings changes for QC                                   
+11/-7   
tsconfig.app.json
Remove Vue application TypeScript config                                 
+0/-16   
tsconfig.json
Extend shared Screenly TypeScript baseline                             
+6/-12   
tsconfig.node.json
Remove Node-specific TypeScript project config                     
+0/-19   
tsconfig.vitest.json
Remove Vitest TypeScript project config                                   
+0/-11   
Enhancement
3 files
main.ts
Render metadata directly into static DOM                                 
+40/-7   
style.css
Add responsive glassmorphic dashboard styling                       
+186/-0 
index.html
Replace app shell with static metadata cards                         
+163/-3 
Formatting
1 files
main.scss
Remove legacy Vue grid stylesheet                                               
+0/-572 
Miscellaneous
1 files
App.vue
Delete Vue root component implementation                                 
+0/-131 
Documentation
1 files
README.md
Refresh docs for new workflow and screenshots                       
+24/-46 
Dependencies
1 files
package.json
Switch to shared scripts and dependencies                               
+21/-49 
Additional files
11 files
1080x1920.webp [link]   
1280x720.webp [link]   
1920x1080.webp [link]   
2160x3840.webp [link]   
2160x4096.webp [link]   
3840x2160.webp [link]   
4096x2160.webp [link]   
480x800.webp [link]   
720x1280.webp [link]   
800x480.webp [link]   
bg.webp [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 🔍

(Review updated until commit efecffd)

Here are some key observations to aid the review process:

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

Tests skipped

The documented bun run test path does not execute the new Playwright screenshot spec, because it only runs Bun tests under src/ and succeeds even when no tests are found. In CI or local verification this can report success while the actual E2E coverage in e2e/ never runs.

"test": "bun test --pass-with-no-tests src/",
"test:unit": "bun test --pass-with-no-tests src/",
Unguarded global

hardware is read from screenly.metadata directly, unlike the other fields that go through helper functions. If screenly.js fails to load or the metadata object is missing during local/dev startup, this throws before the dashboard finishes initializing, so nothing renders and the ready signal is never sent.

setText('hostname', getHostname())
setText('screen-name', getScreenName())
setText('hardware', screenly.metadata.hardware)
setText('version', getScreenlyVersion())

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 8, 2026

PR Code Suggestions ✨

Latest suggestions up to efecffd
Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Always signal rendering completion

Make sure signalReady() runs even when branding setup or rendering throws. Right now
any failure before the last line leaves the asset stuck in a non-ready state, which
can block playback indefinitely.

edge-apps/asset-metadata/src/main.ts [19-44]

 document.addEventListener('DOMContentLoaded', async () => {
   setupErrorHandling()
 
-  await setupBranding()
+  try {
+    await setupBranding()
 
-  setText('hostname', getHostname())
-  setText('screen-name', getScreenName())
-  setText('hardware', screenly.metadata.hardware)
-  setText('version', getScreenlyVersion())
-  setText('coordinates', getFormattedCoordinates())
+    setText('hostname', getHostname())
+    setText('screen-name', getScreenName())
+    setText('hardware', screenly.metadata.hardware)
+    setText('version', getScreenlyVersion())
+    setText('coordinates', getFormattedCoordinates())
 
-  const tags = getTags()
-  const labelsContainer = document.getElementById('labels')
-  if (labelsContainer) {
-    if (tags && tags.length > 0) {
-      labelsContainer.innerHTML = tags
-        .map((tag) => `<span class="label-chip">${escapeHtml(tag)}</span>`)
-        .join('')
-    } else {
-      labelsContainer.innerHTML =
-        '<span class="no-labels">No labels assigned</span>'
+    const tags = getTags()
+    const labelsContainer = document.getElementById('labels')
+    if (labelsContainer) {
+      if (tags && tags.length > 0) {
+        labelsContainer.innerHTML = tags
+          .map((tag) => `<span class="label-chip">${escapeHtml(tag)}</span>`)
+          .join('')
+      } else {
+        labelsContainer.innerHTML =
+          '<span class="no-labels">No labels assigned</span>'
+      }
     }
+  } finally {
+    signalReady()
   }
-
-  signalReady()
 })
Suggestion importance[1-10]: 8

__

Why: This is a valid reliability fix because signalReady() currently runs only on the success path, and ready_signal: true means a thrown error before line 43 can leave the asset stuck waiting forever. Wrapping the rendering work in try/finally materially improves correctness for src/main.ts.

Medium
Guard global metadata access

Avoid directly dereferencing screenly.metadata.hardware here. If screenly.js is
missing, late, or malformed, this line throws and prevents the rest of the app from
rendering.

edge-apps/asset-metadata/src/main.ts [26]

-setText('hardware', screenly.metadata.hardware)
+const hardware = globalThis.screenly?.metadata?.hardware
+setText('hardware', typeof hardware === 'string' ? hardware : undefined)
Suggestion importance[1-10]: 5

__

Why: This correctly identifies that direct access to screenly.metadata.hardware can throw if the global is absent or malformed, while the rest of the file already prefers helper accessors like getHostname(). The impact is moderate because screenly.js is expected to load first, but the guard still improves resilience.

Low
General
Always release browser contexts

Close the browser context in a finally block so failed navigations or screenshots do
not leak contexts across resolutions. These leaked resources can make the screenshot
suite flaky or exhaust browser limits in CI.

edge-apps/asset-metadata/e2e/screenshots.spec.ts [26-40]

 const context = await browser.newContext({ viewport: { width, height } })
-const page = await context.newPage()
 
-await setupClockMock(page)
-await setupScreenlyJsMock(page, screenlyJsContent)
+try {
+  const page = await context.newPage()
 
-await page.goto('/')
-await page.waitForLoadState('networkidle')
+  await setupClockMock(page)
+  await setupScreenlyJsMock(page, screenlyJsContent)
 
-await page.screenshot({
-  path: path.join(screenshotsDir, `${width}x${height}.png`),
-  fullPage: false,
-})
+  await page.goto('/')
+  await page.waitForLoadState('networkidle')
 
-await context.close()
+  await page.screenshot({
+    path: path.join(screenshotsDir, `${width}x${height}.png`),
+    fullPage: false,
+  })
+} finally {
+  await context.close()
+}
Suggestion importance[1-10]: 7

__

Why: This is a sound cleanup improvement: if page.goto() or page.screenshot() fails, await context.close() is skipped and the loop can leak browser contexts across resolutions. Using try/finally makes the screenshot test more stable in CI without changing intended behavior.

Medium

Previous suggestions

Suggestions up to commit 479c0f8
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.

nicomiguelino and others added 12 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>
- Add `display_errors` setting to `screenly.yml` and `screenly_qc.yml`
- Update README configuration table with the new setting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace unsafe `getElementById` cast with `querySelector<HTMLImageElement>`
- Remove redundant null check since the element is always present in the DOM

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e overrides

- Remove `theme` setting from `screenly.yml`, `screenly_qc.yml`, and README
- Remove unused `setProperty` calls for `--app-bg`, `--card-bg`, `--card-border`,
  `--text-primary`, `--text-secondary`, `--chip-bg`, and `--chip-color`
- Replace with a single `--theme-color-primary` set from branding colors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Scale up card icons, labels, and values for portrait displays
- Increase card padding, gap, and border radius in portrait mode
- Enlarge brand logo in portrait mode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace `var(--theme-color-primary)` with fixed `rgba(255, 255, 255, 0.25)` on SVG icon fills
- Remove unused `colors` from `setupBranding()` destructure
- Add portrait styles for `.label-chip` (font size, padding, border radius)
- Remove stale settings from e2e screenshots spec

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace grid layout with right-aligned flex column of cards
- Remove app-header, logo card, and associated styles
- Scale down card dimensions, typography, and spacing
- Add 7 mock tags to screenshot spec for label chip preview
- Update screenshots for all resolutions
- Delete asset-metadata-old directory
- Migrate icon.svg to the new app
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

Persistent review updated to latest commit efecffd

- Extract app logic into app.ts for testability
- Add tests for metadata rendering and label chips
- Add test for HTML escaping in label chips
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

Copilot reviewed 27 out of 43 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

edge-apps/asset-metadata/src/main.ts:12

  • index.html uses the <auto-scaler> web component, but this entrypoint never imports @screenly/edge-apps/components to register the custom elements. Without registering, <auto-scaler> will behave like an unknown element and scaling won’t work. Import the components bundle (e.g., import '@screenly/edge-apps/components') early in this file.
    edge-apps/asset-metadata/src/main.ts:28
  • This reads screenly.metadata.hardware directly even though the rest of the file uses @screenly/edge-apps accessors. For consistency and to avoid relying on the global shape, prefer the library helper (e.g., getHardware() / getMetadata()) and stringify/format the result before rendering.

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

nicomiguelino and others added 4 commits April 9, 2026 13:11
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- Update bg.webp to match the one used by simple-timer and welcome-app
- Regenerate screenshots with the updated background

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nicomiguelino nicomiguelino requested a review from 514sid April 10, 2026 13:47
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