diff --git a/packages/desktop/src-tauri/Cargo.lock b/packages/desktop/src-tauri/Cargo.lock index d0c2150a..6b6c8aef 100644 --- a/packages/desktop/src-tauri/Cargo.lock +++ b/packages/desktop/src-tauri/Cargo.lock @@ -2994,7 +2994,7 @@ dependencies = [ [[package]] name = "openchamber-desktop" -version = "1.5.7" +version = "1.5.8" dependencies = [ "anyhow", "axum", diff --git a/packages/ui/src/components/chat/message/parts/UserTextPart.tsx b/packages/ui/src/components/chat/message/parts/UserTextPart.tsx index acf93522..690443fd 100644 --- a/packages/ui/src/components/chat/message/parts/UserTextPart.tsx +++ b/packages/ui/src/components/chat/message/parts/UserTextPart.tsx @@ -53,6 +53,7 @@ const UserTextPart: React.FC = ({ part, messageId, agentMenti return null; } + // Render content with optional agent mention link const renderContent = () => { if (!agentMention?.token || !textContent.includes(agentMention.token)) { return textContent; @@ -67,7 +68,8 @@ const UserTextPart: React.FC = ({ part, messageId, agentMenti href={buildMentionUrl(agentMention.name)} className="text-primary hover:underline" target="_blank" - rel="noreferrer" + rel="noopener noreferrer" + onClick={(e) => e.stopPropagation()} > {agentMention.token} @@ -79,7 +81,7 @@ const UserTextPart: React.FC = ({ part, messageId, agentMenti return (
= ({ part, messageId, agentMenti onClick={handleClick} key={part.id || `${messageId}-user-text`} > - - {renderContent()} - + {renderContent()}
); }; diff --git a/packages/ui/src/components/views/DiffView.tsx b/packages/ui/src/components/views/DiffView.tsx index 88516fe1..f0088a9a 100644 --- a/packages/ui/src/components/views/DiffView.tsx +++ b/packages/ui/src/components/views/DiffView.tsx @@ -1,9 +1,8 @@ import React from 'react'; import { RiArrowDownSLine, RiArrowRightSLine, RiGitCommitLine, RiLoader4Line, RiTextWrap } from '@remixicon/react'; -import { useSessionStore } from '@/stores/useSessionStore'; -import { useDirectoryStore } from '@/stores/useDirectoryStore'; import { useUIStore } from '@/stores/useUIStore'; +import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory'; import { useGitStore, useGitStatus, useIsGitRepo, useGitFileCount } from '@/stores/useGitStore'; import { cn } from '@/lib/utils'; import type { GitStatus } from '@/lib/api/types'; @@ -793,17 +792,6 @@ const MultiFileDiffEntry = React.memo(({ ); }); -const useEffectiveDirectory = () => { - const { currentSessionId, sessions, worktreeMetadata: worktreeMap } = useSessionStore(); - const { currentDirectory: fallbackDirectory } = useDirectoryStore(); - - const worktreeMetadata = currentSessionId ? worktreeMap.get(currentSessionId) ?? undefined : undefined; - const currentSession = sessions.find((session) => session.id === currentSessionId); - const sessionDirectory = (currentSession as Record)?.directory as string | undefined; - - return worktreeMetadata?.path ?? sessionDirectory ?? fallbackDirectory ?? undefined; -}; - export const DiffView: React.FC = () => { const { git } = useRuntimeAPIs(); const effectiveDirectory = useEffectiveDirectory(); @@ -1246,13 +1234,7 @@ export const DiffView: React.FC = () => { // eslint-disable-next-line react-refresh/only-export-components export const useDiffFileCount = (): number => { const { git } = useRuntimeAPIs(); - const { currentSessionId, sessions, worktreeMetadata: worktreeMap } = useSessionStore(); - const { currentDirectory: fallbackDirectory } = useDirectoryStore(); - - const worktreeMetadata = currentSessionId ? worktreeMap.get(currentSessionId) ?? undefined : undefined; - const currentSession = sessions.find((session) => session.id === currentSessionId); - const sessionDirectory = (currentSession as Record)?.directory as string | undefined; - const effectiveDirectory = worktreeMetadata?.path ?? sessionDirectory ?? fallbackDirectory ?? undefined; + const effectiveDirectory = useEffectiveDirectory(); const { setActiveDirectory, fetchStatus } = useGitStore(); const fileCount = useGitFileCount(effectiveDirectory ?? null); diff --git a/packages/ui/src/components/views/FilesView.tsx b/packages/ui/src/components/views/FilesView.tsx index d7c51df9..7f1beb70 100644 --- a/packages/ui/src/components/views/FilesView.tsx +++ b/packages/ui/src/components/views/FilesView.tsx @@ -63,11 +63,11 @@ import { useSessionStore } from '@/stores/useSessionStore'; import { useConfigStore } from '@/stores/useConfigStore'; import { useContextStore } from '@/stores/contextStore'; import { useUIStore } from '@/stores/useUIStore'; -import { useDirectoryStore } from '@/stores/useDirectoryStore'; import { opencodeClient } from '@/lib/opencode/client'; import { useDirectoryShowHidden } from '@/lib/directoryShowHidden'; import { useFilesViewShowGitignored } from '@/lib/filesViewShowGitignored'; import { ErrorBoundary } from '@/components/ui/ErrorBoundary'; +import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory'; type FileNode = { name: string; @@ -101,18 +101,6 @@ const shouldIgnorePath = (path: string): boolean => { return normalized === 'node_modules' || normalized.endsWith('/node_modules') || normalized.includes('/node_modules/'); }; -const useEffectiveDirectory = () => { - const { currentSessionId, sessions, worktreeMetadata: worktreeMap } = useSessionStore(); - const { currentDirectory: fallbackDirectory } = useDirectoryStore(); - - const worktreeMetadata = currentSessionId ? worktreeMap.get(currentSessionId) ?? undefined : undefined; - const currentSession = sessions.find((session) => session.id === currentSessionId); - type SessionWithDirectory = { directory?: string }; - const sessionDirectory = (currentSession as unknown as SessionWithDirectory | undefined)?.directory; - - return worktreeMetadata?.path ?? sessionDirectory ?? fallbackDirectory ?? ''; -}; - const MAX_VIEW_CHARS = 200_000; const CODE_EXTENSIONS = new Set([ @@ -263,7 +251,7 @@ export const FilesView: React.FC = () => { const showHidden = useDirectoryShowHidden(); const showGitignored = useFilesViewShowGitignored(); - const currentDirectory = useEffectiveDirectory(); + const currentDirectory = useEffectiveDirectory() ?? ''; const root = normalizePath(currentDirectory); const searchFiles = useFileSearchStore((state) => state.searchFiles); diff --git a/packages/ui/src/components/views/GitView.tsx b/packages/ui/src/components/views/GitView.tsx index fca9642b..2dccd00c 100644 --- a/packages/ui/src/components/views/GitView.tsx +++ b/packages/ui/src/components/views/GitView.tsx @@ -4,8 +4,8 @@ import { useConfigStore } from '@/stores/useConfigStore'; import { useFireworksCelebration } from '@/contexts/FireworksContext'; import type { GitIdentityProfile, CommitFileEntry } from '@/lib/api/types'; import { useGitIdentitiesStore } from '@/stores/useGitIdentitiesStore'; -import { useDirectoryStore } from '@/stores/useDirectoryStore'; import { useProjectsStore } from '@/stores/useProjectsStore'; +import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory'; import { useGitStore, useGitStatus, @@ -31,7 +31,7 @@ import { CommandItem, CommandList, } from '@/components/ui/command'; -import type { Session } from '@opencode-ai/sdk/v2'; + import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; import { useUIStore } from '@/stores/useUIStore'; @@ -182,22 +182,6 @@ const matchGitmojiFromSubject = (subject: string, gitmojis: GitmojiEntry[]): Git const gitViewSnapshots = new Map(); -const useEffectiveDirectory = () => { - const { currentSessionId, sessions, worktreeMetadata: worktreeMap } = useSessionStore(); - const { currentDirectory: fallbackDirectory } = useDirectoryStore(); - - const worktreeMetadata = currentSessionId - ? worktreeMap.get(currentSessionId) ?? undefined - : undefined; - const currentSession = sessions.find((session) => session.id === currentSessionId); - type SessionWithDirectory = Session & { directory?: string }; - const sessionDirectory: string | undefined = ( - currentSession as SessionWithDirectory | undefined - )?.directory; - - return worktreeMetadata?.path ?? sessionDirectory ?? fallbackDirectory ?? undefined; -}; - export const GitView: React.FC = () => { const { git } = useRuntimeAPIs(); const currentDirectory = useEffectiveDirectory(); diff --git a/packages/ui/src/components/views/TerminalView.tsx b/packages/ui/src/components/views/TerminalView.tsx index aaeb628e..afb6174d 100644 --- a/packages/ui/src/components/views/TerminalView.tsx +++ b/packages/ui/src/components/views/TerminalView.tsx @@ -4,6 +4,7 @@ import { RiAlertLine, RiArrowDownLine, RiArrowGoBackLine, RiArrowLeftLine, RiArr import { useSessionStore } from '@/stores/useSessionStore'; import { useDirectoryStore } from '@/stores/useDirectoryStore'; import { useTerminalStore } from '@/stores/useTerminalStore'; +import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory'; import { type TerminalStreamEvent } from '@/lib/api/types'; import { useThemeSystem } from '@/contexts/useThemeSystem'; import { useFontPreferences } from '@/hooks/useFontPreferences'; @@ -79,23 +80,11 @@ export const TerminalView: React.FC = () => { const { monoFont } = useFontPreferences(); const { isMobile, hasTouchInput } = useDeviceInfo(); - const { currentSessionId, sessions, worktreeMetadata: worktreeMap } = useSessionStore(); - const worktreeMetadata = currentSessionId ? worktreeMap.get(currentSessionId) ?? undefined : undefined; + const { currentSessionId, newSessionDraft } = useSessionStore(); + const hasActiveContext = currentSessionId !== null || newSessionDraft?.open === true; - const sessionDirectory = React.useMemo(() => { - if (worktreeMetadata?.path) { - return worktreeMetadata.path; - } - if (!currentSessionId) return null; - const entry = sessions.find((session) => session.id === currentSessionId); - const directory = typeof (entry as { directory?: string } | undefined)?.directory === 'string' - ? (entry as { directory?: string }).directory - : null; - return directory && directory.length > 0 ? directory : null; - }, [currentSessionId, sessions, worktreeMetadata]); - - const { currentDirectory: fallbackDirectory, homeDirectory } = useDirectoryStore(); - const effectiveDirectory = sessionDirectory || fallbackDirectory || null; + const effectiveDirectory = useEffectiveDirectory() ?? null; + const { homeDirectory } = useDirectoryStore(); const displayDirectory = React.useMemo(() => { if (!effectiveDirectory) return ''; @@ -269,7 +258,7 @@ export const TerminalView: React.FC = () => { if (!effectiveDirectory) { setConnectionError( - currentSessionId + hasActiveContext ? 'No working directory available for terminal.' : 'Select a session to open the terminal.' ); @@ -328,7 +317,7 @@ export const TerminalView: React.FC = () => { disconnectStream(); }; }, [ - currentSessionId, + hasActiveContext, effectiveDirectory, terminalSessionId, removeTerminalSession, @@ -645,7 +634,7 @@ export const TerminalView: React.FC = () => { ? : ; - if (!currentSessionId) { + if (!hasActiveContext) { return (
Select a session to open the terminal diff --git a/packages/ui/src/hooks/useEffectiveDirectory.ts b/packages/ui/src/hooks/useEffectiveDirectory.ts new file mode 100644 index 00000000..c154753f --- /dev/null +++ b/packages/ui/src/hooks/useEffectiveDirectory.ts @@ -0,0 +1,48 @@ +import { useSessionStore } from '@/stores/useSessionStore'; +import { useDirectoryStore } from '@/stores/useDirectoryStore'; +import type { Session } from '@opencode-ai/sdk/v2'; + +/** + * Hook that resolves the effective working directory for tabs (Git, Diff, Files, Terminal). + * + * Priority order: + * 1. Worktree metadata path (for worktree sessions) + * 2. Session directory (for active sessions) + * 3. Draft session directoryOverride (when creating a new session) + * 4. Fallback directory from DirectoryStore + * + * This ensures that tabs show content from the correct project directory + * even when a draft session is being created. + */ +export const useEffectiveDirectory = (): string | undefined => { + const { + currentSessionId, + sessions, + worktreeMetadata: worktreeMap, + newSessionDraft, + } = useSessionStore(); + const { currentDirectory: fallbackDirectory } = useDirectoryStore(); + + // If we have an active session, use its directory + if (currentSessionId) { + const worktreeMetadata = worktreeMap.get(currentSessionId); + if (worktreeMetadata?.path) { + return worktreeMetadata.path; + } + + const currentSession = sessions.find((session) => session.id === currentSessionId); + type SessionWithDirectory = Session & { directory?: string }; + const sessionDirectory = (currentSession as SessionWithDirectory | undefined)?.directory; + if (sessionDirectory) { + return sessionDirectory; + } + } + + // If a draft session is open, use its directoryOverride + if (newSessionDraft?.open && newSessionDraft.directoryOverride) { + return newSessionDraft.directoryOverride; + } + + // Fall back to the global directory + return fallbackDirectory ?? undefined; +}; diff --git a/packages/ui/src/index.css b/packages/ui/src/index.css index 54b9a65e..9f301dc5 100644 --- a/packages/ui/src/index.css +++ b/packages/ui/src/index.css @@ -572,13 +572,13 @@ html:not(.dark) .chat-scroll { /* Fix list styling - override Tailwind reset */ .streamdown-content ul { list-style-type: disc; - list-style-position: inside; + list-style-position: outside; padding-left: 1.5em; } .streamdown-content ol { list-style-type: decimal; - list-style-position: inside; + list-style-position: outside; padding-left: 1.5em; }