feat(assets): add signage app store to the Add Asset flow - #3114
Merged
Conversation
Add an "Apps" tab to the Add Asset modal that installs pre-defined signage apps from the app store, configured from each app's published manifest. - Apps tab fetches the store index + per-app manifests client-side (operator's browser; CORS-enabled), renders a catalog grid - Manifest-driven config form (ported from the store's tested expand-template.js + config-manifest.js): text/select/toggle/number/ url/timezone widgets plus a Leaflet + OSM location-map (no Google Maps, no API key, bundled locally) - Installs as a webpage asset stamped with metadata.app (id, manifest URL/version, values) so editing reopens the config form seeded from saved values and rebuilds the launch URL - assets_create_app endpoint + app-edit path in assets_update, guarded by a store-host allowlist; playback.refreshIntervalS maps to the viewer's per-asset auto-refresh - APP_STORE_INDEX_URL setting (env-overridable, defaults to staging) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
There was a problem hiding this comment.
Pull request overview
Adds a new “Apps” catalog to the Add Asset modal that installs/edit-configures pre-defined signage apps from a remote app store manifest/index, while keeping the device backend constrained to storing a normal webpage asset plus metadata.app and enforcing an origin allowlist.
Changes:
- UI: New Apps tab in Add Asset modal with manifest-driven settings rendering, launch-URL building, and edit-time reconfiguration.
- Backend: New
assets_create_appendpoint and app-awareassets_updatehandling, gated byAPP_STORE_ALLOWED_HOST_SUFFIXES; plusAPP_STORE_INDEX_URLtemplated into the home page. - Tooling/Deps: Adds Leaflet (+ types) and ambient Alpine typings; updates TS config to bundler resolution and excludes Bun test files from
tsc.
Reviewed changes
Copilot reviewed 18 out of 21 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.json | Switches to moduleResolution: bundler, removes old path aliases, excludes *.test.ts from app tsc pass. |
| tests/test_template_views.py | Adds template integration test for Apps tab wiring plus endpoint tests for app create/update flows. |
| src/anthias_server/django_project/settings.py | Introduces APP_STORE_INDEX_URL and APP_STORE_ALLOWED_HOST_SUFFIXES settings. |
| src/anthias_server/app/views.py | Adds _host_allowed, new assets_create_app endpoint, and app edit handling in assets_update. |
| src/anthias_server/app/urls.py | Routes assets/new-app/ to assets_create_app. |
| src/anthias_server/app/templates/base.html | Exposes app store index URL via <meta name="anthias-app-store-index">. |
| src/anthias_server/app/templates/_asset_modal.html | Adds Apps tab UI, install form, and app settings section for edit mode. |
| src/anthias_server/app/static/src/home.ts | Registers appsTab() / appEdit() Alpine factories on window. |
| src/anthias_server/app/static/src/apps/types.ts | Defines types for store index, manifests, and setting schemas/values. |
| src/anthias_server/app/static/src/apps/manifest-form.ts | Implements generic manifest-driven settings form renderer. |
| src/anthias_server/app/static/src/apps/location-map.ts | Adds Leaflet+OSM map widget for location settings. |
| src/anthias_server/app/static/src/apps/launch-url.ts | Adds RFC6570 {?…} launch URL builder used by add/edit flows. |
| src/anthias_server/app/static/src/apps/launch-url.test.ts | Adds Bun tests for launch URL expansion behavior. |
| src/anthias_server/app/static/src/apps/catalog.ts | Fetches store index + manifests client-side with graceful degradation. |
| src/anthias_server/app/static/src/apps.ts | Alpine glue for Apps tab browsing/config/install + edit-time reconfigure. |
| src/anthias_server/app/static/src/alpinejs.d.ts | Adds minimal ambient Alpine typings for strict TS builds. |
| src/anthias_server/app/static/sass/anthias.scss | Imports Leaflet CSS into the bundled styles. |
| src/anthias_server/app/static/sass/_styles.scss | Adds scoped styling for catalog grid, config form, and map widget. |
| src/anthias_server/app/helpers.py | Threads APP_STORE_INDEX_URL into base template context. |
| package.json | Adds leaflet and @types/leaflet. |
| bun.lock | Locks Leaflet and its type dependencies. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The integration test test_add_asset_via_url fills input[name="uri"] and submits form[action*="assets/new"]; the new Apps form reused both, so the selectors matched two elements (Playwright strict-mode failure). - Rename the Apps form's hidden uri/values to app_uri/app_values (matching the edit path), leaving one name="uri" input (the URL tab) - Move the install endpoint from /assets/new-app/ to /apps/install/ so its submit button no longer matches form[action*="assets/new"] Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- Default APP_STORE_INDEX_URL to production signage-apps.com (env still
overridable to staging / a mirror)
- Harden _host_allowed: match on the port/userinfo-free hostname and
enforce the dotted boundary even for a dot-less configured suffix
- location-map: emit coordinates as numbers (not toFixed strings) so a
{lat,lng} value compares cleanly against a numeric schema default
- location-map: return a teardown (disconnect ResizeObserver + remove
map); the form renderer runs it on re-render so repeated open/select
cycles don't leak detached Leaflet maps (via teardownHost)
- manifest-form: give each timezone <datalist> a unique id to avoid
duplicate ids across the Add/Edit config hosts
- test: assert the configured APP_STORE_INDEX_URL reached the page
rather than a hard-coded literal; add _host_allowed + port-in-URL
coverage
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- Reword the app-store fetch errors to reference the network/browser connection, not "the device's connection" (the fetch is client-side) - Fix the appsTab init comment: the pane lazy-loads via x-effect, not x-init - test: pass the uppercase hostname as-is so the case-insensitivity assertion actually exercises _host_allowed's internal lower-casing Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
This was referenced Jul 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Issues Fixed
Adds a new Apps section to the Add Asset flow: a catalog of pre-defined signage apps from the app store (
app-store.srly.io/signage-apps.com), configured from each app's published manifest. Not tied to an existing issue.Description
Each app publishes a self-describing manifest (
/.well-known/signage-app.json) with asettingsJSON Schema and alaunchURL template. The store index lists them all. This PR consumes those manifests directly:expand-template.js+config-manifest.js: text / select / toggle / number / url / timezone widgets, plus a Leaflet + OpenStreetMap location-map (no Google Maps, no API key — bundled locally, tiles fetched in the browser only).webpageasset stamped withmetadata.app(id, manifest URL/version, chosen values). Editing an app reopens the same config form seeded from the saved values and rebuilds the launch URL.assets_create_appendpoint + app-edit path inassets_update, both guarded by a store-host allowlist.playback.refreshIntervalSmaps to the viewer's existing per-asset auto-refresh.APP_STORE_INDEX_URLsetting defaults to the production store index (signage-apps.com/manifest.json) and stays env-overridable (staging / a self-hosted mirror).Also: added an ambient
alpinejs.d.ts(the package ships no types) and modernizedtsconfig(moduleResolution: bundler, dropped dead React-erapaths).Live walkthrough (x86 dev host, headless Chromium against the running server + staging catalog): all 12 apps render with 0 JS errors; install and edit round-trips verified; found and fixed 3 bugs during the audit — spurious location emit (no-default apps now stay unset for IP auto-detect), a blank map (Leaflet collapsed the canvas to 0 height), and stale config fields when switching to a no-settings app.
Known limitation: the generic renderer skips array widgets (World Clock's repeatable city list) — same as the app store, which uses a bespoke form there. World Clock installs with its default cities; other settings are configurable.
Checklist
test_template_views.pygreen.)mvip@…141: built the arm64 server image from this branch, deployed it to the device, installed an app through the on-device UI (12-app catalog loaded,clock.srly.iobuilt), and confirmed the viewer rendered it — logs showShowing asset Digital Clock (webpage)/Current url is https://clock.srly.io/on eglfs. eglfs has no compositor grab, so no pixel screenshot; verified via viewer logs + the QtWebEngine process + the on-device UI screenshot. Restored afterward.)jdoe@…208: built + deployed the x86 server image from this branch, installed an app on-device, and grim-captured the rendered display showing the clock app live (clock.srly.io). Restored afterward.)🤖 Generated with Claude Code