fix(frontend): ArrowUp rappelle l'historique chat même en multiligne — #304 (cause: le handler ArrowUp remontait dans le texte dès que le draft faisait plus d'une ligne, au lieu de rappeler le message précédent; fix type terminal: ArrowUp déclenche le rappel historique uniquement quand le curseur est à la fin de la dernière ligne du textarea, sinon navigation caret normale préservée; ArrowDown symétrique en fin de draft pour redescendre dans l'historique; QA verte reconfirmée par Git: npm test -- src/features/agents/CustomAgentChatView.test.tsx 71/71, npm run typecheck OK, git diff --check OK; QA fournie: npm run build OK; fichiers .ideai/* tickets/counter/index/attachments et scripts/ hors périmètre; aucun push)

This commit is contained in:
2026-09-10 09:53:29 +02:00
parent 5b0759630f
commit db312bfde3
2 changed files with 166 additions and 11 deletions

View File

@ -1009,6 +1009,7 @@ export function CustomAgentChatView({
const draftRef = useRef("");
draftRef.current = draft;
const draftBeforeHistoryNavigationRef = useRef("");
const placeComposerAtEndAfterHistoryRef = useRef(false);
const attachmentsRef = useRef<AttachmentDraft[]>([]);
attachmentsRef.current = attachments;
const imageTokenSeqRef = useRef(0);
@ -1125,7 +1126,8 @@ export function CustomAgentChatView({
const [composerHistory, setComposerHistory] = useState<string[]>(() =>
readComposerHistory(composerHistoryKey),
);
const [, setComposerHistoryIndex] = useState<number | null>(null);
const [composerHistoryIndex, setComposerHistoryIndex] =
useState<number | null>(null);
const displayTurns = turns;
const sessionLabel = structuredSessionLabel(profile.structuredAdapter);
const [requesterDelegation, setRequesterDelegation] =
@ -1205,6 +1207,7 @@ export function CustomAgentChatView({
const recallComposerHistory = useCallback(
(direction: "previous" | "next") => {
if (composerHistory.length === 0) return;
placeComposerAtEndAfterHistoryRef.current = true;
setComposerHistoryIndex((current) => {
if (current === null && direction === "previous") {
draftBeforeHistoryNavigationRef.current = draftRef.current;
@ -1231,6 +1234,16 @@ export function CustomAgentChatView({
[composerHistory],
);
useEffect(() => {
if (!placeComposerAtEndAfterHistoryRef.current) return;
placeComposerAtEndAfterHistoryRef.current = false;
const composer = composerRef.current;
if (!composer) return;
const end = composer.value.length;
composer.setSelectionRange(end, end);
composer.scrollTop = composer.scrollHeight;
}, [composerHistoryIndex, draft]);
const handleComposerHistoryKeyDown = useCallback(
(event: KeyboardEvent<HTMLTextAreaElement>): boolean => {
if (
@ -1238,28 +1251,30 @@ export function CustomAgentChatView({
event.ctrlKey ||
event.metaKey ||
event.shiftKey ||
event.nativeEvent.isComposing ||
event.keyCode === 229 ||
(event.key !== "ArrowUp" && event.key !== "ArrowDown")
) {
return false;
}
const target = event.currentTarget;
if (
event.key === "ArrowUp" &&
target.value.slice(0, target.selectionStart).includes("\n")
) {
const caretIsAtTextEnd =
target.selectionStart === target.selectionEnd &&
target.selectionEnd === target.value.length;
if (!caretIsAtTextEnd) {
return false;
}
if (
event.key === "ArrowDown" &&
target.value.slice(target.selectionEnd).includes("\n")
) {
if (event.key === "ArrowUp" && composerHistory.length === 0) {
return false;
}
if (event.key === "ArrowDown" && composerHistoryIndex === null) {
return false;
}
event.preventDefault();
recallComposerHistory(event.key === "ArrowUp" ? "previous" : "next");
return true;
},
[recallComposerHistory],
[composerHistory.length, composerHistoryIndex, recallComposerHistory],
);
useEffect(() => {