Breng de opstartkosten terug en geef de werkstand een schakelaar

De inlogpagina haalde 1,46 MB over de lijn, waaronder de volledige kaartmotor
van 213 kB terwijl er geen kaart staat. De werkbank is nu een aparte brok die
pas na aanmelden geladen wordt: de eerste JS-bundel gaat van 476 naar 30 kB, en
met de beeldwerk uit een eerdere commit komt de inlogpagina op 344 kB.

Bij het opstarten gingen 27 verzoeken de deur uit in drie golven. Alles werd
vooraf opgehaald, ook voor werkbladen die de gebruiker misschien nooit opent,
en twee daarvan werden dubbel opgevraagd door effecten die negentig
milliseconde na elkaar liepen. Gelijktijdige en vlak opeenvolgende identieke
GET-verzoeken delen nu één belofte, en elk werkblad haalt zijn eigen gegevens
pas op wanneer het geopend wordt. Nu zestien verzoeken.

Typen in het themazoekveld kostte 53 ms mediaan met uitschieters tot 127 ms,
omdat die filterwaarde in het viewmodel van de hele werkruimte leefde. Het veld
is een eigen component geworden dat zijn invoer zelf bijhoudt. React.memo om de
kaart is geprobeerd en weer verwijderd: van de negentien props worden er te
veel per render opnieuw gemaakt, dus de vergelijking sloeg nooit over.

Verder: donker blijft de standaard maar er is nu een weg terug naar licht, de
kaartondergrond schakelt mee zonder de datalagen te herbouwen, de onderste
hero-afbeelding wordt pas opgehaald als hij in beeld komt, en de werkbank heeft
een h1 zodat schermlezers een ankerpunt hebben.

