From 439b55a23601fc612191fd9bf5361d65a6d854fe Mon Sep 17 00:00:00 2001 From: archipelago Date: Wed, 30 Sep 2026 12:20:29 -0400 Subject: [PATCH] Use manifest names for new services and document release acceptance --- CHANGELOG.md | 9 +++++++++ docs/next-release-20260930.md | 9 ++++++++- neode-ui/src/views/apps/AppCard.vue | 7 ++----- neode-ui/src/views/apps/AppIconGrid.vue | 6 ++---- neode-ui/src/views/apps/__tests__/appsConfig.test.ts | 12 +++++++++++- neode-ui/src/views/apps/appsConfig.ts | 9 +++++++++ 6 files changed, 41 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72057cc3..b21b7b82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +- Kept installed apps visible through restarts and hard refreshes, and delayed app launches until their web interface is ready. +- Made Bitcoin version selection readable and usable in the ThinkPad kiosk, above the pruning settings. +- Restored GitWorkshop build files in installation/update payloads and made slow image-pull progress clearer. +- Fixed same-node Gitea access from Portainer, with persistent runtime migration, state backups and recovery after failed restarts. +- Preserved Gitea configuration and SSH operation during fresh setup and upgrades. +- Improved paid-file delivery, saved-file permissions and repeat-download compatibility; verified Tor-only payment with change, rejection refunds and free repeat downloads. +- Added a headless Angor Indexer service using the existing Mempool/ElectrumX stack, and an optional separate Angor relay. +- Prevented manifest command arguments containing apostrophes from being corrupted in generated services. + ## v1.8.21-alpha (2026-09-30) - Fixed Bitcoin and other containers being forcibly stopped after ten seconds during managed updates and restarts. diff --git a/docs/next-release-20260930.md b/docs/next-release-20260930.md index 36031d40..2c730fbd 100644 --- a/docs/next-release-20260930.md +++ b/docs/next-release-20260930.md @@ -35,7 +35,7 @@ See the Framework incident and 1.8.21 execution records for evidence/limits. | App disappearance/readiness | Durable inventory and safe lifecycle repair; delayed HTTP and desktop/mobile hard-refresh checks passed | Final lifecycle/reboot gate on candidate | | X250 GitWorkshop/Nginx | Missing build contexts restored, dependency/build checks and live UI passed; Nginx slow pull diagnosed; truthful progress label | Verify both artifact payloads contain all build contexts | | PRs 161/162 | Reviewed, repaired, merged/closed normally; combined regression suite passed | Funded Tor-only candidate purchase, retained change, refund, Files bytes and cached repeat passed; include in signed artifacts | -| Gitea/Portainer | Root cause confirmed; source network/backup/retry/catalog changes; real X250 routing repair and restart verified; private Git, SSH, LFS, registry and browser fixture checks passed | Automatic migration, scratch restore, failed-start recovery and reverse installation order passed. Operator confirms production site works through Portainer; still need final candidate reboot convergence and signed delivery | +| Gitea/Portainer | Root cause confirmed; source network/backup/retry/catalog changes; real X250 routing repair and restart verified; private Git, SSH, LFS, registry and browser fixture checks passed | Automatic migration, scratch restore, failed-start recovery and reverse installation order passed. Operator confirms production site works through Portainer; production host reboot also preserved network/Git/Compose access; final candidate delivery and release checks remain | | Angor headless store service | Implemented standard Mempool adapter and separate optional relay, official logo, headless store entries and declarative dependency guard. API security/outage/DNS tests and five relay lifecycle cycles passed | Final candidate prerequisite/install acceptance, management restart/reboot checks and signed catalog delivery; real indexing on dev waits for Bitcoin sync | Durable payment receipts after a lost seller response remain a separately @@ -113,3 +113,10 @@ whitespace-free command arguments containing apostrophes lost those characters in the generated service. The renderer now quotes these arguments and environment values; the updated isolated backend suite passed (1,607 passed, four opt-in tests ignored), and the final candidate rebuild is in progress. Do not tag a release before this live regression is verified. + +The production Portainer host subsequently rebooted after the routing repair. +A post-boot probe from the actual Portainer namespace again verified the Git +smart-HTTP response type, current branch ref and Compose contents. Its +slirp4netns route and all production app containers survived. The temporary +Portainer fixture was absent. This verifies the repaired production route +across reboot; it does not substitute for final new-runtime delivery checks. diff --git a/neode-ui/src/views/apps/AppCard.vue b/neode-ui/src/views/apps/AppCard.vue index 094ec53a..63bf587c 100644 --- a/neode-ui/src/views/apps/AppCard.vue +++ b/neode-ui/src/views/apps/AppCard.vue @@ -214,7 +214,7 @@ import { computed } from 'vue' import { useI18n } from 'vue-i18n' import type { PackageDataEntry } from '@/types/api' import { - isWebOnlyApp, opensInTab, canLaunch, launchBlockedReason, resolveAppIcon, + isWebOnlyApp, opensInTab, canLaunch, launchBlockedReason, resolveAppIcon, resolveAppTitle, getStatusClass, getStatusLabel, handleImageError, } from './appsConfig' import { getCuratedAppList } from '../discover/curatedApps' @@ -255,10 +255,7 @@ const isWebOnly = computed(() => isWebOnlyApp(props.id)) // Enrich from marketplace when backend data is sparse (e.g. during install) const curated = computed(() => curatedMap.get(props.id)) -const title = computed(() => { - const t = props.pkg.manifest?.title - return (t && t !== props.id) ? t : (curated.value?.title || t || props.id) -}) +const title = computed(() => resolveAppTitle(props.id, props.pkg, curated.value?.title)) const description = computed(() => { const d = props.pkg.manifest?.description?.short return (d && d !== 'Installing...') ? d : (curated.value?.description || d || '') diff --git a/neode-ui/src/views/apps/AppIconGrid.vue b/neode-ui/src/views/apps/AppIconGrid.vue index 9155a0b2..ef5d65fd 100644 --- a/neode-ui/src/views/apps/AppIconGrid.vue +++ b/neode-ui/src/views/apps/AppIconGrid.vue @@ -132,7 +132,7 @@ import type { AppCredential, AppCredentialsResponse, PackageDataEntry } from '@/ import { rpcClient } from '@/api/rpc-client' import { resolveAppUrl } from '@/views/appSession/appSessionConfig' import { resolveAppCredentials } from './appCredentials' -import { canLaunch, handleImageError, isWebsitePackage, opensInTab, resolveAppIcon, resolveRuntimeLaunchUrl, WEB_ONLY_APP_URLS } from './appsConfig' +import { canLaunch, handleImageError, isWebsitePackage, opensInTab, resolveAppIcon, resolveAppTitle, resolveRuntimeLaunchUrl, WEB_ONLY_APP_URLS } from './appsConfig' import { getCuratedAppList } from '../discover/curatedApps' const ITEMS_PER_PAGE = 16 // 4 columns x 4 rows @@ -194,9 +194,7 @@ const pages = computed(() => { }) function getTitle(id: string, pkg: PackageDataEntry): string { - const t = pkg.manifest?.title - if (t && t !== id) return t - return curatedMap.get(id)?.title || t || id + return resolveAppTitle(id, pkg, curatedMap.get(id)?.title) } function getIcon(id: string, pkg: PackageDataEntry): string { diff --git a/neode-ui/src/views/apps/__tests__/appsConfig.test.ts b/neode-ui/src/views/apps/__tests__/appsConfig.test.ts index 6443684f..6a967a05 100644 --- a/neode-ui/src/views/apps/__tests__/appsConfig.test.ts +++ b/neode-ui/src/views/apps/__tests__/appsConfig.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' import { ref } from 'vue' import { PackageState, type PackageDataEntry } from '@/types/api' -import { APP_CATEGORY_MAP, canLaunch, filterEntriesForTab, hasFrontendUi, isServiceContainer, isServicePackage, isWebsitePackage, isAppReadyForLaunch, launchBlockedReason, resolveAppIcon, useCategoriesWithApps, DEFAULT_APP_ICON } from '../appsConfig' +import { APP_CATEGORY_MAP, canLaunch, filterEntriesForTab, hasFrontendUi, isServiceContainer, isServicePackage, isWebsitePackage, isAppReadyForLaunch, launchBlockedReason, resolveAppIcon, resolveAppTitle, useCategoriesWithApps, DEFAULT_APP_ICON } from '../appsConfig' function makePkg(id: string, title: string, category: string): PackageDataEntry { return { @@ -214,3 +214,13 @@ describe('HTTP readiness independent of container health', () => { expect(isAppReadyForLaunch(pkg)).toBe(true) }) }) + +describe('manifest-generated service names', () => { + it('gives a new headless service its declared name without a hardcoded curated entry', () => { + const pkg = makePkg('angor-relay', 'angor-relay', 'money') + expect(resolveAppTitle('angor-relay', pkg)).toBe('Angor Relay') + pkg.manifest.title = 'My project relay' + expect(resolveAppTitle('angor-relay', pkg)).toBe('My project relay') + expect(resolveAppTitle('unknown-service', makePkg('unknown-service', 'unknown-service', ''))).toBe('unknown-service') + }) +}) diff --git a/neode-ui/src/views/apps/appsConfig.ts b/neode-ui/src/views/apps/appsConfig.ts index 95a1b68e..235d1c0b 100644 --- a/neode-ui/src/views/apps/appsConfig.ts +++ b/neode-ui/src/views/apps/appsConfig.ts @@ -4,6 +4,7 @@ import type { Ref } from 'vue' import { computed } from 'vue' import { PackageState, type PackageDataEntry } from '@/types/api' import { matchPageScheme, resolveAppUrl } from '../appSession/appSessionConfig' +import { GENERATED_APP_TITLES } from '../appSession/generatedAppSessionConfig' import { portIsGateFronted } from '../discover/curatedApps' import { isAutoTabApp } from '@/utils/autoTabApps' import { @@ -13,6 +14,14 @@ import { export type AppsTab = 'apps' | 'websites' | 'services' +/** Use manifest-generated names when a new service has only its ID in runtime state. */ +export function resolveAppTitle(id: string, pkg: PackageDataEntry, curatedTitle?: string): string { + const title = pkg.manifest?.title + if (title && title !== id) return title + return curatedTitle || GENERATED_APP_TITLES[id] || title || id +} + + // Re-exported for every existing caller — the canon moved to serviceNames.ts // so the App Store's catalog merge can share it without a circular import. export const SERVICE_NAMES = SHARED_SERVICE_NAMES