chore(webapp): scope imperative route refs (#4731)

## Summary

Scopes React Compiler diagnostics to route statements where refs
intentionally coordinate virtualized views, live reload state, transport
lifecycles, and deferred callbacks. Other compiler diagnostics remain
active in those routes.
This commit is contained in:
Chris Arderne
2026-08-20 09:59:45 +01:00
committed by GitHub
parent 4d040e13be
commit 2b2b047089
6 changed files with 13 additions and 0 deletions
@@ -243,6 +243,7 @@ export default function Page() {
const usefulLinksPanelRef = useRef<PanelHandle>(null);
const fetcher = useFetcher();
const fetcherRef = useRef(fetcher);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative route integration outside React state.
fetcherRef.current = fetcher;
const toggleUsefulLinks = useCallback((show: boolean) => {
@@ -207,6 +207,7 @@ function PlaygroundChat() {
activeConversation?.clientData ? JSON.stringify(activeConversation.clientData, null, 2) : "{}"
);
const clientDataJsonRef = useRef(clientDataJson);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative route integration outside React state.
clientDataJsonRef.current = clientDataJson;
const [machine, setMachine] = useState<string | undefined>(undefined);
const [tags, setTags] = useState<string[]>([]);
@@ -267,12 +268,14 @@ function PlaygroundChat() {
// silently ignored on the first send. Mirror the `clientDataJsonRef`
// pattern so the transport always calls the latest `startSession`.
const startSessionRef = useRef(startSession);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative route integration outside React state.
startSessionRef.current = startSession;
// Create TriggerChatTransport directly (not via useTriggerChatTransport hook
// to avoid React version mismatch between SDK and webapp)
const transportRef = useRef<TriggerChatTransport | null>(null);
if (transportRef.current === null) {
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative route integration outside React state.
transportRef.current = new TriggerChatTransport({
task: agent.slug,
// The Remix action is idempotent on `(env, externalId)` and
@@ -301,6 +304,7 @@ function PlaygroundChat() {
: {}),
});
}
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative route integration outside React state.
const transport = transportRef.current;
// Keep the transport's `defaultMetadata` in sync with the JSON editor.
@@ -351,6 +355,7 @@ function PlaygroundChat() {
);
// useChat from AI SDK — handles message accumulation, streaming, stop
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative route integration outside React state.
const { messages, sendMessage, stop, status, error } = useChat({
id: chatId,
messages: initialMessages,
@@ -391,6 +396,7 @@ function PlaygroundChat() {
inputRef.current?.focus();
}, [isEmpty]);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative route integration outside React state.
const session = transport.getSession(chatId);
/* oxlint-disable react/react-compiler -- The transport and chat ID are stable for this component's lifetime. */
@@ -1171,6 +1177,7 @@ function usePlaygroundPendingMessages({
[status, transport, chatId, sendMessage, metadata]
);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative route integration outside React state.
const pending: PendingMessageEntry[] = pendingMsgs.map((m) => ({
id: m.id,
text: m.parts[0]?.text ?? "",
@@ -165,6 +165,7 @@ export function useRunsLiveReload({
const location = useLocation();
const runsPollFetcher = useTypedFetcher<typeof liveRunsLoader>();
const runsPollFetcherStateRef = useRef(runsPollFetcher.state);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative route integration outside React state.
runsPollFetcherStateRef.current = runsPollFetcher.state;
const [visibleRuns, setVisibleRuns] = useState(runs);
@@ -207,6 +207,7 @@ export function MetricWidget({
// Track the latest props so the submit callback always uses fresh values
// without needing to be recreated (which would cause useInterval to re-register listeners).
const propsRef = useRef(props);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative route integration outside React state.
propsRef.current = props;
// Track visibility so we only fetch for on-screen widgets.
@@ -265,6 +266,7 @@ export function MetricWidget({
});
}, [isVisibleRef]);
/* oxlint-enable react/react-compiler */
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative route integration outside React state.
submitRef.current = submit;
// Clean up on unmount
@@ -371,6 +371,7 @@ function CompleteManualWaitpointForm({ waitpoint }: { waitpoint: { id: string }
<div className="max-h-[70vh] min-h-40 overflow-y-auto bg-background-deep scrollbar-thin scrollbar-track-transparent scrollbar-thumb-surface-control">
<JSONEditor
autoFocus
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative route integration outside React state.
defaultValue={currentJson.current}
readOnly={false}
basicSetup
@@ -59,6 +59,7 @@ export function AIGeneratedCronField({ onSuccess }: AIGeneratedCronFieldProps) {
const fetcher = useFetcher<typeof action>();
const [text, setText] = useState<string>("");
const onSuccessRef = useRef(onSuccess);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative route integration outside React state.
onSuccessRef.current = onSuccess;
const organization = useOrganization();
const project = useProject();