更新大量 tsx 原始文件; 已经迁移 login panel; 部分 (#121)

* style(B1-1): 格式化 ink/buddy/cli/context/screens/tasks/services/keybindings/state (43 files)

纯格式化:移除分号、React Compiler import、import 多行展开。
修复了 Box.tsx 和 ScrollBox.tsx 中无效的 global.d.ts import。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-2): 格式化 commands (79 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-3): 格式化 components/messages,permissions,mcp,sandbox,shell (104 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-4): 格式化 components/PromptInput,FeedbackSurvey,tasks,agents,skills,design-system,wizard (73 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-5): 格式化 components其余 + hooks + tools (232 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-6): 格式化 main/entrypoints/utils/moreright (21 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: 更新 README,新增 Run.ps1/TODO.md,删除 V6.md

- README.md: 大幅重写,更详细版本历史和配置示例
- Run.ps1: 新增 Windows 启动脚本
- TODO.md: 新增包完成清单
- V6.md: 删除(架构重构规划已不适用)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: 修复以前的问题

* fix: 修复 login 面板的问题

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
claude-code-best
2026-04-04 23:24:27 +08:00
committed by GitHub
parent 02694918b5
commit 5b1a52b8e0
559 changed files with 103807 additions and 101817 deletions

View File

@@ -1,228 +1,288 @@
import React, { useCallback, useRef, useState } from 'react';
import { getModeFromInput } from 'src/components/PromptInput/inputModes.js';
import { useNotifications } from 'src/context/notifications.js';
import { ConfigurableShortcutHint } from '../components/ConfigurableShortcutHint.js';
import { FOOTER_TEMPORARY_STATUS_TIMEOUT } from '../components/PromptInput/Notifications.js';
import { getHistory } from '../history.js';
import { Text } from '../ink.js';
import type { PromptInputMode } from '../types/textInputTypes.js';
import type { HistoryEntry, PastedContent } from '../utils/config.js';
export type HistoryMode = PromptInputMode;
import React, { useCallback, useRef, useState } from 'react'
import { getModeFromInput } from 'src/components/PromptInput/inputModes.js'
import { useNotifications } from 'src/context/notifications.js'
import { ConfigurableShortcutHint } from '../components/ConfigurableShortcutHint.js'
import { FOOTER_TEMPORARY_STATUS_TIMEOUT } from '../components/PromptInput/Notifications.js'
import { getHistory } from '../history.js'
import { Text } from '../ink.js'
import type { PromptInputMode } from '../types/textInputTypes.js'
import type { HistoryEntry, PastedContent } from '../utils/config.js'
export type HistoryMode = PromptInputMode
// Load history entries in chunks to reduce disk reads on rapid keypresses
const HISTORY_CHUNK_SIZE = 10;
const HISTORY_CHUNK_SIZE = 10
// Shared state for batching concurrent load requests into a single disk read
// Mode filter is included to ensure we don't mix filtered and unfiltered caches
let pendingLoad: Promise<HistoryEntry[]> | null = null;
let pendingLoadTarget = 0;
let pendingLoadModeFilter: HistoryMode | undefined = undefined;
async function loadHistoryEntries(minCount: number, modeFilter?: HistoryMode): Promise<HistoryEntry[]> {
let pendingLoad: Promise<HistoryEntry[]> | null = null
let pendingLoadTarget = 0
let pendingLoadModeFilter: HistoryMode | undefined = undefined
async function loadHistoryEntries(
minCount: number,
modeFilter?: HistoryMode,
): Promise<HistoryEntry[]> {
// Round up to next chunk to avoid repeated small reads
const target = Math.ceil(minCount / HISTORY_CHUNK_SIZE) * HISTORY_CHUNK_SIZE;
const target = Math.ceil(minCount / HISTORY_CHUNK_SIZE) * HISTORY_CHUNK_SIZE
// If a load is already pending with the same mode filter and will satisfy our needs, wait for it
if (pendingLoad && pendingLoadTarget >= target && pendingLoadModeFilter === modeFilter) {
return pendingLoad;
if (
pendingLoad &&
pendingLoadTarget >= target &&
pendingLoadModeFilter === modeFilter
) {
return pendingLoad
}
// If a load is pending but won't satisfy our needs or has different filter, we need to wait for it
// to complete first, then start a new one (can't interrupt an ongoing read)
if (pendingLoad) {
await pendingLoad;
await pendingLoad
}
// Start a new load
pendingLoadTarget = target;
pendingLoadModeFilter = modeFilter;
pendingLoadTarget = target
pendingLoadModeFilter = modeFilter
pendingLoad = (async () => {
const entries: HistoryEntry[] = [];
let loaded = 0;
const entries: HistoryEntry[] = []
let loaded = 0
for await (const entry of getHistory()) {
// If mode filter is specified, only include entries that match the mode
if (modeFilter) {
const entryMode = getModeFromInput(entry.display);
const entryMode = getModeFromInput(entry.display)
if (entryMode !== modeFilter) {
continue;
continue
}
}
entries.push(entry);
loaded++;
if (loaded >= pendingLoadTarget) break;
entries.push(entry)
loaded++
if (loaded >= pendingLoadTarget) break
}
return entries;
})();
return entries
})()
try {
return await pendingLoad;
return await pendingLoad
} finally {
pendingLoad = null;
pendingLoadTarget = 0;
pendingLoadModeFilter = undefined;
pendingLoad = null
pendingLoadTarget = 0
pendingLoadModeFilter = undefined
}
}
export function useArrowKeyHistory(onSetInput: (value: string, mode: HistoryMode, pastedContents: Record<number, PastedContent>) => void, currentInput: string, pastedContents: Record<number, PastedContent>, setCursorOffset?: (offset: number) => void, currentMode?: HistoryMode): {
historyIndex: number;
setHistoryIndex: (index: number) => void;
onHistoryUp: () => void;
onHistoryDown: () => boolean;
resetHistory: () => void;
dismissSearchHint: () => void;
export function useArrowKeyHistory(
onSetInput: (
value: string,
mode: HistoryMode,
pastedContents: Record<number, PastedContent>,
) => void,
currentInput: string,
pastedContents: Record<number, PastedContent>,
setCursorOffset?: (offset: number) => void,
currentMode?: HistoryMode,
): {
historyIndex: number
setHistoryIndex: (index: number) => void
onHistoryUp: () => void
onHistoryDown: () => boolean
resetHistory: () => void
dismissSearchHint: () => void
} {
const [historyIndex, setHistoryIndex] = useState(0);
const [lastShownHistoryEntry, setLastShownHistoryEntry] = useState<(HistoryEntry & {
mode?: HistoryMode;
}) | undefined>(undefined);
const hasShownSearchHintRef = useRef(false);
const {
addNotification,
removeNotification
} = useNotifications();
const [historyIndex, setHistoryIndex] = useState(0)
const [lastShownHistoryEntry, setLastShownHistoryEntry] = useState<
(HistoryEntry & { mode?: HistoryMode }) | undefined
>(undefined)
const hasShownSearchHintRef = useRef(false)
const { addNotification, removeNotification } = useNotifications()
// Cache loaded history entries
const historyCache = useRef<HistoryEntry[]>([]);
const historyCache = useRef<HistoryEntry[]>([])
// Track which mode filter the cache was loaded with
const historyCacheModeFilter = useRef<HistoryMode | undefined>(undefined);
const historyCacheModeFilter = useRef<HistoryMode | undefined>(undefined)
// Synchronous tracker for history index to avoid stale closure issues
// React state updates are async, so rapid keypresses can see stale values
const historyIndexRef = useRef(0);
const historyIndexRef = useRef(0)
// Track the mode filter that was active when history navigation started
// This is set on the first arrow press and stays fixed until reset
const initialModeFilterRef = useRef<HistoryMode | undefined>(undefined);
const initialModeFilterRef = useRef<HistoryMode | undefined>(undefined)
// Refs to track current input values for draft preservation
// These ensure we capture the draft with the latest values, not stale closure values
const currentInputRef = useRef(currentInput);
const pastedContentsRef = useRef(pastedContents);
const currentModeRef = useRef(currentMode);
const currentInputRef = useRef(currentInput)
const pastedContentsRef = useRef(pastedContents)
const currentModeRef = useRef(currentMode)
// Keep refs in sync with props (synchronous update on each render)
currentInputRef.current = currentInput;
pastedContentsRef.current = pastedContents;
currentModeRef.current = currentMode;
const setInputWithCursor = useCallback((value: string, mode: HistoryMode, contents: Record<number, PastedContent>, cursorToStart = false): void => {
onSetInput(value, mode, contents);
setCursorOffset?.(cursorToStart ? 0 : value.length);
}, [onSetInput, setCursorOffset]);
const updateInput = useCallback((input: HistoryEntry | undefined, cursorToStart_0 = false): void => {
if (!input || !input.display) return;
const mode_0 = getModeFromInput(input.display);
const value_0 = mode_0 === 'bash' ? input.display.slice(1) : input.display;
setInputWithCursor(value_0, mode_0, input.pastedContents ?? {}, cursorToStart_0);
}, [setInputWithCursor]);
currentInputRef.current = currentInput
pastedContentsRef.current = pastedContents
currentModeRef.current = currentMode
const setInputWithCursor = useCallback(
(
value: string,
mode: HistoryMode,
contents: Record<number, PastedContent>,
cursorToStart = false,
): void => {
onSetInput(value, mode, contents)
setCursorOffset?.(cursorToStart ? 0 : value.length)
},
[onSetInput, setCursorOffset],
)
const updateInput = useCallback(
(input: HistoryEntry | undefined, cursorToStart = false): void => {
if (!input || !input.display) return
const mode = getModeFromInput(input.display)
const value = mode === 'bash' ? input.display.slice(1) : input.display
setInputWithCursor(value, mode, input.pastedContents ?? {}, cursorToStart)
},
[setInputWithCursor],
)
const showSearchHint = useCallback((): void => {
addNotification({
key: 'search-history-hint',
jsx: <Text dimColor>
<ConfigurableShortcutHint action="history:search" context="Global" fallback="ctrl+r" description="search history" />
</Text>,
jsx: (
<Text dimColor>
<ConfigurableShortcutHint
action="history:search"
context="Global"
fallback="ctrl+r"
description="search history"
/>
</Text>
),
priority: 'immediate',
timeoutMs: FOOTER_TEMPORARY_STATUS_TIMEOUT
});
}, [addNotification]);
timeoutMs: FOOTER_TEMPORARY_STATUS_TIMEOUT,
})
}, [addNotification])
const onHistoryUp = useCallback((): void => {
// Capture and increment synchronously to handle rapid keypresses
const targetIndex = historyIndexRef.current;
historyIndexRef.current++;
const inputAtPress = currentInputRef.current;
const pastedContentsAtPress = pastedContentsRef.current;
const modeAtPress = currentModeRef.current;
const targetIndex = historyIndexRef.current
historyIndexRef.current++
const inputAtPress = currentInputRef.current
const pastedContentsAtPress = pastedContentsRef.current
const modeAtPress = currentModeRef.current
if (targetIndex === 0) {
initialModeFilterRef.current = modeAtPress === 'bash' ? modeAtPress : undefined;
initialModeFilterRef.current =
modeAtPress === 'bash' ? modeAtPress : undefined
// Save draft synchronously using refs for the latest values
// This ensures we capture the draft before any async operations or re-renders
const hasInput = inputAtPress.trim() !== '';
setLastShownHistoryEntry(hasInput ? {
display: inputAtPress,
pastedContents: pastedContentsAtPress,
mode: modeAtPress
} : undefined);
const hasInput = inputAtPress.trim() !== ''
setLastShownHistoryEntry(
hasInput
? {
display: inputAtPress,
pastedContents: pastedContentsAtPress,
mode: modeAtPress,
}
: undefined,
)
}
const modeFilter = initialModeFilterRef.current;
const modeFilter = initialModeFilterRef.current
void (async () => {
const neededCount = targetIndex + 1; // How many entries we need
const neededCount = targetIndex + 1 // How many entries we need
// If mode filter changed, invalidate cache
if (historyCacheModeFilter.current !== modeFilter) {
historyCache.current = [];
historyCacheModeFilter.current = modeFilter;
historyIndexRef.current = 0;
historyCache.current = []
historyCacheModeFilter.current = modeFilter
historyIndexRef.current = 0
}
// Load more entries if needed
if (historyCache.current.length < neededCount) {
// Batches concurrent requests - rapid keypresses share a single disk read
const entries = await loadHistoryEntries(neededCount, modeFilter);
const entries = await loadHistoryEntries(neededCount, modeFilter)
// Only update cache if we loaded more than currently cached
// (handles race condition where multiple loads complete out of order)
if (entries.length > historyCache.current.length) {
historyCache.current = entries;
historyCache.current = entries
}
}
// Check if we can navigate
if (targetIndex >= historyCache.current.length) {
// Rollback the ref since we can't navigate
historyIndexRef.current--;
historyIndexRef.current--
// Keep the draft intact - user stays on their current input
return;
return
}
const newIndex = targetIndex + 1;
setHistoryIndex(newIndex);
updateInput(historyCache.current[targetIndex], true);
const newIndex = targetIndex + 1
setHistoryIndex(newIndex)
updateInput(historyCache.current[targetIndex], true)
// Show hint once per session after navigating through 2 history entries
if (newIndex >= 2 && !hasShownSearchHintRef.current) {
hasShownSearchHintRef.current = true;
showSearchHint();
hasShownSearchHintRef.current = true
showSearchHint()
}
})();
}, [updateInput, showSearchHint]);
})()
}, [updateInput, showSearchHint])
const onHistoryDown = useCallback((): boolean => {
// Use the ref for consistent reads
const currentIndex = historyIndexRef.current;
const currentIndex = historyIndexRef.current
if (currentIndex > 1) {
historyIndexRef.current--;
setHistoryIndex(currentIndex - 1);
updateInput(historyCache.current[currentIndex - 2]);
historyIndexRef.current--
setHistoryIndex(currentIndex - 1)
updateInput(historyCache.current[currentIndex - 2])
} else if (currentIndex === 1) {
historyIndexRef.current = 0;
setHistoryIndex(0);
historyIndexRef.current = 0
setHistoryIndex(0)
if (lastShownHistoryEntry) {
// Restore the draft with its saved mode if available
const savedMode = lastShownHistoryEntry.mode;
const savedMode = lastShownHistoryEntry.mode
if (savedMode) {
setInputWithCursor(lastShownHistoryEntry.display, savedMode, lastShownHistoryEntry.pastedContents ?? {});
setInputWithCursor(
lastShownHistoryEntry.display,
savedMode,
lastShownHistoryEntry.pastedContents ?? {},
)
} else {
updateInput(lastShownHistoryEntry);
updateInput(lastShownHistoryEntry)
}
} else {
// When in filtered mode, stay in that mode when clearing input
setInputWithCursor('', initialModeFilterRef.current ?? 'prompt', {});
setInputWithCursor('', initialModeFilterRef.current ?? 'prompt', {})
}
}
return currentIndex <= 0;
}, [lastShownHistoryEntry, updateInput, setInputWithCursor]);
return currentIndex <= 0
}, [lastShownHistoryEntry, updateInput, setInputWithCursor])
const resetHistory = useCallback((): void => {
setLastShownHistoryEntry(undefined);
setHistoryIndex(0);
historyIndexRef.current = 0;
initialModeFilterRef.current = undefined;
removeNotification('search-history-hint');
historyCache.current = [];
historyCacheModeFilter.current = undefined;
}, [removeNotification]);
setLastShownHistoryEntry(undefined)
setHistoryIndex(0)
historyIndexRef.current = 0
initialModeFilterRef.current = undefined
removeNotification('search-history-hint')
historyCache.current = []
historyCacheModeFilter.current = undefined
}, [removeNotification])
const dismissSearchHint = useCallback((): void => {
removeNotification('search-history-hint');
}, [removeNotification]);
removeNotification('search-history-hint')
}, [removeNotification])
return {
historyIndex,
setHistoryIndex,
onHistoryUp,
onHistoryDown,
resetHistory,
dismissSearchHint
};
dismissSearchHint,
}
}