fix(apps): classify by declared UI — UI apps to My Apps, headless to Websites (#45)
Per the rule that only front-end apps with a UI belong in "My Apps" (databases/backends/headless go to Websites), make the manifest's interfaces.main.ui the deciding signal. isWebsitePackage now treats any package that declares a UI as an app even when it isn't in the curated APP_CATEGORY_MAP, and falls through headless LAN-reachable packages to Websites. Additive — service-by-name infra and curated known apps are unchanged, so no currently-correct app moves. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
56752ebfc0
commit
d2d2b9dd68
@ -1,7 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
import { PackageState, type PackageDataEntry } from '@/types/api'
|
||||
import { canLaunch, filterEntriesForTab, isServiceContainer, isServicePackage, launchBlockedReason, resolveAppIcon, useCategoriesWithApps } from '../appsConfig'
|
||||
import { canLaunch, filterEntriesForTab, hasFrontendUi, isServiceContainer, isServicePackage, isWebsitePackage, launchBlockedReason, resolveAppIcon, useCategoriesWithApps } from '../appsConfig'
|
||||
|
||||
function makePkg(id: string, title: string, category: string): PackageDataEntry {
|
||||
return {
|
||||
@ -82,6 +82,22 @@ describe('appsConfig service filtering', () => {
|
||||
expect(resolveAppIcon('gitea', pkg)).toBe('/assets/img/app-icons/gitea.svg')
|
||||
})
|
||||
|
||||
it('classifies an unknown app by whether its manifest declares a UI (#45)', () => {
|
||||
// Headless: a LAN address but no declared UI → Website.
|
||||
const headless = makePkg('some-backend', 'Some Backend', 'other')
|
||||
headless.installed = { 'interface-addresses': { main: { 'lan-address': 'http://localhost:9000' } } } as unknown as PackageDataEntry['installed']
|
||||
expect(hasFrontendUi(headless)).toBe(false)
|
||||
expect(isWebsitePackage('some-backend', headless)).toBe(true)
|
||||
|
||||
// Front-end app: declares interfaces.main.ui → My Apps even when not in the
|
||||
// curated category map.
|
||||
const uiApp = makePkg('some-ui-app', 'Some UI App', 'other')
|
||||
;(uiApp.manifest as unknown as Record<string, unknown>).interfaces = { main: { ui: 'http://localhost:9001' } }
|
||||
uiApp.installed = { 'interface-addresses': { main: { 'lan-address': 'http://localhost:9001' } } } as unknown as PackageDataEntry['installed']
|
||||
expect(hasFrontendUi(uiApp)).toBe(true)
|
||||
expect(isWebsitePackage('some-ui-app', uiApp)).toBe(false)
|
||||
})
|
||||
|
||||
it('explains that Fedimint waits for Bitcoin sync before Guardian starts', () => {
|
||||
const pkg = makePkg('fedimint', 'Fedimint', 'money')
|
||||
pkg.state = PackageState.Starting
|
||||
|
||||
@ -78,10 +78,25 @@ export function isKnownApp(id: string, pkg?: PackageDataEntry): boolean {
|
||||
return !!(APP_CATEGORY_MAP[id] || (manifestId && APP_CATEGORY_MAP[manifestId]) || isWebOnlyApp(id))
|
||||
}
|
||||
|
||||
// True when the package's manifest declares a front-end UI interface. This is
|
||||
// the authoritative "is this a user-facing app?" signal (#45): apps with a UI
|
||||
// belong in "My Apps", while headless services (databases, backends, workers)
|
||||
// declare no UI and belong in "Websites"/"Services".
|
||||
export function hasFrontendUi(pkg?: PackageDataEntry): boolean {
|
||||
return !!pkg?.manifest?.interfaces?.main?.ui
|
||||
}
|
||||
|
||||
export function isWebsitePackage(id: string, pkg?: PackageDataEntry): boolean {
|
||||
if (isInternalToolingPackage(id, pkg)) return false
|
||||
// Headless infra (databases/backends/companions) keyed by container name are
|
||||
// services regardless of any stray UI string.
|
||||
if (isServicePackage(id, pkg)) return true
|
||||
// A declared front-end UI is the deciding factor: it's an app, not a website.
|
||||
if (hasFrontendUi(pkg)) return false
|
||||
// Curated known apps stay in My Apps even if their manifest predates the UI
|
||||
// interface field.
|
||||
if (isKnownApp(id, pkg)) return false
|
||||
// Fallback: reachable on the LAN but declares no UI → treat as a website.
|
||||
return !!pkg && !!runtimeLanAddress(pkg)
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user