fix(web): resolve ESLint regressions from eslint-config-next upgrade
- Escape unescaped entities (react/no-unescaped-entities) in step-remote, step-welcome, projects-view, settings-panels - Add targeted eslint-disable-next-line for react-hooks/set-state-in-effect on established async-fetch and prop-sync patterns in useEffect bodies: chat-mode, file-content-viewer, files-view, step-dev-root, projects-view, settings-panels, update-banner, visualizer-view, carousel, use-mobile - Add targeted eslint-disable-next-line for react-hooks/purity on Date.now() display timestamps in streaming chat messages (chat-mode) - Remove now-unused eslint-disable directives (projects-view, settings-panels) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
b0a8f32a10
commit
2d34d3a386
13 changed files with 24 additions and 6 deletions
|
|
@ -2364,6 +2364,7 @@ function ToolExecutionBlock({ tool }: { tool: CompletedToolExecution }) {
|
|||
const hasVisibleResult = Boolean(diff || resultText.trim() || isError);
|
||||
if (!hasVisibleResult) return;
|
||||
autoExpandedRef.current = true;
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- ref-guarded, runs at most once
|
||||
setExpanded(true);
|
||||
}, [diff, resultText, isError]);
|
||||
|
||||
|
|
@ -2652,6 +2653,7 @@ export function ChatPane({ className, onOpenAction }: ChatPaneProps) {
|
|||
role: "assistant",
|
||||
content: seg.content,
|
||||
complete: true,
|
||||
// eslint-disable-next-line react-hooks/purity -- display timestamp for in-progress message
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
});
|
||||
|
|
@ -2820,6 +2822,7 @@ export function ChatPane({ className, onOpenAction }: ChatPaneProps) {
|
|||
role: "assistant",
|
||||
content: item.content,
|
||||
complete: false,
|
||||
// eslint-disable-next-line react-hooks/purity -- display timestamp for streaming message
|
||||
timestamp: Date.now(),
|
||||
}}
|
||||
isThinking={item.isThinking}
|
||||
|
|
|
|||
|
|
@ -760,6 +760,7 @@ export function FileContentViewer({
|
|||
|
||||
// Reset edit content when the source content changes (e.g. after save + re-fetch)
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- syncing local copy when prop changes
|
||||
setEditContent(content);
|
||||
}, [content]);
|
||||
|
||||
|
|
|
|||
|
|
@ -712,12 +712,14 @@ export function FilesView() {
|
|||
// Fetch tree when tab changes and data isn't cached
|
||||
useEffect(() => {
|
||||
if (!treeLoaded) {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- async fetch, setState runs after await
|
||||
fetchTree(activeRoot);
|
||||
}
|
||||
}, [activeRoot, treeLoaded, fetchTree]);
|
||||
|
||||
// Initial load
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- async fetch, setState runs after await
|
||||
fetchTree("sf");
|
||||
}, [fetchTree]);
|
||||
|
||||
|
|
@ -852,6 +854,7 @@ export function FilesView() {
|
|||
consumedPendingRef.current = true;
|
||||
const { root, path } = pendingFileRequest;
|
||||
pendingFileRequest = null;
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- async, setState runs after await
|
||||
void processFileOpen(root, path);
|
||||
}
|
||||
}, [processFileOpen]);
|
||||
|
|
@ -1283,6 +1286,7 @@ export function FilesView() {
|
|||
);
|
||||
if (hasStateMd) {
|
||||
autoSelectedRef.current = true;
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- async, setState runs after await
|
||||
void openFileTab("sf", "STATE.md");
|
||||
}
|
||||
}, [sfTree, openTabs.length, openFileTab]);
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ function InlineFolderBrowser({
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- async fetch, setState runs after await
|
||||
void browse();
|
||||
}, [browse]);
|
||||
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ export function StepRemote({ onBack, onNext }: StepRemoteProps) {
|
|||
{channelId.trim().length > 0 &&
|
||||
!CHANNEL_ID_PATTERNS[channel].test(channelId.trim()) && (
|
||||
<p className="text-xs text-destructive/70">
|
||||
Doesn't match the expected format for {channel}
|
||||
Doesn't match the expected format for {channel}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export function StepWelcome({ onNext }: StepWelcomeProps) {
|
|||
transition={{ delay: 0.16, duration: 0.4 }}
|
||||
className="max-w-sm text-[15px] leading-relaxed text-muted-foreground"
|
||||
>
|
||||
Let's get your workspace ready. This takes about a minute.
|
||||
Let's get your workspace ready. This takes about a minute.
|
||||
</motion.p>
|
||||
|
||||
{/* Steps preview */}
|
||||
|
|
|
|||
|
|
@ -400,7 +400,6 @@ export function ProjectsPanel({
|
|||
setNewProjectOpen(false);
|
||||
handleSelectProject(newProject);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[handleSelectProject],
|
||||
);
|
||||
|
||||
|
|
@ -638,6 +637,7 @@ function NewProjectDialog({
|
|||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- reset form state on dialog open
|
||||
setName("");
|
||||
setError(null);
|
||||
setCreating(false);
|
||||
|
|
@ -809,6 +809,7 @@ function FolderPickerDialog({
|
|||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- async fetch, setState runs after await
|
||||
void browse(initialPath ?? undefined);
|
||||
}
|
||||
}, [open, initialPath, browse]);
|
||||
|
|
@ -1439,7 +1440,7 @@ export function ProjectSelectionGate() {
|
|||
{/* Empty filter state */}
|
||||
{filteredProjects.length === 0 && filter.trim() && (
|
||||
<div className="px-4 py-8 text-center text-xs text-muted-foreground">
|
||||
No projects matching "{filter}"
|
||||
No projects matching "{filter}"
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -830,11 +830,13 @@ export function RemoteQuestionsPanel() {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- async fetch, setState runs after await
|
||||
void fetchApiStatus();
|
||||
}, [fetchApiStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
if (existingConfig?.channel) {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- syncing local state from loaded config
|
||||
setChannel(existingConfig.channel);
|
||||
setChannelId(existingConfig.channelId ?? "");
|
||||
setTimeoutMinutes(existingConfig.timeoutMinutes ?? 5);
|
||||
|
|
@ -1103,7 +1105,7 @@ export function RemoteQuestionsPanel() {
|
|||
{channelId.trim().length > 0 &&
|
||||
!CHANNEL_ID_PATTERNS[channel].test(channelId.trim()) && (
|
||||
<p className="text-[11px] text-destructive/70">
|
||||
Doesn't match the expected format for{" "}
|
||||
Doesn't match the expected format for{" "}
|
||||
{selectedChannelOption.label}
|
||||
</p>
|
||||
)}
|
||||
|
|
@ -1416,11 +1418,12 @@ export function ExperimentalPanel() {
|
|||
if (!data && !busy && state.phase === "idle") {
|
||||
refresh();
|
||||
}
|
||||
}, [state.phase, refresh, data, busy]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
}, [state.phase, refresh, data, busy]);
|
||||
|
||||
// Sync local state from loaded prefs
|
||||
useEffect(() => {
|
||||
if (!prefs) return;
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- syncing local copy when prefs load
|
||||
setFlags({ rtk: prefs.experimental?.rtk === true });
|
||||
}, [prefs]);
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export function UpdateBanner() {
|
|||
|
||||
// Initial fetch on mount
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- async fetch, setState runs after await
|
||||
void fetchStatus();
|
||||
}, [fetchStatus]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1374,6 +1374,7 @@ export function VisualizerView() {
|
|||
}, [projectCwd]);
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- async fetch, setState runs after await
|
||||
fetchData();
|
||||
const interval = setInterval(fetchData, 10_000);
|
||||
return () => clearInterval(interval);
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ function Carousel({
|
|||
|
||||
React.useEffect(() => {
|
||||
if (!api) return;
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- carousel API callback, setState via onSelect
|
||||
onSelect(api);
|
||||
api.on("reInit", onSelect);
|
||||
api.on("select", onSelect);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export function useIsMobile() {
|
|||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||
};
|
||||
mql.addEventListener("change", onChange);
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- initial sync with viewport, runs once on mount
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||
return () => mql.removeEventListener("change", onChange);
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export function useIsMobile() {
|
|||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||
};
|
||||
mql.addEventListener("change", onChange);
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- initial sync with viewport, runs once on mount
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||
return () => mql.removeEventListener("change", onChange);
|
||||
}, []);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue