From b7a9588be5f5ab1e2900581d42ca60e7d75c06a3 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Wed, 30 Sep 2026 15:13:05 +0800 Subject: [PATCH 1/3] fix(remote): align workspace names across mobile surfaces --- src/apps/desktop/src/api/dto.rs | 40 +++- .../entry/src/main/ets/i18n/EnUsMessages.ets | 6 + .../entry/src/main/ets/i18n/ZhCnMessages.ets | 6 + .../pages/components/SidebarDeviceGroup.ets | 58 ++++-- .../components/SidebarWorkspacePicker.ets | 178 +++++++++++++++--- .../components/SidebarWorkspaceSection.ets | 9 +- .../pages/components/WorkspaceToolsPanel.ets | 27 +-- .../viewmodel/WorkspaceToolsViewModel.ets | 6 +- .../ets/services/RemoteResponseMapper.ets | 50 ++++- .../core/src/service/workspace/service.rs | 5 + .../core/src/service_agent_runtime.rs | 63 +++++-- .../src/services/RemoteSessionManager.ts | 33 +++- .../src/services/workspaceIdentity.ts | 29 ++- 13 files changed, 428 insertions(+), 82 deletions(-) diff --git a/src/apps/desktop/src/api/dto.rs b/src/apps/desktop/src/api/dto.rs index f5f973940d..242c1bce1f 100644 --- a/src/apps/desktop/src/api/dto.rs +++ b/src/apps/desktop/src/api/dto.rs @@ -127,7 +127,7 @@ impl WorkspaceInfoDto { Self { id: info.id.clone(), - name: info.name.clone(), + name: workspace_display_name(info), root_path, workspace_type: WorkspaceTypeDto::from_workspace_type(&info.workspace_type), workspace_kind: WorkspaceKindDto::from_workspace_kind(&info.workspace_kind), @@ -161,6 +161,44 @@ impl WorkspaceInfoDto { } } +fn workspace_display_name( + info: &openbitfun_core::service::workspace::manager::WorkspaceInfo, +) -> String { + let name = info.name.trim(); + let normalized = name.to_ascii_lowercase(); + if info.workspace_kind != WorkspaceKind::Remote + || (!name.is_empty() + && normalized != "unknown" + && normalized != "unknown project" + && name != "未知" + && name != "未知项目") + { + return info.name.clone(); + } + + if let Some(path_name) = info + .root_path + .file_name() + .and_then(|value| value.to_str()) + .map(str::trim) + .filter(|value| !value.is_empty()) + { + return path_name.to_string(); + } + for key in ["connectionName", "sshHost"] { + if let Some(value) = info + .metadata + .get(key) + .and_then(|value| value.as_str()) + .map(str::trim) + .filter(|value| !value.is_empty()) + { + return value.to_string(); + } + } + "Remote workspace".to_string() +} + impl WorkspaceIdentityDto { pub fn from_workspace_identity( identity: &openbitfun_core::service::workspace::manager::WorkspaceIdentity, diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/i18n/EnUsMessages.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/i18n/EnUsMessages.ets index c5d68bcc0e..c12aab39ed 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/i18n/EnUsMessages.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/i18n/EnUsMessages.ets @@ -19,6 +19,10 @@ export const EN_US_MESSAGES: [string, string][] = [ ['workspaceTools.download', 'Download'], ['workspaceTools.title', 'Tools'], ['workspaceTools.files', 'Files'], + ['sidebar.controlledDevice', 'Controlled device'], + ['sidebar.currentPath', 'Current path'], + ['sidebar.recentWorkspaces', 'Recent workspaces'], + ['sidebar.tapToSwitch', 'Tap to switch'], ['workspaceTools.selectTerminal', 'Select an existing terminal'], ['workspaceTools.terminal', 'Terminal'], ['workspaceTools.up', 'Parent'], @@ -197,6 +201,8 @@ export const EN_US_MESSAGES: [string, string][] = [ ['sidebar.addWorkspace', 'Add workspace'], ['sidebar.targetWorkspacePath', 'Path on controlled device'], ['sidebar.openWorkspacePath', 'Open workspace'], + ['sidebar.remoteRootBrowseOnly', 'The remote root is for browsing. Choose a specific workspace folder.'], + ['sidebar.browseRemoteRoot', 'Browse remote root'], ['sidebar.selectWorkspace', 'Select workspace'], ['sidebar.workspacesOffline', 'Disconnected'], ['sidebar.connectDesktop', 'Connect a computer'], diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/i18n/ZhCnMessages.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/i18n/ZhCnMessages.ets index 693364205d..acb6df1c56 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/i18n/ZhCnMessages.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/i18n/ZhCnMessages.ets @@ -19,6 +19,10 @@ export const ZH_CN_MESSAGES: [string, string][] = [ ['workspaceTools.download', '下载'], ['workspaceTools.title', '工具'], ['workspaceTools.files', '文件'], + ['sidebar.controlledDevice', '受控设备'], + ['sidebar.currentPath', '当前路径'], + ['sidebar.recentWorkspaces', '最近工作区'], + ['sidebar.tapToSwitch', '点击切换'], ['workspaceTools.selectTerminal', '选择已有终端'], ['workspaceTools.terminal', '终端'], ['workspaceTools.up', '上级目录'], @@ -197,6 +201,8 @@ export const ZH_CN_MESSAGES: [string, string][] = [ ['sidebar.addWorkspace', '添加工作区'], ['sidebar.targetWorkspacePath', '受控设备上的路径'], ['sidebar.openWorkspacePath', '打开工作区'], + ['sidebar.remoteRootBrowseOnly', '远程根目录仅用于浏览,请选择具体工作区目录'], + ['sidebar.browseRemoteRoot', '浏览远程根目录'], ['sidebar.selectWorkspace', '选择工作区'], ['sidebar.workspacesOffline', '未连接'], ['sidebar.connectDesktop', '连接电脑'], diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarDeviceGroup.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarDeviceGroup.ets index 6693a02eac..e61d1571ad 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarDeviceGroup.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarDeviceGroup.ets @@ -36,6 +36,7 @@ struct SidebarWorkspaceGroup { @Param connectionId: string = ''; /** Stable record ID of this row; empty only for rows from pre-ID hosts. */ @Param workspaceId: string = ''; + @Param workspaceKind: string = 'normal'; @Event onWorkspaceTools: (intent: WorkspaceToolsIntent) => void = (_intent: WorkspaceToolsIntent) => {}; @Param deviceId: string = ''; @Param path: string = ''; @@ -110,7 +111,7 @@ struct SidebarWorkspaceGroup { .hitTestBehavior(HitTestMode.None) } .width(24) - .height(46) + .height(54) .accessibilityText(this.expanded ? RemoteI18n.t('chat.showLess') : RemoteI18n.t('chat.showMore')) .onClick(() => { @@ -124,18 +125,31 @@ struct SidebarWorkspaceGroup { .width(21) .height(21) .hitTestBehavior(HitTestMode.None) - Text(this.name || this.basename(this.path)) - .fontSize(MobileDesignTypography.titleSmall.size) - .fontWeight(this.currentWorkspace ? FontWeight.Medium : FontWeight.Regular) - .fontColor(SIDEBAR_INK) - .layoutWeight(1) - .maxLines(1) - .textOverflow({ overflow: TextOverflow.Ellipsis }) - .wordBreak(WordBreak.BREAK_ALL) - .hitTestBehavior(HitTestMode.None) + Column({ space: 2 }) { + Text(this.workspaceLabel()) + .fontSize(MobileDesignTypography.titleSmall.size) + .fontWeight(this.currentWorkspace ? FontWeight.Medium : FontWeight.Regular) + .fontColor(SIDEBAR_INK) + .width('100%') + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .wordBreak(WordBreak.BREAK_ALL) + .hitTestBehavior(HitTestMode.None) + if (this.sshHost.trim().length > 0) { + Text(this.sshHost.trim()) + .fontSize(MobileDesignTypography.bodySmall.size) + .fontColor(SIDEBAR_MUTED) + .width('100%') + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .hitTestBehavior(HitTestMode.None) + } + } + .layoutWeight(1) + .alignItems(HorizontalAlign.Start) } .layoutWeight(1) - .height(46) + .height(54) .alignItems(VerticalAlign.Center) .onClick(() => { void this.toggleExpanded(); @@ -174,7 +188,7 @@ struct SidebarWorkspaceGroup { }) } .width('100%') - .height(46) + .height(54) .padding({ left: Math.max(0, this.bodyIndent - 4), right: 6 }) .alignItems(VerticalAlign.Center) .borderRadius(10) @@ -331,6 +345,20 @@ struct SidebarWorkspaceGroup { const index = normalized.lastIndexOf('/'); return index >= 0 ? normalized.substring(index + 1) : normalized; } + + private workspaceLabel(): string { + const path = this.path.trim(); + if (path === '/' && this.workspaceKind.toLowerCase() === 'remote') { + return RemoteI18n.t('sidebar.browseRemoteRoot'); + } + const value = this.name.trim(); + const host = this.sshHost.trim(); + const connection = this.connectionId.trim(); + if (value.length > 0 && value !== host && value !== connection) { + return value; + } + return this.basename(path); + } } /** @@ -405,10 +433,14 @@ export struct SidebarDeviceGroup { connectionId: this.currentWorkspace(row).remoteConnectionId || '', sshHost: this.currentWorkspace(row).remoteSshHost || '', workspaceId: this.currentWorkspace(row).workspaceId || '', + workspaceKind: this.currentWorkspace(row).workspaceKind || 'normal', onWorkspaceTools: this.onWorkspaceTools, deviceId: this.device.deviceId, path: this.currentWorkspace(row).path, - name: this.currentWorkspace(row).remoteSshHost?.trim() ? `${this.currentWorkspace(row).name} · ${(this.currentWorkspace(row).remoteSshHost || '').trim()}` : this.currentWorkspace(row).name, + // The device row already identifies the remote host. Keep the + // workspace row focused on the workspace name instead of appending + // an IP address to it. + name: this.currentWorkspace(row).name, sessions: this.sessionsFor(this.currentWorkspace(row)), selectedSessionId: this.selectedSessionId, runningSessionId: this.runningSessionId, diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarWorkspacePicker.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarWorkspacePicker.ets index d11d72dc6d..bf58de71c5 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarWorkspacePicker.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarWorkspacePicker.ets @@ -3,7 +3,7 @@ import { MobileDesignTypography } from '../../generated/MobileDesignTokens'; import { SavedRuntimeConnection, RecentWorkspaceEntry, RemoteWorkspaceIdentity } from '../../model/RemoteModels'; import { remoteWorkspaceIdentityMatches, remoteWorkspaceKey } from '../../services/RemoteSessionIdentity'; import { RemoteI18n } from '../../i18n/RemoteI18n'; -import { CARD, INK, LINE, MUTED, PAGE_BG, SOFT } from './Theme'; +import { CARD, CONTENT_ON_ACTION, INK, LINE, MUTED, PAGE_BG, PRIMARY_ACTION, SOFT, TRANSPARENT } from './Theme'; @ComponentV2 export struct SidebarWorkspacePicker { @@ -61,30 +61,124 @@ export struct SidebarWorkspacePicker { return options; } + private selectedConnectionIndex(): number { + if (this.connectionId.length === 0) return 0; + const index = this.savedConnections.findIndex((connection: SavedRuntimeConnection): boolean => connection.id === this.connectionId); + return index >= 0 ? index + 1 : 0; + } + + private visibleWorkspaces(): RecentWorkspaceEntry[] { + if (this.connectionId.length === 0) { + return this.workspaces.filter((item: RecentWorkspaceEntry): boolean => !this.isRemoteRoot(item)) + .concat(this.workspaces.filter((item: RecentWorkspaceEntry): boolean => this.isRemoteRoot(item))); + } + // A saved SSH profile is not a live connection. Do not keep showing the + // host catalog while the selected profile is still probing or unavailable. + if (this.tools.browseBusy || this.tools.browseError.length > 0 || + this.tools.browsePath.length === 0 || this.tools.browseEntries.length === 0) return []; + const connection = this.savedConnections.find((item: SavedRuntimeConnection): boolean => item.id === this.connectionId); + const rows = this.workspaces.filter((item: RecentWorkspaceEntry): boolean => + item.remoteConnectionId === this.connectionId || + (connection !== undefined && connection.host.length > 0 && item.remoteSshHost === connection.host) + ); + // Keep a persisted remote root available for browsing, but put concrete + // project folders first so the root cannot masquerade as the project. + return rows.filter((item: RecentWorkspaceEntry): boolean => !this.isRemoteRoot(item)) + .concat(rows.filter((item: RecentWorkspaceEntry): boolean => this.isRemoteRoot(item))); + } + + private isRemoteRoot(workspace: RecentWorkspaceEntry): boolean { + const path = (workspace.path || '').trim(); + return path === '/' && (workspace.workspaceKind || '').toLowerCase() === 'remote'; + } + + private canOpenTargetPath(): boolean { + const path = this.targetPath.trim(); + return !this.loading && path.length > 0 && !(this.connectionId.length > 0 && path === '/'); + } + + private openRemoteRootForBrowsing(workspace: RecentWorkspaceEntry): void { + const connection = workspace.remoteConnectionId || this.connectionId; + if (connection.length === 0) return; + this.connectionId = connection; + this.targetPath = '/'; + this.browsing = true; + this.onBrowse({ type: 'browse-workspace', path: '/', text: connection }); + } + + private workspaceTitle(workspace: RecentWorkspaceEntry): string { + if (this.isRemoteRoot(workspace)) return RemoteI18n.t('sidebar.browseRemoteRoot'); + return workspace.name || this.basename(workspace.path); + } + + private workspaceSubtitle(workspace: RecentWorkspaceEntry): string { + const connectionId = workspace.remoteConnectionId?.trim() || ''; + const savedConnection = connectionId.length > 0 ? + this.savedConnections.find((connection: SavedRuntimeConnection): boolean => connection.id === connectionId) : undefined; + const remoteLabel = savedConnection?.name?.trim() || workspace.remoteSshHost?.trim() || ''; + return remoteLabel.length > 0 ? `${remoteLabel} · ${workspace.path}` : workspace.path; + } + + private selectConnection(index: number): void { + this.connectionId = index > 0 ? this.savedConnections[index - 1].id : ''; + this.browsing = this.connectionId.length > 0; + if (this.connectionId.length > 0) { + this.targetPath = '/'; + this.onBrowse({ type: 'browse-workspace', path: '/', text: this.connectionId }); + } + } + build() { - Column() { + Column({ space: 0 }) { this.Header() Divider().color(LINE) - TextInput({ text: this.targetPath, placeholder: RemoteI18n.t('sidebar.targetWorkspacePath') }) - .onChange((value: string) => { this.targetPath = value; }) - .enabled(!this.loading) - Select(this.connectionOptions()) - .selected(0) - .onSelect((index: number) => { this.connectionId = index > 0 ? this.savedConnections[index - 1].id : ''; this.browsing = false; }) - Button(RemoteI18n.t('workspaceTools.browse')).enabled(this.canBrowse && !this.tools.browseBusy) + if (this.savedConnections.length > 0 || this.deviceName.length > 0) { + Column({ space: 7 }) { + Text(RemoteI18n.t('sidebar.controlledDevice')).fontColor(MUTED).fontSize(MobileDesignTypography.labelSmall.size) + Select(this.connectionOptions()) + .selected(this.selectedConnectionIndex()) + .value(this.connectionId.length > 0 ? this.savedConnections.find((connection: SavedRuntimeConnection): boolean => connection.id === this.connectionId)?.name || this.deviceName : this.deviceName) + .fontColor(INK).width('100%').height(54).enabled(!this.loading) + .backgroundColor(CARD).border({ width: 1, color: LINE }).borderRadius(16) + .onSelect((index: number) => this.selectConnection(index)) + }.width('100%').padding({ top: 14, bottom: 12 }) + } + Column({ space: 7 }) { + Text(RemoteI18n.t('sidebar.currentPath')).fontColor(MUTED).fontSize(MobileDesignTypography.labelSmall.size) + Row({ space: 10 }) { + Stack({ alignContent: Alignment.Center }) { + SymbolGlyph($r('sys.symbol.folder')).fontSize(19).fontColor([INK]) + }.width(38).height(38).borderRadius(11).backgroundColor(CARD) + TextInput({ text: this.targetPath, placeholder: RemoteI18n.t('sidebar.targetWorkspacePath') }) + .fontColor(INK).fontSize(MobileDesignTypography.bodySmall.size).layoutWeight(1) + .height(38).padding({ left: 0, right: 0 }).backgroundColor(TRANSPARENT).border({ width: 0, color: TRANSPARENT }) + .onChange((value: string) => { this.targetPath = value; }) + .enabled(!this.loading) + SymbolGlyph($r('sys.symbol.chevron_right')).fontSize(16).fontColor([MUTED]) + }.width('100%').height(64).padding({ left: 12, right: 12 }).backgroundColor(SOFT).borderRadius(16) + }.width('100%').padding({ bottom: 12 }) + Button(RemoteI18n.t('workspaceTools.browse')) + .fontColor(INK).fontSize(MobileDesignTypography.bodySmall.size).height(44).width('100%') + .backgroundColor(SOFT).borderRadius(13).enabled(this.canBrowse && !this.tools.browseBusy && !this.loading) .onClick(() => { this.browsing = true; this.onBrowse({ type: 'browse-workspace', path: this.targetPath || (this.connectionId.length === 0 ? this.selectedWorkspacePath : '') || '/', text: this.connectionId }); }) if (this.browsing) { this.FolderBrowser() } - Button(RemoteI18n.t('sidebar.openWorkspacePath')) - .enabled(!this.loading && this.targetPath.trim().length > 0) - .onClick(() => this.onSelectWorkspace(this.targetPath.trim(), this.connectionId.length > 0 ? this.connectionId : undefined)) - if (this.loading && this.workspaces.length === 0) { + if (this.connectionId.length > 0 && this.targetPath.trim() === '/') { + Text(RemoteI18n.t('sidebar.remoteRootBrowseOnly')) + .fontColor(MUTED).fontSize(MobileDesignTypography.labelSmall.size) + .maxLines(2).width('100%').padding({ top: 2 }) + } + Row({ space: 8 }) { + Text(RemoteI18n.t('sidebar.recentWorkspaces')).fontColor(MUTED).fontSize(MobileDesignTypography.labelSmall.size).fontWeight(FontWeight.Medium).layoutWeight(1) + Text(RemoteI18n.t('sidebar.tapToSwitch')).fontColor(MUTED).fontSize(MobileDesignTypography.labelSmall.size) + }.width('100%').padding({ top: 14, bottom: 7 }) + if ((this.loading || this.tools.browseBusy) && this.visibleWorkspaces().length === 0) { this.LoadingState() - } else if (this.workspaces.length === 0) { + } else if (this.visibleWorkspaces().length === 0) { this.EmptyState() } else { Scroll() { Column({ space: 4 }) { - ForEach(this.workspaces, (row: RecentWorkspaceEntry) => { + ForEach(this.visibleWorkspaces(), (row: RecentWorkspaceEntry) => { this.WorkspaceRow(row) }, (workspace: RecentWorkspaceEntry): string => remoteWorkspaceKey(workspace)) } @@ -95,6 +189,13 @@ export struct SidebarWorkspacePicker { .layoutWeight(1) .scrollBar(BarState.Off) } + Row() { + Button(RemoteI18n.t('sidebar.openWorkspacePath')) + .fontColor(CONTENT_ON_ACTION).fontSize(MobileDesignTypography.bodyMedium.size).fontWeight(FontWeight.Medium) + .backgroundColor(PRIMARY_ACTION).height(48).width('100%').borderRadius(14) + .enabled(this.canOpenTargetPath()) + .onClick(() => this.onSelectWorkspace(this.targetPath.trim(), this.connectionId.length > 0 ? this.connectionId : undefined)) + }.width('100%').padding({ top: 12, bottom: 16 }).backgroundColor(PAGE_BG) } .width('100%') .height('100%') @@ -105,14 +206,29 @@ export struct SidebarWorkspacePicker { @Builder private FolderBrowser() { Column({ space: 8 }) { - Text(this.tools.browseError || this.tools.browsePath).fontColor(MUTED).maxLines(2) + if (this.tools.browseBusy) { + Row({ space: 8 }) { + LoadingProgress().width(18).height(18).color(MUTED) + Text(RemoteI18n.t('common.loading')).fontColor(MUTED).fontSize(MobileDesignTypography.bodySmall.size) + }.width('100%') + } else if (this.tools.browseError.length > 0) { + Text(this.tools.browseError).fontColor(MUTED).fontSize(MobileDesignTypography.bodySmall.size).maxLines(3) + } else { + Text(this.tools.browsePath).fontColor(MUTED).fontSize(MobileDesignTypography.bodySmall.size).maxLines(2) + } Row({ space: 8 }) { - Button(RemoteI18n.t('workspaceTools.up')).onClick(() => { + Button(RemoteI18n.t('workspaceTools.up')) + .fontColor(INK).fontSize(MobileDesignTypography.labelSmall.size).height(40).layoutWeight(1) + .backgroundColor(CARD).border({ width: 1, color: LINE }).borderRadius(12) + .onClick(() => { const current = this.tools.browsePath.replace(/\/+$/, ''); this.onBrowse({ type: 'browse-workspace', path: current.slice(0, current.lastIndexOf('/')) || '/', text: this.connectionId }); }) - Button(RemoteI18n.t('workspaceTools.selectFolder')).onClick(() => { this.targetPath = this.tools.browsePath; this.browsing = false; }) - }.enabled(!this.tools.browseBusy && this.tools.browsePath.length > 0) + Button(RemoteI18n.t('workspaceTools.selectFolder')) + .fontColor(CONTENT_ON_ACTION).fontSize(MobileDesignTypography.labelSmall.size).height(40).layoutWeight(1) + .backgroundColor(INK).borderRadius(12) + .onClick(() => { this.targetPath = this.tools.browsePath; this.browsing = false; }) + }.enabled(!this.tools.browseBusy && this.tools.browseError.length === 0 && this.tools.browsePath.length > 0) List() { ForEach(this.tools.browseEntries, (entry: WorkspaceFileEntry) => { if (entry.isDirectory) { @@ -173,14 +289,14 @@ export struct SidebarWorkspacePicker { .borderRadius(10) .backgroundColor(CARD) Column({ space: 3 }) { - Text((this.currentWorkspace(row).name || this.basename(this.currentWorkspace(row).path)) + (this.currentWorkspace(row).remoteSshHost?.trim() ? ` · ${(this.currentWorkspace(row).remoteSshHost || '').trim()}` : '')) + Text(this.workspaceTitle(this.currentWorkspace(row))) .width('100%') .fontSize(MobileDesignTypography.bodyLarge.size) .fontWeight(this.isSelected(this.currentWorkspace(row)) ? FontWeight.Medium : FontWeight.Regular) .fontColor(INK) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) - Text(this.currentWorkspace(row).path) + Text(this.workspaceSubtitle(this.currentWorkspace(row))) .width('100%') .fontSize(MobileDesignTypography.labelSmall.size) .fontColor(MUTED) @@ -190,10 +306,11 @@ export struct SidebarWorkspacePicker { .layoutWeight(1) .alignItems(HorizontalAlign.Start) Stack({ alignContent: Alignment.Center }) { - SymbolGlyph(this.isSelected(this.currentWorkspace(row)) ? - $r('sys.symbol.checkmark') : $r('sys.symbol.chevron_right')) - .fontSize(this.isSelected(this.currentWorkspace(row)) ? 17 : 15) - .fontColor([this.isSelected(this.currentWorkspace(row)) ? INK : MUTED]) + SymbolGlyph(this.isRemoteRoot(this.currentWorkspace(row)) ? + $r('sys.symbol.questionmark_circle') : this.isSelected(this.currentWorkspace(row)) ? + $r('sys.symbol.checkmark_circle_fill') : $r('sys.symbol.chevron_right')) + .fontSize(this.isRemoteRoot(this.currentWorkspace(row)) ? 17 : this.isSelected(this.currentWorkspace(row)) ? 19 : 15) + .fontColor([this.isRemoteRoot(this.currentWorkspace(row)) ? MUTED : this.isSelected(this.currentWorkspace(row)) ? PRIMARY_ACTION : MUTED]) } .width(40) .height(40) @@ -203,7 +320,14 @@ export struct SidebarWorkspacePicker { .padding({ left: 10, right: 8 }) .borderRadius(14) .backgroundColor(this.isSelected(this.currentWorkspace(row)) ? SOFT : PAGE_BG) - .onClick(() => this.onSelectWorkspace(this.currentWorkspace(row).path, this.currentWorkspace(row).remoteConnectionId, this.currentWorkspace(row).remoteSshHost, this.currentWorkspace(row).workspaceId)) + .onClick(() => { + const workspace = this.currentWorkspace(row); + if (this.isRemoteRoot(workspace)) { + this.openRemoteRootForBrowsing(workspace); + } else { + this.onSelectWorkspace(workspace.path, workspace.remoteConnectionId, workspace.remoteSshHost, workspace.workspaceId); + } + }) } @Builder @@ -222,7 +346,7 @@ export struct SidebarWorkspacePicker { private EmptyState() { Column({ space: 8 }) { SymbolGlyph($r('sys.symbol.folder')).fontSize(30).fontColor([MUTED]) - Text(RemoteI18n.t('sidebar.emptyWorkspaces')) + Text(this.connectionId.length > 0 ? RemoteI18n.t('status.remoteUnavailable') : RemoteI18n.t('sidebar.emptyWorkspaces')) .fontSize(MobileDesignTypography.bodyMedium.size) .fontColor(MUTED) .textAlign(TextAlign.Center) diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarWorkspaceSection.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarWorkspaceSection.ets index 7739c78a7a..596f24acd1 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarWorkspaceSection.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarWorkspaceSection.ets @@ -432,7 +432,14 @@ export struct SidebarWorkspaceSection { } private liveWorkspaceName(entry: DeviceDirectoryEntry): string { - return this.liveWorkspacePath(entry).length > 0 ? this.workspaceName : ''; + if (this.liveWorkspacePath(entry).length === 0) return ''; + if (this.workspaceKind === 'remote' && this.workspaceConnectionId.length > 0) { + const connection = this.savedConnections.find((item: SavedRuntimeConnection): boolean => { + return item.id === this.workspaceConnectionId && item.name.trim().length > 0; + }); + if (connection) return connection.name; + } + return this.workspaceName; } private liveWorkspaceKind(entry: DeviceDirectoryEntry): string { diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WorkspaceToolsPanel.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WorkspaceToolsPanel.ets index 8eb235336d..f3eeb64470 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WorkspaceToolsPanel.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WorkspaceToolsPanel.ets @@ -28,8 +28,8 @@ export struct WorkspaceToolsPanel { if (this.state.deviceScope) { Select([{ value: RemoteI18n.t('workspaceTools.runtimeLocal') }, ...this.state.locations.map((item: WorkspaceToolLocation): SelectOption => ({ value: item.name }))]) .value(this.state.locations.find((item: WorkspaceToolLocation): boolean => item.id === this.state.connectionId)?.name || RemoteI18n.t('workspaceTools.runtimeLocal')) - .fontColor(INK).width('100%').height(48).enabled(!this.state.busy) - .backgroundColor(CARD).border({ width: 1, color: LINE }).borderRadius(12) + .fontColor(INK).width('100%').height(54).enabled(!this.state.busy) + .backgroundColor(CARD).border({ width: 1, color: LINE }).borderRadius(16) .onSelect((index: number) => this.onIntent({ type: 'open-device', connectionId: index === 0 ? '' : this.state.locations[index - 1]?.id, tab: this.state.initialTab })) } if (this.state.error.length > 0) { Text(this.state.saveConflict ? RemoteI18n.t('workspaceTools.fileConflict') : this.state.error).fontColor(MUTED).fontSize(MobileDesignTypography.bodySmall.size) } @@ -43,7 +43,7 @@ export struct WorkspaceToolsPanel { Tabs({ index: this.state.initialTab }) { TabContent() { this.Files() }.tabBar(new SubTabBarStyle(RemoteI18n.t('workspaceTools.files')).labelStyle({ selectedColor: INK, unselectedColor: MUTED }).indicator({ color: INK })) TabContent() { this.Terminal() }.tabBar(new SubTabBarStyle(RemoteI18n.t('workspaceTools.terminal')).labelStyle({ selectedColor: INK, unselectedColor: MUTED }).indicator({ color: INK })) - }.onChange((index: number) => this.onIntent({ type: 'panel', tab: index })).layoutWeight(1) + }.barHeight(50).onChange((index: number) => this.onIntent({ type: 'panel', tab: index })).layoutWeight(1) }.padding({ left: 16, right: 16, bottom: 16 }).width('100%').height('100%').backgroundColor(PAGE_BG) } @Builder @@ -101,38 +101,39 @@ export struct WorkspaceToolsPanel { .width(40).height(40).padding(0).backgroundColor(TRANSPARENT).borderRadius(8).accessibilityText(RemoteI18n.t('workspaceTools.refresh')) .onClick(() => this.onIntent({ type: 'directory' })) }.width('100%').height(48).padding({ left: 4, right: 4 }).backgroundColor(CARD).border({ width: 1, color: LINE }).borderRadius(8) - Scroll() { - Row({ space: 8 }) { + Row({ space: 8 }) { Button() { Row({ space: 6 }) { SymbolGlyph($r('sys.symbol.doc_text')).fontColor([INK]).fontSize(16) Text(RemoteI18n.t('workspaceTools.newFile')).fontColor(INK).fontSize(MobileDesignTypography.labelSmall.size) } - }.height(36).padding({ left: 10, right: 10 }).backgroundColor(SOFT).borderRadius(8) + }.layoutWeight(1).height(44).padding({ left: 4, right: 4 }).backgroundColor(CARD).border({ width: 1, color: LINE }).borderRadius(13) .onClick(() => this.onIntent({ type: 'file-action', text: 'file' })) Button() { Row({ space: 6 }) { SymbolGlyph($r('sys.symbol.folder')).fontColor([INK]).fontSize(16) Text(RemoteI18n.t('workspaceTools.mkdir')).fontColor(INK).fontSize(MobileDesignTypography.labelSmall.size) } - }.height(36).padding({ left: 10, right: 10 }).backgroundColor(SOFT).borderRadius(8) + }.layoutWeight(1).height(44).padding({ left: 4, right: 4 }).backgroundColor(SOFT).borderRadius(13) .onClick(() => this.onIntent({ type: 'file-action', text: 'folder' })) Button() { Row({ space: 6 }) { SymbolGlyph($r('sys.symbol.doc_text_badge_arrow_up')).fontColor([INK]).fontSize(16) Text(RemoteI18n.t('workspaceTools.upload')).fontColor(INK).fontSize(MobileDesignTypography.labelSmall.size) } - }.height(36).padding({ left: 10, right: 10 }).backgroundColor(SOFT).borderRadius(8) + }.layoutWeight(1).height(44).padding({ left: 4, right: 4 }).backgroundColor(SOFT).borderRadius(13) .onClick(() => this.onIntent({ type: 'file-action', text: 'upload' })) - } - }.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width('100%').height(40) + }.width('100%').height(44) Row({ space: 8 }) { - Blank().layoutWeight(1) + Row({ space: 6 }) { + Text(RemoteI18n.t('workspaceTools.files')).fontColor(INK).fontSize(MobileDesignTypography.bodySmall.size).fontWeight(FontWeight.Medium) + Text(String(this.state.entries.length)).fontColor(MUTED).fontSize(MobileDesignTypography.labelSmall.size) + }.layoutWeight(1).alignItems(VerticalAlign.Center) Select([ { value: RemoteI18n.t('workspaceTools.nameAsc') }, { value: RemoteI18n.t('workspaceTools.nameDesc') }, { value: RemoteI18n.t('workspaceTools.modifiedAsc') }, { value: RemoteI18n.t('workspaceTools.modifiedDesc') } - ]).selected(this.state.sort).value(this.sortTitle()).fontColor(INK).height(36).layoutWeight(1).enabled(!this.state.busy) - .backgroundColor(SOFT).borderRadius(18) + ]).selected(this.state.sort).value(this.sortTitle()).fontColor(MUTED).height(34).width(132).enabled(!this.state.busy) + .backgroundColor(SOFT).borderRadius(17) .onSelect((index: number) => this.onIntent({ type: 'sort', text: String(index) })) }.enabled(!this.state.busy).width('100%') if (this.state.busy) { LoadingProgress().width(24).height(24).color(MUTED) } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/WorkspaceToolsViewModel.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/WorkspaceToolsViewModel.ets index e2c3723f9d..a8c44a04de 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/WorkspaceToolsViewModel.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/WorkspaceToolsViewModel.ets @@ -84,7 +84,11 @@ export class WorkspaceToolsViewModel { const generation = ++this.browseGeneration; const target = this.target(); const path = intent.path || '/'; const append = intent.type === 'browse-more'; this.state.browseBusy = true; this.state.browseError = ''; - if (!append) { this.state.browseEntries = []; this.state.browseHasMore = false; } + if (!append) { + this.state.browsePath = path; + this.state.browseEntries = []; + this.state.browseHasMore = false; + } try { const args: FileArgs = { request: { path: path, remoteConnectionId: intent.text || '', offset: append ? this.state.browseEntries.length : 0, limit: 100, sortBy: 'name', sortOrder: 'asc' } }; diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteResponseMapper.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteResponseMapper.ets index ac7417a1f1..80f3174408 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteResponseMapper.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteResponseMapper.ets @@ -16,11 +16,16 @@ import { export class RemoteResponseMapper { static workspaceFromInitialSync(response: InitialSyncResponse): WorkspaceInfo { const workspacePath = response.path || ''; - const workspaceName = response.project_name || RemoteResponseMapper.basename(workspacePath); + const workspaceName = RemoteResponseMapper.workspaceDisplayName( + response.project_name, + workspacePath, + response.remote_ssh_host, + response.remote_connection_id + ); return { workspaceId: response.workspace_id, path: workspacePath, - name: workspaceName || 'Workspace', + name: workspaceName, hasWorkspace: response.has_workspace !== false && workspacePath.length > 0, gitBranch: response.git_branch || '', workspaceKind: response.workspace_kind || 'normal', @@ -32,11 +37,16 @@ export class RemoteResponseMapper { static workspaceFromResponse(response: WorkspaceInfoResponse): WorkspaceInfo { const workspacePath = response.path || response.workspace_path || ''; - const workspaceName = response.project_name || response.workspace_name || RemoteResponseMapper.basename(workspacePath); + const workspaceName = RemoteResponseMapper.workspaceDisplayName( + response.project_name || response.workspace_name, + workspacePath, + response.remote_ssh_host, + response.remote_connection_id + ); return { workspaceId: response.workspace_id, path: workspacePath, - name: workspaceName || 'Workspace', + name: workspaceName, hasWorkspace: response.has_workspace !== false && workspacePath.length > 0, gitBranch: response.git_branch || '', workspaceKind: response.workspace_kind || 'normal', @@ -51,7 +61,12 @@ export class RemoteResponseMapper { const workspace: RecentWorkspaceEntry = { workspaceId: item.workspace_id, path: item.path || '', - name: item.name || RemoteResponseMapper.basename(item.path || '') || 'Workspace', + name: RemoteResponseMapper.workspaceDisplayName( + item.name, + item.path || '', + item.remote_ssh_host, + item.remote_connection_id + ), lastOpened: RemoteResponseMapper.firstTimeValue([ item.last_opened, item.lastOpened, @@ -66,6 +81,31 @@ export class RemoteResponseMapper { }).filter((item: RecentWorkspaceEntry) => item.path.length > 0); } + /** Keep remote root workspaces readable when older hosts persisted "Unknown". */ + private static workspaceDisplayName( + rawName: string | undefined, + path: string, + remoteSshHost?: string, + remoteConnectionId?: string + ): string { + const name = (rawName || '').trim(); + const normalized = name.toLowerCase(); + const host = (remoteSshHost || '').trim(); + const connection = (remoteConnectionId || '').trim(); + const isTransportName = (host.length > 0 && name === host) || + (connection.length > 0 && name === connection); + if (name.length > 0 && !isTransportName && normalized !== 'unknown' && normalized !== 'unknown project' && name !== '未知' && name !== '未知项目') { + return name; + } + const basename = RemoteResponseMapper.basename(path); + if (basename.length > 0 && basename !== '/') { + return basename; + } + // Keep the remote root addressable without presenting the transport + // address as if it were a workspace name. + return '/'; + } + static sessions(items: SessionItemResponse[]): RemoteSession[] { return RemoteResponseMapper.visibleSessions(RemoteResponseMapper.allSessions(items)); } diff --git a/src/crates/assembly/core/src/service/workspace/service.rs b/src/crates/assembly/core/src/service/workspace/service.rs index 1ca978af20..51449b1c65 100644 --- a/src/crates/assembly/core/src/service/workspace/service.rs +++ b/src/crates/assembly/core/src/service/workspace/service.rs @@ -507,8 +507,13 @@ impl WorkspaceService { "Remote workspace path is not a directory", )); } + // The root path has no basename. Keep a friendly stable label for + // this common remote workspace instead of letting the manager use + // its generic "Unknown" placeholder. + let display_name = (path == "/").then(|| config.name.clone()); let options = WorkspaceOpenOptions { workspace_kind: WorkspaceKind::Remote, + display_name, remote_connection_id: Some(connection_id.to_string()), remote_ssh_host: Some(host.clone()), stable_workspace_id: Some(remote_workspace_stable_id(&host, &path)), diff --git a/src/crates/assembly/core/src/service_agent_runtime.rs b/src/crates/assembly/core/src/service_agent_runtime.rs index 541686d430..8b5b44ea68 100644 --- a/src/crates/assembly/core/src/service_agent_runtime.rs +++ b/src/crates/assembly/core/src/service_agent_runtime.rs @@ -512,18 +512,49 @@ pub(crate) fn remote_workspace_metadata( #[cfg(feature = "remote-connect")] pub(crate) fn remote_workspace_display_name( workspace: &crate::service::workspace::WorkspaceInfo, -) -> &str { - if workspace.workspace_kind == crate::service::workspace::WorkspaceKind::Assistant { - workspace - .identity - .as_ref() - .and_then(|identity| identity.name.as_deref()) - .map(str::trim) - .filter(|name| !name.is_empty()) - .unwrap_or(&workspace.name) - } else { - &workspace.name +) -> String { + let preferred = + if workspace.workspace_kind == crate::service::workspace::WorkspaceKind::Assistant { + workspace + .identity + .as_ref() + .and_then(|identity| identity.name.as_deref()) + .map(str::trim) + .filter(|name| !name.is_empty()) + .unwrap_or(workspace.name.trim()) + } else { + workspace.name.trim() + }; + let normalized = preferred.to_ascii_lowercase(); + if !preferred.is_empty() + && normalized != "unknown" + && normalized != "unknown project" + && preferred != "未知" + && preferred != "未知项目" + { + return preferred.to_string(); + } + + let path_name = workspace + .root_path + .file_name() + .and_then(|name| name.to_str()) + .map(str::trim) + .filter(|name| !name.is_empty()) + .unwrap_or(""); + if !path_name.is_empty() { + return path_name.to_string(); } + + for key in ["connectionName", "sshHost"] { + if let Some(value) = workspace_metadata_string(&workspace.metadata, key) { + return value; + } + } + if workspace.workspace_kind == crate::service::workspace::WorkspaceKind::Remote { + return "Remote workspace".to_string(); + } + "Workspace".to_string() } #[cfg(feature = "remote-connect")] @@ -536,7 +567,7 @@ pub(crate) async fn remote_opened_workspace_catalog( .into_iter() .map(|workspace| RemoteRecentWorkspaceFacts { workspace_id: workspace.id.clone(), - name: remote_workspace_display_name(&workspace).to_string(), + name: remote_workspace_display_name(&workspace), path: workspace.root_path.to_string_lossy().to_string(), last_opened: workspace.last_accessed.to_rfc3339(), kind: remote_workspace_kind(workspace.workspace_kind.clone()), @@ -565,7 +596,7 @@ async fn current_remote_workspace_facts() -> Option { RemoteWorkspaceFacts { workspace_id: workspace.id.clone(), path: root_path.to_string_lossy().to_string(), - name: workspace.name, + name: remote_workspace_display_name(&workspace), git_branch: git_branch_for_workspace_path(&root_path), kind: remote_workspace_kind(workspace.workspace_kind.clone()), assistant_id: workspace.assistant_id, @@ -610,7 +641,7 @@ async fn open_workspace_with_snapshot( Ok(RemoteWorkspaceUpdate { workspace_id: info.id.clone(), path: info.root_path.to_string_lossy().to_string(), - name: info.name, + name: remote_workspace_display_name(&info), remote_connection_id, remote_ssh_host, }) @@ -646,7 +677,7 @@ fn remote_workspace_facts_from_record( RemoteWorkspaceFacts { workspace_id: workspace.id.clone(), path: workspace.root_path.to_string_lossy().into_owned(), - name: workspace.name.clone(), + name: remote_workspace_display_name(workspace), git_branch: None, kind: remote_workspace_kind(workspace.workspace_kind.clone()), assistant_id: workspace.assistant_id.clone(), @@ -3234,7 +3265,7 @@ impl RemoteWorkspaceRuntimeHost for CoreRemoteWorkspaceRuntimeHost { .map(|workspace| RemoteRecentWorkspaceFacts { workspace_id: workspace.id.clone(), path: workspace.root_path.to_string_lossy().to_string(), - name: workspace.name.clone(), + name: remote_workspace_display_name(&workspace), last_opened: workspace.last_accessed.to_rfc3339(), kind: remote_workspace_kind(workspace.workspace_kind.clone()), remote_connection_id: remote_workspace_metadata( diff --git a/src/mobile-web/src/services/RemoteSessionManager.ts b/src/mobile-web/src/services/RemoteSessionManager.ts index e79e7814bd..c2f80387fe 100644 --- a/src/mobile-web/src/services/RemoteSessionManager.ts +++ b/src/mobile-web/src/services/RemoteSessionManager.ts @@ -1,5 +1,5 @@ import { HostDialogQueue, type QueueSnapshot } from '../../../shared/dialog-queue/HostDialogQueue'; -import { normalizeWorkspaceRouting } from './workspaceIdentity'; +import { normalizeWorkspaceRouting, workspaceDisplayName } from './workspaceIdentity'; import { REMOTE_CAPABILITY_HOST_STREAM_V1, UNSUPPORTED_HOST_MESSAGE, type HostStreamOptions, type SessionStreamHandle, @@ -514,7 +514,7 @@ export class RemoteSessionManager { workspace_id: resp.workspace_id, has_workspace: resp.has_workspace, path: resp.path, - project_name: resp.project_name, + project_name: workspaceDisplayName(resp.project_name, resp.path, resp.remote_ssh_host, resp.remote_connection_id), git_branch: resp.git_branch, workspace_kind: resp.workspace_kind, assistant_id: resp.assistant_id, @@ -529,7 +529,15 @@ export class RemoteSessionManager { resp: string; workspaces: RecentWorkspaceEntry[]; }>({ cmd: 'list_recent_workspaces' }); - return (resp.workspaces || []).map(normalizeWorkspaceRouting); + return (resp.workspaces || []).map((workspace) => ({ + ...workspace, + name: workspaceDisplayName( + workspace.name, + workspace.path, + workspace.remote_ssh_host, + workspace.remote_connection_id, + ), + })).map(normalizeWorkspaceRouting); } async listWorkspaceCatalog(): Promise { @@ -557,7 +565,24 @@ export class RemoteSessionManager { remoteConnectionId: workspace.remote_connection_id, remoteSshHost: workspace.remote_ssh_host, }, 'path', target); - return this.request({ cmd: 'set_workspace', ...reference }, target); + const response = await this.request<{ + success: boolean; + workspace_id?: string; + path?: string; + project_name?: string; + remote_connection_id?: string; + remote_ssh_host?: string; + error?: string; + }>({ cmd: 'set_workspace', ...reference }, target); + return { + ...response, + project_name: workspaceDisplayName( + response.project_name, + response.path || workspace.path, + response.remote_ssh_host ?? workspace.remote_ssh_host, + response.remote_connection_id ?? workspace.remote_connection_id, + ), + }; } /** True when the connected host serves session, terminal and catalog diff --git a/src/mobile-web/src/services/workspaceIdentity.ts b/src/mobile-web/src/services/workspaceIdentity.ts index 6860e0a163..1bec41268c 100644 --- a/src/mobile-web/src/services/workspaceIdentity.ts +++ b/src/mobile-web/src/services/workspaceIdentity.ts @@ -21,7 +21,15 @@ export function projectWorkspaceCatalog( const seen = new Set(); return { source, - workspaces: rows.map(normalizeWorkspaceRouting).map((workspace) => { + workspaces: rows.map((workspace) => ({ + ...workspace, + name: workspaceDisplayName( + workspace.name, + workspace.path, + workspace.remote_ssh_host, + workspace.remote_connection_id, + ), + })).map(normalizeWorkspaceRouting).map((workspace) => { if (workspace.remote_connection_id || workspace.remote_ssh_host) return workspace; const assistant = assistants.find(candidate => sameWorkspace(candidate, workspace)); return assistant?.name.trim() @@ -207,3 +215,22 @@ export function normalizeWorkspaceRouting Date: Wed, 30 Sep 2026 15:46:14 +0800 Subject: [PATCH 2/3] fix(i18n): avoid hardcoded CJK in workspace fallback --- src/mobile-web/src/services/workspaceIdentity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mobile-web/src/services/workspaceIdentity.ts b/src/mobile-web/src/services/workspaceIdentity.ts index 1bec41268c..b4f6b649eb 100644 --- a/src/mobile-web/src/services/workspaceIdentity.ts +++ b/src/mobile-web/src/services/workspaceIdentity.ts @@ -226,7 +226,7 @@ export function workspaceDisplayName( const value = name?.trim() ?? ''; const normalized = value.toLowerCase(); if (value && normalized !== 'unknown' && normalized !== 'unknown project' - && value !== '未知' && value !== '未知项目') { + && value !== '\u672A\u77E5' && value !== '\u672A\u77E5\u9879\u76EE') { return value; } const root = (path ?? '').trim().replace(/\/+$/, ''); From 4d20f589cc1ebcb69aa16b04bb07f16175236190 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Wed, 30 Sep 2026 16:01:16 +0800 Subject: [PATCH 3/3] fix(harmonyos): increment app version code --- src/apps/mobile/harmonyos/AppScope/app.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/mobile/harmonyos/AppScope/app.json5 b/src/apps/mobile/harmonyos/AppScope/app.json5 index 29d9b62a23..8a2ae02e48 100644 --- a/src/apps/mobile/harmonyos/AppScope/app.json5 +++ b/src/apps/mobile/harmonyos/AppScope/app.json5 @@ -4,7 +4,7 @@ // upgrades retain encrypted sessions, preferences, and watch handoff data. "bundleName": "com.bitfun.app", "vendor": "OpenBitFun", - "versionCode": 4, + "versionCode": 5, "versionName": "1.0.0", // x-release-please-version "buildVersion": "1", "icon": "$media:openbitfun_app_icon",