Skip to content

Commit b41978c

Browse files
committed
refactor(acp/editor/ui): remove runtime ACP permission mode switch and stabilize diff/editor behavior
Summary: - Remove frontend/backend runtime API for ACP permission mode switching (acp:set_permission_mode). - Keep backend startup-level permission mode via ANYCODE_ACP_PERMISSION_MODE (full_access by default, ask optional at process start). - Consolidate editor diff-mode ownership inside useEditors and persist per-pane diff mode in local storage. - Improve Changes panel active/selected state handling and keyboard navigation semantics. - Clean up debug logs and minor focus/activation behavior in editor UI. Detailed changes (bugs/fixes/refactors): 1) ACP permission mode pipeline simplification - Removed App-level forced emit for acp:set_permission_mode on reconnect. - Removed backend socket handler registration for acp:set_permission_mode. - Removed ACP permission-mode request handler and related request type from acp_handler. - Removed now-unused frontend permission-mode storage helpers and AcpPermissionMode type wiring in settings flow. - Behavior after change: permission mode is determined at backend startup from env only. 2) Diff mode architecture refactor (App -> useEditors) - Moved per-pane diff mode state and mode-cycling logic into useEditors. - Added persisted editorDiffModeByPane state and helper API: getEditorDiffMode / setEditorDiffMode / cycleEditorDiffMode. - Updated openFile flow to accept explicit diffMode and consistently apply mode when restoring/reopening files. - Added guarded re-application of pane/file diff state to avoid redundant updates. 3) Changes panel behavior and UX updates - Introduced explicit selectedFilePath separate from activeFilePath (active editor file). - Improved arrow key navigation + Enter activation against selected item. - Added active-file mapping from editor active file to git changed file (normalized path compare). - Updated styles for selected row treatment and tighter file-stat spacing. 4) Editor interaction and rendering tweaks - Ensure pane activation on wheel capture in EditorPanel (better focus ownership in multi-pane setups). - Ensure cursor/selection visual refresh after focus restoration in editor base. - Removed leftover debug console logs from click/selection handlers. - Adjusted renderer bottom padding behavior in scroll-into-view routine. 5) ACP settings/agents cleanup - Removed permission mode controls from ACP settings UI and related prop plumbing in AgentPanel/App. - Simplified useAgents dependencies by removing disabled follow-mode/open-file coupling. Notes: - Frontend build and backend cargo check pass after these changes.
1 parent b01b034 commit b41978c

16 files changed

Lines changed: 167 additions & 289 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# AGENTS.md test
1+
# AGENTS.md
22

33
This file provides guidance for AI coding agents working with the Anycode codebase.
44

anycode-backend/src/handlers/acp_handler.rs

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::acp::AcpSelectOption;
2-
use crate::acp::{AcpMessage, AcpPermissionMode};
2+
use crate::acp::AcpMessage;
33
use crate::app_state::AppState;
44
use crate::error_ack;
55
use serde::{Deserialize, Serialize};
@@ -316,11 +316,6 @@ pub struct AcpPermissionResponseRequest {
316316
pub option_id: String,
317317
}
318318

319-
#[derive(Debug, Serialize, Deserialize, Clone)]
320-
pub struct AcpPermissionModeRequest {
321-
pub mode: String,
322-
}
323-
324319
pub async fn handle_acp_permission_response(
325320
Data(request): Data<AcpPermissionResponseRequest>,
326321
ack: AckSender,
@@ -367,29 +362,6 @@ pub async fn handle_acp_permission_response(
367362
}
368363
}
369364

