(null);
useLayoutEffect(() => {
@@ -229,6 +232,11 @@ export function WorkspacesLayout() {
);
// ── Mobile layout ──────────────────────────────────────────────────
+ // These seven panes must stay in lockstep with MobileTabId
+ // (packages/ui/src/components/Navbar.tsx) and MobileTab
+ // (packages/web-core/src/shared/stores/useUiPreferencesStore.ts). Adding a
+ // tab to those unions without a matching pane here compiles cleanly but
+ // silently renders a dead tab button.
// Uses `hidden` CSS class (NOT conditional rendering) to preserve
// WebSocket connections and scroll positions across tab switches.
if (isMobile) {
@@ -355,6 +363,18 @@ export function WorkspacesLayout() {
/>
)}
+
+ {/* Files tab */}
+
+ {selectedWorkspace?.id && !hostId && (
+
+ )}
+
diff --git a/packages/web-core/src/shared/components/ui-new/containers/NavbarContainer.tsx b/packages/web-core/src/shared/components/ui-new/containers/NavbarContainer.tsx
index f496755ce84..ea8dde986ee 100644
--- a/packages/web-core/src/shared/components/ui-new/containers/NavbarContainer.tsx
+++ b/packages/web-core/src/shared/components/ui-new/containers/NavbarContainer.tsx
@@ -6,9 +6,10 @@ import { useSyncErrorContext } from '@/shared/hooks/useSyncErrorContext';
import { useUserOrganizations } from '@/shared/hooks/useUserOrganizations';
import { useOrganizationStore } from '@/shared/stores/useOrganizationStore';
import {
+ MOBILE_TABS,
Navbar,
- type NavbarSectionItem,
type MobileTabId,
+ type NavbarSectionItem,
} from '@vibe/ui/components/Navbar';
import { Tooltip } from '@vibe/ui/components/Tooltip';
import { AppBarUserPopoverContainer } from './AppBarUserPopoverContainer';
@@ -37,6 +38,7 @@ import { CommandBarDialog } from '@/shared/dialogs/command-bar/CommandBarDialog'
import { SettingsDialog } from '@/shared/dialogs/settings/SettingsDialog';
import { useAppNavigation } from '@/shared/hooks/useAppNavigation';
import { getRemoteAuthDegradedMessage } from '@/shared/lib/auth/remoteAuthDegraded';
+import { useHostId } from '@/shared/providers/HostIdProvider';
/**
* Check if a NavbarItem is a divider
@@ -117,6 +119,7 @@ export function NavbarContainer({
mobileMode?: boolean;
}) {
const { t } = useTranslation('common');
+ const hostId = useHostId();
const { executeAction } = useActions();
const { workspace: selectedWorkspace, isCreateMode } = useWorkspaceContext();
const syncErrorContext = useSyncErrorContext();
@@ -126,6 +129,10 @@ export function NavbarContainer({
const { isSignedIn } = useAuth();
const appNavigation = useAppNavigation();
const [mobileActiveTab, setMobileActiveTab] = useMobileActiveTab();
+ const mobileTabs = useMemo(
+ () => (hostId ? MOBILE_TABS.filter((t) => t.id !== 'files') : MOBILE_TABS),
+ [hostId]
+ );
const { data: orgsData } = useUserOrganizations();
const organizations = useMemo(
@@ -327,8 +334,9 @@ export function NavbarContainer({
onOpenCommandBar={handleOpenCommandBar}
onOpenSettings={handleOpenSettings}
onNavigateBack={handleNavigateBack}
- mobileActiveTab={mobileActiveTab as MobileTabId}
- onMobileTabChange={(tab) => setMobileActiveTab(tab)}
+ mobileActiveTab={mobileActiveTab}
+ onMobileTabChange={(tab: MobileTabId) => setMobileActiveTab(tab)}
+ mobileTabs={mobileTabs}
/>
);
}
diff --git a/packages/web-core/src/shared/stores/useUiPreferencesStore.ts b/packages/web-core/src/shared/stores/useUiPreferencesStore.ts
index 4e684c5d508..df7d5d50530 100644
--- a/packages/web-core/src/shared/stores/useUiPreferencesStore.ts
+++ b/packages/web-core/src/shared/stores/useUiPreferencesStore.ts
@@ -9,18 +9,25 @@ export const RIGHT_MAIN_PANEL_MODES = {
PREVIEW: 'preview',
} as const;
+// RightMainPanelMode must stay a subset of MobileTab. The assignments below
+// intentionally rely on structural typing (no cast), so adding a mode without
+// a matching mobile tab fails to compile.
export type RightMainPanelMode =
(typeof RIGHT_MAIN_PANEL_MODES)[keyof typeof RIGHT_MAIN_PANEL_MODES];
export type LayoutMode = 'workspaces' | 'kanban';
+// Keep in lockstep with MobileTabId in
+// packages/ui/src/components/Navbar.tsx — web-core can't be imported by ui,
+// so these two unions are intentionally duplicated.
export type MobileTab =
| 'workspaces'
| 'chat'
| 'changes'
| 'logs'
| 'preview'
- | 'git';
+ | 'git'
+ | 'files';
export type MobileFontScale = 'default' | 'small' | 'smaller';
export const DEFAULT_CREATE_DRAFT_WORKSPACE_BY_DEFAULT = false;
@@ -490,7 +497,7 @@ export const useUiPreferencesStore = create()((set, get) => ({
listViewStatusFilter: null,
// Mobile tab state
- mobileActiveTab: 'chat' as MobileTab,
+ mobileActiveTab: 'chat',
// Mobile font scale
mobileFontScale: loadMobileFontScale(),
@@ -581,8 +588,7 @@ export const useUiPreferencesStore = create()((set, get) => ({
: isWideScreen()
? state.isLeftSidebarVisible
: false,
- ...(isMobile &&
- !isCurrentlyActive && { mobileActiveTab: mode as MobileTab }),
+ ...(isMobile && !isCurrentlyActive && { mobileActiveTab: mode }),
});
},
@@ -605,7 +611,7 @@ export const useUiPreferencesStore = create()((set, get) => ({
? state.isLeftSidebarVisible
: false,
}),
- ...(isMobile && mode !== null && { mobileActiveTab: mode as MobileTab }),
+ ...(isMobile && mode !== null && { mobileActiveTab: mode }),
});
},