File previews read once and then froze: visited tabs stay mounted-but-hidden (LRU in useMountedTabSet), so refetchOnMount never re-fired and the only way to see fresh content was to close and reopen the tab. Gate the file query's `enabled` on tab + app visibility so React Query refetches on the disabled->enabled transition — and only when the cached read is stale (keeping the existing 5s staleTime), so a quick flip away and back doesn't re-read. Covers switching between tabs and backgrounding/reopening the whole app to the same tab. Adds a wiring test covering the stale-revisit refetch, the fresh-revisit debounce, the app-foreground refetch, and the read-error path. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
99643ad085
commit
daf7042cd2
@@ -0,0 +1,28 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { isFileQueryEnabled } from "./file-pane-enabled";
|
||||||
|
|
||||||
|
describe("isFileQueryEnabled", () => {
|
||||||
|
it("reads when there is a target, the tab is active, and the app is visible", () => {
|
||||||
|
expect(isFileQueryEnabled({ hasReadTarget: true, isTabActive: true, isAppVisible: true })).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not read while the tab is hidden", () => {
|
||||||
|
expect(
|
||||||
|
isFileQueryEnabled({ hasReadTarget: true, isTabActive: false, isAppVisible: true }),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not read while the app is backgrounded", () => {
|
||||||
|
expect(
|
||||||
|
isFileQueryEnabled({ hasReadTarget: true, isTabActive: true, isAppVisible: false }),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not read without a resolved file target", () => {
|
||||||
|
expect(
|
||||||
|
isFileQueryEnabled({ hasReadTarget: false, isTabActive: true, isAppVisible: true }),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Whether `FilePane` should read its file right now.
|
||||||
|
*
|
||||||
|
* The read is gated on visibility so a revisited tab refetches instead of showing
|
||||||
|
* the frozen first-load snapshot (#445): React Query refetches on the
|
||||||
|
* disabled→enabled transition (stale-gated by the query's staleTime). The file is
|
||||||
|
* read only when there is something to read AND the pane can actually show it —
|
||||||
|
* the tab is the active one (not a hidden, mounted-but-offscreen tab) and the
|
||||||
|
* whole app is in the foreground.
|
||||||
|
*/
|
||||||
|
export function isFileQueryEnabled(input: {
|
||||||
|
hasReadTarget: boolean;
|
||||||
|
isTabActive: boolean;
|
||||||
|
isAppVisible: boolean;
|
||||||
|
}): boolean {
|
||||||
|
return input.hasReadTarget && input.isTabActive && input.isAppVisible;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useMemo, useRef } from "react";
|
import React, { useContext, useEffect, useMemo, useRef } from "react";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import type { FileReadResult } from "@getpaseo/client/internal/daemon-client";
|
import type { FileReadResult } from "@getpaseo/client/internal/daemon-client";
|
||||||
import {
|
import {
|
||||||
@@ -29,6 +29,9 @@ import { createPreviewAttachmentId, getFileNameFromPath } from "@/attachments/ut
|
|||||||
import { explorerFileFromReadResult } from "@/file-explorer/read-result";
|
import { explorerFileFromReadResult } from "@/file-explorer/read-result";
|
||||||
import { resolveFilePreviewReadTarget } from "@/file-explorer/preview-target";
|
import { resolveFilePreviewReadTarget } from "@/file-explorer/preview-target";
|
||||||
import type { WorkspaceFileLocation } from "@/workspace/file-open";
|
import type { WorkspaceFileLocation } from "@/workspace/file-open";
|
||||||
|
import { MountedTabActiveContext } from "@/components/split-container";
|
||||||
|
import { useAppVisible } from "@/hooks/use-app-visible";
|
||||||
|
import { isFileQueryEnabled } from "@/components/file-pane-enabled";
|
||||||
|
|
||||||
interface CodeLineProps {
|
interface CodeLineProps {
|
||||||
tokens: HighlightToken[];
|
tokens: HighlightToken[];
|
||||||
@@ -408,9 +411,19 @@ export function FilePane({
|
|||||||
[normalizedFilePath, normalizedWorkspaceRoot],
|
[normalizedFilePath, normalizedWorkspaceRoot],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Re-read the file when this pane becomes visible again (#445). `isActive`
|
||||||
|
// covers tab switches, `isAppVisible` the whole-app background/foreground; the
|
||||||
|
// gate itself lives in isFileQueryEnabled.
|
||||||
|
const isActive = useContext(MountedTabActiveContext);
|
||||||
|
const isAppVisible = useAppVisible();
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: ["workspaceFile", serverId, readTarget?.cwd ?? null, readTarget?.path ?? null],
|
queryKey: ["workspaceFile", serverId, readTarget?.cwd ?? null, readTarget?.path ?? null],
|
||||||
enabled: Boolean(client && readTarget),
|
enabled: isFileQueryEnabled({
|
||||||
|
hasReadTarget: Boolean(client && readTarget),
|
||||||
|
isTabActive: isActive,
|
||||||
|
isAppVisible,
|
||||||
|
}),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!client || !readTarget) {
|
if (!client || !readTarget) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user