feat: introduce useEffectiveDirectory hook and adopt across views (#226)
Add a centralized useEffectiveDirectory hook to resolve the active directory Use the hook across DiffView, FilesView, GitView, and TerminalView Fix list styling by rendering ul/ol bullets outside
This commit is contained in:
committed by
GitHub
parent
8b515346bc
commit
b861e4abf1
Generated
+1
-1
@@ -2994,7 +2994,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openchamber-desktop"
|
name = "openchamber-desktop"
|
||||||
version = "1.5.7"
|
version = "1.5.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ const UserTextPart: React.FC<UserTextPartProps> = ({ part, messageId, agentMenti
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render content with optional agent mention link
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
if (!agentMention?.token || !textContent.includes(agentMention.token)) {
|
if (!agentMention?.token || !textContent.includes(agentMention.token)) {
|
||||||
return textContent;
|
return textContent;
|
||||||
@@ -67,7 +68,8 @@ const UserTextPart: React.FC<UserTextPartProps> = ({ part, messageId, agentMenti
|
|||||||
href={buildMentionUrl(agentMention.name)}
|
href={buildMentionUrl(agentMention.name)}
|
||||||
className="text-primary hover:underline"
|
className="text-primary hover:underline"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{agentMention.token}
|
{agentMention.token}
|
||||||
</a>
|
</a>
|
||||||
@@ -79,7 +81,7 @@ const UserTextPart: React.FC<UserTextPartProps> = ({ part, messageId, agentMenti
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"font-sans typography-markdown whitespace-pre-wrap",
|
"break-words whitespace-pre-wrap font-sans typography-markdown",
|
||||||
!isExpanded && "line-clamp-3",
|
!isExpanded && "line-clamp-3",
|
||||||
(isTruncated || isExpanded) && "cursor-pointer"
|
(isTruncated || isExpanded) && "cursor-pointer"
|
||||||
)}
|
)}
|
||||||
@@ -87,9 +89,7 @@ const UserTextPart: React.FC<UserTextPartProps> = ({ part, messageId, agentMenti
|
|||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
key={part.id || `${messageId}-user-text`}
|
key={part.id || `${messageId}-user-text`}
|
||||||
>
|
>
|
||||||
<span className="text-foreground/90">
|
{renderContent()}
|
||||||
{renderContent()}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { RiArrowDownSLine, RiArrowRightSLine, RiGitCommitLine, RiLoader4Line, RiTextWrap } from '@remixicon/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 { useUIStore } from '@/stores/useUIStore';
|
||||||
|
import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
|
||||||
import { useGitStore, useGitStatus, useIsGitRepo, useGitFileCount } from '@/stores/useGitStore';
|
import { useGitStore, useGitStatus, useIsGitRepo, useGitFileCount } from '@/stores/useGitStore';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import type { GitStatus } from '@/lib/api/types';
|
import type { GitStatus } from '@/lib/api/types';
|
||||||
@@ -793,17 +792,6 @@ const MultiFileDiffEntry = React.memo<MultiFileDiffEntryProps>(({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
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<string, unknown>)?.directory as string | undefined;
|
|
||||||
|
|
||||||
return worktreeMetadata?.path ?? sessionDirectory ?? fallbackDirectory ?? undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DiffView: React.FC = () => {
|
export const DiffView: React.FC = () => {
|
||||||
const { git } = useRuntimeAPIs();
|
const { git } = useRuntimeAPIs();
|
||||||
const effectiveDirectory = useEffectiveDirectory();
|
const effectiveDirectory = useEffectiveDirectory();
|
||||||
@@ -1246,13 +1234,7 @@ export const DiffView: React.FC = () => {
|
|||||||
// eslint-disable-next-line react-refresh/only-export-components
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
export const useDiffFileCount = (): number => {
|
export const useDiffFileCount = (): number => {
|
||||||
const { git } = useRuntimeAPIs();
|
const { git } = useRuntimeAPIs();
|
||||||
const { currentSessionId, sessions, worktreeMetadata: worktreeMap } = useSessionStore();
|
const effectiveDirectory = useEffectiveDirectory();
|
||||||
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<string, unknown>)?.directory as string | undefined;
|
|
||||||
const effectiveDirectory = worktreeMetadata?.path ?? sessionDirectory ?? fallbackDirectory ?? undefined;
|
|
||||||
|
|
||||||
const { setActiveDirectory, fetchStatus } = useGitStore();
|
const { setActiveDirectory, fetchStatus } = useGitStore();
|
||||||
const fileCount = useGitFileCount(effectiveDirectory ?? null);
|
const fileCount = useGitFileCount(effectiveDirectory ?? null);
|
||||||
|
|||||||
@@ -63,11 +63,11 @@ import { useSessionStore } from '@/stores/useSessionStore';
|
|||||||
import { useConfigStore } from '@/stores/useConfigStore';
|
import { useConfigStore } from '@/stores/useConfigStore';
|
||||||
import { useContextStore } from '@/stores/contextStore';
|
import { useContextStore } from '@/stores/contextStore';
|
||||||
import { useUIStore } from '@/stores/useUIStore';
|
import { useUIStore } from '@/stores/useUIStore';
|
||||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
|
||||||
import { opencodeClient } from '@/lib/opencode/client';
|
import { opencodeClient } from '@/lib/opencode/client';
|
||||||
import { useDirectoryShowHidden } from '@/lib/directoryShowHidden';
|
import { useDirectoryShowHidden } from '@/lib/directoryShowHidden';
|
||||||
import { useFilesViewShowGitignored } from '@/lib/filesViewShowGitignored';
|
import { useFilesViewShowGitignored } from '@/lib/filesViewShowGitignored';
|
||||||
import { ErrorBoundary } from '@/components/ui/ErrorBoundary';
|
import { ErrorBoundary } from '@/components/ui/ErrorBoundary';
|
||||||
|
import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
|
||||||
|
|
||||||
type FileNode = {
|
type FileNode = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -101,18 +101,6 @@ const shouldIgnorePath = (path: string): boolean => {
|
|||||||
return normalized === 'node_modules' || normalized.endsWith('/node_modules') || normalized.includes('/node_modules/');
|
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 MAX_VIEW_CHARS = 200_000;
|
||||||
|
|
||||||
const CODE_EXTENSIONS = new Set([
|
const CODE_EXTENSIONS = new Set([
|
||||||
@@ -263,7 +251,7 @@ export const FilesView: React.FC = () => {
|
|||||||
const showHidden = useDirectoryShowHidden();
|
const showHidden = useDirectoryShowHidden();
|
||||||
const showGitignored = useFilesViewShowGitignored();
|
const showGitignored = useFilesViewShowGitignored();
|
||||||
|
|
||||||
const currentDirectory = useEffectiveDirectory();
|
const currentDirectory = useEffectiveDirectory() ?? '';
|
||||||
const root = normalizePath(currentDirectory);
|
const root = normalizePath(currentDirectory);
|
||||||
const searchFiles = useFileSearchStore((state) => state.searchFiles);
|
const searchFiles = useFileSearchStore((state) => state.searchFiles);
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { useConfigStore } from '@/stores/useConfigStore';
|
|||||||
import { useFireworksCelebration } from '@/contexts/FireworksContext';
|
import { useFireworksCelebration } from '@/contexts/FireworksContext';
|
||||||
import type { GitIdentityProfile, CommitFileEntry } from '@/lib/api/types';
|
import type { GitIdentityProfile, CommitFileEntry } from '@/lib/api/types';
|
||||||
import { useGitIdentitiesStore } from '@/stores/useGitIdentitiesStore';
|
import { useGitIdentitiesStore } from '@/stores/useGitIdentitiesStore';
|
||||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
|
||||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||||
|
import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
|
||||||
import {
|
import {
|
||||||
useGitStore,
|
useGitStore,
|
||||||
useGitStatus,
|
useGitStatus,
|
||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
CommandItem,
|
CommandItem,
|
||||||
CommandList,
|
CommandList,
|
||||||
} from '@/components/ui/command';
|
} from '@/components/ui/command';
|
||||||
import type { Session } from '@opencode-ai/sdk/v2';
|
|
||||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||||
import { useUIStore } from '@/stores/useUIStore';
|
import { useUIStore } from '@/stores/useUIStore';
|
||||||
|
|
||||||
@@ -182,22 +182,6 @@ const matchGitmojiFromSubject = (subject: string, gitmojis: GitmojiEntry[]): Git
|
|||||||
|
|
||||||
const gitViewSnapshots = new Map<string, GitViewSnapshot>();
|
const gitViewSnapshots = new Map<string, GitViewSnapshot>();
|
||||||
|
|
||||||
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 = () => {
|
export const GitView: React.FC = () => {
|
||||||
const { git } = useRuntimeAPIs();
|
const { git } = useRuntimeAPIs();
|
||||||
const currentDirectory = useEffectiveDirectory();
|
const currentDirectory = useEffectiveDirectory();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { RiAlertLine, RiArrowDownLine, RiArrowGoBackLine, RiArrowLeftLine, RiArr
|
|||||||
import { useSessionStore } from '@/stores/useSessionStore';
|
import { useSessionStore } from '@/stores/useSessionStore';
|
||||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||||
import { useTerminalStore } from '@/stores/useTerminalStore';
|
import { useTerminalStore } from '@/stores/useTerminalStore';
|
||||||
|
import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
|
||||||
import { type TerminalStreamEvent } from '@/lib/api/types';
|
import { type TerminalStreamEvent } from '@/lib/api/types';
|
||||||
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
||||||
import { useFontPreferences } from '@/hooks/useFontPreferences';
|
import { useFontPreferences } from '@/hooks/useFontPreferences';
|
||||||
@@ -79,23 +80,11 @@ export const TerminalView: React.FC = () => {
|
|||||||
const { monoFont } = useFontPreferences();
|
const { monoFont } = useFontPreferences();
|
||||||
const { isMobile, hasTouchInput } = useDeviceInfo();
|
const { isMobile, hasTouchInput } = useDeviceInfo();
|
||||||
|
|
||||||
const { currentSessionId, sessions, worktreeMetadata: worktreeMap } = useSessionStore();
|
const { currentSessionId, newSessionDraft } = useSessionStore();
|
||||||
const worktreeMetadata = currentSessionId ? worktreeMap.get(currentSessionId) ?? undefined : undefined;
|
const hasActiveContext = currentSessionId !== null || newSessionDraft?.open === true;
|
||||||
|
|
||||||
const sessionDirectory = React.useMemo(() => {
|
const effectiveDirectory = useEffectiveDirectory() ?? null;
|
||||||
if (worktreeMetadata?.path) {
|
const { homeDirectory } = useDirectoryStore();
|
||||||
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 displayDirectory = React.useMemo(() => {
|
const displayDirectory = React.useMemo(() => {
|
||||||
if (!effectiveDirectory) return '';
|
if (!effectiveDirectory) return '';
|
||||||
@@ -269,7 +258,7 @@ export const TerminalView: React.FC = () => {
|
|||||||
|
|
||||||
if (!effectiveDirectory) {
|
if (!effectiveDirectory) {
|
||||||
setConnectionError(
|
setConnectionError(
|
||||||
currentSessionId
|
hasActiveContext
|
||||||
? 'No working directory available for terminal.'
|
? 'No working directory available for terminal.'
|
||||||
: 'Select a session to open the terminal.'
|
: 'Select a session to open the terminal.'
|
||||||
);
|
);
|
||||||
@@ -328,7 +317,7 @@ export const TerminalView: React.FC = () => {
|
|||||||
disconnectStream();
|
disconnectStream();
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
currentSessionId,
|
hasActiveContext,
|
||||||
effectiveDirectory,
|
effectiveDirectory,
|
||||||
terminalSessionId,
|
terminalSessionId,
|
||||||
removeTerminalSession,
|
removeTerminalSession,
|
||||||
@@ -645,7 +634,7 @@ export const TerminalView: React.FC = () => {
|
|||||||
? <RiCircleLine size={20} className="text-amber-400 animate-pulse" />
|
? <RiCircleLine size={20} className="text-amber-400 animate-pulse" />
|
||||||
: <RiCircleLine size={20} className="text-muted-foreground" />;
|
: <RiCircleLine size={20} className="text-muted-foreground" />;
|
||||||
|
|
||||||
if (!currentSessionId) {
|
if (!hasActiveContext) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full items-center justify-center p-4 text-center text-sm text-muted-foreground">
|
<div className="flex h-full items-center justify-center p-4 text-center text-sm text-muted-foreground">
|
||||||
Select a session to open the terminal
|
Select a session to open the terminal
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
@@ -572,13 +572,13 @@ html:not(.dark) .chat-scroll {
|
|||||||
/* Fix list styling - override Tailwind reset */
|
/* Fix list styling - override Tailwind reset */
|
||||||
.streamdown-content ul {
|
.streamdown-content ul {
|
||||||
list-style-type: disc;
|
list-style-type: disc;
|
||||||
list-style-position: inside;
|
list-style-position: outside;
|
||||||
padding-left: 1.5em;
|
padding-left: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.streamdown-content ol {
|
.streamdown-content ol {
|
||||||
list-style-type: decimal;
|
list-style-type: decimal;
|
||||||
list-style-position: inside;
|
list-style-position: outside;
|
||||||
padding-left: 1.5em;
|
padding-left: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user