From cf0d812ec4d34ce61b64ebd157308107083a8bc9 Mon Sep 17 00:00:00 2001 From: Bob Lee Date: Fri, 31 Jul 2026 05:20:54 -0700 Subject: [PATCH] fix(market): label installed MiniApps with their marketplace release An installed MiniApp card showed `v{app.version}`, but that counter is the local edit counter, which `build_created_app` always starts at 1. Installing release v2 from the marketplace therefore rendered as "v1", reading as if the install had silently fetched an older release. The download itself was correct: the client requests `/listings/{slug}/releases/{n}/download` for the listing's latest release and rejects any package whose sha256, size, or permissions do not match the reviewed release. Add `miniapp_market_installed_origins`, which returns the marketplace origin of every installed app, and let the gallery label market-installed apps with the release number they actually carry. Apps from anywhere else keep the local counter. Also collapse the listing detail's version history to the newest release, with a toggle for the older ones, so a listing with several releases no longer presents them as equally current. --- .../desktop/src/api/miniapp_market_api.rs | 58 +++++++++++++++---- .../src/api/remote_workspace_policy.rs | 4 ++ src/apps/desktop/src/lib.rs | 1 + .../miniapps/components/MiniAppCard.tsx | 10 +++- .../miniapps/hooks/useMiniAppCatalogSync.ts | 10 +++- .../app/scenes/miniapps/miniAppStore.test.ts | 29 ++++++++++ .../src/app/scenes/miniapps/miniAppStore.ts | 18 ++++++ .../utils/loadInstalledMarketOrigins.ts | 25 ++++++++ .../miniapps/views/MiniAppGalleryView.tsx | 15 ++++- .../miniapps/views/MiniAppMarketView.scss | 17 ++++++ .../miniapps/views/MiniAppMarketView.tsx | 23 +++++++- .../views/miniAppReleaseHistory.test.ts | 40 +++++++++++++ .../miniapps/views/miniAppReleaseHistory.ts | 28 +++++++++ .../api/service-api/MiniAppMarketAPI.ts | 14 +++++ .../src/locales/en-US/scenes/miniapp.json | 2 + .../src/locales/zh-CN/scenes/miniapp.json | 2 + .../src/locales/zh-TW/scenes/miniapp.json | 2 + 17 files changed, 281 insertions(+), 17 deletions(-) create mode 100644 src/web-ui/src/app/scenes/miniapps/utils/loadInstalledMarketOrigins.ts create mode 100644 src/web-ui/src/app/scenes/miniapps/views/miniAppReleaseHistory.test.ts create mode 100644 src/web-ui/src/app/scenes/miniapps/views/miniAppReleaseHistory.ts diff --git a/src/apps/desktop/src/api/miniapp_market_api.rs b/src/apps/desktop/src/api/miniapp_market_api.rs index 304cb4726b..d9c112d884 100644 --- a/src/apps/desktop/src/api/miniapp_market_api.rs +++ b/src/apps/desktop/src/api/miniapp_market_api.rs @@ -21,6 +21,7 @@ use bitfun_services_integrations::miniapp_market::{ RatingAggregate, ValidatedMarketPackage, }; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::path::{Path, PathBuf}; use tauri::{AppHandle, Emitter, Manager, State, WebviewWindow}; use tokio::io::AsyncWriteExt; @@ -248,15 +249,7 @@ pub async fn miniapp_market_installed_status( .await .map_err(|error| error.to_string())?; for app in apps { - let Some(metadata) = state - .miniapp_manager - .load_customization_metadata(&app.id) - .await - .map_err(|error| error.to_string())? - else { - continue; - }; - let Some(origin) = metadata.origin.market else { + let Some((origin, local_override)) = load_market_origin(&state, &app.id).await? else { continue; }; if origin.listing_id == request.listing_id { @@ -265,13 +258,58 @@ pub async fn miniapp_market_installed_status( app_version: app.version, permissions: app.permissions, origin, - local_override: metadata.local_override, + local_override, })); } } Ok(None) } +/// Marketplace origins of every installed MiniApp, keyed by local app id. +/// +/// The local `MiniApp::version` counter tracks edits to the installed copy and +/// is deliberately independent from the marketplace release number, so any +/// surface that wants to name the release a user installed has to read it from +/// the origin instead of from the app itself. +#[tauri::command] +pub async fn miniapp_market_installed_origins( + state: State<'_, AppState>, +) -> Result, String> { + let apps = state + .miniapp_manager + .list() + .await + .map_err(|error| error.to_string())?; + let mut origins = HashMap::new(); + for app in apps { + if let Some((origin, _)) = load_market_origin(&state, &app.id).await? { + origins.insert(app.id, origin); + } + } + Ok(origins) +} + +/// Reads an app's marketplace origin plus its local-override flag, or `None` +/// when the app did not come from the marketplace. +async fn load_market_origin( + state: &AppState, + app_id: &str, +) -> Result, String> { + let Some(metadata) = state + .miniapp_manager + .load_customization_metadata(app_id) + .await + .map_err(|error| error.to_string())? + else { + return Ok(None); + }; + let local_override = metadata.local_override; + Ok(metadata + .origin + .market + .map(|origin| (origin, local_override))) +} + #[tauri::command] pub async fn miniapp_market_install( state: State<'_, AppState>, diff --git a/src/apps/desktop/src/api/remote_workspace_policy.rs b/src/apps/desktop/src/api/remote_workspace_policy.rs index 23dae7d282..d04b4b7018 100644 --- a/src/apps/desktop/src/api/remote_workspace_policy.rs +++ b/src/apps/desktop/src/api/remote_workspace_policy.rs @@ -1149,6 +1149,10 @@ pub const REMOTE_WORKSPACE_COMMAND_POLICIES: &[(&str, RemoteWorkspacePolicy)] = RemoteWorkspacePolicy::LocalOnly, ), ("miniapp_market_install", RemoteWorkspacePolicy::LocalOnly), + ( + "miniapp_market_installed_origins", + RemoteWorkspacePolicy::LocalOnly, + ), ( "miniapp_market_installed_status", RemoteWorkspacePolicy::LocalOnly, diff --git a/src/apps/desktop/src/lib.rs b/src/apps/desktop/src/lib.rs index 181c4878f8..cdf42d1c3e 100644 --- a/src/apps/desktop/src/lib.rs +++ b/src/apps/desktop/src/lib.rs @@ -1679,6 +1679,7 @@ pub async fn run() { api::miniapp_market_api::miniapp_market_list_submissions, api::miniapp_market_api::miniapp_market_withdraw_submission, api::miniapp_market_api::miniapp_market_installed_status, + api::miniapp_market_api::miniapp_market_installed_origins, api::miniapp_market_api::miniapp_market_install, api::miniapp_market_api::miniapp_market_import_package, api::miniapp_market_api::miniapp_market_inspect_package, diff --git a/src/web-ui/src/app/scenes/miniapps/components/MiniAppCard.tsx b/src/web-ui/src/app/scenes/miniapps/components/MiniAppCard.tsx index 2bda9cc72d..7132a385a5 100644 --- a/src/web-ui/src/app/scenes/miniapps/components/MiniAppCard.tsx +++ b/src/web-ui/src/app/scenes/miniapps/components/MiniAppCard.tsx @@ -12,6 +12,13 @@ interface MiniAppCardProps { index?: number; isRunning?: boolean; isCustomizing?: boolean; + /** + * Marketplace release this copy was installed from, when it came from the + * marketplace. It takes over the version label because `app.version` is a + * local edit counter that always starts at 1 — showing it made a freshly + * installed v2 read as "v1". + */ + marketReleaseNumber?: number; onOpenDetails: (app: MiniAppMeta) => void; onOpen: (id: string) => void; onDelete: (id: string) => void; @@ -26,6 +33,7 @@ const MiniAppCard: React.FC = ({ index = 0, isRunning = false, isCustomizing = false, + marketReleaseNumber, onOpenDetails, onOpen, onDelete, @@ -82,7 +90,7 @@ const MiniAppCard: React.FC = ({
{localizedName} - v{app.version} + v{marketReleaseNumber ?? app.version}
{(isRunning || isCustomizing) && (