From 48d4b2dd56af63dedaede2bc74034acfb05cdd75 Mon Sep 17 00:00:00 2001 From: Mariusz Banach Date: Wed, 18 Feb 2026 03:20:58 +0100 Subject: [PATCH] MAESTRO: fix formatting for report lint --- ...er-analyzer-Phase-06-Interactive-Report.md | 2 +- .../src/__tests__/AnalysisControls.test.tsx | 20 +++-------- .../src/__tests__/ProgressIndicator.test.tsx | 8 ++--- frontend/src/__tests__/TestSelector.test.tsx | 8 ++--- .../report/HopChainVisualisation.test.tsx | 4 +-- .../__tests__/report/ReportExport.test.tsx | 14 +++----- .../report/SecurityAppliancesSummary.test.tsx | 4 +-- .../__tests__/report/TestResultCard.test.tsx | 5 +-- frontend/src/__tests__/useAnalysis.test.tsx | 34 ++++++------------ frontend/src/app/page.tsx | 4 +-- frontend/src/components/AnalysisControls.tsx | 12 ++----- frontend/src/components/AnalysisResults.tsx | 36 +++++-------------- frontend/src/components/ProgressIndicator.tsx | 31 +++++++--------- frontend/src/components/TestSelector.tsx | 4 +-- .../report/HopChainVisualisation.tsx | 24 +++---------- .../src/components/report/ReportContainer.tsx | 13 ++----- .../src/components/report/ReportExport.tsx | 4 +-- .../report/SecurityAppliancesSummary.tsx | 9 ++--- .../src/components/report/TestResultCard.tsx | 9 ++--- frontend/src/hooks/useAnalysis.ts | 21 ++++------- 20 files changed, 72 insertions(+), 194 deletions(-) diff --git a/Auto Run Docs/SpecKit-web-header-analyzer-Phase-06-Interactive-Report.md b/Auto Run Docs/SpecKit-web-header-analyzer-Phase-06-Interactive-Report.md index 8ed3371..6c2dd0a 100644 --- a/Auto Run Docs/SpecKit-web-header-analyzer-Phase-06-Interactive-Report.md +++ b/Auto Run Docs/SpecKit-web-header-analyzer-Phase-06-Interactive-Report.md @@ -56,5 +56,5 @@ ReportContainer - [x] Export JSON produces a valid JSON file containing all results - [x] Export HTML produces a styled standalone page viewable in any browser - [x] All report components are keyboard accessible -- [ ] Linting passes (`npx eslint src/`, `npx prettier --check src/`) +- [x] Linting passes (`npx eslint src/`, `npx prettier --check src/`) - [ ] Run `/speckit.analyze` to verify consistency diff --git a/frontend/src/__tests__/AnalysisControls.test.tsx b/frontend/src/__tests__/AnalysisControls.test.tsx index a62cbd7..dba0755 100644 --- a/frontend/src/__tests__/AnalysisControls.test.tsx +++ b/frontend/src/__tests__/AnalysisControls.test.tsx @@ -105,9 +105,7 @@ describe("AnalysisControls", () => { getTestSelector(container); expect(getToggle(container, "toggle-resolve").getAttribute("aria-checked")).toBe("false"); - expect(getToggle(container, "toggle-decode-all").getAttribute("aria-checked")).toBe( - "false", - ); + expect(getToggle(container, "toggle-decode-all").getAttribute("aria-checked")).toBe("false"); }); it("updates toggles without a controlled config", async () => { @@ -126,9 +124,7 @@ describe("AnalysisControls", () => { }); expect(decodeToggle.getAttribute("aria-checked")).toBe("true"); - expect(handleChange).toHaveBeenLastCalledWith( - expect.objectContaining({ decodeAll: true }), - ); + expect(handleChange).toHaveBeenLastCalledWith(expect.objectContaining({ decodeAll: true })); }); it("updates toggles on click and keyboard", async () => { @@ -168,9 +164,7 @@ describe("AnalysisControls", () => { const decodeToggle = getToggle(container, "toggle-decode-all"); act(() => { - decodeToggle.dispatchEvent( - new KeyboardEvent("keydown", { key: "Enter", bubbles: true }), - ); + decodeToggle.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true })); }); expect(decodeToggle.getAttribute("aria-checked")).toBe("true"); @@ -206,14 +200,10 @@ describe("AnalysisControls", () => { const resolveToggle = getToggle(container, "toggle-resolve"); act(() => { - resolveToggle.dispatchEvent( - new KeyboardEvent("keydown", { key: " ", bubbles: true }), - ); + resolveToggle.dispatchEvent(new KeyboardEvent("keydown", { key: " ", bubbles: true })); }); expect(resolveToggle.getAttribute("aria-checked")).toBe("true"); - expect(handleChange).toHaveBeenLastCalledWith( - expect.objectContaining({ resolve: true }), - ); + expect(handleChange).toHaveBeenLastCalledWith(expect.objectContaining({ resolve: true })); }); }); diff --git a/frontend/src/__tests__/ProgressIndicator.test.tsx b/frontend/src/__tests__/ProgressIndicator.test.tsx index b32c18e..401db0c 100644 --- a/frontend/src/__tests__/ProgressIndicator.test.tsx +++ b/frontend/src/__tests__/ProgressIndicator.test.tsx @@ -66,9 +66,7 @@ describe("ProgressIndicator", () => { expect(indicator.getAttribute("data-status")).toBe("analysing"); expect(indicator.getAttribute("data-variant")).toBe("normal"); - expect(getByTestId(container, "progress-percentage").textContent ?? "").toMatch( - /50%/, - ); + expect(getByTestId(container, "progress-percentage").textContent ?? "").toMatch(/50%/); expect(getByTestId(container, "progress-current-test").textContent ?? "").toMatch( /SpamAssassin Rule Hits/, ); @@ -117,8 +115,6 @@ describe("ProgressIndicator", () => { expect(getByTestId(container, "timeout-tests").textContent ?? "").toMatch( /Mimecast Fingerprint/, ); - expect(getByTestId(container, "timeout-tests").textContent ?? "").toMatch( - /Proofpoint TAP/, - ); + expect(getByTestId(container, "timeout-tests").textContent ?? "").toMatch(/Proofpoint TAP/); }); }); diff --git a/frontend/src/__tests__/TestSelector.test.tsx b/frontend/src/__tests__/TestSelector.test.tsx index aa48709..aa4bb75 100644 --- a/frontend/src/__tests__/TestSelector.test.tsx +++ b/frontend/src/__tests__/TestSelector.test.tsx @@ -180,9 +180,7 @@ describe("TestSelector", () => { const selectAllButton = getSelectAllButton(container); act(() => { - selectAllButton.dispatchEvent( - new KeyboardEvent("keydown", { key: "Enter", bubbles: true }), - ); + selectAllButton.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true })); }); sampleTests.forEach((test) => { @@ -191,9 +189,7 @@ describe("TestSelector", () => { const deselectAllButton = getDeselectAllButton(container); act(() => { - deselectAllButton.dispatchEvent( - new KeyboardEvent("keydown", { key: " ", bubbles: true }), - ); + deselectAllButton.dispatchEvent(new KeyboardEvent("keydown", { key: " ", bubbles: true })); }); sampleTests.forEach((test) => { diff --git a/frontend/src/__tests__/report/HopChainVisualisation.test.tsx b/frontend/src/__tests__/report/HopChainVisualisation.test.tsx index 3790ad1..90c10a1 100644 --- a/frontend/src/__tests__/report/HopChainVisualisation.test.tsx +++ b/frontend/src/__tests__/report/HopChainVisualisation.test.tsx @@ -84,9 +84,7 @@ describe("HopChainVisualisation", () => { it("renders connectors between hop nodes", () => { const { container } = render(); - const connectors = container.querySelectorAll( - '[data-testid^="hop-chain-connector-"]', - ); + const connectors = container.querySelectorAll('[data-testid^="hop-chain-connector-"]'); expect(connectors.length).toBe(hopChain.length - 1); }); diff --git a/frontend/src/__tests__/report/ReportExport.test.tsx b/frontend/src/__tests__/report/ReportExport.test.tsx index 3e847d0..f46b0d0 100644 --- a/frontend/src/__tests__/report/ReportExport.test.tsx +++ b/frontend/src/__tests__/report/ReportExport.test.tsx @@ -92,18 +92,14 @@ beforeEach(() => { } lastBlob = null; - createObjectUrlSpy = vi - .spyOn(URL, "createObjectURL") - .mockImplementation((blob: Blob) => { - lastBlob = blob; - return "blob:report"; - }); + createObjectUrlSpy = vi.spyOn(URL, "createObjectURL").mockImplementation((blob: Blob) => { + lastBlob = blob; + return "blob:report"; + }); revokeObjectUrlSpy = vi.spyOn(URL, "revokeObjectURL").mockImplementation(() => { return undefined; }); - clickSpy = vi - .spyOn(HTMLAnchorElement.prototype, "click") - .mockImplementation(() => undefined); + clickSpy = vi.spyOn(HTMLAnchorElement.prototype, "click").mockImplementation(() => undefined); }); afterEach(() => { diff --git a/frontend/src/__tests__/report/SecurityAppliancesSummary.test.tsx b/frontend/src/__tests__/report/SecurityAppliancesSummary.test.tsx index e6ddd00..c3a67f1 100644 --- a/frontend/src/__tests__/report/SecurityAppliancesSummary.test.tsx +++ b/frontend/src/__tests__/report/SecurityAppliancesSummary.test.tsx @@ -63,9 +63,7 @@ afterEach(() => { describe("SecurityAppliancesSummary", () => { it("renders detected security appliances as badges", () => { - const { container } = render( - , - ); + const { container } = render(); const summary = getByTestId(container, "security-appliances-summary"); expect(summary.textContent ?? "").toContain("Mimecast Email Security"); diff --git a/frontend/src/__tests__/report/TestResultCard.test.tsx b/frontend/src/__tests__/report/TestResultCard.test.tsx index c5f8ae5..7137047 100644 --- a/frontend/src/__tests__/report/TestResultCard.test.tsx +++ b/frontend/src/__tests__/report/TestResultCard.test.tsx @@ -83,10 +83,7 @@ describe("TestResultCard", () => { const { container } = render(); - const severityBadge = getByTestId( - container, - `test-result-severity-${result.testId}`, - ); + const severityBadge = getByTestId(container, `test-result-severity-${result.testId}`); expect(severityBadge.textContent ?? "").toContain(severityCase.label); expect(severityBadge.className).toContain(severityCase.className); diff --git a/frontend/src/__tests__/useAnalysis.test.tsx b/frontend/src/__tests__/useAnalysis.test.tsx index 63c6e1c..8cc4bd3 100644 --- a/frontend/src/__tests__/useAnalysis.test.tsx +++ b/frontend/src/__tests__/useAnalysis.test.tsx @@ -148,12 +148,10 @@ describe("useAnalysis", () => { }); it("submits analysis and handles SSE progress + result", async () => { - const streamSpy = vi.spyOn(apiClient, "stream").mockImplementation( - async (_path, options) => { - options.onEvent({ event: "progress", data: progressEvent, raw: "" }); - options.onEvent({ event: "result", data: completeReport, raw: "" }); - }, - ); + const streamSpy = vi.spyOn(apiClient, "stream").mockImplementation(async (_path, options) => { + options.onEvent({ event: "progress", data: progressEvent, raw: "" }); + options.onEvent({ event: "result", data: completeReport, raw: "" }); + }); const statuses: string[] = []; const { container } = render( @@ -161,9 +159,7 @@ describe("useAnalysis", () => { ); act(() => { - getByTestId(container, "submit").dispatchEvent( - new MouseEvent("click", { bubbles: true }), - ); + getByTestId(container, "submit").dispatchEvent(new MouseEvent("click", { bubbles: true })); }); await act(async () => { @@ -180,9 +176,7 @@ describe("useAnalysis", () => { ); expect(statuses).toEqual(["idle", "submitting", "analysing", "complete"]); - expect(getByTestId(container, "current-test").textContent).toMatch( - /SpamAssassin Rule Hits/, - ); + expect(getByTestId(container, "current-test").textContent).toMatch(/SpamAssassin Rule Hits/); expect(getByTestId(container, "percentage").textContent).toBe("33"); expect(getByTestId(container, "result-total").textContent).toBe("3"); }); @@ -198,9 +192,7 @@ describe("useAnalysis", () => { ); act(() => { - getByTestId(container, "submit").dispatchEvent( - new MouseEvent("click", { bubbles: true }), - ); + getByTestId(container, "submit").dispatchEvent(new MouseEvent("click", { bubbles: true })); }); await act(async () => { @@ -220,9 +212,7 @@ describe("useAnalysis", () => { ); act(() => { - getByTestId(container, "submit").dispatchEvent( - new MouseEvent("click", { bubbles: true }), - ); + getByTestId(container, "submit").dispatchEvent(new MouseEvent("click", { bubbles: true })); }); await act(async () => { @@ -252,9 +242,7 @@ describe("useAnalysis", () => { ); act(() => { - getByTestId(container, "submit").dispatchEvent( - new MouseEvent("click", { bubbles: true }), - ); + getByTestId(container, "submit").dispatchEvent(new MouseEvent("click", { bubbles: true })); }); await act(async () => { @@ -262,9 +250,7 @@ describe("useAnalysis", () => { }); act(() => { - getByTestId(container, "cancel").dispatchEvent( - new MouseEvent("click", { bubbles: true }), - ); + getByTestId(container, "cancel").dispatchEvent(new MouseEvent("click", { bubbles: true })); }); await act(async () => { diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index a95b8a0..a3f7eea 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -93,9 +93,7 @@ export default function Home() { - {result ? ( - - ) : null} + {result ? : null} diff --git a/frontend/src/components/AnalysisControls.tsx b/frontend/src/components/AnalysisControls.tsx index 563b2ec..c5ceef0 100644 --- a/frontend/src/components/AnalysisControls.tsx +++ b/frontend/src/components/AnalysisControls.tsx @@ -2,12 +2,7 @@ import { useState, type KeyboardEvent } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faToggleOff, - faToggleOn, - faGlobe, - faCode, -} from "@fortawesome/free-solid-svg-icons"; +import { faToggleOff, faToggleOn, faGlobe, faCode } from "@fortawesome/free-solid-svg-icons"; import type { AnalysisConfig } from "../types/analysis"; import TestSelector from "./TestSelector"; @@ -33,10 +28,7 @@ const handleToggleKeyDown = ( } }; -export default function AnalysisControls({ - config, - onChange, -}: AnalysisControlsProps) { +export default function AnalysisControls({ config, onChange }: AnalysisControlsProps) { const [internalConfig, setInternalConfig] = useState(defaultConfig); const resolvedConfig = config ?? internalConfig; diff --git a/frontend/src/components/AnalysisResults.tsx b/frontend/src/components/AnalysisResults.tsx index 285dd25..c7790f4 100644 --- a/frontend/src/components/AnalysisResults.tsx +++ b/frontend/src/components/AnalysisResults.tsx @@ -3,31 +3,20 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; -import type { - AnalysisReport, - TestResult, - TestSeverity, - TestStatus, -} from "../types/analysis"; +import type { AnalysisReport, TestResult, TestSeverity, TestStatus } from "../types/analysis"; type AnalysisResultsProps = { report: AnalysisReport; }; -const severityStyles: Record< - TestSeverity, - { label: string; className: string } -> = { +const severityStyles: Record = { spam: { label: "Spam", className: "text-spam border-spam/40" }, suspicious: { label: "Suspicious", className: "text-suspicious border-suspicious/40" }, clean: { label: "Clean", className: "text-clean border-clean/40" }, info: { label: "Info", className: "text-accent border-accent/40" }, }; -const statusStyles: Record< - TestStatus, - { label: string; className: string } -> = { +const statusStyles: Record = { success: { label: "Success", className: "text-clean border-clean/40" }, error: { label: "Error", className: "text-spam border-spam/40" }, skipped: { label: "Skipped", className: "text-suspicious border-suspicious/40" }, @@ -45,9 +34,7 @@ export default function AnalysisResults({ report }: AnalysisResultsProps) { className="rounded-2xl border border-info/10 bg-surface p-6 shadow-[0_0_40px_rgba(15,23,42,0.25)]" >
- - Analysis Report - + Analysis Report {report.metadata.passedTests} passed / {report.metadata.failedTests} failed @@ -66,22 +53,16 @@ export default function AnalysisResults({ report }: AnalysisResultsProps) { >
- - {result.testName} - + {result.testName} Test #{result.testId} ยท {result.headerName}
- + {statusStyle.label} - + {severityStyle.label}
@@ -102,8 +83,7 @@ export default function AnalysisResults({ report }: AnalysisResultsProps) { > - Error:{" "} - {getErrorMessage(result)} + Error: {getErrorMessage(result)}
) : null} diff --git a/frontend/src/components/ProgressIndicator.tsx b/frontend/src/components/ProgressIndicator.tsx index e854a8a..7785ea0 100644 --- a/frontend/src/components/ProgressIndicator.tsx +++ b/frontend/src/components/ProgressIndicator.tsx @@ -35,11 +35,14 @@ const getVariant = ( return remainingSeconds <= warningThreshold ? "warning" : "normal"; }; -const variantStyles: Record = { +const variantStyles: Record< + ProgressVariant, + { + bar: string; + badge: string; + track: string; + } +> = { normal: { bar: "bg-clean", badge: "text-clean border-clean/40", @@ -94,9 +97,7 @@ export default function ProgressIndicator({ ? anchor.elapsedMs + Math.max(0, Date.now() - anchor.timestamp) : baseElapsedMs; - setElapsedMs((previous) => - previous === nextElapsedMs ? previous : nextElapsedMs, - ); + setElapsedMs((previous) => (previous === nextElapsedMs ? previous : nextElapsedMs)); }, 1000); return () => { @@ -141,16 +142,10 @@ export default function ProgressIndicator({
- + {progress?.currentTest ?? "Preparing analysis"} - + {percentage}%
@@ -173,9 +168,7 @@ export default function ProgressIndicator({ role="alert" className="mt-4 rounded-xl border border-spam/30 bg-spam/10 p-4 text-xs text-text/80" > -

- Timeout reached at {timeoutSeconds} seconds. -

+

Timeout reached at {timeoutSeconds} seconds.

Incomplete tests:

{incompleteTests.length > 0 ? incompleteTests.join(", ") : "None"} diff --git a/frontend/src/components/TestSelector.tsx b/frontend/src/components/TestSelector.tsx index b49fa6a..a4411ad 100644 --- a/frontend/src/components/TestSelector.tsx +++ b/frontend/src/components/TestSelector.tsx @@ -180,9 +180,7 @@ export default function TestSelector({ selectedTestIds, onSelectionChange }: Tes

- {isLoading ? ( -

Loading tests...

- ) : null} + {isLoading ?

Loading tests...

: null} {error ? (

{error} diff --git a/frontend/src/components/report/HopChainVisualisation.tsx b/frontend/src/components/report/HopChainVisualisation.tsx index b718d3f..65c54f9 100644 --- a/frontend/src/components/report/HopChainVisualisation.tsx +++ b/frontend/src/components/report/HopChainVisualisation.tsx @@ -1,12 +1,7 @@ "use client"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faArrowDown, - faClock, - faNetworkWired, - faServer, -} from "@fortawesome/free-solid-svg-icons"; +import { faArrowDown, faClock, faNetworkWired, faServer } from "@fortawesome/free-solid-svg-icons"; import type { HopChainNode } from "../../types/analysis"; @@ -21,9 +16,7 @@ const formatDelay = (delay?: number | null): string | null => { return `${delay.toFixed(2)}s`; }; -export default function HopChainVisualisation({ - hopChain, -}: HopChainVisualisationProps) { +export default function HopChainVisualisation({ hopChain }: HopChainVisualisationProps) { if (hopChain.length === 0) { return (

- - {node.hostname} - + {node.hostname} {node.ip ? ( - - {node.ip} - + {node.ip} ) : null}
@@ -74,10 +63,7 @@ export default function HopChainVisualisation({ ) : null} {node.serverInfo ? ( - + {node.serverInfo} ) : null} diff --git a/frontend/src/components/report/ReportContainer.tsx b/frontend/src/components/report/ReportContainer.tsx index d270ec2..5f80fbb 100644 --- a/frontend/src/components/report/ReportContainer.tsx +++ b/frontend/src/components/report/ReportContainer.tsx @@ -118,9 +118,7 @@ export default function ReportContainer({ report }: { report: AnalysisReport }) >
-

- Interactive Report -

+

Interactive Report

Header Analysis Summary

@@ -131,9 +129,7 @@ export default function ReportContainer({ report }: { report: AnalysisReport })

Summary Stats

- - Totals - + Totals
{summaryItems.map((item) => ( @@ -180,10 +176,7 @@ export default function ReportContainer({ report }: { report: AnalysisReport })
{filteredResults.map((result) => ( -
+
))} diff --git a/frontend/src/components/report/ReportExport.tsx b/frontend/src/components/report/ReportExport.tsx index adda87e..8215ed3 100644 --- a/frontend/src/components/report/ReportExport.tsx +++ b/frontend/src/components/report/ReportExport.tsx @@ -86,7 +86,7 @@ const buildHtmlReport = (report: AnalysisReport): string => { const renderHopChain = (): string => { if (report.hopChain.length === 0) { - return "

No hop chain data available.

"; + return '

No hop chain data available.

'; } const items = report.hopChain @@ -108,7 +108,7 @@ const buildHtmlReport = (report: AnalysisReport): string => { const renderSecurityAppliances = (): string => { if (report.securityAppliances.length === 0) { - return "

No security appliances detected.

"; + return '

No security appliances detected.

'; } const items = report.securityAppliances diff --git a/frontend/src/components/report/SecurityAppliancesSummary.tsx b/frontend/src/components/report/SecurityAppliancesSummary.tsx index b394b80..53f6cf7 100644 --- a/frontend/src/components/report/SecurityAppliancesSummary.tsx +++ b/frontend/src/components/report/SecurityAppliancesSummary.tsx @@ -9,9 +9,7 @@ type SecurityAppliancesSummaryProps = { appliances: SecurityAppliance[]; }; -export default function SecurityAppliancesSummary({ - appliances, -}: SecurityAppliancesSummaryProps) { +export default function SecurityAppliancesSummary({ appliances }: SecurityAppliancesSummaryProps) { const hasAppliances = appliances.length > 0; return ( @@ -46,10 +44,7 @@ export default function SecurityAppliancesSummary({ ))}
) : ( -

+

No security appliances detected.

)} diff --git a/frontend/src/components/report/TestResultCard.tsx b/frontend/src/components/report/TestResultCard.tsx index c5bd196..74e9a5c 100644 --- a/frontend/src/components/report/TestResultCard.tsx +++ b/frontend/src/components/report/TestResultCard.tsx @@ -58,10 +58,7 @@ const highlightText = (text: string, query: string): ReactNode => { const matchText = text.slice(matchIndex, matchIndex + normalizedQuery.length); parts.push( - + {matchText} , ); @@ -138,9 +135,7 @@ export default function TestResultCard({ result, highlightQuery = "" }: TestResu
- - Header - + Header {highlightText(result.headerName, highlightQuery)} diff --git a/frontend/src/hooks/useAnalysis.ts b/frontend/src/hooks/useAnalysis.ts index a2d4c08..bbb94f5 100644 --- a/frontend/src/hooks/useAnalysis.ts +++ b/frontend/src/hooks/useAnalysis.ts @@ -4,13 +4,7 @@ import { flushSync } from "react-dom"; import { apiClient, type SseEvent } from "../lib/api-client"; import type { AnalysisConfig, AnalysisProgress, AnalysisReport } from "../types/analysis"; -export type AnalysisStatus = - | "idle" - | "submitting" - | "analysing" - | "complete" - | "error" - | "timeout"; +export type AnalysisStatus = "idle" | "submitting" | "analysing" | "complete" | "error" | "timeout"; export interface AnalysisRequest { headers: string; @@ -112,14 +106,11 @@ const useAnalysis = (): UseAnalysisState => { hasProgressRef.current = false; try { - await apiClient.stream( - "/api/analyse", - { - body: request, - signal: controller.signal, - onEvent: (event) => handleEvent(event, requestId, controller.signal), - }, - ); + await apiClient.stream("/api/analyse", { + body: request, + signal: controller.signal, + onEvent: (event) => handleEvent(event, requestId, controller.signal), + }); } catch (err) { if (!mountedRef.current || controller.signal.aborted) { return;