code editor panel keyed (528 -> 516)

The 'editor buffer changed while unfocused' Error stays English — it is the trap
marker for docs/editor-buffer-corruption.md, read from a log rather than a screen.
This commit is contained in:
2026-08-23 14:18:26 +00:00
parent 24d46a815a
commit ac890fd996
4 changed files with 19 additions and 11 deletions
@@ -13,6 +13,7 @@ import { useFilesRefresh } from '../../channels';
import { useWorkspace } from '../../components/Workspace'; import { useWorkspace } from '../../components/Workspace';
import { useEditorTabs } from './use-editor-tabs'; import { useEditorTabs } from './use-editor-tabs';
import { configureMonacoTypescript } from './monaco-setup'; import { configureMonacoTypescript } from './monaco-setup';
import { useTranslation } from '../../locales';
type CodeEditorViewProps = { type CodeEditorViewProps = {
className?: string; className?: string;
@@ -103,6 +104,7 @@ export const CodeEditorView = ({
urlState, urlState,
panelId, panelId,
}: CodeEditorViewProps) => { }: CodeEditorViewProps) => {
const { t } = useTranslation();
const { workspace } = useWorkspace(); const { workspace } = useWorkspace();
const { restored, remember } = useEditorTabs(panelId, workspace); const { restored, remember } = useEditorTabs(panelId, workspace);
const { const {
@@ -138,7 +140,7 @@ export const CodeEditorView = ({
const res = await readFile(path); const res = await readFile(path);
openFile(path, name, res.content); openFile(path, name, res.content);
} catch { } catch {
toast.error('Failed to read file'); toast.error(t('codeEditor.readFailed'));
} }
}; };
@@ -159,7 +161,7 @@ export const CodeEditorView = ({
.then((res) => alive && openFile(path, path.split('/').pop() ?? path, res.content)) .then((res) => alive && openFile(path, path.split('/').pop() ?? path, res.content))
.catch(() => { .catch(() => {
unreadable.current.add(path); unreadable.current.add(path);
if (alive) toast.error('Failed to read file'); if (alive) toast.error(t('codeEditor.readFailed'));
}); });
return () => { return () => {
alive = false; alive = false;
@@ -273,9 +275,9 @@ export const CodeEditorView = ({
try { try {
await writeFile(activeFile.path, activeFile.content); await writeFile(activeFile.path, activeFile.content);
markSaved(activeFile.path, activeFile.content); markSaved(activeFile.path, activeFile.content);
toast.success('File saved'); toast.success(t('codeEditor.saved'));
} catch { } catch {
toast.error('Failed to save file'); toast.error(t('codeEditor.saveFailed'));
} }
}; };
@@ -322,10 +324,10 @@ export const CodeEditorView = ({
className="px-3 py-2 text-xs font-semibold uppercase tracking-wider flex items-center justify-between gap-2" className="px-3 py-2 text-xs font-semibold uppercase tracking-wider flex items-center justify-between gap-2"
style={{ color: '#888', borderBottom: `1px solid ${borderColor}` }} style={{ color: '#888', borderBottom: `1px solid ${borderColor}` }}
> >
<span>Explorer</span> <span>{t('codeEditor.explorer')}</span>
<button <button
onClick={() => setManualRefresh((n) => n + 1)} onClick={() => setManualRefresh((n) => n + 1)}
title="Refresh the tree and reload unmodified open files" title={t('codeEditor.refresh')}
className="p-0.5 rounded hover:bg-white/10 transition-colors cursor-pointer" className="p-0.5 rounded hover:bg-white/10 transition-colors cursor-pointer"
> >
<RefreshCw className="h-3.5 w-3.5" /> <RefreshCw className="h-3.5 w-3.5" />
@@ -383,7 +385,7 @@ export const CodeEditorView = ({
/> />
) : ( ) : (
<div className="flex-1 flex items-center justify-center text-sm" style={{ color: '#888' }}> <div className="flex-1 flex items-center justify-center text-sm" style={{ color: '#888' }}>
Open a file from the explorer to start editing {t('codeEditor.openAFile')}
</div> </div>
)} )}
</div> </div>
@@ -1,12 +1,14 @@
import { Code } from 'lucide-react'; import { Code } from 'lucide-react';
import { useWorkspace } from '../../components/Workspace'; import { useWorkspace } from '../../components/Workspace';
import { useTranslation } from '../../locales';
export const CodeEditorHeader = () => { export const CodeEditorHeader = () => {
const { t } = useTranslation();
const { cwd } = useWorkspace(); const { cwd } = useWorkspace();
return ( return (
<> <>
<Code className="h-3.5 w-3.5 shrink-0" /> <Code className="h-3.5 w-3.5 shrink-0" />
<span className="text-xs font-medium shrink-0">Code Editor</span> <span className="text-xs font-medium shrink-0">{t('codeEditor.title')}</span>
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span> <span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
</> </>
); );
@@ -2,6 +2,7 @@ import { Link } from 'react-router';
import { X, FileX } from 'lucide-react'; import { X, FileX } from 'lucide-react';
import { getIcon } from 'material-file-icons'; import { getIcon } from 'material-file-icons';
import type { OpenFile } from './useEditorState'; import type { OpenFile } from './useEditorState';
import { useTranslation } from '../../locales';
type EditorTabsProps = { type EditorTabsProps = {
files: OpenFile[]; files: OpenFile[];
@@ -19,6 +20,7 @@ const FileIcon = ({ name }: { name: string }) => {
}; };
export const EditorTabs = ({ files, activePath, onSelect, onClose, theme, searchForFile }: EditorTabsProps) => { export const EditorTabs = ({ files, activePath, onSelect, onClose, theme, searchForFile }: EditorTabsProps) => {
const { t } = useTranslation();
if (files.length === 0) return null; if (files.length === 0) return null;
const isDark = theme !== 'vs'; const isDark = theme !== 'vs';
@@ -75,7 +77,7 @@ export const EditorTabs = ({ files, activePath, onSelect, onClose, theme, search
)} )}
<button <button
onClick={() => onClose(file.path)} onClick={() => onClose(file.path)}
aria-label={`Close ${file.name}`} aria-label={t('codeEditor.closeFile', { name: file.name })}
className="p-0.5 rounded cursor-pointer transition-colors hover:bg-white/10" className="p-0.5 rounded cursor-pointer transition-colors hover:bg-white/10"
style={{ color: inactiveColor }} style={{ color: inactiveColor }}
> >
@@ -3,6 +3,7 @@ import { Link } from 'react-router';
import { ChevronRight, ChevronDown, Folder } from 'lucide-react'; import { ChevronRight, ChevronDown, Folder } from 'lucide-react';
import { getIcon } from 'material-file-icons'; import { getIcon } from 'material-file-icons';
import { useFilesAPI, type DirEntry } from '../../hooks/useFilesAPI'; import { useFilesAPI, type DirEntry } from '../../hooks/useFilesAPI';
import { useTranslation } from '../../locales';
/** /**
* `searchForFile` turns file rows into real links when the editor owns the address bar. Folder rows stay * `searchForFile` turns file rows into real links when the editor owns the address bar. Folder rows stay
@@ -148,6 +149,7 @@ const TreeNode = ({ entry, parentPath, root, onOpenFile, searchForFile, depth, r
}; };
export const FileTree = ({ root, basePath, onOpenFile, searchForFile, refreshToken }: FileTreeProps) => { export const FileTree = ({ root, basePath, onOpenFile, searchForFile, refreshToken }: FileTreeProps) => {
const { t } = useTranslation();
const { listDir } = useFilesAPI(root); const { listDir } = useFilesAPI(root);
const [entries, setEntries] = useState<DirEntry[] | null>(null); const [entries, setEntries] = useState<DirEntry[] | null>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -187,11 +189,11 @@ export const FileTree = ({ root, basePath, onOpenFile, searchForFile, refreshTok
} }
if (loading && entries === null) { if (loading && entries === null) {
return <div className="p-2 text-sm text-[#888]">Loading...</div>; return <div className="p-2 text-sm text-[#888]">{t('codeEditor.loading')}</div>;
} }
if (!entries || entries.length === 0) { if (!entries || entries.length === 0) {
return <div className="p-2 text-sm text-[#888]">No files</div>; return <div className="p-2 text-sm text-[#888]">{t('codeEditor.noFiles')}</div>;
} }
return ( return (