Harden RC9 UX and performance states
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-18 06:37:54 +02:00
parent 55685ba1bd
commit 9b0239747d
21 changed files with 845 additions and 25 deletions
@@ -82,6 +82,7 @@ describe('useCoverageResolver', () => {
expect(result.current.coverage).toBeNull()
expect(result.current.coverageError).toBeNull()
expect(result.current.loadingCoverage).toBe(false)
expect(result.current.coverageDurationMs).toBeNull()
})
it('exposes provider failures without retaining stale results', async () => {
@@ -95,4 +96,28 @@ describe('useCoverageResolver', () => {
expect(result.current.coverage).toBeNull()
expect(result.current.loadingCoverage).toBe(false)
})
it('records duration and exposes a slow coverage response', async () => {
let resolveRequest: ((value: CoverageResolveResponse) => void) | null = null
mocks.resolveCoverage.mockImplementationOnce(
() => new Promise<CoverageResolveResponse>((resolve) => {
resolveRequest = resolve
}),
)
const { result } = renderHook(() => useCoverageResolver({ projectId: 'project-1', bbox }))
await act(async () => {
await vi.advanceTimersByTimeAsync(250)
})
expect(result.current.loadingCoverage).toBe(true)
await act(async () => {
await vi.advanceTimersByTimeAsync(4_001)
resolveRequest?.(coverageResult)
await Promise.resolve()
})
expect(result.current.coverageDurationMs).toBe(4_001)
expect(result.current.coverageBudgetExceeded).toBe(true)
})
})
+14
View File
@@ -1,6 +1,10 @@
import { useEffect, useState } from 'react'
import { externalApi } from '../services/api'
import type { CoverageResolveResponse, VectorSelectionBBox } from '../types'
import {
COVERAGE_RESPONSE_BUDGET_MS,
exceedsPerformanceBudget,
} from '../lib/performanceBudget'
interface CoverageResolverOptions {
projectId: string | null
@@ -11,18 +15,22 @@ export function useCoverageResolver({ projectId, bbox }: CoverageResolverOptions
const [coverage, setCoverage] = useState<CoverageResolveResponse | null>(null)
const [loadingCoverage, setLoadingCoverage] = useState(false)
const [coverageError, setCoverageError] = useState<string | null>(null)
const [coverageDurationMs, setCoverageDurationMs] = useState<number | null>(null)
useEffect(() => {
if (!projectId || !bbox) {
setCoverage(null)
setCoverageError(null)
setLoadingCoverage(false)
setCoverageDurationMs(null)
return
}
let cancelled = false
const timer = window.setTimeout(() => {
const startedAt = Date.now()
setLoadingCoverage(true)
setCoverageError(null)
setCoverageDurationMs(null)
externalApi.resolveCoverage({
projectId,
bbox: {
@@ -45,6 +53,7 @@ export function useCoverageResolver({ projectId, bbox }: CoverageResolverOptions
})
.finally(() => {
if (!cancelled) {
setCoverageDurationMs(Date.now() - startedAt)
setLoadingCoverage(false)
}
})
@@ -59,5 +68,10 @@ export function useCoverageResolver({ projectId, bbox }: CoverageResolverOptions
coverage,
loadingCoverage,
coverageError,
coverageDurationMs,
coverageBudgetExceeded: exceedsPerformanceBudget(
coverageDurationMs,
COVERAGE_RESPONSE_BUDGET_MS,
),
}
}