Add mobile context notes tab (#1355)

* Add mobile context notes tab

* Fix bot comments

---------

Co-authored-by: Konstantin Zolin <zolin_ka@vk.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
kostazol
2026-05-23 21:48:56 +03:00
committed by GitHub
co-authored by Konstantin Zolin Bohdan Triapitsyn
parent 0244d7ffcd
commit 9af0de0056
15 changed files with 38 additions and 21 deletions
+8 -2
View File
@@ -655,6 +655,7 @@ export const Header: React.FC<HeaderProps> = ({
const { t } = useI18n(); const { t } = useI18n();
const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen); const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen);
const toggleSidebar = useUIStore((state) => state.toggleSidebar); const toggleSidebar = useUIStore((state) => state.toggleSidebar);
const setRightSidebarOpen = useUIStore((state) => state.setRightSidebarOpen);
const toggleBottomTerminal = useUIStore((state) => state.toggleBottomTerminal); const toggleBottomTerminal = useUIStore((state) => state.toggleBottomTerminal);
const toggleRightSidebar = useUIStore((state) => state.toggleRightSidebar); const toggleRightSidebar = useUIStore((state) => state.toggleRightSidebar);
const openContextOverview = useUIStore((state) => state.openContextOverview); const openContextOverview = useUIStore((state) => state.openContextOverview);
@@ -1506,6 +1507,7 @@ export const Header: React.FC<HeaderProps> = ({
{ id: 'diff', label: t('layout.mainTab.diff'), icon: 'diff' }, { id: 'diff', label: t('layout.mainTab.diff'), icon: 'diff' },
{ id: 'files', label: t('layout.mainTab.files'), icon: "folder-6" }, { id: 'files', label: t('layout.mainTab.files'), icon: "folder-6" },
{ id: 'terminal', label: t('layout.mainTab.terminal'), icon: "terminal-box" }, { id: 'terminal', label: t('layout.mainTab.terminal'), icon: "terminal-box" },
{ id: 'context', label: t('layout.mainTab.context'), icon: "file-list-2" },
); );
return base; return base;
@@ -1520,7 +1522,7 @@ export const Header: React.FC<HeaderProps> = ({
}, [shortcutOverrides]); }, [shortcutOverrides]);
useEffect(() => { useEffect(() => {
if (!isMobile && (activeMainTab === 'git' || activeMainTab === 'terminal' || activeMainTab === 'diff' || activeMainTab === 'files')) { if (!isMobile && (activeMainTab === 'git' || activeMainTab === 'terminal' || activeMainTab === 'diff' || activeMainTab === 'files' || activeMainTab === 'context')) {
setActiveMainTab('chat'); setActiveMainTab('chat');
} }
}, [activeMainTab, isMobile, setActiveMainTab]); }, [activeMainTab, isMobile, setActiveMainTab]);
@@ -1626,13 +1628,16 @@ export const Header: React.FC<HeaderProps> = ({
const num = parseInt(e.key, 10); const num = parseInt(e.key, 10);
if (num >= 1 && num <= tabs.length) { if (num >= 1 && num <= tabs.length) {
e.preventDefault(); e.preventDefault();
if (isMobile) {
setRightSidebarOpen(false);
}
setActiveMainTab(tabs[num - 1].id); setActiveMainTab(tabs[num - 1].id);
} }
} }
}; };
window.addEventListener('keydown', handleKeyDown); window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown);
}, [tabs, setActiveMainTab]); }, [isMobile, setActiveMainTab, setRightSidebarOpen, tabs]);
useEffect(() => { useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
@@ -1995,6 +2000,7 @@ export const Header: React.FC<HeaderProps> = ({
onClick={() => { onClick={() => {
if (isMobile) { if (isMobile) {
blurActiveElement(); blurActiveElement();
setRightSidebarOpen(false);
} }
setActiveMainTab(tab.id); setActiveMainTab(tab.id);
}} }}
@@ -4,7 +4,7 @@ import { Header } from './Header';
import { BottomTerminalDock } from './BottomTerminalDock'; import { BottomTerminalDock } from './BottomTerminalDock';
import { Sidebar, SIDEBAR_CONTENT_WIDTH } from './Sidebar'; import { Sidebar, SIDEBAR_CONTENT_WIDTH } from './Sidebar';
import { RightSidebar, RIGHT_SIDEBAR_CONTENT_WIDTH } from './RightSidebar'; import { RightSidebar, RIGHT_SIDEBAR_CONTENT_WIDTH } from './RightSidebar';
import { RightSidebarTabs } from './RightSidebarTabs'; import { ProjectContextPanel, RightSidebarTabs } from './RightSidebarTabs';
import { ContextPanel } from './ContextPanel'; import { ContextPanel } from './ContextPanel';
import { ErrorBoundary } from '../ui/ErrorBoundary'; import { ErrorBoundary } from '../ui/ErrorBoundary';
import { CommandPalette } from '../ui/CommandPalette'; import { CommandPalette } from '../ui/CommandPalette';
@@ -308,6 +308,13 @@ export const MainLayout: React.FC = () => {
}; };
}, [isMobile, isTablet, setBottomTerminalOpen, setRightSidebarOpen]); }, [isMobile, isTablet, setBottomTerminalOpen, setRightSidebarOpen]);
const handleToggleMobileRightDrawer = React.useCallback(() => {
if (mobileLeftDrawerOpen) {
setMobileLeftDrawerOpen(false);
}
setRightSidebarOpen(!isRightSidebarOpen);
}, [isRightSidebarOpen, mobileLeftDrawerOpen, setRightSidebarOpen]);
const secondaryView = React.useMemo(() => { const secondaryView = React.useMemo(() => {
switch (activeMainTab) { switch (activeMainTab) {
case 'plan': case 'plan':
@@ -320,6 +327,8 @@ export const MainLayout: React.FC = () => {
return <React.Suspense fallback={null}><TerminalView /></React.Suspense>; return <React.Suspense fallback={null}><TerminalView /></React.Suspense>;
case 'files': case 'files':
return <React.Suspense fallback={null}><FilesView /></React.Suspense>; return <React.Suspense fallback={null}><FilesView /></React.Suspense>;
case 'context':
return <React.Suspense fallback={null}><ProjectContextPanel /></React.Suspense>;
default: default:
return null; return null;
} }
@@ -361,12 +370,7 @@ export const MainLayout: React.FC = () => {
} }
setMobileLeftDrawerOpen(!mobileLeftDrawerOpen); setMobileLeftDrawerOpen(!mobileLeftDrawerOpen);
}, },
toggleRightDrawer: () => { toggleRightDrawer: handleToggleMobileRightDrawer,
if (mobileLeftDrawerOpen) {
setMobileLeftDrawerOpen(false);
}
setRightSidebarOpen(!isRightSidebarOpen);
},
leftDrawerX, leftDrawerX,
rightDrawerX, rightDrawerX,
leftDrawerWidth, leftDrawerWidth,
@@ -383,10 +387,7 @@ export const MainLayout: React.FC = () => {
setMobileLeftDrawerOpen(!mobileLeftDrawerOpen); setMobileLeftDrawerOpen(!mobileLeftDrawerOpen);
}} }}
onToggleRightDrawer={() => { onToggleRightDrawer={() => {
if (mobileLeftDrawerOpen) { handleToggleMobileRightDrawer();
setMobileLeftDrawerOpen(false);
}
setRightSidebarOpen(!isRightSidebarOpen);
}} }}
leftDrawerOpen={mobileLeftDrawerOpen} leftDrawerOpen={mobileLeftDrawerOpen}
rightDrawerOpen={isRightSidebarOpen} rightDrawerOpen={isRightSidebarOpen}
@@ -40,7 +40,7 @@ function useRightSidebarGitSync(directory: string | undefined, isSidebarOpen: bo
}, [directory, git, isSidebarOpen, ensureStatus]); }, [directory, git, isSidebarOpen, ensureStatus]);
} }
const ContextSidebarPanel: React.FC = () => { export const ProjectContextPanel: React.FC = () => {
const activeProjectId = useProjectsStore((state) => state.activeProjectId); const activeProjectId = useProjectsStore((state) => state.activeProjectId);
const projects = useProjectsStore((state) => state.projects); const projects = useProjectsStore((state) => state.projects);
const homeDirectory = useDirectoryStore((state) => state.homeDirectory); const homeDirectory = useDirectoryStore((state) => state.homeDirectory);
@@ -113,7 +113,7 @@ export const RightSidebarTabs: React.FC = () => {
{ {
id: 'context', id: 'context',
label: t('layout.rightSidebar.context'), label: t('layout.rightSidebar.context'),
icon: <Icon name="booklet" className="h-3.5 w-3.5" />, icon: <Icon name="file-list-2" className="h-3.5 w-3.5" />,
}, },
], [t]); ], [t]);
@@ -133,7 +133,7 @@ export const RightSidebarTabs: React.FC = () => {
<div className="min-h-0 flex-1 overflow-hidden"> <div className="min-h-0 flex-1 overflow-hidden">
{rightSidebarTab === 'git' && <GitView />} {rightSidebarTab === 'git' && <GitView />}
{rightSidebarTab === 'files' && <SidebarFilesTree />} {rightSidebarTab === 'files' && <SidebarFilesTree />}
{rightSidebarTab === 'context' && <ContextSidebarPanel />} {rightSidebarTab === 'context' && <ProjectContextPanel />}
</div> </div>
</div> </div>
); );
@@ -16,6 +16,7 @@ import type { SortableDragHandleProps } from './sortableItems';
import { SortableGroupItem, SortableProjectItem } from './sortableItems'; import { SortableGroupItem, SortableProjectItem } from './sortableItems';
import { formatProjectLabel } from './utils'; import { formatProjectLabel } from './utils';
import { useI18n } from '@/lib/i18n'; import { useI18n } from '@/lib/i18n';
import type { MainTab } from '@/stores/useUIStore';
type ProjectSection = { type ProjectSection = {
project: { project: {
@@ -50,7 +51,7 @@ type Props = {
alwaysShowActions: boolean; alwaysShowActions: boolean;
toggleProject: (id: string) => void; toggleProject: (id: string) => void;
setActiveProjectIdOnly: (id: string) => void; setActiveProjectIdOnly: (id: string) => void;
setActiveMainTab: (tab: 'chat' | 'plan' | 'git' | 'diff' | 'terminal' | 'files') => void; setActiveMainTab: (tab: MainTab) => void;
setSessionSwitcherOpen: (open: boolean) => void; setSessionSwitcherOpen: (open: boolean) => void;
openNewSessionDraft: (options?: { directoryOverride?: string | null }) => void; openNewSessionDraft: (options?: { directoryOverride?: string | null }) => void;
openNewWorktreeDialog: () => void; openNewWorktreeDialog: () => void;
@@ -2,6 +2,7 @@ import React from 'react';
import type { Session } from '@opencode-ai/sdk/v2'; import type { Session } from '@opencode-ai/sdk/v2';
import type { SessionGroup, SessionNode } from '../types'; import type { SessionGroup, SessionNode } from '../types';
import { normalizePath } from '../utils'; import { normalizePath } from '../utils';
import type { MainTab } from '@/stores/useUIStore';
type ProjectSection = { type ProjectSection = {
project: { id: string; normalizedPath: string }; project: { id: string; normalizedPath: string };
@@ -18,7 +19,7 @@ type Args = {
newSessionDraftOpen: boolean; newSessionDraftOpen: boolean;
mobileVariant: boolean; mobileVariant: boolean;
openNewSessionDraft: (options?: { directoryOverride?: string | null }) => void; openNewSessionDraft: (options?: { directoryOverride?: string | null }) => void;
setActiveMainTab: (tab: 'chat' | 'plan' | 'git' | 'diff' | 'terminal' | 'files') => void; setActiveMainTab: (tab: MainTab) => void;
setSessionSwitcherOpen: (open: boolean) => void; setSessionSwitcherOpen: (open: boolean) => void;
sessions: Session[]; sessions: Session[];
worktreeMetadata: Map<string, { path?: string | null }>; worktreeMetadata: Map<string, { path?: string | null }>;
@@ -3,6 +3,7 @@ import type { Session } from '@opencode-ai/sdk/v2';
import { toast } from '@/components/ui'; import { toast } from '@/components/ui';
import { copyTextToClipboard } from '@/lib/clipboard'; import { copyTextToClipboard } from '@/lib/clipboard';
import { useI18n } from '@/lib/i18n'; import { useI18n } from '@/lib/i18n';
import type { MainTab } from '@/stores/useUIStore';
type DeleteSessionConfirmSetter = React.Dispatch<React.SetStateAction<{ type DeleteSessionConfirmSetter = React.Dispatch<React.SetStateAction<{
session: Session; session: Session;
@@ -24,7 +25,7 @@ type Args = {
setIsSessionSearchOpen: (open: boolean) => void; setIsSessionSearchOpen: (open: boolean) => void;
setActiveProjectIdOnly: (id: string) => void; setActiveProjectIdOnly: (id: string) => void;
setDirectory: (directory: string, options?: { showOverlay?: boolean }) => void; setDirectory: (directory: string, options?: { showOverlay?: boolean }) => void;
setActiveMainTab: (tab: 'chat' | 'plan' | 'git' | 'diff' | 'terminal' | 'files') => void; setActiveMainTab: (tab: MainTab) => void;
setSessionSwitcherOpen: (open: boolean) => void; setSessionSwitcherOpen: (open: boolean) => void;
setCurrentSession: (sessionId: string | null, directoryHint?: string | null) => void; setCurrentSession: (sessionId: string | null, directoryHint?: string | null) => void;
updateSessionTitle: (id: string, title: string) => Promise<void>; updateSessionTitle: (id: string, title: string) => Promise<void>;
+1
View File
@@ -24,6 +24,7 @@ export const dict = {
'layout.mainTab.diff': 'Diff', 'layout.mainTab.diff': 'Diff',
'layout.mainTab.files': 'Files', 'layout.mainTab.files': 'Files',
'layout.mainTab.terminal': 'Terminal', 'layout.mainTab.terminal': 'Terminal',
'layout.mainTab.context': 'Context',
'layout.rightSidebar.git': 'Git', 'layout.rightSidebar.git': 'Git',
'layout.rightSidebar.files': 'Files', 'layout.rightSidebar.files': 'Files',
'layout.rightSidebar.context': 'Context', 'layout.rightSidebar.context': 'Context',
+1
View File
@@ -25,6 +25,7 @@ export const dict: Record<I18nKey, string> = {
"layout.mainTab.diff": "Diff", "layout.mainTab.diff": "Diff",
"layout.mainTab.files": "Archivos", "layout.mainTab.files": "Archivos",
"layout.mainTab.terminal": "Terminal", "layout.mainTab.terminal": "Terminal",
"layout.mainTab.context": "Contexto",
"layout.rightSidebar.git": "Git", "layout.rightSidebar.git": "Git",
"layout.rightSidebar.files": "Archivos", "layout.rightSidebar.files": "Archivos",
"layout.rightSidebar.context": "Contexto", "layout.rightSidebar.context": "Contexto",
+1
View File
@@ -25,6 +25,7 @@ export const dict: Record<I18nKey, string> = {
'layout.mainTab.diff': '변경사항', 'layout.mainTab.diff': '변경사항',
'layout.mainTab.files': '파일', 'layout.mainTab.files': '파일',
'layout.mainTab.terminal': '터미널', 'layout.mainTab.terminal': '터미널',
'layout.mainTab.context': '컨텍스트',
'layout.rightSidebar.git': 'Git', 'layout.rightSidebar.git': 'Git',
'layout.rightSidebar.files': '파일', 'layout.rightSidebar.files': '파일',
'layout.rightSidebar.context': '컨텍스트', 'layout.rightSidebar.context': '컨텍스트',
+1
View File
@@ -26,6 +26,7 @@ export const dict: Record<I18nKey, string> = {
'layout.mainTab.diff': 'Różnice', 'layout.mainTab.diff': 'Różnice',
'layout.mainTab.files': 'Pliki', 'layout.mainTab.files': 'Pliki',
'layout.mainTab.terminal': 'Terminal', 'layout.mainTab.terminal': 'Terminal',
'layout.mainTab.context': 'Kontekst',
'layout.rightSidebar.git': 'Git', 'layout.rightSidebar.git': 'Git',
'layout.rightSidebar.files': 'Pliki', 'layout.rightSidebar.files': 'Pliki',
'layout.rightSidebar.context': 'Kontekst', 'layout.rightSidebar.context': 'Kontekst',
@@ -25,6 +25,7 @@ export const dict: Record<I18nKey, string> = {
"layout.mainTab.diff": "Diff", "layout.mainTab.diff": "Diff",
"layout.mainTab.files": "Arquivos", "layout.mainTab.files": "Arquivos",
"layout.mainTab.terminal": "Terminal", "layout.mainTab.terminal": "Terminal",
"layout.mainTab.context": "Contexto",
"layout.rightSidebar.git": "Git", "layout.rightSidebar.git": "Git",
"layout.rightSidebar.files": "Arquivos", "layout.rightSidebar.files": "Arquivos",
"layout.rightSidebar.context": "Contexto", "layout.rightSidebar.context": "Contexto",
+1
View File
@@ -25,6 +25,7 @@ export const dict: Record<I18nKey, string> = {
"layout.mainTab.diff": "Diff", "layout.mainTab.diff": "Diff",
"layout.mainTab.files": "Файли", "layout.mainTab.files": "Файли",
"layout.mainTab.terminal": "Термінал", "layout.mainTab.terminal": "Термінал",
"layout.mainTab.context": "Контекст",
"layout.rightSidebar.git": "Git", "layout.rightSidebar.git": "Git",
"layout.rightSidebar.files": "Файли", "layout.rightSidebar.files": "Файли",
"layout.rightSidebar.context": "Контекст", "layout.rightSidebar.context": "Контекст",
@@ -25,6 +25,7 @@ export const dict: Record<I18nKey, string> = {
'layout.mainTab.diff': '差异', 'layout.mainTab.diff': '差异',
'layout.mainTab.files': '文件', 'layout.mainTab.files': '文件',
'layout.mainTab.terminal': '终端', 'layout.mainTab.terminal': '终端',
'layout.mainTab.context': '上下文',
'layout.rightSidebar.git': 'Git', 'layout.rightSidebar.git': 'Git',
'layout.rightSidebar.files': '文件', 'layout.rightSidebar.files': '文件',
'layout.rightSidebar.context': '上下文', 'layout.rightSidebar.context': '上下文',
+1 -1
View File
@@ -7,7 +7,7 @@ import type { ShortcutCombo } from '@/lib/shortcuts';
import { DEFAULT_MONO_FONT, DEFAULT_UI_FONT, type MonoFontOption, type UiFontOption } from '@/lib/fontOptions'; import { DEFAULT_MONO_FONT, DEFAULT_UI_FONT, type MonoFontOption, type UiFontOption } from '@/lib/fontOptions';
import { getStoredMobileKeyboardMode, type MobileKeyboardMode } from '@/lib/mobileKeyboardMode'; import { getStoredMobileKeyboardMode, type MobileKeyboardMode } from '@/lib/mobileKeyboardMode';
export type MainTab = 'chat' | 'plan' | 'git' | 'diff' | 'terminal' | 'files'; export type MainTab = 'chat' | 'plan' | 'git' | 'diff' | 'terminal' | 'files' | 'context';
export type RightSidebarTab = 'git' | 'files' | 'context'; export type RightSidebarTab = 'git' | 'files' | 'context';
export type ContextPanelMode = 'diff' | 'file' | 'context' | 'plan' | 'chat' | 'preview' | 'browser'; export type ContextPanelMode = 'diff' | 'file' | 'context' | 'plan' | 'chat' | 'preview' | 'browser';
export type MermaidRenderingMode = 'svg' | 'ascii'; export type MermaidRenderingMode = 'svg' | 'ascii';
+1 -1
View File
@@ -108,7 +108,7 @@ function convertVsCodeTheme(vscodeThemePath) {
const semanticTokenColors = normalizedTheme.semanticTokenColors || {}; const semanticTokenColors = normalizedTheme.semanticTokenColors || {};
// Determine variant // Determine variant
const isDark = normalizedTheme.type === 'dark' || const isDark = normalizedTheme.type === 'dark' ||
colors['editor.background']?.toLowerCase() < '#808080' || colors['editor.background']?.toLowerCase() < '#808080' ||
colors['workbench.colorTheme']?.includes('dark'); colors['workbench.colorTheme']?.includes('dark');