Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/webapp/app/components/admin/FeatureFlagsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export function FeatureFlagsDialog({
const saveFetcher = useFetcher<ActionData>();
const loadFeatureFlags = loadFetcher.load;
const onOpenChangeRef = useRef(onOpenChange);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
onOpenChangeRef.current = onOpenChange;

const [overrides, setOverrides] = useState<Record<string, unknown>>({});
Expand Down
1 change: 1 addition & 0 deletions apps/webapp/app/components/code/JSONEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export function JSONEditor(opts: JSONEditorProps) {
const editor = useRef<HTMLDivElement>(null);
const settings: Omit<UseCodeMirror, "onBlur"> = {
...opts,
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
container: editor.current,
extensions,
editable: !readOnly,
Expand Down
1 change: 1 addition & 0 deletions apps/webapp/app/components/code/TSQLEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ export function TSQLEditor(opts: TSQLEditorProps) {

const settings: Omit<UseCodeMirror, "onBlur"> = {
...opts,
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
container: editor.current,
extensions,
editable: !readOnly,
Expand Down
1 change: 1 addition & 0 deletions apps/webapp/app/components/code/TextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export function TextEditor(opts: TextEditorProps) {
const editor = useRef<HTMLDivElement>(null);
const settings: Omit<UseCodeMirror, "onBlur"> = {
...opts,
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
container: editor.current,
extensions,
editable: !readOnly,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export function DashboardAgentChat({
// The path this chat last rendered on. React never unmounts on a page teardown, so an
// unmount whose live URL has moved is the router having navigated out from under it.
const renderedPathRef = useRef(location.pathname);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
renderedPathRef.current = location.pathname;

const transport = useTriggerChatTransport<typeof dashboardAgent>({
Expand Down Expand Up @@ -209,6 +210,7 @@ export function DashboardAgentChat({
});

const orderRef = useRef(createTranscriptOrder(initialMessages));
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
const messages = orderTranscript(rawMessages, orderRef.current);

// Read here, not in the panel, so it re-reads as each turn settles.
Expand Down Expand Up @@ -359,6 +361,7 @@ export function DashboardAgentChat({
const navigatedRef = useRef<Set<string> | null>(null);
if (navigatedRef.current === null) {
navigatedRef.current = new Set();
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
pendingNavigateIntents(initialMessages, navigatedRef.current);
}
useEffect(() => {
Expand All @@ -374,6 +377,7 @@ export function DashboardAgentChat({
const watchProposedRef = useRef<Set<string> | null>(null);
if (watchProposedRef.current === null) {
watchProposedRef.current = new Set();
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
pendingWatchIntents(initialMessages, watchProposedRef.current);
}
useEffect(() => {
Expand All @@ -388,6 +392,7 @@ export function DashboardAgentChat({
}, [transport, chatId, aiStop]);

const teardownRef = useRef<() => void>(() => {});
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
teardownRef.current = () => {
if (status !== "streaming" && status !== "submitted") return;
const reason = unmountTeardown({
Expand All @@ -401,6 +406,7 @@ export function DashboardAgentChat({

// Read by the settle effect, which must not re-run when the transcript changes.
const messagesRef = useRef(messages);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
messagesRef.current = messages;

const prevStatus = useRef(status);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ export function winningInvestigationOccurrences(messages: UIMessage[]): Map<stri
function useInvestigationWinners(messages: UIMessage[]): Map<string, string> {
const previous = useRef<Map<string, string>>();
const next = useMemo(() => winningInvestigationOccurrences(messages), [messages]);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
previous.current = reuseWinners(previous.current, next);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
return previous.current;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ export function DashboardAgentPanel({

const loadHistory = useMemo(
() =>
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
createCoalescedReload(async () => {
try {
const res = await fetch(actionPath);
Expand Down
1 change: 1 addition & 0 deletions apps/webapp/app/components/navigation/SideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ export function SideMenu({
// object each render, so depending on it would fire the cleanup (flushing the debounce) every
// render — and drags re-render constantly — instead of only on unmount.
const flushPendingPreferencesRef = useRef<() => void>();
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
flushPendingPreferencesRef.current = () => {
if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export function useReorderableList<T>({

const [order, setOrder] = useState<string[]>(() => initialOrder ?? items.map(itemKey));
const resetOrderRef = useRef({ initialOrder, items, itemKey });
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
resetOrderRef.current = { initialOrder, items, itemKey };

// Only an organization switch resets user-managed order. Keep the latest inputs in a ref so
Expand Down
2 changes: 2 additions & 0 deletions apps/webapp/app/components/primitives/AgentDotMatrix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,9 @@ export function AgentDotMatrix({
const playlistKey = playlist.join(",");
const paletteObjRef = useRef(paletteObj);
const playlistRef = useRef(playlist);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
paletteObjRef.current = paletteObj;
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
playlistRef.current = playlist;

useEffect(() => {
Expand Down
1 change: 1 addition & 0 deletions apps/webapp/app/components/primitives/DateField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export function DateField({
});

const stateValueRef = useRef(state.value);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
stateValueRef.current = state.value;

// Sync only when the passed value or timezone mode changes. Depending on state.value directly
Expand Down
2 changes: 2 additions & 0 deletions apps/webapp/app/components/primitives/Resizable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ function collapsibleHandleClassName(show: boolean) {

function useFrozenValue<T>(value: T | null | undefined): T | null | undefined {
const ref = useRef(value);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
if (value != null) ref.current = value;
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
return ref.current;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export function useZoomSelection(): UseZoomSelectionReturn {
const stateRef = useRef<ZoomSelectionState>(state);

// Keep ref in sync with state
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
stateRef.current = state;

const startSelection = useCallback((label: string) => {
Expand Down
6 changes: 6 additions & 0 deletions apps/webapp/app/components/query/QueryEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ export function QueryEditor({

// Use a ref so the effect can read chartConfig without re-firing on every config tweak
const chartConfigRef = useRef(chartConfig);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
chartConfigRef.current = chartConfig;

// Reset chart config only when a column referenced by the current config is no
Expand Down Expand Up @@ -562,6 +563,7 @@ export function QueryEditor({
}, []);

// Compute current save data for the save render prop
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Suppression masks a genuine render-phase ref read that can render stale/empty query text

These new oxlint-disable comments silence the react-compiler rule for editorRef.current?.getQuery() calls that happen during render (apps/webapp/app/components/query/QueryEditor.tsx:566, :795, :850, :900, :963). Unlike the other sites in this PR (which just assign the latest value to a ref during render — a benign latest-value pattern), these actually read an imperative handle during render: on the first render editorRef.current is null, and afterwards the value is not reactive, so the rendered query prop can lag behind the editor's real content until an unrelated re-render occurs. The fallbacks (?? defaultQuery, ?? "") hide this rather than fix it. Suppressing the rule here removes the only signal pointing at that latent staleness; a follow-up that lifts the query text into state (or passes a getter down) would be preferable.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

const currentQuery = editorRef.current?.getQuery() ?? "";
const saveData: QueryEditorSaveData = {
title: queryTitle ?? "Untitled Query",
Expand Down Expand Up @@ -790,6 +792,7 @@ export function QueryEditor({
onRename={handleRenameTitle}
/>
}
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
query={editorRef.current?.getQuery() ?? defaultQuery}
data={{
rows: results.rows,
Expand Down Expand Up @@ -844,6 +847,7 @@ export function QueryEditor({
<ResultsChart
rows={results.rows}
columns={results.columns}
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
query={editorRef.current?.getQuery() ?? defaultQuery}
chartConfig={chartConfig}
onChartConfigChange={handleChartConfigChange}
Expand Down Expand Up @@ -893,6 +897,7 @@ export function QueryEditor({
<ResultsBigNumber
rows={results.rows}
columns={results.columns}
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
query={editorRef.current?.getQuery() ?? defaultQuery}
bigNumberConfig={bigNumberConfig}
onBigNumberConfigChange={setBigNumberConfig}
Expand Down Expand Up @@ -955,6 +960,7 @@ export function QueryEditor({
{mode.type === "standalone" && (
<SaveToDashboardDialog
title={queryTitle ?? "Untitled Query"}
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
query={editorRef.current?.getQuery() ?? ""}
config={
resultsView === "table"
Expand Down
2 changes: 2 additions & 0 deletions apps/webapp/app/components/runs/v3/RunStatusCellTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ function useChildRunStatusesTooltip({
key: `child-statuses-${friendlyId}`,
});
const fetcherStateRef = useRef(fetcher.state);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
fetcherStateRef.current = fetcher.state;

const [childStatuses, setChildStatuses] = useState<ChildStatusEntry[] | undefined>();
Expand All @@ -120,6 +121,7 @@ function useChildRunStatusesTooltip({
// Keep the latest loader callback available to the polling interval
// without recreating the interval on every render.
const loadChildStatusesRef = useRef(loadChildStatuses);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
loadChildStatusesRef.current = loadChildStatuses;

const stopPolling = useCallback(() => {
Expand Down
3 changes: 3 additions & 0 deletions apps/webapp/app/components/runs/v3/agent/AgentView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ function useAgentSessionMessages({
// `scheduleFlush`. The Map *reference* changes on every flush so React
// detects the state update and the downstream `useMemo` recomputes.
const [messagesById, setMessagesById] = useState<Map<string, UIMessage>>(
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
() => new Map(pendingRef.current)
);

Expand All @@ -294,6 +295,7 @@ function useAgentSessionMessages({
const lastFlushAtRef = useRef<number>(0);
const pendingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const scheduleFlush = useRef<() => void>(() => {});
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
scheduleFlush.current = () => {
if (pendingTimerRef.current !== null) return; // already scheduled
const now = Date.now();
Expand Down Expand Up @@ -670,6 +672,7 @@ function useAgentSessionMessages({
return useMemo(() => {
const timestamps = timestampsRef.current;
const arr = Array.from(messagesById.values());
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
arr.sort((a, b) => {
const ta = timestamps.get(a.id) ?? 0;
const tb = timestamps.get(b.id) ?? 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export function useDeliveriesLiveReload({
const location = useLocation();
const deliveriesPollFetcher = useTypedFetcher<typeof liveDeliveriesLoader>();
const deliveriesPollFetcherStateRef = useRef(deliveriesPollFetcher.state);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
deliveriesPollFetcherStateRef.current = deliveriesPollFetcher.state;

const [visibleDeliveries, setVisibleDeliveries] = useState(deliveries);
Expand Down
1 change: 1 addition & 0 deletions apps/webapp/app/hooks/useAutoRevalidate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export function useAutoRevalidate(options: UseAutoRevalidateOptions = {}) {
const { interval = 5000, onFocus = true, disabled = false } = options;
const revalidator = useRevalidator();
const revalidatorRef = useRef(revalidator);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
revalidatorRef.current = revalidator;

useEffect(() => {
Expand Down
2 changes: 2 additions & 0 deletions apps/webapp/app/hooks/useChanged.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export function useChanged<T extends { id: string }>(
const itemRef = useRef<T | undefined>();
const itemId = item?.id;

// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
actionRef.current = action;
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
itemRef.current = item;

useEffect(() => {
Expand Down
1 change: 1 addition & 0 deletions apps/webapp/app/hooks/useDashboardEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ export function useDashboardEditor({
const isInitializedRef = useRef(false);
const currentLayoutJsonRef = useRef<string>(JSON.stringify(initialData.layout));
const initialDataRef = useRef(initialData);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
initialDataRef.current = initialData;

// Sync queue to prevent race conditions
Expand Down
1 change: 1 addition & 0 deletions apps/webapp/app/hooks/useDebounce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function useDebounceEffect<T>(value: T, fn: (value: T) => void, delay: nu
const fnRef = useRef(fn);

// Update the ref whenever the function changes
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
fnRef.current = fn;

useEffect(() => {
Expand Down
1 change: 1 addition & 0 deletions apps/webapp/app/hooks/useElementVisibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export function useElementVisibility({ onVisibilityChange }: UseElementVisibilit
const ref = useRef<HTMLDivElement>(null);
const isVisibleRef = useRef(false);
const callbackRef = useRef(onVisibilityChange);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
callbackRef.current = onVisibilityChange;

useEffect(() => {
Expand Down
Loading