From caed02e6d155d76602a5d3d82b6537b45bf91fa8 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 06:48:08 +0000 Subject: [PATCH 1/2] fix(app-shell): a local install's Details page offers its local menu on a marketplace-off runtime that mounts install-local (objectui#11627) On an offline boot (OS_CLOUD_URL=off) the runtime config reports features.marketplace false and features.installLocal true. Installed Apps links each local install's Details to MarketplacePackagePage, which returned the MarketplaceDisabled notice before anything else, so the local re-seed / purge menu was unreachable and no install-local request was ever issued. The marketplace-off branch now draws, for an admin whose package is a local install, the package header and the same local menu above the unchanged notice. The local install is matched on the ledger entry's packageId (the id Installed Apps builds the link from); the menu acts on its manifest id. The catalog fetch, cloud-installation probe, install-to-cloud CTA, readme and version list stay refused. listLocalInstalls is no longer gated on features.marketplace, since its answer now has a consumer there. The objectui#5620 case that pinned "never fires on a marketplace-off runtime, even for an admin with installLocal on" pinned the branch this removes and is replaced by the two marketplace-off cases where the answer still has no consumer (installLocal off; a refused viewer). Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL Co-authored-by: Claude --- .../11627-offline-local-package-menu.md | 11 + .../marketplace/MarketplacePackagePage.tsx | 237 ++++++++------ .../MarketplacePackagePage-5620.test.tsx | 24 +- ...ackagePage.offlineLocalMenu-11627.test.tsx | 298 ++++++++++++++++++ 4 files changed, 480 insertions(+), 90 deletions(-) create mode 100644 .changeset/11627-offline-local-package-menu.md create mode 100644 packages/app-shell/src/console/marketplace/__tests__/MarketplacePackagePage.offlineLocalMenu-11627.test.tsx diff --git a/.changeset/11627-offline-local-package-menu.md b/.changeset/11627-offline-local-package-menu.md new file mode 100644 index 0000000000..2f6f9c1fe1 --- /dev/null +++ b/.changeset/11627-offline-local-package-menu.md @@ -0,0 +1,11 @@ +--- +'@object-ui/app-shell': patch +--- + +On a runtime with no marketplace that still mounts install-local (an offline boot, `OS_CLOUD_URL=off`), a local install's Details page now offers its local menu: re-seed sample data, purge sample data and uninstall from this runtime (objectui#11627). + +Installed Apps links each local install's Details to the marketplace package page. That page answered a marketplace-off runtime with the "App Marketplace is turned off" notice and nothing else, so the local menu below that check could not be reached and no install-local request was ever issued. Now, when the runtime config reports `features.installLocal` and the viewer is an admin, the page draws the package's header (manifest id and installed version) and its local menu above the same notice. The menu issues the same `POST /api/v1/marketplace/install-local/MANIFEST_ID/reseed-sample-data` and `.../purge-sample-data` calls as on a marketplace-on runtime. The page finds the install by the id Installed Apps put in the link (the ledger entry's `packageId`), and the menu acts on its manifest id. + +Everything that needs a marketplace stays refused as before: the page sends no catalog request and no cloud-installation probe, and it draws no install-to-cloud button, readme or version list. A viewer who is not an admin, a runtime that does not mount install-local, and a package that is not a local install all see the notice alone, as before. + +**Clause-②: no.** Nothing on the package entry changes: no export, prop, type member or i18n key is added. `MarketplacePackagePage` takes no props, and the strings it draws already existed. diff --git a/packages/app-shell/src/console/marketplace/MarketplacePackagePage.tsx b/packages/app-shell/src/console/marketplace/MarketplacePackagePage.tsx index 592a6c2375..e06fd02976 100644 --- a/packages/app-shell/src/console/marketplace/MarketplacePackagePage.tsx +++ b/packages/app-shell/src/console/marketplace/MarketplacePackagePage.tsx @@ -166,13 +166,13 @@ export function MarketplacePackagePage() { // with no marketplace proxy at all), so it stays its own check rather than // being folded into the other two predicates. if (!getRuntimeConfig().features.installLocal) return; - // Its only consumer is `localInstalls.find(...)` in the content branch - // below, which is unreachable whenever the page has already returned - // `MarketplaceDisabled` or (post-objectui#5583) `MarketplaceAccessDenied`. - // Firing anyway would be the same discarded-request class `2573ff434` - // closed for this page, on the flag that change was not about - // (objectui#5620). - if (!marketplaceEnabled) return; + // Its only consumer is the `localInstall` match below, read by the content + // branch AND by the marketplace-off local view (objectui#11627). Neither + // is drawn for a viewer refused the admin surface, so firing for one would + // be the same discarded-request class `2573ff434` closed for this page + // (objectui#5620). `marketplaceEnabled` is deliberately NOT a gate any + // more: on a runtime with no marketplace but a mounted install-local + // surface, this answer is what decides whether the local menu is drawn. if (!isAdmin) return; let cancelled = false; (async () => { @@ -180,7 +180,7 @@ export function MarketplacePackagePage() { if (!cancelled) setLocalInstalls(items); })(); return () => { cancelled = true; }; - }, [packageId, localResult, marketplaceEnabled, isAdmin]); + }, [packageId, localResult, isAdmin]); // Seed cloud-install state so the primary CTA renders as "Installed" on // first paint instead of inviting another install. @@ -555,6 +555,103 @@ export function MarketplacePackagePage() { } }; + // `features.installLocal` is the server's own answer to "is the install-local + // surface mounted here" -- the same read the effect above gates on, never a + // guess from the package's fields. + const supportsLocal = getRuntimeConfig().features.installLocal; + // Which local install, if any, this page is about. With a marketplace the + // catalog row names it, by manifest id. Without one there is no catalog row, + // and the route's id is the one Installed Apps built this link from + // (`entry.packageId`), so it is matched on that same field. Declared ahead of + // every early return: the local sample-data handlers above close over it. + const localInstall = (marketplaceEnabled + ? localInstalls.find((i) => !!data && i.manifestId === data.package.manifest_id) + : localInstalls.find((i) => i.packageId === packageId)) ?? null; + + // The local-install menu and the two status banners, drawn by both the + // catalog view and the marketplace-off local view below (objectui#11627). + const localInstalledBadge = localInstall && ( + + + ); + const localMenu = localInstall && ( + + + + + + + {sampleDataBusy === 'reseed' + ? + + {sampleDataBusy === 'purge' + ? + + + + + ); + const sampleDataNote = sampleDataMsg && ( +
+ {sampleDataMsg.ok ?
+ ); + // `suggestFor` is the manifest id whose suggested audience bindings a + // successful local INSTALL surfaces (ADR-0090 D5), or `null` where no install + // door is drawn and the only result this banner can carry is an uninstall's. + const localResultNote = (suggestFor: string | null) => localResult && ( +
+ {localResult.ok ?
+ ); + // A CONFIGURATION CONCLUSION, not a load failure -- the same informational // state the catalog page renders, so the two pages stop disagreeing about the // same runtime (`2573ff434`). Reached by a pasted or bookmarked package URL, @@ -565,7 +662,45 @@ export function MarketplacePackagePage() { // `features.marketplace` is public runtime config that any client already // reads. Telling an unprivileged operator they lack permission for a surface // that exists for nobody is the same misdirection this fix removes. - if (!marketplaceEnabled) return ; + // + // objectui#11627 — that answer covers the REMOTE half of the page only. A + // runtime with no marketplace can still mount the install-local surface (an + // offline boot, `OS_CLOUD_URL=off`), and Installed Apps links each local + // install's Details here; the notice used to be all that page showed, so + // local reseed / purge had no way in. Now an admin whose package IS a local + // install also gets its header and local menu, ABOVE the same notice: the + // catalog fetch, the install-to-cloud CTA, the readme and the version list + // stay refused exactly as before, and every other viewer sees the notice + // alone. The local half is ADDED once `listLocalInstalls` answers, so the + // notice is never painted and then retracted. + if (!marketplaceEnabled) { + if (!(supportsLocal && isAdmin && (localInstall || localResult))) return ; + return ( +
+ {localInstall && ( +
+ +
+

{localInstall.manifestId}

+
+ {localInstalledBadge} +
+
+
+ {localMenu} +
+
+ )} + {sampleDataNote} + {localResultNote(null)} + +
+ ); + } // Ahead of BOTH the loading and the load-failure branches below // (objectui#5583): authorization is not a function of whether the fetch @@ -630,7 +765,6 @@ export function MarketplacePackagePage() { const pkg = data.package; const loc = localizePackage(pkg as any, language); const latestVersion = pkg.latest_version?.version ?? data.versions[0]?.version ?? null; - const localInstall = localInstalls.find((i) => i.manifestId === pkg.manifest_id) ?? null; // PD4 (ADR-0025 §3.11): code-bearing packages must disclose + be acknowledged. const containsCode = !!pkg.latest_version?.contains_code; // ADR-0010 version lifecycle: installed cloud env is on an OLDER version than @@ -643,7 +777,6 @@ export function MarketplacePackagePage() { && !!latestVersion && isNewerVersion(latestVersion, cloudInstalledVersion); - const supportsLocal = getRuntimeConfig().features.installLocal; const primaryDisabled = !latestVersion || installingLocal || installing || (!supportsLocal && !!cloudInstalledVersion && !cloudUpdateAvailable); const primaryAction = supportsLocal ? { @@ -713,12 +846,7 @@ export function MarketplacePackagePage() { )} {categoryLabel && {categoryLabel}} {pkg.license && {pkg.license}} - {localInstall && ( - - - )} + {localInstalledBadge} {!localInstall && cloudInstalledVersion && (