370-
pub async fn handle_acp_permission_mode(
371-
Data(request): Data<AcpPermissionModeRequest>,
372-
ack: AckSender,
373-
state: State<AppState>,
374-
) {
375-
info!("handle_acp_permission_mode {:?}", request);
376-
let AcpPermissionModeRequest { mode } = request;
377-
378-
let permission_mode = match AcpPermissionMode::from_str(&mode) {
379-
Some(mode) => mode,
380-
None => {
381-
error!("Invalid ACP permission mode requested: {}", mode);
382-
error_ack!(ack, &mode, "Invalid ACP permission mode: {}", mode);
383-
}
384-
};
385-
386-
let acp_manager = state.acp_manager.lock().await;
387-
acp_manager.set_permission_mode(permission_mode);
388-
389-
ack.send(&json!({ "success": true, "mode": permission_mode.as_str() }))
390-
.ok();
391-
}
392-
393365
pub async fn handle_acp_reconnect(socket: SocketRef, ack: AckSender, state: State<AppState>) {
394366
info!("handle_acp_reconnect for socket {}", socket.id);
395367

anycode-backend/src/main.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ async fn on_connect(socket: SocketRef, _state: State<AppState>) {
8080
socket.on("acp:sessions_list", handle_acp_sessions_list);
8181
socket.on("acp:reconnect", handle_acp_reconnect);
8282
socket.on("acp:permission_response", handle_acp_permission_response);
83-
socket.on("acp:set_permission_mode", handle_acp_permission_mode);
8483
socket.on("acp:undo", handle_acp_undo);
8584

8685
socket.on("git:status", handle_git_status);

anycode-base/src/editor.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -526,14 +526,14 @@ export class AnycodeEditor {
526526
this.container.scrollTop = prevScrollTop;
527527
if (!this.readOnly) {
528528
this.codeContent.focus({ preventScroll: true });
529+
this.renderer.renderCursorOrSelection(this.getEditorState(), false);
529530
}
530531
}
531532

532533
return true;
533534
}
534535

535536
private handleClick(e: MouseEvent): void {
536-
console.log("click", e);
537537
this.clearPendingHover();
538538
this.closeHover();
539539

@@ -896,7 +896,6 @@ export class AnycodeEditor {
896896
this.selection = new Selection(start, end);
897897

898898
this.offset = end;
899-
console.log('selectWord', end);
900899
this.renderer.renderSelection(this.code, this.selection);
901900
}
902901

@@ -908,7 +907,6 @@ export class AnycodeEditor {
908907
this.selection = new Selection(start, end);
909908

910909
this.offset = end;
911-
console.log('selectLine', end);
912910
this.renderer.renderSelection(this.code, this.selection);
913911
}
914912

anycode-base/src/renderer/Renderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,7 @@ export class Renderer {
769769
const viewportTop = this.container.scrollTop;
770770
const viewportBottom = viewportTop + this.container.clientHeight;
771771

772-
const bottomPaddingLines = 3;
772+
const bottomPaddingLines = 0;
773773
const padding = settings.lineHeight * bottomPaddingLines;
774774
let targetScrollTop = viewportTop;
775775

anycode-base/src/styles.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,14 @@
274274
-webkit-user-select: none;
275275
margin: 0;
276276
padding: 0;
277+
outline: none;
278+
box-shadow: none;
279+
}
280+
281+
.diff-gap-expand-btn:focus,
282+
.diff-gap-expand-btn:focus-visible {
283+
outline: none;
284+
box-shadow: none;
277285
}
278286

279287
.diff-gap-expand-btn-label {

anycode/App.tsx

Lines changed: 24 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
22
import 'dockview/dist/styles/dockview.css';
3-
import { ChangesPanel } from './components';
3+
import { ChangesPanel, type ChangedFile } from './components';
44
import Search from './components/Search';
55
import { Layout, type LayoutActions, type PanelId } from './components/layout/Layout';
66
import { Toolbar } from './components/toolbar/Toolbar';
@@ -13,10 +13,6 @@ import {
1313
import { AcpAgent, type SearchMatch } from './types';
1414
import './App.css';
1515
import {
16-
loadDiffEnabled,
17-
// loadFollowEnabled,
18-
loadAcpPermissionMode,
19-
saveAcpPermissionMode,
2016
saveItem,
2117
} from './storage';
2218
import { useSocket } from './hooks/useSocket';
@@ -27,36 +23,21 @@ import { useTerminals } from './hooks/useTerminals';
2723
import { useEditors } from './hooks/useEditors';
2824
import { useAgents } from './hooks/useAgents';
2925
import { useLayout } from './hooks/useLayout';
30-
import { type AcpPermissionMode } from './types';
3126
import { useTerminalPanes } from './features/terminal/useTerminalPanes';
3227
import { useAgentPanes } from './features/agents/useAgentPanes';
3328
import { FilesPanel } from './features/files/FilesPanel';
3429
import { EditorPanel } from './features/editor/EditorPanel';
3530
import { TerminalPanel } from './features/terminal/TerminalPanel';
3631
import { AgentPanel } from './features/agents/AgentPanel';
3732
import { BrowserPanel } from './features/browser/BrowserPanel';
38-
import {
39-
DEFAULT_DIFF_VIEW_MODE,
40-
getNextDiffMode,
41-
type DiffMode,
42-
} from './types/diffMode';
33+
import { type DiffMode } from './types/diffMode';
34+
import { normalizePath } from './utils';
4335

4436
const App: React.FC = () => {
45-
const [diffEnabled, setDiffEnabled] = useState<boolean>(loadDiffEnabled());
46-
const [editorDiffModeByPane, setEditorDiffModeByPane] = useState<Record<string, DiffMode>>({});
47-
const layoutActionsRef = useRef<LayoutActions | null>(null);
48-
// const [followEnabled, setFollowEnabled] = useState<boolean>(loadFollowEnabled());
49-
const [permissionMode, setPermissionMode] = useState<AcpPermissionMode>(loadAcpPermissionMode());
50-
5137
const { wsRef, isConnected } = useSocket({});
5238

5339
const fileTree = useFileTree();
54-
const editors = useEditors({
55-
wsRef,
56-
isConnected,
57-
diffEnabled,
58-
});
59-
40+
const editors = useEditors({ wsRef, isConnected });
6041
const terminals = useTerminals({ wsRef, isConnected });
6142
const terminalPanes = useTerminalPanes({
6243
terminals: terminals.terminals,
@@ -66,17 +47,8 @@ const App: React.FC = () => {
6647
const git = useGit({ wsRef, isConnected });
6748
const search = useSearch({ wsRef, isConnected });
6849
const wasConnectedRef = useRef<boolean>(false);
69-
const agents = useAgents({
70-
wsRef,
71-
isConnected,
72-
// followEnabled,
73-
followEnabled: false,
74-
openFile: editors.openFile,
75-
onAgentStarted: () => {
76-
setDiffEnabled(true);
77-
// setFollowEnabled(true);
78-
},
79-
});
50+
const agents = useAgents({ wsRef, isConnected });
51+
const layoutActionsRef = useRef<LayoutActions | null>(null);
8052

8153
const openFolder = useMemo(() => {
8254
return (path: string) => {
@@ -157,24 +129,6 @@ const App: React.FC = () => {
157129
fileTree.selectNode(node.id);
158130
}, [editors.activeFileId, fileTree.fileTree, fileTree.findNodeByPath, fileTree.selectNode]);
159131

160-
useEffect(() => {
161-
saveItem('diffEnabled', diffEnabled);
162-
}, [diffEnabled]);
163-
164-
// useEffect(() => {
165-
// saveItem('followEnabled', followEnabled);
166-
// }, [followEnabled]);
167-
168-
useEffect(() => {
169-
saveAcpPermissionMode(permissionMode);
170-
}, [permissionMode]);
171-
172-
useEffect(() => {
173-
if (!isConnected || !wsRef.current) return;
174-
175-
wsRef.current.emit('acp:set_permission_mode', { mode: permissionMode });
176-
}, [isConnected, permissionMode, wsRef]);
177-
178132
useEffect(() => {
179133
saveItem('terminals', terminals.terminals);
180134
}, [terminals.terminals]);
@@ -188,19 +142,12 @@ const App: React.FC = () => {
188142
return layoutActionsRef.current?.ensureEditorPanel(editors.activeEditorPaneId);
189143
}, [editors]);
190144

191-
const handleOpenFile = useCallback((path: string, line?: number, column?: number) => {
145+
const handleOpenFile = useCallback((path: string, line?: number, column?: number, mode?: DiffMode) => {
192146
const paneId = resolveEditorPaneId();
193147
if (!paneId) return;
194-
editors.openFile(path, line, column, paneId);
148+
editors.openFile(path, line, column, paneId, mode);
195149
}, [editors, resolveEditorPaneId]);
196150

197-
const handleOpenFileDiff = useCallback((path: string, line?: number, column?: number) => {
198-
const paneId = resolveEditorPaneId();
199-
if (!paneId) return;
200-
const mode = editorDiffModeByPane[paneId] ?? (diffEnabled ? 'combine' : DEFAULT_DIFF_VIEW_MODE);
201-
editors.openFile(path, line, column, paneId, { originalContentMode: mode });
202-
}, [diffEnabled, editorDiffModeByPane, editors, resolveEditorPaneId]);
203-
204151
const handleSelectFile = useCallback((fileId: string) => {
205152
const paneId = resolveEditorPaneId();
206153
if (!paneId) return;
@@ -211,57 +158,25 @@ const App: React.FC = () => {
211158
handleOpenFile(filePath, match.line, match.column);
212159
};
213160

214-
const getEditorDiffMode = useCallback((panelKey: string): DiffMode => {
215-
return editorDiffModeByPane[panelKey] ?? (diffEnabled ? 'combine' : DEFAULT_DIFF_VIEW_MODE);
216-
}, [diffEnabled, editorDiffModeByPane]);
217-
218-
const isEditorDiffEnabled = useCallback((panelKey: string) => {
219-
return getEditorDiffMode(panelKey) !== 'plain';
220-
}, [getEditorDiffMode]);
221-
222-
const applyDiffModeToPaneEditor = useCallback((panelKey: string, mode: DiffMode) => {
223-
const fileId = editors.getActiveFileIdForPane(panelKey);
224-
if (!fileId) {
225-
return false;
226-
}
161+
const handleOpenFileDiff = useCallback((path: string, line?: number, column?: number) => {
162+
handleOpenFile(path, line, column, editors.getEditorDiffMode(editors.activeEditorPaneId));
163+
}, [editors, handleOpenFile]);
227164

228-
const editor = editors.getEditorState(fileId);
229-
if (!editor) {
230-
return false;
165+
const activeChangedFile = useMemo<ChangedFile | null>(() => {
166+
if (!editors.activeFileId) {
167+
return null;
231168
}
169+
const normalizedActivePath = normalizePath(editors.activeFileId).replace(/^\.\/+/, '');
170+
return git.changedFiles.find((file) => normalizePath(file.path).replace(/^\.\/+/, '') === normalizedActivePath) ?? null;
171+
}, [editors.activeFileId, git.changedFiles]);
232172

233-
editor.setDiffEnabled(mode !== 'plain');
234-
editor.setFocusedDiffMode(mode === 'diff', 3);
235-
return true;
173+
const isEditorDiffEnabled = useCallback((panelKey: string) => {
174+
return editors.getEditorDiffMode(panelKey) !== 'plain';
236175
}, [editors]);
237176

238177
const handleCycleEditorDiffMode = useCallback((panelKey: string) => {
239-
const currentMode = getEditorDiffMode(panelKey);
240-
const nextMode = getNextDiffMode(currentMode);
241-
242-
if (!applyDiffModeToPaneEditor(panelKey, nextMode)) {
243-
return;
244-
}
245-
246-
setEditorDiffModeByPane((prev) => ({ ...prev, [panelKey]: nextMode }));
247-
248-
if (panelKey === editors.activeEditorPaneId) {
249-
setDiffEnabled(nextMode !== 'plain');
250-
}
251-
}, [applyDiffModeToPaneEditor, editors.activeEditorPaneId, getEditorDiffMode]);
252-
253-
useEffect(() => {
254-
const paneId = editors.activeEditorPaneId;
255-
if (!paneId) {
256-
return;
257-
}
258-
const mode = getEditorDiffMode(paneId);
259-
applyDiffModeToPaneEditor(paneId, mode);
260-
}, [applyDiffModeToPaneEditor, editors.activeEditorPaneId, editors.activeFileId, getEditorDiffMode]);
261-
262-
// const toggleFollowMode = useCallback(() => {
263-
// setFollowEnabled((prev) => !prev);
264-
// }, []);
178+
editors.cycleEditorDiffMode(panelKey);
179+
}, [editors]);
265180

266181
const sessionsArray = useMemo(() => Array.from(agents.acpSessions.values()), [agents.acpSessions]);
267182
const availableAgents = useMemo<AcpAgent[]>(() => getAllAgents(), [agents.agentsVersion]);
@@ -362,9 +277,8 @@ const App: React.FC = () => {
362277
agents.resumeSession(agent, sessionId);
363278
}, [agents.resumeSession, agents.setIsAgentSettingsOpen]);
364279

365-
const handleSaveAgents = useCallback((agentList: AcpAgent[], defaultAgentId: string | null, nextPermissionMode: AcpPermissionMode) => {
280+
const handleSaveAgents = useCallback((agentList: AcpAgent[], defaultAgentId: string | null) => {
366281
updateAgents(agentList, defaultAgentId);
367-
setPermissionMode(nextPermissionMode);
368282
agents.setAgentsVersion((prev) => prev + 1);
369283
}, [agents.setAgentsVersion]);
370284

@@ -409,6 +323,7 @@ const App: React.FC = () => {
409323
return (
410324
<ChangesPanel
411325
files={git.changedFiles}
326+
active={activeChangedFile}
412327
branch={git.gitBranch}
413328
branches={git.branches}
414329
isSwitchingBranch={git.isSwitchingBranch}
@@ -449,7 +364,6 @@ const App: React.FC = () => {
449364
availableAgents={availableAgents}
450365
settingsAgents={settingsAgents}
451366
settingsDefaultAgentId={settingsDefaultAgentId}
452-
permissionMode={permissionMode}
453367
onSaveAgents={handleSaveAgents}
454368
onCloseSettings={handleCloseAgentSettings}
455369
onResumeSettingsSession={handleResumeSettingsSession}
@@ -566,7 +480,7 @@ const App: React.FC = () => {
566480
onPanelActivated={handlePanelActivated}
567481
onCycleEditorDiffMode={handleCycleEditorDiffMode}
568482
isEditorDiffEnabled={isEditorDiffEnabled}
569-
getEditorDiffMode={getEditorDiffMode}
483+
getEditorDiffMode={editors.getEditorDiffMode}
570484
onActionsReady={(actions) => {
571485
layoutActionsRef.current = actions;
572486
}}

0 commit comments

Comments
 (0)