Refactor chat file-link opens to a single disposition seam (#1038)
* Open chat file links in a side pane on cmd-click
* Add hover tooltips to file links showing cmd-click hint
* Collapse file-link open path to a single disposition seam
Replace the parallel "open in side" callback that was threaded
alongside the normal open callback at every layer between the chat
click handler and the workspace router. One callback now carries a
`OpenFileDisposition` ("main" | "side") so the modifier-key decision
lives at the leaf and the routing decision lives at the workspace
screen, with no branching in between.
* Give assistant file links a home
This commit is contained in:
@@ -59,6 +59,7 @@ import type { PendingPermission } from "@/types/shared";
|
||||
import type { StreamItem } from "@/types/stream";
|
||||
import { getInitDeferred, getInitKey } from "@/utils/agent-initialization";
|
||||
import { derivePendingPermissionKey, normalizeAgentSnapshot } from "@/utils/agent-snapshots";
|
||||
import type { OpenFileDisposition } from "@/utils/workspace-file-open";
|
||||
import { mergePendingCreateImages } from "@/utils/pending-create-images";
|
||||
import { navigateToAgent } from "@/utils/navigate-to-agent";
|
||||
import { deriveSidebarStateBucket } from "@/utils/sidebar-agent-state";
|
||||
@@ -288,11 +289,11 @@ function AgentPanel() {
|
||||
const { isInteractive } = usePaneFocus();
|
||||
invariant(target.kind === "agent", "AgentPanel requires agent target");
|
||||
|
||||
function openWorkspaceFile(input: { filePath: string }) {
|
||||
openFileInWorkspace(input.filePath);
|
||||
}
|
||||
|
||||
const handleOpenWorkspaceFile = useStableEvent(openWorkspaceFile);
|
||||
const handleOpenWorkspaceFile = useStableEvent(
|
||||
({ filePath, disposition }: { filePath: string; disposition: OpenFileDisposition }) => {
|
||||
openFileInWorkspace(filePath, disposition);
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<AgentPanelContent
|
||||
@@ -365,7 +366,7 @@ function AgentPanelContent({
|
||||
serverId: string;
|
||||
agentId: string;
|
||||
isPaneFocused: boolean;
|
||||
onOpenWorkspaceFile?: (input: { filePath: string }) => void;
|
||||
onOpenWorkspaceFile?: (input: { filePath: string; disposition: OpenFileDisposition }) => void;
|
||||
}) {
|
||||
const resolvedAgentId = agentId.trim() || undefined;
|
||||
const resolvedServerId = serverId.trim() || undefined;
|
||||
@@ -427,7 +428,7 @@ function AgentPanelBody({
|
||||
client: NonNullable<ReturnType<typeof useHostRuntimeClient>>;
|
||||
isConnected: boolean;
|
||||
connectionStatus: HostRuntimeConnectionStatus;
|
||||
onOpenWorkspaceFile?: (input: { filePath: string }) => void;
|
||||
onOpenWorkspaceFile?: (input: { filePath: string; disposition: OpenFileDisposition }) => void;
|
||||
}) {
|
||||
const { isArchivingAgent: _isArchivingAgent } = useArchiveAgent();
|
||||
const hasSession = useSessionStore((state) => Boolean(state.sessions[serverId]));
|
||||
@@ -592,7 +593,7 @@ function ChatAgentContent({
|
||||
client: NonNullable<ReturnType<typeof useHostRuntimeClient>>;
|
||||
isConnected: boolean;
|
||||
connectionStatus: HostRuntimeConnectionStatus;
|
||||
onOpenWorkspaceFile?: (input: { filePath: string }) => void;
|
||||
onOpenWorkspaceFile?: (input: { filePath: string; disposition: OpenFileDisposition }) => void;
|
||||
}) {
|
||||
const panelToast = useToastHost();
|
||||
const { isArchivingAgent } = useArchiveAgent();
|
||||
@@ -1041,7 +1042,7 @@ function AgentStreamSection({
|
||||
routeBottomAnchorRequest: RouteBottomAnchorRequest;
|
||||
hasAppliedAuthoritativeHistory: boolean;
|
||||
toast: ReturnType<typeof useToastHost>["api"];
|
||||
onOpenWorkspaceFile?: (input: { filePath: string }) => void;
|
||||
onOpenWorkspaceFile?: (input: { filePath: string; disposition: OpenFileDisposition }) => void;
|
||||
}) {
|
||||
const streamItemsRaw = useSessionStore((state) =>
|
||||
agentId ? state.sessions[serverId]?.agentStreamTail?.get(agentId) : undefined,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { usePaneContext, usePaneFocus } from "@/panels/pane-context";
|
||||
import type { PanelRegistration } from "@/panels/panel-registry";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import { normalizeAgentSnapshot } from "@/utils/agent-snapshots";
|
||||
import type { OpenFileDisposition } from "@/utils/workspace-file-open";
|
||||
|
||||
function useDraftPanelDescriptor() {
|
||||
return {
|
||||
@@ -31,8 +32,8 @@ function DraftPanel() {
|
||||
invariant(target.kind === "draft", "DraftPanel requires draft target");
|
||||
|
||||
const handleOpenWorkspaceFile = useCallback(
|
||||
({ filePath }: { filePath: string }) => {
|
||||
openFileInWorkspace(filePath);
|
||||
({ filePath, disposition }: { filePath: string; disposition: OpenFileDisposition }) => {
|
||||
openFileInWorkspace(filePath, disposition);
|
||||
},
|
||||
[openFileInWorkspace],
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { createContext, useContext, type ReactNode } from "react";
|
||||
import invariant from "tiny-invariant";
|
||||
import type { WorkspaceTabTarget } from "@/stores/workspace-tabs-store";
|
||||
import type { OpenFileDisposition } from "@/utils/workspace-file-open";
|
||||
|
||||
export interface PaneContextValue {
|
||||
serverId: string;
|
||||
@@ -10,7 +11,7 @@ export interface PaneContextValue {
|
||||
openTab: (target: WorkspaceTabTarget) => void;
|
||||
closeCurrentTab: () => void;
|
||||
retargetCurrentTab: (target: WorkspaceTabTarget) => void;
|
||||
openFileInWorkspace: (filePath: string) => void;
|
||||
openFileInWorkspace: (filePath: string, disposition: OpenFileDisposition) => void;
|
||||
openImportSheet: () => void;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user