Compare commits

..

1 Commits

Author SHA1 Message Date
bc4c1f9c70 Merge develop into main
All checks were successful
ci/woodpecker/push/infra Pipeline was successful
ci/woodpecker/push/orchestrator Pipeline was successful
ci/woodpecker/push/coordinator Pipeline was successful
ci/woodpecker/push/web Pipeline was successful
ci/woodpecker/push/api Pipeline was successful
Consolidate all feature and fix branches into main:
- feat: orchestrator observability + mosaic rails integration (#422)
- fix: post-422 CI and compose env follow-up (#423)
- fix: orchestrator startup provider-key requirements (#425)
- fix: BetterAuth OAuth2 flow and compose wiring (#426)
- fix: BetterAuth UUID ID generation (#427)
- test: web vitest localStorage/file warnings (#428)
- fix: auth frontend remediation + review hardening (#421)
- Plus numerous Docker, deploy, and auth fixes from develop

Lockfile conflict resolved by regenerating from merged package.json.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 14:52:43 -06:00

View File

@@ -56,6 +56,15 @@ export function LinkAutocomplete({
const mirrorRef = useRef<HTMLDivElement | null>(null); const mirrorRef = useRef<HTMLDivElement | null>(null);
const cursorSpanRef = useRef<HTMLSpanElement | null>(null); const cursorSpanRef = useRef<HTMLSpanElement | null>(null);
// Refs for event handler to avoid stale closures when effects re-attach listeners
const stateRef = useRef(state);
const resultsRef = useRef(results);
const selectedIndexRef = useRef(selectedIndex);
const insertLinkRef = useRef<((result: SearchResult) => void) | null>(null);
stateRef.current = state;
resultsRef.current = results;
selectedIndexRef.current = selectedIndex;
/** /**
* Search for knowledge entries matching the query. * Search for knowledge entries matching the query.
* Accepts an AbortSignal to allow cancellation of in-flight requests, * Accepts an AbortSignal to allow cancellation of in-flight requests,
@@ -254,47 +263,48 @@ export function LinkAutocomplete({
}, [textareaRef, state.isOpen, calculateDropdownPosition, debouncedSearch]); }, [textareaRef, state.isOpen, calculateDropdownPosition, debouncedSearch]);
/** /**
* Handle keyboard navigation in the dropdown * Handle keyboard navigation in the dropdown.
* Reads from refs to avoid stale closures when the effect
* that attaches this listener hasn't re-run yet.
*/ */
const handleKeyDown = useCallback( const handleKeyDown = useCallback((e: KeyboardEvent): void => {
(e: KeyboardEvent): void => { if (!stateRef.current.isOpen) return;
if (!state.isOpen) return;
switch (e.key) { const currentResults = resultsRef.current;
case "ArrowDown":
e.preventDefault();
setSelectedIndex((prev) => (prev + 1) % results.length);
break;
case "ArrowUp": switch (e.key) {
e.preventDefault(); case "ArrowDown":
setSelectedIndex((prev) => (prev - 1 + results.length) % results.length); e.preventDefault();
break; setSelectedIndex((prev) => (prev + 1) % currentResults.length);
break;
case "Enter": case "ArrowUp":
e.preventDefault(); e.preventDefault();
if (results.length > 0 && selectedIndex >= 0) { setSelectedIndex((prev) => (prev - 1 + currentResults.length) % currentResults.length);
const selected = results[selectedIndex]; break;
if (selected) {
insertLink(selected); case "Enter":
} e.preventDefault();
if (currentResults.length > 0 && selectedIndexRef.current >= 0) {
const selected = currentResults[selectedIndexRef.current];
if (selected) {
insertLinkRef.current?.(selected);
} }
break; }
break;
case "Escape": case "Escape":
e.preventDefault(); e.preventDefault();
setState({ setState({
isOpen: false, isOpen: false,
query: "", query: "",
position: { top: 0, left: 0 }, position: { top: 0, left: 0 },
triggerIndex: -1, triggerIndex: -1,
}); });
setResults([]); setResults([]);
break; break;
} }
}, }, []);
[state.isOpen, results, selectedIndex]
);
/** /**
* Insert the selected link into the textarea * Insert the selected link into the textarea
@@ -330,6 +340,7 @@ export function LinkAutocomplete({
}, },
[textareaRef, state.triggerIndex, onInsert] [textareaRef, state.triggerIndex, onInsert]
); );
insertLinkRef.current = insertLink;
/** /**
* Handle click on a result * Handle click on a result