De tests op de bootstrap legden het oude gedrag vast en toetsen nu het nieuwe,
van drie naar zeven gevallen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Jens
2026-08-23 15:00:57 +02:00
co-authored by Claude Opus 5
parent 663855f8a4
commit 70157400d9
16 changed files with 2202 additions and 1532 deletions
@@ -7,8 +7,9 @@ function action() {
return vi.fn().mockResolvedValue(undefined)
}
function options(selectedProjectId: string | null) {
function options(selectedProjectId: string | null, activeWorkspace = 'map') {
return {
activeWorkspace,
selectedProjectId,
selectedDetectionRunId: '',
detectionClassFilter: '',
@@ -36,20 +37,17 @@ function options(selectedProjectId: string | null) {
}
describe('useWorkbenchBootstrap', () => {
it('loads global capabilities and clears project-owned state without a project', async () => {
it('haalt zonder project alleen de werkruimtes op en wist projectgebonden state', async () => {
const state = options(null)
renderHook(() => useWorkbenchBootstrap(state))
await waitFor(() => expect(state.loadProjects).toHaveBeenCalledOnce())
expect(state.loadCapabilities).toHaveBeenCalledOnce()
expect(state.loadDetectionModels).toHaveBeenCalledOnce()
expect(state.loadSegmentationModels).toHaveBeenCalledOnce()
expect(state.resetProjectData).toHaveBeenCalledOnce()
expect(state.resetDatasetForProject).toHaveBeenCalledOnce()
expect(state.loadProjectData).not.toHaveBeenCalled()
})
it('resets stale state before loading every project-owned collection', async () => {
it('laadt op het kaartwerkblad de gebieden en bronnen, en verder niets', async () => {
const state = options('project-1')
renderHook(() => useWorkbenchBootstrap(state))
@@ -59,25 +57,76 @@ describe('useWorkbenchBootstrap', () => {
expect(state.resetDetectionForProject).toHaveBeenCalledOnce()
expect(state.resetSegmentationForProject).toHaveBeenCalledOnce()
expect(state.resetExportsForProject).toHaveBeenCalledOnce()
expect(state.loadDetectionRuns).toHaveBeenCalledWith('project-1')
expect(state.loadSegmentationRuns).toHaveBeenCalledWith('project-1')
expect(state.loadQualityChecks).toHaveBeenCalledWith('project-1')
expect(state.loadExports).toHaveBeenCalledWith('project-1')
// Alles wat bij een ander werkblad hoort blijft liggen tot dat werkblad
// geopend wordt. Voorheen ging dit alles bij het opstarten de deur uit,
// ook voor werkbladen die de gebruiker nooit opende.
expect(state.loadCapabilities).not.toHaveBeenCalled()
expect(state.loadDetectionModels).not.toHaveBeenCalled()
expect(state.loadSegmentationModels).not.toHaveBeenCalled()
expect(state.loadDetectionRuns).not.toHaveBeenCalled()
expect(state.loadSegmentationRuns).not.toHaveBeenCalled()
expect(state.loadQualityChecks).not.toHaveBeenCalled()
expect(state.loadExports).not.toHaveBeenCalled()
})
it('loads the same analysis catalog and project results for a guest', async () => {
const state = { ...options('project-1'), restrictedMode: true }
it('haalt de beeldanalysegegevens op zodra dat werkblad open staat', async () => {
const state = options('project-1', 'ai')
renderHook(() => useWorkbenchBootstrap(state))
await waitFor(() => expect(state.loadProjectData).toHaveBeenCalledWith('project-1'))
expect(state.loadCapabilities).toHaveBeenCalledOnce()
expect(state.loadQualityChecks).toHaveBeenCalledWith('project-1')
expect(state.loadDetectionModels).toHaveBeenCalledOnce()
await waitFor(() => expect(state.loadDetectionModels).toHaveBeenCalledOnce())
expect(state.loadSegmentationModels).toHaveBeenCalledOnce()
expect(state.loadDetectionRuns).toHaveBeenCalledWith('project-1')
expect(state.loadSegmentationRuns).toHaveBeenCalledWith('project-1')
expect(state.loadExports).toHaveBeenCalledWith('project-1')
expect(state.loadDetectionResults).toHaveBeenCalledOnce()
expect(state.loadSegmentationResults).toHaveBeenCalledOnce()
expect(state.loadExports).not.toHaveBeenCalled()
})
it('haalt kwaliteit, downloads en bronkoppelingen op hun eigen werkblad', async () => {
const kwaliteit = options('project-1', 'analysis')
renderHook(() => useWorkbenchBootstrap(kwaliteit))
await waitFor(() => expect(kwaliteit.loadQualityChecks).toHaveBeenCalledWith('project-1'))
const downloads = options('project-1', 'exports')
renderHook(() => useWorkbenchBootstrap(downloads))
await waitFor(() => expect(downloads.loadExports).toHaveBeenCalledWith('project-1'))
const systeem = options('project-1', 'system')
renderHook(() => useWorkbenchBootstrap(systeem))
await waitFor(() => expect(systeem.loadCapabilities).toHaveBeenCalledOnce())
})
it('blijft geladen wanneer de gebruiker terugkeert naar de kaart', async () => {
const state = options('project-1', 'ai')
const { rerender } = renderHook((props: { werkblad: string }) =>
useWorkbenchBootstrap({ ...state, activeWorkspace: props.werkblad }), {
initialProps: { werkblad: 'ai' },
})
await waitFor(() => expect(state.loadDetectionRuns).toHaveBeenCalledWith('project-1'))
const naEerste = state.loadDetectionRuns.mock.calls.length
rerender({ werkblad: 'map' })
// Een bezocht werkblad blijft bijgewerkt worden; het wordt niet opnieuw
// dichtgezet zodra de gebruiker wegklikt.
expect(state.loadDetectionRuns.mock.calls.length).toBeGreaterThanOrEqual(naEerste)
})
it('meldt een mislukte laadactie in plaats van haar weg te slikken', async () => {
const state = options('project-1')
state.loadProjectData = vi.fn().mockRejectedValue(new Error('netwerk weg'))
const gemeld: string[] = []
renderHook(() => useWorkbenchBootstrap({ ...state, onLoadError: (onderdeel) => gemeld.push(onderdeel) }))
await waitFor(() => expect(gemeld).toContain('gebieden en bronnen'))
})
it('laadt voor een gast hetzelfde als voor een operator', async () => {
const state = { ...options('project-1', 'ai'), restrictedMode: true }
renderHook(() => useWorkbenchBootstrap(state))
await waitFor(() => expect(state.loadProjectData).toHaveBeenCalledWith('project-1'))
expect(state.loadDetectionModels).toHaveBeenCalledOnce()
expect(state.loadDetectionRuns).toHaveBeenCalledWith('project-1')
})
})