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) && (